diff --git a/Common/gem.h b/Common/gem.h index 22e5145..49e5963 100644 --- a/Common/gem.h +++ b/Common/gem.h @@ -5,6 +5,7 @@ #ifndef COM_GEM_H #define COM_GEM_H +#include "fix.h" #include "mat.h" #include "pnt.h" #include "vec.h" @@ -60,38 +61,73 @@ static inline uint8_t com_gem_clip_vis_project(com_vec_t a, com_pnt_t *out) { } 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 + com_pnt_t v2, uint8_t *out) { 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); + com_vec_t ws = com_pnt_edge_weight(v0, v1, v2, p); // If the point is inside or on all edges, draw the pixel // (Assumes Clockwise vertex ordering) - if (w0 >= 0 && w1 >= 0 && w2 >= 0) { + if (ws.a[0] >= 0 && ws.a[1] >= 0 && ws.a[2] >= 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] = '#'; + out[(y * 640 + x) * 4 + 0] = 125; + out[(y * 640 + x) * 4 + 1] = 125; + out[(y * 640 + x) * 4 + 2] = 125; + } + } + } +} + +static inline void com_gem_draw_triangle_textured(com_pnt_t v0, com_pnt_t v1, + com_pnt_t v2, uint8_t *out, + com_pnt_t uv0, com_pnt_t uv1, + com_pnt_t uv2, uint8_t *tex) { + 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)); + + com_fix_t const area = com_pnt_area(v0, v1, v2); + + for (int y = minY; y <= maxY; y++) { + for (int x = minX; x <= maxX; x++) { + com_pnt_t p = {.s = {x, y}}; + + /* TODO: Move to a separate weighting function over vec. */ + // Test the pixel center against all 3 edges + com_vec_t ws = com_pnt_edge_weight(v0, v1, v2, p); + + // If the point is inside or on all edges, draw the pixel + // (Assumes Clockwise vertex ordering) + if (ws.a[0] >= 0 && ws.a[1] >= 0 && ws.a[2] >= 0) { + // Barycentric weights + ws = com_vec_div(ws, com_vec_scalar(area)); + + // Affine interpolation of UVs. + com_fix_t const u = + (ws.a[0] * uv0.a[0] + ws.a[1] * uv1.a[0] + ws.a[2] * uv2.a[0]) >> + COM_FIX_FRACBITS; + com_fix_t const v = + (ws.a[0] * uv0.a[1] + ws.a[1] * uv1.a[1] + ws.a[2] * uv2.a[1]) >> + COM_FIX_FRACBITS; + + // Nearest-neighbor texture lookup, in [0..32) range. + int32_t tx = + (int32_t)((u + (COM_FIX_FRACUNIT / 2)) / (COM_FIX_FRACUNIT / 16)); + int32_t ty = + (int32_t)((v + (COM_FIX_FRACUNIT / 2)) / (COM_FIX_FRACUNIT / 16)); + + out[(y * 640 + x) * 4 + 0] = tex[(ty * 32 + tx) * 3 + 0]; + out[(y * 640 + x) * 4 + 1] = tex[(ty * 32 + tx) * 3 + 1]; + out[(y * 640 + x) * 4 + 2] = tex[(ty * 32 + tx) * 3 + 2]; } } } diff --git a/Common/pnt.h b/Common/pnt.h index 1edcc64..6cbb5ec 100644 --- a/Common/pnt.h +++ b/Common/pnt.h @@ -71,13 +71,23 @@ static inline com_fix_t com_pnt_max(com_pnt_t a) { /* 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. */ + Result is positive or zero if it holds true, otherwise it's negative. + This also produces area of a triangle! */ 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); } +#define com_pnt_area(m_p0, m_p1, m_p2) (com_pnt_edge_orient(m_p0, m_p1, m_p2)) + +static inline com_vec_t com_pnt_edge_weight(com_pnt_t v0, com_pnt_t v1, + com_pnt_t v2, com_pnt_t p) { + return (com_vec_t){.s = {.x = com_pnt_edge_orient(v1, v2, p), + .y = com_pnt_edge_orient(v2, v0, p), + .z = com_pnt_edge_orient(v0, v1, p)}}; +} + static inline void com_pnt_print(com_pnt_t a) { com_fix_print(a.a[0]); com_fix_print(a.a[1]); diff --git a/Common/vec.h b/Common/vec.h index 29dd846..6d0a038 100644 --- a/Common/vec.h +++ b/Common/vec.h @@ -24,6 +24,10 @@ 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_scalar(com_fix_t s) { + return (com_vec_t){.s = {.x = s, .y = s, .z = s}}; +} + static inline com_vec_t com_vec_identity(void) { return (com_vec_t){.s = {.x = COM_FIX_FRACUNIT, .y = COM_FIX_FRACUNIT, diff --git a/Player/Display/x11.c b/Player/Display/x11.c index 0d4ff60..f012a43 100644 --- a/Player/Display/x11.c +++ b/Player/Display/x11.c @@ -145,9 +145,13 @@ extern int plr_display_x11_main(int argc, char *argv[]) { 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, + com_vec_t v2 = com_vec_from(5 * COM_FIX_FRACUNIT, 4 * COM_FIX_FRACUNIT, 4 * COM_FIX_FRACUNIT); + com_pnt_t uv0 = com_pnt_from(0 * COM_FIX_FRACUNIT, 0 * COM_FIX_FRACUNIT); + com_pnt_t uv1 = com_pnt_from(1 * COM_FIX_FRACUNIT, 0 * COM_FIX_FRACUNIT); + com_pnt_t uv2 = com_pnt_from(1 * COM_FIX_FRACUNIT, 1 * 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); @@ -161,7 +165,18 @@ extern int plr_display_x11_main(int argc, char *argv[]) { com_pnt_print(p1); com_pnt_print(p2); - com_gem_draw_triangle(p0, p1, p2, (uint8_t *)pixel_buffer); + uint8_t tex[32 * 32 * 3] = {0}; + for (int y = 0; y < 32; ++y) { + for (int x = 0; x < 32; ++x) { + tex[(y * 32 + x) * 3 + 0] = (x / 2 + y / 2) % 2 == 0 ? 175 : 0; + tex[(y * 32 + x) * 3 + 1] = (x / 2 + y / 2) % 2 == 0 ? 0 : 0; + tex[(y * 32 + x) * 3 + 2] = (x / 2 + y / 2) % 2 == 0 ? 175 : 0; + } + } + + com_gem_draw_triangle_textured(p0, p1, p2, (uint8_t *)pixel_buffer, uv0, + uv1, uv2, tex); + // com_gem_draw_triangle(p0, p1, p2, (uint8_t *)pixel_buffer); // Draw the complete image onto the window when exposed XPutImage(display, diff --git a/Player/docs.txt b/Player/docs.txt index 1e8e9eb..d11f84d 100644 --- a/Player/docs.txt +++ b/Player/docs.txt @@ -1,14 +1,13 @@ -Pustina -- Player -Locked step multiplayer oriented engine with great restrictions. +Brightstone -- Player +Locked step multiplayer retro oriented engine with great restrictions. We do not use abstractions, as the simplicity of the required set of features should allow for platform-specific implementations. ---- Restrictions ---- -* Fixed sized paletted frambuffer, defaulted to 640x480. Scaling by 2 is possible. +* Fixed sized paletted frambuffer, defaulted to 640x480. * Deterministic logic via Q15.16 fixed point numbers. * .gam image format, derived from .gif. RLE and Delta Frame Optimiztion is added, palette is predefined. -* Images are 16x16 or multiples of it, aligning to the grid. -* Tile view is capped at 29x29, the rest is allocated to interface. +* Textures are 32x32 pixels. * 30 FPS display rate. * Fonts are in ASCII bitmaps. Tile slicing commands are issued to render them in. * Audio samples are in our own .sam format (s8 frame delta + lzw).