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, matchingparent_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 (
f32for 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'sf32types are the default throughout;f64is used only where a specific algorithm demands it. - Comparisons use explicit epsilons rather than
==on floats. Helpers and tests use a small tolerance (commonly1e-4–1e-6) appropriate to the operation.
Color and color space
Colorstores linear RGBA asf32. 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 above1.0represent HDR / emissive intensity. - Conversions to and from 8-bit sRGB (the space of color pickers, image
files, and
#RRGGBBhex) are explicit:Color::from_srgb_u8,Color::from_hex,Color::to_srgb_u8. Never treat raw 8-bit values as linear.
Geometry primitives
- An
Aabbis empty when anymincomponent exceeds the correspondingmax;Aabb::EMPTYis the identity forunion. - A
Rayalways stores a normalized direction, so its parametertis a true distance. - A
Planeis 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
Frustumstores 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.