Terrain des sites de plongée : champ de hauteur authoré, Banc du Vigilant, formes variées et rendu sous-marin #4

Open
olix21 wants to merge 47 commits from olix21/worktree-terrain-site-zone1 AGit into main
pull from: olix21/worktree-terrain-site-zone1
8 changed files with 73 additions and 36 deletions
Showing only changes of commit d91e6bd02f - Show all commits

No files matched your search

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

+9 -7
View File
@@ -82,7 +82,7 @@ ou `smoothstep`) et **mono-segment**. Il manque :
| Primitive | Paramètres | Forme | | Primitive | Paramètres | Forme |
|---|---|---| |---|---|---|
| `Canyon` | `path: PackedVector2Array`, `half_width_m`, `depth_m`, `wall_softness_m`, `edge_seed`, `edge_amplitude_m` | tranchée à fond plat le long d'une polyligne ; parois `smoothstep` sur `wall_softness_m` ; distance au tracé perturbée par un bruit de contour ; se referme sur une demi-largeur à chaque bout | | `Canyon` | `path: PackedVector2Array`, `half_width_m`, `depth_m`, `wall_softness_m`, `edge_seed`, `edge_amplitude_m`, `floor_material` | tranchée à fond plat le long d'une polyligne ; parois `smoothstep` sur `wall_softness_m` ; distance au tracé perturbée par un bruit de contour ; se referme sur une demi-largeur à chaque bout ; impose `floor_material` (sable en général) sur le fond plat, même à travers une butte rocheuse |
| `Outcrop` | `centre`, `radius_x_m`, `radius_z_m`, `height_m`, `wall_width_m`, `edge_seed`, `edge_amplitude_m`, `rock_top` | butte elliptique à sommet plat, flanc de largeur `wall_width_m`, contour perturbé ; matériau roche partout où elle dépasse 30 % de sa hauteur si `rock_top` | | `Outcrop` | `centre`, `radius_x_m`, `radius_z_m`, `height_m`, `wall_width_m`, `edge_seed`, `edge_amplitude_m`, `rock_top` | butte elliptique à sommet plat, flanc de largeur `wall_width_m`, contour perturbé ; matériau roche partout où elle dépasse 30 % de sa hauteur si `rock_top` |
| `BoulderField` | `centre`, `radius_m`, `seed`, `count`, `boulder_radius_min_m`, `boulder_radius_max_m`, `height_ratio` | `count` demi-ellipsoïdes tirés par `RandomNumberGenerator` seedé dans le disque, combinés par **maximum** ; roche sur toute leur emprise | | `BoulderField` | `centre`, `radius_m`, `seed`, `count`, `boulder_radius_min_m`, `boulder_radius_max_m`, `height_ratio` | `count` demi-ellipsoïdes tirés par `RandomNumberGenerator` seedé dans le disque, combinés par **maximum** ; roche sur toute leur emprise |
| `Escarpment` | `path: PackedVector2Array`, `drop_m`, `softness_m`, `slope_beyond`, `edge_seed`, `edge_amplitude_m` | `Step` le long d'une polyligne : le terrain descend à **droite** du sens de parcours ; ligne de crête perturbée par un bruit de contour | | `Escarpment` | `path: PackedVector2Array`, `drop_m`, `softness_m`, `slope_beyond`, `edge_seed`, `edge_amplitude_m` | `Step` le long d'une polyligne : le terrain descend à **droite** du sens de parcours ; ligne de crête perturbée par un bruit de contour |
@@ -129,7 +129,7 @@ Plan de masse (nord en haut) :
| 55°, gradins 4 m, fond ridé entre buttes | | 55°, gradins 4 m, fond ridé entre buttes |
| [départ] | | [départ] |
| SW : chaos de blocs SE : éperons | | SW : chaos de blocs SE : éperons |
| 80 blocs r 2–6 m sur deux buttes | | 90 blocs r 2–6 m sur deux buttes |
| sable ridé alvéolées, | | sable ridé alvéolées, |
| chenal 20 m | | chenal 20 m |
| ====== falaise sinueuse (Escarpment) ===== | | ====== falaise sinueuse (Escarpment) ===== |
@@ -148,13 +148,15 @@ Plan de masse (nord en haut) :
gradins : au moins deux niveaux plats ; un fond plat ne bouge pas) ; gradins : au moins deux niveaux plats ; un fond plat ne bouge pas) ;
cuisson du Banc du Vigilant identique à l'artefact versionné. cuisson du Banc du Vigilant identique à l'artefact versionné.
- **Contrat (`run_site_content_tests.gd`, section showcase)** : canyon - **Contrat (`run_site_content_tests.gd`, section showcase)** : canyon
(profondeur ≥ 20 m sous la mesa, fond plat à ± 1 m sur 10 m, paroi ≥ 60° (profondeur ≥ 18 m sous le sommet bruité de la mesa, fond de sable plat à
quelque part sur une transversale, ≥ 2 replats de gradin) ; crevasse ± 1 m sur 10 m, paroi ≥ 60° quelque part sur une transversale, ≥ 2 replats
(fond ≥ 16 m sous les bords, largeur au fond ≤ 8 m) ; chaos (roche sur un distincts) ; crevasse (fond de sable ≥ 16 m sous le plateau ouvert, 3 à 8 m
de large à 10 m sous le plateau) ; chaos (roche sur un
bloc, sable entre deux blocs, ≥ 40 cellules de roche distinctes… mesuré par bloc, sable entre deux blocs, ≥ 40 cellules de roche distinctes… mesuré par
fraction de roche dans le disque) ; éperons (chenal plus bas que les deux fraction de roche dans le disque) ; éperons (chenal plus bas que les deux
buttes, roche sur chaque butte) ; falaise (drop ≥ 20 m entre deux points de buttes, roche sur chaque butte) ; falaise (marche ≥ 18 m mesurée dans la
part et d'autre) ; bornes et profondeurs globales. seule bande de crête, pour que la pente en contrebas ne masque pas une
marche absente) ; bornes et profondeurs globales.
- **Intégration (`run_site_terrain_integration.gd`)** : chargement du showcase - **Intégration (`run_site_terrain_integration.gd`)** : chargement du showcase
sous 3 s, rayon dans le canyon plus bas que sur la mesa, scan de référence sous 3 s, rayon dans le canyon plus bas que sur la mesa, scan de référence
lisant roche et sable. lisant roche et sable.
+29 -17
View File
@@ -15,20 +15,23 @@ const DiveRulesResolver = preload("res://game/domain/rules/dive_rules_resolver.g
const SITE_PATH := "res://content/sites/zone1_vigilant.tres" const SITE_PATH := "res://content/sites/zone1_vigilant.tres"
const SHOWCASE_PATH := "res://content/sites/terrain_showcase.tres" const SHOWCASE_PATH := "res://content/sites/terrain_showcase.tres"
# Showcase probe positions (docs/briefs/terrain-varied-landforms.md §5). The canyon # Showcase probe positions (docs/briefs/terrain-varied-landforms.md §5). The canyon
# transect crosses its first leg at right angles; the crevasse transect is the # transect crosses its second leg at right angles; the crevasse transect is the
# z = -120 row between the two buttes. # z = -120 row between the two buttes, compared with the open plateau on the same row;
# the escarpment is probed across its step band at x = 0, where the crest is at z = 217.
const SHOWCASE_MESA_TOP := Vector2(-80.0, -90.0) const SHOWCASE_MESA_TOP := Vector2(-80.0, -90.0)
const SHOWCASE_CANYON_MID := Vector2(-130.0, -135.0) const SHOWCASE_CANYON_MID := Vector2(-130.0, -135.0)
const SHOWCASE_CANYON_AXIS := Vector2(40.0, -30.0) const SHOWCASE_CANYON_AXIS := Vector2(40.0, -30.0)
const SHOWCASE_CREVASSE_ROW_Z := -120.0 const SHOWCASE_CREVASSE_ROW_Z := -120.0
const SHOWCASE_CREVASSE_SHELF_X := 118.0 const SHOWCASE_CREVASSE_PLAIN_X := 0.0
const SHOWCASE_CREVASSE_FLOOR_X := 124.0 const SHOWCASE_CREVASSE_FLOOR_X := 124.0
const SHOWCASE_BOULDER_FIELD_CENTRE := Vector2(-120.0, 110.0) const SHOWCASE_BOULDER_FIELD_CENTRE := Vector2(-120.0, 110.0)
const SHOWCASE_BOULDER_FIELD_RADIUS_M := 75.0 const SHOWCASE_BOULDER_FIELD_RADIUS_M := 75.0
const SHOWCASE_WEST_SPUR := Vector2(80.0, 120.0) const SHOWCASE_WEST_SPUR := Vector2(80.0, 120.0)
const SHOWCASE_CHANNEL := Vector2(125.0, 120.0) const SHOWCASE_CHANNEL := Vector2(125.0, 120.0)
const SHOWCASE_EAST_SPUR := Vector2(170.0, 125.0) const SHOWCASE_EAST_SPUR := Vector2(170.0, 125.0)
const STEEP_WALL_RISE_PER_M := 1.7 # tan(60 degrees) const SHOWCASE_ESCARPMENT_CREST_Z := 217.0
const SHOWCASE_ESCARPMENT_BAND_M := 14.0 # softness 6 m plus 8 m of outline noise
const STEEP_WALL_RISE_PER_M := 1.73 # tan(60 degrees)
const LEDGE_RISE_PER_M := 0.35 const LEDGE_RISE_PER_M := 0.35
const ROUTE_CLEARANCE_M := 3.0 const ROUTE_CLEARANCE_M := 3.0
const MINIMUM_LANDMARK_SPACING_M := 60.0 const MINIMUM_LANDMARK_SPACING_M := 60.0
@@ -36,7 +39,7 @@ const MAXIMUM_ANCHOR_TO_WRECK_M := 320.0
const MAXIMUM_WRECK_SPACING_M := 260.0 const MAXIMUM_WRECK_SPACING_M := 260.0
const HULL_HALF_LENGTH_M := 14.0 const HULL_HALF_LENGTH_M := 14.0
const HULL_SEABED_TOLERANCE_M := 2.0 const HULL_SEABED_TOLERANCE_M := 2.0
const TEST_COUNT := 52 const TEST_COUNT := 54
var _failures: Array[String] = [] var _failures: Array[String] = []
@@ -161,7 +164,7 @@ func _test_showcase_content() -> void:
return return
_expect(field.cell_size_m() == 1.0 and field.columns() == 513 and field.rows() == 513, "showcase grid is 513 x 513 cells of 1 m") _expect(field.cell_size_m() == 1.0 and field.columns() == 513 and field.rows() == 513, "showcase grid is 513 x 513 cells of 1 m")
_expect(definition.playable_bounds_m.is_equal_approx(Rect2(-220.0, -220.0, 440.0, 440.0)), "showcase playable zone is 440 x 440 m") _expect(definition.playable_bounds_m.is_equal_approx(Rect2(-220.0, -220.0, 440.0, 440.0)), "showcase playable zone is 440 x 440 m")
_expect(field.minimum_elevation_m() >= -80.5 and field.maximum_elevation_m() <= -18.5, "showcase terrain stays between 19 m and 80 m deep") _expect(field.minimum_elevation_m() >= -80.5 and field.maximum_elevation_m() <= -19.0, "showcase terrain stays between 19 m and 80 m deep")
var anchors := definition.placements_of_kind(SitePlacement.Kind.VESSEL_ANCHOR) var anchors := definition.placements_of_kind(SitePlacement.Kind.VESSEL_ANCHOR)
_expect(anchors.size() == 1 and definition.placements.size() == 1, "showcase has a single placement, the mooring beacon") _expect(anchors.size() == 1 and definition.placements.size() == 1, "showcase has a single placement, the mooring beacon")
var spawn := definition.spawn_position_m var spawn := definition.spawn_position_m
@@ -171,6 +174,7 @@ func _test_showcase_content() -> void:
var mesa_top := field.elevation_at(SHOWCASE_MESA_TOP.x, SHOWCASE_MESA_TOP.y) var mesa_top := field.elevation_at(SHOWCASE_MESA_TOP.x, SHOWCASE_MESA_TOP.y)
var canyon_floor := field.elevation_at(SHOWCASE_CANYON_MID.x, SHOWCASE_CANYON_MID.y) var canyon_floor := field.elevation_at(SHOWCASE_CANYON_MID.x, SHOWCASE_CANYON_MID.y)
_expect(mesa_top - canyon_floor >= 18.0, "canyon floor is at least 18 m below the mesa top (%.1f m)" % (mesa_top - canyon_floor)) _expect(mesa_top - canyon_floor >= 18.0, "canyon floor is at least 18 m below the mesa top (%.1f m)" % (mesa_top - canyon_floor))
_expect(field.material_at(SHOWCASE_CANYON_MID.x, SHOWCASE_CANYON_MID.y) == SonarMaterial.Value.SAND, "canyon floor is sand through the rock mesa")
var axis := SHOWCASE_CANYON_AXIS.normalized() var axis := SHOWCASE_CANYON_AXIS.normalized()
var lowest := INF var lowest := INF
var highest := -INF var highest := -INF
@@ -186,23 +190,30 @@ func _test_showcase_content() -> void:
var point := SHOWCASE_CANYON_MID + normal * float(offset) var point := SHOWCASE_CANYON_MID + normal * float(offset)
transect.append(field.elevation_at(point.x, point.y)) transect.append(field.elevation_at(point.x, point.y))
var steepest := 0.0 var steepest := 0.0
var ledge_pairs := 0 var ledges := 0
var on_ledge := false
for index in range(transect.size() - 1): for index in range(transect.size() - 1):
var rise := absf(transect[index + 1] - transect[index]) var rise := absf(transect[index + 1] - transect[index])
steepest = maxf(steepest, rise) steepest = maxf(steepest, rise)
var in_wall := transect[index] > canyon_floor + 1.0 and transect[index] < mesa_top - 1.0 var in_wall := transect[index] > canyon_floor + 1.0 and transect[index] < mesa_top - 1.0
# A ledge is a run of flat metres inside the wall, separated from the next by a riser.
if in_wall and rise < LEDGE_RISE_PER_M: if in_wall and rise < LEDGE_RISE_PER_M:
ledge_pairs += 1 if not on_ledge:
_expect(steepest >= STEEP_WALL_RISE_PER_M, "canyon wall is steeper than 60 degrees somewhere (%.1f m per m)" % steepest) ledges += 1
_expect(ledge_pairs >= 2, "canyon wall carries at least two ledges (%d flat metres)" % ledge_pairs) on_ledge = true
else:
on_ledge = false
_expect(steepest >= STEEP_WALL_RISE_PER_M, "canyon wall is steeper than 60 degrees somewhere (%.2f m per m)" % steepest)
_expect(ledges >= 2, "canyon walls carry at least two distinct ledges (%d)" % ledges)
# NE: crevasse between the buttes. # NE: crevasse between the buttes.
var shelf := field.elevation_at(SHOWCASE_CREVASSE_SHELF_X, SHOWCASE_CREVASSE_ROW_Z) var plain := field.elevation_at(SHOWCASE_CREVASSE_PLAIN_X, SHOWCASE_CREVASSE_ROW_Z)
var crevasse_floor := field.elevation_at(SHOWCASE_CREVASSE_FLOOR_X, SHOWCASE_CREVASSE_ROW_Z) var crevasse_floor := field.elevation_at(SHOWCASE_CREVASSE_FLOOR_X, SHOWCASE_CREVASSE_ROW_Z)
_expect(shelf - crevasse_floor >= 16.0, "crevasse floor is at least 16 m below the rim (%.1f m)" % (shelf - crevasse_floor)) _expect(plain - crevasse_floor >= 16.0, "crevasse floor is at least 16 m below the open plateau (%.1f m)" % (plain - crevasse_floor))
_expect(field.material_at(SHOWCASE_CREVASSE_FLOOR_X, SHOWCASE_CREVASSE_ROW_Z) == SonarMaterial.Value.SAND, "crevasse floor is sand")
var deep_cells := 0 var deep_cells := 0
for x in range(100, 146): for x in range(100, 146):
if field.elevation_at(float(x), SHOWCASE_CREVASSE_ROW_Z) < shelf - 10.0: if field.elevation_at(float(x), SHOWCASE_CREVASSE_ROW_Z) < plain - 10.0:
deep_cells += 1 deep_cells += 1
_expect(deep_cells >= 3 and deep_cells <= 8, "crevasse is 3 to 8 m wide at 10 m down (%d m)" % deep_cells) _expect(deep_cells >= 3 and deep_cells <= 8, "crevasse is 3 to 8 m wide at 10 m down (%d m)" % deep_cells)
@@ -234,10 +245,11 @@ func _test_showcase_content() -> void:
) )
_expect(field.material_at(SHOWCASE_CHANNEL.x, SHOWCASE_CHANNEL.y + 30.0) == SonarMaterial.Value.SAND, "the channel floor is sand") _expect(field.material_at(SHOWCASE_CHANNEL.x, SHOWCASE_CHANNEL.y + 30.0) == SonarMaterial.Value.SAND, "the channel floor is sand")
# S: escarpment. # S: escarpment, measured across its step band only so the slope below cannot
var above := field.elevation_at(0.0, 190.0) # stand in for a missing step (the slope alone would give about 7 m here).
var below := field.elevation_at(0.0, 250.0) var above := field.elevation_at(0.0, SHOWCASE_ESCARPMENT_CREST_Z - SHOWCASE_ESCARPMENT_BAND_M)
_expect(above - below >= 20.0, "the escarpment drops at least 20 m (%.1f m)" % (above - below)) var below := field.elevation_at(0.0, SHOWCASE_ESCARPMENT_CREST_Z + SHOWCASE_ESCARPMENT_BAND_M)
_expect(above - below >= 18.0, "the escarpment steps down at least 18 m across its crest band (%.1f m)" % (above - below))
func _rests_on_terrain(field: TerrainHeightField, placement: SitePlacement) -> bool: func _rests_on_terrain(field: TerrainHeightField, placement: SitePlacement) -> bool:
@@ -240,7 +240,7 @@ func _test_showcase_site() -> void:
var mesa_top := _terrain_height(space, terrain, -80.0, -90.0, []) var mesa_top := _terrain_height(space, terrain, -80.0, -90.0, [])
_expect(mesa_top - canyon_floor >= 18.0, "collision rays find the canyon under the mesa (%.1f m vs %.1f m)" % [canyon_floor, mesa_top]) _expect(mesa_top - canyon_floor >= 18.0, "collision rays find the canyon under the mesa (%.1f m vs %.1f m)" % [canyon_floor, mesa_top])
# Sensor above the canyon looking along its first leg (the acquisition probes along # Sensor above the canyon looking along its second leg (the acquisition probes along
# local +Z): rock walls and sand floor. # local +Z): rock walls and sand floor.
var sensor := Transform3D(Basis.IDENTITY, Vector3(-130.0, -15.0, -135.0)) var sensor := Transform3D(Basis.IDENTITY, Vector3(-130.0, -15.0, -135.0))
sensor.basis = Basis.looking_at(-Vector3(40.0, 0.0, -30.0).normalized(), Vector3.UP) sensor.basis = Basis.looking_at(-Vector3(40.0, 0.0, -30.0).normalized(), Vector3.UP)
+5 -3
View File
@@ -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 ZONE1_TERRAIN_PATH := "res://content/sites/zone1_vigilant_terrain.res"
const TEST_COUNT := 40 const TEST_COUNT := 44
var _failures: Array[String] = [] var _failures: Array[String] = []
@@ -121,16 +121,18 @@ func _test_modifiers_and_primitive_materials() -> void:
func _test_canyon() -> void: func _test_canyon() -> void:
var canyon := SiteTerrainBaker.Canyon.new( var canyon := SiteTerrainBaker.Canyon.new(
PackedVector2Array([Vector2(-20.0, 0.0), Vector2(0.0, 0.0), Vector2(0.0, 20.0)]), PackedVector2Array([Vector2(-20.0, 0.0), Vector2(0.0, 0.0), Vector2(0.0, 20.0)]),
4.0, 10.0, 1.0, 0, 0.0 4.0, 10.0, 1.0, 0, 0.0, SonarMaterial.Value.SAND
) )
_expect(is_equal_approx(canyon.elevation_delta(-10.0, 0.0), -10.0), "canyon floor sits at depth on the first leg") _expect(is_equal_approx(canyon.elevation_delta(-10.0, 0.0), -10.0), "canyon floor sits at depth on the first leg")
_expect(canyon.material_at(-10.0, 0.0) == SonarMaterial.Value.SAND, "canyon imposes its floor material on the flat floor")
_expect(canyon.material_at(-10.0, 4.0) == SiteTerrainBaker.NO_MATERIAL, "canyon leaves its walls to the slope rule")
_expect(is_equal_approx(canyon.elevation_delta(1.5, 10.0), -10.0), "canyon floor is flat across its width on the second leg") _expect(is_equal_approx(canyon.elevation_delta(1.5, 10.0), -10.0), "canyon floor is flat across its width on the second leg")
_expect(is_equal_approx(canyon.elevation_delta(-10.0, 6.0), 0.0), "canyon stops past half width plus softness") _expect(is_equal_approx(canyon.elevation_delta(-10.0, 6.0), 0.0), "canyon stops past half width plus softness")
_expect(is_equal_approx(canyon.elevation_delta(15.0, -15.0), 0.0), "canyon is zero far from the path") _expect(is_equal_approx(canyon.elevation_delta(15.0, -15.0), 0.0), "canyon is zero far from the path")
_expect(is_equal_approx(canyon.elevation_delta(0.0, 20.0), 0.0), "canyon closes at its last vertex") _expect(is_equal_approx(canyon.elevation_delta(0.0, 20.0), 0.0), "canyon closes at its last vertex")
_expect(canyon.elevation_delta(-10.0, 4.0) < -4.0 and canyon.elevation_delta(-10.0, 4.0) > -6.0, "canyon wall is half way down at half width") _expect(canyon.elevation_delta(-10.0, 4.0) < -4.0 and canyon.elevation_delta(-10.0, 4.0) > -6.0, "canyon wall is half way down at half width")
var rough := SiteTerrainBaker.Canyon.new( var rough := SiteTerrainBaker.Canyon.new(
PackedVector2Array([Vector2(-200.0, 0.0), Vector2(200.0, 0.0)]), 8.0, 10.0, 2.0, 7, 3.0 PackedVector2Array([Vector2(-200.0, 0.0), Vector2(200.0, 0.0)]), 8.0, 10.0, 2.0, 7, 3.0, SiteTerrainBaker.NO_MATERIAL
) )
var wall_values: Dictionary = {} var wall_values: Dictionary = {}
for x in range(-100, 101, 10): for x in range(-100, 101, 10):
+25 -5
View File
@@ -170,13 +170,15 @@ class Groove extends Primitive:
## Flat-floored trench along a polyline. The floor sits depth_m below the surroundings ## Flat-floored trench along a polyline. The floor sits depth_m below the surroundings
## wherever the (noise-perturbed) distance to the path is under half_width_m minus ## wherever the (noise-perturbed) distance to the path is under half_width_m minus
## wall_softness_m; the walls climb over 2 * wall_softness_m; the trench closes over one ## wall_softness_m; the walls climb over 2 * wall_softness_m; the trench closes over one
## half-width at each end. ## half-width at each end. floor_material (a SonarMaterial value, or NO_MATERIAL) is
## imposed on the flat floor, so a canyon cut through a rock outcrop keeps a sand bed.
class Canyon extends Primitive: class Canyon extends Primitive:
var path: PackedVector2Array var path: PackedVector2Array
var half_width_m: float var half_width_m: float
var depth_m: float var depth_m: float
var wall_softness_m: float var wall_softness_m: float
var edge_amplitude_m: float var edge_amplitude_m: float
var floor_material: int
var _noise: FastNoiseLite var _noise: FastNoiseLite
var _length_m: float var _length_m: float
@@ -186,17 +188,31 @@ class Canyon extends Primitive:
p_depth_m: float, p_depth_m: float,
p_wall_softness_m: float, p_wall_softness_m: float,
p_edge_seed: int, p_edge_seed: int,
p_edge_amplitude_m: float p_edge_amplitude_m: float,
p_floor_material: int
) -> void: ) -> void:
assert(p_path.size() >= 2, "a canyon path needs at least two vertices")
assert(p_half_width_m > 0.0, "a canyon needs a positive half width")
path = p_path path = p_path
half_width_m = p_half_width_m half_width_m = p_half_width_m
depth_m = p_depth_m depth_m = p_depth_m
wall_softness_m = p_wall_softness_m wall_softness_m = p_wall_softness_m
edge_amplitude_m = p_edge_amplitude_m edge_amplitude_m = p_edge_amplitude_m
floor_material = p_floor_material
_noise = edge_noise(p_edge_seed, p_edge_amplitude_m) _noise = edge_noise(p_edge_seed, p_edge_amplitude_m)
_length_m = path_length(p_path) _length_m = path_length(p_path)
func elevation_delta(x_m: float, z_m: float) -> float: func elevation_delta(x_m: float, z_m: float) -> float:
return -depth_m * _presence(x_m, z_m)
func material_at(x_m: float, z_m: float) -> int:
# Full presence means the flat floor: inside the walls and clear of the end tapers.
if floor_material != NO_MATERIAL and _presence(x_m, z_m) >= 0.999:
return floor_material
return NO_MATERIAL
## 1 on the flat floor, 0 outside the trench, in between on the walls and end tapers.
func _presence(x_m: float, z_m: float) -> float:
var sample := path_sample(Vector2(x_m, z_m), path) var sample := path_sample(Vector2(x_m, z_m), path)
var distance := sample.x + edge_offset(_noise, edge_amplitude_m, x_m, z_m) var distance := sample.x + edge_offset(_noise, edge_amplitude_m, x_m, z_m)
var inside := 1.0 - smoothstep( var inside := 1.0 - smoothstep(
@@ -205,13 +221,14 @@ class Canyon extends Primitive:
distance distance
) )
var taper := smoothstep(0.0, half_width_m, sample.y) * smoothstep(0.0, half_width_m, _length_m - sample.y) var taper := smoothstep(0.0, half_width_m, sample.y) * smoothstep(0.0, half_width_m, _length_m - sample.y)
return -depth_m * inside * taper return inside * taper
## Step along a polyline: the terrain drops by drop_m on the right of the travel direction ## Step along a polyline: the terrain drops by drop_m on the right of the travel direction
## (south of an eastbound path), over 2 * softness_m, then keeps falling at slope_beyond. ## (south of an eastbound path), over 2 * softness_m, then keeps falling at slope_beyond.
## The crest line is roughened by outline noise. Extend the path past the field edges: ## The crest line is roughened by outline noise. The path must extend past the field
## beyond its ends the contour wraps around the end vertex. ## edges: beyond an end vertex the drop is decided by the half-plane of the last segment,
## which leaves a seam along the path's extension.
class Escarpment extends Primitive: class Escarpment extends Primitive:
var path: PackedVector2Array var path: PackedVector2Array
var drop_m: float var drop_m: float
@@ -228,6 +245,7 @@ class Escarpment extends Primitive:
p_edge_seed: int, p_edge_seed: int,
p_edge_amplitude_m: float p_edge_amplitude_m: float
) -> void: ) -> void:
assert(p_path.size() >= 2, "an escarpment path needs at least two vertices")
path = p_path path = p_path
drop_m = p_drop_m drop_m = p_drop_m
softness_m = p_softness_m softness_m = p_softness_m
@@ -267,6 +285,7 @@ class Outcrop extends Primitive:
p_edge_amplitude_m: float, p_edge_amplitude_m: float,
p_rock_top: bool p_rock_top: bool
) -> void: ) -> void:
assert(p_radius_x_m > 0.0 and p_radius_z_m > 0.0, "an outcrop needs positive radii")
centre = p_centre centre = p_centre
radius_x_m = p_radius_x_m radius_x_m = p_radius_x_m
radius_z_m = p_radius_z_m radius_z_m = p_radius_z_m
@@ -435,6 +454,7 @@ class Strata extends Modifier:
p_centre: Vector2, p_centre: Vector2,
p_radius_m: float p_radius_m: float
) -> void: ) -> void:
assert(p_layer_height_m > 0.0, "strata need a positive layer height")
layer_height_m = p_layer_height_m layer_height_m = p_layer_height_m
sharpness = clampf(p_sharpness, 0.0, 0.999) sharpness = clampf(p_sharpness, 0.0, 0.999)
centre = p_centre centre = p_centre
+4 -3
View File
@@ -5,6 +5,7 @@ extends RefCounted
## mooring beacon at the centre. Field 512 x 512 m at 1 m, +X east, +Z south. ## mooring beacon at the centre. Field 512 x 512 m at 1 m, +X east, +Z south.
const SiteTerrainBaker = preload("res://tools/site_terrain_baker.gd") const SiteTerrainBaker = preload("res://tools/site_terrain_baker.gd")
const SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd")
const SiteTerrainData = preload("res://game/domain/sites/site_terrain_data.gd") const SiteTerrainData = preload("res://game/domain/sites/site_terrain_data.gd")
const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd") const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd")
const SitePlacement = preload("res://game/domain/sites/site_placement.gd") const SitePlacement = preload("res://game/domain/sites/site_placement.gd")
@@ -59,15 +60,15 @@ static func recipe() -> SiteTerrainBaker.Recipe:
# modifier (one ledge every 2.8 m across, resolvable at 1 m per cell), rippled # modifier (one ledge every 2.8 m across, resolvable at 1 m per cell), rippled
# sand on the floor. # sand on the floor.
SiteTerrainBaker.Outcrop.new(MESA_CENTRE, 95.0, 85.0, 6.0, 10.0, 101, 6.0, true), SiteTerrainBaker.Outcrop.new(MESA_CENTRE, 95.0, 85.0, 6.0, 10.0, 101, 6.0, true),
SiteTerrainBaker.Canyon.new(PackedVector2Array(CANYON_PATH), CANYON_HALF_WIDTH_M, CANYON_DEPTH_M, 7.0, 102, 2.0), SiteTerrainBaker.Canyon.new(PackedVector2Array(CANYON_PATH), CANYON_HALF_WIDTH_M, CANYON_DEPTH_M, 7.0, 102, 2.0, SonarMaterial.Value.SAND),
SiteTerrainBaker.Ripples.new(Vector2(0.8, -0.6), 4.0, 0.25, Vector2(-130.0, -140.0), 70.0), SiteTerrainBaker.Ripples.new(Vector2(0.8, -0.6), 4.0, 0.25, Vector2(-130.0, -140.0), 70.0),
# NE — la Fente: two buttes 8-9 m high with a 6 m wide, 20 m deep slot between them. # NE — la Fente: two buttes 8-9 m high with a 6 m wide, 20 m deep slot between them.
SiteTerrainBaker.Outcrop.new(WEST_BUTTE_CENTRE, 38.0, 70.0, 8.0, 8.0, 201, 5.0, true), SiteTerrainBaker.Outcrop.new(WEST_BUTTE_CENTRE, 38.0, 70.0, 8.0, 8.0, 201, 5.0, true),
SiteTerrainBaker.Outcrop.new(EAST_BUTTE_CENTRE, 38.0, 75.0, 9.0, 8.0, 202, 5.0, true), SiteTerrainBaker.Outcrop.new(EAST_BUTTE_CENTRE, 38.0, 75.0, 9.0, 8.0, 202, 5.0, true),
SiteTerrainBaker.Canyon.new(PackedVector2Array(CREVASSE_PATH), CREVASSE_HALF_WIDTH_M, CREVASSE_DEPTH_M, 1.0, 203, 1.0), SiteTerrainBaker.Canyon.new(PackedVector2Array(CREVASSE_PATH), CREVASSE_HALF_WIDTH_M, CREVASSE_DEPTH_M, 1.0, 203, 1.0, SonarMaterial.Value.SAND),
# SW — le Chaos: ninety boulders 2-6 m across plus six coral heads, on rippled sand. # SW — le Chaos: ninety boulders of 2-6 m radius plus six coral heads, on rippled sand.
SiteTerrainBaker.BoulderField.new(BOULDER_FIELD_CENTRE, BOULDER_FIELD_RADIUS_M, 301, 90, 2.0, 6.0, 0.7), SiteTerrainBaker.BoulderField.new(BOULDER_FIELD_CENTRE, BOULDER_FIELD_RADIUS_M, 301, 90, 2.0, 6.0, 0.7),
SiteTerrainBaker.BoulderField.new(Vector2(-100.0, 130.0), 40.0, 302, 6, 6.0, 9.0, 0.6), 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), SiteTerrainBaker.Ripples.new(Vector2(1.0, 0.3), 3.5, 0.3, BOULDER_FIELD_CENTRE, 110.0),