diff --git a/overworld/camera/OverworldCamera.gd b/overworld/camera/OverworldCamera.gd index e992bae..f32fb18 100644 --- a/overworld/camera/OverworldCamera.gd +++ b/overworld/camera/OverworldCamera.gd @@ -8,10 +8,10 @@ const COLLISION_MARGIN:float = 0.3 @export_category("Orbit") @export var distance:float = 7.0 -@export var minDistance:float = 1.5 +@export var minDistance:float = 5.0 @export var orbitSensitivity:float = 144.0 @export var orbitAcceleration:float = 600.0 -@export var pitchMin:float = -10.0 +@export var pitchMin:float = -80.0 @export var pitchMax:float = 70.0 @export_category("Mouse") @@ -24,19 +24,14 @@ const COLLISION_MARGIN:float = 0.3 @export_category("Collision") @export_flags_3d_physics var collisionMask:int = 1 -@export var distanceReturnSpeed:float = 5.0 var _yaw:float = 0.0 var _pitch:float = 30.0 -var _currentDistance:float = 0.0 var _camVelocity:Vector2 = Vector2.ZERO var _idleTimer:float = 0.0 var _mouseDelta:Vector2 = Vector2.ZERO var _rightMouseHeld:bool = false -func _ready() -> void: - _currentDistance = distance - func _input(event:InputEvent) -> void: if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT: _rightMouseHeld = event.pressed @@ -107,25 +102,52 @@ func _process(delta:float) -> void: cos(yawRad) * cos(pitchRad) ) - # Snap in instantly when terrain blocks; restore smoothly when clear - var collisionDist:float = _resolveDistance(pivot, dir) - if collisionDist < _currentDistance: - _currentDistance = collisionDist - else: - _currentDistance = move_toward(_currentDistance, distance, distanceReturnSpeed * delta) - - global_transform.origin = pivot + dir * _currentDistance + var finalPos:Vector3 = _resolvePosition(pivot, dir) + global_transform.origin = finalPos look_at(pivot, Vector3.UP) -# Ray from pivot outward; returns the safe camera distance along dir. -func _resolveDistance(pivot:Vector3, dir:Vector3) -> float: + # Feed actual position back so camera input resumes from where it really is + var actualDir:Vector3 = (finalPos - pivot).normalized() + _pitch = rad_to_deg(asin(clamp(actualDir.y, -1.0, 1.0))) + _yaw = rad_to_deg(atan2(actualDir.x, actualDir.z)) + +# Returns the camera world position, respecting both minDistance and terrain. +func _resolvePosition(pivot:Vector3, dir:Vector3) -> Vector3: + var desired:Vector3 = pivot + dir * distance var space:PhysicsDirectSpaceState3D = get_world_3d().direct_space_state - var query:PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(pivot, pivot + dir * distance) + var query:PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(pivot, desired) query.collision_mask = collisionMask - var hit:Dictionary = space.intersect_ray(query) - if hit.is_empty(): - return distance + if hit.is_empty(): + return desired + + var hitNormal:Vector3 = hit["normal"] var hitDist:float = (hit["position"] - pivot).length() - COLLISION_MARGIN - return maxf(hitDist, minDistance) + + if hitDist >= minDistance: + # Terrain is beyond minDistance — simple pull-back along the ray + return pivot + dir * hitDist + + # Terrain is closer than minDistance. + # Find the closest point on the circle formed by intersecting: + # • the minDistance sphere centred on pivot + # • the terrain surface plane (inset by COLLISION_MARGIN along its normal) + # That point is exactly minDistance from the pivot and flush with the surface. + var d:float = hitNormal.dot(pivot - hit["position"]) - COLLISION_MARGIN + var rSq:float = minDistance * minDistance - d * d + if rSq <= 0.0: + # Pivot is itself nearly on the terrain — sit at the tangent point + return pivot - hitNormal * d + + var circleCenter:Vector3 = pivot - hitNormal * d + var circleRadius:float = sqrt(rSq) + + # Project desired onto the terrain plane to get the on-plane direction + var toDesired:Vector3 = desired - circleCenter + toDesired -= hitNormal * hitNormal.dot(toDesired) + if toDesired.length_squared() < 0.0001: + # Desired is directly along the normal — any circle point is equally good + return circleCenter + + return circleCenter + toDesired.normalized() * circleRadius