3d-renderer/scripts/triangle.c
2025-07-31 15:00:45 -07:00

329 lines
11 KiB
C

#include "display.h"
#include "swap.h"
#include "triangle.h"
///////////////////////////////////////////////////////////////////////////////
// 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);
}
}
///////////////////////////////////////////////////////////////////////////////
// Draw a triangle using three raw line calls
///////////////////////////////////////////////////////////////////////////////
void draw_triangle(int x0, int y0, int x1, int y1, int x2, int y2, uint32_t color) {
draw_line(x0, y0, x1, y1, color);
draw_line(x1, y1, x2, y2, color);
draw_line(x2, y2, x0, y0, color);
}
///////////////////////////////////////////////////////////////////////////////
// Return the barycentric weights alpha, beta, and gamma for point p
///////////////////////////////////////////////////////////////////////////////
//
// (B)
// /|\
// / | \
// / | \
// / (P) \
// / / \ \
// / / \ \
// // \\
// (A)------------(C)
//
///////////////////////////////////////////////////////////////////////////////
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_sub(c, a);
vec2_t ab = vec2_sub(b, a);
vec2_t ap = vec2_sub(p, a);
vec2_t pc = vec2_sub(c, p);
vec2_t pb = vec2_sub(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 the textured pixel at position x and y using interpolation
///////////////////////////////////////////////////////////////////////////////
void draw_texel(
int x, int y, uint32_t* texture,
vec4_t point_a, vec4_t point_b, vec4_t point_c,
tex2_t a_uv, tex2_t b_uv, tex2_t c_uv
) {
vec2_t p = { x, y };
vec2_t a = vec2_from_vec4(point_a);
vec2_t b = vec2_from_vec4(point_b);
vec2_t c = vec2_from_vec4(point_c);
// Calculate the barycentric coordinates of our point 'p' inside the triangle
vec3_t weights = barycentric_weights(a, b, c, p);
float alpha = weights.x;
float beta = weights.y;
float gamma = weights.z;
// Variables to store the interpolated values of U, V, and also 1/w for the current pixel
float interpolated_u;
float interpolated_v;
float interpolated_reciprocal_w;
// Perform the interpolation of all U/w and V/w values using barycentric weights and a factor of 1/w
interpolated_u = (a_uv.u / point_a.w) * alpha + (b_uv.u / point_b.w) * beta + (c_uv.u / point_c.w) * gamma;
interpolated_v = (a_uv.v / point_a.w) * alpha + (b_uv.v / point_b.w) * beta + (c_uv.v / point_c.w) * gamma;
// Also interpolate the value of 1/w for the current pixel
interpolated_reciprocal_w = (1 / point_a.w) * alpha + (1 / point_b.w) * beta + (1 / point_c.w) * gamma;
// Now we can divide back both interpolated values by 1/w
interpolated_u /= interpolated_reciprocal_w;
interpolated_v /= interpolated_reciprocal_w;
// Map the UV coordinate to the full texture width and height
int tex_x = abs((int)(interpolated_u * texture_width)) % texture_width;
int tex_y = abs((int)(interpolated_v * texture_height)) % texture_height;
//tex_x = SDL_clamp(tex_x, 0, texture_width);
//tex_y = SDL_clamp(tex_y, 0, texture_height);
draw_pixel(x, y, texture[(texture_width * tex_y) + tex_x]);
}
///////////////////////////////////////////////////////////////////////////////
// Draw a textured triangle based on a texture array of colors.
// We split the original triangle in two, half flat-bottom and half flat-top.
///////////////////////////////////////////////////////////////////////////////
//
// v0
// /\
// / \
// / \
// / \
// v1--------\
// \_ \
// \_ \
// \_ \
// \_ \
// \\
// \
// v2
//
///////////////////////////////////////////////////////////////////////////////
void draw_textured_triangle(
int x0, int y0, float z0, float w0, float u0, float v0,
int x1, int y1, float z1, float w1, float u1, float v1,
int x2, int y2, float z2, float w2, float u2, float v2,
uint32_t* texture
) {
// We need to sort the vertices by y-coordinate ascending (y0 < y1 < y2)
if (y0 > y1) {
int_swap(&y0, &y1);
int_swap(&x0, &x1);
float_swap(&z0, &z1);
float_swap(&w0, &w1);
float_swap(&u0, &u1);
float_swap(&v0, &v1);
}
if (y1 > y2) {
int_swap(&y1, &y2);
int_swap(&x1, &x2);
float_swap(&z1, &z2);
float_swap(&w1, &w2);
float_swap(&u1, &u2);
float_swap(&v1, &v2);
}
if (y0 > y1) {
int_swap(&y0, &y1);
int_swap(&x0, &x1);
float_swap(&z0, &z1);
float_swap(&w0, &w1);
float_swap(&u0, &u1);
float_swap(&v0, &v1);
}
// flip v component to account for inverted uv coordinates (where v grows downwrds)
v0 = 1.0f - v0;
v1 = 1.0f - v1;
v2 = 1.0f - v2;
// Create vector points and texture coords after we sort the vertices
vec4_t point_a = { x0, y0, z0, w0 };
vec4_t point_b = { x1, y1, z1, w1 };
vec4_t point_c = { x2, y2, z2, w2 };
tex2_t a_uv = { u0, v0 };
tex2_t b_uv = { u1, v1 };
tex2_t c_uv = { u2, v2 };
///////////////////////////////////////////////////////
// Render the upper part of the triangle (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); // swap if x_start is to the right of x_end
}
for (int x = x_start; x < x_end; x++) {
// Draw our pixel with the color that comes from the texture
draw_texel(x, y, texture, point_a, point_b, point_c, a_uv, b_uv, c_uv);
}
}
}
///////////////////////////////////////////////////////
// Render the bottom part of the triangle (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); // swap if x_start is to the right of x_end
}
for (int x = x_start; x < x_end; x++) {
// Draw our pixel with the color that comes from the texture
draw_texel(x, y, texture, point_a, point_b, point_c, a_uv, b_uv, c_uv);
}
}
}
}