@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,175 @@
|
|
|
1
|
+
const MATERIAL_DEFAULT: u32 = 0u;
|
|
2
|
+
const MATERIAL_GRASS: u32 = 1u;
|
|
3
|
+
const MATERIAL_WATER: u32 = 2u;
|
|
4
|
+
const MATERIAL_ROCK: u32 = 3u;
|
|
5
|
+
const MATERIAL_SAND: u32 = 4u;
|
|
6
|
+
const MATERIAL_MUD: u32 = 5u;
|
|
7
|
+
const MATERIAL_SNOW: u32 = 6u;
|
|
8
|
+
const MATERIAL_FOLIAGE: u32 = 7u;
|
|
9
|
+
|
|
10
|
+
struct MaterialResponse {
|
|
11
|
+
albedo: vec3<f32>,
|
|
12
|
+
normal: vec3<f32>,
|
|
13
|
+
specular: f32,
|
|
14
|
+
emission: vec3<f32>,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
fn make_material(albedo: vec3<f32>, normal: vec3<f32>) -> MaterialResponse {
|
|
18
|
+
return MaterialResponse(albedo, normal, 0.08, vec3<f32>(0.0));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fn apply_grass(base: vec3<f32>, normal: vec3<f32>, world_pos: vec3<f32>, wind: vec4<f32>) -> MaterialResponse {
|
|
22
|
+
let wind_dir = normalize(vec3<f32>(wind.x, 0.0, wind.z));
|
|
23
|
+
let time = wind.w;
|
|
24
|
+
let wave = sin(dot(world_pos.xz, wind_dir.xz) * 2.2 + time * 1.3);
|
|
25
|
+
let detail = fbm2(world_pos.xz * 1.6 + vec2<f32>(time * 0.08, time * 0.04));
|
|
26
|
+
let bend = (wave * 0.14 + detail * 0.12);
|
|
27
|
+
let perturbed = normalize(vec3<f32>(
|
|
28
|
+
normal.x + bend * wind_dir.x,
|
|
29
|
+
normal.y + bend * 0.4,
|
|
30
|
+
normal.z + bend * wind_dir.z
|
|
31
|
+
));
|
|
32
|
+
let tint = base * (0.92 + wave * 0.06);
|
|
33
|
+
var out = make_material(tint, perturbed);
|
|
34
|
+
out.specular = 0.12;
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fn apply_water(
|
|
39
|
+
base: vec3<f32>,
|
|
40
|
+
normal: vec3<f32>,
|
|
41
|
+
world_pos: vec3<f32>,
|
|
42
|
+
wind: vec4<f32>,
|
|
43
|
+
view_dir: vec3<f32>,
|
|
44
|
+
view_dist: f32
|
|
45
|
+
) -> MaterialResponse {
|
|
46
|
+
let time = wind.w;
|
|
47
|
+
let wind2 = normalize(vec2<f32>(wind.x, wind.z) + vec2<f32>(1e-3, 0.0));
|
|
48
|
+
let dir0 = wind2;
|
|
49
|
+
let dir1 = normalize(vec2<f32>(-wind2.y, wind2.x));
|
|
50
|
+
let dir2 = normalize(mix(wind2, vec2<f32>(0.7, 0.7), 0.35));
|
|
51
|
+
|
|
52
|
+
let detail = clamp01(1.0 - (view_dist - 6.0) / 25.0);
|
|
53
|
+
let detail2 = detail * detail;
|
|
54
|
+
|
|
55
|
+
let k0 = 1.6;
|
|
56
|
+
let k1 = 3.2;
|
|
57
|
+
let k2 = 6.4;
|
|
58
|
+
let a0 = 0.12;
|
|
59
|
+
let a1 = 0.05 * detail;
|
|
60
|
+
let a2 = 0.02 * detail2;
|
|
61
|
+
|
|
62
|
+
let p0 = dot(world_pos.xz, dir0) * k0 + time * 1.1;
|
|
63
|
+
let p1 = dot(world_pos.xz, dir1) * k1 + time * 1.7;
|
|
64
|
+
let p2 = dot(world_pos.xz, dir2) * k2 + time * 2.4;
|
|
65
|
+
|
|
66
|
+
let h0 = a0 * sin(p0);
|
|
67
|
+
let h1 = a1 * sin(p1);
|
|
68
|
+
let h2 = a2 * sin(p2);
|
|
69
|
+
|
|
70
|
+
var dHd = vec2<f32>(
|
|
71
|
+
a0 * k0 * cos(p0) * dir0.x + a1 * k1 * cos(p1) * dir1.x + a2 * k2 * cos(p2) * dir2.x,
|
|
72
|
+
a0 * k0 * cos(p0) * dir0.y + a1 * k1 * cos(p1) * dir1.y + a2 * k2 * cos(p2) * dir2.y
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
let ripple_phase = dot(world_pos.xz, vec2<f32>(12.7, 9.3)) + time * 3.5;
|
|
76
|
+
let ripple_amp = 0.006 * detail2;
|
|
77
|
+
let ripple = sin(ripple_phase) * ripple_amp;
|
|
78
|
+
dHd = dHd + vec2<f32>(12.7, 9.3) * cos(ripple_phase) * ripple_amp;
|
|
79
|
+
|
|
80
|
+
let wave_height = h0 + h1 + h2 + ripple;
|
|
81
|
+
let wave_normal = normalize(vec3<f32>(-dHd.x, 1.0, -dHd.y));
|
|
82
|
+
let water_normal = normalize(mix(normal, wave_normal, 0.85));
|
|
83
|
+
|
|
84
|
+
let ndv = clamp(dot(water_normal, view_dir), 0.0, 1.0);
|
|
85
|
+
let fresnel = 0.02 + (1.0 - 0.02) * pow(1.0 - ndv, 5.0);
|
|
86
|
+
|
|
87
|
+
let deep = vec3<f32>(0.02, 0.08, 0.14);
|
|
88
|
+
let tint = mix(base, deep, 0.55 + wave_height * 0.2);
|
|
89
|
+
|
|
90
|
+
let sky_low = vec3<f32>(0.05, 0.1, 0.16);
|
|
91
|
+
let sky_high = vec3<f32>(0.2, 0.35, 0.55);
|
|
92
|
+
let sky = mix(sky_low, sky_high, clamp01(view_dir.y * 0.5 + 0.5));
|
|
93
|
+
|
|
94
|
+
let steepness = length(dHd);
|
|
95
|
+
let foam_noise = fbm2(world_pos.xz * 0.8 + vec2<f32>(time * 0.05, time * 0.04));
|
|
96
|
+
let foam = smoothstep(0.35, 0.75, steepness) * (0.6 + 0.4 * foam_noise) * detail;
|
|
97
|
+
|
|
98
|
+
var out = make_material(tint * 0.6, water_normal);
|
|
99
|
+
out.albedo = mix(out.albedo, vec3<f32>(0.92, 0.94, 0.98), foam);
|
|
100
|
+
out.specular = mix(0.35, 0.95, fresnel);
|
|
101
|
+
out.emission = sky * fresnel * (0.35 + 0.65 * detail);
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn apply_rock(base: vec3<f32>, normal: vec3<f32>, world_pos: vec3<f32>) -> MaterialResponse {
|
|
106
|
+
let grain = fbm2(world_pos.xz * 2.4);
|
|
107
|
+
let tint = base * (0.85 + grain * 0.25);
|
|
108
|
+
var out = make_material(tint, normal);
|
|
109
|
+
out.specular = 0.18;
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn apply_sand(base: vec3<f32>, normal: vec3<f32>, world_pos: vec3<f32>) -> MaterialResponse {
|
|
114
|
+
let grain = noise2(world_pos.xz * 3.5);
|
|
115
|
+
let tint = base * (0.95 + grain * 0.12);
|
|
116
|
+
var out = make_material(tint, normal);
|
|
117
|
+
out.specular = 0.12;
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn apply_mud(base: vec3<f32>, normal: vec3<f32>, world_pos: vec3<f32>) -> MaterialResponse {
|
|
122
|
+
let ooze = fbm2(world_pos.xz * 2.0);
|
|
123
|
+
let tint = base * (0.78 + ooze * 0.1);
|
|
124
|
+
var out = make_material(tint, normal);
|
|
125
|
+
out.specular = 0.22;
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn apply_surface_material(
|
|
130
|
+
material_id: u32,
|
|
131
|
+
base_color: vec3<f32>,
|
|
132
|
+
normal: vec3<f32>,
|
|
133
|
+
world_pos: vec3<f32>,
|
|
134
|
+
wind: vec4<f32>,
|
|
135
|
+
weather: vec4<f32>,
|
|
136
|
+
view_dir: vec3<f32>,
|
|
137
|
+
view_dist: f32
|
|
138
|
+
) -> MaterialResponse {
|
|
139
|
+
let season = clamp01(weather.x);
|
|
140
|
+
let wetness = clamp01(weather.y + weather.w * 0.5);
|
|
141
|
+
var snow = clamp01(weather.z + season * 0.2);
|
|
142
|
+
|
|
143
|
+
var result = make_material(base_color, normal);
|
|
144
|
+
if (material_id == MATERIAL_GRASS || material_id == MATERIAL_FOLIAGE) {
|
|
145
|
+
result = apply_grass(base_color, normal, world_pos, wind);
|
|
146
|
+
} else if (material_id == MATERIAL_WATER) {
|
|
147
|
+
result = apply_water(base_color, normal, world_pos, wind, view_dir, view_dist);
|
|
148
|
+
} else if (material_id == MATERIAL_ROCK) {
|
|
149
|
+
result = apply_rock(base_color, normal, world_pos);
|
|
150
|
+
} else if (material_id == MATERIAL_SAND) {
|
|
151
|
+
result = apply_sand(base_color, normal, world_pos);
|
|
152
|
+
} else if (material_id == MATERIAL_MUD) {
|
|
153
|
+
result = apply_mud(base_color, normal, world_pos);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (material_id == MATERIAL_SNOW) {
|
|
157
|
+
snow = max(snow, 0.7);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (material_id != MATERIAL_WATER) {
|
|
161
|
+
let snow_mask = snow * smoothstep(0.35, 0.92, result.normal.y);
|
|
162
|
+
result.albedo = mix(result.albedo, vec3<f32>(0.88, 0.9, 0.95), snow_mask);
|
|
163
|
+
result.specular = max(result.specular, snow_mask * 0.22);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (material_id != MATERIAL_WATER) {
|
|
167
|
+
let flatness = smoothstep(0.55, 0.96, result.normal.y);
|
|
168
|
+
let puddle_noise = fbm2(world_pos.xz * 0.7 + vec2<f32>(wind.w * 0.03, wind.w * 0.02));
|
|
169
|
+
let puddle = wetness * flatness * smoothstep(0.45, 0.8, puddle_noise);
|
|
170
|
+
result.albedo = mix(result.albedo, result.albedo * 0.78, wetness * 0.35);
|
|
171
|
+
result.specular = max(result.specular, puddle * 0.65);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
}
|
package/src/terrain.wgsl
ADDED
|
@@ -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
|
+
}
|