mirror of
https://github.com/armory3d/armorpaint.git
synced 2026-10-02 02:54:34 +08:00
paint: procedural modeling
This commit is contained in:
@@ -132,3 +132,808 @@ raw_mesh_t *geom_make_uv_sphere(f32 radius, i32 width_segments, i32 height_segme
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// Procedural modeling
|
||||
|
||||
typedef struct geom {
|
||||
f32 *pos; // xyz per vertex
|
||||
i32 vcount;
|
||||
i32 vcap;
|
||||
u32 *ind; // triangles
|
||||
i32 icount;
|
||||
i32 icap;
|
||||
f32 *uv; // uv per index (face corner), NULL until unwrapped
|
||||
} geom_t;
|
||||
|
||||
enum {
|
||||
GEOM_BOX,
|
||||
GEOM_CYLINDER,
|
||||
GEOM_ELLIPSOID,
|
||||
GEOM_CAPSULE,
|
||||
GEOM_TORUS,
|
||||
GEOM_ZIGZAG
|
||||
};
|
||||
enum {
|
||||
GEOM_UNION,
|
||||
GEOM_SUBTRACT,
|
||||
GEOM_INTERSECT
|
||||
};
|
||||
|
||||
typedef struct geom_prim {
|
||||
i32 type;
|
||||
i32 op;
|
||||
f32 a[9];
|
||||
f32 rot[9]; // local to world rotation, row major
|
||||
f32 tr[3]; // local to world translation
|
||||
} geom_prim_t;
|
||||
|
||||
static geom_prim_t *geom_prims;
|
||||
static i32 geom_prim_count;
|
||||
static f32 geom_voxel;
|
||||
static f32 geom_rot[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
|
||||
static f32 geom_tr[3];
|
||||
static u8_array_t *geom_out;
|
||||
static i32 geom_out_base[3]; // vertices, uvs and normals written so far
|
||||
|
||||
static geom_t *geom_create() {
|
||||
return calloc(1, sizeof(geom_t));
|
||||
}
|
||||
|
||||
static i32 geom_add_vert(geom_t *g, f32 x, f32 y, f32 z) {
|
||||
if (g->vcount == g->vcap) {
|
||||
g->vcap = g->vcap == 0 ? 1024 : g->vcap * 2;
|
||||
g->pos = realloc(g->pos, sizeof(f32) * 3 * g->vcap);
|
||||
}
|
||||
g->pos[g->vcount * 3] = x;
|
||||
g->pos[g->vcount * 3 + 1] = y;
|
||||
g->pos[g->vcount * 3 + 2] = z;
|
||||
return g->vcount++;
|
||||
}
|
||||
|
||||
static void geom_add_tri(geom_t *g, i32 a, i32 b, i32 c) {
|
||||
if (g->icount + 3 > g->icap) {
|
||||
g->icap = g->icap == 0 ? 3072 : g->icap * 2;
|
||||
g->ind = realloc(g->ind, sizeof(u32) * g->icap);
|
||||
}
|
||||
g->ind[g->icount++] = a;
|
||||
g->ind[g->icount++] = b;
|
||||
g->ind[g->icount++] = c;
|
||||
}
|
||||
|
||||
static void geom_add_quad(geom_t *g, i32 a, i32 b, i32 c, i32 d) {
|
||||
geom_add_tri(g, a, b, c);
|
||||
geom_add_tri(g, a, c, d);
|
||||
}
|
||||
|
||||
static void geom_rotation(i32 axis, f32 deg, f32 *m) {
|
||||
f32 a = deg * (f32)(3.14159265358979 / 180.0);
|
||||
f32 c = cosf(a);
|
||||
f32 s = sinf(a);
|
||||
f32 x[9] = {1, 0, 0, 0, c, -s, 0, s, c};
|
||||
f32 y[9] = {c, 0, s, 0, 1, 0, -s, 0, c};
|
||||
f32 z[9] = {c, -s, 0, s, c, 0, 0, 0, 1};
|
||||
memcpy(m, axis == 0 ? x : axis == 1 ? y : z, sizeof(f32) * 9);
|
||||
}
|
||||
|
||||
// Noise
|
||||
|
||||
static u8 geom_perm[512];
|
||||
static bool geom_perm_ready = false;
|
||||
|
||||
static f32 geom_fade(f32 t) {
|
||||
return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
|
||||
}
|
||||
|
||||
static f32 geom_grad(i32 h, f32 x, f32 y, f32 z) {
|
||||
h = h & 15;
|
||||
f32 u = h < 8 ? x : y;
|
||||
f32 v = h < 4 ? y : (h == 12 || h == 14) ? x : z;
|
||||
return ((h & 1) ? -u : u) + ((h & 2) ? -v : v);
|
||||
}
|
||||
|
||||
static f32 geom_lerp(f32 t, f32 a, f32 b) {
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
static f32 geom_noise(f32 x, f32 y, f32 z) {
|
||||
if (!geom_perm_ready) {
|
||||
u32 s = 1234567;
|
||||
for (i32 i = 0; i < 256; ++i) {
|
||||
geom_perm[i] = i;
|
||||
}
|
||||
for (i32 i = 255; i > 0; --i) {
|
||||
s = s * 1664525 + 1013904223;
|
||||
i32 j = (s >> 8) % (i + 1);
|
||||
u8 t = geom_perm[i];
|
||||
geom_perm[i] = geom_perm[j];
|
||||
geom_perm[j] = t;
|
||||
}
|
||||
memcpy(geom_perm + 256, geom_perm, 256);
|
||||
geom_perm_ready = true;
|
||||
}
|
||||
f32 fx = floorf(x);
|
||||
f32 fy = floorf(y);
|
||||
f32 fz = floorf(z);
|
||||
i32 X = (i32)fx & 255;
|
||||
i32 Y = (i32)fy & 255;
|
||||
i32 Z = (i32)fz & 255;
|
||||
x -= fx;
|
||||
y -= fy;
|
||||
z -= fz;
|
||||
f32 u = geom_fade(x);
|
||||
f32 v = geom_fade(y);
|
||||
f32 w = geom_fade(z);
|
||||
u8 *p = geom_perm;
|
||||
i32 A = p[X] + Y;
|
||||
i32 AA = p[A] + Z;
|
||||
i32 AB = p[A + 1] + Z;
|
||||
i32 B = p[X + 1] + Y;
|
||||
i32 BA = p[B] + Z;
|
||||
i32 BB = p[B + 1] + Z;
|
||||
return geom_lerp(w,
|
||||
geom_lerp(v, geom_lerp(u, geom_grad(p[AA], x, y, z), geom_grad(p[BA], x - 1, y, z)),
|
||||
geom_lerp(u, geom_grad(p[AB], x, y - 1, z), geom_grad(p[BB], x - 1, y - 1, z))),
|
||||
geom_lerp(v, geom_lerp(u, geom_grad(p[AA + 1], x, y, z - 1), geom_grad(p[BA + 1], x - 1, y, z - 1)),
|
||||
geom_lerp(u, geom_grad(p[AB + 1], x, y - 1, z - 1), geom_grad(p[BB + 1], x - 1, y - 1, z - 1))));
|
||||
}
|
||||
|
||||
// Signed distance field shapes
|
||||
|
||||
void geom_sdf_begin(f32 voxel) {
|
||||
geom_voxel = voxel;
|
||||
geom_prim_count = 0;
|
||||
geom_sdf_transform(0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Places the following shapes: world = translate(x, y, z) * rotate(axis 0/1/2, deg) * local
|
||||
void geom_sdf_transform(i32 axis, f32 deg, f32 x, f32 y, f32 z) {
|
||||
geom_rotation(axis, deg, geom_rot);
|
||||
geom_tr[0] = x;
|
||||
geom_tr[1] = y;
|
||||
geom_tr[2] = z;
|
||||
}
|
||||
|
||||
static void geom_sdf_add(i32 type, i32 op, f32 *a, i32 n) {
|
||||
geom_prims = realloc(geom_prims, sizeof(geom_prim_t) * (geom_prim_count + 1));
|
||||
geom_prim_t *p = &geom_prims[geom_prim_count++];
|
||||
p->type = type;
|
||||
p->op = op;
|
||||
memcpy(p->a, a, sizeof(f32) * n);
|
||||
memcpy(p->rot, geom_rot, sizeof(geom_rot));
|
||||
memcpy(p->tr, geom_tr, sizeof(geom_tr));
|
||||
}
|
||||
|
||||
// Box from min to max corner with edges rounded by radius round
|
||||
void geom_sdf_box(f32 x0, f32 y0, f32 z0, f32 x1, f32 y1, f32 z1, f32 round, i32 op) {
|
||||
f32 a[7] = {(x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2, (x1 - x0) / 2, (y1 - y0) / 2, (z1 - z0) / 2, round};
|
||||
geom_sdf_add(GEOM_BOX, op, a, 7);
|
||||
}
|
||||
|
||||
// Cylinder of radius r and length h centered at (x, y, z) along axis 0/1/2, edges rounded by round
|
||||
void geom_sdf_cylinder(f32 x, f32 y, f32 z, f32 r, f32 h, i32 axis, f32 round, i32 op) {
|
||||
f32 a[7] = {x, y, z, r, h / 2, (f32)axis, round};
|
||||
geom_sdf_add(GEOM_CYLINDER, op, a, 7);
|
||||
}
|
||||
|
||||
void geom_sdf_ellipsoid(f32 x, f32 y, f32 z, f32 rx, f32 ry, f32 rz, i32 op) {
|
||||
f32 a[6] = {x, y, z, rx, ry, rz};
|
||||
geom_sdf_add(GEOM_ELLIPSOID, op, a, 6);
|
||||
}
|
||||
|
||||
// Round-ended segment from a to b, radius blending from ra to rb
|
||||
void geom_sdf_capsule(f32 ax, f32 ay, f32 az, f32 bx, f32 by, f32 bz, f32 ra, f32 rb, i32 op) {
|
||||
f32 a[8] = {ax, ay, az, bx, by, bz, ra, rb};
|
||||
geom_sdf_add(GEOM_CAPSULE, op, a, 8);
|
||||
}
|
||||
|
||||
// Torus around the z axis, squashed along z by zscale
|
||||
void geom_sdf_torus(f32 x, f32 y, f32 z, f32 major, f32 minor, f32 zscale, i32 op) {
|
||||
f32 a[6] = {x, y, z, major, minor, zscale};
|
||||
geom_sdf_add(GEOM_TORUS, op, a, 6);
|
||||
}
|
||||
|
||||
// Half space x * side >= 0, its edge pushed out into count zigzag teeth of height amp around the x axis
|
||||
// (no tooth where the edge faces -y)
|
||||
void geom_sdf_zigzag(f32 side, f32 amp, i32 count, i32 op) {
|
||||
f32 a[3] = {side, amp, (f32)count};
|
||||
geom_sdf_add(GEOM_ZIGZAG, op, a, 3);
|
||||
}
|
||||
|
||||
static f32 geom_len3(f32 x, f32 y, f32 z) {
|
||||
return sqrtf(x * x + y * y + z * z);
|
||||
}
|
||||
|
||||
static f32 geom_prim_eval(geom_prim_t *p, f32 wx, f32 wy, f32 wz) {
|
||||
// World to local: transpose(rot) * (w - tr)
|
||||
f32 dx = wx - p->tr[0];
|
||||
f32 dy = wy - p->tr[1];
|
||||
f32 dz = wz - p->tr[2];
|
||||
f32 *r = p->rot;
|
||||
f32 x = r[0] * dx + r[3] * dy + r[6] * dz;
|
||||
f32 y = r[1] * dx + r[4] * dy + r[7] * dz;
|
||||
f32 z = r[2] * dx + r[5] * dy + r[8] * dz;
|
||||
f32 *a = p->a;
|
||||
if (p->type == GEOM_BOX) {
|
||||
f32 qx = fabsf(x - a[0]) - (a[3] - a[6]);
|
||||
f32 qy = fabsf(y - a[1]) - (a[4] - a[6]);
|
||||
f32 qz = fabsf(z - a[2]) - (a[5] - a[6]);
|
||||
f32 in = fminf(fmaxf(qx, fmaxf(qy, qz)), 0.0f);
|
||||
return geom_len3(fmaxf(qx, 0), fmaxf(qy, 0), fmaxf(qz, 0)) + in - a[6];
|
||||
}
|
||||
if (p->type == GEOM_CYLINDER) {
|
||||
f32 c[3] = {x - a[0], y - a[1], z - a[2]};
|
||||
i32 ax = (i32)a[5];
|
||||
f32 h = c[ax];
|
||||
c[ax] = 0;
|
||||
f32 qr = geom_len3(c[0], c[1], c[2]) - (a[3] - a[6]);
|
||||
f32 qh = fabsf(h) - (a[4] - a[6]);
|
||||
return sqrtf(fmaxf(qr, 0) * fmaxf(qr, 0) + fmaxf(qh, 0) * fmaxf(qh, 0)) + fminf(fmaxf(qr, qh), 0.0f) - a[6];
|
||||
}
|
||||
if (p->type == GEOM_ELLIPSOID) {
|
||||
f32 px = (x - a[0]) / a[3];
|
||||
f32 py = (y - a[1]) / a[4];
|
||||
f32 pz = (z - a[2]) / a[5];
|
||||
f32 k0 = geom_len3(px, py, pz);
|
||||
f32 k1 = geom_len3(px / a[3], py / a[4], pz / a[5]);
|
||||
return k1 > 0.0f ? k0 * (k0 - 1.0f) / k1 : -fminf(a[3], fminf(a[4], a[5]));
|
||||
}
|
||||
if (p->type == GEOM_CAPSULE) {
|
||||
f32 pax = x - a[0], pay = y - a[1], paz = z - a[2];
|
||||
f32 bax = a[3] - a[0], bay = a[4] - a[1], baz = a[5] - a[2];
|
||||
f32 h = (pax * bax + pay * bay + paz * baz) / (bax * bax + bay * bay + baz * baz);
|
||||
h = fminf(fmaxf(h, 0.0f), 1.0f);
|
||||
return geom_len3(pax - bax * h, pay - bay * h, paz - baz * h) - (a[6] + (a[7] - a[6]) * h);
|
||||
}
|
||||
if (p->type == GEOM_TORUS) {
|
||||
f32 px = x - a[0], py = y - a[1], pz = (z - a[2]) / a[5];
|
||||
f32 q = sqrtf(px * px + py * py) - a[3];
|
||||
return (sqrtf(q * q + pz * pz) - a[4]) * fminf(a[5], 1.0f);
|
||||
}
|
||||
// GEOM_ZIGZAG
|
||||
f32 deg = atan2f(z, y) * (f32)(180.0 / 3.14159265358979);
|
||||
f32 period = 360.0f / a[2];
|
||||
f32 tooth = 0.0f;
|
||||
if (fabsf(deg) < 180.0f - period) {
|
||||
f32 t = fmodf((deg + 180.0f - period) / (period / 2.0f), 2.0f);
|
||||
tooth = a[1] * (1.0f - fabsf(t - 1.0f));
|
||||
}
|
||||
return -(x * a[0]) - tooth;
|
||||
}
|
||||
|
||||
static f32 geom_sdf_eval(f32 x, f32 y, f32 z) {
|
||||
f32 d = 1e9f;
|
||||
for (i32 i = 0; i < geom_prim_count; ++i) {
|
||||
f32 p = geom_prim_eval(&geom_prims[i], x, y, z);
|
||||
i32 op = i == 0 ? GEOM_UNION : geom_prims[i].op;
|
||||
d = op == GEOM_UNION ? fminf(d, p) : op == GEOM_SUBTRACT ? fmaxf(d, -p) : fmaxf(d, p);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
static void geom_prim_bounds(geom_prim_t *p, f32 *mn, f32 *mx) {
|
||||
f32 *a = p->a;
|
||||
f32 lo[3], hi[3];
|
||||
if (p->type == GEOM_BOX || p->type == GEOM_ELLIPSOID) {
|
||||
for (i32 i = 0; i < 3; ++i) {
|
||||
lo[i] = a[i] - a[i + 3];
|
||||
hi[i] = a[i] + a[i + 3];
|
||||
}
|
||||
}
|
||||
else if (p->type == GEOM_CYLINDER) {
|
||||
for (i32 i = 0; i < 3; ++i) {
|
||||
f32 e = i == (i32)a[5] ? a[4] : a[3];
|
||||
lo[i] = a[i] - e;
|
||||
hi[i] = a[i] + e;
|
||||
}
|
||||
}
|
||||
else if (p->type == GEOM_CAPSULE) {
|
||||
f32 r = fmaxf(a[6], a[7]);
|
||||
for (i32 i = 0; i < 3; ++i) {
|
||||
lo[i] = fminf(a[i], a[i + 3]) - r;
|
||||
hi[i] = fmaxf(a[i], a[i + 3]) + r;
|
||||
}
|
||||
}
|
||||
else if (p->type == GEOM_TORUS) {
|
||||
f32 e[3] = {a[3] + a[4], a[3] + a[4], a[4] * a[5]};
|
||||
for (i32 i = 0; i < 3; ++i) {
|
||||
lo[i] = a[i] - e[i];
|
||||
hi[i] = a[i] + e[i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
return; // Unbounded
|
||||
}
|
||||
for (i32 c = 0; c < 8; ++c) {
|
||||
f32 l[3] = {c & 1 ? hi[0] : lo[0], c & 2 ? hi[1] : lo[1], c & 4 ? hi[2] : lo[2]};
|
||||
for (i32 i = 0; i < 3; ++i) {
|
||||
f32 w = p->rot[i * 3] * l[0] + p->rot[i * 3 + 1] * l[1] + p->rot[i * 3 + 2] * l[2] + p->tr[i];
|
||||
mn[i] = fminf(mn[i], w);
|
||||
mx[i] = fmaxf(mx[i], w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void geom_smooth(geom_t *g, i32 iterations, f32 factor) {
|
||||
f32 *sum = malloc(sizeof(f32) * 3 * g->vcount);
|
||||
i32 *cnt = malloc(sizeof(i32) * g->vcount);
|
||||
for (i32 it = 0; it < iterations; ++it) {
|
||||
memset(sum, 0, sizeof(f32) * 3 * g->vcount);
|
||||
memset(cnt, 0, sizeof(i32) * g->vcount);
|
||||
for (i32 i = 0; i < g->icount; i += 3) {
|
||||
for (i32 e = 0; e < 3; ++e) {
|
||||
u32 a = g->ind[i + e];
|
||||
u32 b = g->ind[i + (e + 1) % 3];
|
||||
for (i32 k = 0; k < 3; ++k) {
|
||||
sum[a * 3 + k] += g->pos[b * 3 + k];
|
||||
sum[b * 3 + k] += g->pos[a * 3 + k];
|
||||
}
|
||||
cnt[a]++;
|
||||
cnt[b]++;
|
||||
}
|
||||
}
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
if (cnt[v] == 0) {
|
||||
continue;
|
||||
}
|
||||
for (i32 k = 0; k < 3; ++k) {
|
||||
f32 avg = sum[v * 3 + k] / cnt[v];
|
||||
g->pos[v * 3 + k] += (avg - g->pos[v * 3 + k]) * factor;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(sum);
|
||||
free(cnt);
|
||||
}
|
||||
|
||||
geom_t *geom_sdf_end(i32 smooth) {
|
||||
f32 v = geom_voxel;
|
||||
f32 mn[3] = {1e9f, 1e9f, 1e9f};
|
||||
f32 mx[3] = {-1e9f, -1e9f, -1e9f};
|
||||
for (i32 i = 0; i < geom_prim_count; ++i) {
|
||||
if (i == 0 || geom_prims[i].op == GEOM_UNION) {
|
||||
geom_prim_bounds(&geom_prims[i], mn, mx);
|
||||
}
|
||||
}
|
||||
i32 n[3];
|
||||
for (i32 i = 0; i < 3; ++i) {
|
||||
mn[i] -= v * 2;
|
||||
n[i] = (i32)ceilf((mx[i] + v * 2 - mn[i]) / v) + 1;
|
||||
}
|
||||
i32 nx = n[0], ny = n[1], nz = n[2];
|
||||
f32 *f = malloc(sizeof(f32) * nx * ny * nz);
|
||||
for (i32 k = 0; k < nz; ++k) {
|
||||
for (i32 j = 0; j < ny; ++j) {
|
||||
for (i32 i = 0; i < nx; ++i) {
|
||||
f[i + nx * (j + ny * k)] = geom_sdf_eval(mn[0] + i * v, mn[1] + j * v, mn[2] + k * v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
geom_t *g = geom_create();
|
||||
i32 cx = nx - 1, cy = ny - 1, cz = nz - 1;
|
||||
i32 *cell = malloc(sizeof(i32) * cx * cy * cz);
|
||||
for (i32 k = 0; k < cz; ++k) {
|
||||
for (i32 j = 0; j < cy; ++j) {
|
||||
for (i32 i = 0; i < cx; ++i) {
|
||||
f32 c[8];
|
||||
i32 inside = 0;
|
||||
for (i32 b = 0; b < 8; ++b) {
|
||||
c[b] = f[(i + (b & 1)) + nx * ((j + ((b >> 1) & 1)) + ny * (k + ((b >> 2) & 1)))];
|
||||
inside += c[b] < 0.0f;
|
||||
}
|
||||
cell[i + cx * (j + cy * k)] = -1;
|
||||
if (inside == 0 || inside == 8) {
|
||||
continue;
|
||||
}
|
||||
// Average of the edge crossings
|
||||
f32 s[3] = {0, 0, 0};
|
||||
i32 count = 0;
|
||||
for (i32 b = 0; b < 8; ++b) {
|
||||
for (i32 bit = 1; bit < 8; bit <<= 1) {
|
||||
if ((b & bit) || (c[b] < 0.0f) == (c[b | bit] < 0.0f)) {
|
||||
continue;
|
||||
}
|
||||
f32 t = c[b] / (c[b] - c[b | bit]);
|
||||
i32 e = b | bit;
|
||||
s[0] += (b & 1) + ((e & 1) - (b & 1)) * t;
|
||||
s[1] += ((b >> 1) & 1) + (((e >> 1) & 1) - ((b >> 1) & 1)) * t;
|
||||
s[2] += ((b >> 2) & 1) + (((e >> 2) & 1) - ((b >> 2) & 1)) * t;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
cell[i + cx * (j + cy * k)] = geom_add_vert(g, mn[0] + (i + s[0] / count) * v, mn[1] + (j + s[1] / count) * v, mn[2] + (k + s[2] / count) * v);
|
||||
}
|
||||
}
|
||||
}
|
||||
// A quad for every grid edge crossing the surface, from the 4 cells around it
|
||||
for (i32 k = 0; k < nz; ++k) {
|
||||
for (i32 j = 0; j < ny; ++j) {
|
||||
for (i32 i = 0; i < nx; ++i) {
|
||||
i32 p[3] = {i, j, k};
|
||||
for (i32 a = 0; a < 3; ++a) {
|
||||
i32 u = (a + 1) % 3;
|
||||
i32 w = (a + 2) % 3;
|
||||
if (p[a] >= n[a] - 1 || p[u] < 1 || p[u] > n[u] - 2 || p[w] < 1 || p[w] > n[w] - 2) {
|
||||
continue;
|
||||
}
|
||||
i32 q[3] = {i, j, k};
|
||||
q[a]++;
|
||||
bool in0 = f[i + nx * (j + ny * k)] < 0.0f;
|
||||
bool in1 = f[q[0] + nx * (q[1] + ny * q[2])] < 0.0f;
|
||||
if (in0 == in1) {
|
||||
continue;
|
||||
}
|
||||
i32 quad[4];
|
||||
i32 du[4] = {0, 1, 1, 0};
|
||||
i32 dw[4] = {0, 0, 1, 1};
|
||||
for (i32 m = 0; m < 4; ++m) {
|
||||
i32 c[3] = {i, j, k};
|
||||
c[u] = p[u] - 1 + du[m];
|
||||
c[w] = p[w] - 1 + dw[m];
|
||||
quad[m] = cell[c[0] + cx * (c[1] + cy * c[2])];
|
||||
}
|
||||
if (!in0) {
|
||||
i32 t = quad[1];
|
||||
quad[1] = quad[3];
|
||||
quad[3] = t;
|
||||
}
|
||||
// Split along the shorter diagonal
|
||||
f32 *p0 = &g->pos[quad[0] * 3], *p1 = &g->pos[quad[1] * 3], *p2 = &g->pos[quad[2] * 3], *p3 = &g->pos[quad[3] * 3];
|
||||
f32 d02 = geom_len3(p0[0] - p2[0], p0[1] - p2[1], p0[2] - p2[2]);
|
||||
f32 d13 = geom_len3(p1[0] - p3[0], p1[1] - p3[1], p1[2] - p3[2]);
|
||||
if (d02 <= d13) {
|
||||
geom_add_quad(g, quad[0], quad[1], quad[2], quad[3]);
|
||||
}
|
||||
else {
|
||||
geom_add_quad(g, quad[1], quad[2], quad[3], quad[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(f);
|
||||
free(cell);
|
||||
geom_smooth(g, smooth, 0.6f);
|
||||
geom_prim_count = 0;
|
||||
return g;
|
||||
}
|
||||
|
||||
// Direct primitives
|
||||
|
||||
// Flat shaded box
|
||||
geom_t *geom_box(f32 x0, f32 y0, f32 z0, f32 x1, f32 y1, f32 z1) {
|
||||
geom_t *g = geom_create();
|
||||
f32 b[2][3] = {{x0, y0, z0}, {x1, y1, z1}};
|
||||
// Per face: axis, side, then 4 corners counterclockwise seen from outside
|
||||
for (i32 a = 0; a < 3; ++a) {
|
||||
i32 u = (a + 1) % 3;
|
||||
i32 w = (a + 2) % 3;
|
||||
for (i32 s = 0; s < 2; ++s) {
|
||||
i32 du[4] = {0, 1, 1, 0};
|
||||
i32 dw[4] = {0, 0, 1, 1};
|
||||
i32 first = g->vcount;
|
||||
for (i32 m = 0; m < 4; ++m) {
|
||||
f32 c[3];
|
||||
c[a] = b[s][a];
|
||||
c[u] = b[du[m]][u];
|
||||
c[w] = b[dw[m]][w];
|
||||
geom_add_vert(g, c[0], c[1], c[2]);
|
||||
}
|
||||
if (s == 1) {
|
||||
geom_add_quad(g, first, first + 1, first + 2, first + 3);
|
||||
}
|
||||
else {
|
||||
geom_add_quad(g, first, first + 3, first + 2, first + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
// Plane at z = 0 facing +z, divided into nx * ny quads
|
||||
geom_t *geom_grid(f32 x0, f32 y0, f32 x1, f32 y1, i32 nx, i32 ny) {
|
||||
geom_t *g = geom_create();
|
||||
for (i32 j = 0; j <= ny; ++j) {
|
||||
for (i32 i = 0; i <= nx; ++i) {
|
||||
geom_add_vert(g, x0 + (x1 - x0) * i / nx, y0 + (y1 - y0) * j / ny, 0.0f);
|
||||
}
|
||||
}
|
||||
for (i32 j = 0; j < ny; ++j) {
|
||||
for (i32 i = 0; i < nx; ++i) {
|
||||
i32 a = i + j * (nx + 1);
|
||||
geom_add_quad(g, a, a + 1, a + nx + 2, a + nx + 1);
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
// Capped cylinder along z centered at (x, y, z)
|
||||
geom_t *geom_cylinder(f32 x, f32 y, f32 z, f32 r, f32 h, i32 segs) {
|
||||
geom_t *g = geom_create();
|
||||
for (i32 s = 0; s < 2; ++s) {
|
||||
for (i32 i = 0; i < segs; ++i) {
|
||||
f32 a = 2.0f * 3.14159265f * i / segs;
|
||||
geom_add_vert(g, x + cosf(a) * r, y + sinf(a) * r, z + (s == 0 ? -h : h) / 2);
|
||||
}
|
||||
}
|
||||
i32 bottom = geom_add_vert(g, x, y, z - h / 2);
|
||||
i32 top = geom_add_vert(g, x, y, z + h / 2);
|
||||
for (i32 i = 0; i < segs; ++i) {
|
||||
i32 n = (i + 1) % segs;
|
||||
geom_add_quad(g, i, n, segs + n, segs + i);
|
||||
geom_add_tri(g, bottom, n, i);
|
||||
geom_add_tri(g, top, segs + i, segs + n);
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
// Torus around the z axis centered at (x, y, z)
|
||||
geom_t *geom_torus(f32 x, f32 y, f32 z, f32 major, f32 minor, i32 segs, i32 sides) {
|
||||
geom_t *g = geom_create();
|
||||
for (i32 i = 0; i < segs; ++i) {
|
||||
f32 a = 2.0f * 3.14159265f * i / segs;
|
||||
for (i32 k = 0; k < sides; ++k) {
|
||||
f32 b = 2.0f * 3.14159265f * k / sides;
|
||||
f32 r = major + cosf(b) * minor;
|
||||
geom_add_vert(g, x + cosf(a) * r, y + sinf(a) * r, z + sinf(b) * minor);
|
||||
}
|
||||
}
|
||||
for (i32 i = 0; i < segs; ++i) {
|
||||
i32 n = (i + 1) % segs;
|
||||
for (i32 k = 0; k < sides; ++k) {
|
||||
i32 m = (k + 1) % sides;
|
||||
geom_add_quad(g, i * sides + k, n * sides + k, n * sides + m, i * sides + m);
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
// Mesh edits
|
||||
|
||||
void geom_rotate(geom_t *g, i32 axis, f32 deg, f32 px, f32 py, f32 pz) {
|
||||
f32 m[9];
|
||||
geom_rotation(axis, deg, m);
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
f32 *p = &g->pos[v * 3];
|
||||
f32 x = p[0] - px, y = p[1] - py, z = p[2] - pz;
|
||||
p[0] = m[0] * x + m[1] * y + m[2] * z + px;
|
||||
p[1] = m[3] * x + m[4] * y + m[5] * z + py;
|
||||
p[2] = m[6] * x + m[7] * y + m[8] * z + pz;
|
||||
}
|
||||
}
|
||||
|
||||
void geom_translate(geom_t *g, f32 x, f32 y, f32 z) {
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
g->pos[v * 3] += x;
|
||||
g->pos[v * 3 + 1] += y;
|
||||
g->pos[v * 3 + 2] += z;
|
||||
}
|
||||
}
|
||||
|
||||
void geom_flip(geom_t *g) {
|
||||
for (i32 i = 0; i < g->icount; i += 3) {
|
||||
u32 t = g->ind[i + 1];
|
||||
g->ind[i + 1] = g->ind[i + 2];
|
||||
g->ind[i + 2] = t;
|
||||
}
|
||||
}
|
||||
|
||||
geom_t *geom_copy(geom_t *g) {
|
||||
geom_t *c = geom_create();
|
||||
*c = *g;
|
||||
c->pos = malloc(sizeof(f32) * 3 * g->vcap);
|
||||
c->ind = malloc(sizeof(u32) * g->icap);
|
||||
memcpy(c->pos, g->pos, sizeof(f32) * 3 * g->vcount);
|
||||
memcpy(c->ind, g->ind, sizeof(u32) * g->icount);
|
||||
c->uv = NULL;
|
||||
return c;
|
||||
}
|
||||
|
||||
// Keeps the faces whose center lies within lo..hi along axis 0/1/2, drops unused vertices
|
||||
void geom_keep(geom_t *g, i32 axis, f32 lo, f32 hi) {
|
||||
i32 *remap = malloc(sizeof(i32) * g->vcount);
|
||||
i32 icount = 0;
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
remap[v] = -1;
|
||||
}
|
||||
for (i32 i = 0; i < g->icount; i += 3) {
|
||||
f32 c = (g->pos[g->ind[i] * 3 + axis] + g->pos[g->ind[i + 1] * 3 + axis] + g->pos[g->ind[i + 2] * 3 + axis]) / 3.0f;
|
||||
if (c < lo || c > hi) {
|
||||
continue;
|
||||
}
|
||||
for (i32 k = 0; k < 3; ++k) {
|
||||
g->ind[icount++] = g->ind[i + k];
|
||||
remap[g->ind[i + k]] = 0;
|
||||
}
|
||||
}
|
||||
i32 vcount = 0;
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
if (remap[v] == 0) {
|
||||
remap[v] = vcount;
|
||||
memmove(&g->pos[vcount * 3], &g->pos[v * 3], sizeof(f32) * 3);
|
||||
vcount++;
|
||||
}
|
||||
}
|
||||
for (i32 i = 0; i < icount; ++i) {
|
||||
g->ind[i] = remap[g->ind[i]];
|
||||
}
|
||||
g->vcount = vcount;
|
||||
g->icount = icount;
|
||||
free(remap);
|
||||
}
|
||||
|
||||
// Area weighted vertex normals
|
||||
static f32 *geom_normals(geom_t *g) {
|
||||
f32 *n = calloc(g->vcount * 3, sizeof(f32));
|
||||
for (i32 i = 0; i < g->icount; i += 3) {
|
||||
f32 *a = &g->pos[g->ind[i] * 3], *b = &g->pos[g->ind[i + 1] * 3], *c = &g->pos[g->ind[i + 2] * 3];
|
||||
f32 e1[3] = {b[0] - a[0], b[1] - a[1], b[2] - a[2]};
|
||||
f32 e2[3] = {c[0] - a[0], c[1] - a[1], c[2] - a[2]};
|
||||
f32 f[3] = {e1[1] * e2[2] - e1[2] * e2[1], e1[2] * e2[0] - e1[0] * e2[2], e1[0] * e2[1] - e1[1] * e2[0]};
|
||||
for (i32 k = 0; k < 3; ++k) {
|
||||
for (i32 m = 0; m < 3; ++m) {
|
||||
n[g->ind[i + k] * 3 + m] += f[m];
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
f32 l = geom_len3(n[v * 3], n[v * 3 + 1], n[v * 3 + 2]);
|
||||
if (l > 0.0f) {
|
||||
n[v * 3] /= l;
|
||||
n[v * 3 + 1] /= l;
|
||||
n[v * 3 + 2] /= l;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
// Lumpy hand-pressed surface: displaces along the normals with layered noise
|
||||
void geom_clay(geom_t *g, f32 amp, f32 freq, f32 seed, f32 fine) {
|
||||
f32 *n = geom_normals(g);
|
||||
f32 off[3] = {seed * 13.1f, seed * 7.7f, seed * 3.3f};
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
f32 *c = &g->pos[v * 3];
|
||||
f32 p[3] = {c[0] * freq + off[0], c[1] * freq + off[1], c[2] * freq + off[2]};
|
||||
f32 d = geom_noise(p[0], p[1], p[2]) * amp + geom_noise(p[0] * 3.1f, p[1] * 3.1f, p[2] * 3.1f) * amp * 0.35f;
|
||||
if (fine > 0.0f) {
|
||||
d += geom_noise(c[0] * 90.0f + off[0], c[1] * 90.0f + off[1], c[2] * 90.0f + off[2]) * fine;
|
||||
}
|
||||
for (i32 k = 0; k < 3; ++k) {
|
||||
c[k] += n[v * 3 + k] * d;
|
||||
}
|
||||
}
|
||||
free(n);
|
||||
}
|
||||
|
||||
void geom_unwrap(geom_t *g, f32 margin) {
|
||||
f32 mn[3] = {1e9f, 1e9f, 1e9f};
|
||||
f32 mx[3] = {-1e9f, -1e9f, -1e9f};
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
for (i32 k = 0; k < 3; ++k) {
|
||||
mn[k] = fminf(mn[k], g->pos[v * 3 + k]);
|
||||
mx[k] = fmaxf(mx[k], g->pos[v * 3 + k]);
|
||||
}
|
||||
}
|
||||
f32 scale = 1e-6f;
|
||||
for (i32 k = 0; k < 3; ++k) {
|
||||
scale = fmaxf(scale, (mx[k] - mn[k]) / 2);
|
||||
}
|
||||
f32 *n = geom_normals(g);
|
||||
raw_mesh_t *mesh = calloc(1, sizeof(raw_mesh_t));
|
||||
mesh->posa = calloc(1, sizeof(i16_array_t));
|
||||
mesh->nora = calloc(1, sizeof(i16_array_t));
|
||||
mesh->inda = calloc(1, sizeof(u32_array_t));
|
||||
mesh->posa->buffer = malloc(sizeof(i16) * 4 * g->vcount);
|
||||
mesh->posa->length = g->vcount * 4;
|
||||
mesh->nora->buffer = malloc(sizeof(i16) * 2 * g->vcount);
|
||||
mesh->nora->length = g->vcount * 2;
|
||||
mesh->inda->buffer = malloc(sizeof(u32) * g->icount);
|
||||
mesh->inda->length = g->icount;
|
||||
memcpy(mesh->inda->buffer, g->ind, sizeof(u32) * g->icount);
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
for (i32 k = 0; k < 3; ++k) {
|
||||
mesh->posa->buffer[v * 4 + k] = (i16)(((g->pos[v * 3 + k] - (mn[k] + mx[k]) / 2) / scale) * 32767.0f);
|
||||
}
|
||||
mesh->posa->buffer[v * 4 + 3] = (i16)(n[v * 3 + 2] * 32767.0f);
|
||||
mesh->nora->buffer[v * 2] = (i16)(n[v * 3] * 32767.0f);
|
||||
mesh->nora->buffer[v * 2 + 1] = (i16)(n[v * 3 + 1] * 32767.0f);
|
||||
}
|
||||
mesh->vertex_count = g->vcount;
|
||||
mesh->index_count = g->icount;
|
||||
mesh->scale_pos = scale;
|
||||
mesh->scale_tex = 1.0f;
|
||||
f32 last_margin = util_uv_unwrap_margin;
|
||||
util_uv_unwrap_margin = margin;
|
||||
util_uv_unwrap_run(mesh);
|
||||
util_uv_unwrap_margin = last_margin;
|
||||
// Output vertex i is face corner i; texa stores (1 - u, v)
|
||||
free(g->uv);
|
||||
g->uv = malloc(sizeof(f32) * 2 * g->icount);
|
||||
for (i32 i = 0; i < g->icount; ++i) {
|
||||
g->uv[i * 2] = 1.0f - mesh->texa->buffer[i * 2] / 32767.0f;
|
||||
g->uv[i * 2 + 1] = mesh->texa->buffer[i * 2 + 1] / 32767.0f;
|
||||
}
|
||||
free(mesh->posa->buffer);
|
||||
free(mesh->nora->buffer);
|
||||
free(mesh->inda->buffer);
|
||||
free(mesh->texa->buffer);
|
||||
free(mesh->posa);
|
||||
free(mesh->nora);
|
||||
free(mesh->inda);
|
||||
free(mesh->texa);
|
||||
free(mesh);
|
||||
free(n);
|
||||
}
|
||||
|
||||
// OBJ export
|
||||
|
||||
static void geom_write(char *fmt, f32 a, f32 b, f32 c) {
|
||||
char s[128];
|
||||
snprintf(s, sizeof(s), fmt, a, b, c);
|
||||
export_obj_write_string(geom_out, s);
|
||||
}
|
||||
|
||||
void geom_export_add(geom_t *g, char *name, bool flat) {
|
||||
if (geom_out == NULL) {
|
||||
geom_out = u8_array_create(0);
|
||||
memset(geom_out_base, 0, sizeof(geom_out_base));
|
||||
}
|
||||
char s[128];
|
||||
snprintf(s, sizeof(s), "o %s\n", name);
|
||||
export_obj_write_string(geom_out, s);
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
f32 *p = &g->pos[v * 3];
|
||||
geom_write("v %.6f %.6f %.6f\n", p[0], p[2], -p[1]);
|
||||
}
|
||||
if (g->uv != NULL) {
|
||||
for (i32 i = 0; i < g->icount; ++i) {
|
||||
geom_write("vt %.6f %.6f\n", g->uv[i * 2], g->uv[i * 2 + 1], 0);
|
||||
}
|
||||
}
|
||||
f32 *n = geom_normals(g);
|
||||
if (flat) {
|
||||
for (i32 i = 0; i < g->icount; i += 3) {
|
||||
f32 *a = &g->pos[g->ind[i] * 3], *b = &g->pos[g->ind[i + 1] * 3], *c = &g->pos[g->ind[i + 2] * 3];
|
||||
f32 e1[3] = {b[0] - a[0], b[1] - a[1], b[2] - a[2]};
|
||||
f32 e2[3] = {c[0] - a[0], c[1] - a[1], c[2] - a[2]};
|
||||
f32 f[3] = {e1[1] * e2[2] - e1[2] * e2[1], e1[2] * e2[0] - e1[0] * e2[2], e1[0] * e2[1] - e1[1] * e2[0]};
|
||||
f32 l = fmaxf(geom_len3(f[0], f[1], f[2]), 1e-12f);
|
||||
geom_write("vn %.4f %.4f %.4f\n", f[0] / l, f[2] / l, -f[1] / l);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (i32 v = 0; v < g->vcount; ++v) {
|
||||
geom_write("vn %.4f %.4f %.4f\n", n[v * 3], n[v * 3 + 2], -n[v * 3 + 1]);
|
||||
}
|
||||
}
|
||||
free(n);
|
||||
// Indices are 1-based and global to the file; uvs are per corner, flat normals per face
|
||||
i32 *b = geom_out_base;
|
||||
for (i32 i = 0; i < g->icount; i += 3) {
|
||||
export_obj_write_string(geom_out, "f");
|
||||
for (i32 k = 0; k < 3; ++k) {
|
||||
i32 vi = g->ind[i + k] + 1 + b[0];
|
||||
i32 ni = (flat ? i / 3 : (i32)g->ind[i + k]) + 1 + b[2];
|
||||
if (g->uv != NULL) {
|
||||
snprintf(s, sizeof(s), " %d/%d/%d", vi, i + k + 1 + b[1], ni);
|
||||
}
|
||||
else {
|
||||
snprintf(s, sizeof(s), " %d//%d", vi, ni);
|
||||
}
|
||||
export_obj_write_string(geom_out, s);
|
||||
}
|
||||
export_obj_write_string(geom_out, "\n");
|
||||
}
|
||||
b[0] += g->vcount;
|
||||
b[1] += g->uv != NULL ? g->icount : 0;
|
||||
b[2] += flat ? g->icount / 3 : g->vcount;
|
||||
}
|
||||
|
||||
void geom_export_write(char *path) {
|
||||
if (geom_out == NULL) {
|
||||
return;
|
||||
}
|
||||
iron_file_save_bytes(path, geom_out, geom_out->length);
|
||||
array_delete(geom_out);
|
||||
geom_out = NULL;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user