Fix casting beneath overhangs

This commit is contained in:
Alexander Sellite 2026-08-09 12:24:11 -04:00
parent 40696b6bd9
commit deab51a18a
4 changed files with 46 additions and 8 deletions

View file

@ -56,6 +56,7 @@ var _rod: Node3D
var _rod_tip: Marker3D var _rod_tip: Marker3D
var _rod_neutral_rotation: Vector3 var _rod_neutral_rotation: Vector3
var _cast_arrival_position: Vector3 var _cast_arrival_position: Vector3
var _active_cast_arc_height: float = 0.0
var _cast_tracks_water_surface: bool = false var _cast_tracks_water_surface: bool = false
var _bobber_surface_position: Vector3 var _bobber_surface_position: Vector3
var _bobber_idle_elapsed: float = 0.0 var _bobber_idle_elapsed: float = 0.0
@ -153,6 +154,7 @@ func begin_cast(
target: Vector3, target: Vector3,
blocked_landing_position: Vector3 = Vector3.ZERO, blocked_landing_position: Vector3 = Vector3.ZERO,
cast_is_blocked: bool = false, cast_is_blocked: bool = false,
resolved_arc_height: float = -1.0,
) -> void: ) -> void:
if _mode != VisualMode.AIMING or _rod_tip == null: if _mode != VisualMode.AIMING or _rod_tip == null:
return return
@ -165,6 +167,11 @@ func begin_cast(
_cast_arrival_position = ( _cast_arrival_position = (
blocked_landing_position if cast_is_blocked else target blocked_landing_position if cast_is_blocked else target
) )
_active_cast_arc_height = (
maxf(resolved_arc_height, 0.0)
if resolved_arc_height >= 0.0
else cast_arc_height
)
_cast_tracks_water_surface = not cast_is_blocked _cast_tracks_water_surface = not cast_is_blocked
var cast_start: Vector3 = _rod_tip.global_position var cast_start: Vector3 = _rod_tip.global_position
@ -302,6 +309,7 @@ func cleanup() -> void:
_bobber_base_scale = Vector3.ONE _bobber_base_scale = Vector3.ONE
_bobber.rotation = Vector3.ZERO _bobber.rotation = Vector3.ZERO
_cast_arrival_position = Vector3.ZERO _cast_arrival_position = Vector3.ZERO
_active_cast_arc_height = 0.0
_cast_tracks_water_surface = false _cast_tracks_water_surface = false
_bobber_surface_position = Vector3.ZERO _bobber_surface_position = Vector3.ZERO
_bobber_idle_elapsed = 0.0 _bobber_idle_elapsed = 0.0
@ -319,7 +327,7 @@ func _set_cast_sample(
target: Vector3, target: Vector3,
) -> void: ) -> void:
var sample: Vector3 = start.lerp(target, progress) var sample: Vector3 = start.lerp(target, progress)
sample.y += sin(progress * PI) * cast_arc_height sample.y += sin(progress * PI) * _active_cast_arc_height
if _cast_tracks_water_surface: if _cast_tracks_water_surface:
sample.y += ( sample.y += (
WaterSurfaceMotionType.get_default_height_offset() WaterSurfaceMotionType.get_default_height_offset()

View file

@ -783,19 +783,31 @@ func _confirm_cast() -> void:
return return
var cast_launch_position := _get_cast_launch_position() var cast_launch_position := _get_cast_launch_position()
_cast_path_is_clear = is_cast_path_clear( var resolved_cast_arc: Dictionary = _surface_resolver.resolve_cast_arc(
get_world_3d().direct_space_state,
cast_launch_position, cast_launch_position,
_cast_target, _cast_target,
_presentation.cast_arc_height,
) )
var resolved_arc_height: float = float(
resolved_cast_arc.get("arc_height", _presentation.cast_arc_height)
)
var resolved_cast_collision: Dictionary = resolved_cast_arc.get(
"collision",
{},
)
_cast_path_is_clear = resolved_cast_collision.is_empty()
var cast_is_invalid: bool = ( var cast_is_invalid: bool = (
not _aim_surface_sample.is_fishable() or not _cast_path_is_clear not _aim_surface_sample.is_fishable() or not _cast_path_is_clear
) )
var arrival_position: Vector3 = _cast_target var arrival_position: Vector3 = _cast_target
if not _cast_path_is_clear: if not _cast_path_is_clear:
arrival_position = _resolve_cast_impact_position( var collision_position: Variant = resolved_cast_collision.get(
cast_launch_position, "position",
_cast_target, _cast_target,
) )
if typeof(collision_position) == TYPE_VECTOR3:
arrival_position = collision_position
if _active_player != null: if _active_player != null:
# Movement is available while aiming, then locks once the cast is # Movement is available while aiming, then locks once the cast is
# released so the active bobber/fishing event remains anchored. # released so the active bobber/fishing event remains anchored.
@ -806,6 +818,7 @@ func _confirm_cast() -> void:
_cast_target, _cast_target,
arrival_position, arrival_position,
cast_is_invalid, cast_is_invalid,
resolved_arc_height,
) )
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT) _presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
@ -1410,6 +1423,7 @@ func resolve_fishing_surface(
get_world_3d().direct_space_state, get_world_3d().direct_space_state,
target, target,
resolved_reference_y, resolved_reference_y,
water_occlusion_tolerance,
) )

View file

@ -23,6 +23,7 @@ func resolve_surface(
space_state: PhysicsDirectSpaceState3D, space_state: PhysicsDirectSpaceState3D,
query_position: Vector3, query_position: Vector3,
reference_y: float, reference_y: float,
maximum_solid_rise: float = INF,
) -> FishingSurfaceSampleType: ) -> FishingSurfaceSampleType:
var sample := FishingSurfaceSampleType.new() var sample := FishingSurfaceSampleType.new()
var ray_top: float = maxf(query_position.y, reference_y) + ray_start_height var ray_top: float = maxf(query_position.y, reference_y) + ray_start_height
@ -33,12 +34,18 @@ func resolve_surface(
ray_top, ray_top,
ray_bottom, ray_bottom,
) )
var solid_hit: Dictionary = _find_top_solid_surface( var solid_ray_top: float = minf(
space_state,
query_position,
ray_top, ray_top,
ray_bottom, reference_y + maximum_solid_rise,
) )
var solid_hit: Dictionary = {}
if solid_ray_top > ray_bottom:
solid_hit = _find_top_solid_surface(
space_state,
query_position,
solid_ray_top,
ray_bottom,
)
if water_region != null: if water_region != null:
var water_height: float = water_region.get_surface_height() var water_height: float = water_region.get_surface_height()

View file

@ -50,6 +50,7 @@ func _validate_resolver_layers() -> void:
_add_solid_box(world, Vector3(40.0, 2.0, 12.0), Vector3(10.0, -1.0, 0.0)) _add_solid_box(world, Vector3(40.0, 2.0, 12.0), Vector3(10.0, -1.0, 0.0))
_add_water_region(world, Vector3(0.0, 2.0, 0.0), Vector2(8.0, 8.0), PondPool) _add_water_region(world, Vector3(0.0, 2.0, 0.0), Vector2(8.0, 8.0), PondPool)
_add_solid_box(world, Vector3(1.0, 1.0, 1.0), Vector3(2.0, 2.25, 0.0)) _add_solid_box(world, Vector3(1.0, 1.0, 1.0), Vector3(2.0, 2.25, 0.0))
_add_solid_box(world, Vector3(1.0, 1.0, 1.0), Vector3(-2.0, 6.0, 0.0))
_add_water_region(world, Vector3(10.0, 5.0, 0.0), Vector2(6.0, 6.0), PondPool) _add_water_region(world, Vector3(10.0, 5.0, 0.0), Vector2(6.0, 6.0), PondPool)
_add_water_region(world, Vector3(20.0, 3.0, 0.0), Vector2(6.0, 6.0), null) _add_water_region(world, Vector3(20.0, 3.0, 0.0), Vector2(6.0, 6.0), null)
await physics_frame await physics_frame
@ -64,6 +65,14 @@ func _validate_resolver_layers() -> void:
) )
assert(water.is_fishable()) assert(water.is_fishable())
assert(is_equal_approx(water.position.y, 2.0)) assert(is_equal_approx(water.position.y, 2.0))
var water_under_overhang: FishingSurfaceSampleType = resolver.resolve_surface(
space_state,
Vector3(-2.0, 4.0, 0.0),
4.0,
0.08,
)
assert(water_under_overhang.is_fishable())
assert(is_equal_approx(water_under_overhang.position.y, 2.0))
var covered_water: FishingSurfaceSampleType = resolver.resolve_surface( var covered_water: FishingSurfaceSampleType = resolver.resolve_surface(
space_state, space_state,