#include "hmap/hmap.h" #include #include #include #include #include #include #include namespace hmap { // ============================================================ polygon building (double precision) namespace { struct DV { double x, y, z; }; using DPoly = std::vector; 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 d(n); std::vector 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 hollowBrush(const Brush& src, float t) { std::vector 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, 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 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 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 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 makeStairs(const Vec3& mn, const Vec3& mx, int steps, int material) { steps = std::max(1, std::min(steps, 128)); std::vector 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 idx, std::vector uv, int mat, uint32_t smooth) { // collapse consecutive duplicates (degenerate quads at poles become triangles) std::vector i2; std::vector 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 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 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> 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 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