From 74b8795aa3f54a28b40c9e19d422d0d16dd1db45 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 13:30:57 -0700 Subject: [PATCH] feat(editor): add --gen-texture-lightbeam vertical-ray gradient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 74th procedural texture: vertical light-beam / sun-ray gradient. Each pixel's brightness is the product of two fade factors: • vertical: 1.0 at the bright end, vFadeFrac at the dim end (direction flag selects bright-top 'd' or bright- bottom 'u') • radial: 1.0 inside the bright core (within beamHalfW of centerline), then linearly to 0.0 at the texture edge Two-color blend from bgHex to beamHex by combined brightness. Useful for dust-mote sunbeams through cathedral windows, holy radiance auras, crystal glow halos, lighthouse beams, projector / stage-light columns, ritual-summoning effects. First procedural texture with a directional flag (u / d) — flexible orientation for ceiling-down vs floor-up beams. --- tools/editor/cli_arg_required.cpp | 2 +- tools/editor/cli_gen_texture.cpp | 75 +++++++++++++++++++++++++++++++ tools/editor/cli_help.cpp | 2 + 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 62a0fd9c..3f855e9d 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -109,7 +109,7 @@ const char* const kArgRequired[] = { "--gen-texture-houndstooth", "--gen-texture-chevron", "--gen-texture-dunes", "--gen-texture-swirl", "--gen-texture-ironbark", "--gen-texture-mold", - "--gen-texture-embroidery", + "--gen-texture-embroidery", "--gen-texture-lightbeam", "--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 600bcd4d..dd0a6303 100644 --- a/tools/editor/cli_gen_texture.cpp +++ b/tools/editor/cli_gen_texture.cpp @@ -3500,6 +3500,80 @@ int handleKnit(int& i, int argc, char** argv) { return 0; } +int handleLightbeam(int& i, int argc, char** argv) { + // Vertical light-beam / sun-ray gradient. Brightness fades + // both horizontally (away from the center column) and + // vertically (top-down or bottom-up depending on direction + // flag). Useful for dust-mote sunbeams, holy radiance, + // crystal glow, lighthouse columns, projector / stage + // light effects. + std::string outPath = argv[++i]; + std::string bgHex = argv[++i]; + std::string beamHex = argv[++i]; + int beamHalfW = 32; // half-width of bright core in pixels + float vFadeFrac = 0.6f; // brightness retained at far end + char dir = 'd'; // 'd' = brightest at top, fades down + int W = 256, H = 256; + parseOptInt(i, argc, argv, beamHalfW); + parseOptFloat(i, argc, argv, vFadeFrac); + if (i + 1 < argc && argv[i + 1][0] != '-') { + const char* a = argv[++i]; + if (a[0] == 'u' || a[0] == 'U') dir = 'u'; + else if (a[0] == 'd' || a[0] == 'D') dir = 'd'; + } + parseOptInt(i, argc, argv, W); + parseOptInt(i, argc, argv, H); + if (W < 1 || H < 1 || W > 8192 || H > 8192 || + beamHalfW < 1 || beamHalfW > W || + vFadeFrac < 0.0f || vFadeFrac > 1.0f) { + std::fprintf(stderr, + "gen-texture-lightbeam: invalid dims (W/H 1..8192, " + "beamHalfW 1..W, vFadeFrac 0..1)\n"); + return 1; + } + uint8_t br_, bg_, bb_, lr, lg, lb_; + if (!parseHexOrError(bgHex, br_, bg_, bb_, + "gen-texture-lightbeam")) return 1; + if (!parseHexOrError(beamHex, lr, lg, lb_, + "gen-texture-lightbeam")) return 1; + std::vector pixels(static_cast(W) * H * 3, 0); + const float halfW = W * 0.5f; + const float maxRadial = static_cast(W) * 0.5f; + for (int y = 0; y < H; ++y) { + // Vertical fade: 1.0 at the bright end, vFadeFrac at the + // dim end. dir 'd' brightens at top (small y). + float yT = static_cast(y) / (H - 1); + if (dir == 'd') yT = 1.0f - yT * (1.0f - vFadeFrac); + else yT = vFadeFrac + yT * (1.0f - vFadeFrac); + for (int x = 0; x < W; ++x) { + float dx = std::abs(x - halfW); + // Radial fade: brightness drops from 1.0 in the core to + // 0 at maxRadial. Outside the bright core, exponential + // falloff continues to 0. + float radialT; + if (dx < beamHalfW) { + radialT = 1.0f; + } else { + float t = (dx - beamHalfW) / (maxRadial - beamHalfW); + radialT = std::max(0.0f, 1.0f - t); + } + float br = yT * radialT; + uint8_t r = static_cast(br_ + br * (lr - br_)); + uint8_t g = static_cast(bg_ + br * (lg - bg_)); + uint8_t b = static_cast(bb_ + br * (lb_ - bb_)); + setPixelRGB(pixels, W, x, y, r, g, b); + } + } + if (!savePngOrError(outPath, W, H, pixels, + "gen-texture-lightbeam")) return 1; + printPngWrote(outPath, W, H); + std::printf(" bg/beam : %s / %s\n", bgHex.c_str(), beamHex.c_str()); + std::printf(" beam : halfW=%d, vFade=%.2f, dir=%s\n", + beamHalfW, vFadeFrac, + dir == 'd' ? "down (bright top)" : "up (bright bottom)"); + return 0; +} + int handleEmbroidery(int& i, int argc, char** argv) { // Cross-stitch embroidery: a grid of X-shape stitches. Each // cell holds an X formed by two diagonal strokes from cell @@ -5379,6 +5453,7 @@ constexpr TextureEntry kTextureTable[] = { {"--gen-texture-ironbark", 3, handleIronbark}, {"--gen-texture-mold", 3, handleMold}, {"--gen-texture-embroidery", 3, handleEmbroidery}, + {"--gen-texture-lightbeam", 3, handleLightbeam}, }; } // namespace diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 623aa9af..d736739c 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -181,6 +181,8 @@ void printUsage(const char* argv0) { std::printf(" Mold: Worley-noise field patches (cellars / dungeon walls / sewer overflow / fungal growth)\n"); std::printf(" --gen-texture-embroidery [cellSize] [strokeW] [W H]\n"); std::printf(" Embroidery: grid of cross-stitch X marks (counted-thread textile / sampler / folk-art trim)\n"); + std::printf(" --gen-texture-lightbeam [beamHalfW] [vFadeFrac] [u|d] [W H]\n"); + std::printf(" Lightbeam: vertical sun-ray gradient fading horizontally + vertically (sunbeam / holy radiance)\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");