How to set up collisions between control rig physics and ground

Hello,

I am experimenting with control rig physics. Following the video from Stockholm 2025 (YouTube) I have created a simple control rig which aims to put the character in a ragdoll state, using its physics asset for the physics bodies:

It works, but I ran into an issue with terrain collisions though. The aforementioned video shows it too (at 9:45) and solves it by adding a collision shape for the physics solver. However, that seems to me like an unsatisfactory solution for my intent, as I want the character to react to the shape of the ground, not some imaginary “pedestal”.

Setting the physics solver’s “World Collision Type” to Static makes it collide in the preview window of the control rig, but oddly, when I plug that control rig into an animation blueprint, in the preview window for that ABP the character will fall through the ground. And in an actual level when playing it also falls through.

When I create ragdoll an “old” way using Set Simulate Physics it also falls through by default, but it can be fixed by setting the correct collision profile, for which there is a blueprint node:

However, I have not found any way to set a collision profile for the physics bodies created inside the control rig. Even if I set the Ragdoll profile to the character as the default, hoping the control rig would “copy” the settings from the character, it does not work. To me it seems the physics bodies are created with some default collision settings and I have not found any control rig node that would let me change them. Is it possible somehow? There are “Set Physics Body Data”, “Set Physics Body Collision Properties” and “Set Physics Body Collision Mode” nodes, but none of them seem to address the issue. Or is the problem in something else?

Steps to Reproduce

Hi, sorry for the delay getting back to you on this. I managed to get a repro that’s similar to the issue you described, but it’s taken a while to track down the code that’s responsible. It seems that the way objects are added to the control rig physics immediate simulation isn’t working correctly when the object’s location doesn’t change from the point at which it’s added to the simulation. Most of the testing of CRP has been done around workflows involving Sequencer which appears to be why we didn’t find this previously.

Can you try making the following change to FRigPhysicsSimulation::UpdateWorldObjectsPrePhysics, so that it’s using InitWorldTransform rather than SetWorldTransforms, and let me know if that resolves the issue?

Current code:

void FRigPhysicsSimulation::UpdateWorldObjectsPrePhysics(const FRigPhysicsSolverSettings& SolverSettings)
{
...	
    for (TPair<uint32, FWorldObject>& WorldObjectPair : *WorldObjects.Get())
	{
		FWorldObject& WorldObject = WorldObjectPair.Value;
		FTransform SimSpaceTM = ConvertWorldTransformToSimSpace(SolverSettings, WorldObject.ComponentWorldTransform);
		if (WorldObject.LastSeenUpdateCounter == -1)
		{
			UE_LOG(LogRigPhysics, Log,
				TEXT("Control rig %s - Adding object %s"), *OwnerName.ToString(),
				*WorldObject.ActorHandle->GetName().ToString());
 
			WorldObject.ActorHandle->SetWorldTransform(SimSpaceTM);
			// If the object is found in the next overlap, then its update counter will be updated.
			// However, in case it's not, we set it here too to make sure that it expires. 
			WorldObject.LastSeenUpdateCounter = UpdateCounter;
		}

new code:

void FRigPhysicsSimulation::UpdateWorldObjectsPrePhysics(const FRigPhysicsSolverSettings& SolverSettings)
{
...
	for (TPair<uint32, FWorldObject>& WorldObjectPair : *WorldObjects.Get())
	{
		FWorldObject& WorldObject = WorldObjectPair.Value;
		FTransform SimSpaceTM = ConvertWorldTransformToSimSpace(SolverSettings, WorldObject.ComponentWorldTransform);
		if (WorldObject.LastSeenUpdateCounter == -1)
		{
			UE_LOG(LogRigPhysics, Log,
				TEXT("Control rig %s - Adding object %s"), *OwnerName.ToString(),
				*WorldObject.ActorHandle->GetName().ToString());
 
			WorldObject.ActorHandle->InitWorldTransform(SimSpaceTM);  // FIX
			// If the object is found in the next overlap, then its update counter will be updated.
			// However, in case it's not, we set it here too to make sure that it expires. 
			WorldObject.LastSeenUpdateCounter = UpdateCounter;
		}

What component type is the mesh that you’re using for the ground? Is it just static mesh, instanced static mesh, or something else?

It would also be interesting to know whether the ground mesh is being added to the immediate physics simulation created by control rig physics. There’s two ways that you can check that. The first is to turn on a couple of cvars when running in PIE:

ControlRig.EnableDrawInterfaceInGame 1
a.AnimNode.ControlRig.Debug 1

You should then see debug draw around any object that is represented in the immediate physics world. Objects are added when they come within the bounding box that you can see drawn around the mesh.

The other option is to run the Chaos visual debugger. There’s a guide here if you haven’t used it previously. With a recording in there, you should be able to see all the bodies in the regular physics world and the control rig’s immediate physics world. Is the ground mesh visible in both worlds, or just the regular world?

Great, thanks for confirming those changes work for you for CCD.

I tracked down the issue with the ISM components. It’s because those components store the body data for each instance in a different property vs regular primitive components. I have a prospective fix shelved at 51879158, if you want to try it out. This is just a first pass, so it still needs a bit of cleaning up, but it gets things working with ISM in my simple tests.

I’ll start looking at the landscape streaming proxy issue next. I’ll need to spend some time working out how to put together test assets for that since it’s a part of the engine that I’m not familiar with. Unless you happen to have anything you can share?

Unfortunately I am not allowed to share the level where I can reproduce it. I’ve tried to create a simple test level with streaming proxies for Landscape but I am not able to reproduce the issue there, so there must be something specific about our big level that is causing the issue; I will try to get some answers from our level designers next week and will get back to you.

I spoke with some of the Fortnite team about this issue since they have had what sounded like similar problems with landscape mesh and the RBAN node (for which the implementation of gathering the world objects is very similar). But it turned out their issue was caused by the collision channels on the landscape mesh being set to collision query only and not regular collision. So, based on what you mentioned earlier, as long as the collision settings are the same as you had with the instanced mesh, etc, then it won’t be the same problem.

I’d be interested to know what you see in the Chaos visual debugger. Do you see the landscape mesh represented under the global physics solve (you can also double-check the collision settings there)? And if so, is it not present under the immediate solve for the CRP setup?

Sorry I didn’t get a chance to follow up on this this week. My workstation died last week so I’ve been getting a new one setup the last few days. Thanks - what you’re describing actually does sound similar to the issues that we had with the RBAN node and collision with landscape. I’ll take a look at this next week and follow up.

Alright, that info you provided about the collision filter channels was very helpful in tracking down what’s going on here. As you mentioned, the PCR code adds bodies for any component that overlaps with the control rig bounding box, including those set to Query or Probe collision types. This is actually the inverse of the issue we were having with the Rigid Body With Control animation node (a variant of the RBAN node), where only Physics query types were being added. To make the situation more confusing, when we copy the bodies from the world into the PCR simulation via ImmediatePhysics::MakeKinematicActorSetup, the collision filter settings are lost because CreateGeometry in ImmediatePhysicsAdapters.cpp sets the filter channels all to block for these new bodies. So you get into the situation you’re running into, where you have a body that is set to respond to queries, and has the channel filters set to ignore, but it then gets added to the PCR immediate sim as blocking all channels, so you get collisions against it.

There’s two ways that we could go with this. One would be to copy the filter channel data from the original body into the duplicate body in the immediate sim. But based on some of the comments in CreateGeometry, I’m a bit cautious about doing that, as it’s not clear this would behave as expected with the simplified collision of Chaos immediate mode. Instead, the safer route with this is to introduce similar flags to those that exist on the RBWC anim node, which allow you to specify whether you want Query or Probe collision types to be included in the sim. I have a shelf at 52493545 that adds this functionality if you want to test it. On the Spawn Physics Solver control rig node, you can now specify whether you want to include Query, etc, types. I think this should solve your issue, as you can use it to exclude Query-only types. I’m going to talk to the dev team about getting this tested and submitted.

> Also, a bit unexpected is that it is going to ignore objects that have physical collision turned on, but not queries, having an option to include those could be useful, although I don’t have any particular application right now (I guess it’s a bit unusual to have something that is allowed to collide but being queried).

Yes, this also seems problematic. But I don’t think this will be easy to solve, because we rely on the overlap check to know that the body for a component should be added to the PCR immediate sim. But if a component/body has its collision type set to Physics Only, it won’t show up in overlap checks so we’ll never know to add it to the immediate sim. I’ll talk with the dev team about this one as well to see if there’s any obvious solution.

Hi, sorry for the delay in following up. I needed to talk with the dev team to work out the best way forward with these changes. Anyway, I’ve gotten them cleaned up and submitted to the 5.8 release, so you’ll find them there once you’re able to upgrade.

In terms of when you said:

>The first part of IsBodyDesiredInSim always culls it:

I’ve removed the ECC_PhysicsBody filter check from the 5.8 code. But the equivalent code still exists in the RigidBodyWithControl node, and ideally, we’d like to get parity between that node and Control Rig Physics.

So I’m curious about the Query Only collision type objects that you mentioned. Do you see those objects collide against the Skeletal Mesh Bodies if you run a regular world simulation (ie. by calling SetAllBodiesBelowSimulatePhysics or using Physics Control Component)? The intention behind that check in the RBWC code is that, in the regular world sim, we wouldn’t expect an environmental body to collide with a SKM body if the environmental body didn’t have the ECC_PhysicsBody collision response channel set. So RBWC filters out those bodies so they don’t ever get added to the immediate sim.

The collision filtering isn’t particularly rational, unfortunately. I also talked with the dev team about this, and in future, we’d like to re-implement how the filtering works for all the animation-related dynamics systems that use immediate simulation (RBAN, RBWC and PCR).

For the moment, the intention is that we pre-filter based on collision type (ie. query, physics, etc). And the previous line to check against ECC_PhysicsBody effectively added that into the pre-filtering as well (since you could have a Physics/Query collision type that didn’t respond to physics bodies). But that is a change of behaviour for PCR, and for RBWC, where that line has already been added for 5.8, so it needs more thinking about.

The hope is that in future, we can make this all behave in line with how the world sim behaves. That would avoid having to pre-filter based on collision types and also retain the collision response channels on the duplicated bodies as they are in the world sim. And we would like to unify all these systems to remove the duplicate code. But that’s a bigger change, so it’ll take a while for us to get there.

With the changes I mentioned previously committed to 5.8, I think we are probably good to close out this thread if you’re happy with that?

No problem. Thanks for all the feedback on this, I think these changes are going to be very helpful for users moving forward. Feel free to let us know if you have other feedback in future.

Hello, I’ve tried it but unfortunately I get the same results as before (I’m testing it by Play In Editor).

The ground is not added to the control rig physics, I am only adding bodies created by the Instantiate From Physics Asset as shown on picture above; the physics asset being the asset created for our character. I verified using the CR debug + ticking “show world overlap box” and “show world objects” on the Step Physics Solver node -> it draws green box just around the character.

Does it need to be there? I was expecting that setting the World Collision Type in Spawn Physics Solver would somehow take into account the other meshes.

What component type is the mesh that you're using for the ground? Is it just static mesh, instanced static mesh, or something else?I have tried several different levels:

#1 One has the floor as StaticMeshActor like this (I think it’s the default when you create a new Basic level):

The odd part for me is that it has collisions set as WorldDynamic. The character falls through there. When I change the preset to “Block All” (-> object type changes WorldStatic) it seems to be working reliably.

In level #2, we have terrain as “Landscape”, which is set as WorldStatic:

Collisions sort of work there, but not reliably, for example when I drop the character from significant height, it often falls through - but it’s bounding box is large, I would not expect “tunneling” issues there. Perhaps the issue is that there is no preference in which direction collisions should be resolved (above ground rather than under) so when a bone collides, it can easily be resolved to the opposite side? But even if I tick “use CCD” on each collision shape in the physics asset the problem persists; but I am not sure if this setting is transferred to the control rig solver when using Instantiate From Physics Asset. The results is the same with the old code or with the “InitWorldTransform” fix.

Level #3 is a bit more complicated, but it is actually what we are currently testing for production (#1 and #2 are just for testing). It also uses Landscape with the same collision settings as #2, but with streaming (using LandscapeStreamingProxy). There the character always falls through the ground. But even if I wait a minute while already playing (-> the part of landscape I am standing on is definitely streamed in) and then turn my control rig on, the character still falls through. It has HLODs, which are static meshes with collisions disabled (set to Query Only). Is it possible the CR’s solver is somehow mistakenly using the collisions settings from the higher LODs?

So perhaps the issue is not as much in CR but in some landscape-streaming-specific problem, maybe some Landscape expert could help? Although the issues of level #2 are also suspicious.

> Does it need to be there? I was expecting that setting the World Collision Type in Spawn Physics Solver would somehow take into account the other meshes

The way that CRP works in terms of collision with objects in the world is that any physics objects that are within the bounding box around the mesh will be pulled into the immediate simulation, if their collision type matches what the CRP bodies have been set to collide against via the World Collision Type on the solver.

> The odd part for me is that it has collisions set as WorldDynamic. The character falls through there. When I change the preset to “Block All” (-> object type changes WorldStatic) it seems to be working reliably.

This should work, assuming the World Collision Type property matches the collision type on the mesh. In your original message, you mentioned that you had tested setting World Collision Type to Static. I would expect that if you change that to Dynamic or All, you should get collision against WorldDynamic objects (assuming you have the previous fix I mentioned).

> Collisions sort of work there, but not reliably, for example when I drop the character from significant height, it often falls through - but it’s bounding box is large, I would not expect “tunneling” issues there

This sounds like a tunneling related problem like you say rather than the same issue with collisions. The bounding box isn’t used as part of the collision detection, only to add the objects into the immediate mode simulation. It could be collision on the bodies that is failing here, or that the simulation is running too late after the objects have been added to it. I’ll spend some time investigating this and follow up.

> Level #3 is a bit more complicated, but it is actually what we are currently testing for production (#1 and #2 are just for testing). It also uses Landscape with the same collision settings as #2, but with streaming (using LandscapeStreamingProxy).

I’ll also need to find out more about this. There may be similar issues with the fastgeo plugin but I’ll need to speak more with my counterpart that works with the simulation team.

Follow up specifically on the tunnelling issue. There are a few properties that you can change on the CRP solver to try and improve the behaviour. Unfortunately, it seems the immediate Chaos solver doesn’t support CCD, so there will likely always be some issues with interpenetration. But you can try the following to improve the behaviour:

  • Collision Bounds Expansion: sets a minimum expansion amount of the collision bounds for each of the body shapes. The default is that there will always be 2cm of expansion.
  • Bounds Velocity Multiplier: a multiplier that increases the size of the bounds as the velocity increases
  • Max Velocity Bounds Expansion: the maximum that the bounds can be extended by the velocity multiplier. The default here is 25cm. This is likely the place to start - expand this to see if the collisions are handled better
  • Fixed Time Step: if you’re using this (ie. it’s non-zero) reduce the value to substep the solver more often
  • Max Delta Time: if you’re not using a fixed time step, reduce this. If you change this, you should also make sure that Max Time Steps is enough to perform all of the substeps within a frame.

Let me know if this improves the situation at all. I’ll follow up once I hear back about any known issues with collisions and LandscapeStreamingProxy.

I’ve experimented with all the settings and while it does help, at some speeds it still tunnels through even with some extreme settings. I am guessing the issue is although the expansion (Collision Bounds Expansion, Bounds Velocity Multiplier…) increases when the ground is considered for collision, once the speed is so high that in one frame the object is fully above the ground and next frame fully below, it does not help. Decreasing time step helps a bit more, but still even as little as 0.001 can still tunnel through at high speed.

I’ve tested the approach without control rig with Set Simulate Physics (as mentioned in my original post) and with CCD off, it also tunnel through, with CCD turned on it does not. My conclusions:

  • either need to make sure the simulated bodies never reach too high speed (this is tricky…my test is just letting the character fall at gravity 9.81 from tens of meters, I can see that easily happening in a game);
  • or wait for CCD to be implemented in CR (is that planned?) or do it ourselves (unlikely);
  • or just use the “old” approach + CCD for full ragdoll (using CR for this was more of an experimental exercise, currently I don’t think we need to do it this way).
  • or implement some safety net that teleports the character above ground

So I think we can consider this part of the question solved, thank you for your help. I am still interested in learning what is the issue with the streamed landscape, if you find out please let me know, thanks.

I talked with the feature owner about the lack of CCD support, and he spent a bit of time today looking at enabling it. It turns out adding support wasn’t too complex. I have a CL shelved at 51614864 with the changes if you want to take a look. It touches a lot of files, but the individual changes are pretty trivial.

The CL adds an EnableCCD property to the bodies when you select them in the rig hierarchy, and an AllowCCD property to the solver. It should also propagate the UseCCD property on bodies in the physics asset to the bodies that are created via InstantiateFromPhysicsAsset. And it adds SetPhysicsBodyEnableCCD and SetPhysicsSolverAllowCCD rig units so you can change these values dynamically.

Just be aware that if you integrate this, it does change some of the serialized version numbers, so you could run into problems if you later revert this and still need to load the control rig assets.

I tried the changelist and confirm it works, whether by using the new Set Physics Body Enable CCD node, or just setting it on the PA and using IFPA; so thank you. It does not help with the streamed Landscape issue, though I didn’t expect it would.

Chaos visual debugger shows the surface and it seems to have the right collision settings. However, when I select “player collisions” using the “view mode” drop down menu in the main window (that thing on the screenshot), it does not show the terrain surface, while in other levels it does. Any idea why that could be?

> is it not present under the immediate solve for the CRP setup?

I am not sure how to check that…I am not explictily doing anything with it in the CR (as in the landscape meshes are not used in any node I am using), or you mean it’s something I should be able to check in the Chaos Visual Debugger? If so, can you guide me how to do that?