#include "draw.h"
#include "globals.h"
#include "mesh.h"
#include "texture.h"
#include "util_math.h"
static inline u32 vec3_to_u32RGBA(vec3f &c) {
u8 r = (u8)(c.x * 255.0f);
u8 g = (u8)(c.y * 255.0f);
u8 b = (u8)(c.z * 255.0f);
u8 a = 255;
u32 final_color = (r << 24) | (g << 16) | (b << 8) | a;
return final_color;
};
void clear_color_buffer(u32 color, Window *window) {
for (int i = 0; i < window->width * window->height; i++) {
window->back_buffer[i] = color;
};
};
// (0,0) bottom left, screen space origin
// Y- in NDC is X+ in screen space
// Z+ in NDC is Y+ in screen space
static inline void draw_pixel(int x, int y, u32 color, Window *window) {
if (x >= 0 && x < window->width && y >= 0 && y < window->height) {
int flipped_y = (window->height - 1) - y;
window->back_buffer[(window->width * flipped_y) + x] = color;
};
};
void draw_rect(int x, int y, int width, int height, u32 color, Window *window) {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int offset_x = x + j;
int offset_y = y - i;
draw_pixel(offset_x, offset_y, color, window);
};
};
};
void draw_line_grid(u32 color, Window *window, u8 spacing = 10) {
for (int y = 0; y < window->height; y++) {
for (int x = 0; x < window->width; x++) {
if (x % spacing == 0 || y % spacing == 0) {
draw_pixel(x, y, color, window);
};
};
};
};
// Bresenham's Line Algorithm
// https://wikipedia.org/wiki/Bresenham's_line_algorithm
// TODO: Wu's whenever I add pure primitives toggling
void draw_line(int x0, int y0, int x1, int y1, u32 color, Window *window) {
// differences
int delta_x = (x1 > x0) ? (x1 - x0) : (x0 - x1);
int delta_y = (y1 > y0) ? (y1 - y0) : (y0 - y1);
// direction
int step_x = (x0 < x1) ? 1 : -1;
int step_y = (y0 < y1) ? 1 : -1;
// error term, to adjust direction
int error_calib = delta_x - delta_y;
while (1) {
draw_pixel(x0, y0, color, window);
if (x0 == x1 && y0 == y1) {
break;
}
int error_double = 2 * error_calib;
if (error_double > -delta_y) {
error_calib -= delta_y;
x0 += step_x;
}
if (error_double < delta_x) {
error_calib += delta_x;
y0 += step_y;
};
};
};
static inline void process_fragment(int buff_idx, float pixel_depth, u32 pixel_color, Window *window) {
float current_depth_at_pixel = window->depth_buffer[buff_idx];
if (pixel_depth < current_depth_at_pixel) {
window->depth_buffer[buff_idx] = pixel_depth;
window->back_buffer[buff_idx] = pixel_color;
}
}
void rasterize_triangle(ScreenTriangle *tri, Window *window) {
vec3f v0 = tri->points[0];
vec3f v1 = tri->points[1];
vec3f v2 = tri->points[2];
float inv_w0 = tri->inv_w[0];
float inv_w1 = tri->inv_w[1];
float inv_w2 = tri->inv_w[2];
vec3f c0 = tri->color_over_w[0];
vec3f c1 = tri->color_over_w[1];
vec3f c2 = tri->color_over_w[2];
float ndc_depth0 = tri->ndc_depth[0];
float ndc_depth1 = tri->ndc_depth[1];
float ndc_depth2 = tri->ndc_depth[2];
vec2f uv_over_w0 = tri->uv_over_w[0];
vec2f uv_over_w1 = tri->uv_over_w[1];
vec2f uv_over_w2 = tri->uv_over_w[2];
Material mat = materials[tri->mat_idx];
// bounding box (expensive fmin, fmax here I think)
float minX_f = fmin(v0.x, fmin(v1.x, v2.x));
float minY_f = fmin(v0.y, fmin(v1.y, v2.y));
float maxX_f = fmax(v0.x, fmax(v1.x, v2.x));
float maxY_f = fmax(v0.y, fmax(v1.y, v2.y));
minX_f = fmax(0.0f, minX_f);
minY_f = fmax(0.0f, minY_f);
maxX_f = fmin(static_cast<float>(window->width - 1), maxX_f);
maxY_f = fmin(static_cast<float>(window->height - 1), maxY_f);
int minX = static_cast<int>(floor(minX_f));
int minY = static_cast<int>(floor(minY_f));
int maxX = static_cast<int>(ceil(maxX_f));
int maxY = static_cast<int>(ceil(maxY_f));
// edge coefficients (dY, -dX) pineda-style rasterization
float dX01 = v0.x - v1.x; // delta X (v0 to v1)
float dY01 = v0.y - v1.y; // delta Y (v0 to v1)
float dX12 = v1.x - v2.x; // delta X (v1 to v2)
float dY12 = v1.y - v2.y; // delta Y (v1 to v2)
float dX20 = v2.x - v0.x; // delta X (v2 to v0)
float dY20 = v2.y - v0.y; // delta Y (v2 to v0)
float triangleArea = (v1.y - v2.y) * (v0.x - v2.x) + (v2.x - v1.x) * (v0.y - v2.y);
if (fabs(triangleArea) < 1e-6f) { // degen triangle
return;
}
float invArea = 1.0f / triangleArea;
// center of the top-left pixel in the bounding box
vec2f pixel_start = {(float)minX + 0.5f, (float)minY + 0.5f};
// initial edge function (A0, B0, G0) for pixel_start
float E0 = (v1.y - v2.y) * (pixel_start.x - v2.x) + (v2.x - v1.x) * (pixel_start.y - v2.y);
float E1 = (v2.y - v0.y) * (pixel_start.x - v0.x) + (v0.x - v2.x) * (pixel_start.y - v0.y);
float E2 = (v0.y - v1.y) * (pixel_start.x - v1.x) + (v1.x - v0.x) * (pixel_start.y - v1.y);
// x delta step (move right one pixel)
float stepX0 = dY12;
float stepX1 = dY20;
float stepX2 = dY01;
// y delta step (move down one row)
float stepY0 = dX12;
float stepY1 = dX20;
float stepY2 = dX01;
float E0_row = E0;
float E1_row = E1;
float E2_row = E2;
for (int y = minY; y <= maxY; ++y) {
// reset
E0 = E0_row;
E1 = E1_row;
E2 = E2_row;
int flipped_y = (window->height - 1) - y; // draw_pixel origin convention
int row_start = flipped_y * window->width;
// inside tri
for (int x = minX; x <= maxX; ++x) {
if (E0 >= 0.0f && E1 >= 0.0f && E2 >= 0.0f) {
int buffer_idx = row_start + x;
// edge based barycentric coords eval
float alpha = E0 * invArea;
float beta = E1 * invArea;
float gamma = E2 * invArea;
// lerp the attribute accross the triangle's barycentric coords
float lerp_inv_w = alpha * inv_w0 + beta * inv_w1 + gamma * inv_w2;
float lerp_depth = alpha * ndc_depth0 + beta * ndc_depth1 + gamma * ndc_depth2;
vec2f lerp_uv = uv_over_w0 * alpha + uv_over_w1 * beta + uv_over_w2 * gamma;
// vec3f lerped_color = alpha * c0 + beta * c1 + gamma * c2;
// vec3f color = lerped_color / lerp_inv_w;
// u32 u32_color = vec3_to_u32RGBA(color);
vec2f uv = lerp_uv / lerp_inv_w;
u32 tex_rgba = sample_texture(mat.texture_img_idx, uv);
process_fragment(buffer_idx, lerp_depth, tex_rgba, window);
}
// increment edge functions
// advance right one pixel: x -> x + 1
E0 += stepX0;
E1 += stepX1;
E2 += stepX2;
}
// increment edge functions
// advance one row: y -> y + 1
// NOTE: Edge stepping for Y is subtraction since we start a triangle at top left (0, 0)
E0_row -= stepY0;
E1_row -= stepY1;
E2_row -= stepY2;
}
}