-
Notifications
You must be signed in to change notification settings - Fork 1
/
Landscape.cs
301 lines (263 loc) · 12.1 KB
/
Landscape.cs
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
/* Copyright (c) 2018-2024 Nuno Fachada and contributors
* Distributed under the MIT License (See accompanying file LICENSE or copy
* at http://opensource.org/licenses/MIT) */
using System;
using LibGameAI.Util;
namespace LibGameAI.PCG
{
public static class Landscape
{
public static void FaultModifier(
float[,] landscape, float depth, Func<float> randFloat,
float decreaseDistance = 0)
{
// Create random fault epicentre and direction vector
float cx = randFloat.Invoke() * landscape.GetLength(0);
float cy = randFloat.Invoke() * landscape.GetLength(1);
float direction = randFloat.Invoke() * 2 * (float)Math.PI;
float dx = (float)Math.Cos(direction);
float dy = (float)Math.Sin(direction);
// Apply the fault
for (int x = 0; x < landscape.GetLength(0); x++)
{
for (int y = 0; y < landscape.GetLength(1); y++)
{
// Get the dot product of the location with the fault
float ox = cx - x;
float oy = cy - y;
float dp = ox * dx + oy * dy;
float change;
// Positive dot product goes up, negative goes down
if (dp > 0)
{
// Fault size will decrease with distance if
// decreaseDistance > 0
float decrease = decreaseDistance > 0
? decreaseDistance / (decreaseDistance + dp)
: 1;
// Positive dot product goes up
change = depth * decrease;
}
else
{
// Fault size will decrease with distance if
// decreaseDistance > 0
float decrease = decreaseDistance > 0
? decreaseDistance / (decreaseDistance - dp)
: 1;
// Negative dot product goes down
change = -depth * decrease;
}
// Apply fault modification
landscape[x, y] += change;
}
}
}
// Diamond-Square algorithm
// Seems to be working, needs verification / testing
public static void DiamondSquare(float[,] landscape,
float maxInitHeight, float roughness, Func<float> randFloat)
{
int tileSide;
float randness = maxInitHeight;
int vSide = 1;
int height = landscape.GetLength(0);
int width = landscape.GetLength(1);
int largerSize = Math.Max(height, width);
Func<float> rand = () => 2 * (randFloat() - 0.5f);
// Get the smallest 2^n + 1 value larger than the largest size of
// the landscape
for (int n = 1; vSide < largerSize; n++)
{
vSide <<= 1;
}
vSide += 1;
// Flatten height map
for (int i = 0; i < landscape.GetLength(0); i++)
{
for (int j = 0; j < landscape.GetLength(1); j++)
{
landscape[i, j] = 0f;
}
}
// Set virtual corners of the landscape to a random value within the
// specified limit
landscape[0, 0] = randFloat() * randness;
landscape[0, Wrap(vSide - 1, width)] = landscape[0, 0];
landscape[Wrap(vSide - 1, height), 0] = landscape[0, 0];
landscape[Wrap(vSide - 1, height), Wrap(vSide - 1, width)] =
landscape[0, 0];
tileSide = vSide - 1;
// UnityEngine.Debug.Log($"({height},{width})");
while (tileSide > 1)
{
int halfSide = tileSide / 2;
randness *= roughness;
// Diamond step
for (int r = 0; r < vSide; r += tileSide)
{
for (int c = 0; c < vSide; c += tileSide)
{
// UnityEngine.Debug.Log($"({Wrap(r, height)},{Wrap(c, width)}) -- ({Wrap(r + tileSide, height)},{Wrap(c, width)}) -- ({Wrap(r, height)},{Wrap(c + tileSide, width)}) -- ({Wrap(r + tileSide, height)},{Wrap(c + tileSide, width)})");
float cornerSum =
landscape[Wrap(r, height), Wrap(c, width)]
+ landscape[Wrap(r + tileSide, height), Wrap(c, width)]
+ landscape[Wrap(r, height), Wrap(c + tileSide, width)]
+ landscape[Wrap(r + tileSide, height), Wrap(c + tileSide, width)];
landscape[Wrap(r + halfSide, height), Wrap(c + halfSide, width)] =
cornerSum / 4f + rand() * randness;
}
}
// Square step
for (int r = 0; r < vSide; r += halfSide)
{
for (int c = (r + halfSide) % tileSide; c < vSide; c += tileSide)
{
float sideSum =
landscape[Wrap((r - halfSide + vSide - 1) % (vSide - 1), height), Wrap(c, width)]
+ landscape[Wrap((r + halfSide) % (vSide - 1), height), Wrap(c, width)]
+ landscape[Wrap(r, height), Wrap((c + halfSide) % (vSide - 1), width)]
+ landscape[Wrap(r, height), Wrap((c - halfSide + vSide - 1) % (vSide - 1), width)];
landscape[Wrap(r, height), Wrap(c, width)] = sideSum / 4f + rand() * randness;
if (r == 0)
{
landscape[Wrap(vSide - 1, height), Wrap(c, width)] = landscape[r, Wrap(c, width)];
}
if (c == 0)
{
landscape[Wrap(r, height), Wrap(vSide - 1, width)] = landscape[Wrap(r, height), c];
}
}
}
tileSide /= 2;
}
}
// Per Bak sandpile model
public static void Sandpile(float[,] landscape, float threshold,
float increment, float decrement, float grainDropDensity,
bool staticDrop, bool stochastic, Func<int, int> randInt,
Func<double> randDouble,
(int x, int y)[] neighs = null)
{
void Drop(int x, int y, float inc)
{
float inThresh = stochastic
? (float)((Stats.NextNormalDouble(randDouble) + threshold) * (increment / threshold))
: threshold;
landscape[x, y] += inc;
if (landscape[x, y] >= inThresh)
{
float inDec = stochastic
? (float)((Stats.NextNormalDouble(randDouble) + decrement) * (increment / threshold))
: decrement;
landscape[x, y] -= inDec;
float slip = inDec / neighs.Length;
foreach ((int x, int y) neigh in neighs)
{
int nx = x + neigh.x;
int ny = y + neigh.y;
if (nx < 0 || nx >= landscape.GetLength(0) ||
ny < 0 || ny >= landscape.GetLength(1))
{
continue;
}
Drop(x + neigh.x, y + neigh.y, slip);
}
}
}
int xDrop = randInt(landscape.GetLength(0));
int yDrop = randInt(landscape.GetLength(1));
int totalGrains = (int)(grainDropDensity * landscape.GetLength(0) * landscape.GetLength(1));
if (neighs is null)
neighs = new (int x, int y)[] { (1, 0), (-1, 0), (0, 1), (0, -1) };
for (int i = 0; i < totalGrains; i++)
{
Drop(xDrop, yDrop, increment);
if (!staticDrop)
{
xDrop = randInt(landscape.GetLength(0));
yDrop = randInt(landscape.GetLength(1));
}
}
}
// Useful for "wrapping around" matrices
private static int Wrap(int pos, int max)
{
while (pos < 0) pos = max + pos;
while (pos >= max) pos = pos - max;
return pos;
}
// TODO Not working properly
// Check https://github.com/Ernyoke/fractal-erosion/blob/master/src/DiamondSquareFractal.cpp
// and "Fast Hydraulic Erosion Simulation and Visualization on GPU" by Xing Mei, Philippe Decaudin, Bao-Gang Hu
// "Fast Hydraulic and Thermal Erosion on the GPU" by Balazs Jako
public static void ThermalErosion(float[,] landscape, float impact)
{
// This should be updated to use System.ReadOnlySpan when
// Unity supports .NET Standard 2.1 in order to avoid heap
// allocations
// (int, int)[] neighbors =
// new (int, int)[] { (1, 0), (-1, 0), (0, 1), (0, -1) };
// Create a copy of the landscape
float[,] landscapeCopy =
new float[landscape.GetLength(0), landscape.GetLength(1)];
Array.Copy(landscape, landscapeCopy,
landscape.GetLength(0) * landscape.GetLength(1));
float min = landscape[0, 0], max = landscape[0, 0];
for (int i = 0; i < landscape.GetLength(0); i++)
{
for (int j = 0; j < landscape.GetLength(1); j++)
{
if (landscape[i, j] > max) max = landscape[i, j];
if (landscape[i, j] < min) min = landscape[i, j];
}
}
float erosion_height = (max - min) * impact;
// Apply erosion
for (int x = 0; x < landscape.GetLength(0); x++)
{
for (int y = 0; y < landscape.GetLength(1); y++)
{
for (int i = -1; i <= 1; i++)
{
for (int j = -1; j <= 1; j++)
{
if (x + i >= 0 &&
x + i < landscape.GetLength(0) &&
y + j >= 0 &&
y + j < landscape.GetLength(1))
{
if (landscape[x + i, y + j] < landscape[x, y])
{
landscape[x + i, y + j] += erosion_height;
landscape[x, y] -= erosion_height;
}
}
}
// float height = landscape[x, y];
// float limit = height - threshold;
// foreach ((int x, int y) d in neighbors)
// {
// int nx = x + d.x;
// int ny = y + d.y;
// float nHeight = landscape[nx, ny];
// Is the neighbor below the threshold?
// if (nHeight < limit)
// {
// // Some of the height moves, from 0 to 1/4 of the
// // threshold, depending on the height difference
// float delta = (limit - nHeight) / threshold;
// if (delta > 2) delta = 2;
// float change = delta * threshold / 8;
// // Write new height to original landscape
// landscape[x, y] -= change;
// landscape[nx, ny] += change;
// }
// }
// }
}
}
}
}
}
}