const TERRAIN_CACHE_SIZE: i32 = 512;
fn terrain_scramble(value: u32) -> u32 {
var x = value ^ (value >> 17u);
x *= 0xED5AD4BBu;
x ^= x >> 11u;
x *= 0xAC4C1B51u;
x ^= x >> 15u;
x *= 0x31848BABu;
x ^= x >> 14u;
return x;
}
fn terrain_hash_cell(cell: vec2<i32>, salt: u32) -> u32 {
var p = vec2<u32>(bitcast<u32>(cell.x), bitcast<u32>(cell.y))
^ vec2<u32>(salt, salt * 0x9E3779B9u);
p = p * vec2<u32>(1664525u, 1664525u) + vec2<u32>(1013904223u, 1013904223u);
p.x += p.y * 1664525u;
p.y += p.x * 1664525u;
p ^= p >> vec2<u32>(16u, 16u);
p.x += p.y * 1664525u;
p.y += p.x * 1664525u;
return p.x ^ terrain_scramble(p.y);
}
fn terrain_gradient(cell: vec2<i32>, salt: u32) -> vec2<f32> {
let angle = f32(terrain_hash_cell(cell, salt)) * 2.3283064e-10 * 6.2831853;
return vec2<f32>(cos(angle), sin(angle));
}
fn terrain_perlin(p: vec2<f32>, salt: u32) -> f32 {
let base = floor(p);
let fraction = p - base;
let cell = vec2<i32>(base);
let fade = fraction * fraction * fraction * (fraction * (fraction * 6.0 - 15.0) + 10.0);
let d00 = dot(terrain_gradient(cell, salt), fraction);
let d10 = dot(
terrain_gradient(cell + vec2<i32>(1, 0), salt),
fraction - vec2<f32>(1.0, 0.0),
);
let d01 = dot(
terrain_gradient(cell + vec2<i32>(0, 1), salt),
fraction - vec2<f32>(0.0, 1.0),
);
let d11 = dot(
terrain_gradient(cell + vec2<i32>(1, 1), salt),
fraction - vec2<f32>(1.0, 1.0),
);
let x0 = mix(d00, d10, fade.x);
let x1 = mix(d01, d11, fade.x);
return mix(x0, x1, fade.y) * 1.4142;
}
fn terrain_fbm(p: vec2<f32>, octaves: u32, salt: u32) -> f32 {
var amplitude = 0.5;
var frequency = 1.0;
var total = 0.0;
var normalization = 0.0;
for (var octave = 0u; octave < octaves; octave++) {
total += terrain_perlin(p * frequency, salt + octave * 101u) * amplitude;
normalization += amplitude;
amplitude *= 0.5;
frequency *= 2.0;
}
return total / normalization;
}
fn terrain_perlin_d(p: vec2<f32>, salt: u32) -> vec3<f32> {
let base = floor(p);
let fraction = p - base;
let cell = vec2<i32>(base);
let fade = fraction * fraction * fraction * (fraction * (fraction * 6.0 - 15.0) + 10.0);
let fade_derivative =
30.0 * fraction * fraction * (fraction * (fraction - 2.0) + vec2<f32>(1.0, 1.0));
let g00 = terrain_gradient(cell, salt);
let g10 = terrain_gradient(cell + vec2<i32>(1, 0), salt);
let g01 = terrain_gradient(cell + vec2<i32>(0, 1), salt);
let g11 = terrain_gradient(cell + vec2<i32>(1, 1), salt);
let d00 = dot(g00, fraction);
let d10 = dot(g10, fraction - vec2<f32>(1.0, 0.0));
let d01 = dot(g01, fraction - vec2<f32>(0.0, 1.0));
let d11 = dot(g11, fraction - vec2<f32>(1.0, 1.0));
let k1 = d10 - d00;
let k2 = d01 - d00;
let k3 = d00 - d10 - d01 + d11;
let value = d00 + k1 * fade.x + k2 * fade.y + k3 * fade.x * fade.y;
let gradient_mix = mix(mix(g00, g10, fade.x), mix(g01, g11, fade.x), fade.y);
let derivative = gradient_mix
+ vec2<f32>(
fade_derivative.x * (k1 + k3 * fade.y),
fade_derivative.y * (k2 + k3 * fade.x),
);
return vec3<f32>(value, derivative.x, derivative.y) * 1.4142;
}
fn terrain_fbm_eroded(p: vec2<f32>, octaves: u32, salt: u32, sharpness: f32) -> f32 {
var amplitude = 0.5;
var frequency = 1.0;
var total = 0.0;
var gradient_sum = vec2<f32>(0.0, 0.0);
for (var octave = 0u; octave < octaves; octave++) {
let noise = terrain_perlin_d(p * frequency, salt + octave * 101u);
gradient_sum += noise.yz;
total += amplitude * noise.x
/ (1.0 + sharpness * dot(gradient_sum, gradient_sum));
amplitude *= 0.5;
frequency *= 2.0;
}
return total * (1.0 + sharpness);
}
fn terrain_spline(points: array<vec4<f32>, 4>, value: f32) -> f32 {
var table = points;
var previous = table[0].xy;
var output = previous.y;
for (var index = 0u; index < 8u; index++) {
let point = table[index / 2u];
var current = point.xy;
if (index & 1u) == 1u {
current = point.zw;
}
if value <= current.x {
let span = max(current.x - previous.x, 1e-5);
let t = clamp((value - previous.x) / span, 0.0, 1.0);
return mix(previous.y, current.y, t);
}
previous = current;
output = current.y;
}
return output;
}
fn terrain_cache_load(
cache: texture_2d_array<f32>,
layer: u32,
texel: vec2<i32>,
) -> f32 {
let wrapped = vec2<i32>(
((texel.x % TERRAIN_CACHE_SIZE) + TERRAIN_CACHE_SIZE) % TERRAIN_CACHE_SIZE,
((texel.y % TERRAIN_CACHE_SIZE) + TERRAIN_CACHE_SIZE) % TERRAIN_CACHE_SIZE,
);
return textureLoad(cache, wrapped, i32(layer), 0).r;
}
fn terrain_cache_sample(
cache: texture_2d_array<f32>,
layer: u32,
texel_size: f32,
world_xz: vec2<f32>,
) -> f32 {
let texel_coords = world_xz / texel_size - vec2<f32>(0.5, 0.5);
let base = floor(texel_coords);
let fraction = texel_coords - base;
let cell = vec2<i32>(base);
let h00 = terrain_cache_load(cache, layer, cell);
let h10 = terrain_cache_load(cache, layer, cell + vec2<i32>(1, 0));
let h01 = terrain_cache_load(cache, layer, cell + vec2<i32>(0, 1));
let h11 = terrain_cache_load(cache, layer, cell + vec2<i32>(1, 1));
let x0 = mix(h00, h10, fraction.x);
let x1 = mix(h01, h11, fraction.x);
return mix(x0, x1, fraction.y);
}