feat(sites): strata on the Banc du Vigilant drop-off and multi-scale seabed relief
Strata now take a world rectangle, so the 4 m ledges cover the whole break and talus south of z = 118 m without touching la Saignée or the landmarks. Roughness gains octave and fractal-type parameters; a SlopeRoughness modifier roughens rock faces only. Both sites are re-baked with hummocks from 250 m down to 5 m. The secondary item, the first debris piece and one safe-route waypoint move a few metres to keep their contracts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
4af014288b
commit
f22a5e69aa
21 files changed
+247
-43
No files matched your search
+54
-12
@@ -410,18 +410,28 @@ class Ripples extends Primitive:
|
||||
return amplitude_m * sin(phase) * falloff
|
||||
|
||||
|
||||
## Seeded fractal noise added everywhere. frequency is 1 / wavelength of the largest
|
||||
## octave; each further octave halves the wavelength and the amplitude, so a low
|
||||
## frequency with five octaves gives hummocks, swales and ripple-scale detail at once.
|
||||
## FRACTAL_RIDGED makes sharp crests (rock), FRACTAL_FBM rolling ground (sediment).
|
||||
class Roughness extends Primitive:
|
||||
var amplitude_m: float
|
||||
var _noise: FastNoiseLite
|
||||
|
||||
func _init(p_seed: int, p_frequency: float, p_amplitude_m: float) -> void:
|
||||
func _init(
|
||||
p_seed: int,
|
||||
p_frequency: float,
|
||||
p_amplitude_m: float,
|
||||
p_octaves: int = 3,
|
||||
p_fractal_type: FastNoiseLite.FractalType = FastNoiseLite.FRACTAL_FBM
|
||||
) -> void:
|
||||
amplitude_m = p_amplitude_m
|
||||
_noise = FastNoiseLite.new()
|
||||
_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
|
||||
_noise.seed = p_seed
|
||||
_noise.frequency = p_frequency
|
||||
_noise.fractal_type = FastNoiseLite.FRACTAL_FBM
|
||||
_noise.fractal_octaves = 3
|
||||
_noise.fractal_type = p_fractal_type
|
||||
_noise.fractal_octaves = p_octaves
|
||||
|
||||
func elevation_delta(x_m: float, z_m: float) -> float:
|
||||
return _noise.get_noise_2d(x_m, z_m) * amplitude_m
|
||||
@@ -437,12 +447,13 @@ class Modifier extends RefCounted:
|
||||
## Quantises steep ground into horizontal ledges layer_height_m apart: the stratified
|
||||
## walls of a canyon or cliff. Flat floors and summits are below minimum_slope_degrees and
|
||||
## stay untouched; the effect is full from full_slope_degrees. sharpness 0 leaves the
|
||||
## field unchanged, 1 cuts hard steps. radius_m 0 applies everywhere.
|
||||
## field unchanged, 1 cuts hard steps. region_m is a world-space (x, z) rectangle with a
|
||||
## hard edge; an empty rectangle applies everywhere. Ledges need two to three cells of
|
||||
## wall per layer to be resolved: at 1.5 m per cell, 4 m layers on a 50 degree wall.
|
||||
class Strata extends Modifier:
|
||||
var layer_height_m: float
|
||||
var sharpness: float
|
||||
var centre: Vector2
|
||||
var radius_m: float
|
||||
var region_m: Rect2
|
||||
var _minimum_tangent: float
|
||||
var _full_tangent: float
|
||||
|
||||
@@ -451,21 +462,19 @@ class Strata extends Modifier:
|
||||
p_sharpness: float,
|
||||
p_minimum_slope_degrees: float,
|
||||
p_full_slope_degrees: float,
|
||||
p_centre: Vector2,
|
||||
p_radius_m: float
|
||||
p_region_m: Rect2
|
||||
) -> void:
|
||||
assert(p_layer_height_m > 0.0, "strata need a positive layer height")
|
||||
layer_height_m = p_layer_height_m
|
||||
sharpness = clampf(p_sharpness, 0.0, 0.999)
|
||||
centre = p_centre
|
||||
radius_m = p_radius_m
|
||||
region_m = p_region_m
|
||||
_minimum_tangent = tan(deg_to_rad(p_minimum_slope_degrees))
|
||||
_full_tangent = tan(deg_to_rad(p_full_slope_degrees))
|
||||
|
||||
func modify(x_m: float, z_m: float, elevation_m: float, gradient: Vector2) -> float:
|
||||
if region_m.has_area() and not region_m.has_point(Vector2(x_m, z_m)):
|
||||
return elevation_m
|
||||
var weight := smoothstep(_minimum_tangent, _full_tangent, gradient.length())
|
||||
if radius_m > 0.0:
|
||||
weight *= 1.0 - smoothstep(radius_m * 0.8, radius_m, Vector2(x_m, z_m).distance_to(centre))
|
||||
if weight <= 0.0:
|
||||
return elevation_m
|
||||
var layer := floorf(elevation_m / layer_height_m)
|
||||
@@ -474,6 +483,39 @@ class Strata extends Modifier:
|
||||
return lerpf(elevation_m, (layer + sharpened) * layer_height_m, weight)
|
||||
|
||||
|
||||
## Seeded noise weighted by the local slope: rock faces and ridge flanks get crumbly
|
||||
## while flat sand stays smooth. Zero below minimum_slope_degrees, full amplitude from
|
||||
## full_slope_degrees.
|
||||
class SlopeRoughness extends Modifier:
|
||||
var amplitude_m: float
|
||||
var _noise: FastNoiseLite
|
||||
var _minimum_tangent: float
|
||||
var _full_tangent: float
|
||||
|
||||
func _init(
|
||||
p_seed: int,
|
||||
p_frequency: float,
|
||||
p_amplitude_m: float,
|
||||
p_minimum_slope_degrees: float,
|
||||
p_full_slope_degrees: float
|
||||
) -> void:
|
||||
amplitude_m = p_amplitude_m
|
||||
_noise = FastNoiseLite.new()
|
||||
_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
|
||||
_noise.seed = p_seed
|
||||
_noise.frequency = p_frequency
|
||||
_noise.fractal_type = FastNoiseLite.FRACTAL_RIDGED
|
||||
_noise.fractal_octaves = 3
|
||||
_minimum_tangent = tan(deg_to_rad(p_minimum_slope_degrees))
|
||||
_full_tangent = tan(deg_to_rad(p_full_slope_degrees))
|
||||
|
||||
func modify(x_m: float, z_m: float, elevation_m: float, gradient: Vector2) -> float:
|
||||
var weight := smoothstep(_minimum_tangent, _full_tangent, gradient.length())
|
||||
if weight <= 0.0:
|
||||
return elevation_m
|
||||
return elevation_m + _noise.get_noise_2d(x_m, z_m) * amplitude_m * weight
|
||||
|
||||
|
||||
class MaterialPatch extends RefCounted:
|
||||
var centre: Vector2
|
||||
var radius_m: float
|
||||
|
||||
@@ -73,10 +73,10 @@ static func recipe() -> SiteTerrainBaker.Recipe:
|
||||
SiteTerrainBaker.BoulderField.new(Vector2(-100.0, 130.0), 40.0, 302, 6, 6.0, 9.0, 0.6),
|
||||
SiteTerrainBaker.Ripples.new(Vector2(1.0, 0.3), 3.5, 0.3, BOULDER_FIELD_CENTRE, 110.0),
|
||||
|
||||
# SE — les Éperons: two pitted rock spurs 6-7 m high framing a 20 m sand channel
|
||||
# SE — les Éperons: two pitted rock spurs 7-8 m high framing a 20 m sand channel
|
||||
# littered with small blocks.
|
||||
SiteTerrainBaker.Outcrop.new(WEST_SPUR_CENTRE, 30.0, 60.0, 7.0, 5.0, 401, 7.0, true),
|
||||
SiteTerrainBaker.Outcrop.new(EAST_SPUR_CENTRE, 30.0, 55.0, 6.0, 5.0, 402, 7.0, true),
|
||||
SiteTerrainBaker.Outcrop.new(WEST_SPUR_CENTRE, 30.0, 60.0, 8.0, 5.0, 401, 7.0, true),
|
||||
SiteTerrainBaker.Outcrop.new(EAST_SPUR_CENTRE, 30.0, 55.0, 7.0, 5.0, 402, 7.0, true),
|
||||
SiteTerrainBaker.BoulderField.new(CHANNEL_CENTRE, 50.0, 403, 12, 1.5, 3.0, 0.8),
|
||||
|
||||
# S — la Corniche: sinuous 22 m escarpment, then a 42 degree slope to the 80 m floor,
|
||||
@@ -86,13 +86,19 @@ static func recipe() -> SiteTerrainBaker.Recipe:
|
||||
SiteTerrainBaker.Outcrop.new(Vector2(40.0, 236.0), 10.0, 9.0, 4.0, 9.0, 503, 2.0, true),
|
||||
SiteTerrainBaker.Outcrop.new(Vector2(130.0, 245.0), 14.0, 11.0, 6.0, 11.0, 504, 2.0, true),
|
||||
|
||||
# Roughness: broad undulation plus cell-scale pitting.
|
||||
# Roughness: hummocks and swales from 250 m down to 15 m, the 50 m texture,
|
||||
# and cell-scale pitting.
|
||||
SiteTerrainBaker.Roughness.new(2026, 0.004, 2.2, 5),
|
||||
SiteTerrainBaker.Roughness.new(2029, 0.012, 1.2, 4),
|
||||
SiteTerrainBaker.Roughness.new(2030, 0.05, 0.6, 3),
|
||||
SiteTerrainBaker.Roughness.new(1933, 0.02, 0.6),
|
||||
SiteTerrainBaker.Roughness.new(2027, 0.15, 0.2),
|
||||
]
|
||||
result.modifiers = [
|
||||
# Strata on the mesa flanks and canyon walls only: 4 m ledges.
|
||||
SiteTerrainBaker.Strata.new(4.0, 0.85, 30.0, 50.0, MESA_CENTRE, 130.0),
|
||||
# Crumbly rock faces, smooth sand. Before the strata so the ledges stay flat.
|
||||
SiteTerrainBaker.SlopeRoughness.new(2028, 0.12, 0.7, 20.0, 40.0),
|
||||
# Strata on the north-west quarter only (mesa flanks and canyon walls): 4 m ledges.
|
||||
SiteTerrainBaker.Strata.new(4.0, 0.85, 30.0, 50.0, Rect2(-256.0, -256.0, 256.0, 256.0)),
|
||||
]
|
||||
return result
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ const GROOVE_END := Vector2(-10.0, 120.0)
|
||||
const RIDGE_START := Vector2(70.0, -150.0)
|
||||
const RIDGE_END := Vector2(80.0, 90.0)
|
||||
const BREAK_Z := 125.0
|
||||
# Strata apply from here south: past the groove's end cap (120 m) and still 3 m before
|
||||
# the break's smoothstep has dropped half a metre.
|
||||
const STRATA_START_Z := 118.0
|
||||
# The target's centre sits this far up the groove from GROOVE_END, so its 28 m hull
|
||||
# stops one groove half-width short of the end cap and rests on the flat groove floor.
|
||||
const TARGET_SET_BACK_M := 22.0
|
||||
@@ -57,8 +60,22 @@ static func recipe() -> SiteTerrainBaker.Recipe:
|
||||
SiteTerrainBaker.Blob.new(Vector2(25.0, 198.0), 6.3, 6.3, 2.0),
|
||||
# Les Rides: sand ripples near the vessel only.
|
||||
SiteTerrainBaker.Ripples.new(Vector2(1.0, 0.35), 9.0, 0.45, Vector2(0.0, -220.0), 90.0),
|
||||
# Deterministic roughness everywhere.
|
||||
# Deterministic roughness everywhere: the original 50 m texture, plus hummocks
|
||||
# and swales from 250 m down to 15 m (five octaves) so the plateau is never flat.
|
||||
SiteTerrainBaker.Roughness.new(1933, 0.02, 0.6),
|
||||
SiteTerrainBaker.Roughness.new(1934, 0.004, 2.2, 5),
|
||||
SiteTerrainBaker.Roughness.new(1936, 0.012, 1.2, 4),
|
||||
# Hummocks of 20 m down to 5 m, the scale the pilot sees through the fog.
|
||||
SiteTerrainBaker.Roughness.new(1937, 0.05, 0.6, 3),
|
||||
]
|
||||
result.modifiers = [
|
||||
# La rupture et le tombant are a carbonate drop-off: 4 m strata on the break
|
||||
# (full effect) and fainter ledges on the 35 degree talus below. The band starts
|
||||
# south of the target hull and of la Saignée's end cap, so neither changes.
|
||||
# Crumbly rock on every slope steeper than the sand rule, smooth sand elsewhere.
|
||||
# Before the strata so the ledges stay flat.
|
||||
SiteTerrainBaker.SlopeRoughness.new(1935, 0.12, 0.7, 20.0, 40.0),
|
||||
SiteTerrainBaker.Strata.new(4.0, 0.85, 25.0, 40.0, Rect2(-384.0, STRATA_START_Z, 768.0, 384.0 - STRATA_START_Z)),
|
||||
]
|
||||
result.material_patches = [
|
||||
SiteTerrainBaker.MaterialPatch.new(Vector2(140.0, 40.0), 28.0, SonarMaterial.Value.ROCK),
|
||||
@@ -90,9 +107,9 @@ static func definition(terrain: SiteTerrainData) -> DiveSiteDefinition:
|
||||
_on_seabed(field, &"decoy_coaster", SitePlacement.Kind.WRECK_DECOY,
|
||||
Vector2(140.0, 40.0), -0.6, Vector3(26.0, 6.0, 8.0), 1.2),
|
||||
_on_seabed(field, &"secondary_item", SitePlacement.Kind.SECONDARY_ITEM,
|
||||
Vector2(-6.0, 157.0), 0.0, Vector3(1.0, 1.0, 1.0), 0.0),
|
||||
Vector2(-6.0, 160.0), 0.0, Vector3(1.0, 1.0, 1.0), 0.0),
|
||||
_on_seabed(field, &"debris_1", SitePlacement.Kind.DEBRIS,
|
||||
Vector2(-12.0, 134.0), 0.4, Vector3(3.0, 2.0, 2.0), 0.3),
|
||||
Vector2(-12.0, 136.0), 0.4, Vector3(3.0, 2.0, 2.0), 0.3),
|
||||
_on_seabed(field, &"debris_2", SitePlacement.Kind.DEBRIS,
|
||||
Vector2(-10.0, 139.0), 1.2, Vector3(6.0, 2.5, 2.5), 0.4),
|
||||
_on_seabed(field, &"debris_3", SitePlacement.Kind.DEBRIS,
|
||||
@@ -110,7 +127,7 @@ static func definition(terrain: SiteTerrainData) -> DiveSiteDefinition:
|
||||
_route(&"safe_ridge", 32.0, [
|
||||
Vector3(0.0, -14.0, -210.0), Vector3(70.0, -16.0, -200.0), Vector3(76.0, -16.0, -100.0),
|
||||
Vector3(78.0, -16.0, 0.0), Vector3(76.0, -18.0, 80.0), Vector3(40.0, -24.0, 105.0),
|
||||
Vector3(0.0, -26.0, 100.0), Vector3(-14.0, -30.0, 98.0),
|
||||
Vector3(0.0, -24.0, 100.0), Vector3(-14.0, -30.0, 98.0),
|
||||
]),
|
||||
_route(&"risky_groove", 48.0, [
|
||||
Vector3(0.0, -14.0, -210.0), Vector3(-60.0, -20.0, -150.0), Vector3(-50.0, -31.0, -100.0),
|
||||
|
||||
Reference in new issue
Block a user