--- name: pixijs-math description: "Use this skill when working with coordinates, vectors, matrices, shapes, hit testing, or layout rectangles in PixiJS v8. Covers Point/ObservablePoint, Matrix (2D affine, decompose, apply, applyInverse), shapes (Rectangle, Circle, Ellipse, Polygon, RoundedRectangle, Triangle), Rectangle layout helpers (pad, fit, enlarge, ceil, scale, getBounds), strokeContains hit tests, Polygon isClockwise/containsPolygon, toGlobal/toLocal, PointData/PointLike/Size types, DEG_TO_RAD, and pixi.js/math-extras vector and intersection helpers. Triggers on: Point, ObservablePoint, Matrix, Rectangle, Circle, Polygon, Triangle, RoundedRectangle, toGlobal, toLocal, hitArea, strokeContains, pad, fit, enlarge, ceil, getBounds, containsRect, intersects, isClockwise, math-extras, lineIntersection, segmentIntersection, DEG_TO_RAD, PointData." license: MIT --- PixiJS exposes lightweight math primitives (Point, Matrix, shape classes) used throughout the library for transforms, hit testing, and coordinate conversion. Import `pixi.js/math-extras` to add vector operations (add, dot, magnitude, reflect) and Rectangle intersection/union helpers. ## Quick Start ```ts 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)); ``` **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. ```ts 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. ```ts 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. ```ts 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. ```ts 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); ``` ### 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. ```ts 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. ```ts 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. ```ts 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 ```ts 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`. ```ts 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. ```ts 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. ```ts 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`: ```ts 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. ```ts 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: ```ts import { Point } from "@pixi/math"; ``` Correct: ```ts 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: ```ts // Replacing the reference loses observation let pos = container.position; pos = new Point(100, 200); // container.position unchanged ``` Correct: ```ts // 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: ```ts 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: ```ts 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: ```ts const myPoint = Point.shared; myPoint.set(100, 200); // ... later ... console.log(myPoint.x); // 0 (reset on next access) ``` Correct: ```ts const myPoint = new Point(); myPoint.copyFrom(Point.shared.set(100, 200)); // or just const myPoint = new Point(100, 200); ``` `Point.shared` and `Matrix.shared` are reset to zero/identity every time they are accessed. They exist for one-off calculations within a single expression. Never store a reference to them. ## API Reference - [Point](https://pixijs.download/release/docs/maths.Point.html.md) - [ObservablePoint](https://pixijs.download/release/docs/maths.ObservablePoint.html.md) - [Matrix](https://pixijs.download/release/docs/maths.Matrix.html.md) - [Rectangle](https://pixijs.download/release/docs/maths.Rectangle.html.md) - [Circle](https://pixijs.download/release/docs/maths.Circle.html.md) - [Ellipse](https://pixijs.download/release/docs/maths.Ellipse.html.md) - [Polygon](https://pixijs.download/release/docs/maths.Polygon.html.md) - [RoundedRectangle](https://pixijs.download/release/docs/maths.RoundedRectangle.html.md) - [Triangle](https://pixijs.download/release/docs/maths.Triangle.html.md) - [DEG_TO_RAD](https://pixijs.download/release/docs/maths.DEG_TO_RAD.html.md)