diff --git a/core/src/TileTexture.cpp b/core/src/TileTexture.cpp index 4abfe96..be6e80f 100644 --- a/core/src/TileTexture.cpp +++ b/core/src/TileTexture.cpp @@ -312,3 +312,149 @@ PatternSample splatterPattern(const TileSpec& spec, int tileSalt, int x, int y, return out; } +// Travelling crests with ripple on top. When the spec is a cutout the troughs +// open right through, which is how a portal reads as a hole in the world. +PatternSample liquidPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + float drift = lineNoise(tileSalt, x * 2.0f / size, 71u, seed); + float crest = std::sin((y * (1.5f + spec.scale * 2.5f) / size + drift * 0.7f) * twoPi); + float ripple = fractalNoise(tileSalt, x * 3.0f / size, y * 3.0f / size, 73u, seed); + + PatternSample out; + out.accentWeight = clamp01((0.5f + 0.5f * crest) * spec.density * (0.55f + 0.7f * ripple)); + out.shade = 0.95f + 0.12f * ripple; + out.hole = out.accentWeight < 0.16f && ripple < 0.50f; + return out; +} + +// A plant sprite: a wandering stalk with leaves hung off it, everything else +// cut away. Without the vertical flag it becomes the flat cross a dust line or +// a tripwire draws on the ground. +PatternSample crossPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + float centre = (size - 1) * 0.5f; + PatternSample out; + + if (!spec.vertical) { + float armHalf = std::max(0.6f, size * (0.05f + spec.scale * 0.09f)); + float dx = std::fabs(x - centre); + float dy = std::fabs(y - centre); + if (dx <= armHalf || dy <= armHalf) { + float edge = std::min(dx, dy) / std::max(1.0f, armHalf); + out.accentWeight = clamp01(1.0f - edge) * spec.density; + out.shade = 0.92f + 0.16f * gridValue(tileSalt, x, y, 79u, seed); + return out; + } + out.hole = true; + out.shade = 0.34f; + return out; + } + + float sway = (lineNoise(tileSalt, y * 3.0f / size, 79u, seed) - 0.5f) * size * 0.16f; + float stalkHalf = std::max(0.6f, size * (0.05f + spec.scale * 0.07f)); + float dx = std::fabs(x - centre - sway); + if (dx <= stalkHalf) { + // The top of a stalk is its head: the flame on a torch, the ear on + // wheat, the bud on a sapling. It carries the accent, the stem does not. + float headDepth = size * 0.30f; + out.accentWeight = clamp01((headDepth - y) / headDepth); + out.shade = 1.02f - 0.22f * (dx / std::max(1.0f, stalkHalf)); + return out; + } + + float leafField = fractalNoise(tileSalt, x * 4.0f / size, y * 4.0f / size, 83u, seed); + float reach = stalkHalf + size * (0.10f + spec.density * 0.24f) * leafField; + if (dx <= reach && leafField > 0.42f) { + out.accentWeight = 1.0f; + out.shade = 0.90f + 0.20f * leafField; + return out; + } + out.hole = true; + out.shade = 0.34f; + return out; +} + +PatternSample webPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + float centre = (size - 1) * 0.5f; + float dx = x - centre; + float dy = y - centre; + float pixels = std::sqrt(dx * dx + dy * dy); + float radius = pixels / (size * 0.5f); + float spokes = 8.0f; + float angle = std::atan2(dy, dx) / twoPi + 0.5f; + float spokePhase = angle * spokes; + float spokeOffset = std::fabs(spokePhase - std::floor(spokePhase + 0.5f)); + float spokeDistance = spokeOffset * (twoPi / spokes) * pixels; + + PatternSample out; + float rings = 2.0f + spec.scale * 4.0f; + float ringPhase = radius * rings; + float ringOffset = std::fabs(ringPhase - std::floor(ringPhase + 0.5f)); + float ringDistance = ringOffset * (size * 0.5f) / rings; + + bool onSpoke = spokeDistance < 0.60f && radius <= 1.02f; + bool onRing = ringDistance < 0.45f && radius <= 0.98f; + if (onSpoke || onRing) { + out.accentWeight = onRing && !onSpoke ? 1.0f : 0.0f; + out.shade = 0.92f + 0.16f * gridValue(tileSalt, x, y, 89u, seed); + return out; + } + out.hole = true; + out.shade = 0.30f; + return out; +} + +PatternSample panePattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + int border = std::max(1, static_cast(std::lround(size * (0.04f + spec.scale * 0.06f)))); + bool onFrame = x < border || y < border || x >= size - border || y >= size - border; + bool onCorner = (x < border * 3 && y < border * 3) || + (x >= size - border * 3 && y >= size - border * 3); + + PatternSample out; + if (onFrame || onCorner) { + out.accentWeight = onCorner && !onFrame ? 1.0f : 0.0f; + out.shade = 0.94f + 0.12f * gridValue(tileSalt, x, y, 97u, seed); + return out; + } + int quarter = std::max(2, size / 4); + if (x - y == quarter || y - x == quarter) { + out.accentWeight = spec.density; + out.shade = 1.10f; + return out; + } + out.hole = true; + out.shade = 0.55f; + return out; +} + +PatternSample samplePattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + switch (spec.pattern) { + case TilePattern::Solid: return solidPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::OreSpeckle: return orePattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Brick: return brickPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Plank: return plankPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Lattice: return latticePattern(spec, tileSalt, x, y, size, seed); + case TilePattern::EdgeBand: return edgeBandPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::GrowthRings: return growthRingsPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Dots: return dotsPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Strata: return strataPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Splatter: return splatterPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Liquid: return liquidPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Cross: return crossPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Web: return webPattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Pane: return panePattern(spec, tileSalt, x, y, size, seed); + case TilePattern::Count: break; + } + return PatternSample{}; +} + +// The grain the material lays over the pattern, as a brightness multiplier. +// Its amplitude is the spec's noise, so a tile with no noise has no grain at +// all and shows exactly what its pattern drew. +float materialGrain(const TileSpec& spec, int tileSalt, int x, int y, int size, uint32_t seed) { + float amount = spec.noise; + if (amount <= 0.0f) return 1.0f; +