载入中...
搜索中...
未找到
BuiltinTextures.cpp
浏览该文件的文档.
9#include "procgen/PointSet.h"
11
12#include "image/ImageData.h"
13
14#include <algorithm>
15#include <cmath>
16#include <cstdint>
17#include <functional>
18#include <random>
19#include <utility>
20#include <vector>
21
22namespace eve::procgen {
23namespace {
24
25float smoothstep(float edge0, float edge1, float x) {
26 if (edge0 == edge1) return x < edge0 ? 0.f : 1.f;
27 const float t = std::clamp((x - edge0) / (edge1 - edge0), 0.f, 1.f);
28 return t * t * (3.f - 2.f * t);
29}
30
32float voronoiDist(const NoiseField &n, float x, float y) {
33 float best = 1e18f;
34 const int cx0 = int(std::floor(x));
35 const int cy0 = int(std::floor(y));
36 for (int oy = -1; oy <= 1; ++oy) {
37 for (int ox = -1; ox <= 1; ++ox) {
38 const int cx = cx0 + ox;
39 const int cy = cy0 + oy;
40 const float px = float(cx) + n.hash01(cx, cy);
41 const float py = float(cy) + n.hash01(cx * 7 + 3, cy * 13 + 5);
42 const float dx = x - px;
43 const float dy = y - py;
44 const float d = dx * dx + dy * dy;
45 if (d < best) best = d;
46 }
47 }
48 return std::sqrt(best);
49}
50
51std::unique_ptr<image::ImageData> makeFromHeightFn(const Params &params, std::string &error,
52 const TextureRecipeDef &def) {
53 const auto ctx = TextureGenContext::fromParams(params);
54 if (ctx.width > 4096 || ctx.height > 4096) {
55 error = "texture size too large (max 4096)";
56 return nullptr;
57 }
58 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height, "RGBA8");
59 std::vector<float> height;
60 fillHeightField(ctx, def.height, height);
61 paintHeightToImage(*img, height, ctx.width, ctx.height, def.albedo, ctx.colors, ctx.pixelSize);
62 return img;
63}
64
66TextureRecipeDef makeDef(std::string id, ColorRamp ramp,
67 std::function<float(float, float, const NoiseField &)> height,
68 PbrParams pbr = {}) {
69 TextureRecipeDef def;
70 def.id = std::move(id);
71 def.albedo = std::move(ramp);
72 def.height = std::move(height);
73 def.pbr = pbr;
74 return def;
75}
76
77std::vector<TextureRecipeDef> buildDefs() {
78 std::vector<TextureRecipeDef> defs;
79
80 // --- tex.soil: loose dark soil ---
81 {
82 ColorRamp ramp;
83 ramp.add(0.00f, 58, 38, 22);
84 ramp.add(0.35f, 92, 62, 34);
85 ramp.add(0.65f, 120, 84, 48);
86 ramp.add(1.00f, 148, 110, 68);
87 PbrParams pbr;
88 pbr.roughnessLow = 0.75f;
89 pbr.roughnessHigh = 1.f;
90 defs.push_back(makeDef(
91 "tex.soil", std::move(ramp),
92 [](float u, float v, const NoiseField &n) {
93 float h = n.fbm(u, v, 4);
94 h = h * 0.75f + 0.25f * n.valueNoise(u * 3.1f + 2.f, v * 3.1f);
95 return h;
96 },
97 pbr));
98 }
99
100 // --- tex.stone: cracked stone ---
101 {
102 ColorRamp ramp;
103 ramp.add(0.00f, 52, 54, 58);
104 ramp.add(0.40f, 88, 90, 96);
105 ramp.add(0.70f, 130, 132, 138);
106 ramp.add(1.00f, 176, 178, 184);
107 PbrParams pbr;
108 pbr.roughnessLow = 0.6f;
109 pbr.roughnessHigh = 0.9f;
110 pbr.aoStrength = 1.4f;
111 defs.push_back(makeDef(
112 "tex.stone", std::move(ramp),
113 [](float u, float v, const NoiseField &n) {
114 float h = n.ridged(u, v, 4);
115 const float crack = std::fabs(n.valueNoise(u * 2.7f, v * 2.7f) - 0.5f) * 2.f;
116 h = h * 0.7f + (1.f - crack) * 0.3f;
117 return h;
118 },
119 pbr));
120 }
121
122 // --- tex.rock: warm boulder with deep crevices (darker, more contrast) ---
123 {
124 ColorRamp ramp;
125 ramp.add(0.00f, 28, 24, 20);
126 ramp.add(0.28f, 58, 52, 44);
127 ramp.add(0.55f, 92, 84, 70);
128 ramp.add(0.80f, 122, 112, 96);
129 ramp.add(1.00f, 148, 138, 120);
130 PbrParams pbr;
131 pbr.roughnessLow = 0.62f;
132 pbr.roughnessHigh = 0.98f;
133 pbr.aoStrength = 1.7f;
134 pbr.normalStrength = 3.0f;
135 defs.push_back(makeDef(
136 "tex.rock", std::move(ramp),
137 [](float u, float v, const NoiseField &n) {
138 float h = n.ridged(u * 0.9f, v * 0.9f, 5);
139 const float crack =
140 std::pow(std::fabs(n.valueNoise(u * 2.2f, v * 2.2f) - 0.5f) * 2.f, 1.6f);
141 h = h * 0.62f + 0.22f * n.fbm(u * 2.8f, v * 2.8f, 3) + (1.f - crack) * 0.16f;
142 return h;
143 },
144 pbr));
145 }
146
147 // --- tex.marble: polished veined marble ---
148 {
149 ColorRamp ramp;
150 ramp.add(0.00f, 230, 230, 235);
151 ramp.add(0.45f, 200, 200, 210);
152 ramp.add(0.55f, 120, 120, 135);
153 ramp.add(0.70f, 190, 190, 200);
154 ramp.add(1.00f, 245, 245, 248);
155 PbrParams pbr;
156 pbr.roughnessLow = 0.08f;
157 pbr.roughnessHigh = 0.35f;
158 defs.push_back(makeDef(
159 "tex.marble", std::move(ramp),
160 [](float u, float v, const NoiseField &n) {
161 const float w = n.warp(u, v, 2.5f, std::max(2, 3));
162 float vein = std::sin((u + w * 4.f) * 3.14159265f * 2.f) * 0.5f + 0.5f;
163 vein = std::pow(vein, 1.6f);
164 return vein * 0.65f + w * 0.35f;
165 },
166 pbr));
167 }
168
169 // --- tex.water: rippling water surface ---
170 {
171 ColorRamp ramp;
172 ramp.add(0.00f, 18, 60, 110);
173 ramp.add(0.40f, 28, 96, 150);
174 ramp.add(0.70f, 48, 140, 180);
175 ramp.add(1.00f, 120, 200, 210);
176 PbrParams pbr;
177 pbr.roughnessLow = 0.05f;
178 pbr.roughnessHigh = 0.3f;
179 pbr.metallic = 0.05f;
180 defs.push_back(makeDef(
181 "tex.water", std::move(ramp),
182 [](float u, float v, const NoiseField &n) {
183 float wave = 0.5f + 0.5f * std::sin((u * 2.2f + v * 0.4f) * 6.28318f +
184 n.valueNoise(u * 1.3f, v * 1.3f) * 2.f);
185 float detail = n.fbm(u * 1.5f, v * 1.5f, 4);
186 return wave * 0.55f + detail * 0.45f;
187 },
188 pbr));
189 }
190
191 // --- tex.ripple: concentric ripples from scatter points ---
192 {
193 ColorRamp ramp;
194 ramp.add(0.00f, 20, 60, 120);
195 ramp.add(0.40f, 40, 120, 190);
196 ramp.add(0.70f, 90, 170, 220);
197 ramp.add(1.00f, 190, 230, 245);
198 PbrParams pbr;
199 pbr.roughnessLow = 0.04f;
200 pbr.roughnessHigh = 0.5f;
201 defs.push_back(makeDef(
202 "tex.ripple", std::move(ramp),
203 [](float u, float v, const NoiseField &n) {
204 const float rings = 6.f;
205 float d = voronoiDist(n, u, v);
206 float ring = 0.5f + 0.5f * std::sin(d * 6.28318f * rings);
207 // fade rings near each source centre.
208 ring *= smoothstep(0.f, 0.3f, d);
209 return ring * 0.7f + n.fbm(u * 2.f, v * 2.f, 3) * 0.3f;
210 },
211 pbr));
212 }
213
214 // --- tex.sky_cloud: sky with clouds ---
215 {
216 ColorRamp ramp;
217 ramp.add(0.00f, 70, 130, 200);
218 ramp.add(0.45f, 110, 170, 220);
219 ramp.add(0.70f, 180, 210, 235);
220 ramp.add(1.00f, 245, 248, 252);
221 defs.push_back(makeDef(
222 "tex.sky_cloud", std::move(ramp),
223 [](float u, float v, const NoiseField &n) {
224 const float sky = std::clamp(1.f - v / std::max(0.01f, 4.f), 0.f, 1.f);
225 float clouds = n.fbm(u * 0.9f + 3.f, v * 0.9f, 4);
226 clouds = smoothstep(0.35f, 0.75f, clouds);
227 return sky * 0.45f + clouds * 0.55f;
228 }));
229 }
230
231 // --- tex.wood: wavy grain running along V ---
232 {
233 ColorRamp ramp;
234 ramp.add(0.00f, 150, 106, 58);
235 ramp.add(0.30f, 178, 128, 72);
236 ramp.add(0.55f, 130, 88, 48);
237 ramp.add(0.75f, 96, 62, 36);
238 ramp.add(1.00f, 70, 44, 28);
239 PbrParams pbr;
240 pbr.roughnessLow = 0.35f;
241 pbr.roughnessHigh = 0.7f;
242 defs.push_back(makeDef(
243 "tex.wood", std::move(ramp),
244 [](float u, float v, const NoiseField &n) {
245 const float w = n.warp(u * 0.8f, v * 0.8f, 2.5f, 3);
246 float grain = 0.5f + 0.5f * std::sin((u * 4.f + w * 6.f) * 6.28318f);
247 grain = std::pow(grain, 2.4f);
248 const float detail = n.fbm(u * 3.f, v * 3.f, 3);
249 return grain * 0.6f + detail * 0.4f;
250 },
251 pbr));
252 }
253
254 // --- tex.cloth: woven fabric ---
255 {
256 ColorRamp ramp;
257 ramp.add(0.00f, 30, 44, 34);
258 ramp.add(0.40f, 52, 76, 52);
259 ramp.add(0.70f, 74, 104, 68);
260 ramp.add(1.00f, 108, 140, 92);
261 PbrParams pbr;
262 pbr.roughnessLow = 0.8f;
263 pbr.roughnessHigh = 1.f;
264 pbr.normalStrength = 2.f;
265 defs.push_back(makeDef(
266 "tex.cloth", std::move(ramp),
267 [](float u, float v, const NoiseField &n) {
268 const float threads = 9.f;
269 const float fx = u * threads;
270 const float fy = v * threads;
271 const float dx = std::fabs(std::fmod(fx, 1.f) - 0.5f);
272 const float dy = std::fabs(std::fmod(fy, 1.f) - 0.5f);
273 const float c = std::min(dx, dy); // 0 at thread, .5 between
274 float thread = 1.f - c * 2.f; // thread cross-section
275 const int ix = int(std::floor(fx));
276 const int iy = int(std::floor(fy));
277 if ((ix + iy) % 2 == 0) thread = 1.f - thread; // interlace over/under
278 const float noise = n.valueNoise(u * 2.f, v * 2.f);
279 return thread * 0.55f + noise * 0.35f + 0.1f;
280 },
281 pbr));
282 }
283
284 // --- tex.ornament: irregular decorative swirl ---
285 {
286 ColorRamp ramp;
287 ramp.add(0.00f, 88, 30, 60);
288 ramp.add(0.30f, 160, 74, 96);
289 ramp.add(0.50f, 224, 176, 120);
290 ramp.add(0.70f, 150, 96, 70);
291 ramp.add(1.00f, 74, 46, 60);
292 PbrParams pbr;
293 pbr.roughnessLow = 0.3f;
294 pbr.roughnessHigh = 0.6f;
295 defs.push_back(makeDef(
296 "tex.ornament", std::move(ramp),
297 [](float u, float v, const NoiseField &n) {
298 const float w = n.warp(u, v, 3.5f, 4);
299 float swirl = 0.5f + 0.5f * (std::sin((u + w * 4.f) * 6.28318f) *
300 std::cos((v - w * 3.f) * 6.28318f));
301 return swirl * 0.6f + n.fbm(u * 2.f, v * 2.f, 4) * 0.4f;
302 },
303 pbr));
304 }
305
306 // --- tex.spot: blobby spots (leopard / dalmatian) ---
307 {
308 ColorRamp ramp;
309 ramp.add(0.00f, 220, 200, 168); // pale base
310 ramp.add(0.35f, 196, 172, 138);
311 ramp.add(0.50f, 90, 62, 40); // dark spot
312 ramp.add(0.75f, 60, 42, 30);
313 ramp.add(1.00f, 150, 120, 92);
314 PbrParams pbr;
315 pbr.roughnessLow = 0.5f;
316 pbr.roughnessHigh = 0.9f;
317 defs.push_back(makeDef(
318 "tex.spot", std::move(ramp),
319 [](float u, float v, const NoiseField &n) {
320 const float blobs = n.valueNoise(u * 2.2f, v * 2.2f);
321 float spot = smoothstep(0.5f, 0.6f, blobs);
322 spot = spot * 0.8f + n.fbm(u * 3.f, v * 3.f, 3) * 0.2f;
323 return spot;
324 },
325 pbr));
326 }
327
328 // --- tex.zebra: wavy black/white stripes ---
329 {
330 ColorRamp ramp;
331 ramp.add(0.00f, 245, 245, 244);
332 ramp.add(0.50f, 30, 30, 32);
333 ramp.add(1.00f, 245, 245, 244);
334 PbrParams pbr;
335 pbr.roughnessLow = 0.4f;
336 pbr.roughnessHigh = 0.8f;
337 defs.push_back(makeDef(
338 "tex.zebra", std::move(ramp),
339 [](float u, float v, const NoiseField &n) {
340 const float w = n.warp(u, v, 1.5f, 3);
341 float stripe = 0.5f + 0.5f * std::sin((u * 5.f + w * 4.f) * 6.28318f);
342 stripe = smoothstep(0.3f, 0.55f, stripe);
343 return stripe * 0.9f + n.valueNoise(u * 3.f, v * 3.f) * 0.1f;
344 },
345 pbr));
346 }
347
348 // --- tex.wall: brick wall with recessed mortar ---
349 {
350 ColorRamp ramp;
351 ramp.add(0.00f, 78, 46, 38); // mortar
352 ramp.add(0.30f, 130, 74, 54);
353 ramp.add(0.60f, 164, 96, 66);
354 ramp.add(1.00f, 196, 130, 92);
355 PbrParams pbr;
356 pbr.roughnessLow = 0.65f;
357 pbr.roughnessHigh = 0.95f;
358 pbr.aoStrength = 1.6f;
359 defs.push_back(makeDef(
360 "tex.wall", std::move(ramp),
361 [](float u, float v, const NoiseField &n) {
362 const float rows = 5.f;
363 const float cols = 2.f * rows;
364 const int row = int(std::floor(v * rows));
365 const float off = (row % 2 == 0) ? 0.f : 0.5f;
366 const float bx = u * cols + off;
367 const float mh = std::fabs(std::fmod(v * rows, 1.f) - 0.5f);
368 const float mv = std::fabs(std::fmod(bx, 1.f) - 0.5f);
369 float brick = 1.f - smoothstep(0.30f, 0.42f, std::min(mh, mv));
370 const float noise = n.valueNoise(u * 2.f, v * 2.f);
371 return brick * 0.75f + noise * 0.25f * brick + (1.f - brick) * 0.15f;
372 },
373 pbr));
374 }
375
376 // --- tex.cement: mottled grey with hairline cracks ---
377 {
378 ColorRamp ramp;
379 ramp.add(0.00f, 60, 62, 66);
380 ramp.add(0.35f, 96, 100, 106);
381 ramp.add(0.70f, 138, 142, 148);
382 ramp.add(1.00f, 182, 186, 192);
383 PbrParams pbr;
384 pbr.roughnessLow = 0.6f;
385 pbr.roughnessHigh = 0.85f;
386 defs.push_back(makeDef(
387 "tex.cement", std::move(ramp),
388 [](float u, float v, const NoiseField &n) {
389 float mottle = n.fbmPerlin(u, v, 4);
390 const float crack = 1.f - smoothstep(0.55f, 0.72f, n.ridgedPerlin(u * 3.f, v * 3.f, 3));
391 return mottle * 0.6f + crack * 0.4f;
392 },
393 pbr));
394 }
395
396 // --- tex.asphalt: fine aggregate with sparse recessed wear ---
397 // The bright neutral ramp is intentional: road meshes already carry their
398 // authored asphalt colour in vertex RGB, so this map supplies detail as a
399 // multiplicative modulation instead of becoming a second colour authority.
400 {
401 ColorRamp ramp;
402 ramp.add(0.00f, 132, 134, 138);
403 ramp.add(0.30f, 172, 174, 178);
404 ramp.add(0.68f, 214, 216, 220);
405 ramp.add(1.00f, 244, 245, 247);
406 PbrParams pbr;
407 pbr.roughnessLow = 0.78f;
408 pbr.roughnessHigh = 0.96f;
409 pbr.normalStrength = 2.2f;
410 pbr.aoStrength = 0.65f;
411 pbr.heightStrength = 0.45f;
412 defs.push_back(makeDef(
413 "tex.asphalt", std::move(ramp),
414 [](float u, float v, const NoiseField &n) {
415 const float fine = n.fbmPerlin(u * 5.f, v * 5.f, 4);
416 const float aggregate = n.valueNoise(u * 18.f + 3.7f, v * 18.f + 9.1f);
417 const float wear = smoothstep(0.68f, 0.82f, n.ridgedPerlin(u * 1.6f, v * 1.6f, 3));
418 return std::clamp(fine * 0.55f + aggregate * 0.35f - wear * 0.18f + 0.16f, 0.f, 1.f);
419 },
420 pbr));
421 }
422
423 // --- tex.mud: dried cracked mud plates ---
424 {
425 ColorRamp ramp;
426 ramp.add(0.00f, 40, 26, 18); // crack
427 ramp.add(0.30f, 84, 56, 34);
428 ramp.add(0.60f, 116, 78, 46);
429 ramp.add(1.00f, 148, 104, 62);
430 PbrParams pbr;
431 pbr.roughnessLow = 0.7f;
432 pbr.roughnessHigh = 1.f;
433 pbr.aoStrength = 1.8f;
434 defs.push_back(makeDef(
435 "tex.mud", std::move(ramp),
436 [](float u, float v, const NoiseField &n) {
437 float d = voronoiDist(n, u, v);
438 float plate = smoothstep(0.04f, 0.14f, d); // 0 on cracks, 1 inside plates
439 return plate * 0.8f + n.fbm(u * 2.5f, v * 2.5f, 3) * 0.2f;
440 },
441 pbr));
442 }
443
444 // --- tex.bark: vertical fissured trunk bark (TreeMesh atlas left half) ---
445 {
446 ColorRamp ramp;
447 ramp.add(0.00f, 28, 18, 12);
448 ramp.add(0.22f, 58, 38, 24);
449 ramp.add(0.48f, 98, 68, 42);
450 ramp.add(0.72f, 138, 102, 64);
451 ramp.add(1.00f, 72, 48, 30);
452 PbrParams pbr;
453 pbr.roughnessLow = 0.58f;
454 pbr.roughnessHigh = 0.98f;
455 pbr.aoStrength = 1.85f;
456 pbr.normalStrength = 3.6f;
457 defs.push_back(makeDef(
458 "tex.bark", std::move(ramp),
459 [](float u, float v, const NoiseField &n) {
460 const float warp = n.warp(u, v, 2.1f, 3);
461 const float grain =
462 0.5f + 0.5f * std::sin((u * 9.f + warp * 6.f) * 6.28318f);
463 const float ridge = n.ridged(u * 1.6f, v * 0.28f, 5);
464 const float crack =
465 std::pow(std::fabs(n.valueNoise(u * 1.4f, v * 4.2f) - 0.5f) * 2.f, 1.55f);
466 const float flake = n.fbm(u * 3.2f + 1.7f, v * 0.9f, 3);
467 return grain * 0.28f + ridge * 0.38f + (1.f - crack) * 0.22f + flake * 0.12f;
468 },
469 pbr));
470 }
471
472 // --- tex.moss: cool green-gray rock weathering with vivid moss patches ---
473 {
474 ColorRamp ramp;
475 ramp.add(0.00f, 36, 40, 32);
476 ramp.add(0.30f, 62, 72, 48);
477 ramp.add(0.52f, 78, 108, 52);
478 ramp.add(0.72f, 96, 132, 58);
479 ramp.add(1.00f, 148, 156, 138);
480 PbrParams pbr;
481 pbr.roughnessLow = 0.72f;
482 pbr.roughnessHigh = 1.f;
483 pbr.aoStrength = 1.7f;
484 pbr.normalStrength = 3.1f;
485 defs.push_back(makeDef(
486 "tex.moss", std::move(ramp),
487 [](float u, float v, const NoiseField &n) {
488 float rock = n.ridged(u * 0.95f, v * 0.95f, 5);
489 float moss = smoothstep(0.32f, 0.78f, n.fbm(u * 1.8f + 2.f, v * 1.8f, 4));
490 const float crack =
491 std::pow(std::fabs(n.valueNoise(u * 2.4f, v * 2.4f) - 0.5f) * 2.f, 1.5f);
492 return rock * (1.f - moss * 0.55f) + moss * 0.62f + (1.f - crack) * 0.08f;
493 },
494 pbr));
495 }
496
497 return defs;
498}
499
501CloudField::Params cloudFieldFromParams(const Params &params) {
502 CloudField::Params p;
503 p.seed = params.getSeed();
504 p.worldScale = params.getFloat("worldScale", 96.f);
505 p.coverage = params.getFloat("cloudCoverage", 0.55f);
506 p.softness = params.getFloat("cloudSoftness", 0.12f);
507 p.detail = params.getFloat("cloudDetail", 0.5f);
508 p.windSpeed = params.getFloat("windSpeed", 4.f);
509 p.windAngle = params.getFloat("windAngle", 0.f);
510 p.octaves = params.getInt("octaves", 4);
511 return p;
512}
513
515std::unique_ptr<image::ImageData> genCloud(const Params &params, std::string &error) {
516 const auto ctx = TextureGenContext::fromParams(params);
517 if (ctx.width > 4096 || ctx.height > 4096) {
518 error = "texture size too large (max 4096)";
519 return nullptr;
520 }
521 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height, "RGBA8");
522 ColorRamp ramp;
523 ramp.add(0.00f, 90, 140, 205);
524 ramp.add(0.30f, 168, 205, 238);
525 ramp.add(0.55f, 238, 248, 252);
526 ramp.add(1.00f, 255, 255, 255);
527
528 CloudField field(cloudFieldFromParams(params));
529 const float time = params.getFloat("time", 0.f);
530 const float extent = params.getFloat("extent", field.params().worldScale);
531 std::vector<float> height(size_t(ctx.width * ctx.height));
532 field.sample(height.data(), ctx.width, ctx.height, time, 0.f, 0.f, extent);
533 paintHeightToImage(*img, height, ctx.width, ctx.height, ramp, ctx.colors, ctx.pixelSize);
534 return img;
535}
536
538std::unique_ptr<image::ImageData> genCloudShadow(const Params &params, std::string &error) {
539 const auto ctx = TextureGenContext::fromParams(params);
540 if (ctx.width > 4096 || ctx.height > 4096) {
541 error = "texture size too large (max 4096)";
542 return nullptr;
543 }
544 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height, "RGBA8");
545 ColorRamp ramp;
546 ramp.add(0.00f, 18, 20, 26); // clear (lit)
547 ramp.add(0.30f, 60, 62, 70);
548 ramp.add(0.60f, 120, 122, 130);
549 ramp.add(1.00f, 200, 202, 208); // dense cloud (strong shadow)
550
551 CloudField::Params fp = cloudFieldFromParams(params);
552 CloudShadow::Params sp;
553 sp.field = CloudField(fp);
554 sp.sunDirX = params.getFloat("sunDirX", 0.f);
555 sp.sunDirY = params.getFloat("sunDirY", 1.f);
556 sp.sunDirZ = params.getFloat("sunDirZ", 0.f);
557 sp.cloudAltitude = params.getFloat("cloudAltitude", 60.f);
558 sp.strength = params.getFloat("cloudShadowStrength", 0.8f);
559 CloudShadow shadow(sp);
560
561 const float time = params.getFloat("time", 0.f);
562 const float extent = params.getFloat("extent", fp.worldScale);
563 std::vector<float> height(size_t(ctx.width * ctx.height));
564 shadow.sampleCoverage(height.data(), ctx.width, ctx.height, time, 0.f, 0.f, extent);
565 paintHeightToImage(*img, height, ctx.width, ctx.height, ramp, ctx.colors, ctx.pixelSize);
566 return img;
567}
568
575struct LeafStamp {
576 float cx = 0.5f, cy = 0.5f;
577 float halfW = 0.12f, halfH = 0.18f;
578 float ang = 0.f;
579};
580
581constexpr int kLeafCardPanels = 6;
582constexpr int kLeafCardCols = 2;
583constexpr int kLeafCardRows = 3;
584
588void buildBlueNoiseCardStamps(std::uint32_t seed, int count, std::vector<LeafStamp> &stamps) {
589 stamps.clear();
590 count = std::clamp(count, 8, 16);
591 // Bridson on a fine grid, then normalize to the unit square with a margin.
592 const float minDist01 = std::sqrt(0.42f / float(count));
593 constexpr int kSide = 64;
594 const float radius = std::max(1.5f, minDist01 * float(kSide));
595 const PointSet pts = poissonDiskPoints(kSide, kSide, radius, seed, count + 12);
596 std::mt19937 rng(seed ^ 0x9e3779b9u);
597 std::uniform_real_distribution<float> unit(0.f, 1.f);
598 constexpr float kMargin = 0.07f;
599 int placed = 0;
600 for (const ProcgenPoint &p : pts.points()) {
601 if (placed >= count) break;
602 LeafStamp s;
603 s.cx = kMargin + (1.f - 2.f * kMargin) * (p.x / float(kSide));
604 s.cy = kMargin + (1.f - 2.f * kMargin) * (p.z / float(kSide));
605 s.ang = unit(rng) * 6.28318530718f;
606 s.halfW = 0.050f + 0.028f * unit(rng);
607 s.halfH = 0.070f + 0.036f * unit(rng);
608 stamps.push_back(s);
609 ++placed;
610 }
611 // Top up with dart throws if Bridson under-filled (small textures / tight radius).
612 int guard = 0;
613 while (placed < count && guard++ < count * 100) {
614 LeafStamp s;
615 s.cx = kMargin + (1.f - 2.f * kMargin) * unit(rng);
616 s.cy = kMargin + (1.f - 2.f * kMargin) * unit(rng);
617 bool ok = true;
618 for (const LeafStamp &o : stamps) {
619 const float dx = s.cx - o.cx, dy = s.cy - o.cy;
620 if (dx * dx + dy * dy < minDist01 * minDist01) {
621 ok = false;
622 break;
623 }
624 }
625 if (!ok) continue;
626 s.ang = unit(rng) * 6.28318530718f;
627 s.halfW = 0.050f + 0.028f * unit(rng);
628 s.halfH = 0.070f + 0.036f * unit(rng);
629 stamps.push_back(s);
630 ++placed;
631 }
632}
633
634void sampleOvateLeafCard(const NoiseField &noise, float u, float v, const std::vector<LeafStamp> &stamps,
635 float &cover, float &shade) {
636 cover = 0.f;
637 shade = 0.f;
638 for (const LeafStamp &s : stamps) {
639 float dx = u - s.cx;
640 float dy = v - s.cy;
641 const float ca = std::cos(s.ang), sa = std::sin(s.ang);
642 const float rx = dx * ca - dy * sa;
643 const float ry = dx * sa + dy * ca;
644 // Ovate / lanceolate: pointed tip along +Y, wider shoulders below centre.
645 const float halfW = s.halfW * (1.f - 0.42f * std::max(0.f, ry / std::max(1e-4f, s.halfH)));
646 const float d = (rx * rx) / std::max(1e-5f, halfW * halfW) +
647 (ry * ry) / std::max(1e-5f, s.halfH * s.halfH);
648 const float mask = 1.f - smoothstep(0.78f, 1.02f, d);
649 if (mask <= cover) continue;
650 cover = mask;
651 // Flat fill — no veins/mottle; slight per-stamp tone so leaves are not identical.
652 shade = 0.48f + 0.12f * noise.hash01(int(s.cx * 97.f), int(s.cy * 53.f));
653 }
654}
655
659void buildSixLeafCardPanels(std::uint32_t seed, const NoiseField &noise,
660 std::vector<std::vector<LeafStamp>> &panels) {
661 panels.clear();
662 panels.resize(size_t(kLeafCardPanels));
663 for (int i = 0; i < kLeafCardPanels; ++i) {
664 const std::uint32_t panelSeed = seed ^ (std::uint32_t(i + 1) * 2654435761u);
665 const int count = 8 + int(noise.hash01(i * 3 + 1, i * 7 + 2) * 8.999f); // 8..16
666 buildBlueNoiseCardStamps(panelSeed, count, panels[size_t(i)]);
667 }
668}
669
673void sampleLeafCardAtlas(const NoiseField &noise, float fu, float v,
674 const std::vector<std::vector<LeafStamp>> &panels, float &cover, float &shade) {
675 const int col = std::clamp(int(fu * float(kLeafCardCols)), 0, kLeafCardCols - 1);
676 const int row = std::clamp(int(v * float(kLeafCardRows)), 0, kLeafCardRows - 1);
677 const float localU = fu * float(kLeafCardCols) - float(col);
678 const float localV = v * float(kLeafCardRows) - float(row);
679 const int panel = row * kLeafCardCols + col;
680 sampleOvateLeafCard(noise, localU, localV, panels[size_t(panel)], cover, shade);
681}
682
686void sampleFoliageFill(const NoiseField &noise, float u, float v, float scale, float &cover, float &shade) {
687 cover = 0.f;
688 shade = 0.f;
689 const float su = u * scale;
690 const float sv = v * scale;
691 for (int oy = -1; oy <= 1; ++oy) {
692 for (int ox = -1; ox <= 1; ++ox) {
693 const int cx = int(std::floor(su * 2.2f)) + ox;
694 const int cy = int(std::floor(sv * 2.2f)) + oy;
695 const float px = float(cx) + noise.hash01(cx, cy);
696 const float py = float(cy) + noise.hash01(cx * 7 + 3, cy * 13 + 5);
697 float dx = su * 2.2f - px;
698 float dy = sv * 2.2f - py;
699 const float ang = (noise.hash01(cx * 3 + 1, cy * 5 + 2) - 0.5f) * 1.4f;
700 const float ca = std::cos(ang), sa = std::sin(ang);
701 const float rx = dx * ca - dy * sa;
702 const float ry = dx * sa + dy * ca;
703 const float halfW = 0.48f * (1.f - 0.4f * std::max(0.f, ry));
704 const float d = (rx * rx) / std::max(1e-4f, halfW * halfW) + (ry * ry) / 0.58f;
705 const float mask = 1.f - smoothstep(0.55f, 1.05f, d);
706 if (mask <= cover) continue;
707 cover = mask;
708 const float vein = std::pow(
709 std::fabs(std::sin(ry * 8.f + noise.valueNoise(px * 0.35f, py * 0.35f) * 2.5f)), 3.f);
710 const float mott = noise.fbm(px * 0.28f + u * 1.6f, py * 0.28f + v * 1.6f, 3);
711 shade = std::clamp(0.30f + mott * 0.42f + (1.f - vein) * 0.28f, 0.f, 1.f);
712 }
713 }
714 // Keep blob fill nearly opaque so ellipsoid lobes do not go hollow.
715 cover = std::clamp(cover * 0.55f + 0.45f, 0.f, 1.f);
716}
717
721Rgba8 sampleBarkColor(const NoiseField &noise, float bu, float v, float scale, int colors) {
722 ColorRamp barkRamp;
723 barkRamp.add(0.00f, 28, 18, 12);
724 barkRamp.add(0.22f, 58, 38, 24);
725 barkRamp.add(0.48f, 102, 72, 44);
726 barkRamp.add(0.72f, 148, 110, 70);
727 barkRamp.add(1.00f, 68, 46, 28);
728 const float warp = noise.warp(bu * scale, v * scale, 2.1f, 3);
729 const float grain = 0.5f + 0.5f * std::sin((bu * 9.f + warp * 6.f) * 6.28318f);
730 const float ridge = noise.ridged(bu * 1.6f * scale, v * 0.28f * scale, 5);
731 const float crack =
732 std::pow(std::fabs(noise.valueNoise(bu * 1.4f * scale, v * 4.2f * scale) - 0.5f) * 2.f, 1.55f);
733 const float flake = noise.fbm(bu * 3.2f * scale + 1.7f, v * 0.9f * scale, 3);
734 const float h =
735 std::clamp(grain * 0.28f + ridge * 0.38f + (1.f - crack) * 0.22f + flake * 0.12f, 0.f, 1.f);
736 return barkRamp.sampleBanded(h, colors);
737}
738
743std::unique_ptr<image::ImageData> genTreeAtlas(const Params &params, std::string &error) {
744 const auto ctx = TextureGenContext::fromParams(params);
745 if (ctx.width > 4096 || ctx.height > 4096) {
746 error = "texture size too large (max 4096)";
747 return nullptr;
748 }
749 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height, "RGBA8");
750 NoiseField noise;
751 noise.seed = ctx.seed;
752 if (ctx.seamless) {
753 noise.periodX = std::max(1, int(ctx.scale));
754 noise.periodY = noise.periodX;
755 }
756
757 ColorRamp leafRamp;
758 leafRamp.add(0.00f, 14, 36, 10);
759 leafRamp.add(0.28f, 32, 78, 22);
760 leafRamp.add(0.52f, 52, 118, 34);
761 leafRamp.add(0.78f, 88, 148, 46);
762 leafRamp.add(1.00f, 24, 54, 16);
763
764 std::vector<std::vector<LeafStamp>> leafPanels;
765 buildSixLeafCardPanels(std::uint32_t(ctx.seed), noise, leafPanels);
766
767 const float invW = 1.f / float(std::max(1, ctx.width - 1));
768 const float invH = 1.f / float(std::max(1, ctx.height - 1));
769 for (int y = 0; y < ctx.height; ++y) {
770 for (int x = 0; x < ctx.width; ++x) {
771 const float u = float(x) * invW;
772 const float v = float(y) * invH;
773 Rgba8 color;
774 if (u < 0.48f) {
775 color = sampleBarkColor(noise, u / 0.48f, v, ctx.scale, ctx.colors);
776 } else if (u > 0.52f) {
777 const float fu = (u - 0.52f) / 0.48f;
778 float cover = 0.f, shade = 0.f;
779 sampleLeafCardAtlas(noise, fu, v, leafPanels, cover, shade);
780 if (cover < 0.5f) {
781 color = {0, 0, 0, 0};
782 } else {
783 color = leafRamp.sample(shade);
784 color.a = 255;
785 }
786 } else {
787 color = {72, 86, 48, 255};
788 }
789 img->setPixel(x, y,
790 image::ImageData::Colorf{color.r / 255.f, color.g / 255.f, color.b / 255.f,
791 color.a / 255.f});
792 }
793 }
794 return img;
795}
796
803std::unique_ptr<image::ImageData> genFoliage(const Params &params, std::string &error) {
804 const auto ctx = TextureGenContext::fromParams(params);
805 if (ctx.width > 4096 || ctx.height > 4096) {
806 error = "texture size too large (max 4096)";
807 return nullptr;
808 }
809 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height, "RGBA8");
810 NoiseField noise;
811 noise.seed = ctx.seed;
812 if (ctx.seamless) {
813 noise.periodX = std::max(1, int(ctx.scale));
814 noise.periodY = noise.periodX;
815 }
816
817 // Cooler forest green for bushes — less lime/yellow than the tree-atlas canopy.
818 ColorRamp leafRamp;
819 leafRamp.add(0.00f, 12, 32, 16);
820 leafRamp.add(0.28f, 28, 68, 34);
821 leafRamp.add(0.52f, 42, 96, 48);
822 leafRamp.add(0.78f, 62, 122, 58);
823 leafRamp.add(1.00f, 22, 48, 28);
824
825 std::vector<std::vector<LeafStamp>> leafPanels;
826 buildSixLeafCardPanels(std::uint32_t(ctx.seed), noise, leafPanels);
827
828 const float invW = 1.f / float(std::max(1, ctx.width - 1));
829 const float invH = 1.f / float(std::max(1, ctx.height - 1));
830 for (int y = 0; y < ctx.height; ++y) {
831 for (int x = 0; x < ctx.width; ++x) {
832 const float u = float(x) * invW;
833 const float v = float(y) * invH;
834 float cover = 0.f, shade = 0.f;
835 Rgba8 color;
836 if (u < 0.22f) {
837 color = sampleBarkColor(noise, u / 0.22f, v, ctx.scale, ctx.colors);
838 color.a = 255;
839 } else if (u < 0.24f) {
840 color = {72, 64, 42, 255}; // bark → fill blend
841 } else if (u <= 0.50f) {
842 const float fu = (u - 0.24f) / 0.26f;
843 sampleFoliageFill(noise, fu, v, ctx.scale, cover, shade);
844 color = leafRamp.sampleBanded(shade, ctx.colors);
845 color.a = 255;
846 } else if (u < 0.52f) {
847 color = {40, 78, 36, 255};
848 } else {
849 const float fu = (u - 0.52f) / 0.48f;
850 sampleLeafCardAtlas(noise, fu, v, leafPanels, cover, shade);
851 if (cover < 0.5f) {
852 color = {0, 0, 0, 0};
853 } else {
854 color = leafRamp.sample(shade);
855 color.a = 255;
856 }
857 }
858 img->setPixel(x, y,
859 image::ImageData::Colorf{color.r / 255.f, color.g / 255.f, color.b / 255.f,
860 color.a / 255.f});
861 }
862 }
863 return img;
864}
865
870std::unique_ptr<image::ImageData> genFlower(const Params &params, std::string &error) {
871 const auto ctx = TextureGenContext::fromParams(params);
872 if (ctx.width > 4096 || ctx.height > 4096) {
873 error = "texture size too large (max 4096)";
874 return nullptr;
875 }
876 auto img = std::make_unique<image::ImageData>(ctx.width, ctx.height, "RGBA8");
877 NoiseField noise;
878 noise.seed = ctx.seed;
879 if (ctx.seamless) {
880 noise.periodX = std::max(1, int(ctx.scale));
881 noise.periodY = noise.periodX;
882 }
883
884 ColorRamp stemRamp;
885 stemRamp.add(0.00f, 28, 72, 28);
886 stemRamp.add(0.55f, 48, 110, 42);
887 stemRamp.add(1.00f, 34, 86, 32);
888
889 ColorRamp petalRamp;
890 petalRamp.add(0.00f, 210, 200, 205);
891 petalRamp.add(0.40f, 245, 236, 240);
892 petalRamp.add(0.70f, 255, 250, 252);
893 petalRamp.add(1.00f, 255, 236, 180); // warm centre
894
895 const float invW = 1.f / float(std::max(1, ctx.width - 1));
896 const float invH = 1.f / float(std::max(1, ctx.height - 1));
897 for (int y = 0; y < ctx.height; ++y) {
898 for (int x = 0; x < ctx.width; ++x) {
899 const float u = float(x) * invW;
900 const float v = float(y) * invH;
901 Rgba8 color;
902 if (u < 0.32f) {
903 const float su = u / 0.32f;
904 const float h =
905 std::clamp(0.45f + 0.35f * noise.fbm(su * 2.f, v * 4.f, 3) +
906 0.2f * std::sin(v * 18.f),
907 0.f, 1.f);
908 color = stemRamp.sampleBanded(h, ctx.colors);
909 color.a = 255;
910 } else {
911 // Petal local coords: elliptical falloff with soft tip.
912 const float pu = (u - 0.36f) / 0.62f;
913 const float pv = v;
914 const float dx = (pu - 0.5f) * 2.f;
915 const float dy = (pv - 0.5f) * 2.f;
916 const float halfW = 0.72f * (1.f - 0.35f * std::max(0.f, dy));
917 const float d =
918 (dx * dx) / std::max(1e-4f, halfW * halfW) + (dy * dy) / 0.95f;
919 const float cover = 1.f - smoothstep(0.72f, 1.05f, d);
920 const float vein = std::pow(
921 std::fabs(std::sin(dy * 6.f + noise.valueNoise(pu * 2.f, pv * 2.f) * 2.f)),
922 3.f);
923 const float centre = 1.f - smoothstep(0.05f, 0.42f, std::sqrt(dx * dx + dy * dy));
924 const float h = std::clamp(0.35f + (1.f - vein) * 0.25f + centre * 0.45f +
925 noise.fbm(pu * 3.f, pv * 3.f, 2) * 0.1f,
926 0.f, 1.f);
927 color = petalRamp.sampleBanded(h, ctx.colors);
928 color.a = static_cast<uint8_t>(std::clamp(cover, 0.f, 1.f) * 255.f);
929 }
930 img->setPixel(x, y,
931 image::ImageData::Colorf{color.r / 255.f, color.g / 255.f, color.b / 255.f,
932 color.a / 255.f});
933 }
934 }
935 return img;
936}
937
938} // namespace
939
940const std::vector<TextureRecipeDef> &builtinTextureDefs() {
941 static const std::vector<TextureRecipeDef> defs = buildDefs();
942 return defs;
943}
944
946 if (builtinsRegistered_) return;
948 auto cloudDescriptor = [](std::string id, std::string name, bool shadow) {
949 RecipeDescriptor schema = RecipeDescriptor::grid(std::move(id), std::move(name), "Atmosphere", 1, 1);
950 schema.params.push_back(ParamDescriptor::floating("worldScale", "World Scale", 96.f, 1.f, 4096.f, 1.f));
951 schema.params.push_back(ParamDescriptor::floating("cloudCoverage", "Coverage", 0.55f, 0.f, 1.f, 0.01f));
952 schema.params.push_back(ParamDescriptor::floating("cloudSoftness", "Softness", 0.12f, 0.001f, 1.f, 0.01f));
953 schema.params.push_back(ParamDescriptor::floating("cloudDetail", "Detail", 0.5f, 0.f, 2.f, 0.01f));
954 schema.params.push_back(ParamDescriptor::floating("windSpeed", "Wind Speed", 4.f, 0.f, 256.f, 0.1f));
955 schema.params.push_back(ParamDescriptor::floating("windAngle", "Wind Angle", 0.f, -6.2832f, 6.2832f, 0.01f));
956 schema.params.push_back(ParamDescriptor::integer("octaves", "Octaves", 4, 1, 12));
957 schema.params.push_back(ParamDescriptor::boolean("seamless", "Seamless", true));
958 schema.params.push_back(ParamDescriptor::floating("time", "Time", 0.f, 0.f, 86400.f, 0.1f));
959 schema.params.push_back(ParamDescriptor::floating("extent", "Extent", 96.f, 1.f, 4096.f, 1.f));
960 if (shadow) {
961 schema.params.push_back(ParamDescriptor::floating("cloudAltitude", "Cloud Altitude", 60.f, 0.f, 10000.f, 1.f));
962 schema.params.push_back(ParamDescriptor::floating("cloudShadowStrength", "Shadow Strength", 0.8f, 0.f, 1.f, 0.01f));
963 }
964 return schema;
965 };
966 registerRecipe(cloudDescriptor("tex.cloud", "Cloud", false), genCloud);
967 registerRecipe(cloudDescriptor("tex.cloud_shadow", "Cloud Shadow", true), genCloudShadow);
968 {
969 RecipeDescriptor atlas = RecipeDescriptor::grid("tex.tree_atlas", "Tree Atlas", "Nature", 1, 1);
970 atlas.params.push_back(ParamDescriptor::floating("scale", "Scale", 4.f, 0.1f, 64.f, 0.1f));
971 atlas.params.push_back(ParamDescriptor::integer("colors", "Color Bands", 6, 2, 32));
972 atlas.params.push_back(ParamDescriptor::boolean("seamless", "Seamless", true));
973 registerRecipe(std::move(atlas), genTreeAtlas);
974 }
975 {
976 RecipeDescriptor foliage = RecipeDescriptor::grid("tex.foliage", "Foliage", "Nature", 1, 1);
977 foliage.params.push_back(ParamDescriptor::floating("scale", "Scale", 4.f, 0.1f, 64.f, 0.1f));
978 foliage.params.push_back(ParamDescriptor::integer("colors", "Color Bands", 6, 2, 32));
979 foliage.params.push_back(ParamDescriptor::boolean("seamless", "Seamless", true));
980 registerRecipe(std::move(foliage), genFoliage);
981 }
982 {
983 RecipeDescriptor flower = RecipeDescriptor::grid("tex.flower", "Flower", "Nature", 1, 1);
984 flower.params.push_back(ParamDescriptor::floating("scale", "Scale", 4.f, 0.1f, 64.f, 0.1f));
985 flower.params.push_back(ParamDescriptor::integer("colors", "Color Bands", 6, 2, 32));
986 flower.params.push_back(ParamDescriptor::boolean("seamless", "Seamless", true));
987 registerRecipe(std::move(flower), genFlower);
988 }
989 for (const TextureRecipeDef &def : builtinTextureDefs()) {
990 registerRecipe(makeTextureRecipeDescriptor(def), [def](const Params &params, std::string &error) {
991 return makeFromHeightFn(params, error, def);
992 });
993 }
997 builtinsRegistered_ = true;
998}
999
1000} // namespace eve::procgen
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
const std::string & s
int mask
float halfW
float ang
float halfH
float wear
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
int bx
Definition CaveMesh.cpp:114
float py
glm::vec4 p[6]
int rows
int cols
float localU
float warp
float grain
float localV
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
float clouds
float v
std::int32_t c
int h
std::vector< Colorf > px
std::uint32_t height
std::array< float, 3 > scale
std::string name
graphics::PbrSurface pbr
std::string error
Definition Package.cpp:60
float radius
std::string id
Definition PlayHost.cpp:108
std::uint32_t seed
Definition PointSet.cpp:807
float d
int detail
float t
std::vector< float > colors
float dy
float dx
std::uint32_t count
std::map< Cell, int > best
TacticalUnit * unit
bool placed
double oy
double ox
medialoader::Colorf Colorf
Definition ImageData.h:41
Owning, typed generation parameters.
Definition Params.h:27
void registerRecipe(const std::string &id, TextureRecipeFn fn)
Register a recipe without metadata.
void registerBuiltins()
Register engine-provided recipes once.
std::vector< ParamSpec > params
WGPUStringView sv(const char *s)
Build a WGPUStringView from a C string (null-safe, length auto-computed).
Definition wgpu_types.h:20
void registerRoadTextureRecipes(TextureRecipeRegistry &registry)
Registers road texture recipes.
const EditorValue * field(const EditorValue &value, const char *name)
void registerCableTextureRecipes(TextureRecipeRegistry &registry)
Register tex.cable.steel / tex.chain.iron / tex.rope.hemp.
void registerPrototypeTextureRecipes(TextureRecipeRegistry &registry)
Register all patterns as tex.prototype.* texture recipes.
PointSet poissonDiskPoints(int width, int depth, float radius, uint32_t seed, int maxPoints)
Bridson blue-noise (Poisson disk) samples in a width x depth area (XZ, y=0).
Definition PointSet.cpp:502
RecipeDescriptor makeTextureRecipeDescriptor(const TextureRecipeDef &definition)
Build editable metadata for a texture definition.
const std::vector< TextureRecipeDef > & builtinTextureDefs()
All built-in texture definitions (albedo + PBR). Populated lazily.
void paintHeightToImage(image::ImageData &img, const std::vector< float > &height, int w, int h, const ColorRamp &ramp, int bands, int pixelSize)
Paint height to image.
void fillHeightField(const TextureGenContext &ctx, const std::function< float(float, float, const NoiseField &)> &fn, std::vector< float > &height)
Sample a height function over the full [w×h] grid into height (clamped to [0,1]).
void registerFloorTextureRecipes(TextureRecipeRegistry &registry)
Register tex.floor.wood and tex.floor.tile with schema metadata.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Definition Widget.cpp:679
static ParamDescriptor boolean(std::string key, std::string label, bool defaultValue)
Construct a Boolean descriptor stored as zero or one in Params.
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.
Complete metadata and parameter schema shared by every procgen recipe family.
Definition ParamSchema.h:52
static RecipeDescriptor grid(std::string id, std::string displayName, std::string category, int minimumWidth, int minimumHeight, int maximumWidth=4096, int maximumHeight=4096)
Create a schema with common seed, width and height parameters.
std::vector< ParamDescriptor > params
Definition ParamSchema.h:56
static TextureGenContext fromParams(const Params &params)
From params.
A procedural texture is fully described by an albedo ramp + a displacement field + PBR knobs....
glm::vec4 color