mcpField
P
pixijs-math

pixijs-math

github.com/pixijs开发3586.0k 次安装4 周前更新
代码仓库

关于 pixijs-math

pixijs-math 是面向 PixiJS 开发者的数学技能,介绍坐标、变换与几何操作。内容涵盖核心数学类型及需单独导入的扩展方法。

主要功能

  1. 01点与可观察点操作
  2. 02仿射矩阵变换
  3. 03坐标空间转换
  4. 04形状检测与边界
  5. 05math-extras 向量及矩形扩展

适用场景

  1. 01计算场景节点的变换
  2. 02检测指针是否位于形状内
  3. 03获取矩形交集或并集

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pixijs/pixijs-skills --skill pixijs-math

安装后在 Claude Code 中输入/pixijs-math确认已加载。

详细介绍

PixiJS 提供轻量级数学基础类型(Point、Matrix、形状类),供库中各处用于变换、命中测试和坐标转换。导入 pixi.js/math-extras 可添加向量运算(add、dot、magnitude、reflect)以及 Rectangle 的相交和并集辅助方法。

快速开始

const parent = new Container();
parent.position.set(100, 100);
parent.scale.set(2);
app.stage.addChild(parent);

const child = new Container();
child.position.set(50, 50);
parent.addChild(child);

const globalPt = child.toGlobal(new Point(0, 0));

const m = new Matrix()
  .translate(100, 50)
  .rotate(Math.PI / 4)
  .scale(2, 2);
const world = m.apply(new Point(10, 20));

const hitArea = new Rectangle(0, 0, 200, 100);
console.log(hitArea.contains(50, 50));

相关技能: pixijs-scene-container(变换属性)、pixijs-events(hitArea 的用法)、pixijs-scene-core-concepts(使用 Rectangle 进行剔除)。

核心模式

Point 和 ObservablePoint

Point 是简单的 {x, y} 值类型。ObservablePoint 会在 x 或 y 发生变化时触发回调;Container 的 position、scale、pivot、origin 和 skew 内部均使用它。

import { Point } from "pixi.js";

const p = new Point(10, 20);
p.set(30, 40); // set both
p.set(50); // x=50, y=50

const clone = p.clone();
console.log(p.equals(clone)); // true

p.copyFrom({ x: 1, y: 2 }); // accepts any PointData

// Point.shared: temporary point, reset to (0,0) on each access
const temp = Point.shared;
temp.set(100, 200);
// do not store a reference to Point.shared

Container 的 position、scale、pivot、origin 和 skew 等属性都是 ObservablePoint。在这些属性上设置 .x 或 .y 会自动触发变换重新计算。

import { Container } from "pixi.js";

const obj = new Container();
obj.position.set(100, 200); // triggers observer -> marks transform dirty
obj.position.x = 150; // also triggers observer
Matrix(2D 仿射变换)

Matrix 表示一个 3x3 仿射变换:| a c tx | b d ty | 0 0 1 |。它支持 translate、scale、rotate、append、prepend、invert 和 decompose。

import { Matrix, Point } from "pixi.js";

// Build a transform
const m = new Matrix()
  .translate(100, 50)
  .rotate(Math.PI / 4)
  .scale(2, 2);

// Transform a point (local -> parent space)
const local = new Point(10, 20);
const world = m.apply(local);

// Inverse transform (parent -> local space)
const backToLocal = m.applyInverse(world);

// Combine matrices
const a = new Matrix().translate(50, 0);
const b = new Matrix().rotate(Math.PI / 2);
a.append(b); // a = a * b

// Decompose into position/scale/rotation/skew
const transform = {
  position: new Point(),
  scale: new Point(),
  pivot: new Point(),
  skew: new Point(),
  rotation: 0,
};
m.decompose(transform);
console.log(transform.rotation); // ~0.785 (PI/4)

// A mirrored matrix (one axis flipped, axes still perpendicular) decomposes into
// rotation + negative scale.x with zero skew. scale.y is never negative: a y-axis
// flip comes back as scale.x = -1 plus a half turn. Either way the parts rebuild the matrix.
const mirrored = new Matrix().rotate(Math.PI / 6).scale(-1, 1);
mirrored.decompose(transform);
transform.scale.x; // -1
transform.skew.x; // 0

// Shared temporary matrix (reset on each access)
const temp = Matrix.shared;
// IDENTITY is read-only reference
const isDefault = m.equals(Matrix.IDENTITY);
通过 Container 进行坐标变换

Container 提供 toGlobal、toLocal 和 getGlobalPosition,用于坐标转换。

import { Container, Point } from "pixi.js";

const parent = new Container();
parent.position.set(100, 100);
parent.scale.set(2);

const child = new Container();
child.position.set(50, 50);
parent.addChild(child);

// Local point in child's space -> global (world) space
const globalPt = child.toGlobal(new Point(0, 0));
// globalPt = { x: 200, y: 200 } (100 + 50*2, 100 + 50*2)

// Global point -> child's local space
const localPt = child.toLocal(new Point(200, 200));
// localPt = { x: 0, y: 0 }

// Convert between two containers
const other = new Container();
other.position.set(300, 300);
const ptInOther = child.toLocal(new Point(10, 10), other);
形状和命中测试

Rectangle、Circle、Ellipse、Polygon、RoundedRectangle 和 Triangle 均实现了 contains(x, y),用于测试点是否位于形状内;还提供 getBounds(out?) 和 strokeContains(x, y, width, alignment?)(Triangle 会忽略 alignment,始终测试居中的描边)。可将它们用作容器的 hitArea,以定义自定义交互区域。

import { Rectangle, Circle, Ellipse, Polygon, Container } from "pixi.js";

const rect = new Rectangle(0, 0, 200, 100);
rect.contains(50, 50); // true
rect.contains(300, 50); // false
rect.left; // 0
rect.right; // 200
rect.top; // 0
rect.bottom; // 100
rect.isEmpty(); // false (Rectangle.EMPTY returns a fresh empty rect)

// Native Rectangle-to-Rectangle methods (no math-extras needed)
const other = new Rectangle(50, 50, 100, 100);
rect.containsRect(other); // true if `other` is fully inside `rect`
rect.containsRect(new Rectangle(100, 0, 100, 100)); // true: a flush right edge counts
rect.containsRect(rect.clone()); // true: identical rects contain each other
rect.intersects(other); // boolean: do they overlap at all?
rect.intersects(other, matrix); // overlap after transforming `other`

// Stroke hit testing (alignment: 1 = inner, 0.5 = centered, 0 = outer)
rect.strokeContains(0, 50, 4); // true if (0,50) lies on a 4px centered stroke
const circle = new Circle(100, 100, 50);
circle.strokeContains(150, 100, 4, 1); // inner-aligned stroke check
// A stroke that reaches past the shape's centre covers the whole interior; there is no hole
new Ellipse(0, 0, 10, 10).strokeContains(0, 0, 40, 1); // true

// getBounds works on every shape (returns a Rectangle, accepts an out param)
const bounds = circle.getBounds();
const reused = new Rectangle();
new Polygon([0, 0, 100, 0, 50, 100]).getBounds(reused);

// Use as hit area for interaction
const button = new Container();
button.hitArea = new Rectangle(0, 0, 200, 50);
button.eventMode = "static";
button.on("pointerdown", () => {
  /* clicked */
});

不要将原生 Rectangle.intersects(other)(返回 boolean)与 math-extras 的 intersection(other)(返回描述重叠区域的 Rectangle)混淆。

Rectangle 布局辅助方法

Rectangle 提供了可变辅助方法,常用于 UI/布局、边界聚合和像素对齐。所有方法都会返回 this,以便链式调用。

import { Rectangle } from "pixi.js";

const r = new Rectangle(10, 10, 100, 50);

r.pad(5); // grow on all sides: x=5, y=5, w=110, h=60
r.pad(10, 4); // separate horizontal/vertical padding
r.scale(2); // multiply x, y, width, height by 2

// fit shrinks `this` to lie inside another rect (clipping)
const viewport = new Rectangle(0, 0, 200, 200);
new Rectangle(150, 150, 200, 200).fit(viewport); // -> 150, 150, 50, 50

// enlarge expands `this` to include another rect (bounds aggregation)
const total = new Rectangle();
items.forEach((item) =>
  total.enlarge(new Rectangle().copyFromBounds(item.getBounds())),
);

// ceil snaps to a pixel grid (resolution: 1 = whole pixels, 2 = half pixels)
new Rectangle(10.2, 10.6, 100.8, 100.4).ceil();

// copy a Container/Mesh bounds object straight into a Rectangle
new Rectangle().copyFromBounds(container.getBounds());
Polygon

Polygon 支持四种构造函数参数格式:扁平数字数组、类点对象数组,或将上述任一种作为展开参数传入。

import { Polygon, Point } from "pixi.js";

new Polygon([0, 0, 100, 0, 50, 100]); // flat numbers
new Polygon([new Point(0, 0), new Point(100, 0), new Point(50, 100)]); // PointData[]
new Polygon(0, 0, 100, 0, 50, 100); // spread numbers
new Polygon(new Point(0, 0), new Point(100, 0), new Point(50, 100)); // spread points

const poly = new Polygon([0, 0, 100, 0, 100, 100, 0, 100]);
poly.points; // [0, 0, 100, 0, 100, 100, 0, 100] (mutable flat array)
poly.closePath; // true by default; false produces an open path
poly.startX; // 0  - first vertex
poly.lastX; // 0  - last vertex (lastY for y)
poly.isClockwise(); // shoelace winding test (useful for SVG hole detection)

// Polygon-in-polygon containment for hole detection
const outer = new Polygon([0, 0, 100, 0, 100, 100, 0, 100]);
const hole = new Polygon([25, 25, 75, 25, 75, 75, 25, 75]);
outer.containsPolygon(hole); // true

// Stroke hit test follows the drawn stroke: alignment 1 = inside, 0.5 = centered,
// 0 = outside, independent of winding order
outer.strokeContains(-5, 50, 20); // true (centered stroke extends 10px outward)
outer.strokeContains(-5, 50, 20, 1); // false (inner stroke stays inside)
outer.strokeContains(5, 50, 20, 0); // false (outer stroke stays outside)
常量
import { DEG_TO_RAD, RAD_TO_DEG, PI_2 } from "pixi.js";

const angle = 45 * DEG_TO_RAD; // 0.785...
const degrees = angle * RAD_TO_DEG; // 45
const fullCircle = PI_2; // Math.PI * 2
类型
  • PointData - 最简 {x, y} 接口,大多数 API 都接受此类型。为只需读取坐标的参数标注类型时可使用它。
  • PointLike - 在 PointData 的基础上扩展了 set()、copyFrom()、copyTo()、equals()。Point 和 ObservablePoint 均实现此接口。
  • Size - { width, height } 接口,用于渲染器/画布 API。
  • SHAPE_PRIMITIVE - 字符串字面量联合类型:'rectangle' | 'circle' | 'ellipse' | 'polygon' | 'roundedRectangle' | 'triangle'。所有形状都提供 type,因此你无需使用 instanceof 就能进行分支判断。
import type { PointData } from "pixi.js";

function distance(a: PointData, b: PointData): number {
  const dx = a.x - b.x;
  const dy = a.y - b.y;
  return Math.sqrt(dx * dx + dy * dy);
}
math-extras(副作用导入)

import 'pixi.js/math-extras' 通过原型扩展为 Point、ObservablePoint 和 Rectangle 添加方法。默认包中不包含这些方法。

import "pixi.js/math-extras";
import { Point } from "pixi.js";
Point / ObservablePoint 向量方法

所有方法都接受可选的 out 参数,以避免分配内存。不传入 out 时会返回一个新的 Point。

const a = new Point(3, 4);
const b = new Point(1, 2);

// Arithmetic
const sum = a.add(b); // Point(4, 6)
const diff = a.subtract(b); // Point(2, 2)
const prod = a.multiply(b); // Point(3, 8) - component-wise
const scaled = a.multiplyScalar(2); // Point(6, 8)

// Dot and cross product
const dot = a.dot(b); // 11
const cross = a.cross(b); // 2 (z-component of 3D cross)

// Length
const len = a.magnitude(); // 5
const lenSq = a.magnitudeSquared(); // 25 (faster for comparisons)

// Normalize to unit vector
const unit = a.normalize(); // Point(0.6, 0.8)

// Projection and reflection
const proj = a.project(b); // project a onto b
const refl = a.reflect(new Point(0, 1)); // reflect across normal

// Rotation
const rotated = a.rotate(Math.PI / 2); // rotate 90 degrees

// Reuse existing point to avoid allocation
const out = new Point();
a.add(b, out); // result written to out
Rectangle 扩展方法

containsRect 和 intersects 是 Rectangle 的原生方法(见上文)。math-extras 添加了 equals、intersection(返回重叠矩形)和 union:

import "pixi.js/math-extras";
import { Rectangle } from "pixi.js";

const r1 = new Rectangle(0, 0, 100, 100);
const r2 = new Rectangle(50, 50, 100, 100);

r1.equals(r2); // false

const overlap = r1.intersection(r2); // Rectangle(50, 50, 50, 50)
const envelope = r1.union(r2); // Rectangle(0, 0, 150, 150)

// Optional out parameter
const out = new Rectangle();
r1.intersection(r2, out);
几何工具函数

这些函数从 pixi.js/math-extras 导出,不属于主 pixi.js 入口。

import {
  floatEqual,
  lineIntersection,
  segmentIntersection,
} from "pixi.js/math-extras";
import { Point } from "pixi.js";

// Epsilon-based float comparison (default epsilon: Number.EPSILON)
floatEqual(0.1 + 0.2, 0.3, 1e-10); // true with reasonable epsilon
floatEqual(1.0, 1.001, 0.01); // true (custom epsilon)

// Unbounded line intersection (returns {x: NaN, y: NaN} if parallel)
const hit = lineIntersection(
  new Point(0, 0),
  new Point(10, 10), // line A
  new Point(10, 0),
  new Point(0, 10), // line B
); // Point(5, 5)
if (isNaN(hit.x)) {
  /* lines are parallel */
}

// Bounded segment intersection (returns {x: NaN, y: NaN} if segments don't cross)
const segHit = segmentIntersection(
  new Point(0, 0),
  new Point(10, 10),
  new Point(10, 0),
  new Point(0, 10),
); // Point(5, 5)
if (isNaN(segHit.x)) {
  /* segments don't intersect */
}

常见错误

高优先级:从 @pixi/math 导入

错误:

import { Point } from "@pixi/math";

正确:

import { Point } from "pixi.js";

v8 使用单一的 pixi.js 包。@pixi/math、@pixi/core 等所有子包均已移除。

中等:修改 ObservablePoint 时未触发观察者

错误:

// Replacing the reference loses observation
let pos = container.position;
pos = new Point(100, 200); // container.position unchanged

正确:

// Mutate in place to trigger the observer
container.position.set(100, 200);
// or
container.position.x = 100;
container.position.y = 200;
// or copy from another point
container.position.copyFrom(new Point(100, 200));

容器的位置、缩放、轴心、原点和倾斜都是 ObservablePoint。设置它们的 .x 或 .y 会触发容器的变换更新。重新赋值变量引用不会修改容器。请始终通过 .set()、.copyFrom(),或直接为原对象的属性赋值来修改现有的 ObservablePoint。

中等:未导入 math-extras,无法使用扩展方法

错误:

import { Point } from "pixi.js";
const p = new Point(1, 2);
p.add(new Point(3, 4)); // TypeError: p.add is not a function

正确:

import "pixi.js/math-extras";
import { Point } from "pixi.js";
const p = new Point(1, 2);
const sum = p.add(new Point(3, 4)); // works

扩展数学工具(Point 上的 add、subtract、multiply、magnitude、normalize、dot、cross 等方法,以及形状上的相交方法)需要显式执行 import 'pixi.js/math-extras'。这些工具不包含在默认包中。

中等:存储对共享/临时对象的引用

错误:

const myPoint = Point.shared;
myPoint.set(100, 200);
// ... later ...
console.log(myPoint.x); // 0 (reset on next access)

正确:

const myPoint = new Point();
myPoint.copyFrom(Point.shared.set(100, 200));
// or just
const myPoint = new Point(100, 200);

每次访问 Point.shared 和 Matrix.shared 时,它们都会重置为零值/单位矩阵。它们用于单个表达式中的一次性计算。切勿存储对它们的引用。

API 参考

评论

支持 Markdown