refactor(editor): extract 12 composite mesh primitives into cli_gen_mesh.cpp
Moves the recently-added composite-prop mesh handlers (rock,
pillar, bridge, tower, house, fountain, statue, altar, portal,
archway, barrel, chest) into their own translation unit. These
12 handlers were the most contiguous block of similar-shaped
mesh code in main.cpp.
Other mesh handlers (--gen-mesh dispatcher, fence, tree, grid,
stairs, disc, tube, capsule, arch, pyramid, from-heightmap,
textured) still live in main.cpp and may be migrated in
subsequent batches.
main.cpp drops 24,270 → 22,681 lines (-1,589). Behavior
verified by re-running rock/chest/archway/fountain.
2026-05-08 22:19:41 -07:00
|
|
|
|
#include "cli_gen_mesh.hpp"
|
|
|
|
|
|
|
|
|
|
|
|
#include "pipeline/wowee_model.hpp"
|
|
|
|
|
|
#include <glm/glm.hpp>
|
|
|
|
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
#include <cmath>
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
#include <filesystem>
|
|
|
|
|
|
#include <map>
|
|
|
|
|
|
#include <string>
|
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
|
|
namespace wowee {
|
|
|
|
|
|
namespace editor {
|
|
|
|
|
|
namespace cli {
|
|
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
|
|
int handleRock(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Procedural boulder. Starts as an octahedron, subdivides
|
|
|
|
|
|
// each face N times to get a rounded base, then displaces
|
|
|
|
|
|
// each vertex along its outward direction by a smooth
|
|
|
|
|
|
// sin/cos noise term controlled by `seed` and `roughness`.
|
|
|
|
|
|
// Result is a unique-shaped rock per seed — perfect for
|
|
|
|
|
|
// scattering across a zone via random-populate-zone.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The 16th procedural primitive in the WOM library.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float radius = 1.0f;
|
|
|
|
|
|
float roughness = 0.25f; // 0..1, fraction of radius
|
|
|
|
|
|
int subdiv = 2; // 0=8 tris, 1=32, 2=128, 3=512
|
|
|
|
|
|
uint32_t seed = 1;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { radius = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { roughness = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { subdiv = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { seed = static_cast<uint32_t>(std::stoul(argv[++i])); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (radius <= 0 || roughness < 0 || roughness > 1 ||
|
|
|
|
|
|
subdiv < 0 || subdiv > 4) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-rock: radius>0, roughness 0..1, subdiv 0..4\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Build sphere via octahedron subdivision. Vertices are
|
|
|
|
|
|
// accumulated in unit-length form first, then displaced.
|
|
|
|
|
|
std::vector<glm::vec3> sv; // sphere verts (unit)
|
|
|
|
|
|
std::vector<glm::uvec3> st; // sphere tris (vertex indices)
|
|
|
|
|
|
sv = {
|
|
|
|
|
|
{ 1, 0, 0}, {-1, 0, 0},
|
|
|
|
|
|
{ 0, 1, 0}, { 0,-1, 0},
|
|
|
|
|
|
{ 0, 0, 1}, { 0, 0,-1},
|
|
|
|
|
|
};
|
|
|
|
|
|
st = {
|
|
|
|
|
|
{0, 2, 4}, {2, 1, 4}, {1, 3, 4}, {3, 0, 4},
|
|
|
|
|
|
{2, 0, 5}, {1, 2, 5}, {3, 1, 5}, {0, 3, 5},
|
|
|
|
|
|
};
|
|
|
|
|
|
// Edge-midpoint cache so shared edges don't duplicate verts.
|
|
|
|
|
|
for (int s = 0; s < subdiv; ++s) {
|
|
|
|
|
|
std::map<std::pair<uint32_t,uint32_t>, uint32_t> midCache;
|
|
|
|
|
|
auto midpoint = [&](uint32_t a, uint32_t b) -> uint32_t {
|
|
|
|
|
|
auto key = std::make_pair(std::min(a,b), std::max(a,b));
|
|
|
|
|
|
auto it = midCache.find(key);
|
|
|
|
|
|
if (it != midCache.end()) return it->second;
|
|
|
|
|
|
glm::vec3 m = glm::normalize((sv[a] + sv[b]) * 0.5f);
|
|
|
|
|
|
uint32_t idx = static_cast<uint32_t>(sv.size());
|
|
|
|
|
|
sv.push_back(m);
|
|
|
|
|
|
midCache[key] = idx;
|
|
|
|
|
|
return idx;
|
|
|
|
|
|
};
|
|
|
|
|
|
std::vector<glm::uvec3> next;
|
|
|
|
|
|
next.reserve(st.size() * 4);
|
|
|
|
|
|
for (auto& tri : st) {
|
|
|
|
|
|
uint32_t a = tri.x, b = tri.y, c = tri.z;
|
|
|
|
|
|
uint32_t ab = midpoint(a, b);
|
|
|
|
|
|
uint32_t bc = midpoint(b, c);
|
|
|
|
|
|
uint32_t ca = midpoint(c, a);
|
|
|
|
|
|
next.push_back({a, ab, ca});
|
|
|
|
|
|
next.push_back({b, bc, ab});
|
|
|
|
|
|
next.push_back({c, ca, bc});
|
|
|
|
|
|
next.push_back({ab, bc, ca});
|
|
|
|
|
|
}
|
|
|
|
|
|
st.swap(next);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Smooth pseudo-noise displacement. Three orthogonal sin
|
|
|
|
|
|
// products give a coherent bumpy surface; phase shift uses
|
|
|
|
|
|
// the seed so each value yields a distinct silhouette.
|
|
|
|
|
|
float sf = static_cast<float>(seed);
|
|
|
|
|
|
auto displace = [&](glm::vec3 p) -> float {
|
|
|
|
|
|
float n = std::sin(p.x * 3.1f + sf * 0.91f) *
|
|
|
|
|
|
std::sin(p.y * 4.7f + sf * 1.37f) *
|
|
|
|
|
|
std::sin(p.z * 5.3f + sf * 0.43f);
|
|
|
|
|
|
float n2 = std::sin(p.x * 7.1f + sf * 0.11f) *
|
|
|
|
|
|
std::sin(p.y * 8.3f + sf * 2.13f) *
|
|
|
|
|
|
std::sin(p.z * 9.7f + sf * 1.91f);
|
|
|
|
|
|
return 1.0f + roughness * (0.7f * n + 0.3f * n2);
|
|
|
|
|
|
};
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
std::vector<glm::vec3> finalPos(sv.size());
|
|
|
|
|
|
for (size_t v = 0; v < sv.size(); ++v) {
|
|
|
|
|
|
finalPos[v] = sv[v] * (radius * displace(sv[v]));
|
|
|
|
|
|
}
|
|
|
|
|
|
// Per-vertex normals from triangle face normals (averaged).
|
|
|
|
|
|
std::vector<glm::vec3> normals(sv.size(), glm::vec3(0));
|
|
|
|
|
|
for (auto& tri : st) {
|
|
|
|
|
|
glm::vec3 a = finalPos[tri.x];
|
|
|
|
|
|
glm::vec3 b = finalPos[tri.y];
|
|
|
|
|
|
glm::vec3 c = finalPos[tri.z];
|
|
|
|
|
|
glm::vec3 fn = glm::normalize(glm::cross(b - a, c - a));
|
|
|
|
|
|
normals[tri.x] += fn;
|
|
|
|
|
|
normals[tri.y] += fn;
|
|
|
|
|
|
normals[tri.z] += fn;
|
|
|
|
|
|
}
|
|
|
|
|
|
for (auto& n : normals) n = glm::length(n) > 1e-6f
|
|
|
|
|
|
? glm::normalize(n) : glm::vec3(0, 1, 0);
|
|
|
|
|
|
for (size_t v = 0; v < sv.size(); ++v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = finalPos[v];
|
|
|
|
|
|
vtx.normal = normals[v];
|
|
|
|
|
|
// Spherical UV unwrap. Visible seam at u=0/1 is
|
|
|
|
|
|
// acceptable for rocks — usually hidden by terrain.
|
|
|
|
|
|
glm::vec3 d = glm::normalize(sv[v]);
|
|
|
|
|
|
vtx.texCoord = {
|
|
|
|
|
|
0.5f + std::atan2(d.z, d.x) / (2.0f * 3.14159265f),
|
|
|
|
|
|
0.5f - std::asin(d.y) / 3.14159265f,
|
|
|
|
|
|
};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (auto& tri : st) {
|
|
|
|
|
|
wom.indices.push_back(tri.x);
|
|
|
|
|
|
wom.indices.push_back(tri.y);
|
|
|
|
|
|
wom.indices.push_back(tri.z);
|
|
|
|
|
|
}
|
|
|
|
|
|
float bound = radius * (1.0f + roughness);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-bound);
|
|
|
|
|
|
wom.boundMax = glm::vec3( bound);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-rock: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" radius : %.3f\n", radius);
|
|
|
|
|
|
std::printf(" roughness : %.3f\n", roughness);
|
|
|
|
|
|
std::printf(" subdiv : %d\n", subdiv);
|
|
|
|
|
|
std::printf(" seed : %u\n", seed);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handlePillar(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Procedural classical column. Central shaft is a
|
|
|
|
|
|
// cylinder with N concave flutes (radius modulated by
|
|
|
|
|
|
// cos²(theta*flutes/2)), capped above and below by
|
|
|
|
|
|
// wider disc caps that act as a simple capital and
|
|
|
|
|
|
// base. The 17th procedural mesh primitive — useful
|
|
|
|
|
|
// for ruins, temples, dungeons, plaza decoration.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float radius = 0.4f;
|
|
|
|
|
|
float height = 4.0f;
|
|
|
|
|
|
int flutes = 12;
|
|
|
|
|
|
float capScale = 1.25f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { radius = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { flutes = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { capScale = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (radius <= 0 || height <= 0 ||
|
|
|
|
|
|
flutes < 4 || flutes > 64 ||
|
|
|
|
|
|
capScale < 1.0f || capScale > 4.0f) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-pillar: radius>0, height>0, flutes 4..64, capScale 1..4\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
// We use 8 segments per flute so the cosine-modulated
|
|
|
|
|
|
// groove resolves smoothly. Vertical: 2 rings (top/bot
|
|
|
|
|
|
// of shaft) + cap/base discs.
|
|
|
|
|
|
const int radSegs = flutes * 8;
|
|
|
|
|
|
const float fluteDepth = radius * 0.12f;
|
|
|
|
|
|
float capR = radius * capScale;
|
|
|
|
|
|
float capThick = radius * 0.25f;
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Shaft side ring at given y. radius modulated by flute count.
|
|
|
|
|
|
auto buildShaftRing = [&](float y) -> uint32_t {
|
|
|
|
|
|
uint32_t start = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= radSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / radSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
float c = std::cos(ang * flutes * 0.5f);
|
|
|
|
|
|
float r = radius - fluteDepth * (c * c);
|
|
|
|
|
|
glm::vec3 p(r * std::cos(ang), y, r * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, glm::normalize(n), glm::vec2(u, y / height));
|
|
|
|
|
|
}
|
|
|
|
|
|
return start;
|
|
|
|
|
|
};
|
|
|
|
|
|
// Cap/base disc ring (constant radius capR) at given y.
|
|
|
|
|
|
auto buildCapRing = [&](float y, float r) -> uint32_t {
|
|
|
|
|
|
uint32_t start = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= radSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / radSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(r * std::cos(ang), y, r * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, glm::normalize(n), glm::vec2(u, y / height));
|
|
|
|
|
|
}
|
|
|
|
|
|
return start;
|
|
|
|
|
|
};
|
|
|
|
|
|
// Layout (Y goes up):
|
|
|
|
|
|
// capThick: base disc bottom
|
|
|
|
|
|
// capThick: base disc top
|
|
|
|
|
|
// ...shaft from capThick to height-capThick...
|
|
|
|
|
|
// height-capThick: cap disc bottom
|
|
|
|
|
|
// height: cap disc top
|
|
|
|
|
|
float shaftY0 = capThick;
|
|
|
|
|
|
float shaftY1 = height - capThick;
|
|
|
|
|
|
uint32_t baseBot = buildCapRing(0.0f, capR);
|
|
|
|
|
|
uint32_t baseTop = buildCapRing(shaftY0, capR);
|
|
|
|
|
|
uint32_t shaftBot = buildShaftRing(shaftY0);
|
|
|
|
|
|
uint32_t shaftTop = buildShaftRing(shaftY1);
|
|
|
|
|
|
uint32_t capBot = buildCapRing(shaftY1, capR);
|
|
|
|
|
|
uint32_t capTop = buildCapRing(height, capR);
|
|
|
|
|
|
// Quad connector helper.
|
|
|
|
|
|
auto connect = [&](uint32_t a0, uint32_t a1) {
|
|
|
|
|
|
for (int sg = 0; sg < radSegs; ++sg) {
|
|
|
|
|
|
uint32_t i00 = a0 + sg;
|
|
|
|
|
|
uint32_t i01 = a0 + sg + 1;
|
|
|
|
|
|
uint32_t i10 = a1 + sg;
|
|
|
|
|
|
uint32_t i11 = a1 + sg + 1;
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{ i00, i10, i01, i01, i10, i11 });
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
connect(baseBot, baseTop); // base side
|
|
|
|
|
|
connect(shaftBot, shaftTop); // shaft
|
|
|
|
|
|
connect(capBot, capTop); // cap side
|
|
|
|
|
|
// Bottom cap (downward fan), top cap (upward fan).
|
|
|
|
|
|
uint32_t bottomCenter = addV({0, 0, 0}, {0, -1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
uint32_t topCenter = addV({0, height, 0}, {0, 1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < radSegs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{ bottomCenter, baseBot + sg + 1, baseBot + sg });
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{ topCenter, capTop + sg, capTop + sg + 1 });
|
|
|
|
|
|
}
|
|
|
|
|
|
// Annular surfaces where caps meet shaft (top of base disc
|
|
|
|
|
|
// out to shaft, etc.). Just connect the two rings — they
|
|
|
|
|
|
// sit at the same Y so this looks like a flat ring.
|
|
|
|
|
|
connect(baseTop, shaftBot);
|
|
|
|
|
|
connect(shaftTop, capBot);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-capR, 0, -capR);
|
|
|
|
|
|
wom.boundMax = glm::vec3( capR, height, capR);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-pillar: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" radius : %.3f\n", radius);
|
|
|
|
|
|
std::printf(" height : %.3f\n", height);
|
|
|
|
|
|
std::printf(" flutes : %d\n", flutes);
|
|
|
|
|
|
std::printf(" cap scale : %.2fx (capR=%.3f)\n", capScale, capR);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleBridge(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Procedural plank bridge. Deck is N axis-aligned planks
|
|
|
|
|
|
// running across the bridge's width with small gaps
|
|
|
|
|
|
// between, plus two side rails (top + bottom rails on
|
|
|
|
|
|
// posts). Bridge length runs along +X, width is on Z.
|
|
|
|
|
|
// The 18th procedural mesh primitive — useful for
|
|
|
|
|
|
// river crossings, dungeon catwalks, scenic overlooks.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float length = 6.0f; // along X
|
|
|
|
|
|
float width = 2.0f; // along Z
|
|
|
|
|
|
int planks = 6; // plank count across the length
|
|
|
|
|
|
float railHeight = 1.0f; // rail height above deck (0 = no rails)
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { length = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { planks = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { railHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (length <= 0 || width <= 0 ||
|
|
|
|
|
|
planks < 1 || planks > 64 ||
|
|
|
|
|
|
railHeight < 0 || railHeight > 4.0f) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-bridge: length>0, width>0, planks 1..64, rail 0..4\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
// Box helper — builds 24-vert / 12-tri box centered on
|
|
|
|
|
|
// (cx, cy, cz) with half-extents (hx, hy, hz). Each face
|
|
|
|
|
|
// gets unique vertices so flat-shading works. Indices are
|
|
|
|
|
|
// pushed into wom.indices directly.
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
struct Face {
|
|
|
|
|
|
glm::vec3 n;
|
|
|
|
|
|
glm::vec3 du, dv; // unit-length axes spanning the face
|
|
|
|
|
|
};
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}}, // top (+Y)
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}}, // bottom (-Y)
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}}, // right (+X)
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}}, // left (-X)
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}}, // front (+Z)
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}}, // back (-Z)
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p;
|
|
|
|
|
|
vtx.normal = f.n;
|
|
|
|
|
|
vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{ base, base + 1, base + 2, base, base + 2, base + 3 });
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Deck: planks along X, gap = 5% of plank pitch.
|
|
|
|
|
|
float plankThickness = 0.08f;
|
|
|
|
|
|
float plankPitch = length / planks;
|
|
|
|
|
|
float plankWidth = plankPitch * 0.95f;
|
|
|
|
|
|
for (int p = 0; p < planks; ++p) {
|
|
|
|
|
|
float cx = -length * 0.5f + plankPitch * (p + 0.5f);
|
|
|
|
|
|
addBox(cx, plankThickness * 0.5f, 0,
|
|
|
|
|
|
plankWidth * 0.5f, plankThickness * 0.5f, width * 0.5f);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Rails: 2 sides × (top rail + 3 posts) when railHeight > 0
|
|
|
|
|
|
if (railHeight > 0.0f) {
|
|
|
|
|
|
float postR = 0.06f;
|
|
|
|
|
|
float topRailR = 0.08f;
|
|
|
|
|
|
int postCount = 3;
|
|
|
|
|
|
float rzOffset = width * 0.5f - postR;
|
|
|
|
|
|
for (int side = 0; side < 2; ++side) {
|
|
|
|
|
|
float zSign = (side == 0) ? 1.0f : -1.0f;
|
|
|
|
|
|
float z = zSign * rzOffset;
|
|
|
|
|
|
// Top rail: long thin box spanning length
|
|
|
|
|
|
addBox(0, plankThickness + railHeight, z,
|
|
|
|
|
|
length * 0.5f, topRailR, topRailR);
|
|
|
|
|
|
// Posts evenly spaced
|
|
|
|
|
|
for (int p = 0; p < postCount; ++p) {
|
|
|
|
|
|
float t = (postCount > 1)
|
|
|
|
|
|
? static_cast<float>(p) / (postCount - 1)
|
|
|
|
|
|
: 0.5f;
|
|
|
|
|
|
float cx = -length * 0.5f + length * t;
|
|
|
|
|
|
if (p == 0) cx += postR;
|
|
|
|
|
|
if (p == postCount - 1) cx -= postR;
|
|
|
|
|
|
addBox(cx, plankThickness + railHeight * 0.5f, z,
|
|
|
|
|
|
postR, railHeight * 0.5f, postR);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = plankThickness + railHeight;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-length * 0.5f, 0, -width * 0.5f);
|
|
|
|
|
|
wom.boundMax = glm::vec3( length * 0.5f, maxY, width * 0.5f);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-bridge: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" length : %.3f\n", length);
|
|
|
|
|
|
std::printf(" width : %.3f\n", width);
|
|
|
|
|
|
std::printf(" planks : %d\n", planks);
|
|
|
|
|
|
std::printf(" rail H : %.3f%s\n", railHeight,
|
|
|
|
|
|
railHeight > 0 ? "" : " (no rails)");
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleTower(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Procedural castle tower. Solid cylindrical shaft with
|
|
|
|
|
|
// crenellated battlements ringing the top: alternating
|
|
|
|
|
|
// raised "merlons" and gaps. Each merlon is a thin
|
|
|
|
|
|
// angular wedge sitting on the top rim. Useful for
|
|
|
|
|
|
// keeps, watchtowers, perimeter walls.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The 19th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float radius = 1.5f;
|
|
|
|
|
|
float height = 8.0f;
|
|
|
|
|
|
int battlements = 8; // merlons around the rim
|
|
|
|
|
|
float battlementH = 0.5f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { radius = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { battlements = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { battlementH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (radius <= 0 || height <= 0 ||
|
|
|
|
|
|
battlements < 4 || battlements > 64 ||
|
|
|
|
|
|
battlementH < 0 || battlementH > 4.0f) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-tower: radius>0, height>0, battlements 4..64, bH 0..4\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
const int radSegs = std::max(24, battlements * 4);
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Cylinder shaft: side ring at y=0 and y=height.
|
|
|
|
|
|
uint32_t botRing = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= radSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / radSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(radius * std::cos(ang), 0, radius * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, glm::vec2(u, 0));
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t topRing = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= radSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / radSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(radius * std::cos(ang), height, radius * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, glm::vec2(u, 1));
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < radSegs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {
|
|
|
|
|
|
botRing + sg, topRing + sg, botRing + sg + 1,
|
|
|
|
|
|
botRing + sg + 1, topRing + sg, topRing + sg + 1
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Top cap (fan toward upward-facing center).
|
|
|
|
|
|
uint32_t topCenter = addV({0, height, 0}, {0, 1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < radSegs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{ topCenter, topRing + sg, topRing + sg + 1 });
|
|
|
|
|
|
}
|
|
|
|
|
|
// Bottom cap (fan toward downward-facing center).
|
|
|
|
|
|
uint32_t botCenter = addV({0, 0, 0}, {0, -1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < radSegs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{ botCenter, botRing + sg + 1, botRing + sg });
|
|
|
|
|
|
}
|
|
|
|
|
|
// Battlements: thin curved blocks around the top rim,
|
|
|
|
|
|
// half the slots filled (alternating merlon/gap).
|
|
|
|
|
|
// Each merlon is approximated by an extruded arc segment
|
|
|
|
|
|
// at the wall radius extending outward slightly.
|
|
|
|
|
|
if (battlementH > 0.0f) {
|
|
|
|
|
|
int merlonSpan = radSegs / battlements;
|
|
|
|
|
|
int merlonHalf = std::max(1, merlonSpan / 2);
|
|
|
|
|
|
float outerR = radius * 1.05f;
|
|
|
|
|
|
float innerR = radius * 0.95f;
|
|
|
|
|
|
for (int b = 0; b < battlements; ++b) {
|
|
|
|
|
|
int startSeg = b * merlonSpan;
|
|
|
|
|
|
// Build 8-vert box-like segment between angles
|
|
|
|
|
|
// covering merlonHalf slots (so half the rim is
|
|
|
|
|
|
// filled, forming the merlon/gap pattern).
|
|
|
|
|
|
float ang0 = 2.0f * pi * static_cast<float>(startSeg) / radSegs;
|
|
|
|
|
|
float ang1 = 2.0f * pi * static_cast<float>(startSeg + merlonHalf) / radSegs;
|
|
|
|
|
|
glm::vec3 outer0(outerR * std::cos(ang0), 0, outerR * std::sin(ang0));
|
|
|
|
|
|
glm::vec3 outer1(outerR * std::cos(ang1), 0, outerR * std::sin(ang1));
|
|
|
|
|
|
glm::vec3 inner0(innerR * std::cos(ang0), 0, innerR * std::sin(ang0));
|
|
|
|
|
|
glm::vec3 inner1(innerR * std::cos(ang1), 0, innerR * std::sin(ang1));
|
|
|
|
|
|
glm::vec3 yLow(0, height, 0);
|
|
|
|
|
|
glm::vec3 yHigh(0, height + battlementH, 0);
|
|
|
|
|
|
glm::vec3 norm = glm::normalize(
|
|
|
|
|
|
outer0 + outer1 - inner0 - inner1);
|
|
|
|
|
|
auto V = [&](glm::vec3 p, glm::vec3 n) {
|
|
|
|
|
|
return addV(p, n, {0, 0});
|
|
|
|
|
|
};
|
|
|
|
|
|
// 8 verts: 4 corners × 2 heights
|
|
|
|
|
|
uint32_t bbl = V(outer0 + yLow, norm); // bot outer left
|
|
|
|
|
|
uint32_t bbr = V(outer1 + yLow, norm);
|
|
|
|
|
|
uint32_t btl = V(outer0 + yHigh, norm); // top outer left
|
|
|
|
|
|
uint32_t btr = V(outer1 + yHigh, norm);
|
|
|
|
|
|
uint32_t ibl = V(inner0 + yLow, -norm); // bot inner left
|
|
|
|
|
|
uint32_t ibr = V(inner1 + yLow, -norm);
|
|
|
|
|
|
uint32_t itl = V(inner0 + yHigh, -norm); // top inner left
|
|
|
|
|
|
uint32_t itr = V(inner1 + yHigh, -norm);
|
|
|
|
|
|
// outer face
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {bbl, btl, bbr, bbr, btl, btr});
|
|
|
|
|
|
// inner face
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {ibr, itr, ibl, ibl, itr, itl});
|
|
|
|
|
|
// top face
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {btl, itl, btr, btr, itl, itr});
|
|
|
|
|
|
// left and right end caps
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {bbl, ibl, btl, btl, ibl, itl});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {bbr, btr, ibr, ibr, btr, itr});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = height + battlementH;
|
|
|
|
|
|
float maxR = radius * 1.05f;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-maxR, 0, -maxR);
|
|
|
|
|
|
wom.boundMax = glm::vec3( maxR, maxY, maxR);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-tower: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" radius : %.3f\n", radius);
|
|
|
|
|
|
std::printf(" height : %.3f\n", height);
|
|
|
|
|
|
std::printf(" battlements : %d (%.3fm tall)\n",
|
|
|
|
|
|
battlements, battlementH);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleHouse(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Simple procedural house: cube body + pyramid roof
|
|
|
|
|
|
// meeting at a central apex above the body's roofline.
|
|
|
|
|
|
// The pyramid sits flush on the body so the eaves
|
|
|
|
|
|
// line up with the wall edges. No door cutout — that
|
|
|
|
|
|
// can be added later via mesh boolean ops or texture.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The 20th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float width = 4.0f; // along X
|
|
|
|
|
|
float depth = 4.0f; // along Z
|
|
|
|
|
|
float height = 3.0f; // wall height (Y)
|
|
|
|
|
|
float roofH = 2.0f; // pyramid above walls
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { depth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { roofH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (width <= 0 || depth <= 0 || height <= 0 ||
|
|
|
|
|
|
roofH < 0 || roofH > 20.0f) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-house: width/depth/height>0, roof 0..20\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
float hx = width * 0.5f;
|
|
|
|
|
|
float hz = depth * 0.5f;
|
|
|
|
|
|
// 4 walls — each a quad with an outward-facing normal so
|
|
|
|
|
|
// the house reads as solid even with backface culling on.
|
|
|
|
|
|
struct Wall {
|
|
|
|
|
|
glm::vec3 a, b, c, d; // CCW from outside
|
|
|
|
|
|
glm::vec3 n;
|
|
|
|
|
|
};
|
|
|
|
|
|
Wall walls[4] = {
|
|
|
|
|
|
{{ hx, 0, hz}, {-hx, 0, hz}, {-hx, height, hz}, { hx, height, hz}, { 0, 0, 1}}, // +Z
|
|
|
|
|
|
{{-hx, 0, -hz}, { hx, 0, -hz}, { hx, height, -hz}, {-hx, height, -hz}, { 0, 0, -1}}, // -Z
|
|
|
|
|
|
{{ hx, 0, -hz}, { hx, 0, hz}, { hx, height, hz}, { hx, height, -hz}, { 1, 0, 0}}, // +X
|
|
|
|
|
|
{{-hx, 0, hz}, {-hx, 0, -hz}, {-hx, height, -hz}, {-hx, height, hz}, {-1, 0, 0}}, // -X
|
|
|
|
|
|
};
|
|
|
|
|
|
for (const Wall& w : walls) {
|
|
|
|
|
|
uint32_t a = addV(w.a, w.n, {0, 0});
|
|
|
|
|
|
uint32_t b = addV(w.b, w.n, {1, 0});
|
|
|
|
|
|
uint32_t c = addV(w.c, w.n, {1, 1});
|
|
|
|
|
|
uint32_t d = addV(w.d, w.n, {0, 1});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {a, b, c, a, c, d});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Floor (single quad, normal-down so it shows from below;
|
|
|
|
|
|
// texturable as a foundation slab).
|
|
|
|
|
|
{
|
|
|
|
|
|
uint32_t a = addV({-hx, 0, -hz}, {0, -1, 0}, {0, 0});
|
|
|
|
|
|
uint32_t b = addV({ hx, 0, -hz}, {0, -1, 0}, {1, 0});
|
|
|
|
|
|
uint32_t c = addV({ hx, 0, hz}, {0, -1, 0}, {1, 1});
|
|
|
|
|
|
uint32_t d = addV({-hx, 0, hz}, {0, -1, 0}, {0, 1});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {a, c, b, a, d, c});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Roof: 4 triangles meeting at central apex.
|
|
|
|
|
|
float apexY = height + roofH;
|
|
|
|
|
|
glm::vec3 apex(0, apexY, 0);
|
|
|
|
|
|
// Eave corners (Y = wall height) — each triangle shares
|
|
|
|
|
|
// two adjacent corners + the apex. Per-face normal is
|
|
|
|
|
|
// computed once so flat shading works.
|
|
|
|
|
|
glm::vec3 eaves[4] = {
|
|
|
|
|
|
{-hx, height, hz},
|
|
|
|
|
|
{ hx, height, hz},
|
|
|
|
|
|
{ hx, height, -hz},
|
|
|
|
|
|
{-hx, height, -hz},
|
|
|
|
|
|
};
|
|
|
|
|
|
for (int s = 0; s < 4; ++s) {
|
|
|
|
|
|
glm::vec3 e0 = eaves[s];
|
|
|
|
|
|
glm::vec3 e1 = eaves[(s + 1) % 4];
|
|
|
|
|
|
glm::vec3 fn = glm::normalize(glm::cross(e1 - e0, apex - e0));
|
|
|
|
|
|
uint32_t a = addV(e0, fn, {0, 0});
|
|
|
|
|
|
uint32_t b = addV(e1, fn, {1, 0});
|
|
|
|
|
|
uint32_t c = addV(apex, fn, {0.5f, 1});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {a, b, c});
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-hx, 0, -hz);
|
|
|
|
|
|
wom.boundMax = glm::vec3( hx, apexY, hz);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-house: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" width : %.3f\n", width);
|
|
|
|
|
|
std::printf(" depth : %.3f\n", depth);
|
|
|
|
|
|
std::printf(" wall H : %.3f\n", height);
|
|
|
|
|
|
std::printf(" roof H : %.3f (apex %.3f)\n", roofH, apexY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleFountain(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Procedural fountain: low cylindrical basin with a
|
|
|
|
|
|
// narrower spout column rising from its center. Solid
|
|
|
|
|
|
// basin (not hollow) for simplicity — readable as a
|
|
|
|
|
|
// fountain because of the spout silhouette. Useful for
|
|
|
|
|
|
// town squares, plazas, garden centerpieces.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The 21st procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float basinR = 1.5f;
|
|
|
|
|
|
float basinH = 0.5f;
|
|
|
|
|
|
float spoutR = 0.2f;
|
|
|
|
|
|
float spoutH = 1.5f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { basinR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { basinH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { spoutR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { spoutH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (basinR <= 0 || basinH <= 0 || spoutR <= 0 || spoutH <= 0 ||
|
|
|
|
|
|
spoutR >= basinR) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-fountain: all dims > 0; spoutR must be < basinR\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
const int segs = 24;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Cylinder helper: build side ring + caps from y0 to y1
|
|
|
|
|
|
// at given radius. Returns when done; indices appended
|
|
|
|
|
|
// directly. Side ring is 2× (segs+1) verts at y0 then y1.
|
|
|
|
|
|
auto cylinder = [&](float r, float y0, float y1) {
|
|
|
|
|
|
uint32_t bot = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(r * std::cos(ang), y0, r * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, glm::vec2(u, 0));
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t top = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(r * std::cos(ang), y1, r * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, glm::vec2(u, 1));
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {
|
|
|
|
|
|
bot + sg, top + sg, bot + sg + 1,
|
|
|
|
|
|
bot + sg + 1, top + sg, top + sg + 1
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Top cap (faces +Y)
|
|
|
|
|
|
uint32_t topC = addV({0, y1, 0}, {0, 1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{topC, top + sg, top + sg + 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Bottom cap (faces -Y)
|
|
|
|
|
|
uint32_t botC = addV({0, y0, 0}, {0, -1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{botC, bot + sg + 1, bot + sg});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Basin: cylinder from y=0 to y=basinH at basinR.
|
|
|
|
|
|
cylinder(basinR, 0.0f, basinH);
|
|
|
|
|
|
// Spout: cylinder from y=basinH to y=basinH+spoutH at spoutR.
|
|
|
|
|
|
cylinder(spoutR, basinH, basinH + spoutH);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = basinH + spoutH;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-basinR, 0, -basinR);
|
|
|
|
|
|
wom.boundMax = glm::vec3( basinR, maxY, basinR);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-fountain: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" basin : R=%.3f H=%.3f\n", basinR, basinH);
|
|
|
|
|
|
std::printf(" spout : R=%.3f H=%.3f\n", spoutR, spoutH);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", maxY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles: %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleStatue(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Humanoid placeholder: square pedestal block + tall
|
|
|
|
|
|
// narrow body cylinder + head sphere. The silhouette
|
|
|
|
|
|
// reads as a statue without needing limbs. Useful for
|
|
|
|
|
|
// monuments, hero statues, plaza centerpieces, religious
|
|
|
|
|
|
// shrines.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The 22nd procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float pedSize = 1.0f; // pedestal width and depth
|
|
|
|
|
|
float bodyH = 2.5f; // body cylinder height
|
|
|
|
|
|
float headR = 0.4f; // head sphere radius
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { pedSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { bodyH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { headR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (pedSize <= 0 || bodyH <= 0 || headR <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-statue: all dims must be positive\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Pedestal: low square block (24 unique verts).
|
|
|
|
|
|
float pedH = pedSize * 0.4f;
|
|
|
|
|
|
float hp = pedSize * 0.5f;
|
|
|
|
|
|
{
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(0, pedH * 0.5f, 0);
|
|
|
|
|
|
glm::vec3 ext(hp, pedH * 0.5f, hp);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*ext.x, f.n.y*ext.y, f.n.z*ext.z);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
addV(center - du - dv, f.n, {0, 0});
|
|
|
|
|
|
addV(center + du - dv, f.n, {1, 0});
|
|
|
|
|
|
addV(center + du + dv, f.n, {1, 1});
|
|
|
|
|
|
addV(center - du + dv, f.n, {0, 1});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Body cylinder from y=pedH to y=pedH+bodyH at radius pedSize*0.2
|
|
|
|
|
|
float bodyR = pedSize * 0.2f;
|
|
|
|
|
|
float bodyY0 = pedH;
|
|
|
|
|
|
float bodyY1 = pedH + bodyH;
|
|
|
|
|
|
const int segs = 16;
|
|
|
|
|
|
uint32_t bodyBot = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(bodyR * std::cos(ang), bodyY0, bodyR * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 0});
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t bodyTop = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(bodyR * std::cos(ang), bodyY1, bodyR * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {
|
|
|
|
|
|
bodyBot + sg, bodyTop + sg, bodyBot + sg + 1,
|
|
|
|
|
|
bodyBot + sg + 1, bodyTop + sg, bodyTop + sg + 1
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Head sphere centered above body. UV-sphere with 16
|
|
|
|
|
|
// longitude × 12 latitude segments.
|
|
|
|
|
|
float headY = bodyY1 + headR;
|
|
|
|
|
|
const int headLon = 16;
|
|
|
|
|
|
const int headLat = 12;
|
|
|
|
|
|
uint32_t headStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int la = 0; la <= headLat; ++la) {
|
|
|
|
|
|
float v = static_cast<float>(la) / headLat;
|
|
|
|
|
|
float phi = v * pi; // 0..pi
|
|
|
|
|
|
float sphi = std::sin(phi), cphi = std::cos(phi);
|
|
|
|
|
|
for (int lo = 0; lo <= headLon; ++lo) {
|
|
|
|
|
|
float u = static_cast<float>(lo) / headLon;
|
|
|
|
|
|
float theta = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 dir(sphi * std::cos(theta),
|
|
|
|
|
|
cphi,
|
|
|
|
|
|
sphi * std::sin(theta));
|
|
|
|
|
|
glm::vec3 p = glm::vec3(0, headY, 0) + dir * headR;
|
|
|
|
|
|
addV(p, dir, {u, v});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
int rowSize = headLon + 1;
|
|
|
|
|
|
for (int la = 0; la < headLat; ++la) {
|
|
|
|
|
|
for (int lo = 0; lo < headLon; ++lo) {
|
|
|
|
|
|
uint32_t i00 = headStart + la * rowSize + lo;
|
|
|
|
|
|
uint32_t i01 = headStart + la * rowSize + lo + 1;
|
|
|
|
|
|
uint32_t i10 = headStart + (la + 1) * rowSize + lo;
|
|
|
|
|
|
uint32_t i11 = headStart + (la + 1) * rowSize + lo + 1;
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{i00, i10, i01, i01, i10, i11});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = headY + headR;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-hp, 0, -hp);
|
|
|
|
|
|
wom.boundMax = glm::vec3( hp, maxY, hp);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-statue: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" pedestal : %.3f × %.3f × %.3f\n", pedSize, pedH, pedSize);
|
|
|
|
|
|
std::printf(" body : R=%.3f H=%.3f\n", bodyR, bodyH);
|
|
|
|
|
|
std::printf(" head : R=%.3f\n", headR);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", maxY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleAltar(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Round altar: stack of N stepped cylindrical discs,
|
|
|
|
|
|
// each one wider and shorter than the next so the
|
|
|
|
|
|
// silhouette descends like a wedding cake. Top disc is
|
|
|
|
|
|
// the altar surface (where offerings would go); base
|
|
|
|
|
|
// discs widen out to anchor the structure visually.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The 23rd procedural mesh primitive — pairs naturally
|
|
|
|
|
|
// with --gen-texture-marble for a temple aesthetic.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float topR = 0.7f; // top altar disc radius
|
|
|
|
|
|
float topH = 0.3f; // top altar disc height
|
|
|
|
|
|
int steps = 3; // base steps below the top
|
|
|
|
|
|
float stepStride = 0.3f; // each step grows R by this much, shrinks H
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { topR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { topH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { steps = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stepStride = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (topR <= 0 || topH <= 0 || steps < 0 || steps > 16 ||
|
|
|
|
|
|
stepStride <= 0 || stepStride > 5.0f) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-altar: topR/topH > 0, steps 0..16, stride 0..5\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
const int segs = 24;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Build a cylindrical disc from y0 to y1 at radius r.
|
|
|
|
|
|
// Side ring + top cap (faces +Y). Bottom of each disc
|
|
|
|
|
|
// is hidden by the next disc below, so we skip a bottom
|
|
|
|
|
|
// cap on all discs except the last (saves ~24 tris/disc).
|
|
|
|
|
|
auto disc = [&](float r, float y0, float y1, bool capBottom) {
|
|
|
|
|
|
uint32_t bot = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(r * std::cos(ang), y0, r * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 0});
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t top = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(r * std::cos(ang), y1, r * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {
|
|
|
|
|
|
bot + sg, top + sg, bot + sg + 1,
|
|
|
|
|
|
bot + sg + 1, top + sg, top + sg + 1
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Top cap fan (faces +Y).
|
|
|
|
|
|
uint32_t tc = addV({0, y1, 0}, {0, 1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{tc, top + sg, top + sg + 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
if (capBottom) {
|
|
|
|
|
|
uint32_t bc = addV({0, y0, 0}, {0, -1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{bc, bot + sg + 1, bot + sg});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Build bottom-up so y0 starts at floor and tops stack.
|
|
|
|
|
|
// Step k (k=0 is bottom-most) has radius = topR + (steps-k)*stride
|
|
|
|
|
|
// and height = topH * (1 - 0.2 * k). Y position accumulates.
|
|
|
|
|
|
float curY = 0.0f;
|
|
|
|
|
|
for (int k = steps - 1; k >= 0; --k) { // bottom step first
|
|
|
|
|
|
float r = topR + (k + 1) * stepStride;
|
|
|
|
|
|
float h = topH * (1.0f - 0.2f * k);
|
|
|
|
|
|
if (h < topH * 0.4f) h = topH * 0.4f;
|
|
|
|
|
|
bool isBottom = (k == steps - 1);
|
|
|
|
|
|
disc(r, curY, curY + h, isBottom);
|
|
|
|
|
|
curY += h;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Top disc (the actual altar surface)
|
|
|
|
|
|
disc(topR, curY, curY + topH, steps == 0);
|
|
|
|
|
|
float maxY = curY + topH;
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxR = topR + steps * stepStride;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-maxR, 0, -maxR);
|
|
|
|
|
|
wom.boundMax = glm::vec3( maxR, maxY, maxR);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-altar: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" top : R=%.3f H=%.3f\n", topR, topH);
|
|
|
|
|
|
std::printf(" steps : %d (stride %.3f)\n", steps, stepStride);
|
|
|
|
|
|
std::printf(" base R : %.3f\n", maxR);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", maxY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles: %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handlePortal(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Doorway portal: two vertical post boxes plus a
|
|
|
|
|
|
// horizontal lintel box across the top. Posts run along
|
|
|
|
|
|
// the Z axis (so width spans Z), opening faces +X. The
|
|
|
|
|
|
// gap between the posts is the actual doorway. Useful
|
|
|
|
|
|
// for entrances, gates, magical portals, ruins.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The 24th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float width = 2.5f; // outer-to-outer along Z
|
|
|
|
|
|
float height = 4.0f; // total Y
|
|
|
|
|
|
float postThick = 0.4f; // post width in X and Z
|
|
|
|
|
|
float lintelH = 0.5f; // top lintel height (Y)
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postThick = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { lintelH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (width <= 0 || height <= 0 || postThick <= 0 ||
|
|
|
|
|
|
lintelH < 0 || postThick * 2 >= width ||
|
|
|
|
|
|
lintelH > height) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-portal: posts must fit inside width; lintel <= height\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
// Box helper — same pattern as other multi-box meshes.
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p;
|
|
|
|
|
|
vtx.normal = f.n;
|
|
|
|
|
|
vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Two posts at z = ±(width/2 - postThick/2). Each
|
|
|
|
|
|
// post extends from y=0 to y=height-lintelH so it
|
|
|
|
|
|
// tucks under the lintel.
|
|
|
|
|
|
float postY = (height - lintelH) * 0.5f;
|
|
|
|
|
|
float postHy = (height - lintelH) * 0.5f;
|
|
|
|
|
|
float postZ = (width - postThick) * 0.5f;
|
|
|
|
|
|
float postHt = postThick * 0.5f;
|
|
|
|
|
|
addBox(0, postY, postZ, postHt, postHy, postHt);
|
|
|
|
|
|
addBox(0, postY, -postZ, postHt, postHy, postHt);
|
|
|
|
|
|
// Lintel: spans full width across the top, same X
|
|
|
|
|
|
// thickness as posts.
|
|
|
|
|
|
if (lintelH > 0.0f) {
|
|
|
|
|
|
float lintelY = height - lintelH * 0.5f;
|
|
|
|
|
|
addBox(0, lintelY, 0,
|
|
|
|
|
|
postHt, lintelH * 0.5f, width * 0.5f);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-postHt, 0, -width * 0.5f);
|
|
|
|
|
|
wom.boundMax = glm::vec3( postHt, height, width * 0.5f);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-portal: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" width : %.3f\n", width);
|
|
|
|
|
|
std::printf(" height : %.3f\n", height);
|
|
|
|
|
|
std::printf(" post thick : %.3f\n", postThick);
|
|
|
|
|
|
std::printf(" lintel H : %.3f%s\n", lintelH,
|
|
|
|
|
|
lintelH > 0 ? "" : " (no lintel)");
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleArchway(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Semicircular arched doorway. Two cylindrical pillars
|
|
|
|
|
|
// hold up a curved keystone vault: the vault is a series
|
|
|
|
|
|
// of N angular wedge segments tracing a half-circle from
|
|
|
|
|
|
// pillar-top to pillar-top. The opening is the empty
|
|
|
|
|
|
// semicircular space below.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The 25th procedural mesh primitive — the "fancier"
|
|
|
|
|
|
// sibling of --gen-mesh-portal which uses a flat lintel.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float width = 3.0f; // outer-to-outer pillar centers along Z
|
|
|
|
|
|
float pillarH = 3.0f; // pillar height (Y)
|
|
|
|
|
|
float thickness = 0.4f; // pillar radius and arch radial thickness
|
|
|
|
|
|
int archSegs = 12; // segments around the half-circle
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { pillarH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { thickness = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { archSegs = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (width <= 0 || pillarH <= 0 || thickness <= 0 ||
|
|
|
|
|
|
archSegs < 4 || archSegs > 64 ||
|
|
|
|
|
|
thickness * 4 >= width) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-archway: thickness×4 < width, archSegs 4..64\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
const int pillarSegs = 16;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Cylindrical pillar at given (cx, cz), from y=0 to y=pillarH.
|
|
|
|
|
|
auto pillar = [&](float cx, float cz) {
|
|
|
|
|
|
float r = thickness;
|
|
|
|
|
|
uint32_t bot = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= pillarSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / pillarSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(cx + r * std::cos(ang), 0,
|
|
|
|
|
|
cz + r * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 0});
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t top = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= pillarSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / pillarSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(cx + r * std::cos(ang), pillarH,
|
|
|
|
|
|
cz + r * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < pillarSegs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {
|
|
|
|
|
|
bot + sg, top + sg, bot + sg + 1,
|
|
|
|
|
|
bot + sg + 1, top + sg, top + sg + 1
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Caps
|
|
|
|
|
|
uint32_t bc = addV({cx, 0, cz}, {0, -1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
uint32_t tc = addV({cx, pillarH, cz}, {0, 1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < pillarSegs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{bc, bot + sg + 1, bot + sg});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{tc, top + sg, top + sg + 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float pillarZ = (width - 2 * thickness) * 0.5f;
|
|
|
|
|
|
pillar(0, pillarZ);
|
|
|
|
|
|
pillar(0, -pillarZ);
|
|
|
|
|
|
// Arch vault: trace half-circle from (z = +pillarZ, y = pillarH)
|
|
|
|
|
|
// up over to (z = -pillarZ, y = pillarH). Center of arch:
|
|
|
|
|
|
// (z = 0, y = pillarH). Arch radius = pillarZ.
|
|
|
|
|
|
// Inner arch (radius pillarZ - thickness*0.5) and outer
|
|
|
|
|
|
// (radius pillarZ + thickness*0.5) — the vault sits between.
|
|
|
|
|
|
float archCY = pillarH;
|
|
|
|
|
|
float arcInner = pillarZ - thickness * 0.5f;
|
|
|
|
|
|
float arcOuter = pillarZ + thickness * 0.5f;
|
|
|
|
|
|
// Each segment: 4 verts (inner-near, outer-near, inner-far,
|
|
|
|
|
|
// outer-far) extruded along X by thickness so the vault
|
|
|
|
|
|
// has front and back faces.
|
|
|
|
|
|
float archX = thickness * 0.5f; // half-depth in X
|
|
|
|
|
|
// Build vertex rings for inner and outer surfaces at
|
|
|
|
|
|
// each segment boundary, then connect.
|
|
|
|
|
|
// Top half-circle goes from theta=0 to theta=pi.
|
|
|
|
|
|
std::vector<glm::vec3> innerRing;
|
|
|
|
|
|
std::vector<glm::vec3> outerRing;
|
|
|
|
|
|
for (int s = 0; s <= archSegs; ++s) {
|
|
|
|
|
|
float t = static_cast<float>(s) / archSegs;
|
|
|
|
|
|
float theta = t * pi; // 0..pi
|
|
|
|
|
|
float zi = arcInner * std::cos(theta);
|
|
|
|
|
|
float yi = arcInner * std::sin(theta);
|
|
|
|
|
|
float zo = arcOuter * std::cos(theta);
|
|
|
|
|
|
float yo = arcOuter * std::sin(theta);
|
|
|
|
|
|
innerRing.push_back({0, archCY + yi, zi});
|
|
|
|
|
|
outerRing.push_back({0, archCY + yo, zo});
|
|
|
|
|
|
}
|
|
|
|
|
|
// For each segment, add 8 vertices (4 corners × front/back face)
|
|
|
|
|
|
// and stitch them into 6 quads = 12 tris each.
|
|
|
|
|
|
for (int s = 0; s < archSegs; ++s) {
|
|
|
|
|
|
glm::vec3 i0 = innerRing[s];
|
|
|
|
|
|
glm::vec3 i1 = innerRing[s + 1];
|
|
|
|
|
|
glm::vec3 o0 = outerRing[s];
|
|
|
|
|
|
glm::vec3 o1 = outerRing[s + 1];
|
|
|
|
|
|
// Estimate outward (radial) normal as midpoint of o0+o1
|
|
|
|
|
|
// direction from center.
|
|
|
|
|
|
glm::vec3 outDir = glm::normalize(glm::vec3(0,
|
|
|
|
|
|
(i0.y + i1.y + o0.y + o1.y) * 0.25f - archCY,
|
|
|
|
|
|
(i0.z + i1.z + o0.z + o1.z) * 0.25f));
|
|
|
|
|
|
glm::vec3 frontN(1, 0, 0);
|
|
|
|
|
|
glm::vec3 backN(-1, 0, 0);
|
|
|
|
|
|
auto V = [&](glm::vec3 p, glm::vec3 n) {
|
|
|
|
|
|
return addV(p, n, {0, 0});
|
|
|
|
|
|
};
|
|
|
|
|
|
// Outer surface (top of arch): faces outward radially
|
|
|
|
|
|
uint32_t a = V({-archX, o0.y, o0.z}, outDir);
|
|
|
|
|
|
uint32_t b = V({ archX, o0.y, o0.z}, outDir);
|
|
|
|
|
|
uint32_t c = V({ archX, o1.y, o1.z}, outDir);
|
|
|
|
|
|
uint32_t d = V({-archX, o1.y, o1.z}, outDir);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {a, b, c, a, c, d});
|
|
|
|
|
|
// Inner surface (underside of arch): faces inward
|
|
|
|
|
|
uint32_t e = V({-archX, i0.y, i0.z}, -outDir);
|
|
|
|
|
|
uint32_t f = V({ archX, i0.y, i0.z}, -outDir);
|
|
|
|
|
|
uint32_t g = V({ archX, i1.y, i1.z}, -outDir);
|
|
|
|
|
|
uint32_t h = V({-archX, i1.y, i1.z}, -outDir);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {e, g, f, e, h, g});
|
|
|
|
|
|
// Front face (+X) of this wedge
|
|
|
|
|
|
uint32_t fi0 = V({ archX, i0.y, i0.z}, frontN);
|
|
|
|
|
|
uint32_t fo0 = V({ archX, o0.y, o0.z}, frontN);
|
|
|
|
|
|
uint32_t fo1 = V({ archX, o1.y, o1.z}, frontN);
|
|
|
|
|
|
uint32_t fi1 = V({ archX, i1.y, i1.z}, frontN);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{fi0, fo0, fo1, fi0, fo1, fi1});
|
|
|
|
|
|
// Back face (-X)
|
|
|
|
|
|
uint32_t bi0 = V({-archX, i0.y, i0.z}, backN);
|
|
|
|
|
|
uint32_t bo0 = V({-archX, o0.y, o0.z}, backN);
|
|
|
|
|
|
uint32_t bo1 = V({-archX, o1.y, o1.z}, backN);
|
|
|
|
|
|
uint32_t bi1 = V({-archX, i1.y, i1.z}, backN);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{bi0, bo1, bo0, bi0, bi1, bo1});
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = pillarH + arcOuter;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-thickness, 0, -width * 0.5f);
|
|
|
|
|
|
wom.boundMax = glm::vec3( thickness, maxY, width * 0.5f);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-archway: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" width : %.3f\n", width);
|
|
|
|
|
|
std::printf(" pillar H : %.3f\n", pillarH);
|
|
|
|
|
|
std::printf(" thickness : %.3f\n", thickness);
|
|
|
|
|
|
std::printf(" arch segs : %d (radius %.3f)\n", archSegs, arcOuter);
|
|
|
|
|
|
std::printf(" apex Y : %.3f\n", maxY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleBarrel(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Tapered barrel: cylindrical body whose radius bulges
|
|
|
|
|
|
// smoothly from `topRadius` at the rims to `midRadius`
|
|
|
|
|
|
// at the middle (the classic stave-cooper barrel
|
|
|
|
|
|
// silhouette), plus 2 raised hoop bands at 25% and 75%
|
|
|
|
|
|
// of the height. The 26th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float topR = 0.4f; // radius at top and bottom rim
|
|
|
|
|
|
float midR = 0.5f; // radius at the middle bulge
|
|
|
|
|
|
float height = 1.0f;
|
|
|
|
|
|
float hoopThick = 0.06f; // hoop band radial protrusion
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { topR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { midR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { hoopThick = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (topR <= 0 || midR <= 0 || height <= 0 ||
|
|
|
|
|
|
hoopThick < 0 || hoopThick > 0.5f) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-barrel: radii/height > 0, hoopThick 0..0.5\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
const int segs = 16; // angular subdivisions
|
|
|
|
|
|
const int rings = 12; // vertical slices
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Radius profile: smooth cosine bulge from rim to mid.
|
|
|
|
|
|
// r(t) = topR + (midR - topR) * sin(pi*t) where t in 0..1
|
|
|
|
|
|
// gives 0 at t=0/1 and 1 at t=0.5 — exact rim fit.
|
|
|
|
|
|
auto radiusAt = [&](float t) -> float {
|
|
|
|
|
|
return topR + (midR - topR) * std::sin(pi * t);
|
|
|
|
|
|
};
|
|
|
|
|
|
uint32_t firstRing = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int ri = 0; ri <= rings; ++ri) {
|
|
|
|
|
|
float t = static_cast<float>(ri) / rings;
|
|
|
|
|
|
float y = t * height;
|
|
|
|
|
|
float r = radiusAt(t);
|
|
|
|
|
|
// Hoops: bump radius outward in two narrow bands.
|
|
|
|
|
|
float hoop1 = std::abs(t - 0.25f);
|
|
|
|
|
|
float hoop2 = std::abs(t - 0.75f);
|
|
|
|
|
|
if (hoop1 < 0.04f) r += hoopThick * (1.0f - hoop1 / 0.04f);
|
|
|
|
|
|
if (hoop2 < 0.04f) r += hoopThick * (1.0f - hoop2 / 0.04f);
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(r * std::cos(ang), y, r * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, t});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
int rowSize = segs + 1;
|
|
|
|
|
|
for (int ri = 0; ri < rings; ++ri) {
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
uint32_t i00 = firstRing + ri * rowSize + sg;
|
|
|
|
|
|
uint32_t i01 = firstRing + ri * rowSize + sg + 1;
|
|
|
|
|
|
uint32_t i10 = firstRing + (ri + 1) * rowSize + sg;
|
|
|
|
|
|
uint32_t i11 = firstRing + (ri + 1) * rowSize + sg + 1;
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{i00, i10, i01, i01, i10, i11});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// End caps (top + bottom). topR is also the bottom-most
|
|
|
|
|
|
// and top-most ring radius since sin(0) = sin(pi) = 0.
|
|
|
|
|
|
uint32_t botCenter = addV({0, 0, 0}, {0, -1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
uint32_t topCenter = addV({0, height, 0}, {0, 1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
uint32_t botRing = firstRing;
|
|
|
|
|
|
uint32_t topRing = firstRing + rings * rowSize;
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{botCenter, botRing + sg + 1, botRing + sg});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{topCenter, topRing + sg, topRing + sg + 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxR = midR + hoopThick;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-maxR, 0, -maxR);
|
|
|
|
|
|
wom.boundMax = glm::vec3( maxR, height, maxR);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-barrel: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" rim R : %.3f\n", topR);
|
|
|
|
|
|
std::printf(" bulge R : %.3f\n", midR);
|
|
|
|
|
|
std::printf(" height : %.3f\n", height);
|
|
|
|
|
|
std::printf(" hoops : 2 (thickness %.3f)\n", hoopThick);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleChest(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Treasure chest: rectangular body box + smaller lid
|
|
|
|
|
|
// box on top + 3 thin iron bands wrapping around the
|
|
|
|
|
|
// body + a small lock plate on the front center face.
|
|
|
|
|
|
// The 27th procedural mesh primitive — useful for
|
|
|
|
|
|
// dungeon loot, room decoration, quest objectives.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float width = 1.4f; // along X
|
|
|
|
|
|
float depth = 0.9f; // along Z
|
|
|
|
|
|
float bodyH = 0.9f; // body box height
|
|
|
|
|
|
float lidH = 0.25f; // lid height above body
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { depth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { bodyH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { lidH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (width <= 0 || depth <= 0 || bodyH <= 0 || lidH < 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-chest: width/depth/bodyH > 0, lidH >= 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
// Box helper — adds 24 unique verts / 12 tris centered
|
|
|
|
|
|
// on (cx, cy, cz) with half-extents (hx, hy, hz). Each
|
|
|
|
|
|
// face gets unique normals for flat shading.
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float hx = width * 0.5f;
|
|
|
|
|
|
float hz = depth * 0.5f;
|
|
|
|
|
|
// Body: y=0 to y=bodyH
|
|
|
|
|
|
addBox(0, bodyH * 0.5f, 0, hx, bodyH * 0.5f, hz);
|
|
|
|
|
|
// Lid: smaller box on top, slightly inset on each side
|
|
|
|
|
|
float lidInset = std::min(width, depth) * 0.04f;
|
|
|
|
|
|
float lidHx = hx - lidInset;
|
|
|
|
|
|
float lidHz = hz - lidInset;
|
|
|
|
|
|
if (lidH > 0.0f && lidHx > 0 && lidHz > 0) {
|
|
|
|
|
|
addBox(0, bodyH + lidH * 0.5f, 0,
|
|
|
|
|
|
lidHx, lidH * 0.5f, lidHz);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 3 iron bands wrapping the body — thin slabs
|
|
|
|
|
|
// protruding ~3% radially on the sides + top.
|
|
|
|
|
|
// Band positions: 15%, 50%, 85% of body width.
|
|
|
|
|
|
float bandThickX = width * 0.04f; // band depth along X
|
|
|
|
|
|
float bandHy = bodyH * 0.5f + 0.005f;
|
|
|
|
|
|
float bandHz = hz + 0.012f;
|
|
|
|
|
|
float bandPositions[3] = {-hx * 0.7f, 0.0f, hx * 0.7f};
|
|
|
|
|
|
for (float bx : bandPositions) {
|
|
|
|
|
|
addBox(bx, bandHy, 0,
|
|
|
|
|
|
bandThickX * 0.5f, bandHy, bandHz);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Lock plate: small thin box on the front face, centered.
|
|
|
|
|
|
// Front face is +Z. Plate sits at z = hz + tiny epsilon.
|
|
|
|
|
|
float lockW = width * 0.10f;
|
|
|
|
|
|
float lockH = bodyH * 0.18f;
|
|
|
|
|
|
float lockY = bodyH * 0.65f;
|
|
|
|
|
|
float lockEps = 0.008f;
|
|
|
|
|
|
addBox(0, lockY, hz + lockEps,
|
|
|
|
|
|
lockW * 0.5f, lockH * 0.5f, lockEps);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = bodyH + lidH;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-hx, 0, -hz - 0.012f);
|
|
|
|
|
|
wom.boundMax = glm::vec3( hx, maxY, hz + 0.012f);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-chest: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" width × depth : %.3f × %.3f\n", width, depth);
|
|
|
|
|
|
std::printf(" body H : %.3f\n", bodyH);
|
|
|
|
|
|
std::printf(" lid H : %.3f\n", lidH);
|
|
|
|
|
|
std::printf(" components : body + lid + 3 bands + lock\n");
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 22:37:47 -07:00
|
|
|
|
int handleAnvil(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Blacksmith anvil: stepped pedestal base + flat work
|
|
|
|
|
|
// surface (the "face") + tapered horn extending forward.
|
|
|
|
|
|
// Built from 3 boxes + a 4-vertex tapered prism for the
|
|
|
|
|
|
// horn. The 28th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float length = 1.0f; // along X (face length)
|
|
|
|
|
|
float width = 0.4f; // along Z
|
|
|
|
|
|
float hornLen = 0.5f; // horn extending past face
|
|
|
|
|
|
float bodyH = 0.5f; // total height
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { length = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { hornLen = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { bodyH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (length <= 0 || width <= 0 || hornLen < 0 || bodyH <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-anvil: length/width/bodyH > 0, hornLen >= 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Pedestal: bottom 60% of total height, narrower base
|
|
|
|
|
|
// (4-step taper would be classic but for simplicity we use
|
|
|
|
|
|
// a wide base + narrow waist + wide cap structure as 3 boxes).
|
|
|
|
|
|
float baseH = bodyH * 0.25f;
|
|
|
|
|
|
float waistH = bodyH * 0.30f;
|
|
|
|
|
|
float capH = bodyH * 0.20f;
|
|
|
|
|
|
float faceH = bodyH * 0.25f;
|
|
|
|
|
|
float baseHx = length * 0.45f;
|
|
|
|
|
|
float baseHz = width * 0.55f;
|
|
|
|
|
|
float waistHx = length * 0.30f;
|
|
|
|
|
|
float waistHz = width * 0.40f;
|
|
|
|
|
|
float capHx = length * 0.50f;
|
|
|
|
|
|
float capHz = width * 0.55f;
|
|
|
|
|
|
float faceHx = length * 0.50f;
|
|
|
|
|
|
float faceHz = width * 0.50f;
|
|
|
|
|
|
float y0 = 0.0f;
|
|
|
|
|
|
addBox(0, y0 + baseH * 0.5f, 0, baseHx, baseH * 0.5f, baseHz);
|
|
|
|
|
|
y0 += baseH;
|
|
|
|
|
|
addBox(0, y0 + waistH * 0.5f, 0, waistHx, waistH * 0.5f, waistHz);
|
|
|
|
|
|
y0 += waistH;
|
|
|
|
|
|
addBox(0, y0 + capH * 0.5f, 0, capHx, capH * 0.5f, capHz);
|
|
|
|
|
|
y0 += capH;
|
|
|
|
|
|
addBox(0, y0 + faceH * 0.5f, 0, faceHx, faceH * 0.5f, faceHz);
|
|
|
|
|
|
// Horn: tapered prism extending in +X past the face. 6 verts
|
|
|
|
|
|
// (rectangle at face edge tapering to a point at the tip).
|
|
|
|
|
|
if (hornLen > 0.0f) {
|
|
|
|
|
|
float hornBaseX = faceHx;
|
|
|
|
|
|
float hornTipX = faceHx + hornLen;
|
|
|
|
|
|
float hornY0 = y0 + faceH * 0.25f;
|
|
|
|
|
|
float hornY1 = y0 + faceH * 0.75f;
|
|
|
|
|
|
float hornHz = faceHz * 0.6f;
|
|
|
|
|
|
// 4 base verts + 2 tip verts (tip is a vertical edge)
|
|
|
|
|
|
// Build 4 face triangles + 2 base/tip caps
|
|
|
|
|
|
glm::vec3 b00(hornBaseX, hornY0, hornHz);
|
|
|
|
|
|
glm::vec3 b01(hornBaseX, hornY0, -hornHz);
|
|
|
|
|
|
glm::vec3 b10(hornBaseX, hornY1, hornHz);
|
|
|
|
|
|
glm::vec3 b11(hornBaseX, hornY1, -hornHz);
|
|
|
|
|
|
glm::vec3 t0 (hornTipX, (hornY0 + hornY1) * 0.5f, 0);
|
|
|
|
|
|
// Top face triangles (b10, b11, t0)
|
|
|
|
|
|
auto addTri = [&](glm::vec3 a, glm::vec3 b, glm::vec3 c) {
|
|
|
|
|
|
glm::vec3 n = glm::normalize(glm::cross(b - a, c - a));
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
wom.vertices.push_back({a, n, {0, 0}});
|
|
|
|
|
|
wom.vertices.push_back({b, n, {1, 0}});
|
|
|
|
|
|
wom.vertices.push_back({c, n, {0.5f, 1}});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {base, base + 1, base + 2});
|
|
|
|
|
|
};
|
|
|
|
|
|
// 4 side faces converging to t0
|
|
|
|
|
|
addTri(b00, b01, t0); // bottom
|
|
|
|
|
|
addTri(b11, b10, t0); // top
|
|
|
|
|
|
addTri(b10, b00, t0); // +Z side
|
|
|
|
|
|
addTri(b01, b11, t0); // -Z side
|
|
|
|
|
|
// Base of horn (closes the rectangle on the face side).
|
|
|
|
|
|
// The base is hidden against the anvil face but include it
|
|
|
|
|
|
// so the mesh is watertight.
|
|
|
|
|
|
glm::vec3 baseN(-1, 0, 0);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
wom.vertices.push_back({b00, baseN, {0, 0}});
|
|
|
|
|
|
wom.vertices.push_back({b10, baseN, {0, 1}});
|
|
|
|
|
|
wom.vertices.push_back({b11, baseN, {1, 1}});
|
|
|
|
|
|
wom.vertices.push_back({b01, baseN, {1, 0}});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxX = std::max(faceHx, faceHx + hornLen);
|
|
|
|
|
|
float maxZ = std::max({baseHz, waistHz, capHz, faceHz});
|
|
|
|
|
|
wom.boundMin = glm::vec3(-faceHx, 0, -maxZ);
|
|
|
|
|
|
wom.boundMax = glm::vec3( maxX, bodyH, maxZ);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-anvil: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" length × width : %.3f × %.3f\n", length, width);
|
|
|
|
|
|
std::printf(" body H : %.3f\n", bodyH);
|
|
|
|
|
|
std::printf(" horn length : %.3f\n", hornLen);
|
|
|
|
|
|
std::printf(" components : 4 step pedestal + tapered horn\n");
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
refactor(editor): extract 12 composite mesh primitives into cli_gen_mesh.cpp
Moves the recently-added composite-prop mesh handlers (rock,
pillar, bridge, tower, house, fountain, statue, altar, portal,
archway, barrel, chest) into their own translation unit. These
12 handlers were the most contiguous block of similar-shaped
mesh code in main.cpp.
Other mesh handlers (--gen-mesh dispatcher, fence, tree, grid,
stairs, disc, tube, capsule, arch, pyramid, from-heightmap,
textured) still live in main.cpp and may be migrated in
subsequent batches.
main.cpp drops 24,270 → 22,681 lines (-1,589). Behavior
verified by re-running rock/chest/archway/fountain.
2026-05-08 22:19:41 -07:00
|
|
|
|
|
refactor(editor): extract 9 more mesh handlers into cli_gen_mesh.cpp
Moves stairs, grid, disc, tube, capsule, arch, pyramid, fence,
and tree into the existing cli_gen_mesh.cpp module. Together
with the 12 composite props extracted in 00754941 and the
anvil added in cd4bdfec, the module now contains 22 of the
28 procedural mesh primitives.
Remaining mesh handlers in main.cpp: --gen-mesh dispatcher
(cube/plane/sphere/cylinder/torus/cone/ramp), --gen-mesh-from-
heightmap, --gen-mesh-textured. Those have specialized I/O or
embedded sub-dispatch and will move in a follow-up batch.
main.cpp drops 22,681 → 21,526 lines (-1,155). Behavior
verified by re-running stairs/disc/fence/tree.
2026-05-08 22:58:21 -07:00
|
|
|
|
int handleStairs(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Procedural straight staircase along +X. N steps with
|
|
|
|
|
|
// configurable rise/run/width. Each step is a closed
|
|
|
|
|
|
// box, sharing no vertices with neighbors so per-face
|
|
|
|
|
|
// normals are flat (looks correct without smoothing).
|
|
|
|
|
|
//
|
|
|
|
|
|
// Defaults: 5 steps, stepHeight=0.2, stepDepth=0.3,
|
|
|
|
|
|
// width=1.0 — roughly 1m tall × 1.5m long × 1m wide,
|
|
|
|
|
|
// a believable single flight.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Useful for level-design placeholders ("I need a staircase
|
|
|
|
|
|
// up to this platform"), test-bench geometry for camera/
|
|
|
|
|
|
// movement, and quick prototyping of stepped terrain.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
int steps = 5;
|
|
|
|
|
|
float stepHeight = 0.2f, stepDepth = 0.3f, width = 1.0f;
|
|
|
|
|
|
try { steps = std::stoi(argv[++i]); }
|
|
|
|
|
|
catch (...) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-stairs: <steps> must be an integer\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (steps < 1 || steps > 256) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-stairs: steps %d out of range (1..256)\n", steps);
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stepHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stepDepth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (stepHeight <= 0 || stepDepth <= 0 || width <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-stairs: dimensions must be positive\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addV = [&](float x, float y, float z,
|
|
|
|
|
|
float nx, float ny, float nz,
|
|
|
|
|
|
float u, float v) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = glm::vec3(x, y, z);
|
|
|
|
|
|
vtx.normal = glm::vec3(nx, ny, nz);
|
|
|
|
|
|
vtx.texCoord = glm::vec2(u, v);
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfW = width * 0.5f;
|
|
|
|
|
|
// Each step is a box from y=0 to y=(k+1)*stepHeight,
|
|
|
|
|
|
// depth-wise from x=k*stepDepth to x=(k+1)*stepDepth,
|
|
|
|
|
|
// width-wise from z=-halfW to z=+halfW. Six faces per
|
|
|
|
|
|
// step, four verts each = 24 verts / 12 tris per step.
|
|
|
|
|
|
for (int k = 0; k < steps; ++k) {
|
|
|
|
|
|
float x0 = k * stepDepth;
|
|
|
|
|
|
float x1 = (k + 1) * stepDepth;
|
|
|
|
|
|
float y0 = 0.0f;
|
|
|
|
|
|
float y1 = (k + 1) * stepHeight;
|
|
|
|
|
|
float z0 = -halfW;
|
|
|
|
|
|
float z1 = halfW;
|
|
|
|
|
|
struct Face { float nx, ny, nz; float verts[4][3]; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{ 0, 1, 0, {{x0,y1,z0},{x1,y1,z0},{x1,y1,z1},{x0,y1,z1}}}, // top +Y
|
|
|
|
|
|
{ 0, -1, 0, {{x0,y0,z0},{x0,y0,z1},{x1,y0,z1},{x1,y0,z0}}}, // bot -Y
|
|
|
|
|
|
{-1, 0, 0, {{x0,y0,z0},{x0,y1,z0},{x0,y1,z1},{x0,y0,z1}}}, // back -X
|
|
|
|
|
|
{ 1, 0, 0, {{x1,y0,z0},{x1,y0,z1},{x1,y1,z1},{x1,y1,z0}}}, // front+X (riser)
|
|
|
|
|
|
{ 0, 0, -1, {{x0,y0,z0},{x1,y0,z0},{x1,y1,z0},{x0,y1,z0}}}, // -Z
|
|
|
|
|
|
{ 0, 0, 1, {{x0,y0,z1},{x0,y1,z1},{x1,y1,z1},{x1,y0,z1}}}, // +Z
|
|
|
|
|
|
};
|
|
|
|
|
|
float uvs[4][2] = {{0,0},{1,0},{1,1},{0,1}};
|
|
|
|
|
|
for (auto& f : faces) {
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int q = 0; q < 4; ++q) {
|
|
|
|
|
|
addV(f.verts[q][0], f.verts[q][1], f.verts[q][2],
|
|
|
|
|
|
f.nx, f.ny, f.nz, uvs[q][0], uvs[q][1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 1);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 3);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundMin = glm::vec3(1e30f);
|
|
|
|
|
|
wom.boundMax = glm::vec3(-1e30f);
|
|
|
|
|
|
for (const auto& v : wom.vertices) {
|
|
|
|
|
|
wom.boundMin = glm::min(wom.boundMin, v.position);
|
|
|
|
|
|
wom.boundMax = glm::max(wom.boundMax, v.position);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundRadius = glm::length(wom.boundMax - wom.boundMin) * 0.5f;
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch b;
|
|
|
|
|
|
b.indexStart = 0;
|
|
|
|
|
|
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
b.textureIndex = 0;
|
|
|
|
|
|
b.blendMode = 0;
|
|
|
|
|
|
b.flags = 0;
|
|
|
|
|
|
wom.batches.push_back(b);
|
|
|
|
|
|
wom.texturePaths.push_back("");
|
|
|
|
|
|
std::filesystem::path womPath(womBase);
|
|
|
|
|
|
std::filesystem::create_directories(womPath.parent_path());
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-stairs: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" steps : %d\n", steps);
|
|
|
|
|
|
std::printf(" stepHt : %.3f\n", stepHeight);
|
|
|
|
|
|
std::printf(" stepDep : %.3f\n", stepDepth);
|
|
|
|
|
|
std::printf(" width : %.3f\n", width);
|
|
|
|
|
|
std::printf(" vertices : %zu (%d per step × %d)\n",
|
|
|
|
|
|
wom.vertices.size(), 24, steps);
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
std::printf(" span : %.3fL × %.3fH × %.3fW\n",
|
|
|
|
|
|
steps * stepDepth, steps * stepHeight, width);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleGrid(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Flat plane subdivided into NxN cells. Useful for LOD
|
|
|
|
|
|
// demos, deformable surfaces (later --displace passes),
|
|
|
|
|
|
// testbench geometry that needs many triangles. Default
|
|
|
|
|
|
// size is 1.0 (centered on origin). Hard cap at N=256
|
|
|
|
|
|
// so a typo doesn't generate a mesh with 130k+ vertices.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
int N = 0;
|
|
|
|
|
|
try { N = std::stoi(argv[++i]); }
|
|
|
|
|
|
catch (...) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-grid: <subdivisions> must be an integer\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (N < 1 || N > 256) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-grid: subdivisions %d out of range (1..256)\n", N);
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
float size = 1.0f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { size = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (size <= 0.0f) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-grid: size must be positive\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
// (N+1)x(N+1) vertices on the XY plane centered on origin,
|
|
|
|
|
|
// Z=0. Normals all point +Z; UVs are 0..1 across the grid.
|
|
|
|
|
|
float halfSize = size * 0.5f;
|
|
|
|
|
|
float cellSize = size / N;
|
|
|
|
|
|
for (int j = 0; j <= N; ++j) {
|
|
|
|
|
|
for (int k = 0; k <= N; ++k) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex v;
|
|
|
|
|
|
v.position = glm::vec3(-halfSize + k * cellSize,
|
|
|
|
|
|
-halfSize + j * cellSize,
|
|
|
|
|
|
0.0f);
|
|
|
|
|
|
v.normal = glm::vec3(0, 0, 1);
|
|
|
|
|
|
v.texCoord = glm::vec2(static_cast<float>(k) / N,
|
|
|
|
|
|
static_cast<float>(j) / N);
|
|
|
|
|
|
wom.vertices.push_back(v);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
int stride = N + 1;
|
|
|
|
|
|
for (int j = 0; j < N; ++j) {
|
|
|
|
|
|
for (int k = 0; k < N; ++k) {
|
|
|
|
|
|
uint32_t a = j * stride + k;
|
|
|
|
|
|
uint32_t b = a + 1;
|
|
|
|
|
|
uint32_t c = a + stride;
|
|
|
|
|
|
uint32_t d = c + 1;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfSize, -halfSize, 0);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfSize, halfSize, 0);
|
|
|
|
|
|
wom.boundRadius = glm::length(wom.boundMax - wom.boundMin) * 0.5f;
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch b;
|
|
|
|
|
|
b.indexStart = 0;
|
|
|
|
|
|
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
b.textureIndex = 0;
|
|
|
|
|
|
b.blendMode = 0;
|
|
|
|
|
|
b.flags = 0;
|
|
|
|
|
|
wom.batches.push_back(b);
|
|
|
|
|
|
wom.texturePaths.push_back("");
|
|
|
|
|
|
std::filesystem::path womPath(womBase);
|
|
|
|
|
|
std::filesystem::create_directories(womPath.parent_path());
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-grid: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" subdivisions : %d (%dx%d cells)\n", N, N, N);
|
|
|
|
|
|
std::printf(" size : %.3f\n", size);
|
|
|
|
|
|
std::printf(" vertices : %zu = (N+1)²\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu = 2N²\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleDisc(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Flat circular disc on XY centered at origin. Center
|
|
|
|
|
|
// vertex + ring of <segments> verts, indexed as a fan.
|
|
|
|
|
|
// Useful for magic circles, coin meshes, lily pads, top
|
|
|
|
|
|
// caps for cylinders the user wants without making a
|
|
|
|
|
|
// full cylinder.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float radius = 1.0f;
|
|
|
|
|
|
int segments = 32;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { radius = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { segments = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (radius <= 0.0f || segments < 3 || segments > 1024) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-disc: radius must be positive, segments 3..1024\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
// Center vertex.
|
|
|
|
|
|
{
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex v;
|
|
|
|
|
|
v.position = glm::vec3(0, 0, 0);
|
|
|
|
|
|
v.normal = glm::vec3(0, 0, 1);
|
|
|
|
|
|
v.texCoord = glm::vec2(0.5f, 0.5f);
|
|
|
|
|
|
wom.vertices.push_back(v);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Ring vertices (one extra at end so UV-seam isn't shared).
|
|
|
|
|
|
for (int k = 0; k <= segments; ++k) {
|
|
|
|
|
|
float t = static_cast<float>(k) / segments;
|
|
|
|
|
|
float ang = t * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex v;
|
|
|
|
|
|
v.position = glm::vec3(radius * ca, radius * sa, 0);
|
|
|
|
|
|
v.normal = glm::vec3(0, 0, 1);
|
|
|
|
|
|
v.texCoord = glm::vec2(0.5f + 0.5f * ca, 0.5f + 0.5f * sa);
|
|
|
|
|
|
wom.vertices.push_back(v);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Fan indices.
|
|
|
|
|
|
for (int k = 0; k < segments; ++k) {
|
|
|
|
|
|
wom.indices.push_back(0);
|
|
|
|
|
|
wom.indices.push_back(1 + k);
|
|
|
|
|
|
wom.indices.push_back(2 + k);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundMin = glm::vec3(-radius, -radius, 0);
|
|
|
|
|
|
wom.boundMax = glm::vec3( radius, radius, 0);
|
|
|
|
|
|
wom.boundRadius = radius;
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch b;
|
|
|
|
|
|
b.indexStart = 0;
|
|
|
|
|
|
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
b.textureIndex = 0;
|
|
|
|
|
|
b.blendMode = 0;
|
|
|
|
|
|
b.flags = 0;
|
|
|
|
|
|
wom.batches.push_back(b);
|
|
|
|
|
|
wom.texturePaths.push_back("");
|
|
|
|
|
|
std::filesystem::path womPath(womBase);
|
|
|
|
|
|
std::filesystem::create_directories(womPath.parent_path());
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-disc: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" radius : %.3f\n", radius);
|
|
|
|
|
|
std::printf(" segments : %d\n", segments);
|
|
|
|
|
|
std::printf(" vertices : %zu (1 center + %d ring)\n",
|
|
|
|
|
|
wom.vertices.size(), segments + 1);
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleTube(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Hollow cylinder along Y axis. Outer + inner walls + top
|
|
|
|
|
|
// and bottom annular caps. Useful for railings, fence
|
|
|
|
|
|
// posts, pipes, hollow logs, ring towers — anywhere a
|
|
|
|
|
|
// solid cylinder would feel wrong because you should be
|
|
|
|
|
|
// able to see through the middle.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float outerR = 1.0f;
|
|
|
|
|
|
float innerR = 0.7f;
|
|
|
|
|
|
float height = 2.0f;
|
|
|
|
|
|
int segments = 24;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { outerR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { innerR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { segments = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (outerR <= 0 || innerR <= 0 || innerR >= outerR ||
|
|
|
|
|
|
height <= 0 || segments < 3 || segments > 1024) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-tube: 0 < innerR < outerR, height > 0, segments 3..1024\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
float h = height * 0.5f;
|
|
|
|
|
|
auto addV = [&](float x, float y, float z,
|
|
|
|
|
|
float nx, float ny, float nz,
|
|
|
|
|
|
float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = glm::vec3(x, y, z);
|
|
|
|
|
|
vtx.normal = glm::vec3(nx, ny, nz);
|
|
|
|
|
|
vtx.texCoord = glm::vec2(u, v);
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Outer wall: 2 rows × (segments+1) verts, normals point
|
|
|
|
|
|
// radially outward.
|
|
|
|
|
|
uint32_t outerStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(outerR * ca, -h, outerR * sa, ca, 0, sa, u, 0);
|
|
|
|
|
|
addV(outerR * ca, h, outerR * sa, ca, 0, sa, u, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
uint32_t a = outerStart + sg * 2;
|
|
|
|
|
|
uint32_t b = a + 1, c = a + 2, d = a + 3;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Inner wall: normals point radially inward, winding
|
|
|
|
|
|
// reversed so the inside-facing surfaces face the viewer
|
|
|
|
|
|
// when looking through the tube.
|
|
|
|
|
|
uint32_t innerStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(innerR * ca, -h, innerR * sa, -ca, 0, -sa, u, 0);
|
|
|
|
|
|
addV(innerR * ca, h, innerR * sa, -ca, 0, -sa, u, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
uint32_t a = innerStart + sg * 2;
|
|
|
|
|
|
uint32_t b = a + 1, c = a + 2, d = a + 3;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Top annular cap: ring at +Y. Inner + outer ring of verts,
|
|
|
|
|
|
// quads stitched between them, normal +Y.
|
|
|
|
|
|
uint32_t topInner = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(innerR * ca, h, innerR * sa, 0, 1, 0,
|
|
|
|
|
|
0.5f + 0.5f * (innerR / outerR) * ca,
|
|
|
|
|
|
0.5f + 0.5f * (innerR / outerR) * sa);
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t topOuter = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(outerR * ca, h, outerR * sa, 0, 1, 0,
|
|
|
|
|
|
0.5f + 0.5f * ca, 0.5f + 0.5f * sa);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
uint32_t a = topInner + sg;
|
|
|
|
|
|
uint32_t b = topInner + sg + 1;
|
|
|
|
|
|
uint32_t c = topOuter + sg;
|
|
|
|
|
|
uint32_t d = topOuter + sg + 1;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Bottom annular cap, normal -Y, winding reversed.
|
|
|
|
|
|
uint32_t botInner = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(innerR * ca, -h, innerR * sa, 0, -1, 0,
|
|
|
|
|
|
0.5f + 0.5f * (innerR / outerR) * ca,
|
|
|
|
|
|
0.5f - 0.5f * (innerR / outerR) * sa);
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t botOuter = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(outerR * ca, -h, outerR * sa, 0, -1, 0,
|
|
|
|
|
|
0.5f + 0.5f * ca, 0.5f - 0.5f * sa);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
uint32_t a = botInner + sg;
|
|
|
|
|
|
uint32_t b = botInner + sg + 1;
|
|
|
|
|
|
uint32_t c = botOuter + sg;
|
|
|
|
|
|
uint32_t d = botOuter + sg + 1;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundMin = glm::vec3(-outerR, -h, -outerR);
|
|
|
|
|
|
wom.boundMax = glm::vec3( outerR, h, outerR);
|
|
|
|
|
|
wom.boundRadius = glm::length(wom.boundMax - wom.boundMin) * 0.5f;
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch b;
|
|
|
|
|
|
b.indexStart = 0;
|
|
|
|
|
|
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
b.textureIndex = 0;
|
|
|
|
|
|
b.blendMode = 0;
|
|
|
|
|
|
b.flags = 0;
|
|
|
|
|
|
wom.batches.push_back(b);
|
|
|
|
|
|
wom.texturePaths.push_back("");
|
|
|
|
|
|
std::filesystem::path womPath(womBase);
|
|
|
|
|
|
std::filesystem::create_directories(womPath.parent_path());
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-tube: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" outer R : %.3f\n", outerR);
|
|
|
|
|
|
std::printf(" inner R : %.3f\n", innerR);
|
|
|
|
|
|
std::printf(" height : %.3f\n", height);
|
|
|
|
|
|
std::printf(" segments : %d\n", segments);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleCapsule(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Capsule along the Y axis: cylindrical body of length
|
|
|
|
|
|
// cylHeight bookended by two hemispheres of radius. Total
|
|
|
|
|
|
// height is cylHeight + 2*radius. Useful for character
|
|
|
|
|
|
// collision shells, pill-shaped buttons, hot-dog props,
|
|
|
|
|
|
// and physics-friendly placeholders.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float radius = 0.5f;
|
|
|
|
|
|
float cylHeight = 1.0f;
|
|
|
|
|
|
int segments = 16;
|
|
|
|
|
|
int stacks = 8; // per hemisphere
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { radius = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { cylHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { segments = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stacks = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (radius <= 0 || cylHeight < 0 ||
|
|
|
|
|
|
segments < 3 || segments > 1024 ||
|
|
|
|
|
|
stacks < 1 || stacks > 256) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-capsule: radius > 0, cylHeight >= 0, segments 3..1024, stacks 1..256\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
float halfBody = cylHeight * 0.5f;
|
|
|
|
|
|
float totalH = cylHeight + 2.0f * radius;
|
|
|
|
|
|
auto addV = [&](float x, float y, float z,
|
|
|
|
|
|
float nx, float ny, float nz,
|
|
|
|
|
|
float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = glm::vec3(x, y, z);
|
|
|
|
|
|
vtx.normal = glm::vec3(nx, ny, nz);
|
|
|
|
|
|
vtx.texCoord = glm::vec2(u, v);
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Top hemisphere: stacks rings from north pole down to
|
|
|
|
|
|
// body top. Vertex layout per ring: (segments+1) verts.
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
int totalVPerRing = segments + 1;
|
|
|
|
|
|
// Top hemisphere rings: stacks+1 rings (ring 0 is the
|
|
|
|
|
|
// pole). v texcoord goes 0..0.25 across the cap.
|
|
|
|
|
|
for (int st = 0; st <= stacks; ++st) {
|
|
|
|
|
|
float t = static_cast<float>(st) / stacks;
|
|
|
|
|
|
float phi = t * (pi * 0.5f); // 0 at pole, π/2 at body
|
|
|
|
|
|
float sphi = std::sin(phi), cphi = std::cos(phi);
|
|
|
|
|
|
float ringR = radius * sphi;
|
|
|
|
|
|
float ringY = halfBody + radius * cphi;
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(ringR * ca, ringY, ringR * sa,
|
|
|
|
|
|
sphi * ca, cphi, sphi * sa,
|
|
|
|
|
|
u, t * 0.25f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Body: 2 rings (top and bottom of cylinder), normal
|
|
|
|
|
|
// radial (no Y component). UV goes 0.25..0.75.
|
|
|
|
|
|
int bodyTopRingStart = static_cast<int>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(radius * ca, halfBody, radius * sa, ca, 0, sa, u, 0.25f);
|
|
|
|
|
|
}
|
|
|
|
|
|
int bodyBotRingStart = static_cast<int>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(radius * ca, -halfBody, radius * sa, ca, 0, sa, u, 0.75f);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Bottom hemisphere: mirror of top.
|
|
|
|
|
|
int botHemiStart = static_cast<int>(wom.vertices.size());
|
|
|
|
|
|
for (int st = 0; st <= stacks; ++st) {
|
|
|
|
|
|
float t = static_cast<float>(st) / stacks;
|
|
|
|
|
|
float phi = t * (pi * 0.5f);
|
|
|
|
|
|
float sphi = std::sin(phi), cphi = std::cos(phi);
|
|
|
|
|
|
float ringR = radius * cphi;
|
|
|
|
|
|
float ringY = -halfBody - radius * sphi;
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(ringR * ca, ringY, ringR * sa,
|
|
|
|
|
|
cphi * ca, -sphi, cphi * sa,
|
|
|
|
|
|
u, 0.75f + t * 0.25f);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Index the rings: top hemi (stacks rings → stacks-1
|
|
|
|
|
|
// bands), body (1 band), bottom hemi (stacks bands).
|
|
|
|
|
|
auto stitch = [&](int topRingStart, int botRingStart) {
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
uint32_t a = topRingStart + sg;
|
|
|
|
|
|
uint32_t b = a + 1;
|
|
|
|
|
|
uint32_t c = botRingStart + sg;
|
|
|
|
|
|
uint32_t d = c + 1;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Top hemisphere bands.
|
|
|
|
|
|
for (int st = 0; st < stacks; ++st) {
|
|
|
|
|
|
stitch(st * totalVPerRing, (st + 1) * totalVPerRing);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Body band: between bodyTopRingStart and bodyBotRingStart.
|
|
|
|
|
|
stitch(bodyTopRingStart, bodyBotRingStart);
|
|
|
|
|
|
// Bottom hemisphere bands.
|
|
|
|
|
|
for (int st = 0; st < stacks; ++st) {
|
|
|
|
|
|
stitch(botHemiStart + st * totalVPerRing,
|
|
|
|
|
|
botHemiStart + (st + 1) * totalVPerRing);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundMin = glm::vec3(-radius, -totalH * 0.5f, -radius);
|
|
|
|
|
|
wom.boundMax = glm::vec3( radius, totalH * 0.5f, radius);
|
|
|
|
|
|
wom.boundRadius = glm::length(wom.boundMax - wom.boundMin) * 0.5f;
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch b;
|
|
|
|
|
|
b.indexStart = 0;
|
|
|
|
|
|
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
b.textureIndex = 0;
|
|
|
|
|
|
b.blendMode = 0;
|
|
|
|
|
|
b.flags = 0;
|
|
|
|
|
|
wom.batches.push_back(b);
|
|
|
|
|
|
wom.texturePaths.push_back("");
|
|
|
|
|
|
std::filesystem::path womPath(womBase);
|
|
|
|
|
|
std::filesystem::create_directories(womPath.parent_path());
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-capsule: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" radius : %.3f\n", radius);
|
|
|
|
|
|
std::printf(" cylHeight : %.3f\n", cylHeight);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" segments : %d\n", segments);
|
|
|
|
|
|
std::printf(" stacks : %d (per hemisphere)\n", stacks);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleArch(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Doorway/portal arch: two rectangular columns connected
|
|
|
|
|
|
// by a semicircular top band. Total width = openingWidth +
|
|
|
|
|
|
// 2*thickness; total height = openingHeight + thickness +
|
|
|
|
|
|
// archRadius (where archRadius = openingWidth/2). Depth
|
|
|
|
|
|
// is the Y-axis thickness (extruded slab).
|
|
|
|
|
|
//
|
|
|
|
|
|
// Two box columns + curved arch band on top. Useful for
|
|
|
|
|
|
// doorways, portal frames, gates. Aligned so the inside
|
|
|
|
|
|
// of the opening is centered on the Y axis.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float openingW = 1.0f, openingH = 1.5f;
|
|
|
|
|
|
float thickness = 0.2f; // column thickness (X)
|
|
|
|
|
|
float depth = 0.3f; // Y extrusion
|
|
|
|
|
|
int segments = 12; // arch curve segments
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { openingW = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { openingH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { thickness = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { depth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { segments = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (openingW <= 0 || openingH <= 0 ||
|
|
|
|
|
|
thickness <= 0 || depth <= 0 ||
|
|
|
|
|
|
segments < 2 || segments > 256) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-arch: dimensions must be positive, segments 2..256\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
// Helper to push a vertex.
|
|
|
|
|
|
auto addV = [&](float x, float y, float z,
|
|
|
|
|
|
float nx, float ny, float nz,
|
|
|
|
|
|
float u, float v) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = glm::vec3(x, y, z);
|
|
|
|
|
|
vtx.normal = glm::vec3(nx, ny, nz);
|
|
|
|
|
|
vtx.texCoord = glm::vec2(u, v);
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Helper to emit an axis-aligned box from min to max.
|
|
|
|
|
|
auto addBox = [&](glm::vec3 lo, glm::vec3 hi) {
|
|
|
|
|
|
struct Face { float nx, ny, nz; float verts[4][3]; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{ 0, 0, 1, {{lo.x,lo.y,hi.z},{hi.x,lo.y,hi.z},{hi.x,hi.y,hi.z},{lo.x,hi.y,hi.z}}},
|
|
|
|
|
|
{ 0, 0, -1, {{hi.x,lo.y,lo.z},{lo.x,lo.y,lo.z},{lo.x,hi.y,lo.z},{hi.x,hi.y,lo.z}}},
|
|
|
|
|
|
{ 1, 0, 0, {{hi.x,lo.y,hi.z},{hi.x,lo.y,lo.z},{hi.x,hi.y,lo.z},{hi.x,hi.y,hi.z}}},
|
|
|
|
|
|
{-1, 0, 0, {{lo.x,lo.y,lo.z},{lo.x,lo.y,hi.z},{lo.x,hi.y,hi.z},{lo.x,hi.y,lo.z}}},
|
|
|
|
|
|
{ 0, 1, 0, {{lo.x,hi.y,hi.z},{hi.x,hi.y,hi.z},{hi.x,hi.y,lo.z},{lo.x,hi.y,lo.z}}},
|
|
|
|
|
|
{ 0, -1, 0, {{lo.x,lo.y,lo.z},{hi.x,lo.y,lo.z},{hi.x,lo.y,hi.z},{lo.x,lo.y,hi.z}}},
|
|
|
|
|
|
};
|
|
|
|
|
|
float uvs[4][2] = {{0,0},{1,0},{1,1},{0,1}};
|
|
|
|
|
|
for (auto& f : faces) {
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int k = 0; k < 4; ++k) {
|
|
|
|
|
|
addV(f.verts[k][0], f.verts[k][1], f.verts[k][2],
|
|
|
|
|
|
f.nx, f.ny, f.nz, uvs[k][0], uvs[k][1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 1);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 3);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfOW = openingW * 0.5f;
|
|
|
|
|
|
float halfD = depth * 0.5f;
|
|
|
|
|
|
// Left column.
|
|
|
|
|
|
addBox(glm::vec3(-halfOW - thickness, -halfD, 0),
|
|
|
|
|
|
glm::vec3(-halfOW, halfD, openingH));
|
|
|
|
|
|
// Right column.
|
|
|
|
|
|
addBox(glm::vec3(halfOW, -halfD, 0),
|
|
|
|
|
|
glm::vec3(halfOW + thickness, halfD, openingH));
|
|
|
|
|
|
// Arch top band: curve from (-halfOW, openingH) through
|
|
|
|
|
|
// (0, openingH+halfOW) to (halfOW, openingH). Radius =
|
|
|
|
|
|
// halfOW. Outer surface follows the curve, inner surface
|
|
|
|
|
|
// is the underside. Built from <segments> bands of 4
|
|
|
|
|
|
// verts each (front + back faces handled per band).
|
|
|
|
|
|
float archCenterZ = openingH;
|
|
|
|
|
|
float archR = halfOW;
|
|
|
|
|
|
float pi = 3.14159265358979f;
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
float t0 = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float t1 = static_cast<float>(sg + 1) / segments;
|
|
|
|
|
|
float a0 = pi - t0 * pi; // start at 180°, sweep to 0°
|
|
|
|
|
|
float a1 = pi - t1 * pi;
|
|
|
|
|
|
float c0 = std::cos(a0), s0 = std::sin(a0);
|
|
|
|
|
|
float c1 = std::cos(a1), s1 = std::sin(a1);
|
|
|
|
|
|
// Outer ring point at angle a.
|
|
|
|
|
|
glm::vec3 outer0(archR * c0, 0, archCenterZ + archR * s0);
|
|
|
|
|
|
glm::vec3 outer1(archR * c1, 0, archCenterZ + archR * s1);
|
2026-05-09 10:32:26 -07:00
|
|
|
|
// The arch band is a flat strip along the curve, no
|
|
|
|
|
|
// explicit inner ring. Top face of band points radially
|
|
|
|
|
|
// outward from arch center.
|
refactor(editor): extract 9 more mesh handlers into cli_gen_mesh.cpp
Moves stairs, grid, disc, tube, capsule, arch, pyramid, fence,
and tree into the existing cli_gen_mesh.cpp module. Together
with the 12 composite props extracted in 00754941 and the
anvil added in cd4bdfec, the module now contains 22 of the
28 procedural mesh primitives.
Remaining mesh handlers in main.cpp: --gen-mesh dispatcher
(cube/plane/sphere/cylinder/torus/cone/ramp), --gen-mesh-from-
heightmap, --gen-mesh-textured. Those have specialized I/O or
embedded sub-dispatch and will move in a follow-up batch.
main.cpp drops 22,681 → 21,526 lines (-1,155). Behavior
verified by re-running stairs/disc/fence/tree.
2026-05-08 22:58:21 -07:00
|
|
|
|
glm::vec3 n((c0 + c1) * 0.5f, 0, (s0 + s1) * 0.5f);
|
|
|
|
|
|
n = glm::normalize(n);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
addV(outer0.x, outer0.y - halfD, outer0.z, n.x, 0, n.z, 0, 0);
|
|
|
|
|
|
addV(outer1.x, outer1.y - halfD, outer1.z, n.x, 0, n.z, 1, 0);
|
|
|
|
|
|
addV(outer1.x, outer1.y + halfD, outer1.z, n.x, 0, n.z, 1, 1);
|
|
|
|
|
|
addV(outer0.x, outer0.y + halfD, outer0.z, n.x, 0, n.z, 0, 1);
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 1);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 3);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundMin = glm::vec3(1e30f);
|
|
|
|
|
|
wom.boundMax = glm::vec3(-1e30f);
|
|
|
|
|
|
for (const auto& v : wom.vertices) {
|
|
|
|
|
|
wom.boundMin = glm::min(wom.boundMin, v.position);
|
|
|
|
|
|
wom.boundMax = glm::max(wom.boundMax, v.position);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundRadius = glm::length(wom.boundMax - wom.boundMin) * 0.5f;
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch b;
|
|
|
|
|
|
b.indexStart = 0;
|
|
|
|
|
|
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
b.textureIndex = 0;
|
|
|
|
|
|
b.blendMode = 0;
|
|
|
|
|
|
b.flags = 0;
|
|
|
|
|
|
wom.batches.push_back(b);
|
|
|
|
|
|
wom.texturePaths.push_back("");
|
|
|
|
|
|
std::filesystem::path womPath(womBase);
|
|
|
|
|
|
std::filesystem::create_directories(womPath.parent_path());
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-arch: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" opening : %.3f W × %.3f H\n", openingW, openingH);
|
|
|
|
|
|
std::printf(" thickness : %.3f (column), depth %.3f (Y)\n", thickness, depth);
|
|
|
|
|
|
std::printf(" segments : %d (arch curve)\n", segments);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
std::printf(" bounds : (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)\n",
|
|
|
|
|
|
wom.boundMin.x, wom.boundMin.y, wom.boundMin.z,
|
|
|
|
|
|
wom.boundMax.x, wom.boundMax.y, wom.boundMax.z);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handlePyramid(int& i, int argc, char** argv) {
|
|
|
|
|
|
// N-sided polygonal pyramid with apex at +Y. 4 sides
|
|
|
|
|
|
// gives a square pyramid; 3 gives a tetrahedron-like
|
|
|
|
|
|
// shape; 8+ approaches a cone.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Different from --gen-mesh cone: cone has smooth
|
|
|
|
|
|
// round sides with per-vertex radial-ish normals;
|
|
|
|
|
|
// pyramid has flat per-face normals on N triangular
|
|
|
|
|
|
// sides + a flat polygonal base.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
int sides = 4;
|
|
|
|
|
|
float baseR = 1.0f;
|
|
|
|
|
|
float height = 1.0f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { sides = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (sides < 3 || sides > 256 || baseR <= 0 || height <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-pyramid: sides 3..256, baseR > 0, height > 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p;
|
|
|
|
|
|
vtx.normal = n;
|
|
|
|
|
|
vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Build base ring vertices (one per side).
|
|
|
|
|
|
std::vector<glm::vec3> basePts;
|
|
|
|
|
|
for (int k = 0; k < sides; ++k) {
|
|
|
|
|
|
float a = static_cast<float>(k) / sides * 2.0f * pi;
|
|
|
|
|
|
basePts.push_back(glm::vec3(baseR * std::cos(a), 0,
|
|
|
|
|
|
baseR * std::sin(a)));
|
|
|
|
|
|
}
|
|
|
|
|
|
glm::vec3 apex(0, height, 0);
|
|
|
|
|
|
// Side faces: per-face flat normals (cross of two edges).
|
|
|
|
|
|
for (int k = 0; k < sides; ++k) {
|
|
|
|
|
|
glm::vec3 a = basePts[k];
|
|
|
|
|
|
glm::vec3 b = basePts[(k + 1) % sides];
|
|
|
|
|
|
glm::vec3 e1 = b - a;
|
|
|
|
|
|
glm::vec3 e2 = apex - a;
|
|
|
|
|
|
glm::vec3 n = glm::normalize(glm::cross(e1, e2));
|
|
|
|
|
|
float u0 = static_cast<float>(k) / sides;
|
|
|
|
|
|
float u1 = static_cast<float>(k + 1) / sides;
|
|
|
|
|
|
uint32_t i0 = addV(a, n, glm::vec2(u0, 1));
|
|
|
|
|
|
uint32_t i1 = addV(b, n, glm::vec2(u1, 1));
|
|
|
|
|
|
uint32_t i2 = addV(apex, n, glm::vec2(0.5f * (u0 + u1), 0));
|
|
|
|
|
|
wom.indices.push_back(i0);
|
|
|
|
|
|
wom.indices.push_back(i1);
|
|
|
|
|
|
wom.indices.push_back(i2);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Base: fan from a center vertex (normal -Y).
|
|
|
|
|
|
uint32_t baseCenter = addV(glm::vec3(0, 0, 0),
|
|
|
|
|
|
glm::vec3(0, -1, 0),
|
|
|
|
|
|
glm::vec2(0.5f, 0.5f));
|
|
|
|
|
|
uint32_t baseRingStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int k = 0; k < sides; ++k) {
|
|
|
|
|
|
float a = static_cast<float>(k) / sides * 2.0f * pi;
|
|
|
|
|
|
addV(basePts[k], glm::vec3(0, -1, 0),
|
|
|
|
|
|
glm::vec2(0.5f + 0.5f * std::cos(a),
|
|
|
|
|
|
0.5f - 0.5f * std::sin(a)));
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int k = 0; k < sides; ++k) {
|
|
|
|
|
|
wom.indices.push_back(baseCenter);
|
|
|
|
|
|
wom.indices.push_back(baseRingStart + (k + 1) % sides);
|
|
|
|
|
|
wom.indices.push_back(baseRingStart + k);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundMin = glm::vec3(-baseR, 0, -baseR);
|
|
|
|
|
|
wom.boundMax = glm::vec3( baseR, height, baseR);
|
|
|
|
|
|
wom.boundRadius = glm::length(wom.boundMax - wom.boundMin) * 0.5f;
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch b;
|
|
|
|
|
|
b.indexStart = 0;
|
|
|
|
|
|
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
b.textureIndex = 0;
|
|
|
|
|
|
b.blendMode = 0;
|
|
|
|
|
|
b.flags = 0;
|
|
|
|
|
|
wom.batches.push_back(b);
|
|
|
|
|
|
wom.texturePaths.push_back("");
|
|
|
|
|
|
std::filesystem::path womPath(womBase);
|
|
|
|
|
|
std::filesystem::create_directories(womPath.parent_path());
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-pyramid: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" sides : %d\n", sides);
|
|
|
|
|
|
std::printf(" base R : %.3f\n", baseR);
|
|
|
|
|
|
std::printf(" height : %.3f\n", height);
|
|
|
|
|
|
std::printf(" vertices : %zu (%d side tris × 3 + 1 base center + %d base ring)\n",
|
|
|
|
|
|
wom.vertices.size(), sides, sides);
|
|
|
|
|
|
std::printf(" triangles : %zu (%d sides + %d base)\n",
|
|
|
|
|
|
wom.indices.size() / 3, sides, sides);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleFence(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Repeating fence: N square posts along +X spaced
|
|
|
|
|
|
// <postSpacing> apart, with two horizontal rails (top
|
|
|
|
|
|
// and bottom) connecting consecutive posts. Posts span
|
|
|
|
|
|
// from Y=0 up to Y=postHeight; each post is a small box
|
|
|
|
|
|
// of width = railThick × 2.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Useful for fences around plots, pen boundaries,
|
|
|
|
|
|
// walkway dividers, garden beds.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
int posts = 5;
|
|
|
|
|
|
float spacing = 1.0f;
|
|
|
|
|
|
float postH = 1.0f;
|
|
|
|
|
|
float rt = 0.05f; // rail/post thickness
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { posts = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { spacing = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { rt = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (posts < 2 || posts > 256 ||
|
|
|
|
|
|
spacing <= 0 || postH <= 0 || rt <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-fence: posts 2..256, spacing/height/thick > 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p;
|
|
|
|
|
|
vtx.normal = n;
|
|
|
|
|
|
vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
auto addBox = [&](glm::vec3 lo, glm::vec3 hi) {
|
|
|
|
|
|
struct Face { float nx, ny, nz; float verts[4][3]; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{ 0, 1, 0, {{lo.x,hi.y,hi.z},{hi.x,hi.y,hi.z},{hi.x,hi.y,lo.z},{lo.x,hi.y,lo.z}}},
|
|
|
|
|
|
{ 0, -1, 0, {{lo.x,lo.y,lo.z},{hi.x,lo.y,lo.z},{hi.x,lo.y,hi.z},{lo.x,lo.y,hi.z}}},
|
|
|
|
|
|
{ 0, 0, 1, {{lo.x,lo.y,hi.z},{hi.x,lo.y,hi.z},{hi.x,hi.y,hi.z},{lo.x,hi.y,hi.z}}},
|
|
|
|
|
|
{ 0, 0, -1, {{hi.x,lo.y,lo.z},{lo.x,lo.y,lo.z},{lo.x,hi.y,lo.z},{hi.x,hi.y,lo.z}}},
|
|
|
|
|
|
{ 1, 0, 0, {{hi.x,lo.y,hi.z},{hi.x,lo.y,lo.z},{hi.x,hi.y,lo.z},{hi.x,hi.y,hi.z}}},
|
|
|
|
|
|
{-1, 0, 0, {{lo.x,lo.y,lo.z},{lo.x,lo.y,hi.z},{lo.x,hi.y,hi.z},{lo.x,hi.y,lo.z}}},
|
|
|
|
|
|
};
|
|
|
|
|
|
float uvs[4][2] = {{0,0},{1,0},{1,1},{0,1}};
|
|
|
|
|
|
for (auto& f : faces) {
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int k = 0; k < 4; ++k) {
|
|
|
|
|
|
addV(glm::vec3(f.verts[k][0], f.verts[k][1], f.verts[k][2]),
|
|
|
|
|
|
glm::vec3(f.nx, f.ny, f.nz),
|
|
|
|
|
|
glm::vec2(uvs[k][0], uvs[k][1]));
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 1);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 3);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float postHalfW = rt;
|
|
|
|
|
|
// Posts along +X starting at X=0.
|
|
|
|
|
|
for (int k = 0; k < posts; ++k) {
|
|
|
|
|
|
float cx = k * spacing;
|
|
|
|
|
|
addBox(glm::vec3(cx - postHalfW, -postHalfW, 0),
|
|
|
|
|
|
glm::vec3(cx + postHalfW, postHalfW, postH));
|
|
|
|
|
|
}
|
|
|
|
|
|
// Rails between consecutive posts. Two rails per gap:
|
|
|
|
|
|
// top (~80% up) and bottom (~30% up).
|
|
|
|
|
|
float topRailZ = postH * 0.8f;
|
|
|
|
|
|
float botRailZ = postH * 0.3f;
|
|
|
|
|
|
float railHalfH = rt * 0.5f; // rail is thinner than posts
|
|
|
|
|
|
for (int k = 0; k + 1 < posts; ++k) {
|
|
|
|
|
|
float xL = k * spacing + postHalfW;
|
|
|
|
|
|
float xR = (k + 1) * spacing - postHalfW;
|
|
|
|
|
|
if (xR <= xL) continue; // posts touching
|
|
|
|
|
|
addBox(glm::vec3(xL, -railHalfH, topRailZ - railHalfH),
|
|
|
|
|
|
glm::vec3(xR, railHalfH, topRailZ + railHalfH));
|
|
|
|
|
|
addBox(glm::vec3(xL, -railHalfH, botRailZ - railHalfH),
|
|
|
|
|
|
glm::vec3(xR, railHalfH, botRailZ + railHalfH));
|
|
|
|
|
|
}
|
|
|
|
|
|
// Bounds.
|
|
|
|
|
|
wom.boundMin = glm::vec3(-postHalfW, -postHalfW, 0);
|
|
|
|
|
|
wom.boundMax = glm::vec3((posts - 1) * spacing + postHalfW,
|
|
|
|
|
|
postHalfW, postH);
|
|
|
|
|
|
wom.boundRadius = glm::length(wom.boundMax - wom.boundMin) * 0.5f;
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch b;
|
|
|
|
|
|
b.indexStart = 0;
|
|
|
|
|
|
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
b.textureIndex = 0;
|
|
|
|
|
|
b.blendMode = 0;
|
|
|
|
|
|
b.flags = 0;
|
|
|
|
|
|
wom.batches.push_back(b);
|
|
|
|
|
|
wom.texturePaths.push_back("");
|
|
|
|
|
|
std::filesystem::path womPath(womBase);
|
|
|
|
|
|
std::filesystem::create_directories(womPath.parent_path());
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-fence: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" posts : %d\n", posts);
|
|
|
|
|
|
std::printf(" spacing : %.3f\n", spacing);
|
|
|
|
|
|
std::printf(" height : %.3f\n", postH);
|
|
|
|
|
|
std::printf(" thickness : %.3f\n", rt);
|
|
|
|
|
|
std::printf(" span X : %.3f\n", (posts - 1) * spacing);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleTree(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Procedural tree: cylinder trunk + UV-sphere foliage.
|
|
|
|
|
|
// Trunk goes from Y=0 up to Y=trunkHeight; foliage sphere
|
|
|
|
|
|
// centered at trunk-top + foliageRadius/2 so the trunk
|
|
|
|
|
|
// pokes up into the bottom of the canopy.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Useful for ambient zone decoration, distant tree
|
|
|
|
|
|
// placeholders, magic-grove props. The 15th procedural
|
|
|
|
|
|
// primitive — pairs naturally with --add-texture-to-mesh
|
|
|
|
|
|
// for trunk-bark and leaf textures (or just one texture
|
|
|
|
|
|
// since this is a single-batch mesh).
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float trunkR = 0.1f;
|
|
|
|
|
|
float trunkH = 2.0f;
|
|
|
|
|
|
float foliR = 0.7f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { trunkR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { trunkH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { foliR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (trunkR <= 0 || trunkH <= 0 || foliR <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-tree: trunkR / trunkH / foliR must be positive\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p;
|
|
|
|
|
|
vtx.normal = n;
|
|
|
|
|
|
vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Trunk cylinder: 12 segments, side ring + top + bottom.
|
|
|
|
|
|
const int trunkSegs = 12;
|
|
|
|
|
|
uint32_t trunkSideStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= trunkSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / trunkSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addV(glm::vec3(trunkR * ca, 0, trunkR * sa),
|
|
|
|
|
|
glm::vec3(ca, 0, sa),
|
|
|
|
|
|
glm::vec2(u, 0));
|
|
|
|
|
|
addV(glm::vec3(trunkR * ca, trunkH, trunkR * sa),
|
|
|
|
|
|
glm::vec3(ca, 0, sa),
|
|
|
|
|
|
glm::vec2(u, 1));
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < trunkSegs; ++sg) {
|
|
|
|
|
|
uint32_t a = trunkSideStart + sg * 2;
|
|
|
|
|
|
uint32_t b = a + 1, c = a + 2, d = a + 3;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Foliage UV sphere: 12 segments × 8 stacks. Center at
|
|
|
|
|
|
// (0, trunkH + foliR * 0.7, 0) so the trunk pokes into
|
|
|
|
|
|
// the bottom of the canopy.
|
|
|
|
|
|
const int fSegs = 12;
|
|
|
|
|
|
const int fStacks = 8;
|
|
|
|
|
|
float foliCY = trunkH + foliR * 0.7f;
|
|
|
|
|
|
uint32_t foliStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int st = 0; st <= fStacks; ++st) {
|
|
|
|
|
|
float v = static_cast<float>(st) / fStacks;
|
|
|
|
|
|
float phi = v * pi;
|
|
|
|
|
|
float sphi = std::sin(phi), cphi = std::cos(phi);
|
|
|
|
|
|
for (int sg = 0; sg <= fSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / fSegs;
|
|
|
|
|
|
float theta = u * 2.0f * pi;
|
|
|
|
|
|
float ctheta = std::cos(theta), stheta = std::sin(theta);
|
|
|
|
|
|
float nx = sphi * ctheta;
|
|
|
|
|
|
float ny = cphi;
|
|
|
|
|
|
float nz = sphi * stheta;
|
|
|
|
|
|
addV(glm::vec3(foliR * nx, foliCY + foliR * ny, foliR * nz),
|
|
|
|
|
|
glm::vec3(nx, ny, nz),
|
|
|
|
|
|
glm::vec2(u, v));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
int fStride = fSegs + 1;
|
|
|
|
|
|
for (int st = 0; st < fStacks; ++st) {
|
|
|
|
|
|
for (int sg = 0; sg < fSegs; ++sg) {
|
|
|
|
|
|
uint32_t a = foliStart + st * fStride + sg;
|
|
|
|
|
|
uint32_t b = a + 1;
|
|
|
|
|
|
uint32_t c = a + fStride;
|
|
|
|
|
|
uint32_t d = c + 1;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundMin = glm::vec3(-foliR, 0, -foliR);
|
|
|
|
|
|
wom.boundMax = glm::vec3( foliR, foliCY + foliR, foliR);
|
|
|
|
|
|
wom.boundRadius = glm::length(wom.boundMax - wom.boundMin) * 0.5f;
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch b;
|
|
|
|
|
|
b.indexStart = 0;
|
|
|
|
|
|
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
b.textureIndex = 0;
|
|
|
|
|
|
b.blendMode = 0;
|
|
|
|
|
|
b.flags = 0;
|
|
|
|
|
|
wom.batches.push_back(b);
|
|
|
|
|
|
wom.texturePaths.push_back("");
|
|
|
|
|
|
std::filesystem::path womPath(womBase);
|
|
|
|
|
|
std::filesystem::create_directories(womPath.parent_path());
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-tree: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" trunk R : %.3f\n", trunkR);
|
|
|
|
|
|
std::printf(" trunk H : %.3f\n", trunkH);
|
|
|
|
|
|
std::printf(" foliage R : %.3f\n", foliR);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", foliCY + foliR);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 23:32:04 -07:00
|
|
|
|
int handleMeshDispatch(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Synthesize a procedural primitive WOM. Generates proper
|
|
|
|
|
|
// per-face normals, planar UVs, a bounding box, and a
|
|
|
|
|
|
// single batch covering all indices so the model renders
|
|
|
|
|
|
// immediately in the editor without further processing.
|
|
|
|
|
|
//
|
|
|
|
|
|
// Shapes:
|
|
|
|
|
|
// cube — 24 verts / 12 tris, axis-aligned, ±size/2
|
|
|
|
|
|
// plane — 4 verts / 2 tris, on XY plane (Z=0), ±size/2
|
|
|
|
|
|
// sphere — UV sphere, 16 segments × 12 stacks, radius=size/2
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
std::string shape = argv[++i];
|
|
|
|
|
|
float size = 1.0f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { size = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (size <= 0.0f) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh: size must be positive (got %g)\n", size);
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Strip .wom if user passed a full filename — saver expects base.
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
// Helper to push a vertex with explicit normal + uv.
|
|
|
|
|
|
auto addVertex = [&](float x, float y, float z,
|
|
|
|
|
|
float nx, float ny, float nz,
|
|
|
|
|
|
float u, float v) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = glm::vec3(x, y, z);
|
|
|
|
|
|
vtx.normal = glm::vec3(nx, ny, nz);
|
|
|
|
|
|
vtx.texCoord = glm::vec2(u, v);
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
std::string s = shape;
|
|
|
|
|
|
std::transform(s.begin(), s.end(), s.begin(),
|
|
|
|
|
|
[](unsigned char c) { return std::tolower(c); });
|
|
|
|
|
|
float h = size * 0.5f;
|
|
|
|
|
|
if (s == "cube") {
|
|
|
|
|
|
// 6 faces, 4 verts each (so per-face normals are flat).
|
|
|
|
|
|
struct Face { float nx, ny, nz; float verts[4][3]; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{ 0, 0, 1, {{-h,-h, h},{ h,-h, h},{ h, h, h},{-h, h, h}}}, // +Z
|
|
|
|
|
|
{ 0, 0, -1, {{ h,-h,-h},{-h,-h,-h},{-h, h,-h},{ h, h,-h}}}, // -Z
|
|
|
|
|
|
{ 1, 0, 0, {{ h,-h, h},{ h,-h,-h},{ h, h,-h},{ h, h, h}}}, // +X
|
|
|
|
|
|
{-1, 0, 0, {{-h,-h,-h},{-h,-h, h},{-h, h, h},{-h, h,-h}}}, // -X
|
|
|
|
|
|
{ 0, 1, 0, {{-h, h, h},{ h, h, h},{ h, h,-h},{-h, h,-h}}}, // +Y
|
|
|
|
|
|
{ 0, -1, 0, {{-h,-h,-h},{ h,-h,-h},{ h,-h, h},{-h,-h, h}}}, // -Y
|
|
|
|
|
|
};
|
|
|
|
|
|
float uvs[4][2] = {{0,0},{1,0},{1,1},{0,1}};
|
|
|
|
|
|
for (auto& f : faces) {
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int k = 0; k < 4; ++k) {
|
|
|
|
|
|
addVertex(f.verts[k][0], f.verts[k][1], f.verts[k][2],
|
|
|
|
|
|
f.nx, f.ny, f.nz, uvs[k][0], uvs[k][1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 1);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 3);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (s == "plane") {
|
|
|
|
|
|
addVertex(-h, -h, 0, 0, 0, 1, 0, 0);
|
|
|
|
|
|
addVertex( h, -h, 0, 0, 0, 1, 1, 0);
|
|
|
|
|
|
addVertex( h, h, 0, 0, 0, 1, 1, 1);
|
|
|
|
|
|
addVertex(-h, h, 0, 0, 0, 1, 0, 1);
|
|
|
|
|
|
wom.indices = {0, 1, 2, 0, 2, 3};
|
|
|
|
|
|
} else if (s == "sphere") {
|
|
|
|
|
|
const int segments = 16;
|
|
|
|
|
|
const int stacks = 12;
|
|
|
|
|
|
float r = h;
|
|
|
|
|
|
for (int st = 0; st <= stacks; ++st) {
|
|
|
|
|
|
float v = static_cast<float>(st) / stacks;
|
|
|
|
|
|
float phi = v * 3.14159265358979f;
|
|
|
|
|
|
float sphi = std::sin(phi), cphi = std::cos(phi);
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float theta = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float stheta = std::sin(theta), ctheta = std::cos(theta);
|
|
|
|
|
|
float nx = sphi * ctheta;
|
|
|
|
|
|
float ny = sphi * stheta;
|
|
|
|
|
|
float nz = cphi;
|
|
|
|
|
|
addVertex(r * nx, r * ny, r * nz, nx, ny, nz, u, v);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
int stride = segments + 1;
|
|
|
|
|
|
for (int st = 0; st < stacks; ++st) {
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
uint32_t a = st * stride + sg;
|
|
|
|
|
|
uint32_t b = a + 1;
|
|
|
|
|
|
uint32_t c = a + stride;
|
|
|
|
|
|
uint32_t d = c + 1;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (s == "cylinder") {
|
|
|
|
|
|
// Capped cylinder along the Y axis. radius=size/2,
|
|
|
|
|
|
// height=size. 24 side segments — smooth enough for
|
|
|
|
|
|
// pillars and torches without exploding the vertex
|
|
|
|
|
|
// count. UVs: side wraps the texture once around;
|
|
|
|
|
|
// caps map [0..1] from a square sampled at the disc.
|
|
|
|
|
|
const int segments = 24;
|
|
|
|
|
|
float r = h;
|
|
|
|
|
|
// Side ring: 2 vertex rows (top, bottom), each with
|
|
|
|
|
|
// (segments+1) verts so UV-seam doesn't share verts.
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
// Bottom ring (Y = -h).
|
|
|
|
|
|
addVertex(r * ca, -h, r * sa, ca, 0, sa, u, 0);
|
|
|
|
|
|
// Top ring (Y = +h).
|
|
|
|
|
|
addVertex(r * ca, h, r * sa, ca, 0, sa, u, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Side quad indices.
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
uint32_t a = sg * 2;
|
|
|
|
|
|
uint32_t b = a + 1;
|
|
|
|
|
|
uint32_t c = a + 2;
|
|
|
|
|
|
uint32_t d = a + 3;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Top cap fan.
|
|
|
|
|
|
uint32_t topCenter = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
addVertex(0, h, 0, 0, 1, 0, 0.5f, 0.5f);
|
|
|
|
|
|
uint32_t topRingStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addVertex(r * ca, h, r * sa, 0, 1, 0,
|
|
|
|
|
|
0.5f + 0.5f * ca, 0.5f + 0.5f * sa);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
wom.indices.push_back(topCenter);
|
|
|
|
|
|
wom.indices.push_back(topRingStart + sg);
|
|
|
|
|
|
wom.indices.push_back(topRingStart + sg + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Bottom cap fan (winding flipped so normal points -Y).
|
|
|
|
|
|
uint32_t botCenter = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
addVertex(0, -h, 0, 0, -1, 0, 0.5f, 0.5f);
|
|
|
|
|
|
uint32_t botRingStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addVertex(r * ca, -h, r * sa, 0, -1, 0,
|
|
|
|
|
|
0.5f + 0.5f * ca, 0.5f - 0.5f * sa);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
wom.indices.push_back(botCenter);
|
|
|
|
|
|
wom.indices.push_back(botRingStart + sg + 1);
|
|
|
|
|
|
wom.indices.push_back(botRingStart + sg);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (s == "torus") {
|
|
|
|
|
|
// Torus around the Y axis. Major radius (ring center
|
|
|
|
|
|
// distance from origin) = size/2, minor radius (tube
|
|
|
|
|
|
// thickness) = size/8 — the 4:1 ratio reads as a
|
|
|
|
|
|
// ring rather than a fat donut. 32 ring segments × 16
|
|
|
|
|
|
// tube segments = ~544 verts / ~1024 tris.
|
|
|
|
|
|
const int ringSeg = 32;
|
|
|
|
|
|
const int tubeSeg = 16;
|
|
|
|
|
|
float R = h; // major radius
|
|
|
|
|
|
float r = h * 0.25f; // minor radius (h/4)
|
|
|
|
|
|
for (int i2 = 0; i2 <= ringSeg; ++i2) {
|
|
|
|
|
|
float u = static_cast<float>(i2) / ringSeg;
|
|
|
|
|
|
float theta = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ct = std::cos(theta), st = std::sin(theta);
|
|
|
|
|
|
for (int j2 = 0; j2 <= tubeSeg; ++j2) {
|
|
|
|
|
|
float v = static_cast<float>(j2) / tubeSeg;
|
|
|
|
|
|
float phi = v * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float cp = std::cos(phi), sp = std::sin(phi);
|
|
|
|
|
|
// Position on the surface.
|
|
|
|
|
|
float x = (R + r * cp) * ct;
|
|
|
|
|
|
float y = r * sp;
|
|
|
|
|
|
float z = (R + r * cp) * st;
|
|
|
|
|
|
// Normal: from the tube center outward.
|
|
|
|
|
|
float nx = cp * ct;
|
|
|
|
|
|
float ny = sp;
|
|
|
|
|
|
float nz = cp * st;
|
|
|
|
|
|
addVertex(x, y, z, nx, ny, nz, u, v);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
int stride = tubeSeg + 1;
|
|
|
|
|
|
for (int i2 = 0; i2 < ringSeg; ++i2) {
|
|
|
|
|
|
for (int j2 = 0; j2 < tubeSeg; ++j2) {
|
|
|
|
|
|
uint32_t a = i2 * stride + j2;
|
|
|
|
|
|
uint32_t b = a + 1;
|
|
|
|
|
|
uint32_t c = a + stride;
|
|
|
|
|
|
uint32_t d = c + 1;
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (s == "cone") {
|
|
|
|
|
|
// Cone with apex at +Y. radius=size/2, height=size.
|
|
|
|
|
|
// 24 side segments. Side has smooth radial-ish normals
|
|
|
|
|
|
// (slanted up by half the slope angle) for a curved
|
|
|
|
|
|
// shaded surface; bottom cap has flat -Y normal.
|
|
|
|
|
|
const int segments = 24;
|
|
|
|
|
|
float r = h;
|
|
|
|
|
|
float H = size;
|
|
|
|
|
|
// Slant length used for the side normal Y component.
|
|
|
|
|
|
// Side normal direction: (cos(a), nyComponent, sin(a))
|
|
|
|
|
|
// where the slope is r/H per unit of horizontal travel.
|
|
|
|
|
|
// Normalize so the normal has unit length.
|
|
|
|
|
|
float sideXZScale = H / std::sqrt(H * H + r * r);
|
|
|
|
|
|
float sideY = r / std::sqrt(H * H + r * r);
|
|
|
|
|
|
// Side ring (apex repeated per segment so each tri has
|
|
|
|
|
|
// its own apex vertex with the correct normal).
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
// Base vertex (Y = 0).
|
|
|
|
|
|
addVertex(r * ca, 0.0f, r * sa,
|
|
|
|
|
|
sideXZScale * ca, sideY, sideXZScale * sa,
|
|
|
|
|
|
u, 1.0f);
|
|
|
|
|
|
// Apex vertex (Y = H), one per ring step so the
|
|
|
|
|
|
// top vertex carries the segment-specific normal.
|
|
|
|
|
|
addVertex(0.0f, H, 0.0f,
|
|
|
|
|
|
sideXZScale * ca, sideY, sideXZScale * sa,
|
|
|
|
|
|
u, 0.0f);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Side triangle indices.
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
uint32_t base = sg * 2;
|
|
|
|
|
|
// Two tris per quad band. The apex collapses to a
|
|
|
|
|
|
// point, so really one triangle per segment, but
|
|
|
|
|
|
// emitting both keeps the indexing uniform across
|
|
|
|
|
|
// the cylinder/cone code paths.
|
|
|
|
|
|
uint32_t a = base + 0; // base k
|
|
|
|
|
|
uint32_t b = base + 1; // apex k
|
|
|
|
|
|
uint32_t c = base + 2; // base k+1
|
|
|
|
|
|
uint32_t d = base + 3; // apex k+1
|
|
|
|
|
|
wom.indices.push_back(a);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
// Second triangle would be (b,c,d) but b == d at
|
|
|
|
|
|
// the apex visually — we still emit it so the
|
|
|
|
|
|
// per-vertex normals on b and d shade the joining
|
|
|
|
|
|
// seam smoothly.
|
|
|
|
|
|
wom.indices.push_back(b);
|
|
|
|
|
|
wom.indices.push_back(c);
|
|
|
|
|
|
wom.indices.push_back(d);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Bottom cap fan (flat -Y normal).
|
|
|
|
|
|
uint32_t botCenter = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
addVertex(0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.5f, 0.5f);
|
|
|
|
|
|
uint32_t botRingStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segments; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segments;
|
|
|
|
|
|
float ang = u * 2.0f * 3.14159265358979f;
|
|
|
|
|
|
float ca = std::cos(ang), sa = std::sin(ang);
|
|
|
|
|
|
addVertex(r * ca, 0.0f, r * sa, 0.0f, -1.0f, 0.0f,
|
|
|
|
|
|
0.5f + 0.5f * ca, 0.5f - 0.5f * sa);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segments; ++sg) {
|
|
|
|
|
|
wom.indices.push_back(botCenter);
|
|
|
|
|
|
wom.indices.push_back(botRingStart + sg + 1);
|
|
|
|
|
|
wom.indices.push_back(botRingStart + sg);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (s == "ramp") {
|
|
|
|
|
|
// Right-triangular prism: a wedge that climbs along
|
|
|
|
|
|
// +X. Footprint is size×size on XY (centered on origin
|
|
|
|
|
|
// in X, Y from 0 to size); rises from Z=0 at -X to
|
|
|
|
|
|
// Z=size at +X. Useful for ramps onto platforms,
|
|
|
|
|
|
// simple roof slopes, cliff faces.
|
|
|
|
|
|
//
|
|
|
|
|
|
// 6 verts × 5 faces = 18 verts so per-face normals
|
|
|
|
|
|
// stay flat: top slope, bottom, back-tall, +Y side,
|
|
|
|
|
|
// -Y side. Front-short (X = -size/2) is open since
|
|
|
|
|
|
// the ramp meets ground there at zero height.
|
|
|
|
|
|
// Actually we still emit 5 faces — the "front" edge
|
|
|
|
|
|
// is just where slope and ground meet, no separate
|
|
|
|
|
|
// face needed.
|
|
|
|
|
|
float xMin = -h, xMax = h;
|
|
|
|
|
|
float yMin = 0, yMax = size;
|
|
|
|
|
|
float zMin = 0, zMax = size;
|
|
|
|
|
|
// Faces: top slope (normal = normalize(-1,0,1) since
|
|
|
|
|
|
// the slope rises with +X going up, normal points
|
|
|
|
|
|
// up-and-back).
|
|
|
|
|
|
float slopeLen = std::sqrt(size * size + size * size);
|
|
|
|
|
|
float nSlopeX = -size / slopeLen;
|
|
|
|
|
|
float nSlopeZ = size / slopeLen;
|
|
|
|
|
|
struct Face { float nx, ny, nz; float verts[4][3]; };
|
|
|
|
|
|
Face faces[5] = {
|
|
|
|
|
|
// Top sloped quad: from (xMin, yMin, zMin) up to
|
|
|
|
|
|
// (xMax, yMin/yMax, zMax)
|
|
|
|
|
|
{ nSlopeX, 0, nSlopeZ,
|
|
|
|
|
|
{{xMin, yMin, zMin},{xMin, yMax, zMin},
|
|
|
|
|
|
{xMax, yMax, zMax},{xMax, yMin, zMax}}},
|
|
|
|
|
|
// Bottom (-Z normal)
|
|
|
|
|
|
{ 0, 0, -1,
|
|
|
|
|
|
{{xMin, yMin, zMin},{xMax, yMin, zMin},
|
|
|
|
|
|
{xMax, yMax, zMin},{xMin, yMax, zMin}}},
|
|
|
|
|
|
// Back-tall vertical wall (+X)
|
|
|
|
|
|
{ 1, 0, 0,
|
|
|
|
|
|
{{xMax, yMin, zMin},{xMax, yMin, zMax},
|
|
|
|
|
|
{xMax, yMax, zMax},{xMax, yMax, zMin}}},
|
|
|
|
|
|
// -Y side triangle (degenerate quad — last 2 verts
|
|
|
|
|
|
// collapse to a point — but indexing uniformly is
|
|
|
|
|
|
// simpler than a special tri path)
|
|
|
|
|
|
{ 0, -1, 0,
|
|
|
|
|
|
{{xMin, yMin, zMin},{xMax, yMin, zMin},
|
|
|
|
|
|
{xMax, yMin, zMax},{xMax, yMin, zMax}}},
|
|
|
|
|
|
// +Y side triangle (same shape mirrored)
|
|
|
|
|
|
{ 0, 1, 0,
|
|
|
|
|
|
{{xMin, yMax, zMin},{xMax, yMax, zMax},
|
|
|
|
|
|
{xMax, yMax, zMin},{xMax, yMax, zMin}}},
|
|
|
|
|
|
};
|
|
|
|
|
|
float uvs[4][2] = {{0,0},{1,0},{1,1},{0,1}};
|
|
|
|
|
|
for (auto& f : faces) {
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int k = 0; k < 4; ++k) {
|
|
|
|
|
|
addVertex(f.verts[k][0], f.verts[k][1], f.verts[k][2],
|
|
|
|
|
|
f.nx, f.ny, f.nz, uvs[k][0], uvs[k][1]);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 1);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 0);
|
|
|
|
|
|
wom.indices.push_back(base + 2);
|
|
|
|
|
|
wom.indices.push_back(base + 3);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh: shape must be cube, plane, sphere, cylinder, torus, cone, or ramp (got '%s')\n",
|
|
|
|
|
|
shape.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Compute bounds from the vertex positions we just emitted.
|
|
|
|
|
|
wom.boundMin = glm::vec3(1e30f);
|
|
|
|
|
|
wom.boundMax = glm::vec3(-1e30f);
|
|
|
|
|
|
for (const auto& v : wom.vertices) {
|
|
|
|
|
|
wom.boundMin = glm::min(wom.boundMin, v.position);
|
|
|
|
|
|
wom.boundMax = glm::max(wom.boundMax, v.position);
|
|
|
|
|
|
}
|
|
|
|
|
|
wom.boundRadius = glm::length(wom.boundMax - wom.boundMin) * 0.5f;
|
|
|
|
|
|
// Single material batch covering everything — keeps the
|
|
|
|
|
|
// model immediately renderable.
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch b;
|
|
|
|
|
|
b.indexStart = 0;
|
|
|
|
|
|
b.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
b.textureIndex = 0;
|
|
|
|
|
|
b.blendMode = 0;
|
|
|
|
|
|
b.flags = 0;
|
|
|
|
|
|
wom.batches.push_back(b);
|
|
|
|
|
|
// Empty texture path slot so batch.textureIndex=0 is a
|
|
|
|
|
|
// valid index into texturePaths. The user can later set a
|
|
|
|
|
|
// real path or run --gen-texture next to it.
|
|
|
|
|
|
wom.texturePaths.push_back("");
|
|
|
|
|
|
std::filesystem::path womPath(womBase);
|
|
|
|
|
|
std::filesystem::create_directories(womPath.parent_path());
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" shape : %s\n", s.c_str());
|
|
|
|
|
|
std::printf(" size : %.3f\n", size);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" indices : %zu (%zu tri%s)\n",
|
|
|
|
|
|
wom.indices.size(), wom.indices.size() / 3,
|
|
|
|
|
|
wom.indices.size() / 3 == 1 ? "" : "s");
|
|
|
|
|
|
std::printf(" bounds : (%.3f, %.3f, %.3f) - (%.3f, %.3f, %.3f)\n",
|
|
|
|
|
|
wom.boundMin.x, wom.boundMin.y, wom.boundMin.z,
|
|
|
|
|
|
wom.boundMax.x, wom.boundMax.y, wom.boundMax.z);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int handleTextured(int& i, int argc, char** argv) {
|
|
|
|
|
|
// One-shot composer: --gen-mesh + --gen-texture wired
|
|
|
|
|
|
// together so the resulting WOM's texturePaths[0] points
|
|
|
|
|
|
// at the freshly-written PNG sidecar. Output is a model
|
|
|
|
|
|
// that renders with the synthesized texture out of the
|
|
|
|
|
|
// box — useful for prototyping textured props without
|
|
|
|
|
|
// chaining three commands by hand.
|
|
|
|
|
|
//
|
|
|
|
|
|
// The texture is written next to the mesh as
|
|
|
|
|
|
// <wom-base>.png
|
|
|
|
|
|
// and the WOM's texturePaths[0] is set to that filename
|
|
|
|
|
|
// (just the leaf — runtime resolves it relative to the
|
|
|
|
|
|
// model's own directory).
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
std::string shape = argv[++i];
|
|
|
|
|
|
std::string colorSpec = argv[++i];
|
|
|
|
|
|
std::string sizeArg;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') sizeArg = argv[++i];
|
|
|
|
|
|
// Strip .wom if user passed full filename.
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
std::string self = argv[0];
|
|
|
|
|
|
// 1) Mesh.
|
|
|
|
|
|
std::string meshCmd = "\"" + self + "\" --gen-mesh \"" + womBase +
|
|
|
|
|
|
"\" " + shape;
|
|
|
|
|
|
if (!sizeArg.empty()) meshCmd += " " + sizeArg;
|
|
|
|
|
|
meshCmd += " >/dev/null 2>&1";
|
|
|
|
|
|
int rc = std::system(meshCmd.c_str());
|
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-textured: gen-mesh step failed (rc=%d)\n", rc);
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 2) Texture as a PNG sidecar at the mesh's base path.
|
|
|
|
|
|
std::string pngPath = womBase + ".png";
|
|
|
|
|
|
std::string texCmd = "\"" + self + "\" --gen-texture \"" + pngPath +
|
|
|
|
|
|
"\" \"" + colorSpec + "\" 256 256";
|
|
|
|
|
|
texCmd += " >/dev/null 2>&1";
|
|
|
|
|
|
rc = std::system(texCmd.c_str());
|
|
|
|
|
|
if (rc != 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-textured: gen-texture step failed (rc=%d)\n", rc);
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 3) Load the WOM, set texturePaths[0] to the PNG leaf,
|
|
|
|
|
|
// and re-save so the binding is permanent.
|
|
|
|
|
|
auto wom = wowee::pipeline::WoweeModelLoader::load(womBase);
|
|
|
|
|
|
if (!wom.isValid()) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-textured: cannot load %s.wom after gen-mesh\n",
|
|
|
|
|
|
womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::string pngLeaf = std::filesystem::path(pngPath).filename().string();
|
|
|
|
|
|
if (wom.texturePaths.empty()) {
|
|
|
|
|
|
wom.texturePaths.push_back(pngLeaf);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
wom.texturePaths[0] = pngLeaf;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-textured: failed to re-save %s.wom\n",
|
|
|
|
|
|
womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom + %s\n", womBase.c_str(), pngPath.c_str());
|
|
|
|
|
|
std::printf(" shape : %s\n", shape.c_str());
|
|
|
|
|
|
std::printf(" color : %s\n", colorSpec.c_str());
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" texture : %s (wired into batch 0)\n", pngLeaf.c_str());
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 23:48:03 -07:00
|
|
|
|
int handleMushroom(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Mushroom: cylindrical stalk + UV-sphere top half (cap).
|
|
|
|
|
|
// Cap radius is independent so users get the classic
|
|
|
|
|
|
// narrow-stalk-wide-cap silhouette of a forest mushroom.
|
|
|
|
|
|
// The 29th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float stalkR = 0.1f;
|
|
|
|
|
|
float stalkH = 0.6f;
|
|
|
|
|
|
float capR = 0.4f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stalkR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stalkH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { capR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (stalkR <= 0 || stalkH <= 0 || capR <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-mushroom: all dims must be positive\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Stalk: 12-segment cylinder from y=0 to y=stalkH.
|
|
|
|
|
|
const int segs = 12;
|
|
|
|
|
|
uint32_t bot = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(stalkR * std::cos(ang), 0, stalkR * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 0});
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t top = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(stalkR * std::cos(ang), stalkH,
|
|
|
|
|
|
stalkR * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {
|
|
|
|
|
|
bot + sg, top + sg, bot + sg + 1,
|
|
|
|
|
|
bot + sg + 1, top + sg, top + sg + 1
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Bottom cap (faces -Y) so the stalk is closed
|
|
|
|
|
|
uint32_t bc = addV({0, 0, 0}, {0, -1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{bc, bot + sg + 1, bot + sg});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Cap: top half of UV sphere centered at (0, stalkH, 0).
|
|
|
|
|
|
// Latitude 0..pi/2 (top hemisphere only). 16 longitude × 8
|
|
|
|
|
|
// latitude segments.
|
|
|
|
|
|
const int capLon = 16;
|
|
|
|
|
|
const int capLat = 8;
|
|
|
|
|
|
uint32_t capStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int la = 0; la <= capLat; ++la) {
|
|
|
|
|
|
float v = static_cast<float>(la) / capLat;
|
|
|
|
|
|
float phi = (1.0f - v) * pi * 0.5f; // pi/2 down to 0
|
|
|
|
|
|
float sphi = std::sin(phi), cphi = std::cos(phi);
|
|
|
|
|
|
for (int lo = 0; lo <= capLon; ++lo) {
|
|
|
|
|
|
float u = static_cast<float>(lo) / capLon;
|
|
|
|
|
|
float theta = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 dir(cphi * std::cos(theta),
|
|
|
|
|
|
sphi,
|
|
|
|
|
|
cphi * std::sin(theta));
|
|
|
|
|
|
glm::vec3 p(dir.x * capR, stalkH + dir.y * capR,
|
|
|
|
|
|
dir.z * capR);
|
|
|
|
|
|
addV(p, dir, {u, v});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
int rowSize = capLon + 1;
|
|
|
|
|
|
for (int la = 0; la < capLat; ++la) {
|
|
|
|
|
|
for (int lo = 0; lo < capLon; ++lo) {
|
|
|
|
|
|
uint32_t i00 = capStart + la * rowSize + lo;
|
|
|
|
|
|
uint32_t i01 = capStart + la * rowSize + lo + 1;
|
|
|
|
|
|
uint32_t i10 = capStart + (la + 1) * rowSize + lo;
|
|
|
|
|
|
uint32_t i11 = capStart + (la + 1) * rowSize + lo + 1;
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{i00, i10, i01, i01, i10, i11});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Underside of cap (the "gills" disc, faces -Y) so the
|
|
|
|
|
|
// mushroom is watertight viewed from below.
|
|
|
|
|
|
uint32_t capBot = addV({0, stalkH, 0}, {0, -1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < capLon; ++sg) {
|
|
|
|
|
|
uint32_t edge0 = capStart + capLat * rowSize + sg;
|
|
|
|
|
|
uint32_t edge1 = capStart + capLat * rowSize + sg + 1;
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{capBot, edge1, edge0});
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = stalkH + capR;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-capR, 0, -capR);
|
|
|
|
|
|
wom.boundMax = glm::vec3( capR, maxY, capR);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-mushroom: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" stalk : R=%.3f H=%.3f\n", stalkR, stalkH);
|
|
|
|
|
|
std::printf(" cap : R=%.3f\n", capR);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", maxY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 00:52:13 -07:00
|
|
|
|
int handleCart(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Wooden cart: rectangular bed box + 2 cylindrical wheels
|
|
|
|
|
|
// mounted axis-along-Z on the sides at the bottom of the
|
|
|
|
|
|
// bed. Wheels are full cylinders (16-segment) so the round
|
|
|
|
|
|
// silhouette reads from any angle. The 30th procedural mesh
|
|
|
|
|
|
// primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float bedLen = 1.6f; // along X (cart length)
|
|
|
|
|
|
float bedWidth = 0.8f; // along Z
|
|
|
|
|
|
float bedH = 0.5f; // bed height (Y)
|
|
|
|
|
|
float wheelR = 0.35f; // wheel radius
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { bedLen = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { bedWidth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { bedH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { wheelR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (bedLen <= 0 || bedWidth <= 0 || bedH <= 0 || wheelR <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-cart: all dims must be positive\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Bed sits at y = wheelR (so wheels touch ground at y=0)
|
|
|
|
|
|
// up to y = wheelR + bedH.
|
|
|
|
|
|
float bedY = wheelR + bedH * 0.5f;
|
|
|
|
|
|
addBox(0, bedY, 0, bedLen * 0.5f, bedH * 0.5f, bedWidth * 0.5f);
|
|
|
|
|
|
// Wheels: cylinder with axis along Z, mounted on each side
|
|
|
|
|
|
// of the bed. Each wheel has 16 angular segments + 2 caps.
|
|
|
|
|
|
const int wheelSegs = 16;
|
|
|
|
|
|
float wheelThick = bedWidth * 0.08f; // wheel thickness (along Z)
|
|
|
|
|
|
float wheelOffsetZ = bedWidth * 0.5f + wheelThick * 0.5f;
|
|
|
|
|
|
auto addWheel = [&](float cz) {
|
|
|
|
|
|
// Front face (z = cz + wheelThick/2)
|
|
|
|
|
|
float zFront = cz + wheelThick * 0.5f;
|
|
|
|
|
|
float zBack = cz - wheelThick * 0.5f;
|
|
|
|
|
|
uint32_t frontStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= wheelSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / wheelSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(wheelR * std::cos(ang), wheelR + wheelR * std::sin(ang), zFront);
|
|
|
|
|
|
addV(p, {0, 0, 1}, {0.5f + 0.5f * std::cos(ang),
|
|
|
|
|
|
0.5f + 0.5f * std::sin(ang)});
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t backStart = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= wheelSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / wheelSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(wheelR * std::cos(ang), wheelR + wheelR * std::sin(ang), zBack);
|
|
|
|
|
|
addV(p, {0, 0, -1}, {0.5f + 0.5f * std::cos(ang),
|
|
|
|
|
|
0.5f + 0.5f * std::sin(ang)});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Front cap fan
|
|
|
|
|
|
uint32_t fc = addV({0, wheelR, zFront}, {0, 0, 1}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < wheelSegs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{fc, frontStart + sg, frontStart + sg + 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Back cap fan (reversed winding)
|
|
|
|
|
|
uint32_t bc = addV({0, wheelR, zBack}, {0, 0, -1}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < wheelSegs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{bc, backStart + sg + 1, backStart + sg});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Side ring: connect each pair of front/back rim verts
|
|
|
|
|
|
// with a quad. Side normals point outward radially.
|
|
|
|
|
|
for (int sg = 0; sg < wheelSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / wheelSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
float u2 = static_cast<float>(sg + 1) / wheelSegs;
|
|
|
|
|
|
float ang2 = u2 * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 n0(std::cos(ang), std::sin(ang), 0);
|
|
|
|
|
|
glm::vec3 n1(std::cos(ang2), std::sin(ang2), 0);
|
|
|
|
|
|
uint32_t a = addV({wheelR * std::cos(ang), wheelR + wheelR * std::sin(ang), zFront}, n0, {u, 0});
|
|
|
|
|
|
uint32_t b = addV({wheelR * std::cos(ang), wheelR + wheelR * std::sin(ang), zBack}, n0, {u, 1});
|
|
|
|
|
|
uint32_t c = addV({wheelR * std::cos(ang2), wheelR + wheelR * std::sin(ang2), zBack}, n1, {u2, 1});
|
|
|
|
|
|
uint32_t d = addV({wheelR * std::cos(ang2), wheelR + wheelR * std::sin(ang2), zFront}, n1, {u2, 0});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{a, b, c, a, c, d});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
addWheel( wheelOffsetZ);
|
|
|
|
|
|
addWheel(-wheelOffsetZ);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = wheelR + bedH;
|
|
|
|
|
|
float maxZ = wheelOffsetZ + wheelThick * 0.5f;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-bedLen * 0.5f, 0, -maxZ);
|
|
|
|
|
|
wom.boundMax = glm::vec3( bedLen * 0.5f, std::max(maxY, 2 * wheelR), maxZ);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-cart: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" bed : %.3f × %.3f × %.3f\n",
|
|
|
|
|
|
bedLen, bedWidth, bedH);
|
|
|
|
|
|
std::printf(" wheels : 2 × R=%.3f thickness=%.3f\n",
|
|
|
|
|
|
wheelR, wheelThick);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 02:10:23 -07:00
|
|
|
|
int handleBanner(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Banner: vertical pole + rectangular flag hanging off it.
|
|
|
|
|
|
// Pole is a 12-segment cylinder along Y. Flag is a flat
|
|
|
|
|
|
// rectangle attached at the top of the pole, draped along
|
|
|
|
|
|
// -Z. Flag has both front (+X) and back (-X) faces so it
|
|
|
|
|
|
// reads from any viewing angle. The 31st mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float poleH = 3.0f;
|
|
|
|
|
|
float poleR = 0.05f;
|
|
|
|
|
|
float flagW = 0.8f; // along -Z (drape direction)
|
|
|
|
|
|
float flagH = 1.2f; // along Y (down from top)
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { poleH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { poleR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { flagW = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { flagH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (poleH <= 0 || poleR <= 0 || flagW <= 0 || flagH <= 0 ||
|
|
|
|
|
|
flagH > poleH) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-banner: all dims > 0; flagH must be <= poleH\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
// Pole cylinder (12 segments)
|
|
|
|
|
|
const int poleSegs = 12;
|
|
|
|
|
|
uint32_t bot = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= poleSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / poleSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(poleR * std::cos(ang), 0, poleR * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 0});
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t top = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= poleSegs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / poleSegs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(poleR * std::cos(ang), poleH, poleR * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < poleSegs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {
|
|
|
|
|
|
bot + sg, top + sg, bot + sg + 1,
|
|
|
|
|
|
bot + sg + 1, top + sg, top + sg + 1
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Pole top + bottom caps
|
|
|
|
|
|
uint32_t bc = addV({0, 0, 0}, {0, -1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
uint32_t tc = addV({0, poleH, 0}, {0, 1, 0}, {0.5f, 0.5f});
|
|
|
|
|
|
for (int sg = 0; sg < poleSegs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{bc, bot + sg + 1, bot + sg});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{tc, top + sg, top + sg + 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Flag: rectangle from (poleR, poleH-flagH, 0) to
|
|
|
|
|
|
// (poleR, poleH, -flagW). Two faces (front +X, back -X)
|
|
|
|
|
|
// so it reads from both sides.
|
|
|
|
|
|
float fy0 = poleH - flagH;
|
|
|
|
|
|
float fy1 = poleH;
|
|
|
|
|
|
float fz0 = 0;
|
|
|
|
|
|
float fz1 = -flagW;
|
|
|
|
|
|
float fx = poleR;
|
|
|
|
|
|
glm::vec3 frontN(1, 0, 0);
|
|
|
|
|
|
glm::vec3 backN(-1, 0, 0);
|
|
|
|
|
|
// Front face (faces +X, looking at it from outside)
|
|
|
|
|
|
uint32_t fa = addV({fx, fy0, fz0}, frontN, {0, 0});
|
|
|
|
|
|
uint32_t fb = addV({fx, fy0, fz1}, frontN, {1, 0});
|
|
|
|
|
|
uint32_t fc_ = addV({fx, fy1, fz1}, frontN, {1, 1});
|
|
|
|
|
|
uint32_t fd = addV({fx, fy1, fz0}, frontN, {0, 1});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {fa, fb, fc_, fa, fc_, fd});
|
|
|
|
|
|
// Back face (faces -X)
|
|
|
|
|
|
uint32_t ba = addV({fx, fy0, fz0}, backN, {0, 0});
|
|
|
|
|
|
uint32_t bb = addV({fx, fy1, fz0}, backN, {0, 1});
|
|
|
|
|
|
uint32_t bc_v = addV({fx, fy1, fz1}, backN, {1, 1});
|
|
|
|
|
|
uint32_t bd = addV({fx, fy0, fz1}, backN, {1, 0});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {ba, bb, bc_v, ba, bc_v, bd});
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-poleR, 0, fz1);
|
|
|
|
|
|
wom.boundMax = glm::vec3(fx + poleR, poleH, poleR);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-banner: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" pole : R=%.3f H=%.3f\n", poleR, poleH);
|
|
|
|
|
|
std::printf(" flag : W=%.3f H=%.3f (drapes -Z)\n",
|
|
|
|
|
|
flagW, flagH);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 03:00:19 -07:00
|
|
|
|
int handleGrave(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Tombstone: low rectangular base + vertical tablet on top.
|
|
|
|
|
|
// Tablet sits centered on the base; base is wider so the
|
|
|
|
|
|
// grave reads with a stable foundation. The 32nd procedural
|
|
|
|
|
|
// mesh primitive — useful for graveyards, undead zones,
|
|
|
|
|
|
// memorial set dressing.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float tabletW = 0.6f; // along X
|
|
|
|
|
|
float tabletH = 1.0f; // along Y
|
|
|
|
|
|
float tabletT = 0.15f; // along Z (thickness)
|
|
|
|
|
|
float baseW = 0.8f; // base wider than tablet
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { tabletW = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { tabletH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { tabletT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseW = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (tabletW <= 0 || tabletH <= 0 || tabletT <= 0 || baseW <= 0 ||
|
|
|
|
|
|
baseW < tabletW) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-grave: all dims > 0; baseW must be >= tabletW\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Base: wider, lower. Sits at y=0 to baseH where baseH = 20% of tablet H.
|
|
|
|
|
|
float baseH = tabletH * 0.2f;
|
|
|
|
|
|
float baseDepth = tabletT * 1.5f; // deeper than tablet for stability
|
|
|
|
|
|
addBox(0, baseH * 0.5f, 0,
|
|
|
|
|
|
baseW * 0.5f, baseH * 0.5f, baseDepth * 0.5f);
|
|
|
|
|
|
// Tablet: sits on top of base, centered.
|
|
|
|
|
|
float tabletY = baseH + tabletH * 0.5f;
|
|
|
|
|
|
addBox(0, tabletY, 0,
|
|
|
|
|
|
tabletW * 0.5f, tabletH * 0.5f, tabletT * 0.5f);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = baseH + tabletH;
|
|
|
|
|
|
float maxXZ = std::max(baseW * 0.5f, tabletW * 0.5f);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-maxXZ, 0, -baseDepth * 0.5f);
|
|
|
|
|
|
wom.boundMax = glm::vec3( maxXZ, maxY, baseDepth * 0.5f);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-grave: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" base : %.3f × %.3f (h=%.3f)\n",
|
|
|
|
|
|
baseW, baseDepth, baseH);
|
|
|
|
|
|
std::printf(" tablet : %.3f × %.3f × %.3f\n",
|
|
|
|
|
|
tabletW, tabletH, tabletT);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", maxY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 03:43:11 -07:00
|
|
|
|
int handleBench(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Wooden bench: long thin seat plank (X×Z plane) supported
|
|
|
|
|
|
// by 2 leg slabs (vertical Y rectangles) at each end. Legs
|
|
|
|
|
|
// are 90% of the bench's depth and span the full seat
|
|
|
|
|
|
// height down to the floor. The 33rd procedural mesh
|
|
|
|
|
|
// primitive — useful for taverns, plazas, roadside rest
|
|
|
|
|
|
// stops.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float length = 1.5f; // along X (bench length)
|
|
|
|
|
|
float seatY = 0.5f; // seat top height
|
|
|
|
|
|
float seatT = 0.06f; // seat plank thickness (Y)
|
|
|
|
|
|
float seatW = 0.4f; // seat width (Z)
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { length = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { seatY = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { seatT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { seatW = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (length <= 0 || seatY <= 0 || seatT <= 0 || seatW <= 0 ||
|
|
|
|
|
|
seatT > seatY) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-bench: all dims > 0; seatT must be <= seatY\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Seat: top plank at y=seatY-seatT to y=seatY.
|
|
|
|
|
|
float seatCY = seatY - seatT * 0.5f;
|
|
|
|
|
|
addBox(0, seatCY, 0, length * 0.5f, seatT * 0.5f, seatW * 0.5f);
|
|
|
|
|
|
// Two leg slabs: thin Y slabs at the +X and -X ends, span
|
|
|
|
|
|
// 90% of the seat depth, 5% of bench length thick, full
|
|
|
|
|
|
// height from floor to bottom-of-seat.
|
|
|
|
|
|
float legHy = (seatY - seatT) * 0.5f;
|
|
|
|
|
|
float legCY = legHy;
|
|
|
|
|
|
float legHx = length * 0.025f; // ~2.5% of length on each side
|
|
|
|
|
|
float legHz = seatW * 0.45f;
|
|
|
|
|
|
float legX = length * 0.45f; // legs at 90% of length out
|
|
|
|
|
|
addBox( legX, legCY, 0, legHx, legHy, legHz);
|
|
|
|
|
|
addBox(-legX, legCY, 0, legHx, legHy, legHz);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-length * 0.5f, 0, -seatW * 0.5f);
|
|
|
|
|
|
wom.boundMax = glm::vec3( length * 0.5f, seatY, seatW * 0.5f);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-bench: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" length : %.3f\n", length);
|
|
|
|
|
|
std::printf(" seat Y : %.3f (thickness %.3f)\n", seatY, seatT);
|
|
|
|
|
|
std::printf(" seat W : %.3f\n", seatW);
|
|
|
|
|
|
std::printf(" legs : 2 (at ±%.3f along X)\n", legX);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 04:25:52 -07:00
|
|
|
|
int handleShrine(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Small open canopy: square base + 4 cylindrical pillars
|
|
|
|
|
|
// at the corners + a flat roof slab covering all 4. Useful
|
|
|
|
|
|
// for wayside shrines, gazebos, well covers, market stalls.
|
|
|
|
|
|
// The 34th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float size = 1.5f; // base width = depth
|
|
|
|
|
|
float pillarH = 2.0f; // pillar height
|
|
|
|
|
|
float pillarR = 0.10f; // pillar radius
|
|
|
|
|
|
float roofT = 0.15f; // roof thickness
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { size = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { pillarH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { pillarR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { roofT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (size <= 0 || pillarH <= 0 || pillarR <= 0 || roofT <= 0 ||
|
|
|
|
|
|
pillarR * 2 >= size) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-shrine: dims > 0; pillarR×2 must fit inside size\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Base: low square slab, 10% of pillar height tall.
|
|
|
|
|
|
float baseH = pillarH * 0.1f;
|
|
|
|
|
|
float halfSize = size * 0.5f;
|
|
|
|
|
|
addBox(0, baseH * 0.5f, 0, halfSize, baseH * 0.5f, halfSize);
|
|
|
|
|
|
// 4 pillars at corners (inset by pillarR so they sit fully
|
|
|
|
|
|
// on the base). Each is a 12-segment cylinder.
|
|
|
|
|
|
const int segs = 12;
|
|
|
|
|
|
float pillarOffset = halfSize - pillarR;
|
|
|
|
|
|
auto addPillar = [&](float cx, float cz) {
|
|
|
|
|
|
float y0 = baseH;
|
|
|
|
|
|
float y1 = baseH + pillarH;
|
|
|
|
|
|
uint32_t bot = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(cx + pillarR * std::cos(ang), y0,
|
|
|
|
|
|
cz + pillarR * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 0});
|
|
|
|
|
|
}
|
|
|
|
|
|
uint32_t top = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int sg = 0; sg <= segs; ++sg) {
|
|
|
|
|
|
float u = static_cast<float>(sg) / segs;
|
|
|
|
|
|
float ang = u * 2.0f * pi;
|
|
|
|
|
|
glm::vec3 p(cx + pillarR * std::cos(ang), y1,
|
|
|
|
|
|
cz + pillarR * std::sin(ang));
|
|
|
|
|
|
glm::vec3 n(std::cos(ang), 0, std::sin(ang));
|
|
|
|
|
|
addV(p, n, {u, 1});
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int sg = 0; sg < segs; ++sg) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {
|
|
|
|
|
|
bot + sg, top + sg, bot + sg + 1,
|
|
|
|
|
|
bot + sg + 1, top + sg, top + sg + 1
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
addPillar( pillarOffset, pillarOffset);
|
|
|
|
|
|
addPillar(-pillarOffset, pillarOffset);
|
|
|
|
|
|
addPillar( pillarOffset, -pillarOffset);
|
|
|
|
|
|
addPillar(-pillarOffset, -pillarOffset);
|
|
|
|
|
|
// Roof: flat slab on top of pillars, slightly larger than
|
|
|
|
|
|
// the base so it overhangs the pillars.
|
|
|
|
|
|
float roofY = baseH + pillarH;
|
|
|
|
|
|
float roofHalfSize = halfSize * 1.05f;
|
|
|
|
|
|
addBox(0, roofY + roofT * 0.5f, 0,
|
|
|
|
|
|
roofHalfSize, roofT * 0.5f, roofHalfSize);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = roofY + roofT;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-roofHalfSize, 0, -roofHalfSize);
|
|
|
|
|
|
wom.boundMax = glm::vec3( roofHalfSize, maxY, roofHalfSize);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-shrine: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" size : %.3f × %.3f\n", size, size);
|
|
|
|
|
|
std::printf(" pillars : 4 × R=%.3f H=%.3f\n", pillarR, pillarH);
|
|
|
|
|
|
std::printf(" roof : %.3f thick (%.3f overhang)\n",
|
|
|
|
|
|
roofT, halfSize * 0.05f);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", maxY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(editor): add --gen-mesh-totem stacked carved totem primitive
Stack of N square blocks with alternating widths: even-indexed
blocks (0, 2, 4...) get full base width, odd blocks (1, 3, 5...)
get 70% — gives the carved-segment look characteristic of
totem poles.
Defaults: baseW=0.5, 5 segments, segH=0.5. Useful for tribal
zones, druid groves, fairgrounds, primitive village markers.
Brings the procedural mesh primitive set to 35.
2026-05-09 04:57:33 -07:00
|
|
|
|
int handleTotem(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Tribal totem: stack of N square blocks alternating wide/
|
|
|
|
|
|
// narrow widths so each carved face reads as distinct.
|
|
|
|
|
|
// Even-indexed blocks are full width, odd are 70% — gives
|
|
|
|
|
|
// the carved-segment look characteristic of totem poles.
|
|
|
|
|
|
// The 35th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float baseW = 0.5f; // base block half-width × 2
|
|
|
|
|
|
int segments = 5; // number of stacked blocks
|
|
|
|
|
|
float segH = 0.5f; // height of each block
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseW = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { segments = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { segH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (baseW <= 0 || segH <= 0 || segments < 1 || segments > 32) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-totem: dims > 0, segments 1..32\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Stack blocks bottom-up. Bottom block always full width.
|
|
|
|
|
|
// Even blocks (0, 2, 4...) get full width, odd blocks 70%.
|
|
|
|
|
|
for (int s = 0; s < segments; ++s) {
|
|
|
|
|
|
float cy = (s + 0.5f) * segH;
|
|
|
|
|
|
float halfW = (s & 1) ? (baseW * 0.5f * 0.70f) : (baseW * 0.5f);
|
|
|
|
|
|
addBox(0, cy, 0, halfW, segH * 0.5f, halfW);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = segments * segH;
|
|
|
|
|
|
float maxXZ = baseW * 0.5f;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-maxXZ, 0, -maxXZ);
|
|
|
|
|
|
wom.boundMax = glm::vec3( maxXZ, maxY, maxXZ);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-totem: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" base width : %.3f\n", baseW);
|
|
|
|
|
|
std::printf(" segments : %d (each %.3f tall)\n", segments, segH);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", maxY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 05:25:39 -07:00
|
|
|
|
int handleCage(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Square cage: top + bottom thin frame slabs + 4 corner
|
|
|
|
|
|
// posts + N evenly spaced bars on each of the 4 sides.
|
|
|
|
|
|
// Bars are thin square cross-section so they read as
|
|
|
|
|
|
// metal rods. Useful for prison cells, animal pens,
|
|
|
|
|
|
// dungeon set dressing.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float width = 1.5f; // along X = Z (square footprint)
|
|
|
|
|
|
float height = 2.0f;
|
|
|
|
|
|
int barsPerSide = 5;
|
|
|
|
|
|
float barRadius = 0.04f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { barsPerSide = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { barRadius = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (width <= 0 || height <= 0 || barRadius <= 0 ||
|
|
|
|
|
|
barsPerSide < 0 || barsPerSide > 64) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-cage: dims > 0, barsPerSide 0..64\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfW = width * 0.5f;
|
|
|
|
|
|
float frameT = barRadius * 1.5f; // top/bottom slab thickness
|
|
|
|
|
|
// Top + bottom frame slabs
|
|
|
|
|
|
addBox(0, frameT * 0.5f, 0, halfW, frameT * 0.5f, halfW);
|
|
|
|
|
|
addBox(0, height - frameT * 0.5f, 0, halfW, frameT * 0.5f, halfW);
|
|
|
|
|
|
// 4 corner posts (thicker than bars)
|
|
|
|
|
|
float postR = barRadius * 1.5f;
|
|
|
|
|
|
float postCY = height * 0.5f;
|
|
|
|
|
|
float postHy = height * 0.5f;
|
|
|
|
|
|
float corner = halfW - postR;
|
|
|
|
|
|
addBox( corner, postCY, corner, postR, postHy, postR);
|
|
|
|
|
|
addBox(-corner, postCY, corner, postR, postHy, postR);
|
|
|
|
|
|
addBox( corner, postCY, -corner, postR, postHy, postR);
|
|
|
|
|
|
addBox(-corner, postCY, -corner, postR, postHy, postR);
|
|
|
|
|
|
// Bars: N bars per side, evenly distributed between corners.
|
|
|
|
|
|
// Side spans from -corner to +corner; bars at (k+1)/(N+1)
|
|
|
|
|
|
// along the span so they're inset (no overlap with corners).
|
|
|
|
|
|
float barHy = (height - 2 * frameT) * 0.5f;
|
|
|
|
|
|
float barCYadj = frameT + barHy;
|
|
|
|
|
|
int barTotal = 0;
|
|
|
|
|
|
for (int k = 0; k < barsPerSide; ++k) {
|
|
|
|
|
|
float t = (k + 1.0f) / (barsPerSide + 1.0f);
|
|
|
|
|
|
float pos = -corner + t * 2.0f * corner; // from -corner to +corner
|
|
|
|
|
|
// +Z and -Z sides (bars span X)
|
|
|
|
|
|
addBox(pos, barCYadj, halfW - barRadius,
|
|
|
|
|
|
barRadius, barHy, barRadius);
|
|
|
|
|
|
addBox(pos, barCYadj, -halfW + barRadius,
|
|
|
|
|
|
barRadius, barHy, barRadius);
|
|
|
|
|
|
// +X and -X sides (bars span Z)
|
|
|
|
|
|
addBox( halfW - barRadius, barCYadj, pos,
|
|
|
|
|
|
barRadius, barHy, barRadius);
|
|
|
|
|
|
addBox(-halfW + barRadius, barCYadj, pos,
|
|
|
|
|
|
barRadius, barHy, barRadius);
|
|
|
|
|
|
barTotal += 4;
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfW, 0, -halfW);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfW, height, halfW);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-cage: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" width × height : %.3f × %.3f\n", width, height);
|
|
|
|
|
|
std::printf(" bars per side : %d (%d total)\n",
|
|
|
|
|
|
barsPerSide, barTotal);
|
|
|
|
|
|
std::printf(" bar radius : %.3f\n", barRadius);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 05:50:23 -07:00
|
|
|
|
int handleThrone(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Throne: pedestal slab + seat block + tall backrest +
|
|
|
|
|
|
// 2 armrests on either side. Reads as a regal seat from
|
|
|
|
|
|
// any angle. The 37th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float seatW = 0.8f; // along X
|
|
|
|
|
|
float seatH = 0.5f; // top of seat above pedestal
|
|
|
|
|
|
float backH = 1.5f; // backrest extends this above seat
|
|
|
|
|
|
float pedSize = 1.2f; // pedestal width = depth
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { seatW = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { seatH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { backH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { pedSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (seatW <= 0 || seatH <= 0 || backH <= 0 || pedSize <= 0 ||
|
|
|
|
|
|
pedSize < seatW) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-throne: dims > 0; pedSize must be >= seatW\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Pedestal: low square slab at the floor
|
|
|
|
|
|
float pedH = seatH * 0.4f;
|
|
|
|
|
|
float halfPed = pedSize * 0.5f;
|
|
|
|
|
|
addBox(0, pedH * 0.5f, 0, halfPed, pedH * 0.5f, halfPed);
|
|
|
|
|
|
// Seat: thick square cushion sitting on pedestal
|
|
|
|
|
|
float seatT = seatH * 0.3f; // seat thickness (along Y)
|
|
|
|
|
|
float seatCY = pedH + seatT * 0.5f;
|
|
|
|
|
|
float halfSeat = seatW * 0.5f;
|
|
|
|
|
|
addBox(0, seatCY, 0, halfSeat, seatT * 0.5f, halfSeat);
|
|
|
|
|
|
// Backrest: tall vertical slab at -Z edge of seat, slim in Z
|
|
|
|
|
|
float backT = seatT * 0.6f;
|
|
|
|
|
|
float backCY = pedH + seatT + backH * 0.5f;
|
|
|
|
|
|
addBox(0, backCY, -halfSeat + backT * 0.5f,
|
|
|
|
|
|
halfSeat, backH * 0.5f, backT * 0.5f);
|
|
|
|
|
|
// Armrests: 2 small blocks on the sides
|
|
|
|
|
|
float armW = backT * 0.8f;
|
|
|
|
|
|
float armH = seatH * 0.4f;
|
|
|
|
|
|
float armCY = pedH + seatT + armH * 0.5f;
|
|
|
|
|
|
float armDepth = halfSeat * 0.7f;
|
|
|
|
|
|
addBox( halfSeat - armW * 0.5f, armCY, 0,
|
|
|
|
|
|
armW * 0.5f, armH * 0.5f, armDepth);
|
|
|
|
|
|
addBox(-halfSeat + armW * 0.5f, armCY, 0,
|
|
|
|
|
|
armW * 0.5f, armH * 0.5f, armDepth);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxY = pedH + seatT + backH;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfPed, 0, -halfPed);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfPed, maxY, halfPed);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-throne: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" pedestal : %.3f × %.3f (h=%.3f)\n",
|
|
|
|
|
|
pedSize, pedSize, pedH);
|
|
|
|
|
|
std::printf(" seat : %.3f × %.3f\n", seatW, seatT);
|
|
|
|
|
|
std::printf(" backrest : H=%.3f\n", backH);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", maxY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 06:18:42 -07:00
|
|
|
|
int handleCoffin(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Coffin: classic 6-sided "hexagonal" prism with the
|
|
|
|
|
|
// characteristic narrow-head / wide-shoulder / tapered-foot
|
|
|
|
|
|
// top-down profile that reads as a coffin from any angle.
|
|
|
|
|
|
// Six side faces + top lid + bottom panel — face-shared
|
|
|
|
|
|
// normals via separate vertex sets per face. The 38th
|
|
|
|
|
|
// procedural mesh primitive — useful for graveyard set
|
|
|
|
|
|
// dressing alongside --gen-mesh-grave.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float length = 2.0f; // along Z
|
|
|
|
|
|
float width = 0.8f; // shoulder width along X
|
|
|
|
|
|
float height = 0.6f; // along Y
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { length = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (length <= 0 || width <= 0 || height <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-coffin: length/width/height must be > 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
// Top-down hexagonal coffin profile (CCW from head looking
|
|
|
|
|
|
// down +Y). Head end is narrow, shoulder is widest, feet
|
|
|
|
|
|
// taper to a narrow toe — the canonical "casket" silhouette.
|
|
|
|
|
|
float hL = length * 0.5f;
|
|
|
|
|
|
float hW = width * 0.5f;
|
|
|
|
|
|
glm::vec2 ring[6] = {
|
|
|
|
|
|
{ 0.0f, hL }, // p0 head tip
|
|
|
|
|
|
{-hW, hL * 0.6f }, // p1 left shoulder (widest)
|
|
|
|
|
|
{-hW * 0.8f, -hL * 0.6f }, // p2 left hip
|
|
|
|
|
|
{ 0.0f, -hL }, // p3 foot tip
|
|
|
|
|
|
{ hW * 0.8f, -hL * 0.6f }, // p4 right hip
|
|
|
|
|
|
{ hW, hL * 0.6f }, // p5 right shoulder
|
|
|
|
|
|
};
|
|
|
|
|
|
auto addQuad = [&](glm::vec3 a, glm::vec3 b, glm::vec3 c, glm::vec3 d,
|
|
|
|
|
|
glm::vec3 n) {
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(a, 0, 0);
|
|
|
|
|
|
push(b, 1, 0);
|
|
|
|
|
|
push(c, 1, 1);
|
|
|
|
|
|
push(d, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
};
|
|
|
|
|
|
// Six side faces — each a quad from bottom-edge to top-edge
|
|
|
|
|
|
// of one segment of the hexagon. Normal is the outward
|
|
|
|
|
|
// perpendicular to the side edge in the XZ plane.
|
|
|
|
|
|
for (int s = 0; s < 6; ++s) {
|
|
|
|
|
|
const glm::vec2& a = ring[s];
|
|
|
|
|
|
const glm::vec2& b = ring[(s + 1) % 6];
|
|
|
|
|
|
glm::vec3 bot0(a.x, 0.0f, a.y);
|
|
|
|
|
|
glm::vec3 bot1(b.x, 0.0f, b.y);
|
|
|
|
|
|
glm::vec3 top1(b.x, height, b.y);
|
|
|
|
|
|
glm::vec3 top0(a.x, height, a.y);
|
|
|
|
|
|
// Outward normal: 90° CW rotation of edge vector in XZ
|
|
|
|
|
|
// (since vertices wind CCW looking down, outward is +X
|
|
|
|
|
|
// when edge goes -Z, i.e. swap & negate one component).
|
|
|
|
|
|
glm::vec2 edge = b - a;
|
|
|
|
|
|
glm::vec3 n(edge.y, 0.0f, -edge.x);
|
|
|
|
|
|
n = glm::normalize(n);
|
|
|
|
|
|
addQuad(bot0, bot1, top1, top0, n);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Top lid: fan of 4 triangles from p0, all sharing +Y normal.
|
|
|
|
|
|
{
|
|
|
|
|
|
glm::vec3 normal(0.0f, 1.0f, 0.0f);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int v = 0; v < 6; ++v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = glm::vec3(ring[v].x, height, ring[v].y);
|
|
|
|
|
|
vtx.normal = normal;
|
|
|
|
|
|
// Cheap planar UV from top-down ring coords.
|
|
|
|
|
|
vtx.texCoord = { ring[v].x / width + 0.5f,
|
|
|
|
|
|
ring[v].y / length + 0.5f };
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int t = 1; t < 5; ++t) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + static_cast<uint32_t>(t),
|
|
|
|
|
|
base + static_cast<uint32_t>(t + 1)});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Bottom panel: same fan but reversed winding for -Y normal.
|
|
|
|
|
|
{
|
|
|
|
|
|
glm::vec3 normal(0.0f, -1.0f, 0.0f);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
for (int v = 0; v < 6; ++v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = glm::vec3(ring[v].x, 0.0f, ring[v].y);
|
|
|
|
|
|
vtx.normal = normal;
|
|
|
|
|
|
vtx.texCoord = { ring[v].x / width + 0.5f,
|
|
|
|
|
|
ring[v].y / length + 0.5f };
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
}
|
|
|
|
|
|
for (int t = 1; t < 5; ++t) {
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + static_cast<uint32_t>(t + 1),
|
|
|
|
|
|
base + static_cast<uint32_t>(t)});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-hW, 0.0f, -hL);
|
|
|
|
|
|
wom.boundMax = glm::vec3( hW, height, hL);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-coffin: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" length : %.3f\n", length);
|
|
|
|
|
|
std::printf(" width : %.3f (shoulder)\n", width);
|
|
|
|
|
|
std::printf(" height : %.3f\n", height);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 10:11:24 -07:00
|
|
|
|
int handleArchwayDouble(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Double archway: 5-box twin-opening passage — 3 vertical
|
|
|
|
|
|
// posts (left / shared center / right) plus 2 horizontal
|
|
|
|
|
|
// lintels spanning each opening. Pairs with the existing
|
|
|
|
|
|
// single --gen-mesh-archway for plaza approaches, double-
|
|
|
|
|
|
// door tomb fronts, formal garden entrances. The 58th
|
|
|
|
|
|
// procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float openingWidth = 1.40f; // each opening's width
|
|
|
|
|
|
float openingHeight = 2.40f; // post height under lintel
|
|
|
|
|
|
float postT = 0.18f;
|
|
|
|
|
|
float lintelT = 0.20f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { openingWidth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { openingHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { lintelT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (openingWidth <= 0 || openingHeight <= 0 ||
|
|
|
|
|
|
postT <= 0 || lintelT <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-archway-double: all dims must be > 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfPost = postT * 0.5f;
|
|
|
|
|
|
float halfLintel = lintelT * 0.5f;
|
|
|
|
|
|
// Post X positions: -X = left edge of left opening, 0 = shared
|
|
|
|
|
|
// center, +X = right edge of right opening. Posts straddle
|
|
|
|
|
|
// those positions so the inside opening stays openingWidth.
|
|
|
|
|
|
float leftPostX = -(openingWidth + postT * 0.5f);
|
|
|
|
|
|
float rightPostX = (openingWidth + postT * 0.5f);
|
|
|
|
|
|
float centerPostX = 0.0f;
|
|
|
|
|
|
float postCY = openingHeight * 0.5f;
|
|
|
|
|
|
addBox(leftPostX, postCY, 0, halfPost, openingHeight * 0.5f, halfPost);
|
|
|
|
|
|
addBox(centerPostX, postCY, 0, halfPost, openingHeight * 0.5f, halfPost);
|
|
|
|
|
|
addBox(rightPostX, postCY, 0, halfPost, openingHeight * 0.5f, halfPost);
|
|
|
|
|
|
// 2 lintels: each spans from the outer post inner-face to
|
|
|
|
|
|
// the center post inner-face. Lintel center sits at the
|
|
|
|
|
|
// midpoint of (leftPost, centerPost) for the left opening,
|
|
|
|
|
|
// and (centerPost, rightPost) for the right opening.
|
|
|
|
|
|
float lintelCY = openingHeight + halfLintel;
|
|
|
|
|
|
float halfLintelLen = openingWidth * 0.5f + halfPost;
|
|
|
|
|
|
float leftLintelX = (leftPostX + centerPostX) * 0.5f;
|
|
|
|
|
|
float rightLintelX = (centerPostX + rightPostX) * 0.5f;
|
|
|
|
|
|
addBox(leftLintelX, lintelCY, 0, halfLintelLen, halfLintel, halfPost);
|
|
|
|
|
|
addBox(rightLintelX, lintelCY, 0, halfLintelLen, halfLintel, halfPost);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = openingHeight + lintelT;
|
|
|
|
|
|
float halfTotalX = rightPostX + halfPost;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfTotalX, 0.0f, -halfPost);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfTotalX, totalH, halfPost);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-archway-double: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" total W : %.3f (2 openings × %.3f + 3 posts × %.3f)\n",
|
|
|
|
|
|
halfTotalX * 2, openingWidth, postT);
|
|
|
|
|
|
std::printf(" height : %.3f opening + %.3f lintel\n",
|
|
|
|
|
|
openingHeight, lintelT);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 09:56:38 -07:00
|
|
|
|
int handleBrazier(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Brazier: 7-box fire-pit on a pedestal — square base
|
|
|
|
|
|
// plate, narrow vertical stem, wider bowl on top of the
|
|
|
|
|
|
// stem, and 3 small flame boxes of varying heights rising
|
|
|
|
|
|
// from the bowl. Useful for dungeons, temples, watchtowers,
|
|
|
|
|
|
// throne rooms — anywhere a fantasy world needs visible
|
|
|
|
|
|
// light sources. The 57th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float bowlSize = 0.55f;
|
|
|
|
|
|
float stemHeight = 0.80f;
|
|
|
|
|
|
float stemT = 0.10f;
|
|
|
|
|
|
float baseSize = 0.35f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { bowlSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stemHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stemT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (bowlSize <= 0 || stemHeight <= 0 || stemT <= 0 || baseSize <= 0 ||
|
|
|
|
|
|
stemT >= baseSize || stemT >= bowlSize) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-brazier: dims > 0; stem must fit in base & bowl\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Base plate.
|
|
|
|
|
|
float baseHeight = baseSize * 0.20f;
|
|
|
|
|
|
float halfBase = baseSize * 0.5f;
|
|
|
|
|
|
addBox(0, baseHeight * 0.5f, 0,
|
|
|
|
|
|
halfBase, baseHeight * 0.5f, halfBase);
|
|
|
|
|
|
// Stem rising from base to where the bowl will sit.
|
|
|
|
|
|
float halfStemT = stemT * 0.5f;
|
|
|
|
|
|
float stemCY = baseHeight + stemHeight * 0.5f;
|
|
|
|
|
|
addBox(0, stemCY, 0,
|
|
|
|
|
|
halfStemT, stemHeight * 0.5f, halfStemT);
|
|
|
|
|
|
// Bowl: wide thin slab on top of the stem.
|
|
|
|
|
|
float bowlH = bowlSize * 0.25f;
|
|
|
|
|
|
float halfBowl = bowlSize * 0.5f;
|
|
|
|
|
|
float bowlCY = baseHeight + stemHeight + bowlH * 0.5f;
|
|
|
|
|
|
addBox(0, bowlCY, 0, halfBowl, bowlH * 0.5f, halfBowl);
|
|
|
|
|
|
// 3 flame boxes rising from the bowl, varying heights so
|
|
|
|
|
|
// the silhouette reads as a fire rather than a uniform
|
|
|
|
|
|
// block. Centered triangle layout.
|
|
|
|
|
|
float flameTop = baseHeight + stemHeight + bowlH;
|
|
|
|
|
|
float flameW = bowlSize * 0.18f;
|
|
|
|
|
|
float halfFW = flameW * 0.5f;
|
|
|
|
|
|
struct Flame { float dx, dz, h; };
|
|
|
|
|
|
Flame flames[3] = {
|
|
|
|
|
|
{ 0.0f, 0.0f, bowlSize * 0.50f }, // tallest center
|
|
|
|
|
|
{ bowlSize * 0.18f, bowlSize * 0.10f, bowlSize * 0.30f },
|
|
|
|
|
|
{-bowlSize * 0.18f, bowlSize * 0.10f, bowlSize * 0.35f },
|
|
|
|
|
|
};
|
|
|
|
|
|
for (const auto& f : flames) {
|
|
|
|
|
|
addBox(f.dx, flameTop + f.h * 0.5f, f.dz,
|
|
|
|
|
|
halfFW, f.h * 0.5f, halfFW);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = flameTop + bowlSize * 0.50f;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfBowl, 0.0f, -halfBowl);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfBowl, totalH, halfBowl);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-brazier: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" base : %.3f square × %.3f thick\n",
|
|
|
|
|
|
baseSize, baseHeight);
|
|
|
|
|
|
std::printf(" stem : %.3f square × %.3f tall\n",
|
|
|
|
|
|
stemT, stemHeight);
|
|
|
|
|
|
std::printf(" bowl : %.3f wide × %.3f thick\n", bowlSize, bowlH);
|
|
|
|
|
|
std::printf(" flames : 3 (varied heights)\n");
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 09:46:59 -07:00
|
|
|
|
int handlePodium(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Podium: 4-box stepped pyramid speaker stand — large
|
|
|
|
|
|
// bottom step, medium middle step, small top platform,
|
|
|
|
|
|
// and a small lectern box on top of the platform. Useful
|
|
|
|
|
|
// for throne rooms, ceremonies, NPC speaker positions,
|
|
|
|
|
|
// monument bases. The 56th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float baseSize = 1.60f; // bottom step width = depth
|
|
|
|
|
|
float baseHeight = 0.20f;
|
|
|
|
|
|
int stepCount = 3; // total stepped tiers (incl. top)
|
|
|
|
|
|
float lecternSize = 0.30f; // lectern at the very top
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stepCount = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { lecternSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (baseSize <= 0 || baseHeight <= 0 || lecternSize <= 0 ||
|
|
|
|
|
|
stepCount < 2 || stepCount > 8) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-podium: dims > 0; stepCount 2..8\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Each step is shorter on each side by ~15% of base size,
|
|
|
|
|
|
// and the top platform's footprint is half the base. Step
|
|
|
|
|
|
// heights are equal for visual rhythm.
|
|
|
|
|
|
float topSize = baseSize * 0.50f;
|
|
|
|
|
|
float sizeStep = (baseSize - topSize) / (stepCount - 1);
|
|
|
|
|
|
float stepHeight = baseHeight;
|
|
|
|
|
|
for (int s = 0; s < stepCount; ++s) {
|
|
|
|
|
|
float halfSide = (baseSize - s * sizeStep) * 0.5f;
|
|
|
|
|
|
float stepCY = stepHeight * 0.5f + s * stepHeight;
|
|
|
|
|
|
addBox(0, stepCY, 0, halfSide, stepHeight * 0.5f, halfSide);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Lectern: small box on top of the top platform, at the
|
|
|
|
|
|
// back so a speaker has room in front. Faces +Z.
|
|
|
|
|
|
float halfL = lecternSize * 0.5f;
|
|
|
|
|
|
float lecternH = lecternSize * 1.2f;
|
|
|
|
|
|
float platformTopY = stepHeight * stepCount;
|
|
|
|
|
|
float lecternCY = platformTopY + lecternH * 0.5f;
|
|
|
|
|
|
float lecternZ = -topSize * 0.25f; // pushed back
|
|
|
|
|
|
addBox(0, lecternCY, lecternZ,
|
|
|
|
|
|
halfL, lecternH * 0.5f, halfL * 0.4f);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = lecternCY + lecternH * 0.5f;
|
|
|
|
|
|
float halfBase = baseSize * 0.5f;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfBase, 0.0f, -halfBase);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfBase, totalH, halfBase);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-podium: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" base : %.3f square × %.3f thick\n",
|
|
|
|
|
|
baseSize, baseHeight);
|
|
|
|
|
|
std::printf(" steps : %d (top %.3f square)\n", stepCount, topSize);
|
|
|
|
|
|
std::printf(" lectern : %.3f wide (at back)\n", lecternSize);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 09:37:13 -07:00
|
|
|
|
int handleSundial(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Sundial: 8-box garden timekeeper — square base plate at
|
|
|
|
|
|
// floor, central vertical gnomon slab spanning the diameter
|
|
|
|
|
|
// (long axis along Z, simulates the angled blade that casts
|
|
|
|
|
|
// a shadow), and 4 small hour-marker nubs at the cardinal
|
|
|
|
|
|
// points around the rim. Useful for monastery courtyards,
|
|
|
|
|
|
// mage tower observatories, druidic stone circles. The
|
|
|
|
|
|
// 55th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float baseSize = 0.80f;
|
|
|
|
|
|
float baseHeight = 0.06f;
|
|
|
|
|
|
float gnomonHeight = 0.35f;
|
|
|
|
|
|
float gnomonT = 0.04f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { gnomonHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { gnomonT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (baseSize <= 0 || baseHeight <= 0 ||
|
|
|
|
|
|
gnomonHeight <= 0 || gnomonT <= 0 ||
|
|
|
|
|
|
gnomonT * 2 >= baseSize) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-sundial: dims > 0; gnomonT must fit in base\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfBase = baseSize * 0.5f;
|
|
|
|
|
|
// Base plate at floor.
|
|
|
|
|
|
addBox(0, baseHeight * 0.5f, 0,
|
|
|
|
|
|
halfBase, baseHeight * 0.5f, halfBase);
|
|
|
|
|
|
// Gnomon: vertical slab centered on the base, spanning the
|
|
|
|
|
|
// diameter along Z (the long axis). Sits on top of the base.
|
|
|
|
|
|
float halfGnomonT = gnomonT * 0.5f;
|
|
|
|
|
|
float halfGnomonZ = baseSize * 0.45f; // slightly inset from base edges
|
|
|
|
|
|
float gnomonCY = baseHeight + gnomonHeight * 0.5f;
|
|
|
|
|
|
addBox(0, gnomonCY, 0,
|
|
|
|
|
|
halfGnomonT, gnomonHeight * 0.5f, halfGnomonZ);
|
|
|
|
|
|
// 4 hour-marker nubs at cardinal positions (N, S, E, W) on
|
|
|
|
|
|
// the base's top face. Small protrusions above the base
|
|
|
|
|
|
// plate so they read as raised markers.
|
|
|
|
|
|
float markerW = baseSize * 0.06f;
|
|
|
|
|
|
float markerH = baseHeight * 1.5f;
|
|
|
|
|
|
float halfMW = markerW * 0.5f;
|
|
|
|
|
|
float markerOff = halfBase * 0.85f;
|
|
|
|
|
|
float markerCY = baseHeight + markerH * 0.5f;
|
|
|
|
|
|
addBox( markerOff, markerCY, 0, halfMW, markerH * 0.5f, halfMW); // E
|
|
|
|
|
|
addBox(-markerOff, markerCY, 0, halfMW, markerH * 0.5f, halfMW); // W
|
|
|
|
|
|
addBox(0, markerCY, markerOff, halfMW, markerH * 0.5f, halfMW); // N
|
|
|
|
|
|
addBox(0, markerCY, -markerOff, halfMW, markerH * 0.5f, halfMW); // S
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = baseHeight + gnomonHeight;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfBase, 0.0f, -halfBase);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfBase, totalH, halfBase);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-sundial: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" base : %.3f square × %.3f thick\n",
|
|
|
|
|
|
baseSize, baseHeight);
|
|
|
|
|
|
std::printf(" gnomon : %.3f tall × %.3f thick (along Z)\n",
|
|
|
|
|
|
gnomonHeight, gnomonT);
|
|
|
|
|
|
std::printf(" markers : 4 (N/S/E/W cardinal points)\n");
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 09:27:56 -07:00
|
|
|
|
int handleScarecrow(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Scarecrow: 5-box cruciform farm pest deterrent — anchor
|
|
|
|
|
|
// post into the ground, vertical body, horizontal arm cross,
|
|
|
|
|
|
// round-ish head box at the top, and a brimmed hat box on
|
|
|
|
|
|
// the head. The cross silhouette reads as a scarecrow even
|
|
|
|
|
|
// without rotated geometry. The 54th procedural mesh
|
|
|
|
|
|
// primitive — useful for crop fields, abandoned villages,
|
|
|
|
|
|
// harvest set dressing.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float bodyHeight = 1.80f;
|
|
|
|
|
|
float armSpan = 1.40f; // total cross-arm width
|
|
|
|
|
|
float postT = 0.06f;
|
|
|
|
|
|
float headSize = 0.22f;
|
|
|
|
|
|
float hatSize = 0.32f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { bodyHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { armSpan = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { headSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { hatSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (bodyHeight <= 0 || armSpan <= 0 || postT <= 0 ||
|
|
|
|
|
|
headSize <= 0 || hatSize <= 0) {
|
|
|
|
|
|
std::fprintf(stderr, "gen-mesh-scarecrow: all dims must be > 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Vertical body post — full bodyHeight.
|
|
|
|
|
|
float halfPost = postT * 0.5f;
|
|
|
|
|
|
float bodyCY = bodyHeight * 0.5f;
|
|
|
|
|
|
addBox(0, bodyCY, 0, halfPost, bodyHeight * 0.5f, halfPost);
|
|
|
|
|
|
// Cross-arm — horizontal, sits about 75% up the body.
|
|
|
|
|
|
float armT = postT * 0.85f;
|
|
|
|
|
|
float halfArmT = armT * 0.5f;
|
|
|
|
|
|
float armCY = bodyHeight * 0.72f;
|
|
|
|
|
|
addBox(0, armCY, 0, armSpan * 0.5f, halfArmT, halfArmT);
|
|
|
|
|
|
// Head — sits on top of the body. Slightly above the post
|
|
|
|
|
|
// tip so it visually sits on the post rather than passing
|
|
|
|
|
|
// through it.
|
|
|
|
|
|
float halfHead = headSize * 0.5f;
|
|
|
|
|
|
float headCY = bodyHeight + halfHead;
|
|
|
|
|
|
addBox(0, headCY, 0, halfHead, halfHead, halfHead);
|
|
|
|
|
|
// Hat — wider than the head (the brim) but shorter
|
|
|
|
|
|
// (so the head still pokes through visually).
|
|
|
|
|
|
float halfHat = hatSize * 0.5f;
|
|
|
|
|
|
float hatH = headSize * 0.40f;
|
|
|
|
|
|
float hatCY = headCY + halfHead - hatH * 0.3f;
|
|
|
|
|
|
addBox(0, hatCY, 0, halfHat, hatH * 0.5f, halfHat);
|
|
|
|
|
|
// Hat crown — taller, narrower top of the hat (so the
|
|
|
|
|
|
// overall hat reads as a brim + crown silhouette).
|
|
|
|
|
|
float crownSize = hatSize * 0.55f;
|
|
|
|
|
|
float crownH = headSize * 0.65f;
|
|
|
|
|
|
float halfCrown = crownSize * 0.5f;
|
|
|
|
|
|
float crownCY = hatCY + hatH * 0.5f + crownH * 0.5f;
|
|
|
|
|
|
addBox(0, crownCY, 0, halfCrown, crownH * 0.5f, halfCrown);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = crownCY + crownH * 0.5f;
|
|
|
|
|
|
float halfArm = armSpan * 0.5f;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfArm, 0.0f, -halfHead);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfArm, totalH, halfHead);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-scarecrow: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" body : %.3f tall (%.3f square post)\n",
|
|
|
|
|
|
bodyHeight, postT);
|
|
|
|
|
|
std::printf(" arm span : %.3f wide\n", armSpan);
|
|
|
|
|
|
std::printf(" head/hat : %.3f / %.3f\n", headSize, hatSize);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 09:20:08 -07:00
|
|
|
|
int handleWeathervane(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Weathervane: 6-box rooftop wind indicator — base plate,
|
|
|
|
|
|
// tall vertical post, perpendicular N-S and E-W cross arms
|
|
|
|
|
|
// (cardinal direction markers), a long horizontal arrow on
|
|
|
|
|
|
// top of the cross, and a small tail box at the back end of
|
|
|
|
|
|
// the arrow that visually balances the head. Useful for
|
|
|
|
|
|
// farm rooftops, chapel spires, town halls, lighthouse caps.
|
|
|
|
|
|
// The 53rd procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float postHeight = 1.50f;
|
|
|
|
|
|
float postT = 0.05f;
|
|
|
|
|
|
float baseSize = 0.30f;
|
|
|
|
|
|
float armLen = 0.40f; // half-length of each cross arm
|
|
|
|
|
|
float arrowLen = 0.55f; // half-length of the arrow body
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { armLen = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { arrowLen = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (postHeight <= 0 || postT <= 0 || baseSize <= 0 ||
|
|
|
|
|
|
armLen <= 0 || arrowLen <= 0 || postT >= baseSize) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-weathervane: dims > 0; post must fit in base\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Base plate at floor.
|
|
|
|
|
|
float baseHeight = baseSize * 0.30f;
|
|
|
|
|
|
float halfBase = baseSize * 0.5f;
|
|
|
|
|
|
addBox(0, baseHeight * 0.5f, 0,
|
|
|
|
|
|
halfBase, baseHeight * 0.5f, halfBase);
|
|
|
|
|
|
// Vertical post.
|
|
|
|
|
|
float halfPost = postT * 0.5f;
|
|
|
|
|
|
float poleBottomY = baseHeight;
|
|
|
|
|
|
float poleTopY = baseHeight + postHeight;
|
|
|
|
|
|
float poleCY = (poleBottomY + poleTopY) * 0.5f;
|
|
|
|
|
|
addBox(0, poleCY, 0, halfPost, postHeight * 0.5f, halfPost);
|
|
|
|
|
|
// Cross arms at the top of the post — 2 perpendicular thin
|
|
|
|
|
|
// bars forming the cardinal-direction "+" marker.
|
|
|
|
|
|
float armT = postT * 0.7f;
|
|
|
|
|
|
float halfArmT = armT * 0.5f;
|
|
|
|
|
|
float crossY = poleTopY - armT * 1.0f;
|
|
|
|
|
|
addBox(0, crossY, 0, armLen, halfArmT, halfArmT); // E-W (along X)
|
|
|
|
|
|
addBox(0, crossY, 0, halfArmT, halfArmT, armLen); // N-S (along Z)
|
|
|
|
|
|
// Arrow body on top of the cross — long thin bar that
|
|
|
|
|
|
// would rotate to the wind direction. Aligned along +X by
|
|
|
|
|
|
// default (designers can rotate at placement time).
|
|
|
|
|
|
float arrowY = poleTopY + armT * 0.7f;
|
|
|
|
|
|
float arrowT2 = armT * 1.1f;
|
|
|
|
|
|
float halfAT = arrowT2 * 0.5f;
|
|
|
|
|
|
addBox(0, arrowY, 0, arrowLen, halfAT, halfAT);
|
|
|
|
|
|
// Arrow tail: small box at -X end so the arrow looks
|
|
|
|
|
|
// directional rather than symmetric.
|
|
|
|
|
|
float tailLen = arrowLen * 0.3f;
|
|
|
|
|
|
float tailX = -arrowLen + tailLen;
|
|
|
|
|
|
addBox(tailX, arrowY, 0, halfAT, arrowT2 * 1.4f, halfAT);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = arrowY + arrowT2 * 1.4f;
|
|
|
|
|
|
float maxX = std::max({halfBase, arrowLen, tailX + halfAT});
|
|
|
|
|
|
wom.boundMin = glm::vec3(-maxX, 0.0f, -armLen);
|
|
|
|
|
|
wom.boundMax = glm::vec3( maxX, totalH, armLen);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-weathervane: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" base : %.3f square × %.3f tall\n",
|
|
|
|
|
|
baseSize, baseHeight);
|
|
|
|
|
|
std::printf(" post : %.3f square × %.3f tall\n",
|
|
|
|
|
|
postT, postHeight);
|
|
|
|
|
|
std::printf(" cross arms : 2 × %.3f half-length (N-S + E-W)\n", armLen);
|
|
|
|
|
|
std::printf(" arrow : %.3f half-length (with tail at -X)\n",
|
|
|
|
|
|
arrowLen);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 09:11:36 -07:00
|
|
|
|
int handleBeehive(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Beehive: 5-box woven straw skep — stacked tiers of
|
|
|
|
|
|
// decreasing width approximating a dome, with a small
|
|
|
|
|
|
// entrance notch box at the front. Useful for druidic
|
|
|
|
|
|
// groves, beekeeper farms, hunter camps. The 52nd
|
|
|
|
|
|
// procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float baseWidth = 0.70f; // bottom tier width
|
|
|
|
|
|
float height = 0.85f; // total dome height (excluding base plate)
|
|
|
|
|
|
float plateH = 0.05f; // optional foundation plate thickness
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseWidth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { plateH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (baseWidth <= 0 || height <= 0 || plateH < 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-beehive: baseWidth/height > 0; plateH >= 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Optional wooden base plate, slightly wider than the
|
|
|
|
|
|
// bottom tier so it reads as a foundation.
|
|
|
|
|
|
float halfBaseW = baseWidth * 0.5f;
|
|
|
|
|
|
float halfPlateW = baseWidth * 0.55f;
|
|
|
|
|
|
if (plateH > 0) {
|
|
|
|
|
|
addBox(0, plateH * 0.5f, 0, halfPlateW, plateH * 0.5f, halfPlateW);
|
|
|
|
|
|
}
|
|
|
|
|
|
// 4 stacked tiers approximating a conical dome. Tier widths
|
|
|
|
|
|
// ramp 100% -> 90% -> 70% -> 40% of baseWidth. Each tier
|
|
|
|
|
|
// takes 1/4 of the dome height.
|
|
|
|
|
|
float tierHeight = height / 4.0f;
|
|
|
|
|
|
float tierWidths[4] = {1.00f, 0.90f, 0.70f, 0.40f};
|
|
|
|
|
|
float tierBase = plateH;
|
|
|
|
|
|
for (int t = 0; t < 4; ++t) {
|
|
|
|
|
|
float halfW = baseWidth * tierWidths[t] * 0.5f;
|
|
|
|
|
|
float tierCY = tierBase + tierHeight * 0.5f + t * tierHeight;
|
|
|
|
|
|
addBox(0, tierCY, 0, halfW, tierHeight * 0.5f, halfW);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Entrance notch: a small dark box at the front (+Z face)
|
|
|
|
|
|
// of the bottom tier, slightly proud of it so it reads as
|
|
|
|
|
|
// a separate cutout rather than texture detail.
|
|
|
|
|
|
float entryW = baseWidth * 0.20f;
|
|
|
|
|
|
float entryH = tierHeight * 0.55f;
|
|
|
|
|
|
float entryT = baseWidth * 0.04f;
|
|
|
|
|
|
float entryCY = tierBase + entryH * 0.5f;
|
|
|
|
|
|
float entryCZ = halfBaseW + entryT * 0.5f;
|
|
|
|
|
|
addBox(0, entryCY, entryCZ,
|
|
|
|
|
|
entryW * 0.5f, entryH * 0.5f, entryT * 0.5f);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = plateH + height;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfPlateW, 0.0f, -halfPlateW);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfPlateW, totalH, halfBaseW + entryT);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-beehive: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" base width : %.3f (plate %.3f thick)\n", baseWidth, plateH);
|
|
|
|
|
|
std::printf(" height : %.3f dome (4 tapered tiers)\n", height);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" entrance : %.3f wide × %.3f tall on +Z face\n",
|
|
|
|
|
|
entryW, entryH);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 09:02:04 -07:00
|
|
|
|
int handleGate(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Gate: 5-box wooden farm gate — 2 vertical posts on either
|
|
|
|
|
|
// side and 3 horizontal cross rails (top, middle, bottom)
|
|
|
|
|
|
// spanning the opening. The opening sits flat in the X-Y
|
|
|
|
|
|
// plane (rails along X, posts along Y) so it can hang in
|
|
|
|
|
|
// a wall slot without rotation. The 51st procedural mesh
|
|
|
|
|
|
// primitive — useful for fenced fields, manor entrances,
|
|
|
|
|
|
// pen openings, courtyard barriers.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float openingWidth = 1.80f; // gap between posts (rail span)
|
|
|
|
|
|
float postHeight = 1.30f; // post height (= gate frame height)
|
|
|
|
|
|
float postT = 0.10f; // post square cross-section
|
|
|
|
|
|
float railT = 0.06f; // rail square cross-section
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { openingWidth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { railT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (openingWidth <= 0 || postHeight <= 0 || postT <= 0 ||
|
|
|
|
|
|
railT <= 0 || railT >= postHeight / 4) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-gate: dims > 0; railT < postHeight/4\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Total gate width = openingWidth + 2*postT (posts sit flush
|
|
|
|
|
|
// against the rails so the rail length = openingWidth).
|
|
|
|
|
|
float halfPost = postT * 0.5f;
|
|
|
|
|
|
float halfRail = railT * 0.5f;
|
|
|
|
|
|
float postX = openingWidth * 0.5f + halfPost;
|
|
|
|
|
|
float postCY = postHeight * 0.5f;
|
|
|
|
|
|
// 2 vertical posts.
|
|
|
|
|
|
addBox( postX, postCY, 0, halfPost, postHeight * 0.5f, halfPost);
|
|
|
|
|
|
addBox(-postX, postCY, 0, halfPost, postHeight * 0.5f, halfPost);
|
|
|
|
|
|
// 3 horizontal rails: top, middle, bottom. Bottom sits a
|
|
|
|
|
|
// little above the floor so it reads as a gate rather than
|
|
|
|
|
|
// bouncing off the ground; top sits a little below the post
|
|
|
|
|
|
// top so the post crowns are visible.
|
|
|
|
|
|
float halfRailLen = openingWidth * 0.5f;
|
|
|
|
|
|
float topRailY = postHeight - halfRail * 1.5f;
|
|
|
|
|
|
float bottomRailY = halfRail * 2.0f;
|
|
|
|
|
|
float midRailY = (topRailY + bottomRailY) * 0.5f;
|
|
|
|
|
|
addBox(0, topRailY, 0, halfRailLen, halfRail, halfRail);
|
|
|
|
|
|
addBox(0, midRailY, 0, halfRailLen, halfRail, halfRail);
|
|
|
|
|
|
addBox(0, bottomRailY, 0, halfRailLen, halfRail, halfRail);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float halfTotalX = postX + halfPost;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfTotalX, 0.0f, -halfPost);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfTotalX, postHeight, halfPost);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-gate: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" total W : %.3f (opening %.3f + 2 posts)\n",
|
|
|
|
|
|
openingWidth + postT * 2, openingWidth);
|
|
|
|
|
|
std::printf(" posts : 2 × %.3f square × %.3f tall\n",
|
|
|
|
|
|
postT, postHeight);
|
|
|
|
|
|
std::printf(" rails : 3 × %.3f square (top/mid/bottom)\n", railT);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 08:54:27 -07:00
|
|
|
|
int handleCauldron(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Cauldron: 7-box witch's pot — 4 small corner legs at the
|
|
|
|
|
|
// floor, narrow bottom-bowl tier, wider mid-bowl tier, and
|
|
|
|
|
|
// a still-wider thin rim at the top. The stacked tiers
|
|
|
|
|
|
// approximate the curved silhouette of a cast-iron pot
|
|
|
|
|
|
// without needing rotated faces. The 50th procedural mesh
|
|
|
|
|
|
// primitive — pairs with --gen-mesh-shrine / --gen-mesh-totem
|
|
|
|
|
|
// for ritual / alchemy set dressing.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float rimWidth = 0.80f; // top-rim extent (widest dim)
|
|
|
|
|
|
float bodyHeight = 0.70f; // total height excluding legs
|
|
|
|
|
|
float legHeight = 0.10f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { rimWidth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { bodyHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { legHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (rimWidth <= 0 || bodyHeight <= 0 || legHeight <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-cauldron: all dims must be > 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Tier proportions (bottom → top): 60% / 90% / 100% of rimWidth.
|
|
|
|
|
|
// Heights split: legs / 30% body / 55% body / 15% body (rim).
|
|
|
|
|
|
float bottomW = rimWidth * 0.60f;
|
|
|
|
|
|
float midW = rimWidth * 0.90f;
|
|
|
|
|
|
float bottomH = bodyHeight * 0.30f;
|
|
|
|
|
|
float midH = bodyHeight * 0.55f;
|
|
|
|
|
|
float rimH = bodyHeight * 0.15f;
|
|
|
|
|
|
// 4 legs at corners of a footprint slightly smaller than the
|
|
|
|
|
|
// bottom tier so the legs visually carry the pot's weight.
|
|
|
|
|
|
float legT = bottomW * 0.18f;
|
|
|
|
|
|
float halfLegT = legT * 0.5f;
|
|
|
|
|
|
float legX = bottomW * 0.5f - halfLegT * 1.4f;
|
|
|
|
|
|
float legCY = legHeight * 0.5f;
|
|
|
|
|
|
addBox( legX, legCY, legX, halfLegT, legHeight * 0.5f, halfLegT);
|
|
|
|
|
|
addBox(-legX, legCY, legX, halfLegT, legHeight * 0.5f, halfLegT);
|
|
|
|
|
|
addBox( legX, legCY, -legX, halfLegT, legHeight * 0.5f, halfLegT);
|
|
|
|
|
|
addBox(-legX, legCY, -legX, halfLegT, legHeight * 0.5f, halfLegT);
|
|
|
|
|
|
// Bottom tier (narrow): sits on top of the legs.
|
|
|
|
|
|
float halfBottom = bottomW * 0.5f;
|
|
|
|
|
|
float bottomCY = legHeight + bottomH * 0.5f;
|
|
|
|
|
|
addBox(0, bottomCY, 0, halfBottom, bottomH * 0.5f, halfBottom);
|
|
|
|
|
|
// Middle tier (widest body): main bulge of the pot.
|
|
|
|
|
|
float halfMid = midW * 0.5f;
|
|
|
|
|
|
float midCY = legHeight + bottomH + midH * 0.5f;
|
|
|
|
|
|
addBox(0, midCY, 0, halfMid, midH * 0.5f, halfMid);
|
|
|
|
|
|
// Rim: thin slab capping the body, slightly wider than mid.
|
|
|
|
|
|
float halfRim = rimWidth * 0.5f;
|
|
|
|
|
|
float rimCY = legHeight + bottomH + midH + rimH * 0.5f;
|
|
|
|
|
|
addBox(0, rimCY, 0, halfRim, rimH * 0.5f, halfRim);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = legHeight + bodyHeight;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfRim, 0.0f, -halfRim);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfRim, totalH, halfRim);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-cauldron: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" rim width : %.3f (widest)\n", rimWidth);
|
|
|
|
|
|
std::printf(" body H : %.3f (legs %.3f tall)\n", bodyHeight, legHeight);
|
|
|
|
|
|
std::printf(" tiers : bottom %.3f / mid %.3f / rim %.3f\n",
|
|
|
|
|
|
bottomW, midW, rimWidth);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 08:44:51 -07:00
|
|
|
|
int handleStool(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Stool: 5-box small backless seat — flat round-ish seat
|
|
|
|
|
|
// (square here, since axis-aligned) on 4 short legs at the
|
|
|
|
|
|
// corners. Pairs with --gen-mesh-table for taverns and
|
|
|
|
|
|
// workshops. Smaller-footprint counterpart to --gen-mesh-bench.
|
|
|
|
|
|
// The 49th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float seatSize = 0.36f; // seat side length
|
|
|
|
|
|
float seatT = 0.04f; // seat thickness
|
|
|
|
|
|
float legHeight = 0.45f;
|
|
|
|
|
|
float legT = 0.04f; // square leg cross-section
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { seatSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { seatT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { legHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { legT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (seatSize <= 0 || seatT <= 0 || legHeight <= 0 || legT <= 0 ||
|
|
|
|
|
|
legT * 2 >= seatSize) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-stool: dims > 0; legT must fit in seatSize\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfSeat = seatSize * 0.5f;
|
|
|
|
|
|
float halfLeg = legT * 0.5f;
|
|
|
|
|
|
float seatTopY = legHeight + seatT;
|
|
|
|
|
|
// Seat: flat slab on top of the legs.
|
|
|
|
|
|
addBox(0, legHeight + seatT * 0.5f, 0,
|
|
|
|
|
|
halfSeat, seatT * 0.5f, halfSeat);
|
|
|
|
|
|
// 4 legs: corner-inset by halfLeg so they sit flush with
|
|
|
|
|
|
// the seat's edge.
|
|
|
|
|
|
float legX = halfSeat - halfLeg;
|
|
|
|
|
|
float legCY = legHeight * 0.5f;
|
|
|
|
|
|
addBox( legX, legCY, legX, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
addBox(-legX, legCY, legX, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
addBox( legX, legCY, -legX, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
addBox(-legX, legCY, -legX, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfSeat, 0.0f, -halfSeat);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfSeat, seatTopY, halfSeat);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-stool: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" seat : %.3f square × %.3f thick\n", seatSize, seatT);
|
|
|
|
|
|
std::printf(" legs : 4 × %.3f square (%.3f tall)\n",
|
|
|
|
|
|
legT, legHeight);
|
|
|
|
|
|
std::printf(" total H : %.3f\n", seatTopY);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 08:28:53 -07:00
|
|
|
|
int handleCrate(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Crate: 5-box wooden shipping crate — main cube body
|
|
|
|
|
|
// plus 4 reinforcement posts running along the vertical
|
|
|
|
|
|
// edges. The posts are slightly proud of the body so they
|
|
|
|
|
|
// read as separate rails rather than texture detail. The
|
|
|
|
|
|
// 48th procedural mesh primitive — useful for dock yards,
|
|
|
|
|
|
// warehouse interiors, dungeon room set dressing.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float size = 0.80f; // cube side length
|
|
|
|
|
|
float postRadius = 0.05f; // half-thickness of corner posts
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { size = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postRadius = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (size <= 0 || postRadius <= 0 || postRadius * 4 >= size) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-crate: size/postRadius > 0; postRadius < size/4\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfBody = size * 0.5f;
|
|
|
|
|
|
// Main body: cube centered at (0, halfBody, 0).
|
|
|
|
|
|
addBox(0, halfBody, 0, halfBody, halfBody, halfBody);
|
|
|
|
|
|
// 4 corner posts: thin boxes running the full height,
|
|
|
|
|
|
// positioned at the 4 vertical edges of the cube. Posts
|
|
|
|
|
|
// extend slightly proud of the body on each axis (from
|
|
|
|
|
|
// halfBody to halfBody + postRadius) so they're visible
|
|
|
|
|
|
// from any angle without z-fighting the body's faces.
|
|
|
|
|
|
float postOffset = halfBody;
|
|
|
|
|
|
float postCY = halfBody;
|
|
|
|
|
|
float postHeight = size;
|
|
|
|
|
|
float halfPost = postRadius;
|
|
|
|
|
|
addBox( postOffset, postCY, postOffset, halfPost, postHeight * 0.5f, halfPost);
|
|
|
|
|
|
addBox(-postOffset, postCY, postOffset, halfPost, postHeight * 0.5f, halfPost);
|
|
|
|
|
|
addBox( postOffset, postCY, -postOffset, halfPost, postHeight * 0.5f, halfPost);
|
|
|
|
|
|
addBox(-postOffset, postCY, -postOffset, halfPost, postHeight * 0.5f, halfPost);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float halfTotal = halfBody + halfPost;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfTotal, 0.0f, -halfTotal);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfTotal, size, halfTotal);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-crate: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" size : %.3f cube\n", size);
|
|
|
|
|
|
std::printf(" posts : 4 × %.3f square (full height)\n",
|
|
|
|
|
|
postRadius * 2);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 08:19:04 -07:00
|
|
|
|
int handleTombstone(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Tombstone: 3-box vertical headstone — wide low base
|
|
|
|
|
|
// plinth, tall thin main slab on top, and a small
|
|
|
|
|
|
// decorative crown / cornice at the very top. Pairs
|
|
|
|
|
|
// naturally with --gen-mesh-grave and --gen-mesh-coffin
|
|
|
|
|
|
// for graveyards. The 47th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float width = 0.60f; // along X (face width)
|
|
|
|
|
|
float height = 1.10f; // total tombstone height including base + crown
|
|
|
|
|
|
float depth = 0.18f; // along Z (slab thickness)
|
|
|
|
|
|
float baseScale = 1.45f; // base extends this much beyond slab in X & Z
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { depth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseScale = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (width <= 0 || height <= 0 || depth <= 0 ||
|
|
|
|
|
|
baseScale < 1.0f || baseScale > 5.0f) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-tombstone: dims > 0; baseScale 1.0..5.0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Vertical layout: base 15%, slab 75%, crown 10% of total height.
|
|
|
|
|
|
float baseH = height * 0.15f;
|
|
|
|
|
|
float crownH = height * 0.10f;
|
|
|
|
|
|
float slabH = height - baseH - crownH;
|
|
|
|
|
|
// Base plinth: wider in X & Z than the slab so it reads as
|
|
|
|
|
|
// an explicit foundation.
|
|
|
|
|
|
float halfBaseW = width * baseScale * 0.5f;
|
|
|
|
|
|
float halfBaseD = depth * baseScale * 0.5f;
|
|
|
|
|
|
addBox(0, baseH * 0.5f, 0,
|
|
|
|
|
|
halfBaseW, baseH * 0.5f, halfBaseD);
|
|
|
|
|
|
// Main slab: thin tall rectangle centered above the base.
|
|
|
|
|
|
float slabCY = baseH + slabH * 0.5f;
|
|
|
|
|
|
float halfW = width * 0.5f;
|
|
|
|
|
|
float halfD = depth * 0.5f;
|
|
|
|
|
|
addBox(0, slabCY, 0, halfW, slabH * 0.5f, halfD);
|
|
|
|
|
|
// Crown: slightly wider/deeper than the slab, sits on top.
|
|
|
|
|
|
// Acts as a decorative cornice (a flat-cap variant of the
|
|
|
|
|
|
// arched-top headstone shape that we can't do with
|
|
|
|
|
|
// axis-aligned boxes alone).
|
|
|
|
|
|
float crownScale = 1.18f;
|
|
|
|
|
|
float halfCrownW = width * crownScale * 0.5f;
|
|
|
|
|
|
float halfCrownD = depth * crownScale * 0.5f;
|
|
|
|
|
|
float crownCY = baseH + slabH + crownH * 0.5f;
|
|
|
|
|
|
addBox(0, crownCY, 0, halfCrownW, crownH * 0.5f, halfCrownD);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfBaseW, 0.0f, -halfBaseD);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfBaseW, height, halfBaseD);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-tombstone: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" total H : %.3f (base %.3f + slab %.3f + crown %.3f)\n",
|
|
|
|
|
|
height, baseH, slabH, crownH);
|
|
|
|
|
|
std::printf(" slab : %.3f wide × %.3f deep\n", width, depth);
|
|
|
|
|
|
std::printf(" base scale : %.2fx (base %.3f wide × %.3f deep)\n",
|
|
|
|
|
|
baseScale, halfBaseW * 2, halfBaseD * 2);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 08:08:37 -07:00
|
|
|
|
int handleMailbox(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Mailbox: 4-box wayside prop — vertical post, horizontal
|
|
|
|
|
|
// box body mounted on top of the post (long axis along Z),
|
|
|
|
|
|
// small rectangular flag mounted on the right side near the
|
|
|
|
|
|
// front of the body. Useful for inns, post stations, manor
|
|
|
|
|
|
// gates, frontier outposts. The 46th procedural mesh.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float postHeight = 1.10f;
|
|
|
|
|
|
float postThickness = 0.08f;
|
|
|
|
|
|
float boxLength = 0.45f; // along Z
|
|
|
|
|
|
float boxWidth = 0.20f; // along X
|
|
|
|
|
|
float boxHeight = 0.20f; // along Y
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postThickness = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { boxLength = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { boxWidth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { boxHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (postHeight <= 0 || postThickness <= 0 ||
|
|
|
|
|
|
boxLength <= 0 || boxWidth <= 0 || boxHeight <= 0) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-mailbox: all dims must be > 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Vertical post from y=0 to y=postHeight.
|
|
|
|
|
|
float halfPost = postThickness * 0.5f;
|
|
|
|
|
|
addBox(0, postHeight * 0.5f, 0,
|
|
|
|
|
|
halfPost, postHeight * 0.5f, halfPost);
|
|
|
|
|
|
// Mailbox body: sits on top of the post, slightly wider than
|
|
|
|
|
|
// the post on each axis so the body visually caps the post.
|
|
|
|
|
|
float bodyCY = postHeight + boxHeight * 0.5f;
|
|
|
|
|
|
float halfBoxW = boxWidth * 0.5f;
|
|
|
|
|
|
float halfBoxH = boxHeight * 0.5f;
|
|
|
|
|
|
float halfBoxL = boxLength * 0.5f;
|
|
|
|
|
|
addBox(0, bodyCY, 0, halfBoxW, halfBoxH, halfBoxL);
|
|
|
|
|
|
// Small rectangular flag mounted on the right side (+X face)
|
|
|
|
|
|
// of the body near the front (+Z end). Flag pole is a thin
|
|
|
|
|
|
// box; the flag itself is a thin square plate at the top of
|
|
|
|
|
|
// the pole.
|
|
|
|
|
|
float flagPoleH = boxHeight * 0.7f;
|
|
|
|
|
|
float flagPoleT = postThickness * 0.4f;
|
|
|
|
|
|
float halfFlagPole = flagPoleT * 0.5f;
|
|
|
|
|
|
float flagPoleX = halfBoxW + halfFlagPole; // sits flush against +X face
|
|
|
|
|
|
float flagPoleZ = halfBoxL - flagPoleT * 1.5f;
|
|
|
|
|
|
float flagPoleCY = postHeight + boxHeight + flagPoleH * 0.5f;
|
|
|
|
|
|
addBox(flagPoleX, flagPoleCY, flagPoleZ,
|
|
|
|
|
|
halfFlagPole, flagPoleH * 0.5f, halfFlagPole);
|
|
|
|
|
|
// Flag plate at the top of the pole, extending +X away from
|
|
|
|
|
|
// the body so it reads as a raised flag.
|
|
|
|
|
|
float flagPlateW = boxHeight * 0.6f; // along Y (vertical extent)
|
|
|
|
|
|
float flagPlateL = boxHeight * 0.7f; // along X (away from body)
|
|
|
|
|
|
float flagPlateT = flagPoleT * 0.6f; // along Z (thickness)
|
|
|
|
|
|
float halfFlagL = flagPlateL * 0.5f;
|
|
|
|
|
|
float flagPlateX = flagPoleX + halfFlagL;
|
|
|
|
|
|
float flagPlateCY = postHeight + boxHeight + flagPoleH - flagPlateW * 0.5f;
|
|
|
|
|
|
addBox(flagPlateX, flagPlateCY, flagPoleZ,
|
|
|
|
|
|
halfFlagL, flagPlateW * 0.5f, flagPlateT * 0.5f);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = postHeight + boxHeight + flagPoleH;
|
|
|
|
|
|
float maxX = std::max(halfBoxW, flagPlateX + halfFlagL);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfBoxW, 0.0f, -halfBoxL);
|
|
|
|
|
|
wom.boundMax = glm::vec3( maxX, totalH, halfBoxL);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-mailbox: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" post : %.3f square × %.3f tall\n",
|
|
|
|
|
|
postThickness, postHeight);
|
|
|
|
|
|
std::printf(" box body : %.3f L × %.3f W × %.3f H\n",
|
|
|
|
|
|
boxLength, boxWidth, boxHeight);
|
|
|
|
|
|
std::printf(" flag : pole + plate on +X side near front\n");
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 07:58:45 -07:00
|
|
|
|
int handleSignpost(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Signpost: 4-box wayfinding prop — stone base anchor at the
|
|
|
|
|
|
// ground, tall vertical pole, decorative cap, and one
|
|
|
|
|
|
// horizontal sign board mounted face-out from the pole near
|
|
|
|
|
|
// the top. Useful for crossroads, tavern fronts, town
|
|
|
|
|
|
// entrances, dungeon area markers. The 45th procedural mesh.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float postHeight = 2.5f;
|
|
|
|
|
|
float postThickness = 0.10f;
|
|
|
|
|
|
float baseSize = 0.30f;
|
|
|
|
|
|
float signWidth = 0.80f; // along Z (perpendicular to pole face)
|
|
|
|
|
|
float signHeight = 0.35f; // along Y
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postThickness = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { signWidth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { signHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (postHeight <= 0 || postThickness <= 0 || baseSize <= 0 ||
|
|
|
|
|
|
signWidth <= 0 || signHeight <= 0 ||
|
|
|
|
|
|
postThickness >= baseSize) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-signpost: dims > 0; post must fit in base\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Base plinth at the floor.
|
|
|
|
|
|
float baseHeight = baseSize * 0.45f;
|
|
|
|
|
|
float halfBase = baseSize * 0.5f;
|
|
|
|
|
|
addBox(0, baseHeight * 0.5f, 0,
|
|
|
|
|
|
halfBase, baseHeight * 0.5f, halfBase);
|
|
|
|
|
|
// Pole rising above the base.
|
|
|
|
|
|
float poleBottomY = baseHeight;
|
|
|
|
|
|
float poleTopY = baseHeight + postHeight;
|
|
|
|
|
|
float poleCY = (poleBottomY + poleTopY) * 0.5f;
|
|
|
|
|
|
float halfPole = postThickness * 0.5f;
|
|
|
|
|
|
addBox(0, poleCY, 0,
|
|
|
|
|
|
halfPole, postHeight * 0.5f, halfPole);
|
|
|
|
|
|
// Sign board: thin rectangle mounted on the pole near the top.
|
|
|
|
|
|
// signWidth runs along Z (the long axis), signHeight along Y,
|
|
|
|
|
|
// and a sliver of postThickness along X — a billboard that
|
|
|
|
|
|
// reads as a sign when viewed from either +Z or -Z.
|
|
|
|
|
|
float signCenterY = poleTopY - signHeight * 0.7f;
|
|
|
|
|
|
float signThickness = postThickness * 0.6f;
|
|
|
|
|
|
addBox(0, signCenterY, 0,
|
|
|
|
|
|
signThickness * 0.5f, signHeight * 0.5f, signWidth * 0.5f);
|
|
|
|
|
|
// Decorative cap on top of the pole.
|
|
|
|
|
|
float capHeight = postThickness * 0.8f;
|
|
|
|
|
|
float capCY = poleTopY + capHeight * 0.5f;
|
|
|
|
|
|
float halfCap = postThickness * 0.9f;
|
|
|
|
|
|
addBox(0, capCY, 0,
|
|
|
|
|
|
halfCap, capHeight * 0.5f, halfCap);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = capCY + capHeight * 0.5f;
|
|
|
|
|
|
float halfSignZ = signWidth * 0.5f;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-std::max(halfBase, halfSignZ), 0.0f, -halfSignZ);
|
|
|
|
|
|
wom.boundMax = glm::vec3( std::max(halfBase, halfSignZ), totalH, halfSignZ);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-signpost: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" base : %.3f square × %.3f tall\n",
|
|
|
|
|
|
baseSize, baseHeight);
|
|
|
|
|
|
std::printf(" pole : %.3f square × %.3f tall\n",
|
|
|
|
|
|
postThickness, postHeight);
|
|
|
|
|
|
std::printf(" sign board : %.3f × %.3f (wide × tall)\n",
|
|
|
|
|
|
signWidth, signHeight);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 07:47:15 -07:00
|
|
|
|
int handleWell(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Well: 4 stone walls arranged in a square ring (hollow
|
|
|
|
|
|
// interior so a player can see down the shaft) + 2 vertical
|
|
|
|
|
|
// roof posts on opposite sides + 1 horizontal cross beam at
|
|
|
|
|
|
// the top (where the rope/bucket would mount). Useful for
|
|
|
|
|
|
// village squares, courtyards, dungeon water sources.
|
|
|
|
|
|
// The 44th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float outerSize = 1.4f; // square wall outer footprint
|
|
|
|
|
|
float wallH = 0.8f; // wall height above ground
|
|
|
|
|
|
float wallT = 0.15f; // wall thickness
|
|
|
|
|
|
float postH = 1.6f; // roof post height above wall
|
|
|
|
|
|
float postT = 0.12f; // roof post thickness (square)
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { outerSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { wallH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { wallT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (outerSize <= 0 || wallH <= 0 || wallT <= 0 ||
|
|
|
|
|
|
postH <= 0 || postT <= 0 || wallT * 2 >= outerSize) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-well: dims > 0; wallT must fit in outerSize\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfOuter = outerSize * 0.5f;
|
|
|
|
|
|
float halfWallT = wallT * 0.5f;
|
|
|
|
|
|
float halfPostT = postT * 0.5f;
|
|
|
|
|
|
// 4 wall panels arranged in a hollow square. Each panel
|
|
|
|
|
|
// spans full outerSize along its long axis. Walls along X
|
|
|
|
|
|
// sit at z = ±(halfOuter - halfWallT); walls along Z sit at
|
|
|
|
|
|
// x = ±(halfOuter - halfWallT) but shortened so they don't
|
|
|
|
|
|
// overlap the X walls (interior length = outerSize - 2*wallT).
|
|
|
|
|
|
float wallCY = wallH * 0.5f;
|
|
|
|
|
|
// North wall (+Z edge) — full outerSize wide.
|
|
|
|
|
|
addBox(0, wallCY, halfOuter - halfWallT,
|
|
|
|
|
|
halfOuter, wallH * 0.5f, halfWallT);
|
|
|
|
|
|
// South wall (-Z edge) — full outerSize wide.
|
|
|
|
|
|
addBox(0, wallCY, -halfOuter + halfWallT,
|
|
|
|
|
|
halfOuter, wallH * 0.5f, halfWallT);
|
|
|
|
|
|
// East wall (+X edge) — interior length only.
|
|
|
|
|
|
float eastWestLen = outerSize - 2 * wallT;
|
|
|
|
|
|
addBox(halfOuter - halfWallT, wallCY, 0,
|
|
|
|
|
|
halfWallT, wallH * 0.5f, eastWestLen * 0.5f);
|
|
|
|
|
|
// West wall (-X edge) — interior length only.
|
|
|
|
|
|
addBox(-halfOuter + halfWallT, wallCY, 0,
|
|
|
|
|
|
halfWallT, wallH * 0.5f, eastWestLen * 0.5f);
|
|
|
|
|
|
// 2 vertical roof posts mounted on top of the east and west
|
|
|
|
|
|
// walls, centred in z. Posts rise from the top of the walls
|
|
|
|
|
|
// (y=wallH) by postH.
|
|
|
|
|
|
float postCY = wallH + postH * 0.5f;
|
|
|
|
|
|
float postX = halfOuter - halfPostT;
|
|
|
|
|
|
addBox( postX, postCY, 0, halfPostT, postH * 0.5f, halfPostT);
|
|
|
|
|
|
addBox(-postX, postCY, 0, halfPostT, postH * 0.5f, halfPostT);
|
|
|
|
|
|
// Horizontal cross beam connecting the post tops. The beam
|
|
|
|
|
|
// spans the full distance between posts (so it ends inside
|
|
|
|
|
|
// each post). Beam is square in cross section, slightly
|
|
|
|
|
|
// thicker than the posts so it visually overlaps the joint.
|
|
|
|
|
|
float beamT = postT * 1.2f;
|
|
|
|
|
|
float halfBeamT = beamT * 0.5f;
|
|
|
|
|
|
float beamCY = wallH + postH - halfBeamT;
|
|
|
|
|
|
addBox(0, beamCY, 0,
|
|
|
|
|
|
halfOuter * 0.85f, halfBeamT, halfBeamT);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = wallH + postH;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfOuter, 0.0f, -halfOuter);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfOuter, totalH, halfOuter);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-well: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" outerSize : %.3f square\n", outerSize);
|
|
|
|
|
|
std::printf(" wall : %.3f tall, %.3f thick\n", wallH, wallT);
|
|
|
|
|
|
std::printf(" roof posts : 2 × %.3f tall\n", postH);
|
|
|
|
|
|
std::printf(" total H : %.3f (with cross beam)\n", totalH);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 07:36:02 -07:00
|
|
|
|
int handleLadder(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Ladder: 2 vertical rails + N horizontal rungs evenly
|
|
|
|
|
|
// spaced between them. Sits flat against +Z (the climbing
|
|
|
|
|
|
// face) so it can be parented to walls / wagons / ship
|
|
|
|
|
|
// hulls. The 43rd procedural mesh primitive — useful for
|
|
|
|
|
|
// attics, ship rigging, dungeons, mage towers.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float height = 3.0f;
|
|
|
|
|
|
float width = 0.6f;
|
|
|
|
|
|
int rungs = 8;
|
|
|
|
|
|
float railT = 0.06f;
|
|
|
|
|
|
float rungT = 0.04f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { rungs = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { railT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { rungT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (height <= 0 || width <= 0 || railT <= 0 || rungT <= 0 ||
|
|
|
|
|
|
rungs < 2 || rungs > 64 || railT * 2 >= width) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-ladder: dims > 0; rungs 2..64; rails must fit in width\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfW = width * 0.5f;
|
|
|
|
|
|
float halfRail = railT * 0.5f;
|
|
|
|
|
|
float halfRung = rungT * 0.5f;
|
|
|
|
|
|
// 2 rails: full-height vertical boxes at x = ±(halfW - halfRail).
|
|
|
|
|
|
float railX = halfW - halfRail;
|
|
|
|
|
|
float railCY = height * 0.5f;
|
|
|
|
|
|
addBox( railX, railCY, 0, halfRail, height * 0.5f, halfRail);
|
|
|
|
|
|
addBox(-railX, railCY, 0, halfRail, height * 0.5f, halfRail);
|
|
|
|
|
|
// N rungs: horizontal boxes between rails, evenly spaced.
|
|
|
|
|
|
// First rung is rungSpacing/2 from the bottom; last is the
|
|
|
|
|
|
// same distance from the top — keeps the ladder symmetric.
|
|
|
|
|
|
// Rung interior length is width - 2*railT (between the rails).
|
|
|
|
|
|
float rungLen = width - 2 * railT;
|
|
|
|
|
|
float halfRungLen = rungLen * 0.5f;
|
|
|
|
|
|
float rungSpacing = height / static_cast<float>(rungs + 1);
|
|
|
|
|
|
for (int r = 0; r < rungs; ++r) {
|
|
|
|
|
|
float rungCY = (r + 1) * rungSpacing;
|
|
|
|
|
|
addBox(0, rungCY, 0, halfRungLen, halfRung, halfRung);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfW, 0.0f, -halfRail);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfW, height, halfRail);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-ladder: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" size : %.3f wide × %.3f tall\n", width, height);
|
|
|
|
|
|
std::printf(" rails : 2 × %.3f square (full height)\n", railT);
|
|
|
|
|
|
std::printf(" rungs : %d × %.3f (spacing %.3f)\n",
|
|
|
|
|
|
rungs, rungT, rungSpacing);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 07:25:11 -07:00
|
|
|
|
int handleBed(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Bed: 7-box bedroom prop — flat mattress slab, tall
|
|
|
|
|
|
// headboard at one end, short shorter footboard at the
|
|
|
|
|
|
// other, 4 corner legs, and a small pillow box at the
|
|
|
|
|
|
// headboard end. Pairs with --gen-mesh-table /
|
|
|
|
|
|
// --gen-mesh-bookshelf for inn rooms, manor bedrooms,
|
|
|
|
|
|
// barracks. The 42nd procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float length = 2.0f; // along Z (head-to-foot)
|
|
|
|
|
|
float width = 1.2f; // along X
|
|
|
|
|
|
float legHeight = 0.30f;
|
|
|
|
|
|
float matThick = 0.20f;
|
|
|
|
|
|
float headH = 1.0f; // headboard height above mattress
|
|
|
|
|
|
float footH = 0.4f; // footboard height above mattress
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { length = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { legHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { matThick = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { headH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { footH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (length <= 0 || width <= 0 || legHeight <= 0 ||
|
|
|
|
|
|
matThick <= 0 || headH <= 0 || footH <= 0) {
|
|
|
|
|
|
std::fprintf(stderr, "gen-mesh-bed: all dims must be > 0\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfL = length * 0.5f;
|
|
|
|
|
|
float halfW = width * 0.5f;
|
|
|
|
|
|
float legT = std::min(width, length) * 0.06f; // square cross-section
|
|
|
|
|
|
float halfLeg = legT * 0.5f;
|
|
|
|
|
|
// Head end is at +Z, foot end at -Z.
|
|
|
|
|
|
// 4 legs: one per corner, inset from edges by half a leg-thickness.
|
|
|
|
|
|
float legX = halfW - halfLeg;
|
|
|
|
|
|
float legZ = halfL - halfLeg;
|
|
|
|
|
|
float legCY = legHeight * 0.5f;
|
|
|
|
|
|
addBox( legX, legCY, legZ, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
addBox(-legX, legCY, legZ, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
addBox( legX, legCY, -legZ, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
addBox(-legX, legCY, -legZ, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
// Mattress: spans full width × length, sits on top of legs.
|
|
|
|
|
|
float matBottomY = legHeight;
|
|
|
|
|
|
float matCY = matBottomY + matThick * 0.5f;
|
|
|
|
|
|
addBox(0, matCY, 0, halfW, matThick * 0.5f, halfL);
|
|
|
|
|
|
// Headboard: tall thin slab at +Z end, spanning full width.
|
|
|
|
|
|
// Sits on top of the mattress base (its bottom is at matBottomY).
|
|
|
|
|
|
float headThick = legT * 1.4f;
|
|
|
|
|
|
float headCY = matBottomY + headH * 0.5f;
|
|
|
|
|
|
addBox(0, headCY, halfL - headThick * 0.5f,
|
|
|
|
|
|
halfW, headH * 0.5f, headThick * 0.5f);
|
|
|
|
|
|
// Footboard: shorter slab at -Z end.
|
|
|
|
|
|
float footCY = matBottomY + footH * 0.5f;
|
|
|
|
|
|
addBox(0, footCY, -halfL + headThick * 0.5f,
|
|
|
|
|
|
halfW, footH * 0.5f, headThick * 0.5f);
|
|
|
|
|
|
// Pillow: small box on the mattress, near the headboard end.
|
|
|
|
|
|
float pillowW = halfW * 1.6f; // 80% of mattress width
|
|
|
|
|
|
float pillowL = halfL * 0.25f; // ~12.5% of mattress length
|
|
|
|
|
|
float pillowH = matThick * 0.5f;
|
|
|
|
|
|
float pillowCY = matBottomY + matThick + pillowH * 0.5f;
|
|
|
|
|
|
float pillowZ = halfL - pillowL - headThick;
|
|
|
|
|
|
addBox(0, pillowCY, pillowZ,
|
|
|
|
|
|
pillowW * 0.5f, pillowH * 0.5f, pillowL * 0.5f);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = matBottomY + std::max({matThick + pillowH, headH, footH});
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfW, 0.0f, -halfL);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfW, totalH, halfL);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-bed: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" size : %.3f x %.3f x %.3f (W x H x L)\n",
|
|
|
|
|
|
width, totalH, length);
|
|
|
|
|
|
std::printf(" mattress : %.3f thick at y=%.3f\n",
|
|
|
|
|
|
matThick, matBottomY);
|
|
|
|
|
|
std::printf(" headboard : %.3f tall (foot %.3f tall)\n",
|
|
|
|
|
|
headH, footH);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 07:12:46 -07:00
|
|
|
|
int handleLamppost(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Lamppost: 4-box urban prop — square base plinth, tall
|
|
|
|
|
|
// vertical pole, lantern body box around the pole top,
|
|
|
|
|
|
// and a small cap box on top. Useful for streets, plazas,
|
|
|
|
|
|
// taverns, anywhere that wants explicit lighting fixtures.
|
|
|
|
|
|
// The 41st procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float postHeight = 3.0f;
|
|
|
|
|
|
float postThickness = 0.12f;
|
|
|
|
|
|
float baseSize = 0.4f;
|
|
|
|
|
|
float lanternSize = 0.35f;
|
|
|
|
|
|
float lanternHeight = 0.5f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { postThickness = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { baseSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { lanternSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { lanternHeight = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (postHeight <= 0 || postThickness <= 0 || baseSize <= 0 ||
|
|
|
|
|
|
lanternSize <= 0 || lanternHeight <= 0 ||
|
|
|
|
|
|
postThickness >= baseSize || postThickness >= lanternSize) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-lamppost: dims > 0; post must fit in base & lantern\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Base plinth: low square slab at the floor.
|
|
|
|
|
|
float baseHeight = baseSize * 0.4f;
|
|
|
|
|
|
float halfBase = baseSize * 0.5f;
|
|
|
|
|
|
addBox(0, baseHeight * 0.5f, 0,
|
|
|
|
|
|
halfBase, baseHeight * 0.5f, halfBase);
|
|
|
|
|
|
// Vertical pole: thin square box from top of base to top.
|
|
|
|
|
|
float poleBottomY = baseHeight;
|
|
|
|
|
|
float poleTopY = baseHeight + postHeight;
|
|
|
|
|
|
float poleCY = (poleBottomY + poleTopY) * 0.5f;
|
|
|
|
|
|
float halfPole = postThickness * 0.5f;
|
|
|
|
|
|
addBox(0, poleCY, 0,
|
|
|
|
|
|
halfPole, postHeight * 0.5f, halfPole);
|
|
|
|
|
|
// Lantern body: box centred on the top of the pole; bottom
|
|
|
|
|
|
// of the box overlaps the pole so the lamp visually 'caps'
|
|
|
|
|
|
// the pole rather than just floating above it.
|
|
|
|
|
|
float halfLantern = lanternSize * 0.5f;
|
|
|
|
|
|
float lanternBottomY = poleTopY - lanternHeight * 0.3f;
|
|
|
|
|
|
float lanternCY = lanternBottomY + lanternHeight * 0.5f;
|
|
|
|
|
|
addBox(0, lanternCY, 0,
|
|
|
|
|
|
halfLantern, lanternHeight * 0.5f, halfLantern);
|
|
|
|
|
|
// Cap: thin square plate on top of the lantern. Slightly
|
|
|
|
|
|
// wider than the lantern body so the cap reads as an awning.
|
|
|
|
|
|
float capH = lanternHeight * 0.18f;
|
|
|
|
|
|
float capSize = lanternSize * 1.15f;
|
|
|
|
|
|
float halfCap = capSize * 0.5f;
|
|
|
|
|
|
float capCY = lanternBottomY + lanternHeight + capH * 0.5f;
|
|
|
|
|
|
addBox(0, capCY, 0,
|
|
|
|
|
|
halfCap, capH * 0.5f, halfCap);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float totalH = capCY + capH * 0.5f;
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfBase, 0.0f, -halfBase);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfBase, totalH, halfBase);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-lamppost: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" total H : %.3f\n", totalH);
|
|
|
|
|
|
std::printf(" base : %.3f square × %.3f tall\n",
|
|
|
|
|
|
baseSize, baseHeight);
|
|
|
|
|
|
std::printf(" pole : %.3f square × %.3f tall\n",
|
|
|
|
|
|
postThickness, postHeight);
|
|
|
|
|
|
std::printf(" lantern : %.3f square × %.3f tall (with cap)\n",
|
|
|
|
|
|
lanternSize, lanternHeight);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 06:58:38 -07:00
|
|
|
|
int handleTable(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Table: 5 boxes — flat tabletop slab on top of 4 vertical
|
|
|
|
|
|
// legs at each corner. Thinnest of the furniture meshes,
|
|
|
|
|
|
// pairs naturally with --gen-mesh-bench / --gen-mesh-throne
|
|
|
|
|
|
// for taverns and dining halls. The 40th procedural mesh
|
|
|
|
|
|
// primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float width = 1.6f; // along X
|
|
|
|
|
|
float depth = 1.0f; // along Z
|
|
|
|
|
|
float height = 0.85f; // along Y (top of tabletop)
|
|
|
|
|
|
float legT = 0.10f; // leg thickness (square cross-section)
|
|
|
|
|
|
float topT = 0.06f; // tabletop thickness
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { depth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { legT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { topT = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (width <= 0 || depth <= 0 || height <= 0 || legT <= 0 ||
|
|
|
|
|
|
topT <= 0 || legT * 2 > width || legT * 2 > depth ||
|
|
|
|
|
|
topT >= height) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-table: dims > 0; legT must fit in width/depth; topT < height\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*hx, f.du.y*hy, f.du.z*hz);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*hx, f.dv.y*hy, f.dv.z*hz);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
float halfW = width * 0.5f;
|
|
|
|
|
|
float halfD = depth * 0.5f;
|
|
|
|
|
|
float halfLeg = legT * 0.5f;
|
|
|
|
|
|
float legHeight = height - topT;
|
|
|
|
|
|
// Tabletop: spans full width × depth, sits at y=height-topT to y=height.
|
|
|
|
|
|
addBox(0, height - topT * 0.5f, 0,
|
|
|
|
|
|
halfW, topT * 0.5f, halfD);
|
|
|
|
|
|
// 4 legs: one at each corner, inset by legT/2 from the edge.
|
|
|
|
|
|
float legCY = legHeight * 0.5f;
|
|
|
|
|
|
float legX = halfW - halfLeg;
|
|
|
|
|
|
float legZ = halfD - halfLeg;
|
|
|
|
|
|
addBox( legX, legCY, legZ, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
addBox(-legX, legCY, legZ, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
addBox( legX, legCY, -legZ, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
addBox(-legX, legCY, -legZ, halfLeg, legHeight * 0.5f, halfLeg);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfW, 0.0f, -halfD);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfW, height, halfD);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-table: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" size : %.3f x %.3f x %.3f\n", width, height, depth);
|
|
|
|
|
|
std::printf(" legs : 4 × %.3f square (%.3f tall)\n",
|
|
|
|
|
|
legT, legHeight);
|
|
|
|
|
|
std::printf(" top thick : %.3f\n", topT);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 06:39:39 -07:00
|
|
|
|
int handleBookshelf(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Bookshelf: cabinet (5 panels: back / left / right / top /
|
|
|
|
|
|
// bottom) divided by N-1 horizontal shelves, with rows of
|
|
|
|
|
|
// thin "book" boxes at varying heights on each shelf.
|
|
|
|
|
|
// Books sway in width and height pseudo-randomly so the
|
|
|
|
|
|
// shelf doesn't read as a perfect grid. The 39th procedural
|
|
|
|
|
|
// mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float width = 1.5f;
|
|
|
|
|
|
float height = 2.0f;
|
|
|
|
|
|
float depth = 0.4f;
|
|
|
|
|
|
int shelves = 4;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { depth = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { shelves = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (width <= 0 || height <= 0 || depth <= 0 ||
|
|
|
|
|
|
shelves < 2 || shelves > 12) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-bookshelf: dims > 0; shelves must be 2..12\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}},
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}},
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}},
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}},
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}},
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du = glm::vec3(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv = glm::vec3(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base + 1, base + 2, base, base + 2, base + 3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Cabinet skin: thickness scales with the smaller cabinet
|
|
|
|
|
|
// dimension so the shelf reads at any size without becoming
|
|
|
|
|
|
// either too chunky or too flimsy.
|
|
|
|
|
|
float panelT = std::min(width, depth) * 0.06f;
|
|
|
|
|
|
float halfW = width * 0.5f;
|
|
|
|
|
|
float halfD = depth * 0.5f;
|
|
|
|
|
|
// Bottom + top panels span full width and depth.
|
|
|
|
|
|
addBox(0, panelT * 0.5f, 0,
|
|
|
|
|
|
halfW, panelT * 0.5f, halfD);
|
|
|
|
|
|
addBox(0, height - panelT * 0.5f, 0,
|
|
|
|
|
|
halfW, panelT * 0.5f, halfD);
|
|
|
|
|
|
// Left + right side panels span between bottom and top panels.
|
|
|
|
|
|
float sideCY = (panelT + (height - panelT)) * 0.5f;
|
|
|
|
|
|
float sideHY = (height - 2 * panelT) * 0.5f;
|
|
|
|
|
|
addBox(-halfW + panelT * 0.5f, sideCY, 0,
|
|
|
|
|
|
panelT * 0.5f, sideHY, halfD);
|
|
|
|
|
|
addBox( halfW - panelT * 0.5f, sideCY, 0,
|
|
|
|
|
|
panelT * 0.5f, sideHY, halfD);
|
|
|
|
|
|
// Back panel — thin slab at the rear of the cabinet.
|
|
|
|
|
|
addBox(0, sideCY, -halfD + panelT * 0.5f,
|
|
|
|
|
|
halfW - panelT, sideHY, panelT * 0.5f);
|
|
|
|
|
|
// Horizontal shelves divide the interior into 'shelves' bays.
|
|
|
|
|
|
// shelf[0] is the cabinet bottom, shelf[shelves] is the top —
|
|
|
|
|
|
// we only emit the (shelves-1) interior shelves between them.
|
|
|
|
|
|
float interiorTop = height - panelT;
|
|
|
|
|
|
float interiorBottom = panelT;
|
|
|
|
|
|
float bayHeight = (interiorTop - interiorBottom) /
|
|
|
|
|
|
static_cast<float>(shelves);
|
|
|
|
|
|
float shelfT = panelT; // shelf thickness matches panel skin
|
|
|
|
|
|
float interiorHalfW = halfW - panelT;
|
|
|
|
|
|
for (int s = 1; s < shelves; ++s) {
|
|
|
|
|
|
float shelfCY = interiorBottom + s * bayHeight - shelfT * 0.5f;
|
|
|
|
|
|
addBox(0, shelfCY, 0,
|
|
|
|
|
|
interiorHalfW, shelfT * 0.5f, halfD - panelT * 0.5f);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Books: per-bay row of thin boxes leaning along the shelf.
|
|
|
|
|
|
// Pseudo-random width/height variation seeded by bay index so
|
|
|
|
|
|
// re-generating the same shelf gives the same layout.
|
|
|
|
|
|
auto rngStep = [](uint32_t& s) {
|
|
|
|
|
|
s ^= s << 13; s ^= s >> 17; s ^= s << 5;
|
|
|
|
|
|
return s;
|
|
|
|
|
|
};
|
|
|
|
|
|
int totalBooks = 0;
|
|
|
|
|
|
for (int s = 0; s < shelves; ++s) {
|
|
|
|
|
|
// Bottom Y of this bay's books = top of the shelf below.
|
|
|
|
|
|
float bayBottomY = (s == 0) ? interiorBottom
|
|
|
|
|
|
: interiorBottom + s * bayHeight;
|
|
|
|
|
|
float bayTopY = interiorBottom + (s + 1) * bayHeight - shelfT;
|
|
|
|
|
|
if (s == shelves - 1) bayTopY = interiorTop - shelfT;
|
|
|
|
|
|
float availableH = bayTopY - bayBottomY;
|
|
|
|
|
|
if (availableH < bayHeight * 0.3f) continue;
|
|
|
|
|
|
// Lay books from left to right with narrow gaps. Variable
|
|
|
|
|
|
// book widths are 50–120% of nominal — yields ~6 books per
|
|
|
|
|
|
// bay at default size.
|
|
|
|
|
|
float nominalBookW = bayHeight * 0.18f;
|
|
|
|
|
|
float bookHalfD = (halfD - panelT) * 0.7f;
|
|
|
|
|
|
uint32_t rng = static_cast<uint32_t>(s * 0x9E3779B9u + 1);
|
|
|
|
|
|
float cursor = -interiorHalfW + nominalBookW * 0.6f;
|
|
|
|
|
|
while (cursor + nominalBookW < interiorHalfW) {
|
|
|
|
|
|
float wScale = 0.5f + (rngStep(rng) & 0xFFFF) / 65535.0f * 0.7f;
|
|
|
|
|
|
float hScale = 0.7f + (rngStep(rng) & 0xFFFF) / 65535.0f * 0.3f;
|
|
|
|
|
|
float bookW = nominalBookW * wScale;
|
|
|
|
|
|
float bookH = availableH * 0.85f * hScale;
|
|
|
|
|
|
if (cursor + bookW > interiorHalfW) break;
|
|
|
|
|
|
addBox(cursor + bookW * 0.5f,
|
|
|
|
|
|
bayBottomY + bookH * 0.5f,
|
|
|
|
|
|
0,
|
|
|
|
|
|
bookW * 0.5f, bookH * 0.5f, bookHalfD);
|
|
|
|
|
|
cursor += bookW + nominalBookW * 0.05f;
|
|
|
|
|
|
totalBooks++;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-halfW, 0.0f, -halfD);
|
|
|
|
|
|
wom.boundMax = glm::vec3( halfW, height, halfD);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-bookshelf: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" size : %.3f x %.3f x %.3f\n", width, height, depth);
|
|
|
|
|
|
std::printf(" shelves : %d (%d books across all bays)\n",
|
|
|
|
|
|
shelves, totalBooks);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 10:25:38 -07:00
|
|
|
|
int handleTent(int& i, int argc, char** argv) {
|
|
|
|
|
|
// A-frame canvas tent: ridge running along X from
|
|
|
|
|
|
// (-L/2, H, 0) to (+L/2, H, 0); rectangular footprint LxW
|
|
|
|
|
|
// on the ground; two sloped roof panels meeting at the ridge
|
|
|
|
|
|
// and two triangular gables closing the ends. Optionally a
|
|
|
|
|
|
// simple inverted-V door notch is cut from the +X gable so
|
|
|
|
|
|
// there is a visible entrance. Watertight bottom face is
|
|
|
|
|
|
// included so the model is a closed solid for collision
|
|
|
|
|
|
// baking. The 53rd procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float length = 1.6f;
|
|
|
|
|
|
float width = 1.0f;
|
|
|
|
|
|
float height = 0.9f;
|
|
|
|
|
|
float doorH = 0.5f;
|
|
|
|
|
|
float doorW = 0.4f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { length = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { width = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { height = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { doorH = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { doorW = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (length <= 0 || width <= 0 || height <= 0 ||
|
|
|
|
|
|
doorH < 0 || doorH >= height ||
|
|
|
|
|
|
doorW < 0 || doorW >= width) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-tent: dims > 0; doorH < height; doorW < width\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addV = [&](glm::vec3 p, glm::vec3 n, glm::vec2 uv) -> uint32_t {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = n; vtx.texCoord = uv;
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
return static_cast<uint32_t>(wom.vertices.size() - 1);
|
|
|
|
|
|
};
|
|
|
|
|
|
const float L2 = length * 0.5f;
|
|
|
|
|
|
const float W2 = width * 0.5f;
|
|
|
|
|
|
// Slope normals for the two roof panels — built from the panel
|
|
|
|
|
|
// edge vectors then normalized so adjacent vertices share the
|
|
|
|
|
|
// same per-face shading.
|
|
|
|
|
|
glm::vec3 nBack = glm::normalize(glm::vec3(0.0f, W2, -height));
|
|
|
|
|
|
glm::vec3 nFront = glm::normalize(glm::vec3(0.0f, W2, height));
|
|
|
|
|
|
// Back roof panel (faces -Z and +Y): A=(-L2,0,-W2), B=(+L2,0,-W2),
|
|
|
|
|
|
// R1=(+L2,H,0), R0=(-L2,H,0). Quad → 2 triangles, CCW from outside.
|
|
|
|
|
|
{
|
|
|
|
|
|
uint32_t a = addV({-L2, 0, -W2}, nBack, {0, 0});
|
|
|
|
|
|
uint32_t b = addV({+L2, 0, -W2}, nBack, {1, 0});
|
|
|
|
|
|
uint32_t r1 = addV({+L2, height, 0}, nBack, {1, 1});
|
|
|
|
|
|
uint32_t r0 = addV({-L2, height, 0}, nBack, {0, 1});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {a, b, r1, a, r1, r0});
|
|
|
|
|
|
}
|
|
|
|
|
|
// Front roof panel (faces +Z and +Y).
|
|
|
|
|
|
{
|
|
|
|
|
|
uint32_t d = addV({-L2, 0, +W2}, nFront, {0, 0});
|
|
|
|
|
|
uint32_t r0 = addV({-L2, height, 0}, nFront, {0, 1});
|
|
|
|
|
|
uint32_t r1 = addV({+L2, height, 0}, nFront, {1, 1});
|
|
|
|
|
|
uint32_t c = addV({+L2, 0, +W2}, nFront, {1, 0});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {d, r0, r1, d, r1, c});
|
|
|
|
|
|
}
|
|
|
|
|
|
// -X gable (full triangle, no door): A=(-L2,0,-W2), R0=(-L2,H,0),
|
|
|
|
|
|
// D=(-L2,0,+W2). Faces -X.
|
|
|
|
|
|
{
|
|
|
|
|
|
glm::vec3 n(-1, 0, 0);
|
|
|
|
|
|
uint32_t a = addV({-L2, 0, -W2}, n, {0, 0});
|
|
|
|
|
|
uint32_t r0 = addV({-L2, height, 0}, n, {0.5f, 1});
|
|
|
|
|
|
uint32_t d = addV({-L2, 0, +W2}, n, {1, 0});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {a, r0, d});
|
|
|
|
|
|
}
|
|
|
|
|
|
// +X gable: B=(+L2,0,-W2), C=(+L2,0,+W2), R1=(+L2,H,0). Faces +X.
|
|
|
|
|
|
// If doorH>0 we carve out a tapered notch — bottom edge of width
|
2026-05-09 10:45:27 -07:00
|
|
|
|
// doorW, apex on the centerline at height doorH. The remaining
|
|
|
|
|
|
// polygon (B → bl → dt → br → C → R1, going CCW from -X view to
|
|
|
|
|
|
// match the existing gable winding) is triangulated as a fan
|
|
|
|
|
|
// from R1: 4 triangles, no overlap with the door area, every
|
|
|
|
|
|
// vertex referenced by at least one triangle.
|
2026-05-09 10:25:38 -07:00
|
|
|
|
{
|
|
|
|
|
|
glm::vec3 n(+1, 0, 0);
|
|
|
|
|
|
if (doorH > 0 && doorW > 0) {
|
2026-05-09 10:45:27 -07:00
|
|
|
|
uint32_t r1 = addV({+L2, height, 0}, n, {0.5f, 1});
|
2026-05-09 10:25:38 -07:00
|
|
|
|
uint32_t b = addV({+L2, 0, -W2}, n, {0, 0});
|
|
|
|
|
|
uint32_t bl = addV({+L2, 0, -doorW * 0.5f}, n,
|
|
|
|
|
|
{0.5f - doorW / (2 * width), 0});
|
2026-05-09 10:45:27 -07:00
|
|
|
|
uint32_t dt = addV({+L2, doorH, 0}, n,
|
|
|
|
|
|
{0.5f, doorH / height});
|
2026-05-09 10:25:38 -07:00
|
|
|
|
uint32_t br = addV({+L2, 0, +doorW * 0.5f}, n,
|
|
|
|
|
|
{0.5f + doorW / (2 * width), 0});
|
|
|
|
|
|
uint32_t c = addV({+L2, 0, +W2}, n, {1, 0});
|
2026-05-09 10:45:27 -07:00
|
|
|
|
wom.indices.insert(wom.indices.end(), {r1, b, bl});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {r1, bl, dt});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {r1, dt, br});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {r1, br, c});
|
2026-05-09 10:25:38 -07:00
|
|
|
|
} else {
|
|
|
|
|
|
uint32_t b = addV({+L2, 0, -W2}, n, {0, 0});
|
|
|
|
|
|
uint32_t c = addV({+L2, 0, +W2}, n, {1, 0});
|
|
|
|
|
|
uint32_t r1 = addV({+L2, height, 0}, n, {0.5f, 1});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {b, c, r1});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Ground face (faces -Y) so the tent is a closed solid for
|
|
|
|
|
|
// collision baking.
|
|
|
|
|
|
{
|
|
|
|
|
|
glm::vec3 n(0, -1, 0);
|
|
|
|
|
|
uint32_t a = addV({-L2, 0, -W2}, n, {0, 0});
|
|
|
|
|
|
uint32_t b = addV({+L2, 0, -W2}, n, {1, 0});
|
|
|
|
|
|
uint32_t c = addV({+L2, 0, +W2}, n, {1, 1});
|
|
|
|
|
|
uint32_t d = addV({-L2, 0, +W2}, n, {0, 1});
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(), {a, d, c, a, c, b});
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-L2, 0, -W2);
|
|
|
|
|
|
wom.boundMax = glm::vec3(+L2, height, +W2);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-tent: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" footprint : %.3f x %.3f\n", length, width);
|
|
|
|
|
|
std::printf(" height : %.3f (ridge along X)\n", height);
|
|
|
|
|
|
if (doorH > 0 && doorW > 0) {
|
|
|
|
|
|
std::printf(" door : H=%.3f W=%.3f on +X gable\n",
|
|
|
|
|
|
doorH, doorW);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
std::printf(" door : (none)\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-09 10:37:37 -07:00
|
|
|
|
int handleFirepit(int& i, int argc, char** argv) {
|
|
|
|
|
|
// Camp firepit: a ring of N stone cubes around two crossed log
|
|
|
|
|
|
// boxes (one along X, one along Z, slightly raised). Pairs
|
|
|
|
|
|
// naturally with --gen-mesh-tent for outdoor camp set dressing.
|
|
|
|
|
|
// The 54th procedural mesh primitive.
|
|
|
|
|
|
std::string womBase = argv[++i];
|
|
|
|
|
|
float ringR = 0.5f;
|
|
|
|
|
|
int stones = 8;
|
|
|
|
|
|
float stoneSize = 0.10f;
|
|
|
|
|
|
float logLen = 0.45f;
|
|
|
|
|
|
float logThick = 0.05f;
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { ringR = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stones = std::stoi(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { stoneSize = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { logLen = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (i + 1 < argc && argv[i + 1][0] != '-') {
|
|
|
|
|
|
try { logThick = std::stof(argv[++i]); } catch (...) {}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (ringR <= 0 || stoneSize <= 0 || logLen <= 0 || logThick <= 0 ||
|
|
|
|
|
|
stones < 3 || stones > 64) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-firepit: dims > 0; stones must be 3..64\n");
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (womBase.size() >= 4 &&
|
|
|
|
|
|
womBase.substr(womBase.size() - 4) == ".wom") {
|
|
|
|
|
|
womBase = womBase.substr(0, womBase.size() - 4);
|
|
|
|
|
|
}
|
|
|
|
|
|
wowee::pipeline::WoweeModel wom;
|
|
|
|
|
|
wom.name = std::filesystem::path(womBase).stem().string();
|
|
|
|
|
|
wom.version = 3;
|
|
|
|
|
|
auto addBox = [&](float cx, float cy, float cz,
|
|
|
|
|
|
float hx, float hy, float hz) {
|
|
|
|
|
|
struct Face { glm::vec3 n, du, dv; };
|
|
|
|
|
|
const Face faces[6] = {
|
|
|
|
|
|
{{0, 1, 0}, {1, 0, 0}, {0, 0, 1}}, // +Y
|
|
|
|
|
|
{{0,-1, 0}, {1, 0, 0}, {0, 0,-1}}, // -Y
|
|
|
|
|
|
{{1, 0, 0}, {0, 0, 1}, {0, 1, 0}}, // +X
|
|
|
|
|
|
{{-1,0, 0}, {0, 0,-1}, {0, 1, 0}}, // -X
|
|
|
|
|
|
{{0, 0, 1}, {-1,0, 0}, {0, 1, 0}}, // +Z
|
|
|
|
|
|
{{0, 0,-1}, {1, 0, 0}, {0, 1, 0}}, // -Z
|
|
|
|
|
|
};
|
|
|
|
|
|
glm::vec3 c(cx, cy, cz);
|
|
|
|
|
|
glm::vec3 ext(hx, hy, hz);
|
|
|
|
|
|
for (const Face& f : faces) {
|
|
|
|
|
|
glm::vec3 center = c + glm::vec3(f.n.x*hx, f.n.y*hy, f.n.z*hz);
|
|
|
|
|
|
glm::vec3 du(f.du.x*ext.x, f.du.y*ext.y, f.du.z*ext.z);
|
|
|
|
|
|
glm::vec3 dv(f.dv.x*ext.x, f.dv.y*ext.y, f.dv.z*ext.z);
|
|
|
|
|
|
uint32_t base = static_cast<uint32_t>(wom.vertices.size());
|
|
|
|
|
|
auto push = [&](glm::vec3 p, float u, float v) {
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Vertex vtx;
|
|
|
|
|
|
vtx.position = p; vtx.normal = f.n; vtx.texCoord = {u, v};
|
|
|
|
|
|
wom.vertices.push_back(vtx);
|
|
|
|
|
|
};
|
|
|
|
|
|
push(center - du - dv, 0, 0);
|
|
|
|
|
|
push(center + du - dv, 1, 0);
|
|
|
|
|
|
push(center + du + dv, 1, 1);
|
|
|
|
|
|
push(center - du + dv, 0, 1);
|
|
|
|
|
|
wom.indices.insert(wom.indices.end(),
|
|
|
|
|
|
{base, base+1, base+2, base, base+2, base+3});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
// Ring of stones — N axis-aligned cube stones evenly placed
|
|
|
|
|
|
// around the firepit center. Slight Y offset puts them sitting
|
|
|
|
|
|
// on the ground rather than sunk into it.
|
|
|
|
|
|
const float pi = 3.14159265358979f;
|
|
|
|
|
|
for (int s = 0; s < stones; ++s) {
|
|
|
|
|
|
float ang = (2.0f * pi * s) / stones;
|
|
|
|
|
|
float cx = ringR * std::cos(ang);
|
|
|
|
|
|
float cz = ringR * std::sin(ang);
|
|
|
|
|
|
addBox(cx, stoneSize, cz, stoneSize, stoneSize, stoneSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Two crossed logs at center, raised so they sit on the ash
|
|
|
|
|
|
// bed. The two-log cross is the unmistakable visual cue that
|
|
|
|
|
|
// separates a firepit from a generic stone ring.
|
|
|
|
|
|
float logCY = logThick;
|
|
|
|
|
|
addBox(0, logCY, 0, logLen * 0.5f, logThick * 0.5f, logThick * 0.5f);
|
|
|
|
|
|
addBox(0, logCY + logThick, 0, logThick * 0.5f, logThick * 0.5f,
|
|
|
|
|
|
logLen * 0.5f);
|
|
|
|
|
|
wowee::pipeline::WoweeModel::Batch batch;
|
|
|
|
|
|
batch.indexStart = 0;
|
|
|
|
|
|
batch.indexCount = static_cast<uint32_t>(wom.indices.size());
|
|
|
|
|
|
batch.textureIndex = 0;
|
|
|
|
|
|
wom.batches.push_back(batch);
|
|
|
|
|
|
float maxR = std::max(ringR + stoneSize, logLen * 0.5f);
|
|
|
|
|
|
float maxY = std::max(stoneSize * 2.0f, logCY + logThick * 1.5f);
|
|
|
|
|
|
wom.boundMin = glm::vec3(-maxR, 0, -maxR);
|
|
|
|
|
|
wom.boundMax = glm::vec3( maxR, maxY, maxR);
|
|
|
|
|
|
if (!wowee::pipeline::WoweeModelLoader::save(wom, womBase)) {
|
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
|
"gen-mesh-firepit: failed to save %s.wom\n", womBase.c_str());
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
std::printf("Wrote %s.wom\n", womBase.c_str());
|
|
|
|
|
|
std::printf(" ring : R=%.3f, %d stones (%.3f cubes)\n",
|
|
|
|
|
|
ringR, stones, stoneSize);
|
|
|
|
|
|
std::printf(" logs : 2 crossed (len %.3f, thick %.3f)\n",
|
|
|
|
|
|
logLen, logThick);
|
|
|
|
|
|
std::printf(" vertices : %zu\n", wom.vertices.size());
|
|
|
|
|
|
std::printf(" triangles : %zu\n", wom.indices.size() / 3);
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
refactor(editor): extract 12 composite mesh primitives into cli_gen_mesh.cpp
Moves the recently-added composite-prop mesh handlers (rock,
pillar, bridge, tower, house, fountain, statue, altar, portal,
archway, barrel, chest) into their own translation unit. These
12 handlers were the most contiguous block of similar-shaped
mesh code in main.cpp.
Other mesh handlers (--gen-mesh dispatcher, fence, tree, grid,
stairs, disc, tube, capsule, arch, pyramid, from-heightmap,
textured) still live in main.cpp and may be migrated in
subsequent batches.
main.cpp drops 24,270 → 22,681 lines (-1,589). Behavior
verified by re-running rock/chest/archway/fountain.
2026-05-08 22:19:41 -07:00
|
|
|
|
} // namespace
|
|
|
|
|
|
|
2026-05-09 10:32:26 -07:00
|
|
|
|
namespace {
|
|
|
|
|
|
// Table-driven dispatch for every --gen-mesh-* flag. minNextArgs
|
|
|
|
|
|
// is the count of *required* positional args after the flag — used
|
|
|
|
|
|
// as a guard so a bare `--gen-mesh-X` (or `--gen-mesh-X` followed
|
|
|
|
|
|
// only by the next switch) is rejected by the dispatcher even
|
|
|
|
|
|
// without consulting kArgRequired. Every primitive in this file
|
|
|
|
|
|
// must have an entry here OR it will not be reachable from the CLI.
|
|
|
|
|
|
struct MeshEntry {
|
|
|
|
|
|
const char* flag;
|
|
|
|
|
|
int minNextArgs;
|
|
|
|
|
|
int (*fn)(int&, int, char**);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
constexpr MeshEntry kMeshTable[] = {
|
|
|
|
|
|
{"--gen-mesh-textured", 3, handleTextured},
|
|
|
|
|
|
{"--gen-mesh", 2, handleMeshDispatch},
|
|
|
|
|
|
{"--gen-mesh-stairs", 2, handleStairs},
|
|
|
|
|
|
{"--gen-mesh-grid", 2, handleGrid},
|
|
|
|
|
|
{"--gen-mesh-disc", 1, handleDisc},
|
|
|
|
|
|
{"--gen-mesh-tube", 1, handleTube},
|
|
|
|
|
|
{"--gen-mesh-capsule", 1, handleCapsule},
|
|
|
|
|
|
{"--gen-mesh-arch", 1, handleArch},
|
|
|
|
|
|
{"--gen-mesh-pyramid", 1, handlePyramid},
|
|
|
|
|
|
{"--gen-mesh-fence", 1, handleFence},
|
|
|
|
|
|
{"--gen-mesh-tree", 1, handleTree},
|
|
|
|
|
|
{"--gen-mesh-rock", 1, handleRock},
|
|
|
|
|
|
{"--gen-mesh-pillar", 1, handlePillar},
|
|
|
|
|
|
{"--gen-mesh-bridge", 1, handleBridge},
|
|
|
|
|
|
{"--gen-mesh-tower", 1, handleTower},
|
|
|
|
|
|
{"--gen-mesh-house", 1, handleHouse},
|
|
|
|
|
|
{"--gen-mesh-fountain", 1, handleFountain},
|
|
|
|
|
|
{"--gen-mesh-statue", 1, handleStatue},
|
|
|
|
|
|
{"--gen-mesh-altar", 1, handleAltar},
|
|
|
|
|
|
{"--gen-mesh-portal", 1, handlePortal},
|
|
|
|
|
|
{"--gen-mesh-archway", 1, handleArchway},
|
|
|
|
|
|
{"--gen-mesh-archway-double", 1, handleArchwayDouble},
|
|
|
|
|
|
{"--gen-mesh-barrel", 1, handleBarrel},
|
|
|
|
|
|
{"--gen-mesh-chest", 1, handleChest},
|
|
|
|
|
|
{"--gen-mesh-anvil", 1, handleAnvil},
|
|
|
|
|
|
{"--gen-mesh-mushroom", 1, handleMushroom},
|
|
|
|
|
|
{"--gen-mesh-cart", 1, handleCart},
|
|
|
|
|
|
{"--gen-mesh-banner", 1, handleBanner},
|
|
|
|
|
|
{"--gen-mesh-grave", 1, handleGrave},
|
|
|
|
|
|
{"--gen-mesh-bench", 1, handleBench},
|
|
|
|
|
|
{"--gen-mesh-shrine", 1, handleShrine},
|
|
|
|
|
|
{"--gen-mesh-totem", 1, handleTotem},
|
|
|
|
|
|
{"--gen-mesh-cage", 1, handleCage},
|
|
|
|
|
|
{"--gen-mesh-throne", 1, handleThrone},
|
|
|
|
|
|
{"--gen-mesh-coffin", 1, handleCoffin},
|
|
|
|
|
|
{"--gen-mesh-bookshelf", 1, handleBookshelf},
|
|
|
|
|
|
{"--gen-mesh-tent", 1, handleTent},
|
2026-05-09 10:37:37 -07:00
|
|
|
|
{"--gen-mesh-firepit", 1, handleFirepit},
|
2026-05-09 10:32:26 -07:00
|
|
|
|
{"--gen-mesh-table", 1, handleTable},
|
|
|
|
|
|
{"--gen-mesh-lamppost", 1, handleLamppost},
|
|
|
|
|
|
{"--gen-mesh-bed", 1, handleBed},
|
|
|
|
|
|
{"--gen-mesh-ladder", 1, handleLadder},
|
|
|
|
|
|
{"--gen-mesh-well", 1, handleWell},
|
|
|
|
|
|
{"--gen-mesh-signpost", 1, handleSignpost},
|
|
|
|
|
|
{"--gen-mesh-mailbox", 1, handleMailbox},
|
|
|
|
|
|
{"--gen-mesh-tombstone", 1, handleTombstone},
|
|
|
|
|
|
{"--gen-mesh-crate", 1, handleCrate},
|
|
|
|
|
|
{"--gen-mesh-stool", 1, handleStool},
|
|
|
|
|
|
{"--gen-mesh-cauldron", 1, handleCauldron},
|
|
|
|
|
|
{"--gen-mesh-gate", 1, handleGate},
|
|
|
|
|
|
{"--gen-mesh-beehive", 1, handleBeehive},
|
|
|
|
|
|
{"--gen-mesh-weathervane", 1, handleWeathervane},
|
|
|
|
|
|
{"--gen-mesh-scarecrow", 1, handleScarecrow},
|
|
|
|
|
|
{"--gen-mesh-sundial", 1, handleSundial},
|
|
|
|
|
|
{"--gen-mesh-podium", 1, handlePodium},
|
|
|
|
|
|
{"--gen-mesh-brazier", 1, handleBrazier},
|
|
|
|
|
|
};
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
refactor(editor): extract 12 composite mesh primitives into cli_gen_mesh.cpp
Moves the recently-added composite-prop mesh handlers (rock,
pillar, bridge, tower, house, fountain, statue, altar, portal,
archway, barrel, chest) into their own translation unit. These
12 handlers were the most contiguous block of similar-shaped
mesh code in main.cpp.
Other mesh handlers (--gen-mesh dispatcher, fence, tree, grid,
stairs, disc, tube, capsule, arch, pyramid, from-heightmap,
textured) still live in main.cpp and may be migrated in
subsequent batches.
main.cpp drops 24,270 → 22,681 lines (-1,589). Behavior
verified by re-running rock/chest/archway/fountain.
2026-05-08 22:19:41 -07:00
|
|
|
|
bool handleGenMesh(int& i, int argc, char** argv, int& outRc) {
|
2026-05-09 10:32:26 -07:00
|
|
|
|
for (const auto& e : kMeshTable) {
|
|
|
|
|
|
if (std::strcmp(argv[i], e.flag) == 0 && i + e.minNextArgs < argc) {
|
|
|
|
|
|
outRc = e.fn(i, argc, argv);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2026-05-09 10:11:24 -07:00
|
|
|
|
}
|
refactor(editor): extract 12 composite mesh primitives into cli_gen_mesh.cpp
Moves the recently-added composite-prop mesh handlers (rock,
pillar, bridge, tower, house, fountain, statue, altar, portal,
archway, barrel, chest) into their own translation unit. These
12 handlers were the most contiguous block of similar-shaped
mesh code in main.cpp.
Other mesh handlers (--gen-mesh dispatcher, fence, tree, grid,
stairs, disc, tube, capsule, arch, pyramid, from-heightmap,
textured) still live in main.cpp and may be migrated in
subsequent batches.
main.cpp drops 24,270 → 22,681 lines (-1,589). Behavior
verified by re-running rock/chest/archway/fountain.
2026-05-08 22:19:41 -07:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace cli
|
|
|
|
|
|
} // namespace editor
|
|
|
|
|
|
} // namespace wowee
|