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