Results 1 to 12 of 12

Thread: Runtime Foliage

  1. #1

    Default Runtime Foliage

    So you have a large landscape. You put trees on that landscape. You probably put to many trees on that landscape and had to remove some to get performance to an acceptable level. Then you realized you need grass too, that's where this script comes in.


    - I created this script to reduce the need for level designers to place large amounts of small foliage when working with large environments. Often having so many grass meshes (even with the foliage painter) can be very expensive when working with large areas.

    The Scripts -

    NearFoliageManager.uc
    Code:
    class NearFoliageManager extends Actor;
    
    // Visible/ Collision Components
    var const CylinderComponent	CylinderComponent;
    var const DrawSphereComponent	SphereComponent;
    
    // Grass Counts
    var array<RuntimeGrass> currentGrass;
    var int GrassToSpawnInRing;
    var int offsetammount;
    
    // Phys Material
    var PhysicalMaterial physMatMask;
    
    // Optimization
    var Vector lastSpawnPosition;
    var int ReCheckDistance;
    var int MaxGrassCount;
    
    simulated function PostBeginPlay() {
    	super.PostBeginPlay();
    	Owner.AttachComponent(SphereComponent);
    	Owner.AttachComponent(CylinderComponent);
    }
    
    event Destroyed() {
    	DestroyAllGrass();
    	Owner.DetachComponent(SphereComponent);
    	Owner.DetachComponent(CylinderComponent);
    	super.Destroyed();
    }
    
    event Tick( float DeltaTime ) {
    	local RuntimeGrass localGrass;
    
    	super.Tick(DeltaTime);
    
    	// Hacky replication check to ensure destroyed is called.
    	// Also ensure this only exists on client that owns the pawn.
    	if (myPawn(Owner).IsAliveAndWell() == false || myPawn(Owner).Controller == none) Destroy();
    
    
    	// Only do both of these either on the client or in singleplayer (NO SPAWNED GRASS ON SERVER)
    	if (WorldInfo.NetMode == NM_Client || WorldInfo.NetMode == NM_Standalone) {
    		createGrassMesh(GrassToSpawnInRing);
    	}
    
    	if (WorldInfo.NetMode == NM_Client || WorldInfo.NetMode == NM_Standalone) {
    		foreach currentGrass(localGrass) {
    			localGrass.checkDistanceFromOwner();
    		}
    	}
    }
    
    function DestroyAllGrass() {
    	local RuntimeGrass localGrass;
    
    	foreach currentGrass(localGrass) {
    		localGrass.Destroy();
    	}
    }
    
    function RemoveGrassInstance(RuntimeGrass grassToRemove) {
    	currentGrass.RemoveItem(grassToRemove);
    }
    
    // Creates a RunTimeGrass, if it succeeds the grass is placed into an array, otherwise it returns false.
    unreliable client function createGrassMesh(int grassToSpawn) {
    	local Vector spawnLocation;
    	local Rotator spawnRotation;
    	local RuntimeGrass spawnedGrass;
    	local int i, rad;
    	local float radian;
    
    	// Trace Info
    	local TraceHitInfo spawnedGrassTraceInfo;
    	local Vector traceHitPos, traceHitNormal, traceEndPos;
    
    	// Optimization :: Stops Spawning Grass if Max Reach
    	if (currentGrass.Length > MaxGrassCount) return;
    
    	// Optimization :: If player hasnt moved more than 100 UUs since last check, don't spawn grass.
    	if (VSize(lastSpawnPosition - CylinderComponent.GetPosition()) < ReCheckDistance) return;
    
    	rad = CylinderComponent.CollisionRadius-(offsetammount*2);
    	lastSpawnPosition = CylinderComponent.GetPosition();
    
    	for (i=0; i < grassToSpawn; i++) {
    
    		// Creates the grass in a circle.
    		radian = i * Pi/(grassToSpawn/ 2);
    		spawnLocation = CylinderComponent.GetPosition();
            spawnLocation.X += rad * Cos(radian);
    		spawnLocation.Y += rad * Sin(radian);
    
    		// Offsets position to make it feel more random
    
    		spawnLocation.X += (offsetammount * ((FRand()*2)-1));
    		spawnLocation.Y += (offsetammount * ((FRand()*2)-1));
    
    		spawnLocation.Z += 500; // Allows grass to spawn on ledges/ hills above player
    
    		// Trace downwards to get surface/ hit location/ hitnormal
    		traceEndPos = spawnLocation;
    		traceEndPos.Z -= 1000;
    		//DrawDebugLine(spawnLocation, traceEndPos, 128,128,255, true);
    		Trace(traceHitPos, traceHitNormal, traceEndPos, spawnLocation, false,, spawnedGrassTraceInfo);
    
    		// If the physical material is not grass, back out.
    		if(spawnedGrassTraceInfo.PhysMaterial != physMatMask) goto'end';
    
    		// Align grass to surface normal.
    		spawnLocation = traceHitPos;
    		spawnRotation = Rotator(normal(traceHitNormal));
    		spawnRotation.Pitch-= 16384;
    
    		// Spawn da grass. If it fails for whatever reason, back out.
    		spawnedGrass = Spawn(class'RuntimeGrass', self,, spawnLocation,spawnRotation,,true);
    		if (spawnedGrass != none) currentGrass.AddItem(spawnedGrass);
    end:
    	}   
    }
    
    DefaultProperties
    {
    	physMatMask = PhysicalMaterial'PhysMaterials.PhysMats.Grass'
    	MaxGrassCount = 100
    	ReCheckDistance = 100
    	GrassToSpawnInRing = 25
    	offsetammount = 64
    
    	Begin Object Class=CylinderComponent NAME=CollisionCylinder LegacyClassName=Trigger_TriggerCylinderComponent_C  lass
    		CollideActors=true
    		CollisionRadius=+1024
    		CollisionHeight=+512.000000
    		HiddenGame=true
    	End Object
    	CollisionComponent=CollisionCylinder
    	CylinderComponent = CollisionCylinder
    	Components.Add(CollisionCylinder)
    
    /**
    	Begin Object Class=DrawSphereComponent Name=Sphere
    		bDrawOnlyIfSelected=false
    		bDrawWireSphere=true
    		SphereRadius=1024
    		SphereSides=32
    		SphereColor=(R=255,G=32,B=32)
    		HiddenEditor=false
    		HiddenGame=false
    	End Object
    	SphereComponent = Sphere
    	Components.Add(Sphere)
    **/
    
    	CollisionType = COLLIDE_NoCollision 
    
    	bBlockActors = false
    	bCollideActors = true
    	bStatic = false;
    	bMovable = true;
    	bNoDelete = false;
    	bHidden = false;
    }
    RuntimeGrass
    Code:
    class RuntimeGrass extends StaticMeshActor
    	notplaceable;
    
    function checkDistanceFromOwner() {
    	local CylinderComponent localManagerCollision;
    	local float GrassDistance;
    
    	if (Owner == none) return;
    
    	localManagerCollision = NearFoliageManager(Owner).CylinderComponent;
    	GrassDistance = VSize(Location - localManagerCollision.GetPosition());
    	if (GrassDistance > localManagerCollision.CollisionRadius) {
    		NearFoliageManager(Owner).RemoveGrassInstance(self  );
    		Destroy();
    	}
    }
    
    event Tick( float DeltaTime ) {
    	// If FoliageManager is destroyed whilst this is created some grass can be left over.
    	// Hacky fix.
    	if (Owner == none) Destroy();
    }
    
    DefaultProperties
    {
    	Begin Object Name=StaticMeshComponent0
    		StaticMesh=StaticMesh'myGrass.Meshes.GrassTest'
    	End Object
    	DrawScale = 2.5
    	bNoDelete = false
    	bStatic = false
    	bMovable = true
    }
    Add to Pawn Class
    Code:
    simulated function PostBeginPlay() {
    	super.PostBeginPlay();
    	if (myFoliageManager == none) myFoliageManager = Spawn(class'NearFoliageManager', self,,,,,true);
    }
    
    function UnPossessed() {
    	myFoliageManager.Destroy();
    	super.UnPossessed();
    }
    You'll also notice that in order to reduce popping I added a scale by distance to the foliage, this isn't perfect as you do obviously notice the grass 'grow', this could be reduced by increasing the distance of your grass spawning. I did this over transparency as it is cheaper. Here is the material that does this -


    How To Improve -
    • Randomize Scale/ Rotation of spawned grass.
    • Random colour variation (Material)
    • List of archetypes to spawn for random models (different grass/ small rock/ bushes)
    • Include LightEnvironment for non-dynamic lighting.
    • Different Physical Material return different spawn types (such as woodland physical material spawns more bushes)
    • Density/ Seperation control.
    • Max spawn slope angle.
    • Optimization! I'm sure better programmers could come up with methods to improve this a lot. I am no programmer


    To answer some obvious questions/ statements that will come up -

    Q.) Looks like ****.
    - With a better material and foliage mesh it would look no different than placing it in the editor (unless you use lightmass).

    Q.) No Foliage at a distance!?!
    - This system was intended to give a sense of density and movement as the player walked through the grass. For further away foliage personally I'd look at using planes that always face the camera.

    Q.) Does it work online?
    - The system is designed to be client side only. It works online, but the server, and other clients, have little to no interaction with it.

    Have fun!

  2. #2

  3. #3

    Default

    thank you for this checked it out and it works really well

    can't wait for all the things in the improvement list to be added

    edit: any ideas on how I can go about changing some of these variables in the editor or ingame?
    Last edited by Pampers; 05-04-2012 at 12:03 AM.

  4. #4
    Redeemer
    Join Date
    Jun 2010
    Location
    Barcelona
    Posts
    1,902

    Default

    whoa very nice idea and nice implementation!
    I probably won't be needing it for now but once I do I'd add an option to check to see it only spawns at a landscape actor, and options for checking against the PhysMaterial it should spawn on (ie. so I only spawn it on my terrain's grass layer, since you can tell layers to use different PhysMat's). adding a max slope angle should be quite easy too (a check with HitNormal).

    now the phrase 'How To Improve' is a little vague. are you planning on improving these yourself or are they just hints for other people?
    anyway very nice work and thanks for the code!

  5. #5

    Default

    Thanks guys!
    Chicken+Ribs Combo : I'll be upset if you don't use this system
    Pampers : If you just want to change variables in the editor then just creating an archetype of the NearFoliageManager for the Pawn to spawn would work. If you want to be able to change them at runtime I'd suggest creating an additional class that held variables you wanted to change and create an archetype of that for the foliage manager to access.
    Chosker : Thanks man, at the moment it already does a check to ensure the PhysMaterial is only a grass layer, and yup I should have done the slope angle, seeing as I already am using the hitnormal to align the foliage :P.
    Yeah I purposely left it a little vague, they were things I'd suggest others (or myself) can do if they want to make the system better. If I do add anything I will update the code

  6. #6

    Default

    Ah, okay
    Seems like changing phys_materials on landscape layers is really broken, I'm trying to control where the grass is rendered, and it works fine on some layers, but other layers won't change ingame even if it's says they have a different phys_mat in the landscape edit tool. Anyone else have had any similar problems?

  7. #7
    Boomshot
    Join Date
    Aug 2011
    Posts
    2,286

    Default

    Nice tutorial. First suggestion would be to remove the cylinder component from the manager as it doesn't serve any real purpose.

    Also, this would benefit from a pooling system that recycles a cache of grass actors instead of spawning/destroying them. That might limit your ability to use archetypes. You could just consult the archetype and update the grass actor's mesh when recycling.

  8. #8
    Iron Guard
    Join Date
    Jan 2009
    Posts
    743

    Default

    1) use vsizesq, not vsize in tick, its faster
    2) looks like you'll be hitting trace and spawn a lot as you move around a dense scene, so I would be surprised if its cheaper than foliage editor instances, but if so, more power to you
    3) you should probably use traceflag_bullet so you get visual poly location, not a collision hull, but with high poly meshes you will notice some more hit.
    Last edited by Jetfire; 05-12-2012 at 02:36 PM.

  9. #9
    MSgt. Shooter Person
    Join Date
    Apr 2012
    Posts
    128

    Default

    What really surprises me is that something like this is not natively implemented in the terrain/landscape system.

  10. #10
    MSgt. Shooter Person
    Join Date
    Nov 2009
    Location
    Classified
    Posts
    221

    Default


  11. #11
    MSgt. Shooter Person
    Join Date
    Nov 2009
    Location
    Classified
    Posts
    221

    Default

    Ok, cool pictures aside, I'm having this problem when I try to build the scripts:



    It's just me being a newb yeah? How do I fix it?


  12. #12
    MSgt. Shooter Person
    Join Date
    Nov 2009
    Location
    Classified
    Posts
    221

    Default

    Ah I've got it...

    I had to add the following:

    var NearFoliageManager myFoliageManager;

    At the top of MyPawn.uc

    Heh heh.


 

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Copyright ©2009-2011 Epic Games, Inc. All Rights Reserved.
Digital Point modules: Sphinx-based search vBulletin skin by CompletevB.com.