Skip to content

Instantly share code, notes, and snippets.

@hyperlogic
Last active December 6, 2019 04:24
Show Gist options
  • Save hyperlogic/5c11f3c86970a5d2a1ac0e8367708ef6 to your computer and use it in GitHub Desktop.
Save hyperlogic/5c11f3c86970a5d2a1ac0e8367708ef6 to your computer and use it in GitHub Desktop.
ctoy 2d sdf renderer
static struct m_image buffer = M_IMAGE_IDENTITY();
#include <math.h>
#include <m_math.h>
#include <m_raster.h>
typedef struct Prim_t {
int type; // 0 = sphere, 1 = box
float m[6];
float inv_m[6];
float r[2];
} Prim;
#define NUM_PRIMS 20
Prim prims[NUM_PRIMS];
static float sign(float v) {
return v > 0.0f ? 1.0f : 0.0f;
}
// transform point p by the 2x3 homogenous matrix m
// | m[0] m[2] m[4] | | p[0] | | r[0] |
// | m[1] m[3] m[5] | * | p[1] | = | r[1] |
// | 0 0 1 | | 1 | | |
static float* xform_2x3(float *r, float *m, float *p) {
float temp[2];
temp[0] = m[0] * p[0] + m[2] * p[1] + m[4];
temp[1] = m[1] * p[0] + m[3] * p[1] + m[5];
r[0] = temp[0];
r[1] = temp[1];
}
// transform p by a 2x2 matrix.
// | m[0] m[2] | * | p[0] | = | r[0] |
// | m[1] m[3] | | p[1] | | r[1] |
static void xform_2x2(float *r, float *m, float *p) {
float temp[2];
temp[0] = m[0] * p[0] + m[2] * p[1];
temp[1] = m[1] * p[0] + m[3] * p[1];
r[0] = temp[0];
r[1] = temp[1];
}
// transpose a 2x2 matrix
static void transpose_2x2(float *r, float *m) {
r[0] = m[0];
float temp = m[1];
r[1] = m[2];
r[2] = temp;
r[3] = m[3];
}
// invert a orthonormal 2x3 matrix.
static void orthonormal_invert_2x3(float *r, float *m) {
transpose_2x2(r, m);
float trans[2] = {-m[4], -m[5]};
xform_2x2(trans, r, trans);
r[4] = trans[0];
r[5] = trans[1];
}
static float min(float a, float b) {
return (a < b) ? a : b;
}
static float max(float a, float b) {
return (a <= b) ? b : a;
}
static float sdf_box(float *p, Prim* prim) {
// vec2 d = abs(p) - r;
// return length(max(d, vec2(0))) + min(max(d.x, d.y), 0.0);
float d[2] = {fabs(p[0]) - prim->r[0], fabs(p[1]) - prim->r[1]};
float m[2] = {max(d[0], 0.0f), max(d[1], 0.0f)};
float a = sqrt(m[0] * m[0] + m[1] * m[1]);
float b = min(max(d[0], d[1]), 0.0f);
return a + b;
}
static float sdf_sphere(float *p, Prim* prim) {
// return length(p) - r;
return sqrtf(p[0] * p[0] + p[1] * p[1]) - prim->r[0];
}
static float sdf_prim(float* p, Prim* prim) {
float local_p[2];
switch (prim->type) {
default:
case 0:
// Shortcut: don't need to rotate by inv_m, just translate.
local_p[0] = p[0] + prim->inv_m[4];
local_p[1] = p[1] + prim->inv_m[5];
return sdf_sphere(local_p, prim);
case 1:
// transform from global into local space
xform_2x3(local_p, prim->inv_m, p);
return sdf_box(local_p, prim);
}
}
// evaluate sdf at point p
static float map(float* p) {
float dist = FLT_MAX;
int i;
for (i = 0; i < NUM_PRIMS; i++) {
dist = min(dist, sdf_prim(p, prims + i));
}
return dist;
}
static void draw(void) {
int x, y;
for (y = 0; y < buffer.height; y++) {
for (x = 0; x < buffer.width; x++) {
float *pixel = (float *)buffer.data + (y * buffer.width + x) * 3;
// convert from "viewport" coordinates into "world" space
float p[2] = {(float)x / (float)buffer.width, (float)y / (float)buffer.height};
float dist = map(p);
// convert dist into a color (with fancy stripes)
float s1 = 1.0f - expf(-3.0f * fabsf(dist));
float s2 = 0.9f + 0.3f * cosf(180.0f * dist);
float shade = s1 * s2;
float color[3] = {(1.0f - sign(dist) * 0.1f) * shade,
(1.0f - sign(dist) * 0.4f) * shade,
(1.0f - sign(dist) * 0.7f) * shade};
pixel[0] = color[0];
pixel[1] = color[1];
pixel[2] = color[2];
}
}
}
static void random_matrix(float* m) {
float theta = m_randf() * 2.0f * M_PI;
m[0] = cosf(theta);
m[1] = sinf(theta);
m[2] = m[1];
m[3] = -m[0];
m[4] = m_randf();
m[5] = m_randf();
}
void ctoy_begin(void) {
ctoy_window_title("SDF");
ctoy_window_size(512, 512);
m_image_create(&buffer, M_FLOAT, 256, 256, 3);
// initialize prims.
prims[0].type = 0;
prims[0].m[0] = 1.0f; prims[0].m[2] = 0.0f; prims[0].m[4] = 0.0f;
prims[0].m[1] = 0.0f; prims[0].m[3] = 1.0f; prims[0].m[5] = 0.0f;
orthonormal_invert_2x3(prims[0].inv_m, prims[0].m);
prims[0].r[0] = 0.5f;
prims[0].r[1] = 0.5f;
prims[1].type = 0;
prims[1].m[0] = 1.0f; prims[1].m[2] = 0.0f; prims[1].m[4] = 0.0f;
prims[1].m[1] = 0.0f; prims[1].m[3] = 1.0f; prims[1].m[5] = 0.0f;
orthonormal_invert_2x3(prims[1].inv_m, prims[1].m);
prims[1].r[0] = 0.2f;
prims[1].r[1] = 0.1f;
prims[2].type = 1;
prims[2].m[0] = 1.0f; prims[2].m[2] = 0.0f; prims[2].m[4] = 0.2f;
prims[2].m[1] = 0.0f; prims[2].m[3] = 1.0f; prims[2].m[5] = 0.5f;
orthonormal_invert_2x3(prims[2].inv_m, prims[2].m);
prims[2].r[0] = 0.5f;
prims[2].r[1] = 0.05f;
int i;
for (i = 3; i < NUM_PRIMS; i++) {
prims[i].type = m_randf() > 0.5f ? 0 : 1;
random_matrix(prims[i].m);
orthonormal_invert_2x3(prims[i].inv_m, prims[i].m);
prims[i].r[0] = m_randf() * 0.1f;
prims[i].r[1] = m_randf() * 0.1f;
}
}
void ctoy_end(void) {
m_image_destroy(&buffer);
}
void ctoy_main_loop(void) {
float t = ctoy_t() * 0.01f;
// animate prim1 in a circle
prims[1].m[4] = sinf(t) * 0.5f + 0.5;
prims[1].m[5] = cosf(t) * 0.5f + 0.5;
orthonormal_invert_2x3(prims[1].inv_m, prims[1].m);
// rotate prim2
float u[2] = {cosf(t), sinf(t)};
float v[2] = {u[1], -u[0]};
prims[2].m[0] = u[0];
prims[2].m[1] = u[1];
prims[2].m[2] = v[0];
prims[2].m[3] = v[1];
orthonormal_invert_2x3(prims[2].inv_m, prims[2].m);
draw();
ctoy_swap_buffer(&buffer);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment