The ol' belt conveyor problem aka Implementing surface velocity

Discuss any questions about BEPUphysics or problems encountered.
Post Reply
alektron
Posts: 2
Joined: Tue Nov 19, 2019 9:16 am

The ol' belt conveyor problem aka Implementing surface velocity

Post by alektron »

Hey guys,
I recently started playing around with Bepu and, coming from PhysX, I have to say, the API so far is really easy to understand and use.
At least for the more simple tests I did so far I could implement them without any problems by just looking at one or two demos.
But when it comes to implementing conveyor belt like objects I'm running into some issues. I am aware that there are already a few questions regarding conveyors in the forum, so I am already aware of the following solutions:

1. Set a velocity but reset the position every frame
Problem: While that actually works great it still feels kind of hacky to me and in a scenario where I want to simulate a large amount of conveyors, iterating over every Body that I want to act this way, this seems to me to become a potential performance issue.
On the upside, the results should be pretty realistic since it kind of accurately simulates a moving surface.


2. Modifying contacts
The other solution is something I already successfully implemented in PhysX but can't really get to work in Bepu. At least not properly.

I added an additional Property called SurfaceVelocity to BodyReference (and the corresponding Buffer in BodySet) and a HasSurfaceVelocity flag that gets set when the linear SurfaceVelocity vector has a length >0 (Since I have to check this every frame for every conveyor but only change the actual SurfaceVelocity ever so often this should be faster then recalculating all the vector lengths every frame).

Thanks to the NarrowPhaseCallback it is super easy to get access to the generated contacts. So in the ConfigureContactManifold callback I try to figure out which body of the pair is the conveyor and which one is the item carried by it. This is relatively easy since conveyors in my case are always kinematic and the items always dynamic.
I then iterate all the contacts, check if the kinematic body has a SurfaceVelocity and if it does, apply an impulse to my item in the direction of the SurfaceVelocity vector (and hopefully at the location of the contact. I'm not so sure about that with the current implementation). In PhysX I had the ability to set a target velocity on a contact and then the solver did the rest. I do no seem to have this ability in Bepu hence the approach with the impulse.

Code: Select all

public bool ConfigureContactManifold<TManifold>(int workerIndex, CollidablePair pair, ref TManifold manifold, out PairMaterialProperties pairMaterial) where TManifold : struct, IContactManifold<TManifold>
        {
            pairMaterial.FrictionCoefficient = 0.5f;
            pairMaterial.MaximumRecoveryVelocity = 2f;
            pairMaterial.SpringSettings = new SpringSettings(1, 1.0f);

            CollidableReference dynamicCollider, kinematicCollider;
            if (pair.A.Mobility == CollidableMobility.Kinematic && pair.B.Mobility != CollidableMobility.Kinematic)
            {
                dynamicCollider = pair.B;
                kinematicCollider = pair.A;
            }
            else if (pair.B.Mobility == CollidableMobility.Kinematic && pair.A.Mobility != CollidableMobility.Kinematic)
            {
                dynamicCollider = pair.A;
                kinematicCollider = pair.B;
            }
            else
                return true;

            BodyReference kinematicBody, dynamicBody;
            BodyDescription description;
            for (int i = 0; i < manifold.Count; i++)
            {
                kinematicBody = Simulation.Bodies.GetBodyReference(kinematicCollider.Handle);
                kinematicBody.GetDescription(out description);
                if (description.HasSurfaceVelocity)
                {
                    dynamicBody = Simulation.Bodies.GetBodyReference(dynamicCollider.Handle);
                    manifold.GetContact(i, out Vector3 offset, out Vector3 normal, out float depth, out int featureId);
                    dynamicBody.ApplyImpulse(description.SurfaceVelocity.Linear, dynamicBody.Pose.Position + offset);
                    dynamicBody.Awake = true;
                    //dynamicBody.Velocity.Linear = (description.SurfaceVelocity.Linear);
                }
            }

            return true;
        }
I am aware that this is probably not the most optimized code atm but since I'm still trying to even get it to work properly this isn't my main concern.

Now this works to a degree but comes with some issues/questions.
1. First of all, I can't find a SurfaceVelocity vector that moves the items at a correct speed. It either fires them off way too fast or doesn't move them at all. I tried different values for the sleep behaviour as well as friction/springsettings etc. but with no changes in the conveying speed. Any idea what I could do to fix this
2. Secondly, is there something I'm missing here? Is there a generally better approach? Maybe Solution 1 is not as bad as I think it is?
3. Is expanding the BodyReference struct / the BodySet Buffers to add my SurfaceVelocity property a good idea? Do I impact the performance of accessing the other buffers by shifting the layout? Are there better approaches? In PhysX every body had a void* that I could attach any data I wanted to and had access to in various callbacks. Didn't exactly help code readability and/or type safety but got the job done.

(Additional question:
Talking about the buffer layout in BodySet. The comment on top of the struct says the body information is stored in AOS format. It looks more like SOA to me, though. I'm not exactly an expert here so I might be misinterpreting it, just curious if the comment is wrong or I am.
Not really related to my main question, just sneaking it in)

Thanks already in advance.
From what I've seen so far this is a really great physics library and I can't even imagine all the effort put into it by just one single person.
Very well done!

alektron
Norbo
Site Admin
Posts: 4929
Joined: Tue Jul 04, 2006 4:45 am

Re: The ol' belt conveyor problem aka Implementing surface velocity

Post by Norbo »

1. First of all, I can't find a SurfaceVelocity vector that moves the items at a correct speed. It either fires them off way too fast or doesn't move them at all. I tried different values for the sleep behaviour as well as friction/springsettings etc. but with no changes in the conveying speed. Any idea what I could do to fix this
Applying an impulse equal to the target velocity will keep accelerating the body every single invocation. This could be addressed by applying an impulse according to the desired acceleration, not velocity. (In other words, compute the difference between the current and target velocity, and apply an impulse to correct a portion of that error.)

Also, when using manifold.GetContact, be careful about body order. By convention, manifolds store offsets from A's center to the contact point, and normals point from B to A.

Further, modifying a body's velocity in a narrow phase callback will result in corruption eventually due to executing in a multithreaded context. Waking a sleeping body there will immediately corrupt a lot of stuff since it modifies the broad phase as it's in use.
3. Is expanding the BodyReference struct / the BodySet Buffers to add my SurfaceVelocity property a good idea? Do I impact the performance of accessing the other buffers by shifting the layout? Are there better approaches?
Provided that it is stored in its own buffer, it won't negatively affect locality for other body data. However, it is pretty complicated to add more 'body memory aligned' data like that, especially ensuring that it's handled properly by sleeping/waking. Further, any data accessed from narrow phase callbacks will tend to be near random, so storing it in the same order as the pose and velocity doesn't provide much value.

In the demos, custom body data grabbed by narrow phase callbacks is just stored in an external buffer, indexed by the body handle. You can see this in the TankCallbacks, ContactEventsDemo, RagdollDemo, and so on.

There's nothing special about the BodyProperty type, though- you can store that external data however you'd like.
2. Secondly, is there something I'm missing here? Is there a generally better approach? Maybe Solution 1 is not as bad as I think it is?
Modifying velocities of conveyor bodies will likely be much cheaper than the contact callback approach given how simple it is. If you have several thousand conveyors, you might want to toss some extra threads at it. Maybe.

Also, an alternative to modifying velocities/resetting position is to just modify the velocity before the solver runs, and then reset the velocity after the solver runs. You can attach delegates to the ITimestepper you pass into the Simulation.Create function. (The parameter has a default value that results in a PositionFirstTimestepper, but you can provide your own instance.)

On the PositionFirstTimestepper, for example, there's a CollisionDetected event and a ConstraintsSolved event. Those two sandwich the solver.

Performance wise, resetting position or velocity will be pretty near identical. It's possible that touching only velocity near the velocity solver will be a little faster by virtue of hitting shared cache more often, but the main benefit is simplicity. You won't have to worry about 'ephemeral' velocities or positions being visible outside of the timestep or the solver's execution.

As for other alternatives, a couple spring to mind:
1) Set friction to zero for conveyor contacts. Create a LinearAxisMotor for each conveyor tangent direction in response to contacts, with a maximum force based on contact penetration impulses. This would be pretty annoying to set up, and you wouldn't be able to exactly match the TangentFriction constraint due to the lack of per-iteration impulses. But it'd work to some degree. Overall, I wouldn't recommend this unless you didn't have any other option.

2) Expand callbacks to allow the creation of a more customizable contact constraint, including tangent surface velocity. This would involve a new set of contact constraints with more control parameters since you wouldn't want to expand the prestep data for all contacts, but contact constraint type generation is already done through codegen anyway so it wouldn't be too horrible. Realistically, I'd have to be the one to do this, since anyone else would have to spend way too long learning the internals of the engine. This is something I'm considering- it would be a good place for bounciness and inertia scaling too.
Talking about the buffer layout in BodySet. The comment on top of the struct says the body information is stored in AOS format. It looks more like SOA to me, though. I'm not exactly an expert here so I might be misinterpreting it, just curious if the comment is wrong or I am.
While the body properties are split up into different chunks, that's only for the sake of cache line efficiency given common access patterns. Constraints, for example, don't need to look up information about a body's activity state, so including that in the same struct would just waste space in a cache line.

SOA would go a step further. Individual scalar properties are given their own buffer. So rather than an array of RigidPoses, you'd have an array of Pose.Position.X, another array of Pose.Position.Y, and so on. That gives you a very direct way to load a set of components as a SIMD vector without having to do a bunch of shuffling. Vectorizing that way would be counterproductive, though, since unavoidably nonsequential constraint accesses are the dominant concern. Even the often superior AOSOA would make things worse.
From what I've seen so far this is a really great physics library and I can't even imagine all the effort put into it by just one single person.
Very well done!
Thanks! :*
alektron
Posts: 2
Joined: Tue Nov 19, 2019 9:16 am

Re: The ol' belt conveyor problem aka Implementing surface velocity

Post by alektron »

In the demos, custom body data grabbed by narrow phase callbacks is just stored in an external buffer, indexed by the body handle. You can see this in the TankCallbacks, ContactEventsDemo, RagdollDemo, and so on.
I did not know about BodyProperty. Will try it
Also, an alternative to modifying velocities/resetting position is to just modify the velocity before the solver runs, and then reset the velocity after the solver runs.
Despite the result being pretty much the same, I somewhat prefer that approach, thanks.
Post Reply