Files
Rockgrave/libhmap/src/geometry.cpp
T
2026-10-25 03:29:04 -03:00

656 lines
25 KiB
C++

#include "hmap/hmap.h"
#include <array>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <map>
#include <sstream>
namespace hmap {
// ============================================================ polygon building (double precision)
namespace {
struct DV { double x, y, z; };
using DPoly = std::vector<DV>;
double ddist(const Plane& p, const DV& v) {
return (double)p.n.x * v.x + (double)p.n.y * v.y + (double)p.n.z * v.z - (double)p.d;
}
DPoly basePoly(const Plane& p) {
Vec3 ref = std::fabs(p.n.y) < 0.9f ? Vec3(0, 1, 0) : Vec3(1, 0, 0);
Vec3 t = normalize(cross(p.n, ref));
Vec3 b = cross(p.n, t);
const double S = 65536.0;
DV o{(double)p.n.x * p.d, (double)p.n.y * p.d, (double)p.n.z * p.d};
auto P = [&](double a, double c) {
return DV{o.x + t.x * a + b.x * c, o.y + t.y * a + b.y * c, o.z + t.z * a + b.z * c};
};
return {P(-S, -S), P(S, -S), P(S, S), P(-S, S)}; // CCW seen from +n
}
// Keep the part of poly on the back (<= 0) side of the plane.
void clipBack(DPoly& poly, const Plane& pl, double eps) {
size_t n = poly.size();
if (n == 0) return;
std::vector<double> d(n);
std::vector<int> s(n);
int nf = 0, nb = 0;
for (size_t i = 0; i < n; ++i) {
d[i] = ddist(pl, poly[i]);
s[i] = d[i] > eps ? 1 : (d[i] < -eps ? -1 : 0);
nf += s[i] > 0;
nb += s[i] < 0;
}
if (nf == 0) return;
if (nb == 0) { poly.clear(); return; }
DPoly out;
for (size_t i = 0; i < n; ++i) {
size_t j = (i + 1) % n;
if (s[i] <= 0) out.push_back(poly[i]);
if ((s[i] > 0 && s[j] < 0) || (s[i] < 0 && s[j] > 0)) {
double t = d[i] / (d[i] - d[j]);
out.push_back({poly[i].x + (poly[j].x - poly[i].x) * t, poly[i].y + (poly[j].y - poly[i].y) * t,
poly[i].z + (poly[j].z - poly[i].z) * t});
}
}
poly.swap(out);
}
Vec3 newellRaw(const Polygon& p) {
Vec3 n;
for (size_t i = 0; i < p.size(); ++i) {
const Vec3& a = p[i];
const Vec3& b = p[(i + 1) % p.size()];
n.x += (a.y - b.y) * (a.z + b.z);
n.y += (a.z - b.z) * (a.x + b.x);
n.z += (a.x - b.x) * (a.y + b.y);
}
return n;
}
} // namespace
Vec3 polyNormal(const Polygon& p) { return normalize(newellRaw(p)); }
BrushGeometry buildBrushGeometry(const Brush& b) {
BrushGeometry g;
size_t nf = b.faces.size();
g.polys.resize(nf);
if (nf < 4) return g;
int good = 0;
for (size_t i = 0; i < nf; ++i) {
const Plane& pi = b.faces[i].plane;
bool dup = false;
for (size_t j = 0; j < i && !dup; ++j) {
const Plane& pj = b.faces[j].plane;
dup = dot(pi.n, pj.n) > 0.99999f && std::fabs(pi.d - pj.d) < 0.01f;
}
if (dup) continue;
DPoly p = basePoly(pi);
for (size_t j = 0; j < nf && p.size() >= 3; ++j)
if (j != i) clipBack(p, b.faces[j].plane, 1e-3);
if (p.size() < 3) continue;
Polygon fp;
for (const DV& v : p) {
Vec3 q((float)v.x, (float)v.y, (float)v.z);
if (fp.empty() || length(q - fp.back()) > 1e-3f) fp.push_back(q);
}
while (fp.size() > 1 && length(fp.front() - fp.back()) <= 1e-3f) fp.pop_back();
if (fp.size() < 3 || length(newellRaw(fp)) * 0.5f < 1e-3f) continue;
for (const Vec3& q : fp) { g.bounds.add(q); g.points.push_back(q); }
g.polys[i] = std::move(fp);
++good;
}
Vec3 sz = g.bounds.size();
g.valid = good >= 4 && sz.x > 0.01f && sz.y > 0.01f && sz.z > 0.01f;
return g;
}
AABB brushBounds(const Brush& b) { return buildBrushGeometry(b).bounds; }
AABB meshBounds(const MeshObject& m) {
AABB bb;
for (const Vec3& v : m.verts) bb.add(v);
return bb;
}
// ============================================================ raycasts
bool rayBrush(const Brush& b, const Vec3& o, const Vec3& d, float& tHit, int& face) {
float tEnter = -1e30f, tExit = 1e30f;
int fe = -1;
for (size_t i = 0; i < b.faces.size(); ++i) {
const Plane& p = b.faces[i].plane;
float denom = dot(p.n, d), dist = p.dist(o);
if (std::fabs(denom) < 1e-8f) {
if (dist > 0) return false;
continue;
}
float t = -dist / denom;
if (denom < 0) { if (t > tEnter) { tEnter = t; fe = (int)i; } }
else if (t < tExit) tExit = t;
if (tEnter > tExit) return false;
}
if (fe < 0 || tEnter < 0 || tEnter > tExit) return false;
tHit = tEnter;
face = fe;
return true;
}
static bool rayTri(const Vec3& o, const Vec3& d, const Vec3& a, const Vec3& b, const Vec3& c, float& t) {
Vec3 e1 = b - a, e2 = c - a, p = cross(d, e2);
float det = dot(e1, p);
if (std::fabs(det) < 1e-9f) return false;
float inv = 1.0f / det;
Vec3 s = o - a;
float u = dot(s, p) * inv;
if (u < 0 || u > 1) return false;
Vec3 q = cross(s, e1);
float v = dot(d, q) * inv;
if (v < 0 || u + v > 1) return false;
float tt = dot(e2, q) * inv;
if (tt < 0) return false;
t = tt;
return true;
}
bool rayMesh(const MeshObject& m, const Vec3& o, const Vec3& d, float& tHit, int& poly) {
bool hit = false;
float best = 1e30f;
for (size_t pi = 0; pi < m.polys.size(); ++pi) {
const auto& idx = m.polys[pi].idx;
for (size_t k = 1; k + 1 < idx.size(); ++k) {
if (idx[0] >= m.verts.size() || idx[k] >= m.verts.size() || idx[k + 1] >= m.verts.size()) continue;
float t;
if (rayTri(o, d, m.verts[idx[0]], m.verts[idx[k]], m.verts[idx[k + 1]], t) && t < best) {
best = t; poly = (int)pi; hit = true;
}
}
}
if (hit) tHit = best;
return hit;
}
// ============================================================ texture mapping
void uvAxes(const Vec3& n, const UVInfo& uv, Vec3& u, Vec3& v) {
float ax = std::fabs(n.x), ay = std::fabs(n.y), az = std::fabs(n.z);
Vec3 ua, va;
if (ay >= ax && ay >= az) {
if (n.y >= 0) { ua = {1, 0, 0}; va = {0, 0, 1}; } else { ua = {1, 0, 0}; va = {0, 0, -1}; }
} else if (ax >= az) {
if (n.x >= 0) { ua = {0, 0, -1}; va = {0, -1, 0}; } else { ua = {0, 0, 1}; va = {0, -1, 0}; }
} else {
if (n.z >= 0) { ua = {1, 0, 0}; va = {0, -1, 0}; } else { ua = {-1, 0, 0}; va = {0, -1, 0}; }
}
u = normalize(ua - n * dot(ua, n));
v = normalize(va - n * dot(va, n));
if (uv.rotation != 0) {
float r = uv.rotation * kDeg2Rad, c = std::cos(r), s = std::sin(r);
Vec3 u2 = u * c + v * s, v2 = v * c - u * s;
u = u2; v = v2;
}
}
Vec2 computeUV(const Vec3& p, const Vec3& n, const UVInfo& uv, const Vec2& tile) {
Vec3 u, v;
uvAxes(n, uv, u, v);
float sx = std::fabs(uv.scale.x) < 1e-4f ? 1e-4f : uv.scale.x;
float sy = std::fabs(uv.scale.y) < 1e-4f ? 1e-4f : uv.scale.y;
float tx = tile.x < 1e-3f ? 64.0f : tile.x, ty = tile.y < 1e-3f ? 64.0f : tile.y;
return {(dot(p, u) + uv.offset.x) / (tx * sx), (dot(p, v) + uv.offset.y) / (ty * sy)};
}
// ============================================================ brush / mesh edits
void translateBrush(Brush& b, const Vec3& t, bool lockTexture) {
for (Face& f : b.faces) {
if (lockTexture) {
Vec3 u, v;
uvAxes(f.plane.n, f.uv, u, v);
f.uv.offset.x -= dot(t, u);
f.uv.offset.y -= dot(t, v);
}
f.plane.d += dot(f.plane.n, t);
}
}
void transformBrush(Brush& b, const Mat34& m) {
for (Face& f : b.faces) f.plane = m.transformPlane(f.plane);
}
void transformMesh(MeshObject& m, const Mat34& xf) {
for (Vec3& v : m.verts) v = xf.point(v);
if (xf.det3() < 0) flipMesh(m); // mirrored: keep CCW winding
}
std::vector<Brush> hollowBrush(const Brush& src, float t) {
std::vector<Brush> out;
BrushGeometry g = buildBrushGeometry(src);
if (!g.valid || t <= 0) return out;
size_t n = src.faces.size();
for (size_t k = 0; k < n; ++k) {
if (g.polys[k].size() < 3) continue;
Brush w;
w.contents = src.contents;
w.owner = src.owner;
w.flags = src.flags;
for (size_t j = 0; j < n; ++j)
if (g.polys[j].size() >= 3) w.faces.push_back(src.faces[j]);
Face inner = src.faces[k]; // room-facing wall surface
inner.plane = Plane{-src.faces[k].plane.n, -(src.faces[k].plane.d - t)};
w.faces.push_back(inner);
for (size_t j = 0; j < k; ++j) { // carve away what earlier walls already own
if (g.polys[j].size() < 3) continue;
Face cut = src.faces[j];
cut.plane.d -= t;
cut.flags |= FACE_NODRAW;
w.faces.push_back(cut);
}
if (buildBrushGeometry(w).valid) out.push_back(std::move(w));
}
return out;
}
bool clipBrush(const Brush& b, const Plane& p, Brush* front, Brush* back) {
int mat = b.faces.empty() ? 0 : b.faces[0].material;
bool okF = false, okB = false;
Brush f = b, k = b;
Face ff; ff.material = mat; ff.plane = Plane{-p.n, -p.d};
Face fk; fk.material = mat; fk.plane = p;
f.faces.push_back(ff);
k.faces.push_back(fk);
if (buildBrushGeometry(f).valid) { okF = true; if (front) *front = std::move(f); }
if (buildBrushGeometry(k).valid) { okB = true; if (back) *back = std::move(k); }
return okF || okB;
}
MeshObject brushToMesh(const Brush& b) {
MeshObject m;
m.owner = b.owner;
m.flags = b.flags;
BrushGeometry g = buildBrushGeometry(b);
std::map<std::array<long long, 3>, uint32_t> weld;
for (size_t i = 0; i < g.polys.size(); ++i) {
if (g.polys[i].size() < 3) continue;
MeshPoly p;
p.material = b.faces[i].material;
p.proj = b.faces[i].uv;
for (const Vec3& v : g.polys[i]) {
std::array<long long, 3> key{std::llround(v.x * 100.0), std::llround(v.y * 100.0), std::llround(v.z * 100.0)};
auto it = weld.find(key);
if (it == weld.end()) {
it = weld.emplace(key, (uint32_t)m.verts.size()).first;
m.verts.push_back(v);
}
p.idx.push_back(it->second);
}
m.polys.push_back(std::move(p));
}
return m;
}
void flipMesh(MeshObject& m) {
for (MeshPoly& p : m.polys) {
std::reverse(p.idx.begin(), p.idx.end());
std::reverse(p.uv.begin(), p.uv.end());
}
}
static float hash2(int x, int z, uint32_t seed) {
uint32_t h = (uint32_t)x * 374761393u + (uint32_t)z * 668265263u + seed * 2246822519u;
h = (h ^ (h >> 13)) * 1274126177u;
h ^= h >> 16;
return (float)(h & 0xffffffu) / (float)0xffffff;
}
static float valueNoise(float x, float z, uint32_t seed) {
int xi = (int)std::floor(x), zi = (int)std::floor(z);
float fx = x - (float)xi, fz = z - (float)zi;
fx = fx * fx * (3 - 2 * fx);
fz = fz * fz * (3 - 2 * fz);
float a = hash2(xi, zi, seed), b = hash2(xi + 1, zi, seed), c = hash2(xi, zi + 1, seed), d = hash2(xi + 1, zi + 1, seed);
float top = a + (b - a) * fx, bot = c + (d - c) * fx;
return top + (bot - top) * fz;
}
void noiseMesh(MeshObject& m, float amp, float freq, uint32_t seed) {
for (Vec3& v : m.verts) {
float n = valueNoise(v.x * freq, v.z * freq, seed) * 0.6f + valueNoise(v.x * freq * 2.1f, v.z * freq * 2.1f, seed + 7) * 0.28f +
valueNoise(v.x * freq * 4.3f, v.z * freq * 4.3f, seed + 13) * 0.12f;
v.y += (n * 2.0f - 1.0f) * amp;
}
}
bool extrudePoly(MeshObject& m, int pi, float dist) {
if (pi < 0 || (size_t)pi >= m.polys.size() || std::fabs(dist) < 1e-5f) return false;
MeshPoly poly = m.polys[(size_t)pi];
size_t n = poly.idx.size();
if (n < 3) return false;
Polygon pp;
for (uint32_t i : poly.idx) pp.push_back(m.verts[i]);
Vec3 N = polyNormal(pp);
std::vector<uint32_t> ring(n);
for (size_t k = 0; k < n; ++k) {
ring[k] = (uint32_t)m.verts.size();
m.verts.push_back(m.verts[poly.idx[k]] + N * dist);
}
for (size_t k = 0; k < n; ++k) {
size_t k1 = (k + 1) % n;
MeshPoly side;
side.material = poly.material;
side.proj = poly.proj;
side.smooth = 0;
if (dist > 0) side.idx = {poly.idx[k], poly.idx[k1], ring[k1], ring[k]};
else side.idx = {poly.idx[k], ring[k], ring[k1], poly.idx[k1]};
m.polys.push_back(side);
}
m.polys[(size_t)pi].idx = ring;
return true;
}
Status importOBJ(const std::string& path, Map& map, MeshObject& out) {
std::ifstream f(path);
if (!f) return Status::Fail("cannot open " + path);
out = MeshObject();
size_t slash = path.find_last_of("/\\");
std::string base = slash == std::string::npos ? path : path.substr(slash + 1);
size_t dotp = base.find_last_of('.');
out.name = dotp == std::string::npos ? base : base.substr(0, dotp);
std::vector<Vec2> tex;
int mat = map.materials.empty() ? map.addMaterial("dev/gray") : 0;
uint32_t smooth = 0;
std::string line;
while (std::getline(f, line)) {
std::istringstream ss(line);
std::string tag;
ss >> tag;
if (tag == "v") {
Vec3 v;
ss >> v.x >> v.y >> v.z;
out.verts.push_back(v);
} else if (tag == "vt") {
Vec2 t;
ss >> t.x >> t.y;
t.y = 1.0f - t.y;
tex.push_back(t);
} else if (tag == "usemtl") {
std::string n;
ss >> n;
if (!n.empty()) mat = map.addMaterial(n);
} else if (tag == "s") {
std::string v;
ss >> v;
smooth = (v == "off" || v == "0") ? 0u : (uint32_t)std::atoi(v.c_str());
} else if (tag == "f") {
MeshPoly p;
p.material = mat;
p.smooth = smooth;
bool allUV = true;
std::string tok;
while (ss >> tok) {
int vi = std::atoi(tok.c_str()), ti = 0;
size_t s1 = tok.find('/');
if (s1 != std::string::npos) {
std::string rest = tok.substr(s1 + 1);
if (!rest.empty() && rest[0] != '/') ti = std::atoi(rest.c_str());
}
int v = vi < 0 ? (int)out.verts.size() + vi : vi - 1;
if (v < 0 || (size_t)v >= out.verts.size()) { p.idx.clear(); break; }
p.idx.push_back((uint32_t)v);
int t = ti < 0 ? (int)tex.size() + ti : ti - 1;
if (ti != 0 && t >= 0 && (size_t)t < tex.size()) p.uv.push_back(tex[(size_t)t]);
else allUV = false;
}
if (!allUV) p.uv.clear();
if (p.idx.size() >= 3) out.polys.push_back(std::move(p));
}
}
if (out.polys.empty()) return Status::Fail("no faces in " + path);
return Status::Ok();
}
// ============================================================ brush primitives
Brush makeBox(const Vec3& mn, const Vec3& mx, int material) {
Brush b;
const Vec3 ns[6] = {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}};
const float ds[6] = {mx.x, -mn.x, mx.y, -mn.y, mx.z, -mn.z};
b.faces.resize(6);
for (int i = 0; i < 6; ++i) {
b.faces[(size_t)i].plane = Plane{ns[i], ds[i]};
b.faces[(size_t)i].material = material;
}
return b;
}
Brush makeWedge(const Vec3& mn, const Vec3& mx, int material) {
Brush b = makeBox(mn, mx, material);
Vec3 s(mx.x - mn.x, mx.y - mn.y, 0);
Vec3 n = normalize(Vec3(-s.y, s.x, 0));
b.faces[2].plane = Plane{n, dot(n, Vec3(mn.x, mn.y, 0))};
b.faces.erase(b.faces.begin() + 1); // -X face collapses to a line
return b;
}
static void addOutwardFace(Brush& b, Vec3 n, const Vec3& onPlane, const Vec3& inside, int material) {
n = normalize(n);
float d = dot(n, onPlane);
if (dot(n, inside) - d > 0) { n = -n; d = -d; }
Face f;
f.plane = Plane{n, d};
f.material = material;
b.faces.push_back(f);
}
Brush makeCylinder(const Vec3& mn, const Vec3& mx, int segs, int material) {
segs = std::max(3, std::min(segs, 64));
Vec3 c = (mn + mx) * 0.5f;
float rx = (mx.x - mn.x) * 0.5f, rz = (mx.z - mn.z) * 0.5f;
Brush b;
for (int i = 0; i < segs; ++i) {
float a0 = 2 * kPi * (float)i / (float)segs, a1 = 2 * kPi * (float)(i + 1) / (float)segs;
Vec3 p0(c.x + rx * std::cos(a0), mn.y, c.z + rz * std::sin(a0));
Vec3 p1(c.x + rx * std::cos(a1), mn.y, c.z + rz * std::sin(a1));
addOutwardFace(b, cross(Vec3(0, 1, 0), p1 - p0), p0, c, material);
}
addOutwardFace(b, {0, 1, 0}, Vec3(c.x, mx.y, c.z), c, material);
addOutwardFace(b, {0, -1, 0}, Vec3(c.x, mn.y, c.z), c, material);
return b;
}
Brush makeCone(const Vec3& mn, const Vec3& mx, int segs, int material) {
segs = std::max(3, std::min(segs, 64));
Vec3 c = (mn + mx) * 0.5f;
float rx = (mx.x - mn.x) * 0.5f, rz = (mx.z - mn.z) * 0.5f;
Vec3 apex(c.x, mx.y, c.z), inside(c.x, mn.y + 0.25f * (mx.y - mn.y), c.z);
Brush b;
for (int i = 0; i < segs; ++i) {
float a0 = 2 * kPi * (float)i / (float)segs, a1 = 2 * kPi * (float)(i + 1) / (float)segs;
Vec3 p0(c.x + rx * std::cos(a0), mn.y, c.z + rz * std::sin(a0));
Vec3 p1(c.x + rx * std::cos(a1), mn.y, c.z + rz * std::sin(a1));
addOutwardFace(b, cross(p1 - p0, apex - p0), p0, inside, material);
}
addOutwardFace(b, {0, -1, 0}, Vec3(c.x, mn.y, c.z), inside, material);
return b;
}
std::vector<Brush> makeStairs(const Vec3& mn, const Vec3& mx, int steps, int material) {
steps = std::max(1, std::min(steps, 128));
std::vector<Brush> out;
float dx = (mx.x - mn.x) / (float)steps, dy = (mx.y - mn.y) / (float)steps;
for (int i = 0; i < steps; ++i) {
float x0 = mn.x + dx * (float)i;
out.push_back(makeBox({x0, mn.y, mn.z}, {x0 + dx, mn.y + dy * (float)(i + 1), mx.z}, material));
}
return out;
}
// ============================================================ mesh primitives
namespace {
void addPoly(MeshObject& m, std::vector<uint32_t> idx, std::vector<Vec2> uv, int mat, uint32_t smooth) {
// collapse consecutive duplicates (degenerate quads at poles become triangles)
std::vector<uint32_t> i2;
std::vector<Vec2> u2;
bool hasUV = uv.size() == idx.size();
for (size_t k = 0; k < idx.size(); ++k) {
size_t prev = (k + idx.size() - 1) % idx.size();
if (idx[k] == idx[prev]) continue;
i2.push_back(idx[k]);
if (hasUV) u2.push_back(uv[k]);
}
if (i2.size() < 3) return;
MeshPoly p;
p.idx = std::move(i2);
p.uv = std::move(u2);
p.material = mat;
p.smooth = smooth;
m.polys.push_back(std::move(p));
}
} // namespace
MeshObject meshBox(const Vec3& mn, const Vec3& mx, int mat) {
MeshObject m;
m.verts = {{mn.x, mn.y, mn.z}, {mx.x, mn.y, mn.z}, {mx.x, mx.y, mn.z}, {mn.x, mx.y, mn.z},
{mn.x, mn.y, mx.z}, {mx.x, mn.y, mx.z}, {mx.x, mx.y, mx.z}, {mn.x, mx.y, mx.z}};
const uint32_t q[6][4] = {{1, 2, 6, 5}, {0, 4, 7, 3}, {3, 7, 6, 2}, {0, 1, 5, 4}, {4, 5, 6, 7}, {0, 3, 2, 1}};
for (auto& f : q) addPoly(m, {f[0], f[1], f[2], f[3]}, {}, mat, 0);
return m;
}
MeshObject meshCylinder(const Vec3& mn, const Vec3& mx, int segs, int mat) {
segs = std::max(3, std::min(segs, 128));
MeshObject m;
Vec3 c = (mn + mx) * 0.5f;
float rx = (mx.x - mn.x) * 0.5f, rz = (mx.z - mn.z) * 0.5f;
float circ = 2 * kPi * (rx + rz) * 0.5f, h = mx.y - mn.y;
for (int s = 0; s < segs; ++s) { // bottom ring [0,segs) then top ring [segs,2*segs)
float a = 2 * kPi * (float)s / (float)segs;
m.verts.push_back({c.x + rx * std::cos(a), mn.y, c.z + rz * std::sin(a)});
}
for (int s = 0; s < segs; ++s) {
float a = 2 * kPi * (float)s / (float)segs;
m.verts.push_back({c.x + rx * std::cos(a), mx.y, c.z + rz * std::sin(a)});
}
for (int s = 0; s < segs; ++s) {
uint32_t s0 = (uint32_t)s, s1 = (uint32_t)((s + 1) % segs);
float u0 = circ * (float)s / (float)segs / 64.0f, u1 = circ * (float)(s + 1) / (float)segs / 64.0f, v = h / 64.0f;
addPoly(m, {s1, s0, (uint32_t)segs + s0, (uint32_t)segs + s1}, {{u1, v}, {u0, v}, {u0, 0}, {u1, 0}}, mat, 1);
}
std::vector<uint32_t> top, bot;
for (int s = 0; s < segs; ++s) {
bot.push_back((uint32_t)s);
top.push_back((uint32_t)(2 * segs - 1 - s));
}
addPoly(m, top, {}, mat, 0);
addPoly(m, bot, {}, mat, 0);
return m;
}
MeshObject meshCone(const Vec3& mn, const Vec3& mx, int segs, int mat) {
segs = std::max(3, std::min(segs, 128));
MeshObject m;
Vec3 c = (mn + mx) * 0.5f;
float rx = (mx.x - mn.x) * 0.5f, rz = (mx.z - mn.z) * 0.5f;
float circ = 2 * kPi * (rx + rz) * 0.5f, h = mx.y - mn.y;
for (int s = 0; s < segs; ++s) {
float a = 2 * kPi * (float)s / (float)segs;
m.verts.push_back({c.x + rx * std::cos(a), mn.y, c.z + rz * std::sin(a)});
}
uint32_t apex = (uint32_t)m.verts.size();
m.verts.push_back({c.x, mx.y, c.z});
for (int s = 0; s < segs; ++s) {
uint32_t s0 = (uint32_t)s, s1 = (uint32_t)((s + 1) % segs);
float u0 = circ * (float)s / (float)segs / 64.0f, u1 = circ * (float)(s + 1) / (float)segs / 64.0f, v = h / 64.0f;
addPoly(m, {s1, s0, apex}, {{u1, v}, {u0, v}, {(u0 + u1) * 0.5f, 0}}, mat, 1);
}
std::vector<uint32_t> bot;
for (int s = 0; s < segs; ++s) bot.push_back((uint32_t)s);
addPoly(m, bot, {}, mat, 0);
return m;
}
MeshObject meshSphere(const Vec3& mn, const Vec3& mx, int rings, int segs, int mat) {
rings = std::max(2, std::min(rings, 128));
segs = std::max(3, std::min(segs, 128));
MeshObject m;
Vec3 c = (mn + mx) * 0.5f;
float rx = (mx.x - mn.x) * 0.5f, ry = (mx.y - mn.y) * 0.5f, rz = (mx.z - mn.z) * 0.5f;
float circ = 2 * kPi * (rx + rz) * 0.5f, arc = kPi * ry;
std::vector<std::vector<uint32_t>> ring((size_t)rings + 1);
for (int r = 0; r <= rings; ++r) {
float th = kPi * (float)r / (float)rings;
if (r == 0 || r == rings) {
uint32_t id = (uint32_t)m.verts.size();
m.verts.push_back({c.x, c.y + std::cos(th) * ry, c.z});
ring[(size_t)r].assign((size_t)segs + 1, id);
continue;
}
for (int s = 0; s < segs; ++s) {
float ph = 2 * kPi * (float)s / (float)segs;
ring[(size_t)r].push_back((uint32_t)m.verts.size());
m.verts.push_back({c.x + std::sin(th) * std::cos(ph) * rx, c.y + std::cos(th) * ry, c.z + std::sin(th) * std::sin(ph) * rz});
}
ring[(size_t)r].push_back(ring[(size_t)r][0]);
}
for (int r = 0; r < rings; ++r) {
for (int s = 0; s < segs; ++s) {
size_t R0 = (size_t)r, R1 = (size_t)r + 1, S0 = (size_t)s, S1 = (size_t)s + 1;
float u0 = circ * (float)s / (float)segs / 64.0f, u1 = circ * (float)(s + 1) / (float)segs / 64.0f;
float v0 = arc * (float)r / (float)rings / 64.0f, v1 = arc * (float)(r + 1) / (float)rings / 64.0f;
addPoly(m, {ring[R1][S1], ring[R1][S0], ring[R0][S0], ring[R0][S1]}, {{u1, v1}, {u0, v1}, {u0, v0}, {u1, v0}}, mat, 1);
}
}
return m;
}
MeshObject meshTorus(const Vec3& mn, const Vec3& mx, int segs, int sides, int mat) {
segs = std::max(3, std::min(segs, 128));
sides = std::max(3, std::min(sides, 64));
MeshObject m;
Vec3 c = (mn + mx) * 0.5f;
float outer = std::min(mx.x - mn.x, mx.z - mn.z) * 0.5f, r = (mx.y - mn.y) * 0.5f;
r = std::min(r, outer * 0.95f);
float R = outer - r;
for (int i = 0; i < segs; ++i) {
float ph = 2 * kPi * (float)i / (float)segs;
for (int j = 0; j < sides; ++j) {
float th = 2 * kPi * (float)j / (float)sides;
float rr = R + r * std::cos(th);
m.verts.push_back({c.x + rr * std::cos(ph), c.y + r * std::sin(th), c.z + rr * std::sin(ph)});
}
}
auto V = [&](int i, int j) { return (uint32_t)(((i % segs) * sides) + (j % sides)); };
float uc = 2 * kPi * R / 64.0f, vc = 2 * kPi * r / 64.0f;
for (int i = 0; i < segs; ++i) {
for (int j = 0; j < sides; ++j) {
float u0 = uc * (float)i / (float)segs, u1 = uc * (float)(i + 1) / (float)segs;
float v0 = vc * (float)j / (float)sides, v1 = vc * (float)(j + 1) / (float)sides;
addPoly(m, {V(i, j), V(i, j + 1), V(i + 1, j + 1), V(i + 1, j)}, {{u0, v0}, {u0, v1}, {u1, v1}, {u1, v0}}, mat, 1);
}
}
return m;
}
MeshObject meshGrid(const Vec3& mn, const Vec3& mx, int sx, int sz, int mat) {
sx = std::max(1, std::min(sx, 256));
sz = std::max(1, std::min(sz, 256));
MeshObject m;
float y = (mn.y + mx.y) * 0.5f;
for (int j = 0; j <= sz; ++j)
for (int i = 0; i <= sx; ++i)
m.verts.push_back({mn.x + (mx.x - mn.x) * (float)i / (float)sx, y, mn.z + (mx.z - mn.z) * (float)j / (float)sz});
auto V = [&](int i, int j) { return (uint32_t)(j * (sx + 1) + i); };
for (int j = 0; j < sz; ++j)
for (int i = 0; i < sx; ++i) {
uint32_t a = V(i, j), b = V(i, j + 1), c = V(i + 1, j + 1), d = V(i + 1, j);
std::vector<Vec2> uv;
for (uint32_t k : {a, b, c, d}) uv.push_back({m.verts[k].x / 64.0f, m.verts[k].z / 64.0f});
addPoly(m, {a, b, c, d}, uv, mat, 1);
}
return m;
}
} // namespace hmap