Refactor fishing surfaces around authored water
This commit is contained in:
parent
9f52620a73
commit
9afa60f6a5
11 changed files with 198 additions and 172 deletions
|
|
@ -14,6 +14,12 @@ 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()
|
||||
## 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:
|
||||
set(value):
|
||||
derive_coverage_from_visual_mesh = value
|
||||
_sync_owned_nodes()
|
||||
## Material applied to the visible water surface.
|
||||
@export var water_material: Material:
|
||||
set(value):
|
||||
|
|
@ -44,6 +50,11 @@ var fishing_depth: float = 4.0:
|
|||
_sync_owned_nodes()
|
||||
|
||||
@export_group("Recovery Coverage")
|
||||
## Keep recovery coverage independent when a water body uses custom volumes.
|
||||
@export var manage_recovery_coverage: bool = true:
|
||||
set(value):
|
||||
manage_recovery_coverage = value
|
||||
_sync_owned_nodes()
|
||||
@export_range(0.1, 20.0, 0.1, "or_greater", "suffix:m")
|
||||
var recovery_depth: float = 4.8:
|
||||
set(value):
|
||||
|
|
@ -75,9 +86,10 @@ func _sync_owned_nodes() -> void:
|
|||
var visual_water := get_node_or_null(visual_water_path) as MeshInstance3D
|
||||
if visual_water != null:
|
||||
var plane_mesh := visual_water.mesh as PlaneMesh
|
||||
if plane_mesh != null:
|
||||
if plane_mesh != null and not derive_coverage_from_visual_mesh:
|
||||
plane_mesh.size = surface_size
|
||||
visual_water.material_override = water_material
|
||||
var surface_footprint: Rect2 = _resolve_surface_footprint(visual_water)
|
||||
|
||||
var fishing_shape_node := (
|
||||
get_node_or_null(fishing_shape_path) as CollisionShape3D
|
||||
|
|
@ -99,34 +111,73 @@ func _sync_owned_nodes() -> void:
|
|||
# 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.
|
||||
fishing_shape_node.position.y = -fishing_depth * 0.5
|
||||
fishing_shape_node.position = Vector3(
|
||||
surface_footprint.get_center().x,
|
||||
-fishing_depth * 0.5,
|
||||
surface_footprint.get_center().y,
|
||||
)
|
||||
fishing_shape.size = Vector3(
|
||||
surface_size.x,
|
||||
surface_footprint.size.x,
|
||||
fishing_depth,
|
||||
surface_size.y
|
||||
surface_footprint.size.y,
|
||||
)
|
||||
|
||||
var recovery_region := (
|
||||
get_node_or_null(recovery_region_path) as Area3D
|
||||
)
|
||||
if recovery_region != null:
|
||||
recovery_region.position.y = -recovery_depth * 0.5
|
||||
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:
|
||||
recovery_shape.size = Vector3(
|
||||
surface_size.x,
|
||||
recovery_depth,
|
||||
surface_size.y
|
||||
if manage_recovery_coverage:
|
||||
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:
|
||||
recovery_shape.size = Vector3(
|
||||
surface_footprint.size.x,
|
||||
recovery_depth,
|
||||
surface_footprint.size.y,
|
||||
)
|
||||
|
||||
if Engine.is_editor_hint():
|
||||
update_configuration_warnings()
|
||||
|
||||
|
||||
func _resolve_surface_footprint(visual_water: MeshInstance3D) -> Rect2:
|
||||
var authored_footprint := Rect2(-surface_size * 0.5, surface_size)
|
||||
if (
|
||||
not derive_coverage_from_visual_mesh
|
||||
or visual_water == null
|
||||
or visual_water.mesh == null
|
||||
):
|
||||
return authored_footprint
|
||||
var mesh_bounds: AABB = visual_water.mesh.get_aabb()
|
||||
if mesh_bounds.size.x <= 0.0 or mesh_bounds.size.z <= 0.0:
|
||||
return authored_footprint
|
||||
var visual_to_body: Transform3D = (
|
||||
global_transform.affine_inverse() * visual_water.global_transform
|
||||
)
|
||||
var minimum := Vector2(INF, INF)
|
||||
var maximum := Vector2(-INF, -INF)
|
||||
for corner_index: int in 8:
|
||||
var corner := mesh_bounds.position + Vector3(
|
||||
mesh_bounds.size.x if (corner_index & 1) != 0 else 0.0,
|
||||
mesh_bounds.size.y if (corner_index & 2) != 0 else 0.0,
|
||||
mesh_bounds.size.z if (corner_index & 4) != 0 else 0.0,
|
||||
)
|
||||
var body_corner: Vector3 = visual_to_body * corner
|
||||
minimum.x = minf(minimum.x, body_corner.x)
|
||||
minimum.y = minf(minimum.y, body_corner.z)
|
||||
maximum.x = maxf(maximum.x, body_corner.x)
|
||||
maximum.y = maxf(maximum.y, body_corner.z)
|
||||
return Rect2(minimum, maximum - minimum)
|
||||
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
var warnings := PackedStringArray()
|
||||
if not scale.is_equal_approx(Vector3.ONE):
|
||||
|
|
@ -134,28 +185,36 @@ func _get_configuration_warnings() -> PackedStringArray:
|
|||
"Keep root scale at 1; resize water with Surface Size."
|
||||
)
|
||||
var visual_water := get_node_or_null(visual_water_path) as MeshInstance3D
|
||||
if visual_water == null or not visual_water.mesh is PlaneMesh:
|
||||
warnings.append("VisualWater must provide a PlaneMesh.")
|
||||
if visual_water == null or visual_water.mesh == null:
|
||||
warnings.append("VisualWater must provide a mesh.")
|
||||
elif (
|
||||
not derive_coverage_from_visual_mesh
|
||||
and not visual_water.mesh is PlaneMesh
|
||||
):
|
||||
warnings.append(
|
||||
"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:
|
||||
warnings.append("FishingRegion must provide a BoxShape3D.")
|
||||
var recovery_region := (
|
||||
get_node_or_null(recovery_region_path) as PlayerWaterTrigger
|
||||
)
|
||||
if recovery_region == null:
|
||||
warnings.append("RecoveryRegion must use PlayerWaterTrigger.")
|
||||
elif (
|
||||
recovery_region.surface_height_mode
|
||||
!= PlayerWaterTrigger.SurfaceHeightMode.PARENT_GLOBAL_Y
|
||||
):
|
||||
warnings.append(
|
||||
"RecoveryRegion must derive surface height from its parent."
|
||||
if manage_recovery_coverage:
|
||||
var recovery_region := (
|
||||
get_node_or_null(recovery_region_path) as PlayerWaterTrigger
|
||||
)
|
||||
var recovery_shape := (
|
||||
get_node_or_null(recovery_shape_path) as CollisionShape3D
|
||||
)
|
||||
if recovery_shape == null or not recovery_shape.shape is BoxShape3D:
|
||||
warnings.append("RecoveryRegion must provide a BoxShape3D.")
|
||||
if recovery_region == null:
|
||||
warnings.append("RecoveryRegion must use PlayerWaterTrigger.")
|
||||
elif (
|
||||
recovery_region.surface_height_mode
|
||||
!= PlayerWaterTrigger.SurfaceHeightMode.PARENT_GLOBAL_Y
|
||||
):
|
||||
warnings.append(
|
||||
"RecoveryRegion must derive surface height from its parent."
|
||||
)
|
||||
var recovery_shape := (
|
||||
get_node_or_null(recovery_shape_path) as CollisionShape3D
|
||||
)
|
||||
if recovery_shape == null or not recovery_shape.shape is BoxShape3D:
|
||||
warnings.append("RecoveryRegion must provide a BoxShape3D.")
|
||||
var fishing_region := get_node_or_null(
|
||||
fishing_region_path
|
||||
) as FishableWaterRegion
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue