From ce9a30aded86faff862ec7f581e80f6b99b592a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Wed, 30 Nov 2022 15:49:58 +0100 Subject: [PATCH 01/10] Let's experiment with a donut! --- effects/donut/Makefile | 10 +++++ effects/donut/donut-pc.c | 18 +++++++++ effects/donut/donut.c | 83 ++++++++++++++++++++++++++++++++++++++++ effects/donut/donut.h | 62 ++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 effects/donut/Makefile create mode 100644 effects/donut/donut-pc.c create mode 100644 effects/donut/donut.c create mode 100644 effects/donut/donut.h diff --git a/effects/donut/Makefile b/effects/donut/Makefile new file mode 100644 index 00000000..ac25eeee --- /dev/null +++ b/effects/donut/Makefile @@ -0,0 +1,10 @@ +TOPDIR := $(realpath ../..) + +CLEAN-FILES := data/drdos8x8.c donut-pc + +PSF2C.drdos8x8 := --name drdos8x8 --type console + +include $(TOPDIR)/build/effect.mk + +donut-pc: donut-pc.c donut.h + cc -g -Og -Wall -o $@ $< diff --git a/effects/donut/donut-pc.c b/effects/donut/donut-pc.c new file mode 100644 index 00000000..b4f1d1b9 --- /dev/null +++ b/effects/donut/donut-pc.c @@ -0,0 +1,18 @@ +#include +#include +#include +#include + +#include "donut.h" + +int main(void) { + for (;;) { + CalcDonut(); + + for (int k = 0; (W * H + 1) > k; k++) + putchar(k % W ? b[k] : 10); + usleep(15000); + printf("\x1b[%dA", H + 1); + } + return 0; +} diff --git a/effects/donut/donut.c b/effects/donut/donut.c new file mode 100644 index 00000000..90695b7d --- /dev/null +++ b/effects/donut/donut.c @@ -0,0 +1,83 @@ +#include "effect.h" +#include "console.h" +#include "copper.h" +#include +#include +#include + +#define WIDTH 320 +#define HEIGHT 256 +#define DEPTH 1 + +#include "data/drdos8x8.c" + +static BitmapT *screen; +static CopListT *cp; +static ConsoleT console; + +static void Init(void) { + screen = NewBitmap(WIDTH, HEIGHT, DEPTH); + cp = NewCopList(100); + + SetupPlayfield(MODE_LORES, DEPTH, X(0), Y(0), WIDTH, HEIGHT); + SetColor(0, 0x000); + SetColor(1, 0xfff); + + CopInit(cp); + CopSetupBitplanes(cp, NULL, screen, DEPTH); + CopEnd(cp); + CopListActivate(cp); + EnableDMA(DMAF_RASTER); + + ConsoleInit(&console, &drdos8x8, screen); + + KeyboardInit(); +} + +static void Kill(void) { + DisableDMA(DMAF_COPPER | DMAF_RASTER); + + KeyboardKill(); + + DeleteCopList(cp); + DeleteBitmap(screen); +} + +static bool HandleEvent(void) { + EventT ev; + + if (!PopEvent(&ev)) + return true; + + if (ev.type != EV_KEY) + return true; + + if (ev.key.modifier & MOD_PRESSED) + return true; + + if (ev.key.code == KEY_ESCAPE) + return false; + + ConsoleDrawCursor(&console); + + ConsoleDrawCursor(&console); + + return true; +} + +#include "donut.h" + +static void Render(void) { + int k; + + ConsoleSetCursor(&console, 0, 0); + + CalcDonut(); + + for (k = 0; (W * H + 1) > k; k++) + ConsolePutChar(&console, k % W ? b[k] : 10); + + exitLoop = !HandleEvent(); +} + +EFFECT(KbdTest, NULL, NULL, Init, Kill, Render); diff --git a/effects/donut/donut.h b/effects/donut/donut.h new file mode 100644 index 00000000..2b1cabce --- /dev/null +++ b/effects/donut/donut.h @@ -0,0 +1,62 @@ +#define W 40 +#define H 32 +#define D 30 + +#define R(mul, shift, x, y) \ + _ = x; \ + x -= mul * y >> shift; \ + y += mul * _ >> shift; \ + _ = (3145728 - x * x - y * y) >> 11; \ + x = x * _ >> 10; \ + y = y * _ >> 10; + +static char b[W * H], z[W * H]; +static const char pixels[] = ".,-~:;=!*#$@"; + +void CalcDonut(void) { + static int sA = 1024, cA = 0, sB = 1024, cB = 0, _; + + int sj = 0, cj = 1024; + int i, j; + + memset(b, 32, W*H); // text buffer + memset(z, 127, W*H); // z buffer + + for (j = 0; j < 90; j++) { + int si = 0, ci = 1024; // sine and cosine of angle i +#ifdef Log + Log("j = %d\n", j); +#endif + for (i = 0; i < 324; i++) { + int R1 = 1, R2 = 2048, K2 = 5120 * 1024; + + int x0 = R1 * cj + R2; + int x1 = ci * x0 >> 10; + int x2 = cA * sj >> 10; + int x3 = si * x0 >> 10; + int x4 = R1 * x2 - (sA * x3 >> 10); + int x5 = sA * sj >> 10; + int x6 = K2 + R1 * 1024 * x5 + cA * x3; + int x7 = cj * si >> 10; + int x = W / 2 + D * (cB * x1 - sB * x4) / x6; + int y = H / 2 + D / 2 * (cB * x4 + sB * x1) / x6; + int _N1 = cB * ((-sA * x7 >> 10) + x2); + int _N2 = ci * (cj * sB >> 10); + int N = (((-cA * x7 - _N1 - _N2) >> 10) - x5) >> 7; + + int o = x + W * y; + char zz = (x6 - K2) >> 15; + + if (H > y && y >= 0 && x >= 0 && W > x && zz < z[o]) { + z[o] = zz; + b[o] = pixels[N > 0 ? N : 0]; + } + R(5, 8, ci, si) // rotate i + } + R(9, 7, cj, sj) // rotate j + } + + R(5, 7, cA, sA); + R(5, 8, cB, sB); +} + From c8dada132ba51a4c59161080b48b81322ec695e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Wed, 30 Nov 2022 15:50:53 +0100 Subject: [PATCH 02/10] Cite the source! --- effects/donut/donut.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/effects/donut/donut.h b/effects/donut/donut.h index 2b1cabce..eb137984 100644 --- a/effects/donut/donut.h +++ b/effects/donut/donut.h @@ -1,3 +1,5 @@ +/* Adapted from https://www.a1k0n.net/2021/01/13/optimizing-donut.html */ + #define W 40 #define H 32 #define D 30 From c61a0513f93e406d871a6071ec563244e0f0b6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Wed, 30 Nov 2022 16:08:34 +0100 Subject: [PATCH 03/10] Avoid long multiplications. --- effects/donut/donut.h | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/effects/donut/donut.h b/effects/donut/donut.h index eb137984..92a4ce33 100644 --- a/effects/donut/donut.h +++ b/effects/donut/donut.h @@ -16,35 +16,39 @@ static char b[W * H], z[W * H]; static const char pixels[] = ".,-~:;=!*#$@"; void CalcDonut(void) { - static int sA = 1024, cA = 0, sB = 1024, cB = 0, _; + static short sA = 1024, cA = 0, sB = 1024, cB = 0, _; - int sj = 0, cj = 1024; - int i, j; + short sj = 0, cj = 1024; + short i, j; memset(b, 32, W*H); // text buffer memset(z, 127, W*H); // z buffer for (j = 0; j < 90; j++) { - int si = 0, ci = 1024; // sine and cosine of angle i + // sine and cosine of angle i + short si = 0; + short ci = 1024; #ifdef Log Log("j = %d\n", j); #endif for (i = 0; i < 324; i++) { - int R1 = 1, R2 = 2048, K2 = 5120 * 1024; + short R1 = 1; + short R2 = 2048; + int K2 = 5120 * 1024; - int x0 = R1 * cj + R2; - int x1 = ci * x0 >> 10; - int x2 = cA * sj >> 10; - int x3 = si * x0 >> 10; - int x4 = R1 * x2 - (sA * x3 >> 10); - int x5 = sA * sj >> 10; + /* int */ short x0 = R1 * cj + R2; + /* int */ short x1 = ci * x0 >> 10; + /* int */ short x2 = cA * sj >> 10; + /* int */ short x3 = si * x0 >> 10; + /* int */ short x4 = R1 * x2 - (sA * x3 >> 10); + /* int */ short x5 = sA * sj >> 10; int x6 = K2 + R1 * 1024 * x5 + cA * x3; - int x7 = cj * si >> 10; - int x = W / 2 + D * (cB * x1 - sB * x4) / x6; - int y = H / 2 + D / 2 * (cB * x4 + sB * x1) / x6; - int _N1 = cB * ((-sA * x7 >> 10) + x2); - int _N2 = ci * (cj * sB >> 10); - int N = (((-cA * x7 - _N1 - _N2) >> 10) - x5) >> 7; + /* int */ short x7 = cj * si >> 10; + /* int */ short x = W / 2 + D * (cB * x1 - sB * x4) / x6; + /* int */ short y = H / 2 + D / 2 * (cB * x4 + sB * x1) / x6; + short _N1 = (-sA * x7 >> 10) + x2; + short _N2 = cj * sB >> 10; + int N = (((-cA * x7 - cB * _N1 - ci * _N2) >> 10) - x5) >> 7; int o = x + W * y; char zz = (x6 - K2) >> 15; @@ -61,4 +65,3 @@ void CalcDonut(void) { R(5, 7, cA, sA); R(5, 8, cB, sB); } - From 57437a347d1907ef37be7c71212cc32292312ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Thu, 1 Dec 2022 16:46:59 +0100 Subject: [PATCH 04/10] Converted shaders into Java. God! It's sooooo slow! --- prototypes/donut/donut.pde | 253 +++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 prototypes/donut/donut.pde diff --git a/prototypes/donut/donut.pde b/prototypes/donut/donut.pde new file mode 100644 index 00000000..b8051f02 --- /dev/null +++ b/prototypes/donut/donut.pde @@ -0,0 +1,253 @@ +final int WIDTH = 640/4; +final int HEIGHT = 512/4; + +final int MAX_STEPS = 100; +final float MAX_DIST = 100.0; +final float SURF_DIST = 0.01; + +float iTime; + +void settings() { + size(WIDTH, HEIGHT); + noSmooth(); +} + +void setup() { + frameRate(50); +} + +PVector abs(PVector p) { + return new PVector(abs(p.x), abs(p.y), abs(p.z)); +} + +PVector max(PVector p, float v) { + return new PVector(max(p.x, v), max(p.y, v), max(p.z, v)); +} + +/* @brief Rotate around X axis */ +PMatrix3D RotationX(float angle) { + PMatrix3D m = new PMatrix3D(); + float s = sin(angle); + float c = cos(angle); + m.set( + 1, 0, 0, 0, + 0, c, -s, 0, + 0, s, c, 0, + 0, 0, 0, 1); + return m; +} + +/* @brief Rotate around Y axis */ +PMatrix3D RotationY(float angle) { + PMatrix3D m = new PMatrix3D(); + float s = sin(angle); + float c = cos(angle); + m.set( + c, 0, s, 0, + 0, 1, 0, 0, + -s, 0, c, 0, + 0, 0, 0, 1); + return m; +} + +/* @brief Rotate around Z axis */ +PMatrix3D RotationZ(float angle) { + PMatrix3D m = new PMatrix3D(); + float s = sin(angle); + float c = cos(angle); + m.set( + c, -s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1); + return m; +} + +PMatrix3D Rotate(float x, float y, float z) { + PMatrix3D m = RotationX(x); + m.apply(RotationY(y)); + m.apply(RotationZ(z)); + return m; +} + +PMatrix3D Move(float x, float y, float z) { + PMatrix3D m = new PMatrix3D(); + m.set( + 1, 0, 0, -x, + 0, 1, 0, -y, + 0, 0, 1, -z, + 0, 0, 0, 1); + return m; +} + + +/* @brief Calculate distance from sphere + * @param p point of interest + * @param r sphere radius + * @return distance of s from p + */ +float SphereDist(PVector p, float r) { + return p.mag() - r; +} + +/* @brief Calculate distance from plane at (0,0,0) origin + * @param p point of interest + * @return distance of plane from p + */ +float PlaneDist(PVector p) { + return p.y; +} + +/* @brief Calculate distance from a capsule */ +float CapsuleDist(PVector p, PVector a, PVector b, float r) { + PVector ab = PVector.sub(b, a); + PVector ap = PVector.sub(p, a); + + float t = PVector.dot(ab, ap) / PVector.dot(ab, ab); + + t = constrain(t, 0.0, 1.0); + + PVector c = PVector.add(a, PVector.mult(ab, t)); + + return PVector.sub(p, c).mag() - r; +} + +/* @brief Calculate distance from a torus */ +float TorusDist(PVector p, float r1, float r2) { + PVector r = new PVector(p.x, p.z); + PVector q = new PVector(r.mag() - r1, p.y); + return q.mag() - r2; +} + +/* @brief Calculate distance from a box */ +float BoxDist(PVector p, PVector s) { + PVector d = PVector.sub(abs(p), s); + float e = max(d, 0.0).mag(); + float i = min(max(d.x, max(d.y, d.z)), 0.0); + return e + i; +} + +/* @brief Calculate distance from a cylinder */ +float CylinderDist(PVector p, PVector a, PVector b, float r) { + PVector ab = PVector.sub(b, a); + PVector ap = PVector.sub(p, a); + + float t = PVector.dot(ab, ap) / PVector.dot(ab, ab); + + PVector c = PVector.add(a, PVector.mult(ab, t)); + float x = PVector.sub(p, c).mag() - r; + float y = (abs(t - 0.5) - 0.5) * ab.mag(); + float e = max(new PVector(x, y), 0.0).mag(); + float i = min(max(x, y), 0.0); + + return e + i; +} + +float GetDist(PVector p) { + float sd, pd, cd, ccd, td, bd; + + PMatrix3D so = Move(0, 1, 5); + PVector sp = so.mult(p, null); + sd = SphereDist(sp, 1.0); + + pd = PlaneDist(p); + + cd = CapsuleDist(p, new PVector(3, 1, 8), new PVector(3, 3, 8), .5); + ccd = CylinderDist(p, new PVector(2, 1, 6), new PVector(3, 1, 4), .5); + + PMatrix3D to = Move(-3, 1, 6); + PMatrix3D tr = Rotate(iTime, 0., 0.); + to.preApply(tr); + PVector tp = to.mult(p, null); + td = TorusDist(tp, 1.0, 0.25); + + PMatrix3D bo = Move(0, 1, 8); + PMatrix3D br = Rotate(0., iTime * .2, 0.); + bo.preApply(br); + PVector bp = bo.mult(p, null); + bd = BoxDist(bp, new PVector(1, 1, 1)); + + return min(min(min(ccd, bd), min(sd, td)), min(pd, cd)); +} + +PVector GetNormal(PVector p) { + PVector e = new PVector(0.01, 0.0); + float d = GetDist(p); + PVector px = new PVector(e.x, e.y, e.y); + PVector py = new PVector(e.y, e.x, e.y); + PVector pz = new PVector(e.y, e.y, e.x); + PVector n = new PVector( + d - GetDist(PVector.sub(p, px)), + d - GetDist(PVector.sub(p, py)), + d - GetDist(PVector.sub(p, pz))); + n.normalize(); + return n; +} + +float RayMarch(PVector ro, PVector rd) { + // distance from origin + float dO = 0.0; + + for (int i = 0; i < MAX_STEPS; i++) { + PVector p = PVector.add(ro, PVector.mult(rd, dO)); + // distance to the scene + float dS = GetDist(p); + if (dS < SURF_DIST) + break; + dO += dS; + // prevent ray from escaping the scene + if (dO > MAX_DIST) + break; + } + + return dO; +} + +float GetLight(PVector p) { + PVector lightPos = new PVector(0.0, 5.0, 6.0); + + lightPos.x += sin(iTime) * 2.0; + lightPos.z += cos(iTime) * 2.0; + + PVector l = PVector.sub(lightPos, p); + l.normalize(); + + PVector n = GetNormal(p); + + float diff = PVector.dot(n, l); + + float d = RayMarch(PVector.add(p, PVector.mult(n, SURF_DIST)), l); + + if (d < lightPos.mag()) + diff *= 0.1; + + return constrain(diff, 0.0, 1.0); +} + +void draw() { + iTime = frameCount / 50.0; + + for (int y = 0; y < HEIGHT; y++) { + for (int x = 0; x < WIDTH; x++) { + // Normalized pixel coordinates (from -1.0 to 1.0) + PVector uv = new PVector( + (float)x * 2.0 / WIDTH - 1.0, + 1.0 - (float)y * 2.0 / HEIGHT); + + // Simple camera model + PVector ro = new PVector(0.0, 4.0, 0.0); + PVector rd = new PVector(uv.x, uv.y - 0.5, 1.0); + rd.normalize(); + + float d = RayMarch(ro, rd); + + PVector p = PVector.add(ro, PVector.mult(rd, d)); + + float diff = GetLight(p); + + diff = constrain(diff, 0.0, 1.0); + + set(x, y, color(int(diff * 255.0))); + } + } +} From 366fc8c611897324411020a09efb0f62fa9f09cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Thu, 1 Dec 2022 16:47:25 +0100 Subject: [PATCH 05/10] Let's try to convert shaders into C. --- prototypes/donut/Makefile | 7 ++++++ prototypes/donut/donut.c | 47 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 prototypes/donut/Makefile create mode 100644 prototypes/donut/donut.c diff --git a/prototypes/donut/Makefile b/prototypes/donut/Makefile new file mode 100644 index 00000000..666d8ecb --- /dev/null +++ b/prototypes/donut/Makefile @@ -0,0 +1,7 @@ +CFLAGS = -g -Og -Wall -Werror $(shell pkg-config --cflags sdl2) +LDLIBS = $(shell pkg-config --libs sdl2) + +donut: donut.c + +clean: + rm donut *~ diff --git a/prototypes/donut/donut.c b/prototypes/donut/donut.c new file mode 100644 index 00000000..7f4f38c9 --- /dev/null +++ b/prototypes/donut/donut.c @@ -0,0 +1,47 @@ +#include + +static const int WIDTH = 800; +static const int HEIGHT = 600; + +int main(void) { + SDL_Init(SDL_INIT_EVERYTHING); + + SDL_Window *window = + SDL_CreateWindow("Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + WIDTH, HEIGHT, 0); + + SDL_Surface *window_surface = SDL_GetWindowSurface(window); + + SDL_Surface *canvas = SDL_CreateRGBSurfaceWithFormat( + 0, WIDTH, HEIGHT, 32, SDL_PIXELFORMAT_RGBA8888); + + Uint32 *buffer = (Uint32 *)canvas->pixels; + + SDL_LockSurface(canvas); + for (int row = 0; row < HEIGHT; row++) { + for (int column = 0; column < WIDTH; column++) { + int offset = row * WIDTH + column; + buffer[offset] = SDL_MapRGBA(canvas->format, 255, 0, 0, 255); + } + } + SDL_UnlockSurface(canvas); + + int quit = 0; + SDL_Event event; + while (!quit) { + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + quit = 1; + } + } + + SDL_BlitSurface(canvas, 0, window_surface, 0); + SDL_UpdateWindowSurface(window); + + SDL_Delay(10); + } + + SDL_Quit(); + + return 0; +} From d5dac1553309e45c8f4746420f096086bf4df997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Thu, 1 Dec 2022 17:53:16 +0100 Subject: [PATCH 06/10] Convert shader to C. It's sloooow, but much better than Java. --- prototypes/donut/Makefile | 2 +- prototypes/donut/donut.c | 303 +++++++++++++++++++++++++++++++++++-- prototypes/donut/donut.pde | 253 ------------------------------- 3 files changed, 291 insertions(+), 267 deletions(-) delete mode 100644 prototypes/donut/donut.pde diff --git a/prototypes/donut/Makefile b/prototypes/donut/Makefile index 666d8ecb..d9399572 100644 --- a/prototypes/donut/Makefile +++ b/prototypes/donut/Makefile @@ -1,5 +1,5 @@ CFLAGS = -g -Og -Wall -Werror $(shell pkg-config --cflags sdl2) -LDLIBS = $(shell pkg-config --libs sdl2) +LDLIBS = $(shell pkg-config --libs sdl2) -lm donut: donut.c diff --git a/prototypes/donut/donut.c b/prototypes/donut/donut.c index 7f4f38c9..ce4e0929 100644 --- a/prototypes/donut/donut.c +++ b/prototypes/donut/donut.c @@ -1,7 +1,291 @@ #include +#include -static const int WIDTH = 800; -static const int HEIGHT = 600; +static const int WIDTH = 640/4; +static const int HEIGHT = 512/4; + +static const int MAX_STEPS = 100; +static const float MAX_DIST = 100.0; +static const float SURF_DIST = 0.01; + +static float iTime; + +typedef struct vec3 { + float x, y, z, w; +} vec3; + +typedef struct mat4 { + float m00, m01, m02, m03; + float m10, m11, m12, m13; + float m20, m21, m22, m23; + float m30, m31, m32, m33; +} mat4; + +static float clamp(float v, float min, float max) { + return fmin(fmax(v, min), max); +} + +static vec3 v3_abs(vec3 p) { + return (vec3){fabs(p.x), fabs(p.y), fabs(p.z)}; +} + +static vec3 v3_max(vec3 p, float v) { + return (vec3){fmax(p.x, v), fmax(p.y, v), fmax(p.z, v)}; +} + +static vec3 v3_add(vec3 a, vec3 b) { + return (vec3){a.x + b.x, a.y + b.y, a.z + b.z}; +} + +static vec3 v3_sub(vec3 a, vec3 b) { + return (vec3){a.x - b.x, a.y - b.y, a.z - b.z}; +} + +static vec3 v3_mul(vec3 a, float v) { + return (vec3){a.x * v, a.y * v, a.z * v}; +} + +static float v3_dot(vec3 a, vec3 b) { + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +static float v3_length(vec3 p) { + return sqrtf(p.x * p.x + p.y * p.y + p.z * p.z); +} + +static vec3 v3_normalize(vec3 p) { + return v3_mul(p, 1.0 / v3_length(p)); +} + +static mat4 m4_mul(mat4 a, mat4 b) { + mat4 c; + + float *ma = (float *)&a.m00; + float *mb = (float *)&b.m00; + float *mc = (float *)&c.m00; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + mc[i * 4 + j] = 0; + for (int k = 0; k < 4; k++) { + mc[i * 4 + j] += ma[i * 4 + k] * mb[k * 4 + j]; + } + } + } + + return c; +} + +/* @brief Rotate around X axis */ +static mat4 m4_rotateX(float angle) { + float s = sin(angle); + float c = cos(angle); + return (mat4) {1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1}; +} + +/* @brief Rotate around Y axis */ +static mat4 m4_rotateY(float angle) { + float s = sin(angle); + float c = cos(angle); + return (mat4){c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1}; +} + +/* @brief Rotate around Z axis */ +static mat4 m4_rotateZ(float angle) { + float s = sin(angle); + float c = cos(angle); + return (mat4){c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; +} + +static mat4 m4_move(float x, float y, float z) { + return (mat4){1, 0, 0, -x, 0, 1, 0, -y, 0, 0, 1, -z, 0, 0, 0, 1}; +} + +static mat4 m4_rotate(float x, float y, float z) { + return m4_mul(m4_mul(m4_rotateX(x), m4_rotateY(y)), m4_rotateZ(z)); +} + +static vec3 m4_translate(vec3 p, mat4 a) { + vec3 r; + + p.w = 1.0; + + float *vp = (float *)&p.x; + float *vr = (float *)&r.x; + float *ma = (float *)&a.m00; + + for (int i = 0; i < 4; i++) { + vr[i] = 0; + for (int j = 0; j < 4; j++) { + vr[i] += ma[i * 4 + j] * vp[j]; + } + } + + return r; +} + +/* @brief Calculate distance from sphere + * @param p point of interest + * @param r sphere radius + * @return distance of s from p + */ +float SphereDist(vec3 p, float r) { + return v3_length(p) - r; +} + +/* @brief Calculate distance from plane at (0,0,0) origin + * @param p point of interest + * @return distance of plane from p + */ +float PlaneDist(vec3 p) { + return p.y; +} + +/* @brief Calculate distance from a capsule */ +float CapsuleDist(vec3 p, vec3 a, vec3 b, float r) { + vec3 ab = v3_sub(b, a); + vec3 ap = v3_sub(p, a); + + float t = v3_dot(ab, ap) / v3_dot(ab, ab); + + t = clamp(t, 0.0, 1.0); + + vec3 c = v3_add(a, v3_mul(ab, t)); + + return v3_length(v3_sub(p, c)) - r; +} + +/* @brief Calculate distance from a torus */ +float TorusDist(vec3 p, float r1, float r2) { + vec3 r = {p.x, p.z}; + vec3 q = {v3_length(r) - r1, p.y}; + return v3_length(q) - r2; +} + +/* @brief Calculate distance from a box */ +float BoxDist(vec3 p, vec3 s) { + vec3 d = v3_sub(v3_abs(p), s); + float e = v3_length(v3_max(d, 0.0)); + float i = fmin(fmax(d.x, fmax(d.y, d.z)), 0.0); + return e + i; +} + +/* @brief Calculate distance from a cylinder */ +float CylinderDist(vec3 p, vec3 a, vec3 b, float r) { + vec3 ab = v3_sub(b, a); + vec3 ap = v3_sub(p, a); + + float t = v3_dot(ab, ap) / v3_dot(ab, ab); + + vec3 c = v3_add(a, v3_mul(ab, t)); + float x = v3_length(v3_sub(p, c)) - r; + float y = (abs(t - 0.5) - 0.5) * v3_length(ab); + float e = v3_length(v3_max((vec3){x, y}, 0.0)); + float i = fmin(fmax(x, y), 0.0); + + return e + i; +} + +float GetDist(vec3 p) { + mat4 so = m4_move(0, 1, 5); + vec3 sp = m4_translate(p, so); + float sd = SphereDist(sp, 1.0); + + float pd = PlaneDist(p); + + float cd = CapsuleDist(p, (vec3){3, 1, 8}, (vec3){3, 3, 8}, .5); + float ccd = CylinderDist(p, (vec3){2, 1, 6}, (vec3){3, 1, 4}, .5); + + mat4 to = m4_move(-3, 1, 6); + mat4 tr = m4_rotate(iTime, 0., 0.); + vec3 tp = m4_translate(p, m4_mul(tr, to)); + float td = TorusDist(tp, 1.0, 0.25); + + mat4 bo = m4_move(0, 1, 8); + mat4 br = m4_rotate(0., iTime * .2, 0.); + vec3 bp = m4_translate(p, m4_mul(br, bo)); + float bd = BoxDist(bp, (vec3){1, 1, 1}); + + return fmin(fmin(fmin(ccd, bd), fmin(sd, td)), fmin(pd, cd)); +} + +vec3 GetNormal(vec3 p) { + float d = GetDist(p); + vec3 e = (vec3){0.01, 0.0}; + vec3 n = (vec3){d - GetDist(v3_sub(p, (vec3){e.x, e.y, e.y})), + d - GetDist(v3_sub(p, (vec3){e.y, e.x, e.y})), + d - GetDist(v3_sub(p, (vec3){e.y, e.y, e.x}))}; + return v3_normalize(n); +} + +float RayMarch(vec3 ro, vec3 rd) { + // distance from origin + float dO = 0.0; + + for (int i = 0; i < MAX_STEPS; i++) { + vec3 p = v3_add(ro, v3_mul(rd, dO)); + // distance to the scene + float dS = GetDist(p); + if (dS < SURF_DIST) + break; + dO += dS; + // prevent ray from escaping the scene + if (dO > MAX_DIST) + break; + } + + return dO; +} + +float GetLight(vec3 p) { + vec3 lightPos = {0.0, 5.0, 6.0}; + + lightPos.x += sinf(iTime) * 2.0; + lightPos.z += cosf(iTime) * 2.0; + + vec3 l = v3_normalize(v3_sub(lightPos, p)); + vec3 n = GetNormal(p); + + float diff = v3_dot(n, l); + + float d = RayMarch(v3_add(p, v3_mul(n, SURF_DIST)), l); + + if (d < v3_length(lightPos)) + diff *= 0.1; + + return clamp(diff, 0.0, 1.0); +} + +void Draw(SDL_Surface *canvas) { + Uint32 *buffer = (Uint32 *)canvas->pixels; + + iTime = SDL_GetTicks() / 1000.0; + + for (int y = 0; y < HEIGHT; y++) { + // SDL_Log("y = %d\n", y); + for (int x = 0; x < WIDTH; x++) { + // Normalized pixel coordinates (from -1.0 to 1.0) + vec3 uv = (vec3){ + (float)x * 2.0 / WIDTH - 1.0, + 1.0 - (float)y * 2.0 / HEIGHT}; + + // Simple camera model + vec3 ro = (vec3){0.0, 4.0, 0.0}; + vec3 rd = v3_normalize((vec3){uv.x, uv.y - 0.5, 1.0}); + + float d = RayMarch(ro, rd); + + vec3 p = v3_add(ro, v3_mul(rd, d)); + + float diff = GetLight(p); + + diff = clamp(diff, 0.0, 1.0); + + int c = (int)(diff * 255.0); + buffer[y * WIDTH + x] = SDL_MapRGBA(canvas->format, c, c, c, 255); + } + } +} int main(void) { SDL_Init(SDL_INIT_EVERYTHING); @@ -15,17 +299,6 @@ int main(void) { SDL_Surface *canvas = SDL_CreateRGBSurfaceWithFormat( 0, WIDTH, HEIGHT, 32, SDL_PIXELFORMAT_RGBA8888); - Uint32 *buffer = (Uint32 *)canvas->pixels; - - SDL_LockSurface(canvas); - for (int row = 0; row < HEIGHT; row++) { - for (int column = 0; column < WIDTH; column++) { - int offset = row * WIDTH + column; - buffer[offset] = SDL_MapRGBA(canvas->format, 255, 0, 0, 255); - } - } - SDL_UnlockSurface(canvas); - int quit = 0; SDL_Event event; while (!quit) { @@ -35,6 +308,10 @@ int main(void) { } } + SDL_LockSurface(canvas); + Draw(canvas); + SDL_UnlockSurface(canvas); + SDL_BlitSurface(canvas, 0, window_surface, 0); SDL_UpdateWindowSurface(window); diff --git a/prototypes/donut/donut.pde b/prototypes/donut/donut.pde deleted file mode 100644 index b8051f02..00000000 --- a/prototypes/donut/donut.pde +++ /dev/null @@ -1,253 +0,0 @@ -final int WIDTH = 640/4; -final int HEIGHT = 512/4; - -final int MAX_STEPS = 100; -final float MAX_DIST = 100.0; -final float SURF_DIST = 0.01; - -float iTime; - -void settings() { - size(WIDTH, HEIGHT); - noSmooth(); -} - -void setup() { - frameRate(50); -} - -PVector abs(PVector p) { - return new PVector(abs(p.x), abs(p.y), abs(p.z)); -} - -PVector max(PVector p, float v) { - return new PVector(max(p.x, v), max(p.y, v), max(p.z, v)); -} - -/* @brief Rotate around X axis */ -PMatrix3D RotationX(float angle) { - PMatrix3D m = new PMatrix3D(); - float s = sin(angle); - float c = cos(angle); - m.set( - 1, 0, 0, 0, - 0, c, -s, 0, - 0, s, c, 0, - 0, 0, 0, 1); - return m; -} - -/* @brief Rotate around Y axis */ -PMatrix3D RotationY(float angle) { - PMatrix3D m = new PMatrix3D(); - float s = sin(angle); - float c = cos(angle); - m.set( - c, 0, s, 0, - 0, 1, 0, 0, - -s, 0, c, 0, - 0, 0, 0, 1); - return m; -} - -/* @brief Rotate around Z axis */ -PMatrix3D RotationZ(float angle) { - PMatrix3D m = new PMatrix3D(); - float s = sin(angle); - float c = cos(angle); - m.set( - c, -s, 0, 0, - s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - return m; -} - -PMatrix3D Rotate(float x, float y, float z) { - PMatrix3D m = RotationX(x); - m.apply(RotationY(y)); - m.apply(RotationZ(z)); - return m; -} - -PMatrix3D Move(float x, float y, float z) { - PMatrix3D m = new PMatrix3D(); - m.set( - 1, 0, 0, -x, - 0, 1, 0, -y, - 0, 0, 1, -z, - 0, 0, 0, 1); - return m; -} - - -/* @brief Calculate distance from sphere - * @param p point of interest - * @param r sphere radius - * @return distance of s from p - */ -float SphereDist(PVector p, float r) { - return p.mag() - r; -} - -/* @brief Calculate distance from plane at (0,0,0) origin - * @param p point of interest - * @return distance of plane from p - */ -float PlaneDist(PVector p) { - return p.y; -} - -/* @brief Calculate distance from a capsule */ -float CapsuleDist(PVector p, PVector a, PVector b, float r) { - PVector ab = PVector.sub(b, a); - PVector ap = PVector.sub(p, a); - - float t = PVector.dot(ab, ap) / PVector.dot(ab, ab); - - t = constrain(t, 0.0, 1.0); - - PVector c = PVector.add(a, PVector.mult(ab, t)); - - return PVector.sub(p, c).mag() - r; -} - -/* @brief Calculate distance from a torus */ -float TorusDist(PVector p, float r1, float r2) { - PVector r = new PVector(p.x, p.z); - PVector q = new PVector(r.mag() - r1, p.y); - return q.mag() - r2; -} - -/* @brief Calculate distance from a box */ -float BoxDist(PVector p, PVector s) { - PVector d = PVector.sub(abs(p), s); - float e = max(d, 0.0).mag(); - float i = min(max(d.x, max(d.y, d.z)), 0.0); - return e + i; -} - -/* @brief Calculate distance from a cylinder */ -float CylinderDist(PVector p, PVector a, PVector b, float r) { - PVector ab = PVector.sub(b, a); - PVector ap = PVector.sub(p, a); - - float t = PVector.dot(ab, ap) / PVector.dot(ab, ab); - - PVector c = PVector.add(a, PVector.mult(ab, t)); - float x = PVector.sub(p, c).mag() - r; - float y = (abs(t - 0.5) - 0.5) * ab.mag(); - float e = max(new PVector(x, y), 0.0).mag(); - float i = min(max(x, y), 0.0); - - return e + i; -} - -float GetDist(PVector p) { - float sd, pd, cd, ccd, td, bd; - - PMatrix3D so = Move(0, 1, 5); - PVector sp = so.mult(p, null); - sd = SphereDist(sp, 1.0); - - pd = PlaneDist(p); - - cd = CapsuleDist(p, new PVector(3, 1, 8), new PVector(3, 3, 8), .5); - ccd = CylinderDist(p, new PVector(2, 1, 6), new PVector(3, 1, 4), .5); - - PMatrix3D to = Move(-3, 1, 6); - PMatrix3D tr = Rotate(iTime, 0., 0.); - to.preApply(tr); - PVector tp = to.mult(p, null); - td = TorusDist(tp, 1.0, 0.25); - - PMatrix3D bo = Move(0, 1, 8); - PMatrix3D br = Rotate(0., iTime * .2, 0.); - bo.preApply(br); - PVector bp = bo.mult(p, null); - bd = BoxDist(bp, new PVector(1, 1, 1)); - - return min(min(min(ccd, bd), min(sd, td)), min(pd, cd)); -} - -PVector GetNormal(PVector p) { - PVector e = new PVector(0.01, 0.0); - float d = GetDist(p); - PVector px = new PVector(e.x, e.y, e.y); - PVector py = new PVector(e.y, e.x, e.y); - PVector pz = new PVector(e.y, e.y, e.x); - PVector n = new PVector( - d - GetDist(PVector.sub(p, px)), - d - GetDist(PVector.sub(p, py)), - d - GetDist(PVector.sub(p, pz))); - n.normalize(); - return n; -} - -float RayMarch(PVector ro, PVector rd) { - // distance from origin - float dO = 0.0; - - for (int i = 0; i < MAX_STEPS; i++) { - PVector p = PVector.add(ro, PVector.mult(rd, dO)); - // distance to the scene - float dS = GetDist(p); - if (dS < SURF_DIST) - break; - dO += dS; - // prevent ray from escaping the scene - if (dO > MAX_DIST) - break; - } - - return dO; -} - -float GetLight(PVector p) { - PVector lightPos = new PVector(0.0, 5.0, 6.0); - - lightPos.x += sin(iTime) * 2.0; - lightPos.z += cos(iTime) * 2.0; - - PVector l = PVector.sub(lightPos, p); - l.normalize(); - - PVector n = GetNormal(p); - - float diff = PVector.dot(n, l); - - float d = RayMarch(PVector.add(p, PVector.mult(n, SURF_DIST)), l); - - if (d < lightPos.mag()) - diff *= 0.1; - - return constrain(diff, 0.0, 1.0); -} - -void draw() { - iTime = frameCount / 50.0; - - for (int y = 0; y < HEIGHT; y++) { - for (int x = 0; x < WIDTH; x++) { - // Normalized pixel coordinates (from -1.0 to 1.0) - PVector uv = new PVector( - (float)x * 2.0 / WIDTH - 1.0, - 1.0 - (float)y * 2.0 / HEIGHT); - - // Simple camera model - PVector ro = new PVector(0.0, 4.0, 0.0); - PVector rd = new PVector(uv.x, uv.y - 0.5, 1.0); - rd.normalize(); - - float d = RayMarch(ro, rd); - - PVector p = PVector.add(ro, PVector.mult(rd, d)); - - float diff = GetLight(p); - - diff = constrain(diff, 0.0, 1.0); - - set(x, y, color(int(diff * 255.0))); - } - } -} From a3eaea800f5d52a9049bc74a3928f03dbff21a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Thu, 1 Dec 2022 18:39:33 +0100 Subject: [PATCH 07/10] Apply some optimization and measure time spend on each frame. --- prototypes/donut/Makefile | 3 +- prototypes/donut/donut.c | 143 +++++++++++++++++++++++++------------- 2 files changed, 98 insertions(+), 48 deletions(-) diff --git a/prototypes/donut/Makefile b/prototypes/donut/Makefile index d9399572..f45b0086 100644 --- a/prototypes/donut/Makefile +++ b/prototypes/donut/Makefile @@ -1,4 +1,5 @@ -CFLAGS = -g -Og -Wall -Werror $(shell pkg-config --cflags sdl2) +CC = clang-15 +CFLAGS = -g -O3 -ffast-math -Wall -Werror $(shell pkg-config --cflags sdl2) LDLIBS = $(shell pkg-config --libs sdl2) -lm donut: donut.c diff --git a/prototypes/donut/donut.c b/prototypes/donut/donut.c index ce4e0929..6cc18f6f 100644 --- a/prototypes/donut/donut.c +++ b/prototypes/donut/donut.c @@ -1,8 +1,8 @@ #include #include -static const int WIDTH = 640/4; -static const int HEIGHT = 512/4; +static const int WIDTH = 640; +static const int HEIGHT = 512; static const int MAX_STEPS = 100; static const float MAX_DIST = 100.0; @@ -21,43 +21,55 @@ typedef struct mat4 { float m30, m31, m32, m33; } mat4; -static float clamp(float v, float min, float max) { +/* https://en.wikipedia.org/wiki/Fast_inverse_square_root */ +float Q_rsqrt(float number) { + union { + float f; + uint32_t i; + } conv = {.f = number}; + conv.i = 0x5f3759df - (conv.i >> 1); + conv.f *= 1.5f - (number * 0.5f * conv.f * conv.f); + return conv.f; +} + +float clamp(float v, float min, float max) { return fmin(fmax(v, min), max); } -static vec3 v3_abs(vec3 p) { +vec3 v3_abs(vec3 p) { return (vec3){fabs(p.x), fabs(p.y), fabs(p.z)}; } -static vec3 v3_max(vec3 p, float v) { +vec3 v3_max(vec3 p, float v) { return (vec3){fmax(p.x, v), fmax(p.y, v), fmax(p.z, v)}; } -static vec3 v3_add(vec3 a, vec3 b) { +vec3 v3_add(vec3 a, vec3 b) { return (vec3){a.x + b.x, a.y + b.y, a.z + b.z}; } -static vec3 v3_sub(vec3 a, vec3 b) { +vec3 v3_sub(vec3 a, vec3 b) { return (vec3){a.x - b.x, a.y - b.y, a.z - b.z}; } -static vec3 v3_mul(vec3 a, float v) { +vec3 v3_mul(vec3 a, float v) { return (vec3){a.x * v, a.y * v, a.z * v}; } -static float v3_dot(vec3 a, vec3 b) { +float v3_dot(vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; } -static float v3_length(vec3 p) { +float v3_length(vec3 p) { return sqrtf(p.x * p.x + p.y * p.y + p.z * p.z); } -static vec3 v3_normalize(vec3 p) { - return v3_mul(p, 1.0 / v3_length(p)); +vec3 v3_normalize(vec3 p) { + /* return v3_mul(p, 1.0f / v3_length(p)); */ + return v3_mul(p, Q_rsqrt(p.x * p.x + p.y * p.y + p.z * p.z)); } -static mat4 m4_mul(mat4 a, mat4 b) { +mat4 m4_mul(mat4 a, mat4 b) { mat4 c; float *ma = (float *)&a.m00; @@ -77,35 +89,35 @@ static mat4 m4_mul(mat4 a, mat4 b) { } /* @brief Rotate around X axis */ -static mat4 m4_rotateX(float angle) { - float s = sin(angle); - float c = cos(angle); - return (mat4) {1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1}; +mat4 m4_rotateX(float angle) { + float s = sinf(angle); + float c = cosf(angle); + return (mat4){1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1}; } /* @brief Rotate around Y axis */ -static mat4 m4_rotateY(float angle) { - float s = sin(angle); - float c = cos(angle); +mat4 m4_rotateY(float angle) { + float s = sinf(angle); + float c = cosf(angle); return (mat4){c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1}; } /* @brief Rotate around Z axis */ -static mat4 m4_rotateZ(float angle) { - float s = sin(angle); - float c = cos(angle); +mat4 m4_rotateZ(float angle) { + float s = sinf(angle); + float c = cosf(angle); return (mat4){c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; } -static mat4 m4_move(float x, float y, float z) { +mat4 m4_move(float x, float y, float z) { return (mat4){1, 0, 0, -x, 0, 1, 0, -y, 0, 0, 1, -z, 0, 0, 0, 1}; } -static mat4 m4_rotate(float x, float y, float z) { +mat4 m4_rotate(float x, float y, float z) { return m4_mul(m4_mul(m4_rotateX(x), m4_rotateY(y)), m4_rotateZ(z)); } -static vec3 m4_translate(vec3 p, mat4 a) { +vec3 m4_translate(vec3 p, mat4 a) { vec3 r; p.w = 1.0; @@ -179,32 +191,69 @@ float CylinderDist(vec3 p, vec3 a, vec3 b, float r) { vec3 c = v3_add(a, v3_mul(ab, t)); float x = v3_length(v3_sub(p, c)) - r; - float y = (abs(t - 0.5) - 0.5) * v3_length(ab); + float y = (fabs(t - 0.5) - 0.5) * v3_length(ab); float e = v3_length(v3_max((vec3){x, y}, 0.0)); float i = fmin(fmax(x, y), 0.0); return e + i; } +static mat4 bm, tm, sm; +static vec3 lightPos; + +void PerFrame(void) { + iTime = SDL_GetTicks() / 1000.0; + + sm = m4_move(0, 1, 5); + + mat4 to = m4_move(-3, 1, 6); + mat4 tr = m4_rotate(iTime, 0., 0.); + tm = m4_mul(tr, to); + + mat4 bo = m4_move(0, 1, 8); + mat4 br = m4_rotate(0., iTime * .2, 0.); + bm = m4_mul(br, bo); + + lightPos = (vec3){0.0, 5.0, 6.0}; + lightPos.x += sinf(iTime) * 2.0; + lightPos.z += cosf(iTime) * 2.0; +} + float GetDist(vec3 p) { - mat4 so = m4_move(0, 1, 5); - vec3 sp = m4_translate(p, so); +#if 1 + vec3 sp = m4_translate(p, sm); float sd = SphereDist(sp, 1.0); +#else + float sd = MAX_DIST; +#endif +#if 1 float pd = PlaneDist(p); - +#else + float pd = MAX_DIST; +#endif + +#if 1 float cd = CapsuleDist(p, (vec3){3, 1, 8}, (vec3){3, 3, 8}, .5); +#else + float cd = MAX_DIST; +#endif + +#if 1 float ccd = CylinderDist(p, (vec3){2, 1, 6}, (vec3){3, 1, 4}, .5); +#else + float ccd = MAX_DIST; +#endif - mat4 to = m4_move(-3, 1, 6); - mat4 tr = m4_rotate(iTime, 0., 0.); - vec3 tp = m4_translate(p, m4_mul(tr, to)); + vec3 tp = m4_translate(p, tm); float td = TorusDist(tp, 1.0, 0.25); - mat4 bo = m4_move(0, 1, 8); - mat4 br = m4_rotate(0., iTime * .2, 0.); - vec3 bp = m4_translate(p, m4_mul(br, bo)); +#if 1 + vec3 bp = m4_translate(p, bm); float bd = BoxDist(bp, (vec3){1, 1, 1}); +#else + float bd = MAX_DIST; +#endif return fmin(fmin(fmin(ccd, bd), fmin(sd, td)), fmin(pd, cd)); } @@ -238,11 +287,6 @@ float RayMarch(vec3 ro, vec3 rd) { } float GetLight(vec3 p) { - vec3 lightPos = {0.0, 5.0, 6.0}; - - lightPos.x += sinf(iTime) * 2.0; - lightPos.z += cosf(iTime) * 2.0; - vec3 l = v3_normalize(v3_sub(lightPos, p)); vec3 n = GetNormal(p); @@ -256,18 +300,17 @@ float GetLight(vec3 p) { return clamp(diff, 0.0, 1.0); } -void Draw(SDL_Surface *canvas) { +void Render(SDL_Surface *canvas) { Uint32 *buffer = (Uint32 *)canvas->pixels; - iTime = SDL_GetTicks() / 1000.0; + uint32_t start = SDL_GetTicks(); for (int y = 0; y < HEIGHT; y++) { // SDL_Log("y = %d\n", y); for (int x = 0; x < WIDTH; x++) { // Normalized pixel coordinates (from -1.0 to 1.0) - vec3 uv = (vec3){ - (float)x * 2.0 / WIDTH - 1.0, - 1.0 - (float)y * 2.0 / HEIGHT}; + vec3 uv = + (vec3){(float)x * 2.0 / WIDTH - 1.0, 1.0 - (float)y * 2.0 / HEIGHT}; // Simple camera model vec3 ro = (vec3){0.0, 4.0, 0.0}; @@ -285,6 +328,10 @@ void Draw(SDL_Surface *canvas) { buffer[y * WIDTH + x] = SDL_MapRGBA(canvas->format, c, c, c, 255); } } + + uint32_t end = SDL_GetTicks(); + + SDL_Log("Render took %dms\n", end - start); } int main(void) { @@ -308,8 +355,10 @@ int main(void) { } } + PerFrame(); + SDL_LockSurface(canvas); - Draw(canvas); + Render(canvas); SDL_UnlockSurface(canvas); SDL_BlitSurface(canvas, 0, window_surface, 0); From ec48d77561a34c0c128e9bc370c6bf366625252a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Thu, 1 Dec 2022 18:41:58 +0100 Subject: [PATCH 08/10] Delete old effect, new one will be based on raymarching. --- effects/donut/Makefile | 10 ----- effects/donut/donut-pc.c | 18 --------- effects/donut/donut.c | 83 ---------------------------------------- effects/donut/donut.h | 67 -------------------------------- 4 files changed, 178 deletions(-) delete mode 100644 effects/donut/Makefile delete mode 100644 effects/donut/donut-pc.c delete mode 100644 effects/donut/donut.c delete mode 100644 effects/donut/donut.h diff --git a/effects/donut/Makefile b/effects/donut/Makefile deleted file mode 100644 index ac25eeee..00000000 --- a/effects/donut/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -TOPDIR := $(realpath ../..) - -CLEAN-FILES := data/drdos8x8.c donut-pc - -PSF2C.drdos8x8 := --name drdos8x8 --type console - -include $(TOPDIR)/build/effect.mk - -donut-pc: donut-pc.c donut.h - cc -g -Og -Wall -o $@ $< diff --git a/effects/donut/donut-pc.c b/effects/donut/donut-pc.c deleted file mode 100644 index b4f1d1b9..00000000 --- a/effects/donut/donut-pc.c +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include -#include -#include - -#include "donut.h" - -int main(void) { - for (;;) { - CalcDonut(); - - for (int k = 0; (W * H + 1) > k; k++) - putchar(k % W ? b[k] : 10); - usleep(15000); - printf("\x1b[%dA", H + 1); - } - return 0; -} diff --git a/effects/donut/donut.c b/effects/donut/donut.c deleted file mode 100644 index 90695b7d..00000000 --- a/effects/donut/donut.c +++ /dev/null @@ -1,83 +0,0 @@ -#include "effect.h" -#include "console.h" -#include "copper.h" -#include -#include -#include - -#define WIDTH 320 -#define HEIGHT 256 -#define DEPTH 1 - -#include "data/drdos8x8.c" - -static BitmapT *screen; -static CopListT *cp; -static ConsoleT console; - -static void Init(void) { - screen = NewBitmap(WIDTH, HEIGHT, DEPTH); - cp = NewCopList(100); - - SetupPlayfield(MODE_LORES, DEPTH, X(0), Y(0), WIDTH, HEIGHT); - SetColor(0, 0x000); - SetColor(1, 0xfff); - - CopInit(cp); - CopSetupBitplanes(cp, NULL, screen, DEPTH); - CopEnd(cp); - CopListActivate(cp); - EnableDMA(DMAF_RASTER); - - ConsoleInit(&console, &drdos8x8, screen); - - KeyboardInit(); -} - -static void Kill(void) { - DisableDMA(DMAF_COPPER | DMAF_RASTER); - - KeyboardKill(); - - DeleteCopList(cp); - DeleteBitmap(screen); -} - -static bool HandleEvent(void) { - EventT ev; - - if (!PopEvent(&ev)) - return true; - - if (ev.type != EV_KEY) - return true; - - if (ev.key.modifier & MOD_PRESSED) - return true; - - if (ev.key.code == KEY_ESCAPE) - return false; - - ConsoleDrawCursor(&console); - - ConsoleDrawCursor(&console); - - return true; -} - -#include "donut.h" - -static void Render(void) { - int k; - - ConsoleSetCursor(&console, 0, 0); - - CalcDonut(); - - for (k = 0; (W * H + 1) > k; k++) - ConsolePutChar(&console, k % W ? b[k] : 10); - - exitLoop = !HandleEvent(); -} - -EFFECT(KbdTest, NULL, NULL, Init, Kill, Render); diff --git a/effects/donut/donut.h b/effects/donut/donut.h deleted file mode 100644 index 92a4ce33..00000000 --- a/effects/donut/donut.h +++ /dev/null @@ -1,67 +0,0 @@ -/* Adapted from https://www.a1k0n.net/2021/01/13/optimizing-donut.html */ - -#define W 40 -#define H 32 -#define D 30 - -#define R(mul, shift, x, y) \ - _ = x; \ - x -= mul * y >> shift; \ - y += mul * _ >> shift; \ - _ = (3145728 - x * x - y * y) >> 11; \ - x = x * _ >> 10; \ - y = y * _ >> 10; - -static char b[W * H], z[W * H]; -static const char pixels[] = ".,-~:;=!*#$@"; - -void CalcDonut(void) { - static short sA = 1024, cA = 0, sB = 1024, cB = 0, _; - - short sj = 0, cj = 1024; - short i, j; - - memset(b, 32, W*H); // text buffer - memset(z, 127, W*H); // z buffer - - for (j = 0; j < 90; j++) { - // sine and cosine of angle i - short si = 0; - short ci = 1024; -#ifdef Log - Log("j = %d\n", j); -#endif - for (i = 0; i < 324; i++) { - short R1 = 1; - short R2 = 2048; - int K2 = 5120 * 1024; - - /* int */ short x0 = R1 * cj + R2; - /* int */ short x1 = ci * x0 >> 10; - /* int */ short x2 = cA * sj >> 10; - /* int */ short x3 = si * x0 >> 10; - /* int */ short x4 = R1 * x2 - (sA * x3 >> 10); - /* int */ short x5 = sA * sj >> 10; - int x6 = K2 + R1 * 1024 * x5 + cA * x3; - /* int */ short x7 = cj * si >> 10; - /* int */ short x = W / 2 + D * (cB * x1 - sB * x4) / x6; - /* int */ short y = H / 2 + D / 2 * (cB * x4 + sB * x1) / x6; - short _N1 = (-sA * x7 >> 10) + x2; - short _N2 = cj * sB >> 10; - int N = (((-cA * x7 - cB * _N1 - ci * _N2) >> 10) - x5) >> 7; - - int o = x + W * y; - char zz = (x6 - K2) >> 15; - - if (H > y && y >= 0 && x >= 0 && W > x && zz < z[o]) { - z[o] = zz; - b[o] = pixels[N > 0 ? N : 0]; - } - R(5, 8, ci, si) // rotate i - } - R(9, 7, cj, sj) // rotate j - } - - R(5, 7, cA, sA); - R(5, 8, cB, sB); -} From 4d0c978d431e96002d81da636e599d43b91d718f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Mon, 5 Dec 2022 19:54:02 +0100 Subject: [PATCH 09/10] raymarch: direct conversion from shadertoy to C --- prototypes/raymarch/Makefile | 8 + prototypes/raymarch/raymarch.c | 373 +++++++++++++++++++++++++++++++++ 2 files changed, 381 insertions(+) create mode 100644 prototypes/raymarch/Makefile create mode 100644 prototypes/raymarch/raymarch.c diff --git a/prototypes/raymarch/Makefile b/prototypes/raymarch/Makefile new file mode 100644 index 00000000..2c95e1c9 --- /dev/null +++ b/prototypes/raymarch/Makefile @@ -0,0 +1,8 @@ +CC = clang +CFLAGS = -g -O3 -ffast-math -Wall -Werror $(shell pkg-config --cflags sdl2) +LDLIBS = $(shell pkg-config --libs sdl2) -lm + +raymarch: raymarch.c + +clean: + rm -rf raymarch raymarch.dSYM *~ diff --git a/prototypes/raymarch/raymarch.c b/prototypes/raymarch/raymarch.c new file mode 100644 index 00000000..6cc18f6f --- /dev/null +++ b/prototypes/raymarch/raymarch.c @@ -0,0 +1,373 @@ +#include +#include + +static const int WIDTH = 640; +static const int HEIGHT = 512; + +static const int MAX_STEPS = 100; +static const float MAX_DIST = 100.0; +static const float SURF_DIST = 0.01; + +static float iTime; + +typedef struct vec3 { + float x, y, z, w; +} vec3; + +typedef struct mat4 { + float m00, m01, m02, m03; + float m10, m11, m12, m13; + float m20, m21, m22, m23; + float m30, m31, m32, m33; +} mat4; + +/* https://en.wikipedia.org/wiki/Fast_inverse_square_root */ +float Q_rsqrt(float number) { + union { + float f; + uint32_t i; + } conv = {.f = number}; + conv.i = 0x5f3759df - (conv.i >> 1); + conv.f *= 1.5f - (number * 0.5f * conv.f * conv.f); + return conv.f; +} + +float clamp(float v, float min, float max) { + return fmin(fmax(v, min), max); +} + +vec3 v3_abs(vec3 p) { + return (vec3){fabs(p.x), fabs(p.y), fabs(p.z)}; +} + +vec3 v3_max(vec3 p, float v) { + return (vec3){fmax(p.x, v), fmax(p.y, v), fmax(p.z, v)}; +} + +vec3 v3_add(vec3 a, vec3 b) { + return (vec3){a.x + b.x, a.y + b.y, a.z + b.z}; +} + +vec3 v3_sub(vec3 a, vec3 b) { + return (vec3){a.x - b.x, a.y - b.y, a.z - b.z}; +} + +vec3 v3_mul(vec3 a, float v) { + return (vec3){a.x * v, a.y * v, a.z * v}; +} + +float v3_dot(vec3 a, vec3 b) { + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +float v3_length(vec3 p) { + return sqrtf(p.x * p.x + p.y * p.y + p.z * p.z); +} + +vec3 v3_normalize(vec3 p) { + /* return v3_mul(p, 1.0f / v3_length(p)); */ + return v3_mul(p, Q_rsqrt(p.x * p.x + p.y * p.y + p.z * p.z)); +} + +mat4 m4_mul(mat4 a, mat4 b) { + mat4 c; + + float *ma = (float *)&a.m00; + float *mb = (float *)&b.m00; + float *mc = (float *)&c.m00; + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + mc[i * 4 + j] = 0; + for (int k = 0; k < 4; k++) { + mc[i * 4 + j] += ma[i * 4 + k] * mb[k * 4 + j]; + } + } + } + + return c; +} + +/* @brief Rotate around X axis */ +mat4 m4_rotateX(float angle) { + float s = sinf(angle); + float c = cosf(angle); + return (mat4){1, 0, 0, 0, 0, c, -s, 0, 0, s, c, 0, 0, 0, 0, 1}; +} + +/* @brief Rotate around Y axis */ +mat4 m4_rotateY(float angle) { + float s = sinf(angle); + float c = cosf(angle); + return (mat4){c, 0, s, 0, 0, 1, 0, 0, -s, 0, c, 0, 0, 0, 0, 1}; +} + +/* @brief Rotate around Z axis */ +mat4 m4_rotateZ(float angle) { + float s = sinf(angle); + float c = cosf(angle); + return (mat4){c, -s, 0, 0, s, c, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; +} + +mat4 m4_move(float x, float y, float z) { + return (mat4){1, 0, 0, -x, 0, 1, 0, -y, 0, 0, 1, -z, 0, 0, 0, 1}; +} + +mat4 m4_rotate(float x, float y, float z) { + return m4_mul(m4_mul(m4_rotateX(x), m4_rotateY(y)), m4_rotateZ(z)); +} + +vec3 m4_translate(vec3 p, mat4 a) { + vec3 r; + + p.w = 1.0; + + float *vp = (float *)&p.x; + float *vr = (float *)&r.x; + float *ma = (float *)&a.m00; + + for (int i = 0; i < 4; i++) { + vr[i] = 0; + for (int j = 0; j < 4; j++) { + vr[i] += ma[i * 4 + j] * vp[j]; + } + } + + return r; +} + +/* @brief Calculate distance from sphere + * @param p point of interest + * @param r sphere radius + * @return distance of s from p + */ +float SphereDist(vec3 p, float r) { + return v3_length(p) - r; +} + +/* @brief Calculate distance from plane at (0,0,0) origin + * @param p point of interest + * @return distance of plane from p + */ +float PlaneDist(vec3 p) { + return p.y; +} + +/* @brief Calculate distance from a capsule */ +float CapsuleDist(vec3 p, vec3 a, vec3 b, float r) { + vec3 ab = v3_sub(b, a); + vec3 ap = v3_sub(p, a); + + float t = v3_dot(ab, ap) / v3_dot(ab, ab); + + t = clamp(t, 0.0, 1.0); + + vec3 c = v3_add(a, v3_mul(ab, t)); + + return v3_length(v3_sub(p, c)) - r; +} + +/* @brief Calculate distance from a torus */ +float TorusDist(vec3 p, float r1, float r2) { + vec3 r = {p.x, p.z}; + vec3 q = {v3_length(r) - r1, p.y}; + return v3_length(q) - r2; +} + +/* @brief Calculate distance from a box */ +float BoxDist(vec3 p, vec3 s) { + vec3 d = v3_sub(v3_abs(p), s); + float e = v3_length(v3_max(d, 0.0)); + float i = fmin(fmax(d.x, fmax(d.y, d.z)), 0.0); + return e + i; +} + +/* @brief Calculate distance from a cylinder */ +float CylinderDist(vec3 p, vec3 a, vec3 b, float r) { + vec3 ab = v3_sub(b, a); + vec3 ap = v3_sub(p, a); + + float t = v3_dot(ab, ap) / v3_dot(ab, ab); + + vec3 c = v3_add(a, v3_mul(ab, t)); + float x = v3_length(v3_sub(p, c)) - r; + float y = (fabs(t - 0.5) - 0.5) * v3_length(ab); + float e = v3_length(v3_max((vec3){x, y}, 0.0)); + float i = fmin(fmax(x, y), 0.0); + + return e + i; +} + +static mat4 bm, tm, sm; +static vec3 lightPos; + +void PerFrame(void) { + iTime = SDL_GetTicks() / 1000.0; + + sm = m4_move(0, 1, 5); + + mat4 to = m4_move(-3, 1, 6); + mat4 tr = m4_rotate(iTime, 0., 0.); + tm = m4_mul(tr, to); + + mat4 bo = m4_move(0, 1, 8); + mat4 br = m4_rotate(0., iTime * .2, 0.); + bm = m4_mul(br, bo); + + lightPos = (vec3){0.0, 5.0, 6.0}; + lightPos.x += sinf(iTime) * 2.0; + lightPos.z += cosf(iTime) * 2.0; +} + +float GetDist(vec3 p) { +#if 1 + vec3 sp = m4_translate(p, sm); + float sd = SphereDist(sp, 1.0); +#else + float sd = MAX_DIST; +#endif + +#if 1 + float pd = PlaneDist(p); +#else + float pd = MAX_DIST; +#endif + +#if 1 + float cd = CapsuleDist(p, (vec3){3, 1, 8}, (vec3){3, 3, 8}, .5); +#else + float cd = MAX_DIST; +#endif + +#if 1 + float ccd = CylinderDist(p, (vec3){2, 1, 6}, (vec3){3, 1, 4}, .5); +#else + float ccd = MAX_DIST; +#endif + + vec3 tp = m4_translate(p, tm); + float td = TorusDist(tp, 1.0, 0.25); + +#if 1 + vec3 bp = m4_translate(p, bm); + float bd = BoxDist(bp, (vec3){1, 1, 1}); +#else + float bd = MAX_DIST; +#endif + + return fmin(fmin(fmin(ccd, bd), fmin(sd, td)), fmin(pd, cd)); +} + +vec3 GetNormal(vec3 p) { + float d = GetDist(p); + vec3 e = (vec3){0.01, 0.0}; + vec3 n = (vec3){d - GetDist(v3_sub(p, (vec3){e.x, e.y, e.y})), + d - GetDist(v3_sub(p, (vec3){e.y, e.x, e.y})), + d - GetDist(v3_sub(p, (vec3){e.y, e.y, e.x}))}; + return v3_normalize(n); +} + +float RayMarch(vec3 ro, vec3 rd) { + // distance from origin + float dO = 0.0; + + for (int i = 0; i < MAX_STEPS; i++) { + vec3 p = v3_add(ro, v3_mul(rd, dO)); + // distance to the scene + float dS = GetDist(p); + if (dS < SURF_DIST) + break; + dO += dS; + // prevent ray from escaping the scene + if (dO > MAX_DIST) + break; + } + + return dO; +} + +float GetLight(vec3 p) { + vec3 l = v3_normalize(v3_sub(lightPos, p)); + vec3 n = GetNormal(p); + + float diff = v3_dot(n, l); + + float d = RayMarch(v3_add(p, v3_mul(n, SURF_DIST)), l); + + if (d < v3_length(lightPos)) + diff *= 0.1; + + return clamp(diff, 0.0, 1.0); +} + +void Render(SDL_Surface *canvas) { + Uint32 *buffer = (Uint32 *)canvas->pixels; + + uint32_t start = SDL_GetTicks(); + + for (int y = 0; y < HEIGHT; y++) { + // SDL_Log("y = %d\n", y); + for (int x = 0; x < WIDTH; x++) { + // Normalized pixel coordinates (from -1.0 to 1.0) + vec3 uv = + (vec3){(float)x * 2.0 / WIDTH - 1.0, 1.0 - (float)y * 2.0 / HEIGHT}; + + // Simple camera model + vec3 ro = (vec3){0.0, 4.0, 0.0}; + vec3 rd = v3_normalize((vec3){uv.x, uv.y - 0.5, 1.0}); + + float d = RayMarch(ro, rd); + + vec3 p = v3_add(ro, v3_mul(rd, d)); + + float diff = GetLight(p); + + diff = clamp(diff, 0.0, 1.0); + + int c = (int)(diff * 255.0); + buffer[y * WIDTH + x] = SDL_MapRGBA(canvas->format, c, c, c, 255); + } + } + + uint32_t end = SDL_GetTicks(); + + SDL_Log("Render took %dms\n", end - start); +} + +int main(void) { + SDL_Init(SDL_INIT_EVERYTHING); + + SDL_Window *window = + SDL_CreateWindow("Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + WIDTH, HEIGHT, 0); + + SDL_Surface *window_surface = SDL_GetWindowSurface(window); + + SDL_Surface *canvas = SDL_CreateRGBSurfaceWithFormat( + 0, WIDTH, HEIGHT, 32, SDL_PIXELFORMAT_RGBA8888); + + int quit = 0; + SDL_Event event; + while (!quit) { + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + quit = 1; + } + } + + PerFrame(); + + SDL_LockSurface(canvas); + Render(canvas); + SDL_UnlockSurface(canvas); + + SDL_BlitSurface(canvas, 0, window_surface, 0); + SDL_UpdateWindowSurface(window); + + SDL_Delay(10); + } + + SDL_Quit(); + + return 0; +} From 6a79fa43f337a87ef1ca2cfd17c75def777eb7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krystian=20Bac=C5=82awski?= Date: Mon, 5 Dec 2022 19:57:30 +0100 Subject: [PATCH 10/10] Focus on playing with a donut --- prototypes/donut/Makefile | 4 +- prototypes/donut/donut.c | 110 +++----------------------------------- 2 files changed, 9 insertions(+), 105 deletions(-) diff --git a/prototypes/donut/Makefile b/prototypes/donut/Makefile index f45b0086..db0a6b1c 100644 --- a/prototypes/donut/Makefile +++ b/prototypes/donut/Makefile @@ -1,8 +1,8 @@ -CC = clang-15 +CC = clang CFLAGS = -g -O3 -ffast-math -Wall -Werror $(shell pkg-config --cflags sdl2) LDLIBS = $(shell pkg-config --libs sdl2) -lm donut: donut.c clean: - rm donut *~ + rm -rf donut donut.dSYM *~ diff --git a/prototypes/donut/donut.c b/prototypes/donut/donut.c index 6cc18f6f..4f50ee17 100644 --- a/prototypes/donut/donut.c +++ b/prototypes/donut/donut.c @@ -136,126 +136,30 @@ vec3 m4_translate(vec3 p, mat4 a) { return r; } -/* @brief Calculate distance from sphere - * @param p point of interest - * @param r sphere radius - * @return distance of s from p - */ -float SphereDist(vec3 p, float r) { - return v3_length(p) - r; -} - -/* @brief Calculate distance from plane at (0,0,0) origin - * @param p point of interest - * @return distance of plane from p - */ -float PlaneDist(vec3 p) { - return p.y; -} - -/* @brief Calculate distance from a capsule */ -float CapsuleDist(vec3 p, vec3 a, vec3 b, float r) { - vec3 ab = v3_sub(b, a); - vec3 ap = v3_sub(p, a); - - float t = v3_dot(ab, ap) / v3_dot(ab, ab); - - t = clamp(t, 0.0, 1.0); - - vec3 c = v3_add(a, v3_mul(ab, t)); - - return v3_length(v3_sub(p, c)) - r; -} - /* @brief Calculate distance from a torus */ float TorusDist(vec3 p, float r1, float r2) { - vec3 r = {p.x, p.z}; - vec3 q = {v3_length(r) - r1, p.y}; - return v3_length(q) - r2; + float r = sqrtf(p.x * p.x + p.z * p.z) - r1; + float q = sqrtf(r * r + p.y * p.y); + return q - r2; } -/* @brief Calculate distance from a box */ -float BoxDist(vec3 p, vec3 s) { - vec3 d = v3_sub(v3_abs(p), s); - float e = v3_length(v3_max(d, 0.0)); - float i = fmin(fmax(d.x, fmax(d.y, d.z)), 0.0); - return e + i; -} - -/* @brief Calculate distance from a cylinder */ -float CylinderDist(vec3 p, vec3 a, vec3 b, float r) { - vec3 ab = v3_sub(b, a); - vec3 ap = v3_sub(p, a); - - float t = v3_dot(ab, ap) / v3_dot(ab, ab); - - vec3 c = v3_add(a, v3_mul(ab, t)); - float x = v3_length(v3_sub(p, c)) - r; - float y = (fabs(t - 0.5) - 0.5) * v3_length(ab); - float e = v3_length(v3_max((vec3){x, y}, 0.0)); - float i = fmin(fmax(x, y), 0.0); - - return e + i; -} - -static mat4 bm, tm, sm; +static mat4 tm; static vec3 lightPos; void PerFrame(void) { iTime = SDL_GetTicks() / 1000.0; - sm = m4_move(0, 1, 5); - - mat4 to = m4_move(-3, 1, 6); - mat4 tr = m4_rotate(iTime, 0., 0.); + mat4 to = m4_move(0, 0, 6); + mat4 tr = m4_rotate(iTime, iTime, 0.); tm = m4_mul(tr, to); - mat4 bo = m4_move(0, 1, 8); - mat4 br = m4_rotate(0., iTime * .2, 0.); - bm = m4_mul(br, bo); - lightPos = (vec3){0.0, 5.0, 6.0}; lightPos.x += sinf(iTime) * 2.0; lightPos.z += cosf(iTime) * 2.0; } float GetDist(vec3 p) { -#if 1 - vec3 sp = m4_translate(p, sm); - float sd = SphereDist(sp, 1.0); -#else - float sd = MAX_DIST; -#endif - -#if 1 - float pd = PlaneDist(p); -#else - float pd = MAX_DIST; -#endif - -#if 1 - float cd = CapsuleDist(p, (vec3){3, 1, 8}, (vec3){3, 3, 8}, .5); -#else - float cd = MAX_DIST; -#endif - -#if 1 - float ccd = CylinderDist(p, (vec3){2, 1, 6}, (vec3){3, 1, 4}, .5); -#else - float ccd = MAX_DIST; -#endif - - vec3 tp = m4_translate(p, tm); - float td = TorusDist(tp, 1.0, 0.25); - -#if 1 - vec3 bp = m4_translate(p, bm); - float bd = BoxDist(bp, (vec3){1, 1, 1}); -#else - float bd = MAX_DIST; -#endif - - return fmin(fmin(fmin(ccd, bd), fmin(sd, td)), fmin(pd, cd)); + return TorusDist(m4_translate(p, tm), 2.0, 0.666); } vec3 GetNormal(vec3 p) {