blob: ab118d96ce7399b4a25a328ee9eac8c87ae9be81 [file] [log] [blame] [edit]
#include <algorithm>
#include "renderer.hpp"
using namespace std;
using namespace blit;
int32_t edge_function(const Point& p1, const Point& p2, const Point& p3) {
return ((p2.x - p1.x) * (p3.y - p1.y)) - ((p2.y - p1.y) * (p3.x - p1.x));
}
bool is_top_left(const Point& p1, const Point& p2) {
return (p1.y == p2.y && p1.x > p2.x) || (p1.y < p2.y);
}
int32_t modulo(int32_t x, int32_t n) {
return (x % n + n) % n;
}
uint32_t pixels_drawn = 0;
void draw_face(Vec3* vertices, Vec3* normals, Vec2* texture_coordinates, Surface* texture, Vec3 light, Pen *color, int16_t* zbuffer, int16_t near, int16_t far) {
// convert vertices into Q8 integer points
Point p0(vertices[0].x, vertices[0].y);
Point p1(vertices[1].x, vertices[1].y);
Point p2(vertices[2].x, vertices[2].y);
// back-face cull if winding order is reversed
int32_t area = edge_function(p0, p1, p2);
if (area <= 0) { return; }
// calculate bounds clipped to screen
int32_t minx = max(screen.clip.x, min(p0.x, min(p1.x, p2.x)));
int32_t miny = max(screen.clip.y, min(p0.y, min(p1.y, p2.y)));
int32_t maxx = min(screen.clip.x + screen.clip.w - 1, max(p0.x, max(p1.x, p2.x)));
int32_t maxy = min(screen.clip.y + screen.clip.h - 1, max(p0.y, max(p1.y, p2.y)));
// nothing to draw
if (minx == maxx && miny == maxy) { return; }
// bias ensures no overdraw between neighbouring triangles along the shared edge
// now it's fixed point can this just always be applied as -128?
int32_t bias0 = is_top_left(p1, p2) ? 0 : -1;
int32_t bias1 = is_top_left(p2, p0) ? 0 : -1;
int32_t bias2 = is_top_left(p0, p1) ? 0 : -1;
Point p(minx, miny);
int32_t a01 = p0.y - p1.y, b01 = p1.x - p0.x;
int32_t a12 = p1.y - p2.y, b12 = p2.x - p1.x;
int32_t a20 = p2.y - p0.y, b20 = p0.x - p2.x;
int32_t w0row = edge_function(p1, p2, p) - bias0;
int32_t w1row = edge_function(p2, p0, p) - bias1;
int32_t w2row = edge_function(p0, p1, p) - bias2;
float invarea = 1.0f / area;
float t1x, t1y, t2x, t2y, t3x, t3y;
if (texture) {
t1x = texture_coordinates[0].x * texture->bounds.w;
t1y = texture_coordinates[0].y * texture->bounds.h;
t2x = texture_coordinates[1].x * texture->bounds.w;
t2y = texture_coordinates[1].y * texture->bounds.h;
t3x = texture_coordinates[2].x * texture->bounds.w;
t3y = texture_coordinates[2].y * texture->bounds.h;
}
for (p.y = miny; p.y < maxy; p.y++) {
uint32_t pixel_offset = screen.offset(minx, p.y);
int32_t w0 = w0row;
int32_t w1 = w1row;
int32_t w2 = w2row;
for (p.x = minx; p.x < maxx; p.x++) {
// check if sign bit is set in any of our barycentric coordinates
// if so we're outside of the triangle so skip the pixel
if ((w0 | w1 | w2) >= 0) {
// calculate barycentric coordinates of current pixel
float alpha = w0 * invarea;
float beta = w1 * invarea;
float gamma = 1.0f - alpha - beta; // ensures sum == 1.0f
int16_t z = (vertices[0].z * alpha + vertices[1].z * beta + vertices[2].z * gamma) * 16384;
if (z > zbuffer[p.x + (p.y * screen.bounds.w)] && z > -1) {
zbuffer[p.x + (p.y * screen.bounds.w)] = z;
pixels_drawn++;
if (texture) {
Point interpolated_uv(
t1x * alpha + t2x * beta + t3x * gamma,
t1y * alpha + t2y * beta + t3y * gamma
);
interpolated_uv.x = modulo(interpolated_uv.x, texture->bounds.w);
interpolated_uv.y = texture->bounds.h - modulo(interpolated_uv.y, texture->bounds.h);
screen.pen = texture->palette[texture->data[interpolated_uv.x + interpolated_uv.y * texture->bounds.w]];
} else {
screen.pen = *color;
}
Vec3 interpolated_n = normals[0] * alpha + normals[1] * beta + normals[2] * gamma;
float diffuse = interpolated_n.dot(light);
diffuse = std::max(diffuse, 0.2f);
screen.pen.r *= diffuse;
screen.pen.g *= diffuse;
screen.pen.b *= diffuse;
screen.pbf(&screen.pen, &screen, pixel_offset, 1);
}
}
w0 += a12;
w1 += a20;
w2 += a01;
pixel_offset++;
}
w0row += b12;
w1row += b20;
w2row += b01;
}
}