Raytracer docs 🌐 FR

Home / Primitives

Primitives

Primitives are the geometric surfaces that rays hit. Each type below corresponds to a class implementing the IPrimitive interface and to a value of the "type" key in the JSON scene file. Everything on this page reflects the actual code (srcs/plugins/primitive/, PrimitiveBuilder, SceneParser).

10 primitives IPrimitive Analytic & marching intersection AABB / BVH

What is a primitive?

A primitive is a geometric object that a ray can intersect. Every primitive implements the common IPrimitive interface, which exposes three essential behaviours:

  • Intersection test — given a ray P(t) = O + tD, the primitive computes the smallest valid parameter t (within the range [t_min, t_max]) at which the ray meets its surface, or reports no hit.
  • Normal — at the hit point H = O + t·D, the primitive returns the surface normal N, oriented against the ray (flipped when D·N > 0). The normal drives shading, reflections and refractions.
  • Bounding box (AABB) — every primitive exposes an axis-aligned box that fully contains it. This box lets the BVH quickly reject rays that miss the object. See AABB & BVH.

Common properties

In the scene file, every primitive shares a base set of common keys; the remaining keys depend on the type (radius, height, vertices…).

KeyTypeRole
namestringObject name (identifier shown in the editor).
typestringPrimitive type (sphere, plane, …).
position[x, y, z]World position; places the center of the primitive.
rotation[x, y, z]Euler rotation in degrees (applied X, then Y, then Z).
scale[x, y, z]Per-axis scale factor (non-uniform allowed).
materialmaterial refName of a material declared in the materials list. See Materials.

Not every primitive uses all of these keys: for instance the plane is oriented by an axis rather than rotation, and the triangle is defined by its three vertices rather than position/scale. Each section lists the keys actually read by the parser.

Sphere

type: "sphere" — the set of points at a constant distance r from a center C. It is the basic analytic primitive.

Intersection — substituting the ray into the implicit equation ‖P − C‖² − r² = 0 yields a quadratic equation a·t² + b·t + c = 0 with a = D·D, b = 2·(oc·D), c = oc·oc − r² and oc = O − C. The discriminant Δ = b² − 4ac decides: Δ < 0 means no hit, otherwise the smallest valid root is kept. The normal is N = normalize(H − C).

KeyTypeDescription
position[x, y, z]Sphere center.
radiusnumberRadius r (> 0).
scale[x, y, z]Scale applied to the sphere.
materialmaterial refSurface material.
{
    "name": "Ball",
    "type": "sphere",
    "position": [0.0, 1.0, 0.0],
    "scale": [1.0, 1.0, 1.0],
    "radius": 1.0,
    "material": "Red"
}

Plane

type: "plane" — a flat surface, oriented along an axis aligned with the world frame (x, y or z). The axis key is converted into a normal vector via AxisUtils::toVector; the plane passes through position.

Intersection — the plane solves (P − P₀)·N = 0 directly, giving t = ((P₀ − O)·N) / (D·N). When the denominator D·N is near zero the ray is parallel to the plane and there is no hit. The normal is constant everywhere, equal to the axis (flipped to face the ray).

KeyTypeDescription
axis"x" / "y" / "z"Orientation axis of the plane (its normal).
position[x, y, z]Point the plane passes through.
sizenumberExtent of the plane.
materialmaterial refSurface material.
{
    "name": "Floor",
    "type": "plane",
    "axis": "y",
    "position": [0.0, 0.0, 0.0],
    "size": 100.0,
    "material": "Checker"
}

Cube

type: "cube" — a finite convex polyhedron with 6 faces, built as an oriented cube (position + rotation + scale) from 6 planar faces of edge length size.

Intersection — the ray is tested against each of the 6 faces (each face is a bounded plane); the smallest valid t is kept. A candidate hit point is accepted only if it falls within the face bounds (0 ≤ u ≤ 1, 0 ≤ v ≤ 1 in the face's local basis). The normal is the axis-aligned normal of the winning face (±X, ±Y, ±Z), corrected by the rotation and the inverse scale.

KeyTypeDescription
position[x, y, z]Cube center.
rotation[x, y, z]Euler rotation in degrees.
scale[x, y, z]Non-uniform scale.
sizenumberBase edge length.
materialmaterial refSurface material.
{
    "name": "Box",
    "type": "cube",
    "position": [0.0, 1.0, 0.0],
    "rotation": [0.0, 45.0, 0.0],
    "scale": [1.0, 1.0, 1.0],
    "size": 2.0,
    "material": "Wood"
}

A cube can be converted exactly into a mesh of 8 vertices / 12 triangles for vertex editing (see Vertex editing).

Cylinder

type: "cylinder" — a right circular cylinder defined by an axis, a radius radius and a finite height height.

Intersection — ray and origin are projected perpendicular to the axis (Dperp, ocPerp), which reduces the lateral surface to a quadratic equation a·t² + b·t + c = 0 with a = Dperp·Dperp, b = 2·(ocPerp·Dperp), c = ocPerp·ocPerp − r². A root is kept only if the point's projection onto the axis stays within [0, h]. The normal at point H is normalize(H − Q), where Q is the projection of H onto the axis.

KeyTypeDescription
position[x, y, z]Cylinder center.
rotation[x, y, z]Orientation (Euler, degrees).
scale[x, y, z]Scale.
radiusnumberRadius r.
heightnumberFinite height h.
materialmaterial refSurface material.
{
    "name": "Column",
    "type": "cylinder",
    "position": [0.0, 0.0, 0.0],
    "rotation": [0.0, 0.0, 0.0],
    "scale": [1.0, 1.0, 1.0],
    "radius": 0.5,
    "height": 3.0,
    "material": "Marble"
}

Cone

type: "cone" — a right circular cone defined by an apex, an axis and an opening, clamped to a finite height height.

Intersection — like the cylinder, the ray is split into components parallel and perpendicular to the axis, but the implicit equation involves the tangent of the angle (k): ‖v_perp‖² − k²·v_parallel² = 0. This produces a quadratic with a = Dperp·Dperp − k²·dv², b = 2·(ocPerp·Dperp − k²·ov·dv), c = ocPerp·ocPerp − k²·ov². The roots are clipped to [0, h] along the axis. The normal derives from the gradient of the implicit function.

KeyTypeDescription
position[x, y, z]Cone center.
rotation[x, y, z]Orientation (Euler, degrees).
scale[x, y, z]Scale.
radiusnumberBase radius.
heightnumberCone height.
materialmaterial refSurface material.
{
    "name": "Cone",
    "type": "cone",
    "position": [0.0, 0.0, 0.0],
    "rotation": [0.0, 0.0, 0.0],
    "scale": [1.0, 1.0, 1.0],
    "radius": 1.0,
    "height": 2.0,
    "material": "Orange"
}

Triangle

type: "triangle" — a finite planar primitive defined by its three vertices vertex0, vertex1, vertex2. It is the building block of triangulated geometry and an editable primitive (its 3 corners can be moved in the viewport).

Intersection — the Möller–Trumbore algorithm. From the edges E1 = V1 − V0 and E2 = V2 − V0, the barycentric system is solved to obtain (u, v, t): the hit is rejected if u < 0, v < 0 or u + v > 1. The flat normal is normalize(E1 × E2) (or interpolated from vertex normals for smooth shading).

KeyTypeDescription
vertex0[x, y, z]First vertex V0.
vertex1[x, y, z]Second vertex V1.
vertex2[x, y, z]Third vertex V2.
materialmaterial refSurface material.
{
    "name": "Facet",
    "type": "triangle",
    "vertex0": [0.0, 0.0, 0.0],
    "vertex1": [1.0, 0.0, 0.0],
    "vertex2": [0.0, 1.0, 0.0],
    "material": "Green"
}

Torus

type: "torus" — a donut-shaped surface, defined by a major radius radius (R, distance from the center to the tube axis) and a minor radius height (r, tube radius).

Intersection — after moving into the torus's local frame, the quartic implicit surface (‖P‖² + R² − r²)² − 4R²(…) = 0 reduces to a degree-4 equation c₄t⁴ + c₃t³ + c₂t² + c₁t + c₀ = 0. The quartic is solved and the smallest valid real root is kept. The normal is the gradient of the implicit function, rotated back into world space.

Mind the convention: here radius is the major radius R and height is the tube radius r.

KeyTypeDescription
position[x, y, z]Torus center.
rotation[x, y, z]Orientation (Euler, degrees).
radiusnumberMajor radius R.
heightnumberTube radius r.
materialmaterial refSurface material.
{
    "name": "Ring",
    "type": "torus",
    "position": [0.0, 1.0, 0.0],
    "rotation": [90.0, 0.0, 0.0],
    "radius": 1.0,
    "height": 0.3,
    "material": "Gold"
}

Tanglecube

type: "tanglecube" — a smooth implicit algebraic surface (isosurface), generated by a quartic scalar field and a threshold: f(p) = x⁴ − 5x² + y⁴ − 5y² + z⁴ − 5z² + threshold, the surface being the zero level set f(p) = 0.

Intersection — rather than solving the quartic analytically, the implementation uses marching: an entry/exit interval from a bounding sphere, adaptive-step progression until a sign change g(t_prev)·g(t_cur) < 0 is detected, then root refinement (Newton with a bisection fallback). The normal is normalize(∇f) with ∇f = (4x³ − 10x, 4y³ − 10y, 4z³ − 10z).

KeyTypeDescription
position[x, y, z]Surface center.
rotation[x, y, z]Rotation (stored on the object).
thresholdnumberIso-value offset for f(p) = 0.
sizenumberGlobal scale factor.
materialmaterial refSurface material.
{
    "name": "Tanglecube",
    "type": "tanglecube",
    "position": [0.0, 2.0, 0.0],
    "rotation": [0.0, 0.0, 0.0],
    "threshold": 11.8,
    "size": 1.0,
    "material": "Blue"
}

Fractal

type: "fractal" — a 3D fractal object (Mandelbulb-style) rendered by distance estimation (distance estimator / sphere tracing). The surface is defined implicitly by an estimator DE(p) = 0 computed iteratively.

Intersectionray marching: the ray is first bounded by the primitive's AABB to get [tEnter, tExit], then advanced by steps equal to the estimated distance d = DE(p)·size. When d < ε, the point is accepted; otherwise t += d, until the ray exits the box or reaches the step limit. The normal is estimated numerically by finite differences of DE around the point.

KeyTypeDescription
position[x, y, z]Fractal center.
sizenumberWorld scaling of the fractal domain (default 100).
powernumberFractal exponent (default 8).
iterationsintegerIteration count in the estimator (default 8).
materialmaterial refSurface material.
{
    "name": "Mandelbulb",
    "type": "fractal",
    "position": [0.0, 0.0, 0.0],
    "size": 100.0,
    "power": 8.0,
    "iterations": 8,
    "material": "Purple"
}

Mesh

type: "mesh" — a triangulated surface loaded from a Wavefront .obj file (via ObjParser), or defined inline through vertices / faces / normals arrays. The mesh is a single primitive that internally owns its triangles and a local BVH, so hundreds of thousands of triangles are intersected in logarithmic time. It is vertex-editable.

Intersection — each triangle is tested with Möller–Trumbore, but rays never traverse the triangles linearly: the local BVH prunes them. Normals are interpolated by barycentric coordinates when the .obj provides vertex normals (vn), otherwise a flat per-face normal is used as a fallback. The geometry is recentered on its own center: position therefore places the mesh center.

When inline geometry is provided (vertices non-empty), it takes precedence over the file path: this is how "baked" primitives (converted to a mesh) are stored.

KeyTypeDescription
filestringPath to the .obj (resolved from the working directory).
verticeslist of [x, y, z]Inline geometry (takes precedence over file).
faceslist of [i, j, k]Triangular faces (vertex indices).
normalslist of [x, y, z]Vertex normals (optional).
position[x, y, z]Position of the mesh center.
rotation[x, y, z]Euler rotation in degrees.
scale[x, y, z]Per-axis scale (non-uniform allowed).
vertex_overrideslistVertex overrides (index + position) re-applied after loading the .obj.
materialmaterial refMaterial applied to every hit.
{
    "name": "Suzanne",
    "type": "mesh",
    "file": "tests/obj/suzanne.obj",
    "position": [0.0, 0.0, 0.0],
    "rotation": [0.0, 0.0, 0.0],
    "scale": [1.0, 1.0, 1.0],
    "material": "Blue"
}

Only triangular faces are accepted; the .obj's texture coordinates (vt) and material libraries (mtllib / usemtl) are ignored. Details and editing: Features → .obj meshes.

AABB & BVH

An AABB (Axis-Aligned Bounding Box) is a box aligned with the coordinate axes, defined by its minimum Bmin and maximum Bmax corners. It is not a primitive you place in a scene: it is the bounding volume exposed by every primitive and used by the BVH.

The ray/AABB test uses the slab method: for each axis the entry and exit parameters t1 = (Bmin − O)/D, t2 = (Bmax − O)/D are computed, then combined into tEnter = max(tNear) and tExit = min(tFar). A hit exists when tEnter ≤ tExit. A ray that misses a node's box skips its whole subtree.

Before rendering, the scene builds a bounding volume hierarchy (BVH) via scene.buildBvh(); every node carries an AABB. Meshes additionally hold a local BVH over their triangles. The construction is detailed in Features → BVH acceleration.

The repository also contains the Mobius_strip.* source files, but this primitive is not wired into the parser or builder: it is not available in a scene.