Initial commit

This commit is contained in:
Eagle517
2025-02-17 23:17:30 -06:00
commit 7cad314c94
4726 changed files with 1145203 additions and 0 deletions

1683
engine/sim/actionMap.cc Executable file

File diff suppressed because it is too large Load Diff

146
engine/sim/actionMap.h Executable file
View File

@ -0,0 +1,146 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _ACTIONMAP_H_
#define _ACTIONMAP_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
struct InputEvent;
struct EventDescriptor
{
U8 flags; ///< Combination of any modifier flags.
U8 eventType; ///< SI_KEY, etc.
U16 eventCode; ///< From event.h
};
/// Map raw inputs to a variety of actions. This is used for all keymapping
/// in the engine.
/// @see ActionMap::Node
class ActionMap : public SimObject
{
typedef SimObject Parent;
protected:
bool onAdd();
struct Node {
U32 modifiers;
U32 action;
enum Flags {
Ranged = BIT(0), ///< Ranged input.
HasScale = BIT(1), ///< Scaled input.
HasDeadZone = BIT(2), ///< Dead zone is present.
Inverted = BIT(3), ///< Input is inverted.
BindCmd = BIT(4) ///< Bind a console command to this.
};
U32 flags; /// @see Node::Flags
F32 deadZoneBegin;
F32 deadZoneEnd;
F32 scaleFactor;
StringTableEntry consoleFunction; ///< Console function to call with new values.
char *makeConsoleCommand; ///< Console command to execute when we make this command.
char *breakConsoleCommand; ///< Console command to execute when we break this command.
};
/// Used to represent a devices.
struct DeviceMap
{
U32 deviceType;
U32 deviceInst;
Vector<Node> nodeMap;
DeviceMap() {
VECTOR_SET_ASSOCIATION(nodeMap);
}
~DeviceMap();
};
struct BreakEntry
{
U32 deviceType;
U32 deviceInst;
U32 objInst;
StringTableEntry consoleFunction;
char *breakConsoleCommand;
// It's possible that the node could be deleted (unlikely, but possible,
// so we replicate the node flags here...
//
U32 flags;
F32 deadZoneBegin;
F32 deadZoneEnd;
F32 scaleFactor;
};
Vector<DeviceMap*> mDeviceMaps;
static Vector<BreakEntry> smBreakTable;
// Find: return NULL if not found in current map, Get: create if not
// found.
const Node* findNode(const U32 inDeviceType, const U32 inDeviceInst,
const U32 inModifiers, const U32 inAction);
bool findBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex );
Node* getNode(const U32 inDeviceType, const U32 inDeviceInst,
const U32 inModifiers, const U32 inAction);
void removeNode(const U32 inDeviceType, const U32 inDeviceInst,
const U32 inModifiers, const U32 inAction);
void enterBreakEvent(const InputEvent* pEvent, const Node* pNode);
static const char* getModifierString(const U32 modifiers);
public:
ActionMap();
~ActionMap();
void dumpActionMap(const char* fileName, const bool append) const;
static bool createEventDescriptor(const char* pEventString, EventDescriptor* pDescriptor);
bool processBind(const U32 argc, const char** argv);
bool processBindCmd(const char *device, const char *action, const char *makeCmd, const char *breakCmd);
bool processUnbind(const char *device, const char *action);
/// @name Console Interface Functions
/// @{
const char* getBinding( const char* command ); ///< Find what the given command is bound to.
const char* getCommand( const char* device, const char* action ); ///< Find what command is bound to the given event descriptor .
bool isInverted( const char* device, const char* action );
F32 getScale( const char* device, const char* action );
const char* getDeadZone( const char* device, const char* action );
/// @}
static bool getKeyString(const U32 action, char* buffer);
static bool getDeviceName(const U32 deviceType, const U32 deviceInstance, char* buffer);
static const char* buildActionString( const InputEvent* event );
bool processAction(const InputEvent*);
static bool checkBreakTable(const InputEvent*);
static bool handleEvent(const InputEvent*);
static bool handleEventGlobal(const InputEvent*);
static bool getDeviceTypeAndInstance(const char *device, U32 &deviceType, U32 &deviceInstance);
DECLARE_CONOBJECT(ActionMap);
};
#endif // _ACTIONMAP_H_

View File

@ -0,0 +1,172 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (c) 2002 GarageGames.Com
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/dnet.h"
#include "console/simBase.h"
#include "sim/netConnection.h"
#include "core/bitStream.h"
#include "console/consoleTypes.h"
class NetStringEvent : public NetEvent
{
StringHandle mString;
U32 mIndex;
public:
NetStringEvent(U32 index = 0, StringHandle string = StringHandle())
{
mIndex = index;
mString = string;
}
virtual void pack(NetConnection* /*ps*/, BitStream *bstream)
{
bstream->writeInt(mIndex, ConnectionStringTable::EntryBitSize);
bstream->writeString(mString.getString());
}
virtual void write(NetConnection* /*ps*/, BitStream *bstream)
{
bstream->writeInt(mIndex, ConnectionStringTable::EntryBitSize);
bstream->writeString(mString.getString());
}
virtual void unpack(NetConnection* /*con*/, BitStream *bstream)
{
char buf[256];
mIndex = bstream->readInt(ConnectionStringTable::EntryBitSize);
bstream->readString(buf);
mString = StringHandle(buf);
}
virtual void notifyDelivered(NetConnection *ps, bool madeit)
{
if(madeit)
ps->confirmStringReceived(mString, mIndex);
}
virtual void process(NetConnection *connection)
{
Con::printf("Mapping string: %s to index: %d", mString.getString(), mIndex);
connection->mapString(mIndex, mString);
}
#ifdef TORQUE_DEBUG_NET
const char *getDebugName()
{
static char buffer[512];
dSprintf(buffer, sizeof(buffer), "%s - \"", getClassName());
expandEscape(buffer + dStrlen(buffer), mString.getString());
dStrcat(buffer, "\"");
return buffer;
}
#endif
DECLARE_CONOBJECT(NetStringEvent);
};
IMPLEMENT_CO_NETEVENT_V1(NetStringEvent);
//--------------------------------------------------------------------
ConnectionStringTable::ConnectionStringTable(NetConnection *parent)
{
mParent = parent;
for(U32 i = 0; i < EntryCount; i++)
{
mEntryTable[i].nextHash = NULL;
mEntryTable[i].nextLink = &mEntryTable[i+1];
mEntryTable[i].prevLink = &mEntryTable[i-1];
mEntryTable[i].index = i;
mHashTable[i] = NULL;
}
mLRUHead.nextLink = &mEntryTable[0];
mEntryTable[0].prevLink = &mLRUHead;
mLRUTail.prevLink = &mEntryTable[EntryCount-1];
mEntryTable[EntryCount-1].nextLink = &mLRUTail;
}
U32 ConnectionStringTable::getNetSendId(StringHandle &string)
{
// see if the entry is in the hash table right now
U32 hashIndex = string.getIndex() % EntryCount;
for(Entry *walk = mHashTable[hashIndex]; walk; walk = walk->nextHash)
if(walk->string == string)
return walk->index;
AssertFatal(0, "Net send id is not in the table. Error!");
return 0;
}
U32 ConnectionStringTable::checkString(StringHandle &string, bool *isOnOtherSide)
{
// see if the entry is in the hash table right now
U32 hashIndex = string.getIndex() % EntryCount;
for(Entry *walk = mHashTable[hashIndex]; walk; walk = walk->nextHash)
{
if(walk->string == string)
{
pushBack(walk);
if(isOnOtherSide)
*isOnOtherSide = walk->receiveConfirmed;
return walk->index;
}
}
// not in the hash table, means we have to add it
Entry *newEntry;
// pull the new entry from the LRU list.
newEntry = mLRUHead.nextLink;
pushBack(newEntry);
// remove the string from the hash table
Entry **hashWalk;
for (hashWalk = &mHashTable[newEntry->string.getIndex() % EntryCount]; *hashWalk; hashWalk = &((*hashWalk)->nextHash))
{
if(*hashWalk == newEntry)
{
*hashWalk = newEntry->nextHash;
break;
}
}
newEntry->string = string;
newEntry->receiveConfirmed = false;
newEntry->nextHash = mHashTable[hashIndex];
mHashTable[hashIndex] = newEntry;
mParent->postNetEvent(new NetStringEvent(newEntry->index, string));
if(isOnOtherSide)
*isOnOtherSide = false;
return newEntry->index;
}
void ConnectionStringTable::mapString(U32 netId, StringHandle &string)
{
// the netId is sent by the other side... throw it in our mapping table:
mRemoteStringTable[netId] = string;
}
void ConnectionStringTable::readDemoStartBlock(BitStream *stream)
{
// ok, reading the demo start block
for(U32 i = 0; i < EntryCount; i++)
{
if(stream->readFlag())
{
char buffer[256];
stream->readString(buffer);
mRemoteStringTable[i] = StringHandle(buffer);
}
}
}
void ConnectionStringTable::writeDemoStartBlock(ResizeBitStream *stream)
{
// ok, writing the demo start block
for(U32 i = 0; i < EntryCount; i++)
{
if(stream->writeFlag(mRemoteStringTable[i].isValidString()))
{
stream->writeString(mRemoteStringTable[i].getString());
stream->validate();
}
}
}

View File

@ -0,0 +1,92 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (c) 2002 GarageGames.Com
//-----------------------------------------------------------------------------
#ifndef _H_CONNECTIONSTRINGTABLE
#define _H_CONNECTIONSTRINGTABLE
/// Maintain a table of strings which are shared across the network.
///
/// This allows us to reference strings in our network streams more efficiently.
class ConnectionStringTable
{
public:
enum Constants {
EntryCount = 32,
EntryBitSize = 5,
InvalidEntryId = 32,
};
private:
struct Entry {
StringHandle string; ///< Global string table entry of this string
/// will be 0 if this string is unused.
U32 index; ///< index of this entry
Entry *nextHash; ///< the next hash entry for this id
Entry *nextLink; ///< the next in the LRU list
Entry *prevLink; ///< the prev entry in the LRU list
bool receiveConfirmed; ///< The other side now has this string.
};
Entry mEntryTable[EntryCount];
Entry *mHashTable[EntryCount];
StringHandle mRemoteStringTable[EntryCount];
Entry mLRUHead, mLRUTail;
/// Connection over which we are maintaining this string table.
NetConnection *mParent;
inline void pushBack(Entry *entry) // pushes an entry to the back of the LRU list
{
entry->prevLink->nextLink = entry->nextLink;
entry->nextLink->prevLink = entry->prevLink;
entry->nextLink = &mLRUTail;
entry->prevLink = mLRUTail.prevLink;
entry->nextLink->prevLink = entry;
entry->prevLink->nextLink = entry;
}
public:
/// Initialize the connection string table.
///
/// @param parent Connection over which we are maintaining this string table.
ConnectionStringTable(NetConnection *parent);
/// Has the specified string been received on the other side?
inline void confirmStringReceived(StringHandle &string, U32 index)
{
if(mEntryTable[index].string == string)
mEntryTable[index].receiveConfirmed = true;
}
U32 checkString(StringHandle &stringTableId, bool *stringOnOtherSide = NULL); ///< Checks if the global string ID is
/// currently valid for this connection
/// and returns the table ID.
/// Sends a string event to the other side
/// if it is not active.
/// It will fill in stringOnOtherSide.
U32 getNetSendId(StringHandle &stringTableId); ///< Same return value as checkString
/// but will assert if the string is not
/// valid.
void mapString(U32 netId, StringHandle &string); ///< Maps a string that
/// was just sent over the net
/// to the corresponding net ID.
inline StringHandle lookupString(U32 netId) ///< looks up the string ID and returns
{ /// the global string table ID for that string.
return mRemoteStringTable[netId];
}
/// @name Demo functionality
/// @{
void readDemoStartBlock(BitStream *stream);
void writeDemoStartBlock(ResizeBitStream *stream);
/// @}
};
#endif

408
engine/sim/decalManager.cc Executable file
View File

@ -0,0 +1,408 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "sim/decalManager.h"
#include "dgl/dgl.h"
#include "sceneGraph/sceneGraph.h"
#include "sceneGraph/sceneState.h"
#include "ts/tsShapeInstance.h"
#include "core/bitStream.h"
#include "console/consoleTypes.h"
bool DecalManager::smDecalsOn = true;
const U32 DecalManager::csmFreePoolBlockSize = 256;
U32 DecalManager::smMaxNumDecals = 256;
U32 DecalManager::smDecalTimeout = 5000;
DecalManager* gDecalManager = NULL;
IMPLEMENT_CONOBJECT(DecalManager);
IMPLEMENT_CO_DATABLOCK_V1(DecalData);
namespace {
int QSORT_CALLBACK cmpDecalInstance(const void* p1, const void* p2)
{
const DecalInstance** pd1 = (const DecalInstance**)p1;
const DecalInstance** pd2 = (const DecalInstance**)p2;
return int(((char *)(*pd1)->decalData) - ((char *)(*pd2)->decalData));
}
} // namespace {}
//--------------------------------------------------------------------------
DecalData::DecalData()
{
sizeX = 1;
sizeY = 1;
textureName = "";
}
DecalData::~DecalData()
{
if(gDecalManager)
gDecalManager->dataDeleted(this);
}
void DecalData::packData(BitStream* stream)
{
Parent::packData(stream);
stream->write(sizeX);
stream->write(sizeY);
stream->writeString(textureName);
}
void DecalData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
stream->read(&sizeX);
stream->read(&sizeY);
textureName = stream->readSTString();
}
bool DecalData::preload(bool server, char errorBuffer[256])
{
if (Parent::preload(server, errorBuffer) == false)
return false;
if (sizeX < 0.0) {
Con::warnf("DecalData::preload: sizeX < 0");
sizeX = 0;
}
if (sizeY < 0.0) {
Con::warnf("DecalData::preload: sizeX < 0");
sizeY = 0;
}
if (textureName == NULL || textureName[0] == '\0') {
Con::errorf("No texture name for decal!");
return false;
}
if (!server) {
textureHandle = TextureHandle(textureName, MeshTexture);
if (textureHandle.getGLName() == 0) {
Con::errorf("Unable to load texture: %s for decal!", textureName);
return false;
}
}
return true;
}
IMPLEMENT_CONSOLETYPE(DecalData)
IMPLEMENT_SETDATATYPE(DecalData)
IMPLEMENT_GETDATATYPE(DecalData)
void DecalData::initPersistFields()
{
addField("sizeX", TypeF32, Offset(sizeX, DecalData));
addField("sizeY", TypeF32, Offset(sizeY, DecalData));
addField("textureName", TypeFilename, Offset(textureName, DecalData));
}
DecalManager::DecalManager()
{
mTypeMask |= DecalManagerObjectType;
mObjBox.min.set(-1e7, -1e7, -1e7);
mObjBox.max.set( 1e7, 1e7, 1e7);
mWorldBox.min.set(-1e7, -1e7, -1e7);
mWorldBox.max.set( 1e7, 1e7, 1e7);
mFreePool = NULL;
VECTOR_SET_ASSOCIATION(mDecalQueue);
VECTOR_SET_ASSOCIATION(mFreePoolBlocks);
}
DecalManager::~DecalManager()
{
mFreePool = NULL;
for (S32 i = 0; i < mFreePoolBlocks.size(); i++)
{
delete [] mFreePoolBlocks[i];
}
mDecalQueue.clear();
}
DecalInstance* DecalManager::allocateDecalInstance()
{
if (mFreePool == NULL)
{
// Allocate a new block of decals
mFreePoolBlocks.push_back(new DecalInstance[csmFreePoolBlockSize]);
// Init them onto the free pool chain
DecalInstance* pNewInstances = mFreePoolBlocks.last();
for (U32 i = 0; i < csmFreePoolBlockSize - 1; i++)
pNewInstances[i].next = &pNewInstances[i + 1];
pNewInstances[csmFreePoolBlockSize - 1].next = NULL;
mFreePool = pNewInstances;
}
AssertFatal(mFreePool != NULL, "Error, should always have a free pool available here!");
DecalInstance* pRet = mFreePool;
mFreePool = pRet->next;
pRet->next = NULL;
return pRet;
}
void DecalManager::freeDecalInstance(DecalInstance* trash)
{
AssertFatal(trash != NULL, "Error, no trash pointer to free!");
trash->next = mFreePool;
mFreePool = trash;
}
void DecalManager::dataDeleted(DecalData *data)
{
for(S32 i = mDecalQueue.size() - 1; i >= 0; i--)
{
DecalInstance *inst = mDecalQueue[i];
if(inst->decalData == data)
{
freeDecalInstance(inst);
mDecalQueue.erase(U32(i));
}
}
}
void DecalManager::consoleInit()
{
Con::addVariable("$pref::decalsOn", TypeBool, &smDecalsOn);
Con::addVariable("$pref::Decal::maxNumDecals", TypeS32, &smMaxNumDecals);
Con::addVariable("$pref::Decal::decalTimeout", TypeS32, &smDecalTimeout);
}
void DecalManager::addDecal(const Point3F& pos,
Point3F normal,
DecalData* decalData)
{
if (smMaxNumDecals == 0)
return;
// DMM: Rework this, should be based on time
if(mDecalQueue.size() >= smMaxNumDecals)
{
DecalInstance* holder = mDecalQueue.front();
mDecalQueue.pop_front();
freeDecalInstance(holder);
}
Point3F vecX, vecY;
DecalInstance* newDecal = allocateDecalInstance();
newDecal->decalData = decalData;
newDecal->allocTime = Platform::getVirtualMilliseconds();
if(mFabs(normal.z) > 0.9f)
mCross(normal, Point3F(0.0f, 1.0f, 0.0f), &vecX);
else
mCross(normal, Point3F(0.0f, 0.0f, 1.0f), &vecX);
mCross(vecX, normal, &vecY);
normal.normalizeSafe();
Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008));
vecX.normalizeSafe();
vecY.normalizeSafe();
vecX *= decalData->sizeX;
vecY *= decalData->sizeY;
newDecal->point[0] = position + vecX + vecY;
newDecal->point[1] = position + vecX - vecY;
newDecal->point[2] = position - vecX - vecY;
newDecal->point[3] = position - vecX + vecY;
mDecalQueue.push_back(newDecal);
mQueueDirty = true;
}
void DecalManager::addDecal(const Point3F& pos,
const Point3F& rot,
Point3F normal,
DecalData* decalData)
{
if (smMaxNumDecals == 0)
return;
addDecal( pos, rot, normal, Point3F( 1, 1, 1 ), decalData );
}
void DecalManager::addDecal(const Point3F& pos,
const Point3F& rot,
Point3F normal,
const Point3F& scale,
DecalData* decalData)
{
if (smMaxNumDecals == 0)
return;
if(mDot(rot, normal) < 0.98)
{
// DMM: Rework this, should be based on time
if(mDecalQueue.size() >= smMaxNumDecals)
{
DecalInstance* holder = mDecalQueue.front();
mDecalQueue.pop_front();
freeDecalInstance(holder);
}
Point3F vecX, vecY;
DecalInstance* newDecal = allocateDecalInstance();
newDecal->decalData = decalData;
newDecal->allocTime = Platform::getVirtualMilliseconds();
mCross(rot, normal, &vecX);
mCross(normal, vecX, &vecY);
normal.normalize();
Point3F position = Point3F(pos.x + (normal.x * 0.008), pos.y + (normal.y * 0.008), pos.z + (normal.z * 0.008));
vecX.normalize();
vecX.convolve( scale );
vecY.normalize();
vecY.convolve( scale );
vecX *= decalData->sizeX;
vecY *= decalData->sizeY;
newDecal->point[0] = position + vecX + vecY;
newDecal->point[1] = position + vecX - vecY;
newDecal->point[2] = position - vecX - vecY;
newDecal->point[3] = position - vecX + vecY;
mDecalQueue.push_back(newDecal);
mQueueDirty = true;
}
}
bool DecalManager::prepRenderImage(SceneState* state, const U32 stateKey,
const U32 /*startZone*/, const bool /*modifyBaseState*/)
{
if (!smDecalsOn) return false;
if (isLastState(state, stateKey))
return false;
setLastState(state, stateKey);
if (mDecalQueue.size() == 0)
return false;
// This should be sufficient for most objects that don't manage zones, and
// don't need to return a specialized RenderImage...
SceneRenderImage* image = new SceneRenderImage;
image->obj = this;
image->isTranslucent = true;
image->sortType = SceneRenderImage::BeginSort;
state->insertRenderImage(image);
U32 currMs = Platform::getVirtualMilliseconds();
for (S32 i = mDecalQueue.size() - 1; i >= 0; i--)
{
U32 age = currMs - mDecalQueue[i]->allocTime;
if (age > smDecalTimeout)
{
freeDecalInstance(mDecalQueue[i]);
mDecalQueue.erase(i);
}
else if (age > ((3 * smDecalTimeout) / 4))
{
mDecalQueue[i]->fade = 1.0f - (F32(age - ((3 * smDecalTimeout) / 4)) / F32(smDecalTimeout / 4));
}
else
{
mDecalQueue[i]->fade = 1.0f;
}
}
if (mQueueDirty == true)
{
// Sort the decals based on the data pointers...
dQsort(mDecalQueue.address(),
mDecalQueue.size(),
sizeof(DecalInstance*),
cmpDecalInstance);
mQueueDirty = false;
}
return false;
}
void DecalManager::renderObject(SceneState* state, SceneRenderImage*)
{
if (!smDecalsOn) return;
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");
RectI viewport;
glMatrixMode(GL_PROJECTION);
glPushMatrix();
dglGetViewport(&viewport);
state->setupBaseProjection();
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
renderDecal();
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
dglSetViewport(viewport);
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
}
void DecalManager::renderDecal()
{
glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glEnable(GL_ALPHA_TEST);
glDepthMask(GL_FALSE);
glAlphaFunc(GL_GREATER, 0.1f);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
DecalData* pLastData = NULL;
for (S32 x = 0; x < mDecalQueue.size(); x++)
{
if (mDecalQueue[x]->decalData != pLastData)
{
glBindTexture(GL_TEXTURE_2D, mDecalQueue[x]->decalData->textureHandle.getGLName());
pLastData = mDecalQueue[x]->decalData;
}
glColor4f(1, 1, 1, mDecalQueue[x]->fade);
glBegin(GL_TRIANGLE_FAN); {
glTexCoord2f(0, 0); glVertex3fv(mDecalQueue[x]->point[0]);
glTexCoord2f(0, 1); glVertex3fv(mDecalQueue[x]->point[1]);
glTexCoord2f(1, 1); glVertex3fv(mDecalQueue[x]->point[2]);
glTexCoord2f(1, 0); glVertex3fv(mDecalQueue[x]->point[3]);
} glEnd();
}
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glDisable(GL_TEXTURE_2D);
glDisable(GL_ALPHA_TEST);
}

111
engine/sim/decalManager.h Executable file
View File

@ -0,0 +1,111 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _DECALMANAGER_H_
#define _DECALMANAGER_H_
#ifndef _SCENEOBJECT_H_
#include "sim/sceneObject.h"
#endif
#ifndef _GTEXMANAGER_H_
#include "dgl/gTexManager.h"
#endif
/// DataBlock implementation for decals.
class DecalData : public SimDataBlock
{
typedef SimDataBlock Parent;
//-------------------------------------- Console set variables
public:
F32 sizeX;
F32 sizeY;
StringTableEntry textureName;
//-------------------------------------- load set variables
public:
TextureHandle textureHandle;
public:
DecalData();
~DecalData();
void packData(BitStream*);
void unpackData(BitStream*);
bool preload(bool server, char errorBuffer[256]);
DECLARE_CONOBJECT(DecalData);
static void initPersistFields();
};
DECLARE_CONSOLETYPE(DecalData)
/// Store an instance of a decal.
struct DecalInstance
{
DecalData* decalData;
Point3F point[4];
U32 allocTime;
F32 fade;
DecalInstance* next;
};
/// Manage decals in the world.
class DecalManager : public SceneObject
{
typedef SceneObject Parent;
Vector<DecalInstance*> mDecalQueue;
bool mQueueDirty;
static U32 smMaxNumDecals;
static U32 smDecalTimeout;
static const U32 csmFreePoolBlockSize;
Vector<DecalInstance*> mFreePoolBlocks;
DecalInstance* mFreePool;
protected:
bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState);
void renderObject(SceneState *state, SceneRenderImage *image);
DecalInstance* allocateDecalInstance();
void freeDecalInstance(DecalInstance*);
public:
DecalManager();
~DecalManager();
static void consoleInit();
/// @name Decal Addition
///
/// These functions allow you to add new decals to the world.
/// @{
void addDecal(const Point3F& pos,
const Point3F& rot,
Point3F normal,
const Point3F& scale,
DecalData*);
void addDecal(const Point3F& pos,
const Point3F& rot,
Point3F normal,
DecalData*);
void addDecal(const Point3F& pos,
Point3F normal,
DecalData*);
/// @}
void dataDeleted(DecalData *data);
void renderDecal();
DECLARE_CONOBJECT(DecalManager);
static bool smDecalsOn;
};
extern DecalManager* gDecalManager;
#endif // _H_DecalManager

1193
engine/sim/netConnection.cc Executable file

File diff suppressed because it is too large Load Diff

1105
engine/sim/netConnection.h Executable file

File diff suppressed because it is too large Load Diff

244
engine/sim/netDownload.cc Executable file
View File

@ -0,0 +1,244 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (c) GarageGames.Com
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/dnet.h"
#include "console/simBase.h"
#include "sim/netConnection.h"
#include "core/bitStream.h"
#include "sim/netObject.h"
#include "core/resManager.h"
class FileDownloadRequestEvent : public NetEvent
{
public:
enum
{
MaxFileNames = 31,
};
U32 nameCount;
char mFileNames[MaxFileNames][256];
FileDownloadRequestEvent(Vector<char *> *nameList = NULL)
{
nameCount = 0;
if(nameList)
{
nameCount = nameList->size();
if(nameCount > MaxFileNames)
nameCount = MaxFileNames;
for(U32 i = 0; i < nameCount; i++)
{
dStrcpy(mFileNames[i], (*nameList)[i]);
Con::printf("Sending request for file %s", mFileNames[i]);
}
}
}
virtual void pack(NetConnection *, BitStream *bstream)
{
bstream->writeRangedU32(nameCount, 0, MaxFileNames);
for(U32 i = 0; i < nameCount; i++)
bstream->writeString(mFileNames[i]);
}
virtual void write(NetConnection *, BitStream *bstream)
{
bstream->writeRangedU32(nameCount, 0, MaxFileNames);
for(U32 i = 0; i < nameCount; i++)
bstream->writeString(mFileNames[i]);
}
virtual void unpack(NetConnection *, BitStream *bstream)
{
nameCount = bstream->readRangedU32(0, MaxFileNames);
for(U32 i = 0; i < nameCount; i++)
bstream->readString(mFileNames[i]);
}
virtual void process(NetConnection *connection)
{
U32 i;
for(i = 0; i < nameCount; i++)
if(connection->startSendingFile(mFileNames[i]))
break;
if(i == nameCount)
connection->startSendingFile(NULL); // none of the files were sent
}
DECLARE_CONOBJECT(FileDownloadRequestEvent);
};
IMPLEMENT_CO_NETEVENT_V1(FileDownloadRequestEvent);
class FileChunkEvent : public NetEvent
{
public:
enum
{
ChunkSize = 63,
};
U8 chunkData[ChunkSize];
U32 chunkLen;
FileChunkEvent(U8 *data = NULL, U32 len = 0)
{
if(data)
dMemcpy(chunkData, data, len);
chunkLen = len;
}
virtual void pack(NetConnection *, BitStream *bstream)
{
bstream->writeRangedU32(chunkLen, 0, ChunkSize);
bstream->write(chunkLen, chunkData);
}
virtual void write(NetConnection *, BitStream *bstream)
{
bstream->writeRangedU32(chunkLen, 0, ChunkSize);
bstream->write(chunkLen, chunkData);
}
virtual void unpack(NetConnection *, BitStream *bstream)
{
chunkLen = bstream->readRangedU32(0, ChunkSize);
bstream->read(chunkLen, chunkData);
}
virtual void process(NetConnection *connection)
{
connection->chunkReceived(chunkData, chunkLen);
}
virtual void notifyDelivered(NetConnection *nc, bool madeIt)
{
if(!nc->isRemoved())
nc->sendFileChunk();
}
DECLARE_CONOBJECT(FileChunkEvent);
};
IMPLEMENT_CO_NETEVENT_V1(FileChunkEvent);
void NetConnection::sendFileChunk()
{
U8 buffer[FileChunkEvent::ChunkSize];
U32 len = FileChunkEvent::ChunkSize;
if(len + mCurrentFileBufferOffset > mCurrentFileBufferSize)
len = mCurrentFileBufferSize - mCurrentFileBufferOffset;
if(!len)
{
ResourceManager->closeStream(mCurrentDownloadingFile);
mCurrentDownloadingFile = NULL;
return;
}
mCurrentFileBufferOffset += len;
mCurrentDownloadingFile->read(len, buffer);
postNetEvent(new FileChunkEvent(buffer, len));
}
bool NetConnection::startSendingFile(const char *fileName)
{
if(!fileName || Con::getBoolVariable("$NetConnection::neverUploadFiles"))
{
sendConnectionMessage(SendNextDownloadRequest);
return false;
}
mCurrentDownloadingFile = ResourceManager->openStream(fileName);
if(!mCurrentDownloadingFile)
{
// the server didn't have the file, so send a 0 byte chunk:
Con::printf("No such file '%s'.", fileName);
postNetEvent(new FileChunkEvent(NULL, 0));
return false;
}
Con::printf("Sending file '%s'.", fileName);
mCurrentFileBufferSize = mCurrentDownloadingFile->getStreamSize();
mCurrentFileBufferOffset = 0;
// always have 32 file chunks (64 bytes each) in transit
sendConnectionMessage(FileDownloadSizeMessage, mCurrentFileBufferSize);
for(U32 i = 0; i < 32; i++)
sendFileChunk();
return true;
}
void NetConnection::sendNextFileDownloadRequest()
{
// see if we've already downloaded this file...
while(mMissingFileList.size() && (ResourceManager->find(mMissingFileList[0]) || Con::getBoolVariable("$NetConnection::neverDownloadFiles")))
{
dFree(mMissingFileList[0]);
mMissingFileList.pop_front();
}
if(mMissingFileList.size())
{
postNetEvent(new FileDownloadRequestEvent(&mMissingFileList));
}
else
{
fileDownloadSegmentComplete();
}
}
void NetConnection::chunkReceived(U8 *chunkData, U32 chunkLen)
{
if(chunkLen == 0)
{
// the server didn't have the file... apparently it's one we don't need...
dFree(mCurrentFileBuffer);
mCurrentFileBuffer = NULL;
dFree(mMissingFileList[0]);
mMissingFileList.pop_front();
return;
}
if(chunkLen + mCurrentFileBufferOffset > mCurrentFileBufferSize)
{
setLastError("Invalid file chunk from server.");
return;
}
dMemcpy(((U8 *) mCurrentFileBuffer) + mCurrentFileBufferOffset, chunkData, chunkLen);
mCurrentFileBufferOffset += chunkLen;
if(mCurrentFileBufferOffset == mCurrentFileBufferSize)
{
// this file's done...
// save it to disk:
FileStream stream;
Con::printf("Saving file %s.", mMissingFileList[0]);
if(!ResourceManager->openFileForWrite(stream, mMissingFileList[0]))
{
setLastError("Couldn't open file downloaded by server.");
return;
}
dFree(mMissingFileList[0]);
mMissingFileList.pop_front();
stream.write(mCurrentFileBufferSize, mCurrentFileBuffer);
stream.close();
mNumDownloadedFiles++;
dFree(mCurrentFileBuffer);
mCurrentFileBuffer = NULL;
sendNextFileDownloadRequest();
}
else
{
Con::executef(4, "onFileChunkReceived", mMissingFileList[0], Con::getIntArg(mCurrentFileBufferOffset), Con::getIntArg(mCurrentFileBufferSize));
}
}

413
engine/sim/netEvent.cc Executable file
View File

@ -0,0 +1,413 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/dnet.h"
#include "console/simBase.h"
#include "sim/netConnection.h"
#include "core/bitStream.h"
#define DebugChecksum 0xF00DBAAD
FreeListChunker<NetEventNote> NetConnection::mEventNoteChunker;
NetEvent::~NetEvent()
{
AssertWarn(mRefCount == 0, "NetEvent::~NetEvent - encountered non-zero ref count!");
}
void NetEvent::notifyDelivered(NetConnection *, bool)
{
}
void NetEvent::notifySent(NetConnection *)
{
}
#ifdef TORQUE_DEBUG_NET
const char *NetEvent::getDebugName()
{
return getClassName();
}
#endif
void NetConnection::eventOnRemove()
{
while(mNotifyEventList)
{
NetEventNote *temp = mNotifyEventList;
mNotifyEventList = temp->mNextEvent;
temp->mEvent->notifyDelivered(this, true);
temp->mEvent->decRef();
mEventNoteChunker.free(temp);
}
while(mUnorderedSendEventQueueHead)
{
NetEventNote *temp = mUnorderedSendEventQueueHead;
mUnorderedSendEventQueueHead = temp->mNextEvent;
temp->mEvent->notifyDelivered(this, true);
temp->mEvent->decRef();
mEventNoteChunker.free(temp);
}
while(mSendEventQueueHead)
{
NetEventNote *temp = mSendEventQueueHead;
mSendEventQueueHead = temp->mNextEvent;
temp->mEvent->notifyDelivered(this, true);
temp->mEvent->decRef();
mEventNoteChunker.free(temp);
}
}
void NetConnection::eventPacketDropped(PacketNotify *notify)
{
NetEventNote *walk = notify->eventList;
NetEventNote **insertList = &mSendEventQueueHead;
NetEventNote *temp;
while(walk)
{
switch(walk->mEvent->mGuaranteeType)
{
// It was a guaranteed ordered packet, reinsert it back into
// mSendEventQueueHead in the right place (based on seq numbers)
case NetEvent::GuaranteedOrdered:
//Con::printf("EVT %d: DROP - %d", getId(), walk->mSeqCount);
while(*insertList && (*insertList)->mSeqCount < walk->mSeqCount)
insertList = &((*insertList)->mNextEvent);
temp = walk->mNextEvent;
walk->mNextEvent = *insertList;
if(!walk->mNextEvent)
mSendEventQueueTail = walk;
*insertList = walk;
insertList = &(walk->mNextEvent);
walk = temp;
break;
// It was a guaranteed packet, put it at the top of
// mUnorderedSendEventQueueHead.
case NetEvent::Guaranteed:
temp = walk->mNextEvent;
walk->mNextEvent = mUnorderedSendEventQueueHead;
mUnorderedSendEventQueueHead = walk;
if(!walk->mNextEvent)
mUnorderedSendEventQueueTail = walk;
walk = temp;
break;
// Or else it was an unguaranteed packet, notify that
// it was _not_ delivered and blast it.
case NetEvent::Unguaranteed:
walk->mEvent->notifyDelivered(this, false);
walk->mEvent->decRef();
temp = walk->mNextEvent;
mEventNoteChunker.free(walk);
walk = temp;
}
}
}
void NetConnection::eventPacketReceived(PacketNotify *notify)
{
NetEventNote *walk = notify->eventList;
NetEventNote **noteList = &mNotifyEventList;
while(walk)
{
NetEventNote *next = walk->mNextEvent;
if(walk->mEvent->mGuaranteeType != NetEvent::GuaranteedOrdered)
{
walk->mEvent->notifyDelivered(this, true);
walk->mEvent->decRef();
mEventNoteChunker.free(walk);
walk = next;
}
else
{
while(*noteList && (*noteList)->mSeqCount < walk->mSeqCount)
noteList = &((*noteList)->mNextEvent);
walk->mNextEvent = *noteList;
*noteList = walk;
noteList = &walk->mNextEvent;
walk = next;
}
}
while(mNotifyEventList && mNotifyEventList->mSeqCount == mLastAckedEventSeq + 1)
{
mLastAckedEventSeq++;
NetEventNote *next = mNotifyEventList->mNextEvent;
//Con::printf("EVT %d: ACK - %d", getId(), mNotifyEventList->mSeqCount);
mNotifyEventList->mEvent->notifyDelivered(this, true);
mNotifyEventList->mEvent->decRef();
mEventNoteChunker.free(mNotifyEventList);
mNotifyEventList = next;
}
}
void NetConnection::eventWritePacket(BitStream *bstream, PacketNotify *notify)
{
#ifdef TORQUE_DEBUG_NET
bstream->writeInt(DebugChecksum, 32);
#endif
NetEventNote *packQueueHead = NULL, *packQueueTail = NULL;
while(mUnorderedSendEventQueueHead)
{
if(bstream->isFull())
break;
// dequeue the first event
NetEventNote *ev = mUnorderedSendEventQueueHead;
mUnorderedSendEventQueueHead = ev->mNextEvent;
U32 start = bstream->getCurPos();
bstream->writeFlag(true);
S32 classId = ev->mEvent->getClassId(getNetClassGroup());
bstream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup());
ev->mEvent->pack(this, bstream);
DEBUG_LOG(("PKLOG %d EVENT %d: %s", getId(), bstream->getCurPos() - start, ev->mEvent->getDebugName()) );
#ifdef TORQUE_DEBUG_NET
bstream->writeInt(classId ^ DebugChecksum, 32);
#endif
// add this event onto the packet queue
ev->mNextEvent = NULL;
if(!packQueueHead)
packQueueHead = ev;
else
packQueueTail->mNextEvent = ev;
packQueueTail = ev;
}
bstream->writeFlag(false);
S32 prevSeq = -2;
while(mSendEventQueueHead)
{
if(bstream->isFull())
break;
// if the event window is full, stop processing
if(mSendEventQueueHead->mSeqCount > mLastAckedEventSeq + 126)
break;
// dequeue the first event
NetEventNote *ev = mSendEventQueueHead;
mSendEventQueueHead = ev->mNextEvent;
//Con::printf("EVT %d: SEND - %d", getId(), ev->mSeqCount);
bstream->writeFlag(true);
ev->mNextEvent = NULL;
if(!packQueueHead)
packQueueHead = ev;
else
packQueueTail->mNextEvent = ev;
packQueueTail = ev;
if(!bstream->writeFlag(ev->mSeqCount == prevSeq + 1))
bstream->writeInt(ev->mSeqCount, 7);
prevSeq = ev->mSeqCount;
U32 start = bstream->getCurPos();
S32 classId = ev->mEvent->getClassId(getNetClassGroup());
bstream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup());
ev->mEvent->pack(this, bstream);
DEBUG_LOG(("PKLOG %d EVENT %d: %s", getId(), bstream->getCurPos() - start, ev->mEvent->getDebugName()) );
#ifdef TORQUE_DEBUG_NET
bstream->writeInt(classId ^ DebugChecksum, 32);
#endif
}
for(NetEventNote *ev = packQueueHead; ev; ev = ev->mNextEvent)
ev->mEvent->notifySent(this);
notify->eventList = packQueueHead;
bstream->writeFlag(0);
}
void NetConnection::eventReadPacket(BitStream *bstream)
{
#ifdef TORQUE_DEBUG_NET
U32 sum = bstream->readInt(32);
AssertISV(sum == DebugChecksum, "Invalid checksum.");
#endif
S32 prevSeq = -2;
NetEventNote **waitInsert = &mWaitSeqEvents;
bool unguaranteedPhase = true;
while(true)
{
bool bit = bstream->readFlag();
if(unguaranteedPhase && !bit)
{
unguaranteedPhase = false;
bit = bstream->readFlag();
}
if(!unguaranteedPhase && !bit)
break;
S32 seq = -1;
if(!unguaranteedPhase) // get the sequence
{
if(bstream->readFlag())
seq = (prevSeq + 1) & 0x7f;
else
seq = bstream->readInt(7);
prevSeq = seq;
}
S32 classId = bstream->readClassId(NetClassTypeEvent, getNetClassGroup());
if(classId == -1)
{
setLastError("Invalid packet.");
return;
}
NetEvent *evt = (NetEvent *) ConsoleObject::create(getNetClassGroup(), NetClassTypeEvent, classId);
if(!evt)
{
setLastError("Invalid packet.");
return;
}
AbstractClassRep *rep = evt->getClassRep();
if((rep->mNetEventDir == NetEventDirServerToClient && !isConnectionToServer())
|| (rep->mNetEventDir == NetEventDirClientToServer && isConnectionToServer()) )
{
setLastError("Invalid Packet.");
return;
}
evt->mSourceId = getId();
evt->unpack(this, bstream);
if(mErrorBuffer[0])
return;
#ifdef TORQUE_DEBUG_NET
U32 checksum = bstream->readInt(32);
AssertISV( (checksum ^ DebugChecksum) == (U32)classId,
avar("unpack did not match pack for event of class %s.",
evt->getClassName()) );
#endif
if(unguaranteedPhase)
{
evt->process(this);
evt->decRef();
if(mErrorBuffer[0])
return;
continue;
}
seq |= (mNextRecvEventSeq & ~0x7F);
if(seq < mNextRecvEventSeq)
seq += 128;
NetEventNote *note = mEventNoteChunker.alloc();
note->mEvent = evt;
note->mEvent->incRef();
note->mSeqCount = seq;
//Con::printf("EVT %d: RECV - %d", getId(), evt->mSeqCount);
while(*waitInsert && (*waitInsert)->mSeqCount < seq)
waitInsert = &((*waitInsert)->mNextEvent);
note->mNextEvent = *waitInsert;
*waitInsert = note;
waitInsert = &(note->mNextEvent);
}
while(mWaitSeqEvents && mWaitSeqEvents->mSeqCount == mNextRecvEventSeq)
{
mNextRecvEventSeq++;
NetEventNote *temp = mWaitSeqEvents;
mWaitSeqEvents = temp->mNextEvent;
//Con::printf("EVT %d: PROCESS - %d", getId(), temp->mSeqCount);
temp->mEvent->process(this);
temp->mEvent->decRef();
mEventNoteChunker.free(temp);
if(mErrorBuffer[0])
return;
}
}
bool NetConnection::postNetEvent(NetEvent *theEvent)
{
if(!mSendingEvents)
{
theEvent->decRef();
return false;
}
NetEventNote *event = mEventNoteChunker.alloc();
event->mEvent = theEvent;
theEvent->incRef();
event->mNextEvent = NULL;
if(theEvent->mGuaranteeType == NetEvent::GuaranteedOrdered)
{
event->mSeqCount = mNextSendEventSeq++;
if(!mSendEventQueueHead)
mSendEventQueueHead = event;
else
mSendEventQueueTail->mNextEvent = event;
mSendEventQueueTail = event;
}
else
{
event->mSeqCount = InvalidSendEventSeq;
if(!mUnorderedSendEventQueueHead)
mUnorderedSendEventQueueHead = event;
else
mUnorderedSendEventQueueTail->mNextEvent = event;
mUnorderedSendEventQueueTail = event;
}
return true;
}
void NetConnection::eventWriteStartBlock(ResizeBitStream *stream)
{
stream->write(mNextRecvEventSeq);
for(NetEventNote *walk = mWaitSeqEvents; walk; walk = walk->mNextEvent)
{
stream->writeFlag(true);
S32 classId = walk->mEvent->getClassId(getNetClassGroup());
stream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup());
walk->mEvent->write(this, stream);
stream->validate();
}
stream->writeFlag(false);
}
void NetConnection::eventReadStartBlock(BitStream *stream)
{
stream->read(&mNextRecvEventSeq);
NetEventNote *lastEvent = NULL;
while(stream->readFlag())
{
S32 classTag = stream->readClassId(NetClassTypeEvent, getNetClassGroup());
NetEvent *evt = (NetEvent *) ConsoleObject::create(getNetClassGroup(), NetClassTypeEvent, classTag);
evt->unpack(this, stream);
NetEventNote *add = mEventNoteChunker.alloc();
add->mEvent = evt;
evt->incRef();
add->mNextEvent = NULL;
if(!lastEvent)
mWaitSeqEvents = add;
else
lastEvent->mNextEvent = add;
lastEvent = add;
}
}

1103
engine/sim/netGhost.cc Executable file

File diff suppressed because it is too large Load Diff

622
engine/sim/netInterface.cc Executable file
View File

@ -0,0 +1,622 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (c) 2002 GarageGames.Com
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "platform/event.h"
#include "sim/netConnection.h"
#include "sim/netInterface.h"
#include "core/bitStream.h"
#include "math/mRandom.h"
#include "platform/gameInterface.h"
NetInterface *GNet = NULL;
NetInterface::NetInterface()
{
AssertFatal(GNet == NULL, "ERROR: Multiple net interfaces declared.");
GNet = this;
mLastTimeoutCheckTime = 0;
mAllowConnections = true;
}
void NetInterface::initRandomData()
{
mRandomDataInitialized = true;
U32 seed = Platform::getRealMilliseconds();
if(Game->isJournalReading())
Game->journalRead(&seed);
else if(Game->isJournalWriting())
Game->journalWrite(seed);
MRandomR250 myRandom(seed);
for(U32 i = 0; i < 12; i++)
mRandomHashData[i] = myRandom.randI();
}
void NetInterface::addPendingConnection(NetConnection *connection)
{
Con::printf("Adding a pending connection");
mPendingConnections.push_back(connection);
}
void NetInterface::removePendingConnection(NetConnection *connection)
{
for(U32 i = 0; i < mPendingConnections.size(); i++)
if(mPendingConnections[i] == connection)
mPendingConnections.erase(i);
}
NetConnection *NetInterface::findPendingConnection(const NetAddress *address, U32 connectSequence)
{
for(U32 i = 0; i < mPendingConnections.size(); i++)
if(Net::compareAddresses(address, mPendingConnections[i]->getNetAddress()) &&
connectSequence == mPendingConnections[i]->getSequence())
return mPendingConnections[i];
return NULL;
}
void NetInterface::processPacketReceiveEvent(PacketReceiveEvent *prEvent)
{
U32 dataSize = prEvent->size - PacketReceiveEventHeaderSize;
BitStream pStream(prEvent->data, dataSize);
// Determine what to do with this packet:
if(prEvent->data[0] & 0x01) // it's a protocol packet...
{
// if the LSB of the first byte is set, it's a game data packet
// so pass it to the appropriate connection.
// lookup the connection in the addressTable
NetConnection *conn = NetConnection::lookup(&prEvent->sourceAddress);
if(conn)
conn->processRawPacket(&pStream);
}
else
{
// Otherwise, it's either a game info packet or a
// connection handshake packet.
U8 packetType;
pStream.read(&packetType);
NetAddress *addr = &prEvent->sourceAddress;
if(packetType <= GameHeartbeat)
handleInfoPacket(addr, packetType, &pStream);
else
{
// check if there's a connection already:
NetConnection *conn;
switch(packetType)
{
case ConnectChallengeRequest:
handleConnectChallengeRequest(addr, &pStream);
break;
case ConnectRequest:
handleConnectRequest(addr, &pStream);
break;
case ConnectChallengeResponse:
handleConnectChallengeResponse(addr, &pStream);
break;
case ConnectAccept:
handleConnectAccept(addr, &pStream);
break;
case Disconnect:
handleDisconnect(addr, &pStream);
break;
case ConnectReject:
handleConnectReject(addr, &pStream);
break;
}
}
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// Connection handshaking basic overview:
// The torque engine does a two phase connect handshake to
// prevent a spoofed source address Denial-of-Service (DOS) attack
//
// Basically, the initiator of a connection (client) sends a
// Connect Challenge Request packet to the server to initiate the connection
// The server then hashes the source address of the client request
// with some random magic server data to come up with a 16-byte key that
// the client can then use to gain entry to the server.
// This way there are no partially active connection records on the
// server at all.
//
// The client then sends a Connect Request packet to the server,
// including any game specific data necessary to start a connection (a
// server password, for instance), along with the key the server sent
// on the Connect Challenge Response packet.
//
// The server, on receipt of the Connect Request, compares the
// entry key with a computed key, makes sure it can create the requested
// NetConnection subclass, and then passes all processing on to the connection
// instance.
//
// If the subclass reads and accepts he connect request successfully, the
// server sends a Connect Accept packet - otherwise the connection
// is rejected with the sendConnectReject function
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void NetInterface::sendConnectChallengeRequest(NetConnection *conn)
{
Con::printf("Sending Connect challenge Request");
BitStream *out = BitStream::getPacketStream();
out->write(U8(ConnectChallengeRequest));
out->write(conn->getSequence());
conn->mConnectSendCount++;
conn->mConnectLastSendTime = Platform::getVirtualMilliseconds();
BitStream::sendPacketStream(conn->getNetAddress());
}
void NetInterface::handleConnectChallengeRequest(const NetAddress *addr, BitStream *stream)
{
char buf[256];
Net::addressToString(addr, buf);
Con::printf("Got Connect challenge Request from %s", buf);
if(!mAllowConnections)
return;
U32 connectSequence;
stream->read(&connectSequence);
if(!mRandomDataInitialized)
initRandomData();
U32 addressDigest[4];
computeNetMD5(addr, connectSequence, addressDigest);
BitStream *out = BitStream::getPacketStream();
out->write(U8(ConnectChallengeResponse));
out->write(connectSequence);
out->write(addressDigest[0]);
out->write(addressDigest[1]);
out->write(addressDigest[2]);
out->write(addressDigest[3]);
BitStream::sendPacketStream(addr);
}
//-----------------------------------------------------------------------------
void NetInterface::handleConnectChallengeResponse(const NetAddress *address, BitStream *stream)
{
Con::printf("Got Connect challenge Response");
U32 connectSequence;
stream->read(&connectSequence);
NetConnection *conn = findPendingConnection(address, connectSequence);
if(!conn || conn->getConnectionState() != NetConnection::AwaitingChallengeResponse)
return;
U32 addressDigest[4];
stream->read(&addressDigest[0]);
stream->read(&addressDigest[1]);
stream->read(&addressDigest[2]);
stream->read(&addressDigest[3]);
conn->setAddressDigest(addressDigest);
conn->setConnectionState(NetConnection::AwaitingConnectResponse);
conn->mConnectSendCount = 0;
Con::printf("Sending Connect Request");
sendConnectRequest(conn);
}
//-----------------------------------------------------------------------------
void NetInterface::sendConnectRequest(NetConnection *conn)
{
BitStream *out = BitStream::getPacketStream();
out->write(U8(ConnectRequest));
out->write(conn->getSequence());
U32 addressDigest[4];
conn->getAddressDigest(addressDigest);
out->write(addressDigest[0]);
out->write(addressDigest[1]);
out->write(addressDigest[2]);
out->write(addressDigest[3]);
out->writeString(conn->getClassName());
conn->writeConnectRequest(out);
conn->mConnectSendCount++;
conn->mConnectLastSendTime = Platform::getVirtualMilliseconds();
BitStream::sendPacketStream(conn->getNetAddress());
}
//-----------------------------------------------------------------------------
void NetInterface::handleConnectRequest(const NetAddress *address, BitStream *stream)
{
if(!mAllowConnections)
return;
Con::printf("Got Connect Request");
U32 connectSequence;
stream->read(&connectSequence);
// see if the connection is in the main connection table:
NetConnection *connect = NetConnection::lookup(address);
if(connect && connect->getSequence() == connectSequence)
{
sendConnectAccept(connect);
return;
}
U32 addressDigest[4];
U32 computedAddressDigest[4];
stream->read(&addressDigest[0]);
stream->read(&addressDigest[1]);
stream->read(&addressDigest[2]);
stream->read(&addressDigest[3]);
computeNetMD5(address, connectSequence, computedAddressDigest);
if(addressDigest[0] != computedAddressDigest[0] ||
addressDigest[1] != computedAddressDigest[1] ||
addressDigest[2] != computedAddressDigest[2] ||
addressDigest[3] != computedAddressDigest[3])
return; // bogus connection attempt
if(connect)
{
if(connect->getSequence() > connectSequence)
return; // the existing connection should be kept - the incoming request is stale.
else
connect->deleteObject(); // disconnect this one, and allow the new one to be created.
}
char connectionClass[255];
stream->readString(connectionClass);
ConsoleObject *co = ConsoleObject::create(connectionClass);
NetConnection *conn = dynamic_cast<NetConnection *>(co);
if(!conn || !conn->canRemoteCreate())
{
delete co;
return;
}
conn->registerObject();
conn->setNetAddress(address);
conn->setNetworkConnection(true);
conn->setSequence(connectSequence);
const char *errorString = NULL;
if(!conn->readConnectRequest(stream, &errorString))
{
sendConnectReject(conn, errorString);
conn->deleteObject();
return;
}
conn->setNetworkConnection(true);
conn->onConnectionEstablished(false);
conn->setEstablished();
conn->setConnectSequence(connectSequence);
sendConnectAccept(conn);
}
//-----------------------------------------------------------------------------
void NetInterface::sendConnectAccept(NetConnection *conn)
{
BitStream *out = BitStream::getPacketStream();
out->write(U8(ConnectAccept));
out->write(conn->getSequence());
conn->writeConnectAccept(out);
BitStream::sendPacketStream(conn->getNetAddress());
}
void NetInterface::handleConnectAccept(const NetAddress *address, BitStream *stream)
{
U32 connectSequence;
stream->read(&connectSequence);
NetConnection *conn = findPendingConnection(address, connectSequence);
if(!conn || conn->getConnectionState() != NetConnection::AwaitingConnectResponse)
return;
const char *errorString = NULL;
if(!conn->readConnectAccept(stream, &errorString))
{
conn->handleStartupError(errorString);
removePendingConnection(conn);
conn->deleteObject();
return;
}
removePendingConnection(conn); // remove from the pending connection list
conn->setNetworkConnection(true);
conn->onConnectionEstablished(true); // notify the connection that it has been established
conn->setEstablished(); // installs the connection in the connection table, and causes pings/timeouts to happen
conn->setConnectSequence(connectSequence);
}
void NetInterface::sendConnectReject(NetConnection *conn, const char *reason)
{
if(!reason)
return; // if the stream is NULL, we reject silently
BitStream *out = BitStream::getPacketStream();
out->write(U8(ConnectReject));
out->write(conn->getSequence());
out->writeString(reason);
BitStream::sendPacketStream(conn->getNetAddress());
}
void NetInterface::handleConnectReject(const NetAddress *address, BitStream *stream)
{
U32 connectSequence;
stream->read(&connectSequence);
NetConnection *conn = findPendingConnection(address, connectSequence);
if(!conn || (conn->getConnectionState() != NetConnection::AwaitingChallengeResponse &&
conn->getConnectionState() != NetConnection::AwaitingConnectResponse))
return;
removePendingConnection(conn);
char reason[256];
stream->readString(reason);
conn->onConnectionRejected(reason);
conn->deleteObject();
}
void NetInterface::handleDisconnect(const NetAddress *address, BitStream *stream)
{
NetConnection *conn = NetConnection::lookup(address);
if(!conn)
return;
U32 connectSequence;
char reason[256];
stream->read(&connectSequence);
stream->readString(reason);
if(conn->getSequence() != connectSequence)
return;
conn->onDisconnect(reason);
conn->deleteObject();
}
void NetInterface::handleInfoPacket(const NetAddress *address, U8 packetType, BitStream *stream)
{
}
void NetInterface::processClient()
{
NetObject::collapseDirtyList(); // collapse all the mask bits...
for(NetConnection *walk = NetConnection::getConnectionList();
walk; walk = walk->getNext())
{
if(walk->isConnectionToServer() && (walk->isLocalConnection() || walk->isNetworkConnection()))
walk->checkPacketSend(false);
}
}
void NetInterface::processServer()
{
NetObject::collapseDirtyList(); // collapse all the mask bits...
for(NetConnection *walk = NetConnection::getConnectionList();
walk; walk = walk->getNext())
{
if(!walk->isConnectionToServer() && (walk->isLocalConnection() || walk->isNetworkConnection()))
walk->checkPacketSend(false);
}
}
void NetInterface::startConnection(NetConnection *conn)
{
addPendingConnection(conn);
conn->mConnectionSendCount = 0;
conn->setConnectSequence(Platform::getVirtualMilliseconds());
conn->setConnectionState(NetConnection::AwaitingChallengeResponse);
// This is a the client side of the connection, so set the connection to
// server flag. We need to set this early so that if the connection times
// out, its onRemove() will handle the cleanup properly.
conn->setIsConnectionToServer();
// Everything set, so send off the request.
sendConnectChallengeRequest(conn);
}
void NetInterface::sendDisconnectPacket(NetConnection *conn, const char *reason)
{
Con::printf("Issuing Disconnect packet.");
// send a disconnect packet...
U32 connectSequence = conn->getSequence();
BitStream *out = BitStream::getPacketStream();
out->write(U8(Disconnect));
out->write(connectSequence);
out->writeString(reason);
BitStream::sendPacketStream(conn->getNetAddress());
}
void NetInterface::checkTimeouts()
{
U32 time = Platform::getVirtualMilliseconds();
if(time > mLastTimeoutCheckTime + TimeoutCheckInterval)
{
for(U32 i = 0; i < mPendingConnections.size();)
{
NetConnection *pending = mPendingConnections[i];
if(pending->getConnectionState() == NetConnection::AwaitingChallengeResponse &&
time > pending->mConnectLastSendTime + ChallengeRetryTime)
{
if(pending->mConnectSendCount > ChallengeRetryCount)
{
pending->onConnectTimedOut();
removePendingConnection(pending);
pending->deleteObject();
continue;
}
else
sendConnectChallengeRequest(pending);
}
else if(pending->getConnectionState() == NetConnection::AwaitingConnectResponse &&
time > pending->mConnectLastSendTime + ConnectRetryTime)
{
if(pending->mConnectSendCount > ConnectRetryCount)
{
pending->onConnectTimedOut();
removePendingConnection(pending);
pending->deleteObject();
continue;
}
else
sendConnectRequest(pending);
}
i++;
}
mLastTimeoutCheckTime = time;
NetConnection *walk = NetConnection::getConnectionList();
while(walk)
{
NetConnection *next = walk->getNext();
if(walk->checkTimeout(time))
{
// this baddie timed out
walk->onTimedOut();
walk->deleteObject();
}
walk = next;
}
}
}
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
inline U32 rotlFixed(U32 x, unsigned int y)
{
return (x >> y) | (x << (32 - y));
}
#define MD5STEP(f, w, x, y, z, data, s) w = rotlFixed(w + f(x, y, z) + data, s) + x
void NetInterface::computeNetMD5(const NetAddress *address, U32 connectSequence, U32 digest[4])
{
digest[0] = 0x67452301L;
digest[1] = 0xefcdab89L;
digest[2] = 0x98badcfeL;
digest[3] = 0x10325476L;
U32 a, b, c, d;
a=digest[0];
b=digest[1];
c=digest[2];
d=digest[3];
U32 in[16];
in[0] = address->type;
in[1] = (U32(address->netNum[0]) << 24) |
(U32(address->netNum[1]) << 16) |
(U32(address->netNum[2]) << 8) |
(U32(address->netNum[3]));
in[2] = address->port;
in[3] = connectSequence;
for(U32 i = 0; i < 12; i++)
in[i + 4] = mRandomHashData[i];
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
digest[0]+=a;
digest[1]+=b;
digest[2]+=c;
digest[3]+=d;
}
ConsoleFunctionGroupBegin(NetInterface, "Global control functions for the netInterfaces.");
ConsoleFunction(allowConnections,void,2,2,"allowConnections(bool);")
{
argc;
GNet->setAllowsConnections(dAtob(argv[1]));
}
ConsoleFunctionGroupEnd(NetInterface);

120
engine/sim/netInterface.h Executable file
View File

@ -0,0 +1,120 @@
#ifndef _H_NETINTERFACE
#define _H_NETINTERFACE
/// NetInterface class. Manages all valid and pending notify protocol connections.
///
/// @see NetConnection, GameConnection, NetObject, NetEvent
class NetInterface
{
public:
/// PacketType is encoded as the first byte of each packet. If the LSB of
/// the first byte is set (i.e. if the type number is odd), then the packet
/// is a data protocol packet, otherwise it's an OOB packet, suitable for
/// use in strange protocols, like game querying or connection initialization.
enum PacketTypes
{
MasterServerGameTypesRequest = 2,
MasterServerGameTypesResponse = 4,
MasterServerListRequest = 6,
MasterServerListResponse = 8,
GameMasterInfoRequest = 10,
GameMasterInfoResponse = 12,
GamePingRequest = 14,
GamePingResponse = 16,
GameInfoRequest = 18,
GameInfoResponse = 20,
GameHeartbeat = 22,
ConnectChallengeRequest = 26,
ConnectChallengeReject = 28,
ConnectChallengeResponse = 30,
ConnectRequest = 32,
ConnectReject = 34,
ConnectAccept = 36,
Disconnect = 38,
};
protected:
Vector<NetConnection *> mPendingConnections; ///< List of connections that are in the startup phase.
U32 mLastTimeoutCheckTime; ///< Last time all the active connections were checked for timeouts.
U32 mRandomHashData[12]; ///< Data that gets hashed with connect challenge requests to prevent connection spoofing.
bool mRandomDataInitialized; ///< Have we initialized our random number generator?
bool mAllowConnections; ///< Is this NetInterface allowing connections at this time?
enum NetInterfaceConstants
{
MaxPendingConnects = 20, ///< Maximum number of pending connections. If new connection requests come in before
ChallengeRetryCount = 4, ///< Number of times to send connect challenge requests before giving up.
ChallengeRetryTime = 2500, ///< Timeout interval in milliseconds before retrying connect challenge.
ConnectRetryCount = 4, ///< Number of times to send connect requests before giving up.
ConnectRetryTime = 2500, ///< Timeout interval in milliseconds before retrying connect request.
TimeoutCheckInterval = 1500, ///< Interval in milliseconds between checking for connection timeouts.
};
/// Initialize random data.
void initRandomData();
/// @name Connection management
/// Most of these are pretty self-explanatory.
/// @{
void addPendingConnection(NetConnection *conn);
NetConnection *findPendingConnection(const NetAddress *address, U32 packetSequence);
void removePendingConnection(NetConnection *conn);
void sendConnectChallengeRequest(NetConnection *conn);
void handleConnectChallengeRequest(const NetAddress *addr, BitStream *stream);
void handleConnectChallengeResponse(const NetAddress *address, BitStream *stream);
void sendConnectRequest(NetConnection *conn);
void handleConnectRequest(const NetAddress *address, BitStream *stream);
void sendConnectAccept(NetConnection *conn);
void handleConnectAccept(const NetAddress *address, BitStream *stream);
void sendConnectReject(NetConnection *conn, const char *reason);
void handleConnectReject(const NetAddress *address, BitStream *stream);
void handleDisconnect(const NetAddress *address, BitStream *stream);
/// @}
/// Calculate an MD5 sum representing a connection, and store it into addressDigest.
void computeNetMD5(const NetAddress *address, U32 connectSequence, U32 addressDigest[4]);
public:
NetInterface();
/// Returns whether or not this NetInterface allows connections from remote hosts.
bool doesAllowConnections() { return mAllowConnections; }
/// Sets whether or not this NetInterface allows connections from remote hosts.
void setAllowsConnections(bool conn) { mAllowConnections = conn; }
/// Dispatch function for processing all network packets through this NetInterface.
virtual void processPacketReceiveEvent(PacketReceiveEvent *event);
/// Handles all packets that don't fall into the category of connection handshake or game data.
virtual void handleInfoPacket(const NetAddress *address, U8 packetType, BitStream *stream);
/// Checks all connections marked as client to server for packet sends.
void processClient();
/// Checks all connections marked as server to client for packet sends.
void processServer();
/// Begins the connection handshaking process for a connection.
void startConnection(NetConnection *conn);
/// Checks for timeouts on all valid and pending connections.
void checkTimeouts();
/// Send a disconnect packet on a connection, along with a reason.
void sendDisconnectPacket(NetConnection *conn, const char *reason);
};
/// The global net interface instance.
extern NetInterface *GNet;
#endif

265
engine/sim/netObject.cc Executable file
View File

@ -0,0 +1,265 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/simBase.h"
#include "core/dnet.h"
#include "sim/netConnection.h"
#include "sim/netObject.h"
#include "console/consoleTypes.h"
IMPLEMENT_CONOBJECT(NetObject);
//----------------------------------------------------------------------------
NetObject *NetObject::mDirtyList = NULL;
NetObject::NetObject()
{
// netFlags will clear itself to 0
mNetIndex = U32(-1);
mFirstObjectRef = NULL;
mPrevDirtyList = NULL;
mNextDirtyList = NULL;
mDirtyMaskBits = 0;
}
NetObject::~NetObject()
{
if(mDirtyMaskBits)
{
if(mPrevDirtyList)
mPrevDirtyList->mNextDirtyList = mNextDirtyList;
else
mDirtyList = mNextDirtyList;
if(mNextDirtyList)
mNextDirtyList->mPrevDirtyList = mPrevDirtyList;
}
}
void NetObject::setMaskBits(U32 orMask)
{
AssertFatal(orMask != 0, "Invalid net mask bits set.");
AssertFatal(mDirtyMaskBits == 0 || (mPrevDirtyList != NULL || mNextDirtyList != NULL || mDirtyList == this), "Invalid dirty list state.");
if(!mDirtyMaskBits)
{
AssertFatal(mNextDirtyList == NULL && mPrevDirtyList == NULL, "Object with zero mask already in list.");
if(mDirtyList)
{
mNextDirtyList = mDirtyList;
mDirtyList->mPrevDirtyList = this;
}
mDirtyList = this;
}
mDirtyMaskBits |= orMask;
AssertFatal(mDirtyMaskBits == 0 || (mPrevDirtyList != NULL || mNextDirtyList != NULL || mDirtyList == this), "Invalid dirty list state.");
}
void NetObject::clearMaskBits(U32 orMask)
{
if(isDeleted())
return;
if(mDirtyMaskBits)
{
mDirtyMaskBits &= ~orMask;
if(!mDirtyMaskBits)
{
if(mPrevDirtyList)
mPrevDirtyList->mNextDirtyList = mNextDirtyList;
else
mDirtyList = mNextDirtyList;
if(mNextDirtyList)
mNextDirtyList->mPrevDirtyList = mPrevDirtyList;
mNextDirtyList = mPrevDirtyList = NULL;
}
}
for(GhostInfo *walk = mFirstObjectRef; walk; walk = walk->nextObjectRef)
{
if(walk->updateMask && walk->updateMask == orMask)
{
walk->updateMask = 0;
walk->connection->ghostPushToZero(walk);
}
else
walk->updateMask &= ~orMask;
}
}
void NetObject::collapseDirtyList()
{
Vector<NetObject *> tempV;
for(NetObject *t = mDirtyList; t; t = t->mNextDirtyList)
tempV.push_back(t);
for(NetObject *obj = mDirtyList; obj; )
{
NetObject *next = obj->mNextDirtyList;
U32 orMask = obj->mDirtyMaskBits;
obj->mNextDirtyList = NULL;
obj->mPrevDirtyList = NULL;
obj->mDirtyMaskBits = 0;
if(!obj->isDeleted() && orMask)
{
for(GhostInfo *walk = obj->mFirstObjectRef; walk; walk = walk->nextObjectRef)
{
if(!walk->updateMask)
{
walk->updateMask = orMask;
walk->connection->ghostPushNonZero(walk);
}
else
walk->updateMask |= orMask;
}
}
obj = next;
}
mDirtyList = NULL;
for(U32 i = 0; i < tempV.size(); i++)
{
AssertFatal(tempV[i]->mNextDirtyList == NULL && tempV[i]->mPrevDirtyList == NULL && tempV[i]->mDirtyMaskBits == 0, "Error in collapse");
}
}
//-----------------------------------------------------------------------------
ConsoleMethod(NetObject,scopeToClient,void,3,3,"(NetConnection %client)"
"Cause the NetObject to be forced as scoped on the specified NetConnection.")
{
argc;
NetConnection *conn;
if(!Sim::findObject(argv[2], conn))
{
Con::errorf(ConsoleLogEntry::General, "NetObject::scopeToClient: Couldn't find connection %s", argv[2]);
return;
}
conn->objectLocalScopeAlways(object);
}
ConsoleMethod(NetObject,clearScopeToClient,void,3,3,"clearScopeToClient(%client)"
"Undo the effects of a scopeToClient() call.")
{
argc;
NetConnection *conn;
if(!Sim::findObject(argv[2], conn))
{
Con::errorf(ConsoleLogEntry::General, "NetObject::clearScopeToClient: Couldn't find connection %s", argv[2]);
return;
}
conn->objectLocalClearAlways(object);
}
ConsoleMethod(NetObject,setScopeAlways,void,2,2,"Always scope this object on all connections.")
{
argc; argv;
object->setScopeAlways();
}
void NetObject::setScopeAlways()
{
if(mNetFlags.test(Ghostable) && !mNetFlags.test(IsGhost))
{
mNetFlags.set(ScopeAlways);
// if it's a ghost always object, add it to the ghost always set
// for ClientReps created later.
Sim::getGhostAlwaysSet()->addObject(this);
// add it to all Connections that already exist.
SimGroup *clientGroup = Sim::getClientGroup();
SimGroup::iterator i;
for(i = clientGroup->begin(); i != clientGroup->end(); i++)
{
NetConnection *con = (NetConnection *) (*i);
if(con->isGhosting())
con->objectInScope(this);
}
}
}
void NetObject::clearScopeAlways()
{
if(!mNetFlags.test(IsGhost))
{
mNetFlags.clear(ScopeAlways);
Sim::getGhostAlwaysSet()->removeObject(this);
// Un ghost this object from all the connections
while(mFirstObjectRef)
mFirstObjectRef->connection->detachObject(mFirstObjectRef);
}
}
bool NetObject::onAdd()
{
if(mNetFlags.test(ScopeAlways))
setScopeAlways();
return Parent::onAdd();
}
void NetObject::onRemove()
{
while(mFirstObjectRef)
mFirstObjectRef->connection->detachObject(mFirstObjectRef);
Parent::onRemove();
}
//-----------------------------------------------------------------------------
F32 NetObject::getUpdatePriority(CameraScopeQuery*, U32, S32 updateSkips)
{
return F32(updateSkips) * 0.1;
}
U32 NetObject::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
{
return 0;
}
void NetObject::unpackUpdate(NetConnection*, BitStream*)
{
}
void NetObject::onCameraScopeQuery(NetConnection *cr, CameraScopeQuery* /*camInfo*/)
{
// default behavior -
// ghost everything that is ghostable
for (SimSetIterator obj(Sim::getRootGroup()); *obj; ++obj)
{
NetObject* nobj = dynamic_cast<NetObject*>(*obj);
if (nobj)
{
AssertFatal(!nobj->mNetFlags.test(NetObject::Ghostable) || !nobj->mNetFlags.test(NetObject::IsGhost),
"NetObject::onCameraScopeQuery: object marked both ghostable and as ghost");
// Some objects don't ever want to be ghosted
if (!nobj->mNetFlags.test(NetObject::Ghostable))
continue;
if (!nobj->mNetFlags.test(NetObject::ScopeAlways))
{
// it's in scope...
cr->objectInScope(nobj);
}
}
}
}
//-----------------------------------------------------------------------------
void NetObject::initPersistFields()
{
Parent::initPersistFields();
}
ConsoleMethod( NetObject, getGhostID, S32, 2, 2, "")
{
return object->getNetIndex();
}

403
engine/sim/netObject.h Executable file
View File

@ -0,0 +1,403 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _NETOBJECT_H_
#define _NETOBJECT_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
//-----------------------------------------------------------------------------
class NetConnection;
class NetObject;
//-----------------------------------------------------------------------------
struct CameraScopeQuery
{
NetObject *camera; ///< Pointer to the viewing object.
Point3F pos; ///< Position in world space
Point3F orientation; ///< Viewing vector in world space
F32 fov; ///< Viewing angle/2
F32 sinFov; ///< sin(fov/2);
F32 cosFov; ///< cos(fov/2);
F32 visibleDistance; ///< Visible distance.
};
struct GhostInfo;
//-----------------------------------------------------------------------------
/// Superclass for ghostable networked objects.
///
/// @section NetObject_intro Introduction To NetObject And Ghosting
///
/// One of the most powerful aspects of Torque's networking code is its support
/// for ghosting and prioritized, most-recent-state network updates. The way
/// this works is a bit complex, but it is immensely efficient. Let's run
/// through the steps that the server goes through for each client in this part
/// of Torque's networking:
/// - First, the server determines what objects are in-scope for the client.
/// This is done by calling onCameraScopeQuery() on the object which is
/// considered the "scope" object. This is usually the player object, but
/// it can be something else. (For instance, the current vehicle, or a
/// object we're remote controlling.)
/// - Second, it ghosts them to the client; this is implemented in netGhost.cc.
/// - Finally, it sends updates as needed, by checking the dirty list and packing
/// updates.
///
/// There several significant advantages to using this networking system:
/// - Efficient network usage, since we only send data that has changed. In addition,
/// since we only care about most-recent data, if a packet is dropped, we don't waste
/// effort trying to deliver stale data.
/// - Cheating protection; since we don't deliver information about game objects which
/// aren't in scope, we dramatically reduce the ability of clients to hack the game and
/// gain a meaningful advantage. (For instance, they can't find out about things behind
/// them, since objects behind them don't fall in scope.) In addition, since ghost IDs are
/// assigned per-client, it's difficult for any sort of co-ordination between cheaters to
/// occur.
///
/// NetConnection contains the Ghost Manager implementation, which deals with transferring data to
/// the appropriate clients and keeping state in synch.
///
/// @section NetObject_Implementation An Example Implementation
///
/// The basis of the ghost implementation in Torque is NetObject. It tracks the dirty flags for the
/// various states that the object trackers, and does some other book-keeping to allow more efficient
/// operation of the networking layer.
///
/// Using a NetObject is very simple; let's go through a simple example implementation:
///
/// @code
/// class SimpleNetObject : public NetObject
/// {
/// public:
/// typedef NetObject Parent;
/// DECLARE_CONOBJECT(SimpleNetObject);
/// @endcode
///
/// Above is the standard boilerplate code for a Torque class. You can find out more about this in SimObject.
///
/// @code
/// char message1[256];
/// char message2[256];
/// enum States {
/// Message1Mask = BIT(0),
/// Message2Mask = BIT(1),
/// };
/// @endcode
///
/// For our example, we're having two "states" that we keep track of, message1 and message2. In a real
/// object, we might map our states to health and position, or some other set of fields. You have 32
/// bits to work with, so it's possible to be very specific when defining states. In general, you
/// should try to use as few states as possible (you never know when you'll need to expand your object's
/// functionality!), and in fact, most of your fields will end up changing all at once, so it's not worth
/// it to be too fine-grained. (As an example, position and velocity on Player are controlled by the same
/// bit, as one rarely changes without the other changing, too.)
///
/// @code
/// SimpleNetObject()
/// {
/// // in order for an object to be considered by the network system,
/// // the Ghostable net flag must be set.
/// // the ScopeAlways flag indicates that the object is always scoped
/// // on all active connections.
/// mNetFlags.set(ScopeAlways | Ghostable);
/// dStrcpy(message1, "Hello World 1!");
/// dStrcpy(message2, "Hello World 2!");
/// }
/// @endcode
///
/// Here is the constructor. Here, you see that we initialize our net flags to show that
/// we should always be scoped, and that we're to be taken into consideration for ghosting. We
/// also provide some initial values for the message fields.
///
/// @code
/// U32 packUpdate(NetConnection *, U32 mask, BitStream *stream)
/// {
/// // check which states need to be updated, and update them
/// if(stream->writeFlag(mask & Message1Mask))
/// stream->writeString(message1);
/// if(stream->writeFlag(mask & Message2Mask))
/// stream->writeString(message2);
///
/// // the return value from packUpdate can set which states still
/// // need to be updated for this object.
/// return 0;
/// }
/// @endcode
///
/// Here's half of the meat of the networking code, the packUpdate() function. (The other half, unpackUpdate(),
/// we'll get to in a second.) The comments in the code pretty much explain everything, however, notice that the
/// code follows a pattern of if(writeFlag(mask & StateMask)) { ... write data ... }. The packUpdate()/unpackUpdate()
/// functions are responsible for reading and writing the dirty bits to the bitstream by themselves.
///
/// @code
/// void unpackUpdate(NetConnection *, BitStream *stream)
/// {
/// // the unpackUpdate function must be symmetrical to packUpdate
/// if(stream->readFlag())
/// {
/// stream->readString(message1);
/// Con::printf("Got message1: %s", message1);
/// }
/// if(stream->readFlag())
/// {
/// stream->readString(message2);
/// Con::printf("Got message2: %s", message2);
/// }
/// }
/// @endcode
///
/// The other half of the networking code in any NetObject, unpackUpdate(). In our simple example, all that
/// the code does is print the new messages to the console; however, in a more advanced object, you might
/// trigger animations, update complex object properties, or even spawn new objects, based on what packet
/// data you unpack.
///
/// @code
/// void setMessage1(const char *msg)
/// {
/// setMaskBits(Message1Mask);
/// dStrcpy(message1, msg);
/// }
/// void setMessage2(const char *msg)
/// {
/// setMaskBits(Message2Mask);
/// dStrcpy(message2, msg);
/// }
/// @endcode
///
/// Here are the accessors for the two properties. It is good to encapsulate your state
/// variables, so that you don't have to remember to make a call to setMaskBits every time you change
/// anything; the accessors can do it for you. In a more complex object, you might need to set
/// multiple mask bits when you change something; this can be done using the | operator, for instance,
/// setMaskBits( Message1Mask | Message2Mask ); if you changed both messages.
///
/// @code
/// IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject);
///
/// ConsoleMethod(SimpleNetObject, setMessage1, void, 3, 3, "(string msg) Set message 1.")
/// {
/// object->setMessage1(argv[2]);
/// }
///
/// ConsoleMethod(SimpleNetObject, setMessage2, void, 3, 3, "(string msg) Set message 2.")
/// {
/// object->setMessage2(argv[2]);
/// }
/// @endcode
///
/// Finally, we use the NetObject implementation macro, IMPLEMENT_CO_NETOBJECT_V1(), to implement our
/// NetObject. It is important that we use this, as it makes Torque perform certain initialization tasks
/// that allow us to send the object over the network. IMPLEMENT_CONOBJECT() doesn't perform these tasks, see
/// the documentation on AbstractClassRep for more details.
///
/// @nosubgrouping
class NetObject: public SimObject
{
// The Ghost Manager needs read/write access
friend class NetConnection;
friend struct GhostInfo;
friend class ProcessList;
// Not the best way to do this, but the event needs access to mNetFlags
friend class GhostAlwaysObjectEvent;
private:
typedef SimObject Parent;
/// Mask indicating which states are dirty and need to be retransmitted on this
/// object.
U32 mDirtyMaskBits;
/// @name Dirty List
///
/// Whenever a NetObject becomes "dirty", we add it to the dirty list.
/// We also remove ourselves on the destructor.
///
/// This is done so that when we want to send updates (in NetConnection),
/// it's very fast to find the objects that need to be updated.
/// @{
/// Static pointer to the head of the dirty NetObject list.
static NetObject *mDirtyList;
/// Next item in the dirty list...
NetObject *mPrevDirtyList;
/// Previous item in the dirty list...
NetObject *mNextDirtyList;
/// @}
protected:
/// Pointer to the server object; used only when we are doing "short-circuited" networking.
///
/// When we are running with client and server on the same system (which can happen be either
/// when we are doing a single player game, or if we're hosting a multiplayer game and having
/// someone playing on the same instance), we can do some short circuited code to enhance
/// performance.
///
/// This variable is used to make it simpler; if we are running in short-circuited mode, it's set
/// to the object on the server that this NetObject is ghosting.
///
/// @note "Premature optimization is the root of all evil" - Donald Knuth. The current codebase
/// uses this feature in three small places, mostly for non-speed-related purposes.
SimObjectPtr<NetObject> mServerObject;
enum NetFlags
{
IsGhost = BIT(1), ///< This is a ghost.
ScopeAlways = BIT(6), ///< Object always ghosts to clients.
ScopeLocal = BIT(7), ///< Ghost only to local client.
Ghostable = BIT(8), ///< Set if this object CAN ghost.
MaxNetFlagBit = 15
};
BitSet32 mNetFlags; ///< Flag values from NetFlags
U32 mNetIndex; ///< The index of this ghost in the GhostManager on the server.
GhostInfo *mFirstObjectRef; ///< Head of a linked list storing GhostInfos referencing this NetObject.
public:
NetObject();
~NetObject();
/// @name Miscellaneous
/// @{
DECLARE_CONOBJECT(NetObject);
static void initPersistFields();
bool onAdd();
void onRemove();
/// @}
static void collapseDirtyList();
/// Used to mark a bit as dirty; ie, that its corresponding set of fields need to be transmitted next update.
///
/// @param orMask Bit(s) to set
void setMaskBits(U32 orMask);
/// Clear the specified bits from the dirty mask.
///
/// @param orMask Bits to clear
void clearMaskBits(U32 orMask);
/// Scope the object to all connections.
///
/// The object is marked as ScopeAlways and is immediately ghosted to
/// all active connections. This function has no effect if the object
/// is not marked as Ghostable.
void setScopeAlways();
/// Stop scoping the object to all connections.
///
/// The object's ScopeAlways flag is cleared and the object is removed from
/// all current active connections.
void clearScopeAlways();
/// This returns a value which is used to prioritize which objects need to be updated.
///
/// In NetObject, our returned priority is 0.1 * updateSkips, so that less recently
/// updated objects are more likely to be updated.
///
/// In subclasses, this can be adjusted. For instance, ShapeBase provides priority
/// based on proximity to the camera.
///
/// @param focusObject Information from a previous call to onCameraScopeQuery.
/// @param updateMask Current update mask.
/// @param updateSkips Number of ticks we haven't been updated for.
/// @returns A floating point value indicating priority. These are typically < 5.0.
virtual F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips);
/// Instructs this object to pack its state for transfer over the network.
///
/// @param conn Net connection being used
/// @param mask Mask indicating fields to transmit.
/// @param stream Bitstream to pack data to
///
/// @returns Any bits which were not dealt with. The value is stored by the networking
/// system. Don't set bits you weren't passed.
virtual U32 packUpdate(NetConnection * conn, U32 mask, BitStream *stream);
/// Instructs this object to read state data previously packed with packUpdate.
///
/// @param conn Net connection being used
/// @param stream stream to read from
virtual void unpackUpdate(NetConnection * conn, BitStream *stream);
/// Queries the object about information used to determine scope.
///
/// Something that is 'in scope' is somehow interesting to the client.
///
/// If we are a NetConnection's scope object, it calls this method to determine
/// how things should be scoped; basically, we tell it our field of view with camInfo,
/// and have the opportunity to manually mark items as "in scope" as we see fit.
///
/// By default, we just mark all ghostable objects as in scope.
///
/// @param cr Net connection requesting scope information.
/// @param camInfo Information about what this object can see.
virtual void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo);
/// Get the ghost index of this object.
U32 getNetIndex() { return mNetIndex; }
bool isServerObject() const; ///< Is this a server object?
bool isClientObject() const; ///< Is this a client object?
bool isGhost() const; ///< Is this is a ghost?
bool isScopeLocal() const; ///< Should this object only be visible to the client which created it?
bool isScopeable() const; ///< Is this object subject to scoping?
bool isGhostable() const; ///< Is this object ghostable?
bool isGhostAlways() const; ///< Should this object always be ghosted?
};
//-----------------------------------------------------------------------------
inline bool NetObject::isGhost() const
{
return mNetFlags.test(IsGhost);
}
inline bool NetObject::isClientObject() const
{
return mNetFlags.test(IsGhost);
}
inline bool NetObject::isServerObject() const
{
return !mNetFlags.test(IsGhost);
}
inline bool NetObject::isScopeLocal() const
{
return mNetFlags.test(ScopeLocal);
}
inline bool NetObject::isScopeable() const
{
return mNetFlags.test(Ghostable) && !mNetFlags.test(ScopeAlways);
}
inline bool NetObject::isGhostable() const
{
return mNetFlags.test(Ghostable);
}
inline bool NetObject::isGhostAlways() const
{
AssertFatal(mNetFlags.test(Ghostable) || mNetFlags.test(ScopeAlways) == false,
"That's strange, a ScopeAlways non-ghostable object? Something wrong here");
return mNetFlags.test(Ghostable) && mNetFlags.test(ScopeAlways);
}
#endif

260
engine/sim/netStringTable.cc Executable file
View File

@ -0,0 +1,260 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (c) 2002 GarageGames.Com
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/dnet.h"
#include "console/simBase.h"
#include "sim/netStringTable.h"
#include "core/stringTable.h"
NetStringTable *gNetStringTable = NULL;
NetStringTable::NetStringTable()
{
firstFree = 1;
firstValid = 1;
table = (Entry *) dMalloc(sizeof(Entry) * InitialSize);
size = InitialSize;
for(U32 i = 0; i < InitialSize; i++)
{
table[i].next = i + 1;
table[i].refCount = 0;
table[i].scriptRefCount = 0;
}
table[InitialSize-1].next = InvalidEntry;
for(U32 j = 0; j < HashTableSize; j++)
hashTable[j] = 0;
allocator = new DataChunker(DataChunkerSize);
}
NetStringTable::~NetStringTable()
{
delete allocator;
}
void NetStringTable::incStringRef(U32 id)
{
AssertFatal(table[id].refCount != 0 || table[id].scriptRefCount != 0 , "Cannot inc ref count from zero.");
table[id].refCount++;
}
void NetStringTable::incStringRefScript(U32 id)
{
AssertFatal(table[id].refCount != 0 || table[id].scriptRefCount != 0 , "Cannot inc ref count from zero.");
table[id].scriptRefCount++;
}
U32 NetStringTable::addString(const char *string)
{
U32 hash = _StringTable::hashString(string);
U32 bucket = hash % HashTableSize;
for(U32 walk = hashTable[bucket];walk; walk = table[walk].next)
{
if(!dStrcmp(table[walk].string, string))
{
table[walk].refCount++;
return walk;
}
}
U32 e = firstFree;
firstFree = table[e].next;
if(firstFree == InvalidEntry)
{
// in this case, we should expand the table for next time...
U32 newSize = size * 2;
table = (Entry *) dRealloc(table, newSize * sizeof(Entry));
for(U32 i = size; i < newSize; i++)
{
table[i].next = i + 1;
table[i].refCount = 0;
table[i].scriptRefCount = 0;
}
firstFree = size;
table[newSize - 1].next = InvalidEntry;
size = newSize;
}
table[e].refCount++;
table[e].string = (char *) allocator->alloc(dStrlen(string) + 1);
dStrcpy(table[e].string, string);
table[e].next = hashTable[bucket];
hashTable[bucket] = e;
table[e].link = firstValid;
table[firstValid].prevLink = e;
firstValid = e;
table[e].prevLink = 0;
return e;
}
U32 GameAddTaggedString(const char *string)
{
return gNetStringTable->addString(string);
}
const char *NetStringTable::lookupString(U32 id)
{
if(table[id].refCount == 0 && table[id].scriptRefCount == 0)
return NULL;
return table[id].string;
}
void NetStringTable::removeString(U32 id, bool script)
{
if(!script)
{
AssertFatal(table[id].refCount != 0, "Error, ref count is already 0!!");
if(--table[id].refCount)
return;
if(table[id].scriptRefCount)
return;
}
else
{
// If both ref counts are already 0, this id is not valid. Ignore
// the remove
if (table[id].scriptRefCount == 0 && table[id].refCount == 0)
return;
if(table[id].scriptRefCount == 0 && table[id].refCount)
{
Con::errorf("removeTaggedString failed! Ref count is already 0 for string: %s", table[id].string);
return;
}
if(--table[id].scriptRefCount)
return;
if(table[id].refCount)
return;
}
// unlink first:
U32 prev = table[id].prevLink;
U32 next = table[id].link;
if(next)
table[next].prevLink = prev;
if(prev)
table[prev].link = next;
else
firstValid = next;
// remove it from the hash table
U32 hash = _StringTable::hashString(table[id].string);
U32 bucket = hash % HashTableSize;
for(U32 *walk = &hashTable[bucket];*walk; walk = &table[*walk].next)
{
if(*walk == id)
{
*walk = table[id].next;
break;
}
}
table[id].next = firstFree;
firstFree = id;
}
void NetStringTable::repack()
{
DataChunker *newAllocator = new DataChunker(DataChunkerSize);
for(U32 walk = firstValid; walk; walk = table[walk].link)
{
const char *prevStr = table[walk].string;
table[walk].string = (char *) newAllocator->alloc(dStrlen(prevStr) + 1);
dStrcpy(table[walk].string, prevStr);
}
delete allocator;
allocator = newAllocator;
}
void NetStringTable::create()
{
AssertFatal(gNetStringTable == NULL, "Error, calling NetStringTable::create twice.");
gNetStringTable = new NetStringTable();
}
void NetStringTable::destroy()
{
AssertFatal(gNetStringTable != NULL, "Error, not calling NetStringTable::create.");
delete gNetStringTable;
gNetStringTable = NULL;
}
void NetStringTable::expandString(StringHandle &inString, char *buf, U32 bufSize, U32 argc, const char **argv)
{
buf[0] = StringTagPrefixByte;
dSprintf(buf + 1, bufSize - 1, "%d ", inString.getIndex());
const char *string = inString.getString();
if (string != NULL) {
U32 index = dStrlen(buf);
while(index < bufSize)
{
char c = *string++;
if(c == '%')
{
c = *string++;
if(c >= '1' && c <= '9')
{
U32 strIndex = c - '1';
if(strIndex >= argc)
continue;
// start copying out of arg index
const char *copy = argv[strIndex];
// skip past any tags:
if(*copy == StringTagPrefixByte)
{
while(*copy && *copy != ' ')
copy++;
if(*copy)
copy++;
}
while(*copy && index < bufSize)
buf[index++] = *copy++;
continue;
}
}
buf[index++] = c;
if(!c)
break;
}
buf[bufSize - 1] = 0;
} else {
dStrcat(buf, "<NULL>");
}
}
#if defined(TORQUE_DEBUG)
void NetStringTable::dumpToConsole()
{
U32 count = 0;
S32 maxIndex = -1;
for ( U32 i = 0; i < size; i++ )
{
if ( table[i].refCount > 0 || table[i].scriptRefCount > 0)
{
Con::printf( "%d: \"%c%s%c\" REF: %d", i, 0x10, table[i].string, 0x11, table[i].refCount );
if ( maxIndex == -1 || table[i].refCount > table[maxIndex].refCount )
maxIndex = i;
count++;
}
}
Con::printf( ">> STRINGS: %d MAX REF COUNT: %d \"%c%s%c\" <<",
count,
( maxIndex == -1 ) ? 0 : table[maxIndex].refCount,
0x10,
( maxIndex == -1 ) ? "" : table[maxIndex].string,
0x11 );
}
ConsoleFunctionGroupBegin(NetStringTable, "Debug functions for the NetStringTable.");
ConsoleFunction( dumpNetStringTable, void, 1, 1, "dumpNetStringTable()" )
{
argc; argv;
gNetStringTable->dumpToConsole();
}
ConsoleFunctionGroupEnd(NetStringTable);
#endif // DEBUG

139
engine/sim/netStringTable.h Executable file
View File

@ -0,0 +1,139 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (c) 2002 GarageGames.Com
//-----------------------------------------------------------------------------
#ifndef _NETSTRINGTABLE_H_
#define _NETSTRINGTABLE_H_
#ifndef _DATACHUNKER_H_
#include "core/dataChunker.h"
#endif
#ifndef _CONSOLE_H_
#include "console/console.h"
#endif
class NetConnection;
class StringHandle;
extern U32 GameAddTaggedString(const char *string);
class NetStringTable
{
friend class StringHandle;
friend U32 GameAddTaggedString(const char *string);
#ifdef TORQUE_DEBUG_NET
friend class RemoteCommandEvent;
#endif
enum Constants {
InitialSize = 16,
InvalidEntry = 0xFFFFFFFF,
HashTableSize = 2128,
DataChunkerSize = 65536
};
struct Entry
{
char *string;
U32 refCount;
U32 scriptRefCount;
U32 next;
U32 link;
U32 prevLink;
U32 seq;
};
U32 size;
U32 firstFree;
U32 firstValid;
U32 sequenceCount;
Entry *table;
U32 hashTable[HashTableSize];
DataChunker *allocator;
NetStringTable();
~NetStringTable();
U32 addString(const char *string);
// XA: Moved this ones to public to avoid using the friend_ConsoleMethod hack.
public:
const char *lookupString(U32 id);
void removeString(U32 id, bool script = false);
void incStringRefScript(U32 id);
private:
void incStringRef(U32 id);
void repack();
public:
static void create();
static void destroy();
static void expandString(StringHandle &string, char *buf, U32 bufSize, U32 argc, const char **argv);
#if defined(TORQUE_DEBUG)
void dumpToConsole();
#endif // DEBUG
};
extern NetStringTable *gNetStringTable;
class StringHandle
{
U32 index;
public:
StringHandle() { index = 0; }
StringHandle(const StringHandle &string) {
index = string.index;
if(index)
gNetStringTable->incStringRef(index);
}
StringHandle(const char *string) {
index = gNetStringTable->addString(string);
}
StringHandle(U32 initIndex)
{
index = initIndex;
if(index)
gNetStringTable->incStringRef(index);
}
~StringHandle()
{
if(index)
gNetStringTable->removeString(index);
}
void setFromIndex(U32 newIndex)
{
if(index)
gNetStringTable->removeString(index);
index = newIndex;
}
bool operator==(const StringHandle &s) const { return index == s.index; }
bool operator!=(const StringHandle &s) const { return index != s.index; }
StringHandle &operator=(const StringHandle &s)
{
if(index)
gNetStringTable->removeString(index);
index = s.index;
if(index)
gNetStringTable->incStringRef(index);
return *this;
}
const char *getString()
{
if(index)
return gNetStringTable->lookupString(index);
else
return NULL;
}
bool isNull() { return index == 0; }
bool isValidString() { return index != 0; }
U32 getIndex() { return index; }
};
#endif

391
engine/sim/pathManager.cc Executable file
View File

@ -0,0 +1,391 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
//
// Copyright (c) 2001 GarageGames.Com
//-----------------------------------------------------------------------------
#include "sim/pathManager.h"
#include "sim/netConnection.h"
#include "core/bitStream.h"
#include "sim/simPath.h"
#include "interior/interiorInstance.h"
#include "math/mathIO.h"
#include "dgl/dgl.h"
#include "sceneGraph/sceneState.h"
#include "sceneGraph/sceneGraph.h"
extern bool gEditingMission;
namespace {
U32 countNumBits(U32 n)
{
U32 count = 0;
while (n != 0) {
n >>= 1;
count++;
}
return count ? count : 1;
}
} // namespace {}
//--------------------------------------------------------------------------
//-------------------------------------- PathManagerEvent
//
class PathManagerEvent : public NetEvent
{
public:
U32 modifiedPath;
bool clearPaths;
PathManager::PathEntry path;
public:
PathManagerEvent() { }
void pack(NetConnection*, BitStream*);
void write(NetConnection*, BitStream*);
void unpack(NetConnection*, BitStream*);
void process(NetConnection*);
DECLARE_CONOBJECT(PathManagerEvent);
};
void PathManagerEvent::pack(NetConnection*, BitStream* stream)
{
// Write out the modified path...
stream->write(modifiedPath);
stream->writeFlag(clearPaths);
stream->write(path.totalTime);
stream->write(path.positions.size());
// This is here for safety. You can remove it if you want to try your luck at bigger sizes. -- BJG
AssertWarn(path.positions.size() < 1500/40, "Warning! Path size is pretty big - may cause packet overrun!");
// Each one of these is about 8 floats and 2 ints
// so we'll say it's about 40 bytes in size, which is where the 40 in the above calc comes from.
for (U32 j = 0; j < path.positions.size(); j++)
{
mathWrite(*stream, path.positions[j]);
mathWrite(*stream, path.rotations[j]);
stream->write(path.msToNext[j]);
stream->write(path.smoothingType[j]);
}
}
void PathManagerEvent::write(NetConnection*nc, BitStream *stream)
{
pack(nc, stream);
}
void PathManagerEvent::unpack(NetConnection*, BitStream* stream)
{
// Read in the modified path...
stream->read(&modifiedPath);
clearPaths = stream->readFlag();
stream->read(&path.totalTime);
U32 numPoints;
stream->read(&numPoints);
path.positions.setSize(numPoints);
path.rotations.setSize(numPoints);
path.msToNext.setSize(numPoints);
path.smoothingType.setSize(numPoints);
for (U32 j = 0; j < path.positions.size(); j++)
{
mathRead(*stream, &path.positions[j]);
mathRead(*stream, &path.rotations[j]);
stream->read(&path.msToNext[j]);
stream->read(&path.smoothingType[j]);
}
}
void PathManagerEvent::process(NetConnection*)
{
if (clearPaths)
{
// Clear out all the client's paths...
gClientPathManager->clearPaths();
}
AssertFatal(modifiedPath <= gClientPathManager->mPaths.size(), "Error out of bounds path!");
if (modifiedPath == gClientPathManager->mPaths.size()) {
PathManager::PathEntry *pe = new PathManager::PathEntry;
*pe = path;
gClientPathManager->mPaths.push_back(pe);
}
else
*(gClientPathManager->mPaths[modifiedPath]) = path;
}
IMPLEMENT_CO_NETEVENT_V1(PathManagerEvent);
//--------------------------------------------------------------------------
//-------------------------------------- PathManager Implementation
//
PathManager* gClientPathManager = NULL;
PathManager* gServerPathManager = NULL;
//--------------------------------------------------------------------------
PathManager::PathManager(const bool isServer)
{
VECTOR_SET_ASSOCIATION(mPaths);
mIsServer = isServer;
}
PathManager::~PathManager()
{
clearPaths();
}
void PathManager::clearPaths()
{
for (U32 i = 0; i < mPaths.size(); i++)
delete mPaths[i];
mPaths.setSize(0);
}
ConsoleFunction(clearServerPaths, void, 1, 1, "")
{
gServerPathManager->clearPaths();
}
void PathManager::init()
{
AssertFatal(gClientPathManager == NULL && gServerPathManager == NULL, "Error, already initialized the path manager!");
gClientPathManager = new PathManager(false);
gServerPathManager = new PathManager(true);
}
void PathManager::destroy()
{
AssertFatal(gClientPathManager != NULL && gServerPathManager != NULL, "Error, path manager not initialized!");
delete gClientPathManager;
gClientPathManager = NULL;
delete gServerPathManager;
gServerPathManager = NULL;
}
//--------------------------------------------------------------------------
U32 PathManager::allocatePathId()
{
mPaths.increment();
mPaths.last() = new PathEntry;
return (mPaths.size() - 1);
}
void PathManager::updatePath(const U32 id,
const Vector<Point3F>& positions,
const Vector<QuatF>& rotations,
const Vector<U32>& times,
const Vector<U32>& smoothingTypes)
{
AssertFatal(mIsServer == true, "PathManager::updatePath: Error, must be called on the server side");
AssertFatal(id < mPaths.size(), "PathManager::updatePath: error, id out of range");
AssertFatal(positions.size() == times.size() && positions.size() == smoothingTypes.size(), "Error, times and positions must match!");
PathEntry& rEntry = *mPaths[id];
rEntry.positions = positions;
rEntry.rotations = rotations;
rEntry.msToNext = times;
rEntry.smoothingType = smoothingTypes;
rEntry.totalTime = 0;
for (S32 i = 0; i < S32(rEntry.msToNext.size()); i++)
rEntry.totalTime += rEntry.msToNext[i];
transmitPath(id);
}
//--------------------------------------------------------------------------
void PathManager::transmitPaths(NetConnection* nc)
{
AssertFatal(mIsServer, "Error, cannot call transmitPaths on client path manager!");
// Send over paths
for(S32 i = 0; i < mPaths.size(); i++)
{
PathManagerEvent* event = new PathManagerEvent;
event->clearPaths = (i == 0);
event->modifiedPath = i;
event->path = *(mPaths[i]);
nc->postNetEvent(event);
}
}
void PathManager::transmitPath(const U32 id)
{
AssertFatal(mIsServer, "Error, cannot call transmitNewPath on client path manager!");
// Post to all active clients that have already received their paths...
//
SimGroup* pClientGroup = Sim::getClientGroup();
for (SimGroup::iterator itr = pClientGroup->begin(); itr != pClientGroup->end(); itr++) {
NetConnection* nc = dynamic_cast<NetConnection*>(*itr);
if (nc && nc->missionPathsSent())
{
// Transmit the updated path...
PathManagerEvent* event = new PathManagerEvent;
event->modifiedPath = id;
event->clearPaths = false;
event->path = *(mPaths[id]);
nc->postNetEvent(event);
}
}
}
void PathManager::getPathPosition(const U32 id,
const F64 msPosition,
Point3F& rPosition,
QuatF &rotation)
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
// Ok, query holds our path information...
F64 ms = msPosition;
if (ms > mPaths[id]->totalTime)
ms = mPaths[id]->totalTime;
S32 startNode = 0;
while (ms > mPaths[id]->msToNext[startNode]) {
ms -= mPaths[id]->msToNext[startNode];
startNode++;
}
S32 endNode = (startNode + 1) % mPaths[id]->positions.size();
Point3F& rStart = mPaths[id]->positions[startNode];
Point3F& rEnd = mPaths[id]->positions[endNode];
F64 interp = ms / F32(mPaths[id]->msToNext[startNode]);
if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeLinear)
{
rPosition = (rStart * (1.0 - interp)) + (rEnd * interp);
}
else if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeAccelerate)
{
interp = mSin(interp * M_PI - (M_PI / 2)) * 0.5 + 0.5;
rPosition = (rStart * (1.0 - interp)) + (rEnd * interp);
}
else if(mPaths[id]->smoothingType[startNode] == Marker::SmoothingTypeSpline)
{
S32 preStart = startNode - 1;
S32 postEnd = endNode + 1;
if(postEnd >= mPaths[id]->positions.size())
postEnd = 0;
if(preStart < 0)
preStart = mPaths[id]->positions.size() - 1;
Point3F p0 = mPaths[id]->positions[preStart];
Point3F p1 = rStart;
Point3F p2 = rEnd;
Point3F p3 = mPaths[id]->positions[postEnd];
rPosition.x = mCatmullrom(interp, p0.x, p1.x, p2.x, p3.x);
rPosition.y = mCatmullrom(interp, p0.y, p1.y, p2.y, p3.y);
rPosition.z = mCatmullrom(interp, p0.z, p1.z, p2.z, p3.z);
}
rotation.interpolate( mPaths[id]->rotations[startNode], mPaths[id]->rotations[endNode], interp );
}
U32 PathManager::getPathTotalTime(const U32 id) const
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
return mPaths[id]->totalTime;
}
U32 PathManager::getPathNumWaypoints(const U32 id) const
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
return mPaths[id]->positions.size();
}
U32 PathManager::getWaypointTime(const U32 id, const U32 wayPoint) const
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
AssertFatal(wayPoint < getPathNumWaypoints(id), "Invalid waypoint!");
U32 time = 0;
for (U32 i = 0; i < wayPoint; i++)
time += mPaths[id]->msToNext[i];
return time;
}
U32 PathManager::getPathTimeBits(const U32 id)
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
return countNumBits(mPaths[id]->totalTime);
}
U32 PathManager::getPathWaypointBits(const U32 id)
{
AssertFatal(isValidPath(id), "Error, this is not a valid path!");
return countNumBits(mPaths[id]->positions.size());
}
bool PathManager::dumpState(BitStream* stream) const
{
stream->write(mPaths.size());
for (U32 i = 0; i < mPaths.size(); i++) {
const PathEntry& rEntry = *mPaths[i];
stream->write(rEntry.totalTime);
stream->write(rEntry.positions.size());
for (U32 j = 0; j < rEntry.positions.size(); j++) {
mathWrite(*stream, rEntry.positions[j]);
stream->write(rEntry.msToNext[j]);
}
}
return stream->getStatus() == Stream::Ok;
}
bool PathManager::readState(BitStream* stream)
{
U32 i;
for (i = 0; i < mPaths.size(); i++)
delete mPaths[i];
U32 numPaths;
stream->read(&numPaths);
mPaths.setSize(numPaths);
for (i = 0; i < mPaths.size(); i++) {
mPaths[i] = new PathEntry;
PathEntry& rEntry = *mPaths[i];
stream->read(&rEntry.totalTime);
U32 numPositions;
stream->read(&numPositions);
rEntry.positions.setSize(numPositions);
rEntry.msToNext.setSize(numPositions);
for (U32 j = 0; j < rEntry.positions.size(); j++) {
mathRead(*stream, &rEntry.positions[j]);
stream->read(&rEntry.msToNext[j]);
}
}
return stream->getStatus() == Stream::Ok;
}

115
engine/sim/pathManager.h Executable file
View File

@ -0,0 +1,115 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
//
// Copyright (c) 2001 GarageGames.Com
//-----------------------------------------------------------------------------
#ifndef _PATHMANAGER_H_
#define _PATHMANAGER_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _MPOINT_H_
#include "math/mPoint.h"
#endif
#ifndef _MQUAT_H_
#include "math/mQuat.h"
#endif
#ifndef _SCENEOBJECT_H_
#include "sim/sceneObject.h"
#endif
class NetConnection;
class BitStream;
class PathManager
{
friend class PathManagerEvent;
private:
struct PathEntry {
U32 totalTime;
Vector<Point3F> positions;
Vector<QuatF> rotations;
Vector<U32> smoothingType;
Vector<U32> msToNext;
PathEntry() {
VECTOR_SET_ASSOCIATION(positions);
VECTOR_SET_ASSOCIATION(rotations);
VECTOR_SET_ASSOCIATION(smoothingType);
VECTOR_SET_ASSOCIATION(msToNext);
}
};
Vector<PathEntry*> mPaths;
public:
enum PathType {
BackAndForth,
Looping
};
public:
PathManager(const bool isServer);
~PathManager();
static void init();
static void destroy();
void clearPaths();
//-------------------------------------- Path querying
public:
bool isValidPath(const U32 id) const;
void getPathPosition(const U32 id, const F64 msPosition, Point3F& rPosition, QuatF &rotation);
U32 getPathTotalTime(const U32 id) const;
U32 getPathNumWaypoints(const U32 id) const;
U32 getWaypointTime(const U32 id, const U32 wayPoint) const;
U32 getPathTimeBits(const U32 id);
U32 getPathWaypointBits(const U32 id);
//-------------------------------------- Path Registration/Transmission/Management
public:
// Called after mission load to clear out the paths on the client, and to transmit
// the information for the current mission's paths.
void transmitPaths(NetConnection*);
void transmitPath(U32);
U32 allocatePathId();
void updatePath(const U32 id, const Vector<Point3F>&, const Vector<QuatF>&, const Vector<U32> &, const Vector<U32>&);
//-------------------------------------- State dumping/reading
public:
bool dumpState(BitStream*) const;
bool readState(BitStream*);
private:
bool mIsServer;
bool mPathsSent;
};
struct PathNode {
Point3F position;
QuatF rotation;
U32 smoothingType;
U32 msToNext;
};
extern PathManager* gClientPathManager;
extern PathManager* gServerPathManager;
//--------------------------------------------------------------------------
inline bool PathManager::isValidPath(const U32 id) const
{
return (id < mPaths.size()) && mPaths[id]->positions.size() > 0;
}
#endif // _H_PATHMANAGER

2203
engine/sim/sceneObject.cc Executable file

File diff suppressed because it is too large Load Diff

867
engine/sim/sceneObject.h Executable file
View File

@ -0,0 +1,867 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _SCENEOBJECT_H_
#define _SCENEOBJECT_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _NETOBJECT_H_
#include "sim/netObject.h"
#endif
#ifndef _COLLISION_H_
#include "collision/collision.h"
#endif
#ifndef _POLYHEDRON_H_
#include "collision/polyhedron.h"
#endif
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
#ifndef _OBJECTTYPES_H_
#include "game/objectTypes.h"
#endif
#ifndef _COLOR_H_
#include "core/color.h"
#endif
#ifndef _LIGHTMANAGER_H_
#include "sceneGraph/lightManager.h"
#endif
//-------------------------------------- Forward declarations...
class SceneObject;
class SceneGraph;
class SceneState;
class SceneRenderImage;
class Box3F;
class Point3F;
class LightManager;
class Convex;
//----------------------------------------------------------------------------
/// Extension of the collision structore to allow use with raycasting.
///
/// @see Collision
struct RayInfo: public Collision
{
// The collision struct has object, point, normal & material.
F32 t;
};
//--------------------------------------------------------------------------
/// Reference to a scene object.
///
/// @note There are two indiscretions here. First is the name, which refers rather
/// blatantly to the container bin system. A hygiene issue. Next is the
/// user defined U32, which is added solely for the zoning system. This should
/// properly be split up into two structures, for the disparate purposes, especially
/// since it's not nice to force the container bin to use 20 bytes structures when
/// it could get away with a 16 byte version.
class SceneObjectRef
{
public:
SceneObject* object;
SceneObjectRef* nextInBin;
SceneObjectRef* prevInBin;
SceneObjectRef* nextInObj;
U32 zone;
};
/// A scope frustum describes a pyramid to clip new portals against. It is
/// rooted at the root position of the scoping query, which is not stored
/// here.
class ScopeFrustum
{
public:
enum Constants {
TopLeft = 0,
TopRight = 1,
BottomLeft = 2,
BottomRight = 3
};
Point3F frustumPoints[4];
};
//----------------------------------------------------------------------------
class Container
{
public:
struct Link
{
Link* next;
Link* prev;
Link();
void unlink();
void linkAfter(Link* ptr);
};
struct CallbackInfo
{
AbstractPolyList* polyList;
Box3F boundingBox;
SphereF boundingSphere;
void *key;
};
static const U32 csmNumBins;
static const F32 csmBinSize;
static const F32 csmTotalBinSize;
static const U32 csmRefPoolBlockSize;
static U32 smCurrSeqKey;
private:
Link mStart,mEnd;
SceneObjectRef* mFreeRefPool;
Vector<SceneObjectRef*> mRefPoolBlocks;
SceneObjectRef* mBinArray;
SceneObjectRef mOverflowBin;
public:
Container();
~Container();
/// @name Basic database operations
/// @{
///
typedef void (*FindCallback)(SceneObject*,void *key);
void findObjects(U32 mask, FindCallback, void *key = NULL);
void findObjects(const Box3F& box, U32 mask, FindCallback, void *key = NULL);
void polyhedronFindObjects(const Polyhedron& polyhedron, U32 mask,
FindCallback, void *key = NULL);
/// @}
/// @name Line intersection
/// @{
///
bool castRay(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info);
bool collideBox(const Point3F &start, const Point3F &end, U32 mask, RayInfo* info);
/// @}
/// @name Poly list
/// @{
///
bool buildPolyList(const Box3F& box, U32 mask, AbstractPolyList*,FindCallback=0,void *key = NULL);
bool buildCollisionList(const Box3F& box, const Point3F& start, const Point3F& end, const VectorF& velocity,
U32 mask,CollisionList* collisionList,FindCallback = 0,void *key = NULL,const Box3F *queryExpansion = 0);
bool buildCollisionList(const Polyhedron& polyhedron,
const Point3F& start, const Point3F& end,
const VectorF& velocity,
U32 mask, CollisionList* collisionList,
FindCallback callback = 0, void *key = NULL);
/// @}
///
bool addObject(SceneObject*);
bool removeObject(SceneObject*);
void addRefPoolBlock();
SceneObjectRef* allocateObjectRef();
void freeObjectRef(SceneObjectRef*);
void insertIntoBins(SceneObject*);
void removeFromBins(SceneObject*);
/// Checkbins makes sure that we're not just sticking the object right back
/// where it came from. The overloaded insertInto is so we don't calculate
/// the ranges twice.
void checkBins(SceneObject*);
void insertIntoBins(SceneObject*, U32, U32, U32, U32);
private:
Vector<SimObjectPtr<SceneObject>*> mSearchList;///< Object searches to support console querying of the database. ONLY WORKS ON SERVER
S32 mCurrSearchPos;
Point3F mSearchReferencePoint;
void cleanupSearchVectors();
public:
void initRadiusSearch(const Point3F& searchPoint,
const F32 searchRadius,
const U32 searchMask);
U32 containerSearchNext();
F32 containerSearchCurrDist();
F32 containerSearchCurrRadiusDist();
};
//----------------------------------------------------------------------------
/// For simple queries. Simply creates a vector of the objects
class SimpleQueryList
{
public:
Vector<SceneObject*> mList;
public:
SimpleQueryList()
{
VECTOR_SET_ASSOCIATION(mList);
}
void insertObject(SceneObject* obj) { mList.push_back(obj); }
static void insertionCallback(SceneObject* obj, void *key);
};
//----------------------------------------------------------------------------
/// A 3D object.
///
/// @section SceneObject_intro Introduction
///
/// SceneObject exists as a foundation for 3D objects in Torque. It provides the
/// basic functionality for:
/// - A scene graph (in the Zones and Portals sections), allowing efficient
/// and robust rendering of the game scene.
/// - Various helper functions, including functions to get bounding information
/// and momentum/velocity.
/// - Collision detection, as well as ray casting.
/// - Lighting. SceneObjects can register lights both at lightmap generation time,
/// and dynamic lights at runtime (for special effects, such as from flame or
/// a projectile, or from an explosion).
/// - Manipulating scene objects, for instance varying scale.
///
/// @section SceneObject_example An Example
///
/// Melv May has written a most marvelous example object deriving from SceneObject.
/// Unfortunately this page is too small to contain it.
///
/// @see http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=3217
/// for a copy of Melv's example.
class SceneObject : public NetObject, public Container::Link
{
typedef NetObject Parent;
friend class Container;
friend class SceneGraph;
friend class SceneState;
//-------------------------------------- Public constants
public:
enum
{
MaxObjectZones = 128
};
enum TraversalState
{
Pending = 0,
Working = 1,
Done = 2
};
enum SceneObjectMasks
{
ScaleMask = BIT(0),
NextFreeMask = BIT(1)
};
//-------------------------------------- Public interfaces
// C'tors and D'tors
private:
SceneObject(const SceneObject&); ///< @deprecated disallowed
public:
SceneObject();
virtual ~SceneObject();
/// Returns a value representing this object which can be passed to script functions.
const char* scriptThis();
public:
/// @name Collision and transform related interface
///
/// The Render Transform is the interpolated transform with respect to the
/// frame rate. The Render Transform will differ from the object transform
/// because the simulation is updated in fixed intervals, which controls the
/// object transform. The framerate is, most likely, higher than this rate,
/// so that is why the render transform is interpolated and will differ slightly
/// from the object transform.
///
/// @{
/// Disables collisions for this object including raycasts
virtual void disableCollision();
/// Enables collisions for this object
virtual void enableCollision();
/// Returns true if collisions are enabled
bool isCollisionEnabled() const { return mCollisionCount == 0; }
/// Returns true if this object allows itself to be displaced
/// @see displaceObject
virtual bool isDisplacable() const;
/// Returns the momentum of this object
virtual Point3F getMomentum() const;
/// Sets the momentum of this object
/// @param momentum Momentum
virtual void setMomentum(const Point3F &momentum);
/// Returns the mass of this object
virtual F32 getMass() const;
/// Displaces this object by a vector
/// @param displaceVector Displacement vector
virtual bool displaceObject(const Point3F& displaceVector);
/// Returns the transform which can be used to convert object space
/// to world space
const MatrixF& getTransform() const { return mObjToWorld; }
/// Returns the transform which can be used to convert world space
/// into object space
const MatrixF& getWorldTransform() const { return mWorldToObj; }
/// Returns the scale of the object
const VectorF& getScale() const { return mObjScale; }
/// Returns the bounding box for this object in local coordinates
const Box3F& getObjBox() const { return mObjBox; }
/// Returns the bounding box for this object in world coordinates
const Box3F& getWorldBox() const { return mWorldBox; }
/// Returns the bounding sphere for this object in world coordinates
const SphereF& getWorldSphere() const { return mWorldSphere; }
/// Returns the center of the bounding box in world coordinates
Point3F getBoxCenter() const { return (mWorldBox.min + mWorldBox.max) * 0.5f; }
/// Sets the Object -> World transform
///
/// @param mat New transform matrix
virtual void setTransform(const MatrixF & mat);
/// Sets the scale for the object
/// @param scale Scaling values
virtual void setScale(const VectorF & scale);
/// This sets the render transform for this object
/// @param mat New render transform
virtual void setRenderTransform(const MatrixF &mat);
/// Returns the render transform
const MatrixF& getRenderTransform() const { return mRenderObjToWorld; }
/// Returns the render transform to convert world to local coordinates
const MatrixF& getRenderWorldTransform() const { return mRenderWorldToObj; }
/// Returns the render world box
const Box3F& getRenderWorldBox() const { return mRenderWorldBox; }
/// Builds a convex hull for this object.
///
/// Think of a convex hull as a low-res mesh which covers, as tightly as
/// possible, the object mesh, and is used as a collision mesh.
/// @param box
/// @param convex Convex mesh generated (out)
virtual void buildConvex(const Box3F& box,Convex* convex);
/// Builds a list of polygons which intersect a bounding volume.
///
/// This will use either the sphere or the box, not both, the
/// SceneObject implimentation ignores sphere.
///
/// @see AbstractPolyList
/// @param polyList Poly list build (out)
/// @param box Box bounding volume
/// @param sphere Sphere bounding volume
virtual bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere);
/// Builds a collision tree of all the polygons which collide with a bounding volume.
///
/// @note Not implemented in SceneObject. @see TerrainBlock::buildCollisionBSP
/// @param tree BSP tree built (out)
/// @param box Box bounding volume
/// @param sphere Sphere bounding volume
virtual BSPNode* buildCollisionBSP(BSPTree *tree, const Box3F &box, const SphereF &sphere);
/// Casts a ray and obtain collision information, returns true if RayInfo is modified.
///
/// @param start Start point of ray
/// @param end End point of ray
/// @param info Collision information obtained (out)
virtual bool castRay(const Point3F &start, const Point3F &end, RayInfo* info);
virtual bool collideBox(const Point3F &start, const Point3F &end, RayInfo* info);
/// Returns the position of the object.
Point3F getPosition() const;
/// Returns the render-position of the object.
///
/// @see getRenderTransform
Point3F getRenderPosition() const;
/// Sets the position of the object
void setPosition(const Point3F &pos);
/// Gets the velocity of the object
virtual Point3F getVelocity() const;
/// Sets the velocity of the object
/// @param v Velocity
virtual void setVelocity(const Point3F &v);
/// @}
public:
/// @name Zones
///
/// A zone is a portalized section of an InteriorInstance, and an InteriorInstance can manage more than one zone.
/// There is always at least one zone in the world, zone 0, which represens the whole world. Any
/// other zone will be in an InteriorInstance. Torque keeps track of the zones containing an object
/// as it moves throughout the world. An object can exists in multiple zones at once.
/// @{
/// Returns true if this object is managing zones.
///
/// This is only true in the case of InteriorInstances which have zones in them.
bool isManagingZones() const;
/// Gets the index of the first zone this object manages in the collection of zones.
U32 getZoneRangeStart() const { return mZoneRangeStart; }
/// Gets the number of zones containing this object.
U32 getNumCurrZones() const { return mNumCurrZones; }
/// Returns the nth zone containing this object.
U32 getCurrZone(const U32 index) const;
/// If an object exists in multiple zones, this method will give you the
/// number and indices of these zones (storing them in the provided variables).
///
/// @param obj Object in question.
/// @param zones Indices of zones containing the object. (out)
/// @param numZones Number of elements in the returned array. (out)
virtual bool getOverlappingZones(SceneObject* obj, U32* zones, U32* numZones);
/// Returns the zone containing p.
///
/// @param p Point to test.
virtual U32 getPointZone(const Point3F& p);
/// This is called on a zone managing object to scope all the zones managed.
///
/// @param rootPosition Camera position
/// @param rootDistance Camera visible distance
/// @param zoneScopeState Array of booleans which line up with the collection of zones, marked true if that zone is scoped (out)
virtual bool scopeObject(const Point3F& rootPosition,
const F32 rootDistance,
bool* zoneScopeState);
/// @}
/// Called when the object is supposed to render itself.
///
/// @param state Current rendering state.
/// @see SceneState
/// @param image Image associated with this object to render.
/// @see SceneRenderImage
virtual void renderObject(SceneState *state, SceneRenderImage *image);
/// Called when the SceneGraph is ready for the registration of RenderImages.
///
/// @see SceneState
///
/// @param state SceneState
/// @param stateKey State key of the current SceneState
/// @param startZone Base zone index
/// @param modifyBaseZoneState If true, the object needs to modify the zone state.
virtual bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone,
const bool modifyBaseZoneState = false);
/// Adds object to the client or server container depending on the object
void addToScene();
/// Removes the object from the client/server container
void removeFromScene();
//-------------------------------------- Derived class interface
// Overrides
protected:
bool onAdd();
void onRemove();
// Overrideables
protected:
/// Called when this is added to the SceneGraph.
///
/// @param graph SceneGraph this is getting added to
virtual bool onSceneAdd(SceneGraph *graph);
/// Called when this is removed from the SceneGraph
virtual void onSceneRemove();
/// Called when the size of the object changes
virtual void onScaleChanged();
/// @name Portals
/// @{
/// This is used by a portal controling object to transform the base-modelview
/// used by the scenegraph for rendering to the modelview it needs to render correctly.
///
/// @see MirrorSubObject
///
/// @param portalIndex Index of portal in the list of portals controlled by the object.
/// @param oldMV Current modelview matrix used by the SceneGraph (in)
/// @param newMV New modelview to be used by the SceneGraph (out)
virtual void transformModelview(const U32 portalIndex, const MatrixF& oldMV, MatrixF* newMV);
/// Used to tranform the position of a point based on a portal.
///
/// @param portalIndex Index of a portal to transform by.
/// @param point Point to transform.
virtual void transformPosition(const U32 portalIndex, Point3F& point);
/// Returns a new view frustum for the portal.
///
/// @param portalIndex Which portal in the list of portals the object controls
/// @param oldFrustum Current frustum.
/// @param nearPlane Near clipping plane.
/// @param farPlane Far clipping plane.
/// @param oldViewport Current viewport.
/// @param newFrustum New view frustum to use. (out)
/// @param newViewport New viewport to use. (out)
/// @param flippedMatrix Should the object should use a flipped matrix to calculate viewport and frustum?
virtual bool computeNewFrustum(const U32 portalIndex,
const F64* oldFrustum,
const F64 nearPlane,
const F64 farPlane,
const RectI& oldViewport,
F64* newFrustum,
RectI& newViewport,
const bool flippedMatrix);
/// Called before things are to be rendered from the portals point of view, to set up
/// everything the portal needs to render correctly.
///
/// @param portalIndex Index of portal to use.
/// @param pCurrState Current SceneState
/// @param pParentState SceneState used before this portal was activated
virtual void openPortal(const U32 portalIndex,
SceneState* pCurrState,
SceneState* pParentState);
/// Called after rendering of a portal is complete, this resets the states
/// the previous call to openPortal() changed.
///
/// @param portalIndex Index of portal to use.
/// @param pCurrState Current SceneState
/// @param pParentState SceneState used before this portal was activated
virtual void closePortal(const U32 portalIndex,
SceneState* pCurrState,
SceneState* pParentState);
public:
/// Returns the plane of the portal in world space.
///
/// @param portalIndex Index of portal to use.
/// @param plane Plane of the portal in world space (out)
virtual void getWSPortalPlane(const U32 portalIndex, PlaneF *plane);
/// @}
protected:
/// Sets the mLastState and mLastStateKey.
///
/// @param state SceneState to set as the last state
/// @param key Key to set as the last state key
void setLastState(SceneState *state, U32 key);
/// Returns true if the provided SceneState and key are set as this object's
/// last state and key.
///
/// @param state SceneState in question
/// @param key State key in question
bool isLastState(SceneState *state, U32 key) const;
/// @name Traversal State
///
/// The SceneGraph traversal is recursive and the traversal state of an object
/// can be one of three things:
/// - Pending - The object has not yet been examined for zone traversal.
/// - Working - The object is currently having its zones traversed.
/// - Done - The object has had all of its zones traversed or doesn't manage zones.
///
/// @note These states were formerly referred to as TraverseColor, with White, Black, and
/// Gray; this was changed in Torque 1.2 by Pat "KillerBunny" Wilson. This code is
/// only used internal to this class
/// @{
// These two replaced by TraversalState because that makes more sense -KB
//void setTraverseColor(TraverseColor);
//TraverseColor getTraverseColor() const;
// ph34r teh muskrat! - Travis Colure
/// This sets the traversal state of the object.
///
/// @note This is used internally; you should not normally need to call it.
/// @param s Traversal state to assign
void setTraversalState( TraversalState s );
/// Returns the traversal state of this object
TraversalState getTraversalState() const;
/// @}
/// @name Lighting
/// @{
struct LightingInfo
{
LightingInfo();
bool mUseInfo;
bool mDirty;
ColorF mDefaultColor;
ColorF mAlarmColor;
SimObjectPtr<SceneObject> mInterior;
bool mHasLastColor;
ColorF mLastColor;
U32 mLastTime;
static LightInfo smAmbientLight;
enum
{
Interior = 0,
Terrain,
};
U32 mLightSource;
};
/// Sets up lighting for the rendering of this object
virtual void installLights();
/// Removes lighting for the rendering of this object
virtual void uninstallLights();
/// Gets the color of the ambient light in the area of the object and
/// stores it in the provided ColorF.
///
/// @param col Ambient color (out)
virtual bool getLightingAmbientColor(ColorF * col);
LightingInfo mLightingInfo; ///< Lighting info for this object
/// @}
protected:
/// @name Transform and Collision Members
/// @{
///
Container* mContainer;
MatrixF mObjToWorld; ///< Transform from object space to world space
MatrixF mWorldToObj; ///< Transform from world space to object space (inverse)
Point3F mObjScale; ///< Object scale
Box3F mObjBox; ///< Bounding box in object space
Box3F mWorldBox; ///< Bounding box in world space
SphereF mWorldSphere; ///< Bounding sphere in world space
MatrixF mRenderObjToWorld; ///< Render matrix to transform object space to world space
MatrixF mRenderWorldToObj; ///< Render matrix to transform world space to object space
Box3F mRenderWorldBox; ///< Render bounding box in world space
SphereF mRenderWorldSphere; ///< Render bounxing sphere in world space
/// Regenerates the world-space bounding box and bounding sphere
void resetWorldBox();
/// Regenerates the render-world-space bounding box and sphere
void resetRenderWorldBox();
SceneObjectRef* mZoneRefHead;
SceneObjectRef* mBinRefHead;
U32 mBinMinX;
U32 mBinMaxX;
U32 mBinMinY;
U32 mBinMaxY;
/// @}
/// @name Container Interface
///
/// When objects are searched, we go through all the zones and ask them for
/// all of their objects. Because an object can exist in multiple zones, the
/// container sequence key is set to the id of the current search. Then, while
/// searching, we check to see if an object's sequence key is the same as the
/// current search key. If it is, it will NOT be added to the list of returns
/// since it has already been processed.
///
/// @{
U32 mContainerSeqKey; ///< Container sequence key
/// Returns the container sequence key
U32 getContainerSeqKey() const { return mContainerSeqKey; }
/// Sets the container sequence key
void setContainerSeqKey(const U32 key) { mContainerSeqKey = key; }
/// @}
public:
/// Returns a pointer to the container that contains this object
Container* getContainer() { return mContainer; }
protected:
S32 mCollisionCount;
bool mGlobalBounds;
public:
/// Returns the type mask for this object
U32 getTypeMask() { return(mTypeMask); }
const bool isGlobalBounds() const
{
return mGlobalBounds;
}
/// If global bounds are set to be true, then the object is assumed to
/// have an infinitely large bounding box for collision and rendering
/// purposes.
///
/// They can't be toggled currently.
void setGlobalBounds()
{
if(mContainer)
mContainer->removeFromBins(this);
mGlobalBounds = true;
mObjBox.min.set(-1e10, -1e10, -1e10);
mObjBox.max.set( 1e10, 1e10, 1e10);
if(mContainer)
mContainer->insertIntoBins(this);
}
/// @name Rendering Members
/// @{
protected:
SceneGraph* mSceneManager; ///< SceneGraph that controls this object
U32 mZoneRangeStart; ///< Start of range of zones this object controls, 0xFFFFFFFF == no zones
U32 mNumCurrZones; ///< Number of zones this object exists in
private:
TraversalState mTraversalState; ///< State of this object in the SceneGraph traversal - DON'T MESS WITH THIS
SceneState* mLastState; ///< Last SceneState that was used to render this object.
U32 mLastStateKey; ///< Last state key that was used to render this object.
/// @}
/// @name Persist and console
/// @{
public:
static void initPersistFields();
void inspectPostApply();
DECLARE_CONOBJECT(SceneObject);
/// @}
};
//--------------------------------------------------------------------------
extern Container gServerContainer;
extern Container gClientContainer;
//--------------------------------------------------------------------------
//-------------------------------------- Inlines
//
inline bool SceneObject::isManagingZones() const
{
return mZoneRangeStart != 0xFFFFFFFF;
}
inline void SceneObject::setLastState(SceneState* state, U32 key)
{
mLastState = state;
mLastStateKey = key;
}
inline bool SceneObject::isLastState(SceneState* state, U32 key) const
{
return (mLastState == state && mLastStateKey == key);
}
inline void SceneObject::setTraversalState( TraversalState s ) {
mTraversalState = s;
}
inline SceneObject::TraversalState SceneObject::getTraversalState() const {
return mTraversalState;
}
inline U32 SceneObject::getCurrZone(const U32 index) const
{
// Not the most efficient way to do this, walking the list,
// but it's an uncommon call...
SceneObjectRef* walk = mZoneRefHead;
for (U32 i = 0; i < index; i++)
{
walk = walk->nextInObj;
AssertFatal(walk!=NULL, "Error, too few object refs!");
}
AssertFatal(walk!=NULL, "Error, too few object refs!");
return walk->zone;
}
//--------------------------------------------------------------------------
inline SceneObjectRef* Container::allocateObjectRef()
{
if (mFreeRefPool == NULL)
{
addRefPoolBlock();
}
AssertFatal(mFreeRefPool!=NULL, "Error, should always have a free reference here!");
SceneObjectRef* ret = mFreeRefPool;
mFreeRefPool = mFreeRefPool->nextInObj;
ret->nextInObj = NULL;
return ret;
}
inline void Container::freeObjectRef(SceneObjectRef* trash)
{
trash->object = NULL;
trash->nextInBin = NULL;
trash->prevInBin = NULL;
trash->nextInObj = mFreeRefPool;
mFreeRefPool = trash;
}
inline void Container::findObjects(U32 mask, FindCallback callback, void *key)
{
for (Link* itr = mStart.next; itr != &mEnd; itr = itr->next) {
SceneObject* ptr = static_cast<SceneObject*>(itr);
if ((ptr->getType() & mask) != 0 && !ptr->mCollisionCount)
(*callback)(ptr,key);
}
}
#endif // _H_SCENEOBJECT_

456
engine/sim/simPath.cc Executable file
View File

@ -0,0 +1,456 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "sim/simPath.h"
#include "console/consoleTypes.h"
#include "sim/pathManager.h"
#include "dgl/dgl.h"
#include "sceneGraph/sceneState.h"
#include "math/mathIO.h"
#include "core/bitStream.h"
extern bool gEditingMission;
//--------------------------------------------------------------------------
//-------------------------------------- Console functions and cmp funcs
//
namespace {
static ColorF cubeColors[8] = {
ColorF(0, 0, 0),
ColorF(1, 0, 0),
ColorF(0, 1, 0),
ColorF(0, 0, 1),
ColorF(1, 1, 0),
ColorF(1, 0, 1),
ColorF(0, 1, 1),
ColorF(1, 1, 1)
};
static Point3F cubePoints[8] = {
Point3F(-1, -1, -1),
Point3F(-1, -1, 1),
Point3F(-1, 1, -1),
Point3F(-1, 1, 1),
Point3F( 1, -1, -1),
Point3F( 1, -1, 1),
Point3F( 1, 1, -1),
Point3F( 1, 1, 1)
};
static U32 cubeFaces[6][4] = {
{ 0, 2, 6, 4 },
{ 0, 2, 3, 1 },
{ 0, 1, 5, 4 },
{ 3, 2, 6, 7 },
{ 7, 6, 4, 5 },
{ 3, 7, 5, 1 }
};
void wireCube(F32 size, Point3F pos)
{
glDisable(GL_CULL_FACE);
for(int i = 0; i < 6; i++)
{
glBegin(GL_LINE_LOOP);
for(int vert = 0; vert < 4; vert++)
{
int idx = cubeFaces[i][vert];
glColor3f(cubeColors[idx].red, cubeColors[idx].green, cubeColors[idx].blue);
glVertex3f(cubePoints[idx].x * size + pos.x, cubePoints[idx].y * size + pos.y, cubePoints[idx].z * size + pos.z);
}
glEnd();
}
}
static Point3F wedgePoints[4] = {
Point3F(-1, -1, 0),
Point3F( 0, 1, 0),
Point3F( 1, -1, 0),
Point3F( 0,-.75, .5),
};
static U32 wedgeFaces[4][3] = {
{ 0, 3, 1 },
{ 3, 1, 2 },
{ 0, 3, 2 },
{ 0, 2, 1 }
};
void wireWedge(F32 size, Point3F pos)
{
glDisable(GL_CULL_FACE);
for(int i = 0; i < 4; i++)
{
glBegin(GL_LINE_LOOP);
for(int vert = 0; vert < 3; vert++)
{
int idx = wedgeFaces[i][vert];
glColor3f(0,1,0);
glVertex3f(wedgePoints[idx].x * size + pos.x, wedgePoints[idx].y * size + pos.y, wedgePoints[idx].z * size + pos.z);
}
glEnd();
}
}
ConsoleFunction( pathOnMissionLoadDone, void, 1, 1, "Load all path information from interiors.")
{
// Need to load subobjects for all loaded interiors...
SimGroup* pMissionGroup = dynamic_cast<SimGroup*>(Sim::findObject("MissionGroup"));
AssertFatal(pMissionGroup != NULL, "Error, mission done loading and no mission group?");
U32 currStart = 0;
U32 currEnd = 1;
Vector<SimGroup*> groups;
groups.push_back(pMissionGroup);
while (true) {
for (U32 i = currStart; i < currEnd; i++) {
for (SimGroup::iterator itr = groups[i]->begin(); itr != groups[i]->end(); itr++) {
if (dynamic_cast<SimGroup*>(*itr) != NULL)
groups.push_back(static_cast<SimGroup*>(*itr));
}
}
if (groups.size() == currEnd) {
break;
} else {
currStart = currEnd;
currEnd = groups.size();
}
}
for (U32 i = 0; i < groups.size(); i++) {
Path* pPath = dynamic_cast<Path*>(groups[i]);
if (pPath)
pPath->updatePath();
}
}
S32 FN_CDECL cmpPathObject(const void* p1, const void* p2)
{
SimObject* o1 = *((SimObject**)p1);
SimObject* o2 = *((SimObject**)p2);
Marker* m1 = dynamic_cast<Marker*>(o1);
Marker* m2 = dynamic_cast<Marker*>(o2);
if (m1 == NULL && m2 == NULL)
return 0;
else if (m1 != NULL && m2 == NULL)
return 1;
else if (m1 == NULL && m2 != NULL)
return -1;
else {
// Both markers...
return S32(m1->mSeqNum) - S32(m2->mSeqNum);
}
}
} // namespace {}
//--------------------------------------------------------------------------
//-------------------------------------- Implementation
//
IMPLEMENT_CONOBJECT(Path);
Path::Path()
{
mPathIndex = NoPathIndex;
mIsLooping = true;
}
Path::~Path()
{
//
}
//--------------------------------------------------------------------------
void Path::initPersistFields()
{
Parent::initPersistFields();
addField("isLooping", TypeBool, Offset(mIsLooping, Path));
//
}
//--------------------------------------------------------------------------
bool Path::onAdd()
{
if(!Parent::onAdd())
return false;
return true;
}
void Path::onRemove()
{
//
Parent::onRemove();
}
//--------------------------------------------------------------------------
/// Sort the markers objects into sequence order
void Path::sortMarkers()
{
dQsort(objectList.address(), objectList.size(), sizeof(SimObject*), cmpPathObject);
}
void Path::updatePath()
{
// If we need to, allocate a path index from the manager
if (mPathIndex == NoPathIndex)
mPathIndex = gServerPathManager->allocatePathId();
sortMarkers();
Vector<Point3F> positions;
Vector<QuatF> rotations;
Vector<U32> times;
Vector<U32> smoothingTypes;
for (iterator itr = begin(); itr != end(); itr++)
{
Marker* pMarker = dynamic_cast<Marker*>(*itr);
if (pMarker != NULL)
{
Point3F pos;
pMarker->getTransform().getColumn(3, &pos);
positions.push_back(pos);
QuatF rot;
rot.set(pMarker->getTransform());
rotations.push_back(rot);
times.push_back(pMarker->mMSToNext);
smoothingTypes.push_back(pMarker->mSmoothingType);
}
}
// DMMTODO: Looping paths.
gServerPathManager->updatePath(mPathIndex, positions, rotations, times, smoothingTypes);
}
void Path::addObject(SimObject* obj)
{
Parent::addObject(obj);
if (mPathIndex != NoPathIndex) {
// If we're already finished, and this object is a marker, then we need to
// update our path information...
if (dynamic_cast<Marker*>(obj) != NULL)
updatePath();
}
}
void Path::removeObject(SimObject* obj)
{
bool recalc = dynamic_cast<Marker*>(obj) != NULL;
Parent::removeObject(obj);
if (mPathIndex != NoPathIndex && recalc == true)
updatePath();
}
ConsoleMethod(Path,getPathId,S32,2, 2,"getPathId();")
{
return object->getPathIndex();
}
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1(Marker);
Marker::Marker()
{
// Not ghostable unless we're editing...
mNetFlags.clear(Ghostable);
mTypeMask = MarkerObjectType;
mSeqNum = 0;
mMSToNext = 1000;
mSmoothingType = SmoothingTypeSpline;
mKnotType = KnotTypeNormal;
}
Marker::~Marker()
{
//
}
//--------------------------------------------------------------------------
static EnumTable::Enums markerEnums[] =
{
{ Marker::SmoothingTypeSpline , "Spline" },
{ Marker::SmoothingTypeLinear , "Linear" },
//{ Marker::SmoothingTypeAccelerate , "Accelerate" },
};
static EnumTable markerSmoothingTable(2, &markerEnums[0]);
static EnumTable::Enums knotEnums[] =
{
{ Marker::KnotTypeNormal , "Normal" },
{ Marker::KnotTypePositionOnly, "Position Only" },
{ Marker::KnotTypeKink, "Kink" },
};
static EnumTable markerKnotTable(3, &knotEnums[0]);
void Marker::initPersistFields()
{
Parent::initPersistFields();
addGroup("Misc");
addField("seqNum", TypeS32, Offset(mSeqNum, Marker));
addField("type", TypeEnum, Offset(mKnotType, Marker), 1, &markerKnotTable);
addField("msToNext", TypeS32, Offset(mMSToNext, Marker));
addField("smoothingType", TypeEnum, Offset(mSmoothingType, Marker), 1, &markerSmoothingTable);
endGroup("Misc");
}
//--------------------------------------------------------------------------
bool Marker::onAdd()
{
if(!Parent::onAdd())
return false;
mObjBox = Box3F(Point3F(-.25, -.25, -.25), Point3F(.25, .25, .25));
resetWorldBox();
if(gEditingMission)
onEditorEnable();
return true;
}
void Marker::onRemove()
{
if(gEditingMission)
onEditorDisable();
Parent::onRemove();
}
void Marker::onGroupAdd()
{
mSeqNum = getGroup()->size();
}
/// Enable scoping so we can see this thing on the client.
void Marker::onEditorEnable()
{
mNetFlags.set(Ghostable);
setScopeAlways();
addToScene();
}
/// Disable scoping so we can see this thing on the client
void Marker::onEditorDisable()
{
removeFromScene();
mNetFlags.clear(Ghostable);
clearScopeAlways();
}
/// Tell our parent that this Path has been modified
void Marker::inspectPostApply()
{
Path *path = dynamic_cast<Path*>(getGroup());
if (path)
path->updatePath();
}
//--------------------------------------------------------------------------
bool Marker::prepRenderImage(SceneState* state, const U32 stateKey,
const U32 /*startZone*/, const bool /*modifyBaseState*/)
{
if (isLastState(state, stateKey))
return false;
setLastState(state, stateKey);
// This should be sufficient for most objects that don't manage zones, and
// don't need to return a specialized RenderImage...
if (state->isObjectRendered(this)) {
SceneRenderImage* image = new SceneRenderImage;
image->obj = this;
state->insertRenderImage(image);
}
return false;
}
void Marker::renderObject(SceneState* state, SceneRenderImage*)
{
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on entry");
RectI viewport;
glMatrixMode(GL_PROJECTION);
glPushMatrix();
dglGetViewport(&viewport);
// Uncomment this if this is a "simple" (non-zone managing) object
state->setupObjectProjection(this);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
dglMultMatrix(&mObjToWorld);
glScalef(mObjScale.x, mObjScale.y, mObjScale.z);
// RENDER CODE HERE
wireWedge(.25, Point3F(0, 0, 0));
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
dglSetViewport(viewport);
AssertFatal(dglIsInCanonicalState(), "Error, GL not in canonical state on exit");
}
//--------------------------------------------------------------------------
U32 Marker::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
// Note that we don't really care about efficiency here, since this is an
// edit-only ghost...
stream->writeAffineTransform(mObjToWorld);
return retMask;
}
void Marker::unpackUpdate(NetConnection* con, BitStream* stream)
{
Parent::unpackUpdate(con, stream);
// Transform
MatrixF otow;
stream->readAffineTransform(&otow);
setTransform(otow);
}

111
engine/sim/simPath.h Executable file
View File

@ -0,0 +1,111 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _SIMPATH_H_
#define _SIMPATH_H_
#ifndef _SCENEOBJECT_H_
#include "sim/sceneObject.h"
#endif
//--------------------------------------------------------------------------
/// A path!
class Path : public SimGroup
{
typedef SimGroup Parent;
public:
enum {
NoPathIndex = 0xFFFFFFFF
};
private:
U32 mPathIndex;
bool mIsLooping;
protected:
bool onAdd();
void onRemove();
public:
Path();
~Path();
void addObject(SimObject*);
void removeObject(SimObject*);
void sortMarkers();
void updatePath();
bool isLooping() { return mIsLooping; }
U32 getPathIndex() const;
DECLARE_CONOBJECT(Path);
static void initPersistFields();
};
//--------------------------------------------------------------------------
class Marker : public SceneObject
{
typedef SceneObject Parent;
friend class Path;
public:
enum {
SmoothingTypeLinear,
SmoothingTypeSpline,
SmoothingTypeAccelerate,
};
enum {
KnotTypeNormal,
KnotTypePositionOnly,
KnotTypeKink,
};
U32 mSeqNum;
U32 mSmoothingType;
U32 mKnotType;
U32 mMSToNext;
// Rendering
protected:
bool prepRenderImage(SceneState *state, const U32 stateKey, const U32 startZone, const bool modifyBaseZoneState);
void renderObject(SceneState *state, SceneRenderImage *image);
protected:
bool onAdd();
void onRemove();
void onGroupAdd();
void onEditorEnable();
void onEditorDisable();
public:
Marker();
~Marker();
DECLARE_CONOBJECT(Marker);
static void initPersistFields();
void inspectPostApply();
U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream);
void unpackUpdate(NetConnection *conn, BitStream *stream);
};
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
inline U32 Path::getPathIndex() const
{
return mPathIndex;
}
#endif // _H_PATH