This project has been created as part of the 42 curriculum by kmurugan, tlarina.
Create a mini ray tracer that parses scene descriptions and renders 3D images with lighting, shadows, reflections, and basic camera controls.
miniRT is a challenging 42 project that teaches core graphics and rendering concepts. The program reads scene files describing a camera, lights, and simple geometric primitives (planes, spheres, cylinders and cones (bonus)), then computes pixel colors by tracing rays from the camera into the scene and displays the result using MiniLibX.
Checker field scatter — reflective spheres on a checkerboard floor (specular reflection & transparency)

Mirror floor scatter — Phong spheres on a reflective plane

MinilibX library
# MinilibX library for linux
git clone https://github.com/42Paris/minilibx-linux.git mlx_linux
# MinilibX library for macOS
git clone https://github.com/dannywillems/minilibx-mac-osx mlx# mandatory
make && ./miniRT <file.rt>
# bonus
make bonus && ./miniRT_bonus <file.rt>NOTE: Currently supports Linux and macOS only.
- Each element type can be separated by one or more line breaks.
- Each field within an element can be separated by one or more spaces.
- Trailing spaces at the end of a line are not permitted.
- Elements can appear in any order.
A,C, andLmust each be declared exactly once (multipleLallowed in bonus).- Light/ambient ratios, and object diameter/height must not be
0. - Camera FOV must be in the range
(0, 180). Both0°and180°are mathematically invalid and will cause the projection to collapse: tan(0) = 0 collapses the viewport to a point, tan(90°) = ∞ makes it infinitely wide. Very small angles (< 10°) produce extreme telephoto compression where all depth is lost. Very large angles (> 120°) produce extreme wide-angle barrel distortion where most of the scene is crushed toward the centre. Both extremes render without error but produce images that are not useful for verifying scene content. - Normal vectors must be unit length (
√(x² + y² + z²) = 1), with each x, y, z component in the range[-1, 1].
See example.rt for a minimal valid scene.
A <ratio> <r,g,b>
A 0.2 255,255,255
| Field | Type | Range | Example |
|---|---|---|---|
ratio * |
float | (0, 1] | 0.2 |
r,g,b |
int | [0, 255] | 255,255,255 |
*
ratio- brightness intensity of the ambient light applied to all objects in the scene
C <x,y,z> <nx,ny,nz> <fov>
C -50,0,20 0,0,1 70
| Field | Type | Range | Example |
|---|---|---|---|
x,y,z * |
float | any | -50,0,20 |
nx,ny,nz * |
float (unit vector) | [-1, 1] | 0,0,1 |
fov * |
int | (0, 180) | 70 |
*
x,y,z- position of the camera (viewpoint/eye) in the scene
*
nx,ny,nz- orientation vector defining the direction the camera looks toward
*
fov- horizontal field of view in degrees; controls how wide the camera sees
L <x,y,z> <ratio> <r,g,b>
L -40,0,30 0.7 255,255,255
| Field | Type | Range | Example |
|---|---|---|---|
x,y,z |
float | any | -40,0,30 |
ratio * |
float | (0, 1] | 0.7 |
r,g,b * |
int | [0, 255] | 255,255,255 |
*
ratio- brightness intensity of the light source
*
r,g,b- unused in the mandatory part; available in the bonus version
Multiple
Ldeclarations are allowed in the bonus version
pl <x,y,z> <nx,ny,nz> <r,g,b> [diffuse_ratio specular_ratio shininess checkerboard reflectivity opacity]
# mandatory
pl 0.0,0.0,-10.0 0.0,1.0,0.0 0,0,225
# bonus
pl 0.0,0.0,-10.0 0.0,1.0,0.0 0,0,225 1.0 0.5 32.0 1 0.0 1.0
| Field | Type | Range | Mandatory | Example |
|---|---|---|---|---|
x,y,z * |
float | any | ✓ | 0.0,0.0,-10.0 |
nx,ny,nz * |
float (unit vector) | [-1, 1] | ✓ | 0.0,1.0,0.0 |
r,g,b |
int | [0, 255] | ✓ | 0,0,225 |
| bonus fields ** | - | - | bonus | see below |
*
x,y,z- coordinates of any point lying on the plane
*
nx,ny,nz- normal vector perpendicular to the plane surface
** See Bonus material fields
sp <x,y,z> <diameter> <r,g,b> [diffuse_ratio specular_ratio shininess checkerboard reflectivity opacity]
# mandatory
sp 0.0,0.0,20.6 12.6 10,0,255
# bonus
sp 0.0,0.0,20.6 12.6 10,0,255 0.8 0.5 64.0 0 0.3 1.0
| Field | Type | Range | Mandatory | Example |
|---|---|---|---|---|
x,y,z * |
float | any | ✓ | 0.0,0.0,20.6 |
diameter |
float | > 0 | ✓ | 12.6 |
r,g,b |
int | [0, 255] | ✓ | 10,0,255 |
| bonus fields ** | - | - | bonus | see below |
*
x,y,z- coordinates of the sphere's center point
** See Bonus material fields
cy <x,y,z> <nx,ny,nz> <diameter> <height> <r,g,b> [diffuse_ratio specular_ratio shininess checkerboard reflectivity opacity]
# mandatory
cy 50.0,0.0,20.6 0.0,0.0,1.0 14.2 21.42 10,0,255
# bonus
cy 50.0,0.0,20.6 0.0,0.0,1.0 14.2 21.42 10,0,255 0.6 0.8 128.0 0 0.9 0.5
| Field | Type | Range | Mandatory | Example |
|---|---|---|---|---|
x,y,z * |
float | any | ✓ | 50.0,0.0,20.6 |
nx,ny,nz * |
float (unit vector) | [-1, 1] | ✓ | 0.0,0.0,1.0 |
diameter |
float | > 0 | ✓ | 14.2 |
height |
float | > 0 | ✓ | 21.42 |
r,g,b |
int | [0, 255] | ✓ | 10,0,255 |
| bonus fields ** | - | - | bonus | see below |
*
x,y,z- coordinates of the cylinder's center point (midpoint along its axis)
*
nx,ny,nz- normalized direction vector of the cylinder's axis
** See Bonus material fields
co <x,y,z> <nx,ny,nz> <diameter> <height> <r,g,b> [diffuse_ratio specular_ratio shininess checkerboard reflectivity opacity]
# bonus
co -20.0,-9.0,50.0 0.0,1.0,0.0 15.2 7.5 0,255,0 0.8 0.5 64.0 0 0.3 1.0
| Field | Type | Range | Mandatory | Example |
|---|---|---|---|---|
x,y,z * |
float | any | ✓ | -20,-9,50 |
nx,ny,nz * |
float (unit vector) | [-1, 1] | ✓ | 0,1.0,0 |
diameter |
float | > 0 | ✓ | 15 |
height |
float | > 0 | ✓ | 7 |
r,g,b |
int | [0, 255] | ✓ | 0,255,0 |
| bonus fields ** | - | - | bonus | see below |
*
x,y,z- coordinates of the cone's center point (midpoint along its axis, same convention as the cylinder)
*
nx,ny,nz- normalized direction vector of the cone's axis, pointing from the base toward the apex (e.g.0,1,0for a cone standing upright)
*
diameter- diameter of the circular base
*
height- distance from the base to the apex along the axis
The cone has a closed base cap at the bottom; the apex is a point. Available in the bonus version only.
** See Bonus material fields
All geometric objects accept an optional bonus section following the mandatory fields. All six bonus fields are required when any bonus field is present — partial bonus sections are rejected.
<mandatory fields> <diffuse_ratio> <specular_ratio> <shininess> <checkerboard> <reflectivity> <opacity>
| Field | Type | Range | Default | Description |
|---|---|---|---|---|
diffuse_ratio |
float | [0, 1] | 1.0 |
Strength of the diffuse (matte) component of the Phong reflection model |
specular_ratio |
float | [0, 1] | 0.0 |
Strength of the specular (shiny highlight) component of the Phong reflection model |
shininess |
float | > 0 | 1.0 |
Sharpness of the specular highlight - higher values produce a tighter, more polished look |
checkerboard |
bool | 0 or 1 | 0 |
Enables checkerboard pattern on the surface (0 = off, 1 = on) |
reflectivity |
float | [0, 1] | 0.0 |
Fraction of incoming light reflected as a mirror ray (0.0 = no reflection, 1.0 = perfect mirror) |
opacity |
float | [0, 1] | 1.0 |
Surface opacity (1.0 = fully opaque, 0.0 = fully transparent/glass) |
When the optional fields are absent the mandatory defaults apply and the object behaves identically to a mandatory-build object.
Challenge: A clean 50/50 division of work from day one was difficult because shared structures (t_ray, t_scene, math utils) needed to evolve as both the parser and renderer were being built simultaneously.
Solution: Embraced iterative development and constant communication. We started by building the parser with basic structures, and as the renderer's requirements became clear, we actively refactored the shared interfaces to support the growing codebase.
Challenge: Hardcoding mandatory constraints (single light, basic colors, separate object lists) forces massive rewrites when adding bonus features.
Solution: Designed for the bonus upfront. We used t_material instead of basic colors, linked lists for lights, and a t_object to unite all scene objects and eliminate redundant list-walking across the codebase and one free path in cleanup_objects.
Challenge: Direct equality (==) on long double fails because sqrtl rounding and accumulated arithmetic produce binary representations that differ slightly from mathematical equality. This causes false positives in input validation and self-intersections in the rendering loop (shadow rays hitting the surface they originated from).
Solution: Exact equality checks were replaced with magnitude-based epsilon comparisons (fabsl(a - b) <= EPSILON). Two distinct epsilon scales were implemented based on the accumulated error of the source values:
-
1e-12for validating input data (e.g., confirming normal vectors satisfy√(x² + y² + z²) = 1) -
1e-6for computed geometric intersections (e.g., the self-intersection bias applied to shadow ray origins)
Additionally, vec3_len_sq was introduced to calculate squared length (x² + y² + z²). This avoids the precision loss and performance cost of sqrtl entirely in cases where only relative magnitude comparisons are needed.
Challenge: Early parsing used ft_strtok_r with ft_strtold with separate helpers (get_point, get_double, get_color) per field. Each object type duplicated the same pattern: token, validate, next_token, check for extra tokens. That was easy to get wrong and hard to scale when adding planes, cylinders and bonus material fields.
Solution: A custom ft_sscanf with one format string per element. Mandatory-only lines (A, C, L) enforce strict end-of-line via a trailing %c. Object lines use %n to capture the position after the mandatory fields so a shared parse_material helper can validate the trailing newline or parse optional bonus material fields in one place.
- 3D Computer Graphics Primer: Ray-Tracing as an Example: Algorithm
- CS 418 Raytracing: Secondary Rays & bias
- Ray Intersection
- The Ray Tracer Challenge: A Test-Driven Guide to Your First 3D Renderer
- Ray Tracing in One Weekend - The Book Series
- Ray Tracing - The Cherno
- Coding Adventure: Ray Tracing
- Unit Vector
- Developing a Math Engine in C++: Implementing Vectors
- Linear Algebra in C++ - A simple Vector class
- Bresenham's line algorithm
- Learning and verification: Clarifying complex ray-geometry math, intersection logic, and shading formulas.
- Concept clarification: Breaking down core ray tracing principles and how different parts of the rendering pipeline fit together.
- Design discussion: Exploring possible approaches to common ray tracing problems and comparing their trade-offs.
- Documentation: Proofreading and refining the README and project descriptions, including docs/bvh_benchmark.md.
- Testing and scene generators: Parser tests and BVH benchmark scripts (timing comparisons).