pixijs-math

Use this skill when working with coordinates, vectors, matrices, shapes, hit testing, or layout rectangles in PixiJS v8. Covers Point/ObservablePoint, Matrix…

INSTALLATION
npx skills add https://github.com/pixijs/pixijs-skills --skill pixijs-math
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2c

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));

**Related skills:** `pixijs-scene-container` (transform properties), `pixijs-events` (hitArea usage), `pixijs-scene-core-concepts` (culling with Rectangle).

## Core Patterns

### Point and ObservablePoint

Point is a simple {x, y} value type. ObservablePoint fires a callback when x or y changes; it is used internally by Container's position, scale, pivot, origin, and 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 properties like `position`, `scale`, `pivot`, `origin`, and `skew` are ObservablePoints. Setting `.x` or `.y` on them triggers transform recalculation automatically.

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 affine transform)

Matrix represents a 3x3 affine transform: `| a c tx | b d ty | 0 0 1 |`. It supports translate, scale, rotate, append, prepend, invert, and 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)

// Shared temporary matrix (reset on each access)

const temp = Matrix.shared;

// IDENTITY is read-only reference

const isDefault = m.equals(Matrix.IDENTITY);


### Coordinate transforms via Container

Containers provide `toGlobal`, `toLocal`, and `getGlobalPosition` for coordinate conversion.

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 + 502, 100 + 502)

// 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);


### Shapes and hit testing

Rectangle, Circle, Ellipse, Polygon, RoundedRectangle, and Triangle all implement `contains(x, y)` for point-in-shape tests, plus `getBounds(out?)` and `strokeContains(x, y, width, alignment?)`. They can be used as `hitArea` on containers for custom interaction regions.

import { Rectangle, Circle, 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.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

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

});


Do not confuse native `Rectangle.intersects(other)` (returns `boolean`) with math-extras `intersection(other)` (returns a `Rectangle` describing the overlap area).

### Rectangle layout helpers

Rectangle ships with mutating helpers used heavily in UI/layout, bounds aggregation, and pixel snapping. All return `this` for chaining.

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 accepts four constructor formats: a flat number array, an array of point-like objects, or either passed as spread arguments.

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


### Constants

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


### Types

- `PointData` - minimal `{x, y}` interface accepted by most APIs. Use it when typing parameters that only need to read coordinates.

- `PointLike` - extends `PointData` with `set()`, `copyFrom()`, `copyTo()`, `equals()`. Implemented by both `Point` and `ObservablePoint`.

- `Size` - `{ width, height }` interface used by renderer/canvas APIs.

- `SHAPE_PRIMITIVE` - string literal union: `'rectangle' | 'circle' | 'ellipse' | 'polygon' | 'roundedRectangle' | 'triangle'`. Every shape exposes `type` so you can branch without `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 (side-effect import)

`import 'pixi.js/math-extras'` adds methods to Point, ObservablePoint, and Rectangle via prototype extension. Not included in the default bundle.

import "pixi.js/math-extras";

import { Point } from "pixi.js";


#### Point / ObservablePoint vector methods

All methods accept an optional `out` parameter to avoid allocations. Without `out`, a new Point is returned.

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 extended methods

`containsRect` and `intersects` are native `Rectangle` methods (see above). math-extras adds `equals`, `intersection` (returns the overlap rect), and `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);


#### Geometry utility functions

These functions are exported from `pixi.js/math-extras`, not the main `pixi.js` entry.

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 /

}


## Common Mistakes

### HIGH: Importing from @pixi/math

Wrong:

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


Correct:

import { Point } from "pixi.js";


v8 uses a single `pixi.js` package. All sub-packages like `@pixi/math`, `@pixi/core`, etc. were removed.

### MEDIUM: Mutating ObservablePoint without triggering observer

Wrong:

// Replacing the reference loses observation

let pos = container.position;

pos = new Point(100, 200); // container.position unchanged


Correct:

// 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));


Container's position, scale, pivot, origin, and skew are ObservablePoints. Setting `.x` or `.y` on them triggers the container's transform update. Reassigning the variable reference does not modify the container. Always mutate the existing ObservablePoint via `.set()`, `.copyFrom()`, or direct property assignment on the original object.

### MEDIUM: Not importing math-extras for extended methods

Wrong:

import { Point } from "pixi.js";

const p = new Point(1, 2);

p.add(new Point(3, 4)); // TypeError: p.add is not a function


Correct:

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


Extended math utilities (add, subtract, multiply, magnitude, normalize, dot, cross, etc. on Point; intersection methods on shapes) require an explicit `import 'pixi.js/math-extras'`. These are not included in the default bundle.

### MEDIUM: Storing references to shared/temporary objects

Wrong:

const myPoint = Point.shared;

myPoint.set(100, 200);

// ... later ...

console.log(myPoint.x); // 0 (reset on next access)


Correct:

const myPoint = new Point();

myPoint.copyFrom(Point.shared.set(100, 200));

// or just

const myPoint = new Point(100, 200);

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card