From 6b1b901bc59e94aa84911ccea2bac22c8381cbd0 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 03:23:01 -0700 Subject: [PATCH] feat(editor): add --gen-texture-mosaic 3-color tile pattern Per-cell hash picks one of 3 colors for each square tile, with 1-pixel black grout on the top + left edges of every cell so tiles read as physically separated. Exit log breaks down how many tiles got each color so callers can verify the seed distribution. Defaults: tilePx=16, seed=1. Useful for cathedral floors, stained-glass set dressing, ornate plaza paving. Brings the procedural texture pattern set to 28. --- tools/editor/cli_gen_texture.cpp | 95 ++++++++++++++++++++++++++++++++ tools/editor/cli_help.cpp | 2 + tools/editor/main.cpp | 1 + 3 files changed, 98 insertions(+) diff --git a/tools/editor/cli_gen_texture.cpp b/tools/editor/cli_gen_texture.cpp index 8e4ef547..ac9e4b45 100644 --- a/tools/editor/cli_gen_texture.cpp +++ b/tools/editor/cli_gen_texture.cpp @@ -2278,6 +2278,98 @@ int handleVines(int& i, int argc, char** argv) { return 0; } +int handleMosaic(int& i, int argc, char** argv) { + // 3-color mosaic: small square tiles randomly assigned one + // of 3 colors, with 1-px black grout lines between them. + // Per-tile color picked from a stable hash so the same seed + // always yields the same mosaic. + std::string outPath = argv[++i]; + std::string aHex = argv[++i]; + std::string bHex = argv[++i]; + std::string cHex = argv[++i]; + int tilePx = 16; + uint32_t seed = 1; + int W = 256, H = 256; + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { tilePx = std::stoi(argv[++i]); } catch (...) {} + } + if (i + 1 < argc && argv[i + 1][0] != '-') { + try { seed = static_cast(std::stoul(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 || + tilePx < 4 || tilePx > 256) { + std::fprintf(stderr, + "gen-texture-mosaic: invalid dims (W/H 1..8192, tilePx 4..256)\n"); + return 1; + } + uint8_t ar, ag, ab, br, bg, bb_, cr, cg, cb; + if (!parseHex(aHex, ar, ag, ab) || + !parseHex(bHex, br, bg, bb_) || + !parseHex(cHex, cr, cg, cb)) { + std::fprintf(stderr, + "gen-texture-mosaic: one of the hex colors is invalid\n"); + return 1; + } + auto cellPick = [seed](int cx, int cy) -> int { + uint32_t h = static_cast(cx) * 374761393u + + static_cast(cy) * 668265263u + + seed * 2147483647u; + h = (h ^ (h >> 13)) * 1274126177u; + h = h ^ (h >> 16); + return h % 3; + }; + std::vector pixels(static_cast(W) * H * 3, 0); + int counts[3] = {0, 0, 0}; + for (int y = 0; y < H; ++y) { + int cy = y / tilePx; + int yInCell = y % tilePx; + for (int x = 0; x < W; ++x) { + int cx = x / tilePx; + int xInCell = x % tilePx; + // 1-px grout on the top and left edge of every cell. + bool grout = (xInCell == 0) || (yInCell == 0); + size_t i2 = (static_cast(y) * W + x) * 3; + if (grout) { + pixels[i2 + 0] = 0; + pixels[i2 + 1] = 0; + pixels[i2 + 2] = 0; + } else { + int pick = cellPick(cx, cy); + if (yInCell == 1 && xInCell == 1) ++counts[pick]; + if (pick == 0) { + pixels[i2 + 0] = ar; pixels[i2 + 1] = ag; pixels[i2 + 2] = ab; + } else if (pick == 1) { + pixels[i2 + 0] = br; pixels[i2 + 1] = bg; pixels[i2 + 2] = bb_; + } else { + pixels[i2 + 0] = cr; pixels[i2 + 1] = cg; pixels[i2 + 2] = cb; + } + } + } + } + if (!stbi_write_png(outPath.c_str(), W, H, 3, + pixels.data(), W * 3)) { + std::fprintf(stderr, + "gen-texture-mosaic: 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(" colors : %s / %s / %s\n", + aHex.c_str(), bHex.c_str(), cHex.c_str()); + std::printf(" tile px : %d\n", tilePx); + std::printf(" tile counts: A=%d B=%d C=%d\n", + counts[0], counts[1], counts[2]); + std::printf(" seed : %u\n", seed); + return 0; +} + } // namespace bool handleGenTexture(int& i, int argc, char** argv, int& outRc) { @@ -2355,6 +2447,9 @@ bool handleGenTexture(int& i, int argc, char** argv, int& outRc) { if (std::strcmp(argv[i], "--gen-texture-vines") == 0 && i + 3 < argc) { outRc = handleVines(i, argc, argv); return true; } + if (std::strcmp(argv[i], "--gen-texture-mosaic") == 0 && i + 4 < argc) { + outRc = handleMosaic(i, argc, argv); return true; + } return false; } diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index 0ed37ee7..ee064899 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -83,6 +83,8 @@ void printUsage(const char* argv0) { std::printf(" Night sky with scattered stars (varied brightness, density 0..1, default 0.005)\n"); std::printf(" --gen-texture-vines [seed] [vineCount] [W H]\n"); std::printf(" Wall with climbing vines: meandering vertical paths via random walk (default 8 vines)\n"); + std::printf(" --gen-texture-mosaic [tilePx] [seed] [W H]\n"); + std::printf(" 3-color mosaic: small square tiles with random color picks + grout (default 16px)\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 837f544d..2343434b 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -215,6 +215,7 @@ int main(int argc, char* argv[]) { "--gen-texture-leather", "--gen-texture-sand", "--gen-texture-snow", "--gen-texture-lava", "--gen-texture-tile", "--gen-texture-bark", "--gen-texture-clouds", "--gen-texture-stars", "--gen-texture-vines", + "--gen-texture-mosaic", "--validate-glb", "--info-glb", "--info-glb-tree", "--info-glb-bytes", "--validate-jsondbc", "--check-glb-bounds", "--validate-stl", "--validate-png", "--validate-blp",