342 lines
14 KiB
C

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <SDL.h>
#include "array.h"
#include "display.h"
#include "vector.h"
#include "matrix.h"
#include "light.h"
#include "triangle.h"
#include "texture.h"
#include "mesh.h"
///////////////////////////////////////////////////////////////////////////////
// Array of triangles that should be rendered frame by frame
///////////////////////////////////////////////////////////////////////////////
triangle_t* triangles_to_render = NULL;
int render_method, cull_method;
///////////////////////////////////////////////////////////////////////////////
// Global variables for execution status and game loop
///////////////////////////////////////////////////////////////////////////////
bool is_running = false;
int previous_frame_time = 0;
vec3_t camera_position = { .x = 0, .y = 0, .z = 0 };
mat4_t proj_matrix;
///////////////////////////////////////////////////////////////////////////////
// Setup function to initialize variables and game objects
///////////////////////////////////////////////////////////////////////////////
void setup(void) {
// Initialize render mode and triangle culling method
render_method = RENDER_TEXTURED_WIRE;
cull_method = CULL_BACKFACE;
// Allocate the required memory in bytes to hold the color buffer
color_buffer = (uint32_t*)malloc(sizeof(uint32_t) * window_width * window_height);
// Creating a SDL texture that is used to display the color buffer
color_buffer_texture = SDL_CreateTexture(
renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
window_width,
window_height
);
// Initialize the perspective projection matrix
float fov = M_PI / 3.0; // the same as 180/3, or 60deg
float aspect = (float)window_height / (float)window_width;
float znear = 0.1;
float zfar = 100.0;
proj_matrix = mat4_make_perspective(fov, aspect, znear, zfar);
// Manually load the hardcoded texture data from the static array
//mesh_texture = (uint32_t*)REDBRICK_TEXTURE;
//texture_width = 64;
//texture_height = 64;
load_png_texture_data("../assets/cube.png");
// Loads the vertex and face values for the mesh data structure
//load_cube_mesh_data();
load_obj_file_data("../assets/cube.obj");
}
///////////////////////////////////////////////////////////////////////////////
// Poll system events and handle keyboard input
///////////////////////////////////////////////////////////////////////////////
void process_input(void) {
SDL_Event event;
SDL_PollEvent(&event);
switch (event.type) {
case SDL_QUIT:
is_running = false;
break;
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_ESCAPE)
is_running = false;
if (event.key.keysym.sym == SDLK_1)
render_method = RENDER_WIRE_VERTEX;
if (event.key.keysym.sym == SDLK_2)
render_method = RENDER_WIRE;
if (event.key.keysym.sym == SDLK_3)
render_method = RENDER_FILL_TRIANGLE;
if (event.key.keysym.sym == SDLK_4)
render_method = RENDER_FILL_TRIANGLE_WIRE;
if (event.key.keysym.sym == SDLK_5)
render_method = RENDER_TEXTURED;
if (event.key.keysym.sym == SDLK_6)
render_method = RENDER_TEXTURED_WIRE;
if (event.key.keysym.sym == SDLK_c)
cull_method = CULL_BACKFACE;
if (event.key.keysym.sym == SDLK_d)
cull_method = CULL_NONE;
break;
}
}
///////////////////////////////////////////////////////////////////////////////
// Update function frame by frame with a fixed time step
///////////////////////////////////////////////////////////////////////////////
void update(void) {
// Wait some time until the reach the target frame time in milliseconds
int time_to_wait = FRAME_TARGET_TIME - (SDL_GetTicks() - previous_frame_time);
// Only delay execution if we are running too fast
if (time_to_wait > 0 && time_to_wait <= FRAME_TARGET_TIME) {
SDL_Delay(time_to_wait);
}
previous_frame_time = SDL_GetTicks();
// Initialize the array of triangles to render
triangles_to_render = NULL;
// Change the mesh scale, rotation, and translation values per animation frame
mesh.rotation.x += 0.000;
mesh.rotation.y += 0.003;
mesh.rotation.z += 0.000;
mesh.translation.z = 5.0;
// Create scale, rotation, and translation matrices that will be used to multiply the mesh vertices
mat4_t scale_matrix = mat4_make_scale(mesh.scale.x, mesh.scale.y, mesh.scale.z);
mat4_t translation_matrix = mat4_make_translation(mesh.translation.x, mesh.translation.y, mesh.translation.z);
mat4_t rotation_matrix_x = mat4_make_rotation_x(mesh.rotation.x);
mat4_t rotation_matrix_y = mat4_make_rotation_y(mesh.rotation.y);
mat4_t rotation_matrix_z = mat4_make_rotation_z(mesh.rotation.z);
// Loop all triangle faces of our mesh
int num_faces = array_length(mesh.faces);
for (int i = 0; i < num_faces; i++) {
face_t mesh_face = mesh.faces[i];
vec3_t face_vertices[3];
face_vertices[0] = mesh.vertices[mesh_face.a];
face_vertices[1] = mesh.vertices[mesh_face.b];
face_vertices[2] = mesh.vertices[mesh_face.c];
vec4_t transformed_vertices[3];
// Loop all three vertices of this current face and apply transformations
for (int j = 0; j < 3; j++) {
vec4_t transformed_vertex = vec4_from_vec3(face_vertices[j]);
// Create a World Matrix combining scale, rotation, and translation matrices
mat4_t world_matrix = mat4_identity();
// Order matters: First scale, then rotate, then translate. [T]*[R]*[S]*v
world_matrix = mat4_mul_mat4(scale_matrix, world_matrix);
world_matrix = mat4_mul_mat4(rotation_matrix_z, world_matrix);
world_matrix = mat4_mul_mat4(rotation_matrix_y, world_matrix);
world_matrix = mat4_mul_mat4(rotation_matrix_x, world_matrix);
world_matrix = mat4_mul_mat4(translation_matrix, world_matrix);
// Multiply the world matrix by the original vector
transformed_vertex = mat4_mul_vec4(world_matrix, transformed_vertex);
// Save transformed vertex in the array of transformed vertices
transformed_vertices[j] = transformed_vertex;
}
// Get individual vectors from A, B, and C vertices to compute normal
vec3_t vector_a = vec3_from_vec4(transformed_vertices[0]); /* A */
vec3_t vector_b = vec3_from_vec4(transformed_vertices[1]); /* / \ */
vec3_t vector_c = vec3_from_vec4(transformed_vertices[2]); /* C---B */
// Get the vector subtraction of B-A and C-A
vec3_t vector_ab = vec3_sub(vector_b, vector_a);
vec3_t vector_ac = vec3_sub(vector_c, vector_a);
vec3_normalize(&vector_ab);
vec3_normalize(&vector_ac);
// Compute the face normal (using cross product to find perpendicular)
vec3_t normal = vec3_cross(vector_ab, vector_ac);
vec3_normalize(&normal);
// Find the vector between vertex A in the triangle and the camera origin
vec3_t camera_ray = vec3_sub(camera_position, vector_a);
// Calculate how aligned the camera ray is with the face normal (using dot product)
float dot_normal_camera = vec3_dot(normal, camera_ray);
// Backface culling test to see if the current face should be projected
if (cull_method == CULL_BACKFACE) {
// Backface culling, bypassing triangles that are looking away from the camera
if (dot_normal_camera < 0) {
continue;
}
}
vec4_t projected_points[3];
// Loop all three vertices to perform projection
for (int j = 0; j < 3; j++) {
// Project the current vertex
projected_points[j] = mat4_mul_vec4_project(proj_matrix, transformed_vertices[j]);
// Flip vertically since the y values of the 3D mesh grow bottom->up and in screen space y values grow top->down
projected_points[j].y *= -1;
// Scale into the view
projected_points[j].x *= (window_width / 2.0);
projected_points[j].y *= (window_height / 2.0);
// Translate the projected points to the middle of the screen
projected_points[j].x += (window_width / 2.0);
projected_points[j].y += (window_height / 2.0);
}
// Calculate the average depth for each face based on the vertices after transformation
float avg_depth = (transformed_vertices[0].z + transformed_vertices[1].z + transformed_vertices[2].z) / 3.0;
// Calculate the shade intensity based on how aliged is the normal with the flipped light direction ray
float light_intensity_factor = -vec3_dot(normal, light.direction);
// Calculate the triangle color based on the light angle
uint32_t triangle_color = light_apply_intensity(mesh_face.color, light_intensity_factor);
triangle_t projected_triangle = {
.points = {
{ projected_points[0].x, projected_points[0].y, projected_points[0].z, projected_points[0].w },
{ projected_points[1].x, projected_points[1].y, projected_points[1].z, projected_points[1].w },
{ projected_points[2].x, projected_points[2].y, projected_points[2].z, projected_points[2].w },
},
.texcoords = {
{ mesh_face.a_uv.u, mesh_face.a_uv.v },
{ mesh_face.b_uv.u, mesh_face.b_uv.v },
{ mesh_face.c_uv.u, mesh_face.c_uv.v }
},
.color = triangle_color,
.avg_depth = avg_depth
};
// Save the projected triangle in the array of triangles to render
array_push(triangles_to_render, projected_triangle);
}
// Sort the triangles to render by their avg_depth
int num_triangles = array_length(triangles_to_render);
for (int i = 0; i < num_triangles; i++) {
for (int j = i; j < num_triangles; j++) {
if (triangles_to_render[i].avg_depth < triangles_to_render[j].avg_depth) {
// Swap the triangles positions in the array
triangle_t temp = triangles_to_render[i];
triangles_to_render[i] = triangles_to_render[j];
triangles_to_render[j] = temp;
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
// Render function to draw objects on the display
///////////////////////////////////////////////////////////////////////////////
void render(void) {
SDL_RenderClear(renderer);
draw_grid();
// Loop all projected triangles and render them
int num_triangles = array_length(triangles_to_render);
for (int i = 0; i < num_triangles; i++) {
triangle_t triangle = triangles_to_render[i];
// Draw filled triangle
if (render_method == RENDER_FILL_TRIANGLE || render_method == RENDER_FILL_TRIANGLE_WIRE) {
draw_filled_triangle(
triangle.points[0].x, triangle.points[0].y, // vertex A
triangle.points[1].x, triangle.points[1].y, // vertex B
triangle.points[2].x, triangle.points[2].y, // vertex C
triangle.color
);
}
// Draw textured triangle
if (render_method == RENDER_TEXTURED || render_method == RENDER_TEXTURED_WIRE) {
draw_textured_triangle(
triangle.points[0].x, triangle.points[0].y, triangle.points[0].z, triangle.points[0].w, triangle.texcoords[0].u, triangle.texcoords[0].v, // vertex A
triangle.points[1].x, triangle.points[1].y, triangle.points[1].z, triangle.points[1].w, triangle.texcoords[1].u, triangle.texcoords[1].v, // vertex B
triangle.points[2].x, triangle.points[2].y, triangle.points[2].z, triangle.points[2].w, triangle.texcoords[2].u, triangle.texcoords[2].v, // vertex C
mesh_texture
);
}
// Draw triangle wireframe
if (render_method == RENDER_WIRE || render_method == RENDER_WIRE_VERTEX || render_method == RENDER_FILL_TRIANGLE_WIRE || render_method == RENDER_TEXTURED_WIRE) {
draw_triangle(
triangle.points[0].x, triangle.points[0].y, // vertex A
triangle.points[1].x, triangle.points[1].y, // vertex B
triangle.points[2].x, triangle.points[2].y, // vertex C
0xFFFFFFFF
);
}
// Draw triangle vertex points
if (render_method == RENDER_WIRE_VERTEX) {
draw_rect(triangle.points[0].x - 3, triangle.points[0].y - 3, 6, 6, 0xFFFF0000); // vertex A
draw_rect(triangle.points[1].x - 3, triangle.points[1].y - 3, 6, 6, 0xFFFF0000); // vertex B
draw_rect(triangle.points[2].x - 3, triangle.points[2].y - 3, 6, 6, 0xFFFF0000); // vertex C
}
}
// Clear the array of triangles to render every frame loop
array_free(triangles_to_render);
render_color_buffer();
clear_color_buffer(0xFF000000);
SDL_RenderPresent(renderer);
}
///////////////////////////////////////////////////////////////////////////////
// Free the memory that was dynamically allocated by the program
///////////////////////////////////////////////////////////////////////////////
void free_resources(void) {
free(color_buffer);
array_free(mesh.faces);
array_free(mesh.vertices);
upng_free(png_texture);
}
///////////////////////////////////////////////////////////////////////////////
// Main function
///////////////////////////////////////////////////////////////////////////////
int main(int argv, char* args[]) {
is_running = initialize_window();
setup();
while (is_running) {
process_input();
update();
render();
}
destroy_window();
free_resources();
return 0;
}