1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
#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);
}
}
|