feat(tools): Outcrop and BoulderField primitives with forced rock material

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
OlivierandClaude Fable 5.1 committed 2026-10-06 21:38:32 +02:00
1 parent aae9bcd301
commit 6dd2e86f91
2 files changed
+135 -1

No files matched your search

+104
View File
@@ -243,6 +243,110 @@ class Escarpment extends Primitive:
return -(step + slope)
## Flat-topped elliptical mound (or, with a negative height, a flat-bottomed pit) whose
## flank spans wall_width_m inside a noise-roughened outline. With rock_top the whole
## mound above 30 % of its height is rock, sommet plat compris.
class Outcrop extends Primitive:
var centre: Vector2
var radius_x_m: float
var radius_z_m: float
var height_m: float
var wall_width_m: float
var edge_amplitude_m: float
var rock_top: bool
var _noise: FastNoiseLite
var _mean_radius_m: float
func _init(
p_centre: Vector2,
p_radius_x_m: float,
p_radius_z_m: float,
p_height_m: float,
p_wall_width_m: float,
p_edge_seed: int,
p_edge_amplitude_m: float,
p_rock_top: bool
) -> void:
centre = p_centre
radius_x_m = p_radius_x_m
radius_z_m = p_radius_z_m
height_m = p_height_m
wall_width_m = p_wall_width_m
edge_amplitude_m = p_edge_amplitude_m
rock_top = p_rock_top
_noise = edge_noise(p_edge_seed, p_edge_amplitude_m)
_mean_radius_m = (p_radius_x_m + p_radius_z_m) * 0.5
func elevation_delta(x_m: float, z_m: float) -> float:
var dx := (x_m - centre.x) / radius_x_m
var dz := (z_m - centre.y) / radius_z_m
var edge_distance := (sqrt(dx * dx + dz * dz) - 1.0) * _mean_radius_m
edge_distance += edge_offset(_noise, edge_amplitude_m, x_m, z_m)
return height_m * (1.0 - smoothstep(-wall_width_m, 0.0, edge_distance))
func material_at(x_m: float, z_m: float) -> int:
if rock_top and absf(elevation_delta(x_m, z_m)) > 0.3 * absf(height_m):
return SonarMaterial.Value.ROCK
return NO_MATERIAL
## count half-ellipsoid boulders scattered uniformly in a disc by a seeded generator.
## Boulders combine by maximum, so overlapping blocks never pile into one mound, and every
## cell they cover is rock.
class BoulderField extends Primitive:
var centre: Vector2
var radius_m: float
var height_ratio: float
var _boulders := PackedVector3Array() # x, z, radius
var _reach_m: float
func _init(
p_centre: Vector2,
p_radius_m: float,
p_seed: int,
p_count: int,
p_boulder_radius_min_m: float,
p_boulder_radius_max_m: float,
p_height_ratio: float
) -> void:
centre = p_centre
radius_m = p_radius_m
height_ratio = p_height_ratio
_reach_m = p_radius_m + p_boulder_radius_max_m
var generator := RandomNumberGenerator.new()
generator.seed = p_seed
for _index in p_count:
var distance := p_radius_m * sqrt(generator.randf())
var angle := TAU * generator.randf()
var boulder_radius := generator.randf_range(p_boulder_radius_min_m, p_boulder_radius_max_m)
_boulders.append(Vector3(
p_centre.x + distance * cos(angle),
p_centre.y + distance * sin(angle),
boulder_radius
))
func boulders() -> PackedVector3Array:
return _boulders
func elevation_delta(x_m: float, z_m: float) -> float:
if Vector2(x_m, z_m).distance_to(centre) > _reach_m:
return 0.0
var highest := 0.0
for boulder in _boulders:
var dx := x_m - boulder.x
var dz := z_m - boulder.y
var squared := dx * dx + dz * dz
var radius_squared := boulder.z * boulder.z
if squared < radius_squared:
highest = maxf(highest, boulder.z * height_ratio * sqrt(1.0 - squared / radius_squared))
return highest
func material_at(x_m: float, z_m: float) -> int:
if elevation_delta(x_m, z_m) > 0.0:
return SonarMaterial.Value.ROCK
return NO_MATERIAL
class Step extends Primitive:
var point: Vector2
var normal: Vector2