@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,290 @@
|
|
|
1
|
+
struct FractalParams {
|
|
2
|
+
grid: vec4<f32>, // grid_points, extent, step, seed
|
|
3
|
+
field0: vec4<f32>, // scale, warp_scale, warp_strength, power
|
|
4
|
+
field1: vec4<f32>, // detail_scale, detail_power, ridge_power, heat_bias
|
|
5
|
+
field2: vec4<f32>, // moisture_bias, mandel_scale, mandel_strength, mandel_rock_boost
|
|
6
|
+
field3: vec4<f32>, // iterations, detail_iterations, macro_scale, macro_warp_strength
|
|
7
|
+
field4: vec4<f32>, // style_mix_strength, terrace_steps, terrace_strength, crater_strength
|
|
8
|
+
field5: vec4<f32>, // crater_scale, height_min, height_max, pad
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
struct Sample {
|
|
12
|
+
data0: vec4<f32>, // height, heat, moisture, rockiness
|
|
13
|
+
data1: vec4<f32>, // water, ridge, base, detail
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
@group(0) @binding(0) var<uniform> params: FractalParams;
|
|
17
|
+
@group(0) @binding(1) var<storage, read_write> samples_out: array<Sample>;
|
|
18
|
+
@group(0) @binding(2) var<storage, read> samples_in: array<Sample>;
|
|
19
|
+
|
|
20
|
+
fn hash32(x: u32) -> u32 {
|
|
21
|
+
var v = x;
|
|
22
|
+
v ^= v >> 17u;
|
|
23
|
+
v *= 0xed5ad4bbu;
|
|
24
|
+
v ^= v >> 11u;
|
|
25
|
+
v *= 0xac4c1b51u;
|
|
26
|
+
v ^= v >> 15u;
|
|
27
|
+
v *= 0x31848babu;
|
|
28
|
+
v ^= v >> 14u;
|
|
29
|
+
return v;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fn hash01(x: u32) -> f32 {
|
|
33
|
+
let v = hash32(x) & 0x00ffffffu;
|
|
34
|
+
return f32(v) / 16777216.0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn smooth_mandelbrot(cx: f32, cy: f32, iterations: u32, power: f32) -> f32 {
|
|
38
|
+
var zx = 0.0;
|
|
39
|
+
var zy = 0.0;
|
|
40
|
+
var i: u32 = 0u;
|
|
41
|
+
loop {
|
|
42
|
+
if (i >= iterations) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
let r2 = zx * zx + zy * zy;
|
|
46
|
+
if (r2 > 4.0) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
let r = sqrt(r2);
|
|
50
|
+
let theta = atan2(zy, zx);
|
|
51
|
+
let rp = pow(r, power);
|
|
52
|
+
zx = rp * cos(theta * power) + cx;
|
|
53
|
+
zy = rp * sin(theta * power) + cy;
|
|
54
|
+
i = i + 1u;
|
|
55
|
+
}
|
|
56
|
+
if (i >= iterations) {
|
|
57
|
+
return 1.0;
|
|
58
|
+
}
|
|
59
|
+
let r = max(sqrt(zx * zx + zy * zy), 1e-6);
|
|
60
|
+
let nu = log2(log(r));
|
|
61
|
+
let smoothVal = (f32(i) + 1.0 - nu) / f32(iterations);
|
|
62
|
+
return clamp(smoothVal, 0.0, 1.0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn terrace_height(h: f32, steps: f32) -> f32 {
|
|
66
|
+
let count = max(1.0, steps);
|
|
67
|
+
let step = 1.0 / count;
|
|
68
|
+
let clamped = clamp(h, 0.0, 1.0);
|
|
69
|
+
let band = floor(clamped / step);
|
|
70
|
+
let t = fract(clamped / step);
|
|
71
|
+
let blend = t * t * (3.0 - 2.0 * t);
|
|
72
|
+
return (band + blend) * step;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fn crater_field(p: vec2<f32>, scale: f32, seed: u32) -> f32 {
|
|
76
|
+
let sp = p * scale;
|
|
77
|
+
let cell = floor(sp);
|
|
78
|
+
let local = sp - cell;
|
|
79
|
+
let cx = i32(cell.x);
|
|
80
|
+
let cz = i32(cell.y);
|
|
81
|
+
let hx = u32(bitcast<u32>(cx));
|
|
82
|
+
let hz = u32(bitcast<u32>(cz));
|
|
83
|
+
let h0 = hash01((hx * 374761393u) ^ (hz * 668265263u) ^ seed ^ 0x9e3779b9u);
|
|
84
|
+
let h1 = hash01((hx * 2246822519u) ^ (hz * 3266489917u) ^ seed ^ 0x85ebca6bu);
|
|
85
|
+
let h2 = hash01((hx * 1597334677u) ^ (hz * 3812015801u) ^ seed ^ 0xc2b2ae35u);
|
|
86
|
+
let center = vec2<f32>(h0, h1);
|
|
87
|
+
let radius = 0.22 + 0.25 * h2;
|
|
88
|
+
let dist = distance(local, center);
|
|
89
|
+
return smoothstep(radius, radius * 0.35, dist);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn macro_map(p: vec2<f32>, iterations: u32, power: f32, scale: f32, warp_strength: f32, mix_strength: f32) -> f32 {
|
|
93
|
+
let macro_iter = max(12u, iterations / 3u);
|
|
94
|
+
let macroA = smooth_mandelbrot(p.x * scale, p.y * scale, macro_iter, power);
|
|
95
|
+
let macroB = smooth_mandelbrot((p.x + 13.7) * scale, (p.y - 9.2) * scale, macro_iter, power + 0.35);
|
|
96
|
+
let warp = vec2<f32>(macroA - 0.5, macroB - 0.5) * warp_strength;
|
|
97
|
+
let macroC = smooth_mandelbrot((p.x + warp.x) * scale, (p.y + warp.y) * scale, macro_iter, power);
|
|
98
|
+
return clamp((macroC - 0.5) * mix_strength + 0.5, 0.0, 1.0);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn sample_field(p: vec2<f32>) -> Sample {
|
|
102
|
+
let seed = u32(params.grid.w);
|
|
103
|
+
let iterations = u32(params.field3.x);
|
|
104
|
+
let detail_iterations = u32(params.field3.y);
|
|
105
|
+
let macro_scale = params.field3.z;
|
|
106
|
+
let macro_warp_strength = params.field3.w;
|
|
107
|
+
let style_mix_strength = params.field4.x;
|
|
108
|
+
let terrace_steps = params.field4.y;
|
|
109
|
+
let terrace_strength = params.field4.z;
|
|
110
|
+
let crater_strength = params.field4.w;
|
|
111
|
+
let crater_scale = params.field5.x;
|
|
112
|
+
let height_min = params.field5.y;
|
|
113
|
+
let height_max = params.field5.z;
|
|
114
|
+
let scale = params.field0.x;
|
|
115
|
+
let warp_scale = params.field0.y;
|
|
116
|
+
let warp_strength = params.field0.z;
|
|
117
|
+
let power = params.field0.w;
|
|
118
|
+
let detail_scale = params.field1.x;
|
|
119
|
+
let detail_power = params.field1.y;
|
|
120
|
+
let ridge_power = params.field1.z;
|
|
121
|
+
let heat_bias = params.field1.w;
|
|
122
|
+
let moisture_bias = params.field2.x;
|
|
123
|
+
|
|
124
|
+
let offX = hash01(seed ^ 0x7f4a7c15u) * 4.0 - 2.0;
|
|
125
|
+
let offZ = hash01(seed ^ 0xa9d84d2bu) * 4.0 - 2.0;
|
|
126
|
+
let warpOffX = hash01(seed ^ 0x8c2f1d3bu) * 6.0 - 3.0;
|
|
127
|
+
let warpOffZ = hash01(seed ^ 0x5d2c79e9u) * 6.0 - 3.0;
|
|
128
|
+
|
|
129
|
+
let warpIter = max(16u, iterations * 6u / 10u);
|
|
130
|
+
let warpA = smooth_mandelbrot(
|
|
131
|
+
(p.x + warpOffX) * warp_scale,
|
|
132
|
+
(p.y + warpOffZ) * warp_scale,
|
|
133
|
+
warpIter,
|
|
134
|
+
power
|
|
135
|
+
);
|
|
136
|
+
let warpB = smooth_mandelbrot(
|
|
137
|
+
(p.x - warpOffZ) * warp_scale,
|
|
138
|
+
(p.y + warpOffX) * warp_scale,
|
|
139
|
+
warpIter,
|
|
140
|
+
power
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
let warped = vec2<f32>(
|
|
144
|
+
p.x + (warpA - 0.5) * warp_strength,
|
|
145
|
+
p.y + (warpB - 0.5) * warp_strength
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
let base = smooth_mandelbrot(
|
|
149
|
+
warped.x * scale + offX,
|
|
150
|
+
warped.y * scale + offZ,
|
|
151
|
+
iterations,
|
|
152
|
+
power
|
|
153
|
+
);
|
|
154
|
+
let mid = smooth_mandelbrot(
|
|
155
|
+
warped.x * scale * 2.15 + offX * 0.6,
|
|
156
|
+
warped.y * scale * 2.15 + offZ * 0.6,
|
|
157
|
+
max(18u, iterations * 7u / 10u),
|
|
158
|
+
power + 0.2
|
|
159
|
+
);
|
|
160
|
+
let detail = smooth_mandelbrot(
|
|
161
|
+
warped.x * scale * detail_scale + offX * 1.4,
|
|
162
|
+
warped.y * scale * detail_scale + offZ * 1.4,
|
|
163
|
+
detail_iterations,
|
|
164
|
+
detail_power
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
let ridge = 1.0 - abs(2.0 * mid - 1.0);
|
|
168
|
+
let baseHeight = pow(base, 0.9) * pow(mid, 1.05) * pow(detail, 1.1);
|
|
169
|
+
|
|
170
|
+
let styleMask = macro_map(warped, iterations, power, macro_scale, macro_warp_strength, style_mix_strength);
|
|
171
|
+
let terrace = terrace_height(baseHeight, terrace_steps);
|
|
172
|
+
let crater = crater_field(warped, crater_scale, seed);
|
|
173
|
+
|
|
174
|
+
let styleA = clamp(pow(baseHeight, 0.8) + pow(ridge, 1.4) * 0.2, 0.0, 1.0);
|
|
175
|
+
let styleB = clamp(
|
|
176
|
+
baseHeight * (1.0 - terrace_strength) +
|
|
177
|
+
terrace * terrace_strength -
|
|
178
|
+
crater * crater_strength +
|
|
179
|
+
pow(ridge, 1.6) * 0.12,
|
|
180
|
+
0.0,
|
|
181
|
+
1.0
|
|
182
|
+
);
|
|
183
|
+
let mixed = mix(styleA, styleB, styleMask);
|
|
184
|
+
let ridgeBoost = pow(ridge, 1.35) * 0.22;
|
|
185
|
+
let centered = (mixed - 0.5) * 2.0;
|
|
186
|
+
let shaped = sign(centered) * pow(abs(centered), 0.75);
|
|
187
|
+
let macroOffset = (styleMask - 0.5) * 0.25;
|
|
188
|
+
let rawHeight = clamp(0.5 + shaped * 0.8 + macroOffset + ridgeBoost, height_min, height_max);
|
|
189
|
+
let height01 = clamp(rawHeight, 0.0, 1.0);
|
|
190
|
+
|
|
191
|
+
let roughness = clamp(pow(ridge, ridge_power) * 0.7 + detail * 0.3, 0.0, 1.0);
|
|
192
|
+
let heat = clamp(0.55 * mid + 0.35 * (1.0 - height01) + heat_bias, 0.0, 1.0);
|
|
193
|
+
let moisture = clamp(
|
|
194
|
+
0.55 * detail + 0.35 * (1.0 - height01) - (heat - 0.5) * 0.1 + moisture_bias,
|
|
195
|
+
0.0,
|
|
196
|
+
1.0
|
|
197
|
+
);
|
|
198
|
+
let rockiness = clamp(roughness * 0.6 + height01 * 0.4, 0.0, 1.0);
|
|
199
|
+
let water = clamp((0.32 - height01) * 3.0 + (moisture - 0.5) * 0.2, 0.0, 1.0);
|
|
200
|
+
|
|
201
|
+
return Sample(
|
|
202
|
+
vec4<f32>(rawHeight, heat, moisture, rockiness),
|
|
203
|
+
vec4<f32>(water, ridge, base, detail)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@compute @workgroup_size(8, 8)
|
|
208
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
209
|
+
let grid_points = u32(params.grid.x);
|
|
210
|
+
if (gid.x >= grid_points || gid.y >= grid_points) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
let extent = params.grid.y;
|
|
214
|
+
let step = params.grid.z;
|
|
215
|
+
let x = -extent + f32(gid.x) * step;
|
|
216
|
+
let z = -extent + f32(gid.y) * step;
|
|
217
|
+
|
|
218
|
+
var sample = sample_field(vec2<f32>(x, z));
|
|
219
|
+
|
|
220
|
+
let mandel_scale = params.field2.y;
|
|
221
|
+
let mandel_strength = params.field2.z;
|
|
222
|
+
let mandel_rock = params.field2.w;
|
|
223
|
+
let height_min = params.field5.y;
|
|
224
|
+
let height_max = params.field5.z;
|
|
225
|
+
let mandel = smooth_mandelbrot(x * mandel_scale, z * mandel_scale, 24u, 2.0);
|
|
226
|
+
let mandel_bias = (mandel - 0.5) * mandel_strength;
|
|
227
|
+
let rawHeight = clamp(sample.data0.x + mandel_bias, height_min, height_max);
|
|
228
|
+
let height01 = clamp(rawHeight, 0.0, 1.0);
|
|
229
|
+
let rockiness = clamp(sample.data0.w + max(0.0, mandel - 0.55) * mandel_rock, 0.0, 1.0);
|
|
230
|
+
let water = clamp((0.32 - height01) * 3.0 + (sample.data0.z - 0.5) * 0.2, 0.0, 1.0);
|
|
231
|
+
|
|
232
|
+
sample.data0.x = rawHeight;
|
|
233
|
+
sample.data0.w = rockiness;
|
|
234
|
+
sample.data1.x = water;
|
|
235
|
+
|
|
236
|
+
let index = gid.y * grid_points + gid.x;
|
|
237
|
+
samples_out[index] = sample;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
fn height_at(ix: i32, iy: i32, size: i32) -> f32 {
|
|
241
|
+
let x = clamp(ix, 0, size - 1);
|
|
242
|
+
let y = clamp(iy, 0, size - 1);
|
|
243
|
+
let idx = y * size + x;
|
|
244
|
+
return samples_in[idx].data0.x;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
@compute @workgroup_size(8, 8)
|
|
248
|
+
fn accent_heights(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
249
|
+
let grid_points = i32(params.grid.x);
|
|
250
|
+
let ix = i32(gid.x);
|
|
251
|
+
let iy = i32(gid.y);
|
|
252
|
+
if (ix >= grid_points || iy >= grid_points) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
let idx = iy * grid_points + ix;
|
|
256
|
+
let center = samples_in[idx];
|
|
257
|
+
var sum = 0.0;
|
|
258
|
+
var count = 0.0;
|
|
259
|
+
for (var dx = -1; dx <= 1; dx = dx + 1) {
|
|
260
|
+
for (var dy = -1; dy <= 1; dy = dy + 1) {
|
|
261
|
+
if (dx == 0 && dy == 0) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
sum = sum + height_at(ix + dx, iy + dy, grid_points);
|
|
265
|
+
count = count + 1.0;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
let avg = select(center.data0.x, sum / count, count > 0.0);
|
|
269
|
+
let delta = center.data0.x - avg;
|
|
270
|
+
let signed = sign(delta) * pow(abs(delta), 0.8);
|
|
271
|
+
let ridge = max(0.0, delta);
|
|
272
|
+
let accentStrength = 1.6;
|
|
273
|
+
let ridgeStrength = 0.35;
|
|
274
|
+
let macroStrength = 0.25;
|
|
275
|
+
let minHeight = params.field5.y;
|
|
276
|
+
let maxHeight = params.field5.z;
|
|
277
|
+
let macroOffset = (avg - 0.5) * macroStrength;
|
|
278
|
+
let rawHeight = clamp(
|
|
279
|
+
avg + signed * accentStrength + ridge * ridgeStrength + macroOffset,
|
|
280
|
+
minHeight,
|
|
281
|
+
maxHeight
|
|
282
|
+
);
|
|
283
|
+
let clampedHeight = clamp(rawHeight, 0.0, 1.0);
|
|
284
|
+
|
|
285
|
+
var out = center;
|
|
286
|
+
out.data0.x = rawHeight;
|
|
287
|
+
out.data0.w = clamp(out.data0.w + max(0.0, clampedHeight - 0.65) * 0.5, 0.0, 1.0);
|
|
288
|
+
out.data1.x = clamp((0.32 - clampedHeight) * 3.0 + (out.data0.z - 0.5) * 0.2, 0.0, 1.0);
|
|
289
|
+
samples_out[idx] = out;
|
|
290
|
+
}
|
package/src/generator.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MicroFeature,
|
|
3
|
+
SlopeBand,
|
|
4
|
+
SurfaceCover,
|
|
5
|
+
type HexCell,
|
|
6
|
+
type MicroFeatureId,
|
|
7
|
+
type SlopeBandId,
|
|
8
|
+
type SurfaceCoverId,
|
|
9
|
+
type TerrainBiomeId,
|
|
10
|
+
type TerrainCell,
|
|
11
|
+
type TerrainParams,
|
|
12
|
+
} from "./types";
|
|
13
|
+
|
|
14
|
+
export function packHexCells(cells: HexCell[]): Int32Array {
|
|
15
|
+
const data = new Int32Array(cells.length * 4);
|
|
16
|
+
cells.forEach((cell, i) => {
|
|
17
|
+
const base = i * 4;
|
|
18
|
+
data[base] = cell.q | 0;
|
|
19
|
+
data[base + 1] = cell.r | 0;
|
|
20
|
+
data[base + 2] = cell.level | 0;
|
|
21
|
+
data[base + 3] = cell.flags ?? 0;
|
|
22
|
+
});
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function encodeTerrainParams(params: TerrainParams): ArrayBuffer {
|
|
27
|
+
const buffer = new ArrayBuffer(16 * 4);
|
|
28
|
+
const u32 = new Uint32Array(buffer);
|
|
29
|
+
const f32 = new Float32Array(buffer);
|
|
30
|
+
u32[0] = params.seed >>> 0;
|
|
31
|
+
u32[1] = params.cellCount >>> 0;
|
|
32
|
+
f32[2] = params.heatBias ?? 0;
|
|
33
|
+
f32[3] = params.heightScale ?? 1;
|
|
34
|
+
f32[4] = params.macroScale ?? 0.035;
|
|
35
|
+
f32[5] = params.macroWarpStrength ?? 0.18;
|
|
36
|
+
f32[6] = params.styleMixStrength ?? 1.0;
|
|
37
|
+
f32[7] = params.terraceSteps ?? 6;
|
|
38
|
+
f32[8] = params.terraceStrength ?? 0.35;
|
|
39
|
+
f32[9] = params.craterStrength ?? 0.25;
|
|
40
|
+
f32[10] = params.craterScale ?? 0.18;
|
|
41
|
+
f32[11] = params.heightMin ?? -0.35;
|
|
42
|
+
f32[12] = params.heightMax ?? 1.6;
|
|
43
|
+
f32[13] = params.slopeDownMax ?? 0.2;
|
|
44
|
+
f32[14] = params.slopeUpMin ?? 0.8;
|
|
45
|
+
f32[15] = 0;
|
|
46
|
+
return buffer;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isExpandedTerrainLayout(u32: Uint32Array): boolean {
|
|
50
|
+
if (u32.length % 8 !== 0) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const count = u32.length / 8;
|
|
54
|
+
if (count === 0) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
const sampleCount = Math.min(16, count);
|
|
58
|
+
let valid = 0;
|
|
59
|
+
for (let i = 0; i < sampleCount; i += 1) {
|
|
60
|
+
const base = i * 8;
|
|
61
|
+
const surface = u32[base + 4];
|
|
62
|
+
const slopeBand = u32[base + 7];
|
|
63
|
+
if (surface <= SurfaceCover.Sludge && slopeBand <= SlopeBand.Upward) {
|
|
64
|
+
valid += 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return valid >= Math.max(1, Math.floor(sampleCount * 0.8));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function unpackTerrain(buffer: ArrayBuffer): TerrainCell[] {
|
|
71
|
+
const f32 = new Float32Array(buffer);
|
|
72
|
+
const u32 = new Uint32Array(buffer);
|
|
73
|
+
const expanded = isExpandedTerrainLayout(u32);
|
|
74
|
+
const stride = expanded ? 8 : 4;
|
|
75
|
+
const count = Math.floor(f32.length / stride);
|
|
76
|
+
const cells: TerrainCell[] = [];
|
|
77
|
+
for (let i = 0; i < count; i += 1) {
|
|
78
|
+
const base = i * stride;
|
|
79
|
+
const cell: TerrainCell = {
|
|
80
|
+
height: f32[base],
|
|
81
|
+
heat: f32[base + 1],
|
|
82
|
+
moisture: f32[base + 2],
|
|
83
|
+
biome: u32[base + 3] as TerrainBiomeId,
|
|
84
|
+
};
|
|
85
|
+
if (expanded) {
|
|
86
|
+
const surface = u32[base + 4];
|
|
87
|
+
const feature = u32[base + 5];
|
|
88
|
+
const foliage = f32[base + 6];
|
|
89
|
+
const slopeBand = u32[base + 7];
|
|
90
|
+
if (surface <= SurfaceCover.Sludge) {
|
|
91
|
+
cell.surface = surface as SurfaceCoverId;
|
|
92
|
+
}
|
|
93
|
+
if (feature <= MicroFeature.Flower) {
|
|
94
|
+
cell.feature = feature as MicroFeatureId;
|
|
95
|
+
}
|
|
96
|
+
if (Number.isFinite(foliage)) {
|
|
97
|
+
cell.foliage = foliage;
|
|
98
|
+
}
|
|
99
|
+
if (slopeBand <= SlopeBand.Upward) {
|
|
100
|
+
cell.slopeBand = slopeBand as SlopeBandId;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
cells.push(cell);
|
|
104
|
+
}
|
|
105
|
+
return cells;
|
|
106
|
+
}
|
package/src/hex.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { HexCell, HexLevelSpec } from "./types";
|
|
2
|
+
|
|
3
|
+
const HEX_AREA_FACTOR = (3 * Math.sqrt(3)) / 2;
|
|
4
|
+
|
|
5
|
+
export function hexAreaFromSide(sideMeters: number): number {
|
|
6
|
+
return HEX_AREA_FACTOR * sideMeters * sideMeters;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function hexSideFromArea(areaM2: number): number {
|
|
10
|
+
return Math.sqrt(areaM2 / HEX_AREA_FACTOR);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function axialToWorld(q: number, r: number, sizeMeters: number) {
|
|
14
|
+
const x = sizeMeters * (Math.sqrt(3) * q + (Math.sqrt(3) / 2) * r);
|
|
15
|
+
const y = sizeMeters * (1.5 * r);
|
|
16
|
+
return { x, y };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function generateHexGrid(radius: number, level = 0): HexCell[] {
|
|
20
|
+
const cells: HexCell[] = [];
|
|
21
|
+
for (let q = -radius; q <= radius; q += 1) {
|
|
22
|
+
const r1 = Math.max(-radius, -q - radius);
|
|
23
|
+
const r2 = Math.min(radius, -q + radius);
|
|
24
|
+
for (let r = r1; r <= r2; r += 1) {
|
|
25
|
+
cells.push({ q, r, level, flags: 0 });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return cells;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function buildHexLevels(options: {
|
|
32
|
+
topAreaKm2?: number;
|
|
33
|
+
minAreaM2?: number;
|
|
34
|
+
levels?: number;
|
|
35
|
+
} = {}): HexLevelSpec[] {
|
|
36
|
+
const topAreaKm2 = options.topAreaKm2 ?? 1000;
|
|
37
|
+
const minAreaM2 = options.minAreaM2 ?? 10;
|
|
38
|
+
const levels = Math.max(2, options.levels ?? 6);
|
|
39
|
+
const topAreaM2 = topAreaKm2 * 1_000_000;
|
|
40
|
+
const ratio = Math.pow(topAreaM2 / minAreaM2, 1 / (levels - 1));
|
|
41
|
+
|
|
42
|
+
const specs: HexLevelSpec[] = [];
|
|
43
|
+
for (let level = 0; level < levels; level += 1) {
|
|
44
|
+
const areaM2 = topAreaM2 / Math.pow(ratio, level);
|
|
45
|
+
const sideMeters = hexSideFromArea(areaM2);
|
|
46
|
+
specs.push({
|
|
47
|
+
level,
|
|
48
|
+
areaM2,
|
|
49
|
+
sideMeters,
|
|
50
|
+
acrossFlatsMeters: sideMeters * Math.sqrt(3),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return specs;
|
|
54
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./types";
|
|
2
|
+
export * from "./hex";
|
|
3
|
+
export * from "./generator";
|
|
4
|
+
export * from "./wgsl";
|
|
5
|
+
export * from "./fields";
|
|
6
|
+
export * from "./biomes/temperate";
|
|
7
|
+
export * from "./perf-monitor";
|
|
8
|
+
export * from "./fractal-prepass";
|
|
9
|
+
export * from "./tiles";
|
|
10
|
+
export * from "./tile-cache";
|
|
11
|
+
export * from "./mesh";
|