mcpField
P
pixijs-math

pixijs-math

github.com/pixijs開発3586.0k 回インストール4 週間前に更新
リポジトリ

pixijs-math について

pixijs-mathは、PixiJS開発者向けに数学プリミティブやジオメトリを解説するスキルです。基本型に加え、別途インポートするベクトルや矩形の拡張機能も扱います。

主な機能

  1. 01PointとObservablePointの操作
  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、シェイプクラス)を公開します。ベクトル演算(加算、内積、大きさ、反射)と Rectangle の交差・和集合ヘルパーを追加するには、pixi.js/math-extras をインポートします。

クイックスタート

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

position、scale、pivot、origin、skew などの Container プロパティは 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 は、フラットな数値配列、点に似たオブジェクトの配列、またはそれらをスプレッド引数として渡す形式の、4 種類のコンストラクター形式を受け付けます。

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 - ほとんどの API が受け付ける最小限の {x, y} インターフェースです。座標の読み取りだけが必要なパラメーターの型指定に使用します。
  • PointLike - PointData を拡張し、set()、copyFrom()、copyTo()、equals() を備えています。Point と ObservablePoint の両方で実装されています。
  • Size - レンダラー/キャンバス API で使用される { width, height } インターフェースです。
  • 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 エントリではなく、pixi.js/math-extras からエクスポートされます。

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 */
}

よくある間違い

HIGH:@pixi/math からインポートする

誤り:

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

正しい方法:

import { Point } from "pixi.js";

v8 では単一の pixi.js パッケージを使用します。@pixi/math、@pixi/core などのすべてのサブパッケージは削除されました。

MEDIUM:オブザーバーをトリガーせずに 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 を設定すると、コンテナーのトランスフォームが更新されます。変数の参照を再代入しても、コンテナーは変更されません。既存の ObservablePoint は、.set()、.copyFrom()、または元のオブジェクトのプロパティへの直接代入で必ず変更してください。

MEDIUM:拡張メソッドを使うための 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 の加算、減算、乗算、マグニチュード、正規化、内積、外積など、および図形の交差判定メソッド)を使用するには、import 'pixi.js/math-extras' を明示的に記述する必要があります。これらはデフォルトのバンドルには含まれていません。

MEDIUM:共有オブジェクトや一時オブジェクトへの参照の保持

誤り:

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 リファレンス

コメント