From ad21d16df0ce8ec98cdd8aff9677071ebcb54ccc Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 13:03:00 -0700 Subject: [PATCH] feat(editor): add --gen-texture-dunes wave-ripple pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 69th procedural texture: stack of parallel sinusoidal curves spaced verticalSpacing apart. A pixel falls on a dune line if its Y is within lineW/2 pixels of the nearest curve at y = N*spacing + amp*sin(2π*x/period). Distinct from --gen-texture-corrugated (uniform parallel lines) and --gen-texture-zebra (sin-shifted strip fills) — this is the discrete-curve variant for desert ground textures and shallow-water sand patterns. Useful for desert ground, shallow-water sand bottoms, beach close-ups, mirage scene-setters, sand-storm overlays, zone-specific weathering on stone surfaces. --- tools/editor/cli_arg_required.cpp | 1 + tools/editor/cli_gen_texture.cpp | 67 +++++++++++++++++++++++++++++++ tools/editor/cli_help.cpp | 2 + 3 files changed, 70 insertions(+) diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 028ad653..a6e7cca5 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -105,6 +105,7 @@ const char* const kArgRequired[] = { "--gen-texture-blueprint", "--gen-texture-rust-streaks", "--gen-texture-plaid", "--gen-texture-diamond-grid", "--gen-texture-houndstooth", "--gen-texture-chevron", + "--gen-texture-dunes", "--validate-glb", "--info-glb", "--info-glb-tree", "--info-glb-bytes", "--validate-jsondbc", "--check-glb-bounds", "--validate-stl", "--validate-png", "--validate-blp", diff --git a/tools/editor/cli_gen_texture.cpp b/tools/editor/cli_gen_texture.cpp index f31664c8..8521ef82 100644 --- a/tools/editor/cli_gen_texture.cpp +++ b/tools/editor/cli_gen_texture.cpp @@ -3500,6 +3500,72 @@ int handleKnit(int& i, int argc, char** argv) { return 0; } +int handleDunes(int& i, int argc, char** argv) { + // Sand dunes / wave-ripple pattern: a stack of parallel + // sinusoidal curves spaced verticalSpacing apart. A pixel + // falls on a curve if its (y mod spacing) is within lineW + // pixels of the sine offset for the current x. Distinct + // from --gen-texture-corrugated (uniform parallel lines) + // and --gen-texture-zebra (sin-shifted strip fills) — this + // is the discrete-curve variant for desert ground textures + // and shallow-water sand patterns. + std::string outPath = argv[++i]; + std::string bgHex = argv[++i]; + std::string lineHex = argv[++i]; + int verticalSpacing = 16; // pixels between adjacent dunes + int period = 64; // sine period in pixels + int amp = 6; // sine amplitude in pixels + int lineW = 2; // line thickness + int W = 256, H = 256; + parseOptInt(i, argc, argv, verticalSpacing); + parseOptInt(i, argc, argv, period); + parseOptInt(i, argc, argv, amp); + parseOptInt(i, argc, argv, lineW); + parseOptInt(i, argc, argv, W); + parseOptInt(i, argc, argv, H); + if (W < 1 || H < 1 || W > 8192 || H > 8192 || + verticalSpacing < 4 || verticalSpacing > 1024 || + period < 4 || period > 4096 || + amp < 0 || amp * 2 >= verticalSpacing || + lineW < 1 || lineW * 2 >= verticalSpacing) { + std::fprintf(stderr, + "gen-texture-dunes: invalid dims (W/H 1..8192, " + "spacing 4..1024, period 4..4096, amp*2 < spacing, " + "lineW*2 < spacing)\n"); + return 1; + } + uint8_t br_, bg_, bb_, lr, lg, lb_; + if (!parseHexOrError(bgHex, br_, bg_, bb_, "gen-texture-dunes")) return 1; + if (!parseHexOrError(lineHex, lr, lg, lb_, + "gen-texture-dunes")) return 1; + std::vector pixels(static_cast(W) * H * 3, 0); + const float twoPi = 6.28318530717958f; + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + float sineOffset = amp * std::sin(twoPi * x / period); + // The "ideal" curve is at y = N*spacing + sineOffset. + // Check distance to the nearest curve. + float yShifted = y - sineOffset; + int bandIdx = static_cast(std::round(yShifted / verticalSpacing)); + float idealY = bandIdx * verticalSpacing + sineOffset; + float dist = std::abs(y - idealY); + uint8_t r, g, b; + if (dist < lineW * 0.5f) { + r = lr; g = lg; b = lb_; + } else { + r = br_; g = bg_; b = bb_; + } + setPixelRGB(pixels, W, x, y, r, g, b); + } + } + if (!savePngOrError(outPath, W, H, pixels, "gen-texture-dunes")) return 1; + printPngWrote(outPath, W, H); + std::printf(" bg/line : %s / %s\n", bgHex.c_str(), lineHex.c_str()); + std::printf(" ripples : spacing=%d, period=%d, amp=%d, lineW=%d\n", + verticalSpacing, period, amp, lineW); + return 0; +} + int handleChevron(int& i, int argc, char** argv) { // Chevron: stack of V-shape bands. Within each vertical // period, the upper half slopes one way and the lower half @@ -5012,6 +5078,7 @@ constexpr TextureEntry kTextureTable[] = { {"--gen-texture-diamond-grid", 3, handleDiamondGrid}, {"--gen-texture-houndstooth", 3, handleHoundstooth}, {"--gen-texture-chevron", 3, handleChevron}, + {"--gen-texture-dunes", 3, handleDunes}, }; } // namespace diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index e25ded96..5492899b 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -171,6 +171,8 @@ void printUsage(const char* argv0) { std::printf(" Houndstooth: classic textile broken-check pattern via seamless 8x8 motif (Scottish weave)\n"); std::printf(" --gen-texture-chevron [period] [stride] [lineW] [W H]\n"); std::printf(" Chevron: stack of V-shape stripes with sharp seams (military / sportswear / heraldic banners)\n"); + std::printf(" --gen-texture-dunes [verticalSpacing] [period] [amp] [lineW] [W H]\n"); + std::printf(" Dunes: stack of parallel sinusoidal curves (desert ground / shallow-water sand / wave ripples)\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");