perf(sites): validate the site once and hand collision the field array
DiveSite.configure() builds the height field once (TerrainHeightField validates the data) instead of running the definition validation and then the field construction again; the composition root relies on the error returned by configure(). The collision shape takes the field's row-major elevations directly instead of copying 263k cells one by one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
be16d2ee07
commit
682e1f18e5
5 files changed
+14
-16
No files matched your search
@@ -130,10 +130,6 @@ func _start_dive() -> void:
|
|||||||
if definition == null:
|
if definition == null:
|
||||||
push_error("dive site definition %s cannot be loaded" % _selected_site_path)
|
push_error("dive site definition %s cannot be loaded" % _selected_site_path)
|
||||||
return
|
return
|
||||||
var definition_error := definition.validation_error()
|
|
||||||
if not definition_error.is_empty():
|
|
||||||
push_error("dive site %s is invalid: %s" % [_selected_site_path, definition_error])
|
|
||||||
return
|
|
||||||
var world_scene := load(DIVE_WORLD_SCENE_PATH) as PackedScene
|
var world_scene := load(DIVE_WORLD_SCENE_PATH) as PackedScene
|
||||||
if world_scene == null:
|
if world_scene == null:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ func maximum_elevation_m() -> float:
|
|||||||
return _maximum_elevation_m
|
return _maximum_elevation_m
|
||||||
|
|
||||||
|
|
||||||
|
## Row-major elevations (index = row * columns + column), the HeightMapShape3D layout.
|
||||||
|
func elevations_m() -> PackedFloat32Array:
|
||||||
|
return _data.elevations_m
|
||||||
|
|
||||||
|
|
||||||
func elevation_at_cell(column: int, row: int) -> float:
|
func elevation_at_cell(column: int, row: int) -> float:
|
||||||
return _data.elevations_m[_data.index_of(_clamp_column(column), _clamp_row(row))]
|
return _data.elevations_m[_data.index_of(_clamp_column(column), _clamp_row(row))]
|
||||||
|
|
||||||
|
|||||||
@@ -116,12 +116,7 @@ func _build_collision() -> void:
|
|||||||
var shape := HeightMapShape3D.new()
|
var shape := HeightMapShape3D.new()
|
||||||
shape.map_width = _field.columns()
|
shape.map_width = _field.columns()
|
||||||
shape.map_depth = _field.rows()
|
shape.map_depth = _field.rows()
|
||||||
var heights := PackedFloat32Array()
|
shape.map_data = _field.elevations_m()
|
||||||
heights.resize(_field.columns() * _field.rows())
|
|
||||||
for row in _field.rows():
|
|
||||||
for column in _field.columns():
|
|
||||||
heights[row * _field.columns() + column] = _field.elevation_at_cell(column, row)
|
|
||||||
shape.map_data = heights
|
|
||||||
_collision = CollisionShape3D.new()
|
_collision = CollisionShape3D.new()
|
||||||
_collision.name = "Collision"
|
_collision.name = "Collision"
|
||||||
_collision.shape = shape
|
_collision.shape = shape
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ var _placeholder_count := 0
|
|||||||
func configure(definition: DiveSiteDefinition) -> String:
|
func configure(definition: DiveSiteDefinition) -> String:
|
||||||
if definition == null:
|
if definition == null:
|
||||||
return "dive site definition is missing"
|
return "dive site definition is missing"
|
||||||
var error := definition.validation_error()
|
var field := definition.height_field()
|
||||||
if not error.is_empty():
|
if field == null:
|
||||||
return error
|
return "dive site definition is invalid"
|
||||||
var terrain_error := _terrain.configure(definition.height_field())
|
var terrain_error := _terrain.configure(field)
|
||||||
if not terrain_error.is_empty():
|
if not terrain_error.is_empty():
|
||||||
return terrain_error
|
return terrain_error
|
||||||
_definition = definition
|
_definition = definition
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const SitePlacement = preload("res://game/domain/sites/site_placement.gd")
|
|||||||
const SiteRoute = preload("res://game/domain/sites/site_route.gd")
|
const SiteRoute = preload("res://game/domain/sites/site_route.gd")
|
||||||
const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd")
|
const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd")
|
||||||
|
|
||||||
const TEST_COUNT := 38
|
const TEST_COUNT := 39
|
||||||
|
|
||||||
var _failures: Array[String] = []
|
var _failures: Array[String] = []
|
||||||
|
|
||||||
@@ -83,10 +83,12 @@ func _test_terrain_data_validation() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _test_height_field_sampling() -> void:
|
func _test_height_field_sampling() -> void:
|
||||||
var field := TerrainHeightField.from_data(_make_terrain(3, 4))
|
var data := _make_terrain(3, 4)
|
||||||
|
var field := TerrainHeightField.from_data(data)
|
||||||
_expect(field != null, "valid data yields a field")
|
_expect(field != null, "valid data yields a field")
|
||||||
if field == null:
|
if field == null:
|
||||||
return
|
return
|
||||||
|
_expect(field.elevations_m() == data.elevations_m, "elevations are exposed in row-major data order")
|
||||||
_expect(field.bounds_m().is_equal_approx(Rect2(-10.0, 100.0, 4.0, 6.0)), "bounds cover (columns-1) by (rows-1) cells")
|
_expect(field.bounds_m().is_equal_approx(Rect2(-10.0, 100.0, 4.0, 6.0)), "bounds cover (columns-1) by (rows-1) cells")
|
||||||
_expect(field.cell_world_position(2, 3).is_equal_approx(Vector3(-6.0, -26.0, 106.0)), "cell position follows origin and cell size")
|
_expect(field.cell_world_position(2, 3).is_equal_approx(Vector3(-6.0, -26.0, 106.0)), "cell position follows origin and cell size")
|
||||||
_expect(is_equal_approx(field.elevation_at(-10.0, 100.0), -20.0), "elevation at a vertex is exact")
|
_expect(is_equal_approx(field.elevation_at(-10.0, 100.0), -20.0), "elevation at a vertex is exact")
|
||||||
|
|||||||
Reference in new issue
Block a user