350 lines
11 KiB
C
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;
|
|
}
|