(Case 1293499) Angular velocity is incorrectly applied on some bodies with convex shapes

The problem is that when I apply angular impulses to physics bodies using PhysicsWorld.ApplyAngularImpulse, the body adds some other angular velocity that I don’t want since it may actually flip my car over. The “ground truth” implementation in regular C# and PhysX works fine. Here’s the demonstration when the car is in the air without gravity and not considering if it’s landed:

Current system code is attached. Can anyone help me please?

6447048–721734–WRCarPhysicsSystem.cs (5.15 KB)

You can take a look at the implementation of the ApplyAngularImpulse method, it works in local space of the motion, therefore it needs to apply rotation to your input angular impulse in order to apply it. So the y axis you are trying to change the velocity around might not be the one you want, thus giving you rotations you don’t want. Perhaps you could just apply the angular impulse on the MotionVelocity instead, that could help?

I’m not sure. Thing is, when i switch the physics shape to Cube, the impulse is applied just fine. That might have something to do with center of mass mismatching somewhere.

So I decided to investigate the cause. Didn’t go far, but for whatever reason when for some complicated shapes (Kenney’s Car Pack in this case, I’m using the sports hatchback for this case but others have the same issue) I set the shape type to Convex Hull, the parent (which has a Physics Body) gets rotated a tiny amount, which gets really noticable when adding angular velocity to it. But when I set the type to Mesh or any other, the parent doesn’t rotate and works fine when adding angular velocity. So for now I’m forced to use a more expensive or a more broad method of collision detection, which sucks.

Also why does center of mass have rotation (which by itself doesn’t make sense, how can a dot be rotated) on convex mesh? Think there’s a miscalculation of CoM on convex hulls for whatever reason.

Aha! Found the issue.
Here’s how Unity.Physics.MeshCollider sets Transform in MassProperties’s MassDistricbution (Unity.Physics/Collision/Colliders/Physics_MeshCollider.cs, line 152):

Transform = new RigidTransform(quaternion.identity, m_Aabb.Center)

And here’s how Unity.Physics.ConvexCollider does that (Unity.Physics/Collision/Colliders/ConvexCollider.cs, lines 196-197 and 209):

var massProperties = builder.HullMassProperties;
Math.DiagonalizeSymmetricApproximation(massProperties.InertiaTensor, out float3x3 orientation, out float3 inertia);

// when setting MassDistibution...

Transform = new RigidTransform(orientation, massProperties.CenterOfMass)

So I fixed the issue by replacing orientation in RigidTransform constructor arguments to quaternion.identity, similar to MeshCollider. This fix will probably cause some regressions (and I would like to know which), but the mesh rotates as expected now.

You definitely don’t want a dynamic mesh collider :slight_smile: That’s a performance killer.
The center of mass when calculated from a convex hull could easily be offset and rotated from the original pivot depending on the hulls shape.

This is a little confusing but hopefully this will clear things up.
Your WRCarPhysicsSystem is using the physicsWorld.ApplyAngularImpulse extension function which expects world space impulses. If you directly the angular impulse via the physics MotionVelocity runtime struct, then it expects motion space impulses.
So, from the quick look I had at your code, I believe the problem is the combination of the impulse being calculated in one space, the inertia diagonal being in motion space and being applied in world space.

Still, I don’t understand why would center of mass have orientation. This is kind of counter-intuitive.

Its not that COM has orientation, it’s that the motion space might be different from the body space.
The Motion space origin will be at the COM and its orientation will be defined by the axes around which the body can be rotated most easily and with most difficulty (i.e. axes with the smallest and largest inertia components).
Imagine a long body that isn’t axis aligned i.e the OBB of the body is not aligned to the AABB of the body. In calculating the mass properties (including COM and Inertia), the center of mass won’t be at the body pivot point and the convex hull major axis won’t be aligned with the bodies canonical axes either, hence Motion space will have a rotation and position offset from Body space.

1 Like

Alright, let’s say I use MotionVelocity to apply impulses to the body. When I use it as a parameter in Entities.ForEach, I get this error: parameter 'velocityMotion' has type MotionVelocity. This type is not a IComponentData / ISharedComponentData and is therefore not a supported parameter type for Entities.ForEach.. How do I use it then? And do I just apply local space impulses? Don’t really understand what “motion space” means, and Google doesn’t find anything useful

Motion space is just the name for a frame of reference whose origin is at the bodies COM and whose axes line up with the mass distribution. So it’s just another handy name, like World space or Local space. Local space is to generic though (it will be context dependent) so we specifically use Body space (reference frame of the pivot) and Motion space (reference frame of the mass distribution).

Apologies if I’ve exacerbated the confusion between the ECS Component structs (e.g. PhysicsVelocity, PhysicsGravityFactor & PhysicsMass) which work with Entities.ForEach, and the simulation runtime structs (e.g. MotionData & MotionVelocity) which are processed by the core Physics algorithms.
ECS data is nicely aligned for extremely fast contiguous memory access. The physics simulation really needs to bounce randomly between different parts of memory so the BuildPhysicsWorld system first converts the IComponentData structs into the simulation runtime optimized structs. StepPhysicsWorld works on the simulation optimized structs before ExportPhysicsWorld spits the data back into the ECS component data structs. (see more details in this video)

I recommend you work at the ECS level data and use the extension functions that work on them. The problem is the ApplyAngularImpulse doesn’t work in WorldSpace. We should really add something like this:

        /// <summary>
        /// Apply a world-space angular impulse to a rigid body.
        /// </summary>
        /// <param name="bodyVelocity">The body's <see cref="PhysicsVelocity"/> component.</param>
        /// <param name="bodyMass">The body's <see cref="PhysicsMass"/> component</param>
        /// <param name="bodyOrientation">The body's <see cref="Rotation"/> component</param>
        /// <param name="impulse">An angular impulse in world space specifying radians per second about each axis.</param>
        public static void ApplyAngularImpulseWorldSpace(ref this PhysicsVelocity bodyVelocity, in PhysicsMass bodyMass, in Rotation bodyOrientation, in float3 impulse)
        {
            quaternion inertiaOrientationInWorldSpace = math.mul(bodyOrientation.Value, bodyMass.InertiaOrientation);
            float3 angularImpulseInertiaSpace = math.rotate(math.inverse(inertiaOrientationInWorldSpace), impulse);
            bodyVelocity.Angular += angularImpulseInertiaSpace * bodyMass.InverseInertia;
        }

I hope this helps clear things up and I haven’t just added more to the confusion!

2 Likes