Game Development Community

Towards turning materials into materials

by Kirk Longendyke · in Torque Game Engine Advanced · 07/13/2007 (4:55 am) · 23 replies

(this is gonna get a bit spammy. apologies)

First up, let me start out by making it verry clear: this is a nasty, filthy, hacky kludge of a work in progress, and isn't likely to see the inside of a recource for quite a while at this rate.

That being said, till there's an official writeup on how to do this type of thing, more than willing to see if this doesn't start the ball rolling on something better.

So, with the disclaimer out of the way, on to the meat of the endeavor:
Theres several things that the current material system is highly defective for in terms of asset interactivity, that we'll be looking to adress as this goes on, namely:

1- decal placement on a per-material basis
2- sounds likewise
3- general expolosive behavior(you'll see what i mean by that one shortly)
4- damage modification
5- elasticity/friction modification
Page «Previous 1 2
#1
07/13/2007 (4:56 am)
First up, we'll be starting our spelunking session with the following thread as a guideline for the additional raycasts:

www.garagegames.com/mg/forums/result.thread.php?qt=29713

This is so we'll have a few tools in our set that allow us to grab the visual face, and material of an object.

A few examples of this are as follows (and apologies for not making this a generalised sceneobject based solution right off the bat. Still studying that wrinkle):

diffs:

interior.h:
//add
#include "ts/tsmesh.h"
//endadd
interiorCollision.cc/cpp: (yes, I'm still use the oldschool file naming convention for ease of file comparrison with other resources. shoot me :P)
//add
#include "interior/interiorInstance.h"
#include "materials/material.h"
#include "materials/matInstance.h"
#include "materials/materialList.h"
//endadd
//replace in, or clone and overload:
bool Interior::castRay_r(
if(inside)
         {
            info->face = surfaceIndex;
            break;
         }
      }

      return true;
to
if(inside)
         {
			 info->face = surfaceIndex;
             info->material = rSurface.textureIndex;

             MatInstance* matInst = mMaterialList->getMaterialInst( rSurface.textureIndex );
             if ( matInst && matInst->getMaterial() )
			 {
				 info->material = matInst->getMaterial()->getId();
			 }
			 else
			 {
				 info->material = 0;
			 }

	  }
	  return true;
//endadd
#2
07/13/2007 (4:56 am)
Shapebase Derivations:

shapebase.h
//add
#include "materials/materialList.h"
#include "materials/matInstance.h"
#include "materials/material.h"

bool castRay( const Point3F& start, const Point3F& end, TriRayInfo* rayInfo );
//endadd

shapebase.cc
//add
#include "materials/materialList.h"
#include "materials/matInstance.h"
//endadd

bool ShapeBase::castRay( const Point3F& start, const Point3F& end, TriRayInfo* rayInfo )
{
   TSShapeInstance* si = this->getShapeInstance();
   if ( !si || !si->getShape() || si->getShape()->details.empty() )
      return false;
   
   TSShape* tsShape = si->getShape();

   Point3F tstart, tend;

   MatrixF mat = this->getWorldTransform();
   mat.mulP(start,&tstart);
   mat.mulP(end,&tend);

   si->setStatics(0);

   rayInfo->distance = F32_MAX;
   
   const TSDetail& detail = tsShape->details[0];
   S32 ss = detail.subShapeNum;
   S32 od = detail.objectDetailNum;

   S32 first = tsShape->subShapeFirstObject[ss];
   S32 last = tsShape->subShapeFirstObject[ss] + tsShape->subShapeNumObjects[ss];

   for ( S32 m=first; m < last; m++ )
   {
      TSMesh* mesh = tsShape->meshes[ m ];
      if ( !mesh || mesh->getMeshType() != TSMesh::StandardMeshType )
         continue;

      TriRayInfo info;
      if ( mesh->castRayTri( 0, tstart, tend, &info ) && info.distance < rayInfo->distance )
      {
         *rayInfo = info;

         // Ok... we got a hit... cast back into world 
         // space and return it.
         MatrixF invmat( mat );
         invmat.inverse();
         invmat.mulP(rayInfo->point);

         // Find the material for this surface.
         MatInstance* matInst = tsShape->materialList->getMaterialInst( rayInfo->material );
         if ( matInst && matInst->getMaterial() ) {
            rayInfo->material = matInst->getMaterial()->getId();
         } else {
            rayInfo->material = 0;
         }
      }
   }

   si->clearStatics();
   return rayInfo->distance < F32_MAX;
}

(we'll be revisiting this file in a bit, but for now, let's just get to the point of grabbing faces)
#3
07/13/2007 (4:57 am)
So ok. at this point, you've got a way to grab the material from an object. time to make that mean something, by adding some meaningfull data:

Nasty Hack Alert:

you really probably shouldn't use an array as we'll be setting up here, but for now, it's a quick, simple, and above all clear method for setting up a material functionality index

materials.h:
//add
#define MAX_MAT_FX 32
//endadd

class Material : public SimObject
//add
   S32 mFXIndex;
//endadd

(you'll notice that says FX, not sound, not gfx, ect. this is because for right now, higher complications will just serve to overcomplicate matters. Besides, I've never had call to use more than 32 types of material on a level, when you get right down to it)
#4
07/13/2007 (4:57 am)
You. Yeah, you in the back rolling your eyes. Time to wake on up, because now were getting to the fun parts: actually doing something with that mess up top.

First up, we'll be hitting on a verry specific case, then move on more general one that's still giving me a few issues.

For our first game, were focusing mostly on a simple little racing style to knock the kinks out of the vehiclular collisions, and generally all arround polish things up as far as the engine allows. One of the under-rated things folks don't usually notice unless it's missing is things like tire tracks, so heres a setup for that:
#5
07/13/2007 (4:58 am)
Data storage:

wheeledvehicle.h
in
struct WheeledVehicleTire: public SimDataBlock
//add
   DecalData *decalData[MAX_MAT_FX];
   S32 decalID[MAX_MAT_FX];
//endadd

in
struct WheeledVehicleData: public VehicleData
//add
   ParticleEmitterData* tireEmitter[MAX_MAT_FX];
//endadd

wheeledvehicletire.cc/.cpp:

WheeledVehicleTire::WheeledVehicleTire()
//add
   for (int i = 0; i<MAX_MAT_FX; i++)
   {
	   decalData[i]      = NULL;
	   decalID[i]        = 0;
   }
//endadd

bool WheeledVehicleTire::preload(bool server, char errorBuffer[256])
(place inside the if (shapeName && shapeName[0]) loop)
//add
	  for (int i = 0; i<MAX_MAT_FX; i++)
	  {
		  if (!decalData[i] && decalID[i] != 0 )
			  if (!Sim::findObject(decalID[i], decalData[i]))
				  Con::errorf(ConsoleLogEntry::General, "WheeledVehicleTire::preload Invalid packet, bad datablockId(decalData)[%i]: 0x%x",i, decalID);
	  }
//endadd

void WheeledVehicleTire::initPersistFields()

I'm going to take a moment to emphasise this one, as it's one of the more overlooked functions that the engine comes with stock:
//add
   addField("decalData", TypeSimObjectPtr, Offset(decalData, WheeledVehicleTire), MAX_MAT_FX);
//endadd
if you'll refference the shapeimmage source, you'll notice similar functinality for the various states things can be used with.


moving right along:
void WheeledVehicleTire::packData(BitStream* stream)
//add
   for (int i = 0; i<MAX_MAT_FX; i++)
   {
	   if( stream->writeFlag(decalData[i]))
	   {
		   stream->writeRangedU32( decalData[i]->getId(), DataBlockObjectIdFirst,  DataBlockObjectIdLast );
	   }
   }
//endadd

void WheeledVehicleTire::unpackData(BitStream* stream)
//add
   for (int i = 0; i<MAX_MAT_FX; i++)
   {
	   if( stream->readFlag() )
	   {
		   decalID[i] = (S32) stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
	   }
   }
//endadd

WheeledVehicleData::WheeledVehicleData()
//add
	for (int i=0; i<MAX_MAT_FX; i++)
		tireEmitter[i] = 0;
//endadd

bool WheeledVehicleData::preload(bool server, char errorBuffer[256])
//add
	for (int i=0; i<MAX_MAT_FX; i++)
      if (tireEmitter[i])
         Sim::findObject(SimObjectId(tireEmitter[i]),tireEmitter[i]);
//endadd

void WheeledVehicleData::initPersistFields()
//add
   addField("tireEmitter", TypeParticleEmitterDataPtr, Offset(tireEmitter, WheeledVehicleData), MAX_MAT_FX);
//endadd

void WheeledVehicleData::packData(BitStream* stream)
//add
   for (int i=0; i<MAX_MAT_FX; i++)
	   if (stream->writeFlag(tireEmitter[i]))
		   stream->writeRangedU32(packed? SimObjectId(tireEmitter[i]):
					tireEmitter[i]->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
//endadd

void WheeledVehicleData::unpackData(BitStream* stream)
//add
   for (int i=0; i<MAX_MAT_FX; i++)
	   tireEmitter[i] = stream->readFlag()?
								(ParticleEmitterData*) stream->readRangedU32(DataBlockObjectIdFirst,
								DataBlockObjectIdLast): 0;
//endadd

bool WheeledVehicle::onNewDataBlock(GameBaseData* dptr)
alter
// Each wheel get's it's own particle emitter
      if (mDataBlock->tireEmitter) {
         wheel->emitter = new ParticleEmitter;
         wheel->emitter->onNewDataBlock(mDataBlock->tireEmitter);

to
// Each wheel get's it's own particle emitter
      if (mDataBlock->tireEmitter[0]) {
         wheel->emitter = new ParticleEmitter;
         wheel->emitter->onNewDataBlock(mDataBlock->tireEmitter[0]);
#6
07/13/2007 (4:58 am)
Aaaaaaaand finally:

void WheeledVehicle::updateWheelParticles(F32 dt)
alter
if (speed > 1.0f)  
   {
      MaterialPropertyMap* matMap = MaterialPropertyMap::get();
      Point3F axis = vel;
      axis.normalize();

      // Only emit dust on the terrain
      Wheel* wend = &mWheel[mDataBlock->wheelCount];
      for (Wheel* wheel = mWheel; wheel < wend; wheel++) 
      {
         // Is this wheel in contact with the ground?
         if (wheel->tire && wheel->spring && !wheel->emitter.isNull() &&
               wheel->surface.contact && wheel->surface.object &&
               wheel->surface.object->getTypeMask() & TerrainObjectType)
         {

            TerrainBlock* tBlock = static_cast<TerrainBlock*>(wheel->surface.object);
            S32 mapIndex = tBlock->mMPMIndex[0];
            // Emit the dust, the density (time) is scaled by the
            // the vehicles velocity.
            wheel->emitter->emitParticles(wheel->surface.pos,true,
               axis, vel, (U32)(3/*dt * (speed / mDataBlock->maxWheelSpeed) * 1000 * wheel->slip*/));
         }
      }
to
if (speed > 1.0f)  
   {
	   MaterialPropertyMap* matMap = MaterialPropertyMap::get();
	   Point3F axis = vel;
	   axis.normalize();
	   
	   // Only emit dust on the terrain
	   Wheel* wend = &mWheel[mDataBlock->wheelCount];
	   for (Wheel* wheel = mWheel; wheel < wend; wheel++) 
	   {
		   // Is this wheel in contact with the ground?
		   if (wheel->tire && wheel->spring && !wheel->emitter.isNull() &&
			   wheel->surface.contact && wheel->surface.object &&
			   wheel->surface.object->getTypeMask() & STATIC_COLLISION_MASK)
		   {
			   //similar to the extend wheels function, except we multiply the length *2 
			   //to ensure were going to make a ray long enough to intersect the model were testing against
			   Point3F sp,vec;
			   currMatrix.mulP(wheel->data->pos,&sp);
			   currMatrix.mulV(VectorF(0,0,-wheel->spring->length),&vec);
			   F32 ts = wheel->tire->radius *2 / wheel->spring->length;
			   Point3F ep = sp + (vec * (1 + ts));
	   
			   TriRayInfo rInfo; 
			   if (mContainer->castRay(sp, ep, STATIC_COLLISION_MASK, &rInfo)) 
			   {
				   Material* matInst = static_cast<Material*>(Sim::findObject(rInfo.material)); 
				   if (matInst != NULL)
				   {
					   //Con::errorf("Tex: %s", matInst->getName());
					   if (this->mDataBlock->tireEmitter[matInst->mFXIndex] != NULL)
					   {
							wheel->emitter->setDataBlock(this->mDataBlock->tireEmitter[matInst->mFXIndex]);
							//Con::errorf("FX: %s", mDataBlock->tireEmitter[matInst->mFXIndex]->getName());
							if (wheel->tire->decalData[matInst->mFXIndex] != NULL)
							{
								if(mSceneManager->getCurrentDecalManager())
								{
									mSceneManager->getCurrentDecalManager()->addDecal(wheel->surface.pos, vel, wheel->surface.normal, wheel->tire->decalData[matInst->mFXIndex]);
							
								}
							}
							//else Con::errorf("Decals Still NULL");
					   }
				   }
				  //else Con::errorf("matInst Still Registers as (%i)",matInst); 
			   }

			   TerrainBlock* tBlock = static_cast<TerrainBlock*>(wheel->surface.object);
			   S32 mapIndex = tBlock->mMPMIndex[0];
			   // Emit the dust, the density (time) is scaled by the
			   // the vehicles velocity.
			   wheel->emitter->emitParticles(wheel->surface.pos,true,
				   axis, vel, (U32)(3));//dt * (speed / mEngineDatablock->maxWheelSpeed) * 1000 * wheel->slip));
		   }
	   }

and be sure to place a disableCollision();/enableCollision(); pair at rhe begining and end of the function
#7
07/13/2007 (4:58 am)
Now if you've followed along this far, youre likely thinking to yourself that this is pretty darned game specific
(even if you account for the fact that with minor changes you can do the same thing for footprints for
humans, instead of cars, or alter up sounds, instead in addition to, ect)

so lets cut to the chase then, and see about more generalised solutions:
#8
07/13/2007 (4:59 am)
Shapebase revisited:

shapebase.h

struct ShapeBaseData : public GameBaseData {
//add

   ExplosionData* mSpark[MAX_MAT_FX];
   S32 mSparkId[MAX_MAT_FX];

   ParticleEmitterData* SparkEmitter;
   S32 SparkEmitterId;

protected:
   bool onAdd();
//endadd

class ShapeBase : public GameBase
enum ShapeBaseMasks {
//alter
       SparkMask = Parent::NextFreeMask,
	   NameMask        = Parent::NextFreeMask << 1, //change each of these up by 1 till you hit ThreadMaskN     = SoundMaskN  << MaxSoundThreads,
   
//endalter

//add
   SimObjectPtr<ParticleEmitter> mSparkParticleEmitter;
   Point3F mSparkPosition;
   Point3F mSparkNormal;
   U32     mCollideSparkHitType;
   bool mHasCollided;
   SimObjectPtr<ShapeBase> lastHitObject;
   void ShapeBase::processFX();

   /// What to do when this projectile explodes
   virtual void ShapeBase::Spark(const Point3F& p, const Point3F& n, S32 matFxIndex);
   void emitParticles(const Point3F&, const Point3F&, const Point3F&, const U32);
   bool mFadeValue;
//endadd

again, you'll note the funcname's processFX, to denote were gonna be working with more than just visuals here

shapebase.cc/.cpp
ShapeBaseData::ShapeBaseData()
//add

	for (int i = 0; i<MAX_MAT_FX; i++)
	{
        mSpark[i] = NULL;
        mSparkId[i] = 0;
	}
}
//endadd

//add
bool ShapeBaseData::onAdd()
{
   if(!Parent::onAdd())
      return false;

   for (int i = 0; i<MAX_MAT_FX; i++)
	   if (!mSpark[i] && mSparkId[i] != 0)
		   if (Sim::findObject(mSparkId[i], mSpark[i]) == false)
			   Con::errorf(ConsoleLogEntry::General, "ProjectileData::onAdd: Invalid packet, bad datablockId(explosion)[%i]: %d", mSparkId[i],i);
   
   return true;
}
//endadd

void ShapeBaseData::initPersistFields()
//add
   addField("Sparks", TypeExplosionDataPtr, Offset(mSpark, ShapeBaseData), MAX_MAT_FX);
//endadd

(yes, I know. explosions. I'll explain that in a sec)

void ShapeBaseData::packData(BitStream* stream)
//add
   for (int i=0; i<MAX_MAT_FX; i++)
	   if (stream->writeFlag(mSpark[i] != NULL))
		   stream->writeRangedU32(mSpark[i]->getId(), DataBlockObjectIdFirst,
                                                 DataBlockObjectIdLast);
//endadd


void ShapeBaseData::unpackData(BitStream* stream)
//add
   for (int i = 0; i<MAX_MAT_FX; i++)
       if (stream->readFlag())
		   mSparkId[i] = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
//endadd

//add
//==============================================================================
//---------------------- Start FX Implementation ------------------------
//==============================================================================
void ShapeBase::Spark(const Point3F& p, const Point3F& n, S32 matFxIndex)
{
   // Make sure we don't explode twice...
   //if (mHidden == true)
      //return;

   //mHidden = true;
   // Client just plays the explosion at the right place...
   //
   Explosion* pSpark = NULL;
   
   F32 waterHeight;
   if (mDataBlock->mSpark[matFxIndex])
   {
	   pSpark = new Explosion;
	   pSpark->onNewDataBlock(mDataBlock->mSpark[matFxIndex]);
   }
   if( pSpark )
   {
	   MatrixF xform(true);
	   xform.setPosition(p);
	   pSpark->setTransform(xform);
	   pSpark->setInitialState(p, n);
	   pSpark->setCollideType( STATIC_COLLISION_MASK );
	   if (pSpark->registerObject() == false)
	   {
		   Con::errorf(ConsoleLogEntry::General, "Projectile(%s)::Spark: couldn't register Spark",
			   mDataBlock->getName() );
		   delete pSpark;
		   pSpark = NULL;
	   }
   }
   // Client object
   //updateSound();
}

void ShapeBase::processFX()
{
	if (this->isClientObject()) return;
	this->disableCollision();
	TriRayInfo rInfo; 
	for (CollisionTimeout* ptr = mTimeoutList; ptr; ptr = ptr->next)
	{
		ShapeBase* obj = static_cast<ShapeBase*>(Sim::findObject(ptr->objectNumber)); 
		if (obj != NULL)
		{
			Point3F sp, ep;
			sp = this->getPosition();
			ep = obj->getPosition();
			
			if (obj->castRay(sp, ep, &rInfo))
			{
				Material* matInst = static_cast<Material*>(Sim::findObject(rInfo.material));
				if (matInst != NULL)
				{
					//Con::errorf("Tex: %s", matInst->getName());
					//if (this->mDataBlock->tireEmitter[matInst->mFXIndex] != NULL)
					//{
					Spark(rInfo.point, rInfo.normal,matInst->mFXIndex);
					//Con::errorf("FX: %s", mDataBlock->tireEmitter[matInst->mFXIndex]->getName());
					//}
				}
				//else Con::errorf("matInst Still Registers as (%i)",matInst);
			}
		}
		//else Con::errorf("CollisionTimeout Object (%i) Still Registers as (%i)",ptr->objectNumber, obj);
	}
	//since there's no listing for static objects /interiorinstances in the CollisionTimeout list, we'll just have to
	//do this the brute-force way, till someone brighter throws us a bone
	Point3F sp, ep;
	sp = this->getPosition();
	ep = sp + this->getVelocity();
	if (mContainer->castRay(sp, ep, STATIC_COLLISION_MASK, &rInfo))
	{
		Material* matInst = static_cast<Material*>(Sim::findObject(rInfo.material));
		if (matInst != NULL)
		{
			//Con::errorf("Tex: %s", matInst->getName());
			Spark(rInfo.point, rInfo.normal,matInst->mFXIndex);
		}
		//else Con::errorf("matInst Still Registers as (%i)",matInst);
	}
	this->enableCollision();
}
//endadd
#9
07/13/2007 (4:59 am)
That knocks out 90% of what all we'll need to do, as explosions have
1- a special effect
2- a physical impulse you can use to replicate springiness vs a simple hard impact, vs some give, ect
3- an already embeded sound play state
4- damage dealing capabilities, so that you can modify the damage dealt by hitting that spot on that model

unfortunately folks, now's when I have to mention the catch:

at the moment, for singleplayer you can get the explosions working just fine by adding in
void ShapeBase::onImpact(SceneObject* obj, VectorF vec)
//add
	  processFX();
//endadd

void ShapeBase::onImpact(VectorF vec)
//add
	  processFX();
//endadd

However, doing some dedicated server testing earlier this morning, since I'd promised a couple
weeks back to toss this up for folks to pick apart, it would seem that there's a bit of a missing
link to track on down. I'll be taking a look at www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=8135
shortly, but really can't guarantee anything more solid than whats right here for a bit.

So there ya go. Potentially, at least, the makings of a real material system.

I know I for one will be taking it farther than this, but just how fast that get's done, and how much of the underlaying tech
is shared across other projects I'll leave to you folks, meaning just like this other www.garagegames.com/mg/forums/result.thread.php?qt=58720
don't expect to see it in a rescource any time soon. At least not without some severe acid testing from folks with other angles to throw at it
(and likely point out flaws and breaks, wich to my mind invalidates this type of thing as a 'rescource')

So there we are folks, warts and all. Feel free to shred the thing to your heart's contents. Know I'm not in love with the solution set myself just yet.
#10
07/13/2007 (8:43 am)
Neat idea, and neat post, but I would highly suggest that TDN is a better format for posting a mini-tutorial like this :)

Also, although you didn't discuss it in depth, it's important to note that in stock, explosions are not 'objects' within the world--they are the results of projectiles colliding. There is no way in stock to directly create just an explosion itself in your server or client without re-implementing.
#11
07/13/2007 (9:37 am)
Quote:
but I would highly suggest that TDN is a better format for posting a mini-tutorial like this

alright. next time will do, though this is, unfortunately, nothing even close to a tutorial, since about the only thing that's accomplished thusfar is decal aplication. Had I not stated I'd put something up by today, this wouldn't have seen the light of day till more was worked on out. (woulda been nice to have presented working sparks, or even terrain, wich what got me started on this line of experimentation: replacing the addmaterialmaping functionality that was gutted out)

Quote:
Also, although you didn't discuss it in depth, it's important to note that in stock, explosions are not 'objects' within the world--they are the results of projectiles colliding. There is no way in stock to directly create just an explosion itself in your server or client without re-implementing.

figured as much after going through things. One of many aspects I'm still mulling over at this point/researching/forumbrowsing for. But I suppose thats the wonders of self-education for ya. Spending 90% working out that 10% that's obvious to the next guy. Mebbey it'll start to make more sense when I finally get back to the house after this last couple weeks of travel-coding.
#12
07/13/2007 (12:07 pm)
You're casting the contact surface as a TerrainBlock. Any thoughts of support Atlas with this?
#13
07/13/2007 (3:08 pm)
Not yet, no. Was one of the things I was reffering to as sceneobject derivations though.

If you want to talk an educated guess based on a little research in terms of the available documentation and generation tools (ld3t this end) ... theres not really *a* atlas2, as I understand it, so much as unique and blended versions of the core type...

The former (if i'm reading this right) uses one real big texture, and the above methodologies are essentially based opon grabbing the corresponding material/custommaterial associated with that texture, so 10-1 that's out, while the latter uses an accumulation of textures simliar (yes?no?) to the old-style terrain, so the latter *might* be suitable for a specialised castray... either that or perhapse embedding an additional material channel?

Frankly, right now, i'll just be happy to get half the types of objects working. Should give us some pointers along the way on how to deal with the similar derivations at some point. That, and I for one have to admit reluctance to mess arround with atlas just yet, till I see what kinds of changes they make to support shadowmapping et al.
#14
07/13/2007 (3:58 pm)
You're correct about the 2 kinds of Atlas terrains. There's no real way to use this kind of system on the "unique" textured terrain, right now, without embedding additional information into the Atlas file. The Blended method could use the "blend map" (or texture map) to determine the kind of material that was struck.
#15
07/13/2007 (7:10 pm)
Quote:
without embedding additional information into the Atlas file.

I want to highlight this very strongly--that's exactly why you can embed additional information into the Atlas file (Table of Contents, and the abstract nature of the TOC/Chunk interface).

The structures are built in for all sorts of additional information availability, primarily for commercial simulation, but also extremely useful for other purposes as well.
#16
07/14/2007 (6:34 am)
...
or, completely contrary to what I was informed initally that made it seem a viable notion to totally waste this much time towards implementing this type of thing, materials were not merely removed from the ghosting procedure to save on loading, but removed from the server end entirely...that'll require quite a few more fundamental engine changes to get going... suppose for pure singleplayer someone might find the base research usefull though.
#17
07/20/2007 (5:26 pm)
Update on the *client-side* explosions bit, as a bit of a modification of the codebase posted

http://www.garagegames.com/mg/forums/result.thread.php?qt=63305

in the function:

void Vehicle::updatePos(F32 dt)
after the check
if (isServerObject()) {
...
}
replacing

else {
      // Play impact sounds on the client.
with
else {
      notifyCollision();
      // Server side impact script callback
      if (collided) {
         VectorF collVec = mRigid.linVelocity - origVelocity;
         F32 collSpeed = collVec.len();
         if (collSpeed > mDataBlock->minImpactSpeed)
            onImpact(collVec);
      }

      // Play impact sounds on the client.

sets it up to run the onImpact func on clinets as well as servers,

shifting processFX(); to outside theisGhost() check makes sure it'll play on both clients and servers (though thats more stub-placement for later expansion when I/someone brighter cooks up how to store the material data serverside without it horking, you coud just as easily set it an else call the func and call it a day)

and of course, going on up the chain for your favorite shapebase derivation and making sure you've got a client-side setup for a mTimeoutList, or equivalent.
#18
09/04/2007 (10:34 am)
To add to the list of getting materials from different object types, here's somehing for the legacy terrain (at least 1.01). I haven't embedded it in the castRay method since I've put my stuff in a utils class together with some other friction utils... I'll see if I get around to do it.

What we need to do is really to fix the broken terrData.cpp so it works as in TGE 1.5x.

In onAdd() find
StringTableEntry fn = mMaterialFileName[0];
   if(!dStrncmp(fn, "terrain.", 8))
      fn += 8;

   char nameBuff[512];
   dStrcpy(nameBuff, mTerrFileName);
   char *p = dStrrchr(nameBuff, '/');
   if (p) p++;
   else p = nameBuff;
   dStrcat(p,fn);

and change it to

MaterialPropertyMap* pMatMap = static_cast<MaterialPropertyMap*>(Sim::findObject("MaterialPropertyMap"));
   for (int i = 0; i < TerrainBlock::MaterialGroups; i++)
   {
      StringTableEntry fn = mMaterialFileName[i];
      if (fn != NULL)
      {
         if(!dStrncmp(fn, "terrain.", 8))
            fn += 8;
   
		char nameBuff[512];
		dStrcpy(nameBuff, fn);
		char *p = dStrrchr(nameBuff, '/');
		if (p) p++;
		else p = nameBuff;
		   mMPMIndex[i] = pMatMap->getIndexFromName(p);
      }
      else 
         mMPMIndex[i] = -1;
   }
You need to include "materials/materialPropertyMap.h" as well.
Next we add the method getTerrainMapIndex (also from TGE)
S32 TerrainBlock::getTerrainMapIndex(Point3F& pt)
{	
	U8 alphas[MaterialGroups];	
	int mapIndex= -1;
	const MatrixF & mat = getTransform();	
	Point3F origin;	
	mat.getColumn(3, &origin);	
	F32 squareSize = (F32) getSquareSize();	
	F32 halfSquareSize = squareSize / 2;	
	float x = (pt.x - origin.x + halfSquareSize) / squareSize;	
	float y = (pt.y - origin.y + halfSquareSize) / squareSize;	
	getMaterialAlpha(Point2I(x,y), alphas);	
	MaterialPropertyMap* matMap = static_cast<MaterialPropertyMap*>(Sim::findObject("MaterialPropertyMap"));	
	if (matMap != NULL)	
	{         		
		U8 maxAlpha = alphas[0];         		
		U8 maxAlpha2;         		
		int indexTexture = 0;                  		
		for (int i=1; i<MaterialGroups; i++)		
		{			
			maxAlpha2 = getMax(alphas[i],maxAlpha);			
			if (maxAlpha2 != maxAlpha)			
			{				
				indexTexture = i;				
				maxAlpha = maxAlpha2;			
			} 		
		} 		
		mapIndex = mMPMIndex[indexTexture];	
	} 	
	return mapIndex;
}

And that's all there's to it. This is how I use it:
if (ray.object->getTypeMask() & TerrainObjectType)
{
     TerrainBlock* tBlock = static_cast<TerrainBlock*>(ray.object);
     mapIndex = tBlock->getTerrainMapIndex(ray.point);
}

I'm also implementing a system for modifying friction, top speed, etc for my racing game depending on the material the wheels touches. The current implementation is just a grid of material vs material modifiers. Easy to implement, but a pain to maintain when you add new materials and properties. So I'm thinking of alternative system which would be more modifier based. I.e. you specify properties for the materials in terms of roughness, hardness, and so on, and calculate how the materials would react against each other; slick rubber vs tarmac = good friction, rough rubber vs tarmac = not as good, etc.
I don't know how racing games normally do this... Any thoghts anyone?

Thanks,
Kristoffer
#19
09/24/2007 (2:52 am)
@Stephen
tdn.garagegames.com/wiki/Material_Based_Effects_Projects

Wouldn't surprise me to find I missed including a thing or two here or there, but since it would appear the things pretty much stabile, there ya go.
#20
10/02/2007 (1:06 am)
Thanks a lot Kirk and Kristoffer, very much appreciated!

Unfortunately this code change in Interior::castRay_r (getting the material) didn't work for me in TGEA 1.0.3.

if(inside)
         {
			 info->face = surfaceIndex;
             info->material = rSurface.textureIndex;

             MatInstance* matInst = mMaterialList->getMaterialInst( rSurface.textureIndex );
             if ( matInst && matInst->getMaterial() )
			 {
				 info->material = matInst->getMaterial()->getId();
			 }
			 else
			 {
				 info->material = 0;
			 }

	  }

I replaced it with this (thanks to Clint S. Brewer)

if(inside)
{
   info->face = surfaceIndex;

   MaterialPropertyMap* pMatMap = MaterialPropertyMap::get();
   const char* pName = mMaterialList->getMaterialName(rSurface.textureIndex);

   info->material = pMatMap->getIndexFromName(pName);
   if(info->material == -1)
      info->material = 0; //no entry, use material 0
}

to get it working.
Page «Previous 1 2