Skip to content

Transforms

@shell/transform is the canonical owner of shared transform components. Renderers, physics, and spatial packages consume these component identities directly rather than defining or re-exporting their own copies.

Applications using an engine aggregate can import transform APIs from that aggregate. Standalone renderers and shared libraries should depend on @shell/transform directly.

ComponentPurpose
PositionAuthored simulation position
RotationAuthored XYZ Euler rotation
ScaleAuthored XYZ scale
AnchorNormalized render anchor
PivotRender pivot
SizeRender width and height
TransformInterpolated local position, rotation, and scale
PreviousPositionPrevious fixed-step position
PreviousRotationPrevious fixed-step rotation
PreviousScalePrevious fixed-step scale
GlobalPositionCurrent hierarchy-derived world position
GlobalTransformInterpolated hierarchy-derived world transform
GlobalTransformMatrixExact hierarchy-derived affine matrix, including possible shear

TransformPlugin snapshots simulation values during FixedPreUpdate and interpolates them into Transform during PreRender:

  • Position and PreviousPosition produce Transform.x, y, and z.
  • Rotation and PreviousRotation produce Transform.rotationX, rotationY, and rotationZ.
  • Scale and PreviousScale produce Transform.scaleX, scaleY, and scaleZ.

Adding an authored simulation transform automatically attaches its required previous-state and Transform components.

import { Position, Rotation, Scale, TransformPlugin } from '@shell/transform';
world.addPlugin(TransformPlugin);
const entity = world.get('commands').spawn().root;
entity.addComponent(Position, { x: 4, y: 2, z: 0 });
entity.addComponent(Rotation, { z: Math.PI / 4 });
entity.addComponent(Scale, { x: 2, y: 2, z: 1 });

Without HierarchyTransformPlugin, authored transforms are world-space and ECS parent relationships do not affect them.

Use the transforms resource to move an entity without interpolating from its previous position:

world.get('transforms').teleport(entity, { x: 100, y: 50 });

Teleportation updates Position, PreviousPosition, and the positional fields of Transform together, so the rendered local position changes immediately even after interpolation has already run. Omitted axes retain their current values, rotation and scale are preserved, and velocity is not changed. The entity must already have Position. With HierarchyTransformPlugin, the destination is local to the entity’s parent; hierarchy-derived transforms update during hierarchy propagation.

Install HierarchyTransformPlugin when child transforms should be relative to their parents. It depends on TransformPlugin, so only the hierarchy plugin needs to be installed:

import { GlobalPosition, HierarchyTransformPlugin, Position } from '@shell/transform';
world.addPlugin(HierarchyTransformPlugin);
const commands = world.get('commands');
const parent = commands.spawn().root.addComponent(Position, { x: 10, y: 5 });
const child = commands.spawn().root.addComponent(Position, { x: 2, y: 3 });
child.setParent(parent);
// After the next tick, the child's GlobalPosition is (12, 8, 0).
const worldPosition = child.getComponent(GlobalPosition);

With this plugin installed:

  • Position, Rotation, and Scale are local simulation state relative to the parent.
  • Transform remains the interpolated local transform.
  • GlobalPosition is the current world-space simulation position.
  • GlobalTransform is the interpolated world-space position, rotation, and scale.
  • GlobalTransformMatrix is the exact interpolated world-space affine transform.

Hierarchy nodes without local transform components use an identity local transform, so they can organize entities while forwarding inherited transforms to descendants.

PropagateGlobalPositionSystem runs at PostUpdate after ordinary Update movement. PropagateGlobalTransformSystem runs at PreRender after InterpolateTransformSystem. Fixed-step physics or spatial systems that need hierarchy-aware world coordinates require explicitly ordered fixed-schedule propagation.

GlobalTransformMatrix stores the upper 3x4 portion of a column-major affine matrix. Use it when composed rotation and non-uniform scale produce shear that cannot be represented by decomposed position, rotation, and scale fields.

Reparenting never rewrites local Position, Rotation, or Scale; the same values are interpreted relative to the new parent on the next propagation pass.

Pixi, Three, and WebGPU import canonical components from @shell/transform. Their package barrels do not re-export them. Pixi can synchronize GlobalTransform in hierarchy-enabled worlds; Three and WebGPU currently synchronize local Transform.