#include "StarWormCave.hpp" #include "StarJsonExtra.hpp" #include "StarRandom.hpp" #include "StarInterpolation.hpp" namespace Star { char const* const WormCaveSelector::Name = "wormcave"; WormCaveSector::WormCaveSector(int sectorSize, Vec2I sector, Json const& config, size_t seed, float commonality) : m_sectorSize(sectorSize), m_sector(sector), m_values(sectorSize * sectorSize) { struct Worm { Vec2F pos; float angle; float goalAngle; float size; float length; float goalLength; }; List worms; Vec2F numberOfWormsPerSectorRange = jsonToVec2F(config.get("numberOfWormsPerSectorRange")); Vec2F wormSizeRange = jsonToVec2F(config.get("wormSizeRange")); Vec2F wormLengthRange = jsonToVec2F(config.get("wormLengthRange")); float wormTaperDistance = config.getFloat("wormTaperDistance"); Vec2F wormAngleRange = jsonToVec2F(config.get("wormAngleRange")); float wormTurnChance = config.getFloat("wormTurnChance"); float wormTurnRate = config.getFloat("wormTurnRate"); float wormSpeed = config.getFloat("wormSpeed", 1); float twoPi = Constants::pi * 2; m_maxValue = wormSizeRange[1] / 2; // determine worms for the neighbouring sectors int sectorRadius = m_sectorSize * config.getInt("sectorRadius"); for (int x = sector[0] - sectorRadius; x <= sector[0] + sectorRadius; x += m_sectorSize) for (int y = sector[1] - sectorRadius; y <= sector[1] + sectorRadius; y += m_sectorSize) { RandomSource rs(staticRandomU64(x, y, seed)); float numberOfWorms = rs.randf(numberOfWormsPerSectorRange[0], numberOfWormsPerSectorRange[1]) * commonality; int intNumberOfWorms = (int)numberOfWorms; if (rs.randf() < (numberOfWorms - intNumberOfWorms)) intNumberOfWorms++; for (int n = 0; n < intNumberOfWorms; ++n) { Worm worm; worm.pos = Vec2F(x, y) + Vec2F(rs.randf(0, m_sectorSize), rs.randf(0, m_sectorSize)); worm.angle = rs.randf(wormAngleRange[0], wormAngleRange[1]); worm.goalAngle = rs.randf(wormAngleRange[0], wormAngleRange[1]); worm.size = rs.randf(wormSizeRange[0], wormSizeRange[1]) * commonality; worm.length = 0; worm.goalLength = rs.randf(wormLengthRange[0], wormLengthRange[1]) * commonality; worms.append(worm); } } while (true) { bool idle = true; for (auto& worm : worms) { if (worm.length >= worm.goalLength) continue; idle = false; // taper size float wormRadius = worm.size / 2; float taperFactor = 1.0f; if (worm.length < wormTaperDistance) taperFactor = std::sin(0.5 * Constants::pi * worm.length / wormTaperDistance); else if (worm.goalLength - worm.length < wormTaperDistance) taperFactor = std::sin(0.5 * Constants::pi * (worm.goalLength - worm.length) / wormTaperDistance); wormRadius *= taperFactor; // carve out worm area int size = ceil(wormRadius); for (float dx = -size; dx <= size; dx += 1) for (float dy = -size; dy <= size; dy += 1) { float m = sqrt((dx * dx) + (dy * dy)); if (m <= wormRadius) { int x = floor(dx + worm.pos[0]); int y = floor(dy + worm.pos[1]); if (inside(x, y)) { float v = get(x, y); set(x, y, max(v, wormRadius - m)); } } } // move the worm, slowing down a bit as we reach the ends to reduce // stutter float thisSpeed = max(0.75f, wormSpeed * taperFactor); worm.pos += Vec2F::withAngle(worm.angle) * thisSpeed; worm.length += thisSpeed; // maybe set new goal angle if (staticRandomFloat(worm.pos[0], worm.pos[1], seed, 1) < wormTurnChance * thisSpeed) { worm.goalAngle = pfmod( staticRandomFloatRange(wormAngleRange[0], wormAngleRange[1], worm.pos[0], worm.pos[1], seed, 2), twoPi); } if (worm.angle != worm.goalAngle) { // turn the worm toward goal angle float angleDiff = worm.goalAngle - worm.angle; // stop if we're close enough if (abs(angleDiff) < wormTurnRate * thisSpeed) worm.angle = worm.goalAngle; else { // turn the shortest angular distance if (abs(angleDiff) > twoPi) angleDiff = -angleDiff; worm.angle = pfmod(worm.angle + copysign(wormTurnRate * thisSpeed, angleDiff), twoPi); } } } if (idle) break; } } float WormCaveSector::get(int x, int y) { auto val = m_values[(x - m_sector[0]) + m_sectorSize * (y - m_sector[1])]; if (val > 0) return val; else return -m_maxValue; } bool WormCaveSector::inside(int x, int y) { int x_ = x - m_sector[0]; if (x_ < 0 || x_ >= m_sectorSize) return false; int y_ = y - m_sector[1]; if (y_ < 0 || y_ >= m_sectorSize) return false; return true; } void WormCaveSector::set(int x, int y, float value) { m_values[(x - m_sector[0]) + m_sectorSize * (y - m_sector[1])] = value; } WormCaveSelector::WormCaveSelector(Json const& config, TerrainSelectorParameters const& parameters) : TerrainSelector(Name, config, parameters) { m_sectorSize = config.getUInt("sectorSize", 64); m_cache.setMaxSize(config.getUInt("lruCacheSize", 16)); } float WormCaveSelector::get(int x, int y) const { Vec2I sector = Vec2I(x - pmod(x, m_sectorSize), y - pmod(y, m_sectorSize)); return m_cache.get(sector, [=](Vec2I const& sector) { return WormCaveSector(m_sectorSize, sector, config, parameters.seed, parameters.commonality); }).get(x, y); } }