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>
No files matched your search
@@ -9,7 +9,7 @@
|
||||
script = ExtResource("1_n3xpu")
|
||||
placement_id = &"prospection_vessel"
|
||||
kind = 0
|
||||
position_m = Vector3(-14.154642, -37.198338, 98.39586)
|
||||
position_m = Vector3(-14.154642, -36.88306, 98.39586)
|
||||
yaw_radians = -1.380808047881126
|
||||
footprint_m = Vector3(28, 8, 9)
|
||||
|
||||
@@ -17,7 +17,7 @@ footprint_m = Vector3(28, 8, 9)
|
||||
script = ExtResource("1_n3xpu")
|
||||
placement_id = &"decoy_yacht"
|
||||
kind = 1
|
||||
position_m = Vector3(-30, -25.199738, -150)
|
||||
position_m = Vector3(-30, -24.996498, -150)
|
||||
yaw_radians = 1.1
|
||||
footprint_m = Vector3(12, 3, 4)
|
||||
|
||||
@@ -25,7 +25,7 @@ footprint_m = Vector3(12, 3, 4)
|
||||
script = ExtResource("1_n3xpu")
|
||||
placement_id = &"decoy_coaster"
|
||||
kind = 1
|
||||
position_m = Vector3(140, -35.265244, 40)
|
||||
position_m = Vector3(140, -35.34944, 40)
|
||||
yaw_radians = -0.6
|
||||
footprint_m = Vector3(26, 6, 8)
|
||||
|
||||
@@ -33,14 +33,14 @@ footprint_m = Vector3(26, 6, 8)
|
||||
script = ExtResource("1_n3xpu")
|
||||
placement_id = &"secondary_item"
|
||||
kind = 2
|
||||
position_m = Vector3(-6, -61.61916, 157)
|
||||
position_m = Vector3(-6, -63.26594, 160)
|
||||
footprint_m = Vector3(1, 1, 1)
|
||||
|
||||
[sub_resource type="Resource" id="Resource_aervf"]
|
||||
script = ExtResource("1_n3xpu")
|
||||
placement_id = &"debris_1"
|
||||
kind = 3
|
||||
position_m = Vector3(-12, -45.756382, 134)
|
||||
position_m = Vector3(-12, -45.80824, 136)
|
||||
yaw_radians = 0.4
|
||||
footprint_m = Vector3(3, 2, 2)
|
||||
|
||||
@@ -48,7 +48,7 @@ footprint_m = Vector3(3, 2, 2)
|
||||
script = ExtResource("1_n3xpu")
|
||||
placement_id = &"debris_2"
|
||||
kind = 3
|
||||
position_m = Vector3(-10, -48.518223, 139)
|
||||
position_m = Vector3(-10, -47.295185, 139)
|
||||
yaw_radians = 1.2
|
||||
footprint_m = Vector3(6, 2.5, 2.5)
|
||||
|
||||
@@ -56,14 +56,14 @@ footprint_m = Vector3(6, 2.5, 2.5)
|
||||
script = ExtResource("1_n3xpu")
|
||||
placement_id = &"debris_3"
|
||||
kind = 3
|
||||
position_m = Vector3(-9, -53.75445, 146)
|
||||
position_m = Vector3(-9, -52.854576, 146)
|
||||
footprint_m = Vector3(2, 1.5, 2)
|
||||
|
||||
[sub_resource type="Resource" id="Resource_hd5cl"]
|
||||
script = ExtResource("1_n3xpu")
|
||||
placement_id = &"debris_4"
|
||||
kind = 3
|
||||
position_m = Vector3(-7, -58.142864, 152)
|
||||
position_m = Vector3(-7, -58.174175, 152)
|
||||
yaw_radians = 2.0
|
||||
footprint_m = Vector3(4, 1.5, 1.5)
|
||||
|
||||
@@ -91,7 +91,7 @@ position_m = Vector3(0, -25, -230)
|
||||
[sub_resource type="Resource" id="Resource_u8dqp"]
|
||||
script = ExtResource("1_n3xpu")
|
||||
placement_id = &"saignee_entrance"
|
||||
position_m = Vector3(-56.978443, -35.474903, -124.287895)
|
||||
position_m = Vector3(-56.978443, -35.54769, -124.287895)
|
||||
|
||||
[sub_resource type="Resource" id="Resource_3uttw"]
|
||||
script = ExtResource("1_n3xpu")
|
||||
@@ -101,7 +101,7 @@ position_m = Vector3(40, -40, 125)
|
||||
[sub_resource type="Resource" id="Resource_xvesq"]
|
||||
script = ExtResource("2_36hd6")
|
||||
route_id = &"safe_ridge"
|
||||
waypoints_m = PackedVector3Array(0, -14, -210, 70, -16, -200, 76, -16, -100, 78, -16, 0, 76, -18, 80, 40, -24, 105, 0, -26, 100, -14, -30, 98)
|
||||
waypoints_m = PackedVector3Array(0, -14, -210, 70, -16, -200, 76, -16, -100, 78, -16, 0, 76, -18, 80, 40, -24, 105, 0, -24, 100, -14, -30, 98)
|
||||
maximum_depth_m = 32.0
|
||||
|
||||
[sub_resource type="Resource" id="Resource_lcu7s"]
|
||||
|
||||
@@ -74,8 +74,11 @@ pente, des matériaux imposés par primitive et des `MaterialPatch`.
|
||||
(butte ou fosse à sommet plat, contour bruité, matériau roche imposé),
|
||||
`BoulderField` (blocs semi-ellipsoïdaux tirés par graine, combinés par
|
||||
maximum, roche imposée).
|
||||
- Modificateur : `Strata` quantifie les pentes raides en gradins horizontaux ;
|
||||
les fonds et sommets plats ne bougent pas.
|
||||
- Modificateurs : `Strata` quantifie les pentes raides en gradins horizontaux
|
||||
dans un rectangle monde, les fonds et sommets plats ne bougent pas ;
|
||||
`SlopeRoughness` ajoute un bruit à crêtes pondéré par la pente (roche
|
||||
éraillée, sable lisse). `Roughness` accepte un nombre d'octaves et un type
|
||||
fractal pour donner du relief de fond à plusieurs échelles.
|
||||
|
||||
Tout aléa passe par une graine explicite (`FastNoiseLite`,
|
||||
`RandomNumberGenerator`) ; `tests/unit/run_site_baker_tests.gd` vérifie que
|
||||
|
||||
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 97 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d3qajwshxpplq"
|
||||
path="res://.godot/imported/zone1_vigilant_plateau.png-6e6da03a831034d096e040e9808fd1b8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://docs/briefs/captures/zone1_vigilant_plateau.png"
|
||||
dest_files=["res://.godot/imported/zone1_vigilant_plateau.png-6e6da03a831034d096e040e9808fd1b8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 74 KiB |
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cm2nm65ktggp"
|
||||
path="res://.godot/imported/zone1_vigilant_tombant.png-71b9b2e227c187a355cfe8ea0a7a15f7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://docs/briefs/captures/zone1_vigilant_tombant.png"
|
||||
dest_files=["res://.godot/imported/zone1_vigilant_tombant.png-71b9b2e227c187a355cfe8ea0a7a15f7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
**Statut : validé par Olivier le 2026-10-06** (plan de masse, objet secondaire à 62 m, pipeline par script de cuisson, nom « Banc du Vigilant » conservé). Sert de spécification de référence pour l'implémentation.
|
||||
|
||||
**Recuisson du 2026-10-06 (demande d'Olivier)** : la rupture et le tombant
|
||||
sont stratifiés en gradins de 4 m (`Strata`, bande z ≥ 118 m, hors Saignée et
|
||||
amers) ; le plateau reçoit un relief de fond à trois échelles (250–15 m ± 2,2 m,
|
||||
80–10 m ± 1,2 m, 20–5 m ± 0,6 m) et la roche un bruit de pente (`SlopeRoughness`).
|
||||
L'objet secondaire est reculé de 3 m (63 m), le premier débris de 2 m, et le
|
||||
point de la route sûre au-dessus de la lèvre de la Saignée remonté à 24 m pour
|
||||
garder 3 m de dégagement. Voir `docs/briefs/terrain-varied-landforms.md` §4.3 bis.
|
||||
|
||||
Rédigée le 2026-10-06 à partir de `docs/GAME_DESIGN_PLAN.md`,
|
||||
`docs/VERTICAL_SLICE_TECHNICAL_PLAN.md`, `docs/POC_B_TECHNICAL_BASELINE.md`,
|
||||
des ADR 0004, 0006, 0008, 0010 et du code sous `game/world/`. Rien n'a été
|
||||
|
||||
@@ -91,12 +91,23 @@ ou `smoothstep`) et **mono-segment**. Il manque :
|
||||
|
||||
| Modificateur | Paramètres | Effet |
|
||||
|---|---|---|
|
||||
| `Strata` | `layer_height_m`, `sharpness` (0–1), `minimum_slope_degrees`, `full_slope_degrees`, `centre`, `radius_m` (0 = partout) | quantifie l'élévation en gradins de `layer_height_m` là où la pente dépasse `minimum_slope_degrees` (plein effet à `full_slope_degrees`) ; `sharpness` 0 = aucun effet, 1 = marches franches ; le fond plat et le plateau ne bougent pas |
|
||||
| `Strata` | `layer_height_m`, `sharpness` (0–1), `minimum_slope_degrees`, `full_slope_degrees`, `region_m: Rect2` (vide = partout) | quantifie l'élévation en gradins de `layer_height_m` là où la pente dépasse `minimum_slope_degrees` (plein effet à `full_slope_degrees`) ; `sharpness` 0 = aucun effet, 1 = marches franches ; le fond plat et le plateau ne bougent pas |
|
||||
|
||||
Formule : `f = frac(e / h)`, `f' = clamp((f - 0.5) / (1 - s) + 0.5, 0, 1)`,
|
||||
`e' = lerp(e, floor(e / h) * h + h * f', w)` avec
|
||||
`w = smoothstep(tan(min), tan(full), |gradient|) * masque`.
|
||||
|
||||
### 4.3 bis Relief de fond (ajout du 2026-10-06, demande d'Olivier)
|
||||
|
||||
Les plateaux cuits avec la seule `Roughness` à 50 m (± 0,6 m) restaient
|
||||
plats. `Roughness` prend maintenant un nombre d'octaves et un type fractal
|
||||
(`FRACTAL_FBM` roulant, `FRACTAL_RIDGED` à crêtes) : une fréquence de 0,004
|
||||
sur cinq octaves donne des mamelons et des cuvettes de 250 m à 15 m en une
|
||||
primitive. Le modificateur `SlopeRoughness` (`seed`, `frequency`,
|
||||
`amplitude_m`, `minimum_slope_degrees`, `full_slope_degrees`) ajoute un bruit
|
||||
à crêtes pondéré par la pente : la roche s'éraille, le sable reste lisse. Les
|
||||
deux sites l'utilisent (ondulation ± 2,2 m, roche ± 0,7 m).
|
||||
|
||||
### 4.4 Ce qui ne change pas
|
||||
|
||||
Le contrat de données (`SiteTerrainData`, ADR 0012 §2), l'acteur de monde, la
|
||||
@@ -170,7 +181,10 @@ Plan de masse (nord en haut) :
|
||||
(champ de hauteur vu de dessus, roche teintée) et cinq vues en jeu, graybox,
|
||||
`terrain_showcase_canyon.png` (gradins et fond ridé), `_crevasse.png`,
|
||||
`_boulders.png`, `_spurs.png`, `_escarpment.png` (face de la falaise vue du
|
||||
pied). Le rendu (brouillard, rides en albédo) n'est pas l'objet de ce brief.
|
||||
pied) ; pour le Banc du Vigilant recuit, `zone1_vigilant_tombant.png`
|
||||
(gradins du tombant) et `zone1_vigilant_plateau.png` (plateau ondulé). Le
|
||||
rendu (brouillard, rides en albédo, absence d'ombrage de détail) n'est pas
|
||||
l'objet de ce brief : à ce stade, le relief se lit surtout au sonar.
|
||||
|
||||
## 8. Décisions laissées à Olivier
|
||||
|
||||
@@ -179,9 +193,11 @@ Plan de masse (nord en haut) :
|
||||
couche : à 1 m par cellule, des couches de 4 m sur une paroi à 55° ; une
|
||||
paroi à 76° (première cuisson) ne montrait aucun gradin. Les sites de mission restent à 1,5 m sauf décision contraire ; une
|
||||
crevasse de 5 m à 1,5 m par cellule n'a que trois cellules de large.
|
||||
2. **Strates partout ?** Le modificateur est appliqué au canyon seulement.
|
||||
L'appliquer à la falaise sud du Banc du Vigilant est un changement de
|
||||
recette, donc de `.res`, donc une re-cuisson à valider.
|
||||
2. **Strates sur le Banc du Vigilant** : décidé par Olivier le 2026-10-06,
|
||||
appliquées à la rupture et au tombant (bande z ≥ 118 m, couches de 4 m,
|
||||
plein effet dès 40° ; la Saignée, l'Échine et les amers sont hors bande).
|
||||
Le `.res` du site a été recuit ; l'objet secondaire, les débris et les
|
||||
routes restent dans leurs contrats.
|
||||
3. **Sites de mission suivants** (U-Boot sous falaise, océanographique en
|
||||
canyon) : les primitives sont prêtes ; le plan de masse de chaque site
|
||||
reste à proposer et valider séparément.
|
||||
@@ -8,7 +8,7 @@ const SiteTerrainData = preload("res://game/domain/sites/site_terrain_data.gd")
|
||||
|
||||
const ZONE1_TERRAIN_PATH := "res://content/sites/zone1_vigilant_terrain.res"
|
||||
|
||||
const TEST_COUNT := 44
|
||||
const TEST_COUNT := 48
|
||||
|
||||
var _failures: Array[String] = []
|
||||
|
||||
@@ -23,6 +23,7 @@ func _init() -> void:
|
||||
_test_outcrop()
|
||||
_test_boulder_field()
|
||||
_test_strata()
|
||||
_test_slope_roughness()
|
||||
|
||||
if _failures.is_empty():
|
||||
print("Site baker unit tests: %d passed, 0 failed." % TEST_COUNT)
|
||||
@@ -187,7 +188,7 @@ func _test_strata() -> void:
|
||||
recipe.origin_z_m = -10.0
|
||||
recipe.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.0, 1.0, 0.0, 0.0, 0.0))
|
||||
var plain := SiteTerrainBaker.bake(recipe)
|
||||
recipe.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.9, 20.0, 35.0, Vector2.ZERO, 0.0))
|
||||
recipe.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.9, 20.0, 35.0, Rect2()))
|
||||
var terraced := SiteTerrainBaker.bake(recipe)
|
||||
var flat_cells := 0
|
||||
var riser_cells := 0
|
||||
@@ -202,17 +203,48 @@ func _test_strata() -> void:
|
||||
|
||||
var flat := _small_recipe()
|
||||
flat.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.3, 0.0, 0.0, 0.0, 0.0))
|
||||
flat.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.9, 20.0, 35.0, Vector2.ZERO, 0.0))
|
||||
flat.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.9, 20.0, 35.0, Rect2()))
|
||||
var flat_data := SiteTerrainBaker.bake(flat)
|
||||
_expect(is_equal_approx(flat_data.elevations_m[flat_data.index_of(10, 10)], -30.3), "strata leave flat ground alone")
|
||||
|
||||
var identity := _small_recipe()
|
||||
identity.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.0, 1.0, 0.0, 0.0, 0.0))
|
||||
var before := SiteTerrainBaker.bake(identity)
|
||||
identity.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.0, 20.0, 35.0, Vector2.ZERO, 0.0))
|
||||
identity.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.0, 20.0, 35.0, Rect2()))
|
||||
var after := SiteTerrainBaker.bake(identity)
|
||||
_expect(before.elevations_m == after.elevations_m, "sharpness 0 is the identity")
|
||||
|
||||
# Depth 30.7 keeps the plane off the layer boundaries, where quantisation is the identity.
|
||||
var banded := _small_recipe()
|
||||
banded.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.7, 1.0, 0.0, 0.0, 0.0))
|
||||
var untouched := SiteTerrainBaker.bake(banded)
|
||||
banded.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.9, 20.0, 35.0, Rect2(-20.0, 0.0, 40.0, 20.0)))
|
||||
var half := SiteTerrainBaker.bake(banded)
|
||||
var north_same := half.elevations_m[half.index_of(13, 5)] == untouched.elevations_m[untouched.index_of(13, 5)]
|
||||
var south_changed := half.elevations_m[half.index_of(13, 15)] != untouched.elevations_m[untouched.index_of(13, 15)]
|
||||
_expect(north_same and south_changed, "strata stay inside their region")
|
||||
|
||||
|
||||
func _test_slope_roughness() -> void:
|
||||
var recipe := _small_recipe()
|
||||
recipe.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.0, 0.0, 0.0, 0.0, 0.0))
|
||||
recipe.primitives.append(SiteTerrainBaker.Blob.new(Vector2(10.0, 10.0), 5.0, 5.0, 12.0))
|
||||
var smooth := SiteTerrainBaker.bake(recipe)
|
||||
recipe.modifiers.append(SiteTerrainBaker.SlopeRoughness.new(5, 0.2, 1.0, 20.0, 40.0))
|
||||
var rough := SiteTerrainBaker.bake(recipe)
|
||||
_expect(rough.elevations_m[rough.index_of(0, 0)] == smooth.elevations_m[smooth.index_of(0, 0)], "slope roughness leaves flat sand untouched")
|
||||
var flank_changed := false
|
||||
for column in range(13, 18):
|
||||
flank_changed = flank_changed or rough.elevations_m[rough.index_of(column, 15)] != smooth.elevations_m[smooth.index_of(column, 15)]
|
||||
_expect(flank_changed, "slope roughness roughens the steep flank")
|
||||
var octaves := _small_recipe()
|
||||
octaves.primitives.append(SiteTerrainBaker.Roughness.new(7, 0.01, 2.0, 5, FastNoiseLite.FRACTAL_RIDGED))
|
||||
var ridged := SiteTerrainBaker.bake(octaves)
|
||||
var within := true
|
||||
for elevation: float in ridged.elevations_m:
|
||||
within = within and absf(elevation) <= 2.0 + 0.001
|
||||
_expect(within, "roughness amplitude bounds the ridged multi-octave noise")
|
||||
|
||||
|
||||
func _test_zone1_artefact_is_stable() -> void:
|
||||
var versioned := load(ZONE1_TERRAIN_PATH) as SiteTerrainData
|
||||
|
||||
@@ -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),
|
||||
|
||||