osb/source/game/terrain/StarWormCave.cpp
2023-06-20 14:33:09 +10:00

158 lines
5.4 KiB
C++

#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<Worm> 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);
}
}