@plasius/gpu-world-generator 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +203 -0
- package/README.md +73 -0
- package/dist/field.wgsl +225 -0
- package/dist/fractal-prepass.wgsl +290 -0
- package/dist/index.cjs +1942 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +447 -0
- package/dist/index.d.ts +447 -0
- package/dist/index.js +1848 -0
- package/dist/index.js.map +1 -0
- package/dist/terrain.wgsl +451 -0
- package/docs/adrs/adr-0001-package-scope.md +18 -0
- package/docs/adrs/adr-0002-world-tiling-lod-stitching.md +28 -0
- package/docs/adrs/adr-0003-terrain-generation-style-mixing.md +21 -0
- package/docs/adrs/index.md +5 -0
- package/docs/biomes.md +206 -0
- package/docs/lod-zoning.md +22 -0
- package/docs/plan.md +107 -0
- package/docs/procedural-surface.md +73 -0
- package/docs/resources.md +55 -0
- package/package.json +53 -0
- package/src/biomes/temperate.ts +387 -0
- package/src/field.wgsl +225 -0
- package/src/fields.ts +321 -0
- package/src/fractal-prepass.ts +237 -0
- package/src/fractal-prepass.wgsl +290 -0
- package/src/generator.ts +106 -0
- package/src/hex.ts +54 -0
- package/src/index.ts +11 -0
- package/src/mesh.ts +285 -0
- package/src/perf-monitor.ts +133 -0
- package/src/shaders/demo-terrain.wgsl +193 -0
- package/src/shaders/demo-trees.wgsl +172 -0
- package/src/shaders/demo-water-sim.wgsl +361 -0
- package/src/shaders/library/common.wgsl +38 -0
- package/src/shaders/library/materials.wgsl +175 -0
- package/src/terrain.wgsl +451 -0
- package/src/tile-cache.ts +274 -0
- package/src/tiles.ts +417 -0
- package/src/types.ts +220 -0
- package/src/wgsl/field.job.wgsl +225 -0
- package/src/wgsl/terrain.job.wgsl +451 -0
- package/src/wgsl.ts +77 -0
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
struct HexCell {
|
|
2
|
+
q: i32,
|
|
3
|
+
r: i32,
|
|
4
|
+
level: i32,
|
|
5
|
+
flags: i32,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
struct TerrainCell {
|
|
9
|
+
height: f32,
|
|
10
|
+
heat: f32,
|
|
11
|
+
moisture: f32,
|
|
12
|
+
biome: u32,
|
|
13
|
+
surface: u32,
|
|
14
|
+
feature: u32,
|
|
15
|
+
foliage: f32,
|
|
16
|
+
slope_band: u32,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
struct TerrainParams {
|
|
20
|
+
seed: u32,
|
|
21
|
+
cell_count: u32,
|
|
22
|
+
heat_bias: f32,
|
|
23
|
+
height_scale: f32,
|
|
24
|
+
macro_scale: f32,
|
|
25
|
+
macro_warp_strength: f32,
|
|
26
|
+
style_mix_strength: f32,
|
|
27
|
+
terrace_steps: f32,
|
|
28
|
+
terrace_strength: f32,
|
|
29
|
+
crater_strength: f32,
|
|
30
|
+
crater_scale: f32,
|
|
31
|
+
height_min: f32,
|
|
32
|
+
height_max: f32,
|
|
33
|
+
slope_low: f32,
|
|
34
|
+
slope_high: f32,
|
|
35
|
+
pad0: f32,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
@group(1) @binding(0) var<storage, read> cells: array<HexCell>;
|
|
39
|
+
@group(1) @binding(1) var<storage, read_write> terrain: array<TerrainCell>;
|
|
40
|
+
@group(1) @binding(2) var<uniform> params: TerrainParams;
|
|
41
|
+
|
|
42
|
+
const BIOME_PLAINS: u32 = 0u;
|
|
43
|
+
const BIOME_TUNDRA: u32 = 1u;
|
|
44
|
+
const BIOME_SAVANNA: u32 = 2u;
|
|
45
|
+
const BIOME_RIVER: u32 = 3u;
|
|
46
|
+
const BIOME_CITY: u32 = 4u;
|
|
47
|
+
const BIOME_VILLAGE: u32 = 5u;
|
|
48
|
+
const BIOME_ICE: u32 = 6u;
|
|
49
|
+
const BIOME_MOUNTAINOUS: u32 = 8u;
|
|
50
|
+
const BIOME_VOLCANIC: u32 = 9u;
|
|
51
|
+
const BIOME_ROAD: u32 = 10u;
|
|
52
|
+
const BIOME_TOWN: u32 = 11u;
|
|
53
|
+
const BIOME_CASTLE: u32 = 12u;
|
|
54
|
+
const BIOME_MIXED_FOREST: u32 = 13u;
|
|
55
|
+
|
|
56
|
+
const SURFACE_GRASS: u32 = 0u;
|
|
57
|
+
const SURFACE_DIRT: u32 = 1u;
|
|
58
|
+
const SURFACE_SAND: u32 = 2u;
|
|
59
|
+
const SURFACE_ROCK: u32 = 3u;
|
|
60
|
+
const SURFACE_GRAVEL: u32 = 4u;
|
|
61
|
+
const SURFACE_ICE: u32 = 6u;
|
|
62
|
+
const SURFACE_MUD: u32 = 7u;
|
|
63
|
+
const SURFACE_WATER: u32 = 11u;
|
|
64
|
+
const SURFACE_BASALT: u32 = 12u;
|
|
65
|
+
|
|
66
|
+
const FEATURE_TREE: u32 = 0u;
|
|
67
|
+
const FEATURE_BUSH: u32 = 1u;
|
|
68
|
+
const FEATURE_GRASS_TUFT: u32 = 2u;
|
|
69
|
+
const FEATURE_REED: u32 = 3u;
|
|
70
|
+
const FEATURE_ROCK: u32 = 4u;
|
|
71
|
+
const FEATURE_BOULDER: u32 = 5u;
|
|
72
|
+
const FEATURE_WATER_RIPPLE: u32 = 6u;
|
|
73
|
+
const FEATURE_ICE_SPIKE: u32 = 7u;
|
|
74
|
+
const FEATURE_RUIN: u32 = 13u;
|
|
75
|
+
const FEATURE_NONE: u32 = 4294967295u;
|
|
76
|
+
|
|
77
|
+
const SLOPE_DOWNWARD: u32 = 0u;
|
|
78
|
+
const SLOPE_FLAT: u32 = 1u;
|
|
79
|
+
const SLOPE_UPWARD: u32 = 2u;
|
|
80
|
+
|
|
81
|
+
fn hash32(x: u32) -> u32 {
|
|
82
|
+
var v = x;
|
|
83
|
+
v ^= v >> 17u;
|
|
84
|
+
v *= 0xed5ad4bbu;
|
|
85
|
+
v ^= v >> 11u;
|
|
86
|
+
v *= 0xac4c1b51u;
|
|
87
|
+
v ^= v >> 15u;
|
|
88
|
+
v *= 0x31848babu;
|
|
89
|
+
v ^= v >> 14u;
|
|
90
|
+
return v;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn hash01(x: u32) -> f32 {
|
|
94
|
+
let v = hash32(x) & 0x00ffffffu;
|
|
95
|
+
return f32(v) / 16777216.0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fn smooth_mandelbrot(cx: f32, cy: f32, iterations: u32, power: f32) -> f32 {
|
|
99
|
+
var zx = 0.0;
|
|
100
|
+
var zy = 0.0;
|
|
101
|
+
var i: u32 = 0u;
|
|
102
|
+
loop {
|
|
103
|
+
if (i >= iterations) {
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
let r2 = zx * zx + zy * zy;
|
|
107
|
+
if (r2 > 4.0) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
let r = sqrt(r2);
|
|
111
|
+
let theta = atan2(zy, zx);
|
|
112
|
+
let rp = pow(r, power);
|
|
113
|
+
zx = rp * cos(theta * power) + cx;
|
|
114
|
+
zy = rp * sin(theta * power) + cy;
|
|
115
|
+
i = i + 1u;
|
|
116
|
+
}
|
|
117
|
+
if (i >= iterations) {
|
|
118
|
+
return 1.0;
|
|
119
|
+
}
|
|
120
|
+
let r = max(sqrt(zx * zx + zy * zy), 1e-6);
|
|
121
|
+
let nu = log2(log(r));
|
|
122
|
+
let smoothVal = (f32(i) + 1.0 - nu) / f32(iterations);
|
|
123
|
+
return clamp(smoothVal, 0.0, 1.0);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fn terrace_height(h: f32, steps: f32) -> f32 {
|
|
127
|
+
let count = max(1.0, steps);
|
|
128
|
+
let step = 1.0 / count;
|
|
129
|
+
let clamped = clamp(h, 0.0, 1.0);
|
|
130
|
+
let band = floor(clamped / step);
|
|
131
|
+
let t = fract(clamped / step);
|
|
132
|
+
let blend = t * t * (3.0 - 2.0 * t);
|
|
133
|
+
return (band + blend) * step;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fn crater_field(p: vec2<f32>, scale: f32, seed: u32) -> f32 {
|
|
137
|
+
let sp = p * scale;
|
|
138
|
+
let cell = floor(sp);
|
|
139
|
+
let local = sp - cell;
|
|
140
|
+
let cx = i32(cell.x);
|
|
141
|
+
let cz = i32(cell.y);
|
|
142
|
+
let hx = u32(bitcast<u32>(cx));
|
|
143
|
+
let hz = u32(bitcast<u32>(cz));
|
|
144
|
+
let h0 = hash01((hx * 374761393u) ^ (hz * 668265263u) ^ seed ^ 0x9e3779b9u);
|
|
145
|
+
let h1 = hash01((hx * 2246822519u) ^ (hz * 3266489917u) ^ seed ^ 0x85ebca6bu);
|
|
146
|
+
let h2 = hash01((hx * 1597334677u) ^ (hz * 3812015801u) ^ seed ^ 0xc2b2ae35u);
|
|
147
|
+
let center = vec2<f32>(h0, h1);
|
|
148
|
+
let radius = 0.22 + 0.25 * h2;
|
|
149
|
+
let dist = distance(local, center);
|
|
150
|
+
return smoothstep(radius, radius * 0.35, dist);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fn macro_map(p: vec2<f32>, iterations: u32, power: f32) -> f32 {
|
|
154
|
+
let macro_iter = max(10u, iterations / 3u);
|
|
155
|
+
let macroA = smooth_mandelbrot(p.x * params.macro_scale, p.y * params.macro_scale, macro_iter, power);
|
|
156
|
+
let macroB = smooth_mandelbrot(
|
|
157
|
+
(p.x + 13.7) * params.macro_scale,
|
|
158
|
+
(p.y - 9.2) * params.macro_scale,
|
|
159
|
+
macro_iter,
|
|
160
|
+
power + 0.35
|
|
161
|
+
);
|
|
162
|
+
let warp = vec2<f32>(macroA - 0.5, macroB - 0.5) * params.macro_warp_strength;
|
|
163
|
+
let macroC = smooth_mandelbrot(
|
|
164
|
+
(p.x + warp.x) * params.macro_scale,
|
|
165
|
+
(p.y + warp.y) * params.macro_scale,
|
|
166
|
+
macro_iter,
|
|
167
|
+
power
|
|
168
|
+
);
|
|
169
|
+
return clamp((macroC - 0.5) * params.style_mix_strength + 0.5, 0.0, 1.0);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fn hash_cell(cell: HexCell, seed: u32, salt: u32) -> f32 {
|
|
173
|
+
let q = u32(bitcast<u32>(cell.q));
|
|
174
|
+
let r = u32(bitcast<u32>(cell.r));
|
|
175
|
+
let lvl = u32(bitcast<u32>(cell.level));
|
|
176
|
+
let mixed = q * 1664525u ^ r * 1013904223u ^ lvl * 747796405u ^ seed ^ salt;
|
|
177
|
+
return hash01(mixed);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fn classify_slope(cumulative_height: f32, low: f32, high: f32) -> u32 {
|
|
181
|
+
if (cumulative_height < low) {
|
|
182
|
+
return SLOPE_DOWNWARD;
|
|
183
|
+
}
|
|
184
|
+
if (cumulative_height >= high) {
|
|
185
|
+
return SLOPE_UPWARD;
|
|
186
|
+
}
|
|
187
|
+
return SLOPE_FLAT;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn classify_biome(
|
|
191
|
+
heat: f32,
|
|
192
|
+
moisture: f32,
|
|
193
|
+
surface: u32,
|
|
194
|
+
slope_band: u32,
|
|
195
|
+
obstacle: f32,
|
|
196
|
+
foliage: f32
|
|
197
|
+
) -> u32 {
|
|
198
|
+
if (surface == SURFACE_WATER) {
|
|
199
|
+
if (heat < 0.25) {
|
|
200
|
+
return BIOME_ICE;
|
|
201
|
+
}
|
|
202
|
+
return BIOME_RIVER;
|
|
203
|
+
}
|
|
204
|
+
if (surface == SURFACE_ICE) {
|
|
205
|
+
return BIOME_ICE;
|
|
206
|
+
}
|
|
207
|
+
if (slope_band == SLOPE_UPWARD && obstacle > 0.68) {
|
|
208
|
+
if (heat > 0.72) {
|
|
209
|
+
return BIOME_VOLCANIC;
|
|
210
|
+
}
|
|
211
|
+
return BIOME_MOUNTAINOUS;
|
|
212
|
+
}
|
|
213
|
+
if (heat < 0.3) {
|
|
214
|
+
return BIOME_TUNDRA;
|
|
215
|
+
}
|
|
216
|
+
if (heat > 0.74 && moisture < 0.33) {
|
|
217
|
+
return BIOME_SAVANNA;
|
|
218
|
+
}
|
|
219
|
+
if (foliage > 0.55 && moisture > 0.45) {
|
|
220
|
+
return BIOME_MIXED_FOREST;
|
|
221
|
+
}
|
|
222
|
+
return BIOME_PLAINS;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
fn maybe_settlement(current: u32, height: f32, cell: HexCell, seed: u32, obstacle: f32) -> u32 {
|
|
226
|
+
let settlement = hash_cell(cell, seed, 0x9e3779b9u);
|
|
227
|
+
let level = cell.level;
|
|
228
|
+
if (obstacle > 0.8) {
|
|
229
|
+
return current;
|
|
230
|
+
}
|
|
231
|
+
if (height > 0.6 && settlement > 0.997) {
|
|
232
|
+
return BIOME_CASTLE;
|
|
233
|
+
}
|
|
234
|
+
if (level <= 0 && settlement > 0.994) {
|
|
235
|
+
return BIOME_CITY;
|
|
236
|
+
}
|
|
237
|
+
if (level <= 1 && settlement > 0.992) {
|
|
238
|
+
return BIOME_TOWN;
|
|
239
|
+
}
|
|
240
|
+
if (level <= 2 && settlement > 0.988) {
|
|
241
|
+
return BIOME_VILLAGE;
|
|
242
|
+
}
|
|
243
|
+
let roadNoise = hash_cell(cell, seed, 0x6c8e9cf5u);
|
|
244
|
+
if (level >= 2 && roadNoise > 0.996) {
|
|
245
|
+
return BIOME_ROAD;
|
|
246
|
+
}
|
|
247
|
+
return current;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
fn process_job(job_index: u32, job_type: u32, payload_words: u32) {
|
|
251
|
+
if (job_index >= params.cell_count) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let cell = cells[job_index];
|
|
256
|
+
let p = vec2<f32>(f32(cell.q), f32(cell.r));
|
|
257
|
+
let seed = params.seed;
|
|
258
|
+
|
|
259
|
+
let scale = 0.14;
|
|
260
|
+
let warp_scale = 0.5;
|
|
261
|
+
let warp_strength = 0.75;
|
|
262
|
+
let power = 2.2;
|
|
263
|
+
let detail_scale = 3.2;
|
|
264
|
+
let detail_power = 2.0;
|
|
265
|
+
let iterations = 24u;
|
|
266
|
+
let detail_iterations = 14u;
|
|
267
|
+
|
|
268
|
+
let offX = hash01(seed ^ 0x7f4a7c15u) * 4.0 - 2.0;
|
|
269
|
+
let offZ = hash01(seed ^ 0xa9d84d2bu) * 4.0 - 2.0;
|
|
270
|
+
let warpOffX = hash01(seed ^ 0x8c2f1d3bu) * 6.0 - 3.0;
|
|
271
|
+
let warpOffZ = hash01(seed ^ 0x5d2c79e9u) * 6.0 - 3.0;
|
|
272
|
+
|
|
273
|
+
let warpIter = max(12u, iterations * 6u / 10u);
|
|
274
|
+
let warpA = smooth_mandelbrot(
|
|
275
|
+
(p.x + warpOffX) * warp_scale,
|
|
276
|
+
(p.y + warpOffZ) * warp_scale,
|
|
277
|
+
warpIter,
|
|
278
|
+
power
|
|
279
|
+
);
|
|
280
|
+
let warpB = smooth_mandelbrot(
|
|
281
|
+
(p.x - warpOffZ) * warp_scale,
|
|
282
|
+
(p.y + warpOffX) * warp_scale,
|
|
283
|
+
warpIter,
|
|
284
|
+
power
|
|
285
|
+
);
|
|
286
|
+
let warped = vec2<f32>(
|
|
287
|
+
p.x + (warpA - 0.5) * warp_strength,
|
|
288
|
+
p.y + (warpB - 0.5) * warp_strength
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
let base = smooth_mandelbrot(
|
|
292
|
+
warped.x * scale + offX,
|
|
293
|
+
warped.y * scale + offZ,
|
|
294
|
+
iterations,
|
|
295
|
+
power
|
|
296
|
+
);
|
|
297
|
+
let mid = smooth_mandelbrot(
|
|
298
|
+
warped.x * scale * 2.15 + offX * 0.6,
|
|
299
|
+
warped.y * scale * 2.15 + offZ * 0.6,
|
|
300
|
+
max(14u, iterations * 7u / 10u),
|
|
301
|
+
power + 0.2
|
|
302
|
+
);
|
|
303
|
+
let detail = smooth_mandelbrot(
|
|
304
|
+
warped.x * scale * detail_scale + offX * 1.4,
|
|
305
|
+
warped.y * scale * detail_scale + offZ * 1.4,
|
|
306
|
+
detail_iterations,
|
|
307
|
+
detail_power
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
let ridge = 1.0 - abs(2.0 * mid - 1.0);
|
|
311
|
+
let baseHeight = pow(base, 0.9) * pow(mid, 1.05) * pow(detail, 1.1);
|
|
312
|
+
let styleMask = macro_map(warped, iterations, power);
|
|
313
|
+
let terrace = terrace_height(baseHeight, params.terrace_steps);
|
|
314
|
+
let crater = crater_field(warped, params.crater_scale, seed);
|
|
315
|
+
|
|
316
|
+
let styleA = clamp(pow(baseHeight, 0.8) + pow(ridge, 1.4) * 0.2, 0.0, 1.0);
|
|
317
|
+
let styleB = clamp(
|
|
318
|
+
baseHeight * (1.0 - params.terrace_strength) +
|
|
319
|
+
terrace * params.terrace_strength -
|
|
320
|
+
crater * params.crater_strength +
|
|
321
|
+
pow(ridge, 1.6) * 0.12,
|
|
322
|
+
0.0,
|
|
323
|
+
1.0
|
|
324
|
+
);
|
|
325
|
+
let mixed = mix(styleA, styleB, styleMask);
|
|
326
|
+
let cumulative_height = clamp(
|
|
327
|
+
base * 0.38 + mid * 0.33 + detail * 0.21 + styleMask * 0.08,
|
|
328
|
+
0.0,
|
|
329
|
+
1.0
|
|
330
|
+
);
|
|
331
|
+
let slopeLow = clamp(params.slope_low, 0.01, 0.49);
|
|
332
|
+
let slopeHigh = clamp(max(slopeLow + 0.01, params.slope_high), 0.5, 0.99);
|
|
333
|
+
let slopeBand = classify_slope(cumulative_height, slopeLow, slopeHigh);
|
|
334
|
+
let downwardStrength = 1.0 - smoothstep(0.0, slopeLow, cumulative_height);
|
|
335
|
+
let upwardStrength = smoothstep(slopeHigh, 1.0, cumulative_height);
|
|
336
|
+
let flatStrength = clamp(1.0 - max(downwardStrength, upwardStrength), 0.0, 1.0);
|
|
337
|
+
|
|
338
|
+
let ridgeBoost = pow(ridge, 1.35) * 0.22;
|
|
339
|
+
let centered = (mixed - 0.5) * 2.0;
|
|
340
|
+
let shaped = sign(centered) * pow(abs(centered), 0.75);
|
|
341
|
+
let macroOffset = (styleMask - 0.5) * 0.25;
|
|
342
|
+
let rawHeight = clamp(
|
|
343
|
+
0.5 +
|
|
344
|
+
shaped * 0.8 +
|
|
345
|
+
macroOffset +
|
|
346
|
+
ridgeBoost +
|
|
347
|
+
upwardStrength * 0.22 -
|
|
348
|
+
downwardStrength * 0.22,
|
|
349
|
+
params.height_min,
|
|
350
|
+
params.height_max
|
|
351
|
+
);
|
|
352
|
+
let height01 = clamp(rawHeight, 0.0, 1.0);
|
|
353
|
+
|
|
354
|
+
let roughness = clamp(pow(ridge, 1.25) * 0.7 + detail * 0.3, 0.0, 1.0);
|
|
355
|
+
let heat = clamp(0.55 * mid + 0.35 * (1.0 - height01) + params.heat_bias, 0.0, 1.0);
|
|
356
|
+
let moisture = clamp(
|
|
357
|
+
0.55 * detail + 0.35 * (1.0 - height01) - (heat - 0.5) * 0.1,
|
|
358
|
+
0.0,
|
|
359
|
+
1.0
|
|
360
|
+
);
|
|
361
|
+
let rockiness = clamp(roughness * 0.6 + height01 * 0.4, 0.0, 1.0);
|
|
362
|
+
let water = clamp((0.32 - height01) * 3.0 + (moisture - 0.5) * 0.2, 0.0, 1.0);
|
|
363
|
+
|
|
364
|
+
let feature_mask = smooth_mandelbrot(
|
|
365
|
+
warped.x * scale * (detail_scale + 1.25) - offX * 0.85,
|
|
366
|
+
warped.y * scale * (detail_scale + 1.25) - offZ * 0.85,
|
|
367
|
+
max(12u, detail_iterations),
|
|
368
|
+
detail_power + 0.15
|
|
369
|
+
);
|
|
370
|
+
let obstacle = clamp(
|
|
371
|
+
feature_mask * 0.58 +
|
|
372
|
+
roughness * 0.25 +
|
|
373
|
+
upwardStrength * 0.25 -
|
|
374
|
+
moisture * 0.16 -
|
|
375
|
+
water * 0.2,
|
|
376
|
+
0.0,
|
|
377
|
+
1.0
|
|
378
|
+
);
|
|
379
|
+
let foliage_field = smooth_mandelbrot(
|
|
380
|
+
warped.x * scale * (detail_scale * 1.85 + 0.35) + offX * 1.9,
|
|
381
|
+
warped.y * scale * (detail_scale * 1.85 + 0.35) + offZ * 1.9,
|
|
382
|
+
max(16u, detail_iterations + 2u),
|
|
383
|
+
max(1.6, detail_power - 0.2)
|
|
384
|
+
);
|
|
385
|
+
let foliage = clamp(
|
|
386
|
+
foliage_field * moisture * (1.0 - water) * (0.35 + flatStrength * 0.65) * (1.0 - obstacle * 0.82),
|
|
387
|
+
0.0,
|
|
388
|
+
1.0
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
var surface = SURFACE_DIRT;
|
|
392
|
+
if (water > 0.58 || cumulative_height < 0.14) {
|
|
393
|
+
surface = SURFACE_WATER;
|
|
394
|
+
} else if (heat < 0.2 && water > 0.42) {
|
|
395
|
+
surface = SURFACE_ICE;
|
|
396
|
+
} else if (slopeBand == SLOPE_DOWNWARD && moisture > 0.52) {
|
|
397
|
+
surface = SURFACE_MUD;
|
|
398
|
+
} else if (obstacle > 0.72 || slopeBand == SLOPE_UPWARD) {
|
|
399
|
+
surface = SURFACE_ROCK;
|
|
400
|
+
if (heat > 0.72) {
|
|
401
|
+
surface = SURFACE_BASALT;
|
|
402
|
+
}
|
|
403
|
+
} else if (heat > 0.74 && moisture < 0.32) {
|
|
404
|
+
surface = SURFACE_SAND;
|
|
405
|
+
} else if (foliage > 0.45 && moisture > 0.42) {
|
|
406
|
+
surface = SURFACE_GRASS;
|
|
407
|
+
} else if (moisture > 0.6) {
|
|
408
|
+
surface = SURFACE_DIRT;
|
|
409
|
+
} else if (obstacle > 0.5) {
|
|
410
|
+
surface = SURFACE_GRAVEL;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
let featureNoise = hash_cell(cell, seed, 0x77abu);
|
|
414
|
+
var feature = FEATURE_NONE;
|
|
415
|
+
if (surface == SURFACE_WATER) {
|
|
416
|
+
feature = FEATURE_WATER_RIPPLE;
|
|
417
|
+
} else if (surface == SURFACE_ICE && featureNoise > 0.45) {
|
|
418
|
+
feature = FEATURE_ICE_SPIKE;
|
|
419
|
+
} else if (
|
|
420
|
+
surface == SURFACE_ROCK ||
|
|
421
|
+
surface == SURFACE_GRAVEL ||
|
|
422
|
+
surface == SURFACE_BASALT
|
|
423
|
+
) {
|
|
424
|
+
feature = FEATURE_ROCK;
|
|
425
|
+
if (obstacle > 0.85) {
|
|
426
|
+
feature = FEATURE_BOULDER;
|
|
427
|
+
}
|
|
428
|
+
} else if (surface == SURFACE_MUD && featureNoise > 0.75) {
|
|
429
|
+
feature = FEATURE_REED;
|
|
430
|
+
} else if (foliage > 0.72 && moisture > 0.55 && featureNoise > 0.35) {
|
|
431
|
+
feature = FEATURE_TREE;
|
|
432
|
+
} else if (foliage > 0.5 && featureNoise > 0.28) {
|
|
433
|
+
feature = FEATURE_BUSH;
|
|
434
|
+
} else if (surface == SURFACE_GRASS && foliage > 0.3 && featureNoise > 0.15) {
|
|
435
|
+
feature = FEATURE_GRASS_TUFT;
|
|
436
|
+
} else if (slopeBand == SLOPE_UPWARD && obstacle > 0.6 && featureNoise > 0.94) {
|
|
437
|
+
feature = FEATURE_RUIN;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
var biome = classify_biome(heat, moisture, surface, slopeBand, obstacle, foliage);
|
|
441
|
+
biome = maybe_settlement(biome, height01, cell, params.seed, obstacle);
|
|
442
|
+
|
|
443
|
+
terrain[job_index].height = rawHeight * params.height_scale;
|
|
444
|
+
terrain[job_index].heat = heat;
|
|
445
|
+
terrain[job_index].moisture = moisture;
|
|
446
|
+
terrain[job_index].biome = biome;
|
|
447
|
+
terrain[job_index].surface = surface;
|
|
448
|
+
terrain[job_index].feature = feature;
|
|
449
|
+
terrain[job_index].foliage = foliage;
|
|
450
|
+
terrain[job_index].slope_band = slopeBand;
|
|
451
|
+
}
|
package/src/wgsl.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
declare const __PLASIUS_IMPORT_META_URL__: string;
|
|
2
|
+
|
|
3
|
+
const wgslBaseUrl =
|
|
4
|
+
typeof __PLASIUS_IMPORT_META_URL__ === "string" && __PLASIUS_IMPORT_META_URL__
|
|
5
|
+
? __PLASIUS_IMPORT_META_URL__
|
|
6
|
+
: typeof document !== "undefined" && document.baseURI
|
|
7
|
+
? document.baseURI
|
|
8
|
+
: typeof location !== "undefined"
|
|
9
|
+
? location.href
|
|
10
|
+
: "file:///";
|
|
11
|
+
|
|
12
|
+
export const terrainWgslUrl = new URL("./terrain.wgsl", wgslBaseUrl);
|
|
13
|
+
export const fieldWgslUrl = new URL("./field.wgsl", wgslBaseUrl);
|
|
14
|
+
export const fractalPrepassWgslUrl = new URL("./fractal-prepass.wgsl", wgslBaseUrl);
|
|
15
|
+
|
|
16
|
+
export async function loadTerrainWgsl(options: {
|
|
17
|
+
url?: URL | string;
|
|
18
|
+
fetcher?: typeof fetch | null;
|
|
19
|
+
} = {}) {
|
|
20
|
+
const { url = terrainWgslUrl, fetcher = globalThis.fetch } = options;
|
|
21
|
+
const resolved = url instanceof URL ? url : new URL(url, terrainWgslUrl);
|
|
22
|
+
|
|
23
|
+
if (!fetcher || resolved.protocol === "file:") {
|
|
24
|
+
const { readFile } = await import("node:fs/promises");
|
|
25
|
+
const { fileURLToPath } = await import("node:url");
|
|
26
|
+
return readFile(fileURLToPath(resolved), "utf8");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const response = await fetcher(resolved);
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
const statusText = response.statusText ? ` ${response.statusText}` : "";
|
|
32
|
+
throw new Error(`Failed to load WGSL (${response.status}${statusText})`);
|
|
33
|
+
}
|
|
34
|
+
return response.text();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function loadFieldWgsl(options: {
|
|
38
|
+
url?: URL | string;
|
|
39
|
+
fetcher?: typeof fetch | null;
|
|
40
|
+
} = {}) {
|
|
41
|
+
const { url = fieldWgslUrl, fetcher = globalThis.fetch } = options;
|
|
42
|
+
const resolved = url instanceof URL ? url : new URL(url, fieldWgslUrl);
|
|
43
|
+
|
|
44
|
+
if (!fetcher || resolved.protocol === "file:") {
|
|
45
|
+
const { readFile } = await import("node:fs/promises");
|
|
46
|
+
const { fileURLToPath } = await import("node:url");
|
|
47
|
+
return readFile(fileURLToPath(resolved), "utf8");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const response = await fetcher(resolved);
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const statusText = response.statusText ? ` ${response.statusText}` : "";
|
|
53
|
+
throw new Error(`Failed to load WGSL (${response.status}${statusText})`);
|
|
54
|
+
}
|
|
55
|
+
return response.text();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function loadFractalPrepassWgsl(options: {
|
|
59
|
+
url?: URL | string;
|
|
60
|
+
fetcher?: typeof fetch | null;
|
|
61
|
+
} = {}) {
|
|
62
|
+
const { url = fractalPrepassWgslUrl, fetcher = globalThis.fetch } = options;
|
|
63
|
+
const resolved = url instanceof URL ? url : new URL(url, fractalPrepassWgslUrl);
|
|
64
|
+
|
|
65
|
+
if (!fetcher || resolved.protocol === "file:") {
|
|
66
|
+
const { readFile } = await import("node:fs/promises");
|
|
67
|
+
const { fileURLToPath } = await import("node:url");
|
|
68
|
+
return readFile(fileURLToPath(resolved), "utf8");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const response = await fetcher(resolved);
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const statusText = response.statusText ? ` ${response.statusText}` : "";
|
|
74
|
+
throw new Error(`Failed to load WGSL (${response.status}${statusText})`);
|
|
75
|
+
}
|
|
76
|
+
return response.text();
|
|
77
|
+
}
|