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 position and rotation are computed from that node's own local transform, so they are values in the parent's space rather than the world's. That is what those two fields are for every entity; what adoption changes is that construction writes no transform over the one authoring gave.

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. The glTF loader still creates __root__ -- rightHanded only decides whether the mirroring transform is applied to it. With rightHanded: true, __root__ exists at identity: no mirror, so no conversion is needed, but the node is still there and still the parent of everything the file imports.

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.