656 lines
25 KiB
C++
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
|