Builds the identity transform: position and pivot at the origin, identity
rotation, unit scale, identity local and world matrices, matrixAutoUpdate
on, version 0.
ReadonlylocalComposed local transform. Engine-owned while
Transform.matrixAutoUpdate is true; user-owned when it is false.
The Matrix4 instance is never replaced, so views onto its elements stay
valid for the transform's lifetime.
When true (the default) Transform.updateLocalMatrix recomposes Transform.localMatrix from position/rotation/scale/pivot. When false the user owns the local matrix and nothing here recomposes or back-derives it (§7).
ReadonlypivotPoint, in the node's own unscaled and unrotated local space, that rotation
and scale act about. (0, 0, 0) — the node origin — by default.
ReadonlypositionLocal-space translation. In 2D scenes z is normally left at 0 (§7); the
world is right-handed with +Y up in both 2D and 3D (§7a).
ReadonlyrotationLocal-space rotation as a unit quaternion. Angles are radians (§7a).
ReadonlyscaleLocal-space scale; (1, 1, 1) by default. In 2D scenes z stays 1 (§7).
ReadonlyworldLocal matrix concatenated with every ancestor's — written by the world-transform resolver, never by this class. See the class documentation.
Version stamp the resolver writes after resolving Transform.worldMatrix, so callers can tell a resolved world matrix from a stale one. Meaningless to this class, which never reads it; the resolver defines what it counts.
Counter incremented on every local mutation (§7), so dependent systems — world-transform resolution, physics synchronization, render-item caches — can skip work by comparing against the value they last saw. Only the ordering matters: treat it as opaque and compare for inequality, never subtract two stamps.
The counter is monotonic and never wraps. At one bump per mutation it would
take on the order of a million years of continuous 60 Hz mutation to reach
Number.MAX_SAFE_INTEGER, which is where distinct increments would stop
being distinct.
Called from Node's constructor so Transform.markDirty can name
the owner. Not public API.
Announces a mutation the change hooks could not see — a direct field write
(transform.position.x = 5), or a direct write into
transform.localMatrix.elements under matrixAutoUpdate = false. Bumps
Transform.version by one.
Calling it after a method-based mutation is harmless but counts as a second mutation: the version advances again and the local matrix is recomposed once more. It is never wrong to call this, only wasteful.
Copies m into Transform.localMatrix and bumps
Transform.version — the supported way to author a matrix directly
under matrixAutoUpdate = false (§7). Nothing is back-derived: the
authored position/rotation/scale/pivot are left exactly as they were and no
longer describe the node.
Set matrixAutoUpdate = false first. With it left on, this write is
transient — it marks the transform dirty, so the next
Transform.updateLocalMatrix recomposes over it from the authored
values. That is deliberate: with auto-update on, composition is the
definition of the local matrix, and silently keeping a hand-written matrix
would make the two disagree. No error is raised, because §7 states the
requirement and detecting it here would cost a branch on a path the
resolver runs per node.
Brings Transform.localMatrix up to date with the authored
position/rotation/scale/pivot, composing T · Tp · R · S · Tp⁻¹ (§7) via
Matrix4.compose. Allocates nothing.
Lazy: it recomposes only if Transform.version advanced since the last composition, so callers may call it as often as they like — the resolver calls it on every node it visits.
A no-op while matrixAutoUpdate is false: the matrix is then the user's
(§7). Turning matrixAutoUpdate back on does not restore the user's matrix
— the next mutation-and-update recomposes over it from the authored
position/rotation/scale/pivot, which were never back-derived.
Local/world transform of a scene node (§7).
The transform is authored as translation, rotation, scale, and pivot and is composed into Transform.localMatrix as the §7 product
T · Tp · R · S · Tp⁻¹— translation, then rotation and scale about the pivot. The pivot therefore affects rotation and scale but never Transform.position: a node sits atpositionwhatever its pivot is, and the pivot only decides which point of the node stays fixed while it turns or grows. SeeMatrix4.compose, which evaluates that product directly.The dirty channel (plan D3)
Dependent systems cache against Transform.version, so every local mutation has to be visible. There are exactly two ways to mutate a transform, and both keep
versioncorrect:Method-based mutation — the normal path. The constructor installs an
onChangedhook on this transform's ownposition,rotation,scale, andpivot, so any math method call on them bumpsversiononce:One method call is one bump, including methods that write several components;
Vector3.copyandQuaternion.copydeliberately do not copy the source's hook, so copying another vector in keeps this channel intact.Direct field writes — legal, but they bypass the hook by design (the same rule the math types state for
v.x = 1), so the caller must announce them:The four math members are
readonlyfor this reason: replacing one wholesale (transform.position = someVector) would silently drop the hook and leaveversionfrozen while the transform kept moving. Write into them instead.Transform.localMatrix deliberately carries no hook — it is the output of composition, and a hook there would mark the transform dirty every time it was recomposed.
Local matrix
With
matrixAutoUpdate = true(the default) the local matrix is owned by the engine and Transform.updateLocalMatrix recomposes it from position/rotation/scale/pivot — lazily: it recomposes only whenversionhas advanced since the last composition, so calling it repeatedly in a frame costs one comparison.With
matrixAutoUpdate = falsethe user owns the local matrix (§7):updateLocalMatrix()becomes a no-op and position/rotation/scale are not back-derived from the matrix — they simply go stale and stop describing the node. Write the matrix through Transform.setLocalMatrix soversionstill advances; writinglocalMatrix.elementsdirectly needs a following Transform.markDirty exactly like a direct field write.World matrix
Transform.worldMatrix and Transform.worldVersion are storage for the world-transform resolver, which walks the hierarchy and is the single writer of both (§7: world matrices resolve lazily, once per fixed step before physics synchronization, before render-item generation, and on demand for queries). This class never touches them: a lone
Transformhas no parent and so cannot know its own world matrix. ReadingworldMatrixwithout having resolved it yields whatever the last resolution left there.