Making hard stops for a rotating Rigidbody3D
Asked Answered
L

3

0

I'm working on building a physics-based pinball flipper and I'm trying to use RigidBody physics rather than manually animating or tweening the motion. Basically I'd like to apply a torque to the flipper to rotate it when the flipper button is flipped, and apply a torque in the other direction when it's released. So far so good, but I'd like to add "end stops" to this motion, Ideally without having to get other physics bodies involved.

The reason I would like to do this with physics is so that things like bounciness, and the strength of the flip and return mechanisms can be tuned to get a realistic acceleration for the flipper when it starts, and so that it reacts to the ball hitting it.

So far what I have is an input signal handler which sets a boolean, and in the integrate_forces callback if that variable is set, a torque will be applied in the forward rotation direction, and if not in the backward rotation direction. Things get messy when the resting position or the maximum rotated position are reached. Ideally when either of those two states occur, the rotational velocity should become zero, but not immediately. The "end stop" would apply an opposite torque to the flipper causing it to come to a stop, and it might even bounce a small amount.

That's what I'd like to have happen, but currently it's a flickering teleporting mess. Does anyone have ideas on how this could be accomplished more cleanly?

Here is the code for my flipper:

extends RigidBody3D

@export_enum("left", "right") var flipper_side: String = "left"
@export var flipper_torque: float = 100
@export var flip_angle: float = 25.0
@export var stop_margin: float = 1

var flipper_action: String = "left_flipper"
var resting_transform: Transform3D
var resting_rotation: Vector3
var default_damp: float

var flipping: bool = false
		
func flip():
#	constant_torque = Vector3()
#	add_constant_torque(Vector3(0, flipper_torque, 0) * global_transform.basis)
	flipping = true
	
func retract():
#	constant_torque = Vector3()
#	add_constant_torque(Vector3(0, -flipper_torque, 0) * global_transform.basis)
	flipping = false

# Called when the node enters the scene tree for the first time.
func _ready():
	flipper_action = "left_flipper" if flipper_side == "left" else "right_flipper"
	resting_transform = self.transform
	resting_rotation = self.rotation_degrees
	default_damp = angular_damp
	retract()
	
# the flipper should be stopped within a small range from the rest position
func at_retract_stop():
	var angle = rotation_degrees.y
	var boolean = angle < resting_rotation.y
	return boolean
	
func at_flip_stop():
	var angle = rotation_degrees.y
	return angle > flip_angle + resting_rotation.y

func _input(event):
	if Input.is_action_just_pressed(flipper_action):
		flip()
	
	if Input.is_action_just_released(flipper_action):
		retract()
		
func _physics_process(delta):
	pass


func _integrate_forces(state):
	
	var flipper_angle_fraction = (rotation_degrees.y - resting_rotation.y) / flip_angle
	var local_angular_velocity = angular_velocity * global_transform.basis
	var current_torque: Vector3 = Vector3()
	if flipping:
		var solenoid_accel_factor = 5
		var torque_magnitude = flipper_torque + solenoid_accel_factor * flipper_angle_fraction
		current_torque.y = torque_magnitude

		if at_flip_stop():
			if local_angular_velocity.y > 0:
				current_torque = current_torque - Vector3(0,current_torque.length()+1,0)
				angular_damp = 100
			else:
				current_torque = Vector3()
				angular_damp = default_damp
	else:
		var spring_accel_factor = 4
		var torque_magnitude = flipper_torque + spring_accel_factor * flipper_angle_fraction
		current_torque.y = -torque_magnitude

		if at_retract_stop():
			if local_angular_velocity.y < 0:
				current_torque = current_torque + Vector3(0,current_torque.length()+1,0)
				angular_damp = 100
			else:
				current_torque = Vector3()
				angular_damp = default_damp

	var transformed_torque = current_torque * global_transform.basis
	state.set_constant_torque(transformed_torque)
	# lock angular velocity to the flipper's local Y axis
	self.rotation_degrees.x=0
	self.rotation_degrees.z=0
	if rotation_degrees.y < resting_rotation.y:
		self.rotation_degrees.y = resting_rotation.y
	elif rotation_degrees.y > resting_rotation.y + flip_angle:
		self.rotation_degrees.y = resting_rotation.y + flip_angle
	
	state.integrate_forces()
Lymphoma answered 4/10, 2023 at 20:4 Comment(0)
S
1

Your code looks sensible enough to me. I might suggest predicting if you are about to pass the stop rather than if you have already passed the stop. Before anything though, you may want to consider switching away from Godot's physics and using Jolt physics. Godot's 3D physics are unreliable and a bit wonky. I believe they are planning on switching to use Jolt by default internally, but until then there is a nice plugin for integrating Jolt physics in Godot. It could possibly eliminate some of the issues you are experiencing. I've not used it myself, but I've read that it's simple to switch to.

Stroke answered 4/10, 2023 at 20:27 Comment(0)
L
0

Stroke I'll definitely give it a try with Jolt! I was hoping to use Godot's built-in physics, but as it's a completely physics based game it definitely needs stability.

Lymphoma answered 5/10, 2023 at 13:41 Comment(0)
L
0

The lower-code solution turned out to be a combination of switching to jolt, and using a hinge joint to control the motion rather than building the constraint into the RigidBody.

Not a particularly well documented part of the physics system, but a joint seems to be perfectly usable with only one body connected, as I didn't want a whole extra physics body to just statically represent the pivot point. The other thing that needed to be tweaked for the joint was its rotation. It rotates around its z-axis, so rotating the joint to align with the rotation axis of the body was the other piece of the puzzle.

The JoltHingeJoint also allows tuning of a spring representing the springiness of the end stops, so that's another parameter to play with.

Lymphoma answered 6/10, 2023 at 13:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.