Engine standard (unreleased; no +Z-forward or non-reverse-Z path):
| Property | Choice |
|---|---|
| Handedness | Right-handed (+X × +Y = +Z) |
| Up | +Y |
| Forward | −Z (identity camera looks down −Z) |
| Matrix storage | Column-major |
| Clip / depth | Reverse-Z (NDC: near → 1, far → 0; infinite-far perspective preferred) |
This matches glTF / classic GL math (right-handed, Y-up, −Z forward), plus reverse-Z depth as used in contemporary high-end renderers. It is not left-handed +Z-forward, and it is not Z-up.
Source of truth: src/hikari/src/math/, scene/camera.zig, sdk/src/camera_rig.zig, projection consumers, depth/Hi-Z paths.
Why this set
- RH Y-up −Z forward — Object +X, view-right, and
lookAt’s X axis coincide at identity. No separate “view-right = −local X” story. - glTF / DCC alignment — Assets and tools already speak RH Y-up; camera space matches OpenGL-style −Z view.
- Reverse-Z — Best depth precision for large
far/ outdoor scales with 24–32-bit depth; pairs naturally with an infinite-far projection. - Rejected alternatives — left-handed +Z-forward keeps glTF awkward; Z-up forces constant axis remaps; RH +Z forward would force view-right ≠ local +X.
World axes
| Axis | Direction |
|---|---|
| +X | Right (math_vector.right) — also view/screen right for an identity camera |
| +Y | Up |
| −Z | Forward (identity camera look direction) |
| +Z | Backward |
Right-handed: +X × +Y = +Z (so forward −Z is “into the screen” in the usual GL view diagram).
Camera and gameplay
- Identity orientation: local +X right, +Y up, −Z forward.
Camera.getRight/Actor.get_right/FlyCamera.right= rotated local +X (same ascross(forward, up)when forward is the look direction).getForward/ look direction = rotated −Z (math_vector.forward).- Ground move basis: yaw about +Y; at yaw 0, forward is (0,0,−1), right is (1,0,0).
- Camera frustum gizmos (editor overlay) place near/far on local −Z.
- Look controllers store pitch/yaw degrees as source of truth (
FlyCamera,CameraRig, play-modecamera_controller.kawa) and derive the quaternion. Do not round-triptoEulerevery look frame — near the pitch clamp that is ill-conditioned in f32.
Euler angles (degrees)
Triples are (pitch, yaw, roll) in degrees, composition on column vectors:
R = Ry(yaw) · Rx(pitch) · Rz(roll)
q = qy · qx · qz| Component | Axis | Positive sense (RH, facing −Z) |
|---|---|---|
| pitch | +X | Look up |
| yaw | +Y | Turn left (CCW from above) |
| roll | +Z | Bank around the view axis |
Yaw-first ordering is what makes this usable for cameras:
- Yaw is full-range.
toEulerreports yaw over (−180°, 180°]; only pitch is limited to [−90°, 90°]. - Gimbal lock sits at the poles (pitch ±90°), which look controllers already clamp away from.
- Roll does not steer. Because roll is innermost, it rotates about the local forward axis, so banking never changes where a camera or light points.
That last pair is load-bearing. FlyCamera.look and Actor.look both round-trip through toEuler, clamp pitch, force roll to 0, and rebuild — an order with yaw in the middle turns every |yaw| > 90° into roll = ±180°, and clearing that roll flips the view upside down.
fromEuler / toEuler must stay inverses. CameraRig.Look / mouse deltas use gameplay sense (turn right / look up) and convert into these euler values.
Quaternions
The runtime type is { w, x, y, z } — Hamilton, scalar-first (math/quaternion.zig).
This is the minority layout in the wider ecosystem: glTF and most DCC/engine formats serialise xyzw scalar-last, and so does the cooked model format (ImportNode.rotation in shinra/src/import/mod.rs, and skeletal animation keyframes). Anything crossing that boundary must swizzle. Scene assets are unaffected — they store rotation_euler degrees, not quaternions.
Rotating a vector uses local axes as columns of toMatrix, so rotateVector(q, forward) is the look direction.
Units
| Quantity | Unit |
|---|---|
| Distance | metres (1 world unit = 1 m, matching glTF) |
| Time | seconds |
| Mass | kilograms |
| Angles | radians everywhere except euler triples and authored/inspector fields, which are degrees |
Physics assumes this: tenkai's default gravity is -9.81 on +Y. Authoring content at a different scale silently changes how everything falls, how character motors tune, and how attenuation ranges read.
Projection and clip space
- CPU builds reverse-Z perspective (finite or infinite far) with NDC z already in [0, 1] (near → 1, far → 0).
remap_clip_zis identity. - After projection, larger depth buffer values are closer. Depth tests use greater. Clear depth / empty shadow atlas to 0.
- Metal and D3D12 share the same CPU matrices (no GL −1…+1 remap).
- Linear G-buffer / Hi-Z depth (meters,
−view.z) keeps min=nearer, max=farther. Hardware-depth consumers use reverse-Z ordering.
vs other engines
| Hikari | Unity | Godot 4 | Unreal | |
|---|---|---|---|---|
| Handedness | Right | Left | Right | Left |
| Up | +Y | +Y | +Y | +Z |
| Forward | −Z | +Z | −Z | +X |
| Depth | Reverse-Z | Varies | Typically GL-style | Reverse-Z (modern) |