Add freshwater habitats and saltwater tide ribbons
This commit is contained in:
parent
53fdc9ef21
commit
69b7969e25
40 changed files with 1323 additions and 148 deletions
138
world/water/shoreline_ribbon_baker.gd
Normal file
138
world/water/shoreline_ribbon_baker.gd
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
@tool
|
||||
class_name ShorelineRibbonBaker
|
||||
extends Node
|
||||
|
||||
@export var water_bodies: Array[ShorelineRibbonConfig] = []
|
||||
@export_enum("Off", "Raw", "Simplified", "Smoothed") var debug_path_stage := 0
|
||||
@export_tool_button("Rebuild Shoreline Ribbons")
|
||||
var rebuild_shorelines: Callable = rebuild_all
|
||||
|
||||
|
||||
func rebuild_all() -> Array[Dictionary]:
|
||||
var results: Array[Dictionary] = []
|
||||
for configuration: ShorelineRibbonConfig in water_bodies:
|
||||
var result := _rebuild(configuration)
|
||||
if result.is_empty():
|
||||
return []
|
||||
results.append(result)
|
||||
_update_debug_display(results)
|
||||
return results
|
||||
|
||||
|
||||
func _rebuild(configuration: ShorelineRibbonConfig) -> Dictionary:
|
||||
if configuration == null:
|
||||
push_error("Shoreline ribbon configuration is missing.")
|
||||
return {}
|
||||
if configuration.water_type != WaterType.Type.SALT_WATER:
|
||||
return {
|
||||
"skipped": true,
|
||||
"water_type": configuration.water_type,
|
||||
"output_path": configuration.output_resource_path,
|
||||
}
|
||||
var source := get_node_or_null(configuration.terrain_source) as Node3D
|
||||
if source == null:
|
||||
push_error(
|
||||
"Shoreline terrain source was not found: %s"
|
||||
% configuration.terrain_source
|
||||
)
|
||||
return {}
|
||||
if configuration.output_resource_path.is_empty():
|
||||
push_error("Shoreline output resource path is empty.")
|
||||
return {}
|
||||
var faces := _terrain_faces(source)
|
||||
if faces.is_empty():
|
||||
push_error("Configured shoreline terrain source exposes no triangles.")
|
||||
return {}
|
||||
var result := ShorelineRibbonGenerator.generate(
|
||||
faces,
|
||||
configuration.water_height,
|
||||
configuration.generation_bounds,
|
||||
configuration.water_reference,
|
||||
configuration.water_is_inside,
|
||||
configuration.simplification_tolerance,
|
||||
configuration.smoothing_iterations,
|
||||
configuration.resample_spacing
|
||||
)
|
||||
var output_directory := configuration.output_resource_path.get_base_dir()
|
||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output_directory))
|
||||
var save_error := ResourceSaver.save(
|
||||
result["mesh"], configuration.output_resource_path
|
||||
)
|
||||
if save_error != OK:
|
||||
push_error(
|
||||
"Failed to save %s: %s"
|
||||
% [configuration.output_resource_path, error_string(save_error)]
|
||||
)
|
||||
return {}
|
||||
result["output_path"] = configuration.output_resource_path
|
||||
result["water_height"] = configuration.water_height
|
||||
result["water_type"] = configuration.water_type
|
||||
result["skipped"] = false
|
||||
return result
|
||||
|
||||
|
||||
func _update_debug_display(results: Array[Dictionary]) -> void:
|
||||
var previous := get_node_or_null("_ShorelinePathDebug")
|
||||
if previous != null:
|
||||
previous.queue_free()
|
||||
if not Engine.is_editor_hint() or debug_path_stage == 0:
|
||||
return
|
||||
var debug_mesh := ImmediateMesh.new()
|
||||
var debug_material := StandardMaterial3D.new()
|
||||
debug_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
|
||||
debug_material.albedo_color = Color(1.0, 0.35, 0.65, 1.0)
|
||||
var stage_key: String = [
|
||||
"", "debug_raw_paths", "debug_simplified_paths", "debug_smoothed_paths"
|
||||
][debug_path_stage]
|
||||
for result: Dictionary in results:
|
||||
if result.get("skipped", false):
|
||||
continue
|
||||
var height := float(result["water_height"]) + 0.06
|
||||
for path_data: Dictionary in result[stage_key]:
|
||||
var points: PackedVector2Array = path_data["points"]
|
||||
if points.size() < 2:
|
||||
continue
|
||||
debug_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP, debug_material)
|
||||
for point: Vector2 in points:
|
||||
debug_mesh.surface_add_vertex(Vector3(point.x, height, point.y))
|
||||
if path_data["closed"]:
|
||||
debug_mesh.surface_add_vertex(Vector3(points[0].x, height, points[0].y))
|
||||
debug_mesh.surface_end()
|
||||
var debug_instance := MeshInstance3D.new()
|
||||
debug_instance.name = "_ShorelinePathDebug"
|
||||
debug_instance.mesh = debug_mesh
|
||||
add_child(debug_instance)
|
||||
|
||||
|
||||
func _terrain_faces(source: Node3D) -> PackedVector3Array:
|
||||
var result := PackedVector3Array()
|
||||
if source is CollisionShape3D:
|
||||
var collision_shape := source as CollisionShape3D
|
||||
var concave_shape := collision_shape.shape as ConcavePolygonShape3D
|
||||
if concave_shape == null:
|
||||
return result
|
||||
for vertex: Vector3 in concave_shape.get_faces():
|
||||
result.append(_to_map_space(source, vertex))
|
||||
return result
|
||||
if source is MeshInstance3D:
|
||||
var mesh_instance := source as MeshInstance3D
|
||||
if mesh_instance.mesh == null:
|
||||
return result
|
||||
for surface: int in mesh_instance.mesh.get_surface_count():
|
||||
var arrays := mesh_instance.mesh.surface_get_arrays(surface)
|
||||
var vertices := arrays[Mesh.ARRAY_VERTEX] as PackedVector3Array
|
||||
var indices := arrays[Mesh.ARRAY_INDEX] as PackedInt32Array
|
||||
if indices.is_empty():
|
||||
for vertex: Vector3 in vertices:
|
||||
result.append(_to_map_space(source, vertex))
|
||||
else:
|
||||
for index: int in indices:
|
||||
result.append(_to_map_space(source, vertices[index]))
|
||||
return result
|
||||
|
||||
|
||||
func _to_map_space(source: Node3D, vertex: Vector3) -> Vector3:
|
||||
var map_root := get_parent() as Node3D
|
||||
if map_root == null:
|
||||
return source.to_global(vertex)
|
||||
return map_root.to_local(source.to_global(vertex))
|
||||
Loading…
Add table
Add a link
Reference in a new issue