1
Conventions
Jaroslav Beneš edited this page 2026-08-08 18:11:58 +02:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Conventions

Cross-cutting conventions every Oxide system follows. These are decided once, here, so individual systems don't each invent their own.

Coordinate system

Oxide uses a right-handed coordinate system, consistent with glam's *_rh matrix constructors and the glTF asset format the engine will load.

In a default (identity) orientation:

Axis Direction Local accessor on Transform
+X right Transform::right()
+Y up Transform::up()
-Z forward (the direction a camera/object looks) Transform::forward()

So forward is -Z. This matches the convention used by glTF, OpenGL, and glam's view-matrix helpers, which keeps asset import and camera math consistent.

Transform::looking_at(eye, target, up) produces an orientation whose forward() points from eye toward target.

Rotations

  • Rotations are stored as unit quaternions (glam::Quat), not Euler angles or matrices, to avoid gimbal lock and accumulate cleanly under composition.
  • Euler-angle helpers (Quat::from_euler) are available for authoring, but the canonical stored form is always a quaternion.
  • Angles are in radians. Convert from degrees explicitly at the boundary (90_f32.to_radians()).

Transform composition

  • Transforms compose parent-first: parent.mul_transform(&child) yields the child resolved in the parent's space, matching parent_matrix * child_matrix.
  • The effective matrix order is T * R * S — scale is applied first, then rotation, then translation.
  • Composition is exact for uniform scale. With non-uniform scale plus rotation the true product is not representable as a single translation / rotation / scale triple, so the result is the closest TRS approximation (re-decomposed from the matrix). Prefer uniform scale in deep hierarchies.

See math.md for details.

Units

  • Length: meters. Physics (rapier3d, Stage 6) tunes its solver for meter-scale geometry, so the whole engine adopts meters to avoid conversions.
  • Time: seconds (f32 for per-frame deltas; a fixed timestep drives physics from Stage 6).
  • Angles: radians (see above).
  • Mass: kilograms (Stage 6+).

Numeric type

  • The engine is f32-first. glam's f32 types are the default throughout; f64 is used only where a specific algorithm demands it.
  • Comparisons use explicit epsilons rather than == on floats. Helpers and tests use a small tolerance (commonly 1e-41e-6) appropriate to the operation.

Color and color space

  • Color stores linear RGBA as f32. Lighting and blending math is correct only in linear space, so that is the engine's working space.
  • Values are nominally in [0, 1] but are not clamped — values above 1.0 represent HDR / emissive intensity.
  • Conversions to and from 8-bit sRGB (the space of color pickers, image files, and #RRGGBB hex) are explicit: Color::from_srgb_u8, Color::from_hex, Color::to_srgb_u8. Never treat raw 8-bit values as linear.

Geometry primitives

  • An Aabb is empty when any min component exceeds the corresponding max; Aabb::EMPTY is the identity for union.
  • A Ray always stores a normalized direction, so its parameter t is a true distance.
  • A Plane is stored in Hessian normal form (normal·p + d = 0) with a unit normal; the positive half-space is the side the normal points toward.
  • A Frustum stores six planes with inward-facing normals; a point is inside when it is in the positive half-space of all six.

Determinism

Procedural systems (Stage 11+) must be deterministic: the same seed always produces the same output. Tests that need randomness use a small, explicit, seeded PRNG rather than a system RNG, so failures are reproducible.