From 4cbd72c75b377c492bb224b450ef7a30998806d9 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 9 Sep 2026 19:34:47 +0200 Subject: [PATCH] feat: add clamp01 and toByte --- core/src/TileTexture.cpp | 152 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 core/src/TileTexture.cpp diff --git a/core/src/TileTexture.cpp b/core/src/TileTexture.cpp new file mode 100644 index 0000000..34451e7 --- /dev/null +++ b/core/src/TileTexture.cpp @@ -0,0 +1,152 @@ +#include "vf/TileTexture.h" + +#include +#include + +namespace vf { + +namespace { + +constexpr float twoPi = 6.28318530718f; + +float clamp01(float v) { + if (v < 0.0f) return 0.0f; + if (v > 1.0f) return 1.0f; + return v; +} + +uint8_t toByte(float v) { + if (v < 0.0f) return 0; + if (v > 255.0f) return 255; + return static_cast(v + 0.5f); +} + +uint32_t scramble(uint32_t v) { + v ^= v >> 16; + v *= 0x7feb352du; + v ^= v >> 15; + v *= 0x846ca68bu; + v ^= v >> 16; + return v; +} + +// The tile id takes part in the hash so two tiles that share a pattern still +// get different grain, and the seed is folded in so params.noiseSeed actually +// reaches every sample. +uint32_t gridHash(int tileSalt, int x, int y, uint32_t salt, uint32_t seed) { + uint32_t h = static_cast(tileSalt) * 374761393u + + static_cast(x) * 668265263u + + static_cast(y) * 2246822519u + salt * 2654435761u + seed; + return scramble(h); +} + +float gridValue(int tileSalt, int x, int y, uint32_t salt, uint32_t seed) { + return static_cast(gridHash(tileSalt, x, y, salt, seed) & 0xffffu) / 65535.0f; +} + +float easeCurve(float t) { return t * t * (3.0f - 2.0f * t); } + +float valueNoise(int tileSalt, float fx, float fy, uint32_t salt, uint32_t seed) { + float baseX = std::floor(fx); + float baseY = std::floor(fy); + int x0 = static_cast(baseX); + int y0 = static_cast(baseY); + float tx = easeCurve(fx - baseX); + float ty = easeCurve(fy - baseY); + float corner00 = gridValue(tileSalt, x0, y0, salt, seed); + float corner10 = gridValue(tileSalt, x0 + 1, y0, salt, seed); + float corner01 = gridValue(tileSalt, x0, y0 + 1, salt, seed); + float corner11 = gridValue(tileSalt, x0 + 1, y0 + 1, salt, seed); + float top = corner00 + (corner10 - corner00) * tx; + float bottom = corner01 + (corner11 - corner01) * tx; + return top + (bottom - top) * ty; +} + +float fractalNoise(int tileSalt, float fx, float fy, uint32_t salt, uint32_t seed) { + float coarse = valueNoise(tileSalt, fx, fy, salt, seed); + float fine = valueNoise(tileSalt, fx * 2.17f + 3.1f, fy * 2.17f - 1.7f, salt + 101u, seed); + return coarse * 0.66f + fine * 0.34f; +} + +// Noise that varies along one axis only, for grain and for warping band edges. +float lineNoise(int tileSalt, float along, uint32_t salt, uint32_t seed) { + return valueNoise(tileSalt, along, 0.5f, salt, seed); +} + +TileRgb mixRgb(TileRgb from, TileRgb to, float t) { + float k = clamp01(t); + TileRgb out; + out.r = static_cast(from.r + (to.r - from.r) * k + 0.5f); + out.g = static_cast(from.g + (to.g - from.g) * k + 0.5f); + out.b = static_cast(from.b + (to.b - from.b) * k + 0.5f); + return out; +} + +// What a pattern says about one pixel: how far it moved from base towards +// accent, how it is lit, and whether the pattern left a gap there. +struct PatternSample { + float accentWeight = 0.0f; + float shade = 1.0f; + bool hole = false; +}; + +PatternSample solidPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + float cells = 2.0f + spec.scale * 4.0f; + float blob = fractalNoise(tileSalt, x * cells / size, y * cells / size, 11u, seed); + PatternSample out; + out.accentWeight = clamp01((blob - 0.34f) * 2.0f) * spec.density; + return out; +} + +PatternSample orePattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + float cells = 6.5f - spec.scale * 3.5f; + float blob = fractalNoise(tileSalt, x * cells / size, y * cells / size, 23u, seed); + float threshold = 1.0f - spec.density * 1.15f; + PatternSample out; + if (blob > threshold) { + out.accentWeight = clamp01((blob - threshold) * 9.0f); + out.shade = 1.0f + 0.10f * out.accentWeight; + } else if (blob > threshold - 0.05f) { + out.shade = 0.78f; + } + return out; +} + +// Courses of brick with mortar between them. Odd rows shift by half a brick; +// without that shift the tile reads as a grid and every brick wall in the world +// lines its joints up vertically. +PatternSample brickPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + int courseHeight = std::max(2, static_cast(std::lround(size * (0.16f + spec.scale * 0.2f)))); + int brickWidth = std::max(3, courseHeight * 2); + int course = y / courseHeight; + int shift = (course % 2 == 0) ? 0 : brickWidth / 2; + int localY = y - course * courseHeight; + int localX = (x + shift) % brickWidth; + + PatternSample out; + if (localY == 0 || localX == 0) { + out.accentWeight = 1.0f; + out.shade = 0.95f; + return out; + } + int brickIndex = (x + shift) / brickWidth; + out.shade = 0.93f + 0.14f * gridValue(tileSalt, brickIndex, course, 29u, seed); + if (localY == 1) out.shade *= 1.05f; + if (localY == courseHeight - 1) out.shade *= 0.93f; + return out; +} + +// Boards laid side by side with a seam between them, and grain that runs the +// length of the board rather than across it. +PatternSample plankPattern(const TileSpec& spec, int tileSalt, int x, int y, int size, + uint32_t seed) { + int across = spec.vertical ? x : y; + int along = spec.vertical ? y : x; + int boardWidth = + std::max(3, static_cast(std::lround(size * (0.16f + spec.scale * 0.30f)))); + int board = across / boardWidth; + int localAcross = across - board * boardWidth; +