I want to destroy a rigidbody if it is being crushed by other rigidbodies. The first step that came to my mind was to use the simple force formula (force = mass * acceleration) to calculate how much force is being inflicted to the rigidbody. I think it could be used to detect if technically, an object is capable of crushing another object. But I don't know how to detect if the rigidbody is being jammed/crushed between two objects. I thought of using raycasts but I don't think that's a good way to do it. How can I detect if a rigidbody is being crushed?
How to detect if an Object is being crushed
You could detect impacts with other RigidBody nodes using the on_body_entered
signal (documentation) instead of raycasting. This would only give you impact data on the first frame of impact between two RigidBody nodes though, so you would probably need to use on_body_exited
in combination so you can tell how many objects are colliding at any given point, then calculate the crush force from all the objects that have collided.
That is what I would try right off at least. I'm not sure if it would work though.
@TwistedTwigleg said: You could detect impacts with other RigidBody nodes using the
on_body_entered
signal (documentation) instead of raycasting. The problem with this method is that, with the rigidbody lying on the ground, it already counts it as a collision. So I don't think if I can count that if there are two or more collisions, consider that as being stuck. For an object to be 'crushed' there needs to be two opposite forces acting on it I believe.
- Edited
You could abstract it like this:
The body will break if total pressure force exceeds a certain hardcoded threshold.
Total pressure force can be looked at as a part of sum of all forces that acted on the body at a given moment, that didn't contribute to the change of body's impulse.
So you'll need to make a vector sum of: - all forces caused by contacts with other bodies - gravity force - minus an apparent force that caused the change of impulse (deduced from mass and delta velocity between frames)
The magnitude of this vector will then be reciprocal to pressure energy/force that didn't get converted into kinetic energy/motion. When this becomes large enough, the body breaks.
I tried approaching this from couple of different angles but I this is as far as I got. I managed to calculate the current force of the object but I am not sure how to get the forces that are being applied to the object Here is what my code looks:
extends RigidBody
var threshold = 20
var forces_sum
var gravity = Vector3 (0, -9.8, 0)
var own_force
var previous_velocity = Vector3 (0,0,0)
func _physics_process(delta):
var delta_velocity = self.linear_velocity - previous_velocity
var acceleration = delta_velocity / delta
own_force = self.mass * acceleration
forces_sum = gravity - own_force
previous_velocity = self.linear_velocity
But I don't know how to get the actual amount of force being applied to the current object. I thought of maybe calculating the force of the other rigidbody that has collided with the object but I don't know what to do with that value. Since I'm calculating force from the object's velocity, I don't know how to calculate it when there is force being applied to the object but it's not moving.
- Edited
Asumming we're dealing only with linear velocities, there are two forces to consider for each contact: 1) weight 2) impact force
Weight is caused by gravity and you can directly calculate it via second Newton's law:
F_weight = m * g
However you're only interested in normal component of this force at the contact point so you'll need to project it to receiving body's normal vector at the contact point. Projection can be done via dot product:
F_weight_normal = normal * normal.dot(F_weight)
The second force is the force that caused contact object's velocity change on impact (partial or full stopping force). Again from second Newton's law we know that impulse is an integral sum of force over time. If we assume that constant impact force was applied during the frame duration time then it follows that:
F_impact = (m * delta_velocity) / frame time
You're again interested only in normal component of this force so you'll need to project it in the same way you've projected the weight.
Total contact force for a single contact then is the sum of these two forces.
F_contact = F_weight_normal + F_impact_normal
Do this for all contacts, sum the magnitudes and you'll get total "input" force magnitude on the object.
- Edited
Btw if you're wondering how to implement this, I think the best way would be to define RigidBody._integrate_forces() callback. This callback will supply you with PhysicsDirectBodyState object that contains all the details of body's current state, including all contacts.
So the code is almost complete. I only need to find a way to calculate the acceleration of the touching objects. Here is my progress:
func _integrate_forces(state):
for i in range (0, state.get_contact_count()):
var object = state.get_contact_collider_object(i)
if object is RigidBody:
var normal = state.get_contact_local_normal(i)
var f_weight_normal = normal * normal.dot(Vector3(0, object.weight,0))
f_weight_normal = f_weight_normal.round()
# Force = mass * acceleration
var impact_force = object.mass * object.linear_velocity
This gets the weight of the touching objects and projects it to the object getting crushed using the formula xyz mentioned.
However, I don't know how to get the impact force because it needs me to calculate acceleration. I don't know how can I calculate the acceleration of another object using _integrate_forces
.
Also one issue with this system would be stacking objects, if objects are stacked like this:
then their weights should accumulate. But with this system only the weight of the touching objects is calculated.
- Edited
I see what you are saying. The object won't know about other objects that aren't touching it. But the forces should still be applied (like in your case, the bottom box would receive a greater force) so maybe the physics engine already calculates this somehow.
You might be looking at this the wrong way. Instead of being smart with math, it might be better to be smart with... uhh.. idk... design tricks? Having all that logic basically in the physics loop also could turn out expensive. So.. do you have any more information? Do you know when and where you need to check if it's going to be crushed? Is one object always doing the crushing while others aren't? You might opt for a well timed illusion instead of hard math.
@Erich_L said: Do you know when and where you need to check if it's going to be crushed? Is one object always doing the crushing while others aren't? You might opt for a well timed illusion instead of hard math.
Well, I want these physics bodies to interact with each other. I want to make a robust system that works in a variety of different situations. If this was for just one instance, as you said, I would've gone for the illusion. But since I want to implement this systematically, I believe physics calculation is the only way.
So I made some progress and came up with this:
func _integrate_forces(state):
var total_force = Vector3.ZERO
for i in range (0, state.get_contact_count()):
var object = state.get_contact_collider_object(i)
if object is RigidBody:
var impulse = state.get_contact_impulse(i) * state.get_contact_local_normal(i)
total_force += impulse
I believe this is the way to go. I've done some testing and physics-wise, this works correctly. As stacking objects work correctly in any axis. My only concern is that the actual impulse value is very small. I thought of maybe multiplying it by a constant or the weight of the object, but I'm not sure if either of those are a good idea. What do you think? How should I wrap this up?
- Edited
I think that code looks good, but I have yet to delve into the integrate_forces function and what state holds. I have a feeling it might be best to get some of that logic out of integrate_forces, but idk what saving state would entail. What's next? How will you destroy the crushed objects?
I am working on a physics game... it would be fun to have bananas get crushed but the game can barely handle my 1k bananas as is I'm afraid of adding logic to these functions.
By definition, you need to divide impulse with frame time to get actual average force. Are you sure it picks up forces (including weight) from stacked objects?
@xyz said: By definition, you need to divide impulse with frame time to get actual average force. Are you sure it picks up forces (including weight) from stacked objects? Yeah I'm certain. Take a look:
Here is the total_force with 1 object on top:
Here is with 2 objects stacked on top:
With 3 cubes stacked:
The grey cube is detecting all the forces and the orange cubes weigh the same (100).
@Erich_L said: What's next? How will you destroy the crushed objects?
There are couple of ideas on how to break them. The standard idea is that when the pressure on the object exceeds a predetermined threshold, it breaks. The way it breaks is that it deletes the current rigidbody object and spawns the premade debris (which is a scene). Here is how I am doing it for now:
if total_force.x > threshold or total_force.y > threshold or total_force.z > threshold:
# delete the rigidbody and spawn debris
However, in my case, the only moving objects are rigidbodies, so you want to also run a piece of code if a non-rigidbody is crushing the object. But I think if you want to do that, you have to do some trickery.
A trick half-life 2 did was that it had death_triggers
on the end of it's crushing objects (like the bottom of elevators) so when it hit a breakable object and or the player, it would inflict an infinite amount of damage, and would break that object, effectively removing the obstacle. And if the object was non-destructible, it would cancel it's motion and return back to where the moving platform was. You could probably benefit from such a system in your game. The illusion is very hard to break.