2025-07-14 18:24:46 -07:00

350 lines
11 KiB
C

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <math.h>
#include <SDL.h>
#include "display.h"
#include "mesh.h"
#include "vector.h"
#include "array.h"
#include "matrix.h"
#include "light.h"
#define STEP 0.075
uint32_t colors[12] = {
0xFF6A2EFF, // purple
0xFFE9C46A, // sand yellow
0xFF2A9D8F, // teal green
0xFFF4A261, // orange
0xFF264653, // dark blue-grey
0xFFB5838D, // mauve
0xFFD7263D, // red
0xFF38A3A5, // turquoise
0xFF757575, // grey
0xFF70C1B3, // light green-blue
0xFFFFC300, // yellow
0xFF5D2E8C // deep purple
};
triangle_t* triangles_to_render = NULL; //[N_MESH_FACES];
vec3_t camera_position = { 0, 0, 0 };
mat4_t proj_matrix;
light_t light = { .direction = { .x = 0, .y = 0, .z = 1 }};
bool is_running = false;
int cull_method = CULL_BACKFACE;
int render_method = RENDER_FILL_TRIANGLE;
bool sort_faces = true;
uint32_t previous_frame_time = 0;
void setup(void) {
// 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_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
window_width,
window_height
);
float fov = M_PI / 3;
float aspect = (float)window_height / (float)window_width;
float znear = 0.0f;
float zfar = 100.0f;
proj_matrix = mat4_make_perspective(fov, aspect, znear, zfar);
// load cube mesh data
load_file("..\\assets\\f22.obj");
//load_cube_mesh_data();
}
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;
//break;
if (event.key.keysym.sym == SDLK_UP)
mesh.rotation.z += STEP;
if (event.key.keysym.sym == SDLK_LEFT)
mesh.rotation.x += STEP;
if (event.key.keysym.sym == SDLK_RIGHT)
mesh.rotation.y += STEP;
break;
case SDL_KEYUP:
SDL_Keycode key = event.key.keysym.sym;
if (key == SDLK_c) {
cull_method = !cull_method;
}
if (key == SDLK_s) {
sort_faces = !sort_faces;
}
if (key == SDLK_1) {
render_method = RENDER_WIRE_VERTEX;
}
if (key == SDLK_2) {
render_method = RENDER_WIRE;
}
if (key == SDLK_3) {
render_method = RENDER_FILL_TRIANGLE;
}
if (key == SDLK_4) {
render_method = RENDER_FILL_TRIANGLE_LINE;
}
break;
}
}
////////////////////////////////////////////////////////////////////////////////
// Function that receives a 3D vector and returns a projected 2D point
////////////////////////////////////////////////////////////////////////////////
vec2_t project(vec3_t point) {
float fov_factor = 1; // placeholder
vec2_t projected_point = {
.x = roundf((fov_factor * point.x) / point.z),
.y = roundf((fov_factor * point.y) / point.z),
};
return projected_point;
}
void update(void) {
//while (!SDL_TICKS_PASSED(SDL_GetTicks(), previous_frame_time + FRAME_TARGET_TIME));
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();
//mesh.rotation.y += 0.01f;
mesh.rotation.x -= 0.01f;
//mesh.rotation.z += 0.01f;
//mesh.translation.x += 0.004f;
mesh.translation.z = 5.0f;
//mesh.scale.x += 0.002f;
//mesh.scale.y += 0.002f;
// create scale matrix that will be used
mat4_t scale_matrix = mat4_make_scale(mesh.scale.x, mesh.scale.y, mesh.scale.z);
// create translation matrix
mat4_t translation_matrix = mat4_make_translation(mesh.translation.x, mesh.translation.y, mesh.translation.z);
// create rotation matrices
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);
// create world matrix
mat4_t world_matrix = mat4_identity();
world_matrix = mat4_mul_mat4(&scale_matrix, &world_matrix);
world_matrix = mat4_mul_mat4(&rotation_matrix_x, &world_matrix);
world_matrix = mat4_mul_mat4(&rotation_matrix_y, &world_matrix);
world_matrix = mat4_mul_mat4(&rotation_matrix_z, &world_matrix);
world_matrix = mat4_mul_mat4(&translation_matrix, &world_matrix);
triangles_to_render = NULL;
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 - 1];
face_vertices[1] = mesh.vertices[mesh_face.b - 1];
face_vertices[2] = mesh.vertices[mesh_face.c - 1];
vec4_t transformed_vertices[3];
for (int j = 0; j < 3; j++) {
vec4_t transformed_vertex = vec4_from_vec3(face_vertices[j]);
// use matrix to scale original matrix
transformed_vertex = mat4_multiply_vec4(world_matrix, transformed_vertex);
// save translated vertex in the array of transformed vertices
transformed_vertices[j] = transformed_vertex;
}
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_subtract(vector_b, vector_a);
vec3_t vector_ac = vec3_subtract(vector_c, vector_a);
vec3_normalize(&vector_ab);
vec3_normalize(&vector_ac);
// compute the face normal using the cross product to find perpindicular
vec3_t normal = vec3_cross(vector_ab, vector_ac);
vec3_normalize(&normal);
if (cull_method == CULL_BACKFACE) {
// check backface culling
// find the vector between a point in the triangle and the camera origin
vec3_t camera_ray = vec3_subtract(camera_position, vector_a);
// calculate how aligned the camera ray is with the face normal
float dot_normal_camera = vec3_dot(normal, camera_ray);
if (dot_normal_camera < 0) {
continue;
}
}
vec4_t projected_points[3];
for (int j = 0; j < 3; j++) {
// project the current vertex
projected_points[j] = mat4_mul_vec4_project(proj_matrix, transformed_vertices[j]);
// 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);
//projected_triangle.points[j] = projected_point;
}
// calculate color using normal and light direction, then invert
float dot_color_p = -vec3_dot(normal, light.direction);
color_t flat_shaded_color = light_apply_intensity(0xFFFFFFFF, dot_color_p);
// calculate the average depth of the triangle
float avg_depth = (transformed_vertices[0].z + transformed_vertices[1].z + transformed_vertices[2].z) / 3.0f;
#define FUNC3 roundf
triangle_t projected_triangle = {
.points = {
{ FUNC3(projected_points[0].x), FUNC3(projected_points[0].y) },
{ FUNC3(projected_points[1].x), FUNC3(projected_points[1].y) },
{ FUNC3(projected_points[2].x), FUNC3(projected_points[2].y) },
},
.color = flat_shaded_color,
.average_depth = avg_depth,
};
//vec2_t midpoint = triangle_midpoint(projected_triangle);
//draw_rect(midpoint.x, midpoint.y, 10, 10, 0xFFFF0000);
// save the projected triangle in the array of triangles
array_push(triangles_to_render, projected_triangle);
triangle_t normal_triangle;
}
}
int depth_comp(const void* a, const void* b) {
triangle_t arg_a = *(const triangle_t*)a;
triangle_t arg_b = *(const triangle_t*)b;
if (arg_a.average_depth > arg_b.average_depth) return -1;
if (arg_a.average_depth < arg_b.average_depth) return 1;
return 0;
}
void render(void) {
draw_grid();
//uint32_t colors[] = { 0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFF757500, 0xFF007575, 0xFF750075 };
// Loop all projected points and render them
size_t arr_length = array_length(triangles_to_render);
// sort triangles
if (sort_faces) qsort(triangles_to_render, arr_length, sizeof(triangle_t), depth_comp);
for (int i = 0; i < arr_length; i++) {
triangle_t triangle = triangles_to_render[i];
switch (render_method) {
case RENDER_WIRE:
draw_triangle(triangle, 0x0000ff);
break;
case RENDER_WIRE_VERTEX:
draw_triangle(triangle, 0x0000ff);
draw_rect(triangle.points[0].x, triangle.points[0].y, 3, 3, 0xFFFFFF00);
draw_rect(triangle.points[1].x, triangle.points[1].y, 3, 3, 0xFFFFFF00);
draw_rect(triangle.points[2].x, triangle.points[2].y, 3, 3, 0xFFFFFF00);
break;
case RENDER_FILL_TRIANGLE:
draw_filled_triangle(triangle);
break;
case RENDER_FILL_TRIANGLE_LINE:
draw_filled_triangle(triangle);
draw_triangle(triangle, 0x0000ff);
break;
default:
break;
}
}
//vec2_t m = triangle_midpoint(t);
//draw_rect(m.x, m.y, 10, 10, 0xFFFF0000);
//draw_line(10, 10, 500, 500, 0x00FF00);
array_free(triangles_to_render);
render_color_buffer();
clear_color_buffer(0xFF000000);
SDL_RenderPresent(renderer);
}
void free_resources(void) {
free(color_buffer);
array_free(mesh.faces);
array_free(mesh.vertices);
}
int main(int argc, char *argv[]) {
is_running = initialize_window();
setup();
while (is_running) {
process_input();
update();
render();
}
destroy_window();
free_resources();
return 0;
}