Game Development Community

dev|Pro Game Development Curriculum

3d Mesh Line Tracers for Torque

by Max Robinson · 01/19/2006 (10:35 pm) · 16 comments

Download Code File

Notes: I modified the files to work with TGE 1.4. They probably won't compile on anything older. If you have an older project, and it doesn't compile, start looking at the initPersistFields() function and the DATATYPE macro calls.

Otherwise: I tested this just before uploading on TGE 1.4 and it works.


This resource implements 3d mesh line tracers into stock TGE. It is fully functional, but there are still some things that I need to finish here and there. These are:
- TSE support
- a "face camera" feature
- dynamic scaling
+ a few other things I've forgotten since I started working on it.

A foreword on the limitations:
In retrospect, I would have come at this with a different approach. At the time, I had no idea what I was doing, and while the problems of then are no longer present, the naive approach I took is still an issue. Basically, the tracers are very independent. That's good in some ways, since it lets you stick it on any object with minimal work, but it's also slightly limiting. As is, it'd be very hard to stick this onto a sword to make swooshes when it swings - that was one use I had in mind, too (note: this wouldn't be too hard to mod in, but its not present without modification). However, it works for almost every other situation I can think of, so I consider it a success. If you desire more control, you could modify it and leave pretty much only the allocation & rendering code intact. You could also make a second class that piggybacks off some of the existing systems and allows more direct control of the tracer geometry by the object it is attached to. I may change it up for the TSE version to be more direct - I'll give it some thought.

Anyway, to the resource.
You have to modify 2 files in normal TGE to make it work. Other than that, you need to include the 2 new files (I suggest putting them in game/fx/ ), and you need to modify any object that's supposed to use these so it properly intializes them.

Go to terrain/sky.cc.

At the very beginning, where it says:
// This dependency on game code needs to be removed.
#include "game/fx/particleEngine.h"
Change it to:
// This dependency on game code needs to be removed.
#include "game/fx/particleEngine.h"
[b]#include "game/fx/3dLineTracer.h"[/b]

So much for removing dependencies.... moving on now!

Find:
void Sky::setWindVelocity(const Point3F & vel)
{
   mWindVelocity = vel;
   ParticleEngine::setWindVelocity(vel);
   if(isServerObject())
      setMaskBits(WindMask);
}
Change it to:
void Sky::setWindVelocity(const Point3F & vel)
{
   mWindVelocity = vel;
   ParticleEngine::setWindVelocity(vel);
[b]   TracerEngine::setWindVelocity(vel);[/b]
   if(isServerObject())
      setMaskBits(WindMask);
}

I'll agree with GG - this is stupid! But I don't see an alternative. Maybe that's why it's still in TGE 1.4.


Now go to game/main.cc.

In the includes section, add in:
#include "game/fx/3dLineTracer.h"

I put it in after:
#include "game/fx/particleEngine.h"

Now find:
DetailManager::init();
   PathManager::init();
   ParticleEngine::init();
Add in:
DetailManager::init();
   PathManager::init();
   ParticleEngine::init();
[b]   TracerEngine::init();[/b]

And find:
ParticleEngine::destroy();
   PathManager::destroy();
   DetailManager::shutdown();
Similar to above, add in:
[b]   TracerEngine::destroy();[/b]
   ParticleEngine::destroy();
   PathManager::destroy();
   DetailManager::shutdown();

====================================================

Now extract 3dLineTracer.cc and 3dLineTracer.h to game/fx and include them in your project.
Compiling should work now, but you'll probably want to implement them first.

You can copy the projectile.cc and .h I have included if you want, but if you want to use this on something else, check out the how-to below. It outlines the steps needed to put these on *ANY* object.

====================================================

Check the header file on how to use the tracers. The fields on the Data object are documented, as well as the public functions of the tracer itself.

Some tips:

- normally the tracer just puts points in a ring around the object specified. It uses the "up" axis of its transform as the radius of the circle.

- you can use "useCustomShape=true;" and "shapeDim[0...32]" to specify distance coefficients for each point on your tracer. Only use dims up to the number of points you are using. You can draw a star or oval shape with this.

- you can use "useNodeTracking=true;" and "targetNodes[0...32]" to specify nodes for the tracer to track. In the engine, to make this work, the tracer needs to be asked if it wants a shape ("->wantsShape") and sent a shape ("->setTargetShape") when it is created. One tracer datablock can be added to any shape, and as long as they have nodes with matching names, they will attach. Like shapeDim, dont use more nodes than you have points!

- you can use "useNodeTracking=true;" and "useAssignedNodes=true;" and specify the nodes in the engine ("->setTargetShape and ->setTargetNode") if you want to use 1 datablock on 2 (or more) different sets of nodes. This saves you from making a lot of datablocks, and is very handy. I used it on a spaceship to create 12 maneuvering jets triggered by the engine code, it worked perfectly and only needed 1 datablock.

In terms of aesthetics, it is like anything else - mess around with it until you like it. They are quite flexible. You can use multiple textures and have them rotate and translate over time. They look good even without textures, too.

====================================================

Here's How to add tracers to any object. This class is patterned off of the particle emitter class, and they are very similar to add. I am leaving a little leeway on this tutorial for specific implementations. If adding particle emitters to an object is too difficult, you probably won't be able to add tracers to an object. I'll keep the steps to a minimum, to avoid confusion. Mostly, this is meant as a sanity check/reference - if you're an experienced user of Torque you can probably do this on your own.


The header

1) Declare.

Add:
#include "game/fx/3dLineTracer.h"
And:
class LineTracer;
class LineTracerData;

At the top of your header file.

2) You will probably want to use a constant to determine how many tracers you want. Here is a modified "Constants" section of a projectile that I use:
enum Constants {
	   NUM_PARTICLE_EMITTERS = 3,
	   NUM_TRACERS = 3,
	};

Just make sure its public.

3) Add in an array to point to your LineTracerData objects. This goes in the Data class. I like to put tracer stuff next to particle emitter stuff, if it's there.

LineTracerData* LineTracerList[NUM_TRACERS];
   S32 LineTracerIDList[NUM_TRACERS];


4) Now to the object class. You need an array of pointers to the tracers. Like above, I put it next to the particle stuff usually:

LineTracer *mLineTracer[YourObjectData::NUM_TRACERS];



The code file.

1) In the intialization function for the Data class, initialize the array for the datablock:
dMemset( LineTracerList, 0, sizeof( LineTracerList ) );
   dMemset( LineTracerIDList, 0, sizeof( LineTracerIDList ) );

In the normal projectile class, there's:
dMemset( particleEmitterList, 0, sizeof( particleEmitterList ) );
   dMemset( particleEmitterIDList, 0, sizeof( particleEmitterIDList ) );

I'd put it next to here.

2) Data::initPersistFields() - Expose the tracer array to script:
addField("linetracer",	TypeLineTracerDataPtr,	Offset(LineTracerList, YourObjectData), NUM_TRACERS);

3) Data::onAdd() - (some classes will do this stuff in a different place) - Let the client resolve the actual datablocks:

for (int idx=0; idx<NUM_TRACERS; idx++)
      if( !LineTracerList[idx] && LineTracerIDList[idx] != 0 )
         if( Sim::findObject( LineTracerIDList[idx], LineTracerList[idx] ) == false)
            Con::errorf(ConsoleLogEntry::General, "PlayerData::onAdd: Invalid packet, bad datablockId(linetracer): 0x%x", LineTracerIDList[idx]);

Look for anything similar (the "invalid packet" part is a good thing to search for) and put this alongside those.

4) Data::packData() - send the tracer datablocks down the wire. Here is a pretty efficient loop that will do it:

// - TRACERS
   int idx = 0;
   do
   {
      if( LineTracerList[idx] != NULL )
      {
         stream->writeFlag(true);
         stream->writeRangedU32( LineTracerList[idx]->getId(), DataBlockObjectIdFirst,  DataBlockObjectIdLast );
      }
      idx++;
   } while(idx<NUM_TRACERS);
   stream->writeFlag(false);

I'd put this at the very bottom of the packData function.

5) ::unpackData() - Just make sure you put this at the same point in ::unpackData as you put it in ::packData:

// - TRACERS
   int idx = 0;
   while(stream->readFlag() != false)
   {
      LineTracerIDList[idx] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
      idx++;
   }

If you put the part in ::packData at the bottom, put this at the bottom of ::unpackData

6) In the initialization function of the object, add:

dMemset( mLineTracer, 0, sizeof( mLineTracer ) );

On a projectile, I'd put it next to " dMemset( mParticleEmitter, 0, sizeof( mParticleEmitter ) ); "

7) ::onAdd or :onNewDatablock() - (or another applicable function) - Add:

for(int idx = 0; idx < YourObjectData::NUM_TRACERS; idx++)
	{
		if (mDataBlock->LineTracerList[idx] != NULL)
		{
			LineTracer* lTracer = new LineTracer;
			lTracer->setDataBlock(mDataBlock->LineTracerList[idx]);

			if (lTracer->registerObject() == false)
			{
				Con::warnf(ConsoleLogEntry::General, "Could not register LineTracer for object: %s", mDataBlock->getName());
				delete lTracer;
				lTracer = NULL;
			}
			else
			{
				lTracer->setTransform( getTransform() );
				if(lTracer->wantsShape())
					lTracer->setTargetShape(mDataBlock->projectileShapeName);
				lTracer->setTarget(this);
				mLineTracer[idx] = lTracer;
			}
		}
       }

Make sure this is only done on the client! It will not destabilize the server, but it is wasteful.

If you put this in ::onNewDataBlock() - make sure you delete the previous set or you'll have a memory leak.

Tracers can be turned on and off, dont delete and create them to turn them on and off. It's best to create the tracers when the object is created, unless you need to change out whichever tracers are in use during the object's lifetime.

8) ::onRemove - Add:

for(int idx = 0; idx < YourObjectData::NUM_TRACERS; idx++)
   {
      if (bool(mLineTracer[idx]))
      {
         mLineTracer[idx]->setDead();
         mLineTracer[idx] = NULL;
      }
   }

This is important. If you need to delete tracers elsewhere, remember to use ->setDead() before clearing a pointer to one. You don't need to use (and probably shouldn't use) ->deleteObject() with the tracer class.



Finally, you need to control the tracers. This is up to you. For instance, on a projectile, you should add something identical to the ::onRemove portion in the code where the projectile "dies," like in ::onExplode or ::processTick, or wherever is valid.

To control tracers, use "->setActive( );"
I would reccomend testing if the pointer is NULL first.

If you have a missile projectile that emits particles until its fuel runs out, you will probably want to stop the tracers when the particles stop also. Maybe you want to stop only 1 of the tracers, that's up to you. You can set up your own systems here.

One important thing to keep in mind: The tracer updates itself. All you need to do it turn it off and on. You can implement these tracers onto vehicle engines. When you run through to emit particles (or not emit particles), you can call "->isActive()" to check the state of the tracer, and use "->setActive()" to change its state to whatever it needs to be.

#1
01/15/2006 (8:49 pm)
cool resource I just popped it in and it worked very nicely I have yet to explore it but so far so good.
#2
01/16/2006 (4:26 am)
Have you got a larger screen shot of this in action?
#3
01/16/2006 (10:13 am)
Joseph:

here is a very old dev pic with some good shots of wierd stuff I did with it.

www.garagegames.com/mg/snapshot/view.php?qid=343

The pic there is of something rather conventional.

edit: the tracer in the bottom right of the dev pic is very similar to the one in use on the pic for this resource
#4
01/20/2006 (6:18 pm)
Very nice! I'm experimenting with high muzzle projectile velocities around 600... I see it's going to need some tweaking but, looks pretty cool so far! Thanks for the resource!
#5
01/20/2006 (6:20 pm)
Yeah, I havn't looked into it, but if you are using really fast projectiles, the techniques used on this resource:
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=9553

may apply to the tracer as well, since they are so similar. But I havn't checked this out yet.
#6
01/24/2006 (6:09 pm)
Thanks Max. Unfortunately, I wasn't able to get the 'Fixed Projectile Particle Rendering' resource you recomended working with the Torque Lighting Kit But, Your Tracers do work fine with TLK 1.4! So if anyone was curious.
Thanks
#7
01/27/2006 (3:47 am)
Love this resource, thanks so much for sharing!!!
#8
02/01/2006 (3:19 am)
I managed to get this working last night on a Pre-1.4 build. If anyone has any questions, I could answer questions.

Love this resource. My Minigun now looks MENACING :-D
#9
03/31/2006 (6:17 pm)
edit: had strange errors that were fixed by merging into a new SDK
#10
03/31/2006 (6:34 pm)
works well in the lighting pack
#11
06/04/2006 (5:18 am)
Great resource.

Your readme file has datablock definition with 'texture[0]' pointing to one of your own textures.
I just set it to:

texture[0] ="~/data/shapes/particles/spark";
#12
06/24/2006 (4:12 am)
i'm getting
game/fx/3dLineTracer.cc:28: error: expected constructor, destructor, or type conversion before [b]*[/b] token
while trying to compile it under linux.
Any thoughts?
#13
06/24/2006 (5:42 am)
blah! found a workaround..
moving block
namespace {
	TEngine * sgTracerEngine = NULL;
}
after codeblock
class TEngine
{
	static const U32  csmPointBlockSize;
	static const U32  csmRingBlockSize;
	
	U32 numRings;

	Vector<LineTracerRingPoint*>	mAllocatedPointBlocks;
	Vector<LineTracerRing*>			mAllocatedRingBlocks;
	LineTracerRingPoint*				mFreePointList;
	LineTracerRing*					mFreeRingList;

public:
	LineTracerRingPoint*		allocatePoint();
	LineTracerRing*			allocateRing();
	
	void      releasePoint(LineTracerRingPoint*);
	void      releaseRing(LineTracerRing*);

public:
	TEngine();
	~TEngine();
};
made it work.
#14
09/24/2006 (2:09 pm)
Hey Max...I have a heavily modified V1.3. I needed tracers for some of my weapons so I thought I'd give this a shot even though you wrote it for V1.4. Other than a few hiccups that made me pull my hair out,(basically registering the right type), I got it to work. It is exactly what I was looking for.

Thanks man!
#15
12/11/2006 (7:00 pm)
I feel for you Jackie. The big mixup from 1.3->1.4 for registering was quite confusing. I'm sure there was a post or something explaining it all, but I never read it :)
#16
01/07/2008 (4:39 am)
Successfully integrated this.

Great resource!

BTW, it would be good if there are some other example datablocks but testTrailer one.

Thank you for the cool resource!