From 1063442547df1da248982f6b282f5f3d4fd9e559 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 19:58:45 +0200 Subject: [PATCH] feat: add latticePattern and edgeBandPattern to TileTexture --- core/src/TileTexture.cpp | 162 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/core/src/TileTexture.cpp b/core/src/TileTexture.cpp index 34451e7..4abfe96 100644 --- a/core/src/TileTexture.cpp +++ b/core/src/TileTexture.cpp @@ -150,3 +150,165 @@ PatternSample plankPattern(const TileSpec& spec, int tileSalt, int x, int y, int int board = across / boardWidth; int localAcross = across - board * boardWidth; + PatternSample out; + if (localAcross == 0) { + out.accentWeight = 1.0f; + out.shade = 0.88f; + return out; + } + out.shade = 0.94f + 0.12f * gridValue(tileSalt, board, spec.flipped ? 5 : 0, 31u, seed); + if (localAcross == boardWidth - 1) out.shade *= 0.94f; + float grain = lineNoise(tileSalt, along * (2.0f + spec.density * 5.0f) / size + board * 7.0f, + 33u, seed); + out.accentWeight = clamp01((grain - 0.52f) * 3.2f) * 0.7f; + return out; +} + +// Two rails with rungs strung between them, or a full grid when flipped. The +// rails sit a little inside the tile edge, which is the difference between a +// ladder and a border, and the rungs only span the gap between them. +PatternSample latticePattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + int barWidth = std::max(1, static_cast(std::lround(size * (0.06f + spec.scale * 0.14f)))); + int spacing = std::max(barWidth * 2 + 1, + static_cast(std::lround(size * (0.22f + (1.0f - spec.density) * 0.3f)))); + int across = spec.vertical ? x : y; + int along = spec.vertical ? y : x; + + bool onRail = false; + bool onCross = false; + if (spec.flipped) { + onRail = (across % spacing) < barWidth; + onCross = (along % spacing) < barWidth; + } else { + int inset = std::max(0, size / 8); + onRail = (across >= inset && across < inset + barWidth) || + (across >= size - inset - barWidth && across < size - inset); + bool betweenRails = across >= inset && across < size - inset; + onCross = betweenRails && (along % spacing) < barWidth; + } + + PatternSample out; + if (onRail || onCross) { + out.accentWeight = (onCross && !onRail) ? 1.0f : 0.0f; + out.shade = 0.90f + 0.18f * gridValue(tileSalt, x, y, 41u, seed); + return out; + } + out.hole = true; + out.shade = 0.42f; + return out; +} + +// A band of accent hugging one edge, its inner boundary chewed up by noise so +// it does not read as a ruled line. +PatternSample edgeBandPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + float thickness = size * (0.15f + spec.scale * 0.35f); + float depth = spec.flipped ? static_cast(size - 1 - y) : static_cast(y); + float fringe = (lineNoise(tileSalt, x * 4.0f / size, 43u, seed) - 0.5f) * size * 0.16f; + float edge = thickness + fringe; + + PatternSample out; + if (depth < edge) { + out.accentWeight = 1.0f; + out.shade = 1.0f - 0.10f * (depth / std::max(1.0f, edge)); + return out; + } + if (depth < edge + 1.0f) out.shade = 0.84f; + out.accentWeight = 0.0f; + return out; +} + +PatternSample growthRingsPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + float centre = size * 0.5f; + float dx = x + 0.5f - centre; + float dy = y + 0.5f - centre; + float radius = std::sqrt(dx * dx + dy * dy); + float spacing = std::max(1.2f, size * (0.05f + spec.scale * 0.16f)); + // The warp has to stay a fraction of the ring spacing. Scaled to the tile + // instead, it swallows tightly spaced rings and the tile goes flat. + float warp = (fractalNoise(tileSalt, x * 3.0f / size, y * 3.0f / size, 47u, seed) - 0.5f) * + spacing * 0.7f; + float phase = (radius + warp) / spacing; + float ring = std::fabs(phase - std::floor(phase) - 0.5f) * 2.0f; + + PatternSample out; + out.accentWeight = clamp01((1.0f - ring) * 1.4f) * spec.density; + if (radius < spacing * 0.55f) out.accentWeight = spec.density; + out.shade = 1.0f - 0.10f * clamp01(radius / (size * 0.7f)); + return out; +} + +// Round dots on a jittered cell grid: glowstone crumbs, amethyst facets, the +// sculk's blue eyes. Only the nine surrounding cells are ever consulted. +PatternSample dotsPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + float cells = 2.0f + (1.0f - spec.scale) * 4.0f; + float cellSize = size / cells; + float fx = (x + 0.5f) / cellSize; + float fy = (y + 0.5f) / cellSize; + int cx = static_cast(std::floor(fx)); + int cy = static_cast(std::floor(fy)); + + float nearest = 8.0f; + for (int oy = -1; oy <= 1; ++oy) { + for (int ox = -1; ox <= 1; ++ox) { + int gx = cx + ox; + int gy = cy + oy; + if (gridValue(tileSalt, gx, gy, 53u, seed) > spec.density) continue; + float jitterX = gx + 0.25f + 0.5f * gridValue(tileSalt, gx, gy, 51u, seed); + float jitterY = gy + 0.25f + 0.5f * gridValue(tileSalt, gx, gy, 52u, seed); + float dx = fx - jitterX; + float dy = fy - jitterY; + float distance = std::sqrt(dx * dx + dy * dy); + if (distance < nearest) nearest = distance; + } + } + + float radius = 0.16f + spec.scale * 0.30f; + PatternSample out; + if (nearest < radius) { + out.accentWeight = clamp01((1.0f - nearest / radius) * 1.7f); + out.shade = 1.0f + 0.10f * out.accentWeight; + } + return out; +} + +PatternSample strataPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + float across = spec.vertical ? static_cast(x) : static_cast(y); + float along = spec.vertical ? static_cast(y) : static_cast(x); + float bands = 3.0f + spec.scale * 6.0f; + float warp = (lineNoise(tileSalt, along * 3.0f / size, 59u, seed) - 0.5f) * size * 0.12f; + float phase = (across + warp) * bands / size; + float within = phase - std::floor(phase); + + PatternSample out; + out.accentWeight = within < spec.density ? 1.0f : 0.0f; + out.shade = 1.0f + 0.07f * std::sin(phase * twoPi); + if (within < 0.10f) out.shade *= 0.88f; + return out; +} + +// Irregular blobs with a dark rim, which is what turns a flat colour into +// cobble, gravel or netherrack. +PatternSample splatterPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + float coarseCells = 2.0f + spec.scale * 3.0f; + float fineCells = coarseCells * 2.6f; + float coarse = fractalNoise(tileSalt, x * coarseCells / size, y * coarseCells / size, 61u, seed); + float fine = fractalNoise(tileSalt, x * fineCells / size, y * fineCells / size, 67u, seed); + float field = coarse * 0.66f + fine * 0.34f; + float threshold = 1.0f - spec.density * 0.9f; + + PatternSample out; + if (field > threshold) { + out.accentWeight = clamp01((field - threshold) * 6.0f); + } else if (field > threshold - 0.05f) { + out.shade = 0.80f; + } + out.shade *= 0.95f + 0.10f * fine; + return out; +} +