20using prototype_detail::Vec3;
22constexpr std::array<PrototypePieceDescriptor, 75> kPieces{{
23 {
"arrow",
"Arrow", 1.0f, 0.18f, 2.0f},
24 {
"box",
"Open Box", 1.5f, 1.0f, 1.5f},
25 {
"coin",
"Coin", 0.8f, 0.12f, 0.8f},
26 {
"cone",
"Cone", 1.0f, 1.5f, 1.0f},
27 {
"cone1",
"Cone Low", 1.2f, 0.8f, 1.2f},
28 {
"cone2",
"Cone Tall", 0.8f, 2.0f, 0.8f},
29 {
"cone3",
"Frustum", 1.0f, 1.5f, 1.0f},
30 {
"cone4",
"Pyramid", 1.2f, 1.5f, 1.2f},
31 {
"cube",
"Cube", 1.0f, 1.0f, 1.0f},
32 {
"cube4",
"Low Block", 1.0f, 0.5f, 1.0f},
33 {
"cube5",
"Tall Block", 1.0f, 2.0f, 1.0f},
34 {
"cube6",
"Wide Block", 2.0f, 1.0f, 1.0f},
35 {
"cube7",
"Platform Block", 2.0f, 0.35f, 2.0f},
36 {
"cube8",
"Beam Block", 3.0f, 0.4f, 0.4f},
37 {
"cube9",
"Inset Block", 1.2f, 1.2f, 1.2f},
38 {
"cylinder",
"Cylinder", 1.0f, 1.0f, 1.0f},
39 {
"cylinder1",
"Cylinder Detailed", 1.0f, 1.0f, 1.0f},
40 {
"cylinder2",
"Cylinder Low", 1.3f, 0.5f, 1.3f},
41 {
"cylinder3",
"Cylinder Tall", 0.8f, 2.0f, 0.8f},
42 {
"cylinder4",
"Hex Cylinder", 1.0f, 1.0f, 1.0f},
43 {
"cylinder5",
"Octagonal Column", 1.0f, 2.0f, 1.0f},
44 {
"door",
"Door", 1.0f, 2.1f, 0.15f},
45 {
"door1",
"Door Framed", 1.2f, 2.2f, 0.2f},
46 {
"door2",
"Door Double", 1.8f, 2.2f, 0.18f},
47 {
"door3",
"Door Reinforced", 1.2f, 2.2f, 0.22f},
48 {
"fence-edge",
"Fence Edge", 0.4f, 1.2f, 0.4f},
49 {
"fence-wood",
"Wood Fence", 2.5f, 1.2f, 0.2f},
50 {
"fence",
"Fence", 2.5f, 1.2f, 0.16f},
51 {
"fence2",
"Fence Dense", 2.5f, 1.4f, 0.18f},
52 {
"fence3",
"Fence Low", 2.5f, 0.8f, 0.18f},
53 {
"ground-corner",
"Ground Corner", 2.0f, 0.15f, 2.0f},
54 {
"ground",
"Ground Tile", 2.0f, 0.12f, 2.0f},
55 {
"ground1",
"Ground Slab", 2.0f, 0.3f, 2.0f},
56 {
"key",
"Key", 1.8f, 0.12f, 0.7f},
57 {
"ladder",
"Ladder", 1.0f, 2.5f, 0.16f},
58 {
"ladder1",
"Wide Ladder", 1.4f, 3.0f, 0.18f},
59 {
"pillar",
"Pillar", 0.8f, 2.5f, 0.8f},
60 {
"pillar1",
"Pillar Square", 0.8f, 2.5f, 0.8f},
61 {
"pillar2",
"Pillar Round", 0.8f, 2.5f, 0.8f},
62 {
"pillar3",
"Pillar Heavy", 1.0f, 3.0f, 1.0f},
63 {
"pillar4",
"Pillar Tapered", 0.9f, 2.5f, 0.9f},
64 {
"railing-edge",
"Railing Edge", 0.35f, 1.0f, 0.35f},
65 {
"railing",
"Railing", 2.5f, 1.0f, 0.12f},
66 {
"ramp",
"Ramp", 2.0f, 1.0f, 2.0f},
67 {
"ramp1",
"Ramp Wide", 3.0f, 1.2f, 2.0f},
68 {
"sphere",
"Sphere", 1.0f, 1.0f, 1.0f},
69 {
"sphere1",
"Sphere Detailed", 1.0f, 1.0f, 1.0f},
70 {
"sphere2",
"Ellipsoid", 1.2f, 1.8f, 1.2f},
71 {
"spike",
"Spike", 0.5f, 1.2f, 0.5f},
72 {
"spikes-big",
"Spikes Big", 2.0f, 1.2f, 2.0f},
73 {
"spikes-small",
"Spikes Small", 2.0f, 0.6f, 2.0f},
74 {
"stairs-corner",
"Stairs Corner", 2.0f, 1.5f, 2.0f},
75 {
"stairs-corner1",
"Stairs Corner Wide", 3.0f, 1.5f, 3.0f},
76 {
"stairs",
"Stairs", 2.0f, 1.5f, 2.5f},
77 {
"stairs1",
"Stairs Solid", 2.0f, 1.5f, 2.5f},
78 {
"stairs2",
"Stairs Low", 2.0f, 0.8f, 2.5f},
79 {
"toggle-switch",
"Toggle Switch", 0.8f, 0.6f, 0.5f},
80 {
"torus",
"Torus", 1.4f, 0.35f, 1.4f},
81 {
"wall-corner-bottom",
"Wall Corner Bottom", 2.0f, 1.0f, 2.0f},
82 {
"wall-corner-bottom1",
"Wall Corner Bottom Thin", 2.0f, 0.6f, 2.0f},
83 {
"wall-corner-bottom2",
"Wall Corner Bottom Heavy", 2.0f, 1.2f, 2.0f},
84 {
"wall-corner-top",
"Wall Corner Top", 2.0f, 1.0f, 2.0f},
85 {
"wall-corner-top1",
"Wall Corner Top Thin", 2.0f, 0.6f, 2.0f},
86 {
"wall-corner",
"Wall Corner", 2.0f, 3.0f, 2.0f},
87 {
"wall-corner1",
"Wall Corner Short", 2.0f, 2.0f, 2.0f},
88 {
"wall-corner4",
"Wall Corner Heavy", 2.0f, 3.0f, 2.0f},
89 {
"wall-door",
"Wall Door", 3.0f, 3.0f, 0.25f},
90 {
"wall-door1",
"Wall Door Wide", 4.0f, 3.0f, 0.25f},
91 {
"wall-door2",
"Wall Door Tall", 3.0f, 4.0f, 0.25f},
92 {
"wall-window",
"Wall Window", 3.0f, 3.0f, 0.25f},
93 {
"wall-window1",
"Wall Window Wide", 4.0f, 3.0f, 0.25f},
94 {
"wall",
"Wall", 3.0f, 3.0f, 0.25f},
95 {
"wall1",
"Wall Half", 3.0f, 1.5f, 0.25f},
96 {
"window",
"Window", 1.2f, 1.2f, 0.15f},
97 {
"window1",
"Window Wide", 1.8f, 1.2f, 0.15f},
100const PrototypePieceDescriptor* findPiece(std::string_view
id) {
101 const auto it = std::find_if(kPieces.begin(), kPieces.end(), [
id](
const auto& piece) { return piece.id == id; });
102 return it == kPieces.end() ? nullptr : &*it;
115float positiveParam(
const Params&
params,
const char*
name,
float fallback) {
119void addFrame(BuildContext&
c,
float openingWidth,
float openingHeight,
float sill,
const char*
group) {
120 const float side = std::max(
c.t, (
c.w - openingWidth) * 0.5f);
121 addBox(
c.mesh, {-c.w * 0.5f + side * 0.5f, c.h * 0.5f, 0}, {side, c.h, c.d},
group);
122 addBox(
c.mesh, {c.w * 0.5f - side * 0.5f, c.h * 0.5f, 0}, {side, c.h, c.d},
group);
123 if (sill > 0.0f)
addBox(
c.mesh, {0, sill * 0.5f, 0}, {openingWidth, sill, c.d},
group);
124 const float top = std::max(
c.t,
c.h - sill - openingHeight);
125 addBox(
c.mesh, {0, c.h - top * 0.5f, 0}, {openingWidth, top, c.d},
group);
128void addCorner(BuildContext&
c,
const char*
group) {
129 addBox(
c.mesh, {0, c.h * 0.5f, -c.d * 0.5f + c.t * 0.5f}, {c.w, c.h, c.t},
group);
130 addBox(
c.mesh, {-c.w * 0.5f + c.t * 0.5f, c.h * 0.5f, 0}, {c.t, c.h, c.d},
group);
133void addFence(BuildContext&
c,
int posts,
int rails,
bool pickets) {
134 posts = std::max(2, posts);
135 for (
int i = 0; i < posts; ++i) {
136 const float x = -
c.w * 0.5f +
c.w * float(i) / float(posts - 1);
137 addBox(
c.mesh, {x, c.h * 0.5f, 0}, {c.t, c.h, c.d},
"posts");
139 for (
int i = 0; i < rails; ++i) {
140 const float y =
c.h * float(i + 1) / float(rails + 1);
141 addBox(
c.mesh, {0, y, 0}, {c.w, c.t, c.d * 0.75f},
"rails");
144 const int count = std::max(3, posts * 2 - 1);
145 for (
int i = 1; i <
count - 1; ++i) {
146 const float x = -
c.w * 0.5f +
c.w * float(i) / float(
count - 1);
147 addBox(
c.mesh, {x, c.h * 0.45f, 0}, {c.t * 0.55f, c.h * 0.9f, c.d * 0.6f},
"pickets");
152void addLadder(BuildContext&
c) {
153 addBox(
c.mesh, {-c.w * 0.5f + c.t * 0.5f, c.h * 0.5f, 0}, {c.t, c.h, c.d},
"rails");
154 addBox(
c.mesh, {c.w * 0.5f - c.t * 0.5f, c.h * 0.5f, 0}, {c.t, c.h, c.d},
"rails");
155 for (
int i = 1; i <
c.steps; ++i) {
156 addBox(
c.mesh, {0, c.h * float(i) / float(c.steps), 0}, {c.w, c.t * 0.75f, c.d * 0.75f},
"rungs");
160void addStairs(BuildContext&
c,
bool corner) {
161 for (
int i = 0; i <
c.steps; ++i) {
162 const float fraction = float(i + 1) / float(
c.steps);
163 const float stepDepth =
c.d / float(
c.steps);
164 const float z =
c.d * 0.5f - stepDepth * (float(i) + 0.5f);
165 addBox(
c.mesh, {0, c.h * fraction * 0.5f, z}, {c.w, c.h * fraction, stepDepth},
"steps");
167 const float x =
c.w * 0.5f - stepDepth * (float(i) + 0.5f);
168 addBox(
c.mesh, {x, c.h * fraction * 0.5f, 0}, {stepDepth, c.h * fraction, c.d},
"corner-steps");
173void buildPiece(std::string_view
id, BuildContext&
c) {
175 addBox(
c.mesh, {0, c.h * 0.5f, -c.d * 0.15f}, {c.w * 0.22f, c.h, c.d * 0.7f},
"shaft");
176 addWedge(
c.mesh, {0, c.h * 0.5f, c.d * 0.35f}, {c.w, c.h, c.d * 0.3f},
false,
"head");
177 }
else if (
id ==
"box") {
178 addBox(
c.mesh, {0, c.t * 0.5f, 0}, {c.w, c.t, c.d},
"base");
179 addBox(
c.mesh, {-c.w * 0.5f + c.t * 0.5f, c.h * 0.5f, 0}, {c.t, c.h, c.d},
"walls");
180 addBox(
c.mesh, {c.w * 0.5f - c.t * 0.5f, c.h * 0.5f, 0}, {c.t, c.h, c.d},
"walls");
181 addBox(
c.mesh, {0, c.h * 0.5f, -c.d * 0.5f + c.t * 0.5f}, {c.w, c.h, c.t},
"walls");
182 addBox(
c.mesh, {0, c.h * 0.5f, c.d * 0.5f - c.t * 0.5f}, {c.w, c.h, c.t},
"walls");
183 }
else if (
id ==
"coin") {
184 addCylinder(
c.mesh, {0, c.h * 0.5f, 0},
c.w * 0.5f,
c.w * 0.5f,
c.h,
c.detail,
"coin");
185 }
else if (
id.starts_with(
"cone")) {
186 int variant =
id ==
"cone" ? 0 :
id.back() -
'0';
188 const float top =
variant == 3 ?
c.w * 0.22f : 0.0f;
190 }
else if (
id.starts_with(
"cube")) {
191 addBox(
c.mesh, {0, c.h * 0.5f, 0}, {c.w, c.h, c.d},
"body");
192 if (
id ==
"cube9")
addBox(
c.mesh, {0, c.h + c.t * 0.25f, 0}, {c.w * 0.72f, c.t * 0.5f, c.d * 0.72f},
"inset");
193 }
else if (
id.starts_with(
"cylinder")) {
194 const int variant =
id ==
"cylinder" ? 0 :
id.back() -
'0';
197 }
else if (
id.starts_with(
"door")) {
198 addBox(
c.mesh, {0, c.h * 0.5f, 0}, {c.w, c.h, c.d},
"panel");
200 addFrame(
c,
c.w - 2.0f *
c.t,
c.h - 2.0f *
c.t, 0.0f,
"frame");
201 if (
id ==
"door3")
addBox(
c.mesh, {0, c.h * 0.55f, c.d}, {c.w * 0.9f, c.t, c.t},
"brace");
203 }
else if (
id.starts_with(
"fence")) {
204 const bool edge =
id ==
"fence-edge";
206 addBox(
c.mesh, {0, c.h * 0.5f, 0}, {c.w, c.h, c.d},
"post");
208 addFence(
c,
id ==
"fence2" ? 7 : 5,
id ==
"fence3" ? 1 : 2,
id ==
"fence-wood");
209 }
else if (
id.starts_with(
"ground")) {
210 if (
id ==
"ground-corner") {
211 addBox(
c.mesh, {0, c.h * 0.5f, -c.d * 0.25f}, {c.w, c.h, c.d * 0.5f},
"ground");
212 addBox(
c.mesh, {-c.w * 0.25f, c.h * 0.5f, c.d * 0.25f}, {c.w * 0.5f, c.h, c.d * 0.5f},
"ground");
214 addBox(
c.mesh, {0, c.h * 0.5f, 0}, {c.w, c.h, c.d},
"ground");
215 }
else if (
id ==
"key") {
216 addTorus(
c.mesh, {-c.w * 0.35f, c.h * 0.5f, 0},
c.d * 0.28f,
c.t * 0.45f,
c.detail, 6,
"ring");
217 addBox(
c.mesh, {c.w * 0.1f, c.h * 0.5f, 0}, {c.w * 0.65f, c.t, c.t},
"shaft");
218 addBox(
c.mesh, {c.w * 0.38f, c.h * 0.5f, c.d * 0.13f}, {c.t, c.t, c.d * 0.35f},
"teeth");
219 }
else if (
id.starts_with(
"ladder")) {
221 }
else if (
id.starts_with(
"pillar")) {
222 const bool square =
id ==
"pillar1";
224 addBox(
c.mesh, {0, c.h * 0.5f, 0}, {c.w * 0.65f, c.h, c.d * 0.65f},
"shaft");
226 addCylinder(
c.mesh, {0, c.h * 0.5f, 0},
c.w * 0.3f,
id ==
"pillar4" ?
c.w * 0.22f :
c.
w * 0.3f,
c.
h,
227 id ==
"pillar2" ?
c.
detail : 8,
"shaft");
228 addBox(
c.mesh, {0, c.t * 0.5f, 0}, {c.w, c.t, c.d},
"base");
229 addBox(
c.mesh, {0, c.h - c.t * 0.5f, 0}, {c.w, c.t, c.d},
"capital");
230 }
else if (
id.starts_with(
"railing")) {
231 if (
id ==
"railing-edge")
232 addBox(
c.mesh, {0, c.h * 0.5f, 0}, {c.w, c.h, c.d},
"post");
234 addFence(
c, 5, 1,
false);
235 }
else if (
id.starts_with(
"ramp")) {
236 addWedge(
c.mesh, {0, c.h * 0.5f, 0}, {c.w, c.h, c.d},
false,
"ramp");
237 }
else if (
id.starts_with(
"sphere")) {
238 addSphere(
c.mesh, {0, c.h * 0.5f, 0}, {c.w * 0.5f, c.h * 0.5f, c.d * 0.5f},
239 id ==
"sphere1" ? std::max(4,
c.detail / 2) :
std::
max(3,
c.
detail / 3),
241 }
else if (
id ==
"spike") {
242 addCylinder(
c.mesh, {0, c.h * 0.5f, 0},
c.w * 0.5f, 0.0f,
c.h, 4,
"spike");
243 }
else if (
id.starts_with(
"spikes")) {
244 const int count =
id ==
"spikes-big" ? 3 : 5;
247 const float px = -
c.w * 0.5f +
c.w * (float(
x) + 0.5f) /
float(
count);
248 const float pz = -
c.d * 0.5f +
c.d * (float(
z) + 0.5f) /
float(
count);
252 }
else if (
id.starts_with(
"stairs")) {
253 addStairs(
c,
id.starts_with(
"stairs-corner"));
254 }
else if (
id ==
"toggle-switch") {
255 addBox(
c.mesh, {0, c.t * 0.5f, 0}, {c.w, c.t, c.d},
"base");
256 addWedge(
c.mesh, {0, c.t + (c.h - c.t) * 0.5f, 0}, {c.w * 0.3f, c.h - c.t, c.d * 0.3f},
false,
"lever");
257 }
else if (
id ==
"torus") {
258 addTorus(
c.mesh, {0, c.h * 0.5f, 0},
c.w * 0.36f,
c.h * 0.5f,
c.detail * 2, std::max(4,
c.detail / 2),
"ring");
259 }
else if (
id.starts_with(
"wall-corner")) {
260 addCorner(
c,
"wall");
261 }
else if (
id.starts_with(
"wall-door")) {
262 addFrame(
c,
c.w * 0.42f,
c.h * 0.72f, 0.0f,
"wall");
263 }
else if (
id.starts_with(
"wall-window")) {
264 addFrame(
c,
c.w * 0.5f,
c.h * 0.42f,
c.h * 0.28f,
"wall");
265 }
else if (
id.starts_with(
"wall")) {
266 addBox(
c.mesh, {0, c.h * 0.5f, 0}, {c.w, c.h, c.d},
"wall");
267 }
else if (
id.starts_with(
"window")) {
268 addFrame(
c,
c.w - 2.0f *
c.t,
c.h - 2.0f *
c.t,
c.t,
"frame");
269 addBox(
c.mesh, {0, c.h * 0.5f, 0}, {c.t * 0.6f, c.h - 2.0f * c.t, c.d * 0.55f},
"mullion");
273RecipeDescriptor recipeDescriptor(
const PrototypePieceDescriptor& piece) {
274 RecipeDescriptor schema{
"prototype." + std::string(piece.id), std::string(piece.displayName),
"Prototype 3D", {}};
282 schema.params.push_back(
297 const float scale = positiveParam(
params,
"scale", 1.0f);
298 const float uvScale = positiveParam(
params,
"uvScale", 0.5f);
304 std::clamp(
params.getInt(
"detail", 12), 3, 64),
305 std::clamp(
params.getInt(
"steps", 6), 2, 64)};
307 !std::isfinite(uvScale) || !std::isfinite(
context.d) || !std::isfinite(
context.t) ||
context.w <= 0.0f ||
311 "prototype dimensions and scale must be finite and positive",
"params"));
319 for (
float&
uv :
context.mesh.uvs())
uv *= uvScale;
320 context.mesh.setMeta(
"recipe",
"prototype." + std::string(pieceId));
321 context.mesh.setMeta(
"origin",
"ground-center");
322 context.mesh.setMeta(
"source",
"procedural");
327 for (
const auto& piece : kPieces) {
328 const std::string
id(piece.id);
332 if (!generated.ok()) {
333 error = generated.status().describe();
336 out = std::move(generated).takeValue();
building::EdgeCurveGroup group
std::array< float, 3 > scale
const VegetationPresetContext & context
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
static Result failure(Status status)
Construct a failed result from a structured status.
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
EVENGINE_API_DOMAINS public API.
void registerRecipe(const std::string &id, MeshRecipeFn fn)
Register a recipe without metadata.
Owning, typed generation parameters.
std::vector< ParamSpec > params
void addWedge(MeshBuild &mesh, Vec3 center, Vec3 size, bool riseAlongX, const char *group)
Adds wedge.
void addTorus(MeshBuild &mesh, Vec3 center, float majorRadius, float minorRadius, int majorSegments, int minorSegments, const char *group)
Adds torus.
void addCylinder(MeshBuild &mesh, Vec3 center, float radiusBottom, float radiusTop, float height, int sides, const char *group)
Adds cylinder.
void addBox(MeshBuild &mesh, Vec3 center, Vec3 size, const char *group)
Adds box.
void addSphere(MeshBuild &mesh, Vec3 center, Vec3 radii, int rings, int sides, const char *group)
Adds sphere.
eve::Result< MeshBuild > generatePrototypePiece(std::string_view pieceId, const Params ¶ms)
Build one prototype module as an owning CPU triangle mesh.
void registerPrototypePieceRecipes(MeshRecipeRegistry ®istry)
Register all prototype modules as prototype.* mesh recipes.
std::span< const PrototypePieceDescriptor > prototypePieceDescriptors() noexcept
Return the stable catalogue of 75 procedural prototype modules.
static ParamDescriptor floating(std::string key, std::string label, float defaultValue, float minimum, float maximum, float step)
Construct a bounded floating-point descriptor.
static ParamDescriptor integer(std::string key, std::string label, int defaultValue, int minimum, int maximum, int step=1)
Construct a bounded integer descriptor.
Metadata for one centered, Y-up module in the built-in prototype construction kit.
float defaultWidth
Default X extent in world units.
float defaultHeight
Default Y extent in world units.
float defaultDepth
Default Z extent in world units.