API reference

Every public class, method, and event in @codexo/exojs. Generated from source.

C

classSceneNode

@codexo/exojs / core / stable

Transform-bearing leaf in the scene-graph hierarchy. Carries position, rotation, scale, skew and origin. Implements Collidable so any node can participate directly in the SAT collision pipeline via its AABB or rotated/skewed quad. The normalized `anchor` convenience that derives `origin` from a layout box lives on Drawable: it needs a box to measure, which a node that carries no geometry of its own does not have. Transform state is dirty-flag-cached: position/rotation/scale/skew/origin mutations invalidate the local transform; either kind of mutation also invalidates the global transform (own + descendants) and the bounds rect for this node and every Container ancestor up the parent chain. The caches rebuild lazily on the next read. Collision and hit-testing take the cheaper AABB path when the node's *world* box is axis-aligned (its own and any inherited rotation compose to a multiple of 90° with no skew) and the oriented-quad SAT path otherwise. The public `isAlignedBox` getter reports that predicate for this node's own rotation only. `_invalidate*` methods are exported as `public` for friend-class access from Container and InteractionSystem; treat them as implementation detail, not public API. Subclasses: Container (carries children), RenderNode (carries draw payloads).

16
props
22
methods
0
events
Import
import { SceneNode } from '@codexo/exojs'

Transform-bearing leaf in the scene-graph hierarchy. Carries position, rotation, scale, skew and origin. Implements Collidable so any node can participate directly in the SAT collision pipeline via its AABB or rotated/skewed quad.

The normalized `anchor` convenience that derives `origin` from a layout box lives on Drawable: it needs a box to measure, which a node that carries no geometry of its own does not have.

Transform state is dirty-flag-cached: position/rotation/scale/skew/origin mutations invalidate the local transform; either kind of mutation also invalidates the global transform (own + descendants) and the bounds rect for this node and every Container ancestor up the parent chain. The caches rebuild lazily on the next read.

Collision and hit-testing take the cheaper AABB path when the node's *world* box is axis-aligned (its own and any inherited rotation compose to a multiple of 90° with no skew) and the oriented-quad SAT path otherwise. The public `isAlignedBox` getter reports that predicate for this node's own rotation only.

`_invalidate*` methods are exported as `public` for friend-class access from Container and InteractionSystem; treat them as implementation detail, not public API.

Subclasses: Container (carries children), RenderNode (carries draw payloads).

Constructors1
new(): SceneNode
Methods22
Compute a full CollisionResponse between this shape and target. Returns null in two cases: - the shapes do not overlap, **or** - the specific shape-pair combination does not support response generation (e.g. Line against any shape, Ellipse against Ellipse or Polygon). Use intersectsWith for a universal boolean overlap check that works across all supported shape pairs.
contains(x: number, y: number): boolean
Hit-test the world-space point (x, y) against this node. For world-axis-aligned nodes (own and inherited rotation a multiple of 90° and no skew) the AABB equals the oriented box, so the cheap getBounds test is exact. For rotated or skewed nodes the point is mapped back into local space with the inverse of the global transform and tested against the untransformed getLocalBounds - i.e. a true oriented-box test. This is the exact inverse of the forward map that getBounds and the renderer use to place the node's corners, so picking matches the rendered quad instead of over-reporting hits in the empty AABB corners of a rotated node.
destroy(): void
Release this node's pooled state (transform, bounds, flags) and unlink it from its parent. The unlink comes first and is what makes a bare destroy() safe. A node left in its parent's child list bumps no revision, so every cache keyed on that revision - a Container's retained draw slots, a RetainedContainer's recorded GPU instruction set - would keep replaying the dead node's last captured frame. Removing it stamps the parent structure-dirty, which drops those captures, and runs the node's regular detach cleanup (bounds cascade, interaction/focus deregistration, stage clearing) while this node's own state is still intact. Container.removeChild is a no-op for a node it does not hold, so the already-correct removeChild()-then-destroy() order costs nothing more than the null check here. The destroyed flag is raised before the unlink so detach-time observers (e.g. focus, which suppresses onBlur on a destroyed node) see a node that is going away rather than one merely being reparented. Idempotent: a second call is a no-op.
Axis-aligned bounding box of this node in its GLOBAL-transform space. That is world space for ordinary nodes, but GROUP-LOCAL space for nodes inside an engaged RetainedContainer transform group (the group matrix is applied on the GPU, not here) - this is deliberate and matches the rendering convention. For a true world-space extent of such a node, lift this rect by the group's getWorldTransform matrix. Pass out to receive a copy you own. Without it the return value is this node's **cached** rectangle, rebuilt in place whenever the transform or the local extent changes - retaining it across frames hands you a value that silently moves. The cache is why the no-arg form does not allocate: this runs per node per frame for culling and hit-testing.
This node's untransformed extent in its own local coordinate space. Returns the LIVE internal rectangle, not a copy: it is read on hot paths (updateBounds re-reads it on every transform-dirty recompute, i.e. potentially every frame for a moving node), so copying it here would add a per-frame allocation. It is therefore typed as a ReadonlyRectangle - reads are unchanged, writes are rejected at compile time. Writing to it directly would skip the bounds/content invalidation the engine needs, leaving culling, hit-testing and retained render fragments on a stale extent. A custom Drawable that owns its own size sets it through setLocalBounds, which writes and invalidates in one step.
Return the outward-facing edge normals used by the SAT solver. The array should be cached and reused across calls.
The node's TRUE world-space transform, composed through every transform-group boundary (RetainedContainer) in the ancestor chain. getGlobalTransform deliberately stops at the nearest engaged boundary (descendants resolve group-RELATIVE transforms; the renderer multiplies the group matrix back in on the GPU), so it is the right space for rendering but the wrong one for spatial queries. Use THIS accessor whenever a real world position/orientation is needed - picking, spatial audio, physics, world-space math against nodes outside the group. Without any engaged boundary ancestor it returns the exact getGlobalTransform matrix (same instance, no extra work). With one, it lazily caches groupLocal × groupWorld and revalidates on read via version/stamp compares - including runtime space flips such as RetainedContainer's deep-barrier sub-branch escape.
Test whether this shape overlaps target using a fast boolean algorithm (no penetration depth or normal computed). Prefer this over collidesWith when only the yes/no result is needed.
move(x: number, y: number): this
rotate(degrees: number): this
setLocalBounds(x: number, y: number, width: number, height: number): this
Write this node's local extent and run the bounds invalidation the change implies - the node's own bounds flag, the ancestor bounds cascade, and the content-dirty stamp that keeps retained fragments from replaying the old extent. This is the only supported way to resize a node from outside getLocalBounds; the rectangle itself is handed out read-only so the invalidation cannot be forgotten. Built-in drawables (Sprite, Text, BitmapText, Mesh, ...) and custom ones alike go through here. Part of the renderer SDK contract for extension renderers.
setOrigin(x: number, y: number): this
setPosition(x: number, y: number): this
setRotation(degrees: number): this
setScale(x: number, y: number): this
setSkew(x: number, y: number): this
updateBounds(): this
updateParentTransform(): this
updateTransform(): this
Properties16
name: null | string
Optional human-readable identity for this node. Defaults to null. Purely a label the engine never interprets: useful for debugging, find-by-name lookups, prefab references, and as a stable key when merging serialized state back onto an existing tree. Not required to be unique.
destroyed: boolean
true once destroy has run on this node. A destroyed node has released its pooled resources (transform/bounds), has been unlinked from its parent, renders nothing, and must not be reused or re-attached. The render plan skips a destroyed node, so even one handed straight to a renderer as a detached root contributes nothing.
isAlignedBox: boolean
rotation: number
Rotation angle in degrees. Wraps via trimRotation on assignment.
skewX: number
Horizontal skew angle in degrees. Shears the node along the X axis (positive values lean the top edge right). Combines correctly with rotation and scale.
skewY: number
Vertical skew angle in degrees. Shears the node along the Y axis (positive values lean the left edge downward). Combines correctly with rotation and scale.
visible: boolean
x: number
y: number
zIndex: number
Source