@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.
Files changed (43) hide show
  1. package/LICENSE +203 -0
  2. package/README.md +73 -0
  3. package/dist/field.wgsl +225 -0
  4. package/dist/fractal-prepass.wgsl +290 -0
  5. package/dist/index.cjs +1942 -0
  6. package/dist/index.cjs.map +1 -0
  7. package/dist/index.d.cts +447 -0
  8. package/dist/index.d.ts +447 -0
  9. package/dist/index.js +1848 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/terrain.wgsl +451 -0
  12. package/docs/adrs/adr-0001-package-scope.md +18 -0
  13. package/docs/adrs/adr-0002-world-tiling-lod-stitching.md +28 -0
  14. package/docs/adrs/adr-0003-terrain-generation-style-mixing.md +21 -0
  15. package/docs/adrs/index.md +5 -0
  16. package/docs/biomes.md +206 -0
  17. package/docs/lod-zoning.md +22 -0
  18. package/docs/plan.md +107 -0
  19. package/docs/procedural-surface.md +73 -0
  20. package/docs/resources.md +55 -0
  21. package/package.json +53 -0
  22. package/src/biomes/temperate.ts +387 -0
  23. package/src/field.wgsl +225 -0
  24. package/src/fields.ts +321 -0
  25. package/src/fractal-prepass.ts +237 -0
  26. package/src/fractal-prepass.wgsl +290 -0
  27. package/src/generator.ts +106 -0
  28. package/src/hex.ts +54 -0
  29. package/src/index.ts +11 -0
  30. package/src/mesh.ts +285 -0
  31. package/src/perf-monitor.ts +133 -0
  32. package/src/shaders/demo-terrain.wgsl +193 -0
  33. package/src/shaders/demo-trees.wgsl +172 -0
  34. package/src/shaders/demo-water-sim.wgsl +361 -0
  35. package/src/shaders/library/common.wgsl +38 -0
  36. package/src/shaders/library/materials.wgsl +175 -0
  37. package/src/terrain.wgsl +451 -0
  38. package/src/tile-cache.ts +274 -0
  39. package/src/tiles.ts +417 -0
  40. package/src/types.ts +220 -0
  41. package/src/wgsl/field.job.wgsl +225 -0
  42. package/src/wgsl/terrain.job.wgsl +451 -0
  43. package/src/wgsl.ts +77 -0
package/src/types.ts ADDED
@@ -0,0 +1,220 @@
1
+ export const TerrainBiome = {
2
+ Plains: 0,
3
+ Tundra: 1,
4
+ Savanna: 2,
5
+ River: 3,
6
+ City: 4,
7
+ Village: 5,
8
+ Ice: 6,
9
+ Snow: 7,
10
+ Mountainous: 8,
11
+ Volcanic: 9,
12
+ Road: 10,
13
+ Town: 11,
14
+ Castle: 12,
15
+ MixedForest: 13,
16
+ } as const;
17
+
18
+ export type TerrainBiomeId = (typeof TerrainBiome)[keyof typeof TerrainBiome];
19
+
20
+ export const TerrainBiomeLabel: Record<TerrainBiomeId, string> = {
21
+ [TerrainBiome.Plains]: "Plains",
22
+ [TerrainBiome.Tundra]: "Tundra",
23
+ [TerrainBiome.Savanna]: "Savanna",
24
+ [TerrainBiome.River]: "River",
25
+ [TerrainBiome.City]: "City",
26
+ [TerrainBiome.Village]: "Village",
27
+ [TerrainBiome.Ice]: "Ice",
28
+ [TerrainBiome.Snow]: "Snow",
29
+ [TerrainBiome.Mountainous]: "Mountainous",
30
+ [TerrainBiome.Volcanic]: "Volcanic",
31
+ [TerrainBiome.Road]: "Road",
32
+ [TerrainBiome.Town]: "Town",
33
+ [TerrainBiome.Castle]: "Castle",
34
+ [TerrainBiome.MixedForest]: "Mixed Forest",
35
+ };
36
+
37
+ export const MacroBiome = {
38
+ Polar: 0,
39
+ ColdTemperate: 1,
40
+ Temperate: 2,
41
+ Arid: 3,
42
+ Tropical: 4,
43
+ Alpine: 5,
44
+ Volcanic: 6,
45
+ Freshwater: 7,
46
+ Coastal: 8,
47
+ Urban: 9,
48
+ Underground: 10,
49
+ } as const;
50
+
51
+ export type MacroBiomeId = (typeof MacroBiome)[keyof typeof MacroBiome];
52
+
53
+ export const MacroBiomeLabel: Record<MacroBiomeId, string> = {
54
+ [MacroBiome.Polar]: "Polar",
55
+ [MacroBiome.ColdTemperate]: "Cold Temperate",
56
+ [MacroBiome.Temperate]: "Temperate",
57
+ [MacroBiome.Arid]: "Arid",
58
+ [MacroBiome.Tropical]: "Tropical",
59
+ [MacroBiome.Alpine]: "Alpine",
60
+ [MacroBiome.Volcanic]: "Volcanic",
61
+ [MacroBiome.Freshwater]: "Freshwater",
62
+ [MacroBiome.Coastal]: "Coastal",
63
+ [MacroBiome.Urban]: "Urban",
64
+ [MacroBiome.Underground]: "Underground",
65
+ };
66
+
67
+ export const SurfaceCover = {
68
+ Grass: 0,
69
+ Dirt: 1,
70
+ Sand: 2,
71
+ Rock: 3,
72
+ Gravel: 4,
73
+ Snowpack: 5,
74
+ Ice: 6,
75
+ Mud: 7,
76
+ Ash: 8,
77
+ Cobble: 9,
78
+ Road: 10,
79
+ Water: 11,
80
+ Basalt: 12,
81
+ Crystal: 13,
82
+ Sludge: 14,
83
+ } as const;
84
+
85
+ export type SurfaceCoverId = (typeof SurfaceCover)[keyof typeof SurfaceCover];
86
+
87
+ export const SurfaceCoverLabel: Record<SurfaceCoverId, string> = {
88
+ [SurfaceCover.Grass]: "Grass",
89
+ [SurfaceCover.Dirt]: "Dirt",
90
+ [SurfaceCover.Sand]: "Sand",
91
+ [SurfaceCover.Rock]: "Rock",
92
+ [SurfaceCover.Gravel]: "Gravel",
93
+ [SurfaceCover.Snowpack]: "Snowpack",
94
+ [SurfaceCover.Ice]: "Ice",
95
+ [SurfaceCover.Mud]: "Mud",
96
+ [SurfaceCover.Ash]: "Ash",
97
+ [SurfaceCover.Cobble]: "Cobble",
98
+ [SurfaceCover.Road]: "Road",
99
+ [SurfaceCover.Water]: "Water",
100
+ [SurfaceCover.Basalt]: "Basalt",
101
+ [SurfaceCover.Crystal]: "Crystal",
102
+ [SurfaceCover.Sludge]: "Sludge",
103
+ };
104
+
105
+ export const MicroFeature = {
106
+ Tree: 0,
107
+ Bush: 1,
108
+ GrassTuft: 2,
109
+ Reed: 3,
110
+ Rock: 4,
111
+ Boulder: 5,
112
+ WaterRipple: 6,
113
+ IceSpike: 7,
114
+ Hut: 8,
115
+ Wall: 9,
116
+ Bridge: 10,
117
+ Gate: 11,
118
+ Tower: 12,
119
+ Ruin: 13,
120
+ Stalactite: 14,
121
+ Stalagmite: 15,
122
+ CrystalSpire: 16,
123
+ Mushroom: 17,
124
+ TimberSupport: 18,
125
+ Rail: 19,
126
+ Cart: 20,
127
+ Lantern: 21,
128
+ Grate: 22,
129
+ BrickTunnel: 23,
130
+ Flower: 24,
131
+ } as const;
132
+
133
+ export type MicroFeatureId = (typeof MicroFeature)[keyof typeof MicroFeature];
134
+
135
+ export const MicroFeatureLabel: Record<MicroFeatureId, string> = {
136
+ [MicroFeature.Tree]: "Tree",
137
+ [MicroFeature.Bush]: "Bush",
138
+ [MicroFeature.GrassTuft]: "Grass Tuft",
139
+ [MicroFeature.Reed]: "Reed",
140
+ [MicroFeature.Rock]: "Rock",
141
+ [MicroFeature.Boulder]: "Boulder",
142
+ [MicroFeature.WaterRipple]: "Water Ripple",
143
+ [MicroFeature.IceSpike]: "Ice Spike",
144
+ [MicroFeature.Hut]: "Hut",
145
+ [MicroFeature.Wall]: "Wall",
146
+ [MicroFeature.Bridge]: "Bridge",
147
+ [MicroFeature.Gate]: "Gate",
148
+ [MicroFeature.Tower]: "Tower",
149
+ [MicroFeature.Ruin]: "Ruin",
150
+ [MicroFeature.Stalactite]: "Stalactite",
151
+ [MicroFeature.Stalagmite]: "Stalagmite",
152
+ [MicroFeature.CrystalSpire]: "Crystal Spire",
153
+ [MicroFeature.Mushroom]: "Mushroom",
154
+ [MicroFeature.TimberSupport]: "Timber Support",
155
+ [MicroFeature.Rail]: "Rail",
156
+ [MicroFeature.Cart]: "Cart",
157
+ [MicroFeature.Lantern]: "Lantern",
158
+ [MicroFeature.Grate]: "Grate",
159
+ [MicroFeature.BrickTunnel]: "Brick Tunnel",
160
+ [MicroFeature.Flower]: "Flower",
161
+ };
162
+
163
+ export interface HexCell {
164
+ q: number;
165
+ r: number;
166
+ level: number;
167
+ flags?: number;
168
+ }
169
+
170
+ export const SlopeBand = {
171
+ Downward: 0,
172
+ Flat: 1,
173
+ Upward: 2,
174
+ } as const;
175
+
176
+ export type SlopeBandId = (typeof SlopeBand)[keyof typeof SlopeBand];
177
+
178
+ export const SlopeBandLabel: Record<SlopeBandId, string> = {
179
+ [SlopeBand.Downward]: "Downward",
180
+ [SlopeBand.Flat]: "Flat",
181
+ [SlopeBand.Upward]: "Upward",
182
+ };
183
+
184
+ export interface TerrainCell {
185
+ height: number;
186
+ heat: number;
187
+ moisture: number;
188
+ biome: TerrainBiomeId;
189
+ macroBiome?: MacroBiomeId;
190
+ surface?: SurfaceCoverId;
191
+ feature?: MicroFeatureId;
192
+ obstacle?: number;
193
+ foliage?: number;
194
+ slopeBand?: SlopeBandId;
195
+ }
196
+
197
+ export interface TerrainParams {
198
+ seed: number;
199
+ cellCount: number;
200
+ heatBias?: number;
201
+ heightScale?: number;
202
+ macroScale?: number;
203
+ macroWarpStrength?: number;
204
+ styleMixStrength?: number;
205
+ terraceSteps?: number;
206
+ terraceStrength?: number;
207
+ craterStrength?: number;
208
+ craterScale?: number;
209
+ heightMin?: number;
210
+ heightMax?: number;
211
+ slopeDownMax?: number;
212
+ slopeUpMin?: number;
213
+ }
214
+
215
+ export interface HexLevelSpec {
216
+ level: number;
217
+ areaM2: number;
218
+ sideMeters: number;
219
+ acrossFlatsMeters: number;
220
+ }
@@ -0,0 +1,225 @@
1
+ struct FieldParams {
2
+ seed: u32,
3
+ sample_count: u32,
4
+ scale: f32,
5
+ warp_scale: f32,
6
+ warp_strength: f32,
7
+ iterations: u32,
8
+ power: f32,
9
+ detail_scale: f32,
10
+ detail_iterations: u32,
11
+ detail_power: f32,
12
+ ridge_power: f32,
13
+ heat_bias: f32,
14
+ moisture_bias: f32,
15
+ macro_scale: f32,
16
+ macro_warp_strength: f32,
17
+ style_mix_strength: f32,
18
+ terrace_steps: f32,
19
+ terrace_strength: f32,
20
+ crater_strength: f32,
21
+ crater_scale: f32,
22
+ height_min: f32,
23
+ height_max: f32,
24
+ };
25
+
26
+ struct FieldSample {
27
+ height: f32,
28
+ heat: f32,
29
+ moisture: f32,
30
+ roughness: f32,
31
+ rockiness: f32,
32
+ water: f32,
33
+ ridge: f32,
34
+ base: f32,
35
+ detail: f32,
36
+ };
37
+
38
+ @group(1) @binding(0) var<storage, read> points: array<vec2<f32>>;
39
+ @group(1) @binding(1) var<storage, read_write> samples: array<FieldSample>;
40
+ @group(1) @binding(2) var<uniform> params: FieldParams;
41
+
42
+ fn hash32(x: u32) -> u32 {
43
+ var v = x;
44
+ v ^= v >> 17u;
45
+ v *= 0xed5ad4bbu;
46
+ v ^= v >> 11u;
47
+ v *= 0xac4c1b51u;
48
+ v ^= v >> 15u;
49
+ v *= 0x31848babu;
50
+ v ^= v >> 14u;
51
+ return v;
52
+ }
53
+
54
+ fn hash01(x: u32) -> f32 {
55
+ let v = hash32(x) & 0x00ffffffu;
56
+ return f32(v) / 16777216.0;
57
+ }
58
+
59
+ fn smooth_mandelbrot(cx: f32, cy: f32, iterations: u32, power: f32) -> f32 {
60
+ var zx = 0.0;
61
+ var zy = 0.0;
62
+ var i: u32 = 0u;
63
+ loop {
64
+ if (i >= iterations) {
65
+ break;
66
+ }
67
+ let r2 = zx * zx + zy * zy;
68
+ if (r2 > 4.0) {
69
+ break;
70
+ }
71
+ let r = sqrt(r2);
72
+ let theta = atan2(zy, zx);
73
+ let rp = pow(r, power);
74
+ zx = rp * cos(theta * power) + cx;
75
+ zy = rp * sin(theta * power) + cy;
76
+ i = i + 1u;
77
+ }
78
+ if (i >= iterations) {
79
+ return 1.0;
80
+ }
81
+ let r = max(sqrt(zx * zx + zy * zy), 1e-6);
82
+ let nu = log2(log(r));
83
+ let smoothVal = (f32(i) + 1.0 - nu) / f32(iterations);
84
+ return clamp(smoothVal, 0.0, 1.0);
85
+ }
86
+
87
+ fn terrace_height(h: f32, steps: f32) -> f32 {
88
+ let count = max(1.0, steps);
89
+ let step = 1.0 / count;
90
+ let clamped = clamp(h, 0.0, 1.0);
91
+ let band = floor(clamped / step);
92
+ let t = fract(clamped / step);
93
+ let blend = t * t * (3.0 - 2.0 * t);
94
+ return (band + blend) * step;
95
+ }
96
+
97
+ fn crater_field(p: vec2<f32>, scale: f32, seed: u32) -> f32 {
98
+ let sp = p * scale;
99
+ let cell = floor(sp);
100
+ let local = sp - cell;
101
+ let cx = i32(cell.x);
102
+ let cz = i32(cell.y);
103
+ let hx = u32(bitcast<u32>(cx));
104
+ let hz = u32(bitcast<u32>(cz));
105
+ let h0 = hash01((hx * 374761393u) ^ (hz * 668265263u) ^ seed ^ 0x9e3779b9u);
106
+ let h1 = hash01((hx * 2246822519u) ^ (hz * 3266489917u) ^ seed ^ 0x85ebca6bu);
107
+ let h2 = hash01((hx * 1597334677u) ^ (hz * 3812015801u) ^ seed ^ 0xc2b2ae35u);
108
+ let center = vec2<f32>(h0, h1);
109
+ let radius = 0.22 + 0.25 * h2;
110
+ let dist = distance(local, center);
111
+ return smoothstep(radius, radius * 0.35, dist);
112
+ }
113
+
114
+ fn macro_map(p: vec2<f32>, iterations: u32, power: f32) -> f32 {
115
+ let macro_iter = max(12u, iterations / 3u);
116
+ let macroA = smooth_mandelbrot(p.x * params.macro_scale, p.y * params.macro_scale, macro_iter, power);
117
+ let macroB = smooth_mandelbrot(
118
+ (p.x + 13.7) * params.macro_scale,
119
+ (p.y - 9.2) * params.macro_scale,
120
+ macro_iter,
121
+ power + 0.35
122
+ );
123
+ let warp = vec2<f32>(macroA - 0.5, macroB - 0.5) * params.macro_warp_strength;
124
+ let macroC = smooth_mandelbrot(
125
+ (p.x + warp.x) * params.macro_scale,
126
+ (p.y + warp.y) * params.macro_scale,
127
+ macro_iter,
128
+ power
129
+ );
130
+ return clamp((macroC - 0.5) * params.style_mix_strength + 0.5, 0.0, 1.0);
131
+ }
132
+
133
+ fn sample_field(p: vec2<f32>) -> FieldSample {
134
+ let seed = params.seed;
135
+ let offX = hash01(seed ^ 0x7f4a7c15u) * 4.0 - 2.0;
136
+ let offZ = hash01(seed ^ 0xa9d84d2bu) * 4.0 - 2.0;
137
+ let warpOffX = hash01(seed ^ 0x8c2f1d3bu) * 6.0 - 3.0;
138
+ let warpOffZ = hash01(seed ^ 0x5d2c79e9u) * 6.0 - 3.0;
139
+
140
+ let warpIter = max(16u, params.iterations * 6u / 10u);
141
+ let warpA = smooth_mandelbrot(
142
+ (p.x + warpOffX) * params.warp_scale,
143
+ (p.y + warpOffZ) * params.warp_scale,
144
+ warpIter,
145
+ params.power
146
+ );
147
+ let warpB = smooth_mandelbrot(
148
+ (p.x - warpOffZ) * params.warp_scale,
149
+ (p.y + warpOffX) * params.warp_scale,
150
+ warpIter,
151
+ params.power
152
+ );
153
+
154
+ let warped = vec2<f32>(
155
+ p.x + (warpA - 0.5) * params.warp_strength,
156
+ p.y + (warpB - 0.5) * params.warp_strength
157
+ );
158
+
159
+ let base = smooth_mandelbrot(
160
+ warped.x * params.scale + offX,
161
+ warped.y * params.scale + offZ,
162
+ params.iterations,
163
+ params.power
164
+ );
165
+ let mid = smooth_mandelbrot(
166
+ warped.x * params.scale * 2.15 + offX * 0.6,
167
+ warped.y * params.scale * 2.15 + offZ * 0.6,
168
+ max(18u, params.iterations * 7u / 10u),
169
+ params.power + 0.2
170
+ );
171
+ let detail = smooth_mandelbrot(
172
+ warped.x * params.scale * params.detail_scale + offX * 1.4,
173
+ warped.y * params.scale * params.detail_scale + offZ * 1.4,
174
+ params.detail_iterations,
175
+ params.detail_power
176
+ );
177
+
178
+ let ridge = 1.0 - abs(2.0 * mid - 1.0);
179
+ let baseHeight = pow(base, 0.9) * pow(mid, 1.05) * pow(detail, 1.1);
180
+
181
+ let styleMask = macro_map(p, params.iterations, params.power);
182
+ let terrace = terrace_height(baseHeight, params.terrace_steps);
183
+ let crater = crater_field(p, params.crater_scale, seed);
184
+
185
+ let styleA = clamp(pow(baseHeight, 0.8) + pow(ridge, 1.4) * 0.2, 0.0, 1.0);
186
+ let styleB = clamp(
187
+ baseHeight * (1.0 - params.terrace_strength) +
188
+ terrace * params.terrace_strength -
189
+ crater * params.crater_strength +
190
+ pow(ridge, 1.6) * 0.12,
191
+ 0.0,
192
+ 1.0
193
+ );
194
+ let mixed = mix(styleA, styleB, styleMask);
195
+ let ridgeBoost = pow(ridge, 1.35) * 0.22;
196
+ let centered = (mixed - 0.5) * 2.0;
197
+ let shaped = sign(centered) * pow(abs(centered), 0.75);
198
+ let macroOffset = (styleMask - 0.5) * 0.25;
199
+ let rawHeight = clamp(
200
+ 0.5 + shaped * 0.8 + macroOffset + ridgeBoost,
201
+ params.height_min,
202
+ params.height_max
203
+ );
204
+ let height01 = clamp(rawHeight, 0.0, 1.0);
205
+
206
+ let roughness = clamp(pow(ridge, params.ridge_power) * 0.7 + detail * 0.3, 0.0, 1.0);
207
+ let heat = clamp(0.55 * mid + 0.35 * (1.0 - height01) + params.heat_bias, 0.0, 1.0);
208
+ let moisture = clamp(
209
+ 0.55 * detail + 0.35 * (1.0 - height01) - (heat - 0.5) * 0.1 + params.moisture_bias,
210
+ 0.0,
211
+ 1.0
212
+ );
213
+ let rockiness = clamp(roughness * 0.6 + height01 * 0.4, 0.0, 1.0);
214
+ let water = clamp((0.32 - height01) * 3.0 + (moisture - 0.5) * 0.2, 0.0, 1.0);
215
+
216
+ return FieldSample(rawHeight, heat, moisture, roughness, rockiness, water, ridge, base, detail);
217
+ }
218
+
219
+ fn process_job(job_index: u32, job_type: u32, payload_words: u32) {
220
+ if (job_index >= params.sample_count) {
221
+ return;
222
+ }
223
+ let p = points[job_index];
224
+ samples[job_index] = sample_field(p);
225
+ }