]> git.localhorst.tv Git - blank.git/blob - src/world/Generator.cpp
b6d53ae4cf895c189941117bd1e7a9cab7638030
[blank.git] / src / world / Generator.cpp
1 #include "Generator.hpp"
2
3 #include "BlockType.hpp"
4 #include "BlockTypeRegistry.hpp"
5 #include "Chunk.hpp"
6 #include "../rand/OctaveNoise.hpp"
7
8 #include <glm/glm.hpp>
9
10
11 namespace blank {
12
13 namespace {
14
15 struct Candidate {
16         const BlockType *type;
17         float threshold;
18         Candidate(const BlockType *type, float threshold)
19         : type(type), threshold(threshold) { }
20 };
21
22 std::vector<Candidate> candidates;
23
24 }
25
26 Generator::Generator(const Config &config) noexcept
27 : config(config)
28 , types()
29 , min_solidity(2.0f)
30 , solidity_noise(config.seed ^ config.solidity.seed_mask)
31 , humidity_noise(config.seed ^ config.humidity.seed_mask)
32 , temperature_noise(config.seed ^ config.temperature.seed_mask)
33 , richness_noise(config.seed ^ config.richness.seed_mask)
34 , random_noise(config.seed ^ config.randomness.seed_mask) {
35
36 }
37
38 void Generator::LoadTypes(const BlockTypeRegistry &reg) {
39         types.clear();
40         min_solidity = 2.0f;
41         for (const BlockType &type : reg) {
42                 if (type.generate) {
43                         types.push_back(&type);
44                         if (type.min_solidity < min_solidity) {
45                                 min_solidity = type.min_solidity;
46                         }
47                 }
48         }
49         candidates.reserve(types.size());
50 }
51
52 namespace {
53
54 struct Interpolation {
55         /// sample points for interpolation
56         /// given coordinates should be the absoloute position of the chunk's (0,0,0) block
57         Interpolation(
58                 const SimplexNoise &noise,
59                 const glm::vec3 &base,
60                 const Generator::Config::NoiseParam &conf
61         ) noexcept {
62                 for (int z = 0; z < 5; ++z) {
63                         for (int y = 0; y < 5; ++y) {
64                                 for (int x = 0; x < 5; ++x) {
65                                         samples[z][y][x] = OctaveNoise(
66                                                 noise,
67                                                 base + (glm::vec3(x, y, z) * 4.0f),
68                                                 conf.octaves,
69                                                 conf.persistence,
70                                                 conf.frequency,
71                                                 conf.amplitude,
72                                                 conf.growth
73                                         );
74                                 }
75                         }
76                 }
77         }
78         float samples[5][5][5];
79 };
80
81 struct Parameters {
82         glm::ivec3 a;
83         glm::ivec3 b;
84         glm::ivec3 d;
85 };
86
87 struct Detail {
88         float humidity;
89         float temperature;
90         float richness;
91         float randomness;
92 };
93
94 }
95
96 struct Generator::ValueField {
97
98         Interpolation solidity;
99         Interpolation humidity;
100         Interpolation temperature;
101         Interpolation richness;
102         Interpolation randomness;
103
104         static Parameters GetParams(const glm::ivec3 &pos) noexcept {
105                 Parameters p;
106                 p.a = pos / 4;
107                 p.b = p.a + 1;
108                 p.d = pos % 4;
109                 return p;
110         }
111
112         static float Interpolate(const Interpolation &i, const Parameters &p) noexcept {
113                 constexpr float A[4] = { 1.0f, 0.75f, 0.5f, 0.25f };
114                 constexpr float B[4] = { 0.0f, 0.25f, 0.5f, 0.75f };
115                 const float l1[4] = {
116                         i.samples[p.a.z][p.a.y][p.a.x] * A[p.d.x] + i.samples[p.a.z][p.a.y][p.b.x] * B[p.d.x],
117                         i.samples[p.a.z][p.b.y][p.a.x] * A[p.d.x] + i.samples[p.a.z][p.b.y][p.b.x] * B[p.d.x],
118                         i.samples[p.b.z][p.a.y][p.a.x] * A[p.d.x] + i.samples[p.b.z][p.a.y][p.b.x] * B[p.d.x],
119                         i.samples[p.b.z][p.b.y][p.a.x] * A[p.d.x] + i.samples[p.b.z][p.b.y][p.b.x] * B[p.d.x],
120                 };
121                 const float l2[2] = {
122                         l1[0] * A[p.d.y] + l1[1] * B[p.d.y],
123                         l1[2] * A[p.d.y] + l1[3] * B[p.d.y],
124                 };
125                 return l2[0] * A[p.d.z] + l2[1] * B[p.d.z];
126         }
127
128 };
129
130 void Generator::operator ()(Chunk &chunk) const noexcept {
131         ExactLocation::Fine coords(chunk.Position() * ExactLocation::Extent());
132         coords += 0.5f;
133         ValueField field {
134                 { solidity_noise, coords, config.solidity },
135                 { humidity_noise, coords, config.humidity },
136                 { temperature_noise, coords, config.temperature },
137                 { richness_noise, coords, config.richness },
138                 { random_noise, coords, config.randomness },
139         };
140         for (int z = 0; z < Chunk::side; ++z) {
141                 for (int y = 0; y < Chunk::side; ++y) {
142                         for (int x = 0; x < Chunk::side; ++x) {
143                                 chunk.SetBlock(RoughLocation::Fine(x, y, z), Generate(field, RoughLocation::Fine(x, y, z)));
144                         }
145                 }
146         }
147         chunk.SetGenerated();
148 }
149
150 Block Generator::Generate(const ValueField &field, const glm::ivec3 &pos) const noexcept {
151         Parameters params(ValueField::GetParams(pos));
152         float solidity = ValueField::Interpolate(field.solidity, params);
153         if (solidity < min_solidity) {
154                 return Block(0);
155         }
156         float humidity = ValueField::Interpolate(field.humidity, params);
157         float temperature = ValueField::Interpolate(field.temperature, params);
158         float richness = ValueField::Interpolate(field.richness, params);
159
160         candidates.clear();
161         float total = 0.0f;
162         for (const BlockType *type : types) {
163                 if (solidity < type->min_solidity || solidity > type->max_solidity) continue;
164                 if (humidity < type->min_humidity || humidity > type->max_humidity) continue;
165                 if (temperature < type->min_temperature || temperature > type->max_temperature) continue;
166                 if (richness < type->min_richness || richness > type->max_richness) continue;
167                 float solidity_match = 4.0f - ((solidity - type->mid_solidity) * (solidity - type->mid_solidity));
168                 float humidity_match = 4.0f - ((humidity - type->mid_humidity) * (humidity - type->mid_humidity));
169                 float temperature_match = 4.0f - ((temperature - type->mid_temperature) * (temperature - type->mid_temperature));
170                 float richness_match = 4.0f - ((richness - type->mid_richness) * (richness - type->mid_richness));
171                 float chance = (solidity_match + humidity_match + temperature_match + richness_match) * type->commonness;
172                 total += chance;
173                 candidates.emplace_back(type, total);
174         }
175         if (candidates.empty()) {
176                 return Block(0);
177         }
178         float random = ValueField::Interpolate(field.randomness, params);
179         // as weird as it sounds, but this is faster tham glm::fract and generates a
180         // better distribution than (transformed variants of) erf, erfc, atan, and smoothstep
181         if (random < 0.0f) random += 1.0f;
182         float value = random * total;
183         // TODO: change to binary search
184         for (const Candidate &cand : candidates) {
185                 if (value < cand.threshold) {
186                         return Block(cand.type->id);
187                 }
188         }
189         // theoretically, this should never happen
190         return Block(candidates.back().type->id);
191 }
192
193 }