# 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](math.md#transform) 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-4`–`1e-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`](math.md#aabb) is *empty* when any `min` component exceeds the corresponding `max`; `Aabb::EMPTY` is the identity for `union`. - A [`Ray`](math.md#ray) always stores a **normalized** direction, so its parameter `t` is a true distance. - A [`Plane`](math.md#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`](math.md#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.