Generate complete freshwater and elevated terrain features
This commit is contained in:
parent
219c462181
commit
f2b96f2c1f
7 changed files with 2105 additions and 141 deletions
|
|
@ -356,7 +356,14 @@ func _configure_fresh_water(records: Array[Dictionary]) -> void:
|
|||
var surface_size: Vector2 = record.get(
|
||||
"water_surface_size", Vector2.ZERO
|
||||
)
|
||||
if surface_size.x <= 0.0 or surface_size.y <= 0.0:
|
||||
var surface_polygon: PackedVector2Array = record.get(
|
||||
"water_surface_polygon",
|
||||
PackedVector2Array(),
|
||||
)
|
||||
if (
|
||||
(surface_size.x <= 0.0 or surface_size.y <= 0.0)
|
||||
and surface_polygon.size() < 3
|
||||
):
|
||||
continue
|
||||
var body := WATER_BODY_SCENE.instantiate() as WaterBodyAuthoring
|
||||
if body == null:
|
||||
|
|
@ -379,7 +386,10 @@ func _configure_fresh_water(records: Array[Dictionary]) -> void:
|
|||
+ Vector3.UP * WATER_HEIGHT
|
||||
)
|
||||
body.rotation.y = angle
|
||||
body.surface_size = surface_size
|
||||
if surface_polygon.size() >= 3:
|
||||
body.surface_polygon = surface_polygon
|
||||
else:
|
||||
body.surface_size = surface_size
|
||||
body.visual_surface_enabled = false
|
||||
body.water_material = _fresh_water_material()
|
||||
body.water_type = WaterType.Type.FRESH_WATER
|
||||
|
|
@ -394,6 +404,8 @@ func _fresh_water_location_tags(tags: PackedStringArray) -> Array[StringName]:
|
|||
result.append(&"pond")
|
||||
if "river" in tags:
|
||||
result.append(&"river")
|
||||
if "lake" in tags:
|
||||
result.append(&"lake")
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -31,16 +31,23 @@ gatherable_anchor_root = NodePath("GatherableAnchors")
|
|||
unique_name_in_owner = true
|
||||
script = ExtResource("2_generator")
|
||||
catalog = ExtResource("3_catalog")
|
||||
grid_size = Vector2i(15, 13)
|
||||
grid_size = Vector2i(20, 20)
|
||||
generation_seed = 13001
|
||||
generate_on_ready = false
|
||||
build_collision = true
|
||||
force_center_chunk_id = &"chunk_spawn"
|
||||
required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004")
|
||||
grass_sand_smoothing_enabled = true
|
||||
lake_feature_enabled = true
|
||||
lake_minimum_size = 7
|
||||
lake_maximum_size = 8
|
||||
river_feature_enabled = true
|
||||
river_minimum_length = 7
|
||||
river_maximum_length = 8
|
||||
elevated_cliff_feature_enabled = true
|
||||
elevated_cliff_coastal_feature_required = true
|
||||
required_chunk_weight_multiplier = 64.0
|
||||
maximum_backtracks = 100000
|
||||
maximum_backtracks = 1000
|
||||
|
||||
[node name="WaterBodies" type="Node3D" parent="."]
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -3,6 +3,7 @@ class_name WaterBodyAuthoring
|
|||
extends Node3D
|
||||
|
||||
const FishPoolType = preload("res://fish/fish_pool.gd")
|
||||
const GENERATED_POLYGON_SHAPE_META := &"water_body_generated_polygon_shape"
|
||||
|
||||
@export_group("Surface")
|
||||
## Visible water footprint in meters.
|
||||
|
|
@ -14,6 +15,13 @@ var surface_size: Vector2 = Vector2(10.0, 10.0):
|
|||
set(value):
|
||||
surface_size = Vector2(maxf(value.x, 0.1), maxf(value.y, 0.1))
|
||||
_sync_owned_nodes()
|
||||
## Optional exact local X/Z coverage. Three or more points replace the box
|
||||
## collision footprint with triangulated, vertically extruded volumes. The
|
||||
## visible PlaneMesh remains controlled by surface_size.
|
||||
@export var surface_polygon := PackedVector2Array():
|
||||
set(value):
|
||||
surface_polygon = value
|
||||
_sync_owned_nodes()
|
||||
## Derive fishing coverage from VisualWater's mesh bounds instead of resizing
|
||||
## the mesh from Surface Size. Use this for imported or shared water surfaces.
|
||||
@export var derive_coverage_from_visual_mesh: bool = false:
|
||||
|
|
@ -97,6 +105,7 @@ func _sync_owned_nodes() -> void:
|
|||
plane_mesh.size = surface_size
|
||||
visual_water.material_override = water_material
|
||||
var surface_footprint: Rect2 = _resolve_surface_footprint(visual_water)
|
||||
var uses_polygon := surface_polygon.size() >= 3
|
||||
|
||||
var fishing_shape_node := (
|
||||
get_node_or_null(fishing_shape_path) as CollisionShape3D
|
||||
|
|
@ -112,9 +121,19 @@ func _sync_owned_nodes() -> void:
|
|||
fishing_region.surface_height_mode = (
|
||||
FishableWaterRegion.SurfaceHeightMode.PARENT_GLOBAL_Y
|
||||
)
|
||||
if fishing_shape_node != null:
|
||||
var fishing_shape := fishing_shape_node.shape as BoxShape3D
|
||||
if fishing_shape != null:
|
||||
if fishing_shape_node != null and fishing_region != null:
|
||||
if uses_polygon and _sync_polygon_collision_shapes(
|
||||
fishing_region,
|
||||
fishing_shape_node,
|
||||
fishing_depth,
|
||||
):
|
||||
pass
|
||||
else:
|
||||
_clear_generated_polygon_shapes(fishing_region)
|
||||
var fishing_shape := fishing_shape_node.shape as BoxShape3D
|
||||
if fishing_shape == null:
|
||||
fishing_shape = BoxShape3D.new()
|
||||
fishing_shape_node.shape = fishing_shape
|
||||
# The authored root is the one water-surface height. Keep the fishable
|
||||
# volume entirely below that plane so visible water and interaction
|
||||
# can be moved together without a second height to tune.
|
||||
|
|
@ -133,18 +152,28 @@ func _sync_owned_nodes() -> void:
|
|||
var recovery_region := (
|
||||
get_node_or_null(recovery_region_path) as Area3D
|
||||
)
|
||||
if recovery_region != null:
|
||||
recovery_region.position = Vector3(
|
||||
surface_footprint.get_center().x,
|
||||
-recovery_depth * 0.5,
|
||||
surface_footprint.get_center().y,
|
||||
)
|
||||
var recovery_shape_node := (
|
||||
get_node_or_null(recovery_shape_path) as CollisionShape3D
|
||||
)
|
||||
if recovery_shape_node != null:
|
||||
var recovery_shape := recovery_shape_node.shape as BoxShape3D
|
||||
if recovery_shape != null:
|
||||
if recovery_region != null and recovery_shape_node != null:
|
||||
if uses_polygon and _sync_polygon_collision_shapes(
|
||||
recovery_region,
|
||||
recovery_shape_node,
|
||||
recovery_depth,
|
||||
):
|
||||
recovery_region.position = Vector3.ZERO
|
||||
else:
|
||||
_clear_generated_polygon_shapes(recovery_region)
|
||||
recovery_region.position = Vector3(
|
||||
surface_footprint.get_center().x,
|
||||
-recovery_depth * 0.5,
|
||||
surface_footprint.get_center().y,
|
||||
)
|
||||
recovery_shape_node.position = Vector3.ZERO
|
||||
var recovery_shape := recovery_shape_node.shape as BoxShape3D
|
||||
if recovery_shape == null:
|
||||
recovery_shape = BoxShape3D.new()
|
||||
recovery_shape_node.shape = recovery_shape
|
||||
recovery_shape.size = Vector3(
|
||||
surface_footprint.size.x,
|
||||
recovery_depth,
|
||||
|
|
@ -155,6 +184,57 @@ func _sync_owned_nodes() -> void:
|
|||
update_configuration_warnings()
|
||||
|
||||
|
||||
func _sync_polygon_collision_shapes(
|
||||
region: CollisionObject3D,
|
||||
primary_shape_node: CollisionShape3D,
|
||||
depth: float,
|
||||
) -> bool:
|
||||
var triangle_indices := Geometry2D.triangulate_polygon(surface_polygon)
|
||||
if triangle_indices.size() < 3:
|
||||
return false
|
||||
_clear_generated_polygon_shapes(region)
|
||||
for offset: int in range(0, triangle_indices.size(), 3):
|
||||
var shape_node := primary_shape_node
|
||||
if offset > 0:
|
||||
shape_node = CollisionShape3D.new()
|
||||
shape_node.name = "GeneratedPolygonShape%d" % floori(
|
||||
float(offset) / 3.0
|
||||
)
|
||||
shape_node.set_meta(GENERATED_POLYGON_SHAPE_META, true)
|
||||
region.add_child(shape_node)
|
||||
shape_node.position = Vector3.ZERO
|
||||
shape_node.disabled = false
|
||||
shape_node.shape = _extruded_triangle_shape(
|
||||
triangle_indices,
|
||||
offset,
|
||||
depth,
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
func _extruded_triangle_shape(
|
||||
triangle_indices: PackedInt32Array,
|
||||
offset: int,
|
||||
depth: float,
|
||||
) -> ConvexPolygonShape3D:
|
||||
var points := PackedVector3Array()
|
||||
for point_offset: int in 3:
|
||||
var point := surface_polygon[triangle_indices[offset + point_offset]]
|
||||
points.append(Vector3(point.x, 0.0, point.y))
|
||||
points.append(Vector3(point.x, -depth, point.y))
|
||||
var shape := ConvexPolygonShape3D.new()
|
||||
shape.points = points
|
||||
return shape
|
||||
|
||||
|
||||
func _clear_generated_polygon_shapes(region: CollisionObject3D) -> void:
|
||||
for child: Node in region.get_children():
|
||||
if not child.has_meta(GENERATED_POLYGON_SHAPE_META):
|
||||
continue
|
||||
region.remove_child(child)
|
||||
child.free()
|
||||
|
||||
|
||||
func _resolve_surface_footprint(visual_water: MeshInstance3D) -> Rect2:
|
||||
var authored_footprint := Rect2(-surface_size * 0.5, surface_size)
|
||||
if (
|
||||
|
|
@ -202,7 +282,14 @@ func _get_configuration_warnings() -> PackedStringArray:
|
|||
"VisualWater must provide a PlaneMesh when coverage is authored by size."
|
||||
)
|
||||
var fishing_shape := get_node_or_null(fishing_shape_path) as CollisionShape3D
|
||||
if fishing_shape == null or not fishing_shape.shape is BoxShape3D:
|
||||
if fishing_shape == null:
|
||||
warnings.append("FishingRegion must provide a collision shape.")
|
||||
elif surface_polygon.size() >= 3:
|
||||
if not fishing_shape.shape is ConvexPolygonShape3D:
|
||||
warnings.append(
|
||||
"FishingRegion must provide polygon coverage for Surface Polygon."
|
||||
)
|
||||
elif not fishing_shape.shape is BoxShape3D:
|
||||
warnings.append("FishingRegion must provide a BoxShape3D.")
|
||||
if manage_recovery_coverage:
|
||||
var recovery_region := (
|
||||
|
|
@ -220,7 +307,14 @@ func _get_configuration_warnings() -> PackedStringArray:
|
|||
var recovery_shape := (
|
||||
get_node_or_null(recovery_shape_path) as CollisionShape3D
|
||||
)
|
||||
if recovery_shape == null or not recovery_shape.shape is BoxShape3D:
|
||||
if recovery_shape == null:
|
||||
warnings.append("RecoveryRegion must provide a collision shape.")
|
||||
elif surface_polygon.size() >= 3:
|
||||
if not recovery_shape.shape is ConvexPolygonShape3D:
|
||||
warnings.append(
|
||||
"RecoveryRegion must provide polygon coverage for Surface Polygon."
|
||||
)
|
||||
elif not recovery_shape.shape is BoxShape3D:
|
||||
warnings.append("RecoveryRegion must provide a BoxShape3D.")
|
||||
var fishing_region := get_node_or_null(
|
||||
fishing_region_path
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue