Gama C Library
Gama C API Documentation
project.h
Go to the documentation of this file.
1/**
2 * @file project.h
3 * @brief Implements the software rasterizer for projecting 3D meshes onto a 2D `gm3Image`.
4 *
5 * This file contains the core logic for the 3D rendering pipeline, including
6 * lighting calculations, vertex transformation, clipping, backface culling,
7 * and triangle assembly for the `gm3Image` output.
8 */
9#pragma once
10
11#include "../color.h"
12#include "../position.h"
13#include "image.h"
14#include "light.h"
15#include "mesh.h"
16#include "mtl.h"
17#include "position.h"
18#include "scene.h"
19#include "transform.h"
20#include <stddef.h>
21#include <stdlib.h>
22
23// --- Optimized Lighting ---
24
25/**
26 * @brief Calculates the final lit color of a face based on its normal, center, material, and scene lighting.
27 *
28 * This function implements a Blinn-Phong lighting model, calculating ambient,
29 * diffuse, and specular components.
30 *
31 * @param norm The normal vector of the face in world space.
32 * @param face_center The world-space center position of the face.
33 * @param mat A pointer to the `gm3Material` of the face. Can be NULL for a default material.
34 * @param scene A pointer to the `gm3Scene` containing light and camera information.
35 * @return The calculated `gmColor` of the lit face.
36 */
37static inline gmColor gm3_calculate_lighting(gm3Pos norm, gm3Pos face_center,
38 const gm3Material *mat,
39 const gm3Scene *scene) {
40 // 1. Vector Setup (Manually inlined for speed)
41
42 // Light Vector (L): From Surface to Light
43 double lx = scene->light.position.x - face_center.x;
44 double ly = scene->light.position.y - face_center.y;
45 double lz = scene->light.position.z - face_center.z;
46
47 // Normalize L
48 double l_len_sq = lx * lx + ly * ly + lz * lz;
49 double l_inv_len = 1.0 / sqrt(l_len_sq);
50 lx *= l_inv_len;
51 ly *= l_inv_len;
52 lz *= l_inv_len;
53
54 // 2. Diffuse Component (N dot L)
55 double dot_diff = norm.x * lx + norm.y * ly + norm.z * lz;
56 double diffuse = (dot_diff > 0.0) ? dot_diff : 0.0;
57
58 // 3. Specular Component (Blinn-Phong)
59 double specular = 0.0;
60
61 // Only calculate specular if the face is lit and material is shiny
62 if (diffuse > 0.0 && mat && mat->shininess > 0.0) {
63 // View Vector (V): From Surface to Camera (0,0,0)
64 // So V = (0,0,0) - FaceCenter = -FaceCenter
65 double vx = -face_center.x;
66 double vy = -face_center.y;
67 double vz = -face_center.z;
68
69 // Normalize V
70 double v_len_sq = vx * vx + vy * vy + vz * vz;
71 double v_inv_len = 1.0 / sqrt(v_len_sq);
72 vx *= v_inv_len;
73 vy *= v_inv_len;
74 vz *= v_inv_len;
75
76 // Half Vector (H) = Normalize(L + V)
77 double hx = lx + vx;
78 double hy = ly + vy;
79 double hz = lz + vz;
80
81 double h_len_sq = hx * hx + hy * hy + hz * hz;
82
83 // Avoid divide by zero if L and V are exactly opposite
84 if (h_len_sq > 0.000001) {
85 double h_inv_len = 1.0 / sqrt(h_len_sq);
86 hx *= h_inv_len;
87 hy *= h_inv_len;
88 hz *= h_inv_len;
89
90 // N dot H
91 double dot_spec = norm.x * hx + norm.y * hy + norm.z * hz;
92 if (dot_spec > 0.0) {
93 // Expensive POW call, only done when strictly necessary
94 specular = pow(dot_spec, mat->shininess);
95 }
96 }
97 }
98
99 // 4. Combine Light Intensities
100 double intensity = scene->light.intensity;
101 double ambient = scene->light.ambient;
102
103 // Pre-calculate common factors
104 // Diffuse affects the material color
105 double light_factor = (diffuse * intensity) + ambient;
106 // Specular is usually white (light color) added on top
107 double spec_factor = specular * intensity;
108
109 // 5. Final Color Mixing
110 // Light Source Color (normalized 0-1)
111 double lr = gm_red(scene->light.color) / 255.0;
112 double lg = gm_green(scene->light.color) / 255.0;
113 double lb = gm_blue(scene->light.color) / 255.0;
114
115 int r, g, b, a;
116
117 if (mat) {
118 double mr = gm_red(mat->diffuse);
119 double mg = gm_green(mat->diffuse);
120 double mb = gm_blue(mat->diffuse);
121
122 double msr = gm_red(mat->specular);
123 double msg = gm_green(mat->specular);
124 double msb = gm_blue(mat->specular);
125
126 // Color = (MatDiffuse * LightFactor * LightColor) + (MatSpec * SpecFactor *
127 // LightColor)
128 r = (int)((mr * light_factor + msr * spec_factor) * lr);
129 g = (int)((mg * light_factor + msg * spec_factor) * lg);
130 b = (int)((mb * light_factor + msb * spec_factor) * lb);
131 a = (int)(mat->alpha * 255);
132 } else {
133 // Default white material
134 double k = light_factor + spec_factor; // Simple mix
135 r = (int)(lr * 255.0 * k);
136 g = (int)(lg * 255.0 * k);
137 b = (int)(lb * 255.0 * k);
138 a = 255;
139 }
140
141 // Clamping (Manual min/max is faster than fmax/fmin calls usually)
142 if (r > 255)
143 r = 255;
144 if (r < 0)
145 r = 0;
146 if (g > 255)
147 g = 255;
148 if (g < 0)
149 g = 0;
150 if (b > 255)
151 b = 255;
152 if (b < 0)
153 b = 0;
154
155 return gm_rgba(r, g, b, a);
156}
157
158// --- Main Projection Function ---
159
160/**
161 * @brief Projects a 3D mesh onto a 2D image buffer, applying transformations, lighting, and culling.
162 *
163 * This function performs the core 3D to 2D rendering pipeline:
164 * - Transforms mesh vertices from model space to world space, then to screen space.
165 * - Stores world-space vertex positions for lighting and culling.
166 * - Performs basic frustum clipping and backface culling.
167 * - Calculates lighting for each visible face using the Blinn-Phong model.
168 * - Assembles the projected 2D triangles, colors, and depth values into the `gm3Image` output.
169 *
170 * @param output A pointer to the `gm3Image` struct where the projected 2D scene data will be stored.
171 * @param mesh A pointer to the `gm3Mesh` to project.
172 * @param transform A pointer to the `gm3Transform` to apply to the mesh (can be NULL for identity).
173 * @param scene A pointer to the `gm3Scene` containing camera and light settings (can be NULL for defaults).
174 * @return 0 on success, -1 on memory allocation failure, or if the mesh is invalid.
175 */
176int gm3_project(gm3Image *output, const gm3Mesh *mesh,
177 const gm3Transform *transform, const gm3Scene *scene) {
178 if (transform == NULL)
179 transform = &gm3_default_transform;
180 if (scene == NULL)
181 scene = &gm3_default_scene;
182
183 if (!mesh || mesh->n_vertices == 0)
184 return 0;
185
186 // 1. Ensure Capacity in Output Image
187 if (!gm3_image_ensure_cap(output, output->n_vertices + mesh->n_vertices,
188 output->n_triangles + mesh->n_faces)) {
189 return -1;
190 }
191
192 // 2. Ensure Scratch Buffer (World Coordinates) Capacity
193 // This buffer is inside the image struct, safe for this specific image
194 // instance
195 if (mesh->n_vertices > output->_internal.cap_world) {
196 size_t new_cap = mesh->n_vertices + 512;
197 void *tmp =
198 realloc(output->_internal.world_verts, new_cap * sizeof(gm3Pos));
199 if (!tmp)
200 return -1;
201 output->_internal.world_verts = tmp;
202 output->_internal.cap_world = new_cap;
203 }
204
205 // Access pointers for speed
206 gm3Pos *scratch_world = output->_internal.world_verts;
207 size_t start_v = output->n_vertices;
208 size_t start_t = output->n_triangles;
209
210 double focal = scene->camera.focal;
211 double near_plane = scene->camera.near;
212 double far_plane = scene->camera.far;
213
214 // ========================================================
215 // STAGE A: VERTEX PROCESSING (Linear O(V))
216 // Transform Local -> World -> Screen
217 // ========================================================
218 for (size_t i = 0; i < mesh->n_vertices; i++) {
219 // 1. Model -> World
220 gm3Pos p = mesh->vertices[i];
221 gm3_transform_pos(&p, transform);
222
223 // Save World position for Lighting/Culling later
224 scratch_world[i] = p;
225
226 // 2. World -> Screen (Projected)
227 if (p.z > 0.1) {
228 double inv_z = 1.0 / p.z; // Optimization: Mult is faster than Div
229 double s = focal * inv_z;
230
231 output->vertices[start_v + i].x = p.x * s;
232 output->vertices[start_v + i].y = p.y * s;
233 // Z is not stored in vertex buffer, used later for depth buffer
234 } else {
235 // Behind camera: mark as garbage
236 output->vertices[start_v + i].x = -99999.0;
237 output->vertices[start_v + i].y = -99999.0;
238 }
239 }
240
241 // Update total vertices in image
242 output->n_vertices += mesh->n_vertices;
243
244 // ========================================================
245 // STAGE B: FACE PROCESSING (Linear O(F))
246 // Connect vertices, Cull, Light
247 // ========================================================
248 size_t t_idx = start_t;
249
250 for (size_t i = 0; i < mesh->n_faces; i++) {
251 gm3MeshFace *face = &mesh->faces[i];
252
253 // Get the World Positions we calculated in Stage A
254 gm3Pos *w0 = &scratch_world[face->vertices[0]];
255 gm3Pos *w1 = &scratch_world[face->vertices[1]];
256 gm3Pos *w2 = &scratch_world[face->vertices[2]];
257
258 // 1. Clipping / Safety Check
259 if (w0->z < near_plane || w0->z > far_plane)
260 continue;
261 if (w1->z < 0.1 || w2->z < 0.1)
262 continue; // Basic near clip
263
264 // 2. Face Center & Backface Culling
265 // Inline Center Calculation
266 gm3Pos center = {(w0->x + w1->x + w2->x) * 0.333333,
267 (w0->y + w1->y + w2->y) * 0.333333,
268 (w0->z + w1->z + w2->z) * 0.333333};
269
270 // Calculate Normal (Rotated)
271 gm3Pos norm = face->normal;
272 // NOTE: gm3_pos_rotate is typically for vectors, not normals with matrix.
273 // For proper normal transformation, inverse transpose of modelview matrix is needed.
274 // However, if only rotation is applied (no non-uniform scaling), simple rotation is fine.
275 // The current transform->rotation is likely a quaternion or rotation matrix representation.
276 // Assuming gm3_pos_rotate correctly applies rotation to a vector.
277 gm3_pos_rotate(&norm, &transform->rotation);
278
279 // Backface Check: Dot(Normal, Vector_to_Cam)
280 // Camera is at (0,0,0), so Vector_to_Cam is (0 - center).
281 // Dot(N, -C) > 0 == Dot(N, C) < 0
282 if (gm3_pos_dot(norm, center) >= 0)
283 continue;
284
285 // 3. Lighting
286 gm3Material *mat = NULL;
287 if (face->material_file >= 0 && face->material >= 0) {
288 // Assuming bounds checks happen in mesh loader or are safe
289 mat = &mesh->mtllibs[face->material_file].materials[face->material];
290 }
291 output->colors[t_idx] = gm3_calculate_lighting(norm, center, mat, scene);
292
293 // 4. Triangle Assembly
294 // Point to the global indices in the image vertex array
295 output->triangles[t_idx * 3 + 0] = start_v + face->vertices[0];
296 output->triangles[t_idx * 3 + 1] = start_v + face->vertices[1];
297 output->triangles[t_idx * 3 + 2] = start_v + face->vertices[2];
298
299 output->depths[t_idx] = center.z;
300 t_idx++;
301 }
302
303 output->n_triangles = t_idx;
304 return 0;
305}
#define gm3_pos_dot(a, b)
Calculates the dot product of two gm3Pos vectors.
Definition position.h:138
#define gm_blue(col)
Extracts the blue component from a color.
Definition color.h:41
#define gm_green(col)
Extracts the green component from a color.
Definition color.h:34
#define gm_red(col)
Extracts the red component from a color.
Definition color.h:27
uint32_t gmColor
Type definition for color values, stored as a 32-bit unsigned integer. The color components are packe...
Definition color.h:13
void * realloc(void *ptr, size_t size)
Custom implementation of realloc for memory allocated by malloc (this custom version).
Definition malloc.h:236
double pow(double base, double exp)
Calculates the base raised to the power of the exponent (base^exp).
Definition math.h:358
double sqrt(double x)
Calculates the square root of x.
Definition math.h:339
Defines structures for 3D materials and material libraries, and functions for loading MTL files.
int gm3_project(gm3Image *output, const gm3Mesh *mesh, const gm3Transform *transform, const gm3Scene *scene)
Projects a 3D mesh onto a 2D image buffer, applying transformations, lighting, and culling.
Definition project.h:176
Defines the 3D scene structure, encompassing lights, cameras, and viewport settings.
const gm3Scene gm3_default_scene
A default gm3Scene instance.
Definition scene.h:30
double near
Definition camera.h:12
double focal
Definition camera.h:11
double far
Definition camera.h:13
Structure representing the 2D projected output of a 3D scene.
Definition image.h:22
double * depths
Definition image.h:27
gmColor * colors
Definition image.h:25
gmPos * vertices
Definition image.h:24
size_t * triangles
Definition image.h:26
size_t n_vertices
Definition image.h:30
size_t cap_world
Definition image.h:45
gm3Pos * world_verts
Definition image.h:44
size_t n_triangles
Definition image.h:32
struct gm3Image::@171174116104107177311324262265324115365007060225 _internal
double intensity
Definition light.h:17
gm3Pos position
Definition light.h:14
gmColor color
Definition light.h:16
double ambient
Definition light.h:18
Represents a single 3D material with various rendering properties.
Definition mtl.h:26
double alpha
Definition mtl.h:31
gmColor diffuse
Definition mtl.h:28
gmColor specular
Definition mtl.h:29
double shininess
Definition mtl.h:30
Represents a single face (triangle) in a 3D mesh.
Definition mesh.h:10
int material
Definition mesh.h:13
int material_file
Definition mesh.h:14
size_t vertices[3]
Definition mesh.h:11
gm3Pos normal
Definition mesh.h:15
Represents a 3D mesh composed of vertices, faces, normals, and texture coordinates.
Definition mesh.h:30
gm3MeshFace * faces
Definition mesh.h:34
size_t n_vertices
Definition mesh.h:32
gm3Pos * vertices
Definition mesh.h:31
gm3MtlLib * mtllibs
Definition mesh.h:43
size_t n_faces
Definition mesh.h:35
gm3Material * materials
Definition mtl.h:56
Represents a 3D position or vector.
Definition position.h:11
double y
Definition position.h:12
double z
Definition position.h:12
double x
Definition position.h:12
Represents a complete 3D scene, including its camera, lights, and viewport.
Definition scene.h:18
gm3Camera camera
Definition scene.h:22
gm3Light light
Definition scene.h:21
Represents a 3D transformation, including position, rotation, and scale.
Definition transform.h:16
gm3Pos rotation
Definition transform.h:18
double x
Definition position.h:9
double y
Definition position.h:9
Defines structures and functions for 3D transformations (position, rotation, scale).
const gm3Transform gm3_default_transform
A default gm3Transform instance.
Definition transform.h:28
void gm3_pos_rotate(gm3Pos *res, const gm3Pos *rot)
Rotates a gm3Pos vector by the given Euler angles (X, Y, Z).
Definition transform.h:39
void gm3_transform_pos(gm3Pos *p, const gm3Transform *t)
Applies a gm3Transform to a gm3Pos vector.
Definition transform.h:67