From 63048c135683308b080cb7daac49517d188698d8 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 11:20:31 -0700 Subject: [PATCH] feat(editor): add --gen-texture-starburst radial-rays pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 53rd procedural texture: N rays radiating from the texture center. Each pixel computes its angle via atan2 and finds the angular distance to the nearest ray axis (handling the ±π wrap so rays around the seam are continuous). Pixels inside any ray's angular band get the ray color. Brightness tapers linearly with distance from center: 1.0 at the hub, 0.4 at the texture diagonal — gives sun-rays that fade as they extend outward instead of reading as infinite lines. Useful for sun motifs, holy/divine symbols, paladin insignias, mage-robe trim, mosaic medallion centers, shrine floor inlays, and any "radiant glory" surface. --- tools/editor/cli_arg_required.cpp | 1 + tools/editor/cli_gen_texture.cpp | 97 +++++++++++++++++++++++++++++++ tools/editor/cli_help.cpp | 2 + 3 files changed, 100 insertions(+) diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index c2b3b005..40b326d1 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -86,6 +86,7 @@ const char* const kArgRequired[] = { "--gen-texture-knit", "--gen-texture-chainmail", "--gen-texture-planks", "--gen-texture-corrugated", "--gen-texture-rope", "--gen-texture-caustics", + "--gen-texture-starburst", "--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 b83d6224..c93ecbab 100644 --- a/tools/editor/cli_gen_texture.cpp +++ b/tools/editor/cli_gen_texture.cpp @@ -4586,6 +4586,102 @@ int handleKnit(int& i, int argc, char** argv) { return 0; } +int handleStarburst(int& i, int argc, char** argv) { + // Starburst: N rays radiating from the texture center. Each + // pixel computes its angle from center; if it falls inside any + // ray's angular band, paint it as the ray color. Brightness + // tapers with distance from the center (1.0 at the hub down to + // a configurable rim factor at the texture edge) so the rays + // read as fading light beams rather than infinite lines. + std::string outPath = argv[++i]; + std::string bgHex = argv[++i]; + std::string rayHex = argv[++i]; + int rayCount = 12; + float beamWidth = 0.18f; // radians half-width of each ray + int W = 256, H = 256; + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { rayCount = std::stoi(argv[++i]); } catch (...) {} + } + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { beamWidth = std::stof(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 || + rayCount < 2 || rayCount > 256 || + beamWidth <= 0 || beamWidth >= 3.14f) { + std::fprintf(stderr, + "gen-texture-starburst: invalid dims (W/H 1..8192, " + "rays 2..256, beamWidth (0,π))\n"); + return 1; + } + uint8_t br_, bg_, bb_, rr_, rg_, rb_; + if (!parseHex(bgHex, br_, bg_, bb_) || + !parseHex(rayHex, rr_, rg_, rb_)) { + std::fprintf(stderr, + "gen-texture-starburst: bg or ray hex color is invalid\n"); + return 1; + } + std::vector pixels(static_cast(W) * H * 3, 0); + const float twoPi = 6.28318530717958f; + const float cx = W * 0.5f; + const float cy = H * 0.5f; + const float maxR = std::sqrt(cx * cx + cy * cy); + const float anglePer = twoPi / rayCount; + for (int y = 0; y < H; ++y) { + for (int x = 0; x < W; ++x) { + float dx = x - cx; + float dy = y - cy; + float r = std::sqrt(dx * dx + dy * dy); + float theta = std::atan2(dy, dx); // [-π, +π] + // Distance in angle space to the nearest ray axis. The + // ray axes are at integer multiples of anglePer; we + // wrap theta into [0, anglePer) and take the smaller of + // (wrapped, anglePer - wrapped) so the ends meet around + // the wraparound boundary. + float wrapped = std::fmod(theta + twoPi, anglePer); + float angDist = std::min(wrapped, anglePer - wrapped); + uint8_t resR, resG, resB; + if (angDist < beamWidth) { + // Brightness: 1.0 at the hub, falling linearly to + // 0.4 at the texture diagonal — gives sun-rays that + // taper as they extend outward. + float t = std::max(0.0f, std::min(1.0f, r / maxR)); + float bright = 1.0f - 0.6f * t; + resR = static_cast(std::min(255.0f, + br_ + bright * (rr_ - br_))); + resG = static_cast(std::min(255.0f, + bg_ + bright * (rg_ - bg_))); + resB = static_cast(std::min(255.0f, + bb_ + bright * (rb_ - bb_))); + } else { + resR = br_; resG = bg_; resB = bb_; + } + size_t idx = (static_cast(y) * W + x) * 3; + pixels[idx + 0] = resR; + pixels[idx + 1] = resG; + pixels[idx + 2] = resB; + } + } + if (!stbi_write_png(outPath.c_str(), W, H, 3, + pixels.data(), W * 3)) { + std::fprintf(stderr, + "gen-texture-starburst: 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/ray : %s / %s\n", bgHex.c_str(), rayHex.c_str()); + std::printf(" rays : %d (beam width %.3f rad)\n", + rayCount, beamWidth); + return 0; +} + int handleCaustics(int& i, int argc, char** argv) { // Water caustics: 4 superimposed sine waves running along // x, y, x+y, and x-y, summed into [-4,+4] and remapped to @@ -5097,6 +5193,7 @@ constexpr TextureEntry kTextureTable[] = { {"--gen-texture-corrugated", 3, handleCorrugated}, {"--gen-texture-rope", 3, handleRope}, {"--gen-texture-caustics", 3, handleCaustics}, + {"--gen-texture-starburst", 3, handleStarburst}, }; } // namespace diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index ac71e4f7..30bd9795 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -139,6 +139,8 @@ void printUsage(const char* argv0) { std::printf(" Rope: two interleaved sinusoidal strands with cylindrical highlight (twisted cordage)\n"); std::printf(" --gen-texture-caustics [period] [W H]\n"); std::printf(" Caustics: 4 superimposed sine waves (x/y/x+y/x-y) producing diamond-mesh water shimmer\n"); + std::printf(" --gen-texture-starburst [rays] [beamWidth] [W H]\n"); + std::printf(" Starburst: N rays radiating from center with linear falloff (sun / holy symbol / mosaic hub)\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");