Announcement

Collapse
No announcement yet.

How to enable collision on foliage

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    How to enable collision on foliage

    Adding collision to meshes placed with the (somewhat) new Foliage Mode is currently impossible, although we may want to enable collision if we use the Foliage system to add large objects like trees and rocks to our level (or even other things, creativity is your limit...). Adding such objects individually as normal static meshes not only is a pain, but poor for performance (you'll get thousands of draw calls, one for each tree, rock, etc.). A search through the forum returned no solutions, so I implemented my own and thought I'd share it.

    This tutorial will show how to create collision for objects placed using the foliage system. You'll need basic understanding of unrealscript.

    So, let's start with the basic. After you paint some foliage into your level, Unreal will group some of those trees and rocks and grass into one single mesh, containing several instances of the object you painted. This is done to decrease the number of draw calls, greatly improving render time when you have thousands of meshes (which is the case of foliage).

    The class InstancedFoliageActor, instanced once in each level, contains a pointer to each of these large clusters of meshes through its variable InstancedStaticMeshComponents (an array<InstancedStaticMeshComponent>). So first thing we need is to get access to this class and iterate through these components. Put this in your GameInfo class.

    (MyGameInfo.uc)
    PHP Code:
    event PostBeginPlay()
    {
        
    super.PostBeginPlay();
        
    CreateFoliageCollision();
    }
    function 
    CreateFoliageCollision()
    {
        
    local InstancedFoliageActor ac;
        
    local InstancedStaticMeshComponent comp;
        
    local int i;
        
    //look for the InstancedFoliageActor
        
    foreach AllActors(class'InstancedFoliageActor',ac)
        {
            
    //iterate through the various foliage components
            
    for(i=0i<ac.InstancedStaticMeshComponents.Lengthi++)
            {
                
    comp ac.InstancedStaticMeshComponents[i];
                `
    log(comp);
            }
        }

    If you try this on a level with a lot of foliage used, you should get a lot of messages on your log.

    Now, if you take a look at InstancedStaticMeshComponent.uc, you will see it has a very useful attribute: PerInstanceSMData. This object contains information of every mesh instance in this foliage cluster. Its type is InstancedStaticMeshInstanceData, which is a struct that contains, among other things, a transform matrix (var Matrix Transform). It also extends StaticMeshComponent, meaning you have access to which static mesh this cluster represents (var() const StaticMesh StaticMesh).

    So let's decompose that transform matrix to get each instance's location, rotation and scale. UDK has built-in functions to decompose location and rotation only, not scale, so we'll have to implement that as well.

    (MyGameInfo.uc)
    PHP Code:
    static final function vector MatrixGetScale(Matrix TM)
    {
        
    local Vector s;
        
    s.sqrt(TM.XPlane.X**TM.XPlane.Y**TM.XPlane.Z**2);
        
    s.sqrt(TM.YPlane.X**TM.YPlane.Y**TM.YPlane.Z**2);
        
    s.sqrt(TM.ZPlane.X**TM.ZPlane.Y**TM.ZPlane.Z**2);
        return 
    s;
    }

    function 
    CreateFoliageCollision()
    {
        
    local InstancedFoliageActor ac;
        
    local InstancedStaticMeshComponent comp;
        
    local vector locscale;
        
    local Rotator rot;
        
    local int ij;
        
    //look for the InstancedFoliageActor
        
    foreach AllActors(class'InstancedFoliageActor',ac)
        {
            
    //iterate through the various foliage components
            
    for(i=0i<ac.InstancedStaticMeshComponents.Lengthi++)
            {
                
    comp ac.InstancedStaticMeshComponents[i];
                
    //iterate through the various meshes in this component
                
    for (j=0j<comp.PerInstanceSMData.Lengthj++)
                {
                    
    //decompose the instance's transform matrix
                    
    loc MatrixGetOrigin(comp.PerInstanceSMData[j].Transform);
                    
    rot MatrixGetRotator(comp.PerInstanceSMData[j].Transform);
                    
    scale MatrixGetScale(comp.PerInstanceSMData[j].Transform);
                    
    DrawDebugBox(loc,vect(200,200,200),255,0,0,true);
                }
            }
        }

    If you run the game now, you should see a lot of red boxes around your foliage instances.

    Now, we have the mesh, the location, scale and rotation. We just need to add collision to that. The logical choice would be a BlockingVolume, but volumes can't be spawned in run time (actually they can, but you can't change their shape or position, far as I know). What about an invisible static mesh actor? Sounds great, but instead of using, say, DynamicSMActor_Spawnable, let's create our own class, optimized for this purpose.

    (BlockingMesh.uc)
    PHP Code:
    class BlockingMesh extends DynamicSMActor_Spawnable;

    defaultproperties
    {
        
    Begin Object Name=StaticMeshComponent0
            HiddenGame 
    true //we don't wanna draw this, it's just used to test collision
            
    BlockRigidBody=true //DynamicSMActors have collision disabled by default
        
    End object
        bCollideActors
    =true //same here
        
    bTickIsDisabled true //greatly improves performance on the game thread

    Now we just need to spawn this class, using the variables we got earlier. Add this after the matrix decomposition.

    PHP Code:
                    CreatedBlockingMesh Spawn(class'BlockingMesh',ac,,loc,rot);
                    
    CreatedBlockingMesh.StaticMeshComponent.SetStaticMesh(comp.StaticMesh);
                    
    CreatedBlockingMesh.SetDrawScale3D(scale); 
    Here we spawned a BlockingMesh, setting its owner to the level's InstancedFoliageActor (just for organization), and setting its location and rotation from the values we got earlier. Then we set the BlockingMesh's static mesh to that of the cluster's static mesh. Unreal will automatically assign the collision model, so don't worry about that. Finally, we set the scale.

    One last thing: some meshes don't have a collision model. They typically include small objects like grass and flowers and shouldn't have collision. So, before spawning the blocking mesh, check if they have a collision model:

    PHP Code:
                        if (comp.StaticMesh.BodySetup != none)
                {
                            
    CreatedBlockingMesh Spawn(class'BlockingMesh',ac,,loc,rot);
                            
    CreatedBlockingMesh.StaticMeshComponent.SetStaticMesh(comp.StaticMesh);
                            
    CreatedBlockingMesh.SetDrawScale3D(scale);
                } 
    Run the game now, load a level full of foliage, and type in the console: show collision or show zeroextent. You should have collision on every foliage instance now. Hurray!



    Performance

    It is speculated that Epic didn't enable collision on foliage due to performance reasons, and indeed, adding collision to every foliage instance can be quite heavy. In my test map, which contains 9042 instances (7 different meshes), I got 40 FPS with collision (20ms on the game thread), and 55 FPS without collision (6ms on the game thread), so this is certainly pretty cpu intensive. Also, that's on a Core i7, so the performance impact should be even greater on weaker processors.

    We probably don't need to check foliage collision everywhere, there are probably several forests on our map that are just eyecandy and the player can't reach them. So instead of creating collision meshes everywhere, let's create a new volume type, which tells "only create collision for foliage objects if they are inside me".

    So delete everything we did so far on MyGameInfo.uc and create a new file:

    (FoliageCollisionVolume.uc)
    PHP Code:
    /** Creates collision for foliage instances inside the volume */
    class FoliageCollisionVolume extends Volume
        placeable
    ;

    static final function 
    vector MatrixGetScale(Matrix TM)
    {
        
    local Vector s;
        
    s.sqrt(TM.XPlane.X**TM.XPlane.Y**TM.XPlane.Z**2);
        
    s.sqrt(TM.YPlane.X**TM.YPlane.Y**TM.YPlane.Z**2);
        
    s.sqrt(TM.ZPlane.X**TM.ZPlane.Y**TM.ZPlane.Z**2);
        return 
    s;
    }

    function 
    PostBeginPlay()
    {
        
    local InstancedFoliageActor ac;
        
    local InstancedStaticMeshComponent comp;
        
    local vector locscale;
        
    local Rotator rot;
        
    local BlockingMesh CreatedBlockingMesh;
        
    local int ij;

        
    super.PostBeginPlay();

        
    //look for the InstancedFoliageActor
        
    foreach AllActors(class'InstancedFoliageActor',ac)
        {
            
    //iterate through the various foliage components
            
    for(i=0i<ac.InstancedStaticMeshComponents.Lengthi++)
            {
                
    comp ac.InstancedStaticMeshComponents[i];
                if (
    comp.StaticMesh.BodySetup != none)
                {
                    
    //iterate through the various meshes in this component, if it has a collision model
                    
    for (j=0j<comp.PerInstanceSMData.Lengthj++)
                    {
                        
    //decompose the instance's transform matrix
                        
    loc MatrixGetOrigin(comp.PerInstanceSMData[j].Transform);
                        if (
    ContainsPoint(loc)) //check if this instance is within the volume
                        
    {
                            
    rot MatrixGetRotator(comp.PerInstanceSMData[j].Transform);
                            
    scale MatrixGetScale(comp.PerInstanceSMData[j].Transform);
                            
    CreatedBlockingMesh Spawn(class'BlockingMesh',ac,,loc,rot);
                            
    CreatedBlockingMesh.StaticMeshComponent.SetStaticMesh(comp.StaticMesh);
                            
    CreatedBlockingMesh.SetDrawScale3D(scale);
                        }
                    }
                }
            }
        }

    Compile this and open the editor. Create a builder cube that includes several of your level's foliage instances and right-click the Add Volume button. Choose FoliageCollisionVolume to create our custom volume.



    When you run the game, collision should be created only for foliage inside that volume.





    And that's it! I hope you found this tutorial useful.

    #2
    oh man this is great!
    thank you

    Comment


      #3
      Part of the performance hit might be related to the many non-static actors you create as host for the collision components. Wouldn't it make sense to use a single actor instead and add all the components to it?

      Comment


        #4
        wow , thank you that's useful , added to favs..

        Comment


          #5
          Originally posted by Wormbo View Post
          Part of the performance hit might be related to the many non-static actors you create as host for the collision components. Wouldn't it make sense to use a single actor instead and add all the components to it?
          Hmm yes, that would probably be better.

          Comment


            #6
            Nice tutorial but somehow i always get this error when i rebuild my scripts:

            I get this error when i use the first variant:

            http://www.bilder-hochladen.net/file...-b6d7-png.html

            And when i try to do it with the 2nd nothing happens

            Comment


              #7
              Hmm are you editing GameInfo.uc itself? I never tried that, try creating a new MyGameInfo that extends GameInfo, as per the tutorial.

              Also, note that this will only work if the meshes you're using actually have a collision model (double-click your static mesh in the browser and click Show Collision to verify).

              Comment


                #8
                Sorry that i have bothered you with this question. It just was a silly mistake from me. I have scaled the wrong volume XD so nothing was in the volume. But thank you for the quick reply

                Comment


                  #9
                  People of the future! UDK has added foliage collision to its foliage tool (apparently a long time ago), you can find the options right below the lighting menu inside the foliage window for the individual mesh settings.

                  Only problem is, navigation mesh doesn't recognize it, otherwise, it works fine, and (presumably) doesn't have considerable impact on performance.

                  It's baffling not to be able to find any reference to this, I spent a good hour just looking at different ways of making collision with foliage tool. Learn from my mistakes!

                  Comment


                    #10
                    Very nice!

                    Comment


                      #11
                      Unfortunately foliage with UDK collision doesn't collide with vehicles ...

                      Does someone have a solution?

                      Comment


                        #12
                        cool but in UDK version without UT there is not the Foliagevolume(like the watervolume)..
                        how can we do, without use the UT package?

                        Comment


                          #13
                          Damnit! Why did I not see this earlier? This would have really been useful in the long run before Unreal Engine 4 became free.

                          Comment


                            #14
                            Hey man, thanks for this script! I've been using it in an online game called Renegade-X. Be sure to visit www.renegade-x.com and check it out! It's awesome! It's a remake of the old C&C Renegade by Westwood Studios.

                            So, the first time I tried to implement your script, it was VERY laggy in a multiplayer server, I could not figure out why. I thought it had something to do with the amount of foliage actors and the calculations involved, those thoughts later have been proven correct. I've taken your script and optimised it with a few others.

                            We're using this now on a multiplayer server and it runs really really well. I have the feeling that it's even better to use than actual static meshes but, that's just a guess.
                            What have we changed?

                            - Instead of PostBeginPlay() we now use a Touch() event to spawn the foliage. As you never really need ALL the BlockingMesh at once you only stress out the server if you use that. Basically, you only need the BlockingMesh once you're close. This has lead us to the Touch() event instead of the PostBeginPlay(). We've also looked into logging and comparing players' positions but that wasn't doable.
                            - Instead of looking for all actors on a touch event, we only search for vehicles and add them to an array. Infantry get blocked by foliage by default, so we don't need to spawn the BlockingMesh when infantry is inside the forest. This means that, if a vehicle is touching the volume, it's getting added to an array. As long as there are vehicles inside the array, the BlockingMesh stay spawned. If all vehicles leave the array, the BlockingMesh gets destroyed inside that specific volume. This optimisation has lead to great server performance (and also client side)
                            - Collision gets culled out at a distance of 1000 client side. This means that we don't have much load on the GPU's of the client.
                            - bStatic is set to false, so we have less performance hits

                            All together, we have a new volume, that the mapper himself is able to dimension himself and therefore controlling the amount of load on a server himself. The mapper also has to take into account that when ever the touch event happens by vehicles, his BlockingMesh will spawn, therefore he has to take into account that he should not create over-sized volumes.
                            The code as is, is really optimised as we consider it to be. We've tested this in our renegade-x servers already and it really looks promising. Where we had 300-400 pings (where you normally have 100) with the original script you posted, we now have a normal 100 ping again (even when spawning these BlockingMesh). Apparently it's all come down to the amount of BlockingMesh and when to spawn them.

                            Feel free to use this code, it has helped us a lot. I've mentioned you Pinheiro in the top post, I've also mentioned people that helped me and myself ofcourse.

                            It works perfectly.

                            BlockingMesh.uc
                            Code:
                            //=============================================================================
                            // FoliageCollisionVolume:  a vehicle collision solution
                            // used to collide certain classes of actors
                            // primary use is to provide collision for non-zero extent traces around static meshes
                            // Created by Pinheiro, https://forums.epicgames.com/threads/913559-How-to-enable-collision-on-foliage
                            // Modified by Ruud033
                            //=============================================================================
                            
                            class BlockingMesh extends DynamicSMActor_Spawnable; 
                            
                            defaultproperties 
                            { 
                                Begin Object Name=StaticMeshComponent0 
                                    HiddenGame = true //we don't wanna draw this, it's just used to test collision 
                                    BlockRigidBody=true //DynamicSMActors have collision disabled by default 
                            		CastShadow=false
                            		MaxDrawDistance = 500
                            		bAllowCullDistanceVolume=true
                            		AlwaysLoadOnServer=false
                                End object 
                                bCollideActors=true //same here 
                                bTickIsDisabled = true //greatly improves performance on the game thread 
                            	bStatic=false
                            	bNoDelete=false
                            }
                            FoliageCollisionVolume.uc
                            Code:
                            //=============================================================================
                            // FoliageCollisionVolume: a vehicle collision solution
                            // used to collide certain classes of actors
                            // primary use is to provide collision for non-zero extent traces around static meshes
                            // Created by Pinheiro, https://forums.epicgames.com/threads/91 ... on-foliage 
                            // Heavily modified by Ruud033 & Handepsilon
                            // Thanks Crnyo and nameloc for the additional help
                            //=============================================================================
                            
                            class FoliageCollisionVolume extends Volume
                            placeable;
                            var BlockingMesh CreatedBlockingMesh;
                            var Array<BlockingMesh> SpawnedBlockingMeshes;
                            var bool bBlockersHaveSpawned; //Check if blockers have already spawned. If it has, no need to spawn more
                            var Array<Vehicle> VehiclesInVolume;
                            
                            static final function vector MatrixGetScale(Matrix TM)
                            {
                            local Vector s;
                            s.x = sqrt(TM.XPlane.X**2 + TM.XPlane.Y**2 + TM.XPlane.Z**2);
                            s.y = sqrt(TM.YPlane.X**2 + TM.YPlane.Y**2 + TM.YPlane.Z**2);
                            s.z = sqrt(TM.ZPlane.X**2 + TM.ZPlane.Y**2 + TM.ZPlane.Z**2);
                            return s;
                            }
                            
                            //Search for each tree apart, the code goes trough the volume and searches each tree.
                            event Touch( Actor Other, PrimitiveComponent OtherComp, vector HitLocation, vector HitNormal )
                            {
                            	local InstancedFoliageActor ac;
                            	local InstancedStaticMeshComponent comp;
                            	local vector loc, scale;
                            	local Rotator rot;
                            	local int i, j;
                            
                            	super.Touch(Other, OtherComp, HitLocation, HitNormal);
                            
                            	If(Other.IsA('Vehicle'))
                            	{
                            	VehiclesInVolume.AddItem(Vehicle(Other));
                            	
                            		if(!bBlockersHaveSpawned)
                            		{
                            		bBlockersHaveSpawned = true;
                            
                            			//look for the InstancedFoliageActor
                            			foreach AllActors(class'InstancedFoliageActor',ac)
                            			{
                            				//iterate through the various foliage components
                            				for(i=0; i<ac.InstancedStaticMeshComponents.Length; i++)
                            				{
                            					comp = ac.InstancedStaticMeshComponents[i];
                            					
                            					if (comp.StaticMesh.BodySetup != none)
                            					{
                            						//iterate through the various meshes in this component, if it has a collision model
                            						for (j=0; j<comp.PerInstanceSMData.Length; j++)
                            						{
                            							//decompose the instance's transform matrix
                            							loc = MatrixGetOrigin(comp.PerInstanceSMData[j].Transform);
                            							if (ContainsPoint(loc)) //check if this instance is within the volume
                            							{
                            							rot = MatrixGetRotator(comp.PerInstanceSMData[j].Transform);
                            							scale = MatrixGetScale(comp.PerInstanceSMData[j].Transform);
                            							CreatedBlockingMesh = Spawn(class'BlockingMesh',ac,,loc,rot);
                            							CreatedBlockingMesh.StaticMeshComponent.SetStaticMesh(comp.StaticMesh);
                            							CreatedBlockingMesh.SetDrawScale3D(scale);
                            							SpawnedBlockingMeshes.AddItem(CreatedBlockingMesh);
                            							}
                            						}
                            					}
                            				}
                            			}
                            		}
                            	}
                            }
                            
                            //Destroy the spawned meshes once untouched
                            event Untouch( Actor Other )
                            {
                            local int i;
                            Super.Untouch(Other);
                            
                            	VehiclesInVolume.RemoveItem(Vehicle(Other));
                            	if(VehiclesInVolume.Length <= 0)
                            	{
                            		for(i=0; i < SpawnedBlockingMeshes.Length; i++)
                            		{
                            		SpawnedBlockingMeshes[i].Destroy();//if bNoDelete is set to false in defaultproperties; this deletes it
                            		// Ok removed SpawnedBlockingMeshes.Remove(i--,1); // Hande's note : This is not necessary as the below function will automatically empty the array
                            		}
                            	SpawnedBlockingMeshes.Length = 0;
                            	bBlockersHaveSpawned = false;
                            	}
                            }
                            
                            defaultproperties
                            {
                            bColored=true
                            BrushColor=(R=0,G=255,B=255,A=255)
                            
                            bCollideActors=true
                            SupportedEvents.Empty
                            SupportedEvents(0)=class'SeqEvent_Touch'
                            }
                            Original development thread (for those who're interested http://renegade-x.com/forums/viewtop...150935#p150935)

                            Comment

                            Working...
                            X