From 7ca48a5080dc4337d57be1b94cebb23099c55483 Mon Sep 17 00:00:00 2001 From: vekidev Date: Sat, 21 Feb 2026 16:28:09 +0100 Subject: [PATCH] near plane clipping partial --- include/globals.hpp | 4 +- include/math.hpp | 14 ++++++ include/renderer/calls.hpp | 2 +- include/renderer/camera.hpp | 2 + src/main.cpp | 60 +++++++++++++++++++--- src/math.cpp | 99 +++++++++++++++++++++++++++++++++++++ src/player.cpp | 1 + src/renderer/calls.cpp | 78 ++++++++++++++++++++--------- src/renderer/camera.cpp | 48 ++++++++++++++++-- 9 files changed, 271 insertions(+), 37 deletions(-) diff --git a/include/globals.hpp b/include/globals.hpp index 8b27000..6dee423 100644 --- a/include/globals.hpp +++ b/include/globals.hpp @@ -1,8 +1,8 @@ #ifndef DELTA_GLOBALS #define DELTA_GLOBALS -const int SIMULATED_WIDTH = 1920; -const int SIMULATED_HEIGHT = 1080; +const int SIMULATED_WIDTH = 800; +const int SIMULATED_HEIGHT = 600; const int FOCAL = 300; #endif \ No newline at end of file diff --git a/include/math.hpp b/include/math.hpp index 45ba9db..17a8240 100644 --- a/include/math.hpp +++ b/include/math.hpp @@ -36,11 +36,25 @@ class DeltaVector3 { }; float getDeterminant(const DeltaVector2 &pointA, const DeltaVector2 &pointB, const DeltaVector2 &candidate); +DeltaVector2 barycentricToScreen(const DeltaVector3 &baryPoint, const DeltaVector2 &a, const DeltaVector2 &b, const DeltaVector2 &c); +DeltaVector3 intersect(DeltaVector3 a, DeltaVector3 b, float nearZ); struct DeltaTriangle { DeltaVector3 a, b, c; }; +struct DeltaTriangle2D { + DeltaVector2 a, b, c; +}; + + +struct DeltaRenderTriangle { + DeltaVector3 a, b, c; + DeltaVector2 uva, uvb, uvc; +}; + +int clipTriangleAgainstNearPlane(float nearY, const DeltaRenderTriangle &in, DeltaRenderTriangle &out1, DeltaRenderTriangle &out2); + uint32_t SDLColorToUint32(SDL_Color color); #endif \ No newline at end of file diff --git a/include/renderer/calls.hpp b/include/renderer/calls.hpp index 7172cbd..fc14c67 100644 --- a/include/renderer/calls.hpp +++ b/include/renderer/calls.hpp @@ -7,6 +7,6 @@ void rasterizeTriangle(SDL_Renderer *renderer, const DeltaVector2 &a, const DeltaVector2 &b, const DeltaVector2 &c); void rasterizeTriangleFast(SDL_Surface* surface, const DeltaVector2& v0, const DeltaVector2& v1, const DeltaVector2& v2, uint32_t color); -void rasterizeTriangleSurface(SDL_Surface* surface, const DeltaVector2& a, const DeltaVector2& b, const DeltaVector2& c, uint32_t color); +void rasterizeTriangleSurface(SDL_Surface* surface, const DeltaVector2& v0, const DeltaVector2& v1, const DeltaVector2& v2, uint32_t color, float u0, float v0_uv, float u1, float v1_uv, float u2, float v2_uv); void setPixel(SDL_Surface* surface, int x, int y, uint32_t color); #endif \ No newline at end of file diff --git a/include/renderer/camera.hpp b/include/renderer/camera.hpp index 3b3176c..5ac2f6c 100644 --- a/include/renderer/camera.hpp +++ b/include/renderer/camera.hpp @@ -10,7 +10,9 @@ class DeltaCamera { DeltaCamera() {pos = DeltaVector3(); angle = DeltaVector2();}; DeltaCamera(float x, float y, float z); + DeltaVector3 translatePoint(const DeltaVector3 &worldPos); DeltaVector2 projectPointVec(const DeltaVector3 &worldPos); + int projectTriangle(const DeltaTriangle &in, DeltaTriangle2D &out1, DeltaTriangle2D &out2); DeltaVector2 projectPoint(float worldX, float worldY, float worldZ); }; diff --git a/src/main.cpp b/src/main.cpp index e13dd62..81493a8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "globals.hpp" #include "math.hpp" @@ -19,11 +20,11 @@ static SDL_Renderer *renderer = NULL; DeltaVector2 temp1(125, 150), temp2(150, 120), temp3(100, 100); DeltaVector2 topleft, bottomright, p; -Player player = Player(70, 110, 20); +Player player = Player(200, -200, 20); double prev = 0, now = 0, deltaTime; SDL_Surface* backbuffer = NULL; -std::vector triangles; +std::vector triangles; SDL_Texture* gpuTexture = NULL; SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) @@ -36,7 +37,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) return SDL_APP_FAILURE; } - if (!SDL_CreateWindowAndRenderer("game", SIMULATED_WIDTH, SIMULATED_HEIGHT, SDL_WINDOW_FULLSCREEN, &window, &renderer)) { + if (!SDL_CreateWindowAndRenderer("game", SIMULATED_WIDTH, SIMULATED_HEIGHT, 0, &window, &renderer)) { SDL_Log("Couldn't create window/renderer: %s", SDL_GetError()); return SDL_APP_FAILURE; } @@ -45,7 +46,8 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) SDL_SetRenderLogicalPresentation(renderer, SIMULATED_WIDTH, SIMULATED_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX); - triangles.push_back({ {40, 10, 0}, {40, 210, 0}, {40, 10, 200} }); + triangles.push_back({ {40, 10, 200}, {40, 210, 0}, {40, 10, 0} , {0, 0}, {1, 1}, {0, 1}}); + triangles.push_back({ {40, 210, 200}, {40, 210, 0}, {40, 10, 200}, {1, 0}, {1, 1}, {0, 0} }); backbuffer = SDL_CreateSurface(SIMULATED_WIDTH, SIMULATED_HEIGHT, SDL_PIXELFORMAT_ARGB8888); gpuTexture = SDL_CreateTexture( renderer, @@ -55,6 +57,8 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) SIMULATED_HEIGHT ); + SDL_SetRenderVSync(renderer, 1); + return SDL_APP_CONTINUE; /* carry on with the program! */ } @@ -101,12 +105,52 @@ SDL_AppResult SDL_AppIterate(void *appstate) // rendered = player.camera.projectPointVec({ 40, 210, -170 }); // SDL_RenderPoint(renderer, rendered.x, rendered.y); - for (auto triangle : triangles) { - DeltaVector2 pointA = player.camera.projectPointVec(triangle.a), pointB = player.camera.projectPointVec(triangle.b), pointC = player.camera.projectPointVec(triangle.c); - - rasterizeTriangleFast(backbuffer, pointA, pointB, pointC, SDLColorToUint32({ 255, 0, 255, 255 })); + if (SDL_MUSTLOCK(backbuffer)) { + SDL_LockSurface(backbuffer); } + DeltaTriangle triangle1, triangle2; + for (auto triangle : triangles) { + // DeltaVector2 pointA = player.camera.projectPointVec(triangle.a), pointB = player.camera.projectPointVec(triangle.b), pointC = player.camera.projectPointVec(triangle.c); + DeltaTriangle2D out1, out2; + + int numtriangles = player.camera.projectTriangle({ triangle.a, triangle.b, triangle.c }, out1, out2); + // SDL_Log(std::to_string(numtriangles).c_str()); + if (numtriangles == 1) { + // rasterizeTriangleFast(backbuffer, out1.a, out1.b, out1.c, SDLColorToUint32({ 255, 0, 0, 255 })); + // rasterizeTriangleFast(backbuffer, out1.c, out1.b, out1.a, SDLColorToUint32({ 0, 0, 255, 255 })); + rasterizeTriangleSurface(backbuffer, out1.a, out1.b, out1.c, SDLColorToUint32({ 0, 255, 0, 255 }), triangle.uva.x, triangle.uva.y, triangle.uvb.x, triangle.uvb.y, triangle.uvc.x, triangle.uvc.y); + // SDL_Log(std::to_string(triangle.a.y).c_str()); + // SDL_Log(std::to_string(triangle.b.y).c_str()); + // SDL_Log(std::to_string(triangle.c.y).c_str()); + } + if (numtriangles == 2) { + // rasterizeTriangleFast(backbuffer, out1.a, out1.b, out1.c, SDLColorToUint32({ 255, 0, 0, 255 })); + rasterizeTriangleSurface(backbuffer, out1.a, out1.b, out1.c, SDLColorToUint32({ 0, 255, 0, 255 }), triangle.uva.x, triangle.uva.y, triangle.uvb.x, triangle.uvb.y, triangle.uvc.x, triangle.uvc.y); + // rasterizeTriangleFast(backbuffer, out1.c, out1.b, out1.a, SDLColorToUint32({ 0, 255, 0, 255 })); + rasterizeTriangleSurface(backbuffer, out2.a, out2.b, out2.c, SDLColorToUint32({ 0, 255, 0, 255 }), triangle.uva.x, triangle.uva.y, triangle.uvb.x, triangle.uvb.y, triangle.uvc.x, triangle.uvc.y); + } + + // int intersected = clipTriangleAgainstNearPlane(0.1, { triangle.a, triangle.b, triangle.c }, triangle1, triangle2); + + // rasterizeTriangleFast(backbuffer, pointA, pointB, pointC, SDLColorToUint32({ 255, 0, 0, 255 })); + // if (intersected == 0) continue; + + // rasterizeTriangleFast(backbuffer, pointA, pointB, pointC, SDLColorToUint32({ 255, 0, 0, 255 })); + // SDL_Log("==="); + } + SDL_Log("====="); + + if (SDL_MUSTLOCK(backbuffer)) { + SDL_UnlockSurface(backbuffer); + } + + // for (int y = 0; y < SIMULATED_HEIGHT; y++) { + // for (int x = 0; x < SIMULATED_WIDTH; x++) { + // setPixel(backbuffer, x, y, SDLColorToUint32({ 255, 0, 0 })); + // } + // } + SDL_UpdateTexture(gpuTexture, NULL, backbuffer->pixels, backbuffer->pitch); SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); diff --git a/src/math.cpp b/src/math.cpp index b9eb8e1..bb6b07a 100644 --- a/src/math.cpp +++ b/src/math.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "math.hpp" @@ -112,7 +113,105 @@ float getDeterminant(const DeltaVector2 &pointA, const DeltaVector2 &pointB, con return (ab.y * ac.x) - (ab.x * ac.y); } +DeltaVector2 barycentricToScreen(const DeltaVector3 &baryPoint, const DeltaVector2 &a, const DeltaVector2 &b, const DeltaVector2 &c) { + return {(baryPoint.x * a.x) + (baryPoint.y * b.x) + (baryPoint.z * c.x),(baryPoint.x * a.y) + (baryPoint.y * b.y) + (baryPoint.z * c.y)}; +} + uint32_t SDLColorToUint32(SDL_Color color) { // Layout: AAAAAAAA RRRRRRRR GGGGGGGG BBBBBBBB return (uint32_t)((color.a << 24) | (color.r << 16) | (color.g << 8) | (color.b << 0)); +} + +DeltaVector3 intersect(DeltaVector3 a, DeltaVector3 b, float nearZ) { + if (std::abs(b.y - a.y) < 0.0001f) { + SDL_Log("IMAGINEEEE"); + return a; + } + + float t = (nearZ - a.y) / (b.y - a.y); + return DeltaVector3( + a.x + t * (b.x - a.x), + nearZ, + a.z + t * (b.z - a.z) + ); +} + +int clipTriangleAgainstNearPlane(float nearY, const DeltaRenderTriangle &in, DeltaRenderTriangle &out1, DeltaRenderTriangle &out2) { + // DeltaVector3 inside[3], outside[3]; + // DeltaVector3 cur; + // int nIn = 0, nOut = 0; + + // for (int i = 0; i < 3; i++) { + // if (i == 0) cur = in.a; + // else if (i == 1) cur = in.b; + // else cur = in.c; + // if (cur.y > 0.1f) inside[nIn++] = cur; + // else outside[nOut++] = cur; + // } + + // if (nIn == 0) return 0; + // if (nIn == 3) { out1 = in; return 1; } + + // if (nIn == 1) { + // out1.c = inside[0]; + // out1.b = intersect(inside[0], outside[0], nearZ); + // out1.a = intersect(inside[0], outside[1], nearZ); + // return 1; + // } + + // if (nIn == 2) { + // out1.a = inside[0]; + // out1.b = inside[1]; + // out1.c = intersect(inside[1], outside[0], nearZ); + + // out2.a = inside[0]; + // out2.b = out1.a; + // out2.c = intersect(outside[0], inside[0], nearZ); + + // return 2; + // } + + const DeltaVector3* v[3] = { &in.a, &in.b, &in.c }; + bool inside[3]; + int insideCount = 0; + + for (int i = 0; i < 3; i++) { + inside[i] = (v[i]->y >= nearY); + if (inside[i]) insideCount++; + } + + if (insideCount == 0) return 0; + if (insideCount == 3) { out1 = in; return 1; } + + if (insideCount == 1) { + // Find the one vertex that is inside + int i0 = (inside[0]) ? 0 : (inside[1] ? 1 : 2); + int i1 = (i0 + 1) % 3; + int i2 = (i0 + 2) % 3; + + out1.a = *v[i0]; + out1.b = intersect(*v[i0], *v[i1], nearY); + out1.c = intersect(*v[i0], *v[i2], nearY); + return 1; + } + + if (insideCount == 2) { + // Find the one vertex that is OUTSIDE + int i0 = (!inside[0]) ? 0 : (!inside[1] ? 1 : 2); + int i1 = (i0 + 1) % 3; // Inside vertex 1 + int i2 = (i0 + 2) % 3; // Inside vertex 2 + + // Triangle 1: Connects the two inside vertices and one intersection + out1.a = *v[i1]; + out1.b = *v[i2]; + out1.c = intersect(*v[i2], *v[i0], nearY); + + // Triangle 2: Connects the first inside vertex and the two intersections + out2.a = *v[i1]; + out2.b = out1.c; + out2.c = intersect(*v[i0], *v[i1], nearY); + return 2; + } + + return 0; } \ No newline at end of file diff --git a/src/player.cpp b/src/player.cpp index dd0e513..19488ec 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -36,6 +36,7 @@ void Player::update(float deltaTime) { // SDL_Log(std::to_string(pos.x).c_str()); // SDL_Log(std::to_string(pos.y).c_str()); // SDL_Log(std::to_string(pos.z).c_str()); + // SDL_Log("==="); angle.x += angleChange.x * 3.14 * deltaTime; angle.y += angleChange.y * 3.14 * deltaTime; diff --git a/src/renderer/calls.cpp b/src/renderer/calls.cpp index f3606c3..cbda368 100644 --- a/src/renderer/calls.cpp +++ b/src/renderer/calls.cpp @@ -9,37 +9,71 @@ void setPixel(SDL_Surface* surface, int x, int y, uint32_t color) { // 1. Boundary check to prevent memory corruption (Crashes) if (x < 0 || x >= surface->w || y < 0 || y >= surface->h) return; - // 2. Lock the surface if it requires it (usually for hardware surfaces) - if (SDL_MUSTLOCK(surface)) { - SDL_LockSurface(surface); - } - // 3. Calculate the pointer to the specific pixel // We cast to uint8_t* to do byte-math, then to uint32_t* to write the color uint8_t* pixel_ptr = (uint8_t*)surface->pixels + (y * surface->pitch) + (x * sizeof(uint32_t)); *(uint32_t*)pixel_ptr = color; - - if (SDL_MUSTLOCK(surface)) { - SDL_UnlockSurface(surface); - } } -void rasterizeTriangleSurface(SDL_Surface* surface, const DeltaVector2& a, const DeltaVector2& b, const DeltaVector2& c, uint32_t color) { - DeltaVector2 begin, end, p; - begin.x = std::min(a.x, std::min(b.x, c.x)); - begin.y = std::min(a.y, std::min(b.y, c.y)); - end.x = std::max(a.x, std::max(b.x, c.x)); - end.y = std::max(a.y, std::max(b.y, c.y)); +void rasterizeTriangleSurface(SDL_Surface* surface, const DeltaVector2& v0, const DeltaVector2& v1, const DeltaVector2& v2, uint32_t color, float u0, float v0_uv, float u1, float v1_uv, float u2, float v2_uv) { + int minX = std::max(0, (int)std::min({v0.x, v1.x, v2.x})); + int maxX = std::min((int)surface->w - 1, (int)std::max({v0.x, v1.x, v2.x})); + int minY = std::max(0, (int)std::min({v0.y, v1.y, v2.y})); + int maxY = std::min((int)surface->h - 1, (int)std::max({v0.y, v1.y, v2.y})); - for (int y = begin.y; y < end.y; y++) { - for (int x = begin.x; x < end.x; x++) { - p.x = x; p.y = y; - - if (getDeterminant(a, b, p) >= 0 && getDeterminant(b, c, p) >= 0 && getDeterminant(c, a, p) >= 0) { - setPixel(surface, x, y, color); + // precompute edge coefficients + // edge function: E(x, y) = (v_i.y - v_j.y)x + (v_j.x - v_i.x)y + (v_i.x * v_j.y - v_j.x * v_i.y) + float a0 = v1.y - v2.y, b0 = v2.x - v1.x, c0 = v1.x * v2.y - v2.x * v1.y; + float a1 = v2.y - v0.y, b1 = v0.x - v2.x, c1 = v2.x * v0.y - v0.x * v2.y; + float a2 = v0.y - v1.y, b2 = v1.x - v0.x, c2 = v0.x * v1.y - v1.x * v0.y; + + // calculate initial values at (minX, minY) + float e0_row = a0 * minX + b0 * minY + c0; + float e1_row = a1 * minX + b1 * minY + c1; + float e2_row = a2 * minX + b2 * minY + c2; + + float area = a0 * v0.x + b0 * v0.y + c0; + if (std::abs(area) < 0.0001f) return; + float invArea = 1.0f / area; + + uint32_t* pixels = (uint32_t*)surface->pixels; + int pitch = surface->pitch / sizeof(uint32_t); + + // main raster loop + for (int y = minY; y <= maxY; y++) { + float e0 = e0_row; + float e1 = e1_row; + float e2 = e2_row; + + uint32_t* rowPtr = &pixels[y * pitch]; + + for (int x = minX; x <= maxX; x++) { + // only fill if pixel is inside all 3 edges + if (e0 >= 0 && e1 >= 0 && e2 >= 0) { + float w0 = e0 * invArea; + float w1 = e1 * invArea; + float w2 = e2 * invArea; + + float u = w0 * u0 + w1 * u1 + w2 * u2; + float v = w0 * v0_uv + w1 * v1_uv + w2 * v2_uv; + + uint8_t r = (uint8_t)(u * 255.0f); + uint8_t g = (uint8_t)(v * 255.0f); + uint8_t b = 128; + + rowPtr[x] = (255 << 24) | (r << 16) | (g << 8) | b; } - + + // step horizontally: just 3 additions + e0 += a0; + e1 += a1; + e2 += a2; } + + // step vertically: update the row starts + e0_row += b0; + e1_row += b1; + e2_row += b2; } } diff --git a/src/renderer/camera.cpp b/src/renderer/camera.cpp index 689bb80..3aa79b7 100644 --- a/src/renderer/camera.cpp +++ b/src/renderer/camera.cpp @@ -3,10 +3,11 @@ // please, dont. if you do continue, edit the value below // as a warning for future you. -// hoursWasted = 5 +// hoursWasted = 8 #include #include +#include #include "renderer/camera.hpp" #include "globals.hpp" @@ -16,8 +17,8 @@ DeltaCamera::DeltaCamera(float x, float y, float z) { angle = DeltaVector2(); } -DeltaVector2 DeltaCamera::projectPointVec(const DeltaVector3 &worldPos) { - DeltaVector3 calculatedPos; DeltaVector2 projected; +DeltaVector3 DeltaCamera::translatePoint(const DeltaVector3 &worldPos) { + DeltaVector3 calculatedPos; float sineX = std::sin(angle.x); float cosineX = std::cos(angle.x); @@ -39,11 +40,23 @@ DeltaVector2 DeltaCamera::projectPointVec(const DeltaVector3 &worldPos) { calculatedPos.z = t.z * cosineY + ry * sineY; // dont touch calculatedPos.x - // calculatedPos.y = calculatedPos.y * cosineY; + // if (calculatedPos.y <= 0.1f) return {-FLT_MAX, 0, 0}; + + return calculatedPos; +} + +DeltaVector2 DeltaCamera::projectPointVec(const DeltaVector3 &worldPos) { + DeltaVector3 calculatedPos; DeltaVector2 projected; + calculatedPos = worldPos; + + // calculatedPos.y += 0.00001f; projected.x = calculatedPos.x * FOCAL / calculatedPos.y + SIMULATED_WIDTH / 2.0f; projected.y = calculatedPos.z * FOCAL / calculatedPos.y + SIMULATED_HEIGHT / 2.0f; + // projected.x = std::min((float)SIMULATED_WIDTH, std::max(0.0f, projected.x)); + // projected.x = std::min((float)SIMULATED_HEIGHT, std::max(0.0f, projected.y)); + // now gotta convert y to our y projected.y = SIMULATED_HEIGHT - projected.y; @@ -51,6 +64,33 @@ DeltaVector2 DeltaCamera::projectPointVec(const DeltaVector3 &worldPos) { return projected; } +int DeltaCamera::projectTriangle(const DeltaTriangle &in, DeltaTriangle2D &out1, DeltaTriangle2D &out2) { + DeltaTriangle translated = { translatePoint(in.a), translatePoint(in.b), translatePoint(in.c) }, clipped1, clipped2; + + int numClipped = clipTriangleAgainstNearPlane(0.01, translated, clipped1, clipped2); + if (numClipped == 1) { + out1.a = projectPointVec(clipped1.a); + out1.b = projectPointVec(clipped1.b); + out1.c = projectPointVec(clipped1.c); + + // out1.a = projectPointVec(translated.a); + // out1.b = projectPointVec(translated.b); + // out1.c = projectPointVec(translated.c); + } + + if (numClipped == 2) { + + out1.a = projectPointVec(clipped1.a); + out1.b = projectPointVec(clipped1.b); + out1.c = projectPointVec(clipped1.c); + + out2.a = projectPointVec(clipped2.a); + out2.b = projectPointVec(clipped2.b); + out2.c = projectPointVec(clipped2.c); + } + return numClipped; +} + DeltaVector2 DeltaCamera::projectPoint(float worldX, float worldY, float worldZ) { // simple conversion, dont iterate further return projectPointVec({ worldX, worldY, worldZ });