diff --git a/Common/fix.h b/Common/fix.h index 591cf1d..1f4ba9e 100644 --- a/Common/fix.h +++ b/Common/fix.h @@ -139,4 +139,16 @@ static inline com_fix_t com_fix_tan(com_fix_t a) { return com_fix_div(s, c); } +/* Branchless min. + * https://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax */ +static inline com_fix_t com_fix_min(com_fix_t x, com_fix_t y) { + return y ^ ((x ^ y) & -(x < y)); +} + +/* Branchless max. + * https://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax */ +static inline com_fix_t com_fix_max(com_fix_t x, com_fix_t y) { + return x ^ ((x ^ y) & -(x < y)); +} + #endif diff --git a/Common/gem.h b/Common/gem.h index fd5fcf9..22e5145 100644 --- a/Common/gem.h +++ b/Common/gem.h @@ -6,6 +6,8 @@ #define COM_GEM_H #include "mat.h" +#include "pnt.h" +#include "vec.h" #include /* Projects vertex position to a clip space via reordered row-major MVP matrix. @@ -37,6 +39,8 @@ static inline com_vec_t com_gem_vec_project_clip(com_mat_t a, com_vec_t b) { /* TODO: If we clip test bounding volume of a model first we can skip * all clipping whatsoever, which might hold true more often, than the cost of * testing the volume. */ +/* TODO: It should be possible to have results in pixel position, not -1 to 1. + */ #define COM_GEM_VERTEX_UNCLIPPED (0 << 0) #define COM_GEM_VERTEX_CLIPPED_X (1 << 0) @@ -44,15 +48,53 @@ static inline com_vec_t com_gem_vec_project_clip(com_mat_t a, com_vec_t b) { /* Attempt to project clip space vertex to NDC, reporting which component lies * outside of view. Bit test against those per component. This is needed for * determining new view-lying clipped triangles. */ -static inline uint8_t com_gem_clip_vis_project(com_vec_t a, com_vec_t *out) { +static inline uint8_t com_gem_clip_vis_project(com_vec_t a, com_pnt_t *out) { uint8_t mask; if (a.s.x < -a.s.z || a.s.x > a.s.z) mask ^= COM_GEM_VERTEX_CLIPPED_X; if (a.s.y < -a.s.z || a.s.y > a.s.z) mask ^= COM_GEM_VERTEX_CLIPPED_Y; - out->a[0] = com_fix_div(a.a[0], a.a[2]); - out->a[1] = com_fix_div(a.a[1], a.a[2]); + out->a[0] = com_fix_div(a.a[0], a.a[2]) / 480 + 240; + out->a[1] = com_fix_div(a.a[1], a.a[2]) / 640 + 320; return mask; } +static inline void com_gem_draw_triangle(com_pnt_t v0, com_pnt_t v1, + com_pnt_t v2, uint8_t *buffer) { + // 1. Compute the bounding box of the triangle + int minX = com_vec_min(com_vec_from(v0.s.x, v1.s.x, v2.s.x)); + int minY = com_vec_min(com_vec_from(v0.s.y, v1.s.y, v2.s.y)); + int maxX = com_vec_max(com_vec_from(v0.s.x, v1.s.x, v2.s.x)); + int maxY = com_vec_max(com_vec_from(v0.s.y, v1.s.y, v2.s.y)); + + // 2. Clip the bounding box against screen dimensions to avoid out-of-bounds + // errors + // minX = CLAMP(minX, 0, 480 - 1); + // minY = CLAMP(minY, 0, HEIGHT - 1); + // maxX = CLAMP(maxX, 0, 480 - 1); + // maxY = CLAMP(maxY, 0, HEIGHT - 1); + + // 3. Loop over all pixels inside the bounding box + for (int y = minY; y <= maxY; y++) { + for (int x = minX; x <= maxX; x++) { + com_pnt_t p = {.s = {x, y}}; + + // Test the pixel center against all 3 edges + int w0 = com_pnt_edge_orient(v1, v2, p); + int w1 = com_pnt_edge_orient(v2, v0, p); + int w2 = com_pnt_edge_orient(v0, v1, p); + + // If the point is inside or on all edges, draw the pixel + // (Assumes Clockwise vertex ordering) + if (w0 >= 0 && w1 >= 0 && w2 >= 0) { + printf("%i, %i\n", y, x); + buffer[(y * 640 + x) * 4 + 0] = '#'; + buffer[(y * 640 + x) * 4 + 1] = '#'; + buffer[(y * 640 + x) * 4 + 2] = '#'; + buffer[(y * 640 + x) * 4 + 3] = '#'; + } + } + } +} + #endif diff --git a/Common/pnt.h b/Common/pnt.h new file mode 100644 index 0000000..1edcc64 --- /dev/null +++ b/Common/pnt.h @@ -0,0 +1,86 @@ +/* + Fixed point implementation of 2 dimensional pnttors. +*/ + +#ifndef COM_PNT_H +#define COM_PNT_H + +#include "def.h" +#include "fix.h" +#include "vec.h" +#include + +typedef union { + com_def_alignedas(16) com_def_vector(com_fix_t, a, 2); + com_def_alignedas(16) struct { + com_fix_t x; + com_fix_t y; + } s; +} com_pnt_t; + +static inline com_pnt_t com_pnt_from(com_fix_t x, com_fix_t y) { + return (com_pnt_t){.s = {.x = x, .y = y}}; +} + +static inline com_pnt_t com_pnt_from_vec(com_vec_t a) { + return (com_pnt_t){.s = {.x = a.a[0], .y = a.a[1]}}; +} + +static inline com_pnt_t com_pnt_identity(void) { + return (com_pnt_t){.s = { + .x = COM_FIX_FRACUNIT, + .y = COM_FIX_FRACUNIT, + }}; +} + +static inline com_pnt_t com_pnt_add(com_pnt_t a, com_pnt_t b) { + return (com_pnt_t){ + .s = {.x = com_fix_add(a.s.x, b.s.x), .y = com_fix_add(a.s.y, b.s.y)}}; +} + +static inline com_pnt_t com_pnt_sub(com_pnt_t a, com_pnt_t b) { + return (com_pnt_t){ + .s = {.x = com_fix_sub(a.s.x, b.s.x), .y = com_fix_sub(a.s.y, b.s.y)}}; +} + +static inline com_pnt_t com_pnt_mul(com_pnt_t a, com_pnt_t b) { + return (com_pnt_t){ + .s = {.x = com_fix_mul(a.s.x, b.s.x), .y = com_fix_mul(a.s.y, b.s.y)}}; +} + +/* Note: this does not clamp for over/underflow cases */ +static inline com_pnt_t com_pnt_div(com_pnt_t a, com_pnt_t b) { + return (com_pnt_t){ + .s = {.x = com_fix_div(a.s.x, b.s.x), .y = com_fix_div(a.s.y, b.s.y)}}; +} + +/* Scale pnttor by a fixed point number */ +static inline com_pnt_t com_pnt_scl(com_pnt_t a, com_fix_t b) { + return (com_pnt_t){ + .s = {.x = com_fix_mul(a.s.x, b), .y = com_fix_mul(a.s.y, b)}}; +} + +static inline com_fix_t com_pnt_min(com_pnt_t a) { + return com_fix_min(a.a[0], a.a[1]); +} + +static inline com_fix_t com_pnt_max(com_pnt_t a) { + return com_fix_max(a.a[0], a.a[1]); +} + +/* TODO: Safeguard by com_fix_mul()? */ +/* Tests whether a point is lying right to a line produced by l0 and l1. + This assumes that l0 and l1 are in clockwise order. + Result is positive or zero if it holds true, otherwise it's negative. */ +static inline com_fix_t com_pnt_edge_orient(com_pnt_t l0, com_pnt_t l1, + com_pnt_t p) { + return (l0.s.x - l1.s.x) * (p.s.y - l1.s.y) - + (l0.s.y - l1.s.y) * (p.s.x - l1.s.x); +} + +static inline void com_pnt_print(com_pnt_t a) { + com_fix_print(a.a[0]); + com_fix_print(a.a[1]); +} + +#endif diff --git a/Common/vec.h b/Common/vec.h index efd6f03..29dd846 100644 --- a/Common/vec.h +++ b/Common/vec.h @@ -20,6 +20,10 @@ typedef union { } s; } com_vec_t; +static inline com_vec_t com_vec_from(com_fix_t x, com_fix_t y, com_fix_t z) { + return (com_vec_t){.s = {.x = x, .y = y, .z = z}}; +} + static inline com_vec_t com_vec_identity(void) { return (com_vec_t){.s = {.x = COM_FIX_FRACUNIT, .y = COM_FIX_FRACUNIT, @@ -88,4 +92,18 @@ static inline com_vec_t com_vec_nrm(com_vec_t a) { .z = com_fix_div(a.s.z, n)}}; } +static inline com_fix_t com_vec_min(com_vec_t a) { + return com_fix_min(com_fix_min(a.a[0], a.a[1]), a.a[2]); +} + +static inline com_fix_t com_vec_max(com_vec_t a) { + return com_fix_max(com_fix_max(a.a[0], a.a[1]), a.a[2]); +} + +static inline void com_vec_print(com_vec_t a) { + com_fix_print(a.a[0]); + com_fix_print(a.a[1]); + com_fix_print(a.a[2]); +} + #endif diff --git a/Player/Display/x11.c b/Player/Display/x11.c index b8d31f1..0d4ff60 100644 --- a/Player/Display/x11.c +++ b/Player/Display/x11.c @@ -7,6 +7,7 @@ #include #include +#include "../../Common/gem.h" #include "../../Common/lzw.h" #include "../../Common/mat.h" #include "display.h" @@ -132,6 +133,36 @@ extern int plr_display_x11_main(int argc, char *argv[]) { pixel_buffer[pixel_index + 3] = 0; // Alpha/Padding } } + + com_mat_t proj = com_mat_perspective(640, 480, 1, 100 * COM_FIX_FRACUNIT, + COM_FIX_PIHALF); + com_mat_t view = com_mat_look_at(com_vec_from(0, 0, 0), + com_vec_from(0, COM_FIX_FRACUNIT, 0), + com_vec_from(-COM_FIX_FRACUNIT, 0, 0)); + com_mat_t mvp = com_mat_reoder(com_mat_mul(proj, view)); + + com_vec_t v0 = + com_vec_from(5 * COM_FIX_FRACUNIT, 0, 2 * COM_FIX_FRACUNIT); + com_vec_t v1 = com_vec_from(5 * COM_FIX_FRACUNIT, 1 * COM_FIX_FRACUNIT, + 4 * COM_FIX_FRACUNIT); + com_vec_t v2 = com_vec_from(5 * COM_FIX_FRACUNIT, 2 * COM_FIX_FRACUNIT, + 4 * COM_FIX_FRACUNIT); + + com_vec_t c0 = com_gem_vec_project_clip(mvp, v0); + com_vec_t c1 = com_gem_vec_project_clip(mvp, v1); + com_vec_t c2 = com_gem_vec_project_clip(mvp, v2); + + com_pnt_t p0, p1, p2; + uint8_t cc0 = com_gem_clip_vis_project(c0, &p0); + uint8_t cc1 = com_gem_clip_vis_project(c1, &p1); + uint8_t cc2 = com_gem_clip_vis_project(c2, &p2); + + com_pnt_print(p0); + com_pnt_print(p1); + com_pnt_print(p2); + + com_gem_draw_triangle(p0, p1, p2, (uint8_t *)pixel_buffer); + // Draw the complete image onto the window when exposed XPutImage(display, window, // Target drawable diff --git a/Player/Makefile b/Player/Makefile index 51ae9aa..03531a6 100644 --- a/Player/Makefile +++ b/Player/Makefile @@ -2,6 +2,7 @@ CC=clang # todo: Only link to libm on debug. CFLAGS=-lX11 -Wall -std=c99 -g3 -O3 -msse2 -lm DEPS = ../Common/lzw.h ../Common/mat.h ../Common/vec.h ../Common/fix.h ../Common/def.h ../Common/bits.h \ + ../Common/gem.h \ ../Common/Timer/timer.h \ ./Display/display.h OBJ = ../Common/lzw.o ../Common/fix.o ../Common/mat.o