Game Development Community

Terrain Following Missiles

by Eric Lavigne · 12/28/2004 (10:45 pm) · 22 comments

Acknowledgement:

I learned a lot from Derk Adams' guided missile resource. It helped me to create this resource.

Discussion:

To control a projectile's terrain following behavior, you will need to set four variables in the ProjectileData datablock. This is the same place where you would set such attributes as isBallistic, gravityMod, or, if you use Derk's resource, isGuided. The four variables are:
isTerrainFollowing - set to true if you want this behavior
minTerrainHeight - how low is the projectile allowed to go? (meters)
maxTerrainHeight - how high is the projectile allowed to go? (meters)
speedTerrainFollowing - how much verticle speed can TerrainFollowing
use to avoid leaving the specified range? (meters per second)

In general, you should choose a high value for speedTerrainFollowing (greater than the speed of the projectile) and set isBallistic=false. Lower values of speedTerrainFollowing are used if you want the missile to only have a small tendency to stay within the range that you specified.

Ordinarily you would set isBallistic=false. Otherwise TerrainFollowing and Ballistic will fight eachother. Setting both of them to true could be useful though. TerrainFollowing would be able to hold off gravity for a while (smoothly following terrain) then suddenly plummet and explode on the ground. If you want it to follow the ground for time %
// follow the ground for 15 seconds, then drop and explode on the ground
datablock ProjectileData(CrossbowProjectile)
{
   %timeuntilcrash=15
   isBallistic = true;
   gravityMod = 0.8;
   isTerrainFollowing = true;
   minTerrainHeight = 5;
   maxTerrainHeight = 5;
   speedTerrainFollowing = %timeuntilcrash * gravityMod * 9.8;
...
}

Development Environment:

December 2004
TGE 1.3 (installer)
WinXP
Single Player

Key:

I use the same indication of modifications as a patch file. A line beginning with a "-" is to be removed and a line beginning with a "+" is to be added. Be sure to remove the "+" when you actually put the line into your code. A few lines around the changes are shown to give context to the changes. The triple dot "..." is showing that information has been removed for brevity purposes.

Implementation:

EngineFile: /terrain/terrData.h (near the bottom)
extern ResourceInstance *constructTerrainFile(Stream &stream);
+  F32 getTerrainHeight(Point2F position);
   #endif
EngineFile: /terrain/terrData.cc
ConsoleMethod(TerrainBlock, ...
{
   ...
}   

+ F32 getTerrainHeight(Point2F position)
+ {
+    F32 height = 0.0f;
+    TerrainBlock * terrain = dynamic_cast<TerrainBlock*>(Sim::findObject("Terrain"));
+    if(terrain)
+       if(terrain->isServerObject())
+       {
+          Point3F offset;
+          terrain->getTransform().getColumn(3, &offset);
+          position -= Point2F(offset.x, offset.y);
+          terrain->getHeight(position, &height);
+       }
+    return height;   
+ }

ConsoleFunction(getTerrainHeight, F32, 2, 2, "(Point2I pos) - gets the terrain height at the specified position.")
{
   Point2F pos;
-  F32 height = 0.0f;
   dSscanf(argv[1],"%f %f",&pos.x,&pos.y);
-   TerrainBlock * terrain = dynamic_cast<TerrainBlock*>(Sim::findObject("Terrain"));
-   if(terrain)
-      if(terrain->isServerObject())
-      {
-         Point3F offset;
-         terrain->getTransform().getColumn(3, &offset);
-         pos -= Point2F(offset.x, offset.y);
-         terrain->getHeight(pos, &height);
-      }
-   return height;   
+   return getTerrainHeight(pos);   
}

ConsoleMethod(TerrainBlock, ...
...
EngineFile: game/projectile.h
/// Should it arc?
   bool isBallistic;

+   /// Should it follow the terrain?
+   bool isTerrainFollowing;
+   /// How close can it be to the terrain?
+   F32 minTerrainHeight;
+   /// How far can it be from the terrain?
+   F32 maxTerrainHeight;
+   /// If outside these bounds, how quickly does it return?
+   F32 speedTerrainFollowing;

   /// How HIGH should it bounce (parallel to normal), [0,1]
   F32 bounceElasticity;
   /// How much momentum should be lost when it bounces (perpendicular to normal), [0,1]
   F32 bounceFriction;
EngineFile: game/projectile.cc
#include "sim/decalManager.h"
+ #include "terrain/terrData.h"
  IMPLEMENT_CO_DATABLOCK_V1(ProjectileData);
...
ProjectileData::ProjectileData()
{
   ...
   faceViewer = false;
   scale.set( 1.0, 1.0, 1.0 );
+   /// Terrain following is off by default.
+   /// If turned on though, its other defaults
+   /// lead to strong and obvious behavior.
+   /// Good for people who are cluelessly
+   /// experimenting.
+   isTerrainFollowing = false;
+   minTerrainHeight = 5;
+   maxTerrainHeight = 5;
+   speedTerrainFollowing = 100;
   isBallistic = false;
...
void ProjectileData::initPersistFields()
{
...
   addNamedField(hasWaterLight, TypeBool, ProjectileData);
   addNamedField(waterLightColor, TypeColorF, ProjectileData);
+   addNamedField(isTerrainFollowing, TypeBool, ProjectileData);
+   addNamedFieldV(minTerrainHeight, TypeF32, ProjectileData, new FRangeValidator(-5000, 5000));
+   addNamedFieldV(maxTerrainHeight, TypeF32, ProjectileData, new FRangeValidator(-5000, 5000));
+   addNamedFieldV(speedTerrainFollowing, TypeF32, ProjectileData, new FRangeValidator(0, 10000));
   addNamedField(isBallistic, TypeBool, ProjectileData);
...
void ProjectileData::packData(BitStream* stream)
{
...
   if(stream->writeFlag(isBallistic))
   {
      stream->write(gravityMod);
      stream->write(bounceElasticity);
      stream->write(bounceFriction);
   }

+   if(stream->writeFlag(isTerrainFollowing))
+   {
+      stream->write(minTerrainHeight);
+      stream->write(maxTerrainHeight);
+      stream->write(speedTerrainFollowing);
+   }

   // Neither velInheritVelocity nor muzzleVelocity are transmitted to the
   // client. This is because in stock Torque, it is not needed for the
   // client-side simulation - in general, it is good design to not transmit
   // useless information. You could easily add stream->write() calls here,
   // and read calls in unpackData, if you did have need of them.
}

void ProjectileData::unpackData(BitStream* stream)
{
...
   isBallistic = stream->readFlag();
   if(isBallistic)
   {
      stream->read(&gravityMod);
      stream->read(&bounceElasticity);
      stream->read(&bounceFriction);
   }

+   isTerrainFollowing = stream->readFlag();
+   if(isTerrainFollowing)
+   {
+      stream->read(&minTerrainHeight);
+      stream->read(&maxTerrainHeight);
+      stream->read(&speedTerrainFollowing);
+   }
}
...
void Projectile::processTick(const Move* move)
{
   ...
   oldPosition = mCurrPosition;
   if(mDataBlock->isBallistic)
      mCurrVelocity.z -= 9.81 * mDataBlock->gravityMod * (F32(TickMs) / 1000.0f);
   
   newPosition = oldPosition + mCurrVelocity * (F32(TickMs) / 1000.0f);

+   if(mDataBlock->isTerrainFollowing)
+   {
+      F32 maxcorrection = mDataBlock->speedTerrainFollowing * (F32(TickMs)/1000.0f);
+      Point2F projectileshadow;
+      projectileshadow.x = newPosition.x;
+      projectileshadow.y = newPosition.y;
+      F32 terrainheight = getTerrainHeight(projectileshadow);
+      if(newPosition.z > mDataBlock->maxTerrainHeight + terrainheight)
+      {
+         if(newPosition.z > mDataBlock->maxTerrainHeight + terrainheight + maxcorrection)
+            newPosition.z -= maxcorrection;
+         else
+            newPosition.z = mDataBlock->maxTerrainHeight + terrainheight;
+      }
+      if(newPosition.z < mDataBlock->minTerrainHeight + terrainheight)
+      {
+         if(newPosition.z < mDataBlock->minTerrainHeight + terrainheight - maxcorrection)
+            newPosition.z += maxcorrection;
+         else
+            newPosition.z = mDataBlock->minTerrainHeight + terrainheight;
+      }
+   }

   // disable the source objects collision reponse while we determine
   // if the projectile is capable of moving from the old position
   // to the new position, otherwise we'll hit ourself
   if (bool(mSourceObject))
      mSourceObject->disableCollision();

About the author

Recent Blogs

Page «Previous 1 2
#1
12/21/2004 (9:58 pm)
Eric,

Great resource. And I love the formatting :)

Thanks,
Derk
#2
12/21/2004 (11:08 pm)
I love it too. That's why I copied it from you :)
#3
12/27/2004 (3:35 pm)
thanks for helping I'll plug it in right now ;).
#4
12/28/2004 (10:46 pm)
Good stuff!
#5
12/29/2004 (7:35 am)
Eric can I implement your code with Derk Adams
#6
12/29/2004 (7:51 pm)
They are mostly compatible, but there are a couple issues.

If you are using both of these, you need to be careful about the packdata and unpackdata functions. Both of us inserted code at the same point, and order is important. Place his bit of code before my bit of code in both places.

His code makes a missile follow some object. My code makes the missile stay between minTerrainHeight and maxTerrainHeight. What if the object being followed is outside that range? In that case, expect his code and my code to fight eachother. If speedTerrainFollowing is high (compared to precision), then the missile will stay between minTerrainHeight and maxTerrainHeight. If speedTerrainFollowing is low (compared to precision) then the missile will be able to leave this range in order to follow the object it is tracking.

All of this discussion is theoretic. I haven't actually tried to use both patches together. I think it would work fine though. Let us know how it works out.
#7
12/30/2004 (6:59 pm)
Is there a way to make the hovervehicle class be more "stable" in this way? Because my hovercraft I'm trying to make is very unstable. I would like it to be like the ones in UT2004... But great Code, Thanks...
#8
12/31/2004 (2:36 am)
I have never played with any vehicles in Torque before, let alone hovercraft, but here is my 2 cents. Making a stable vehicle is easy. Making a realistically unstable vehicle is hard. You want a stable hovercraft? Find where the code calculates drag, accelleration, thrust, lift... and replace it with nice simple code that keeps a constant height (like my missiles) and moves at a constant velocity.

If you want to mostly leave the physics the way it is but make absolutely sure that your vehicle never hits the ground, then maybe the same technique I used would work for you.

I have not played UT2004 or seen how your hovercraft operates. Post some details about what you're trying to do and what problem you hit and maybe someone can help. As it is, I can only guess what sort of problem you're talking about.
#9
12/31/2004 (11:37 pm)
yeah it works I have it in with derks code but there is no one else for it to seek so I don't think that proves anything.


there is one pretty big problem putting
isterrainfollowing = true
minterrainheight =100
etc..

in crossbow.cs doesn't do a thing its all being controlled by the similar code you put into projectile.cc

I edited it out (worth a try) and recompiled got no comiling errors went into my game and no projectiles appeared. eventually of flying around in the warsparrow firing my invisible machinegun I got this error
"error no zones found should find root at least"
something like thatI'll mess around with it but you might want to check that out.
#10
01/02/2005 (5:41 am)
>there is one pretty big problem putting
>isterrainfollowing = true
>minterrainheight =100
>etc..
>in crossbow.cs doesn
#11
01/02/2005 (8:18 pm)
I guess I'll just give you my whole crossbow.cs
projectile code

datablock ProjectileData(CrossbowProjectile)
{
projectileShapeName = "~/data/shapes/crossbow/projectile.dts";
directDamage = 20;
radiusDamage = 20;
damageRadius = 1.5;
explosion = CrossbowExplosion;
waterExplosion = CrossbowWaterExplosion;

particleEmitter = CrossbowBoltEmitter;
particleWaterEmitter= CrossbowBoltBubbleEmitter;

splash = CrossbowSplash;

muzzleVelocity = 100;
velInheritFactor = 0.3;

armingDelay = 0;
lifetime = 5000;
fadeDelay = 5000;
bounceElasticity = 0;
bounceFriction = 0;
%timeuntilcrash = 15;
isBallistic = false;
gravityMod = 0.80;

isTerrainFollowing = false; //dosn't stop the terrain following
minTerrainHeight = 1; //still stays 5 feet off
maxTerrainHeight = 1;
speedTerrainFollowing = %timeuntilcrash * gravityMod * 9.8;


isGuided = false; //most crossbows don't track
// Precision is how acurately the projectile tracks the target.
// 0 is no tracking (same as not guided)
// 100 is exact tracking
precision = 5;
// TrackDelay is the number of milliseconds after firing to begin tracking
trackDelay = 40;

hasLight = true;
etc..


all my weapons have terraintracking and even setting it to false doesn't work. as I said I believe it is because of the code implimented into projectile.cc which can be tweaked to change it.
so basically my problem is I only want a select few weapons to have terrain tracking and these weapons all to have different stats.

hope that helps

-Treb
#12
01/02/2005 (8:53 pm)
The most interesting thing about this, to me, is that isTerrainFollowing=false as a default in projectile.cc. If the script commands are having no effect, then you should not see any terrainfollowing.

Could you send me copies of projectile.h and projectile.cc? My email is available as a picture on this page:
plaza.ufl.edu/lavigne/ufgoclub/contact.html
#13
01/07/2005 (11:43 pm)
sweet................
#14
01/08/2005 (1:00 am)
I might have fixed it If not I'll send it soon
#15
01/08/2005 (4:47 pm)
Even if you fixed it, I'd like to know what the problem was. What was your fix?
#16
01/12/2005 (12:00 am)
I'm pretty sure this was my bad if it wasn't I'll let you know
#17
11/22/2005 (2:54 am)
This code works perfectly the Homeing code seemed to be conflicting with it so I took I had to take the homing code out I'll try to look for what might have been the problem. another minor problem is that upon getting off a hill the projectiles vector is set to a flat 0 degree's I'll look into that seeing that you're code doesn't tell it to do that.
#18
11/23/2005 (1:52 am)
okay I have a slight update to add

replace the two old if statements with these new ones

if(newPosition.z > mDataBlock->maxTerrainHeight + terrainheight)
{
if(newPosition.z > mDataBlock->maxTerrainHeight + terrainheight + maxcorrection)
newPosition.z -= maxcorrection;
mCurrVelocity.z = (newPosition.z - oldPosition.z)/(F32(TickMs)/1000.0f);
else
newPosition.z = mDataBlock->maxTerrainHeight + terrainheight;
mCurrVelocity.z = (newPosition.z - oldPosition.z)/(F32(TickMs)/1000.0f);
}
if(newPosition.z < mDataBlock->minTerrainHeight + terrainheight)
{
if(newPosition.z < mDataBlock->minTerrainHeight + terrainheight - maxcorrection)
{
newPosition.z += maxcorrection;
} else {
newPosition.z = mDataBlock->minTerrainHeight + terrainheight;
mCurrVelocity.z = (newPosition.z - oldPosition.z)/(F32(TickMs)/1000.0f);
}
}
}

that makes it so if you have a high max hieght projectiles use hills like ramps add some gravity and its quite fun!

Thanks again for this code It taught me quite a bit about C++ and I've made some cool things with it.
#19
06/02/2006 (3:52 am)
This doesn't work properly across a network because the getTerrainHeight() function relies on a serverside-only method of finding the Terrain object. More about this can be found here.

The solution (which is also in the linked thread) is to change the getTerrainHeight() function to:

F32 getTerrainHeight(Point2F position)
{
   F32 height = 0.0;
   
   //TerrainBlock *terrain = dynamic_cast<TerrainBlock*>(Sim::findObject("Terrain"));
   TerrainBlock *terrain = NULL;

   for (SimSetIterator obj(Sim::getRootGroup()); *obj; ++obj) 
   {
	if ((*obj)->getType() & TerrainObjectType) 
	{
		terrain = static_cast<TerrainBlock*>(*obj);
		// This is assuming there is only one TerrainBlock.
		break;
	}
   }

   if(terrain)
   {
	  //if(terrain->isServerObject())
	  //{
		 Point3F offset;
		 terrain->getTransform().getColumn(3, &offset);

		 position -= Point2F(offset.x, offset.y);
		 terrain->getHeight(position, &height);
	  //}
   }

   return height;
}

Special thanks to Scott Richards for coming up with a solution.
#20
01/20/2008 (7:56 pm)
It's a good job, thanks for this! Integrated perfectly. I made a change, however - I added two datablock booleans saying whether the projectile had a ceiling and/or floor height. So if ceiling is true, the projectile uses your code to stay lower than a certain distance above the terrain. If only floor is true, it'll go as high as you like, but will stay away from the terrain.
Page «Previous 1 2