Skip to content

Coordinate Systems

SAGE uses BabylonJS as its rendering engine, which operates in a left-handed coordinate system. Most 3D content is authored in right-handed tools (Blender, glTF standard). This page explains how SAGE handles the conversion transparently.

How BabylonJS Handles glTF Imports

When BabylonJS imports a .glb or .gltf file into a left-handed scene, it creates a hidden __root__ transform node at the top of the imported hierarchy. This node has scaling = (-1, 1, 1), which mirrors the X axis to convert from right-handed to left-handed coordinates.

Most code never needs to think about __root__. It's an implementation detail of BabylonJS's loader. All nodes in the imported scene become children of __root__, so their local coordinates are in the mirrored (right-handed) space, while their world-space positions are correct left-handed values.

How SAGE Handles Spawn Points

Spawn points are nodes in your scene file tagged with a spawn custom property. They live inside the scene's __root__ hierarchy. When SAGE reads spawn points, it computes the node's transform in canonical Babylon space, cancelling the import root before decomposing, and writes the resulting position and rotation into the spawned entity's state. The spawn point's scale is not carried.

Entity nodes are created at the scene root, outside __root__. Since they have no parent, local equals world, so the state values apply directly, and entities appear exactly where the spawn point is, facing the correct direction.

An entity spawned from an entity marker adopts the marked node instead, and stays wherever the authoring put it, under whatever parent it had. Its update never writes state onto that node.

How Entity Meshes Work

When an entity's mesh comes from a GLB, the standard mesh source clones one mesh out of the asset manager's cached container and parents the clone under the entity node at the scene root:

scene root
├── __root__ (scene GLB, handedness conversion)
│   ├── level geometry
│   ├── lights
│   ├── spawn points
│   └── Door_Main (adopted by an entity, stays here)

├── entity-1 (node at the spawn point's canonical position)
│   └── mesh cloned from a GLB fragment

├── entity-2 (primitive, no GLB)
│   └── box mesh

Primitive meshes (box, sphere, capsule, cylinder) need no conversion since they are created natively in BabylonJS's left-handed space.

The rightHanded Option

If your scene file was exported for a right-handed coordinate system, you can set rightHanded: true in your level config:

yaml
scene:
  path: models/level.glb
  rightHanded: true

This sets scene.useRightHandedSystem = true, which tells BabylonJS to interpret all coordinates as right-handed. When enabled, the glTF loader does not create __root__, since both the scene and the format are right-handed and no conversion is needed.

Troubleshooting

Entity appears at mirrored X position: The spawn point's transform was decomposed with the import root still applied. SAGE's getCanonicalTransform cancels the mirrored root before decomposing; a level that reads spawn transforms itself should call it too.

Entity appears correct but at the wrong rotation: Same root cause as the position issue. Rotations under a mirrored parent decompose differently; getCanonicalTransform handles both.

Released under the MIT License.