108 lines
2.6 KiB
GDScript
108 lines
2.6 KiB
GDScript
extends Interactable
|
|
class_name Lever
|
|
|
|
@export var body: RigidBody3D
|
|
@export var hinge: HingeJoint3D
|
|
|
|
@export var angleOn: float = -45.0
|
|
@export var angleOff: float = 0.0
|
|
|
|
@export var snapStrength: float = 200.0
|
|
@export var maxGrabDistance: float = 4.0
|
|
@export var mouseForce: float = 0.015
|
|
|
|
var dragging := false
|
|
var grabLocal := Vector3.ZERO
|
|
var handOffset := Vector3.ZERO
|
|
var targetAngle := 0.0
|
|
|
|
func _ready():
|
|
hinge.set_param(HingeJoint3D.PARAM_LIMIT_LOWER, deg_to_rad(angleOn))
|
|
hinge.set_param(HingeJoint3D.PARAM_LIMIT_UPPER, deg_to_rad(angleOff))
|
|
targetAngle = angleOff
|
|
|
|
|
|
func _physics_process(delta):
|
|
if dragging:
|
|
return
|
|
|
|
var angle = rad_to_deg(get_hinge_angle())
|
|
|
|
# Snap to nearest end
|
|
var mid = (angleOn + angleOff) * 0.5
|
|
if angle < mid:
|
|
targetAngle = angleOn
|
|
else:
|
|
targetAngle = angleOff
|
|
|
|
var angleError = deg_to_rad(targetAngle) - get_hinge_angle()
|
|
|
|
# HARD SNAP — no spring
|
|
var axis = hinge.global_transform.basis.x
|
|
var torque = snapStrength * sign(angleError)
|
|
|
|
body.apply_torque(axis * torque)
|
|
|
|
|
|
func beginInteract(player, hitPosition: Vector3):
|
|
dragging = true
|
|
handOffset = Vector3.ZERO
|
|
grabLocal = body.to_local(hitPosition)
|
|
|
|
|
|
func updateInteract(player):
|
|
if !dragging:
|
|
return
|
|
|
|
var worldGrab = body.to_global(grabLocal)
|
|
|
|
# Only vertical mouse movement (Y)
|
|
handOffset += -player.camera.global_basis.y * player.interactionMouseDelta.y * mouseForce
|
|
handOffset = handOffset.limit_length(0.25)
|
|
|
|
var targetHand = player.hand.global_position + handOffset
|
|
var error = targetHand - worldGrab
|
|
|
|
var handDistance = player.hand.global_position.distance_to(worldGrab)
|
|
if handDistance > maxGrabDistance:
|
|
endInteract(player)
|
|
player.dragging = false
|
|
return
|
|
|
|
var offset = worldGrab - body.global_position
|
|
var pointVelocity = body.linear_velocity + body.angular_velocity.cross(offset)
|
|
|
|
# Correct hinge axis (X)
|
|
var hingeAxis = body.global_basis.x
|
|
var tangent = hingeAxis.cross(offset)
|
|
if tangent.length_squared() < 0.0001:
|
|
return
|
|
tangent = tangent.normalized()
|
|
|
|
var tangentError = tangent * error.dot(tangent)
|
|
var tangentVelocity = tangent * pointVelocity.dot(tangent)
|
|
|
|
var force = tangentError * snapStrength
|
|
body.apply_force(force, offset)
|
|
|
|
|
|
func endInteract(player):
|
|
dragging = false
|
|
handOffset = Vector3.ZERO
|
|
|
|
|
|
func get_hinge_angle() -> float:
|
|
var body_basis = body.global_transform.basis
|
|
var hinge_basis = hinge.global_transform.basis
|
|
|
|
# X-axis rotation → use UP vector
|
|
var up = body_basis.y
|
|
var local_up = hinge_basis.inverse() * up
|
|
|
|
return atan2(local_up.z, local_up.y)
|
|
|
|
|
|
func get_hinge_ang_vel() -> float:
|
|
var axis = hinge.global_transform.basis.x
|
|
return body.angular_velocity.dot(axis)
|