Files
godot-interaction-system/Scripts/Door.gd
T

136 lines
3.8 KiB
GDScript

extends Interactable
class_name Door
@export var body: RigidBody3D
@export var springStrength: float = 200.0
@export var springDamping: float= 50.0
@export var maxGrabDistance: float = 6.0
@export var mouseForce: float = 0.02
@export var hinge: HingeJoint3D
@export var hingeAngularUpperLimit: float = 120
@export var hingeAngularLowerLimit: float = -120
@export var snapAngleThreshold: float = 1
@export var snapVelocityThreshold: float = 5.0
@export var mouseHorizontalFactor: float = 1.0
@export var mouseVerticalFactor: float = 1.0
var dragging: bool = false
var grabLocal: Vector3 = Vector3.ZERO
var handOffset: Vector3 = Vector3.ZERO
func _ready() -> void:
hinge.set_param(HingeJoint3D.PARAM_LIMIT_LOWER, deg_to_rad(hingeAngularLowerLimit))
hinge.set_param(HingeJoint3D.PARAM_LIMIT_UPPER, deg_to_rad(hingeAngularUpperLimit))
func _physics_process(_delta):
if dragging:
return
var angle = get_hinge_angle()
var ang_vel = get_hinge_ang_vel()
if abs(rad_to_deg(angle)) > snapAngleThreshold:
return
if abs(ang_vel) > snapVelocityThreshold:
return
# Strong damping near closed position
var damping = springDamping * 4.0
# PD controller torque toward angle 0
var torque = -angle * springStrength - ang_vel * damping
var axis = hinge.global_transform.basis.y
body.apply_torque(axis * torque)
func beginInteract(player, hitPosition: Vector3):
dragging = true
handOffset = Vector3.ZERO
grabLocal = body.to_local(hitPosition)
player.setMouseFactors(mouseHorizontalFactor, mouseVerticalFactor)
func updateInteract(player):
if !dragging:
return
var worldGrab = body.to_global(grabLocal)
handOffset += player.camera.global_basis.x * player.interactionMouseDelta.x * mouseForce
handOffset += -player.camera.global_basis.y * player.interactionMouseDelta.y * mouseForce
handOffset = handOffset.limit_length(0.3)
var targetHand = player.hand.global_position + handOffset
var error = targetHand - worldGrab
var handDistance = player.hand.global_position.distance_to(worldGrab)
if handDistance > maxGrabDistance:
player.stopInteraction()
return
var offset = worldGrab - body.global_position
var pointVelocity = body.linear_velocity + body.angular_velocity.cross(offset)
var hingeAxis = body.global_basis.y
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 * springStrength - tangentVelocity * springDamping
body.apply_force(force, offset)
func endInteract(player):
dragging = false
handOffset = Vector3.ZERO
player.setMouseFactors(1.0, 1.0)
#func get_hinge_angle() -> float:
#var body_basis = body.global_transform.basis
#var hinge_basis = hinge.global_transform.basis
#
## Body forward vector in world space
#var forward = body_basis.z
#
## Convert forward vector into hinge local space
#var local_forward = hinge_basis.inverse() * forward
#
## Angle around hinge axis (Y)
#return atan2(local_forward.x, local_forward.z)
func get_hinge_angle() -> float:
var hinge_axis = hinge.global_transform.basis.y.normalized()
# Door forward direction
var fwd = body.global_transform.basis.z.normalized()
# Reference forward direction (door closed direction)
var ref = hinge.global_transform.basis.z.normalized()
# Remove hinge axis component (project onto hinge plane)
var fwd_proj = (fwd - hinge_axis * fwd.dot(hinge_axis)).normalized()
var ref_proj = (ref - hinge_axis * ref.dot(hinge_axis)).normalized()
# Signed angle
var angle = atan2(
hinge_axis.dot(ref_proj.cross(fwd_proj)),
ref_proj.dot(fwd_proj)
)
return angle
func get_hinge_ang_vel() -> float:
var axis = hinge.global_transform.basis.y.normalized()
return body.angular_velocity.dot(axis)