From 9031bdb620b48248189dfbbb677c1a520b0444d0 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 11:03:33 -0700 Subject: [PATCH] feat(editor): add --gen-texture-rope twisted-cordage pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 51st procedural texture: two interleaved sinusoidal strands running along the Y axis. Each strand's X position oscillates as W/4·sin(2π·y/period); the second strand is phase-shifted by π so the two snake around each other forming the classic helical twist of a tightened rope. Within each strand, brightness rises from 0.55 at the edge to 1.0 at the centerline (cos² falloff) — gives the rounded 3D appearance of cylindrical fibers without a separate shadow pass. Useful for hanging ropes, ship rigging, tied-bundle textures, suspension bridges, market-stall awning ties, and any "twisted cord" surface where a flat color would read as ribbon rather than rope. Default 24-pixel period with 8-pixel strands renders cleanly at 256x256 with about 10 visible twists. --- tools/editor/cli_arg_required.cpp | 1 + tools/editor/cli_gen_texture.cpp | 92 +++++++++++++++++++++++++++++++ tools/editor/cli_help.cpp | 2 + 3 files changed, 95 insertions(+) diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 14ee1810..bc1b4da7 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -85,6 +85,7 @@ const char* const kArgRequired[] = { "--gen-texture-leopard", "--gen-texture-zebra", "--gen-texture-knit", "--gen-texture-chainmail", "--gen-texture-planks", "--gen-texture-corrugated", + "--gen-texture-rope", "--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 e43ffe6e..9774c6a9 100644 --- a/tools/editor/cli_gen_texture.cpp +++ b/tools/editor/cli_gen_texture.cpp @@ -4586,6 +4586,97 @@ int handleKnit(int& i, int argc, char** argv) { return 0; } +int handleRope(int& i, int argc, char** argv) { + // Twisted rope/cordage: two interleaved sinusoidal strands + // running along the Y axis. Each strand's X position oscillates + // as cellW/2 * sin(2π·y/period), and the second strand is phase- + // shifted by π so the two snake around each other forming the + // classic helical twist. Highlight banding within each strand + // (brightness * cos²) gives the rounded 3D appearance of + // tightened twine. Useful for hanging ropes, ship rigging, + // tied-bundle textures, suspension bridges, market awning ties. + std::string outPath = argv[++i]; + std::string bgHex = argv[++i]; + std::string ropeHex = argv[++i]; + int period = 24; + int strandW = 8; + int W = 256, H = 256; + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { period = std::stoi(argv[++i]); } catch (...) {} + } + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { strandW = 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 || + period < 4 || period > 1024 || + strandW < 2 || strandW > W) { + std::fprintf(stderr, + "gen-texture-rope: invalid dims (W/H 1..8192, period 4..1024, " + "strandW 2..W)\n"); + return 1; + } + uint8_t br_, bg_, bb_, rr_, rg_, rb_; + if (!parseHex(bgHex, br_, bg_, bb_) || + !parseHex(ropeHex, rr_, rg_, rb_)) { + std::fprintf(stderr, + "gen-texture-rope: bg or rope hex color is invalid\n"); + return 1; + } + std::vector pixels(static_cast(W) * H * 3, 0); + const float twoPi = 6.28318530717958f; + const float pi = 3.14159265358979f; + // Amplitude: strands swing across W/2 so the centerline is + // always inside the texture and the two strands cross at the + // image midline x = W/2. + const float amp = (W * 0.25f); + const float fStrandHalf = strandW * 0.5f; + for (int y = 0; y < H; ++y) { + float phase = (static_cast(y) / period) * twoPi; + float cx1 = W * 0.5f + amp * std::sin(phase); + float cx2 = W * 0.5f + amp * std::sin(phase + pi); + for (int x = 0; x < W; ++x) { + float dx1 = std::abs(x - cx1); + float dx2 = std::abs(x - cx2); + float d = std::min(dx1, dx2); + uint8_t r, g, b; + if (d < fStrandHalf) { + // Brightness across the strand width: 1.0 at the + // centerline, 0.0 at the edge — gives the rounded + // highlight of a real cylindrical strand. + float t = 1.0f - (d / fStrandHalf); + float br = 0.55f + 0.45f * t * t; + r = static_cast(rr_ * br); + g = static_cast(rg_ * br); + b = static_cast(rb_ * br); + } else { + r = br_; g = bg_; b = bb_; + } + size_t idx = (static_cast(y) * W + x) * 3; + pixels[idx + 0] = r; + pixels[idx + 1] = g; + pixels[idx + 2] = b; + } + } + if (!stbi_write_png(outPath.c_str(), W, H, 3, + pixels.data(), W * 3)) { + std::fprintf(stderr, + "gen-texture-rope: 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/rope : %s / %s\n", bgHex.c_str(), ropeHex.c_str()); + std::printf(" twist : period=%d, strandW=%d\n", period, strandW); + return 0; +} + int handleCorrugated(int& i, int argc, char** argv) { // Corrugated metal sheeting: smooth cosine ridges along one // axis. Each pixel's brightness comes from cos((x or y) / @@ -4928,6 +5019,7 @@ constexpr TextureEntry kTextureTable[] = { {"--gen-texture-chainmail", 3, handleChainmail}, {"--gen-texture-planks", 3, handlePlanks}, {"--gen-texture-corrugated", 3, handleCorrugated}, + {"--gen-texture-rope", 3, handleRope}, }; } // namespace diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 599ad917..3fb068a0 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -135,6 +135,8 @@ void printUsage(const char* argv0) { std::printf(" Planks: horizontal floor boards with per-plank tint, grain streaks, and stagger seams\n"); std::printf(" --gen-texture-corrugated [period] [v|h] [W H]\n"); std::printf(" Corrugated: smooth cosine ridges between bg and hi (sheet-metal roofing / siding)\n"); + std::printf(" --gen-texture-rope [period] [strandW] [W H]\n"); + std::printf(" Rope: two interleaved sinusoidal strands with cylindrical highlight (twisted cordage)\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");