feat(tools): modifier phase and per-primitive material hook in the site baker
The bake order is now primitive sum, modifiers (with the local gradient), floor clamp, slope material, primitive materials, material patches. A regression test checks the Banc du Vigilant still bakes byte for byte. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
9454b9e552
commit
520a62863b
2 files changed
+118
-3
No files matched your search
@@ -5,10 +5,19 @@ const SiteTerrainData = preload("res://game/domain/sites/site_terrain_data.gd")
|
||||
const TerrainHeightField = preload("res://game/domain/sites/terrain_height_field.gd")
|
||||
|
||||
|
||||
## A primitive returning this from material_at leaves the slope rule in charge.
|
||||
const NO_MATERIAL := -1
|
||||
|
||||
|
||||
class Primitive extends RefCounted:
|
||||
func elevation_delta(_x_m: float, _z_m: float) -> float:
|
||||
return 0.0
|
||||
|
||||
## Material imposed by this primitive at (x, z), or NO_MATERIAL. Applied after the
|
||||
## slope rule, in recipe order, before the material patches.
|
||||
func material_at(_x_m: float, _z_m: float) -> int:
|
||||
return NO_MATERIAL
|
||||
|
||||
static func distance_to_segment(point: Vector2, start: Vector2, end: Vector2) -> float:
|
||||
var segment := end - start
|
||||
var length_squared := segment.length_squared()
|
||||
@@ -171,6 +180,13 @@ class Roughness extends Primitive:
|
||||
return _noise.get_noise_2d(x_m, z_m) * amplitude_m
|
||||
|
||||
|
||||
## Reshapes the summed elevation once every primitive has been added. gradient is the
|
||||
## local slope Vector2(dE/dx, dE/dz) of the field being modified, by central differences.
|
||||
class Modifier extends RefCounted:
|
||||
func modify(_x_m: float, _z_m: float, elevation_m: float, _gradient: Vector2) -> float:
|
||||
return elevation_m
|
||||
|
||||
|
||||
class MaterialPatch extends RefCounted:
|
||||
var centre: Vector2
|
||||
var radius_m: float
|
||||
@@ -193,9 +209,12 @@ class Recipe extends RefCounted:
|
||||
var rock_reflectivity: float = 0.72
|
||||
var sand_reflectivity: float = 0.45
|
||||
var primitives: Array[Primitive] = []
|
||||
var modifiers: Array[Modifier] = []
|
||||
var material_patches: Array[MaterialPatch] = []
|
||||
|
||||
|
||||
## Bake order: primitive sum, modifiers in order, floor clamp, slope material,
|
||||
## primitive materials in order, material patches.
|
||||
static func bake(recipe: Recipe) -> SiteTerrainData:
|
||||
var data := SiteTerrainData.new()
|
||||
data.columns = recipe.columns
|
||||
@@ -218,8 +237,12 @@ static func bake(recipe: Recipe) -> SiteTerrainData:
|
||||
var elevation := 0.0
|
||||
for primitive: Primitive in recipe.primitives:
|
||||
elevation += primitive.elevation_delta(x_m, z_m)
|
||||
elevations[data.index_of(column, row)] = maxf(elevation, recipe.floor_elevation_m)
|
||||
materials[data.index_of(column, row)] = SonarMaterial.Value.SAND
|
||||
elevations[data.index_of(column, row)] = elevation
|
||||
for modifier: Modifier in recipe.modifiers:
|
||||
elevations = _apply_modifier(recipe, modifier, elevations)
|
||||
for index in cell_count:
|
||||
elevations[index] = maxf(elevations[index], recipe.floor_elevation_m)
|
||||
materials[index] = SonarMaterial.Value.SAND
|
||||
data.elevations_m = elevations
|
||||
data.materials = materials
|
||||
var field := TerrainHeightField.from_data(data)
|
||||
@@ -230,6 +253,14 @@ static func bake(recipe: Recipe) -> SiteTerrainData:
|
||||
for column in recipe.columns:
|
||||
if field.normal_at_cell(column, row).y < rock_cosine:
|
||||
materials[data.index_of(column, row)] = SonarMaterial.Value.ROCK
|
||||
for primitive: Primitive in recipe.primitives:
|
||||
for row in recipe.rows:
|
||||
var z_m := recipe.origin_z_m + float(row) * recipe.cell_size_m
|
||||
for column in recipe.columns:
|
||||
var x_m := recipe.origin_x_m + float(column) * recipe.cell_size_m
|
||||
var material := primitive.material_at(x_m, z_m)
|
||||
if material != NO_MATERIAL:
|
||||
materials[data.index_of(column, row)] = material
|
||||
for patch: MaterialPatch in recipe.material_patches:
|
||||
for row in recipe.rows:
|
||||
var z_m := recipe.origin_z_m + float(row) * recipe.cell_size_m
|
||||
@@ -241,6 +272,31 @@ static func bake(recipe: Recipe) -> SiteTerrainData:
|
||||
return data
|
||||
|
||||
|
||||
## Returns the modified copy of elevations; the gradient of each cell is read from the
|
||||
## unmodified input so the modifier sees one consistent field.
|
||||
static func _apply_modifier(recipe: Recipe, modifier: Modifier, source: PackedFloat32Array) -> PackedFloat32Array:
|
||||
var columns := recipe.columns
|
||||
var rows := recipe.rows
|
||||
var cell_size := recipe.cell_size_m
|
||||
var result := PackedFloat32Array()
|
||||
result.resize(source.size())
|
||||
for row in rows:
|
||||
var z_m := recipe.origin_z_m + float(row) * cell_size
|
||||
var north := maxi(row - 1, 0)
|
||||
var south := mini(row + 1, rows - 1)
|
||||
for column in columns:
|
||||
var x_m := recipe.origin_x_m + float(column) * cell_size
|
||||
var west := maxi(column - 1, 0)
|
||||
var east := mini(column + 1, columns - 1)
|
||||
var gradient := Vector2(
|
||||
(source[row * columns + east] - source[row * columns + west]) / (float(east - west) * cell_size),
|
||||
(source[south * columns + column] - source[north * columns + column]) / (float(south - north) * cell_size)
|
||||
)
|
||||
var index := row * columns + column
|
||||
result[index] = modifier.modify(x_m, z_m, source[index], gradient)
|
||||
return result
|
||||
|
||||
|
||||
static func write_preview_png(data: SiteTerrainData, path: String) -> Error:
|
||||
var field := TerrainHeightField.from_data(data)
|
||||
if field == null:
|
||||
|
||||
Reference in new issue
Block a user