3d-renderer/scripts/triangle.c

279 lines
9.0 KiB
C

#include "triangle.h"
#include <stdio.h>
#include <math.h>
#include "display.h"
// TODO: Create implementation for triangle.h functions
int compare_vec2_t(const void* a, const void* b) {
const int_vec2_t* arg1 = (const int_vec2_t*)a;
const int_vec2_t* arg2 = (const int_vec2_t*)b;
if (arg1->y < arg2->y) return -1;
if (arg1->y > arg2->y) return 1;
return 0;
}
#define FUNC (int)ceilf
#define FUNC2 (int)floorf
uint32_t invert_rgb(uint32_t color) {
uint32_t a = color & 0xFF000000; // Alpha channel
uint32_t rgb = color & 0x00FFFFFF; // RGB channels
rgb = ~rgb & 0x00FFFFFF; // Invert RGB only
return a | rgb;
}
void draw_triangle(triangle_t triangle, uint32_t color) {
draw_line(triangle.points[0].x, triangle.points[0].y, triangle.points[1].x, triangle.points[1].y, color);
draw_line(triangle.points[1].x, triangle.points[1].y, triangle.points[2].x, triangle.points[2].y, color);
draw_line(triangle.points[2].x, triangle.points[2].y, triangle.points[0].x, triangle.points[0].y, color);
}
///////////////////////////////////////////////////////////////////////////////
// Draw a filled a triangle with a flat bottom
///////////////////////////////////////////////////////////////////////////////
//
// (x0,y0)
// / \
// / \
// / \
// / \
// / \
// (x1,y1)------(x2,y2)
//
///////////////////////////////////////////////////////////////////////////////
void fill_flat_bottom_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) {
// Find the two slopes (two triangle legs)
float inv_slope_1 = (float)(x1 - x0) / (y1 - y0);
float inv_slope_2 = (float)(x2 - x0) / (y2 - y0);
// Start x_start and x_end from the top vertex (x0,y0)
float x_start = x0;
float x_end = x0;
// Loop all the scanlines from top to bottom
for (int y = y0; y <= y2; y++) {
draw_line(x_start, y, x_end, y, color);
x_start += inv_slope_1;
x_end += inv_slope_2;
}
}
///////////////////////////////////////////////////////////////////////////////
// Draw a filled a triangle with a flat top
///////////////////////////////////////////////////////////////////////////////
//
// (x0,y0)------(x1,y1)
// \ /
// \ /
// \ /
// \ /
// \ /
// (x2,y2)
//
///////////////////////////////////////////////////////////////////////////////
void fill_flat_top_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) {
// Find the two slopes (two triangle legs)
float inv_slope_1 = (float)(x2 - x0) / (y2 - y0);
float inv_slope_2 = (float)(x2 - x1) / (y2 - y1);
// Start x_start and x_end from the bottom vertex (x2,y2)
float x_start = x2;
float x_end = x2;
// Loop all the scanlines from bottom to top
for (int y = y2; y >= y0; y--) {
draw_line(x_start, y, x_end, y, color);
x_start -= inv_slope_1;
x_end -= inv_slope_2;
}
}
///////////////////////////////////////////////////////////////////////////////
// Draw a filled triangle with the flat-top/flat-bottom method
// We split the original triangle in two, half flat-bottom and half flat-top
///////////////////////////////////////////////////////////////////////////////
//
// (x0,y0)
// / \
// / \
// / \
// / \
// / \
// (x1,y1)------(Mx,My)
// \_ \
// \_ \
// \_ \
// \_ \
// \ \
// \_ \
// \_\
// \
// (x2,y2)
//
///////////////////////////////////////////////////////////////////////////////
void draw_filled_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) {
// We need to sort the vertices by y-coordinate ascending (y0 < y1 < y2)
if (y0 > y1) {
int_swap(&y0, &y1);
int_swap(&x0, &x1);
}
if (y1 > y2) {
int_swap(&y1, &y2);
int_swap(&x1, &x2);
}
if (y0 > y1) {
int_swap(&y0, &y1);
int_swap(&x0, &x1);
}
if (y1 == y2) {
// Draw flat-bottom triangle
fill_flat_bottom_triangle(x0, y0, x1, y1, x2, y2, color);
} else if (y0 == y1) {
// Draw flat-top triangle
fill_flat_top_triangle(x0, y0, x1, y1, x2, y2, color);
} else {
// Calculate the new vertex (Mx,My) using triangle similarity
int My = y1;
int Mx = (((x2 - x0) * (y1 - y0)) / (y2 - y0)) + x0;
// Draw flat-bottom triangle
fill_flat_bottom_triangle(x0, y0, x1, y1, Mx, My, color);
// Draw flat-top triangle
fill_flat_top_triangle(x1, y1, Mx, My, x2, y2, color);
}
}
vec3_t barycentric_weights(vec2_t a, vec2_t b, vec2_t c, vec2_t p) {
// Find the vectors between the vertices ABC and point p
vec2_t ac = vec2_subtract(c, a);
vec2_t ab = vec2_subtract(b, a);
vec2_t ap = vec2_subtract(p, a);
vec2_t pc = vec2_subtract(c, p);
vec2_t pb = vec2_subtract(b, p);
// Compute the area of the full parallegram/triangle ABC using 2D cross product
float area_parallelogram_abc = (ac.x * ab.y - ac.y * ab.x); // || AC x AB ||
// Alpha is the area of the small parallelogram/triangle PBC divided by the area of the full parallelogram/triangle ABC
float alpha = (pc.x * pb.y - pc.y * pb.x) / area_parallelogram_abc;
// Beta is the area of the small parallelogram/triangle APC divided by the area of the full parallelogram/triangle ABC
float beta = (ac.x * ap.y - ac.y * ap.x) / area_parallelogram_abc;
// Weight gamma is easily found since barycentric coordinates always add up to 1.0
float gamma = 1 - alpha - beta;
vec3_t weights = { alpha, beta, gamma };
return weights;
}
////////////////////////////
// Function to draw textue at x/y using interpolation
void draw_texel(
int x, int y, uint32_t* texture,
vec2_t point_a, vec2_t point_b, vec2_t point_c,
float u0, float v0, float u1, float v1, float u2, float v2) {
vec2_t point_p = { x, y };
vec3_t weights = barycentric_weights(point_a, point_b, point_c, point_p);
float alpha = weights.x;
float beta = weights.y;
float gamma = weights.z;
// Perform the interpolation of all U and V values using barycentric weights
float interpolated_u = (u0) * alpha + (u1) * beta + (u2) * gamma;
float interpolated_v = (v0) * alpha + (v1) * beta + (v2) * gamma;
// Map the UV coordinate to the full texture width and height
int tex_x = abs((int)(interpolated_u * texture_width));
int tex_y = abs((int)(interpolated_v * texture_height));
draw_pixel(x, y, texture[(texture_width * tex_y) + tex_x]);
}
void draw_textured_triangle(
int x0, int y0, float u0, float v0,
int x1, int y1, float u1, float v1,
int x2, int y2, float u2, float v2,
uint32_t* texture) {
// sort by y
int temp = 0, min_idx = 0, mid_idx = 1, max_idx = 2;
// We need to sort the vertices by y-coordinate ascending (y0 < y1 < y2)
if (y0 > y1) {
int_swap(&y0, &y1);
int_swap(&x0, &x1);
}
if (y1 > y2) {
int_swap(&y1, &y2);
int_swap(&x1, &x2);
}
if (y0 > y1) {
int_swap(&y0, &y1);
int_swap(&x0, &x1);
}
// create vector points after sorting vertices
vec2_t point_a = { x0, y0 };
vec2_t point_b = { x1, y1 };
vec2_t point_c = { x2, y2 };
// FLAT BOTTOM
float inv_slope_1 = 0;
float inv_slope_2 = 0;
if (y1 - y0 != 0) inv_slope_1 = (float)(x1 - x0) / abs(y1 - y0);
if (y2 - y0 != 0) inv_slope_2 = (float)(x2 - x0) / abs(y2 - y0);
if (y1 - y0 != 0) {
for (int y = y0; y <= y1; y++) {
int x_start = x1 + (y - y1) * inv_slope_1;
int x_end = x0 + (y - y0) * inv_slope_2;
if (x_end < x_start) {
int_swap(&x_start, &x_end);
}
for (int x = x_start; x < x_end; x++) {
// draw pixel with color from texture
//draw_pixel(x, y, (x % 2 == 0 && y % 2 == 0) ? 0xFF00FF00 : 0xFF000000);
draw_texel(x, y, texture, point_a, point_b, point_c, u0, v0, u1, v1, u2, v2);
}
}
}
// FLAT TOP
inv_slope_1 = 0;
inv_slope_2 = 0;
if (y2 - y1 != 0) inv_slope_1 = (float)(x2 - x1) / abs(y2 - y1);
if (y2 - y0 != 0) inv_slope_2 = (float)(x2 - x0) / abs(y2 - y0);
if (y2 - y1 != 0) {
for (int y = y1; y <= y2; y++) {
int x_start = x1 + (y - y1) * inv_slope_1;
int x_end = x0 + (y - y0) * inv_slope_2;
if (x_end < x_start) {
int_swap(&x_start, &x_end);
}
for (int x = x_start; x < x_end; x++) {
// draw pixel with color from texture
//draw_pixel(x, y, (x % 2 == 0 && y % 2 == 0) ? 0xFF00FF00 : 0xFF000000);
draw_texel(x, y, texture, point_a, point_b, point_c, u0, v0, u1, v1, u2, v2);
}
}
}
}