From 47b450176787286b3eb6618ab20589ce320c3eb8 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 09:06:56 -0700 Subject: [PATCH] feat(editor): add --gen-texture-cracked branching-walk pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 43rd procedural texture: organic crack network done via recursive random walks from N seed nuclei. Each seed spawns a crack that walks in a random direction for some length, then with 60% chance branches into one or two more cracks of half-remaining length. Most cracks die out after a step or two, a few branch into longer networks — the bias matches real-world fissure formation. Useful for cracked mud, dry earth, broken glass, weathered stone, dragon skin overlays, ice-shard effects. Defaults to 12 seeds at 40-px max length. Iterative DFS instead of true recursion so deep branching chains never blow the stack. --- tools/editor/cli_gen_texture.cpp | 117 +++++++++++++++++++++++++++++++ tools/editor/cli_help.cpp | 2 + tools/editor/main.cpp | 1 + 3 files changed, 120 insertions(+) diff --git a/tools/editor/cli_gen_texture.cpp b/tools/editor/cli_gen_texture.cpp index 3add2518..7c38d205 100644 --- a/tools/editor/cli_gen_texture.cpp +++ b/tools/editor/cli_gen_texture.cpp @@ -4068,6 +4068,120 @@ int handleHoneycomb(int& i, int argc, char** argv) { return 0; } +int handleCracked(int& i, int argc, char** argv) { + // Cracked: organic crack network done via recursive random + // walks from N seed nuclei. Each seed spawns a crack that + // walks in a random direction for some length, then with + // 60% chance branches into one or two more cracks of + // shorter length. Result: irregular fissures that read as + // cracked mud, dry earth, broken glass, weathered stone. + std::string outPath = argv[++i]; + std::string bgHex = argv[++i]; + std::string crackHex = argv[++i]; + int seedCount = 12; + int maxLength = 40; + int W = 256, H = 256; + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { seedCount = std::stoi(argv[++i]); } catch (...) {} + } + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { maxLength = std::stoi(argv[++i]); } catch (...) {} + } + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { W = std::stoi(argv[++i]); } catch (...) {} + } + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { H = std::stoi(argv[++i]); } catch (...) {} + } + if (W < 1 || H < 1 || W > 8192 || H > 8192 || + seedCount < 1 || seedCount > 4096 || + maxLength < 4 || maxLength > 1024) { + std::fprintf(stderr, + "gen-texture-cracked: invalid dims (W/H 1..8192, seeds 1..4096, maxLen 4..1024)\n"); + return 1; + } + uint8_t br_, bg_, bb_, cr_, cg_, cb_; + if (!parseHex(bgHex, br_, bg_, bb_) || + !parseHex(crackHex, cr_, cg_, cb_)) { + std::fprintf(stderr, + "gen-texture-cracked: bg or crack hex color is invalid\n"); + return 1; + } + std::vector pixels(static_cast(W) * H * 3, 0); + for (size_t p = 0; p < pixels.size(); p += 3) { + pixels[p + 0] = br_; + pixels[p + 1] = bg_; + pixels[p + 2] = bb_; + } + // Deterministic LCG so re-runs reproduce the same pattern. + uint32_t rng = static_cast(seedCount) * 0x9E3779B9u + + static_cast(W) * 0x85EBCA6Bu + + static_cast(maxLength); + auto rngStep = [&]() { + rng ^= rng << 13; rng ^= rng >> 17; rng ^= rng << 5; + return rng; + }; + auto next01 = [&]() { return (rngStep() & 0xFFFFFF) / float(0x1000000); }; + auto paintPixel = [&](int x, int y) { + if (x < 0 || x >= W || y < 0 || y >= H) return; + size_t idx = (static_cast(y) * W + x) * 3; + pixels[idx + 0] = cr_; + pixels[idx + 1] = cg_; + pixels[idx + 2] = cb_; + }; + constexpr float kPi = 3.14159265358979323846f; + // Iterative DFS instead of true recursion so we don't blow + // the stack on long branching chains. + struct Crack { float x, y; int remaining; }; + std::vector stack; + for (int s = 0; s < seedCount; ++s) { + Crack seed; + seed.x = next01() * W; + seed.y = next01() * H; + seed.remaining = maxLength; + stack.push_back(seed); + } + while (!stack.empty()) { + Crack c = stack.back(); + stack.pop_back(); + if (c.remaining <= 0) continue; + // Pick a random direction (any angle) and a per-segment + // length up to remaining. + float angle = next01() * 2.0f * kPi; + float dx = std::cos(angle); + float dy = std::sin(angle); + int segLen = 4 + static_cast(next01() * (c.remaining - 4)); + float fx = c.x, fy = c.y; + for (int t = 0; t < segLen; ++t) { + paintPixel(static_cast(fx), static_cast(fy)); + fx += dx; + fy += dy; + } + // Branching: 60% chance the segment endpoint spawns 1 + // more crack of half-remaining length, 25% chance it + // spawns 2 (so most cracks die out, a few network). + float branchRoll = next01(); + int branches = (branchRoll < 0.25f) ? 2 : + (branchRoll < 0.85f) ? 1 : 0; + for (int b = 0; b < branches; ++b) { + stack.push_back({fx, fy, c.remaining / 2}); + } + } + if (!stbi_write_png(outPath.c_str(), W, H, 3, + pixels.data(), W * 3)) { + std::fprintf(stderr, + "gen-texture-cracked: stbi_write_png failed for %s\n", + outPath.c_str()); + return 1; + } + std::printf("Wrote %s\n", outPath.c_str()); + std::printf(" size : %dx%d\n", W, H); + std::printf(" bg / crack : %s / %s\n", bgHex.c_str(), crackHex.c_str()); + std::printf(" seeds : %d (max length %d, branching DFS)\n", + seedCount, maxLength); + return 0; +} + } // namespace bool handleGenTexture(int& i, int argc, char** argv, int& outRc) { @@ -4199,6 +4313,9 @@ bool handleGenTexture(int& i, int argc, char** argv, int& outRc) { if (std::strcmp(argv[i], "--gen-texture-honeycomb") == 0 && i + 3 < argc) { outRc = handleHoneycomb(i, argc, argv); return true; } + if (std::strcmp(argv[i], "--gen-texture-cracked") == 0 && i + 3 < argc) { + outRc = handleCracked(i, argc, argv); return true; + } return false; } diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index e36a992b..5d746212 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -119,6 +119,8 @@ void printUsage(const char* argv0) { std::printf(" Lattice: ±45° diagonal grid forming diamond openings (garden trellis / mesh fence)\n"); std::printf(" --gen-texture-honeycomb [hexSide] [W H]\n"); std::printf(" Honeycomb: hexagonal cells via Voronoi over a triangular seed lattice\n"); + std::printf(" --gen-texture-cracked [seeds] [maxLength] [W H]\n"); + std::printf(" Cracked: branching random walks form fissures (mud / glass / dry earth)\n"); std::printf(" --add-texture-to-zone [renameTo]\n"); std::printf(" Copy an existing PNG into (optionally renaming it on the way in)\n"); std::printf(" --gen-mesh [size]\n"); diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index e7e504ac..12e9c97c 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -179,6 +179,7 @@ int main(int argc, char* argv[]) { "--gen-texture-parquet", "--gen-texture-bubbles", "--gen-texture-spider-web", "--gen-texture-gingham", "--gen-texture-lattice", "--gen-texture-honeycomb", + "--gen-texture-cracked", "--validate-glb", "--info-glb", "--info-glb-tree", "--info-glb-bytes", "--validate-jsondbc", "--check-glb-bounds", "--validate-stl", "--validate-png", "--validate-blp",