added everything

This commit is contained in:
Metario
2017-04-17 06:17:10 -06:00
commit 9c6ff74f19
6121 changed files with 1625704 additions and 0 deletions

33
engine/Makefile Executable file
View File

@ -0,0 +1,33 @@
#--------------------------------------
# include and verify the users mk/conf.mk
-include ../mk/conf.mk
ifndef CONFIG_STATUS
doConfigure:
$(error Configuration file not defined. Please run $(MAKE) -f mk/configure.mk)
#@$(MAKE) --no-print-directory -f ../mk/configure.mk
else
ifeq ($(CONFIG_STATUS),INVALID)
doConfigure:
$(error Invalid Configuration file. Please run $(MAKE) -f mk/configure.mk)
#@$(MAKE) --no-print-directory -f mk/configure.mk
else
include ../mk/conf.$(COMPILER).$(OS).mk
include ../mk/conf.$(COMPILER).mk
endif
endif
include targets.torque.mk
include ../mk/conf.common.mk
#default:
# echo default.
ifneq ($(MAKECMDGOALS),clean)
-include $(addprefix $(DIR.OBJ)/, $(addsuffix $(EXT.DEP), $(basename $(filter %.cc %.c,$(SOURCE.ALL)))))
endif

2503
engine/audio/audio.cc Executable file

File diff suppressed because it is too large Load Diff

16
engine/audio/audio.h Executable file
View File

@ -0,0 +1,16 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIO_H_
#define _AUDIO_H_
#ifndef _PLATFORMAUDIO_H_
#include "platform/platformAudio.h"
#endif
#ifndef _AUDIODATABLOCK_H_
#include "audio/audioDataBlock.h"
#endif
#endif // _H_AUDIO_

429
engine/audio/audioBuffer.cc Executable file
View File

@ -0,0 +1,429 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platformAL.h"
#include "audio/audioBuffer.h"
#include "core/stream.h"
#include "console/console.h"
#include "core/frameAllocator.h"
#ifndef TORQUE_NO_OGGVORBIS
#include "vorbis/codec.h"
#endif
//#define LOG_SOUND_LOADS
/// WAV File-header
struct WAVFileHdr
{
ALubyte id[4];
ALsizei size;
ALubyte type[4];
};
//// WAV Fmt-header
struct WAVFmtHdr
{
ALushort format;
ALushort channels;
ALuint samplesPerSec;
ALuint bytesPerSec;
ALushort blockAlign;
ALushort bitsPerSample;
};
/// WAV FmtEx-header
struct WAVFmtExHdr
{
ALushort size;
ALushort samplesPerBlock;
};
/// WAV Smpl-header
struct WAVSmplHdr
{
ALuint manufacturer;
ALuint product;
ALuint samplePeriod;
ALuint note;
ALuint fineTune;
ALuint SMPTEFormat;
ALuint SMPTEOffest;
ALuint loops;
ALuint samplerData;
struct
{
ALuint identifier;
ALuint type;
ALuint start;
ALuint end;
ALuint fraction;
ALuint count;
} loop[1];
};
/// WAV Chunk-header
struct WAVChunkHdr
{
ALubyte id[4];
ALuint size;
};
#define CHUNKSIZE 4096
//--------------------------------------
AudioBuffer::AudioBuffer(StringTableEntry filename)
{
AssertFatal(StringTable->lookup(filename), "AudioBuffer:: filename is not a string table entry");
mFilename = filename;
mLoading = false;
malBuffer = 0;
}
AudioBuffer::~AudioBuffer()
{
if( malBuffer != 0 ) {
alDeleteBuffers( 1, &malBuffer );
}
}
//--------------------------------------
Resource<AudioBuffer> AudioBuffer::find(const char *filename)
{
U32 mark = FrameAllocator::getWaterMark();
char * f2 = NULL;
Resource<AudioBuffer> buffer = ResourceManager->load(filename);
if (bool(buffer) == false)
{
// wav file doesn't exist, try ogg file instead
S32 len = dStrlen(filename);
if (len>3 && !dStricmp(filename+len-4,".wav"))
{
f2 = (char*)FrameAllocator::alloc(len+1);
dStrcpy(f2,filename);
f2[len-3] = 'o';
f2[len-2] = 'g';
f2[len-1] = 'g';
buffer = ResourceManager->load(f2);
}
}
// if resource still not there, try to create it if file exists
if (bool(buffer) == false)
{
// see if the file exists -- first try default, then try ogg
if (ResourceManager->getPathOf(filename))
{
AudioBuffer *temp = new AudioBuffer(StringTable->insert(filename));
ResourceManager->add(filename, temp);
buffer = ResourceManager->load(filename);
}
else if (f2 && ResourceManager->getPathOf(f2))
{
AudioBuffer *temp = new AudioBuffer(StringTable->insert(f2));
ResourceManager->add(f2, temp);
buffer = ResourceManager->load(f2);
}
}
FrameAllocator::setWaterMark(mark);
return buffer;
}
ResourceInstance* AudioBuffer::construct(Stream &)
{
return NULL;
}
//-----------------------------------------------------------------
ALuint AudioBuffer::getALBuffer()
{
if (!alcGetCurrentContext())
return 0;
// clear the error state
alGetError();
// Intangir> fix for newest openAL from creative (it returns true, yea right 0 is not a valid buffer)
// it MIGHT not work at all for all i know.
if (malBuffer && alIsBuffer(malBuffer))
return malBuffer;
alGenBuffers(1, &malBuffer);
if(alGetError() != AL_NO_ERROR)
return 0;
ResourceObject * obj = ResourceManager->find(mFilename);
if(obj)
{
bool readSuccess = false;
S32 len = dStrlen(mFilename);
if(len > 3 && !dStricmp(mFilename + len - 4, ".wav"))
{
#ifdef LOG_SOUND_LOADS
Con::printf("Reading WAV: %s\n", mFilename);
#endif
readSuccess = readWAV(obj);
}
#ifndef TORQUE_NO_OGGVORBIS
else if(len > 3 && !dStricmp(mFilename + len - 4, ".ogg"))
{
# ifdef LOG_SOUND_LOADS
Con::printf("Reading Ogg: %s\n", mFilename);
# endif
readSuccess = readOgg(obj);
}
#endif
if(readSuccess)
return(malBuffer);
}
alDeleteBuffers(1, &malBuffer);
malBuffer = NULL;
return 0;
}
/*! The Read a WAV file from the given ResourceObject and initialize
an alBuffer with it.
*/
bool AudioBuffer::readWAV(ResourceObject *obj)
{
WAVChunkHdr chunkHdr;
WAVFmtExHdr fmtExHdr;
WAVFileHdr fileHdr;
WAVSmplHdr smplHdr;
WAVFmtHdr fmtHdr;
ALenum format = AL_FORMAT_MONO16;
char *data = NULL;
ALsizei size = 0;
ALsizei freq = 22050;
ALboolean loop = AL_FALSE;
Stream *stream = ResourceManager->openStream(obj);
if (!stream)
return false;
stream->read(4, &fileHdr.id[0]);
stream->read(&fileHdr.size);
stream->read(4, &fileHdr.type[0]);
fileHdr.size=((fileHdr.size+1)&~1)-4;
stream->read(4, &chunkHdr.id[0]);
stream->read(&chunkHdr.size);
// unread chunk data rounded up to nearest WORD
S32 chunkRemaining = chunkHdr.size + (chunkHdr.size&1);
while ((fileHdr.size!=0) && (stream->getStatus() != Stream::EOS))
{
// WAV Format header
if (!dStrncmp((const char*)chunkHdr.id,"fmt ",4))
{
stream->read(&fmtHdr.format);
stream->read(&fmtHdr.channels);
stream->read(&fmtHdr.samplesPerSec);
stream->read(&fmtHdr.bytesPerSec);
stream->read(&fmtHdr.blockAlign);
stream->read(&fmtHdr.bitsPerSample);
if (fmtHdr.format==0x0001)
{
format=(fmtHdr.channels==1?
(fmtHdr.bitsPerSample==8?AL_FORMAT_MONO8:AL_FORMAT_MONO16):
(fmtHdr.bitsPerSample==8?AL_FORMAT_STEREO8:AL_FORMAT_STEREO16));
freq=fmtHdr.samplesPerSec;
chunkRemaining -= sizeof(WAVFmtHdr);
}
else
{
stream->read(sizeof(WAVFmtExHdr), &fmtExHdr);
chunkRemaining -= sizeof(WAVFmtExHdr);
}
}
// WAV Format header
else if (!dStrncmp((const char*)chunkHdr.id,"data",4))
{
if (fmtHdr.format==0x0001)
{
size=chunkHdr.size;
data=new char[chunkHdr.size];
if (data)
{
stream->read(chunkHdr.size, data);
#if defined(TORQUE_OS_MAC)
// need to endian-flip the 16-bit data.
if (fmtHdr.bitsPerSample==16) // !!!TBD we don't handle stereo, so may be RL flipped.
{
U16 *ds = (U16*)data;
U16 *de = (U16*)(data+size);
while (ds<de)
{
#if defined(TORQUE_BIG_ENDIAN)
*ds = convertLEndianToHost(*ds);
#else
*ds = convertBEndianToHost(*ds);
#endif
ds++;
}
}
#endif
chunkRemaining -= chunkHdr.size;
}
else
break;
}
else if (fmtHdr.format==0x0011)
{
//IMA ADPCM
}
else if (fmtHdr.format==0x0055)
{
//MP3 WAVE
}
}
// WAV Loop header
else if (!dStrncmp((const char*)chunkHdr.id,"smpl",4))
{
// this struct read is NOT endian safe but it is ok because
// we are only testing the loops field against ZERO
stream->read(sizeof(WAVSmplHdr), &smplHdr);
loop = (smplHdr.loops ? AL_TRUE : AL_FALSE);
chunkRemaining -= sizeof(WAVSmplHdr);
}
// either we have unread chunk data or we found an unknown chunk type
// loop and read up to 1K bytes at a time until we have
// read to the end of this chunk
char buffer[1024];
AssertFatal(chunkRemaining >= 0, "AudioBuffer::readWAV: remaining chunk data should never be less than zero.");
while (chunkRemaining > 0)
{
S32 readSize = getMin(1024, chunkRemaining);
stream->read(readSize, buffer);
chunkRemaining -= readSize;
}
fileHdr.size-=(((chunkHdr.size+1)&~1)+8);
// read next chunk header...
stream->read(4, &chunkHdr.id[0]);
stream->read(&chunkHdr.size);
// unread chunk data rounded up to nearest WORD
chunkRemaining = chunkHdr.size + (chunkHdr.size&1);
}
ResourceManager->closeStream(stream);
if (data)
{
alBufferData(malBuffer, format, data, size, freq);
delete [] data;
return (alGetError() == AL_NO_ERROR);
}
return false;
}
#ifndef TORQUE_NO_OGGVORBIS
/*! The Read an Ogg Vorbis file from the given ResourceObject and initialize
an alBuffer with it.
*/
bool AudioBuffer::readOgg(ResourceObject *obj)
{
OggVorbisFile vf;
vorbis_info *vi;
ALenum format = AL_FORMAT_MONO16;
char *data = NULL;
ALsizei size = 0;
ALsizei freq = 22050;
ALboolean loop = AL_FALSE;
int current_section = 0;
#if defined(TORQUE_BIG_ENDIAN)
int endian = 1;
#else
int endian = 0;
#endif
int eof = 0;
Stream *stream = ResourceManager->openStream(obj);
if (!stream)
return false;
if(vf.ov_open(stream, NULL, 0) < 0) {
return false;
}
//Read Vorbis File Info
vi = vf.ov_info(-1);
freq = vi->rate;
long samples = (long)vf.ov_pcm_total(-1);
if(vi->channels == 1) {
format = AL_FORMAT_MONO16;
size = 2 * samples;
} else {
format = AL_FORMAT_STEREO16;
size = 4 * samples;
}
data=new char[size];
if (data)
{
long ret = oggRead(&vf, data, size, endian, &current_section);
}
/* cleanup */
vf.ov_clear();
ResourceManager->closeStream(stream);
if (data)
{
alBufferData(malBuffer, format, data, size, freq);
delete [] data;
return (alGetError() == AL_NO_ERROR);
}
return false;
}
// ov_read() only returns a maximum of one page worth of data
// this helper function will repeat the read until buffer is full
long AudioBuffer::oggRead(OggVorbisFile* vf, char *buffer,int length,
int bigendianp,int *bitstream)
{
long bytesRead = 0;
long totalBytes = 0;
long offset = 0;
long bytesToRead = 0;
//while((offset + CHUNKSIZE) < length) {
while((offset) < length)
{
if((length - offset) < CHUNKSIZE)
bytesToRead = length - offset;
else
bytesToRead = CHUNKSIZE;
bytesRead = vf->ov_read(buffer, bytesToRead, bigendianp, bitstream);
if(bytesRead <= 0)
break;
offset += bytesRead;
buffer += bytesRead;
}
return offset;
}
#endif

56
engine/audio/audioBuffer.h Executable file
View File

@ -0,0 +1,56 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIOBUFFER_H_
#define _AUDIOBUFFER_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _PLATFORMAL_H_
#include "platform/platformAL.h"
#endif
#ifndef _RESMANAGER_H_
#include "core/resManager.h"
#endif
// MLH - don't need oggbvorbis in tools
#ifndef TORQUE_NO_OGGVORBIS
#include "audio/vorbisStream.h"
#endif
//--------------------------------------------------------------------------
class AudioBuffer: public ResourceInstance
{
friend class AudioThread;
private:
StringTableEntry mFilename;
bool mLoading;
ALuint malBuffer;
bool readRIFFchunk(Stream &s, const char *seekLabel, U32 *size);
bool readWAV(ResourceObject *obj);
#ifndef TORQUE_NO_OGGVORBIS
bool readOgg(ResourceObject *obj);
long oggRead(OggVorbisFile* vf, char *buffer,int length,
int bigendianp,int *bitstream);
#endif
public:
AudioBuffer(StringTableEntry filename);
~AudioBuffer();
ALuint getALBuffer();
bool isLoading() {return(mLoading);}
static Resource<AudioBuffer> find(const char *filename);
static ResourceInstance* construct(Stream& stream);
};
#endif // _H_AUDIOBUFFER_

532
engine/audio/audioDataBlock.cc Executable file
View File

@ -0,0 +1,532 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "audio/audioDataBlock.h"
#include "console/consoleTypes.h"
#include "platform/platformAL.h"
#include "sim/netConnection.h"
//--------------------------------------------------------------------------
namespace
{
void writeRangedF32(BitStream * bstream, F32 val, F32 min, F32 max, U32 numBits)
{
val = (mClampF(val, min, max) - min) / (max - min);
bstream->writeInt(val * ((1 << numBits) - 1), numBits);
}
F32 readRangedF32(BitStream * bstream, F32 min, F32 max, U32 numBits)
{
return(min + (F32(bstream->readInt(numBits)) / F32((1 << numBits) - 1)) * (max - min));
}
void writeRangedS32(BitStream * bstream, S32 val, S32 min, S32 max)
{
bstream->writeRangedU32((val - min), 0, (max - min));
}
S32 readRangedS32(BitStream * bstream, S32 min, S32 max)
{
return(bstream->readRangedU32(0, (max - min)) + min);
}
}
//--------------------------------------------------------------------------
// Class AudioEnvironment:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioEnvironment);
AudioEnvironment::AudioEnvironment()
{
mUseRoom = true;
mRoom = EAX_ENVIRONMENT_GENERIC;
mRoomHF = 0;
mReflections = 0;
mReverb = 0;
mRoomRolloffFactor = 0.1f;
mDecayTime = 0.1f;
mDecayHFRatio = 0.1f;
mReflectionsDelay = 0.f;
mReverbDelay = 0.f;
mRoomVolume = 0;
mEffectVolume = 0.f;
mDamping = 0.f;
mEnvironmentSize = 10.f;
mEnvironmentDiffusion = 1.f;
mAirAbsorption = 0.f;
mFlags = 0;
}
static EnumTable::Enums roomEnums[] =
{
{ EAX_ENVIRONMENT_GENERIC, "GENERIC" }, // 0
{ EAX_ENVIRONMENT_PADDEDCELL, "PADDEDCELL" },
{ EAX_ENVIRONMENT_ROOM, "ROOM" },
{ EAX_ENVIRONMENT_BATHROOM, "BATHROOM" },
{ EAX_ENVIRONMENT_LIVINGROOM, "LIVINGROOM" },
{ EAX_ENVIRONMENT_STONEROOM, "STONEROOM" }, // 5
{ EAX_ENVIRONMENT_AUDITORIUM, "AUDITORIUM" },
{ EAX_ENVIRONMENT_CONCERTHALL, "CONCERTHALL" },
{ EAX_ENVIRONMENT_CAVE, "CAVE" },
{ EAX_ENVIRONMENT_ARENA, "ARENA" },
{ EAX_ENVIRONMENT_HANGAR, "HANGAR" }, // 10
{ EAX_ENVIRONMENT_CARPETEDHALLWAY, "CARPETEDHALLWAY" },
{ EAX_ENVIRONMENT_HALLWAY, "HALLWAY" },
{ EAX_ENVIRONMENT_STONECORRIDOR, "STONECORRIDOR" },
{ EAX_ENVIRONMENT_ALLEY, "ALLEY" },
{ EAX_ENVIRONMENT_FOREST, "FOREST" }, // 15
{ EAX_ENVIRONMENT_CITY, "CITY" },
{ EAX_ENVIRONMENT_MOUNTAINS, "MOUNTAINS" },
{ EAX_ENVIRONMENT_QUARRY, "QUARRY" },
{ EAX_ENVIRONMENT_PLAIN, "PLAIN" },
{ EAX_ENVIRONMENT_PARKINGLOT, "PARKINGLOT" }, // 20
{ EAX_ENVIRONMENT_SEWERPIPE, "SEWERPIPE" },
{ EAX_ENVIRONMENT_UNDERWATER, "UNDERWATER" },
{ EAX_ENVIRONMENT_DRUGGED, "DRUGGED" },
{ EAX_ENVIRONMENT_DIZZY, "DIZZY" },
{ EAX_ENVIRONMENT_PSYCHOTIC, "PSYCHOTIC" } // 25
};
static EnumTable gAudioEnvironmentRoomTypes(sizeof(roomEnums) / sizeof(roomEnums[0]), &roomEnums[0]);
//--------------------------------------------------------------------------
IMPLEMENT_CONSOLETYPE(AudioEnvironment)
IMPLEMENT_GETDATATYPE(AudioEnvironment)
IMPLEMENT_SETDATATYPE(AudioEnvironment)
void AudioEnvironment::initPersistFields()
{
Parent::initPersistFields();
addField("useRoom", TypeBool, Offset(mUseRoom, AudioEnvironment));
addField("room", TypeEnum, Offset(mRoom, AudioEnvironment), 1, &gAudioEnvironmentRoomTypes);
addField("roomHF", TypeS32, Offset(mRoomHF, AudioEnvironment));
addField("reflections", TypeS32, Offset(mReflections, AudioEnvironment));
addField("reverb", TypeS32, Offset(mReverb, AudioEnvironment));
addField("roomRolloffFactor", TypeF32, Offset(mRoomRolloffFactor, AudioEnvironment));
addField("decayTime", TypeF32, Offset(mDecayTime, AudioEnvironment));
addField("decayHFRatio", TypeF32, Offset(mDecayHFRatio, AudioEnvironment));
addField("reflectionsDelay", TypeF32, Offset(mReflectionsDelay, AudioEnvironment));
addField("reverbDelay", TypeF32, Offset(mReverbDelay, AudioEnvironment));
addField("roomVolume", TypeS32, Offset(mRoomVolume, AudioEnvironment));
addField("effectVolume", TypeF32, Offset(mEffectVolume, AudioEnvironment));
addField("damping", TypeF32, Offset(mDamping, AudioEnvironment));
addField("environmentSize", TypeF32, Offset(mEnvironmentSize, AudioEnvironment));
addField("environmentDiffusion", TypeF32, Offset(mEnvironmentDiffusion, AudioEnvironment));
addField("airAbsorption", TypeF32, Offset(mAirAbsorption, AudioEnvironment));
addField("flags", TypeS32, Offset(mFlags, AudioEnvironment));
}
void AudioEnvironment::packData(BitStream* stream)
{
Parent::packData(stream);
if(stream->writeFlag(mUseRoom))
stream->writeRangedU32(mRoom, EAX_ENVIRONMENT_GENERIC, EAX_ENVIRONMENT_COUNT);
else
{
writeRangedS32(stream, mRoomHF, -10000, 0);
writeRangedS32(stream, mReflections, -10000, 10000);
writeRangedS32(stream, mReverb, -10000, 2000);
writeRangedF32(stream, mRoomRolloffFactor, 0.1f, 10.f, 8);
writeRangedF32(stream, mDecayTime, 0.1f, 20.f, 8);
writeRangedF32(stream, mDecayHFRatio, 0.1f, 20.f, 8);
writeRangedF32(stream, mReflectionsDelay, 0.f, 0.3f, 9);
writeRangedF32(stream, mReverbDelay, 0.f, 0.1f, 7);
writeRangedS32(stream, mRoomVolume, -10000, 0);
writeRangedF32(stream, mEffectVolume, 0.f, 1.f, 8);
writeRangedF32(stream, mDamping, 0.f, 2.f, 9);
writeRangedF32(stream, mEnvironmentSize, 1.f, 100.f, 10);
writeRangedF32(stream, mEnvironmentDiffusion, 0.f, 1.f, 8);
writeRangedF32(stream, mAirAbsorption, -100.f, 0.f, 10);
stream->writeInt(mFlags, 6);
}
}
void AudioEnvironment::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mUseRoom = stream->readFlag();
if(mUseRoom)
mRoom = stream->readRangedU32(EAX_ENVIRONMENT_GENERIC, EAX_ENVIRONMENT_COUNT);
else
{
mRoomHF = readRangedS32(stream, -10000, 0);
mReflections = readRangedS32(stream, -10000, 10000);
mReverb = readRangedS32(stream, -10000, 2000);
mRoomRolloffFactor = readRangedF32(stream, 0.1f, 10.f, 8);
mDecayTime = readRangedF32(stream, 0.1f, 20.f, 8);
mDecayHFRatio = readRangedF32(stream, 0.1f, 20.f, 8);
mReflectionsDelay = readRangedF32(stream, 0.f, 0.3f, 9);
mReverbDelay = readRangedF32(stream, 0.f, 0.1f, 7);
mRoomVolume = readRangedS32(stream, -10000, 0);
mEffectVolume = readRangedF32(stream, 0.f, 1.f, 8);
mDamping = readRangedF32(stream, 0.f, 2.f, 9);
mEnvironmentSize = readRangedF32(stream, 1.f, 100.f, 10);
mEnvironmentDiffusion = readRangedF32(stream, 0.f, 1.f, 8);
mAirAbsorption = readRangedF32(stream, -100.f, 0.f, 10);
mFlags = stream->readInt(6);
}
}
//--------------------------------------------------------------------------
// Class AudioEnvironmentProfile:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioSampleEnvironment);
AudioSampleEnvironment::AudioSampleEnvironment()
{
mDirect = 0;
mDirectHF = 0;
mRoom = 0;
mRoomHF = 0;
mObstruction = 0.f;
mObstructionLFRatio = 0.f;
mOcclusion = 0.f;
mOcclusionLFRatio = 0.f;
mOcclusionRoomRatio = 0.f;
mRoomRolloff = 0.f;
mAirAbsorption = 0.f;
mOutsideVolumeHF = 0.f;
mFlags = 0;
}
//--------------------------------------------------------------------------
IMPLEMENT_CONSOLETYPE(AudioSampleEnvironment)
IMPLEMENT_GETDATATYPE(AudioSampleEnvironment)
IMPLEMENT_SETDATATYPE(AudioSampleEnvironment)
void AudioSampleEnvironment::initPersistFields()
{
Parent::initPersistFields();
addField("direct", TypeS32, Offset(mDirect, AudioSampleEnvironment));
addField("directHF", TypeS32, Offset(mDirectHF, AudioSampleEnvironment));
addField("room", TypeS32, Offset(mRoom, AudioSampleEnvironment));
addField("obstruction", TypeF32, Offset(mObstruction, AudioSampleEnvironment));
addField("obstructionLFRatio", TypeF32, Offset(mObstructionLFRatio, AudioSampleEnvironment));
addField("occlusion", TypeF32, Offset(mOcclusion, AudioSampleEnvironment));
addField("occlusionLFRatio", TypeF32, Offset(mOcclusionLFRatio, AudioSampleEnvironment));
addField("occlusionRoomRatio", TypeF32, Offset(mOcclusionRoomRatio, AudioSampleEnvironment));
addField("roomRolloff", TypeF32, Offset(mRoomRolloff, AudioSampleEnvironment));
addField("airAbsorption", TypeF32, Offset(mAirAbsorption, AudioSampleEnvironment));
addField("outsideVolumeHF", TypeS32, Offset(mOutsideVolumeHF, AudioSampleEnvironment));
addField("flags", TypeS32, Offset(mFlags, AudioSampleEnvironment));
}
void AudioSampleEnvironment::packData(BitStream* stream)
{
Parent::packData(stream);
writeRangedS32(stream, mDirect, -10000, 1000);
writeRangedS32(stream, mDirectHF, -10000, 0);
writeRangedS32(stream, mRoom, -10000, 1000);
writeRangedS32(stream, mRoomHF, -10000, 0);
writeRangedF32(stream, mObstruction, 0.f, 1.f, 9);
writeRangedF32(stream, mObstructionLFRatio, 0.f, 1.f, 8);
writeRangedF32(stream, mOcclusion, 0.f, 1.f, 9);
writeRangedF32(stream, mOcclusionLFRatio, 0.f, 1.f, 8);
writeRangedF32(stream, mOcclusionRoomRatio, 0.f, 10.f, 9);
writeRangedF32(stream, mRoomRolloff, 0.f, 10.f, 9);
writeRangedF32(stream, mAirAbsorption, 0.f, 10.f, 9);
writeRangedS32(stream, mOutsideVolumeHF, -10000, 0);
stream->writeInt(mFlags, 3);
}
void AudioSampleEnvironment::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mDirect = readRangedS32(stream, -10000, 1000);
mDirectHF = readRangedS32(stream, -10000, 0);
mRoom = readRangedS32(stream, -10000, 1000);
mRoomHF = readRangedS32(stream, -10000, 0);
mObstruction = readRangedF32(stream, 0.f, 1.f, 9);
mObstructionLFRatio = readRangedF32(stream, 0.f, 1.f, 8);
mOcclusion = readRangedF32(stream, 0.f, 1.f, 9);
mOcclusionLFRatio = readRangedF32(stream, 0.f, 1.f, 8);
mOcclusionRoomRatio = readRangedF32(stream, 0.f, 10.f, 9);
mRoomRolloff = readRangedF32(stream, 0.f, 10.f, 9);
mAirAbsorption = readRangedF32(stream, 0.f, 10.f, 9);
mOutsideVolumeHF = readRangedS32(stream, -10000, 0);
mFlags = stream->readInt(3);
}
//--------------------------------------------------------------------------
// Class AudioDescription:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioDescription);
AudioDescription::AudioDescription()
{
mDescription.mVolume = 1.0f;
mDescription.mIsLooping = false;
mDescription.mIsStreaming = false;
mDescription.mIs3D = false;
mDescription.mReferenceDistance = 1.0f;
mDescription.mMaxDistance = 100.0f;
mDescription.mConeInsideAngle = 360;
mDescription.mConeOutsideAngle = 360;
mDescription.mConeOutsideVolume = 1.0f;
mDescription.mConeVector.set(0, 0, 1);
mDescription.mEnvironmentLevel = 0.f;
mDescription.mLoopCount = -1;
mDescription.mMinLoopGap = 0;
mDescription.mMaxLoopGap = 0;
mDescription.mType = 0;
}
//--------------------------------------------------------------------------
IMPLEMENT_CONSOLETYPE(AudioDescription)
IMPLEMENT_GETDATATYPE(AudioDescription)
IMPLEMENT_SETDATATYPE(AudioDescription)
void AudioDescription::initPersistFields()
{
Parent::initPersistFields();
addField("volume", TypeF32, Offset(mDescription.mVolume, AudioDescription));
addField("isLooping", TypeBool, Offset(mDescription.mIsLooping, AudioDescription));
addField("isStreaming", TypeBool, Offset(mDescription.mIsStreaming, AudioDescription));
addField("is3D", TypeBool, Offset(mDescription.mIs3D, AudioDescription));
addField("referenceDistance", TypeF32, Offset(mDescription.mReferenceDistance, AudioDescription));
addField("maxDistance", TypeF32, Offset(mDescription.mMaxDistance, AudioDescription));
addField("coneInsideAngle", TypeS32, Offset(mDescription.mConeInsideAngle, AudioDescription));
addField("coneOutsideAngle", TypeS32, Offset(mDescription.mConeOutsideAngle, AudioDescription));
addField("coneOutsideVolume", TypeF32, Offset(mDescription.mConeOutsideVolume, AudioDescription));
addField("coneVector", TypePoint3F, Offset(mDescription.mConeVector, AudioDescription));
addField("environmentLevel", TypeF32, Offset(mDescription.mEnvironmentLevel, AudioDescription));
addField("loopCount", TypeS32, Offset(mDescription.mLoopCount, AudioDescription));
addField("minLoopGap", TypeS32, Offset(mDescription.mMinLoopGap, AudioDescription));
addField("maxLoopGap", TypeS32, Offset(mDescription.mMaxLoopGap, AudioDescription));
addField("type", TypeS32, Offset(mDescription.mType, AudioDescription));
}
//--------------------------------------------------------------------------
bool AudioDescription::onAdd()
{
if (!Parent::onAdd())
return false;
// validate the data
mDescription.mVolume = mClampF(mDescription.mVolume, 0.0f, 1.0f);
mDescription.mLoopCount = mClamp(mDescription.mLoopCount, -1, mDescription.mLoopCount);
mDescription.mMaxLoopGap = mClamp(mDescription.mMaxLoopGap, mDescription.mMinLoopGap, mDescription.mMaxLoopGap);
mDescription.mMinLoopGap = mClamp(mDescription.mMinLoopGap, 0, mDescription.mMaxLoopGap);
if (mDescription.mIs3D)
{
// validate the data
mDescription.mReferenceDistance = mClampF(mDescription.mReferenceDistance, 0.f, mDescription.mReferenceDistance);
mDescription.mMaxDistance = (mDescription.mMaxDistance > mDescription.mReferenceDistance) ? mDescription.mMaxDistance : (mDescription.mReferenceDistance+0.01f);
mDescription.mConeInsideAngle = mClamp(mDescription.mConeInsideAngle, 0, 360);
mDescription.mConeOutsideAngle = mClamp(mDescription.mConeOutsideAngle, mDescription.mConeInsideAngle, 360);
mDescription.mConeOutsideVolume = mClampF(mDescription.mConeOutsideVolume, 0.0f, 1.0f);
mDescription.mConeVector.normalize();
mDescription.mEnvironmentLevel = mClampF(mDescription.mEnvironmentLevel, 0.f, 1.f);
}
if(mDescription.mType >= Audio::NumAudioTypes)
mDescription.mType = 0;
return true;
}
//--------------------------------------------------------------------------
void AudioDescription::packData(BitStream* stream)
{
Parent::packData(stream);
stream->writeFloat(mDescription.mVolume, 6);
if(stream->writeFlag(mDescription.mIsLooping))
{
stream->write(mDescription.mLoopCount);
stream->write(mDescription.mMinLoopGap);
stream->write(mDescription.mMaxLoopGap);
}
stream->writeFlag(mDescription.mIsStreaming);
stream->writeFlag(mDescription.mIs3D);
if (mDescription.mIs3D)
{
stream->write(mDescription.mReferenceDistance);
stream->write(mDescription.mMaxDistance);
stream->writeInt(mDescription.mConeInsideAngle, 9);
stream->writeInt(mDescription.mConeOutsideAngle, 9);
stream->writeInt(mDescription.mConeOutsideVolume, 6);
stream->writeNormalVector(mDescription.mConeVector, 8);
stream->write(mDescription.mEnvironmentLevel);
}
stream->writeInt(mDescription.mType, 3);
}
//--------------------------------------------------------------------------
void AudioDescription::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mDescription.mVolume = stream->readFloat(6);
mDescription.mIsLooping = stream->readFlag();
if(mDescription.mIsLooping)
{
stream->read(&mDescription.mLoopCount);
stream->read(&mDescription.mMinLoopGap);
stream->read(&mDescription.mMaxLoopGap);
}
mDescription.mIsStreaming = stream->readFlag();
mDescription.mIs3D = stream->readFlag();
if ( mDescription.mIs3D )
{
stream->read(&mDescription.mReferenceDistance);
stream->read(&mDescription.mMaxDistance);
mDescription.mConeInsideAngle = stream->readInt(9);
mDescription.mConeOutsideAngle = stream->readInt(9);
mDescription.mConeOutsideVolume = stream->readFloat(6);
stream->readNormalVector(&mDescription.mConeVector, 8);
stream->read(&mDescription.mEnvironmentLevel);
}
mDescription.mType = stream->readInt(3);
}
//--------------------------------------------------------------------------
// Class AudioProfile:
//--------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(AudioProfile);
AudioProfile::AudioProfile()
{
mFilename = NULL;
mDescriptionObjectID = 0;
mDescriptionObject = NULL;
mSampleEnvironment = 0;
mPreload = false;
}
//--------------------------------------------------------------------------
IMPLEMENT_CONSOLETYPE(AudioProfile)
IMPLEMENT_GETDATATYPE(AudioProfile)
IMPLEMENT_SETDATATYPE(AudioProfile)
void AudioProfile::initPersistFields()
{
Parent::initPersistFields();
addField("filename", TypeFilename, Offset(mFilename, AudioProfile));
addField("description", TypeAudioDescriptionPtr, Offset(mDescriptionObject, AudioProfile));
addField("environment", TypeAudioSampleEnvironmentPtr, Offset(mSampleEnvironment, AudioProfile));
addField("preload", TypeBool, Offset(mPreload, AudioProfile));
}
bool AudioProfile::preload(bool server, char errorBuffer[256])
{
if(!Parent::preload(server, errorBuffer))
return false;
if(!server && NetConnection::filesWereDownloaded() && !bool(AudioBuffer::find(mFilename)))
return false;
return true;
}
//--------------------------------------------------------------------------
bool AudioProfile::onAdd()
{
if (!Parent::onAdd())
return false;
if (!mDescriptionObject && mDescriptionObjectID)
Sim::findObject(mDescriptionObjectID , mDescriptionObject);
// if this is client side, make sure that description is as well
if(mDescriptionObject)
{ // client side dataBlock id's are not in the dataBlock id range
if (getId() >= DataBlockObjectIdFirst && getId() <= DataBlockObjectIdLast)
{
SimObjectId pid = mDescriptionObject->getId();
if (pid < DataBlockObjectIdFirst || pid > DataBlockObjectIdLast)
{
Con::errorf(ConsoleLogEntry::General,"AudioProfile: data dataBlock not networkable (use datablock to create).");
return false;
}
}
}
if(mPreload && mFilename != NULL && alcGetCurrentContext())
{
mBuffer = AudioBuffer::find(mFilename);
if(bool(mBuffer))
{
ALuint bufferId = mBuffer->getALBuffer();
}
}
return(true);
}
//--------------------------------------------------------------------------
void AudioProfile::packData(BitStream* stream)
{
Parent::packData(stream);
// audio description:
if (stream->writeFlag(mDescriptionObject))
stream->writeRangedU32(mDescriptionObject->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
// environmental info:
if (stream->writeFlag(mSampleEnvironment))
stream->writeRangedU32(mSampleEnvironment->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
//
char buffer[256];
if(!mFilename)
buffer[0] = 0;
else
dStrcpy(buffer, mFilename);
// S32 len = dStrlen(buffer);
// if(len > 3 && !dStricmp(buffer + len - 4, ".wav"))
// buffer[len-4] = 0;
stream->writeString(buffer);
}
//--------------------------------------------------------------------------
void AudioProfile::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
// audio datablock:
if (stream->readFlag()) {
SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst,
DataBlockObjectIdLast);
mDescriptionObjectID = id;
Sim::findObject(id, mDescriptionObject);
}
// sample environment:
if (stream->readFlag()) {
SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst,
DataBlockObjectIdLast);
Sim::findObject(id, mSampleEnvironment);
}
char buffer[256];
stream->readString(buffer);
// dStrcat(buffer, ".wav");
mFilename = StringTable->insert(buffer);
// Doh! Something missing from the unpackData...don't want to break
// network protocol, so set it to true always here. This is good for
// ThinkTanks only. In the future, simply send the preload bit.
mPreload = true;
}

140
engine/audio/audioDataBlock.h Executable file
View File

@ -0,0 +1,140 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _AUDIODATABLOCK_H_
#define _AUDIODATABLOCK_H_
#ifndef _PLATFORMAUDIO_H_
#include "platform/platformAudio.h"
#endif
#ifndef _AUDIOBUFFER_H_
#include "audio/audioBuffer.h"
#endif
#ifndef _BITSTREAM_H_
#include "core/bitStream.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
//--------------------------------------------------------------------------
class AudioEnvironment : public SimDataBlock
{
typedef SimDataBlock Parent;
public:
bool mUseRoom;
S32 mRoom;
S32 mRoomHF;
S32 mReflections;
S32 mReverb;
F32 mRoomRolloffFactor;
F32 mDecayTime;
F32 mDecayHFRatio;
F32 mReflectionsDelay;
F32 mReverbDelay;
S32 mRoomVolume;
F32 mEffectVolume;
F32 mDamping;
F32 mEnvironmentSize;
F32 mEnvironmentDiffusion;
F32 mAirAbsorption;
S32 mFlags;
AudioEnvironment();
static void initPersistFields();
void packData(BitStream* stream);
void unpackData(BitStream* stream);
DECLARE_CONOBJECT(AudioEnvironment);
};
DECLARE_CONSOLETYPE(AudioEnvironment)
//--------------------------------------------------------------------------
class AudioSampleEnvironment : public SimDataBlock
{
typedef SimDataBlock Parent;
public:
S32 mDirect;
S32 mDirectHF;
S32 mRoom;
S32 mRoomHF;
F32 mObstruction;
F32 mObstructionLFRatio;
F32 mOcclusion;
F32 mOcclusionLFRatio;
F32 mOcclusionRoomRatio;
F32 mRoomRolloff;
F32 mAirAbsorption;
S32 mOutsideVolumeHF;
S32 mFlags;
AudioSampleEnvironment();
static void initPersistFields();
void packData(BitStream* stream);
void unpackData(BitStream* stream);
DECLARE_CONOBJECT(AudioSampleEnvironment);
};
DECLARE_CONSOLETYPE(AudioSampleEnvironment)
//--------------------------------------------------------------------------
class AudioDescription: public SimDataBlock
{
private:
typedef SimDataBlock Parent;
public:
// field info
Audio::Description mDescription;
AudioDescription();
DECLARE_CONOBJECT(AudioDescription);
static void initPersistFields();
virtual bool onAdd();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
const Audio::Description* getDescription() const { return &mDescription; }
};
DECLARE_CONSOLETYPE(AudioDescription)
//----------------------------------------------------------------------------
class AudioProfile: public SimDataBlock
{
private:
typedef SimDataBlock Parent;
Resource<AudioBuffer> mBuffer;
public:
// field info
U32 mDescriptionObjectID;
AudioDescription *mDescriptionObject;
AudioSampleEnvironment *mSampleEnvironment;
StringTableEntry mFilename;
bool mPreload;
AudioProfile();
DECLARE_CONOBJECT(AudioProfile);
static void initPersistFields();
virtual bool onAdd();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
virtual bool preload(bool server, char errorBuffer[256]);
const Audio::Description* getDescription() const { return mDescriptionObject ? mDescriptionObject->getDescription() : NULL; }
bool isPreload() { return mPreload; }
};
DECLARE_CONSOLETYPE(AudioProfile)
#endif // _H_AUDIODATABLOCK_

554
engine/audio/audioFunctions.cc Executable file
View File

@ -0,0 +1,554 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "platform/platformAudio.h"
#include "console/simBase.h"
#include "audio/audioDataBlock.h"
extern ALuint alxGetWaveLen(ALuint buffer);
extern F32 mAudioTypeVolume[Audio::NumAudioTypes];
//--------------------------------------------------------------------------
// Expose all al get/set methods...
//--------------------------------------------------------------------------
enum AL_GetSetBits{
Source = BIT(0),
Listener = BIT(1),
Context = BIT(2),
Environment = BIT(3),
Get = BIT(4),
Set = BIT(5),
Int = BIT(6),
Float = BIT(7),
Float3 = BIT(8),
Float6 = BIT(9)
};
static ALenum getEnum(const char * name, U32 flags)
{
AssertFatal(name, "Audio getEnum: bad param");
static struct {
char * mName;
ALenum mAlenum;
U32 mFlags;
} table[] = {
//-----------------------------------------------------------------------------------------------------------------
// "name" ENUM Flags
//-----------------------------------------------------------------------------------------------------------------
{ "AL_GAIN", AL_GAIN, (Source|Listener|Get|Set|Float) },
{ "AL_GAIN_LINEAR", AL_GAIN_LINEAR, (Source|Listener|Get|Set|Float) },
{ "AL_PITCH", AL_PITCH, (Source|Get|Set|Float) },
{ "AL_REFERENCE_DISTANCE", AL_REFERENCE_DISTANCE, (Source|Get|Set|Float) },
{ "AL_MAX_DISTANCE", AL_MAX_DISTANCE, (Source|Get|Set|Float) },
{ "AL_CONE_OUTER_GAIN", AL_CONE_OUTER_GAIN, (Source|Get|Set|Float) },
{ "AL_POSITION", AL_POSITION, (Source|Listener|Get|Set|Float3) },
{ "AL_DIRECTION", AL_DIRECTION, (Source|Get|Set|Float3) },
{ "AL_VELOCITY", AL_VELOCITY, (Source|Listener|Get|Set|Float3) },
{ "AL_ORIENTATION", AL_ORIENTATION, (Listener|Set|Float6) },
{ "AL_CONE_INNER_ANGLE", AL_CONE_INNER_ANGLE, (Source|Get|Set|Int) },
{ "AL_CONE_OUTER_ANGLE", AL_CONE_OUTER_ANGLE, (Source|Get|Set|Int) },
{ "AL_LOOPING", AL_LOOPING, (Source|Get|Set|Int) },
//{ "AL_STREAMING", AL_STREAMING, (Source|Get|Set|Int) },
//{ "AL_BUFFER", AL_BUFFER, (Source|Get|Set|Int) },
{ "AL_VENDOR", AL_VENDOR, (Context|Get) },
{ "AL_VERSION", AL_VERSION, (Context|Get) },
{ "AL_RENDERER", AL_RENDERER, (Context|Get) },
{ "AL_EXTENSIONS", AL_EXTENSIONS, (Context|Get) },
/*
// environment
{ "AL_ENV_ROOM_IASIG", AL_ENV_ROOM_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_ROOM_HIGH_FREQUENCY_IASIG", AL_ENV_ROOM_HIGH_FREQUENCY_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_REFLECTIONS_IASIG", AL_ENV_REFLECTIONS_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_REVERB_IASIG", AL_ENV_REVERB_IASIG, (Environment|Get|Set|Int) },
{ "AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG", AL_ENV_ROOM_ROLLOFF_FACTOR_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DECAY_TIME_IASIG", AL_ENV_DECAY_TIME_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG", AL_ENV_DECAY_HIGH_FREQUENCY_RATIO_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_REFLECTIONS_DELAY_IASIG", AL_ENV_REFLECTIONS_DELAY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_REVERB_DELAY_IASIG", AL_ENV_REVERB_DELAY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DIFFUSION_IASIG", AL_ENV_DIFFUSION_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_DENSITY_IASIG", AL_ENV_DENSITY_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG", AL_ENV_HIGH_FREQUENCY_REFERENCE_IASIG, (Environment|Get|Set|Float) },
{ "AL_ENV_ROOM_VOLUME_EXT", AL_ENV_ROOM_VOLUME_EXT, (Environment|Get|Set|Int) },
{ "AL_ENV_FLAGS_EXT", AL_ENV_FLAGS_EXT, (Environment|Get|Set|Int) },
{ "AL_ENV_EFFECT_VOLUME_EXT", AL_ENV_EFFECT_VOLUME_EXT, (Environment|Get|Set|Float) },
{ "AL_ENV_DAMPING_EXT", AL_ENV_DAMPING_EXT, (Environment|Get|Set|Float) },
{ "AL_ENV_ENVIRONMENT_SIZE_EXT", AL_ENV_ENVIRONMENT_SIZE_EXT, (Environment|Get|Set|Float) },
// sample environment
{ "AL_ENV_SAMPLE_DIRECT_EXT", AL_ENV_SAMPLE_DIRECT_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_DIRECT_HF_EXT", AL_ENV_SAMPLE_DIRECT_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_ROOM_EXT", AL_ENV_SAMPLE_ROOM_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_ROOM_HF_EXT", AL_ENV_SAMPLE_ROOM_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT", AL_ENV_SAMPLE_OUTSIDE_VOLUME_HF_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_FLAGS_EXT", AL_ENV_SAMPLE_FLAGS_EXT, (Source|Get|Set|Int) },
{ "AL_ENV_SAMPLE_REVERB_MIX_EXT", AL_ENV_SAMPLE_REVERB_MIX_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OBSTRUCTION_EXT", AL_ENV_SAMPLE_OBSTRUCTION_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT", AL_ENV_SAMPLE_OBSTRUCTION_LF_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_EXT", AL_ENV_SAMPLE_OCCLUSION_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_LF_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT", AL_ENV_SAMPLE_OCCLUSION_ROOM_RATIO_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT", AL_ENV_SAMPLE_ROOM_ROLLOFF_EXT, (Source|Get|Set|Float) },
{ "AL_ENV_SAMPLE_AIR_ABSORPTION_EXT", AL_ENV_SAMPLE_AIR_ABSORPTION_EXT, (Source|Get|Set|Float) },
*/
};
for(U32 i = 0; i < (sizeof(table) / sizeof(table[0])); i++)
{
if((table[i].mFlags & flags) != flags)
continue;
if(dStricmp(table[i].mName, name) == 0)
return(table[i].mAlenum);
}
return(AL_INVALID);
}
//-----------------------------------------------
ConsoleFunctionGroupBegin(Audio, "Functions dealing with the OpenAL audio layer.\n\n"
"@see www.OpenAL.org for what these functions do. Variances from posted"
" behaviour is described below.");
ConsoleFunction(OpenALInitDriver, bool, 1, 1, "Initializes the OpenAL driver.\n\n"
"@note You must call this before any sounds will work!")
{
if (Audio::OpenALInit())
{
static bool registered = false;
if (!registered) {
ResourceManager->registerExtension(".wav", AudioBuffer::construct);
ResourceManager->registerExtension(".ogg", AudioBuffer::construct);
}
registered = true;
return true;
}
return false;
}
//-----------------------------------------------
ConsoleFunction(OpenALShutdownDriver, void, 1, 1, "OpenALShutdownDriver()")
{
Audio::OpenALShutdown();
}
//-----------------------------------------------
ConsoleFunction(OpenALRegisterExtensions, void, 1, 1, "OpenALRegisterExtensions()")
{
}
//-----------------------------------------------
ConsoleFunction(alGetString, const char *, 2, 2, "(string item)\n\n"
"This wraps alGetString().")
{
argc;
ALenum e = getEnum(argv[1], (Context|Get));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alGetString: invalid enum name '%s'", argv[1]);
return "";
}
return (const char*)alGetString(e);
}
//--------------------------------------------------------------------------
// Soundfile
//--------------------------------------------------------------------------
ConsoleFunction(alxGetWaveLen, S32, 2, 2, "(string filename)\n\n"
"@param filename File to determine length of.\n"
"@returns Length in milliseconds.")
{
Resource<AudioBuffer> buffer = AudioBuffer::find(argv[1]);
if (bool(buffer)) {
ALuint alBuffer = buffer->getALBuffer();
return alxGetWaveLen(alBuffer);
}
else {
return 0;
}
}
//--------------------------------------------------------------------------
// Source
//--------------------------------------------------------------------------
ConsoleFunction(alxCreateSource, S32, 2, 6,
"(profile) or "
"(profile, x,y,z) or "
"(description, filename) or "
"(description, filename, x,y,z)")
{
AudioDescription *description = NULL;
AudioProfile *profile = dynamic_cast<AudioProfile*>( Sim::findObject( argv[1] ) );
if (profile == NULL)
{
description = dynamic_cast<AudioDescription*>( Sim::findObject( argv[1] ) );
if (description == NULL)
{
Con::printf("Unable to locate audio profile/description '%s'", argv[1]);
return NULL_AUDIOHANDLE;
}
}
if (profile)
{
if (argc == 2)
return alxCreateSource(profile);
MatrixF transform;
transform.set(EulerF(0,0,0), Point3F( dAtof(argv[2]),dAtof(argv[3]),dAtof(argv[4]) ));
return alxCreateSource(profile, &transform);
}
if (description)
{
if (argc == 3)
return alxCreateSource(description, argv[2]);
MatrixF transform;
transform.set(EulerF(0,0,0), Point3F( dAtof(argv[3]),dAtof(argv[4]),dAtof(argv[5]) ));
return alxCreateSource(description, argv[2], &transform);
}
return NULL_AUDIOHANDLE;
}
//-----------------------------------------------
ConsoleFunction(alxSourcef, void, 4, 4, "(handle, ALenum, value)")
{
ALenum e = getEnum(argv[2], (Source|Set|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcef: invalid enum name '%s'", argv[2]);
return;
}
alxSourcef(dAtoi(argv[1]), e, dAtof(argv[3]));
}
//-----------------------------------------------
ConsoleFunction(alxSource3f, void, 3, 6, "(handle, ALenum, x, y, z)\n\n"
"@note You can replace the last three parameters with a string, "
"\"x y z\"")
{
ALenum e = getEnum(argv[2], (Source|Set|Float3));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: invalid enum name '%s'", argv[2]);
return;
}
if((argc != 4 && argc != 6))
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSource3f: wrong number of args");
return;
}
Point3F pos;
if(argc == 4)
dSscanf(argv[3], "%g %g %g", &pos.x, &pos.y, &pos.z);
else
{
pos.x = dAtof(argv[3]);
pos.y = dAtof(argv[4]);
pos.z = dAtof(argv[5]);
}
alxSource3f(dAtoi(argv[1]), e, pos.x, pos.y, pos.z);
}
//-----------------------------------------------
ConsoleFunction(alxSourcei, void, 4, 4, "(handle, ALenum, value)")
{
ALenum e = getEnum(argv[2], (Source|Set|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxSourcei: invalid enum name '%s'", argv[2]);
return;
}
alxSourcei(dAtoi(argv[1]), e, dAtoi(argv[3]));
}
//-----------------------------------------------
ConsoleFunction(alxGetSourcef, F32, 3, 3, "(handle, ALenum)")
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcef: invalid enum name '%s'", argv[2]);
return(0.f);
}
F32 value;
alxGetSourcef(dAtoi(argv[1]), e, &value);
return(value);
}
//-----------------------------------------------
ConsoleFunction(alxGetSource3f, const char *, 3, 3, "(handle, ALenum)" )
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSource3f: invalid enum name '%s'", argv[2]);
return("0 0 0");
}
F32 value1, value2, value3;
alxGetSource3f(dAtoi(argv[1]), e, &value1, &value2, &value3);
char * ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3);
return(ret);
}
//-----------------------------------------------
ConsoleFunction(alxGetSourcei, S32, 3, 3, "(handle, ALenum)")
{
ALenum e = getEnum(argv[2], (Source|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "cAudio_alxGetSourcei: invalid enum name '%s'", argv[2]);
return(0);
}
S32 value;
alxGetSourcei(dAtoi(argv[1]), e, &value);
return(value);
}
//-----------------------------------------------
ConsoleFunction(alxPlay, S32, 2, 5, "alxPlay(handle) or "
"alxPlay(profile) or "
"alxPlay(profile, x,y,z)")
{
if (argc == 2)
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if (handle != 0)
return alxPlay(handle);
}
AudioProfile *profile = dynamic_cast<AudioProfile*>( Sim::findObject( argv[1] ) );
if (profile == NULL)
{
Con::printf("Unable to locate audio profile '%s'", argv[1]);
return NULL_AUDIOHANDLE;
}
Point3F pos(0.f, 0.f, 0.f);
if(argc == 3)
dSscanf(argv[2], "%g %g %g", &pos.x, &pos.y, &pos.z);
else if(argc == 5)
pos.set(dAtof(argv[2]), dAtof(argv[3]), dAtof(argv[4]));
MatrixF transform;
transform.set(EulerF(0,0,0), pos);
return alxPlay(profile, &transform, NULL);
}
//-----------------------------------------------
ConsoleFunction(alxStop, void, 2, 2, "(int handle)")
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if(handle == NULL_AUDIOHANDLE)
return;
alxStop(handle);
}
//-----------------------------------------------
ConsoleFunction(alxStopAll, void, 1, 1, "()")
{
alxStopAll();
}
//-----------------------------------------------
ConsoleFunction(alxIsPlaying, bool, 2, 5, "alxIsPlaying(handle)")
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if(handle == NULL_AUDIOHANDLE)
return false;
return alxIsPlaying(handle);
}
//--------------------------------------------------------------------------
// Listener
//--------------------------------------------------------------------------
ConsoleFunction(alxListenerf, void, 3, 3, "alxListener(ALenum, value)")
{
ALenum e = getEnum(argv[1], (Listener|Set|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alxListenerf: invalid enum name '%s'", argv[1]);
return;
}
alxListenerf(e, dAtof(argv[2]));
}
//-----------------------------------------------
ConsoleFunction(alListener3f, void, 3, 5, "alListener3f(ALenum, \"x y z\") or "
"alListener3f(ALenum, x, y, z)")
{
ALenum e = getEnum(argv[1], (Listener|Set|Float3));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alListener3f: invalid enum name '%s'", argv[1]);
return;
}
if(argc != 3 && argc != 5)
{
Con::errorf(ConsoleLogEntry::General, "alListener3f: wrong number of arguments");
return;
}
Point3F pos;
if(argc == 3)
dSscanf(argv[2], "%g %g %g", &pos.x, &pos.y, &pos.z);
else
{
pos.x = dAtof(argv[2]);
pos.y = dAtof(argv[3]);
pos.z = dAtof(argv[4]);
}
alListener3f(e, pos.x, pos.y, pos.z);
}
//-----------------------------------------------
ConsoleFunction(alxGetListenerf, F32, 2, 2, "alxGetListenerf(Alenum)")
{
ALenum e = getEnum(argv[1], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alxGetListenerf: invalid enum name '%s'", argv[1]);
return(0.f);
}
F32 value;
alxGetListenerf(e, &value);
return(value);
}
//-----------------------------------------------
ConsoleFunction(alGetListener3f, const char *, 2, 2, "alGetListener3f(Alenum)")
{
ALenum e = getEnum(argv[2], (Source|Get|Float));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alGetListener3f: invalid enum name '%s'", argv[1]);
return("0 0 0");
}
F32 value1, value2, value3;
alGetListener3f(e, &value1, &value2, &value3);
char * ret = Con::getReturnBuffer(64);
dSprintf(ret, 64, "%7.3f %7.3 %7.3", value1, value2, value3);
return(ret);
}
//-----------------------------------------------
ConsoleFunction(alGetListeneri, S32, 2, 2, "alGetListeneri(Alenum)")
{
ALenum e = getEnum(argv[1], (Source|Get|Int));
if(e == AL_INVALID)
{
Con::errorf(ConsoleLogEntry::General, "alGetListeneri: invalid enum name '%s'", argv[1]);
return(0);
}
S32 value;
alGetListeneri(e, &value);
return(value);
}
//--------------------------------------------------------------------------
// Channel Volumes
//--------------------------------------------------------------------------
ConsoleFunction(alxGetChannelVolume, F32, 2, 2, "(int channel_id)\n\n"
"@param channel_id ID of channel to fetch volume from.\n"
"@return Volume of channel.")
{
U32 type = dAtoi(argv[1]);
if(type >= Audio::NumAudioTypes)
{
Con::errorf(ConsoleLogEntry::General, "alxGetChannelVolume: invalid channel '%d'", dAtoi(argv[1]));
return(0.f);
}
return(mAudioTypeVolume[type]);
}
//-----------------------------------------------
ConsoleFunction(alxSetChannelVolume, bool, 3, 3, "(int channel_id, float volume)\n\n"
"@param channel_id ID of channel to set volume on.\n"
"@param volume New volume of channel, from 0.0f-1.0f"
)
{
U32 type = dAtoi(argv[1]);
F32 volume = mClampF(dAtof(argv[2]), 0.f, 1.f);
if(type >= Audio::NumAudioTypes)
{
Con::errorf(ConsoleLogEntry::General, "alxSetChannelVolume: channel '%d' out of range [0, %d]", dAtoi(argv[1]), Audio::NumAudioTypes);
return false;
}
mAudioTypeVolume[type] = volume;
alxUpdateTypeGain(type);
return true;
}
//-----------------------------------------------
ConsoleFunction(alxGetStreamPosition, F32, 2, 2, "alxGetStreamPosition(handle)" )
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if(handle == NULL_AUDIOHANDLE)
return -1;
return alxGetStreamPosition( handle );
}
//-----------------------------------------------
ConsoleFunction(alxGetStreamDuration, F32, 2, 2, "alxGetStreamDuration(handle)" )
{
AUDIOHANDLE handle = dAtoi(argv[1]);
if(handle == NULL_AUDIOHANDLE)
return -1;
return alxGetStreamDuration( handle );
}
ConsoleFunctionGroupEnd(Audio);

View File

@ -0,0 +1,64 @@
//--------------------------------------------
// audioStreamSource.h
// header for streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------------
#ifndef _AUDIOSTREAMSOURCE_H_
#define _AUDIOSTREAMSOURCE_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _PLATFORMAUDIO_H_
#include "platform/platformAudio.h"
#endif
#ifndef _PLATFORMAL_H_
#include "platform/platformAL.h"
#endif
#ifndef _AUDIOBUFFER_H_
#include "audio/audioBuffer.h"
#endif
#ifndef _RESMANAGER_H_
#include "core/resManager.h"
#endif
#define NUMBUFFERS 16
class AudioStreamSource
{
public:
//need this because subclasses are deleted through base pointer
virtual ~AudioStreamSource() {}
virtual bool initStream() = 0;
virtual bool updateBuffers() = 0;
virtual void freeStream() = 0;
virtual F32 getElapsedTime() = 0;
virtual F32 getTotalTime() = 0;
//void clear();
AUDIOHANDLE mHandle;
ALuint mSource;
Audio::Description mDescription;
AudioSampleEnvironment *mEnvironment;
Point3F mPosition;
Point3F mDirection;
F32 mPitch;
F32 mScore;
U32 mCullTime;
bool bFinishedPlaying;
bool bIsValid;
#ifdef TORQUE_OS_LINUX
void checkPosition();
#endif
protected:
const char* mFilename;
};
#endif // _AUDIOSTREAMSOURCE_H_

View File

@ -0,0 +1,33 @@
//--------------------------------------
// audioStreamSource.cc
// implementation of streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------
#include "audio/audioStreamSourceFactory.h"
#include "audio/wavStreamSource.h"
#ifndef TORQUE_NO_OGGVORBIS
#include "audio/vorbisStreamSource.h"
#include "audio/oggMixedStreamSource.h"
#endif
AudioStreamSource* AudioStreamSourceFactory::getNewInstance(const char *filename)
{
#ifndef TORQUE_NO_OGGVORBIS
if(!dStricmp(filename, "oggMixedStream"))
return new OggMixedStreamSource(filename);
S32 len = dStrlen(filename);
// Check filename extension and guess filetype from that
if(len > 3 && !dStricmp(filename + len - 4, ".wav"))
return new WavStreamSource(filename);
if(len > 3 && !dStricmp(filename + len - 4, ".ogg"))
return new VorbisStreamSource(filename);
#endif
return NULL;
}

View File

@ -0,0 +1,30 @@
//--------------------------------------------
// audioStreamSource.h
// header for streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------------
#ifndef _AUDIOSTREAMSOURCEFACTORY_H_
#define _AUDIOSTREAMSOURCEFACTORY_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _PLATFORMAUDIO_H_
#include "platform/platformAudio.h"
#endif
#ifndef _PLATFORMAL_H_
#include "platform/platformAL.h"
#endif
#ifndef _AUDIOSTREAMSOURCE_H_
#include "audio/audioStreamSource.h"
#endif
class AudioStreamSourceFactory
{
public:
static AudioStreamSource* getNewInstance(const char* filename);
};
#endif // _AUDIOSTREAMSOURCEFACTORY_H_

View File

@ -0,0 +1,133 @@
//--------------------------------------
//
// This class is basically a buffer that is filled from
// the ogg stream the theoraplayer has open
//
//--------------------------------------
#include "audio/oggMixedStreamSource.h"
OggMixedStreamSource::OggMixedStreamSource(const char *filename)
{
bIsValid = false;
bBuffersAllocated = false;
for(int i = 0; i < BUFFERCNT; i++)
{
mBufferList[i] = 0;
m_fBufferInUse[i] = false;
}
mHandle = NULL_AUDIOHANDLE;
mSource = NULL;
mFilename = filename;
mPosition = Point3F(0.f,0.f,0.f);
dMemset(&mDescription, 0, sizeof(Audio::Description));
mEnvironment = 0;
mPosition.set(0.f,0.f,0.f);
mDirection.set(0.f,1.f,0.f);
mPitch = 1.f;
mScore = 0.f;
mCullTime = 0;
bFinishedPlaying = false;
bIsValid = false;
bBuffersAllocated = false;
}
OggMixedStreamSource::~OggMixedStreamSource()
{
if(bIsValid)
freeStream();
}
bool OggMixedStreamSource::initStream()
{
alSourceStop(mSource);
alSourcei(mSource, AL_BUFFER, 0);
// Clear Error Code
alGetError();
alGenBuffers(BUFFERCNT, mBufferList);
if (alGetError() != AL_NO_ERROR)
return false;
bBuffersAllocated = true;
alSourcei(mSource, AL_LOOPING, AL_FALSE);
bIsValid = true;
return true;
}
bool OggMixedStreamSource::updateBuffers()
{
// buffers are updated from theora player
return true;
}
void OggMixedStreamSource::freeStream()
{
// free the al buffers
if(bBuffersAllocated)
{
alSourceStop(mSource);
alSourcei(mSource, AL_BUFFER, 0);
alDeleteBuffers(BUFFERCNT, mBufferList);
alGetError();
for(int i = 0; i < BUFFERCNT; i++)
{
mBufferList[i] = 0;
m_fBufferInUse[i] = false;
}
bBuffersAllocated = false;
}
}
ALuint OggMixedStreamSource::GetAvailableBuffer()
{
if(!bBuffersAllocated)
return 0;
// test for unused buffers
for(int i = 0; i < BUFFERCNT; i++)
{
if(!m_fBufferInUse[i])
{
m_fBufferInUse[i] = true;
return mBufferList[i];
}
}
alGetError();
// test for processed buffers
ALint processed;
alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);
if(!processed)
return 0; // no available buffers
ALuint BufferID;
alSourceUnqueueBuffers(mSource, 1, &BufferID);
if (alGetError() != AL_NO_ERROR)
return 0; // something went wrong..
return BufferID;
}
bool OggMixedStreamSource::QueueBuffer(ALuint BufferID)
{
alSourceQueueBuffers(mSource, 1, &BufferID);
if (alGetError() != AL_NO_ERROR)
return false;
return true;
}

View File

@ -0,0 +1,50 @@
//--------------------------------------------
// oggMixedStreamSource.h
// header for audio stream dummy class, basicly
// an audio buffer filled remotely
//
//--------------------------------------------
#ifndef _OGGMIXEDSTREAMSOURCE_H_
#define _OGGMIXEDSTREAMSOURCE_H_
#ifndef _AUDIOSTREAMSOURCE_H_
#include "audio/audioStreamSource.h"
#endif
#define BUFFERCNT 128
class OggMixedStreamSource: public AudioStreamSource
{
public:
OggMixedStreamSource(const char *filename);
virtual ~OggMixedStreamSource();
virtual bool initStream();
virtual bool updateBuffers();
virtual void freeStream();
ALuint GetAvailableBuffer();
bool QueueBuffer(ALuint BufferID);
void PlayStream();
virtual F32 getElapsedTime()
{
return 0.0;
}
virtual F32 getTotalTime()
{
return 1.0f;
}
private:
ALuint mBufferList[BUFFERCNT];
bool m_fBufferInUse[BUFFERCNT];
bool bBuffersAllocated;
void clear();
};
#endif // _OGGMIXEDSTREAMSOURCE_H_

1541
engine/audio/vorbisStream.cc Executable file

File diff suppressed because it is too large Load Diff

157
engine/audio/vorbisStream.h Executable file
View File

@ -0,0 +1,157 @@
/********************************************************************
* *
* THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. *
* USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
* GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
* IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
* *
* THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 *
* by the XIPHOPHORUS Company http://www.xiph.org/ *
* *
********************************************************************
function: stdio-based convenience library for opening/seeking/decoding
********************************************************************/
// modified vorbisfile to use Torque Streams
// Kurtis Seebaldt
#ifndef _OV_FILE_H_
#define _OV_FILE_H_
#include <stdio.h>
#include "vorbis/codec.h"
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _RESMANAGER_H_
#include "core/resManager.h"
#endif
/* The function prototypes for the callbacks are basically the same as for
* the stdio functions fread, fseek, fclose, ftell.
* The one difference is that the FILE * arguments have been replaced with
* a void * - this is to be used as a pointer to whatever internal data these
* functions might need. In the stdio case, it's just a FILE * cast to a void *
*
* If you use other functions, check the docs for these functions and return
* the right values. For seek_func(), you *MUST* return -1 if the stream is
* unseekable
*/
//typedef struct {
// size_t (*read_func) (Stream *ptr, size_t size, size_t nmemb, void *datasource);
// int (*seek_func) (Stream *datasource, ogg_int64_t offset, int whence);
// int (*close_func) (void *datasource);
// long (*tell_func) (void *datasource);
//} ov_callbacks;
#define NOTOPEN 0
#define PARTOPEN 1
#define OPENED 2
#define STREAMSET 3
#define INITSET 4
class OggVorbis_File {
public:
Stream *datasource; /* Pointer to a FILE *, etc. */
int seekable;
ogg_int64_t offset;
ogg_int64_t end;
ogg_sync_state oy;
/* If the FILE handle isn't seekable (eg, a pipe), only the current
stream appears */
int links;
ogg_int64_t *offsets;
ogg_int64_t *dataoffsets;
long *serialnos;
ogg_int64_t *pcmlengths;
vorbis_info *vi;
vorbis_comment *vc;
/* Decoding working state local storage */
ogg_int64_t pcm_offset;
int ready_state;
long current_serialno;
int current_link;
double bittrack;
double samptrack;
ogg_stream_state os; /* take physical pages, weld into a logical
stream of packets */
vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
vorbis_block vb; /* local working space for packet->PCM decode */
// ov_callbacks callbacks;
};
class OggVorbisFile {
public:
OggVorbisFile();
~OggVorbisFile();
int ov_clear();
int ov_open(Stream *stream,char *initial,long ibytes);
int ov_open_callbacks(Stream *datasource,
char *initial, long ibytes);
int ov_test(Stream *stream,char *initial,long ibytes);
int ov_test_callbacks(Stream *datasource,
char *initial, long ibytes);
int ov_test_open();
long ov_bitrate(int i);
long ov_bitrate_instant();
long ov_streams();
long ov_seekable();
long ov_serialnumber(int i);
ogg_int64_t ov_raw_total(int i);
ogg_int64_t ov_pcm_total(int i);
double ov_time_total(int i);
int ov_raw_seek(long pos);
int ov_pcm_seek(ogg_int64_t pos);
int ov_pcm_seek_page(ogg_int64_t pos);
int ov_time_seek(double pos);
int ov_time_seek_page(double pos);
ogg_int64_t ov_raw_tell();
ogg_int64_t ov_pcm_tell();
double ov_time_tell();
vorbis_info *ov_info(int link);
vorbis_comment *ov_comment(int link);
long ov_read_float(float ***pcm_channels,
int *bitstream);
long ov_read(char *buffer,int length,
int bigendianp,int *bitstream);
private:
OggVorbis_File *vf;
long _get_data();
void _seek_helper(long offset);
long _get_next_page(ogg_page *og,int boundary);
long _get_prev_page(ogg_page *og);
int _bisect_forward_serialno(long begin,long searched,long end,long currentno,long m);
int _fetch_headers(vorbis_info *vi,vorbis_comment *vc,long *serialno,ogg_page *og_ptr);
void _prefetch_all_headers(long dataoffset);
void _make_decode_ready();
int _open_seekable2();
void _decode_clear();
int _process_packet(int readp);
int _fseek64_wrap(Stream *stream,ogg_int64_t off,int whence);
int _ov_open1(Stream *stream,char *initial,long ibytes);
int _ov_open2();
};
#endif

View File

@ -0,0 +1,442 @@
//--------------------------------------
// vorbisStreamSource.cc
// implementation of streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------
#include "audio/vorbisStreamSource.h"
#include "vorbis/codec.h"
#define BUFFERSIZE 32768
#define CHUNKSIZE 4096
#if defined(TORQUE_BIG_ENDIAN)
#define ENDIAN 1
#else
#define ENDIAN 0
#endif
extern const char * MusicPlayerStreamingHook(const AUDIOHANDLE & handle);
VorbisStreamSource::VorbisStreamSource(const char *filename)
{
stream = NULL;
bIsValid = false;
bBuffersAllocated = false;
bVorbisFileInitialized = false;
mBufferList[0] = 0;
clear();
mFilename = filename;
mPosition = Point3F(0.f,0.f,0.f);
}
VorbisStreamSource::~VorbisStreamSource()
{
if(bReady && bIsValid)
freeStream();
}
void VorbisStreamSource::clear()
{
if(stream)
freeStream();
mHandle = NULL_AUDIOHANDLE;
mSource = NULL;
if(mBufferList[0] != 0)
alDeleteBuffers(NUMBUFFERS, mBufferList);
for(int i = 0; i < NUMBUFFERS; i++)
mBufferList[i] = 0;
dMemset(&mDescription, 0, sizeof(Audio::Description));
mEnvironment = 0;
mPosition.set(0.f,0.f,0.f);
mDirection.set(0.f,1.f,0.f);
mPitch = 1.f;
mScore = 0.f;
mCullTime = 0;
bReady = false;
bFinishedPlaying = false;
bIsValid = false;
bBuffersAllocated = false;
bVorbisFileInitialized = false;
}
bool VorbisStreamSource::initStream()
{
vorbis_info *vi;
ALint error;
bFinished = false;
// JMQ: changed buffer to static and doubled size. workaround for
// https://206.163.64.242/mantis/view_bug_page.php?f_id=0000242
static char data[BUFFERSIZE*2];
alSourceStop(mSource);
alSourcei(mSource, AL_BUFFER, 0);
stream = ResourceManager->openStream(mFilename);
if(stream != NULL)
{
if(vf.ov_open(stream, NULL, 0) < 0)
{
return false;
}
bVorbisFileInitialized = true;
//Read Vorbis File Info
vi = vf.ov_info(-1);
freq = vi->rate;
long samples = (long)vf.ov_pcm_total(-1);
if(vi->channels == 1)
{
format = AL_FORMAT_MONO16;
DataSize = 2 * samples;
}
else
{
format = AL_FORMAT_STEREO16;
DataSize = 4 * samples;
}
DataLeft = DataSize;
// Clear Error Code
alGetError();
alGenBuffers(NUMBUFFERS, mBufferList);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
bBuffersAllocated = true;
int numBuffers = 0;
for(int loop = 0; loop < NUMBUFFERS; loop++)
{
ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft;
if (DataToRead == DataLeft)
bFinished = AL_TRUE;
long ret = oggRead(data, BUFFERSIZE, ENDIAN, &current_section);
if(ret <= 0)
{
bFinished = AL_TRUE;
break;
}
DataLeft -= ret;
alBufferData(mBufferList[loop], format, data, ret, freq);
++numBuffers;
if ((error = alGetError()) != AL_NO_ERROR)
return false;
if(bFinished)
break;
}
// Queue the buffers on the source
alSourceQueueBuffers(mSource, NUMBUFFERS, mBufferList);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
alSourcei(mSource, AL_LOOPING, AL_FALSE);
bReady = true;
}
else
{
return false;
}
bIsValid = true;
return true;
}
bool VorbisStreamSource::updateBuffers()
{
ALint processed;
ALuint BufferID;
ALint error;
// JMQ: changed buffer to static and doubled size. workaround for
// https://206.163.64.242/mantis/view_bug_page.php?f_id=0000242
static char data[BUFFERSIZE*2];
// don't do anything if stream not loaded properly
if(!bIsValid)
return false;
if(bFinished && mDescription.mIsLooping)
resetStream();
// reset AL error code
alGetError();
#if 1 //def TORQUE_OS_LINUX
// JMQ: this doesn't really help on linux. it may make things worse.
// if it doesn't help on mac/win either, could disable it.
ALint state;
alGetSourcei(mSource, AL_SOURCE_STATE, &state);
if (state == AL_STOPPED)
{
// um, yeah. you should be playing
// restart
alSourcePlay(mSource);
//#ifdef TORQUE_DEBUG
//Con::errorf(">><<>><< THE MUSIC STOPPED >><<>><<");
//#endif
return true;
}
#endif
#ifdef TORQUE_OS_LINUX
checkPosition();
#endif
// Get status
alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);
// If some buffers have been played, unqueue them and load new audio into them, then add them to the queue
if (processed > 0)
{
while (processed)
{
alSourceUnqueueBuffers(mSource, 1, &BufferID);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
if (!bFinished)
{
ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft;
if (DataToRead == DataLeft)
bFinished = AL_TRUE;
long ret = oggRead(data, BUFFERSIZE, ENDIAN, &current_section);
if(ret > 0)
{
DataLeft -= ret;
alBufferData(BufferID, format, data, ret, freq);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
// Queue buffer
alSourceQueueBuffers(mSource, 1, &BufferID);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
}
processed--;
if(bFinished && mDescription.mIsLooping)
{
resetStream();
}
}
else
{
buffersinqueue--;
processed--;
if (buffersinqueue == 0)
{
bFinishedPlaying = AL_TRUE;
return AL_FALSE;
}
}
}
}
return true;
}
void VorbisStreamSource::freeStream()
{
bReady = false;
if(stream != NULL)
ResourceManager->closeStream(stream);
stream = NULL;
if(bBuffersAllocated)
{
if(mBufferList[0] != 0)
alDeleteBuffers(NUMBUFFERS, mBufferList);
for(int i = 0; i < NUMBUFFERS; i++)
mBufferList[i] = 0;
bBuffersAllocated = false;
}
if(bVorbisFileInitialized)
{
vf.ov_clear();
bVorbisFileInitialized = false;
}
}
void VorbisStreamSource::resetStream()
{
// MusicPlayerStreamingHook allow you to create a handler
// where you can rotate through streaming files
// Comment in and provide hook if desired.
//
//const char * newFile = MusicPlayerStreamingHook(mHandle);
//if (newFile)
//{
// setNewFile(newFile);
// return;
//}
//else
{
vf.ov_pcm_seek(0);
DataLeft = DataSize;
bFinished = AL_FALSE;
}
}
void VorbisStreamSource::setNewFile(const char * file)
{
//---------------------
// close down old file...
//---------------------
if(stream != NULL)
{
ResourceManager->closeStream(stream);
stream = NULL;
}
if(bVorbisFileInitialized)
{
vf.ov_clear();
bVorbisFileInitialized = false;
}
//---------------------
// open up new file...
//---------------------
mFilename = file;
stream = ResourceManager->openStream(mFilename);
if(stream != NULL)
{
if(vf.ov_open(stream, NULL, 0) < 0)
{
bFinished = AL_TRUE;
bVorbisFileInitialized = false;
bIsValid = false;
return;
}
//Read Vorbis File Info
vorbis_info * vi = vf.ov_info(-1);
freq = vi->rate;
long samples = (long)vf.ov_pcm_total(-1);
if(vi->channels == 1)
{
format = AL_FORMAT_MONO16;
DataSize = 2 * samples;
}
else
{
format = AL_FORMAT_STEREO16;
DataSize = 4 * samples;
}
DataLeft = DataSize;
// Clear Error Code
alGetError();
bFinished = AL_FALSE;
bVorbisFileInitialized = true;
bIsValid = true;
}
}
// ov_read() only returns a maximum of one page worth of data
// this helper function will repeat the read until buffer is full
long VorbisStreamSource::oggRead(char *buffer,int length,
int bigendianp,int *bitstream)
{
long bytesRead = 0;
long totalBytes = 0;
long offset = 0;
long bytesToRead = 0;
while((offset) < length)
{
if((length - offset) < CHUNKSIZE)
bytesToRead = length - offset;
else
bytesToRead = CHUNKSIZE;
bytesRead = vf.ov_read(buffer, bytesToRead, bigendianp, bitstream);
//#ifdef TORQUE_OS_LINUX
#if 1 // Might fix mac audio issue and possibly others...based on references, this looks like correct action
// linux ver will hang on exit after a stream loop if we don't
// do this
if (bytesRead == OV_HOLE)
// retry, see:
// http://www.xiph.org/archives/vorbis-dev/200102/0163.html
// http://www.mit.edu/afs/sipb/user/xiphmont/ogg-sandbox/vorbis/doc/vorbis-errors.txt
continue;
#endif
if(bytesRead <= 0)
break;
offset += bytesRead;
buffer += bytesRead;
}
return offset;
}
#ifdef TORQUE_OS_LINUX
// JMQ: OpenAL sometimes replaces the stream source's position with its own
// nifty value, causing the music to pan around the listener. how nice.
// this function checks to see if the current source position in openal
// is near the initial position, and slams it to the correct value if it
// is wrong.
// This is a bad place to put this, but I don't feel like adding a new
// .cc file. And since this is an incredibly lame hack to
// workaround a stupid OpenAL bug, I see no point in overengineering it.
void AudioStreamSource::checkPosition()
{
ALfloat pos[3];
alGetSourcefv(mSource, AL_POSITION, pos);
// just compute the difference between the openal pos and the
// correct pos. it better be pretty friggin small.
Point3F openalPos(pos[0], pos[1], pos[2]);
F32 slopDist = 0.0001f;
F32 dist = mFabs((openalPos - mPosition).len());
if (dist > slopDist)
// slam!
alSource3f(mSource, AL_POSITION, mPosition.x, mPosition.y, mPosition.z);
}
#endif
F32 VorbisStreamSource::getElapsedTime()
{
return vf.ov_time_tell();
}
F32 VorbisStreamSource::getTotalTime()
{
return vf.ov_time_total(-1);
}

View File

@ -0,0 +1,58 @@
//--------------------------------------------
// vorbisStreamSource.h
// header for streaming audio source for Ogg Vorbis
//
// Kurtis Seebaldt
//--------------------------------------------
#ifndef _VORBISSTREAMSOURCE_H_
#define _VORBISSTREAMSOURCE_H_
#ifndef _AUDIOSTREAMSOURCE_H_
#include "audio/audioStreamSource.h"
#endif
#include "audio/vorbisStream.h"
class VorbisStreamSource: public AudioStreamSource
{
public:
VorbisStreamSource(const char *filename);
virtual ~VorbisStreamSource();
virtual bool initStream();
virtual bool updateBuffers();
virtual void freeStream();
virtual F32 getElapsedTime();
virtual F32 getTotalTime();
private:
ALuint mBufferList[NUMBUFFERS];
S32 mNumBuffers;
S32 mBufferSize;
Stream *stream;
bool bReady;
bool bFinished;
ALenum format;
ALsizei size;
ALsizei freq;
ALuint DataSize;
ALuint DataLeft;
ALuint buffersinqueue;
bool bBuffersAllocated;
bool bVorbisFileInitialized;
int current_section;
OggVorbisFile vf;
void clear();
long oggRead(char *buffer,int length, int bigendianp,int *bitstream);
void resetStream();
void setNewFile(const char * file);
};
#endif // _VORBISSTREAMSOURCE_H_

334
engine/audio/wavStreamSource.cc Executable file
View File

@ -0,0 +1,334 @@
//--------------------------------------
// audioStreamSource.cc
// implementation of streaming audio source
//
// Kurtis Seebaldt
//--------------------------------------
#include "audio/wavStreamSource.h"
#define BUFFERSIZE 32768
typedef struct
{
ALubyte riff[4]; // 'RIFF'
ALsizei riffSize;
ALubyte wave[4]; // 'WAVE'
ALubyte fmt[4]; // 'fmt '
ALuint fmtSize;
ALushort Format;
ALushort Channels;
ALuint SamplesPerSec;
ALuint BytesPerSec;
ALushort BlockAlign;
ALushort BitsPerSample;
ALubyte data[4]; // 'data'
ALuint dataSize;
} WAVE_Struct;
/// WAV File-header
struct WAVFileHdr
{
ALubyte id[4];
ALsizei size;
ALubyte type[4];
};
//// WAV Fmt-header
struct WAVFmtHdr
{
ALushort format;
ALushort channels;
ALuint samplesPerSec;
ALuint bytesPerSec;
ALushort blockAlign;
ALushort bitsPerSample;
};
/// WAV FmtEx-header
struct WAVFmtExHdr
{
ALushort size;
ALushort samplesPerBlock;
};
/// WAV Smpl-header
struct WAVSmplHdr
{
ALuint manufacturer;
ALuint product;
ALuint samplePeriod;
ALuint note;
ALuint fineTune;
ALuint SMPTEFormat;
ALuint SMPTEOffest;
ALuint loops;
ALuint samplerData;
struct
{
ALuint identifier;
ALuint type;
ALuint start;
ALuint end;
ALuint fraction;
ALuint count;
} loop[1];
};
/// WAV Chunk-header
struct WAVChunkHdr
{
ALubyte id[4];
ALuint size;
};
WavStreamSource::WavStreamSource(const char *filename) {
stream = NULL;
bIsValid = false;
bBuffersAllocated = false;
mBufferList[0] = 0;
clear();
mFilename = filename;
mPosition = Point3F(0.f,0.f,0.f);
}
WavStreamSource::~WavStreamSource() {
if(bReady && bIsValid)
freeStream();
}
void WavStreamSource::clear()
{
if(stream)
freeStream();
mHandle = NULL_AUDIOHANDLE;
mSource = NULL;
if(mBufferList[0] != 0)
alDeleteBuffers(NUMBUFFERS, mBufferList);
for(int i = 0; i < NUMBUFFERS; i++)
mBufferList[i] = 0;
dMemset(&mDescription, 0, sizeof(Audio::Description));
mEnvironment = 0;
mPosition.set(0.f,0.f,0.f);
mDirection.set(0.f,1.f,0.f);
mPitch = 1.f;
mScore = 0.f;
mCullTime = 0;
bReady = false;
bFinishedPlaying = false;
bIsValid = false;
bBuffersAllocated = false;
}
bool WavStreamSource::initStream() {
WAVChunkHdr chunkHdr;
WAVFmtExHdr fmtExHdr;
WAVFileHdr fileHdr;
WAVSmplHdr smplHdr;
WAVFmtHdr fmtHdr;
WAVE_Struct wave;
ALint error;
bFinished = false;
char data[BUFFERSIZE];
alSourceStop(mSource);
alSourcei(mSource, AL_BUFFER, 0);
stream = ResourceManager->openStream(mFilename);
if(stream != NULL) {
stream->read(4, &fileHdr.id[0]);
stream->read(&fileHdr.size);
stream->read(4, &fileHdr.type[0]);
stream->read(4, &chunkHdr.id[0]);
stream->read(&chunkHdr.size);
// WAV Format header
stream->read(&fmtHdr.format);
stream->read(&fmtHdr.channels);
stream->read(&fmtHdr.samplesPerSec);
stream->read(&fmtHdr.bytesPerSec);
stream->read(&fmtHdr.blockAlign);
stream->read(&fmtHdr.bitsPerSample);
format=(fmtHdr.channels==1?
(fmtHdr.bitsPerSample==8?AL_FORMAT_MONO8:AL_FORMAT_MONO16):
(fmtHdr.bitsPerSample==8?AL_FORMAT_STEREO8:AL_FORMAT_STEREO16));
freq=fmtHdr.samplesPerSec;
stream->read(4, &chunkHdr.id[0]);
stream->read(&chunkHdr.size);
DataSize = chunkHdr.size;
DataLeft = DataSize;
dataStart = stream->getPosition();
// Clear Error Code
alGetError();
alGenBuffers(NUMBUFFERS, mBufferList);
if ((error = alGetError()) != AL_NO_ERROR) {
return false;
}
bBuffersAllocated = true;
int numBuffers = 0;
for(int loop = 0; loop < NUMBUFFERS; loop++)
{
ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft;
if (DataToRead == DataLeft)
bFinished = AL_TRUE;
stream->read(DataToRead, data);
DataLeft -= DataToRead;
alBufferData(mBufferList[loop], format, data, DataToRead, freq);
if ((error = alGetError()) != AL_NO_ERROR) {
return false;
}
++numBuffers;
if(bFinished)
break;
}
// Queue the buffers on the source
alSourceQueueBuffers(mSource, numBuffers, mBufferList);
if ((error = alGetError()) != AL_NO_ERROR) {
return false;
}
buffersinqueue = numBuffers;
alSourcei(mSource, AL_LOOPING, AL_FALSE);
bReady = true;
}
else {
return false;
}
bIsValid = true;
return true;
}
bool WavStreamSource::updateBuffers() {
ALint processed;
ALuint BufferID;
ALint error;
char data[BUFFERSIZE];
// don't do anything if buffer isn't initialized
if(!bIsValid)
return false;
if(bFinished && mDescription.mIsLooping) {
resetStream();
}
// reset AL error code
alGetError();
// Get status
alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &processed);
// If some buffers have been played, unqueue them and load new audio into them, then add them to the queue
if (processed > 0)
{
while (processed)
{
alSourceUnqueueBuffers(mSource, 1, &BufferID);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
if (!bFinished)
{
ALuint DataToRead = (DataLeft > BUFFERSIZE) ? BUFFERSIZE : DataLeft;
if (DataToRead == DataLeft) {
bFinished = AL_TRUE;
}
stream->read(DataToRead, data);
DataLeft -= DataToRead;
alBufferData(BufferID, format, data, DataToRead, freq);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
// Queue buffer
alSourceQueueBuffers(mSource, 1, &BufferID);
if ((error = alGetError()) != AL_NO_ERROR)
return false;
processed--;
if(bFinished && mDescription.mIsLooping) {
resetStream();
}
}
else
{
buffersinqueue--;
processed--;
if (buffersinqueue == 0)
{
bFinishedPlaying = AL_TRUE;
return AL_FALSE;
}
}
}
}
return AL_TRUE;
}
void WavStreamSource::freeStream() {
bReady = false;
if(stream != NULL)
ResourceManager->closeStream(stream);
stream = NULL;
if(bBuffersAllocated) {
if(mBufferList[0] != 0)
alDeleteBuffers(NUMBUFFERS, mBufferList);
for(int i = 0; i < NUMBUFFERS; i++)
mBufferList[i] = 0;
bBuffersAllocated = false;
}
}
void WavStreamSource::resetStream() {
stream->setPosition(dataStart);
DataLeft = DataSize;
bFinished = AL_FALSE;
}
#include "console/console.h"
F32 WavStreamSource::getElapsedTime()
{
Con::warnf( "GetElapsedTime not implemented in WaveStreams yet" );
return -1.f;
}
F32 WavStreamSource::getTotalTime()
{
Con::warnf( "GetTotalTime not implemented in WaveStreams yet" );
return -1.f;
}

49
engine/audio/wavStreamSource.h Executable file
View File

@ -0,0 +1,49 @@
//--------------------------------------------
// wavStreamSource.h
// header for streaming audio source for WAV
//--------------------------------------------
#ifndef _WAVSTREAMSOURCE_H_
#define _WAVSTREAMSOURCE_H_
#ifndef _AUDIOSTREAMSOURCE_H_
#include "audio/audioStreamSource.h"
#endif
class WavStreamSource: public AudioStreamSource
{
public:
WavStreamSource(const char *filename);
virtual ~WavStreamSource();
virtual bool initStream();
virtual bool updateBuffers();
virtual void freeStream();
virtual F32 getElapsedTime();
virtual F32 getTotalTime();
private:
ALuint mBufferList[NUMBUFFERS];
S32 mNumBuffers;
S32 mBufferSize;
Stream *stream;
bool bReady;
bool bFinished;
ALenum format;
ALsizei size;
ALsizei freq;
ALuint DataSize;
ALuint DataLeft;
ALuint dataStart;
ALuint buffersinqueue;
bool bBuffersAllocated;
void clear();
void resetStream();
};
#endif // _AUDIOSTREAMSOURCE_H_

View File

@ -0,0 +1,97 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "collision/abstractPolyList.h"
//----------------------------------------------------------------------------
AbstractPolyList::~AbstractPolyList()
{
mInterestNormalRegistered = false;
}
static U32 PolyFace[6][4] = {
{ 3, 2, 1, 0 },
{ 7, 4, 5, 6 },
{ 0, 5, 4, 3 },
{ 6, 5, 0, 1 },
{ 7, 6, 1, 2 },
{ 4, 7, 2, 3 },
};
void AbstractPolyList::addBox(const Box3F &box)
{
Point3F pos = box.min;
F32 dx = box.max.x - box.min.x;
F32 dy = box.max.y - box.min.y;
F32 dz = box.max.z - box.min.z;
U32 base = addPoint(pos);
pos.y += dy; addPoint(pos);
pos.x += dx; addPoint(pos);
pos.y -= dy; addPoint(pos);
pos.z += dz; addPoint(pos);
pos.x -= dx; addPoint(pos);
pos.y += dy; addPoint(pos);
pos.x += dx; addPoint(pos);
for (S32 i = 0; i < 6; i++) {
begin(0,i);
S32 v1 = base + PolyFace[i][0];
S32 v2 = base + PolyFace[i][1];
S32 v3 = base + PolyFace[i][2];
S32 v4 = base + PolyFace[i][3];
vertex(v1);
vertex(v2);
vertex(v3);
vertex(v4);
plane(v1,v2,v3);
end();
}
}
bool AbstractPolyList::getMapping(MatrixF *, Box3F *)
{
// return list transform and bounds in list space...optional
return false;
}
bool AbstractPolyList::isInterestedInPlane(const PlaneF& plane)
{
if (mInterestNormalRegistered == false) {
return true;
}
else {
PlaneF xformed;
mPlaneTransformer.transform(plane, xformed);
if (mDot(xformed, mInterestNormal) >= 0.0f)
return false;
else
return true;
}
}
bool AbstractPolyList::isInterestedInPlane(const U32 index)
{
if (mInterestNormalRegistered == false) {
return true;
}
else {
const PlaneF& rPlane = getIndexedPlane(index);
if (mDot(rPlane, mInterestNormal) >= 0.0f)
return false;
else
return true;
}
}
void AbstractPolyList::setInterestNormal(const Point3F& normal)
{
mInterestNormalRegistered = true;
mInterestNormal = normal;
}

View File

@ -0,0 +1,241 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _ABSTRACTPOLYLIST_H_
#define _ABSTRACTPOLYLIST_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _MPLANETRANSFORMER_H_
#include "math/mPlaneTransformer.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
class SceneObject;
//----------------------------------------------------------------------------
/// A polygon filtering interface.
///
/// The various AbstractPolyList subclasses are used in Torque as an interface to
/// handle spatial queries. SceneObject::buildPolyList() takes an implementor of
/// AbstractPolyList (such as ConcretePolyList, ClippedPolyList, etc.) and a
/// bounding volume. The function runs geometry data from all the objects in the
/// bounding volume through the passed PolyList.
///
/// This interface only provides a method to get data INTO your implementation. Different
/// subclasses provide different interfaces to get data back out, depending on their
/// specific quirks.
///
/// The physics engine now uses convex hulls for collision detection.
///
/// @see Convex
class AbstractPolyList
{
protected:
// User set state
SceneObject* mCurrObject;
MatrixF mBaseMatrix; // Base transform
MatrixF mTransformMatrix; // Current object transform
MatrixF mMatrix; // Base * current transform
Point3F mScale;
PlaneTransformer mPlaneTransformer;
bool mInterestNormalRegistered;
Point3F mInterestNormal;
public:
AbstractPolyList();
virtual ~AbstractPolyList();
/// @name Common Interface
/// @{
void setBaseTransform(const MatrixF& mat);
/// Sets the transform applying to the current stream of
/// vertices.
///
/// @param mat Transformation of the object. (in)
/// @param scale Scaling of the object. (in)
void setTransform(const MatrixF* mat, const Point3F& scale);
/// Gets the transform applying to the current stream of
/// vertices.
///
/// @param mat Transformation of the object. (out)
/// @param scale Scaling of the object. (out)
void getTransform(MatrixF* mat, Point3F * scale);
/// This is called by the object which is currently feeding us
/// vertices, to tell us who it is.
void setObject(SceneObject*);
/// Add a box via the query interface (below). This wraps some calls
/// to addPoint and addPlane.
void addBox(const Box3F &box);
void doConstruct();
/// @}
/// @name Query Interface
///
/// It is through this interface that geometry data is fed to the
/// PolyList. The order of calls are:
/// - begin()
/// - One or more calls to vertex() and one call to plane(), in any order.
/// - end()
///
/// @code
/// // Example code that adds data to a PolyList.
/// // See AbstractPolyList::addBox() for the function this was taken from.
///
/// // First, we add points... (note that we use base to track the start of adding.)
/// U32 base = addPoint(pos);
/// pos.y += dy; addPoint(pos);
/// pos.x += dx; addPoint(pos);
/// pos.y -= dy; addPoint(pos);
/// pos.z += dz; addPoint(pos);
/// pos.x -= dx; addPoint(pos);
/// pos.y += dy; addPoint(pos);
/// pos.x += dx; addPoint(pos);
///
/// // Now we add our surfaces. (there are six, as we are adding a cube here)
/// for (S32 i = 0; i < 6; i++) {
/// // Begin a surface
/// begin(0,i);
///
/// // Calculate the vertex ids; we have a lookup table to tell us offset from base.
/// // In your own code, you might use a different method.
/// S32 v1 = base + PolyFace[i][0];
/// S32 v2 = base + PolyFace[i][1];
/// S32 v3 = base + PolyFace[i][2];
/// S32 v4 = base + PolyFace[i][3];
///
/// // Reference the four vertices that make up this surface.
/// vertex(v1);
/// vertex(v2);
/// vertex(v3);
/// vertex(v4);
///
/// // Indicate the plane of this surface.
/// plane(v1,v2,v3);
///
/// // End the surface.
/// end();
/// }
/// @endcode
/// @{
/// Are we empty of data?
virtual bool isEmpty() const = 0;
/// Adds a point to the poly list, and returns
/// an ID number for that point.
virtual U32 addPoint(const Point3F& p) = 0;
/// Adds a plane to the poly list, and returns
/// an ID number for that point.
virtual U32 addPlane(const PlaneF& plane) = 0;
/// Start a surface.
///
/// @param material A material ID for this surface.
/// @param surfaceKey A key value to associate with this surface.
virtual void begin(U32 material,U32 surfaceKey) = 0;
/// Indicate the plane of the surface.
virtual void plane(U32 v1,U32 v2,U32 v3) = 0;
/// Indicate the plane of the surface.
virtual void plane(const PlaneF& p) = 0;
/// Indicate the plane of the surface.
virtual void plane(const U32 index) = 0;
/// Reference a vertex which is in this surface.
virtual void vertex(U32 vi) = 0;
/// Mark the end of a surface.
virtual void end() = 0;
/// Return list transform and bounds in list space.
///
/// @returns False if no data is available.
virtual bool getMapping(MatrixF *, Box3F *);
/// @}
/// @name Interest
///
/// This is a mechanism to let you specify interest in a specific normal.
/// If you set a normal you're interested in, then any planes facing "away"
/// from that normal are culled from the results.
///
/// This is handy if you're using this to do a physics check, as you're not
/// interested in polygons facing away from you (since you don't collide with
/// the backsides/insides of things).
/// @{
void setInterestNormal(const Point3F& normal);
void clearInterestNormal() { mInterestNormalRegistered = false; }
virtual bool isInterestedInPlane(const PlaneF& plane);
virtual bool isInterestedInPlane(const U32 index);
/// @}
protected:
/// A helper function to convert a plane index to a PlaneF structure.
virtual const PlaneF& getIndexedPlane(const U32 index) = 0;
};
inline AbstractPolyList::AbstractPolyList()
{
doConstruct();
}
inline void AbstractPolyList::doConstruct()
{
mCurrObject = NULL;
mBaseMatrix.identity();
mMatrix.identity();
mScale.set(1, 1, 1);
mPlaneTransformer.setIdentity();
mInterestNormalRegistered = false;
}
inline void AbstractPolyList::setBaseTransform(const MatrixF& mat)
{
mBaseMatrix = mat;
}
inline void AbstractPolyList::setTransform(const MatrixF* mat, const Point3F& scale)
{
mMatrix = mBaseMatrix;
mTransformMatrix = *mat;
mMatrix.mul(*mat);
mScale = scale;
mPlaneTransformer.set(mMatrix, mScale);
}
inline void AbstractPolyList::getTransform(MatrixF* mat, Point3F * scale)
{
*mat = mTransformMatrix;
*scale = mScale;
}
inline void AbstractPolyList::setObject(SceneObject* obj)
{
mCurrObject = obj;
}
#endif

199
engine/collision/boxConvex.cc Executable file
View File

@ -0,0 +1,199 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "math/mMath.h"
#include "game/gameBase.h"
#include "collision/boxConvex.h"
//----------------------------------------------------------------------------
static struct Corner {
S32 a,b,c;
S32 ab,ac,bc;
} sCorner[] =
{
{ 1,2,4, 4,0,1 },
{ 0,3,5, 4,0,3 },
{ 0,3,6, 4,1,2 },
{ 1,2,7, 4,3,2 },
{ 0,5,6, 0,1,5 },
{ 1,4,7, 0,3,5 },
{ 2,4,7, 1,2,5 },
{ 3,5,6, 3,2,5 },
};
static struct Face {
S32 vertex[4];
S32 axis;
bool flip;
} sFace[] =
{
{ 0,4,5,1, 1,true },
{ 0,2,6,4, 0,true },
{ 3,7,6,2, 1,false },
{ 3,1,5,7, 0,false },
{ 0,1,3,2, 2,true },
{ 4,6,7,5, 2,false },
};
Point3F BoxConvex::support(const VectorF& v) const
{
Point3F p = mCenter;
p.x += (v.x >= 0)? mSize.x: -mSize.x;
p.y += (v.y >= 0)? mSize.y: -mSize.y;
p.z += (v.z >= 0)? mSize.z: -mSize.z;
return p;
}
Point3F BoxConvex::getVertex(S32 v)
{
Point3F p = mCenter;
p.x += (v & 1)? mSize.x: -mSize.x;
p.y += (v & 2)? mSize.y: -mSize.y;
p.z += (v & 4)? mSize.z: -mSize.z;
return p;
}
inline bool isOnPlane(Point3F p,PlaneF& plane)
{
F32 dist = mDot(plane,p) + plane.d;
return dist < 0.1 && dist > -0.1;
}
void BoxConvex::getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf)
{
cf->material = 0;
cf->object = mObject;
S32 v = 0;
v += (n.x >= 0)? 1: 0;
v += (n.y >= 0)? 2: 0;
v += (n.z >= 0)? 4: 0;
PlaneF plane;
plane.set(getVertex(v),n);
// Emit vertex and edge
S32 mask = 0;
Corner& corner = sCorner[v];
mask |= isOnPlane(getVertex(corner.a),plane)? 1: 0;
mask |= isOnPlane(getVertex(corner.b),plane)? 2: 0;
mask |= isOnPlane(getVertex(corner.c),plane)? 4: 0;
switch(mask) {
case 0: {
cf->mVertexList.increment();
mat.mulP(getVertex(v),&cf->mVertexList.last());
break;
}
case 1:
emitEdge(v,corner.a,mat,cf);
break;
case 2:
emitEdge(v,corner.b,mat,cf);
break;
case 4:
emitEdge(v,corner.c,mat,cf);
break;
case 1 | 2:
emitFace(corner.ab,mat,cf);
break;
case 2 | 4:
emitFace(corner.bc,mat,cf);
break;
case 1 | 4:
emitFace(corner.ac,mat,cf);
break;
}
}
void BoxConvex::getPolyList(AbstractPolyList* list)
{
list->setTransform(&getTransform(), getScale());
list->setObject(getObject());
U32 base = list->addPoint(mCenter + Point3F(-mSize.x, -mSize.y, -mSize.z));
list->addPoint(mCenter + Point3F( mSize.x, -mSize.y, -mSize.z));
list->addPoint(mCenter + Point3F(-mSize.x, mSize.y, -mSize.z));
list->addPoint(mCenter + Point3F( mSize.x, mSize.y, -mSize.z));
list->addPoint(mCenter + Point3F(-mSize.x, -mSize.y, mSize.z));
list->addPoint(mCenter + Point3F( mSize.x, -mSize.y, mSize.z));
list->addPoint(mCenter + Point3F(-mSize.x, mSize.y, mSize.z));
list->addPoint(mCenter + Point3F( mSize.x, mSize.y, mSize.z));
for (U32 i = 0; i < 6; i++) {
list->begin(0, i);
list->vertex(base + sFace[i].vertex[0]);
list->vertex(base + sFace[i].vertex[1]);
list->vertex(base + sFace[i].vertex[2]);
list->vertex(base + sFace[i].vertex[3]);
list->plane(base + sFace[i].vertex[0],
base + sFace[i].vertex[1],
base + sFace[i].vertex[2]);
list->end();
}
}
void BoxConvex::emitEdge(S32 v1,S32 v2,const MatrixF& mat,ConvexFeature* cf)
{
S32 vc = cf->mVertexList.size();
cf->mVertexList.increment(2);
Point3F *vp = cf->mVertexList.begin();
mat.mulP(getVertex(v1),&vp[vc]);
mat.mulP(getVertex(v2),&vp[vc + 1]);
cf->mEdgeList.increment();
ConvexFeature::Edge& edge = cf->mEdgeList.last();
edge.vertex[0] = vc;
edge.vertex[1] = vc + 1;
}
void BoxConvex::emitFace(S32 fi,const MatrixF& mat,ConvexFeature* cf)
{
Face& face = sFace[fi];
// Emit vertices
S32 vc = cf->mVertexList.size();
cf->mVertexList.increment(4);
Point3F *vp = cf->mVertexList.begin();
for (S32 v = 0; v < 4; v++)
mat.mulP(getVertex(face.vertex[v]),&vp[vc + v]);
// Emit edges
cf->mEdgeList.increment(4);
ConvexFeature::Edge* edge = cf->mEdgeList.end() - 4;
for (S32 e = 0; e < 4; e++) {
edge[e].vertex[0] = vc + e;
edge[e].vertex[1] = vc + ((e + 1) & 3);
}
// Emit 2 triangle faces
cf->mFaceList.increment(2);
ConvexFeature::Face* ef = cf->mFaceList.end() - 2;
mat.getColumn(face.axis,&ef->normal);
if (face.flip)
ef[0].normal.neg();
ef[1].normal = ef[0].normal;
ef[1].vertex[0] = ef[0].vertex[0] = vc;
ef[1].vertex[1] = ef[0].vertex[2] = vc + 2;
ef[0].vertex[1] = vc + 1;
ef[1].vertex[2] = vc + 3;
}
const MatrixF& OrthoBoxConvex::getTransform() const
{
Point3F translation;
Parent::getTransform().getColumn(3, &translation);
mOrthoMatrixCache.setColumn(3, translation);
return mOrthoMatrixCache;
}

46
engine/collision/boxConvex.h Executable file
View File

@ -0,0 +1,46 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _BOXCONVEX_H_
#define _BOXCONVEX_H_
#ifndef _CONVEX_H_
#include "collision/convex.h"
#endif
//----------------------------------------------------------------------------
class BoxConvex: public Convex
{
Point3F getVertex(S32 v);
void emitEdge(S32 v1,S32 v2,const MatrixF& mat,ConvexFeature* cf);
void emitFace(S32 fi,const MatrixF& mat,ConvexFeature* cf);
public:
//
Point3F mCenter;
VectorF mSize;
BoxConvex() { mType = BoxConvexType; }
void init(SceneObject* obj) { mObject = obj; }
Point3F support(const VectorF& v) const;
void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf);
void getPolyList(AbstractPolyList* list);
};
class OrthoBoxConvex: public BoxConvex
{
typedef BoxConvex Parent;
mutable MatrixF mOrthoMatrixCache;
public:
OrthoBoxConvex() { mOrthoMatrixCache.identity(); }
virtual const MatrixF& getTransform() const;
};
#endif

View File

@ -0,0 +1,275 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/clippedPolyList.h"
bool ClippedPolyList::allowClipping = true;
//----------------------------------------------------------------------------
ClippedPolyList::ClippedPolyList()
{
VECTOR_SET_ASSOCIATION(mPolyList);
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mIndexList);
VECTOR_SET_ASSOCIATION(mPolyPlaneList);
VECTOR_SET_ASSOCIATION(mPlaneList);
mNormal.set(0,0,0);
mIndexList.reserve(100);
}
ClippedPolyList::~ClippedPolyList()
{
}
//----------------------------------------------------------------------------
void ClippedPolyList::clear()
{
// Only clears internal data
mPolyList.clear();
mVertexList.clear();
mIndexList.clear();
mPolyPlaneList.clear();
}
bool ClippedPolyList::isEmpty() const
{
return mPolyList.size() == 0;
}
//----------------------------------------------------------------------------
U32 ClippedPolyList::addPoint(const Point3F& p)
{
mVertexList.increment();
Vertex& v = mVertexList.last();
v.point.x = p.x * mScale.x;
v.point.y = p.y * mScale.y;
v.point.z = p.z * mScale.z;
mMatrix.mulP(v.point);
// Build the plane mask
register U32 mask = 1;
register S32 count = mPlaneList.size();
register PlaneF * plane = mPlaneList.address();
v.mask = 0;
while(--count >= 0) {
if (plane++->distToPlane(v.point) > 0)
v.mask |= mask;
mask <<= 1;
}
return mVertexList.size() - 1;
}
U32 ClippedPolyList::addPlane(const PlaneF& plane)
{
mPolyPlaneList.increment();
mPlaneTransformer.transform(plane, mPolyPlaneList.last());
return mPolyPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void ClippedPolyList::begin(U32 material,U32 surfaceKey)
{
mPolyList.increment();
Poly& poly = mPolyList.last();
poly.object = mCurrObject;
poly.material = material;
poly.vertexStart = mIndexList.size();
poly.surfaceKey = surfaceKey;
poly.polyFlags = 0;
if(ClippedPolyList::allowClipping)
poly.polyFlags = CLIPPEDPOLYLIST_FLAG_ALLOWCLIPPING;
}
//----------------------------------------------------------------------------
void ClippedPolyList::plane(U32 v1,U32 v2,U32 v3)
{
mPolyList.last().plane.set(mVertexList[v1].point,
mVertexList[v2].point,mVertexList[v3].point);
}
void ClippedPolyList::plane(const PlaneF& p)
{
mPlaneTransformer.transform(p, mPolyList.last().plane);
}
void ClippedPolyList::plane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
mPolyList.last().plane = mPolyPlaneList[index];
}
const PlaneF& ClippedPolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
return mPolyPlaneList[index];
}
//----------------------------------------------------------------------------
void ClippedPolyList::vertex(U32 vi)
{
mIndexList.push_back(vi);
}
//----------------------------------------------------------------------------
void ClippedPolyList::end()
{
Poly& poly = mPolyList.last();
// Anything facing away from the mNormal is rejected
if (mDot(poly.plane,mNormal) > 0) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
// Build initial inside/outside plane masks
U32 indexStart = poly.vertexStart;
U32 vertexCount = mIndexList.size() - indexStart;
U32 frontMask = 0,backMask = 0;
U32 i;
for (i = indexStart; i < mIndexList.size(); i++) {
U32 mask = mVertexList[mIndexList[i]].mask;
frontMask |= mask;
backMask |= ~mask;
}
// Trivial accept if all the vertices are on the backsides of
// all the planes.
if (!frontMask) {
poly.vertexCount = vertexCount;
return;
}
// Trivial reject if any plane not crossed has all it's points
// on the front.
U32 crossMask = frontMask & backMask;
if (~crossMask & frontMask) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
// Need to do some clipping
for (U32 p = 0; p < mPlaneList.size(); p++) {
U32 pmask = 1 << p;
// Only test against this plane if we have something
// on both sides
if (crossMask & pmask) {
U32 indexEnd = mIndexList.size();
U32 i1 = indexEnd - 1;
U32 mask1 = mVertexList[mIndexList[i1]].mask;
for (U32 i2 = indexStart; i2 < indexEnd; i2++) {
U32 mask2 = mVertexList[mIndexList[i2]].mask;
if ((mask1 ^ mask2) & pmask) {
//
mVertexList.increment();
VectorF& v1 = mVertexList[mIndexList[i1]].point;
VectorF& v2 = mVertexList[mIndexList[i2]].point;
VectorF vv = v2 - v1;
F32 t = -mPlaneList[p].distToPlane(v1) / mDot(mPlaneList[p],vv);
mIndexList.push_back(mVertexList.size() - 1);
Vertex& iv = mVertexList.last();
iv.point.x = v1.x + vv.x * t;
iv.point.y = v1.y + vv.y * t;
iv.point.z = v1.z + vv.z * t;
iv.mask = 0;
// Test against the remaining planes
for (U32 i = p + 1; i < mPlaneList.size(); i++)
if (mPlaneList[i].distToPlane(iv.point) > 0) {
iv.mask = 1 << i;
break;
}
}
if (!(mask2 & pmask)) {
U32 index = mIndexList[i2];
mIndexList.push_back(index);
}
mask1 = mask2;
i1 = i2;
}
// Check for degenerate
indexStart = indexEnd;
if (mIndexList.size() - indexStart < 3) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
}
}
// Emit what's left and compress the index list.
poly.vertexCount = mIndexList.size() - indexStart;
memcpy(&mIndexList[poly.vertexStart],
&mIndexList[indexStart],poly.vertexCount);
mIndexList.setSize(poly.vertexStart + poly.vertexCount);
}
//----------------------------------------------------------------------------
void ClippedPolyList::memcpy(U32* dst, U32* src,U32 size)
{
U32* end = src + size;
while (src != end)
*dst++ = *src++;
}
//----------------------------------------------------------------------------
void ClippedPolyList::render()
{
glVertexPointer(3,GL_FLOAT,sizeof(Vertex),mVertexList.address());
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1,0,0,0.25);
glEnable(GL_BLEND);
glDisable(GL_CULL_FACE);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
Poly * p;
for (p = mPolyList.begin(); p < mPolyList.end(); p++) {
glDrawElements(GL_POLYGON,p->vertexCount,
GL_UNSIGNED_INT,&mIndexList[p->vertexStart]);
}
glColor3f(0.6,0.6,0.6);
glDisable(GL_BLEND);
for (p = mPolyList.begin(); p < mPolyList.end(); p++) {
glDrawElements(GL_LINE_LOOP,p->vertexCount,
GL_UNSIGNED_INT,&mIndexList[p->vertexStart]);
}
glDisableClientState(GL_VERTEX_ARRAY);
}

View File

@ -0,0 +1,90 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CLIPPEDPOLYLIST_H_
#define _CLIPPEDPOLYLIST_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
#define CLIPPEDPOLYLIST_FLAG_ALLOWCLIPPING 0x01
//----------------------------------------------------------------------------
/// Clipped PolyList
///
/// This takes the geometry passed to it and clips against the PlaneList set
/// by the caller.
//
/// @see AbstractPolyList
class ClippedPolyList: public AbstractPolyList
{
void memcpy(U32* d, U32* s,U32 size);
public:
struct Vertex {
Point3F point;
U32 mask;
};
struct Poly {
PlaneF plane;
SceneObject* object;
U32 material;
U32 vertexStart;
U32 vertexCount;
U32 surfaceKey;
U32 polyFlags;
};
static bool allowClipping;
typedef Vector<PlaneF> PlaneList;
typedef Vector<Vertex> VertexList;
typedef Vector<Poly> PolyList;
typedef Vector<U32> IndexList;
// Internal data
PolyList mPolyList;
VertexList mVertexList;
IndexList mIndexList;
PlaneList mPolyPlaneList;
// Data set by caller
PlaneList mPlaneList;
VectorF mNormal;
//
ClippedPolyList();
~ClippedPolyList();
void clear();
void render();
// Virtual methods
bool isEmpty() const;
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material,U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif

67
engine/collision/collision.h Executable file
View File

@ -0,0 +1,67 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _COLLISION_H_
#define _COLLISION_H_
#ifndef _DATACHUNKER_H_
#include "core/dataChunker.h"
#endif
#ifndef _MPLANE_H_
#include "math/mPlane.h"
#endif
class SceneObject;
//----------------------------------------------------------------------------
struct Collision
{
SceneObject* object;
Point3F point;
VectorF normal;
U32 material;
// Face and Face dot are currently only set by the extrudedPolyList
// clipper. Values are otherwise undefined.
U32 face; // Which face was hit
F32 faceDot; // -Dot of face with poly normal
F32 distance;
Collision() { material = 0; }
};
struct CollisionList
{
enum {
MaxCollisions = 64
};
int count;
Collision collision[MaxCollisions];
F32 t;
// MaxHeight is currently only set by the extrudedPolyList
// clipper. It represents the maximum vertex z value of
// the returned collision surfaces.
F32 maxHeight;
};
//----------------------------------------------------------------------------
// BSP Collision tree
// Solid nodes are represented by structures with NULL frontNode and
// backNode pointers. The material field is only valid on a solid node.
// There is no structure for empty nodes, frontNode or backNode
// should be set to NULL to represent empty half-spaces.
struct BSPNode
{
U32 material;
PlaneF plane;
BSPNode *frontNode, *backNode;
};
typedef Chunker<BSPNode> BSPTree;
#endif

View File

@ -0,0 +1,149 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/concretePolyList.h"
//----------------------------------------------------------------------------
ConcretePolyList::ConcretePolyList()
{
VECTOR_SET_ASSOCIATION(mPolyList);
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mIndexList);
VECTOR_SET_ASSOCIATION(mPolyPlaneList);
mIndexList.reserve(100);
}
ConcretePolyList::~ConcretePolyList()
{
}
//----------------------------------------------------------------------------
void ConcretePolyList::clear()
{
// Only clears internal data
mPolyList.clear();
mVertexList.clear();
mIndexList.clear();
mPolyPlaneList.clear();
}
//----------------------------------------------------------------------------
U32 ConcretePolyList::addPoint(const Point3F& p)
{
mVertexList.increment();
Point3F& v = mVertexList.last();
v.x = p.x * mScale.x;
v.y = p.y * mScale.y;
v.z = p.z * mScale.z;
mMatrix.mulP(v);
return mVertexList.size() - 1;
}
U32 ConcretePolyList::addPlane(const PlaneF& plane)
{
mPolyPlaneList.increment();
mPlaneTransformer.transform(plane, mPolyPlaneList.last());
return mPolyPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void ConcretePolyList::begin(U32 material,U32 surfaceKey)
{
mPolyList.increment();
Poly& poly = mPolyList.last();
poly.object = mCurrObject;
poly.material = material;
poly.vertexStart = mIndexList.size();
poly.surfaceKey = surfaceKey;
}
//----------------------------------------------------------------------------
void ConcretePolyList::plane(U32 v1,U32 v2,U32 v3)
{
mPolyList.last().plane.set(mVertexList[v1],
mVertexList[v2],mVertexList[v3]);
}
void ConcretePolyList::plane(const PlaneF& p)
{
mPlaneTransformer.transform(p, mPolyList.last().plane);
}
void ConcretePolyList::plane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
mPolyList.last().plane = mPolyPlaneList[index];
}
const PlaneF& ConcretePolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
return mPolyPlaneList[index];
}
//----------------------------------------------------------------------------
void ConcretePolyList::vertex(U32 vi)
{
mIndexList.push_back(vi);
}
//----------------------------------------------------------------------------
bool ConcretePolyList::isEmpty() const
{
return false;
}
void ConcretePolyList::end()
{
Poly& poly = mPolyList.last();
poly.vertexCount = mIndexList.size() - poly.vertexStart;
}
void ConcretePolyList::render()
{
glVertexPointer(3,GL_FLOAT,sizeof(Point3F),mVertexList.address());
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1,0,0,0.25);
glEnable(GL_BLEND);
glDisable(GL_CULL_FACE);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
Poly * p;
for (p = mPolyList.begin(); p < mPolyList.end(); p++) {
if(p->material != 0xFFFFFFFF)
glDrawElements(GL_POLYGON,p->vertexCount,
GL_UNSIGNED_INT,&mIndexList[p->vertexStart]);
}
glColor3f(0.6,0.6,0.6);
glDisable(GL_BLEND);
for (p = mPolyList.begin(); p < mPolyList.end(); p++) {
glDrawElements(GL_LINE_LOOP,p->vertexCount,
GL_UNSIGNED_INT,&mIndexList[p->vertexStart]);
}
glDisableClientState(GL_VERTEX_ARRAY);
}

View File

@ -0,0 +1,66 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONCRETEPOLYLIST_H_
#define _CONCRETEPOLYLIST_H_
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
/// A concrete, renderable PolyList
///
/// This class is used to store geometry from a PolyList query.
///
/// It allows you to render this data, as well.
///
/// @see AbstractPolyList
class ConcretePolyList : public AbstractPolyList
{
public:
struct Poly {
PlaneF plane;
SceneObject* object;
U32 material;
U32 vertexStart;
U32 vertexCount;
U32 surfaceKey;
};
typedef Vector<PlaneF> PlaneList;
typedef Vector<Point3F> VertexList;
typedef Vector<Poly> PolyList;
typedef Vector<U32> IndexList;
PolyList mPolyList;
VertexList mVertexList;
IndexList mIndexList;
PlaneList mPolyPlaneList;
public:
ConcretePolyList();
~ConcretePolyList();
void clear();
// Virtual methods
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material,U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
void render();
bool isEmpty() const;
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif // _H_EARLYOUTPOLYLIST_

644
engine/collision/convex.cc Executable file
View File

@ -0,0 +1,644 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "core/dataChunker.h"
#include "collision/collision.h"
#include "sceneGraph/sceneGraph.h"
#include "sim/sceneObject.h"
#include "terrain/terrData.h"
#include "collision/convex.h"
#include "collision/gjk.h"
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
static DataChunker sChunker;
CollisionStateList CollisionStateList::sFreeList;
CollisionWorkingList CollisionWorkingList::sFreeList;
F32 sqrDistanceEdges(const Point3F& start0,
const Point3F& end0,
const Point3F& start1,
const Point3F& end1,
Point3F* is,
Point3F* it);
//----------------------------------------------------------------------------
// Collision State
//----------------------------------------------------------------------------
CollisionState::CollisionState()
{
mLista = mListb = 0;
}
CollisionState::~CollisionState()
{
if (mLista)
mLista->free();
if (mListb)
mListb->free();
}
void CollisionState::swap()
{
}
void CollisionState::set(Convex* a,Convex* b,const MatrixF& a2w, const MatrixF& b2w)
{
}
F32 CollisionState::distance(const MatrixF& a2w, const MatrixF& b2w, const F32 dontCareDist,
const MatrixF* w2a, const MatrixF* _w2b)
{
return 0;
}
void CollisionState::render()
{
}
//----------------------------------------------------------------------------
// Feature Collision
//----------------------------------------------------------------------------
bool ConvexFeature::collide(ConvexFeature& cf,CollisionList* cList, F32 tol)
{
// Our vertices vs. other faces
const Point3F* vert = mVertexList.begin();
const Point3F* vend = mVertexList.end();
while (vert != vend) {
cf.testVertex(*vert,cList,false, tol);
vert++;
}
// Other vertices vs. our faces
vert = cf.mVertexList.begin();
vend = cf.mVertexList.end();
while (vert != vend) {
U32 storeCount = cList->count;
testVertex(*vert,cList,true, tol);
// Fix up last reference. material and object are copied from this rather
// than the object we're colliding against.
if (storeCount != cList->count) {
cList->collision[cList->count - 1].material = cf.material;
cList->collision[cList->count - 1].object = cf.object;
}
vert++;
}
// Edge vs. Edge
const Edge* edge = mEdgeList.begin();
const Edge* eend = mEdgeList.end();
while (edge != eend) {
cf.testEdge(this,mVertexList[edge->vertex[0]],
mVertexList[edge->vertex[1]],cList, tol);
edge++;
}
return true;
}
inline bool isInside(const Point3F& p, const Point3F& a, const Point3F& b, const VectorF& n)
{
VectorF v;
mCross(n,b - a,&v);
return mDot(v,p - a) < 0.0f;
}
void ConvexFeature::testVertex(const Point3F& v,CollisionList* cList,bool flip, F32 tol)
{
// Test vertex against all faces
const Face* face = mFaceList.begin();
const Face* end = mFaceList.end();
for (; face != end; face++) {
if (cList->count >= CollisionList::MaxCollisions)
return;
const Point3F& p0 = mVertexList[face->vertex[0]];
const Point3F& p1 = mVertexList[face->vertex[1]];
const Point3F& p2 = mVertexList[face->vertex[2]];
// Point near the plane?
F32 distance = mDot(face->normal,v - p0);
if (distance > tol || distance < -tol)
continue;
// Make sure it's within the bounding edges
if (isInside(v,p0,p1,face->normal) && isInside(v,p1,p2,face->normal) &&
isInside(v,p2,p0,face->normal)) {
// Add collision to this face
Collision& info = cList->collision[cList->count++];
info.point = v;
info.normal = face->normal;
if (flip)
info.normal.neg();
info.material = material;
info.object = object;
info.distance = distance;
}
}
}
void ConvexFeature::testEdge(ConvexFeature* cf,const Point3F& s1, const Point3F& e1, CollisionList* cList, F32 tol)
{
F32 tolSquared = tol*tol;
// Test edges against edges
const Edge* edge = mEdgeList.begin();
const Edge* end = mEdgeList.end();
for (; edge != end; edge++) {
if (cList->count >= CollisionList::MaxCollisions)
return;
const Point3F& s2 = mVertexList[edge->vertex[0]];
const Point3F& e2 = mVertexList[edge->vertex[1]];
// Get the distance and closest points
Point3F i1,i2;
F32 distance = sqrDistanceEdges(s1, e1, s2, e2, &i1, &i2);
if (distance > tolSquared)
continue;
distance = mSqrt(distance);
// Need to figure out how to orient the collision normal.
// The current test involves checking to see whether the collision
// points are contained within the convex volumes, which is slow.
if (inVolume(i1) || cf->inVolume(i2))
distance = -distance;
// Contact normal
VectorF normal = i1 - i2;
if ( mIsZero( distance ) )
normal.zero();
else
normal *= 1 / distance;
// Return a collision
Collision& info = cList->collision[cList->count++];
info.point = i1;
info.normal = normal;
info.distance = distance;
info.material = material;
info.object = object;
}
}
bool ConvexFeature::inVolume(const Point3F& v)
{
// Test the point to see if it's inside the volume
const Face* face = mFaceList.begin();
const Face* end = mFaceList.end();
for (; face != end; face++) {
const Point3F& p0 = mVertexList[face->vertex[0]];
if (mDot(face->normal,v - p0) > 0)
return false;
}
return true;
}
//----------------------------------------------------------------------------
// Collision State management
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
CollisionStateList::CollisionStateList()
{
mPrev = mNext = this;
mState = NULL;
}
void CollisionStateList::linkAfter(CollisionStateList* ptr)
{
mPrev = ptr;
mNext = ptr->mNext;
ptr->mNext = this;
mNext->mPrev = this;
}
void CollisionStateList::unlink()
{
mPrev->mNext = mNext;
mNext->mPrev = mPrev;
mPrev = mNext = this;
}
CollisionStateList* CollisionStateList::alloc()
{
if (!sFreeList.isEmpty()) {
CollisionStateList* nxt = sFreeList.mNext;
nxt->unlink();
nxt->mState = NULL;
return nxt;
}
return constructInPlace((CollisionStateList*)sChunker.alloc(sizeof(CollisionStateList)));
}
void CollisionStateList::free()
{
unlink();
linkAfter(&sFreeList);
}
//----------------------------------------------------------------------------
CollisionWorkingList::CollisionWorkingList()
{
wLink.mPrev = wLink.mNext = this;
rLink.mPrev = rLink.mNext = this;
}
void CollisionWorkingList::wLinkAfter(CollisionWorkingList* ptr)
{
wLink.mPrev = ptr;
wLink.mNext = ptr->wLink.mNext;
ptr->wLink.mNext = this;
wLink.mNext->wLink.mPrev = this;
}
void CollisionWorkingList::rLinkAfter(CollisionWorkingList* ptr)
{
rLink.mPrev = ptr;
rLink.mNext = ptr->rLink.mNext;
ptr->rLink.mNext = this;
rLink.mNext->rLink.mPrev = this;
}
void CollisionWorkingList::unlink()
{
wLink.mPrev->wLink.mNext = wLink.mNext;
wLink.mNext->wLink.mPrev = wLink.mPrev;
wLink.mPrev = wLink.mNext = this;
rLink.mPrev->rLink.mNext = rLink.mNext;
rLink.mNext->rLink.mPrev = rLink.mPrev;
rLink.mPrev = rLink.mNext = this;
}
CollisionWorkingList* CollisionWorkingList::alloc()
{
if (sFreeList.wLink.mNext != &sFreeList) {
CollisionWorkingList* nxt = sFreeList.wLink.mNext;
nxt->unlink();
return nxt;
}
return constructInPlace((CollisionWorkingList*)sChunker.alloc(sizeof(CollisionWorkingList)));
}
void CollisionWorkingList::free()
{
unlink();
wLinkAfter(&sFreeList);
}
//----------------------------------------------------------------------------
// Convex Base Class
//----------------------------------------------------------------------------
U32 Convex::sTag = (U32)-1;
//----------------------------------------------------------------------------
Convex::Convex()
{
mNext = mPrev = this;
mTag = 0;
}
Convex::~Convex()
{
// Unlink from Convex Database
unlink();
// Delete collision states
while (mList.mNext != &mList)
delete mList.mNext->mState;
// Free up working list
while (mWorking.wLink.mNext != &mWorking)
mWorking.wLink.mNext->free();
// Free up references
while (mReference.rLink.mNext != &mReference)
mReference.rLink.mNext->free();
}
//----------------------------------------------------------------------------
void Convex::collectGarbage()
{
// Delete unreferenced Convex Objects
for (Convex* itr = mNext; itr != this; itr = itr->mNext) {
if (itr->mReference.rLink.mNext == &itr->mReference) {
Convex* ptr = itr;
itr = itr->mPrev;
delete ptr;
}
}
}
void Convex::nukeList()
{
// Delete all Convex Objects
for (Convex* itr = mNext; itr != this; itr = itr->mNext) {
Convex* ptr = itr;
itr = itr->mPrev;
delete ptr;
}
}
void Convex::registerObject(Convex *convex)
{
convex->linkAfter(this);
}
//----------------------------------------------------------------------------
void Convex::linkAfter(Convex* ptr)
{
mPrev = ptr;
mNext = ptr->mNext;
ptr->mNext = this;
mNext->mPrev = this;
}
void Convex::unlink()
{
mPrev->mNext = mNext;
mNext->mPrev = mPrev;
mPrev = mNext = this;
}
//----------------------------------------------------------------------------
Point3F Convex::support(const VectorF&) const
{
return Point3F(0,0,0);
}
void Convex::getFeatures(const MatrixF&,const VectorF&,ConvexFeature* f)
{
f->object = NULL;
}
const MatrixF& Convex::getTransform() const
{
return mObject->getTransform();
}
const Point3F& Convex::getScale() const
{
return mObject->getScale();
}
Box3F Convex::getBoundingBox() const
{
return mObject->getWorldBox();
}
Box3F Convex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const
{
Box3F wBox = mObject->getObjBox();
wBox.min.convolve(scale);
wBox.max.convolve(scale);
mat.mul(wBox);
return wBox;
}
void Convex::render()
{
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext)
if (itr->mState->mLista == itr)
itr->mState->render();
}
//----------------------------------------------------------------------------
void Convex::addToWorkingList(Convex* ptr)
{
CollisionWorkingList* cl = CollisionWorkingList::alloc();
cl->wLinkAfter(&mWorking);
cl->rLinkAfter(&ptr->mReference);
cl->mConvex = ptr;
};
//----------------------------------------------------------------------------
void Convex::updateWorkingList(const Box3F& box, const U32 colMask)
{
sTag++;
// Clear objects off the working list that are no longer intersecting
for (CollisionWorkingList* itr = mWorking.wLink.mNext; itr != &mWorking; itr = itr->wLink.mNext) {
itr->mConvex->mTag = sTag;
if ((!box.isOverlapped(itr->mConvex->getBoundingBox())) || (!itr->mConvex->getObject()->isCollisionEnabled())) {
CollisionWorkingList* cl = itr;
itr = itr->wLink.mPrev;
cl->free();
}
}
// Special processing for the terrain and interiors...
AssertFatal(mObject->getContainer(), "Must be in a container!");
SimpleQueryList sql;
mObject->getContainer()->findObjects(box, colMask,SimpleQueryList::insertionCallback, &sql);
for (U32 i = 0; i < sql.mList.size(); i++)
sql.mList[i]->buildConvex(box, this);
}
// ---------------------------------------------------------------------------
void Convex::updateStateList(const MatrixF& mat, const Point3F& scale, const Point3F* displacement)
{
Box3F box1 = getBoundingBox(mat, scale);
box1.min -= Point3F(1, 1, 1);
box1.max += Point3F(1, 1, 1);
if (displacement) {
Point3F oldMin = box1.min;
Point3F oldMax = box1.max;
box1.min.setMin(oldMin + *displacement);
box1.min.setMin(oldMax + *displacement);
box1.max.setMax(oldMin + *displacement);
box1.max.setMax(oldMax + *displacement);
}
sTag++;
// Destroy states which are no longer intersecting
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) {
Convex* cv = (itr->mState->a == this)? itr->mState->b: itr->mState->a;
cv->mTag = sTag;
if (!box1.isOverlapped(cv->getBoundingBox())) {
CollisionState* cs = itr->mState;
itr = itr->mPrev;
delete cs;
}
}
// Add collision states for new overlapping objects
for (CollisionWorkingList* itr0 = mWorking.wLink.mNext; itr0 != &mWorking; itr0 = itr0->wLink.mNext) {
register Convex* cv = itr0->mConvex;
if (cv->mTag != sTag && box1.isOverlapped(cv->getBoundingBox())) {
CollisionState* state = new GjkCollisionState;
state->set(this,cv,mat,cv->getTransform());
state->mLista->linkAfter(&mList);
state->mListb->linkAfter(&cv->mList);
}
}
}
//----------------------------------------------------------------------------
CollisionState* Convex::findClosestState(const MatrixF& mat, const Point3F& scale, const F32 dontCareDist)
{
updateStateList(mat, scale);
F32 dist = +1E30;
CollisionState *st = 0;
// Prepare scaled version of transform
MatrixF axform = mat;
axform.scale(scale);
MatrixF axforminv(true);
MatrixF temp(mat);
axforminv.scale(Point3F(1.0f/scale.x,1.0f/scale.y,1.0f/scale.z));
temp.affineInverse();
axforminv.mul(temp);
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) {
CollisionState* state = itr->mState;
if (state->mLista != itr)
state->swap();
// Prepare scaled version of transform
MatrixF bxform = state->b->getTransform();
temp = bxform;
Point3F bscale = state->b->getScale();
bxform.scale(bscale);
MatrixF bxforminv(true);
bxforminv.scale(Point3F(1.0f/bscale.x,1.0f/bscale.y,1.0f/bscale.z));
temp.affineInverse();
bxforminv.mul(temp);
//
F32 dd = state->distance(axform, bxform, dontCareDist, &axforminv, &bxforminv);
if (dd < dist) {
dist = dd;
st = state;
}
}
if (dist < dontCareDist)
return st;
else
return NULL;
}
//----------------------------------------------------------------------------
bool Convex::getCollisionInfo(const MatrixF& mat, const Point3F& scale, CollisionList* cList,F32 tol)
{
for (CollisionStateList* itr = mList.mNext; itr != &mList; itr = itr->mNext) {
CollisionState* state = itr->mState;
if (state->mLista != itr)
state->swap();
if (state->dist <= tol) {
ConvexFeature fa,fb;
VectorF v;
// The idea is that we need to scale the matrix, so we need to
// make a copy of it, before we can pass it in to getFeatures.
// This is used to scale us for comparison against the other
// convex, which is correctly scaled.
MatrixF omat = mat;
omat.scale(scale);
MatrixF imat = omat;
imat.inverse();
imat.mulV(-state->v,&v);
getFeatures(omat,v,&fa);
imat = state->b->getTransform();
imat.scale(state->b->getScale());
MatrixF bxform = imat;
imat.inverse();
imat.mulV(state->v,&v);
state->b->getFeatures(bxform,v,&fb);
fa.collide(fb,cList,tol);
}
}
return (cList->count != 0);
}
void Convex::getPolyList(AbstractPolyList*)
{
}
//-----------------------------------------------------------------------------
// This function based on code orignally written for the book:
// 3D Game Engine Design, by David H. Eberly
//
F32 sqrDistanceEdges(const Point3F& start0, const Point3F& end0,
const Point3F& start1, const Point3F& end1,
Point3F* is, Point3F* it)
{
Point3F direction0 = end0 - start0;
F32 fA00 = direction0.lenSquared();
Point3F direction1 = end1 - start1;
F32 fA11 = direction1.lenSquared();
F32 fA01 = -mDot(direction0, direction1);
Point3F kDiff = start0 - start1;
F32 fC = kDiff.lenSquared();
F32 fB0 = mDot(kDiff, direction0);
F32 fDet = mAbs((S32)(fA00*fA11 - fA01*fA01));
// Since the endpoints are tested as vertices, we're not interested
// in parallel lines, and intersections that don't involve end-points.
if (fDet >= 0.00001) {
// Calculate time of intersection for each line
F32 fB1 = -mDot(kDiff, direction1);
F32 fS = fA01*fB1-fA11*fB0;
F32 fT = fA01*fB0-fA00*fB1;
// Only interested in collisions that don't involve the end points
if (fS >= 0.0 && fS <= fDet && fT >= 0.0 && fT <= fDet) {
F32 fInvDet = 1.0 / fDet;
fS *= fInvDet;
fT *= fInvDet;
F32 fSqrDist = (fS*(fA00*fS + fA01*fT + 2.0*fB0) +
fT*(fA01*fS + fA11*fT + 2.0*fB1) + fC);
// Intersection points.
*is = start0 + direction0 * fS;
*it = start1 + direction1 * fT;
return mFabs(fSqrDist);
}
}
// Return a large number in the cases where endpoints are involved.
return 1e10f;
}

249
engine/collision/convex.h Executable file
View File

@ -0,0 +1,249 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONVEX_H_
#define _CONVEX_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
struct Collision;
struct CollisionList;
struct CollisionStateList;
class AbstractPolyList;
class SceneObject;
class Convex;
//----------------------------------------------------------------------------
class ConvexFeature
{
public:
struct Edge {
S32 vertex[2];
};
struct Face {
VectorF normal;
S32 vertex[3];
};
Vector<Point3F> mVertexList;
Vector<Edge> mEdgeList;
Vector<Face> mFaceList;
S32 material;
SceneObject* object;
ConvexFeature() : mVertexList(64), mEdgeList(128), mFaceList(64) {
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mEdgeList);
VECTOR_SET_ASSOCIATION(mFaceList);
}
bool collide(ConvexFeature& cf,CollisionList* cList, F32 tol = 0.1);
void testVertex(const Point3F& v,CollisionList* cList,bool,F32 tol);
void testEdge(ConvexFeature* cf,const Point3F& s1, const Point3F& e1, CollisionList* cList, F32 tol);
bool inVolume(const Point3F& v);
};
//----------------------------------------------------------------------------
enum ConvexType {
TSConvexType,
BoxConvexType,
TerrainConvexType,
InteriorConvexType,
ShapeBaseConvexType,
TSStaticConvexType,
InteriorMapConvexType
};
//----------------------------------------------------------------------------
struct CollisionState
{
CollisionStateList* mLista;
CollisionStateList* mListb;
Convex* a;
Convex* b;
F32 dist; // Current estimated distance
VectorF v; // Vector between closest points
//
CollisionState();
virtual ~CollisionState();
virtual void swap();
virtual void set(Convex* a,Convex* b,const MatrixF& a2w, const MatrixF& b2w);
virtual F32 distance(const MatrixF& a2w, const MatrixF& b2w, const F32 dontCareDist,
const MatrixF* w2a = NULL, const MatrixF* _w2b = NULL);
void render();
};
//----------------------------------------------------------------------------
struct CollisionStateList
{
static CollisionStateList sFreeList;
CollisionStateList* mNext;
CollisionStateList* mPrev;
CollisionState* mState;
CollisionStateList();
void linkAfter(CollisionStateList* next);
void unlink();
bool isEmpty() { return mNext == this; }
static CollisionStateList* alloc();
void free();
};
//----------------------------------------------------------------------------
struct CollisionWorkingList
{
static CollisionWorkingList sFreeList;
struct WLink {
CollisionWorkingList* mNext;
CollisionWorkingList* mPrev;
} wLink;
struct RLink {
CollisionWorkingList* mNext;
CollisionWorkingList* mPrev;
} rLink;
Convex* mConvex;
void wLinkAfter(CollisionWorkingList* next);
void rLinkAfter(CollisionWorkingList* next);
void unlink();
CollisionWorkingList();
static CollisionWorkingList* alloc();
void free();
};
//----------------------------------------------------------------------------
class Convex {
/// @name Linked list managent
/// @{
Convex* mNext; ///< Next item in linked list of Convex's
Convex* mPrev; ///< Previous item in linked list of Convex's
/// Insert this Convex after the provided convex
/// @param next
void linkAfter(Convex* next);
/// Remove this Convex from the linked list
void unlink();
/// @}
U32 mTag;
static U32 sTag;
protected:
CollisionStateList mList; ///< Objects we're testing against
CollisionWorkingList mWorking; ///< Objects within our bounds
CollisionWorkingList mReference; ///< Other convex testing against us
SceneObject* mObject; ///< Object this Convex is built around
ConvexType mType; ///< Type of Convex this is @see ConvexType
//
public:
/// Constructor
Convex();
/// Destructor
virtual ~Convex();
/// Registers another Convex by linking it after this one
void registerObject(Convex *convex);
/// Runs through the linked list of Convex objects and removes the ones
/// with no references
void collectGarbage();
/// Deletes all convex objects in the list
void nukeList();
/// Returns the type of this Convex
ConvexType getType() { return mType; }
/// Returns the object this Convex is built from
SceneObject* getObject() { return mObject; }
/// Traverses mList and renders all collision states
void render();
/// Adds the provided Convex to the list of objects within the bounds of this Convex
/// @param ptr Convex to add to the working list of this object
void addToWorkingList(Convex* ptr);
/// Returns the list of objects currently inside the bounds of this Convex
CollisionWorkingList& getWorkingList() { return mWorking; }
/// Finds the closest
CollisionState* findClosestState(const MatrixF& mat, const Point3F& scale, const F32 dontCareDist = 1);
/// Returns the list of objects this object is testing against
CollisionStateList* getStateList() { return mList.mNext; }
/// Updates the CollisionStateList (mList) with new collision states and removing
/// ones no longer under consideration
/// @param mat Used as the matrix to create a bounding box for updating the list
/// @param scale Used to scale the bounding box
/// @param displacement Bounding box displacement (optional)
void updateStateList(const MatrixF& mat, const Point3F& scale, const Point3F* displacement = NULL);
/// Updates the working collision list of objects which are currently colliding with
/// (inside the bounds of) this Convex.
///
/// @param box Used as the bounding box.
/// @param colMask Mask of objects to check against.
void updateWorkingList(const Box3F& box, const U32 colMask);
/// Returns the transform of the object this is built around
virtual const MatrixF& getTransform() const;
/// Returns the scale of the object this is built around
virtual const Point3F& getScale() const;
/// Returns the bounding box for the object this is built around in world space
virtual Box3F getBoundingBox() const;
/// Returns the object space bounding box for the object this is built around
/// transformed and scaled
/// @param mat Matrix to transform the object-space box by
/// @param scale Scaling factor to scale the bounding box by
virtual Box3F getBoundingBox(const MatrixF& mat, const Point3F& scale) const;
/// Returns the farthest point, along a vector, still bound by the convex
/// @param v Vector
virtual Point3F support(const VectorF& v) const;
///
virtual void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf);
/// Builds a collision poly list out of this convex
/// @param list (Out) Poly list built
virtual void getPolyList(AbstractPolyList* list);
///
bool getCollisionInfo(const MatrixF& mat, const Point3F& scale, CollisionList* cList,F32 tol);
};
#endif

1290
engine/collision/convexBrush.cc Executable file

File diff suppressed because it is too large Load Diff

127
engine/collision/convexBrush.h Executable file
View File

@ -0,0 +1,127 @@
#ifndef _CONVEXBRUSH_H_
#define _CONVEXBRUSH_H_
#include "platform/platform.h"
#include "core/tVector.h"
#include "math/mPoint.h"
#include "math/mBox.h"
#include "math/mPlane.h"
#include "collision/abstractPolyList.h"
#include "collision/optimizedPolyList.h"
#include "sim/sceneObject.h"
#include "interior/interiorMapRes.h"
#define WINDINGSCALE 10000
class ConvexBrush
{
public:
// Constructor
ConvexBrush();
// Destructor
~ConvexBrush();
enum
{
Front = 0,
Back = 1,
On = 2,
Split = 3,
Unknown = 4
};
enum
{
Unprocessed = 0,
Good,
BadWinding,
ShortPlanes,
BadFaces,
Malformed,
Concave,
Deleted
};
// Supporting structures
struct TexInfo
{
StringTableEntry texture;
PlaneF texGens[2];
F32 scale[2];
F32 rot;
F32 texDiv[2];
};
// The brushes owner entity
InteriorMapResource::Entity* mOwner;
// Bounding box
Box3F mBounds;
Point3F mCentroid;
MatrixF mTransform;
Point3F mScale;
QuatF mRotation;
// lighting info...
MatrixF mLightingTransform;
// Some useful values
F32 mBrushScale;
S32 mID;
U32 mType;
U32 mStatus;
U32 mPrevStatus;
bool mSelected;
StringTableEntry mDebugInfo;
// Scratch buffer storage
MatrixF mTransformScratch;
QuatF mRotationScratch;
// Data
OptimizedPolyList mFaces;
Vector<TexInfo> mTexInfos;
// Setup functions
bool addPlane(PlaneF pln);
bool addFace(PlaneF pln, PlaneF texGen[2], F32 scale[2], char* texture);
bool addFace(PlaneF pln, PlaneF texGen[2], F32 scale[2], char* texture, U32 matIdx);
bool addFace(PlaneF pln, PlaneF texGen[2], F32 scale[2], U32 matIdx);
bool processBrush();
bool setScale(F32 scale);
// Utility functions
bool selfClip();
bool intersectPlanes(const PlaneF& plane1, const PlaneF& plane2, const PlaneF& plane3, Point3D* pOutput);
bool createWinding(const Vector<U32>& rPoints, const Point3F& normal, Vector<U32>& pWinding);
void addEdge(U16 zero, U16 one, U32 face);
bool generateEdgelist();
void validateWindings();
bool validateEdges();
void addIndex(Vector<U32>& indices, U32 index);
void calcCentroid();
void setupTransform();
void resetBrush();
U32 getPolySide(S32 side);
U32 whichSide(PlaneF pln);
U32 whichSide(PlaneF pln, U32 faceIndex);
bool isInsideBox(PlaneF left, PlaneF right, PlaneF top, PlaneF bottom);
bool splitBrush(PlaneF pln, ConvexBrush* fbrush, ConvexBrush* bbrush);
bool calcBounds();
bool castRay(const Point3F& s, const Point3F& e, RayInfo* info);
OptimizedPolyList getIntersectingPolys(OptimizedPolyList* list);
OptimizedPolyList getNonIntersectingPolys(OptimizedPolyList* list);
bool isPolyInside(OptimizedPolyList* list, U32 pdx);
bool getPolyList(AbstractPolyList* list);
// Debug render functions
bool render(bool genColors);
bool renderFace(U32 face, bool renderLighting);
bool renderEdges(ColorF color);
};
#endif

917
engine/collision/depthSortList.cc Executable file
View File

@ -0,0 +1,917 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/depthSortList.h"
#include "core/color.h"
#include "core/fileStream.h" // TODO, remove this
//----------------------------------------------------------------------------
// some defines and global parameters that affect poly split routine
F32 SPLIT_TOL = 0.0005f;
bool ALWAYS_RETURN_FRONT_AND_BACK = false; // if false, split routine will return polys only if a split occurs
// more global parameters
F32 XZ_TOL = 0.0f;
F32 DEPTH_TOL = 0.01f;
#define MIN_Y_DOT 0.05f
DepthSortList * gCurrentSort = NULL;
S32 gForceOverlap = -1; // force an overlap test to result in an overlap
S32 gNoOverlapCount;
S32 gBadSpots = 0;
// if polys are correctly sorted then writing depth values should result in no
// overlap of polys when looking down from camera...otoh, if polys are out of
// order, we should see overlap
bool DepthSortList::renderWithDepth = false;
//----------------------------------------------------------------------------
// following copied from shapeBase.cc because I didn't want to reference
// something from the test program in the core. This should really be a
// stand-alone function, but...
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 DepthSortList::wireCube(const Point3F& size, const 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.x + pos.x, cubePoints[idx].y * size.y + pos.y, cubePoints[idx].z * size.z + pos.z);
}
glEnd();
}
}
//----------------------------------------------------------------------------
DepthSortList::DepthSortList()
{
VECTOR_SET_ASSOCIATION(mPolyExtentsList);
VECTOR_SET_ASSOCIATION(mPolyIndexList);
}
DepthSortList::~DepthSortList()
{
}
//----------------------------------------------------------------------------
void DepthSortList::clear()
{
Parent::clear();
mPolyExtentsList.clear();
mPolyIndexList.clear();
clearSort();
}
void DepthSortList::clearSort()
{
mBase = -1;
mMaxTouched = 0;
gNoOverlapCount = 0;
}
//----------------------------------------------------------------------------
void DepthSortList::end()
{
S32 numPoly = mPolyList.size();
if (mPolyList.last().plane.y >= -MIN_Y_DOT)
{
mIndexList.setSize(mPolyList.last().vertexStart);
mPolyList.decrement();
return;
}
Parent::end();
// we deleted this poly, be sure not to add anything more...
if (mPolyList.size()!=numPoly)
return;
AssertFatal(mPolyList.last().vertexCount>2,"DepthSortList::end: only two vertices in poly");
mPolyExtentsList.increment();
setExtents(mPolyList.last(),mPolyExtentsList.last());
mPolyIndexList.push_back(numPoly-1);
}
//----------------------------------------------------------------------------
bool DepthSortList::getMapping(MatrixF * mat, Box3F * box)
{
// return list transform and bounds in list space...optional
*mat = mMatrix;
mat->inverse();
box->min.set(-mExtent.x, 0.0f, -mExtent.z);
box->max.set( mExtent.x, 2.0f * mExtent.y, mExtent.z);
return true;
}
//----------------------------------------------------------------------------
void DepthSortList::render()
{
glPushMatrix();
glPushAttrib(GL_DEPTH_BUFFER_BIT);
MatrixF mat = mBaseMatrix;
mat.inverse();
dglMultMatrix(&mat);
glVertexPointer(3,GL_FLOAT,sizeof(Vertex),mVertexList.address());
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1,0,0,0.25);
glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
if (renderWithDepth)
glDepthMask(GL_TRUE);
else
glDepthMask(GL_FALSE);
glDisable(GL_CULL_FACE);
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
Poly * p;
S32 only = Con::getIntVariable("$only",-1);
for (S32 i=0; i<mPolyIndexList.size(); i++)
{
if (only>=0 && only!=i)
continue;
p = &mPolyList[mPolyIndexList[i]];
glDrawElements(GL_POLYGON,p->vertexCount,
GL_UNSIGNED_INT,&mIndexList[p->vertexStart]);
}
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
glDisableClientState(GL_VERTEX_ARRAY);
// draw outline around clip zone...
wireCube(mExtent,Point3F(0,mExtent.y*0.5f,0));
glPopMatrix();
glPopAttrib();
}
//----------------------------------------------------------------------------
void DepthSortList::setExtents(Poly & poly, PolyExtents & polyExtents)
{
Point3F p = mVertexList[mIndexList[poly.vertexStart]].point;
polyExtents.xMin = polyExtents.xMax = p.x;
polyExtents.yMin = polyExtents.yMax = p.y;
polyExtents.zMin = polyExtents.zMax = p.z;
for (S32 i=poly.vertexStart+1; i<poly.vertexStart+poly.vertexCount; i++)
{
Point3F p = mVertexList[mIndexList[i]].point;
// x
if (p.x < polyExtents.xMin)
polyExtents.xMin = p.x;
else if (p.x > polyExtents.xMax)
polyExtents.xMax = p.x;
// y
if (p.y < polyExtents.yMin)
polyExtents.yMin = p.y;
else if (p.y > polyExtents.yMax)
polyExtents.yMax = p.y;
// z
if (p.z < polyExtents.zMin)
polyExtents.zMin = p.z;
else if (p.z > polyExtents.zMax)
polyExtents.zMax = p.z;
}
}
//----------------------------------------------------------------------------
// function for comparing two poly indices
S32 FN_CDECL compareYExtents( const void* e1, const void* e2)
{
DepthSortList::PolyExtents & poly1 = gCurrentSort->getExtents(*(U32*)e1);
DepthSortList::PolyExtents & poly2 = gCurrentSort->getExtents(*(U32*)e2);
if (poly1.yMin < poly2.yMin)
return -1;
if (poly2.yMin < poly1.yMin)
return 1;
return 0;
}
//----------------------------------------------------------------------------
void DepthSortList::sortByYExtents()
{
gCurrentSort = this;
dQsort(mPolyIndexList.address(),mPolyIndexList.size(),sizeof(U32),compareYExtents);
}
//----------------------------------------------------------------------------
void DepthSortList::set(const MatrixF & mat, Point3F & extents)
{
setBaseTransform(mat);
mNormal.set(0,1,0); // ignore polys not facing up...
mExtent = extents;
mExtent *= 0.5f;
// set clip planes
mPlaneList.clear();
mPlaneList.increment();
mPlaneList.last().set(-1.0f, 0.0f, 0.0f);
mPlaneList.last().d = -mExtent.x;
mPlaneList.increment();
mPlaneList.last().set( 1.0f, 0.0f, 0.0f);
mPlaneList.last().d = -mExtent.x;
mPlaneList.increment();
mPlaneList.last().set( 0.0f,-1.0f, 0.0f);
mPlaneList.last().d = 0;
mPlaneList.increment();
mPlaneList.last().set( 0.0f, 1.0f, 0.0f);
mPlaneList.last().d = -2.0f * mExtent.y;
mPlaneList.increment();
mPlaneList.last().set( 0.0f, 0.0f,-1.0f);
mPlaneList.last().d = -mExtent.z;
mPlaneList.increment();
mPlaneList.last().set( 0.0f, 0.0f, 1.0f);
mPlaneList.last().d = -mExtent.z;
}
//----------------------------------------------------------------------------
void DepthSortList::setBase(S32 base)
{
mBase = base;
getOrderedPoly(mBase, &mBasePoly, &mBaseExtents);
mBaseNormal = &mBasePoly->plane;
mBaseDot = -mBasePoly->plane.d;
mBaseYMax = mBaseExtents->yMax;
}
//----------------------------------------------------------------------------
bool DepthSortList::sortNext()
{
// find the next poly in the depth order
// NOTE: a closer poly may occur before a farther away poly so long as
// they don't overlap in the x-z plane...
if (++mBase>=mPolyIndexList.size())
return false;
setBase(mBase);
gBadSpots = 0;
ALWAYS_RETURN_FRONT_AND_BACK = false; // split routine will return polys only if a split occurs
bool switched = false; // haven't switched base poly yet
S32 i = 0; // currently comparing base to base+i
Poly * testPoly;
PolyExtents * testExtents;
while (mBase+i+1<mPolyIndexList.size())
{
i++;
// get test poly...
getOrderedPoly(mBase+i,&testPoly,&testExtents);
Point3F & testNormal = testPoly->plane;
F32 testDot = -testPoly->plane.d;
// if base poly's y extents don't overlap test poly's, base poly can stay where it is...
if (mBase+i>mMaxTouched && mBaseYMax<=testExtents->yMin+DEPTH_TOL)
break;
// if base poly and test poly don't have overlapping x & z extents, then order doesn't matter...stay the same
if (mBaseExtents->xMin>=testExtents->xMax-XZ_TOL || mBaseExtents->xMax<=testExtents->xMin+XZ_TOL ||
mBaseExtents->zMin>=testExtents->zMax-XZ_TOL || mBaseExtents->zMax<=testExtents->zMin+XZ_TOL)
continue;
// is test poly completely behind base poly? if so, order is fine as it is
S32 v;
for (v=0; v<testPoly->vertexCount; v++)
if (mDot(mVertexList[mIndexList[testPoly->vertexStart+v]].point,*mBaseNormal)>mBaseDot+DEPTH_TOL)
break;
if (v==testPoly->vertexCount)
// test behind base
continue;
// is base poly completely in front of test poly? if so, order is fine as it is
for (v=0; v<mBasePoly->vertexCount; v++)
if (mDot(mVertexList[mIndexList[mBasePoly->vertexStart+v]].point,testNormal)<testDot-DEPTH_TOL)
break;
if (v==mBasePoly->vertexCount)
// base in front of test
continue;
// if the polys don't overlap in the x-z plane, then order doesn't matter, stay as we are
if (!overlap(mBasePoly,testPoly))
{
gNoOverlapCount++;
if (gNoOverlapCount!=gForceOverlap)
continue;
}
// handle switching/splitting of polys due to overlap
handleOverlap(testPoly,testNormal,testDot,i,switched);
}
return true;
}
//----------------------------------------------------------------------------
void DepthSortList::sort()
{
// depth sort mPolyIndexList -- entries are indices into mPolyList (where poly is found) & mPolyExtentsList
// sort by min y extent
sortByYExtents();
mBase = -1;
while (sortNext())
;
}
//----------------------------------------------------------------------------
void DepthSortList::handleOverlap(Poly * testPoly, Point3F & testNormal, F32 testDot, S32 & testOffset, bool & switched)
{
// first reverse the plane tests (i.e., test to see if basePoly behind testPoly or testPoly in front of basePoly...
// if either succeeds, switch poly
// if they both fail, split base poly
// But split anyway if basePoly has already been switched...
bool doSwitch = false;
if (!switched)
{
S32 v;
for (v=0; v<mBasePoly->vertexCount; v++)
if (mDot(mVertexList[mIndexList[mBasePoly->vertexStart+v]].point,testNormal)>testDot+DEPTH_TOL)
break;
if (v==mBasePoly->vertexCount)
doSwitch = true;
else
{
for (v=0; v<testPoly->vertexCount; v++)
if (mDot(mVertexList[mIndexList[testPoly->vertexStart+v]].point,*mBaseNormal)<mBaseDot-DEPTH_TOL)
break;
if (v==testPoly->vertexCount)
doSwitch = true;
}
}
// try to split base poly along plane of test poly
Poly frontPoly, backPoly;
bool splitBase = false, splitTest = false;
if (!doSwitch)
{
splitBase = splitPoly(*mBasePoly,testNormal,testDot,frontPoly,backPoly);
if (!splitBase)
// didn't take...no splitting happened...try splitting test poly by base poly
splitTest = splitPoly(*testPoly,*mBaseNormal,mBaseDot,frontPoly,backPoly);
}
U32 testIdx = mPolyIndexList[mBase+testOffset];
// should we switch order of test and base poly? Might have to even if we
// don't want to if there's no splitting to do...
// Note: possibility that infinite loop can be introduced here...if that happens,
// then we need to split along edges of polys
if (doSwitch || (!splitTest && !splitBase))
{
if (!doSwitch && gBadSpots++ > (mPolyIndexList.size()-mBase)<<1)
// got here one too many times...just leave and don't touch poly -- avoid infinite loop
return;
// move test poly to the front of the order
dMemmove(&mPolyIndexList[mBase+1],&mPolyIndexList[mBase],testOffset*sizeof(U32));
mPolyIndexList[mBase] = testIdx;
// base poly changed...
setBase(mBase);
if (mBase+testOffset>mMaxTouched)
mMaxTouched=mBase+testOffset;
testOffset=1; // don't need to compare against old base
switched=true;
return;
}
if (splitBase)
{
// need one more spot...frontPoly and backPoly replace basePoly
setExtents(frontPoly,mPolyExtentsList[mPolyIndexList[mBase]]);
mPolyExtentsList.increment();
setExtents(backPoly,mPolyExtentsList.last());
mPolyList[mPolyIndexList[mBase]] = frontPoly;
mPolyIndexList.insert(mBase+1);
mPolyIndexList[mBase+1] = mPolyList.size();
mPolyList.push_back(backPoly);
// new base poly...
setBase(mBase);
// increase tsetOffset & mMaxTouched because of insertion of back poly
testOffset++;
mMaxTouched++;
//
switched=false;
return;
}
// splitTest -- no other way to get here
AssertFatal(splitTest,"DepthSortList::handleOverlap: how did we get here like this?");
// put frontPoly in front of basePoly, leave backPoly where testPoly was
// we need one more spot (testPoly broken into front and back)
// and we need to shift everything from base up to test down one spot
mPolyIndexList.insert(mBase);
// need one more poly for front poly
mPolyIndexList[mBase] = mPolyList.size();
mPolyList.push_back(frontPoly);
mPolyExtentsList.increment();
setExtents(mPolyList.last(),mPolyExtentsList.last());
// set up back poly
mPolyList[testIdx] = backPoly;
setExtents(mPolyList[testIdx],mPolyExtentsList[testIdx]);
// new base poly...
setBase(mBase);
// we inserted an element, increase mMaxTouched...
mMaxTouched++;
testOffset=0;
switched = false;
}
//----------------------------------------------------------------------------
bool DepthSortList::overlap(Poly * poly1, Poly * poly2)
{
// check for overlap without any shortcuts
S32 sz1 = poly1->vertexCount;
S32 sz2 = poly2->vertexCount;
Point3F * a1, * b1;
Point3F * a2, * b2;
Point2F norm;
F32 dot;
b1 = &mVertexList[mIndexList[poly1->vertexStart+sz1-1]].point;
S32 i;
for (i=0; i<sz1; i++)
{
a1 = b1;
b1 = &mVertexList[mIndexList[poly1->vertexStart+i]].point;
// get the mid-point of this edge
Point3F mid1 = *a1+*b1;
mid1 *= 0.5f;
bool midOutside = false;
b2 = &mVertexList[mIndexList[poly2->vertexStart+sz2-1]].point;
for (S32 j=0; j<sz2; j++)
{
a2 = b2;
b2 = &mVertexList[mIndexList[poly2->vertexStart+j]].point;
// test for intersection of a1-b1 and a2-b2 (on x-z plane)
// they intersect if a1 & b1 are on opp. sides of line a2-b2
// and a2 & b2 are on opp. sides of line a1-b1
norm.set(a2->z - b2->z, b2->x - a2->x); // normal to line a2-b2
dot = norm.x * a2->x + norm.y * a2->z; // dot of a2 and norm
if (norm.x * mid1.x + norm.y * mid1.z - dot >= 0) // special check for midpoint of line
midOutside = true;
if ( ((norm.x * a1->x + norm.y * a1->z) - dot) * ((norm.x * b1->x + norm.y * b1->z) - dot) >= 0 )
// a1 & b1 are on the same side of the line a2-b2...edges don't overlap
continue;
norm.set(a1->z - b1->z, b1->x - a1->x); // normal to line a1-b1
dot = norm.x * a1->x + norm.y * a1->z; // dot of a1 and norm
if ( ((norm.x * a2->x + norm.y * a2->z) - dot) * ((norm.x * b2->x + norm.y * b2->z) - dot) >= 0 )
// a2 & b2 are on the same side of the line a1-b1...edges don't overlap
continue;
return true; // edges overlap, so polys overlap
}
if (!midOutside)
return true; // midpoint of a1-b1 is inside the poly
}
// edges don't overlap...but one poly might be contained inside the other
Point3F center = mVertexList[mIndexList[poly2->vertexStart]].point;
for (i=1; i<sz2; i++)
center += mVertexList[mIndexList[poly2->vertexStart+i]].point;
center *= 1.0f / (F32)poly2->vertexCount;
b1 = &mVertexList[mIndexList[poly1->vertexStart+sz1-1]].point;
for (i=0; i<sz1; i++)
{
a1 = b1;
b1 = &mVertexList[mIndexList[poly1->vertexStart+i]].point;
norm.set(a1->z - b1->z, b1->x - a1->x); // normal to line a1-b1
dot = norm.x * a1->x + norm.y * a1->z; // dot of a1 and norm
if (center.x * norm.x + center.z * norm.y > dot)
// center is outside this edge, poly2 is not inside poly1
break;
}
if (i==sz1)
return true; // first vert of poly2 inside poly1 (so all of poly2 inside poly1)
center = mVertexList[mIndexList[poly1->vertexStart]].point;
for (i=1; i<sz1; i++)
center += mVertexList[mIndexList[poly1->vertexStart+i]].point;
center *= 1.0f / (F32)poly1->vertexCount;
b2 = &mVertexList[mIndexList[poly2->vertexStart+sz2-1]].point;
for (i=0; i<sz2; i++)
{
a2 = b2;
b2 = &mVertexList[mIndexList[poly2->vertexStart+i]].point;
norm.set(a2->z - b2->z, b2->x - a2->x); // normal to line a2-b2
dot = norm.x * a2->x + norm.y * a2->z; // dot of a1 and norm
if (center.x * norm.x + center.z * norm.y > dot)
// v is outside this edge, poly1 is not inside poly2
break;
}
if (i==sz2)
return true; // first vert of poly1 inside poly2 (so all of poly1 inside poly2)
return false; // we survived, no overlap
}
//----------------------------------------------------------------------------
Vector<U32> frontVerts(__FILE__, __LINE__);
Vector<U32> backVerts(__FILE__, __LINE__);
// Split source poly into front and back. If either front or back is degenerate, don't do anything.
// If we have a front and a back, then add the verts to our vertex list and fill out the poly structures.
bool DepthSortList::splitPoly(const Poly & src, Point3F & normal, F32 k, Poly & frontPoly, Poly & backPoly)
{
frontVerts.clear();
backVerts.clear();
// already degenerate...
AssertFatal(src.vertexCount>=3,"DepthSortList::splitPoly - Don't need to split a triangle!");
S32 startSize = mVertexList.size();
// Assume back and front are degenerate polygons until proven otherwise.
bool backDegen = true, frontDegen = true;
U32 bIdx;
Point3F * a, * b;
F32 dota, dotb;
S32 signA, signB;
F32 splitTolSq = SPLIT_TOL * SPLIT_TOL * mDot(normal,normal);
bIdx = mIndexList[src.vertexStart+src.vertexCount-1];
b = &mVertexList[bIdx].point;
dotb = mDot(normal,*b)-k;
// Sign variable coded as follows: 1 for outside, 0 on the plane and -1 for inside.
if (dotb*dotb > splitTolSq)
signB = dotb > 0.0f ? 1 : -1;
else
signB = 0;
S32 i;
for (i = 0; i<src.vertexCount; i++)
{
a = b;
bIdx = mIndexList[src.vertexStart+i];
b = &mVertexList[bIdx].point;
dota = dotb;
dotb = mDot(normal,*b)-k;
signA = signB;
if (dotb*dotb > splitTolSq)
signB = dotb > 0.0f ? 1 : -1;
else
signB = 0;
switch(signA*3 + signB + 4) // +4 is to make values go from 0 up...hopefully enticing compiler to make a jump-table
{
case 0: // A-, B-
case 3: // A., B-
backVerts.push_back(bIdx);
backDegen = false;
break;
case 8: // A+, B+
case 5: // A., B+
frontVerts.push_back(bIdx);
frontDegen = false;
break;
case 1: // A-, B.
case 4: // A., B.
case 7: // A+, B.
backVerts.push_back(bIdx);
frontVerts.push_back(bIdx);
break;
case 2: // A-, B+
{
// intersect line A-B with plane
F32 dotA = mDot(*a,normal);
F32 dotB = mDot(*b,normal);
Vertex v;
v.point = *a-*b;
v.point *= (k-dotB)/(dotA-dotB);
v.point += *b;
frontVerts.push_back(mVertexList.size());
backVerts.push_back(mVertexList.size());
frontVerts.push_back(bIdx);
mVertexList.push_back(v);
b = &mVertexList[bIdx].point; // better get this pointer again since we just incremented vector
frontDegen = false;
break;
}
case 6: // A+, B-
{
// intersect line A-B with plane
F32 dotA = mDot(*a,normal);
F32 dotB = mDot(*b,normal);
Vertex v;
v.point = *a-*b;
v.point *= (k-dotB)/(dotA-dotB);
v.point += *b;
frontVerts.push_back(mVertexList.size());
backVerts.push_back(mVertexList.size());
backVerts.push_back(bIdx);
mVertexList.push_back(v);
b = &mVertexList[bIdx].point; // better get this pointer again since we just incremented vector
backDegen = false;
break;
}
}
}
// Check for degeneracy.
if (!ALWAYS_RETURN_FRONT_AND_BACK)
{
if (frontVerts.size()<3 || backVerts.size()<3 || frontDegen || backDegen)
{
// didn't need to be split...return two empty polys
// and restore vertex list to how it was
mVertexList.setSize(startSize);
frontPoly.vertexCount = backPoly.vertexCount = 0;
return false;
}
}
else
{
if (frontDegen)
frontVerts.clear();
if (backDegen)
backVerts.clear();
}
// front poly
frontPoly.plane = src.plane;
frontPoly.object = src.object;
frontPoly.material = src.material;
frontPoly.vertexStart = mIndexList.size();
frontPoly.vertexCount = frontVerts.size();
frontPoly.surfaceKey = src.surfaceKey;
frontPoly.polyFlags = src.polyFlags;
// back poly
backPoly.plane = src.plane;
backPoly.object = src.object;
backPoly.material = src.material;
backPoly.vertexStart = frontPoly.vertexStart + frontPoly.vertexCount;
backPoly.vertexCount = backVerts.size();
backPoly.surfaceKey = src.surfaceKey;
backPoly.polyFlags = src.polyFlags;
// add indices
mIndexList.setSize(backPoly.vertexStart+backPoly.vertexCount);
if( frontPoly.vertexCount ) {
dMemcpy(&mIndexList[frontPoly.vertexStart],
frontVerts.address(),
sizeof(U32)*frontPoly.vertexCount);
}
if( backPoly.vertexCount ) {
dMemcpy(&mIndexList[backPoly.vertexStart],
backVerts.address(),
sizeof(U32)*backPoly.vertexCount);
}
return true;
}
//----------------------------------------------------------------------------
Vector<DepthSortList::Poly> gWorkListA(256);
Vector<DepthSortList::Poly> gWorkListB(256);
Vector<DepthSortList::Poly> gWorkListJunkBin(256);
void DepthSortList::depthPartition(const Point3F * sourceVerts, U32 numVerts, Vector<Poly> & partition, Vector<Point3F> & partitionVerts)
{
// create the depth partition of the passed poly
// a depth partition is a partition of the poly on the
// x-z plane so that each sub-poly in the partition can be
// mapped onto exactly one plane in the depth list (i.e.,
// those polys found in mPolyIndexList... the ones that are
// depth sorted). The plane the sub-polys are mapped onto
// is the plane of the closest facing poly.
//
// y-coord of input polys are ignored, and are remapped
// on output to put the output polys on the
// corresponding planes.
// This routine is confusing because there are three lists of polys.
//
// The source list (passed in as a single poly, but becomes a list as
// it is split up) comprises the poly to be partitioned. Verts for sourcePoly
// are held in sourceVerts when passed to this routine, but immediately copied
// to mVertexList (and indices are added for each vert to mIndexList).
//
// The scraps list is generated from the source poly (it contains the outside
// piece of each cut that is made). Indices for polys in the scraps list are
// found in mIndexList and verts are found in mVerts list. Note that the depthPartition
// routine will add verts and indices to the member lists, but not polys.
//
// Finally, the partition list is the end result -- the depth partition. These
// polys are not indexed polys. The vertexStart field indexes directly into partitionVerts
// array.
if (mBase<0)
// begin the depth sort
sortByYExtents();
// apply cookie cutter to these polys
Vector<Poly> * sourceList = &gWorkListA;
sourceList->clear();
// add source poly for to passed verts
sourceList->increment();
sourceList->last().vertexStart = mIndexList.size();
sourceList->last().vertexCount = numVerts;
// add verts of source poly to mVertexList and mIndexList
mVertexList.setSize(mVertexList.size()+numVerts);
mIndexList.setSize(mIndexList.size()+numVerts);
for (S32 v=0; v<numVerts; v++)
{
mVertexList[mVertexList.size()-numVerts+v].point = sourceVerts[v];
mIndexList[mIndexList.size()-numVerts+v] = mVertexList.size()-numVerts+v;
}
// put scraps from cookie cutter in this list
Vector<Poly> * scraps = &gWorkListB;
scraps->clear();
gWorkListJunkBin.clear();
S32 i=0;
while (sourceList->size())
{
if (i>=mBase && !sortNext())
// that's it, no more polys to sort
break;
AssertFatal(i<=mBase,"DepthSortList::depthPartition - exceeded mBase.");
// use the topmost poly as the cookie cutter
Poly & cutter = mPolyList[mPolyIndexList[i]];
S32 startVert = partitionVerts.size();
bool allowclipping = cutter.polyFlags & CLIPPEDPOLYLIST_FLAG_ALLOWCLIPPING;
S32 j;
for (j=0; j<sourceList->size(); j++)
{
Poly toCut = (*sourceList)[j];
if(allowclipping)
cookieCutter(cutter,toCut,*scraps,partition,partitionVerts);
else
cookieCutter(cutter,toCut,gWorkListJunkBin,partition,partitionVerts);
}
// project all the new verts onto the cutter's plane
AssertFatal(mFabs(cutter.plane.y)>=MIN_Y_DOT,"DepthSortList::depthPartition - below MIN_Y_DOT.");
F32 invY = -1.0f / cutter.plane.y;
for (j=startVert; j<partitionVerts.size(); j++)
partitionVerts[j].y = invY * (cutter.plane.d + cutter.plane.x * partitionVerts[j].x + cutter.plane.z * partitionVerts[j].z);
if(allowclipping)
{
sourceList->clear();
// swap work lists -- scraps become source for next closest poly
Vector<Poly> * tmpListPtr = sourceList;
sourceList = scraps;
scraps = tmpListPtr;
}
i++;
}
}
//----------------------------------------------------------------------------
void DepthSortList::cookieCutter(Poly & cutter, Poly & cuttee,
Vector<Poly> & scraps, // outsides
Vector<Poly> & cookies, Vector<Point3F> & cookieVerts) // insides
{
// perhaps going too far with the cookie cutter analogy, but...
// cutter is used to cut cuttee
//
// the part that is inside of all cutter edges (on x-z plane)
// is put into the "cookie" list, parts that are outside are put
// onto the "scraps" list. "scraps" are indexed polys with indices
// and vertices in mIndexList and mVertexList. Cookies aren't indexed
// and points go into cookieVerts list.
ALWAYS_RETURN_FRONT_AND_BACK = true; // split routine will return polys even if no split occurs
// save off current state in case nothing inside all the edges of cutter (i.e., no "cookie")
S32 vsStart = cuttee.vertexStart;
S32 vcStart = cuttee.vertexCount;
S32 milStart = mIndexList.size();
S32 mvlStart = mVertexList.size();
S32 scStart = scraps.size();
Point3F a, b;
Poly scrap;
a = mVertexList[mIndexList[cutter.vertexStart+cutter.vertexCount-1]].point;
for (S32 i=0; i<cutter.vertexCount; i++)
{
b = mVertexList[mIndexList[cutter.vertexStart+i]].point;
Point3F n(a.z-b.z,0.0f,b.x-a.x);
F32 k = mDot(n,a);
splitPoly(cuttee,n,k,scrap,cuttee);
if (scrap.vertexCount)
scraps.push_back(scrap);
if (!cuttee.vertexCount)
// cut down to nothing...no need to keep cutting
break;
a = b;
}
if (cuttee.vertexCount)
{
// cuttee is non-degenerate, add it to cookies
cookies.push_back(cuttee);
cookies.last().vertexStart = cookieVerts.size();
for (S32 i=0; i<cuttee.vertexCount; i++)
cookieVerts.push_back(mVertexList[mIndexList[cuttee.vertexStart+i]].point);
}
else
{
// no cookie -- leave things as they were (except add cuttee to scraps)
cuttee.vertexStart = vsStart;
cuttee.vertexCount = vcStart;
mIndexList.setSize(milStart);
mVertexList.setSize(mvlStart);
scraps.setSize(scStart);
scraps.push_back(cuttee);
}
}
//----------------------------------------------------------------------------

108
engine/collision/depthSortList.h Executable file
View File

@ -0,0 +1,108 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _DEPTHSORTLIST_H_
#define _DEPTHSORTLIST_H_
#ifndef _CLIPPEDPOLYLIST_H_
#include "collision/clippedPolyList.h"
#endif
//----------------------------------------------------------------------------
class DepthSortList : public ClippedPolyList
{
typedef ClippedPolyList Parent;
public:
struct PolyExtents
{
// extents of poly on each coordinate axis
F32 xMin;
F32 xMax;
F32 yMin;
F32 yMax;
F32 zMin;
F32 zMax;
};
typedef Vector<PolyExtents> PolyExtentsList;
typedef Vector<U32> PolyIndexList;
// Internal data
PolyExtentsList mPolyExtentsList;
PolyIndexList mPolyIndexList;
Point3F mExtent; // dimensions of the sort area
S32 mBase; // base position in the list...everything before this is sorted correctly
Poly * mBasePoly; // poly currently in base position
Point3F * mBaseNormal; // normal of poly currently in base position
F32 mBaseDot; // dot of basePoly with baseNormal
F32 mBaseYMax; // max y extent of base poly
S32 mMaxTouched; // highest index swapped into thus far...y-extents may be improperly sorted before this index
PolyExtents * mBaseExtents; // x,y,z extents of basePoly
// set the base position -- everything before this point should be correctly sorted
void setBase(S32);
// some utilities used for sorting
bool splitPoly(const Poly & sourcePoly, Point3F & normal, F32 k, Poly & front, Poly & back);
bool overlap(Poly *, Poly *);
void handleOverlap(Poly * testPoly, Point3F & testNormal, F32 testDot, S32 & testOffset, bool & switched);
void sortByYExtents();
void setExtents(Poly &, PolyExtents &);
// one iteration of the sort routine -- finds a poly to fill current base position
bool sortNext();
// used by depthPartition
void cookieCutter(Poly & cutter, Poly & cuttee,
Vector<Poly> & scraps,
Vector<Poly> & cookies, Vector<Point3F> & cookieVerts);
void wireCube(const Point3F & size, const Point3F & pos);
public:
//
DepthSortList();
~DepthSortList();
void set(const MatrixF & mat, Point3F & extents);
void clear();
void clearSort();
// the point of this class
void sort();
void depthPartition(const Point3F * sourceVerts, U32 numVerts, Vector<Poly> & partition, Vector<Point3F> & partitionVerts);
// Virtual methods
void end();
// U32 addPoint(const Point3F& p);
// bool isEmpty() const;
// void begin(U32 material,U32 surfaceKey);
// void plane(U32 v1,U32 v2,U32 v3);
// void plane(const PlaneF& p);
// void vertex(U32 vi);
bool getMapping(MatrixF *, Box3F *);
// access to the polys in order (note: returned pointers are volatile, may change if polys added).
void getOrderedPoly(U32 ith, Poly ** poly, PolyExtents ** polyExtent);
// unordered access
PolyExtents & getExtents(U32 idx) { return mPolyExtentsList[idx]; }
Poly & getPoly(U32 idx) { return mPolyList[idx]; }
//
void render();
static bool renderWithDepth;
};
inline void DepthSortList::getOrderedPoly(U32 ith, Poly ** poly, PolyExtents ** polyExtent)
{
*poly = &mPolyList[mPolyIndexList[ith]];
*polyExtent = &mPolyExtentsList[mPolyIndexList[ith]];
}
#endif

View File

@ -0,0 +1,267 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/earlyOutPolyList.h"
//----------------------------------------------------------------------------
EarlyOutPolyList::EarlyOutPolyList()
{
VECTOR_SET_ASSOCIATION(mPolyList);
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mIndexList);
VECTOR_SET_ASSOCIATION(mPolyPlaneList);
VECTOR_SET_ASSOCIATION(mPlaneList);
mNormal.set(0, 0, 0);
mIndexList.reserve(100);
mEarlyOut = false;
}
EarlyOutPolyList::~EarlyOutPolyList()
{
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::clear()
{
// Only clears internal data
mPolyList.clear();
mVertexList.clear();
mIndexList.clear();
mPolyPlaneList.clear();
mEarlyOut = false;
}
bool EarlyOutPolyList::isEmpty() const
{
return mEarlyOut == false;
}
//----------------------------------------------------------------------------
U32 EarlyOutPolyList::addPoint(const Point3F& p)
{
if (mEarlyOut == true)
return 0;
mVertexList.increment();
Vertex& v = mVertexList.last();
v.point.x = p.x * mScale.x;
v.point.y = p.y * mScale.y;
v.point.z = p.z * mScale.z;
mMatrix.mulP(v.point);
// Build the plane mask
v.mask = 0;
for (U32 i = 0; i < mPlaneList.size(); i++)
if (mPlaneList[i].distToPlane(v.point) > 0)
v.mask |= 1 << i;
// If the point is inside all the planes, then we're done!
if (v.mask == 0)
mEarlyOut = true;
return mVertexList.size() - 1;
}
U32 EarlyOutPolyList::addPlane(const PlaneF& plane)
{
mPolyPlaneList.increment();
mPlaneTransformer.transform(plane, mPolyPlaneList.last());
return mPolyPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::begin(U32 material,U32 surfaceKey)
{
if (mEarlyOut == true)
return;
mPolyList.increment();
Poly& poly = mPolyList.last();
poly.object = mCurrObject;
poly.material = material;
poly.vertexStart = mIndexList.size();
poly.surfaceKey = surfaceKey;
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::plane(U32 v1,U32 v2,U32 v3)
{
if (mEarlyOut == true)
return;
mPolyList.last().plane.set(mVertexList[v1].point,
mVertexList[v2].point,mVertexList[v3].point);
}
void EarlyOutPolyList::plane(const PlaneF& p)
{
if (mEarlyOut == true)
return;
mPlaneTransformer.transform(p, mPolyList.last().plane);
}
void EarlyOutPolyList::plane(const U32 index)
{
if (mEarlyOut == true)
return;
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
mPolyList.last().plane = mPolyPlaneList[index];
}
const PlaneF& EarlyOutPolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
return mPolyPlaneList[index];
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::vertex(U32 vi)
{
if (mEarlyOut == true)
return;
mIndexList.push_back(vi);
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::end()
{
if (mEarlyOut == true)
return;
Poly& poly = mPolyList.last();
// Anything facing away from the mNormal is rejected
if (mDot(poly.plane,mNormal) > 0) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
// Build intial inside/outside plane masks
U32 indexStart = poly.vertexStart;
U32 vertexCount = mIndexList.size() - indexStart;
U32 frontMask = 0,backMask = 0;
U32 i;
for (i = indexStart; i < mIndexList.size(); i++) {
U32 mask = mVertexList[mIndexList[i]].mask;
frontMask |= mask;
backMask |= ~mask;
}
// Trivial accept if all the vertices are on the backsides of
// all the planes.
if (!frontMask) {
poly.vertexCount = vertexCount;
mEarlyOut = true;
return;
}
// Trivial reject if any plane not crossed has all it's points
// on the front.
U32 crossMask = frontMask & backMask;
if (~crossMask & frontMask) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
// Need to do some clipping
for (U32 p = 0; p < mPlaneList.size(); p++) {
U32 pmask = 1 << p;
// Only test against this plane if we have something
// on both sides
if (crossMask & pmask) {
U32 indexEnd = mIndexList.size();
U32 i1 = indexEnd - 1;
U32 mask1 = mVertexList[mIndexList[i1]].mask;
for (U32 i2 = indexStart; i2 < indexEnd; i2++) {
U32 mask2 = mVertexList[mIndexList[i2]].mask;
if ((mask1 ^ mask2) & pmask) {
//
mVertexList.increment();
VectorF& v1 = mVertexList[mIndexList[i1]].point;
VectorF& v2 = mVertexList[mIndexList[i2]].point;
VectorF vv = v2 - v1;
F32 t = -mPlaneList[p].distToPlane(v1) / mDot(mPlaneList[p],vv);
mIndexList.push_back(mVertexList.size() - 1);
Vertex& iv = mVertexList.last();
iv.point.x = v1.x + vv.x * t;
iv.point.y = v1.y + vv.y * t;
iv.point.z = v1.z + vv.z * t;
iv.mask = 0;
// Test against the remaining planes
for (U32 i = p + 1; i < mPlaneList.size(); i++)
if (mPlaneList[i].distToPlane(iv.point) > 0) {
iv.mask = 1 << i;
break;
}
}
if (!(mask2 & pmask)) {
U32 index = mIndexList[i2];
mIndexList.push_back(index);
}
mask1 = mask2;
i1 = i2;
}
// Check for degenerate
indexStart = indexEnd;
if (mIndexList.size() - indexStart < 3) {
mIndexList.setSize(poly.vertexStart);
mPolyList.decrement();
return;
}
}
}
// If we reach here, then there's a poly!
mEarlyOut = true;
// Emit what's left and compress the index list.
poly.vertexCount = mIndexList.size() - indexStart;
memcpy(&mIndexList[poly.vertexStart],
&mIndexList[indexStart],poly.vertexCount);
mIndexList.setSize(poly.vertexStart + poly.vertexCount);
}
//----------------------------------------------------------------------------
void EarlyOutPolyList::memcpy(U32* dst, U32* src,U32 size)
{
U32* end = src + size;
while (src != end)
*dst++ = *src++;
}

View File

@ -0,0 +1,80 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _EARLYOUTPOLYLIST_H_
#define _EARLYOUTPOLYLIST_H_
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
/// Early out check PolyList
///
/// This class is used primarily for triggers and similar checks. It checks to see
/// if any of the geometry you feed it is inside its area, and if it is, it stops
/// checking for any more data and returns a true value. This is good if you want
/// to know if anything is in your "trigger" area, for instance.
///
/// @see AbstractPolyList
class EarlyOutPolyList : public AbstractPolyList
{
void memcpy(U32* d, U32* s,U32 size);
// Internal data
struct Vertex {
Point3F point;
U32 mask;
};
struct Poly {
PlaneF plane;
SceneObject* object;
U32 material;
U32 vertexStart;
U32 vertexCount;
U32 surfaceKey;
};
public:
typedef Vector<PlaneF> PlaneList;
private:
typedef Vector<Vertex> VertexList;
typedef Vector<Poly> PolyList;
typedef Vector<U32> IndexList;
PolyList mPolyList;
VertexList mVertexList;
IndexList mIndexList;
bool mEarlyOut;
PlaneList mPolyPlaneList;
public:
// Data set by caller
PlaneList mPlaneList;
VectorF mNormal;
public:
EarlyOutPolyList();
~EarlyOutPolyList();
void clear();
// Virtual methods
bool isEmpty() const;
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material,U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif // _H_EARLYOUTPOLYLIST_

View File

@ -0,0 +1,508 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/extrudedPolyList.h"
#include "collision/polyhedron.h"
#include "collision/collision.h"
// Minimum distance from a face
F32 ExtrudedPolyList::FaceEpsilon = 0.01f;
// Value used to compare collision times
F32 ExtrudedPolyList::EqualEpsilon = 0.0001f;
ExtrudedPolyList::ExtrudedPolyList()
{
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mIndexList);
VECTOR_SET_ASSOCIATION(mExtrudedList);
VECTOR_SET_ASSOCIATION(mPlaneList);
VECTOR_SET_ASSOCIATION(mPolyPlaneList);
mVelocity.set(0.0f,0.0f,0.0f);
mIndexList.reserve(128);
mVertexList.reserve(64);
mPolyPlaneList.reserve(64);
mPlaneList.reserve(64);
mCollisionList = 0;
}
ExtrudedPolyList::~ExtrudedPolyList()
{
}
//----------------------------------------------------------------------------
bool ExtrudedPolyList::isEmpty() const
{
return mCollisionList->count == 0;
}
//----------------------------------------------------------------------------
void ExtrudedPolyList::extrude(const Polyhedron& pt, const VectorF& vector)
{
// Clear state
mIndexList.clear();
mVertexList.clear();
mPlaneList.clear();
mPolyPlaneList.clear();
// Determine which faces will be extruded.
mExtrudedList.setSize(pt.planeList.size());
for (U32 f = 0; f < pt.planeList.size(); f++)
{
const PlaneF& face = pt.planeList[f];
ExtrudedFace& eface = mExtrudedList[f];
F32 dot = mDot(face,vector);
eface.active = dot > EqualEpsilon;
if (eface.active)
{
eface.maxDistance = dot;
eface.plane = face;
eface.planeMask = BIT(mPlaneList.size());
// Add the face as a plane to clip against.
mPlaneList.increment(2);
PlaneF* plane = mPlaneList.end() - 2;
plane[0] = plane[1] = face;
plane[0].invert();
}
}
// Produce extruded planes for bounding and internal edges
for (U32 e = 0; e < pt.edgeList.size(); e++)
{
Polyhedron::Edge const& edge = pt.edgeList[e];
ExtrudedFace& ef1 = mExtrudedList[edge.face[0]];
ExtrudedFace& ef2 = mExtrudedList[edge.face[1]];
if (ef1.active || ef2.active)
{
// Assumes that the edge points are clockwise
// for face[0].
const Point3F& p1 = pt.pointList[edge.vertex[1]];
const Point3F &p2 = pt.pointList[edge.vertex[0]];
Point3F p3 = p2 + vector;
mPlaneList.increment(2);
PlaneF* plane = mPlaneList.end() - 2;
plane[0].set(p3,p2,p1);
plane[1] = plane[0];
plane[1].invert();
U32 pmask = BIT(mPlaneList.size()-2);
ef1.planeMask |= pmask;
ef2.planeMask |= pmask << 1;
}
}
}
//----------------------------------------------------------------------------
void ExtrudedPolyList::setCollisionList(CollisionList* info)
{
mCollisionList = info;
mCollisionList->count = 0;
mCollisionList->t = 2;
}
//----------------------------------------------------------------------------
void ExtrudedPolyList::adjustCollisionTime()
{
if (!mCollisionList->count)
return;
mCollisionList->t = mClampF(mCollisionList->t, 0.f, 1.f);
}
//----------------------------------------------------------------------------
U32 ExtrudedPolyList::addPoint(const Point3F& p)
{
mVertexList.increment();
Vertex& v = mVertexList.last();
v.point.x = p.x * mScale.x;
v.point.y = p.y * mScale.y;
v.point.z = p.z * mScale.z;
mMatrix.mulP(v.point);
// Build the plane mask, planes come in pairs
v.mask = 0;
for (U32 i = 0; i < mPlaneList.size(); i ++)
if (mPlaneList[i].distToPlane(v.point) >= 0.f)
v.mask |= BIT(i);
return mVertexList.size() - 1;
}
U32 ExtrudedPolyList::addPlane(const PlaneF& plane)
{
mPolyPlaneList.increment();
mPlaneTransformer.transform(plane, mPolyPlaneList.last());
return mPolyPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void ExtrudedPolyList::begin(U32 material, U32 /*surfaceKey*/)
{
mPoly.object = mCurrObject;
mPoly.material = material;
mIndexList.clear();
}
void ExtrudedPolyList::plane(U32 v1, U32 v2, U32 v3)
{
mPoly.plane.set(mVertexList[v1].point,
mVertexList[v2].point,
mVertexList[v3].point);
// We hope this isn't needed but we're leaving it in anyway -- BJG/EGH
mPoly.plane.normalizeSafe();
}
void ExtrudedPolyList::plane(const PlaneF& p)
{
mPlaneTransformer.transform(p, mPoly.plane);
}
void ExtrudedPolyList::plane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
mPoly.plane = mPolyPlaneList[index];
}
const PlaneF& ExtrudedPolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
return mPolyPlaneList[index];
}
void ExtrudedPolyList::vertex(U32 vi)
{
mIndexList.push_back(vi);
}
void ExtrudedPolyList::end()
{
// Anything facing away from the mVelocity is rejected (and also
// cap to max collisions)
if (mDot(mPoly.plane, mNormalVelocity) > 0.f ||
mCollisionList->count >= CollisionList::MaxCollisions)
return;
// Test the built up poly (stored in mPoly) against all our extruded
// faces.
U32 cFaceCount = 0;
ExtrudedFace* cFace[30];
bool cEdgeColl[30];
ExtrudedFace* face = mExtrudedList.begin();
ExtrudedFace* end = mExtrudedList.end();
for (; face != end; face++)
{
// Skip inactive..
if (!face->active)
continue;
// Update the dot product.
face->faceDot = -mDot(face->plane,mPoly.plane);
// Skip it if we're facing towards...
if(face->faceDot <= 0.f)
continue;
// Test, and skip if colliding.
if (!testPoly(*face))
continue;
// Note collision.
cFace[cFaceCount] = face;
cEdgeColl[cFaceCount++] = false;
}
if (!cFaceCount)
{
face = mExtrudedList.begin();
end = mExtrudedList.end();
for (; face != end; face++)
{
// Don't need to do dot product second time, so just check if it's
// active (we already did the dot product in the previous loop).
if (!face->active)
continue;
// Skip it if we're facing away...
if(face->faceDot > 0.f)
continue;
// Do collision as above.
if (!testPoly(*face))
continue;
// Note the collision.
cFace[cFaceCount] = face;
cEdgeColl[cFaceCount++] = true;
}
}
// If we STILL don't have any collisions, just skip out.
if (!cFaceCount)
return;
// Pick the best collision face based on best alignment with respective
// face.
face = cFace[0];
bool edge = cEdgeColl[0];
for (U32 f = 1; f < cFaceCount; f++)
{
if (cFace[f]->faceDot <= face->faceDot)
continue;
face = cFace[f];
edge = cEdgeColl[f];
}
// Add it to the collision list if it's better and/or equal
// to our current best.
// Don't add it to the collision list if it's too far away.
if (face->time > mCollisionList->t + EqualEpsilon || face->time >= 1.0f)
return;
if (face->time < mCollisionList->t - EqualEpsilon)
{
// If this is significantly closer than before, then clear out the
// list, as it's a better match than the old stuff.
mCollisionList->t = face->time;
mCollisionList->count = 0;
mCollisionList->maxHeight = face->height;
}
else
{
// Otherwise, just update some book-keeping stuff.
if (face->height > mCollisionList->maxHeight)
mCollisionList->maxHeight = face->height;
}
// Note the collision in our collision list.
Collision& collision = mCollisionList->collision[mCollisionList->count++];
collision.point = face->point;
collision.faceDot = face->faceDot;
collision.face = face - mExtrudedList.begin();
collision.object = mPoly.object;
collision.normal = mPoly.plane;
collision.material = mPoly.material;
}
//----------------------------------------------------------------------------
bool ExtrudedPolyList::testPoly(ExtrudedFace& face)
{
// Build intial inside/outside plane masks
U32 indexStart = 0;
U32 indexEnd = mIndexList.size();
U32 oVertexSize = mVertexList.size();
U32 oIndexSize = mIndexList.size();
U32 frontMask = 0,backMask = 0;
for (U32 i = indexStart; i < indexEnd; i++)
{
U32 mask = mVertexList[mIndexList[i]].mask & face.planeMask;
frontMask |= mask;
backMask |= ~mask;
}
// Clip the mPoly against the planes that bound the face...
// Trivial accept if all the vertices are on the backsides of
// all the planes.
if (frontMask)
{
// Trivial reject if any plane not crossed has all it's points
// on the front.
U32 crossMask = frontMask & backMask;
if (~crossMask & frontMask)
return false;
// Need to do some clipping
for (U32 p=0; p < mPlaneList.size(); p++)
{
U32 pmask = BIT(p);
U32 newStart = mIndexList.size();
// Only test against this plane if we have something
// on both sides - otherwise skip.
if (!(face.planeMask & crossMask & pmask))
continue;
U32 i1 = indexEnd - 1;
U32 mask1 = mVertexList[mIndexList[i1]].mask;
for (U32 i2 = indexStart; i2 < indexEnd; i2++)
{
const U32 mask2 = mVertexList[mIndexList[i2]].mask;
if ((mask1 ^ mask2) & pmask)
{
// Clip the edge against the plane.
mVertexList.increment();
VectorF& v1 = mVertexList[mIndexList[i1]].point;
VectorF& v2 = mVertexList[mIndexList[i2]].point;
VectorF vv = v2 - v1;
F32 t = -mPlaneList[p].distToPlane(v1) / mDot(mPlaneList[p],vv);
mIndexList.push_back(mVertexList.size() - 1);
Vertex& iv = mVertexList.last();
iv.point.x = v1.x + vv.x * t;
iv.point.y = v1.y + vv.y * t;
iv.point.z = v1.z + vv.z * t;
iv.mask = 0;
// Test against the remaining planes
for (U32 i = p+1; i < mPlaneList.size(); i ++)
{
if (mPlaneList[i].distToPlane(iv.point) > 0.f)
iv.mask |= BIT(i);
}
}
if (!(mask2 & pmask))
{
U32 index = mIndexList[i2];
mIndexList.push_back(index);
}
mask1 = mask2;
i1 = i2;
}
// Check for degenerate
indexStart = newStart;
indexEnd = mIndexList.size();
if (mIndexList.size() - indexStart < 3)
{
mVertexList.setSize(oVertexSize);
mIndexList.setSize(oIndexSize);
return false;
}
}
}
// Find closest point on the mPoly
Point3F bp;
F32 bd = 1E30f;
F32 height = -1E30f;
for (U32 b = indexStart; b < indexEnd; b++)
{
Vertex& vertex = mVertexList[mIndexList[b]];
F32 dist = face.plane.distToPlane(vertex.point);
if (dist <= bd)
{
bd = (dist < 0.0f)? 0.0f: dist;
bp = vertex.point;
}
// Since we don't clip against the back plane, we'll
// only include vertex heights that are within range.
if (vertex.point.z > height && dist < face.maxDistance)
height = vertex.point.z;
}
// Do extruded points for back-off.
F32 oldBd=bd;
for (U32 b = indexStart; b < indexEnd; b++)
{
Vertex& vertex = mVertexList[mIndexList[b]];
// Extrude out just a tad to make sure we don't end up getting too close to the
// geometry and getting stuck - but cap it so we don't introduce error into long
// sweeps.
F32 dist = face.plane.distToPlane( vertex.point
+ Point3F(mPoly.plane) * getMin(face.maxDistance * 0.2f, 0.01f));
if (dist <= bd)
{
bd = (dist < 0.0f)? 0.0f: dist;
bp = vertex.point;
}
}
// Remove temporary data
mVertexList.setSize(oVertexSize);
mIndexList.setSize(oIndexSize);
// Don't add it to the collision list if it's worse then our current best.
if (oldBd >= face.maxDistance)
return false;
// Update our info and indicate we should add to the model.
F32 oldT = oldBd / face.maxDistance;
F32 pushBackT = bd / face.maxDistance;
if(oldT - pushBackT > 0.1f)
face.time = oldT - 0.1f;
else
face.time = pushBackT;
face.height = height;
face.point = bp;
return true;
}
//----------------------------------------------------------------------------
void ExtrudedPolyList::render()
{
if (!mCollisionList)
return;
glBegin(GL_LINES);
glColor3f(1.0f,1.0f,0.0f);
for (U32 d = 0; d < mCollisionList->count; d++)
{
Collision& face = mCollisionList->collision[d];
Point3F ep = face.point;
ep += face.normal;
glVertex3fv(face.point);
glVertex3fv(ep);
}
glEnd();
}
//--------------------------------------------------------------------------
void ExtrudedPolyList::setVelocity(const VectorF& velocity)
{
mVelocity = velocity;
if (velocity.isZero() == false)
{
mNormalVelocity = velocity;
mNormalVelocity.normalize();
setInterestNormal(mNormalVelocity);
}
else
{
mNormalVelocity.set(0.0f, 0.0f, 0.0f);
clearInterestNormal();
}
}

View File

@ -0,0 +1,107 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _EXTRUDEDPOLYLIST_H_
#define _EXTRUDEDPOLYLIST_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
struct Polyhedron;
struct CollisionList;
//----------------------------------------------------------------------------
/// Extruded Polytope PolyList
///
/// This class is used primarily for collision detection, by objects which need
/// to check for obstructions along their path. You feed it a polytope to
/// extrude along the direction of movement, and it gives you a list of collisions.
///
/// @see AbstractPolyList
class ExtrudedPolyList: public AbstractPolyList
{
public:
struct Vertex {
Point3F point;
U32 mask;
};
struct Poly {
PlaneF plane;
SceneObject* object;
U32 material;
};
struct ExtrudedFace {
bool active;
PlaneF plane;
F32 maxDistance;
U32 planeMask;
F32 faceDot;
F32 faceShift;
F32 time;
Point3F point;
F32 height;
};
typedef Vector<ExtrudedFace> ExtrudedList;
typedef Vector<PlaneF> PlaneList;
typedef Vector<Vertex> VertexList;
typedef Vector<U32> IndexList;
static F32 EqualEpsilon;
static F32 FaceEpsilon;
// Internal data
VertexList mVertexList;
IndexList mIndexList;
ExtrudedList mExtrudedList;
PlaneList mPlaneList;
VectorF mVelocity;
VectorF mNormalVelocity;
F32 mFaceShift;
Poly mPoly;
// Returned info
CollisionList* mCollisionList;
PlaneList mPolyPlaneList;
//
private:
bool testPoly(ExtrudedFace&);
public:
ExtrudedPolyList();
~ExtrudedPolyList();
void extrude(const Polyhedron&, const VectorF& vec);
void setVelocity(const VectorF& velocity);
void setCollisionList(CollisionList*);
void adjustCollisionTime();
void render();
// Virtual methods
bool isEmpty() const;
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material, U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif

422
engine/collision/gjk.cc Executable file
View File

@ -0,0 +1,422 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//
// The core algorithms in this file are based on code written
// by G. van den Bergen for his interference detection library,
// "SOLID 2.0"
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "core/dataChunker.h"
#include "collision/collision.h"
#include "sceneGraph/sceneGraph.h"
#include "sim/sceneObject.h"
#include "terrain/terrData.h"
#include "collision/convex.h"
#include "collision/gjk.h"
//----------------------------------------------------------------------------
static F32 rel_error = 1E-5; // relative error in the computed distance
static F32 sTolerance = 1E-3; // Distance tolerance
static F32 sEpsilon2 = 1E-20; // Zero length vector
static U32 sIteration = 15; // Stuck in a loop?
S32 num_iterations = 0;
S32 num_irregularities = 0;
//----------------------------------------------------------------------------
GjkCollisionState::GjkCollisionState()
{
a = b = 0;
}
GjkCollisionState::~GjkCollisionState()
{
}
//----------------------------------------------------------------------------
void GjkCollisionState::swap()
{
Convex* t = a; a = b; b = t;
CollisionStateList* l = mLista; mLista = mListb; mListb = l;
v.neg();
}
//----------------------------------------------------------------------------
void GjkCollisionState::compute_det()
{
// Dot new point with current set
for (int i = 0, bit = 1; i < 4; ++i, bit <<=1)
if (bits & bit)
dp[i][last] = dp[last][i] = mDot(y[i], y[last]);
dp[last][last] = mDot(y[last], y[last]);
// Calulate the determinent
det[last_bit][last] = 1;
for (int j = 0, sj = 1; j < 4; ++j, sj <<= 1) {
if (bits & sj) {
int s2 = sj | last_bit;
det[s2][j] = dp[last][last] - dp[last][j];
det[s2][last] = dp[j][j] - dp[j][last];
for (int k = 0, sk = 1; k < j; ++k, sk <<= 1) {
if (bits & sk) {
int s3 = sk | s2;
det[s3][k] = det[s2][j] * (dp[j][j] - dp[j][k]) +
det[s2][last] * (dp[last][j] - dp[last][k]);
det[s3][j] = det[sk | last_bit][k] * (dp[k][k] - dp[k][j]) +
det[sk | last_bit][last] * (dp[last][k] - dp[last][j]);
det[s3][last] = det[sk | sj][k] * (dp[k][k] - dp[k][last]) +
det[sk | sj][j] * (dp[j][k] - dp[j][last]);
}
}
}
}
if (all_bits == 15) {
det[15][0] = det[14][1] * (dp[1][1] - dp[1][0]) +
det[14][2] * (dp[2][1] - dp[2][0]) +
det[14][3] * (dp[3][1] - dp[3][0]);
det[15][1] = det[13][0] * (dp[0][0] - dp[0][1]) +
det[13][2] * (dp[2][0] - dp[2][1]) +
det[13][3] * (dp[3][0] - dp[3][1]);
det[15][2] = det[11][0] * (dp[0][0] - dp[0][2]) +
det[11][1] * (dp[1][0] - dp[1][2]) +
det[11][3] * (dp[3][0] - dp[3][2]);
det[15][3] = det[7][0] * (dp[0][0] - dp[0][3]) +
det[7][1] * (dp[1][0] - dp[1][3]) +
det[7][2] * (dp[2][0] - dp[2][3]);
}
}
//----------------------------------------------------------------------------
inline void GjkCollisionState::compute_vector(int bits, VectorF& v)
{
F32 sum = 0;
v.set(0, 0, 0);
for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
if (bits & bit) {
sum += det[bits][i];
v += y[i] * det[bits][i];
}
}
v *= 1 / sum;
}
//----------------------------------------------------------------------------
inline bool GjkCollisionState::valid(int s)
{
for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
if (all_bits & bit) {
if (s & bit) {
if (det[s][i] <= 0)
return false;
}
else
if (det[s | bit][i] > 0)
return false;
}
}
return true;
}
//----------------------------------------------------------------------------
inline bool GjkCollisionState::closest(VectorF& v)
{
compute_det();
for (int s = bits; s; --s) {
if ((s & bits) == s) {
if (valid(s | last_bit)) {
bits = s | last_bit;
if (bits != 15)
compute_vector(bits, v);
return true;
}
}
}
if (valid(last_bit)) {
bits = last_bit;
v = y[last];
return true;
}
return false;
}
//----------------------------------------------------------------------------
inline bool GjkCollisionState::degenerate(const VectorF& w)
{
for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1)
if ((all_bits & bit) && y[i] == w)
return true;
return false;
}
//----------------------------------------------------------------------------
inline void GjkCollisionState::nextBit()
{
last = 0;
last_bit = 1;
while (bits & last_bit) {
++last;
last_bit <<= 1;
}
}
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
void GjkCollisionState::set(Convex* aa, Convex* bb,
const MatrixF& a2w, const MatrixF& b2w)
{
a = aa;
b = bb;
bits = 0;
all_bits = 0;
reset(a2w,b2w);
// link
mLista = CollisionStateList::alloc();
mLista->mState = this;
mListb = CollisionStateList::alloc();
mListb->mState = this;
}
//----------------------------------------------------------------------------
void GjkCollisionState::reset(const MatrixF& a2w, const MatrixF& b2w)
{
VectorF zero(0,0,0),sa,sb;
a2w.mulP(a->support(zero),&sa);
b2w.mulP(b->support(zero),&sb);
v = sa - sb;
dist = v.len();
}
//----------------------------------------------------------------------------
void GjkCollisionState::getCollisionInfo(const MatrixF& mat, Collision* info)
{
AssertFatal(false, "GjkCollisionState::getCollisionInfo() - There remain scaling problems here.");
// This assumes that the shapes do not intersect
Point3F pa,pb;
if (bits) {
getClosestPoints(pa,pb);
mat.mulP(pa,&info->point);
b->getTransform().mulP(pb,&pa);
info->normal = info->point - pa;
}
else {
mat.mulP(p[last],&info->point);
info->normal = v;
}
info->normal.normalize();
info->object = b->getObject();
}
void GjkCollisionState::getClosestPoints(Point3F& p1, Point3F& p2)
{
F32 sum = 0;
p1.set(0, 0, 0);
p2.set(0, 0, 0);
for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
if (bits & bit) {
sum += det[bits][i];
p1 += p[i] * det[bits][i];
p2 += q[i] * det[bits][i];
}
}
F32 s = 1 / sum;
p1 *= s;
p2 *= s;
}
//----------------------------------------------------------------------------
bool GjkCollisionState::intersect(const MatrixF& a2w, const MatrixF& b2w)
{
num_iterations = 0;
MatrixF w2a,w2b;
w2a = a2w;
w2b = b2w;
w2a.inverse();
w2b.inverse();
reset(a2w,b2w);
bits = 0;
all_bits = 0;
do {
nextBit();
VectorF va,sa;
w2a.mulV(-v,&va);
p[last] = a->support(va);
a2w.mulP(p[last],&sa);
VectorF vb,sb;
w2b.mulV(v,&vb);
q[last] = b->support(vb);
b2w.mulP(q[last],&sb);
VectorF w = sa - sb;
if (mDot(v,w) > 0)
return false;
if (degenerate(w)) {
++num_irregularities;
return false;
}
y[last] = w;
all_bits = bits | last_bit;
++num_iterations;
if (!closest(v) || num_iterations > sIteration) {
++num_irregularities;
return false;
}
}
while (bits < 15 && v.lenSquared() > sEpsilon2);
return true;
}
F32 GjkCollisionState::distance(const MatrixF& a2w, const MatrixF& b2w,
const F32 dontCareDist, const MatrixF* _w2a, const MatrixF* _w2b)
{
num_iterations = 0;
MatrixF w2a,w2b;
if (_w2a == NULL || _w2b == NULL) {
w2a = a2w;
w2b = b2w;
w2a.inverse();
w2b.inverse();
}
else {
w2a = *_w2a;
w2b = *_w2b;
}
reset(a2w,b2w);
bits = 0;
all_bits = 0;
F32 mu = 0;
do {
nextBit();
VectorF va,sa;
w2a.mulV(-v,&va);
p[last] = a->support(va);
a2w.mulP(p[last],&sa);
VectorF vb,sb;
w2b.mulV(v,&vb);
q[last] = b->support(vb);
b2w.mulP(q[last],&sb);
VectorF w = sa - sb;
F32 nm = mDot(v, w) / dist;
if (nm > mu)
mu = nm;
if (mu > dontCareDist)
return mu;
if (mFabs(dist - mu) <= dist * rel_error)
return dist;
++num_iterations;
if (degenerate(w) || num_iterations > sIteration) {
++num_irregularities;
return dist;
}
y[last] = w;
all_bits = bits | last_bit;
if (!closest(v)) {
++num_irregularities;
return dist;
}
dist = v.len();
}
while (bits < 15 && dist > sTolerance) ;
if (bits == 15 && mu <= 0)
dist = 0;
return dist;
}
//----------------------------------------------------------------------------
inline void renderCross(const Point3F x)
{
glVertex3fv(x + VectorF(0,0,+0.1));
glVertex3fv(x + VectorF(0,0,-0.1));
glVertex3fv(x + VectorF(0,+0.1,0));
glVertex3fv(x + VectorF(0,-0.1,0));
glVertex3fv(x + VectorF(-0.1,0,0));
glVertex3fv(x + VectorF(+0.1,0,0));
}
void GjkCollisionState::render()
{
glBegin(GL_LINES);
glColor3f(1,1,0);
// Cross for each witness point
for (int i = 0, bit = 1; i < 4; ++i, bit <<= 1) {
Point3F x;
if (bits & bit) {
a->getTransform().mulP(p[i],&x);
renderCross(x);
b->getTransform().mulP(q[i],&x);
renderCross(x);
}
}
// Cross and line for closest points
Point3F sp,ep;
if (bits) {
Point3F p1,p2;
getClosestPoints(p1,p2);
a->getTransform().mulP(p1,&sp);
b->getTransform().mulP(p2,&ep);
}
else {
a->getTransform().mulP(p[0],&sp);
b->getTransform().mulP(q[0],&ep);
}
renderCross(sp);
renderCross(ep);
glVertex3fv(sp);
glVertex3fv(ep);
glEnd();
}

66
engine/collision/gjk.h Executable file
View File

@ -0,0 +1,66 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _GJK_H_
#define _GJK_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _CONVEX_H_
#include "collision/convex.h"
#endif
class Convex;
struct CollisionStateList;
struct Collision;
//----------------------------------------------------------------------------
struct GjkCollisionState: public CollisionState
{
/// @name Temporary values
/// @{
Point3F p[4]; ///< support points of object A in local coordinates
Point3F q[4]; ///< support points of object B in local coordinates
VectorF y[4]; ///< support points of A - B in world coordinates
S32 bits; ///< identifies current simplex
S32 all_bits; ///< all_bits = bits | last_bit
F32 det[16][4]; ///< cached sub-determinants
F32 dp[4][4]; ///< cached dot products
S32 last; ///< identifies last found support point
S32 last_bit; ///< last_bit = 1<<last
/// @}
///
void compute_det();
bool valid(int s);
void compute_vector(int bits, VectorF& v);
bool closest(VectorF& v);
bool degenerate(const VectorF& w);
void nextBit();
void swap();
void reset(const MatrixF& a2w, const MatrixF& b2w);
GjkCollisionState();
~GjkCollisionState();
void set(Convex* a,Convex* b,const MatrixF& a2w, const MatrixF& b2w);
void getCollisionInfo(const MatrixF& mat, Collision* info);
void getClosestPoints(Point3F& p1, Point3F& p2);
bool intersect(const MatrixF& a2w, const MatrixF& b2w);
F32 distance(const MatrixF& a2w, const MatrixF& b2w, const F32 dontCareDist,
const MatrixF* w2a = NULL, const MatrixF* _w2b = NULL);
void render();
};
#endif

View File

@ -0,0 +1,286 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/optimizedPolyList.h"
#include "core/color.h"
//----------------------------------------------------------------------------
OptimizedPolyList::OptimizedPolyList()
{
VECTOR_SET_ASSOCIATION(mPolyList);
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mIndexList);
VECTOR_SET_ASSOCIATION(mPlaneList);
VECTOR_SET_ASSOCIATION(mEdgeList);
mIndexList.reserve(100);
}
OptimizedPolyList::~OptimizedPolyList()
{
mPolyList.clear();
mVertexList.clear();
mIndexList.clear();
mPlaneList.clear();
mEdgeList.clear();
}
//----------------------------------------------------------------------------
void OptimizedPolyList::clear()
{
// Only clears internal data
mPolyList.clear();
mVertexList.clear();
mIndexList.clear();
mPlaneList.clear();
mEdgeList.clear();
mPolyPlaneList.clear();
}
//----------------------------------------------------------------------------
U32 OptimizedPolyList::addPoint(const Point3F& p)
{
Point3F v;
v.x = p.x * mScale.x;
v.y = p.y * mScale.y;
v.z = p.z * mScale.z;
mMatrix.mulP(v);
for (U32 i = 0; i < mVertexList.size(); i++)
{
if (isEqual(v, mVertexList[i]))
return i;
}
// If we make it to here then we didn't find the vertex
mVertexList.push_back(v);
return mVertexList.size() - 1;
}
U32 OptimizedPolyList::addPlane(const PlaneF& plane)
{
PlaneF pln;
mPlaneTransformer.transform(plane, pln);
for (U32 i = 0; i < mPlaneList.size(); i++)
{
// The PlaneF == operator uses the Point3F == operator and
// doesn't check the d
if (isEqual(pln, mPlaneList[i]) &&
mFabs(pln.d - mPlaneList[i].d) < DEV)
return i;
}
// If we made it here then we didin't find the vertex
mPlaneList.push_back(pln);
return mPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void OptimizedPolyList::begin(U32 material,U32 surfaceKey)
{
mPolyList.increment();
Poly& poly = mPolyList.last();
poly.object = mCurrObject;
poly.material = material;
poly.vertexStart = mIndexList.size();
poly.surfaceKey = surfaceKey;
}
//----------------------------------------------------------------------------
void OptimizedPolyList::plane(U32 v1,U32 v2,U32 v3)
{
AssertFatal(v1 < mVertexList.size() && v2 < mVertexList.size() && v3 < mVertexList.size(),
"OptimizedPolyList::plane(): Vertex indices are larger than vertex list size");
mPolyList.last().plane = addPlane(PlaneF(mVertexList[v1], mVertexList[v2],mVertexList[v3]));
}
void OptimizedPolyList::plane(const PlaneF& p)
{
mPolyList.last().plane = addPlane(p);
}
void OptimizedPolyList::plane(const U32 index)
{
AssertFatal(index < mPlaneList.size(), "Out of bounds index!");
mPolyList.last().plane = index;
}
const PlaneF& OptimizedPolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPlaneList.size(), "Out of bounds index!");
return mPlaneList[index];
}
//----------------------------------------------------------------------------
void OptimizedPolyList::vertex(U32 vi)
{
mIndexList.push_back(vi);
}
//----------------------------------------------------------------------------
bool OptimizedPolyList::isEmpty() const
{
return !mPolyList.size();
}
void OptimizedPolyList::end()
{
Poly& poly = mPolyList.last();
poly.vertexCount = mIndexList.size() - poly.vertexStart;
}
void OptimizedPolyList::render()
{
if (mPolyList.size() == 0 || mVertexList.size() == 0)
return;
//glVertexPointer(3, GL_FLOAT,sizeof(Point3F), mVertexList.address());
//glEnableClientState(GL_VERTEX_ARRAY);
//Poly * p;
//for (p = mPolyList.begin(); p < mPolyList.end(); p++)
// glDrawElements(GL_POLYGON,p->vertexCount, GL_UNSIGNED_INT, &mIndexList[p->vertexStart]);
//glDisableClientState(GL_VERTEX_ARRAY);
// If you need normals
for (U32 i = 0; i < mPolyList.size(); i++)
{
if (mPolyList[i].vertexCount < 3)
continue;
ColorF color(gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f), gRandGen.randF(0.0f, 1.0f));
glColor3f(color.red, color.green, color.blue);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
glBegin(GL_TRIANGLES);
for (U32 j = 1; j < mPolyList[i].vertexCount - 1; j++)
{
U32 i0 = mIndexList[mPolyList[i].vertexStart];
U32 i1 = mIndexList[mPolyList[i].vertexStart + j];
U32 i2 = mIndexList[mPolyList[i].vertexStart + j + 1];
Point3F p0 = mVertexList[i0];
Point3F p1 = mVertexList[i1];
Point3F p2 = mVertexList[i2];
PlaneF normal = mPlaneList[mPolyList[i].plane];
glNormal3f(normal.x, normal.y, normal.z);
glVertex3f(p0.x, p0.y, p0.z);
glVertex3f(p1.x, p1.y, p1.z);
glVertex3f(p2.x, p2.y, p2.z);
}
glEnd();
}
}
void OptimizedPolyList::copyPolyToList(OptimizedPolyList* target, U32 pdx) const
{
target->mPolyList.increment();
OptimizedPolyList::Poly& tpoly = target->mPolyList.last();
tpoly.material = mPolyList[pdx].material;
tpoly.object = mPolyList[pdx].object;
tpoly.surfaceKey = mPolyList[pdx].surfaceKey;
tpoly.vertexCount = mPolyList[pdx].vertexCount;
PlaneF pln = mPlaneList[mPolyList[pdx].plane];
tpoly.plane = target->addPlane(pln);
tpoly.vertexStart = target->mIndexList.size();
for (U32 i = 0; i < mPolyList[pdx].vertexCount; i++)
{
U32 idx = mIndexList[mPolyList[pdx].vertexStart + i];
Point3F pt = mVertexList[idx];
target->mIndexList.increment();
target->mIndexList.last() = target->addPoint(pt);
}
}
void OptimizedPolyList::copyPolyToList(OptimizedPolyList* target, const FullPoly& poly) const
{
target->mPolyList.increment();
OptimizedPolyList::Poly& tpoly = target->mPolyList.last();
tpoly.material = poly.material;
tpoly.object = poly.object;
tpoly.surfaceKey = poly.surfaceKey;
tpoly.vertexCount = poly.indexes.size();
PlaneF pln = poly.plane;
tpoly.plane = target->addPlane(pln);
tpoly.vertexStart = target->mIndexList.size();
for (U32 i = 0; i < poly.indexes.size(); i++)
{
Point3F pt = poly.vertexes[poly.indexes[i]];
target->mIndexList.increment();
target->mIndexList.last() = target->addPoint(pt);
}
}
OptimizedPolyList::FullPoly OptimizedPolyList::getFullPoly(U32 pdx)
{
FullPoly ret;
OptimizedPolyList::Poly& poly = mPolyList[pdx];
ret.material = poly.material;
ret.object = poly.object;
ret.surfaceKey = poly.surfaceKey;
for (U32 i = 0; i < poly.vertexCount; i++)
{
U32 idx = mIndexList[poly.vertexStart + i];
Point3F& pt = mVertexList[idx];
S32 pdx = -1;
for (U32 j = 0; j < ret.vertexes.size(); j++)
{
if (pt == ret.vertexes[j])
{
pdx = j;
break;
}
}
if (pdx == -1)
{
pdx = ret.vertexes.size();
ret.vertexes.push_back(pt);
}
ret.indexes.push_back(pdx);
}
return ret;
}

View File

@ -0,0 +1,128 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _OPTIMIZEDPOLYLIST_H_
#define _OPTIMIZEDPOLYLIST_H_
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
#define DEV 0.01
/// A concrete, renderable PolyList
///
/// This class is used to store geometry from a PolyList query.
///
/// It allows you to render this data, as well.
///
/// @see AbstractPolyList
class OptimizedPolyList : public AbstractPolyList
{
public:
struct Poly
{
S32 plane;
SceneObject* object;
S32 material;
U32 vertexStart;
U32 vertexCount;
U32 surfaceKey;
U32 triangleLightingStartIndex;
Poly() { plane = -1; vertexCount = 0; material = -1; };
};
struct FullPoly
{
PlaneF plane;
SceneObject* object;
S32 material;
U32 surfaceKey;
Vector<U32> indexes;
Vector<Point3F> vertexes;
};
enum
{
NonShared = 0,
Concave,
Convex
};
struct Edge
{
U32 type;
S32 vertexes[2];
S32 faces[2];
};
struct TriangleLighting
{
U32 lightMapId;
PlaneF lightMapEquationX;
PlaneF lightMapEquationY;
};
Vector<TriangleLighting> mTriangleLightingList;
const TriangleLighting *getTriangleLighting(const U32 index)
{
if(index >= mTriangleLightingList.size())
return NULL;
return &mTriangleLightingList[index];
}
typedef Vector<PlaneF> PlaneList;
typedef Vector<Point3F> VertexList;
typedef Vector<Poly> PolyList;
typedef Vector<U32> IndexList;
typedef Vector<Edge> EdgeList;
PolyList mPolyList;
VertexList mVertexList;
IndexList mIndexList;
PlaneList mPlaneList;
EdgeList mEdgeList;
PlaneList mPolyPlaneList;
public:
OptimizedPolyList();
~OptimizedPolyList();
void clear();
// Virtual methods
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material,U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
inline bool isEqual(Point3F& a, Point3F& b)
{
return( ( mFabs( a.x - b.x ) < DEV ) &&
( mFabs( a.y - b.y ) < DEV ) &&
( mFabs( a.z - b.z ) < DEV ) );
}
void copyPolyToList(OptimizedPolyList* target, U32 pdx) const;
void copyPolyToList(OptimizedPolyList* target, const FullPoly& poly) const;
FullPoly getFullPoly(U32 pdx);
void render();
bool isEmpty() const;
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif // _OPTIMIZEDPOLYLIST_H_

View File

@ -0,0 +1,117 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "console/console.h"
#include "collision/planeExtractor.h"
//----------------------------------------------------------------------------
// Plane matching parameters
static F32 NormalEpsilon = 0.93969; // 20 deg.
static F32 DistanceEpsilon = 100;
//----------------------------------------------------------------------------
PlaneExtractorPolyList::PlaneExtractorPolyList()
{
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mPolyPlaneList);
}
PlaneExtractorPolyList::~PlaneExtractorPolyList()
{
}
//----------------------------------------------------------------------------
void PlaneExtractorPolyList::clear()
{
mVertexList.clear();
mPolyPlaneList.clear();
}
U32 PlaneExtractorPolyList::addPoint(const Point3F& p)
{
mVertexList.increment();
Point3F& v = mVertexList.last();
v.x = p.x * mScale.x;
v.y = p.y * mScale.y;
v.z = p.z * mScale.z;
mMatrix.mulP(v);
return mVertexList.size() - 1;
}
U32 PlaneExtractorPolyList::addPlane(const PlaneF& plane)
{
mPolyPlaneList.increment();
mPlaneTransformer.transform(plane, mPolyPlaneList.last());
return mPolyPlaneList.size() - 1;
}
//----------------------------------------------------------------------------
void PlaneExtractorPolyList::plane(U32 v1,U32 v2,U32 v3)
{
mPlaneList->last().set(mVertexList[v1],
mVertexList[v2],mVertexList[v3]);
}
void PlaneExtractorPolyList::plane(const PlaneF& p)
{
mPlaneTransformer.transform(p, mPlaneList->last());
}
void PlaneExtractorPolyList::plane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
mPlaneList->last() = mPolyPlaneList[index];
}
const PlaneF& PlaneExtractorPolyList::getIndexedPlane(const U32 index)
{
AssertFatal(index < mPolyPlaneList.size(), "Out of bounds index!");
return mPolyPlaneList[index];
}
//----------------------------------------------------------------------------
bool PlaneExtractorPolyList::isEmpty() const
{
return true;
}
void PlaneExtractorPolyList::begin(U32,U32)
{
mPlaneList->increment();
}
void PlaneExtractorPolyList::end()
{
// See if there are any duplicate planes
PlaneF &plane = mPlaneList->last();
PlaneF *ptr = mPlaneList->begin();
for (; ptr != &plane; ptr++)
if (mFabs(ptr->d - plane.d) < DistanceEpsilon &&
mDot(*ptr,plane) > NormalEpsilon) {
mPlaneList->decrement();
return;
}
}
void PlaneExtractorPolyList::vertex(U32)
{
}
void PlaneExtractorPolyList::render()
{
}

View File

@ -0,0 +1,62 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _PLANEEXTRACTOR_H_
#define _PLANEEXTRACTOR_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _ABSTRACTPOLYLIST_H_
#include "collision/abstractPolyList.h"
#endif
//----------------------------------------------------------------------------
/// Fill a Vector<PlaneF> with the planes from the geometry passed through this
/// PolyList.
///
/// @see AbstractPolyList
class PlaneExtractorPolyList: public AbstractPolyList
{
void memcpy(U32* d, U32* s,U32 size);
public:
// Internal data
typedef Vector<Point3F> VertexList;
VertexList mVertexList;
Vector<PlaneF> mPolyPlaneList;
// Set by caller
Vector<PlaneF>* mPlaneList;
//
PlaneExtractorPolyList();
~PlaneExtractorPolyList();
void clear();
void render();
// Virtual methods
bool isEmpty() const;
U32 addPoint(const Point3F& p);
U32 addPlane(const PlaneF& plane);
void begin(U32 material,U32 surfaceKey);
void plane(U32 v1,U32 v2,U32 v3);
void plane(const PlaneF& p);
void plane(const U32 index);
void vertex(U32 vi);
void end();
protected:
const PlaneF& getIndexedPlane(const U32 index);
};
#endif

150
engine/collision/polyhedron.cc Executable file
View File

@ -0,0 +1,150 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "console/console.h"
#include "collision/polyhedron.h"
//----------------------------------------------------------------------------
void Polyhedron::buildBox(const MatrixF& transform,const Box3F& box)
{
// Box is assumed to be axis aligned in the source space.
// Transform into geometry space
Point3F xvec,yvec,zvec,min;
transform.getColumn(0,&xvec);
xvec *= box.len_x();
transform.getColumn(1,&yvec);
yvec *= box.len_y();
transform.getColumn(2,&zvec);
zvec *= box.len_z();
transform.mulP(box.min,&min);
// Initial vertices
pointList.setSize(8);
pointList[0] = min;
pointList[1] = min + yvec;
pointList[2] = min + xvec + yvec;
pointList[3] = min + xvec;
pointList[4] = pointList[0] + zvec;
pointList[5] = pointList[1] + zvec;
pointList[6] = pointList[2] + zvec;
pointList[7] = pointList[3] + zvec;
// Initial faces
planeList.setSize(6);
planeList[0].set(pointList[0],xvec);
planeList[0].invert();
planeList[1].set(pointList[2],yvec);
planeList[2].set(pointList[2],xvec);
planeList[3].set(pointList[0],yvec);
planeList[3].invert();
planeList[4].set(pointList[0],zvec);
planeList[4].invert();
planeList[5].set(pointList[4],zvec);
// The edges are constructed so that the vertices
// are oriented clockwise for face[0]
edgeList.setSize(12);
Edge* edge = edgeList.begin();
S32 nextEdge = 0;
for (int i = 0; i < 4; i++) {
S32 n = ( i + 1 ) & 0x3;
S32 p = ( i + 3 ) & 0x3;
edge->vertex[0] = i;
edge->vertex[1] = n;
edge->face[0] = i;
edge->face[1] = 4;
edge++;
edge->vertex[0] = 4 + i;
edge->vertex[1] = 4 + n;
edge->face[0] = 5;
edge->face[1] = i;
edge++;
edge->vertex[0] = i;
edge->vertex[1] = 4 + i;
edge->face[0] = p;
edge->face[1] = i;
edge++;
}
}
//----------------------------------------------------------------------------
void Polyhedron::render()
{
glVertexPointer(3,GL_FLOAT,sizeof(Point3F),pointList.address());
glEnableClientState(GL_VERTEX_ARRAY);
glColor3f(1, 0, 1);
for (S32 e = 0; e < edgeList.size(); e++)
glDrawElements(GL_LINES,2,GL_UNSIGNED_INT,&edgeList[e].vertex);
for (U32 i = 0; i < planeList.size(); i++) {
Point3F origin(0, 0, 0);
U32 num = 0;
for (U32 j = 0; j < edgeList.size(); j++) {
if (edgeList[j].face[0] == i || edgeList[j].face[1] == i) {
origin += pointList[edgeList[j].vertex[0]];
origin += pointList[edgeList[j].vertex[1]];
num += 2;
}
}
origin /= F32(num);
glColor3f(1, 1, 1);
glBegin(GL_LINES);
glVertex3fv(origin);
glVertex3f(origin.x + planeList[i].x * 0.2,
origin.y + planeList[i].y * 0.2,
origin.z + planeList[i].z * 0.2);
glEnd();
}
glDisableClientState(GL_VERTEX_ARRAY);
}
void Polyhedron::render(const VectorF& vec,F32 time)
{
bool faceVisible[50];
for (int f = 0; f < planeList.size(); f++)
faceVisible[f] = mDot(planeList[f],vec) > 0;
VectorF tvec = vec;
tvec *= time;
for (int e = 0; e < edgeList.size(); e++) {
Polyhedron::Edge const& edge = edgeList[e];
if (faceVisible[edge.face[0]] || faceVisible[edge.face[1]]) {
Point3F pp;
glBegin(GL_LINE_LOOP);
glColor3f(0.5,0.5,0.5);
const Point3F& p1 = pointList[edge.vertex[0]];
const Point3F& p2 = pointList[edge.vertex[1]];
glVertex3fv(p1);
glVertex3fv(p2);
pp = p2 + vec;
glVertex3fv(pp);
pp = p1 + vec;
glVertex3fv(pp);
glEnd();
if (time <= 1.0) {
glBegin(GL_LINES);
glColor3f(0.5,0.5,0);
pp = pointList[edge.vertex[0]];
pp += tvec;
glVertex3fv(pp);
pp = pointList[edge.vertex[1]];
pp += tvec;
glVertex3fv(pp);
glEnd();
}
}
}
}

45
engine/collision/polyhedron.h Executable file
View File

@ -0,0 +1,45 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _POLYHEDRON_H_
#define _POLYHEDRON_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
//----------------------------------------------------------------------------
struct Polyhedron
{
struct Edge
{
// Edge vertices must be oriented clockwise
// for face[0]
U32 face[2];
U32 vertex[2];
};
Vector<Point3F> pointList;
Vector<PlaneF> planeList;
Vector<Edge> edgeList;
// Misc support methods
Polyhedron() {
VECTOR_SET_ASSOCIATION(pointList);
VECTOR_SET_ASSOCIATION(planeList);
VECTOR_SET_ASSOCIATION(edgeList);
}
void buildBox(const MatrixF& mat, const Box3F& box);
void render();
void render(const VectorF& vec,F32 time);
};
#endif

433
engine/collision/polytope.cc Executable file
View File

@ -0,0 +1,433 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "dgl/dgl.h"
#include "math/mMath.h"
#include "collision/collision.h"
#include "collision/polytope.h"
//----------------------------------------------------------------------------
Polytope::Polytope()
{
VECTOR_SET_ASSOCIATION(mEdgeList);
VECTOR_SET_ASSOCIATION(mFaceList);
VECTOR_SET_ASSOCIATION(mVertexList);
VECTOR_SET_ASSOCIATION(mVolumeList);
mVertexList.reserve(100);
mFaceList.reserve(200);
mEdgeList.reserve(100);
mVolumeList.reserve(20);
sideCount = 0;
}
//----------------------------------------------------------------------------
// Box should be axis aligned in the transform space provided.
void Polytope::buildBox(const MatrixF& transform,const Box3F& box)
{
// Box is assumed to be axis aligned in the source space.
// Transform into geometry space
Point3F xvec,yvec,zvec,min;
transform.getColumn(0,&xvec);
xvec *= box.len_x();
transform.getColumn(1,&yvec);
yvec *= box.len_y();
transform.getColumn(2,&zvec);
zvec *= box.len_z();
transform.mulP(box.min,&min);
// Initial vertices
mVertexList.setSize(8);
mVertexList[0].point = min;
mVertexList[1].point = min + yvec;
mVertexList[2].point = min + xvec + yvec;
mVertexList[3].point = min + xvec;
mVertexList[4].point = mVertexList[0].point + zvec;
mVertexList[5].point = mVertexList[1].point + zvec;
mVertexList[6].point = mVertexList[2].point + zvec;
mVertexList[7].point = mVertexList[3].point + zvec;
S32 i;
for (i = 0; i < 8; i++)
mVertexList[i].side = 0;
// Initial faces
mFaceList.setSize(6);
for (S32 f = 0; f < 6; f++) {
Face& face = mFaceList[f];
face.original = true;
face.vertex = 0;
}
mFaceList[0].plane.set(mVertexList[0].point,xvec);
mFaceList[0].plane.invert();
mFaceList[1].plane.set(mVertexList[2].point,yvec);
mFaceList[2].plane.set(mVertexList[2].point,xvec);
mFaceList[3].plane.set(mVertexList[0].point,yvec);
mFaceList[3].plane.invert();
mFaceList[4].plane.set(mVertexList[0].point,zvec);
mFaceList[4].plane.invert();
mFaceList[5].plane.set(mVertexList[4].point,zvec);
// Initial edges
mEdgeList.setSize(12);
Edge* edge = mEdgeList.begin();
S32 nextEdge = 0;
for (i = 0; i < 4; i++) {
S32 n = (i == 3)? 0: i + 1;
S32 p = (i == 0)? 3: i - 1;
edge->vertex[0] = i;
edge->vertex[1] = n;
edge->face[0] = i;
edge->face[1] = 4;
edge->next = ++nextEdge;
edge++;
edge->vertex[0] = 4 + i;
edge->vertex[1] = 4 + n;
edge->face[0] = i;
edge->face[1] = 5;
edge->next = ++nextEdge;
edge++;
edge->vertex[0] = i;
edge->vertex[1] = 4 + i;
edge->face[0] = i;
edge->face[1] = p;
edge->next = ++nextEdge;
edge++;
}
edge[-1].next = -1;
// Volume
mVolumeList.setSize(1);
Volume& volume = mVolumeList.last();
volume.edgeList = 0;
volume.material = -1;
volume.object = 0;
sideCount = 0;
}
//----------------------------------------------------------------------------
void Polytope::intersect(SimObject* object,const BSPNode* root)
{
AssertFatal(mVolumeList.size() > 0,"Polytope::intersect: Missing initial volume");
// Always clips the first volume against the BSP
VolumeStack stack;
stack.reserve(50);
stack.increment();
stack.last().edgeList = mVolumeList[0].edgeList;
stack.last().node = root;
while (!stack.empty()) {
StackElement volume = stack.last();
stack.pop_back();
// If it's a solid node, export the volume
const BSPNode* node = volume.node;
if (!node->backNode && !node->frontNode) {
mVolumeList.increment();
Volume& vol = mVolumeList.last();
vol.object = object;
vol.material = node->material;
vol.edgeList = volume.edgeList;
continue;
}
// New front and back faces
mFaceList.increment(2);
Face& frontFace = mFaceList.last();
Face& backFace = *(&frontFace - 1);
backFace.original = frontFace.original = false;
backFace.vertex = frontFace.vertex = 0;
backFace.plane = frontFace.plane = node->plane;
backFace.plane.invert();
// New front and back volumes
StackElement frontVolume,backVolume;
frontVolume.edgeList = backVolume.edgeList = -1;
const PlaneF& plane = node->plane;
S32 startVertex = mVertexList.size();
// Test & clip all the edges
S32 sideBase = ++sideCount << 1;
for (S32 i = volume.edgeList; i >= 0; i = mEdgeList[i].next) {
// Copy into tmp first to avoid problems with the array.
Edge edge = mEdgeList[i];
Vertex& v0 = mVertexList[edge.vertex[0]];
if (v0.side < sideBase)
v0.side = sideBase + ((plane.distToPlane(v0.point) >= 0)? 0: 1);
Vertex& v1 = mVertexList[edge.vertex[1]];
if (v1.side < sideBase)
v1.side = sideBase + ((plane.distToPlane(v1.point) >= 0)? 0: 1);
if (v0.side != v1.side) {
S32 s = v0.side - sideBase;
intersect(plane,v0.point,v1.point);
// Split the edge into each volume
mEdgeList.increment(2);
Edge& e0 = mEdgeList.last();
e0.next = frontVolume.edgeList;
frontVolume.edgeList = mEdgeList.size() - 1;
Edge& e1 = *(&e0 - 1);
e1.next = backVolume.edgeList;
backVolume.edgeList = frontVolume.edgeList - 1;
e0.vertex[0] = edge.vertex[s];
e1.vertex[0] = edge.vertex[s ^ 1];
e0.vertex[1] = e1.vertex[1] = mVertexList.size() - 1;
e0.face[0] = e1.face[0] = edge.face[0];
e0.face[1] = e1.face[1] = edge.face[1];
// Add new edges on the plane, one to each volume
for (S32 f = 0; f < 2; f++) {
Face& face = mFaceList[edge.face[f]];
if (face.vertex < startVertex)
face.vertex = mVertexList.size() - 1;
else {
mEdgeList.increment(2);
Edge& e0 = mEdgeList.last();
e0.next = frontVolume.edgeList;
frontVolume.edgeList = mEdgeList.size() - 1;
Edge& e1 = *(&e0 - 1);
e1.next = backVolume.edgeList;
backVolume.edgeList = frontVolume.edgeList - 1;
e1.vertex[0] = e0.vertex[0] = face.vertex;
e1.vertex[1] = e0.vertex[1] = mVertexList.size() - 1;
e1.face[0] = e0.face[0] = edge.face[f];
e1.face[1] = mFaceList.size() - 1;
e0.face[1] = e1.face[1] - 1;
}
}
}
else
if (v0.side == sideBase) {
mEdgeList.push_back(edge);
Edge& ne = mEdgeList.last();
ne.next = frontVolume.edgeList;
frontVolume.edgeList = mEdgeList.size() - 1;
}
else {
mEdgeList.push_back(edge);
Edge& ne = mEdgeList.last();
ne.next = backVolume.edgeList;
backVolume.edgeList = mEdgeList.size() - 1;
}
}
// Push the front and back nodes
if (node->frontNode && frontVolume.edgeList >= 0) {
frontVolume.node = node->frontNode;
stack.push_back(frontVolume);
}
if (node->backNode && backVolume.edgeList >= 0) {
backVolume.node = node->backNode;
stack.push_back(backVolume);
}
}
}
//----------------------------------------------------------------------------
bool Polytope::intersect(const PlaneF& plane,const Point3F& sp,const Point3F& ep)
{
// If den == 0 then the line and plane are parallel.
F32 den;
Point3F dt = ep - sp;
if ((den = plane.x * dt.x + plane.y * dt.y + plane.z * dt.z) == 0)
return false;
mVertexList.increment();
Vertex& v = mVertexList.last();
F32 s = -(plane.x * sp.x + plane.y * sp.y + plane.z * sp.z + plane.d) / den;
v.point.x = sp.x + dt.x * s;
v.point.y = sp.y + dt.y * s;
v.point.z = sp.z + dt.z * s;
v.side = 0;
return true;
}
//----------------------------------------------------------------------------
void Polytope::render()
{
glVertexPointer(3,GL_FLOAT,sizeof(Vertex),mVertexList.address());
glEnableClientState(GL_VERTEX_ARRAY);
glColor3f(0.5, 0.5, 0.5);
for (VolumeList::iterator itr = mVolumeList.begin();
itr < mVolumeList.end(); itr++) {
for (S32 e = itr->edgeList; e >= 0; e = mEdgeList[e].next)
glDrawElements(GL_LINES,2,GL_UNSIGNED_INT,&mEdgeList[e].vertex);
glColor3f(1, 1, 1);
}
glDisableClientState(GL_VERTEX_ARRAY);
}
//----------------------------------------------------------------------------
void Polytope::extrudeFace(int faceIdx,const VectorF& vec,Polytope* out)
{
// Assumes the face belongs to the first volume.
out->mVertexList.clear();
out->mFaceList.clear();
out->mEdgeList.clear();
out->mVolumeList.clear();
sideCount++;
// Front & end faces
Face nface;
nface.original = true;
nface.vertex = 0;
nface.plane = mFaceList[faceIdx].plane;
out->mFaceList.setSize(2);
out->mFaceList[0] = out->mFaceList[1] = nface;
out->mFaceList[0].plane.invert();
for (S32 e = mVolumeList[0].edgeList; e >= 0; e = mEdgeList[e].next) {
Edge& edge = mEdgeList[e];
if (edge.face[0] == faceIdx || edge.face[1] == faceIdx) {
// Build face for this edge
// Should think about calulating the plane
S32 fi = out->mFaceList.size();
out->mFaceList.push_back(nface);
// Reserve 4 entries to make sure the ve[] pointers
// into the list don't get invalidated.
out->mEdgeList.reserve(out->mEdgeList.size() + 4);
Edge* ve[2];
// Build edges for each vertex
for (S32 v = 0; v < 2; v++) {
if (mVertexList[edge.vertex[v]].side < sideCount) {
mVertexList[edge.vertex[v]].side = sideCount + out->mEdgeList.size();
out->mVertexList.increment(2);
out->mVertexList.end()[-1] =
out->mVertexList.end()[-2] =
mVertexList[edge.vertex[v]];
out->mVertexList.last().point += vec;
out->mEdgeList.increment();
Edge& ne = out->mEdgeList.last();
ne.next = out->mEdgeList.size();
ne.vertex[1] = out->mVertexList.size() - 1;
ne.vertex[0] = ne.vertex[1] - 1;
ne.face[0] = ne.face[1] = -1;
ve[v] = &ne;
}
else {
S32 ei = mVertexList[edge.vertex[v]].side - sideCount;
ve[v] = &out->mEdgeList[ei];
}
// Edge should share this face
if (ve[v]->face[0] == -1)
ve[v]->face[0] = fi;
else
ve[v]->face[1] = fi;
}
// Build parallel edges
out->mEdgeList.increment(2);
for (S32 i = 0; i < 2; i++ ) {
Edge& ne = out->mEdgeList.end()[i - 2];
ne.next = out->mEdgeList.size() - 1 + i;
ne.vertex[0] = ve[0]->vertex[i];
ne.vertex[1] = ve[1]->vertex[i];
ne.face[0] = i;
ne.face[1] = fi;
}
}
}
out->mEdgeList.last().next = -1;
out->mVolumeList.increment();
Volume& nv = out->mVolumeList.last();
nv.edgeList = 0;
nv.object = 0;
nv.material = -1;
}
//----------------------------------------------------------------------------
bool Polytope::findCollision(const VectorF& vec,Polytope::Collision *best)
{
if (mVolumeList.size() <= 1)
return false;
if (!best->object)
best->distance = 1.0E30;
int bestVertex = -1;
Volume* bestVolume;
sideCount++;
// Find the closest point
for (Volume* vol = mVolumeList.begin() + 1;
vol < mVolumeList.end(); vol++) {
for (S32 e = vol->edgeList; e >= 0; e = mEdgeList[e].next) {
Edge& edge = mEdgeList[e];
if (mFaceList[edge.face[0]].original &&
mFaceList[edge.face[1]].original)
continue;
for (S32 v = 0; v < 2; v++) {
S32 vi = edge.vertex[v];
Vertex& vr = mVertexList[vi];
if (vr.side != sideCount) {
vr.side = sideCount;
F32 dist = mDot(vr.point,vec);
if (dist < best->distance) {
best->distance = dist;
bestVertex = vi;
bestVolume = vol;
}
}
}
}
}
if (bestVertex == -1)
return false;
// Fill in the return value
best->point = mVertexList[bestVertex].point;
best->object = bestVolume->object;
best->material = bestVolume->material;
// Pick the best face
F32 bestFaceDot = 1;
for (S32 e = bestVolume->edgeList; e >= 0; e = mEdgeList[e].next) {
Edge& edge = mEdgeList[e];
if (edge.vertex[0] == bestVertex || edge.vertex[1] == bestVertex) {
for (S32 f = 0; f < 2; f++) {
Face& tf = mFaceList[edge.face[f]];
F32 fd = mDot(tf.plane,vec);
if (fd < bestFaceDot) {
bestFaceDot = fd;
best->plane = tf.plane;
}
}
}
}
return true;
}

96
engine/collision/polytope.h Executable file
View File

@ -0,0 +1,96 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _POLYTOPE_H_
#define _POLYTOPE_H_
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
//----------------------------------------------------------------------------
class SimObject;
//----------------------------------------------------------------------------
class Polytope
{
// Convex Polyhedron
public:
struct Vertex {
Point3F point;
/// Temp BSP clip info
S32 side;
};
struct Edge {
S32 vertex[2];
S32 face[2];
S32 next;
};
struct Face {
PlaneF plane;
S32 original;
/// Temp BSP clip info
S32 vertex;
};
struct Volume
{
S32 edgeList;
S32 material;
SimObject* object;
};
struct StackElement
{
S32 edgeList;
const BSPNode *node;
};
struct Collision {
SimObject* object;
S32 material;
PlaneF plane;
Point3F point;
F32 distance;
Collision()
{
object = NULL;
distance = 0.0;
}
};
typedef Vector<Edge> EdgeList;
typedef Vector<Face> FaceList;
typedef Vector<Vertex> VertexList;
typedef Vector<Volume> VolumeList;
typedef Vector<StackElement> VolumeStack;
//
S32 sideCount;
EdgeList mEdgeList;
FaceList mFaceList;
VertexList mVertexList;
VolumeList mVolumeList;
private:
bool intersect(const PlaneF& plane,const Point3F& sp,const Point3F& ep);
public:
//
Polytope();
void buildBox(const MatrixF& transform,const Box3F& box);
void intersect(SimObject*, const BSPNode* node);
inline bool didIntersect() { return mVolumeList.size() > 1; }
void extrudeFace(int fi,const VectorF& vec,Polytope* out);
bool findCollision(const VectorF& vec,Polytope::Collision *best);
void render();
};
#endif

1951
engine/console/BASgram.cc Executable file

File diff suppressed because it is too large Load Diff

533
engine/console/BASgram.y Executable file
View File

@ -0,0 +1,533 @@
%{
// Make sure we don't get gram.h twice.
#define _BASGRAM_H_
#define _CMDGRAM_H_
#include <stdlib.h>
#include <stdio.h>
#include "console/console.h"
#include "console/compiler.h"
#include "console/consoleInternal.h"
#ifndef YYDEBUG
#define YYDEBUG 0
#endif
#define YYSSIZE 350
int outtext(char *fmt, ...);
extern int serrors;
#define nil 0
#undef YY_ARGS
#define YY_ARGS(x) x
int BASlex();
void BASerror(char *a, ...);
#define alloca dMalloc
%}
%{
/* Reserved Word Definitions */
/* IMPORTANT: SEE COMMENTS BELOW IF YOU ARE THINKING ABOUT MODIFYING TOKENS */
/*
ALL TOKENS BETWEEN HERE AND ADDITIONAL TOKENS BELOW MUST BE IDENTICAL TO THOSE
IN CS_CMD.y OTHERWISE BAD STUFF HAPPENS
*/
%}
%token <i> rwDEFINE rwENDDEF rwDECLARE
%token <i> rwBREAK rwELSE rwCONTINUE rwGLOBAL
%token <i> rwIF rwNIL rwRETURN rwWHILE
%token <i> rwENDIF rwENDWHILE rwENDFOR rwDEFAULT
%token <i> rwFOR rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR
%token <i> rwCASEOR rwPACKAGE
%token ILLEGAL_TOKEN
%{
/* Constants and Identifier Definitions */
%}
%token <c> CHRCONST
%token <i> INTCONST
%token <s> TTAG
%token <s> VAR
%token <s> IDENT
%token <str> STRATOM
%token <str> TAGATOM
%token <f> FLTCONST
%{
/* Operator Definitions */
%}
%token <i> '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%'
%token <i> '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@'
%token <i> opMINUSMINUS opPLUSPLUS
%token <i> STMT_SEP
%token <i> opSHL opSHR opPLASN opMIASN opMLASN opDVASN opMODASN opANDASN
%token <i> opXORASN opORASN opSLASN opSRASN opCAT
%token <i> opEQ opNE opGE opLE opAND opOR opSTREQ
%token <i> opCOLONCOLON
%union {
char c;
int i;
const char *s;
char *str;
double f;
StmtNode *stmt;
ExprNode *expr;
SlotAssignNode *slist;
VarNode *var;
SlotDecl slot;
ObjectBlockDecl odcl;
ObjectDeclNode *od;
AssignDecl asn;
IfStmtNode *ifnode;
}
%type <s> parent_block
%type <ifnode> case_block
%type <stmt> switch_stmt
%type <stmt> decl
%type <stmt> decl_list
%type <stmt> package_decl
%type <stmt> fn_decl_stmt
%type <stmt> fn_decl_list
%type <stmt> statement_list
%type <stmt> stmt
%type <expr> expr_list
%type <expr> expr_list_decl
%type <expr> aidx_expr
%type <expr> funcall_expr
%type <expr> object_name
%type <expr> object_args
%type <expr> stmt_expr
%type <expr> case_expr
%type <expr> class_name_expr
%type <stmt> if_stmt
%type <stmt> while_stmt
%type <stmt> for_stmt
%type <stmt> stmt_block
%type <stmt> datablock_decl
%type <od> object_decl
%type <od> object_decl_list
%type <odcl> object_declare_block
%type <expr> expr
%type <slist> slot_assign_list
%type <slist> slot_assign
%type <slot> slot_acc
%type <stmt> expression_stmt
%type <var> var_list
%type <var> var_list_decl
%type <asn> assign_op_struct
%left '['
%right opMODASN opANDASN opXORASN opPLASN opMIASN opMLASN opDVASN opMDASN opNDASN opNTASN opORASN opSLASN opSRASN '='
%left '?' ':'
%left opOR
%left opAND
%left '|'
%left '^'
%left '&'
%left opEQ opNE
%left '<' opLE '>' opGE
%left '@' opCAT opSTREQ opSTRNE
%left opSHL opSHR
%left '+' '-'
%left '*' '/' '%'
%right '!' '~' opPLUSPLUS opMINUSMINUS UNARY
%left '.'
%{
/*
Additional tokens
NOTE: These MUST be here, otherwise the definitions of the usual TorqueScript tokens changes
which will break the compiler. Double check the standard TS tokens arent being redefined before
testing a build.
*/
%}
%token <i> rwTHEN rwEND rwBEGIN rwCFOR rwTO rwSTEP
%%
start
: decl_list
{ }
;
decl_list
:
{ $$ = nil; }
| decl_list decl
{ if(!statementList) { statementList = $2; } else { statementList->append($2); } }
;
decl
: stmt
{ $$ = $1; }
| fn_decl_stmt
{ $$ = $1; }
| package_decl
{ $$ = $1; }
;
package_decl
: rwPACKAGE IDENT fn_decl_list rwEND
{ $$ = $3; for(StmtNode *walk = ($3);walk;walk = walk->getNext() ) walk->setPackage($2); }
;
fn_decl_list
: fn_decl_stmt
{ $$ = $1; }
| fn_decl_list fn_decl_stmt
{ $$ = $1; ($1)->append($2); }
;
statement_list
:
{ $$ = nil; }
| statement_list stmt
{ if(!$1) { $$ = $2; } else { ($1)->append($2); $$ = $1; } }
;
stmt
: if_stmt
| while_stmt
| for_stmt
| datablock_decl
| switch_stmt
| rwBREAK semicolon
{ $$ = BreakStmtNode::alloc(); }
| rwCONTINUE semicolon
{ $$ = ContinueStmtNode::alloc(); }
| rwRETURN semicolon
{ $$ = ReturnStmtNode::alloc(NULL); }
| rwRETURN expr semicolon
{ $$ = ReturnStmtNode::alloc($2); }
| expression_stmt semicolon
{ $$ = $1; }
| TTAG '=' expr semicolon
{ $$ = TTagSetStmtNode::alloc($1, $3, NULL); }
| TTAG '=' expr ',' expr semicolon
{ $$ = TTagSetStmtNode::alloc($1, $3, $5); }
;
fn_decl_stmt
: rwDEFINE IDENT '(' var_list_decl ')' statement_list rwEND
{ $$ = FunctionDeclStmtNode::alloc($2, NULL, $4, $6); }
| rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' statement_list rwEND
{ $$ = FunctionDeclStmtNode::alloc($4, $2, $6, $8); }
;
var_list_decl
:
{ $$ = NULL; }
| var_list
{ $$ = $1; }
;
var_list
: VAR
{ $$ = VarNode::alloc($1, NULL); }
| var_list ',' VAR
{ $$ = $1; ((StmtNode*)($1))->append((StmtNode*)VarNode::alloc($3, NULL)); }
;
datablock_decl
: rwDATABLOCK IDENT '(' IDENT parent_block ')' slot_assign_list rwEND
{ $$ = ObjectDeclNode::alloc(ConstantNode::alloc($2), ConstantNode::alloc($4), NULL, $5, $7, NULL, true); }
;
object_decl
: rwDECLARE class_name_expr '(' object_name parent_block object_args ')' object_declare_block rwEND
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, $8.slots, $8.decls, false); }
| rwDECLARE class_name_expr '(' object_name parent_block object_args ')' rwEND
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, NULL, NULL, false); }
;
parent_block
:
{ $$ = NULL; }
| ':' IDENT
{ $$ = $2; }
;
object_name
:
{ $$ = StrConstNode::alloc("", false); }
| expr
{ $$ = $1; }
;
object_args
:
{ $$ = NULL; }
| ',' expr_list
{ $$ = $2; }
;
object_declare_block
:
{ $$.slots = NULL; $$.decls = NULL; }
| slot_assign_list
{ $$.slots = $1; $$.decls = NULL; }
| object_decl_list
{ $$.slots = NULL; $$.decls = $1; }
| slot_assign_list object_decl_list
{ $$.slots = $1; $$.decls = $2; }
;
object_decl_list
: object_decl semicolon
{ $$ = $1; }
| object_decl_list object_decl semicolon
{ $1->append($2); $$ = $1; }
;
stmt_block
: rwBEGIN statement_list rwEND
{ $$ = $2; }
| stmt
{ $$ = $1; }
;
switch_stmt
: rwSWITCH expr case_block rwEND
{ $$ = $3; $3->propagateSwitchExpr($2, false); }
| rwSWITCHSTR expr case_block rwEND
{ $$ = $3; $3->propagateSwitchExpr($2, true); }
;
case_block
: rwCASE case_expr ':' statement_list
{ $$ = IfStmtNode::alloc($1, $2, $4, NULL, false); }
| rwCASE case_expr ':' statement_list rwDEFAULT ':' statement_list
{ $$ = IfStmtNode::alloc($1, $2, $4, $7, false); }
| rwCASE case_expr ':' statement_list case_block
{ $$ = IfStmtNode::alloc($1, $2, $4, $5, true); }
;
case_expr
: expr
{ $$ = $1;}
| case_expr rwCASEOR expr
{ ($1)->append($3); $$=$1; }
;
if_stmt
: rwIF expr rwTHEN statement_list rwEND
{ $$ = IfStmtNode::alloc($1, $2, $4, NULL, false); }
| rwIF expr rwTHEN statement_list rwELSE statement_list rwEND
{ $$ = IfStmtNode::alloc($1, $2, $4, $6, false); }
;
while_stmt
: rwWHILE expr rwBEGIN statement_list rwEND
{ $$ = LoopStmtNode::alloc($1, nil, $2, nil, $4, false); }
;
for_stmt
: rwFOR expr ',' expr ',' expr rwBEGIN statement_list rwEND
{ $$ = LoopStmtNode::alloc($1, $2, $4, $6, $8, false); }
;
expression_stmt
: stmt_expr
{ $$ = $1; }
;
expr
: stmt_expr
{ $$ = $1; }
| '(' expr ')'
{ $$ = $2; }
| expr '^' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '%' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '&' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '|' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '+' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '-' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '*' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '/' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| '-' expr %prec UNARY
{ $$ = FloatUnaryExprNode::alloc($1, $2); }
| '*' expr %prec UNARY
{ $$ = TTagDerefNode::alloc($2); }
| TTAG
{ $$ = TTagExprNode::alloc($1); }
| expr '?' expr ':' expr
{ $$ = ConditionalExprNode::alloc($1, $3, $5); }
| expr '<' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '>' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opGE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opLE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opEQ expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opNE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opOR expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSHL expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSHR expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opAND expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSTREQ expr
{ $$ = StreqExprNode::alloc($1, $3, true); }
| expr opSTRNE expr
{ $$ = StreqExprNode::alloc($1, $3, false); }
| expr '@' expr
{ $$ = StrcatExprNode::alloc($1, $3, $2); }
| '!' expr
{ $$ = IntUnaryExprNode::alloc($1, $2); }
| '~' expr
{ $$ = IntUnaryExprNode::alloc($1, $2); }
| TAGATOM
{ $$ = StrConstNode::alloc($1, true); }
| FLTCONST
{ $$ = FloatNode::alloc($1); }
| INTCONST
{ $$ = IntNode::alloc($1); }
| rwBREAK
{ $$ = ConstantNode::alloc(StringTable->insert("break")); }
| slot_acc
{ $$ = SlotAccessNode::alloc($1.object, $1.array, $1.slotName); }
| IDENT
{ $$ = ConstantNode::alloc($1); }
| STRATOM
{ $$ = StrConstNode::alloc($1, false); }
| VAR
{ $$ = (ExprNode*)VarNode::alloc($1, NULL); }
| VAR '[' aidx_expr ']'
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
;
slot_acc
: expr '.' IDENT
{ $$.object = $1; $$.slotName = $3; $$.array = NULL; }
| expr '.' IDENT '[' aidx_expr ']'
{ $$.object = $1; $$.slotName = $3; $$.array = $5; }
;
class_name_expr
: IDENT
{ $$ = ConstantNode::alloc($1); }
| '(' expr ')'
{ $$ = $2; }
;
assign_op_struct
: opPLUSPLUS
{ $$.token = '+'; $$.expr = FloatNode::alloc(1); }
| opMINUSMINUS
{ $$.token = '-'; $$.expr = FloatNode::alloc(1); }
| opPLASN expr
{ $$.token = '+'; $$.expr = $2; }
| opMIASN expr
{ $$.token = '-'; $$.expr = $2; }
| opMLASN expr
{ $$.token = '*'; $$.expr = $2; }
| opDVASN expr
{ $$.token = '/'; $$.expr = $2; }
| opMODASN expr
{ $$.token = '%'; $$.expr = $2; }
| opANDASN expr
{ $$.token = '&'; $$.expr = $2; }
| opXORASN expr
{ $$.token = '^'; $$.expr = $2; }
| opORASN expr
{ $$.token = '|'; $$.expr = $2; }
| opSLASN expr
{ $$.token = opSHL; $$.expr = $2; }
| opSRASN expr
{ $$.token = opSHR; $$.expr = $2; }
;
stmt_expr
: funcall_expr
{ $$ = $1; }
| object_decl
{ $$ = $1; }
| VAR '=' expr
{ $$ = AssignExprNode::alloc($1, NULL, $3); }
| VAR '[' aidx_expr ']' '=' expr
{ $$ = AssignExprNode::alloc($1, $3, $6); }
| VAR assign_op_struct
{ $$ = AssignOpExprNode::alloc($1, NULL, $2.expr, $2.token); }
| VAR '[' aidx_expr ']' assign_op_struct
{ $$ = AssignOpExprNode::alloc($1, $3, $5.expr, $5.token); }
| slot_acc assign_op_struct
{ $$ = SlotAssignOpNode::alloc($1.object, $1.slotName, $1.array, $2.token, $2.expr); }
| slot_acc '=' expr
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $3); }
| slot_acc '=' '{' expr_list '}'
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $4); }
;
funcall_expr
: IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc($1, NULL, $3, false); }
| IDENT opCOLONCOLON IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc($3, $1, $5, false); }
| expr '.' IDENT '(' expr_list_decl ')'
{ $1->append($5); $$ = FuncCallExprNode::alloc($3, NULL, $1, true); }
;
expr_list_decl
:
{ $$ = NULL; }
| expr_list
{ $$ = $1; }
;
expr_list
: expr
{ $$ = $1; }
| expr_list ',' expr
{ ($1)->append($3); $$ = $1; }
;
slot_assign_list
: slot_assign
{ $$ = $1; }
| slot_assign_list slot_assign
{ $1->append($2); $$ = $1; }
;
slot_assign
: IDENT '=' expr semicolon
{ $$ = SlotAssignNode::alloc(NULL, NULL, $1, $3); }
| rwDATABLOCK '=' expr semicolon
{ $$ = SlotAssignNode::alloc(NULL, NULL, StringTable->insert("datablock"), $3); }
| IDENT '[' aidx_expr ']' '=' expr semicolon
{ $$ = SlotAssignNode::alloc(NULL, $3, $1, $6); }
;
aidx_expr
: expr
{ $$ = $1; }
| aidx_expr ',' expr
{ $$ = CommaCatExprNode::alloc($1, $3); }
;
semicolon:
| semicolon ';'
;
%%

2190
engine/console/BASscan.cc Executable file

File diff suppressed because it is too large Load Diff

289
engine/console/BASscan.l Executable file
View File

@ -0,0 +1,289 @@
%{
#define YYLMAX 4096
#include <stdio.h>
#include "platform/platform.h"
#include "core/stringTable.h"
#include "console/console.h"
#define _CMDGRAM_H_
#include "console/compiler.h"
#include "console/basgram.h"
using namespace Compiler;
#define YY_NEVER_INTERACTIVE 1
// Some basic parsing primitives...
static int Sc_ScanString(int ret);
static int Sc_ScanNum();
static int Sc_ScanVar();
static int Sc_ScanHex();
// Deal with debuggability of FLEX.
#ifdef TORQUE_DEBUG
#define FLEX_DEBUG 1
#else
#define FLEX_DEBUG 0
#endif
//#undef input
//#undef unput
#undef BASgetc
int BASgetc();
#define YY_INPUT(buf,result,max_size) \
{ \
int c = '*', n; \
for ( n = 0; n < max_size && \
(c = BASgetc()) != EOF && c != '\n'; ++n ) \
buf[n] = (char) c; \
if ( c == '\n' ) \
buf[n++] = (char) c; \
result = n; \
}
static int lineIndex;
// Prototypes
void BASSetScanBuffer(const char *sb, const char *fn);
void BASerror(char * s, ...);
// Error reporting
void CMDerror(char * s, ...);
// Reset the parser.
void CMDrestart(FILE *in);
%}
DIGIT [0-9]
INTEGER {DIGIT}+
FLOAT ({INTEGER}\.{INTEGER})|({INTEGER}(\.{INTEGER})?[eE][+-]?{INTEGER})
LETTER [A-Za-z_]
FILECHAR [A-Za-z_\.]
VARMID [:A-Za-z0-9_]
IDTAIL [A-Za-z0-9_]
VARTAIL {VARMID}*{IDTAIL}
VAR [$%]{LETTER}{VARTAIL}*
ID {LETTER}{IDTAIL}*
ILID [$%]{DIGIT}+{LETTER}{VARTAIL}*
FILENAME {FILECHAR}+
SPACE [ \t\v\f]
HEXDIGIT [a-fA-F0-9]
%%
;
{SPACE}+ { }
"//"[^\n\r]* ;
"rem"{SPACE}+[^\n\r]* ;
[\r] ;
[\n] { lineIndex++; }
\"(\\.|[^\\"\n\r])*\" { return(Sc_ScanString(STRATOM)); }
\'(\\.|[^\\'\n\r])*\' { return(Sc_ScanString(TAGATOM)); }
"==" return(BASlval.i = opEQ);
"!=" return(BASlval.i = opNE);
">=" return(BASlval.i = opGE);
"<=" return(BASlval.i = opLE);
"&&" return(BASlval.i = opAND);
"||" return(BASlval.i = opOR);
"::" return(BASlval.i = opCOLONCOLON);
"--" return(BASlval.i = opMINUSMINUS);
"++" return(BASlval.i = opPLUSPLUS);
"$=" return(BASlval.i = opSTREQ);
"!$=" return(BASlval.i = opSTRNE);
"<<" return(BASlval.i = opSHL);
">>" return(BASlval.i = opSHR);
"+=" return(BASlval.i = opPLASN);
"-=" return(BASlval.i = opMIASN);
"*=" return(BASlval.i = opMLASN);
"/=" return(BASlval.i = opDVASN);
"%=" return(BASlval.i = opMODASN);
"&=" return(BASlval.i = opANDASN);
"^=" return(BASlval.i = opXORASN);
"|=" return(BASlval.i = opORASN);
"<<=" return(BASlval.i = opSLASN);
">>=" return(BASlval.i = opSRASN);
"NL" {BASlval.i = '\n'; return '@'; }
"TAB" {BASlval.i = '\t'; return '@'; }
"SPC" {BASlval.i = ' '; return '@'; }
"@" {BASlval.i = 0; return '@'; }
"?" |
"[" |
"]" |
"(" |
")" |
"+" |
"-" |
"*" |
"/" |
"<" |
">" |
"|" |
"." |
"!" |
":" |
";" |
"{" |
"}" |
"," |
"&" |
"%" |
"^" |
"~" |
"=" { return(BASlval.i = BAStext[0]); }
"or" { BASlval.i = lineIndex; return(rwCASEOR); }
"break" { BASlval.i = lineIndex; return(rwBREAK); }
"return" { BASlval.i = lineIndex; return(rwRETURN); }
"else" { BASlval.i = lineIndex; return(rwELSE); }
"while" { BASlval.i = lineIndex; return(rwWHILE); }
"if" { BASlval.i = lineIndex; return(rwIF); }
"then" { BASlval.i = lineIndex; return(rwTHEN); }
"do" { BASlval.i = lineIndex; return(rwBEGIN); }
"begin" { BASlval.i = lineIndex; return(rwBEGIN); }
"end" { BASlval.i = lineIndex; return(rwEND); }
"for" { BASlval.i = lineIndex; return(rwFOR); }
"cfor" { BASlval.i = lineIndex; return(rwCFOR); }
"to" { BASlval.i = lineIndex; return(rwTO); }
"step" { BASlval.i = lineIndex; return(rwSTEP); }
"continue" { BASlval.i = lineIndex; return(rwCONTINUE); }
"function" { BASlval.i = lineIndex; return(rwDEFINE); }
"sub" { BASlval.i = lineIndex; return(rwDEFINE); }
"new" { BASlval.i = lineIndex; return(rwDECLARE); }
"datablock" { BASlval.i = lineIndex; return(rwDATABLOCK); }
"case" { BASlval.i = lineIndex; return(rwCASE); }
"switch$" { BASlval.i = lineIndex; return(rwSWITCHSTR); }
"switch" { BASlval.i = lineIndex; return(rwSWITCH); }
"default" { BASlval.i = lineIndex; return(rwDEFAULT); }
"package" { BASlval.i = lineIndex; return(rwPACKAGE); }
"true" { BASlval.i = 1; return INTCONST; }
"false" { BASlval.i = 0; return INTCONST; }
{VAR} return(Sc_ScanVar());
{ID} { BAStext[BASleng] = 0; BASlval.s = StringTable->insert(BAStext); return(IDENT); }
0[xX]{HEXDIGIT}+ return(Sc_ScanHex());
{INTEGER} { BAStext[BASleng] = 0; BASlval.i = atoi(BAStext); return INTCONST; }
{FLOAT} return Sc_ScanNum();
{ILID} return(ILLEGAL_TOKEN);
. return(ILLEGAL_TOKEN);
%%
/*
* Scan character constant.
*/
/*
* Scan identifier.
*/
static const char *scanBuffer;
static const char *fileName;
static int scanIndex;
const char * BASGetCurrentFile()
{
return fileName;
}
int BASGetCurrentLine()
{
return lineIndex;
}
void BASerror(char *, ...)
{
gSyntaxError = true;
if(fileName)
Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - Syntax error.",
fileName, lineIndex);
else
Con::errorf(ConsoleLogEntry::Script, "Syntax error in input.");
}
void BASSetScanBuffer(const char *sb, const char *fn)
{
scanBuffer = sb;
fileName = fn;
scanIndex = 0;
lineIndex = 1;
}
int BASgetc()
{
int ret = scanBuffer[scanIndex];
if(ret)
scanIndex++;
else
ret = -1;
return ret;
}
int BASwrap()
{
return 1;
}
static int Sc_ScanVar()
{
BAStext[BASleng] = 0;
BASlval.s = StringTable->insert(BAStext);
return(VAR);
}
/*
* Scan string constant.
*/
// Defined in CS_CMD.l
extern void expandEscape(char *dest, const char *src);
extern bool collapseEscape(char *buf);
static int charConv(int in)
{
switch(in)
{
case 'r':
return '\r';
case 'n':
return '\n';
case 't':
return '\t';
default:
return in;
}
}
static int getHexDigit(char c)
{
if(c >= '0' && c <= '9')
return c - '0';
if(c >= 'A' && c <= 'F')
return c - 'A' + 10;
if(c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
}
static int Sc_ScanString(int ret)
{
BAStext[BASleng - 1] = 0;
if(!collapseEscape(BAStext+1))
return -1;
BASlval.str = (char *) consoleAlloc(dStrlen(BAStext));
dStrcpy(BASlval.str, BAStext + 1);
return(ret);
}
static int Sc_ScanNum()
{
BAStext[BASleng] = 0;
BASlval.f = atof(BAStext);
return(FLTCONST);
}
static int Sc_ScanHex()
{
int val = 0;
dSscanf(BAStext, "%x", &val);
BASlval.i = val;
return INTCONST;
}

2140
engine/console/CMDgram.cc Executable file

File diff suppressed because it is too large Load Diff

526
engine/console/CMDgram.y Executable file
View File

@ -0,0 +1,526 @@
%{
// Make sure we don't get gram.h twice.
#define _CMDGRAM_H_
#include <stdlib.h>
#include <stdio.h>
#include "console/console.h"
#include "console/compiler.h"
#include "console/consoleInternal.h"
#ifndef YYDEBUG
#define YYDEBUG 0
#endif
#define YYSSIZE 350
int outtext(char *fmt, ...);
extern int serrors;
#define nil 0
#undef YY_ARGS
#define YY_ARGS(x) x
int CMDlex();
void CMDerror(char *, ...);
#define alloca dMalloc
%}
%{
/* Reserved Word Definitions */
%}
%token <i> rwDEFINE rwENDDEF rwDECLARE
%token <i> rwBREAK rwELSE rwCONTINUE rwGLOBAL
%token <i> rwIF rwNIL rwRETURN rwWHILE rwDO
%token <i> rwENDIF rwENDWHILE rwENDFOR rwDEFAULT
%token <i> rwFOR rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR
%token <i> rwCASEOR rwPACKAGE rwNAMESPACE rwCLASS
%token ILLEGAL_TOKEN
%{
/* Constants and Identifier Definitions */
%}
%token <c> CHRCONST
%token <i> INTCONST
%token <s> TTAG
%token <s> VAR
%token <s> IDENT
%token <str> STRATOM
%token <str> TAGATOM
%token <f> FLTCONST
%{
/* Operator Definitions */
%}
%token <i> '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%'
%token <i> '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@'
%token <i> opMINUSMINUS opPLUSPLUS
%token <i> STMT_SEP
%token <i> opSHL opSHR opPLASN opMIASN opMLASN opDVASN opMODASN opANDASN
%token <i> opXORASN opORASN opSLASN opSRASN opCAT
%token <i> opEQ opNE opGE opLE opAND opOR opSTREQ
%token <i> opCOLONCOLON
%union {
char c;
int i;
const char * s;
char * str;
double f;
StmtNode * stmt;
ExprNode * expr;
SlotAssignNode * slist;
VarNode * var;
SlotDecl slot;
ObjectBlockDecl odcl;
ObjectDeclNode * od;
AssignDecl asn;
IfStmtNode * ifnode;
}
%type <s> parent_block
%type <ifnode> case_block
%type <stmt> switch_stmt
%type <stmt> decl
%type <stmt> decl_list
%type <stmt> package_decl
%type <stmt> fn_decl_stmt
%type <stmt> fn_decl_list
%type <stmt> statement_list
%type <stmt> stmt
%type <expr> expr_list
%type <expr> expr_list_decl
%type <expr> aidx_expr
%type <expr> funcall_expr
%type <expr> object_name
%type <expr> object_args
%type <expr> stmt_expr
%type <expr> case_expr
%type <expr> class_name_expr
%type <stmt> if_stmt
%type <stmt> while_stmt
%type <stmt> for_stmt
%type <stmt> stmt_block
%type <stmt> datablock_decl
%type <od> object_decl
%type <od> object_decl_list
%type <odcl> object_declare_block
%type <expr> expr
%type <slist> slot_assign_list
%type <slist> slot_assign
%type <slot> slot_acc
%type <stmt> expression_stmt
%type <var> var_list
%type <var> var_list_decl
%type <asn> assign_op_struct
%left '['
%right opMODASN opANDASN opXORASN opPLASN opMIASN opMLASN opDVASN opMDASN opNDASN opNTASN opORASN opSLASN opSRASN '='
%left '?' ':'
%left opOR
%left opAND
%left '|'
%left '^'
%left '&'
%left opEQ opNE
%left '<' opLE '>' opGE
%left '@' opCAT opSTREQ opSTRNE
%left opSHL opSHR
%left '+' '-'
%left '*' '/' '%'
%right '!' '~' opPLUSPLUS opMINUSMINUS UNARY
%left '.'
%%
start
: decl_list
{ }
;
decl_list
:
{ $$ = nil; }
| decl_list decl
{ if(!statementList) { statementList = $2; } else { statementList->append($2); } }
;
decl
: stmt
{ $$ = $1; }
| fn_decl_stmt
{ $$ = $1; }
| package_decl
{ $$ = $1; }
;
package_decl
: rwPACKAGE IDENT '{' fn_decl_list '}' ';'
{ $$ = $4; for(StmtNode *walk = ($4);walk;walk = walk->getNext() ) walk->setPackage($2); }
;
fn_decl_list
: fn_decl_stmt
{ $$ = $1; }
| fn_decl_list fn_decl_stmt
{ $$ = $1; ($1)->append($2); }
;
statement_list
:
{ $$ = nil; }
| statement_list stmt
{ if(!$1) { $$ = $2; } else { ($1)->append($2); $$ = $1; } }
;
stmt
: if_stmt
| while_stmt
| for_stmt
| datablock_decl
| switch_stmt
| rwBREAK ';'
{ $$ = BreakStmtNode::alloc(); }
| rwCONTINUE ';'
{ $$ = ContinueStmtNode::alloc(); }
| rwRETURN ';'
{ $$ = ReturnStmtNode::alloc(NULL); }
| rwRETURN expr ';'
{ $$ = ReturnStmtNode::alloc($2); }
| expression_stmt ';'
{ $$ = $1; }
| TTAG '=' expr ';'
{ $$ = TTagSetStmtNode::alloc($1, $3, NULL); }
| TTAG '=' expr ',' expr ';'
{ $$ = TTagSetStmtNode::alloc($1, $3, $5); }
;
fn_decl_stmt
: rwDEFINE IDENT '(' var_list_decl ')' '{' statement_list '}'
{ $$ = FunctionDeclStmtNode::alloc($2, NULL, $4, $7); }
| rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' '{' statement_list '}'
{ $$ = FunctionDeclStmtNode::alloc($4, $2, $6, $9); }
;
var_list_decl
:
{ $$ = NULL; }
| var_list
{ $$ = $1; }
;
var_list
: VAR
{ $$ = VarNode::alloc($1, NULL); }
| var_list ',' VAR
{ $$ = $1; ((StmtNode*)($1))->append((StmtNode*)VarNode::alloc($3, NULL)); }
;
datablock_decl
: rwDATABLOCK IDENT '(' IDENT parent_block ')' '{' slot_assign_list '}' ';'
{ $$ = ObjectDeclNode::alloc(ConstantNode::alloc($2), ConstantNode::alloc($4), NULL, $5, $8, NULL, true); }
;
object_decl
: rwDECLARE class_name_expr '(' object_name parent_block object_args ')' '{' object_declare_block '}'
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, $9.slots, $9.decls, false); }
| rwDECLARE class_name_expr '(' object_name parent_block object_args ')'
{ $$ = ObjectDeclNode::alloc($2, $4, $6, $5, NULL, NULL, false); }
;
parent_block
:
{ $$ = NULL; }
| ':' IDENT
{ $$ = $2; }
;
object_name
:
{ $$ = StrConstNode::alloc("", false); }
| expr
{ $$ = $1; }
;
object_args
:
{ $$ = NULL; }
| ',' expr_list
{ $$ = $2; }
;
object_declare_block
:
{ $$.slots = NULL; $$.decls = NULL; }
| slot_assign_list
{ $$.slots = $1; $$.decls = NULL; }
| object_decl_list
{ $$.slots = NULL; $$.decls = $1; }
| slot_assign_list object_decl_list
{ $$.slots = $1; $$.decls = $2; }
;
object_decl_list
: object_decl ';'
{ $$ = $1; }
| object_decl_list object_decl ';'
{ $1->append($2); $$ = $1; }
;
stmt_block
: '{' statement_list '}'
{ $$ = $2; }
| stmt
{ $$ = $1; }
;
switch_stmt
: rwSWITCH '(' expr ')' '{' case_block '}'
{ $$ = $6; $6->propagateSwitchExpr($3, false); }
| rwSWITCHSTR '(' expr ')' '{' case_block '}'
{ $$ = $6; $6->propagateSwitchExpr($3, true); }
;
case_block
: rwCASE case_expr ':' statement_list
{ $$ = IfStmtNode::alloc($1, $2, $4, NULL, false); }
| rwCASE case_expr ':' statement_list rwDEFAULT ':' statement_list
{ $$ = IfStmtNode::alloc($1, $2, $4, $7, false); }
| rwCASE case_expr ':' statement_list case_block
{ $$ = IfStmtNode::alloc($1, $2, $4, $5, true); }
;
case_expr
: expr
{ $$ = $1;}
| case_expr rwCASEOR expr
{ ($1)->append($3); $$=$1; }
;
if_stmt
: rwIF '(' expr ')' stmt_block
{ $$ = IfStmtNode::alloc($1, $3, $5, NULL, false); }
| rwIF '(' expr ')' stmt_block rwELSE stmt_block
{ $$ = IfStmtNode::alloc($1, $3, $5, $7, false); }
;
while_stmt
: rwWHILE '(' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, nil, $3, nil, $5, false); }
| rwDO stmt_block rwWHILE '(' expr ')'
{ $$ = LoopStmtNode::alloc($3, nil, $5, nil, $2, true); }
;
for_stmt
: rwFOR '(' expr ';' expr ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, $3, $5, $7, $9, false); }
| rwFOR '(' expr ';' expr ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, $3, $5, NULL, $8, false); }
| rwFOR '(' expr ';' ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, $3, NULL, $6, $8, false); }
| rwFOR '(' expr ';' ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, $3, NULL, NULL, $7, false); }
| rwFOR '(' ';' expr ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, NULL, $4, $6, $8, false); }
| rwFOR '(' ';' expr ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, NULL, $4, NULL, $7, false); }
| rwFOR '(' ';' ';' expr ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, NULL, NULL, $5, $7, false); }
| rwFOR '(' ';' ';' ')' stmt_block
{ $$ = LoopStmtNode::alloc($1, NULL, NULL, NULL, $6, false); }
;
expression_stmt
: stmt_expr
{ $$ = $1; }
;
expr
: stmt_expr
{ $$ = $1; }
| '(' expr ')'
{ $$ = $2; }
| expr '^' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '%' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '&' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '|' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '+' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '-' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '*' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| expr '/' expr
{ $$ = FloatBinaryExprNode::alloc($2, $1, $3); }
| '-' expr %prec UNARY
{ $$ = FloatUnaryExprNode::alloc($1, $2); }
| '*' expr %prec UNARY
{ $$ = TTagDerefNode::alloc($2); }
| TTAG
{ $$ = TTagExprNode::alloc($1); }
| expr '?' expr ':' expr
{ $$ = ConditionalExprNode::alloc($1, $3, $5); }
| expr '<' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr '>' expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opGE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opLE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opEQ expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opNE expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opOR expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSHL expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSHR expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opAND expr
{ $$ = IntBinaryExprNode::alloc($2, $1, $3); }
| expr opSTREQ expr
{ $$ = StreqExprNode::alloc($1, $3, true); }
| expr opSTRNE expr
{ $$ = StreqExprNode::alloc($1, $3, false); }
| expr '@' expr
{ $$ = StrcatExprNode::alloc($1, $3, $2); }
| '!' expr
{ $$ = IntUnaryExprNode::alloc($1, $2); }
| '~' expr
{ $$ = IntUnaryExprNode::alloc($1, $2); }
| TAGATOM
{ $$ = StrConstNode::alloc($1, true); }
| FLTCONST
{ $$ = FloatNode::alloc($1); }
| INTCONST
{ $$ = IntNode::alloc($1); }
| rwBREAK
{ $$ = ConstantNode::alloc(StringTable->insert("break")); }
| slot_acc
{ $$ = SlotAccessNode::alloc($1.object, $1.array, $1.slotName); }
| IDENT
{ $$ = ConstantNode::alloc($1); }
| STRATOM
{ $$ = StrConstNode::alloc($1, false); }
| VAR
{ $$ = (ExprNode*)VarNode::alloc($1, NULL); }
| VAR '[' aidx_expr ']'
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
;
slot_acc
: expr '.' IDENT
{ $$.object = $1; $$.slotName = $3; $$.array = NULL; }
| expr '.' IDENT '[' aidx_expr ']'
{ $$.object = $1; $$.slotName = $3; $$.array = $5; }
;
class_name_expr
: IDENT
{ $$ = ConstantNode::alloc($1); }
| '(' expr ')'
{ $$ = $2; }
;
assign_op_struct
: opPLUSPLUS
{ $$.token = '+'; $$.expr = FloatNode::alloc(1); }
| opMINUSMINUS
{ $$.token = '-'; $$.expr = FloatNode::alloc(1); }
| opPLASN expr
{ $$.token = '+'; $$.expr = $2; }
| opMIASN expr
{ $$.token = '-'; $$.expr = $2; }
| opMLASN expr
{ $$.token = '*'; $$.expr = $2; }
| opDVASN expr
{ $$.token = '/'; $$.expr = $2; }
| opMODASN expr
{ $$.token = '%'; $$.expr = $2; }
| opANDASN expr
{ $$.token = '&'; $$.expr = $2; }
| opXORASN expr
{ $$.token = '^'; $$.expr = $2; }
| opORASN expr
{ $$.token = '|'; $$.expr = $2; }
| opSLASN expr
{ $$.token = opSHL; $$.expr = $2; }
| opSRASN expr
{ $$.token = opSHR; $$.expr = $2; }
;
stmt_expr
: funcall_expr
{ $$ = $1; }
| object_decl
{ $$ = $1; }
| VAR '=' expr
{ $$ = AssignExprNode::alloc($1, NULL, $3); }
| VAR '[' aidx_expr ']' '=' expr
{ $$ = AssignExprNode::alloc($1, $3, $6); }
| VAR assign_op_struct
{ $$ = AssignOpExprNode::alloc($1, NULL, $2.expr, $2.token); }
| VAR '[' aidx_expr ']' assign_op_struct
{ $$ = AssignOpExprNode::alloc($1, $3, $5.expr, $5.token); }
| slot_acc assign_op_struct
{ $$ = SlotAssignOpNode::alloc($1.object, $1.slotName, $1.array, $2.token, $2.expr); }
| slot_acc '=' expr
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $3); }
| slot_acc '=' '{' expr_list '}'
{ $$ = SlotAssignNode::alloc($1.object, $1.array, $1.slotName, $4); }
;
funcall_expr
: IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc($1, NULL, $3, false); }
| IDENT opCOLONCOLON IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc($3, $1, $5, false); }
| expr '.' IDENT '(' expr_list_decl ')'
{ $1->append($5); $$ = FuncCallExprNode::alloc($3, NULL, $1, true); }
;
expr_list_decl
:
{ $$ = NULL; }
| expr_list
{ $$ = $1; }
;
expr_list
: expr
{ $$ = $1; }
| expr_list ',' expr
{ ($1)->append($3); $$ = $1; }
;
slot_assign_list
: slot_assign
{ $$ = $1; }
| slot_assign_list slot_assign
{ $1->append($2); $$ = $1; }
;
slot_assign
: IDENT '=' expr ';'
{ $$ = SlotAssignNode::alloc(NULL, NULL, $1, $3); }
| rwDATABLOCK '=' expr ';'
{ $$ = SlotAssignNode::alloc(NULL, NULL, StringTable->insert("datablock"), $3); }
| IDENT '[' aidx_expr ']' '=' expr ';'
{ $$ = SlotAssignNode::alloc(NULL, $3, $1, $6); }
;
aidx_expr
: expr
{ $$ = $1; }
| aidx_expr ',' expr
{ $$ = CommaCatExprNode::alloc($1, $3); }
;
%%

2426
engine/console/CMDscan.cc Executable file

File diff suppressed because it is too large Load Diff

541
engine/console/CMDscan.l Executable file
View File

@ -0,0 +1,541 @@
%{
#define YYLMAX 4096
#include <stdio.h>
#include "platform/platform.h"
#include "core/stringTable.h"
#include "console/console.h"
#include "console/compiler.h"
using namespace Compiler;
#define YY_NEVER_INTERACTIVE 1
// Some basic parsing primitives...
static int Sc_ScanString(int ret);
static int Sc_ScanNum();
static int Sc_ScanVar();
static int Sc_ScanHex();
// Deal with debuggability of FLEX.
#ifdef TORQUE_DEBUG
#define FLEX_DEBUG 1
#else
#define FLEX_DEBUG 0
#endif
// Install our own input code...
#undef CMDgetc
int CMDgetc();
// Hack to make windows lex happy.
#ifndef isatty
inline int isatty(int) { return 0; }
#endif
// Wrap our getc, so that lex doesn't try to do its own buffering/file IO.
#define YY_INPUT(buf,result,max_size) \
{ \
int c = '*', n; \
for ( n = 0; n < max_size && \
(c = CMDgetc()) != EOF && c != '\n'; ++n ) \
buf[n] = (char) c; \
if ( c == '\n' ) \
buf[n++] = (char) c; \
result = n; \
}
// General helper stuff.
static int lineIndex;
// File state
void CMDSetScanBuffer(const char *sb, const char *fn);
const char * CMDgetFileLine(int &lineNumber);
// Error reporting
void CMDerror(char * s, ...);
// Reset the parser.
void CMDrestart(FILE *in);
%}
DIGIT [0-9]
INTEGER {DIGIT}+
FLOAT ({INTEGER}\.{INTEGER})|({INTEGER}(\.{INTEGER})?[eE][+-]?{INTEGER})|(\.{INTEGER})|((\.{INTEGER})?[eE][+-]?{INTEGER})
LETTER [A-Za-z_]
FILECHAR [A-Za-z_\.]
VARMID [:A-Za-z0-9_]
IDTAIL [A-Za-z0-9_]
VARTAIL {VARMID}*{IDTAIL}
VAR [$%]{LETTER}{VARTAIL}*
ID {LETTER}{IDTAIL}*
ILID [$%]{DIGIT}+{LETTER}{VARTAIL}*
FILENAME {FILECHAR}+
SPACE [ \t\v\f]
HEXDIGIT [a-fA-F0-9]
%%
;
{SPACE}+ { }
"//"[^\n\r]* ;
[\r] ;
[\n] {lineIndex++;}
\"(\\.|[^\\"\n\r])*\" { return(Sc_ScanString(STRATOM)); }
\'(\\.|[^\\'\n\r])*\' { return(Sc_ScanString(TAGATOM)); }
"==" return(CMDlval.i = opEQ);
"!=" return(CMDlval.i = opNE);
">=" return(CMDlval.i = opGE);
"<=" return(CMDlval.i = opLE);
"&&" return(CMDlval.i = opAND);
"||" return(CMDlval.i = opOR);
"::" return(CMDlval.i = opCOLONCOLON);
"--" return(CMDlval.i = opMINUSMINUS);
"++" return(CMDlval.i = opPLUSPLUS);
"$=" return(CMDlval.i = opSTREQ);
"!$=" return(CMDlval.i = opSTRNE);
"<<" return(CMDlval.i = opSHL);
">>" return(CMDlval.i = opSHR);
"+=" return(CMDlval.i = opPLASN);
"-=" return(CMDlval.i = opMIASN);
"*=" return(CMDlval.i = opMLASN);
"/=" return(CMDlval.i = opDVASN);
"%=" return(CMDlval.i = opMODASN);
"&=" return(CMDlval.i = opANDASN);
"^=" return(CMDlval.i = opXORASN);
"|=" return(CMDlval.i = opORASN);
"<<=" return(CMDlval.i = opSLASN);
">>=" return(CMDlval.i = opSRASN);
"NL" {CMDlval.i = '\n'; return '@'; }
"TAB" {CMDlval.i = '\t'; return '@'; }
"SPC" {CMDlval.i = ' '; return '@'; }
"@" {CMDlval.i = 0; return '@'; }
"/*" {
register int c = 0, l;
for ( ; ; )
{
l = c;
c = yyinput();
// Is this an open comment?
if ( c == EOF )
{
CMDerror( "unexpected end of file found in comment" );
break;
}
// Increment line numbers.
else if ( c == '\n' )
lineIndex++;
// Did we find the end of the comment?
else if ( l == '*' && c == '/' )
break;
}
}
"?" |
"[" |
"]" |
"(" |
")" |
"+" |
"-" |
"*" |
"/" |
"<" |
">" |
"|" |
"." |
"!" |
":" |
";" |
"{" |
"}" |
"," |
"&" |
"%" |
"^" |
"~" |
"=" { return(CMDlval.i = CMDtext[0]); }
"or" { CMDlval.i = lineIndex; return(rwCASEOR); }
"break" { CMDlval.i = lineIndex; return(rwBREAK); }
"return" { CMDlval.i = lineIndex; return(rwRETURN); }
"else" { CMDlval.i = lineIndex; return(rwELSE); }
"while" { CMDlval.i = lineIndex; return(rwWHILE); }
"do" { CMDlval.i = lineIndex; return(rwDO); }
"if" { CMDlval.i = lineIndex; return(rwIF); }
"for" { CMDlval.i = lineIndex; return(rwFOR); }
"continue" { CMDlval.i = lineIndex; return(rwCONTINUE); }
"function" { CMDlval.i = lineIndex; return(rwDEFINE); }
"new" { CMDlval.i = lineIndex; return(rwDECLARE); }
"datablock" { CMDlval.i = lineIndex; return(rwDATABLOCK); }
"case" { CMDlval.i = lineIndex; return(rwCASE); }
"switch$" { CMDlval.i = lineIndex; return(rwSWITCHSTR); }
"switch" { CMDlval.i = lineIndex; return(rwSWITCH); }
"default" { CMDlval.i = lineIndex; return(rwDEFAULT); }
"package" { CMDlval.i = lineIndex; return(rwPACKAGE); }
"namespace" { CMDlval.i = lineIndex; return(rwNAMESPACE); }
"true" { CMDlval.i = 1; return INTCONST; }
"false" { CMDlval.i = 0; return INTCONST; }
{VAR} return(Sc_ScanVar());
{ID} { CMDtext[CMDleng] = 0; CMDlval.s = StringTable->insert(CMDtext); return(IDENT); }
0[xX]{HEXDIGIT}+ return(Sc_ScanHex());
{INTEGER} { CMDtext[CMDleng] = 0; CMDlval.i = dAtoi(CMDtext); return INTCONST; }
{FLOAT} return Sc_ScanNum();
{ILID} return(ILLEGAL_TOKEN);
. return(ILLEGAL_TOKEN);
%%
static const char *scanBuffer;
static const char *fileName;
static int scanIndex;
const char * CMDGetCurrentFile()
{
return fileName;
}
int CMDGetCurrentLine()
{
return lineIndex;
}
extern bool gConsoleSyntaxError;
void CMDerror(char *format, ...)
{
Compiler::gSyntaxError = true;
const int BUFMAX = 1024;
char tempBuf[BUFMAX];
va_list args;
va_start( args, format );
#ifdef TORQUE_OS_WIN32
_vsnprintf( tempBuf, BUFMAX, format, args );
#else
vsnprintf( tempBuf, BUFMAX, format, args );
#endif
if(fileName)
{
Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - %s", fileName, lineIndex, tempBuf);
#ifndef NO_ADVANCED_ERROR_REPORT
// dhc - lineIndex is bogus. let's try to add some sanity back in.
int i,j,n;
char c;
int linediv = 1;
// first, walk the buffer, trying to detect line ending type.
// this is imperfect, if inconsistant line endings exist...
for (i=0; i<scanIndex; i++)
{
c = scanBuffer[i];
if (c=='\r' && scanBuffer[i+1]=='\n') linediv = 2; // crlf detected
if (c=='\r' || c=='\n' || c==0) break; // enough for us to stop.
}
// grab some of the chars starting at the error location - lineending.
i = 1; j = 0; n = 1;
// find prev lineending
while (n<BUFMAX-8 && i<scanIndex) // cap at file start
{
c = scanBuffer[scanIndex-i];
if ((c=='\r' || c=='\n') && i>BUFMAX>>2) break; // at least get a little data
n++; i++;
}
// find next lineending
while (n<BUFMAX-8 && j<BUFMAX>>1) // cap at half-buf-size forward
{
c = scanBuffer[scanIndex+j];
if (c==0) break;
if ((c=='\r' || c=='\n') && j>BUFMAX>>2) break; // at least get a little data
n++; j++;
}
if (i) i--; // chop off extra linefeed.
if (j) j--; // chop off extra linefeed.
// build our little text block
if (i) dStrncpy(tempBuf,scanBuffer+scanIndex-i,i);
dStrncpy(tempBuf+i,"##", 2); // bracketing.
tempBuf[i+2] = scanBuffer[scanIndex]; // copy the halt character.
dStrncpy(tempBuf+i+3,"##", 2); // bracketing.
if (j) dStrncpy(tempBuf+i+5,scanBuffer+scanIndex+1,j); // +1 to go past current char.
tempBuf[i+j+5] = 0; // null terminate
for(n=0; n<i+j+5; n++) // convert CR to LF if alone...
if (tempBuf[n]=='\r' && tempBuf[n+1]!='\n') tempBuf[n] = '\n';
// write out to console the advanced error report
Con::warnf(ConsoleLogEntry::Script, ">>> Advanced script error report. Line %d.", lineIndex);
Con::warnf(ConsoleLogEntry::Script, ">>> Some error context, with ## on sides of error halt:");
Con::errorf(ConsoleLogEntry::Script, "%s", tempBuf);
Con::warnf(ConsoleLogEntry::Script, ">>> Error report complete.\n");
#endif
// Update the script-visible error buffer.
const char *prevStr = Con::getVariable("$ScriptError");
if (prevStr[0])
dSprintf(tempBuf, sizeof(tempBuf), "%s\n%s Line: %d - Syntax error.", prevStr, fileName, lineIndex);
else
dSprintf(tempBuf, sizeof(tempBuf), "%s Line: %d - Syntax error.", fileName, lineIndex);
Con::setVariable("$ScriptError", tempBuf);
// We also need to mark that we came up with a new error.
static S32 sScriptErrorHash=1000;
Con::setIntVariable("$ScriptErrorHash", sScriptErrorHash++);
}
else
Con::errorf(ConsoleLogEntry::Script, tempBuf);
}
void CMDSetScanBuffer(const char *sb, const char *fn)
{
scanBuffer = sb;
fileName = fn;
scanIndex = 0;
lineIndex = 1;
}
int CMDgetc()
{
int ret = scanBuffer[scanIndex];
if(ret)
scanIndex++;
else
ret = -1;
return ret;
}
int CMDwrap()
{
return 1;
}
static int Sc_ScanVar()
{
// Truncate the temp buffer...
CMDtext[CMDleng] = 0;
// Make it a stringtable string!
CMDlval.s = StringTable->insert(CMDtext);
return(VAR);
}
static int charConv(int in)
{
switch(in)
{
case 'r':
return '\r';
case 'n':
return '\n';
case 't':
return '\t';
default:
return in;
}
}
static int getHexDigit(char c)
{
if(c >= '0' && c <= '9')
return c - '0';
if(c >= 'A' && c <= 'F')
return c - 'A' + 10;
if(c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1;
}
static int Sc_ScanString(int ret)
{
CMDtext[CMDleng - 1] = 0;
if(!collapseEscape(CMDtext+1))
return -1;
CMDlval.str = (char *) consoleAlloc(dStrlen(CMDtext));
dStrcpy(CMDlval.str, CMDtext + 1);
return(ret);
}
void expandEscape(char *dest, const char *src)
{
U8 c;
while((c = (U8) *src++) != 0)
{
if(c == '\"')
{
*dest++ = '\\';
*dest++ = '\"';
}
else if(c == '\\')
{
*dest++ = '\\';
*dest++ = '\\';
}
else if(c == '\r')
{
*dest++ = '\\';
*dest++ = 'r';
}
else if(c == '\n')
{
*dest++ = '\\';
*dest++ = 'n';
}
else if(c == '\t')
{
*dest++ = '\\';
*dest++ = 't';
}
else if(c == '\'')
{
*dest++ = '\\';
*dest++ = '\'';
}
else if((c >= 1 && c <= 7) ||
(c >= 11 && c <= 12) ||
(c >= 14 && c <= 15))
{
/* Remap around: \b = 0x8, \t = 0x9, \n = 0xa, \r = 0xd */
static U8 expandRemap[15] = { 0x0,
0x0,
0x1,
0x2,
0x3,
0x4,
0x5,
0x6,
0x0,
0x0,
0x0,
0x7,
0x8,
0x0,
0x9 };
*dest++ = '\\';
*dest++ = 'c';
if(c == 15)
*dest++ = 'r';
else if(c == 16)
*dest++ = 'p';
else if(c == 17)
*dest++ = 'o';
else
*dest++ = expandRemap[c] + '0';
}
else if(c < 32)
{
*dest++ = '\\';
*dest++ = 'x';
S32 dig1 = c >> 4;
S32 dig2 = c & 0xf;
if(dig1 < 10)
dig1 += '0';
else
dig1 += 'A' - 10;
if(dig2 < 10)
dig2 += '0';
else
dig2 += 'A' - 10;
*dest++ = dig1;
*dest++ = dig2;
}
else
*dest++ = c;
}
*dest = '\0';
}
bool collapseEscape(char *buf)
{
S32 len = dStrlen(buf) + 1;
for(S32 i = 0; i < len;)
{
if(buf[i] == '\\')
{
if(buf[i+1] == 'x')
{
S32 dig1 = getHexDigit(buf[i+2]);
if(dig1 == -1)
return false;
S32 dig2 = getHexDigit(buf[i+3]);
if(dig2 == -1)
return false;
buf[i] = dig1 * 16 + dig2;
dMemmove(buf + i + 1, buf + i + 4, len - i - 3);
len -= 3;
i++;
}
else if(buf[i+1] == 'c')
{
/* Remap around: \b = 0x8, \t = 0x9, \n = 0xa, \r = 0xd */
static U8 collapseRemap[10] = { 0x1,
0x2,
0x3,
0x4,
0x5,
0x6,
0x7,
0xb,
0xc,
0xe };
if(buf[i+2] == 'r')
buf[i] = 15;
else if(buf[i+2] == 'p')
buf[i] = 16;
else if(buf[i+2] == 'o')
buf[i] = 17;
else
{
int dig1 = buf[i+2] - '0';
if(dig1 < 0 || dig1 > 9)
return false;
buf[i] = collapseRemap[dig1];
}
// Make sure we don't put 0x1 at the beginning of the string.
if ((buf[i] == 0x1) && (i == 0))
{
buf[i] = 0x2;
buf[i+1] = 0x1;
dMemmove(buf + i + 2, buf + i + 3, len - i - 1);
len -= 1;
}
else
{
dMemmove(buf + i + 1, buf + i + 3, len - i - 2);
len -= 2;
}
i++;
}
else
{
buf[i] = charConv(buf[i+1]);
dMemmove(buf + i + 1, buf + i + 2, len - i - 1);
len--;
i++;
}
}
else
i++;
}
return true;
}
static int Sc_ScanNum()
{
CMDtext[CMDleng] = 0;
CMDlval.f = dAtof(CMDtext);
return(FLTCONST);
}
static int Sc_ScanHex()
{
S32 val = 0;
dSscanf(CMDtext, "%x", &val);
CMDlval.i = val;
return INTCONST;
}
void CMD_reset()
{
CMDrestart(NULL);
}

461
engine/console/ast.h Executable file
View File

@ -0,0 +1,461 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _AST_H_
#define _AST_H_
class ExprEvalState;
class Namespace;
class SimObject;
class SimGroup;
enum TypeReq {
TypeReqNone,
TypeReqUInt,
TypeReqFloat,
TypeReqString
};
/// Representation of a node for the scripting language parser.
///
/// When the scripting language is evaluated, it is turned from a string representation,
/// into a parse tree, thence into byte code, which is ultimately interpreted by the VM.
///
/// This is the base class for the nodes in the parse tree. There are a great many subclasses,
/// each representing a different language construct.
struct StmtNode
{
StmtNode *next; ///< Next entry in parse tree.
StmtNode();
/// @name next Accessors
/// @{
///
void append(StmtNode *next);
StmtNode *getNext() { return next; }
/// @}
/// @name Debug Info
/// @{
StringTableEntry dbgFileName; ///< Name of file this node is associated with.
S32 dbgLineNumber; ///< Line number this node is associated with.
/// @}
/// @name Breaking
/// @{
void addBreakCount();
void addBreakLine(U32 ip);
/// @}
/// @name Compilation
/// @{
virtual U32 precompileStmt(U32 loopCount) = 0;
virtual U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint) = 0;
virtual void setPackage(StringTableEntry packageName);
/// @}
};
struct BreakStmtNode : StmtNode
{
static BreakStmtNode *alloc();
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
struct ContinueStmtNode : StmtNode
{
static ContinueStmtNode *alloc();
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
/// A mathematical expression.
struct ExprNode : StmtNode
{
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
virtual U32 precompile(TypeReq type) = 0;
virtual U32 compile(U32 *codeStream, U32 ip, TypeReq type) = 0;
virtual TypeReq getPreferredType() = 0;
};
struct ReturnStmtNode : StmtNode
{
ExprNode *expr;
static ReturnStmtNode *alloc(ExprNode *expr);
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
struct IfStmtNode : StmtNode
{
ExprNode *testExpr;
StmtNode *ifBlock, *elseBlock;
U32 endifOffset;
U32 elseOffset;
bool integer;
bool propagate;
static IfStmtNode *alloc(S32 lineNumber, ExprNode *testExpr, StmtNode *ifBlock, StmtNode *elseBlock, bool propagateThrough);
void propagateSwitchExpr(ExprNode *left, bool string);
ExprNode *getSwitchOR(ExprNode *left, ExprNode *list, bool string);
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
struct LoopStmtNode : StmtNode
{
ExprNode *testExpr;
ExprNode *initExpr;
ExprNode *endLoopExpr;
StmtNode *loopBlock;
bool isDoLoop;
U32 breakOffset;
U32 continueOffset;
U32 loopBlockStartOffset;
bool integer;
static LoopStmtNode *alloc(S32 lineNumber, ExprNode *testExpr, ExprNode *initExpr, ExprNode *endLoopExpr, StmtNode *loopBlock, bool isDoLoop);
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
/// A binary mathematical expression (ie, left op right).
struct BinaryExprNode : ExprNode
{
S32 op;
ExprNode *left;
ExprNode *right;
};
struct FloatBinaryExprNode : BinaryExprNode
{
static FloatBinaryExprNode *alloc(S32 op, ExprNode *left, ExprNode *right);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct ConditionalExprNode : ExprNode
{
ExprNode *testExpr;
ExprNode *trueExpr;
ExprNode *falseExpr;
bool integer;
static ConditionalExprNode *alloc(ExprNode *testExpr, ExprNode *trueExpr, ExprNode *falseExpr);
virtual U32 precompile(TypeReq type);
virtual U32 compile(U32 *codeStream, U32 ip, TypeReq type);
virtual TypeReq getPreferredType();
};
struct IntBinaryExprNode : BinaryExprNode
{
TypeReq subType;
U32 operand;
static IntBinaryExprNode *alloc(S32 op, ExprNode *left, ExprNode *right);
void getSubTypeOperand();
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct StreqExprNode : BinaryExprNode
{
bool eq;
static StreqExprNode *alloc(ExprNode *left, ExprNode *right, bool eq);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct StrcatExprNode : BinaryExprNode
{
int appendChar;
static StrcatExprNode *alloc(ExprNode *left, ExprNode *right, int appendChar);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct CommaCatExprNode : BinaryExprNode
{
static CommaCatExprNode *alloc(ExprNode *left, ExprNode *right);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct IntUnaryExprNode : ExprNode
{
S32 op;
ExprNode *expr;
bool integer;
static IntUnaryExprNode *alloc(S32 op, ExprNode *expr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct FloatUnaryExprNode : ExprNode
{
S32 op;
ExprNode *expr;
static FloatUnaryExprNode *alloc(S32 op, ExprNode *expr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct VarNode : ExprNode
{
StringTableEntry varName;
ExprNode *arrayIndex;
static VarNode *alloc(StringTableEntry varName, ExprNode *arrayIndex);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct IntNode : ExprNode
{
S32 value;
U32 index; // if it's converted to float/string
static IntNode *alloc(S32 value);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct FloatNode : ExprNode
{
F64 value;
U32 index;
static FloatNode *alloc(F64 value);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct StrConstNode : ExprNode
{
char *str;
F64 fVal;
U32 index;
bool tag;
static StrConstNode *alloc(char *str, bool tag);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct ConstantNode : ExprNode
{
StringTableEntry value;
F64 fVal;
U32 index;
static ConstantNode *alloc(StringTableEntry value);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct AssignExprNode : ExprNode
{
StringTableEntry varName;
ExprNode *expr;
ExprNode *arrayIndex;
TypeReq subType;
static AssignExprNode *alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct AssignDecl
{
S32 token;
ExprNode *expr;
bool integer;
};
struct AssignOpExprNode : ExprNode
{
StringTableEntry varName;
ExprNode *expr;
ExprNode *arrayIndex;
S32 op;
U32 operand;
TypeReq subType;
static AssignOpExprNode *alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr, S32 op);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct TTagSetStmtNode : StmtNode
{
StringTableEntry tag;
ExprNode *valueExpr;
ExprNode *stringExpr;
static TTagSetStmtNode *alloc(StringTableEntry tag, ExprNode *valueExpr, ExprNode *stringExpr);
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
};
struct TTagDerefNode : ExprNode
{
ExprNode *expr;
static TTagDerefNode *alloc(ExprNode *expr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct TTagExprNode : ExprNode
{
StringTableEntry tag;
static TTagExprNode *alloc(StringTableEntry tag);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct FuncCallExprNode : ExprNode
{
StringTableEntry funcName;
StringTableEntry nameSpace;
ExprNode *args;
U32 callType;
enum {
FunctionCall,
MethodCall,
ParentCall
};
static FuncCallExprNode *alloc(StringTableEntry funcName, StringTableEntry nameSpace, ExprNode *args, bool dot);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct SlotDecl
{
ExprNode *object;
StringTableEntry slotName;
ExprNode *array;
};
struct SlotAccessNode : ExprNode
{
ExprNode *objectExpr, *arrayExpr;
StringTableEntry slotName;
static SlotAccessNode *alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct SlotAssignNode : ExprNode
{
ExprNode *objectExpr, *arrayExpr;
StringTableEntry slotName;
ExprNode *valueExpr;
static SlotAssignNode *alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName, ExprNode *valueExpr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct SlotAssignOpNode : ExprNode
{
ExprNode *objectExpr, *arrayExpr;
StringTableEntry slotName;
S32 op;
ExprNode *valueExpr;
U32 operand;
TypeReq subType;
static SlotAssignOpNode *alloc(ExprNode *objectExpr, StringTableEntry slotName, ExprNode *arrayExpr, S32 op, ExprNode *valueExpr);
U32 precompile(TypeReq type);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
TypeReq getPreferredType();
};
struct ObjectDeclNode : ExprNode
{
ExprNode *classNameExpr;
StringTableEntry parentObject;
ExprNode *objectNameExpr;
ExprNode *argList;
SlotAssignNode *slotDecls;
ObjectDeclNode *subObjects;
bool structDecl;
U32 failOffset;
static ObjectDeclNode *alloc(ExprNode *classNameExpr, ExprNode *objectNameExpr, ExprNode *argList, StringTableEntry parentObject, SlotAssignNode *slotDecls, ObjectDeclNode *subObjects, bool structDecl);
U32 precompile(TypeReq type);
U32 precompileSubObject(bool);
U32 compile(U32 *codeStream, U32 ip, TypeReq type);
U32 compileSubObject(U32 *codeStream, U32 ip, bool);
TypeReq getPreferredType();
};
struct ObjectBlockDecl
{
SlotAssignNode *slots;
ObjectDeclNode *decls;
};
struct FunctionDeclStmtNode : StmtNode
{
StringTableEntry fnName;
VarNode *args;
StmtNode *stmts;
StringTableEntry nameSpace;
StringTableEntry package;
U32 endOffset;
U32 argc;
static FunctionDeclStmtNode *alloc(StringTableEntry fnName, StringTableEntry nameSpace, VarNode *args, StmtNode *stmts);
U32 precompileStmt(U32 loopCount);
U32 compileStmt(U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
void setPackage(StringTableEntry packageName);
};
extern StmtNode *statementList;
extern void createFunction(const char *fnName, VarNode *args, StmtNode *statements);
extern ExprEvalState gEvalState;
extern bool lookupFunction(const char *fnName, VarNode **args, StmtNode **statements);
typedef const char *(*cfunc)(S32 argc, char **argv);
extern bool lookupCFunction(const char *fnName, cfunc *f);
#endif

335
engine/console/astAlloc.cc Executable file
View File

@ -0,0 +1,335 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "console/compiler.h"
#include "console/consoleInternal.h"
using namespace Compiler;
/// @file
///
/// TorqueScript AST node allocators.
///
/// These static methods exist to allocate new AST node for the compiler. They
/// all allocate memory from the consoleAllocator for efficiency, and often take
/// arguments relating to the state of the nodes. They are called from gram.y
/// (really gram.c) as the lexer analyzes the script code.
//------------------------------------------------------------
BreakStmtNode *BreakStmtNode::alloc()
{
BreakStmtNode *ret = (BreakStmtNode *) consoleAlloc(sizeof(BreakStmtNode));
constructInPlace(ret);
return ret;
}
ContinueStmtNode *ContinueStmtNode::alloc()
{
ContinueStmtNode *ret = (ContinueStmtNode *) consoleAlloc(sizeof(ContinueStmtNode));
constructInPlace(ret);
return ret;
}
ReturnStmtNode *ReturnStmtNode::alloc(ExprNode *expr)
{
ReturnStmtNode *ret = (ReturnStmtNode *) consoleAlloc(sizeof(ReturnStmtNode));
constructInPlace(ret);
ret->expr = expr;
return ret;
}
IfStmtNode *IfStmtNode::alloc(S32 lineNumber, ExprNode *testExpr, StmtNode *ifBlock, StmtNode *elseBlock, bool propagate)
{
IfStmtNode *ret = (IfStmtNode *) consoleAlloc(sizeof(IfStmtNode));
constructInPlace(ret);
ret->dbgLineNumber = lineNumber;
ret->testExpr = testExpr;
ret->ifBlock = ifBlock;
ret->elseBlock = elseBlock;
ret->propagate = propagate;
return ret;
}
LoopStmtNode *LoopStmtNode::alloc(S32 lineNumber, ExprNode *initExpr, ExprNode *testExpr, ExprNode *endLoopExpr, StmtNode *loopBlock, bool isDoLoop)
{
LoopStmtNode *ret = (LoopStmtNode *) consoleAlloc(sizeof(LoopStmtNode));
constructInPlace(ret);
ret->dbgLineNumber = lineNumber;
ret->testExpr = testExpr;
ret->initExpr = initExpr;
ret->endLoopExpr = endLoopExpr;
ret->loopBlock = loopBlock;
ret->isDoLoop = isDoLoop;
// Deal with setting some dummy constant nodes if we weren't provided with
// info... This allows us to play nice with missing parts of for(;;) for
// instance.
if(!ret->testExpr) ret->testExpr = IntNode::alloc(1);
return ret;
}
FloatBinaryExprNode *FloatBinaryExprNode::alloc(S32 op, ExprNode *left, ExprNode *right)
{
FloatBinaryExprNode *ret = (FloatBinaryExprNode *) consoleAlloc(sizeof(FloatBinaryExprNode));
constructInPlace(ret);
ret->op = op;
ret->left = left;
ret->right = right;
return ret;
}
IntBinaryExprNode *IntBinaryExprNode::alloc(S32 op, ExprNode *left, ExprNode *right)
{
IntBinaryExprNode *ret = (IntBinaryExprNode *) consoleAlloc(sizeof(IntBinaryExprNode));
constructInPlace(ret);
ret->op = op;
ret->left = left;
ret->right = right;
return ret;
}
StreqExprNode *StreqExprNode::alloc(ExprNode *left, ExprNode *right, bool eq)
{
StreqExprNode *ret = (StreqExprNode *) consoleAlloc(sizeof(StreqExprNode));
constructInPlace(ret);
ret->left = left;
ret->right = right;
ret->eq = eq;
return ret;
}
StrcatExprNode *StrcatExprNode::alloc(ExprNode *left, ExprNode *right, int appendChar)
{
StrcatExprNode *ret = (StrcatExprNode *) consoleAlloc(sizeof(StrcatExprNode));
constructInPlace(ret);
ret->left = left;
ret->right = right;
ret->appendChar = appendChar;
return ret;
}
CommaCatExprNode *CommaCatExprNode::alloc(ExprNode *left, ExprNode *right)
{
CommaCatExprNode *ret = (CommaCatExprNode *) consoleAlloc(sizeof(CommaCatExprNode));
constructInPlace(ret);
ret->left = left;
ret->right = right;
return ret;
}
IntUnaryExprNode *IntUnaryExprNode::alloc(S32 op, ExprNode *expr)
{
IntUnaryExprNode *ret = (IntUnaryExprNode *) consoleAlloc(sizeof(IntUnaryExprNode));
constructInPlace(ret);
ret->op = op;
ret->expr = expr;
return ret;
}
FloatUnaryExprNode *FloatUnaryExprNode::alloc(S32 op, ExprNode *expr)
{
FloatUnaryExprNode *ret = (FloatUnaryExprNode *) consoleAlloc(sizeof(FloatUnaryExprNode));
constructInPlace(ret);
ret->op = op;
ret->expr = expr;
return ret;
}
VarNode *VarNode::alloc(StringTableEntry varName, ExprNode *arrayIndex)
{
VarNode *ret = (VarNode *) consoleAlloc(sizeof(VarNode));
constructInPlace(ret);
ret->varName = varName;
ret->arrayIndex = arrayIndex;
return ret;
}
IntNode *IntNode::alloc(S32 value)
{
IntNode *ret = (IntNode *) consoleAlloc(sizeof(IntNode));
constructInPlace(ret);
ret->value = value;
return ret;
}
ConditionalExprNode *ConditionalExprNode::alloc(ExprNode *testExpr, ExprNode *trueExpr, ExprNode *falseExpr)
{
ConditionalExprNode *ret = (ConditionalExprNode *) consoleAlloc(sizeof(ConditionalExprNode));
constructInPlace(ret);
ret->testExpr = testExpr;
ret->trueExpr = trueExpr;
ret->falseExpr = falseExpr;
ret->integer = false;
return ret;
}
FloatNode *FloatNode::alloc(F64 value)
{
FloatNode *ret = (FloatNode *) consoleAlloc(sizeof(FloatNode));
constructInPlace(ret);
ret->value = value;
return ret;
}
StrConstNode *StrConstNode::alloc(char *str, bool tag)
{
StrConstNode *ret = (StrConstNode *) consoleAlloc(sizeof(StrConstNode));
constructInPlace(ret);
ret->str = (char *) consoleAlloc(dStrlen(str) + 1);
ret->tag = tag;
dStrcpy(ret->str, str);
return ret;
}
ConstantNode *ConstantNode::alloc(StringTableEntry value)
{
ConstantNode *ret = (ConstantNode *) consoleAlloc(sizeof(ConstantNode));
constructInPlace(ret);
ret->value = value;
return ret;
}
AssignExprNode *AssignExprNode::alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr)
{
AssignExprNode *ret = (AssignExprNode *) consoleAlloc(sizeof(AssignExprNode));
constructInPlace(ret);
ret->varName = varName;
ret->expr = expr;
ret->arrayIndex = arrayIndex;
return ret;
}
AssignOpExprNode *AssignOpExprNode::alloc(StringTableEntry varName, ExprNode *arrayIndex, ExprNode *expr, S32 op)
{
AssignOpExprNode *ret = (AssignOpExprNode *) consoleAlloc(sizeof(AssignOpExprNode));
constructInPlace(ret);
ret->varName = varName;
ret->expr = expr;
ret->arrayIndex = arrayIndex;
ret->op = op;
return ret;
}
TTagSetStmtNode *TTagSetStmtNode::alloc(StringTableEntry tag, ExprNode *valueExpr, ExprNode *stringExpr)
{
TTagSetStmtNode *ret = (TTagSetStmtNode *) consoleAlloc(sizeof(TTagSetStmtNode));
constructInPlace(ret);
ret->tag = tag;
ret->valueExpr = valueExpr;
ret->stringExpr = stringExpr;
return ret;
}
TTagDerefNode *TTagDerefNode::alloc(ExprNode *expr)
{
TTagDerefNode *ret = (TTagDerefNode *) consoleAlloc(sizeof(TTagDerefNode));
constructInPlace(ret);
ret->expr = expr;
return ret;
}
TTagExprNode *TTagExprNode::alloc(StringTableEntry tag)
{
TTagExprNode *ret = (TTagExprNode *) consoleAlloc(sizeof(TTagExprNode));
constructInPlace(ret);
ret->tag = tag;
return ret;
}
FuncCallExprNode *FuncCallExprNode::alloc(StringTableEntry funcName, StringTableEntry nameSpace, ExprNode *args, bool dot)
{
FuncCallExprNode *ret = (FuncCallExprNode *) consoleAlloc(sizeof(FuncCallExprNode));
constructInPlace(ret);
ret->funcName = funcName;
ret->nameSpace = nameSpace;
ret->args = args;
if(dot)
ret->callType = MethodCall;
else
{
if(nameSpace && !dStricmp(nameSpace, "Parent"))
ret->callType = ParentCall;
else
ret->callType = FunctionCall;
}
return ret;
}
SlotAccessNode *SlotAccessNode::alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName)
{
SlotAccessNode *ret = (SlotAccessNode *) consoleAlloc(sizeof(SlotAccessNode));
constructInPlace(ret);
ret->objectExpr = objectExpr;
ret->arrayExpr = arrayExpr;
ret->slotName = slotName;
return ret;
}
SlotAssignNode *SlotAssignNode::alloc(ExprNode *objectExpr, ExprNode *arrayExpr, StringTableEntry slotName, ExprNode *valueExpr)
{
SlotAssignNode *ret = (SlotAssignNode *) consoleAlloc(sizeof(SlotAssignNode));
constructInPlace(ret);
ret->objectExpr = objectExpr;
ret->arrayExpr = arrayExpr;
ret->slotName = slotName;
ret->valueExpr = valueExpr;
return ret;
}
SlotAssignOpNode *SlotAssignOpNode::alloc(ExprNode *objectExpr, StringTableEntry slotName, ExprNode *arrayExpr, S32 op, ExprNode *valueExpr)
{
SlotAssignOpNode *ret = (SlotAssignOpNode *) consoleAlloc(sizeof(SlotAssignOpNode));
constructInPlace(ret);
ret->objectExpr = objectExpr;
ret->arrayExpr = arrayExpr;
ret->slotName = slotName;
ret->op = op;
ret->valueExpr = valueExpr;
return ret;
}
ObjectDeclNode *ObjectDeclNode::alloc(ExprNode *classNameExpr, ExprNode *objectNameExpr, ExprNode *argList, StringTableEntry parentObject, SlotAssignNode *slotDecls, ObjectDeclNode *subObjects, bool structDecl)
{
ObjectDeclNode *ret = (ObjectDeclNode *) consoleAlloc(sizeof(ObjectDeclNode));
constructInPlace(ret);
ret->classNameExpr = classNameExpr;
ret->objectNameExpr = objectNameExpr;
ret->argList = argList;
ret->slotDecls = slotDecls;
ret->subObjects = subObjects;
ret->structDecl = structDecl;
if(parentObject)
ret->parentObject = parentObject;
else
ret->parentObject = StringTable->insert("");
return ret;
}
FunctionDeclStmtNode *FunctionDeclStmtNode::alloc(StringTableEntry fnName, StringTableEntry nameSpace, VarNode *args, StmtNode *stmts)
{
FunctionDeclStmtNode *ret = (FunctionDeclStmtNode *) consoleAlloc(sizeof(FunctionDeclStmtNode));
constructInPlace(ret);
ret->fnName = fnName;
ret->args = args;
ret->stmts = stmts;
ret->nameSpace = nameSpace;
ret->package = NULL;
return ret;
}

1680
engine/console/astNodes.cc Executable file

File diff suppressed because it is too large Load Diff

85
engine/console/basgram.h Executable file
View File

@ -0,0 +1,85 @@
typedef union {
char c;
int i;
const char *s;
char *str;
double f;
StmtNode *stmt;
ExprNode *expr;
SlotAssignNode *slist;
VarNode *var;
SlotDecl slot;
ObjectBlockDecl odcl;
ObjectDeclNode *od;
AssignDecl asn;
IfStmtNode *ifnode;
} YYSTYPE;
#define rwDEFINE 258
#define rwENDDEF 259
#define rwDECLARE 260
#define rwBREAK 261
#define rwELSE 262
#define rwCONTINUE 263
#define rwGLOBAL 264
#define rwIF 265
#define rwNIL 266
#define rwRETURN 267
#define rwWHILE 268
#define rwENDIF 269
#define rwENDWHILE 270
#define rwENDFOR 271
#define rwDEFAULT 272
#define rwFOR 273
#define rwDATABLOCK 274
#define rwSWITCH 275
#define rwCASE 276
#define rwSWITCHSTR 277
#define rwCASEOR 278
#define rwPACKAGE 279
#define ILLEGAL_TOKEN 280
#define CHRCONST 281
#define INTCONST 282
#define TTAG 283
#define VAR 284
#define IDENT 285
#define STRATOM 286
#define TAGATOM 287
#define FLTCONST 288
#define opMINUSMINUS 289
#define opPLUSPLUS 290
#define STMT_SEP 291
#define opSHL 292
#define opSHR 293
#define opPLASN 294
#define opMIASN 295
#define opMLASN 296
#define opDVASN 297
#define opMODASN 298
#define opANDASN 299
#define opXORASN 300
#define opORASN 301
#define opSLASN 302
#define opSRASN 303
#define opCAT 304
#define opEQ 305
#define opNE 306
#define opGE 307
#define opLE 308
#define opAND 309
#define opOR 310
#define opSTREQ 311
#define opCOLONCOLON 312
#define opMDASN 313
#define opNDASN 314
#define opNTASN 315
#define opSTRNE 316
#define UNARY 317
#define rwTHEN 318
#define rwEND 319
#define rwBEGIN 320
#define rwCFOR 321
#define rwTO 322
#define rwSTEP 323
extern YYSTYPE BASlval;

6
engine/console/bison.bat Executable file
View File

@ -0,0 +1,6 @@
echo Changing to %4 ...
cd %4
echo Generating %2 and %3 with prefix %1.
..\..\bin\bison\bison.exe -o %2 %3 --defines -p %1
echo Renaming %2 to %5 .
move /Y %2 %5

686
engine/console/bison.simple Executable file
View File

@ -0,0 +1,686 @@
/* -*-C-*- Note some compilers choke on comments on `#line' lines. */
#line 3 "bison.simple"
/* Skeleton output parser for bison,
Copyright (C) 1984, 1989, 1990 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
/* As a special exception, when this file is copied by Bison into a
Bison output file, you may use that output file without restriction.
This special exception was added by the Free Software Foundation
in version 1.24 of Bison. */
#ifndef alloca
#ifdef __GNUC__
#define alloca __builtin_alloca
#else /* not GNU C. */
#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi)
#include <alloca.h>
#else /* not sparc */
#if defined (MSDOS) && !defined (__TURBOC__)
#include <malloc.h>
#else /* not MSDOS, or __TURBOC__ */
#if defined(_AIX)
#include <malloc.h>
#pragma alloca
#else /* not MSDOS, __TURBOC__, or _AIX */
#ifdef __hpux
#ifdef __cplusplus
extern "C" {
void *alloca (unsigned int);
};
#else /* not __cplusplus */
void *alloca ();
#endif /* not __cplusplus */
#endif /* __hpux */
#endif /* not _AIX */
#endif /* not MSDOS, or __TURBOC__ */
#endif /* not sparc. */
#endif /* not GNU C. */
#endif /* alloca not defined. */
/* This is the parser code that is written into each bison parser
when the %semantic_parser declaration is not specified in the grammar.
It was written by Richard Stallman by simplifying the hairy parser
used when %semantic_parser is specified. */
/* Note: there must be only one dollar sign in this file.
It is replaced by the list of actions, each action
as one case of the switch. */
#define yyerrok (yyerrstatus = 0)
#define yyclearin (yychar = YYEMPTY)
#define YYEMPTY -2
#define YYEOF 0
#define YYACCEPT return(0)
#define YYABORT return(1)
#define YYERROR goto yyerrlab1
/* Like YYERROR except do call yyerror.
This remains here temporarily to ease the
transition to the new meaning of YYERROR, for GCC.
Once GCC version 2 has supplanted version 1, this can go. */
#define YYFAIL goto yyerrlab
#define YYRECOVERING() (!!yyerrstatus)
#define YYBACKUP(token, value) \
do \
if (yychar == YYEMPTY && yylen == 1) \
{ yychar = (token), yylval = (value); \
yychar1 = YYTRANSLATE (yychar); \
YYPOPSTACK; \
goto yybackup; \
} \
else \
{ yyerror ("syntax error: cannot back up"); YYERROR; } \
while (0)
#define YYTERROR 1
#define YYERRCODE 256
#ifndef YYPURE
#define YYLEX yylex()
#endif
#ifdef YYPURE
#ifdef YYLSP_NEEDED
#ifdef YYLEX_PARAM
#define YYLEX yylex(&yylval, &yylloc, YYLEX_PARAM)
#else
#define YYLEX yylex(&yylval, &yylloc)
#endif
#else /* not YYLSP_NEEDED */
#ifdef YYLEX_PARAM
#define YYLEX yylex(&yylval, YYLEX_PARAM)
#else
#define YYLEX yylex(&yylval)
#endif
#endif /* not YYLSP_NEEDED */
#endif
/* If nonreentrant, generate the variables here */
#ifndef YYPURE
int yychar; /* the lookahead symbol */
YYSTYPE yylval; /* the semantic value of the */
/* lookahead symbol */
#ifdef YYLSP_NEEDED
YYLTYPE yylloc; /* location data for the lookahead */
/* symbol */
#endif
int yynerrs; /* number of parse errors so far */
#endif /* not YYPURE */
#if YYDEBUG != 0
int yydebug; /* nonzero means print parse trace */
/* Since this is uninitialized, it does not stop multiple parsers
from coexisting. */
#endif
/* YYINITDEPTH indicates the initial size of the parser's stacks */
#ifndef YYINITDEPTH
#define YYINITDEPTH 200
#endif
/* YYMAXDEPTH is the maximum size the stacks can grow to
(effective only if the built-in stack extension method is used). */
#if YYMAXDEPTH == 0
#undef YYMAXDEPTH
#endif
#ifndef YYMAXDEPTH
#define YYMAXDEPTH 10000
#endif
/* Prevent warning if -Wstrict-prototypes. */
#ifdef __GNUC__
int yyparse (void);
#endif
#if __GNUC__ > 1 /* GNU C and GNU C++ define this. */
#define __yy_memcpy(FROM,TO,COUNT) __builtin_memcpy(TO,FROM,COUNT)
#else /* not GNU C or C++ */
#ifndef __cplusplus
/* This is the most reliable way to avoid incompatibilities
in available built-in functions on various systems. */
static void
__yy_memcpy (from, to, count)
char *from;
char *to;
int count;
{
register char *f = from;
register char *t = to;
register int i = count;
while (i-- > 0)
*t++ = *f++;
}
#else /* __cplusplus */
/* This is the most reliable way to avoid incompatibilities
in available built-in functions on various systems. */
static void
__yy_memcpy (char *from, char *to, int count)
{
register char *f = from;
register char *t = to;
register int i = count;
while (i-- > 0)
*t++ = *f++;
}
#endif
#endif
#line 192 "bison.simple"
/* The user can define YYPARSE_PARAM as the name of an argument to be passed
into yyparse. The argument should have type void *.
It should actually point to an object.
Grammar actions can access the variable by casting it
to the proper pointer type. */
#ifdef YYPARSE_PARAM
#define YYPARSE_PARAM_DECL void *YYPARSE_PARAM;
#else
#define YYPARSE_PARAM
#define YYPARSE_PARAM_DECL
#endif
int
yyparse(YYPARSE_PARAM)
YYPARSE_PARAM_DECL
{
register int yystate;
register int yyn;
register short *yyssp;
register YYSTYPE *yyvsp;
int yyerrstatus; /* number of tokens to shift before error messages enabled */
int yychar1 = 0; /* lookahead token as an internal (translated) token number */
short yyssa[YYINITDEPTH]; /* the state stack */
YYSTYPE yyvsa[YYINITDEPTH]; /* the semantic value stack */
short *yyss = yyssa; /* refer to the stacks thru separate pointers */
YYSTYPE *yyvs = yyvsa; /* to allow yyoverflow to reallocate them elsewhere */
#ifdef YYLSP_NEEDED
YYLTYPE yylsa[YYINITDEPTH]; /* the location stack */
YYLTYPE *yyls = yylsa;
YYLTYPE *yylsp;
#define YYPOPSTACK (yyvsp--, yyssp--, yylsp--)
#else
#define YYPOPSTACK (yyvsp--, yyssp--)
#endif
int yystacksize = YYINITDEPTH;
#ifdef YYPURE
int yychar;
YYSTYPE yylval;
int yynerrs;
#ifdef YYLSP_NEEDED
YYLTYPE yylloc;
#endif
#endif
YYSTYPE yyval; /* the variable used to return */
/* semantic values from the action */
/* routines */
int yylen;
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Starting parse\n");
#endif
yystate = 0;
yyerrstatus = 0;
yynerrs = 0;
yychar = YYEMPTY; /* Cause a token to be read. */
/* Initialize stack pointers.
Waste one element of value and location stack
so that they stay on the same level as the state stack.
The wasted elements are never initialized. */
yyssp = yyss - 1;
yyvsp = yyvs;
#ifdef YYLSP_NEEDED
yylsp = yyls;
#endif
/* Push a new state, which is found in yystate . */
/* In all cases, when you get here, the value and location stacks
have just been pushed. so pushing a state here evens the stacks. */
yynewstate:
*++yyssp = yystate;
if (yyssp >= yyss + yystacksize - 1)
{
/* Give user a chance to reallocate the stack */
/* Use copies of these so that the &'s don't force the real ones into memory. */
YYSTYPE *yyvs1 = yyvs;
short *yyss1 = yyss;
#ifdef YYLSP_NEEDED
YYLTYPE *yyls1 = yyls;
#endif
/* Get the current used size of the three stacks, in elements. */
int size = yyssp - yyss + 1;
#ifdef yyoverflow
/* Each stack pointer address is followed by the size of
the data in use in that stack, in bytes. */
#ifdef YYLSP_NEEDED
/* This used to be a conditional around just the two extra args,
but that might be undefined if yyoverflow is a macro. */
yyoverflow("parser stack overflow",
&yyss1, size * sizeof (*yyssp),
&yyvs1, size * sizeof (*yyvsp),
&yyls1, size * sizeof (*yylsp),
&yystacksize);
#else
yyoverflow("parser stack overflow",
&yyss1, size * sizeof (*yyssp),
&yyvs1, size * sizeof (*yyvsp),
&yystacksize);
#endif
yyss = yyss1; yyvs = yyvs1;
#ifdef YYLSP_NEEDED
yyls = yyls1;
#endif
#else /* no yyoverflow */
/* Extend the stack our own way. */
if (yystacksize >= YYMAXDEPTH)
{
yyerror("parser stack overflow");
return 2;
}
yystacksize *= 2;
if (yystacksize > YYMAXDEPTH)
yystacksize = YYMAXDEPTH;
yyss = (short *) alloca (yystacksize * sizeof (*yyssp));
__yy_memcpy ((char *)yyss1, (char *)yyss, size * sizeof (*yyssp));
yyvs = (YYSTYPE *) alloca (yystacksize * sizeof (*yyvsp));
__yy_memcpy ((char *)yyvs1, (char *)yyvs, size * sizeof (*yyvsp));
#ifdef YYLSP_NEEDED
yyls = (YYLTYPE *) alloca (yystacksize * sizeof (*yylsp));
__yy_memcpy ((char *)yyls1, (char *)yyls, size * sizeof (*yylsp));
#endif
#endif /* no yyoverflow */
yyssp = yyss + size - 1;
yyvsp = yyvs + size - 1;
#ifdef YYLSP_NEEDED
yylsp = yyls + size - 1;
#endif
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Stack size increased to %d\n", yystacksize);
#endif
if (yyssp >= yyss + yystacksize - 1)
YYABORT;
}
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Entering state %d\n", yystate);
#endif
goto yybackup;
yybackup:
/* Do appropriate processing given the current state. */
/* Read a lookahead token if we need one and don't already have one. */
/* yyresume: */
/* First try to decide what to do without reference to lookahead token. */
yyn = yypact[yystate];
if (yyn == YYFLAG)
goto yydefault;
/* Not known => get a lookahead token if don't already have one. */
/* yychar is either YYEMPTY or YYEOF
or a valid token in external form. */
if (yychar == YYEMPTY)
{
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Reading a token: ");
#endif
yychar = YYLEX;
}
/* Convert token to internal form (in yychar1) for indexing tables with */
if (yychar <= 0) /* This means end of input. */
{
yychar1 = 0;
yychar = YYEOF; /* Don't call YYLEX any more */
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Now at end of input.\n");
#endif
}
else
{
yychar1 = YYTRANSLATE(yychar);
#if YYDEBUG != 0
if (yydebug)
{
fprintf (stderr, "Next token is %d (%s", yychar, yytname[yychar1]);
/* Give the individual parser a way to print the precise meaning
of a token, for further debugging info. */
#ifdef YYPRINT
YYPRINT (stderr, yychar, yylval);
#endif
fprintf (stderr, ")\n");
}
#endif
}
yyn += yychar1;
if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1)
goto yydefault;
yyn = yytable[yyn];
/* yyn is what to do for this token type in this state.
Negative => reduce, -yyn is rule number.
Positive => shift, yyn is new state.
New state is final state => don't bother to shift,
just return success.
0, or most negative number => error. */
if (yyn < 0)
{
if (yyn == YYFLAG)
goto yyerrlab;
yyn = -yyn;
goto yyreduce;
}
else if (yyn == 0)
goto yyerrlab;
if (yyn == YYFINAL)
YYACCEPT;
/* Shift the lookahead token. */
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Shifting token %d (%s), ", yychar, yytname[yychar1]);
#endif
/* Discard the token being shifted unless it is eof. */
if (yychar != YYEOF)
yychar = YYEMPTY;
*++yyvsp = yylval;
#ifdef YYLSP_NEEDED
*++yylsp = yylloc;
#endif
/* count tokens shifted since error; after three, turn off error status. */
if (yyerrstatus) yyerrstatus--;
yystate = yyn;
goto yynewstate;
/* Do the default action for the current state. */
yydefault:
yyn = yydefact[yystate];
if (yyn == 0)
goto yyerrlab;
/* Do a reduction. yyn is the number of a rule to reduce with. */
yyreduce:
yylen = yyr2[yyn];
if (yylen > 0)
yyval = yyvsp[1-yylen]; /* implement default value of the action */
#if YYDEBUG != 0
if (yydebug)
{
int i;
fprintf (stderr, "Reducing via rule %d (line %d), ",
yyn, yyrline[yyn]);
/* Print the symbols being reduced, and their result. */
for (i = yyprhs[yyn]; yyrhs[i] > 0; i++)
fprintf (stderr, "%s ", yytname[yyrhs[i]]);
fprintf (stderr, " -> %s\n", yytname[yyr1[yyn]]);
}
#endif
$ /* the action file gets copied in in place of this dollarsign */
#line 487 "bison.simple"
yyvsp -= yylen;
yyssp -= yylen;
#ifdef YYLSP_NEEDED
yylsp -= yylen;
#endif
#if YYDEBUG != 0
if (yydebug)
{
short *ssp1 = yyss - 1;
fprintf (stderr, "state stack now");
while (ssp1 != yyssp)
fprintf (stderr, " %d", *++ssp1);
fprintf (stderr, "\n");
}
#endif
*++yyvsp = yyval;
#ifdef YYLSP_NEEDED
yylsp++;
if (yylen == 0)
{
yylsp->first_line = yylloc.first_line;
yylsp->first_column = yylloc.first_column;
yylsp->last_line = (yylsp-1)->last_line;
yylsp->last_column = (yylsp-1)->last_column;
yylsp->text = 0;
}
else
{
yylsp->last_line = (yylsp+yylen-1)->last_line;
yylsp->last_column = (yylsp+yylen-1)->last_column;
}
#endif
/* Now "shift" the result of the reduction.
Determine what state that goes to,
based on the state we popped back to
and the rule number reduced by. */
yyn = yyr1[yyn];
yystate = yypgoto[yyn - YYNTBASE] + *yyssp;
if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp)
yystate = yytable[yystate];
else
yystate = yydefgoto[yyn - YYNTBASE];
goto yynewstate;
yyerrlab: /* here on detecting error */
if (! yyerrstatus)
/* If not already recovering from an error, report this error. */
{
++yynerrs;
#ifdef YYERROR_VERBOSE
yyn = yypact[yystate];
if (yyn > YYFLAG && yyn < YYLAST)
{
int size = 0;
char *msg;
int x, count;
count = 0;
/* Start X at -yyn if nec to avoid negative indexes in yycheck. */
for (x = (yyn < 0 ? -yyn : 0);
x < (sizeof(yytname) / sizeof(char *)); x++)
if (yycheck[x + yyn] == x)
size += strlen(yytname[x]) + 15, count++;
msg = (char *) malloc(size + 15);
if (msg != 0)
{
strcpy(msg, "parse error");
if (count < 5)
{
count = 0;
for (x = (yyn < 0 ? -yyn : 0);
x < (sizeof(yytname) / sizeof(char *)); x++)
if (yycheck[x + yyn] == x)
{
strcat(msg, count == 0 ? ", expecting `" : " or `");
strcat(msg, yytname[x]);
strcat(msg, "'");
count++;
}
}
yyerror(msg);
free(msg);
}
else
yyerror ("parse error; also virtual memory exceeded");
}
else
#endif /* YYERROR_VERBOSE */
yyerror("parse error");
}
goto yyerrlab1;
yyerrlab1: /* here on error raised explicitly by an action */
if (yyerrstatus == 3)
{
/* if just tried and failed to reuse lookahead token after an error, discard it. */
/* return failure if at end of input */
if (yychar == YYEOF)
YYABORT;
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Discarding token %d (%s).\n", yychar, yytname[yychar1]);
#endif
yychar = YYEMPTY;
}
/* Else will try to reuse lookahead token
after shifting the error token. */
yyerrstatus = 3; /* Each real token shifted decrements this */
goto yyerrhandle;
yyerrdefault: /* current state does not do anything special for the error token. */
#if 0
/* This is wrong; only states that explicitly want error tokens
should shift them. */
yyn = yydefact[yystate]; /* If its default is to accept any token, ok. Otherwise pop it.*/
if (yyn) goto yydefault;
#endif
yyerrpop: /* pop the current state because it cannot handle the error token */
if (yyssp == yyss) YYABORT;
yyvsp--;
yystate = *--yyssp;
#ifdef YYLSP_NEEDED
yylsp--;
#endif
#if YYDEBUG != 0
if (yydebug)
{
short *ssp1 = yyss - 1;
fprintf (stderr, "Error: state stack now");
while (ssp1 != yyssp)
fprintf (stderr, " %d", *++ssp1);
fprintf (stderr, "\n");
}
#endif
yyerrhandle:
yyn = yypact[yystate];
if (yyn == YYFLAG)
goto yyerrdefault;
yyn += YYTERROR;
if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR)
goto yyerrdefault;
yyn = yytable[yyn];
if (yyn < 0)
{
if (yyn == YYFLAG)
goto yyerrpop;
yyn = -yyn;
goto yyreduce;
}
else if (yyn == 0)
goto yyerrpop;
if (yyn == YYFINAL)
YYACCEPT;
#if YYDEBUG != 0
if (yydebug)
fprintf(stderr, "Shifting error token, ");
#endif
*++yyvsp = yylval;
#ifdef YYLSP_NEEDED
*++yylsp = yylloc;
#endif
yystate = yyn;
goto yynewstate;
}

82
engine/console/cmdgram.h Executable file
View File

@ -0,0 +1,82 @@
typedef union {
char c;
int i;
const char * s;
char * str;
double f;
StmtNode * stmt;
ExprNode * expr;
SlotAssignNode * slist;
VarNode * var;
SlotDecl slot;
ObjectBlockDecl odcl;
ObjectDeclNode * od;
AssignDecl asn;
IfStmtNode * ifnode;
} YYSTYPE;
#define rwDEFINE 258
#define rwENDDEF 259
#define rwDECLARE 260
#define rwBREAK 261
#define rwELSE 262
#define rwCONTINUE 263
#define rwGLOBAL 264
#define rwIF 265
#define rwNIL 266
#define rwRETURN 267
#define rwWHILE 268
#define rwDO 269
#define rwENDIF 270
#define rwENDWHILE 271
#define rwENDFOR 272
#define rwDEFAULT 273
#define rwFOR 274
#define rwDATABLOCK 275
#define rwSWITCH 276
#define rwCASE 277
#define rwSWITCHSTR 278
#define rwCASEOR 279
#define rwPACKAGE 280
#define rwNAMESPACE 281
#define rwCLASS 282
#define ILLEGAL_TOKEN 283
#define CHRCONST 284
#define INTCONST 285
#define TTAG 286
#define VAR 287
#define IDENT 288
#define STRATOM 289
#define TAGATOM 290
#define FLTCONST 291
#define opMINUSMINUS 292
#define opPLUSPLUS 293
#define STMT_SEP 294
#define opSHL 295
#define opSHR 296
#define opPLASN 297
#define opMIASN 298
#define opMLASN 299
#define opDVASN 300
#define opMODASN 301
#define opANDASN 302
#define opXORASN 303
#define opORASN 304
#define opSLASN 305
#define opSRASN 306
#define opCAT 307
#define opEQ 308
#define opNE 309
#define opGE 310
#define opLE 311
#define opAND 312
#define opOR 313
#define opSTREQ 314
#define opCOLONCOLON 315
#define opMDASN 316
#define opNDASN 317
#define opNTASN 318
#define opSTRNE 319
#define UNARY 320
extern YYSTYPE CMDlval;

565
engine/console/codeBlock.cc Executable file
View File

@ -0,0 +1,565 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "console/compiler.h"
#include "console/codeBlock.h"
#include "console/telnetDebugger.h"
#include "core/resManager.h"
using namespace Compiler;
bool CodeBlock::smInFunction = false;
U32 CodeBlock::smBreakLineCount = 0;
CodeBlock * CodeBlock::smCodeBlockList = NULL;
CodeBlock * CodeBlock::smCurrentCodeBlock = NULL;
ConsoleParser *CodeBlock::smCurrentParser = NULL;
//-------------------------------------------------------------------------
CodeBlock::CodeBlock()
{
globalStrings = NULL;
functionStrings = NULL;
globalFloats = NULL;
functionFloats = NULL;
lineBreakPairs = NULL;
breakList = NULL;
breakListSize = 0;
refCount = 0;
code = NULL;
name = NULL;
mRoot = StringTable->insert("");
}
CodeBlock::~CodeBlock()
{
// Make sure we aren't lingering in the current code block...
AssertFatal(smCurrentCodeBlock != this, "CodeBlock::~CodeBlock - Caught lingering in smCurrentCodeBlock!")
if(name)
removeFromCodeList();
delete[] const_cast<char*>(globalStrings);
delete[] const_cast<char*>(functionStrings);
delete[] globalFloats;
delete[] functionFloats;
delete[] code;
delete[] breakList;
}
//-------------------------------------------------------------------------
StringTableEntry CodeBlock::getCurrentCodeBlockName()
{
if (CodeBlock::getCurrentBlock())
return CodeBlock::getCurrentBlock()->name;
else
return NULL;
}
CodeBlock *CodeBlock::find(StringTableEntry name)
{
for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
if(walk->name == name)
return walk;
return NULL;
}
//-------------------------------------------------------------------------
void CodeBlock::addToCodeList()
{
// remove any code blocks with my name
for(CodeBlock **walk = &smCodeBlockList; *walk;walk = &((*walk)->nextFile))
{
if((*walk)->name == name)
{
*walk = (*walk)->nextFile;
break;
}
}
nextFile = smCodeBlockList;
smCodeBlockList = this;
}
void CodeBlock::clearAllBreaks()
{
if(!lineBreakPairs)
return;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
code[p[1]] = p[0] & 0xFF;
}
}
void CodeBlock::clearBreakpoint(U32 lineNumber)
{
if(!lineBreakPairs)
return;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
if((p[0] >> 8) == lineNumber)
{
code[p[1]] = p[0] & 0xFF;
return;
}
}
}
void CodeBlock::setAllBreaks()
{
if(!lineBreakPairs)
return;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
code[p[1]] = OP_BREAK;
}
}
bool CodeBlock::setBreakpoint(U32 lineNumber)
{
if(!lineBreakPairs)
return false;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
if((p[0] >> 8) == lineNumber)
{
code[p[1]] = OP_BREAK;
return true;
}
}
return false;
}
U32 CodeBlock::findFirstBreakLine(U32 lineNumber)
{
if(!lineBreakPairs)
return 0;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
U32 line = (p[0] >> 8);
if( lineNumber <= line )
return line;
}
return 0;
}
struct LinePair
{
U32 instLine;
U32 ip;
};
void CodeBlock::findBreakLine(U32 ip, U32 &line, U32 &instruction)
{
U32 min = 0;
U32 max = lineBreakPairCount - 1;
LinePair *p = (LinePair *) lineBreakPairs;
U32 found;
if(!lineBreakPairCount || p[min].ip > ip || p[max].ip < ip)
{
line = 0;
instruction = OP_INVALID;
return;
}
else if(p[min].ip == ip)
found = min;
else if(p[max].ip == ip)
found = max;
else
{
for(;;)
{
if(min == max - 1)
{
found = min;
break;
}
U32 mid = (min + max) >> 1;
if(p[mid].ip == ip)
{
found = mid;
break;
}
else if(p[mid].ip > ip)
max = mid;
else
min = mid;
}
}
instruction = p[found].instLine & 0xFF;
line = p[found].instLine >> 8;
}
const char *CodeBlock::getFileLine(U32 ip)
{
static char nameBuffer[256];
U32 line, inst;
findBreakLine(ip, line, inst);
dSprintf(nameBuffer, sizeof(nameBuffer), "%s (%d)", name ? name : "<input>", line);
return nameBuffer;
}
void CodeBlock::removeFromCodeList()
{
for(CodeBlock **walk = &smCodeBlockList; *walk; walk = &((*walk)->nextFile))
{
if(*walk == this)
{
*walk = nextFile;
// clear out all breakpoints
clearAllBreaks();
return;
}
}
// Let the telnet debugger know that this code
// block has been unloaded and that it needs to
// remove references to it.
if ( TelDebugger )
TelDebugger->clearCodeBlockPointers( this );
}
void CodeBlock::calcBreakList()
{
U32 size = 0;
S32 line = -1;
U32 seqCount = 0;
U32 i;
for(i = 0; i < lineBreakPairCount; i++)
{
U32 lineNumber = lineBreakPairs[i * 2];
if(lineNumber == U32(line + 1))
seqCount++;
else
{
if(seqCount)
size++;
size++;
seqCount = 1;
}
line = lineNumber;
}
if(seqCount)
size++;
breakList = new U32[size];
breakListSize = size;
line = -1;
seqCount = 0;
size = 0;
for(i = 0; i < lineBreakPairCount; i++)
{
U32 lineNumber = lineBreakPairs[i * 2];
if(lineNumber == U32(line + 1))
seqCount++;
else
{
if(seqCount)
breakList[size++] = seqCount;
breakList[size++] = lineNumber - getMax(0, line) - 1;
seqCount = 1;
}
line = lineNumber;
}
if(seqCount)
breakList[size++] = seqCount;
for(i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
p[0] = (p[0] << 8) | code[p[1]];
}
// Let the telnet debugger know that this code
// block has been loaded and that it can add break
// points it has for it.
if ( TelDebugger )
TelDebugger->addAllBreakpoints( this );
}
bool CodeBlock::read(StringTableEntry fileName, Stream &st)
{
name = fileName;
//
if (name)
{
if (const char *slash = dStrchr(this->name, '/'))
{
char root[512];
dStrncpy(root, this->name, slash-this->name);
root[slash-this->name] = 0;
mRoot = StringTable->insert(root);
}
}
//
addToCodeList();
U32 globalSize,size,i;
st.read(&size);
if(size)
{
globalSize = size;
globalStrings = new char[size];
st.read(size, globalStrings);
}
st.read(&size);
if(size)
{
functionStrings = new char[size];
st.read(size, functionStrings);
}
st.read(&size);
if(size)
{
globalFloats = new F64[size];
for(U32 i = 0; i < size; i++)
st.read(&globalFloats[i]);
}
st.read(&size);
if(size)
{
functionFloats = new F64[size];
for(U32 i = 0; i < size; i++)
st.read(&functionFloats[i]);
}
U32 codeSize;
st.read(&codeSize);
st.read(&lineBreakPairCount);
U32 totSize = codeSize + lineBreakPairCount * 2;
code = new U32[totSize];
for(i = 0; i < codeSize; i++)
{
U8 b;
st.read(&b);
if(b == 0xFF)
st.read(&code[i]);
else
code[i] = b;
}
for(i = codeSize; i < totSize; i++)
st.read(&code[i]);
lineBreakPairs = code + codeSize;
// StringTable-ize our identifiers.
U32 identCount;
st.read(&identCount);
while(identCount--)
{
U32 offset;
st.read(&offset);
StringTableEntry ste;
if(offset < globalSize)
ste = StringTable->insert(globalStrings + offset);
else
ste = StringTable->insert("");
U32 count;
st.read(&count);
while(count--)
{
U32 ip;
st.read(&ip);
code[ip] = *((U32 *) &ste);
}
}
if(lineBreakPairCount)
calcBreakList();
return true;
}
bool CodeBlock::compile(const char *codeFileName, StringTableEntry fileName, const char *script)
{
gSyntaxError = false;
consoleAllocReset();
STEtoU32 = compileSTEtoU32;
statementList = NULL;
// Set up the parser.
smCurrentParser = getParserForFile(fileName);
AssertISV(smCurrentParser, avar("CodeBlock::compile - no parser available for '%s'!", fileName));
// Now do some parsing.
smCurrentParser->setScanBuffer(script, fileName);
smCurrentParser->restart(NULL);
smCurrentParser->parse();
if(gSyntaxError)
{
consoleAllocReset();
return false;
}
FileStream st;
if(!ResourceManager->openFileForWrite(st, codeFileName))
return false;
st.write(U32(Con::DSOVersion));
// Reset all our value tables...
resetTables();
smInFunction = false;
smBreakLineCount = 0;
setBreakCodeBlock(this);
if(statementList)
codeSize = precompileBlock(statementList, 0) + 1;
else
codeSize = 1;
lineBreakPairCount = smBreakLineCount;
code = new U32[codeSize + smBreakLineCount * 2];
lineBreakPairs = code + codeSize;
// Write string table data...
getGlobalStringTable().write(st);
getFunctionStringTable().write(st);
// Write float table data...
getGlobalFloatTable().write(st);
getFunctionFloatTable().write(st);
smBreakLineCount = 0;
U32 lastIp;
if(statementList)
lastIp = compileBlock(statementList, code, 0, 0, 0);
else
lastIp = 0;
if(lastIp != codeSize - 1)
Con::errorf(ConsoleLogEntry::General, "CodeBlock::compile - precompile size mismatch, a precompile/compile function pair is probably mismatched.");
code[lastIp++] = OP_RETURN;
U32 totSize = codeSize + smBreakLineCount * 2;
st.write(codeSize);
st.write(lineBreakPairCount);
// Write out our bytecode, doing a bit of compression for low numbers.
U32 i;
for(i = 0; i < codeSize; i++)
{
if(code[i] < 0xFF)
st.write(U8(code[i]));
else
{
st.write(U8(0xFF));
st.write(code[i]);
}
}
// Write the break info...
for(i = codeSize; i < totSize; i++)
st.write(code[i]);
getIdentTable().write(st);
consoleAllocReset();
st.close();
return true;
}
const char *CodeBlock::compileExec(StringTableEntry fileName, const char *string, bool noCalls, int setFrame)
{
STEtoU32 = evalSTEtoU32;
consoleAllocReset();
name = fileName;
if(name)
addToCodeList();
statementList = NULL;
// Set up the parser.
smCurrentParser = getParserForFile(fileName);
AssertISV(smCurrentParser, avar("CodeBlock::compile - no parser available for '%s'!", fileName));
// Now do some parsing.
smCurrentParser->setScanBuffer(string, fileName);
smCurrentParser->restart(NULL);
smCurrentParser->parse();
if(!statementList)
{
delete this;
return "";
}
resetTables();
smInFunction = false;
smBreakLineCount = 0;
setBreakCodeBlock(this);
codeSize = precompileBlock(statementList, 0) + 1;
lineBreakPairCount = smBreakLineCount;
globalStrings = getGlobalStringTable().build();
functionStrings = getFunctionStringTable().build();
globalFloats = getGlobalFloatTable().build();
functionFloats = getFunctionFloatTable().build();
code = new U32[codeSize + lineBreakPairCount * 2];
lineBreakPairs = code + codeSize;
smBreakLineCount = 0;
U32 lastIp = compileBlock(statementList, code, 0, 0, 0);
code[lastIp++] = OP_RETURN;
consoleAllocReset();
if(lineBreakPairCount && fileName)
calcBreakList();
if(lastIp != codeSize)
Con::warnf(ConsoleLogEntry::General, "precompile size mismatch");
return exec(0, fileName, NULL, 0, 0, noCalls, NULL, setFrame);
}
//-------------------------------------------------------------------------
void CodeBlock::incRefCount()
{
refCount++;
}
void CodeBlock::decRefCount()
{
refCount--;
if(!refCount)
delete this;
}

129
engine/console/codeBlock.h Executable file
View File

@ -0,0 +1,129 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CODEBLOCK_H_
#define _CODEBLOCK_H_
#include "console/compiler.h"
#include "console/consoleParser.h"
class Stream;
/// Core TorqueScript code management class.
///
/// This class represents a block of code, usually mapped directly to a file.
class CodeBlock
{
private:
static CodeBlock* smCodeBlockList;
static CodeBlock* smCurrentCodeBlock;
public:
static U32 smBreakLineCount;
static bool smInFunction;
static Compiler::ConsoleParser * smCurrentParser;
static CodeBlock* getCurrentBlock()
{
return smCurrentCodeBlock;
}
static CodeBlock *getCodeBlockList()
{
return smCodeBlockList;
}
static StringTableEntry getCurrentCodeBlockName();
static CodeBlock *find(StringTableEntry);
CodeBlock();
~CodeBlock();
StringTableEntry name;
char *globalStrings;
char *functionStrings;
F64 *globalFloats;
F64 *functionFloats;
U32 codeSize;
U32 *code;
U32 refCount;
U32 lineBreakPairCount;
U32 *lineBreakPairs;
U32 breakListSize;
U32 *breakList;
CodeBlock *nextFile;
StringTableEntry mRoot;
void addToCodeList();
void removeFromCodeList();
void calcBreakList();
void clearAllBreaks();
void setAllBreaks();
/// Returns the first breakable line or 0 if none was found.
/// @param lineNumber The one based line number.
U32 findFirstBreakLine(U32 lineNumber);
void clearBreakpoint(U32 lineNumber);
/// Set a OP_BREAK instruction on a line. If a break
/// is not possible on that line it returns false.
/// @param lineNumber The one based line number.
bool setBreakpoint(U32 lineNumber);
void findBreakLine(U32 ip, U32 &line, U32 &instruction);
void getFunctionArgs(char buffer[1024], U32 offset);
const char *getFileLine(U32 ip);
bool read(StringTableEntry fileName, Stream &st);
bool compile(const char *dsoName, StringTableEntry fileName, const char *script);
void incRefCount();
void decRefCount();
/// Compiles and executes a block of script storing the compiled code in this
/// CodeBlock. If there is no filename breakpoints will not be generated and
/// the CodeBlock will not be added to the linked list of loaded CodeBlocks.
/// Note that if the script contains no executable statements the CodeBlock
/// will delete itself on return an empty string. The return string is any
/// result of the code executed, if any, or an empty string.
///
/// @param fileName The file name, including path and extension, for the
/// block of code or an empty string.
/// @param script The script code to compile and execute.
/// @param noCalls Skips calling functions from the script.
/// @param setFrame A zero based index of the stack frame to execute the code
/// with, zero being the top of the stack. If the the index is
/// -1 a new frame is created. If the index is out of range the
/// top stack frame is used.
const char *compileExec(StringTableEntry fileName, const char *script,
bool noCalls, int setFrame = -1 );
/// Executes the existing code in the CodeBlock. The return string is any
/// result of the code executed, if any, or an empty string.
///
/// @param offset The instruction offset to start executing from.
/// @param fnName The name of the function to execute or null.
/// @param ns The namespace of the function to execute or null.
/// @param argc The number of parameters passed to the function or
/// zero to execute code outside of a function.
/// @param argv The function parameter list.
/// @param noCalls Skips calling functions from the script.
/// @param setFrame A zero based index of the stack frame to execute the code
/// with, zero being the top of the stack. If the the index is
/// -1 a new frame is created. If the index is out of range the
/// top stack frame is used.
/// @param packageName The code package name or null.
const char *exec(U32 offset, const char *fnName, Namespace *ns, U32 argc,
const char **argv, bool noCalls, StringTableEntry packageName,
S32 setFrame = -1);
};
#endif

1205
engine/console/compiledEval.cc Executable file

File diff suppressed because it is too large Load Diff

271
engine/console/compiler.cc Executable file
View File

@ -0,0 +1,271 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/console.h"
#include "console/telnetDebugger.h"
#include "platform/event.h"
#include "console/ast.h"
#include "core/tAlgorithm.h"
#include "core/resManager.h"
#include "core/findMatch.h"
#include "console/consoleInternal.h"
#include "core/fileStream.h"
#include "console/compiler.h"
#include "console/simBase.h"
namespace Compiler
{
F64 consoleStringToNumber(const char *str, StringTableEntry file, U32 line)
{
F64 val = dAtof(str);
if(val != 0)
return val;
else if(!dStricmp(str, "true"))
return 1;
else if(!dStricmp(str, "false"))
return 0;
else if(file)
{
Con::warnf(ConsoleLogEntry::General, "%s (%d): string always evaluates to 0.", file, line);
return 0;
}
return 0;
}
//------------------------------------------------------------
CompilerStringTable *gCurrentStringTable, gGlobalStringTable, gFunctionStringTable;
CompilerFloatTable *gCurrentFloatTable, gGlobalFloatTable, gFunctionFloatTable;
DataChunker gConsoleAllocator;
CompilerIdentTable gIdentTable;
CodeBlock *gCurBreakBlock;
//------------------------------------------------------------
CodeBlock *getBreakCodeBlock() { return gCurBreakBlock; }
void setBreakCodeBlock(CodeBlock *cb) { gCurBreakBlock = cb; }
//------------------------------------------------------------
U32 evalSTEtoU32(StringTableEntry ste, U32)
{
return *((U32 *) &ste);
}
U32 compileSTEtoU32(StringTableEntry ste, U32 ip)
{
if(ste)
getIdentTable().add(ste, ip);
return 0;
}
U32 (*STEtoU32)(StringTableEntry ste, U32 ip) = evalSTEtoU32;
//------------------------------------------------------------
bool gSyntaxError = false;
//------------------------------------------------------------
CompilerStringTable *getCurrentStringTable() { return gCurrentStringTable; }
CompilerStringTable &getGlobalStringTable() { return gGlobalStringTable; }
CompilerStringTable &getFunctionStringTable() { return gFunctionStringTable; }
void setCurrentStringTable (CompilerStringTable* cst) { gCurrentStringTable = cst; }
CompilerFloatTable *getCurrentFloatTable() { return gCurrentFloatTable; }
CompilerFloatTable &getGlobalFloatTable() { return gGlobalFloatTable; }
CompilerFloatTable &getFunctionFloatTable() { return gFunctionFloatTable; }
void setCurrentFloatTable (CompilerFloatTable* cst) { gCurrentFloatTable = cst; }
CompilerIdentTable &getIdentTable() { return gIdentTable; }
void precompileIdent(StringTableEntry ident)
{
if(ident)
gGlobalStringTable.add(ident);
}
void resetTables()
{
setCurrentStringTable(&gGlobalStringTable);
setCurrentFloatTable(&gGlobalFloatTable);
getGlobalFloatTable().reset();
getGlobalStringTable().reset();
getFunctionFloatTable().reset();
getFunctionStringTable().reset();
getIdentTable().reset();
}
void *consoleAlloc(U32 size) { return gConsoleAllocator.alloc(size); }
void consoleAllocReset() { gConsoleAllocator.freeBlocks(); }
}
//-------------------------------------------------------------------------
using namespace Compiler;
//-------------------------------------------------------------------------
U32 CompilerStringTable::add(const char *str, bool caseSens, bool tag)
{
// Is it already in?
Entry **walk;
for(walk = &list; *walk; walk = &((*walk)->next))
{
if((*walk)->tag != tag)
continue;
if(caseSens)
{
if(!dStrcmp((*walk)->string, str))
return (*walk)->start;
}
else
{
if(!dStricmp((*walk)->string, str))
return (*walk)->start;
}
}
// Write it out.
Entry *newStr = (Entry *) consoleAlloc(sizeof(Entry));
*walk = newStr;
newStr->next = NULL;
newStr->start = totalLen;
U32 len = dStrlen(str) + 1;
if(tag && len < 7) // alloc space for the numeric tag 1 for tag, 5 for # and 1 for nul
len = 7;
totalLen += len;
newStr->string = (char *) consoleAlloc(len);
newStr->len = len;
newStr->tag = tag;
dStrcpy(newStr->string, str);
return newStr->start;
}
U32 CompilerStringTable::addIntString(U32 value)
{
dSprintf(buf, sizeof(buf), "%d", value);
return add(buf);
}
U32 CompilerStringTable::addFloatString(F64 value)
{
dSprintf(buf, sizeof(buf), "%g", value);
return add(buf);
}
void CompilerStringTable::reset()
{
list = NULL;
totalLen = 0;
}
char *CompilerStringTable::build()
{
char *ret = new char[totalLen];
for(Entry *walk = list; walk; walk = walk->next)
dStrcpy(ret + walk->start, walk->string);
return ret;
}
void CompilerStringTable::write(Stream &st)
{
st.write(totalLen);
for(Entry *walk = list; walk; walk = walk->next)
st.write(walk->len, walk->string);
}
//------------------------------------------------------------
U32 CompilerFloatTable::add(F64 value)
{
Entry **walk;
U32 i = 0;
for(walk = &list; *walk; walk = &((*walk)->next), i++)
if(value == (*walk)->val)
return i;
Entry *newFloat = (Entry *) consoleAlloc(sizeof(Entry));
newFloat->val = value;
newFloat->next = NULL;
count++;
*walk = newFloat;
return count-1;
}
void CompilerFloatTable::reset()
{
list = NULL;
count = 0;
}
F64 *CompilerFloatTable::build()
{
F64 *ret = new F64[count];
U32 i = 0;
for(Entry *walk = list; walk; walk = walk->next, i++)
ret[i] = walk->val;
return ret;
}
void CompilerFloatTable::write(Stream &st)
{
st.write(count);
for(Entry *walk = list; walk; walk = walk->next)
st.write(walk->val);
}
//------------------------------------------------------------
void CompilerIdentTable::reset()
{
list = NULL;
}
void CompilerIdentTable::add(StringTableEntry ste, U32 ip)
{
U32 index = gGlobalStringTable.add(ste, false);
Entry *newEntry = (Entry *) consoleAlloc(sizeof(Entry));
newEntry->offset = index;
newEntry->ip = ip;
for(Entry *walk = list; walk; walk = walk->next)
{
if(walk->offset == index)
{
newEntry->nextIdent = walk->nextIdent;
walk->nextIdent = newEntry;
return;
}
}
newEntry->next = list;
list = newEntry;
newEntry->nextIdent = NULL;
}
void CompilerIdentTable::write(Stream &st)
{
U32 count = 0;
Entry * walk;
for(walk = list; walk; walk = walk->next)
count++;
st.write(count);
for(walk = list; walk; walk = walk->next)
{
U32 ec = 0;
Entry * el;
for(el = walk; el; el = el->nextIdent)
ec++;
st.write(walk->offset);
st.write(ec);
for(el = walk; el; el = el->nextIdent)
st.write(el->ip);
}
}

235
engine/console/compiler.h Executable file
View File

@ -0,0 +1,235 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _COMPILER_H_
#define _COMPILER_H_
class Stream;
class DataChunker;
#include "platform/platform.h"
#include "console/ast.h"
#include "console/codeBlock.h"
// Autogenerated, so we should only ever include from once place - here.
// (We can't stick include guards in it without patching bison.)
#ifndef _CMDGRAM_H_
#define _CMDGRAM_H_
#include "console/cmdgram.h"
#endif
namespace Compiler
{
/// The opcodes for the TorqueScript VM.
enum CompiledInstructions
{
OP_FUNC_DECL,
OP_CREATE_OBJECT,
OP_ADD_OBJECT,
OP_END_OBJECT,
OP_JMPIFFNOT,
OP_JMPIFNOT,
OP_JMPIFF,
OP_JMPIF,
OP_JMPIFNOT_NP,
OP_JMPIF_NP,
OP_JMP,
OP_RETURN,
OP_CMPEQ,
OP_CMPGR,
OP_CMPGE,
OP_CMPLT,
OP_CMPLE,
OP_CMPNE,
OP_XOR,
OP_MOD,
OP_BITAND,
OP_BITOR,
OP_NOT,
OP_NOTF,
OP_ONESCOMPLEMENT,
OP_SHR,
OP_SHL,
OP_AND,
OP_OR,
OP_ADD,
OP_SUB,
OP_MUL,
OP_DIV,
OP_NEG,
OP_SETCURVAR,
OP_SETCURVAR_CREATE,
OP_SETCURVAR_ARRAY,
OP_SETCURVAR_ARRAY_CREATE,
OP_LOADVAR_UINT,
OP_LOADVAR_FLT,
OP_LOADVAR_STR,
OP_SAVEVAR_UINT,
OP_SAVEVAR_FLT,
OP_SAVEVAR_STR,
OP_SETCUROBJECT,
OP_SETCUROBJECT_NEW,
OP_SETCURFIELD,
OP_SETCURFIELD_ARRAY,
OP_LOADFIELD_UINT,
OP_LOADFIELD_FLT,
OP_LOADFIELD_STR,
OP_SAVEFIELD_UINT,
OP_SAVEFIELD_FLT,
OP_SAVEFIELD_STR,
OP_STR_TO_UINT,
OP_STR_TO_FLT,
OP_STR_TO_NONE,
OP_FLT_TO_UINT,
OP_FLT_TO_STR,
OP_FLT_TO_NONE,
OP_UINT_TO_FLT,
OP_UINT_TO_STR,
OP_UINT_TO_NONE,
OP_LOADIMMED_UINT,
OP_LOADIMMED_FLT,
OP_TAG_TO_STR,
OP_LOADIMMED_STR,
OP_LOADIMMED_IDENT,
OP_CALLFUNC_RESOLVE,
OP_CALLFUNC,
OP_ADVANCE_STR,
OP_ADVANCE_STR_APPENDCHAR,
OP_ADVANCE_STR_COMMA,
OP_ADVANCE_STR_NUL,
OP_REWIND_STR,
OP_TERMINATE_REWIND_STR,
OP_COMPARE_STR,
OP_PUSH,
OP_PUSH_FRAME,
OP_BREAK,
OP_INVALID
};
//------------------------------------------------------------
F64 consoleStringToNumber(const char *str, StringTableEntry file = 0, U32 line = 0);
U32 precompileBlock(StmtNode *block, U32 loopCount);
U32 compileBlock(StmtNode *block, U32 *codeStream, U32 ip, U32 continuePoint, U32 breakPoint);
//------------------------------------------------------------
struct CompilerIdentTable
{
struct Entry
{
U32 offset;
U32 ip;
Entry *next;
Entry *nextIdent;
};
Entry *list;
void add(StringTableEntry ste, U32 ip);
void reset();
void write(Stream &st);
};
//------------------------------------------------------------
struct CompilerStringTable
{
U32 totalLen;
struct Entry
{
char *string;
U32 start;
U32 len;
bool tag;
Entry *next;
};
Entry *list;
char buf[256];
U32 add(const char *str, bool caseSens = true, bool tag = false);
U32 addIntString(U32 value);
U32 addFloatString(F64 value);
void reset();
char *build();
void write(Stream &st);
};
//------------------------------------------------------------
struct CompilerFloatTable
{
struct Entry
{
F64 val;
Entry *next;
};
U32 count;
Entry *list;
U32 add(F64 value);
void reset();
F64 *build();
void write(Stream &st);
};
//------------------------------------------------------------
inline StringTableEntry U32toSTE(U32 u)
{
return *((StringTableEntry *) &u);
}
extern U32 (*STEtoU32)(StringTableEntry ste, U32 ip);
U32 evalSTEtoU32(StringTableEntry ste, U32);
U32 compileSTEtoU32(StringTableEntry ste, U32 ip);
CompilerStringTable *getCurrentStringTable();
CompilerStringTable &getGlobalStringTable();
CompilerStringTable &getFunctionStringTable();
void setCurrentStringTable (CompilerStringTable* cst);
CompilerFloatTable *getCurrentFloatTable();
CompilerFloatTable &getGlobalFloatTable();
CompilerFloatTable &getFunctionFloatTable();
void setCurrentFloatTable (CompilerFloatTable* cst);
CompilerIdentTable &getIdentTable();
void precompileIdent(StringTableEntry ident);
CodeBlock *getBreakCodeBlock();
void setBreakCodeBlock(CodeBlock *cb);
/// Helper function to reset the float, string, and ident tables to a base
/// starting state.
void resetTables();
void *consoleAlloc(U32 size);
void consoleAllocReset();
extern bool gSyntaxError;
};
#endif

1043
engine/console/console.cc Executable file

File diff suppressed because it is too large Load Diff

808
engine/console/console.h Executable file
View File

@ -0,0 +1,808 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONSOLE_H_
#define _CONSOLE_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#include <stdarg.h>
class SimObject;
struct EnumTable;
class Namespace;
/// Indicates that warnings about undefined script variables should be displayed.
///
/// @note This is set and controlled by script.
extern bool gWarnUndefinedScriptVariables;
enum StringTableConstants
{
StringTagPrefixByte = 0x01 ///< Magic value prefixed to tagged strings.
};
/// Represents an entry in the log.
struct ConsoleLogEntry
{
/// This field indicates the severity of the log entry.
///
/// Log entries are filtered and displayed differently based on
/// their severity. Errors are highlighted red, while normal entries
/// are displayed as normal text. Often times, the engine will be
/// configured to hide all log entries except warnings or errors,
/// or to perform a special notification when it encounters an error.
enum Level
{
Normal = 0,
Warning,
Error,
NUM_CLASS
} mLevel;
/// Used to associate a log entry with a module.
///
/// Log entries can come from different sources; for instance,
/// the scripting engine, or the network code. This allows the
/// logging system to be aware of where different log entries
/// originated from.
enum Type
{
General = 0,
Assert,
Script,
GUI,
Network,
NUM_TYPE
} mType;
/// Indicates the actual log entry.
///
/// This contains a description of the event being logged.
/// For instance, "unable to access file", or "player connected
/// successfully", or nearly anything else you might imagine.
///
/// Typically, the description should contain a concise, descriptive
/// string describing whatever is being logged. Whenever possible,
/// include useful details like the name of the file being accessed,
/// or the id of the player or GuiControl, so that if a log needs
/// to be used to locate a bug, it can be done as painlessly as
/// possible.
const char *mString;
};
/// Scripting engine representation of an enum.
///
/// This data structure is used by the scripting engine
/// to expose enumerations to the scripting language. It
/// acts to relate named constants to integer values, just
/// like an enum in C++.
struct EnumTable
{
/// Number of enumerated items in the table.
S32 size;
/// This represents a specific item in the enumeration.
struct Enums
{
S32 index; ///< Index label maps to.
const char *label;///< Label for this index.
};
Enums *table;
/// Constructor.
///
/// This sets up the EnumTable with predefined data.
///
/// @param sSize Size of the table.
/// @param sTable Pointer to table of Enums.
///
/// @see gLiquidTypeTable
/// @see gAlignTable
EnumTable(S32 sSize, Enums *sTable)
{ size = sSize; table = sTable; }
};
typedef const char *StringTableEntry;
/// @defgroup console_callbacks Scripting Engine Callbacks
///
/// The scripting engine makes heavy use of callbacks to represent
/// function exposed to the scripting language. StringCallback,
/// IntCallback, FloatCallback, VoidCallback, and BoolCallback all
/// represent exposed script functions returning different types.
///
/// ConsumerCallback is used with the function Con::addConsumer; functions
/// registered with Con::addConsumer are called whenever something is outputted
/// to the console. For instance, the TelnetConsole registers itself with the
/// console so it can echo the console over the network.
///
/// @note Callbacks to the scripting language - for instance, onExit(), which is
/// a script function called when the engine is shutting down - are handled
/// using Con::executef() and kin.
/// @{
///
typedef const char * (*StringCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef S32 (*IntCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef F32 (*FloatCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef void (*VoidCallback)(SimObject *obj, S32 argc, const char *argv[]); // We have it return a value so things don't break..
typedef bool (*BoolCallback)(SimObject *obj, S32 argc, const char *argv[]);
typedef void (*ConsumerCallback)(ConsoleLogEntry::Level level, const char *consoleLine);
/// @}
/// @defgroup console_types Scripting Engine Type Functions
///
/// @see Con::registerType
/// @{
typedef const char* (*GetDataFunction)(void *dptr, EnumTable *tbl, BitSet32 flag);
typedef void (*SetDataFunction)(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag);
/// @}
/// This namespace contains the core of the console functionality.
///
/// @section con_intro Introduction
///
/// The console is a key part of Torque's architecture. It allows direct run-time control
/// of many aspects of the engine.
///
/// @nosubgrouping
namespace Con
{
/// Various configuration constants.
enum Constants
{
/// This is the version number associated with DSO files.
///
/// If you make any changes to the way the scripting language works
/// (such as DSO format changes, adding/removing op-codes) that would
/// break compatibility, then you should increment this.
///
/// If you make a really major change, increment it to the next multiple
/// of ten.
///
/// 12/29/04 - BJG - 33->34 Removed some opcodes, part of namespace upgrade.
/// 12/30/04 - BJG - 34->35 Reordered some things, further general shuffling.
/// 11/03/05 - BJG - 35->36 Integrated new debugger code.
DSOVersion = 36,
MaxLineLength = 512, ///< Maximum length of a line of console input.
MaxDataTypes = 256 ///< Maximum number of registered data types.
};
/// @name Control Functions
///
/// The console must be initialized and shutdown appropriately during the
/// lifetime of the app. These functions are used to manage this behavior.
///
/// @note Torque deals with this aspect of console management, so you don't need
/// to call these functions in normal usage of the engine.
/// @{
/// Initializes the console.
///
/// This performs the following steps:
/// - Calls Namespace::init() to initialize the scripting namespace hierarchy.
/// - Calls ConsoleConstructor::setup() to initialize globally defined console
/// methods and functions.
/// - Registers some basic global script variables.
/// - Calls AbstractClassRep::init() to initialize Torque's class database.
/// - Registers some basic global script functions that couldn't usefully
/// be defined anywhere else.
void init();
/// Shuts down the console.
///
/// This performs the following steps:
/// - Closes the console log file.
/// - Calls Namespace::shutdown() to shut down the scripting namespace hierarchy.
void shutdown();
/// Is the console active at this time?
bool isActive();
/// @}
/// @name Console Consumers
///
/// The console distributes its output through Torque by using
/// consumers. Every time a new line is printed to the console,
/// all the ConsumerCallbacks registered using addConsumer are
/// called, in order.
///
/// @note The GuiConsole control, which provides the on-screen
/// in-game console, uses a different technique to render
/// the console. It calls getLockLog() to lock the Vector
/// of on-screen console entries, then it renders them as
/// needed. While the Vector is locked, the console will
/// not change the Vector. When the GuiConsole control is
/// done with the console entries, it calls unlockLog()
/// to tell the console that it is again safe to modify
/// the Vector.
///
/// @see TelnetConsole
/// @see TelnetDebugger
/// @see WinConsole
/// @see MacCarbConsole
/// @see StdConsole
/// @see ConsoleLogger
///
/// @{
void addConsumer(ConsumerCallback cb);
void removeConsumer(ConsumerCallback cb);
/// @}
/// @name Miscellaneous
/// @{
/// Remove color marking information from a string.
///
/// @note It does this in-place, so be careful! It may
/// potentially blast data if you're not careful.
/// When in doubt, make a copy of the string first.
void stripColorChars(char* line);
/// Convert from a relative script path to an absolute script path.
///
/// This is used in (among other places) the exec() script function, which
/// takes a parameter indicating a script file and executes it. Script paths
/// can be one of:
/// - <b>Absolute:</b> <i>fps/foo/bar.cs</i> Paths of this sort are passed
/// through.
/// - <b>Mod-relative:</b> <i>~/foo/bar.cs</i> Paths of this sort have their
/// replaced with the name of the current mod.
/// - <b>File-relative:</b> <i>./baz/blip.cs</i> Paths of this sort are
/// calculated relative to the path of the current scripting file.
///
/// @note This function determines paths relative to the currently executing
/// CodeBlock. Calling it outside of script execution will result in
/// it directly copying src to filename, since it won't know to what the
/// path is relative!
///
/// @param filename Pointer to string buffer to fill with absolute path.
/// @param size Size of buffer pointed to by filename.
/// @param src Original, possibly relative script path.
bool expandScriptFilename(char *filename, U32 size, const char *src);
/// Returns true if fn is a global scripting function.
///
/// This looks in the global namespace. It also checks to see if fn
/// is in the StringTable; if not, it returns false.
bool isFunction(const char *fn);
/// This is the basis for tab completion in the console.
///
/// @note This is an internally used function. You probably don't
/// care much about how this works.
///
/// This function does some basic parsing to try to ascertain the namespace in which
/// we are attempting to do tab completion, then bumps control off to the appropriate
/// tabComplete function, either in SimObject or Namespace.
///
/// @param inputBuffer Pointer to buffer containing starting data, or last result.
/// @param cursorPos Location of cursor in this buffer. This is used to indicate
/// what part of the string should be kept and what part should
/// be advanced to the next match if any.
/// @param maxResultLength Maximum amount of result data to put into inputBuffer. This
/// is capped by MaxCompletionBufferSize.
/// @param forwardTab Should we go forward to next match or backwards to previous
/// match? True indicates forward.
U32 tabComplete(char* inputBuffer, U32 cursorPos, U32 maxResultLength, bool forwardTab);
/// @}
/// @name Variable Management
/// @{
/// Add a console variable that references the value of a variable in C++ code.
///
/// If a value is assigned to the console variable the C++ variable is updated,
/// and vice-versa.
///
/// @param name Global console variable name to create
/// @param type The type of the C++ variable; see the ConsoleDynamicTypes enum for a complete list.
/// @param pointer Pointer to the variable.
/// @see ConsoleDynamicTypes
bool addVariable(const char *name, S32 type, void *pointer);
/// Remove a console variable.
///
/// @param name Global console variable name to remove
/// @return true if variable existed before removal.
bool removeVariable(const char *name);
/// Assign a string value to a locally scoped console variable
///
/// @note The context of the variable is determined by gEvalState; that is,
/// by the currently executing code.
///
/// @param name Local console variable name to set
/// @param value String value to assign to name
void setLocalVariable(const char *name, const char *value);
/// Retrieve the string value to a locally scoped console variable
///
/// @note The context of the variable is determined by gEvalState; that is,
/// by the currently executing code.
///
/// @param name Local console variable name to get
const char* getLocalVariable(const char* name);
/// @}
/// @name Global Variable Accessors
/// @{
/// Assign a string value to a global console variable
/// @param name Global console variable name to set
/// @param value String value to assign to this variable.
void setVariable(const char *name, const char *value);
/// Retrieve the string value of a global console variable
/// @param name Global Console variable name to query
/// @return The string value of the variable or "" if the variable does not exist.
const char* getVariable(const char* name);
/// Same as setVariable(), but for bools.
void setBoolVariable (const char* name,bool var);
/// Same as getVariable(), but for bools.
///
/// @param name Name of the variable.
/// @param def Default value to supply if no matching variable is found.
bool getBoolVariable (const char* name,bool def = false);
/// Same as setVariable(), but for ints.
void setIntVariable (const char* name,S32 var);
/// Same as getVariable(), but for ints.
///
/// @param name Name of the variable.
/// @param def Default value to supply if no matching variable is found.
S32 getIntVariable (const char* name,S32 def = 0);
/// Same as setVariable(), but for floats.
void setFloatVariable(const char* name,F32 var);
/// Same as getVariable(), but for floats.
///
/// @param name Name of the variable.
/// @param def Default value to supply if no matching variable is found.
F32 getFloatVariable(const char* name,F32 def = .0f);
/// @}
/// @name Global Function Registration
/// @{
/// Register a C++ function with the console making it a global function callable from the scripting engine.
///
/// @param name Name of the new function.
/// @param cb Pointer to the function implementing the scripting call; a console callback function returning a specific type value.
/// @param usage Documentation for this function. @ref console_autodoc
/// @param minArgs Minimum number of arguments this function accepts
/// @param maxArgs Maximum number of arguments this function accepts
void addCommand(const char *name, StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(const char *name, IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *name, FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *name, VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *name, BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char *, StringCallback, const char *, S32, S32)
/// @}
/// @name Namespace Function Registration
/// @{
/// Register a C++ function with the console making it callable
/// as a method of the given namespace from the scripting engine.
///
/// @param nameSpace Name of the namespace to associate the new function with; this is usually the name of a class.
/// @param name Name of the new function.
/// @param cb Pointer to the function implementing the scripting call; a console callback function returning a specific type value.
/// @param usage Documentation for this function. @ref console_autodoc
/// @param minArgs Minimum number of arguments this function accepts
/// @param maxArgs Maximum number of arguments this function accepts
void addCommand(const char *nameSpace, const char *name,StringCallback cb, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(const char *nameSpace, const char *name,IntCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *nameSpace, const char *name,FloatCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *nameSpace, const char *name,VoidCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
void addCommand(const char *nameSpace, const char *name,BoolCallback cb, const char *usage, S32 minArgs, S32 maxArgs); ///< @copydoc addCommand(const char*, const char *, StringCallback, const char *, S32, S32)
/// @}
/// @name Special Purpose Registration
///
/// These are special-purpose functions that exist to allow commands to be grouped, so
/// that when we generate console docs, they can be more meaningfully presented.
///
/// @ref console_autodoc "Click here for more information about console docs and grouping."
///
/// @{
void markCommandGroup (const char * nsName, const char *name, const char* usage=NULL);
void beginCommandGroup(const char * nsName, const char *name, const char* usage);
void endCommandGroup (const char * nsName, const char *name);
/// @deprecated
void addOverload (const char * nsName, const char *name, const char *altUsage);
/// @}
/// @name Console Output
///
/// These functions process the formatted string and pass it to all the ConsumerCallbacks that are
/// currently registered. The console log file and the console window callbacks are installed by default.
///
/// @see addConsumer()
/// @see removeConsumer()
/// @{
/// @param _format A stdlib printf style formatted out put string
/// @param ... Variables to be written
void printf(const char *_format, ...);
/// @note The console window colors warning text as LIGHT GRAY.
/// @param _format A stdlib printf style formatted out put string
/// @param ... Variables to be written
void warnf(const char *_format, ...);
/// @note The console window colors warning text as RED.
/// @param _format A stdlib printf style formatted out put string
/// @param ... Variables to be written
void errorf(const char *_format, ...);
/// @note The console window colors warning text as LIGHT GRAY.
/// @param type Allows you to associate the warning message with an internal module.
/// @param _format A stdlib printf style formatted out put string
/// @param ... Variables to be written
/// @see Con::warnf()
void warnf(ConsoleLogEntry::Type type, const char *_format, ...);
/// @note The console window colors warning text as RED.
/// @param type Allows you to associate the warning message with an internal module.
/// @param _format A stdlib printf style formatted out put string
/// @param ... Variables to be written
/// @see Con::errorf()
void errorf(ConsoleLogEntry::Type type, const char *_format, ...);
/// @}
/// Returns true when called from the main thread, false otherwise
bool isMainThread();
/// @name Console Execution
///
/// These are functions relating to the execution of script code.
///
/// @{
/// Call a script function from C/C++ code.
///
/// @param argc Number of elements in the argv parameter
/// @param argv A character string array containing the name of the function
/// to call followed by the arguments to that function.
/// @code
/// // Call a Torque script function called mAbs, having one parameter.
/// char* argv[] = {"abs", "-9"};
/// char* result = execute(2, argv);
/// @endcode
const char *execute(S32 argc, const char* argv[]);
/// @see execute(S32 argc, const char* argv[])
const char *executef(S32 argc, ...);
/// Call a Torque Script member function of a SimObject from C/C++ code.
/// @param object Object on which to execute the method call.
/// @param argc Number of elements in the argv parameter (must be >2, see argv)
/// @param argv A character string array containing the name of the member function
/// to call followed by an empty parameter (gets filled with object ID)
/// followed by arguments to that function.
/// @code
/// // Call the method setMode() on an object, passing it one parameter.
///
/// char* argv[] = {"setMode", "", "2"};
/// char* result = execute(mysimobject, 3, argv);
/// @endcode
const char *execute(SimObject *object, S32 argc, const char *argv[]);
/// @see execute(SimObject *, S32 argc, const char *argv[])
const char *executef(SimObject *, S32 argc, ...);
/// Evaluate an arbitrary chunk of code.
///
/// @param string Buffer containing code to execute.
/// @param echo Should we echo the string to the console?
/// @param fileName Indicate what file this code is coming from; used in error reporting and such.
const char *evaluate(const char* string, bool echo = false, const char *fileName = NULL);
/// Evaluate an arbitrary line of script.
///
/// This wraps dVsprintf(), so you can substitute parameters into the code being executed.
const char *evaluatef(const char* string, ...);
/// @}
/// @name Console Function Implementation Helpers
///
/// The functions Con::getIntArg, Con::getFloatArg and Con::getArgBuffer(size) are used to
/// allocate on the console stack string variables that will be passed into the next console
// function called. This allows the console to avoid copying some data.
///
/// getReturnBuffer lets you allocate stack space to return data in.
/// @{
///
char *getReturnBuffer(U32 bufferSize);
char *getArgBuffer(U32 bufferSize);
char *getFloatArg(F64 arg);
char *getIntArg (S32 arg);
/// @}
/// @name Namespaces
/// @{
Namespace *lookupNamespace(const char *nsName);
bool linkNamespaces(const char *parentName, const char *childName);
bool unlinkNamespaces(const char *parentName, const char *childName);
/// @note This should only be called from consoleObject.h
bool classLinkNamespaces(Namespace *parent, Namespace *child);
/// @}
/// @name Logging
/// @{
void getLockLog(ConsoleLogEntry * &log, U32 &size);
void unlockLog(void);
void setLogMode(S32 mode);
/// @}
/// @name Dynamic Type System
/// @{
///
/* void registerType( const char *typeName, S32 type, S32 size, GetDataFunction gdf, SetDataFunction sdf, bool isDatablockType = false );
void registerType( const char* typeName, S32 type, S32 size, bool isDatablockType = false );
void registerTypeGet( S32 type, GetDataFunction gdf );
void registerTypeSet( S32 type, SetDataFunction sdf );
const char *getTypeName(S32 type);
bool isDatablockType( S32 type ); */
void setData(S32 type, void *dptr, S32 index, S32 argc, const char **argv, EnumTable *tbl = NULL, BitSet32 flag = 0);
const char *getData(S32 type, void *dptr, S32 index, EnumTable *tbl = NULL, BitSet32 flag = 0);
/// @}
};
extern void expandEscape(char *dest, const char *src);
extern bool collapseEscape(char *buf);
extern S32 HashPointer(StringTableEntry ptr);
/// This is the backend for the ConsoleMethod()/ConsoleFunction() macros.
///
/// See the group ConsoleConstructor Innards for specifics on how this works.
///
/// @see @ref console_autodoc
/// @nosubgrouping
class ConsoleConstructor
{
public:
/// @name Entry Type Fields
///
/// One of these is set based on the type of entry we want
/// inserted in the console.
///
/// @ref console_autodoc
/// @{
StringCallback sc; ///< A function/method that returns a string.
IntCallback ic; ///< A function/method that returns an int.
FloatCallback fc; ///< A function/method that returns a float.
VoidCallback vc; ///< A function/method that returns nothing.
BoolCallback bc; ///< A function/method that returns a bool.
bool group; ///< Indicates that this is a group marker.
bool overload; ///< Indicates that this is an overload marker.
/// @deprecated Unused.
/// @}
/// Minimum/maximum number of arguments for the function.
S32 mina, maxa;
const char *usage; ///< Usage string.
const char *funcName; ///< Function name.
const char *className; ///< Class name.
/// @name ConsoleConstructer Innards
///
/// The ConsoleConstructor class is used as the backend for the ConsoleFunction() and
/// ConsoleMethod() macros. The way it works takes advantage of several properties of
/// C++.
///
/// The ConsoleFunction()/ConsoleMethod() macros wrap the declaration of a ConsoleConstructor.
///
/// @code
/// // The definition of a ConsoleFunction using the macro
/// ConsoleFunction(ExpandFilename, const char*, 2, 2, "(string filename)")
/// {
/// argc;
/// char* ret = Con::getReturnBuffer( 1024 );
/// Con::expandScriptFilename(ret, 1024, argv[1]);
/// return ret;
/// }
///
/// // Resulting code
/// static const char* cExpandFilename(SimObject *, S32, const char **argv);
/// static ConsoleConstructor
/// gExpandFilenameobj(NULL,"ExpandFilename", cExpandFilename,
/// "(string filename)", 2, 2);
/// static const char* cExpandFilename(SimObject *, S32 argc, const char **argv)
/// {
/// argc;
/// char* ret = Con::getReturnBuffer( 1024 );
/// Con::expandScriptFilename(ret, 1024, argv[1]);
/// return ret;
/// }
///
/// // A similar thing happens when you do a ConsoleMethod.
/// @endcode
///
/// As you can see, several global items are defined when you use the ConsoleFunction method.
/// The macro constructs the name of these items from the parameters you passed it. Your
/// implementation of the console function is is placed in a function with a name based on
/// the actual name of the console funnction. In addition, a ConsoleConstructor is declared.
///
/// Because it is defined as a global, the constructor for the ConsoleConstructor is called
/// before execution of main() is started. The constructor is called once for each global
/// ConsoleConstructor variable, in the order in which they were defined (this property only holds true
/// within file scope).
///
/// We have ConsoleConstructor create a linked list at constructor time, by storing a static
/// pointer to the head of the list, and keeping a pointer to the next item in each instance
/// of ConsoleConstructor. init() is a helper function in this process, automatically filling
/// in commonly used fields and updating first and next as needed. In this way, a list of
/// items to add to the console is assemble in memory, ready for use, before we start
/// execution of the program proper.
///
/// In Con::init(), ConsoleConstructor::setup() is called to process this prepared list. Each
/// item in the list is iterated over, and the appropriate Con namespace functions (usually
/// Con::addCommand) are invoked to register the ConsoleFunctions and ConsoleMethods in
/// the appropriate namespaces.
///
/// @see Namespace
/// @see Con
/// @{
ConsoleConstructor *next;
static ConsoleConstructor *first;
void init(const char *cName, const char *fName, const char *usg, S32 minArgs, S32 maxArgs);
static void setup();
/// @}
/// @name Basic Console Constructors
/// @{
ConsoleConstructor(const char *className, const char *funcName, StringCallback sfunc, const char* usage, S32 minArgs, S32 maxArgs);
ConsoleConstructor(const char *className, const char *funcName, IntCallback ifunc, const char* usage, S32 minArgs, S32 maxArgs);
ConsoleConstructor(const char *className, const char *funcName, FloatCallback ffunc, const char* usage, S32 minArgs, S32 maxArgs);
ConsoleConstructor(const char *className, const char *funcName, VoidCallback vfunc, const char* usage, S32 minArgs, S32 maxArgs);
ConsoleConstructor(const char *className, const char *funcName, BoolCallback bfunc, const char* usage, S32 minArgs, S32 maxArgs);
/// @}
/// @name Magic Console Constructors
///
/// These perform various pieces of "magic" related to consoleDoc functionality.
/// @ref console_autodoc
/// @{
/// Indicates a group marker. (A doxygen illusion)
///
/// @see Con::markCommandGroup
/// @ref console_autodoc
ConsoleConstructor(const char *className, const char *groupName, const char* usage);
/// @}
};
/// @name Global Console Definition Macros
///
/// @note If TORQUE_DEBUG is defined, then we gather documentation information, and
/// do some extra sanity checks.
///
/// @see ConsoleConstructor
/// @ref console_autodoc
/// @{
// O hackery of hackeries
#define conmethod_return_const return (const
#define conmethod_return_S32 return (S32
#define conmethod_return_F32 return (F32
#define conmethod_nullify(val)
#define conmethod_return_void conmethod_nullify(void
#define conmethod_return_bool return (bool
#if !defined(TORQUE_SHIPPING)
// Console function macros
# define ConsoleFunctionGroupBegin(groupName, usage) \
static ConsoleConstructor gConsoleFunctionGroup##groupName##__GroupBegin(NULL,#groupName,usage);
# define ConsoleFunction(name,returnType,minArgs,maxArgs,usage1) \
static returnType c##name(SimObject *, S32, const char **argv); \
static ConsoleConstructor g##name##obj(NULL,#name,c##name,usage1,minArgs,maxArgs); \
static returnType c##name(SimObject *, S32 argc, const char **argv)
# define ConsoleFunctionGroupEnd(groupName) \
static ConsoleConstructor gConsoleFunctionGroup##groupName##__GroupEnd(NULL,#groupName,NULL);
// Console method macros
# define ConsoleMethodGroupBegin(className, groupName, usage) \
static ConsoleConstructor className##groupName##__GroupBegin(#className,#groupName,usage);
# define ConsoleMethod(className,name,returnType,minArgs,maxArgs,usage1) \
static inline returnType c##className##name(className *, S32, const char **argv); \
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
AssertFatal( dynamic_cast<className*>( object ), "Object passed to " #name " is not a " #className "!" ); \
conmethod_return_##returnType ) c##className##name(static_cast<className*>(object),argc,argv); \
}; \
static ConsoleConstructor className##name##obj(#className,#name,c##className##name##caster,usage1,minArgs,maxArgs); \
static inline returnType c##className##name(className *object, S32 argc, const char **argv)
# define ConsoleStaticMethod(className,name,returnType,minArgs,maxArgs,usage1) \
static inline returnType c##className##name(S32, const char **); \
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
conmethod_return_##returnType ) c##className##name(argc,argv); \
}; \
static ConsoleConstructor \
className##name##obj(#className,#name,c##className##name##caster,usage1,minArgs,maxArgs); \
static inline returnType c##className##name(S32 argc, const char **argv)
# define ConsoleMethodGroupEnd(className, groupName) \
static ConsoleConstructor className##groupName##__GroupEnd(#className,#groupName,NULL);
#else
// These do nothing if we don't want doc information.
# define ConsoleFunctionGroupBegin(groupName, usage)
# define ConsoleFunctionGroupEnd(groupName)
# define ConsoleMethodGroupBegin(className, groupName, usage)
# define ConsoleMethodGroupEnd(className, groupName)
// These are identical to what's above, we just want to null out the usage strings.
# define ConsoleFunction(name,returnType,minArgs,maxArgs,usage1) \
static returnType c##name(SimObject *, S32, const char **); \
static ConsoleConstructor g##name##obj(NULL,#name,c##name,"",minArgs,maxArgs);\
static returnType c##name(SimObject *, S32 argc, const char **argv)
# define ConsoleMethod(className,name,returnType,minArgs,maxArgs,usage1) \
static inline returnType c##className##name(className *, S32, const char **argv); \
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
conmethod_return_##returnType ) c##className##name(static_cast<className*>(object),argc,argv); \
}; \
static ConsoleConstructor \
className##name##obj(#className,#name,c##className##name##caster,"",minArgs,maxArgs); \
static inline returnType c##className##name(className *object, S32 argc, const char **argv)
# define ConsoleStaticMethod(className,name,returnType,minArgs,maxArgs,usage1) \
static inline returnType c##className##name(S32, const char **); \
static returnType c##className##name##caster(SimObject *object, S32 argc, const char **argv) { \
conmethod_return_##returnType ) c##className##name(argc,argv); \
}; \
static ConsoleConstructor \
className##name##obj(#className,#name,c##className##name##caster,"",minArgs,maxArgs); \
static inline returnType c##className##name(S32 argc, const char **argv)
#endif
/// @}
#endif

378
engine/console/consoleDoc.cc Executable file
View File

@ -0,0 +1,378 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/console.h"
#include "console/ast.h"
#include "core/tAlgorithm.h"
#include "core/resManager.h"
#include "core/findMatch.h"
#include "console/consoleInternal.h"
#include "console/consoleObject.h"
#include "core/fileStream.h"
#include "console/compiler.h"
//--- Information pertaining to this page... ------------------
/// @file
///
/// For specifics on using the consoleDoc functionality, see @ref console_autodoc
ConsoleFunctionGroupBegin(ConsoleDoc, "Console self-documentation functions. These output psuedo C++ suitable for feeeding through Doxygen or another auto documentation tool.");
ConsoleFunction(dumpConsoleClasses, void, 1, 1, "() dumps all declared console classes to the console.")
{
Namespace::dumpClasses();
}
ConsoleFunction(dumpConsoleFunctions, void, 1, 1, "() dumps all declared console functions to the console.")
{
Namespace::dumpFunctions();
}
ConsoleFunctionGroupEnd(ConsoleDoc);
/// Helper table to convert type ids to human readable names.
const char *typeNames[] =
{
"Script",
"string",
"int",
"float",
"void",
"bool",
"",
"",
"unknown_overload"
};
void printClassHeader(const char * className, const char * superClassName, const bool stub)
{
if(stub)
{
Con::printf("/// Stub class");
Con::printf("/// ");
Con::printf("/// @note This is a stub class to ensure a proper class hierarchy. No ");
Con::printf("/// information was available for this class.");
}
// Print out appropriate class header
if(superClassName)
Con::printf("class %s : public %s {", className, superClassName ? superClassName : "");
else if(!className)
Con::printf("namespace Global {");
else
Con::printf("class %s {", className);
if(className)
Con::printf(" public:");
}
void printClassMethod(const bool isVirtual, const char *retType, const char *methodName, const char* args, const char*usage)
{
if(usage && usage[0] != ';' && usage[0] != 0)
Con::printf(" /*! %s */", usage);
Con::printf(" %s%s %s(%s) {}", isVirtual ? "virtual " : "", retType, methodName, args);
}
void printGroupStart(const char * aName, const char * aDocs)
{
Con::printf("");
Con::printf(" /*! @name %s", aName);
if(aDocs)
{
Con::printf(" ");
Con::printf(" %s", aDocs);
}
Con::printf(" @{ */");
}
void printClassMember(const bool isDeprec, const char * aType, const char * aName, const char * aDocs)
{
Con::printf(" /*!");
if(aDocs)
{
Con::printf(" %s", aDocs);
Con::printf(" ");
}
if(isDeprec)
Con::printf(" @deprecated This member is deprecated, which means that its value is always undefined.");
Con::printf(" */");
Con::printf(" %s %s;", isDeprec ? "deprecated" : aType, aName);
}
void printGroupEnd()
{
Con::printf(" /// @}");
Con::printf("");
}
void printClassFooter()
{
Con::printf("};");
Con::printf("");
}
void Namespace::printNamespaceEntries(Namespace * g)
{
static bool inGroup = false;
// Go through all the entries.
// Iterate through the methods of the namespace...
for(Entry *ewalk = g->mEntryList; ewalk; ewalk = ewalk->mNext)
{
char buffer[1024]; //< This will bite you in the butt someday.
int eType = ewalk->mType;
const char * funcName = ewalk->mFunctionName;
// If it's a function
if(eType >= Entry::ScriptFunctionType || eType == Entry::OverloadMarker)
{
if(eType==Entry::OverloadMarker)
{
// Deal with crap from the OverloadMarker case.
// It has no type information so we have to "correct" its type.
// Find the original
eType = 8;
for(Entry *eseek = g->mEntryList; eseek; eseek = eseek->mNext)
{
if(!dStrcmp(eseek->mFunctionName, ewalk->cb.mGroupName))
{
eType = eseek->mType;
break;
}
}
// And correct the name
funcName = ewalk->cb.mGroupName;
}
// A quick note - if your usage field starts with a (, then it's auto-integrated into
// the script docs! Use this HEAVILY!
// We add some heuristics here as well. If you're of the form:
// *.methodName(*)
// then we will also extract parameters.
const char *use = ewalk->mUsage ? ewalk->mUsage : "";
const char *bgn = dStrchr(use, '(');
const char *end = dStrchr(use, ')');
const char *dot = dStrchr(use, '.');
if(use[0] == '(')
{
if(!end)
end = use + 1;
use++;
U32 len = end - use;
dStrncpy(buffer, use, len);
buffer[len] = 0;
printClassMethod(true, typeNames[eType], funcName, buffer, end+1);
continue; // Skip to next one.
}
// We check to see if they're giving a prototype.
if(dot && bgn && end) // If there's two parentheses, and a dot...
if(dot < bgn && bgn < end) // And they're in the order dot, bgn, end...
{
use++;
U32 len = end - bgn - 1;
dStrncpy(buffer, bgn+1, len);
buffer[len] = 0;
// Then let's do the heuristic-trick
printClassMethod(true, typeNames[eType], funcName, buffer, end+1);
continue; // Get to next item.
}
// Finally, see if they did it foo(*) style.
char* func_pos = dStrstr(use, funcName);
if((func_pos) && (func_pos < bgn) && (end > bgn))
{
U32 len = end - bgn - 1;
dStrncpy(buffer, bgn+1, len);
buffer[len] = 0;
printClassMethod(true, typeNames[eType], funcName, buffer, end+1);
continue;
}
// Default...
printClassMethod(true, typeNames[eType], funcName, "", ewalk->mUsage);
}
else if(ewalk->mType == Entry::GroupMarker)
{
if(!inGroup)
printGroupStart(ewalk->cb.mGroupName, ewalk->mUsage);
else
printGroupEnd();
inGroup = !inGroup;
}
else if(ewalk->mFunctionOffset) // If it's a builtin function...
{
ewalk->mCode->getFunctionArgs(buffer, ewalk->mFunctionOffset);
printClassMethod(false, typeNames[ewalk->mType], ewalk->mFunctionName, buffer, "");
}
else
{
Con::printf(" // got an unknown thing?? %d", ewalk->mType );
}
}
}
void Namespace::dumpClasses()
{
Vector<Namespace *> vec;
trashCache();
// We use mHashSequence to mark if we have traversed...
// so mark all as zero to start.
for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext)
walk->mHashSequence = 0;
for(Namespace *walk = mNamespaceList; walk; walk = walk->mNext)
{
Vector<Namespace *> stack;
// Get all the parents of this namespace... (and mark them as we go)
Namespace *parentWalk = walk;
while(parentWalk)
{
if(parentWalk->mHashSequence != 0)
break;
if(parentWalk->mPackage == 0)
{
parentWalk->mHashSequence = 1; // Mark as traversed.
stack.push_back(parentWalk);
}
parentWalk = parentWalk->mParent;
}
// Load stack into our results vector.
while(stack.size())
{
vec.push_back(stack[stack.size() - 1]);
stack.pop_back();
}
}
// Go through previously discovered classes
U32 i;
for(i = 0; i < vec.size(); i++)
{
const char *className = vec[i]->mName;
const char *superClassName = vec[i]->mParent ? vec[i]->mParent->mName : NULL;
// Skip the global namespace, that gets dealt with in dumpFunctions
if(!className) continue;
// If we hit a class with no members and no classRep, do clever filtering.
if(vec[i]->mEntryList == NULL && vec[i]->mClassRep == NULL)
{
// Print out a short stub so we get a proper class hierarchy.
if(superClassName) { // Filter hack; we don't want non-inheriting classes...
printClassHeader(className,superClassName, true);
printClassFooter();
}
continue;
}
// Print the header for the class..
printClassHeader(className, superClassName, false);
// Deal with entries.
printNamespaceEntries(vec[i]);
// Deal with the classRep (to get members)...
AbstractClassRep *rep = vec[i]->mClassRep;
AbstractClassRep::FieldList emptyList;
AbstractClassRep::FieldList *parentList = &emptyList;
AbstractClassRep::FieldList *fieldList = &emptyList;
if(rep)
{
// Get information about the parent's fields...
AbstractClassRep *parentRep = vec[i]->mParent ? vec[i]->mParent->mClassRep : NULL;
if(parentRep)
parentList = &(parentRep->mFieldList);
// Get information about our fields
fieldList = &(rep->mFieldList);
// Go through all our fields...
for(U32 j = 0; j < fieldList->size(); j++)
{
switch((*fieldList)[j].type)
{
case AbstractClassRep::StartGroupFieldType:
printGroupStart((*fieldList)[j].pGroupname, (*fieldList)[j].pFieldDocs);
break;
case AbstractClassRep::EndGroupFieldType:
printGroupEnd();
break;
default:
case AbstractClassRep::DepricatedFieldType:
{
bool isDeprecated = ((*fieldList)[j].type == AbstractClassRep::DepricatedFieldType);
if(isDeprecated)
{
printClassMember(
true,
"<deprecated>",
(*fieldList)[j].pFieldname,
(*fieldList)[j].pFieldDocs
);
}
else
{
ConsoleBaseType *cbt = ConsoleBaseType::getType((*fieldList)[j].type);
printClassMember(
false,
cbt ? cbt->getTypeClassName() : "<unknown>",
(*fieldList)[j].pFieldname,
(*fieldList)[j].pFieldDocs
);
}
}
}
}
}
// Close the class/namespace.
printClassFooter();
}
}
void Namespace::dumpFunctions()
{
// Get the global namespace.
Namespace* g = find(NULL); //->mParent;
printClassHeader(NULL,NULL, false);
while(g)
{
printNamespaceEntries(g);
g = g->mParent;
}
printClassFooter();
}

176
engine/console/consoleDoc.h Executable file
View File

@ -0,0 +1,176 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
// This file exists solely to document consoleDoc.cc
/// @page console_autodoc Console Auto-Documentation
///
/// @see consoleDoc.cc
///
/// @section console_autodoc_using Using Console Auto-Documentation
///
/// There are on the order of three hundred functions exposed to the script language
/// through the console. It is therefore extremely important that they be documented,
/// but due to their number, it is difficult to maintain a seperate reference document.
///
/// Therefore, a simple documentation system has been built into the scripting engine. It
/// was initially added by Mark Frohnmayer, and later enhanced by Ben Garney. The
/// scripting engine supports grouping functions and methods, to help organize the
/// several hundred functions, as well as associating a "usage string" with functions and
/// groups.
///
/// @note The results of a console doc dump will vary depending on when you run it. If
/// you run it, for example, while in the game menu, it won't output any data for
/// the script-defined classes which are defined for gameplay. To get comprehensive
/// documentation, you may need to write a special script that will get all your
/// classes loaded into the scripting engine.
///
/// The console documentation system is designed to output a dump of the current state
/// of the scripting engine in a format understandable by Doxygen. It does this by
/// traversing the namespace/class hierarchy in memory at the time of the dump, and
/// outputting psuedo-C++ code equivalent to this class hierarchy.
///
/// @subsection console_autodoc_using_script For the Scripter...
///
/// Currently, there is no way to associate usage strings or other documentation with script code
/// like you can with C++ code.
///
/// You can get a list of all the methods and fields of an object from any object which inherits
/// from SimObject (ie, every object), as well as the documentation on those objects by using the
/// dump() method from the console:
///
/// @code
/// ==>$foo = new SimObject();
/// ==>$foo.dump();
/// Member Fields:
/// Tagged Fields:
/// Methods:
/// delete() - obj.delete()
/// dump() - obj.dump()
/// getClassName() - obj.getClassName()
/// getGroup() - obj.getGroup()
/// getId() - obj.getId()
/// getName() - obj.getName()
/// getType() - obj.getType()
/// save() - obj.save(fileName, <selectedOnly>)
/// schedule() - object.schedule(time, command, <arg1...argN>);
/// setName() - obj.setName(newName)
/// @endcode
///
/// In the Torque example app, there are two functions defined in common\\client\\scriptDoc.cs
/// which automate the process of dumping the documentation. They make use of the ConsoleLogger
/// object to output the documentation to a file, and look like this:
///
/// @note You may want to add this code, or code like it, to your project if you have
/// rewritten the script code in common.
///
/// @code
/// // Writes out all script functions to a file
/// function writeOutFunctions() {
/// new ConsoleLogger( logger, "scriptFunctions.txt", false );
/// dumpConsoleFunctions();
/// logger.delete();
/// }
///
/// // Writes out all script classes to a file
/// function writeOutClasses() {
/// new ConsoleLogger( logger, "scriptClasses.txt", false );
/// dumpConsoleClasses();
/// logger.delete();
/// }
/// @endcode
///
/// @subsection console_autodoc_using_coder For the C++ Coder...
///
/// @note <b>It is of the utmost important that you keep your usage strings up to date!</b>
/// Usage strings are the only way that a scripter has to know how to use the methods,
/// functions, and variables you expose. Misleading, missing, or out of date documentation
/// will make their lives much harder - and yours, too, because you'll have to keep
/// explaining things to them! So make everyone's lives easier - keep your usage strings
/// clear, concise, and up to date.
///
/// There are four types of items which can be documented using the autodocumentation system:
/// - <b>Fields</b>, which are defined using the addField() calls. They are documented
/// by passing a string to the usage parameter.
/// - <b>Field groups</b>, which are defined using the beginGroup() and endGroup() calls.
/// They are documented by passing a descriptive string to the usage parameter.
/// - <b>Method groups</b>, which are defined using beginCommandGroup(), endCommandGroup(),
/// ConsoleMethodGroupEnd(), ConsoleMethodGroupBegin(), ConsoleFunctionGroupEnd(), and
/// ConsoleFunctionGroupBegin().
/// - <b>Methods and functions</b>, which are defined using either SimObject::addCommand(),
/// the ConsoleMethod() macro, or the ConsoleFunction() macro. Methods and functions are
/// special in that the usage strings should be in a specific format, so
/// that parameter information can be extracted from them and placed into the Doxygen
/// output.
///
/// You can use standard Doxygen commands in your comments, to make the documentation clearer.
/// Of particular use are \@returns, \@param, \@note, and \@deprecated.
///
/// <b>Examples using global definitions.</b>
///
/// @code
/// // Example of using Doxygen commands.
/// ConsoleFunction(alxGetWaveLen, S32, 2, 2, "(string filename)"
/// "Get length of a wave file\n\n"
/// "@param filename File to determine length of.\n"
/// "@returns Length in milliseconds.")
///
/// // A function group...
/// ConsoleFunctionGroupBegin(Example, "This is an example group! Notice that the name for the group"
/// "must be a valid identifier, due to limitations in the C preprocessor.");
///
/// // ConsoleFunction definitions go here.
///
/// ConsoleFunctionGroupEnd(Example);
///
/// // You can do similar things with methods...
/// ConsoleMethodGroupBegin(SimSet, UsefulFuncs, "Here are some useful functions involving a SimSet.");
/// ConsoleMethod(SimSet, listObjects, void, 2, 2, "set.listObjects();")
/// ConsoleMethodGroupEnd(SimSet, UsefulFuncs, "Here are some more useful functions involving a SimSet.");
/// @endcode
///
/// <b>Examples using addField</b>
///
/// @note Using addCommand is strongly deprecated.
///
/// @code
/// // Example of a field group.
/// addGroup( "Logging", "Things relating to logging." );
/// addField( "level", TypeEnum, Offset( mLevel, ConsoleLogger ), 1, &gLogLevelTable );
/// endGroup( "Logging" );
/// @endcode
///
/// @section console_autodoc_makingdocs How to Generate Console Docs
///
/// Console docs can be generated by running the dumpConsoleFunctions() and
/// dumpConsoleClasses(), then running the output through Doxygen. There is an
/// example Doxygen configuration file to do this in HEAD,
/// at doc\\doxygen\\html\\script_doxygen.html.cfg. Doxygen will parse the psuedo-C++
/// generated by the console doc code and produce a class hierarchy and documentation
/// of the global namespace. You may need to tweak the paths in the configuration file
/// slightly to reflect your individual setup.
///
/// @section console_autodoc_internals Console Auto-Documentation Internals
///
/// The consoleDoc system works by inserting "hidden" entries in Namespace and
/// AbstractClassRep; these hidden entries are assigned special type IDs so that
/// they aren't touched by the standard name resolution code. At documentation
/// creation time, the dumpConsole functions iterate through the Namespace hierarchy
/// and the AbstractClassRep data and extract this "hidden" information, outputting
/// it in a Doxygen-compatible format.
///
/// @note You can customize the output of the console documentation system by modifying
/// these functions:
/// - printClassHeader()
/// - printClassMethod()
/// - printGroupStart()
/// - printClassMember()
/// - printGroupEnd()
/// - printClassFooter()
///
/// @note There was once support for 'overloaded' script functions; ie, script functions
/// with multiple usage strings. Certain functions in the audio library used this.
/// However, it was deemed too complex, and removed from the scripting engine. There
/// are still some latent traces of it, however.

1512
engine/console/consoleFunctions.cc Executable file

File diff suppressed because it is too large Load Diff

1087
engine/console/consoleInternal.cc Executable file

File diff suppressed because it is too large Load Diff

311
engine/console/consoleInternal.h Executable file
View File

@ -0,0 +1,311 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONSOLEINTERNAL_H_
#define _CONSOLEINTERNAL_H_
#ifndef _STRINGTABLE_H_
#include "core/stringTable.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _CONSOLETYPES_H_
#include "console/consoleTypes.h"
#endif
class ExprEvalState;
struct FunctionDecl;
class CodeBlock;
class AbstractClassRep;
class Namespace
{
enum {
MaxActivePackages = 512,
};
static U32 mNumActivePackages;
static U32 mOldNumActivePackages;
static StringTableEntry mActivePackages[MaxActivePackages];
public:
StringTableEntry mName;
StringTableEntry mPackage;
Namespace *mParent;
Namespace *mNext;
AbstractClassRep *mClassRep;
U32 mRefCountToParent;
struct Entry
{
enum {
GroupMarker = -3,
OverloadMarker = -2,
InvalidFunctionType = -1,
ScriptFunctionType,
StringCallbackType,
IntCallbackType,
FloatCallbackType,
VoidCallbackType,
BoolCallbackType
};
Namespace *mNamespace;
Entry *mNext;
StringTableEntry mFunctionName;
S32 mType;
S32 mMinArgs;
S32 mMaxArgs;
const char *mUsage;
StringTableEntry mPackage;
CodeBlock *mCode;
U32 mFunctionOffset;
union {
StringCallback mStringCallbackFunc;
IntCallback mIntCallbackFunc;
VoidCallback mVoidCallbackFunc;
FloatCallback mFloatCallbackFunc;
BoolCallback mBoolCallbackFunc;
const char* mGroupName;
} cb;
Entry();
void clear();
const char *execute(S32 argc, const char **argv, ExprEvalState *state);
};
Entry *mEntryList;
Entry **mHashTable;
U32 mHashSize;
U32 mHashSequence; ///< @note The hash sequence is used by the autodoc console facility
/// as a means of testing reference state.
Namespace();
void addFunction(StringTableEntry name, CodeBlock *cb, U32 functionOffset);
void addCommand(StringTableEntry name,StringCallback, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(StringTableEntry name,IntCallback, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(StringTableEntry name,FloatCallback, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(StringTableEntry name,VoidCallback, const char *usage, S32 minArgs, S32 maxArgs);
void addCommand(StringTableEntry name,BoolCallback, const char *usage, S32 minArgs, S32 maxArgs);
void addOverload(const char *name, const char* altUsage);
void markGroup(const char* name, const char* usage);
char * lastUsage;
void getEntryList(Vector<Entry *> *);
Entry *lookup(StringTableEntry name);
Entry *lookupRecursive(StringTableEntry name);
Entry *createLocalEntry(StringTableEntry name);
void buildHashTable();
void clearEntries();
bool classLinkTo(Namespace *parent);
bool unlinkClass(Namespace *parent);
const char *tabComplete(const char *prevText, S32 baseLen, bool fForward);
static U32 mCacheSequence;
static DataChunker mCacheAllocator;
static DataChunker mAllocator;
static void trashCache();
static Namespace *mNamespaceList;
static Namespace *mGlobalNamespace;
static void init();
static void shutdown();
static Namespace *global();
static Namespace *find(StringTableEntry name, StringTableEntry package=NULL);
static void activatePackage(StringTableEntry name);
static void deactivatePackage(StringTableEntry name);
static void dumpClasses();
static void dumpFunctions();
static void printNamespaceEntries(Namespace * g);
static void unlinkPackages();
static void relinkPackages();
static bool isPackage(StringTableEntry name);
};
extern char *typeValueEmpty;
class Dictionary
{
public:
struct Entry
{
enum
{
TypeInternalInt = -3,
TypeInternalFloat = -2,
TypeInternalString = -1,
};
StringTableEntry name;
Entry *nextEntry;
S32 type;
char *sval;
U32 ival; // doubles as strlen when type = -1
F32 fval;
U32 bufferLen;
void *dataPtr;
Entry(StringTableEntry name);
~Entry();
U32 getIntValue()
{
if(type <= TypeInternalString)
return ival;
else
return dAtoi(Con::getData(type, dataPtr, 0));
}
F32 getFloatValue()
{
if(type <= TypeInternalString)
return fval;
else
return dAtof(Con::getData(type, dataPtr, 0));
}
const char *getStringValue()
{
if(type == TypeInternalString)
return sval;
if(type == TypeInternalFloat)
return Con::getData(TypeF32, &fval, 0);
else if(type == TypeInternalInt)
return Con::getData(TypeS32, &ival, 0);
else
return Con::getData(type, dataPtr, 0);
}
void setIntValue(U32 val)
{
if(type <= TypeInternalString)
{
fval = (F32)val;
ival = val;
if(sval != typeValueEmpty)
{
dFree(sval);
sval = typeValueEmpty;
}
type = TypeInternalInt;
return;
}
else
{
const char *dptr = Con::getData(TypeS32, &val, 0);
Con::setData(type, dataPtr, 0, 1, &dptr);
}
}
void setFloatValue(F32 val)
{
if(type <= TypeInternalString)
{
fval = val;
ival = static_cast<U32>(val);
if(sval != typeValueEmpty)
{
dFree(sval);
sval = typeValueEmpty;
}
type = TypeInternalFloat;
return;
}
else
{
const char *dptr = Con::getData(TypeF32, &val, 0);
Con::setData(type, dataPtr, 0, 1, &dptr);
}
}
void setStringValue(const char *value);
};
private:
struct HashTableData
{
Dictionary* owner;
S32 size;
S32 count;
Entry **data;
};
HashTableData *hashTable;
ExprEvalState *exprState;
public:
StringTableEntry scopeName;
Namespace *scopeNamespace;
CodeBlock *code;
U32 ip;
Dictionary();
Dictionary(ExprEvalState *state, Dictionary* ref=NULL);
~Dictionary();
Entry *lookup(StringTableEntry name);
Entry *add(StringTableEntry name);
void setState(ExprEvalState *state, Dictionary* ref=NULL);
void remove(Entry *);
void reset();
void exportVariables(const char *varString, const char *fileName, bool append);
void deleteVariables(const char *varString);
void setVariable(StringTableEntry name, const char *value);
const char *getVariable(StringTableEntry name, bool *valid = NULL);
void addVariable(const char *name, S32 type, void *dataPtr);
bool removeVariable(StringTableEntry name);
/// Return the best tab completion for prevText, with the length
/// of the pre-tab string in baseLen.
const char *tabComplete(const char *prevText, S32 baseLen, bool);
};
class ExprEvalState
{
public:
/// @name Expression Evaluation
/// @{
///
SimObject *thisObject;
Dictionary::Entry *currentVariable;
bool traceOn;
ExprEvalState();
~ExprEvalState();
/// @}
/// @name Stack Management
/// @{
///
Dictionary globalVars;
Vector<Dictionary *> stack;
void setCurVarName(StringTableEntry name);
void setCurVarNameCreate(StringTableEntry name);
S32 getIntVariable();
F64 getFloatVariable();
const char *getStringVariable();
void setIntVariable(S32 val);
void setFloatVariable(F64 val);
void setStringVariable(const char *str);
void pushFrame(StringTableEntry frameName, Namespace *ns);
void popFrame();
/// Puts a reference to an existing stack frame
/// on the top of the stack.
void pushFrameRef(S32 stackIndex);
/// @}
};
#endif

215
engine/console/consoleLogger.cc Executable file
View File

@ -0,0 +1,215 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/consoleLogger.h"
#include "console/consoleTypes.h"
Vector<ConsoleLogger *> ConsoleLogger::mActiveLoggers;
bool ConsoleLogger::smInitialized = false;
IMPLEMENT_CONOBJECT( ConsoleLogger );
//-----------------------------------------------------------------------------
ConsoleLogger::ConsoleLogger()
{
mFilename = NULL;
mLogging = false;
mAppend = false;
}
//-----------------------------------------------------------------------------
ConsoleLogger::ConsoleLogger( const char *fileName, bool append )
{
mLogging = false;
mLevel = ConsoleLogEntry::Normal;
mFilename = StringTable->insert( fileName );
mAppend = append;
init();
}
//-----------------------------------------------------------------------------
static EnumTable::Enums logLevelEnums[] =
{
{ ConsoleLogEntry::Normal, "normal" },
{ ConsoleLogEntry::Warning, "warning" },
{ ConsoleLogEntry::Error, "error" },
};
static EnumTable gLogLevelTable( 3, &logLevelEnums[0] );
void ConsoleLogger::initPersistFields()
{
Parent::initPersistFields();
addGroup( "Logging" );
addField( "level", TypeEnum, Offset( mLevel, ConsoleLogger ), 1, &gLogLevelTable );
endGroup( "Logging" );
}
//-----------------------------------------------------------------------------
bool ConsoleLogger::processArguments( S32 argc, const char **argv )
{
if( argc == 0 )
return false;
bool append = false;
if( argc == 2 )
append = dAtob( argv[1] );
mAppend = append;
mFilename = StringTable->insert( argv[0] );
if( init() )
{
attach();
return true;
}
return false;
}
//-----------------------------------------------------------------------------
ConsoleLogger::~ConsoleLogger()
{
detach();
}
//-----------------------------------------------------------------------------
bool ConsoleLogger::init()
{
if( smInitialized )
return true;
Con::addConsumer( ConsoleLogger::logCallback );
smInitialized = true;
return true;
}
//-----------------------------------------------------------------------------
bool ConsoleLogger::attach()
{
if( mFilename == NULL )
{
Con::errorf( "ConsoleLogger failed to attach: no filename supplied." );
return false;
}
// Check to see if this is initialized before using it
if( !smInitialized )
{
if( !init() )
{
Con::errorf( "ConsoleLogger failed to initalize." );
return false;
}
}
if( mLogging )
return false;
// Open the filestream
mStream.open( mFilename, ( mAppend ? FileStream::WriteAppend : FileStream::Write ) );
// Add this to list of active loggers
mActiveLoggers.push_back( this );
mLogging = true;
return true;
}
//-----------------------------------------------------------------------------
bool ConsoleLogger::detach()
{
// Make sure this is valid before messing with it
if( !smInitialized )
{
if( !init() )
{
return false;
}
}
if( !mLogging )
return false;
// Close filestream
mStream.close();
// Remove this object from the list of active loggers
for( int i = 0; i < mActiveLoggers.size(); i++ )
{
if( mActiveLoggers[i] == this )
{
mActiveLoggers.erase( i );
mLogging = false;
return true;
}
}
return false; // If this happens, it's bad...
}
//-----------------------------------------------------------------------------
void ConsoleLogger::logCallback( ConsoleLogEntry::Level level, const char *consoleLine )
{
ConsoleLogger *curr;
// Loop through active consumers and send them the message
for( int i = 0; i < mActiveLoggers.size(); i++ )
{
curr = mActiveLoggers[i];
// If the log level is within the log threshhold, log it
if( curr->mLevel <= level )
curr->log( consoleLine );
}
}
//-----------------------------------------------------------------------------
void ConsoleLogger::log( const char *consoleLine )
{
// Check to see if this is intalized before using it
if( !smInitialized )
{
if( !init() )
{
Con::errorf( "I don't know how this happened, but log called on this without it being initialized" );
return;
}
}
mStream.writeLine( (U8 *)consoleLine );
}
//-----------------------------------------------------------------------------
ConsoleMethod( ConsoleLogger, attach, bool, 2, 2, "() Attaches this object to the console and begins logging" )
{
ConsoleLogger *logger = static_cast<ConsoleLogger *>( object );
return logger->attach();
}
//-----------------------------------------------------------------------------
ConsoleMethod( ConsoleLogger, detach, bool, 2, 2, "() Detaches this object from the console and stops logging" )
{
ConsoleLogger *logger = static_cast<ConsoleLogger *>( object );
return logger->detach();
}

93
engine/console/consoleLogger.h Executable file
View File

@ -0,0 +1,93 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/simBase.h"
#include "console/console.h"
#include "core/fileStream.h"
#ifndef _CONSOLE_LOGGER_H_
#define _CONSOLE_LOGGER_H_
/// A class designed to be used as a console consumer and log
/// the data it receives to a file.
class ConsoleLogger : public SimObject
{
typedef SimObject Parent;
private:
bool mLogging; ///< True if it is currently consuming and logging
FileStream mStream; ///< File stream this object writes to
static bool smInitialized; ///< This is for use with the default constructor
bool mAppend; ///< If false, it will clear the file before logging to it.
StringTableEntry mFilename; ///< The file name to log to.
/// List of active ConsoleLoggers to send log messages to
static Vector<ConsoleLogger *> mActiveLoggers;
/// The log function called by the consumer callback
/// @param consoleLine Line of text to log
void log( const char *consoleLine );
/// Utility function, sets up the object (for script interface) returns true if successful
bool init();
public:
// @name Public console variables
/// @{
ConsoleLogEntry::Level mLevel; ///< The level of log messages to log
/// @}
DECLARE_CONOBJECT( ConsoleLogger );
static void initPersistFields();
/// Console constructor
///
/// @code
/// // Example script constructor usage.
/// %obj = new ConsoleLogger( objName, logFileName, [append = false] );
/// @endcode
bool processArguments( S32 argc, const char **argv );
/// Default constructor, make sure to initalize
ConsoleLogger();
/// Constructor
/// @param fileName File name to log to
/// @param append If false, it will clear the file, then start logging, else it will append
ConsoleLogger( const char *fileName, bool append = false );
/// Destructor
~ConsoleLogger();
/// Attach to the console and begin logging
///
/// Returns true if the action is successful
bool attach();
/// Detach from the console and stop logging
///
/// Returns true if the action is successful
bool detach();
/// Sets the level of console messages to log.
///
/// @param level Log level. Only items of the specified level or
/// lower are logged.
/// @see ConsoleLogEntry::Level
void setLogLevel( ConsoleLogEntry::Level level );
/// Returns the level of console messages to log
ConsoleLogEntry::Level getLogLevel() const;
/// The callback for the console consumer
///
/// @note This is a global callback, not executed per-instance.
/// @see Con::addConsumer
static void logCallback( ConsoleLogEntry::Level level, const char *consoleLine );
};
#endif

408
engine/console/consoleObject.cc Executable file
View File

@ -0,0 +1,408 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/consoleObject.h"
#include "core/stringTable.h"
#include "core/crc.h"
#include "console/console.h"
#include "console/consoleInternal.h"
#include "console/typeValidators.h"
AbstractClassRep * AbstractClassRep::classLinkList = NULL;
static AbstractClassRep::FieldList sg_tempFieldList;
U32 AbstractClassRep::NetClassCount [NetClassGroupsCount][NetClassTypesCount] = {{0, },};
U32 AbstractClassRep::NetClassBitSize[NetClassGroupsCount][NetClassTypesCount] = {{0, },};
AbstractClassRep ** AbstractClassRep::classTable[NetClassGroupsCount][NetClassTypesCount];
U32 AbstractClassRep::classCRC[NetClassGroupsCount] = {INITIAL_CRC_VALUE, };
bool AbstractClassRep::initialized = false;
//--------------------------------------
const AbstractClassRep::Field *AbstractClassRep::findField(StringTableEntry name) const
{
for(U32 i = 0; i < mFieldList.size(); i++)
if(mFieldList[i].pFieldname == name)
return &mFieldList[i];
return NULL;
}
//--------------------------------------
void AbstractClassRep::registerClassRep(AbstractClassRep* in_pRep)
{
AssertFatal(in_pRep != NULL, "AbstractClassRep::registerClassRep was passed a NULL pointer!");
#ifdef TORQUE_DEBUG // assert if this class is already registered.
for(AbstractClassRep *walk = classLinkList; walk; walk = walk->nextClass)
{
AssertFatal(dStrcmp(in_pRep->mClassName, walk->mClassName),
"Duplicate class name registered in AbstractClassRep::registerClassRep()");
}
#endif
in_pRep->nextClass = classLinkList;
classLinkList = in_pRep;
}
//--------------------------------------
ConsoleObject* AbstractClassRep::create(const char* in_pClassName)
{
AssertFatal(initialized,
"AbstractClassRep::create() - Tried to create an object before AbstractClassRep::initialize().");
for (AbstractClassRep *walk = classLinkList; walk; walk = walk->nextClass)
if (!dStrcmp(walk->getClassName(), in_pClassName))
return walk->create();
AssertWarn(0, avar("Couldn't find class rep for dynamic class: %s", in_pClassName));
return NULL;
}
//--------------------------------------
ConsoleObject* AbstractClassRep::create(const U32 groupId, const U32 typeId, const U32 in_classId)
{
AssertFatal(initialized,
"AbstractClassRep::create() - Tried to create an object before AbstractClassRep::initialize().");
AssertFatal(in_classId < NetClassCount[groupId][typeId],
"AbstractClassRep::create() - Class id out of range.");
AssertFatal(classTable[groupId][typeId][in_classId] != NULL,
"AbstractClassRep::create() - No class with requested ID type.");
// Look up the specified class and create it.
if(classTable[groupId][typeId][in_classId])
return classTable[groupId][typeId][in_classId]->create();
return NULL;
}
//--------------------------------------
static S32 QSORT_CALLBACK ACRCompare(const void *aptr, const void *bptr)
{
const AbstractClassRep *a = *((const AbstractClassRep **) aptr);
const AbstractClassRep *b = *((const AbstractClassRep **) bptr);
if(a->mClassType != b->mClassType)
return a->mClassType - b->mClassType;
return dStrcmp(a->getClassName(), b->getClassName());
}
void AbstractClassRep::initialize()
{
AssertFatal(!initialized, "Duplicate call to AbstractClassRep::initialize()!");
Vector<AbstractClassRep *> dynamicTable(__FILE__, __LINE__);
AbstractClassRep *walk;
// Initialize namespace references...
for (walk = classLinkList; walk; walk = walk->nextClass)
{
walk->mNamespace = Con::lookupNamespace(StringTable->insert(walk->getClassName()));
walk->mNamespace->mClassRep = walk;
}
// Initialize field lists... (and perform other console registration).
for (walk = classLinkList; walk; walk = walk->nextClass)
{
// sg_tempFieldList is used as a staging area for field lists
// (see addField, addGroup, etc.)
sg_tempFieldList.setSize(0);
walk->init();
// So if we have things in it, copy it over...
if (sg_tempFieldList.size() != 0)
walk->mFieldList = sg_tempFieldList;
// And of course delete it every round.
sg_tempFieldList.clear();
}
// Calculate counts and bit sizes for the various NetClasses.
for (U32 group = 0; group < NetClassGroupsCount; group++)
{
U32 groupMask = 1 << group;
// Specifically, for each NetClass of each NetGroup...
for(U32 type = 0; type < NetClassTypesCount; type++)
{
// Go through all the classes and find matches...
for (walk = classLinkList; walk; walk = walk->nextClass)
{
if(walk->mClassType == type && walk->mClassGroupMask & groupMask)
dynamicTable.push_back(walk);
}
// Set the count for this NetGroup and NetClass
NetClassCount[group][type] = dynamicTable.size();
if(!NetClassCount[group][type])
continue; // If no classes matched, skip to next.
// Sort by type and then by name.
dQsort((void *) &dynamicTable[0], dynamicTable.size(), sizeof(AbstractClassRep *), ACRCompare);
// Allocate storage in the classTable
classTable[group][type] = new AbstractClassRep*[NetClassCount[group][type]];
// Fill this in and assign class ids for this group.
for(U32 i = 0; i < NetClassCount[group][type];i++)
{
classTable[group][type][i] = dynamicTable[i];
dynamicTable[i]->mClassId[group] = i;
}
// And calculate the size of bitfields for this group and type.
NetClassBitSize[group][type] =
getBinLog2(getNextPow2(NetClassCount[group][type] + 1));
dynamicTable.clear();
}
}
// Ok, we're golden!
initialized = true;
}
//------------------------------------------------------------------------------
//-------------------------------------- ConsoleObject
char replacebuf[1024];
char* suppressSpaces(const char* in_pname)
{
U32 i = 0;
char chr;
do
{
chr = in_pname[i];
replacebuf[i++] = (chr != 32) ? chr : '_';
} while(chr);
return replacebuf;
}
void ConsoleObject::addGroup(const char* in_pGroupname, const char* in_pGroupDocs)
{
// Remove spaces.
char* pFieldNameBuf = suppressSpaces(in_pGroupname);
// Append group type to fieldname.
dStrcat(pFieldNameBuf, "_begingroup");
// Create Field.
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(pFieldNameBuf);
f.pGroupname = StringTable->insert(in_pGroupname);
if(in_pGroupDocs)
f.pFieldDocs = StringTable->insert(in_pGroupDocs);
else
f.pFieldDocs = NULL;
f.type = AbstractClassRep::StartGroupFieldType;
f.elementCount = 0;
f.groupExpand = false;
f.validator = NULL;
f.setDataFn = &defaultProtectedSetFn;
f.getDataFn = &defaultProtectedGetFn;
// Add to field list.
sg_tempFieldList.push_back(f);
}
void ConsoleObject::endGroup(const char* in_pGroupname)
{
// Remove spaces.
char* pFieldNameBuf = suppressSpaces(in_pGroupname);
// Append group type to fieldname.
dStrcat(pFieldNameBuf, "_endgroup");
// Create Field.
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(pFieldNameBuf);
f.pGroupname = StringTable->insert(in_pGroupname);
f.pFieldDocs = NULL;
f.type = AbstractClassRep::EndGroupFieldType;
f.groupExpand = false;
f.validator = NULL;
f.setDataFn = &defaultProtectedSetFn;
f.getDataFn = &defaultProtectedGetFn;
f.elementCount = 0;
// Add to field list.
sg_tempFieldList.push_back(f);
}
void ConsoleObject::addField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
const char* in_pFieldDocs)
{
addField(
in_pFieldname,
in_fieldType,
in_fieldOffset,
1,
NULL,
in_pFieldDocs);
}
void ConsoleObject::addProtectedField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
AbstractClassRep::SetDataNotify in_setDataFn,
AbstractClassRep::GetDataNotify in_getDataFn,
const char* in_pFieldDocs)
{
addProtectedField(
in_pFieldname,
in_fieldType,
in_fieldOffset,
in_setDataFn,
in_getDataFn,
1,
NULL,
in_pFieldDocs);
}
void ConsoleObject::addField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
const U32 in_elementCount,
EnumTable *in_table,
const char* in_pFieldDocs)
{
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(in_pFieldname);
f.pGroupname = NULL;
if(in_pFieldDocs)
f.pFieldDocs = StringTable->insert(in_pFieldDocs);
else
f.pFieldDocs = NULL;
f.type = in_fieldType;
f.offset = in_fieldOffset;
f.elementCount = in_elementCount;
f.table = in_table;
f.validator = NULL;
f.setDataFn = &defaultProtectedSetFn;
f.getDataFn = &defaultProtectedGetFn;
sg_tempFieldList.push_back(f);
}
void ConsoleObject::addProtectedField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
AbstractClassRep::SetDataNotify in_setDataFn,
AbstractClassRep::GetDataNotify in_getDataFn,
const U32 in_elementCount,
EnumTable *in_table,
const char* in_pFieldDocs)
{
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(in_pFieldname);
f.pGroupname = NULL;
if(in_pFieldDocs)
f.pFieldDocs = StringTable->insert(in_pFieldDocs);
else
f.pFieldDocs = NULL;
f.type = in_fieldType;
f.offset = in_fieldOffset;
f.elementCount = in_elementCount;
f.table = in_table;
f.validator = NULL;
f.setDataFn = in_setDataFn;
f.getDataFn = in_getDataFn;
sg_tempFieldList.push_back(f);
}
void ConsoleObject::addFieldV(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
TypeValidator *v,
const char* in_pFieldDocs)
{
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(in_pFieldname);
f.pGroupname = NULL;
if(in_pFieldDocs)
f.pFieldDocs = StringTable->insert(in_pFieldDocs);
else
f.pFieldDocs = NULL;
f.type = in_fieldType;
f.offset = in_fieldOffset;
f.elementCount = 1;
f.table = NULL;
f.setDataFn = &defaultProtectedSetFn;
f.getDataFn = &defaultProtectedGetFn;
f.validator = v;
v->fieldIndex = sg_tempFieldList.size();
sg_tempFieldList.push_back(f);
}
void ConsoleObject::addDepricatedField(const char *fieldName)
{
AbstractClassRep::Field f;
f.pFieldname = StringTable->insert(fieldName);
f.pGroupname = NULL;
f.pFieldDocs = NULL;
f.type = AbstractClassRep::DepricatedFieldType;
f.offset = 0;
f.elementCount = 0;
f.table = NULL;
f.validator = NULL;
f.setDataFn = &defaultProtectedSetFn;
f.getDataFn = &defaultProtectedGetFn;
sg_tempFieldList.push_back(f);
}
bool ConsoleObject::removeField(const char* in_pFieldname)
{
for (U32 i = 0; i < sg_tempFieldList.size(); i++) {
if (dStricmp(in_pFieldname, sg_tempFieldList[i].pFieldname) == 0) {
sg_tempFieldList.erase(i);
return true;
}
}
return false;
}
//--------------------------------------
void ConsoleObject::initPersistFields()
{
}
//--------------------------------------
void ConsoleObject::consoleInit()
{
}
ConsoleObject::~ConsoleObject()
{
}
//--------------------------------------
AbstractClassRep* ConsoleObject::getClassRep() const
{
return NULL;
}

744
engine/console/consoleObject.h Executable file
View File

@ -0,0 +1,744 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONSOLEOBJECT_H_
#define _CONSOLEOBJECT_H_
//Includes
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _TVECTOR_H_
#include "core/tVector.h"
#endif
#ifndef _STRINGTABLE_H_
#include "core/stringTable.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#ifndef _CONSOLE_H_
#include "console/console.h"
#endif
class Namespace;
class ConsoleObject;
enum NetClassTypes {
NetClassTypeObject = 0,
NetClassTypeDataBlock,
NetClassTypeEvent,
NetClassTypesCount,
};
enum NetClassGroups {
NetClassGroupGame = 0,
NetClassGroupCommunity,
NetClassGroup3,
NetClassGroup4,
NetClassGroupsCount,
};
enum NetClassMasks {
NetClassGroupGameMask = BIT(NetClassGroupGame),
NetClassGroupCommunityMask = BIT(NetClassGroupCommunity),
};
enum NetDirection
{
NetEventDirAny,
NetEventDirServerToClient,
NetEventDirClientToServer,
};
class SimObject;
class TypeValidator;
/// Core functionality for class manipulation.
///
/// @section AbstractClassRep_intro Introduction (or, Why AbstractClassRep?)
///
/// Many of Torque's subsystems, especially network, console, and sim,
/// require the ability to programatically instantiate classes. For instance,
/// when objects are ghosted, the networking layer needs to be able to create
/// an instance of the object on the client. When the console scripting
/// language runtime encounters the "new" keyword, it has to be able to fill
/// that request.
///
/// Since standard C++ doesn't provide a function to create a new instance of
/// an arbitrary class at runtime, one must be created. This is what
/// AbstractClassRep and ConcreteClassRep are all about. They allow the registration
/// and instantiation of arbitrary classes at runtime.
///
/// In addition, ACR keeps track of the fields (registered via addField() and co.) of
/// a class, allowing programmatic access of class fields.
///
/// @see ConsoleObject
///
/// @note In general, you will only access the functionality implemented in this class via
/// ConsoleObject::create(). Most of the time, you will only ever need to use this part
/// part of the engine indirectly - ie, you will use the networking system or the console,
/// or ConsoleObject, and they will indirectly use this code. <b>The following discussion
/// is really only relevant for advanced engine users.</b>
///
/// @section AbstractClassRep_netstuff NetClasses and Class IDs
///
/// Torque supports a notion of group, type, and direction for objects passed over
/// the network. Class IDs are assigned sequentially per-group, per-type, so that, for instance,
/// the IDs assigned to Datablocks are seperate from the IDs assigned to NetObjects or NetEvents.
/// This can translate into significant bandwidth savings (especially since the size of the fields
/// for transmitting these bits are determined at run-time based on the number of IDs given out.
///
/// @section AbstractClassRep_details AbstractClassRep Internals
///
/// Much like ConsoleConstructor, ACR does some preparatory work at runtime before execution
/// is passed to main(). In actual fact, this preparatory work is done by the ConcreteClassRep
/// template. Let's examine this more closely.
///
/// If we examine ConsoleObject, we see that two macros must be used in the definition of a
/// properly integrated objects. From the ConsoleObject example:
///
/// @code
/// // This is from inside the class definition...
/// DECLARE_CONOBJECT(TorqueObject);
///
/// // And this is from outside the class definition...
/// IMPLEMENT_CONOBJECT(TorqueObject);
/// @endcode
///
/// What do these things actually do?
///
/// Not all that much, in fact. They expand to code something like this:
///
/// @code
/// // This is from inside the class definition...
/// static ConcreteClassRep<TorqueObject> dynClassRep;
/// static AbstractClassRep* getParentStaticClassRep();
/// static AbstractClassRep* getStaticClassRep();
/// virtual AbstractClassRep* getClassRep() const;
/// @endcode
///
/// @code
/// // And this is from outside the class definition...
/// AbstractClassRep* TorqueObject::getClassRep() const { return &TorqueObject::dynClassRep; }
/// AbstractClassRep* TorqueObject::getStaticClassRep() { return &dynClassRep; }
/// AbstractClassRep* TorqueObject::getParentStaticClassRep() { return Parent::getStaticClassRep(); }
/// ConcreteClassRep<TorqueObject> TorqueObject::dynClassRep("TorqueObject", 0, -1, 0);
/// @endcode
///
/// As you can see, getClassRep(), getStaticClassRep(), and getParentStaticClassRep() are just
/// accessors to allow access to various ConcreteClassRep instances. This is where the Parent
/// typedef comes into play as well - it lets getParentStaticClassRep() get the right
/// class rep.
///
/// In addition, dynClassRep is declared as a member of TorqueObject, and defined later
/// on. Much like ConsoleConstructor, ConcreteClassReps add themselves to a global linked
/// list in their constructor.
///
/// Then, when AbstractClassRep::initialize() is called, from Con::init(), we iterate through
/// the list and perform the following tasks:
/// - Sets up a Namespace for each class.
/// - Call the init() method on each ConcreteClassRep. This method:
/// - Links namespaces between parent and child classes, using Con::classLinkNamespaces.
/// - Calls initPersistFields() and consoleInit().
/// - As a result of calling initPersistFields, the field list for the class is populated.
/// - Assigns network IDs for classes based on their NetGroup membership. Determines
/// bit allocations for network ID fields.
///
/// @nosubgrouping
class AbstractClassRep
{
friend class ConsoleObject;
public:
/// @name 'Tructors
/// @{
AbstractClassRep()
{
VECTOR_SET_ASSOCIATION(mFieldList);
parentClass = NULL;
}
virtual ~AbstractClassRep() { }
/// @}
/// @name Representation Interface
/// @{
S32 mClassGroupMask; ///< Mask indicating in which NetGroups this object belongs.
S32 mClassType; ///< Stores the NetClass of this class.
S32 mNetEventDir; ///< Stores the NetDirection of this class.
S32 mClassId[NetClassGroupsCount]; ///< Stores the IDs assigned to this class for each group.
S32 getClassId (U32 netClassGroup) const;
static U32 getClassCRC (U32 netClassGroup);
const char* getClassName() const;
static AbstractClassRep* getClassList();
Namespace* getNameSpace();
AbstractClassRep* getNextClass();
AbstractClassRep* getParentClass();
/// Helper class to see if we are a given class, or a subclass thereof.
bool isClass(AbstractClassRep *acr)
{
AbstractClassRep *walk = this;
// Walk up parents, checking for equivalence.
while(walk)
{
if(walk == acr)
return true;
walk = walk->parentClass;
};
return false;
}
virtual ConsoleObject* create () const = 0;
protected:
virtual void init() const = 0;
const char * mClassName;
AbstractClassRep * nextClass;
AbstractClassRep * parentClass;
Namespace * mNamespace;
/// @}
/// @name Fields
/// @{
public:
/// This is a function pointer typedef to support get/set callbacks for fields
typedef bool (*SetDataNotify)( void *obj, const char *data );
typedef const char *(*GetDataNotify)( void *obj, const char *data );
enum ACRFieldTypes
{
StartGroupFieldType = 0xFFFFFFFD,
EndGroupFieldType = 0xFFFFFFFE,
DepricatedFieldType = 0xFFFFFFFF
};
struct Field {
const char* pFieldname; ///< Name of the field.
const char* pGroupname; ///< Optionally filled field containing the group name.
///
/// This is filled when type is StartField or EndField
const char* pFieldDocs; ///< Documentation about this field; see consoleDoc.cc.
bool groupExpand; ///< Flag to track expanded/not state of this group in the editor.
U32 type; ///< A type ID. @see ACRFieldTypes
U32 offset; ///< Memory offset from beginning of class for this field.
S32 elementCount; ///< Number of elements, if this is an array.
EnumTable * table; ///< If this is an enum, this points to the table defining it.
BitSet32 flag; ///< Stores various flags
TypeValidator *validator; ///< Validator, if any.
SetDataNotify setDataFn; ///< Set data notify Fn
GetDataNotify getDataFn; ///< Get data notify Fn
};
typedef Vector<Field> FieldList;
FieldList mFieldList;
bool mDynamicGroupExpand;
const Field *findField(StringTableEntry fieldName) const;
/// @}
/// @name Abstract Class Database
/// @{
protected:
static AbstractClassRep ** classTable[NetClassGroupsCount][NetClassTypesCount];
static AbstractClassRep * classLinkList;
static U32 classCRC[NetClassGroupsCount];
static bool initialized;
static ConsoleObject* create(const char* in_pClassName);
static ConsoleObject* create(const U32 groupId, const U32 typeId, const U32 in_classId);
public:
static U32 NetClassCount [NetClassGroupsCount][NetClassTypesCount];
static U32 NetClassBitSize[NetClassGroupsCount][NetClassTypesCount];
static void registerClassRep(AbstractClassRep*);
static void initialize(); // Called from Con::init once on startup
/// @}
};
inline AbstractClassRep *AbstractClassRep::getClassList()
{
return classLinkList;
}
inline U32 AbstractClassRep::getClassCRC(U32 group)
{
return classCRC[group];
}
inline AbstractClassRep *AbstractClassRep::getNextClass()
{
return nextClass;
}
inline AbstractClassRep *AbstractClassRep::getParentClass()
{
return parentClass;
}
inline S32 AbstractClassRep::getClassId(U32 group) const
{
return mClassId[group];
}
inline const char* AbstractClassRep::getClassName() const
{
return mClassName;
}
inline Namespace *AbstractClassRep::getNameSpace()
{
return mNamespace;
}
//------------------------------------------------------------------------------
//-------------------------------------- ConcreteClassRep
//
/// Helper class for AbstractClassRep.
///
/// @see AbtractClassRep
/// @see ConsoleObject
template <class T>
class ConcreteClassRep : public AbstractClassRep
{
public:
ConcreteClassRep(const char *name, S32 netClassGroupMask, S32 netClassType, S32 netEventDir, AbstractClassRep *parent)
{
// name is a static compiler string so no need to worry about copying or deleting
mClassName = name;
// Clean up mClassId
for(U32 i = 0; i < NetClassGroupsCount; i++)
mClassId[i] = -1;
// Set properties for this ACR
mClassType = netClassType;
mClassGroupMask = netClassGroupMask;
mNetEventDir = netEventDir;
parentClass = parent;
// Finally, register ourselves.
registerClassRep(this);
};
/// Perform class specific initialization tasks.
///
/// Link namespaces, call initPersistFields() and consoleInit().
void init() const
{
// Get handle to our parent class, if any, and ourselves (we are our parent's child).
AbstractClassRep *parent = T::getParentStaticClassRep();
AbstractClassRep *child = T::getStaticClassRep ();
// If we got reps, then link those namespaces! (To get proper inheritance.)
if(parent && child)
Con::classLinkNamespaces(parent->getNameSpace(), child->getNameSpace());
// Finally, do any class specific initialization...
T::initPersistFields();
T::consoleInit();
}
/// Wrap constructor.
ConsoleObject* create() const { return new T; }
};
//------------------------------------------------------------------------------
// Forward declaration of this function so it can be used in the class
const char *defaultProtectedGetFn( void *obj, const char *data );
/// Interface class to the console.
///
/// @section ConsoleObject_basics The Basics
///
/// Any object which you want to work with the console system should derive from this,
/// and access functionality through the static interface.
///
/// This class is always used with the DECLARE_CONOBJECT and IMPLEMENT_* macros.
///
/// @code
/// // A very basic example object. It will do nothing!
/// class TorqueObject : public ConsoleObject {
/// // Must provide a Parent typedef so the console system knows what we inherit from.
/// typedef ConsoleObject Parent;
///
/// // This does a lot of menial declaration for you.
/// DECLARE_CONOBJECT(TorqueObject);
///
/// // This is for us to register our fields in.
/// static void initPersistFields();
///
/// // A sample field.
/// S8 mSample;
/// }
/// @endcode
///
/// @code
/// // And the accordant implementation...
/// IMPLEMENT_CONOBJECT(TorqueObject);
///
/// void TorqueObject::initPersistFields()
/// {
/// // If you want to inherit any fields from the parent (you do), do this:
/// Parent::initPersistFields();
///
/// // Pass the field, the type, the offset, and a usage string.
/// addField("sample", TypeS8, Offset(mSample, TorqueObject), "A test field.");
/// }
/// @endcode
///
/// That's all you need to do to get a class registered with the console system. At this point,
/// you can instantiate it via script, tie methods to it using ConsoleMethod, register fields,
/// and so forth. You can also register any global variables related to the class by creating
/// a consoleInit() method.
///
/// You will need to use different IMPLEMENT_ macros in different cases; for instance, if you
/// are making a NetObject (for ghosting), a DataBlock, or a NetEvent.
///
/// @see AbstractClassRep for gory implementation details.
/// @nosubgrouping
class ConsoleObject
{
protected:
/// @deprecated This is disallowed.
ConsoleObject() { /* disallowed */ }
/// @deprecated This is disallowed.
ConsoleObject(const ConsoleObject&);
protected:
/// Get a reference to a field by name.
const AbstractClassRep::Field *findField(StringTableEntry fieldName) const;
public:
/// Gets the ClassRep.
virtual AbstractClassRep* getClassRep() const;
/// Set the value of a field.
bool setField(const char *fieldName, const char *value);
virtual ~ConsoleObject();
public:
/// @name Object Creation
/// @{
static ConsoleObject* create(const char* in_pClassName);
static ConsoleObject* create(const U32 groupId, const U32 typeId, const U32 in_classId);
/// @}
public:
/// Get the classname from a class tag.
static const char* lookupClassName(const U32 in_classTag);
protected:
/// @name Fields
/// @{
/// Mark the beginning of a group of fields.
///
/// This is used in the consoleDoc system.
/// @see console_autodoc
static void addGroup(const char* in_pGroupname, const char* in_pGroupDocs = NULL);
/// Mark the end of a group of fields.
///
/// This is used in the consoleDoc system.
/// @see console_autodoc
static void endGroup(const char* in_pGroupname);
/// Register a complex field.
///
/// @param in_pFieldname Name of the field.
/// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
/// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
/// @param in_elementCount Number of elements in this field. Arrays of elements are assumed to be contiguous in memory.
/// @param in_table An EnumTable, if this is an enumerated field.
/// @param in_pFieldDocs Usage string for this field. @see console_autodoc
static void addField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
const U32 in_elementCount = 1,
EnumTable * in_table = NULL,
const char* in_pFieldDocs = NULL);
/// Register a simple field.
///
/// @param in_pFieldname Name of the field.
/// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
/// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
/// @param in_pFieldDocs Usage string for this field. @see console_autodoc
static void addField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
const char* in_pFieldDocs);
/// Register a validated field.
///
/// A validated field is just like a normal field except that you can't
/// have it be an array, and that you give it a pointer to a TypeValidator
/// subclass, which is then used to validate any value placed in it. Invalid
/// values are ignored and an error is printed to the console.
///
/// @see addField
/// @see typeValidators.h
static void addFieldV(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
TypeValidator *v,
const char * in_pFieldDocs = NULL);
/// Register a complex protected field.
///
/// @param in_pFieldname Name of the field.
/// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
/// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
/// @param in_setDataFn When this field gets set, it will call the callback provided. @see console_protected
/// @param in_getDataFn When this field is accessed for it's data, it will return the value of this function
/// @param in_elementCount Number of elements in this field. Arrays of elements are assumed to be contiguous in memory.
/// @param in_table An EnumTable, if this is an enumerated field.
/// @param in_pFieldDocs Usage string for this field. @see console_autodoc
static void addProtectedField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
AbstractClassRep::SetDataNotify in_setDataFn,
AbstractClassRep::GetDataNotify in_getDataFn = &defaultProtectedGetFn,
const U32 in_elementCount = 1,
EnumTable * in_table = NULL,
const char* in_pFieldDocs = NULL);
/// Register a simple protected field.
///
/// @param in_pFieldname Name of the field.
/// @param in_fieldType Type of the field. @see ConsoleDynamicTypes
/// @param in_fieldOffset Offset to the field from the start of the class; calculated using the Offset() macro.
/// @param in_setDataFn When this field gets set, it will call the callback provided. @see console_protected
/// @param in_getDataFn When this field is accessed for it's data, it will return the value of this function
/// @param in_pFieldDocs Usage string for this field. @see console_autodoc
static void addProtectedField(const char* in_pFieldname,
const U32 in_fieldType,
const dsize_t in_fieldOffset,
AbstractClassRep::SetDataNotify in_setDataFn,
AbstractClassRep::GetDataNotify in_getDataFn = &defaultProtectedGetFn,
const char* in_pFieldDocs = NULL);
/// Add a deprecated field.
///
/// A deprecated field will always be undefined, even if you assign a value to it. This
/// is useful when you need to make sure that a field is not being used anymore.
static void addDepricatedField(const char *fieldName);
/// Remove a field.
///
/// Sometimes, you just have to remove a field!
/// @returns True on success.
static bool removeField(const char* in_pFieldname);
/// @}
public:
/// Register dynamic fields in a subclass of ConsoleObject.
///
/// @see addField(), addFieldV(), addDepricatedField(), addGroup(), endGroup()
static void initPersistFields();
/// Register global constant variables and do other one-time initialization tasks in
/// a subclass of ConsoleObject.
///
/// @deprecated You should use ConsoleMethod and ConsoleFunction, not this, to
/// register methods or commands.
/// @see console
static void consoleInit();
/// @name Field List
/// @{
/// Get a list of all the fields. This information cannot be modified.
const AbstractClassRep::FieldList& getFieldList() const;
/// Get a list of all the fields, set up so we can modify them.
///
/// @note This is a bad trick to pull if you aren't very careful,
/// since you can blast field data!
AbstractClassRep::FieldList& getModifiableFieldList();
/// Get a handle to a boolean telling us if we expanded the dynamic group.
///
/// @see GuiInspector::Inspect()
bool& getDynamicGroupExpand();
/// @}
/// @name ConsoleObject Implementation
///
/// These functions are implemented in every subclass of
/// ConsoleObject by an IMPLEMENT_CONOBJECT or IMPLEMENT_CO_* macro.
/// @{
/// Get the abstract class information for this class.
static AbstractClassRep *getStaticClassRep() { return NULL; }
/// Get the abstract class information for this class's superclass.
static AbstractClassRep *getParentStaticClassRep() { return NULL; }
/// Get our network-layer class id.
///
/// @param netClassGroup The net class for which we want our ID.
/// @see
S32 getClassId(U32 netClassGroup) const;
/// Get our compiler and platform independent class name.
///
/// @note This name can be used to instantiate another instance using create()
const char *getClassName() const;
/// @}
};
// Deprecated? -pw
// Nope, not in TGE - THB
#define addNamedField(fieldName,type,className) addField(#fieldName, type, Offset(fieldName,className))
#define addNamedFieldV(fieldName,type,className, validator) addFieldV(#fieldName, type, Offset(fieldName,className), validator)
//------------------------------------------------------------------------------
//-------------------------------------- Inlines
//
inline S32 ConsoleObject::getClassId(U32 netClassGroup) const
{
AssertFatal(getClassRep() != NULL,"Cannot get tag from non-declared dynamic class!");
return getClassRep()->getClassId(netClassGroup);
}
inline const char * ConsoleObject::getClassName() const
{
AssertFatal(getClassRep() != NULL,
"Cannot get tag from non-declared dynamic class");
return getClassRep()->getClassName();
}
inline const AbstractClassRep::Field * ConsoleObject::findField(StringTableEntry name) const
{
AssertFatal(getClassRep() != NULL,
avar("Cannot get field '%s' from non-declared dynamic class.", name));
return getClassRep()->findField(name);
}
inline bool ConsoleObject::setField(const char *fieldName, const char *value)
{
//sanity check
if ((! fieldName) || (! fieldName[0]) || (! value))
return false;
if (! getClassRep())
return false;
const AbstractClassRep::Field *myField = getClassRep()->findField(StringTable->insert(fieldName));
if (! myField)
return false;
Con::setData(
myField->type,
(void *) (((const char *)(this)) + myField->offset),
0,
1,
&value,
myField->table,
myField->flag);
return true;
}
inline ConsoleObject* ConsoleObject::create(const char* in_pClassName)
{
return AbstractClassRep::create(in_pClassName);
}
inline ConsoleObject* ConsoleObject::create(const U32 groupId, const U32 typeId, const U32 in_classId)
{
return AbstractClassRep::create(groupId, typeId, in_classId);
}
inline const AbstractClassRep::FieldList& ConsoleObject::getFieldList() const
{
return getClassRep()->mFieldList;
}
inline AbstractClassRep::FieldList& ConsoleObject::getModifiableFieldList()
{
return getClassRep()->mFieldList;
}
inline bool& ConsoleObject::getDynamicGroupExpand()
{
return getClassRep()->mDynamicGroupExpand;
}
/// @name ConsoleObject Macros
/// @{
#define DECLARE_CONOBJECT(className) \
static ConcreteClassRep<className> dynClassRep; \
static AbstractClassRep* getParentStaticClassRep(); \
static AbstractClassRep* getStaticClassRep(); \
virtual AbstractClassRep* getClassRep() const
#define IMPLEMENT_CONOBJECT(className) \
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
ConcreteClassRep<className> className::dynClassRep(#className, 0, -1, 0, className::getParentStaticClassRep())
#define IMPLEMENT_CO_NETOBJECT_V1(className) \
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
ConcreteClassRep<className> className::dynClassRep(#className, NetClassGroupGameMask, NetClassTypeObject, 0, className::getParentStaticClassRep())
#define IMPLEMENT_CO_DATABLOCK_V1(className) \
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
ConcreteClassRep<className> className::dynClassRep(#className, NetClassGroupGameMask, NetClassTypeDataBlock, 0, className::getParentStaticClassRep())
/// @}
//------------------------------------------------------------------------------
// Protected field default get/set functions
//
// The reason for these functions is that it will save one branch per console
// data request and script functions will still execute at the same speed as
// before the modifications to allow protected static fields. These will just
// inline and the code should be roughly the same size, and just as fast as
// before the modifications. -pw
inline bool defaultProtectedSetFn( void *obj, const char *data )
{
return true;
}
inline const char *defaultProtectedGetFn( void *obj, const char *data )
{
return data;
}
#endif //_CONSOLEOBJECT_H_

76
engine/console/consoleParser.cc Executable file
View File

@ -0,0 +1,76 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/console.h"
#include "console/consoleParser.h"
namespace Compiler
{
static ConsoleParser *gParserList = NULL;
static ConsoleParser *gDefaultParser = NULL;
void freeConsoleParserList(void)
{
ConsoleParser *pParser;
while(pParser = gParserList)
{
gParserList = pParser->next;
delete pParser;
}
gDefaultParser = NULL;
}
bool addConsoleParser(char *ext, fnGetCurrentFile gcf, fnGetCurrentLine gcl, fnParse p, fnRestart r, fnSetScanBuffer ssb, bool def /* = false */)
{
AssertFatal(ext && gcf && gcl && p && r, "AddConsoleParser called with one or more NULL arguments");
ConsoleParser *pParser;
if(pParser = new ConsoleParser)
{
pParser->ext = ext;
pParser->getCurrentFile = gcf;
pParser->getCurrentLine = gcl;
pParser->parse = p;
pParser->restart = r;
pParser->setScanBuffer = ssb;
if(def)
gDefaultParser = pParser;
pParser->next = gParserList;
gParserList = pParser;
return true;
}
return false;
}
ConsoleParser * getParserForFile(const char *filename)
{
char *ptr;
if(filename == NULL)
return gDefaultParser;
if(ptr = dStrrchr((char *)filename, '.'))
{
ptr++;
ConsoleParser *p;
for(p = gParserList;p;p = p->next)
{
if(dStricmp(ptr, p->ext) == 0)
return p;
}
}
return gDefaultParser;
}
} // end namespace Con

105
engine/console/consoleParser.h Executable file
View File

@ -0,0 +1,105 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//
// TorqueBASIC
// (c) Copyright 2004 Burnt Wasp
//-----------------------------------------------------------------------------
#ifndef _CONSOLE_PARSER_H_
#define _CONSOLE_PARSER_H_
#include <stdio.h>
namespace Compiler
{
//////////////////////////////////////////////////////////////////////////
/// \brief Function for GetCurrentFile from the lexer
//////////////////////////////////////////////////////////////////////////
typedef const char *(*fnGetCurrentFile)();
//////////////////////////////////////////////////////////////////////////
/// \brief Function for GetCurrentLine from the lexer
//////////////////////////////////////////////////////////////////////////
typedef S32 (*fnGetCurrentLine)();
//////////////////////////////////////////////////////////////////////////
/// \brief Function for Parse from the lexer
//////////////////////////////////////////////////////////////////////////
typedef S32 (*fnParse)();
//////////////////////////////////////////////////////////////////////////
/// \brief Function for Restart from the lexer
//////////////////////////////////////////////////////////////////////////
typedef void (*fnRestart)(FILE *input_file);
//////////////////////////////////////////////////////////////////////////
/// \brief Function for SetScanBuffer from the lexer
//////////////////////////////////////////////////////////////////////////
typedef void (*fnSetScanBuffer)(const char *sb, const char *fn);
//////////////////////////////////////////////////////////////////////////
/// \brief List of parsers for the compiler
//////////////////////////////////////////////////////////////////////////
struct ConsoleParser
{
struct ConsoleParser *next; //!< Next object in list or NULL
char *ext; //!< Filename extension handled by this parser
fnGetCurrentFile getCurrentFile; //!< GetCurrentFile lexer function
fnGetCurrentLine getCurrentLine; //!< GetCurrentLine lexer function
fnParse parse; //!< Parse lexer function
fnRestart restart; //!< Restart lexer function
fnSetScanBuffer setScanBuffer; //!< SetScanBuffer lexer function
};
// Macros
//////////////////////////////////////////////////////////////////////////
/// \brief Declare a parser's function prototypes
//////////////////////////////////////////////////////////////////////////
#define CON_DECLARE_PARSER(prefix) \
const char * prefix##GetCurrentFile(); \
S32 prefix##GetCurrentLine(); \
void prefix##SetScanBuffer(const char *sb, const char *fn); \
S32 prefix##parse(); \
void prefix##restart(FILE *input_file)
//////////////////////////////////////////////////////////////////////////
/// \brief Helper macro to add console parsers
//////////////////////////////////////////////////////////////////////////
#define CON_ADD_PARSER(prefix, ext, def) \
Compiler::addConsoleParser(ext, prefix##GetCurrentFile, prefix##GetCurrentLine, prefix##parse, \
prefix##restart, prefix##SetScanBuffer, def)
//////////////////////////////////////////////////////////////////////////
/// \brief Free the console parser list
///
/// \sa AddConsoleParser()
//////////////////////////////////////////////////////////////////////////
void freeConsoleParserList(void);
//////////////////////////////////////////////////////////////////////////
/// \brief Add a console parser to the list
///
/// \param ext Filename extension
/// \param gcf GetCurrentFile function
/// \param gcl GetCurrentLine function
/// \param p Parse function
/// \param r Restart function
/// \param ssb SetScanBuffer function
/// \param def true if this is the default parser (<b>Note:</b> set this only on the .cs parser!)
/// \return true for success, false for failure (out of memory)
/// \sa FreeConsoleParserList(), ConsoleParser
//////////////////////////////////////////////////////////////////////////
bool addConsoleParser(char *ext, fnGetCurrentFile gcf, fnGetCurrentLine gcl, fnParse p, fnRestart r, fnSetScanBuffer ssb, bool def = false);
//////////////////////////////////////////////////////////////////////////
/// \brief Get the parser for a particular file based on its extension
///
/// \param filename Filename of file to obtain parser for
/// \sa ConsoleParser
//////////////////////////////////////////////////////////////////////////
ConsoleParser * getParserForFile(const char *filename);
} // end namespace Con
#endif // _CONSOLE_PARSER_H_

527
engine/console/consoleTypes.cc Executable file
View File

@ -0,0 +1,527 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/console.h"
#include "console/consoleTypes.h"
#include "core/stringTable.h"
#include "core/color.h"
#include "console/simBase.h"
//////////////////////////////////////////////////////////////////////////
// TypeString
//////////////////////////////////////////////////////////////////////////
ConsoleType( string, TypeString, sizeof(const char*) )
ConsoleGetType( TypeString )
{
return *((const char **)(dptr));
}
ConsoleSetType( TypeString )
{
if(argc == 1)
*((const char **) dptr) = StringTable->insert(argv[0]);
else
Con::printf("(TypeString) Cannot set multiple args to a single string.");
}
//////////////////////////////////////////////////////////////////////////
// TypeCaseString
//////////////////////////////////////////////////////////////////////////
ConsoleType( caseString, TypeCaseString, sizeof(const char*) )
ConsoleSetType( TypeCaseString )
{
if(argc == 1)
*((const char **) dptr) = StringTable->insert(argv[0], true);
else
Con::printf("(TypeCaseString) Cannot set multiple args to a single string.");
}
ConsoleGetType( TypeCaseString )
{
return *((const char **)(dptr));
}
//////////////////////////////////////////////////////////////////////////
// TypeFileName
//////////////////////////////////////////////////////////////////////////
ConsolePrepType( filename, TypeFilename, sizeof( const char* ) )
ConsoleSetType( TypeFilename )
{
if(argc == 1)
{
char buffer[1024];
if (Con::expandScriptFilename(buffer, 1024, argv[0]))
*((const char **) dptr) = StringTable->insert(buffer);
else
Con::warnf("(TypeFilename) illegal filename detected: %s", argv[0]);
}
else
Con::printf("(TypeFilename) Cannot set multiple args to a single filename.");
}
ConsoleGetType( TypeFilename )
{
return *((const char **)(dptr));
}
ConsoleProcessData( TypeFilename )
{
if( Con::expandScriptFilename( buffer, bufferSz, data ) )
return buffer;
else
{
Con::warnf("(TypeFilename) illegal filename detected: %s", data);
return data;
}
}
//////////////////////////////////////////////////////////////////////////
// TypeS8
//////////////////////////////////////////////////////////////////////////
ConsoleType( char, TypeS8, sizeof(U8) )
ConsoleGetType( TypeS8 )
{
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%d", *((U8 *) dptr) );
return returnBuffer;
}
ConsoleSetType( TypeS8 )
{
if(argc == 1)
*((U8 *) dptr) = dAtoi(argv[0]);
else
Con::printf("(TypeU8) Cannot set multiple args to a single S32.");
}
//////////////////////////////////////////////////////////////////////////
// TypeS32
//////////////////////////////////////////////////////////////////////////
ConsoleType( int, TypeS32, sizeof(S32) )
ConsoleGetType( TypeS32 )
{
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%d", *((S32 *) dptr) );
return returnBuffer;
}
ConsoleSetType( TypeS32 )
{
if(argc == 1)
*((S32 *) dptr) = dAtoi(argv[0]);
else
Con::printf("(TypeS32) Cannot set multiple args to a single S32.");
}
//////////////////////////////////////////////////////////////////////////
// TypeS32Vector
//////////////////////////////////////////////////////////////////////////
ConsoleType( intList, TypeS32Vector, sizeof(Vector<S32>) )
ConsoleGetType( TypeS32Vector )
{
Vector<S32> *vec = (Vector<S32> *)dptr;
S32 buffSize = ( vec->size() * 15 ) + 16 ;
char* returnBuffer = Con::getReturnBuffer( buffSize );
S32 maxReturn = buffSize;
returnBuffer[0] = '\0';
S32 returnLeng = 0;
for (Vector<S32>::iterator itr = vec->begin(); itr != vec->end(); itr++)
{
// concatenate the next value onto the return string
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%d ", *itr);
// update the length of the return string (so far)
returnLeng = dStrlen(returnBuffer);
}
// trim off that last extra space
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
returnBuffer[returnLeng - 1] = '\0';
return returnBuffer;
}
ConsoleSetType( TypeS32Vector )
{
Vector<S32> *vec = (Vector<S32> *)dptr;
// we assume the vector should be cleared first (not just appending)
vec->clear();
if(argc == 1)
{
const char *values = argv[0];
const char *endValues = values + dStrlen(values);
S32 value;
// advance through the string, pulling off S32's and advancing the pointer
while (values < endValues && dSscanf(values, "%d", &value) != 0)
{
vec->push_back(value);
const char *nextValues = dStrchr(values, ' ');
if (nextValues != 0 && nextValues < endValues)
values = nextValues + 1;
else
break;
}
}
else if (argc > 1)
{
for (S32 i = 0; i < argc; i++)
vec->push_back(dAtoi(argv[i]));
}
else
Con::printf("Vector<S32> must be set as { a, b, c, ... } or \"a b c ...\"");
}
//////////////////////////////////////////////////////////////////////////
// TypeF32
//////////////////////////////////////////////////////////////////////////
ConsoleType( float, TypeF32, sizeof(F32) )
ConsoleGetType( TypeF32 )
{
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%g", *((F32 *) dptr) );
return returnBuffer;
}
ConsoleSetType( TypeF32 )
{
if(argc == 1)
*((F32 *) dptr) = dAtof(argv[0]);
else
Con::printf("(TypeF32) Cannot set multiple args to a single F32.");
}
//////////////////////////////////////////////////////////////////////////
// TypeF32Vector
//////////////////////////////////////////////////////////////////////////
ConsoleType( floatList, TypeF32Vector, sizeof(Vector<F32>) )
ConsoleGetType( TypeF32Vector )
{
Vector<F32> *vec = (Vector<F32> *)dptr;
S32 buffSize = ( vec->size() * 15 ) + 16 ;
char* returnBuffer = Con::getReturnBuffer( buffSize );
S32 maxReturn = buffSize;
returnBuffer[0] = '\0';
S32 returnLeng = 0;
for (Vector<F32>::iterator itr = vec->begin(); itr != vec->end(); itr++)
{
// concatenate the next value onto the return string
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%g ", *itr);
// update the length of the return string (so far)
returnLeng = dStrlen(returnBuffer);
}
// trim off that last extra space
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
returnBuffer[returnLeng - 1] = '\0';
return returnBuffer;
}
ConsoleSetType( TypeF32Vector )
{
Vector<F32> *vec = (Vector<F32> *)dptr;
// we assume the vector should be cleared first (not just appending)
vec->clear();
if(argc == 1)
{
const char *values = argv[0];
const char *endValues = values + dStrlen(values);
F32 value;
// advance through the string, pulling off F32's and advancing the pointer
while (values < endValues && dSscanf(values, "%g", &value) != 0)
{
vec->push_back(value);
const char *nextValues = dStrchr(values, ' ');
if (nextValues != 0 && nextValues < endValues)
values = nextValues + 1;
else
break;
}
}
else if (argc > 1)
{
for (S32 i = 0; i < argc; i++)
vec->push_back(dAtof(argv[i]));
}
else
Con::printf("Vector<F32> must be set as { a, b, c, ... } or \"a b c ...\"");
}
//////////////////////////////////////////////////////////////////////////
// TypeBool
//////////////////////////////////////////////////////////////////////////
ConsoleType( bool, TypeBool, sizeof(bool) )
ConsoleGetType( TypeBool )
{
return *((bool *) dptr) ? "1" : "0";
}
ConsoleSetType( TypeBool )
{
if(argc == 1)
*((bool *) dptr) = dAtob(argv[0]);
else
Con::printf("(TypeBool) Cannot set multiple args to a single bool.");
}
//////////////////////////////////////////////////////////////////////////
// TypeBoolVector
//////////////////////////////////////////////////////////////////////////
ConsoleType( boolList, TypeBoolVector, sizeof(Vector<bool>) )
ConsoleGetType( TypeBoolVector )
{
Vector<bool> *vec = (Vector<bool>*)dptr;
char* returnBuffer = Con::getReturnBuffer(1024);
S32 maxReturn = 1024;
returnBuffer[0] = '\0';
S32 returnLeng = 0;
for (Vector<bool>::iterator itr = vec->begin(); itr < vec->end(); itr++)
{
// concatenate the next value onto the return string
dSprintf(returnBuffer + returnLeng, maxReturn - returnLeng, "%d ", (*itr == true ? 1 : 0));
returnLeng = dStrlen(returnBuffer);
}
// trim off that last extra space
if (returnLeng > 0 && returnBuffer[returnLeng - 1] == ' ')
returnBuffer[returnLeng - 1] = '\0';
return(returnBuffer);
}
ConsoleSetType( TypeBoolVector )
{
Vector<bool> *vec = (Vector<bool>*)dptr;
// we assume the vector should be cleared first (not just appending)
vec->clear();
if (argc == 1)
{
const char *values = argv[0];
const char *endValues = values + dStrlen(values);
S32 value;
// advance through the string, pulling off bool's and advancing the pointer
while (values < endValues && dSscanf(values, "%d", &value) != 0)
{
vec->push_back(value == 0 ? false : true);
const char *nextValues = dStrchr(values, ' ');
if (nextValues != 0 && nextValues < endValues)
values = nextValues + 1;
else
break;
}
}
else if (argc > 1)
{
for (S32 i = 0; i < argc; i++)
vec->push_back(dAtob(argv[i]));
}
else
Con::printf("Vector<bool> must be set as { a, b, c, ... } or \"a b c ...\"");
}
//////////////////////////////////////////////////////////////////////////
// TypeEnum
//////////////////////////////////////////////////////////////////////////
ConsoleType( enumval, TypeEnum, sizeof(S32) )
ConsoleGetType( TypeEnum )
{
AssertFatal(tbl, "Null enum table passed to getDataTypeEnum()");
S32 dptrVal = *(S32*)dptr;
for (S32 i = 0; i < tbl->size; i++)
{
if (dptrVal == tbl->table[i].index)
{
return tbl->table[i].label;
}
}
//not found
return "";
}
ConsoleSetType( TypeEnum )
{
AssertFatal(tbl, "Null enum table passed to setDataTypeEnum()");
if (argc != 1) return;
S32 val = 0;
for (S32 i = 0; i < tbl->size; i++)
{
if (! dStricmp(argv[0], tbl->table[i].label))
{
val = tbl->table[i].index;
break;
}
}
*((S32 *) dptr) = val;
}
//////////////////////////////////////////////////////////////////////////
// TypeFlag
//////////////////////////////////////////////////////////////////////////
ConsoleType( flag, TypeFlag, sizeof(S32) )
ConsoleGetType( TypeFlag )
{
BitSet32 tempFlags = *(BitSet32 *)dptr;
if (tempFlags.test(flag)) return "true";
else return "false";
}
ConsoleSetType( TypeFlag )
{
bool value = true;
if (argc != 1)
{
Con::printf("flag must be true or false");
}
else
{
value = dAtob(argv[0]);
}
((BitSet32 *)dptr)->set(flag, value);
}
//////////////////////////////////////////////////////////////////////////
// TypeColorF
//////////////////////////////////////////////////////////////////////////
ConsoleType( ColorF, TypeColorF, sizeof(ColorF) )
ConsoleGetType( TypeColorF )
{
ColorF * color = (ColorF*)dptr;
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%g %g %g %g", color->red, color->green, color->blue, color->alpha);
return(returnBuffer);
}
ConsoleSetType( TypeColorF )
{
ColorF *tmpColor = (ColorF *) dptr;
if(argc == 1)
{
tmpColor->set(0, 0, 0, 1);
F32 r,g,b,a;
S32 args = dSscanf(argv[0], "%g %g %g %g", &r, &g, &b, &a);
tmpColor->red = r;
tmpColor->green = g;
tmpColor->blue = b;
if (args == 4)
tmpColor->alpha = a;
}
else if(argc == 3)
{
tmpColor->red = dAtof(argv[0]);
tmpColor->green = dAtof(argv[1]);
tmpColor->blue = dAtof(argv[2]);
tmpColor->alpha = 1.f;
}
else if(argc == 4)
{
tmpColor->red = dAtof(argv[0]);
tmpColor->green = dAtof(argv[1]);
tmpColor->blue = dAtof(argv[2]);
tmpColor->alpha = dAtof(argv[3]);
}
else
Con::printf("Color must be set as { r, g, b [,a] }");
}
//////////////////////////////////////////////////////////////////////////
// TypeColorI
//////////////////////////////////////////////////////////////////////////
ConsoleType( ColorI, TypeColorI, sizeof(ColorI) )
ConsoleGetType( TypeColorI )
{
ColorI *color = (ColorI *) dptr;
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%d %d %d %d", color->red, color->green, color->blue, color->alpha);
return returnBuffer;
}
ConsoleSetType( TypeColorI )
{
ColorI *tmpColor = (ColorI *) dptr;
if(argc == 1)
{
tmpColor->set(0, 0, 0, 255);
S32 r,g,b,a;
S32 args = dSscanf(argv[0], "%d %d %d %d", &r, &g, &b, &a);
tmpColor->red = r;
tmpColor->green = g;
tmpColor->blue = b;
if (args == 4)
tmpColor->alpha = a;
}
else if(argc == 3)
{
tmpColor->red = dAtoi(argv[0]);
tmpColor->green = dAtoi(argv[1]);
tmpColor->blue = dAtoi(argv[2]);
tmpColor->alpha = 255;
}
else if(argc == 4)
{
tmpColor->red = dAtoi(argv[0]);
tmpColor->green = dAtoi(argv[1]);
tmpColor->blue = dAtoi(argv[2]);
tmpColor->alpha = dAtoi(argv[3]);
}
else
Con::printf("Color must be set as { r, g, b [,a] }");
}
//////////////////////////////////////////////////////////////////////////
// TypeSimObjectPtr
//////////////////////////////////////////////////////////////////////////
ConsoleType( SimObjectPtr, TypeSimObjectPtr, sizeof(SimObject*) )
ConsoleSetType( TypeSimObjectPtr )
{
if(argc == 1)
{
SimObject **obj = (SimObject **)dptr;
*obj = Sim::findObject(argv[0]);
}
else
Con::printf("(TypeSimObjectPtr) Cannot set multiple args to a single S32.");
}
ConsoleGetType( TypeSimObjectPtr )
{
SimObject **obj = (SimObject**)dptr;
char* returnBuffer = Con::getReturnBuffer(256);
dSprintf(returnBuffer, 256, "%s", *obj ? (*obj)->getName() ? (*obj)->getName() : (*obj)->getIdString() : "");
return returnBuffer;
}
//////////////////////////////////////////////////////////////////////////
// TypeSimObjectName
//////////////////////////////////////////////////////////////////////////
ConsoleType( SimObjectPtr, TypeSimObjectName, sizeof(SimObject*) )
ConsoleSetType( TypeSimObjectName )
{
if(argc == 1)
{
SimObject **obj = (SimObject **)dptr;
*obj = Sim::findObject(argv[0]);
}
else
Con::printf("(TypeSimObjectName) Cannot set multiple args to a single S32.");
}
ConsoleGetType( TypeSimObjectName )
{
SimObject **obj = (SimObject**)dptr;
char* returnBuffer = Con::getReturnBuffer(128);
dSprintf(returnBuffer, 128, "%s", *obj && (*obj)->getName() ? (*obj)->getName() : "");
return returnBuffer;
}

39
engine/console/consoleTypes.h Executable file
View File

@ -0,0 +1,39 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _CONSOLETYPES_H_
#define _CONSOLETYPES_H_
#ifndef _DYNAMIC_CONSOLETYPES_H_
#include "console/dynamicTypes.h"
#endif
#ifndef Offset
#if defined(TORQUE_COMPILER_GCC) && (__GNUC__ > 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1))
#define Offset(m,T) ((int)(&((T *)1)->m) - 1)
#else
#define Offset(x, cls) ((dsize_t)((const char *)&(((cls *)0)->x)-(const char *)0))
#endif
#endif
// Define Core Console Types
DefineConsoleType( TypeF32 )
DefineConsoleType( TypeS8 )
DefineConsoleType( TypeS32 )
DefineConsoleType( TypeS32Vector )
DefineConsoleType( TypeBool )
DefineConsoleType( TypeBoolVector )
DefineConsoleType( TypeF32Vector )
DefineConsoleType( TypeString )
DefineConsoleType( TypeCaseString )
DefineConsoleType( TypeFilename )
DefineConsoleType( TypeEnum )
DefineConsoleType( TypeFlag )
DefineConsoleType( TypeColorI )
DefineConsoleType( TypeColorF )
DefineConsoleType( TypeSimObjectPtr )
DefineConsoleType( TypeSimObjectName )
#endif

72
engine/console/dynamicTypes.cc Executable file
View File

@ -0,0 +1,72 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/dynamicTypes.h"
// Init the globals.
ConsoleBaseType *ConsoleBaseType::smListHead = NULL;
S32 ConsoleBaseType::smConsoleTypeCount = 0;
// And, we also privately store the types lookup table.
VectorPtr<ConsoleBaseType*> gConsoleTypeTable;
ConsoleBaseType *ConsoleBaseType::getListHead()
{
return smListHead;
}
void ConsoleBaseType::initialize()
{
// Prep and empty the vector.
gConsoleTypeTable.setSize(smConsoleTypeCount+1);
dMemset(gConsoleTypeTable.address(), 0, sizeof(ConsoleBaseType*) * gConsoleTypeTable.size());
// Walk the list and register each one with the console system.
ConsoleBaseType *walk = getListHead();
while(walk)
{
// Store a pointer to the type in the appropriate slot.
const S32 id = walk->getTypeID();
AssertFatal(gConsoleTypeTable[id]==NULL, "ConsoleBaseType::initialize - encountered a table slot that contained something!");
gConsoleTypeTable[id] = walk;
// Advance down the list...
walk = walk->getListNext();
}
// Alright, we're all done here; we can now achieve fast lookups by ID.
}
ConsoleBaseType *ConsoleBaseType::getType(const S32 typeID)
{
return gConsoleTypeTable[typeID];
}
//-------------------------------------------------------------------------
ConsoleBaseType::ConsoleBaseType(const S32 size, S32 *idPtr, const char *aTypeName)
{
// General initialization.
mInspectorFieldType = NULL;
// Store general info.
mTypeSize = size;
mTypeName = aTypeName;
// Get our type ID and store it.
mTypeID = smConsoleTypeCount++;
*idPtr = mTypeID;
// Link ourselves into the list.
mListNext = smListHead;
smListHead = this;
// Alright, all done for now. Console initialization time code
// takes us from having a list of general info and classes to
// a fully initialized type table.
}
ConsoleBaseType::~ConsoleBaseType()
{
// Nothing to do for now; we could unlink ourselves from the list, but why?
}

123
engine/console/dynamicTypes.h Executable file
View File

@ -0,0 +1,123 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _DYNAMIC_CONSOLETYPES_H_
#define _DYNAMIC_CONSOLETYPES_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
class ConsoleBaseType
{
protected:
/// This is used to generate unique IDs for each type.
static S32 smConsoleTypeCount;
/// We maintain a linked list of all console types; this is its head.
static ConsoleBaseType *smListHead;
/// Next item in the list of all console types.
ConsoleBaseType *mListNext;
/// Destructor is private to avoid people mucking up the list.
~ConsoleBaseType();
S32 mTypeID;
dsize_t mTypeSize;
const char *mTypeName;
const char *mInspectorFieldType;
public:
/// @name cbt_list List Interface
///
/// Interface for accessing/traversing the list of types.
/// Get the head of the list.
static ConsoleBaseType *getListHead();
/// Get the item that follows this item in the list.
ConsoleBaseType *getListNext() const
{
return mListNext;
}
/// Called once to initialize the console type system.
static void initialize();
/// Call me to get a pointer to a type's info.
static ConsoleBaseType *getType(const S32 typeID);
/// @}
/// The constructor is responsible for linking an element into the
/// master list, registering the type ID, etc.
ConsoleBaseType(const S32 size, S32 *idPtr, const char *aTypeName);
const S32 getTypeID() const { return mTypeID; }
const S32 getTypeSize() const { return mTypeSize; }
const char *getTypeName() const { return mTypeName; }
void setInspectorFieldType(const char *type) { mInspectorFieldType = type; }
const char *getInspectorFieldType() { return mInspectorFieldType; }
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag)=0;
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag )=0;
virtual const char *getTypeClassName()=0;
virtual const bool isDatablock() { return false; };
virtual const char *prepData(const char *data, char *buffer, U32 bufferLen) { return data; };
};
#define DefineConsoleType( type ) extern S32 type;
#define ConsoleType( typeName, type, size ) \
class ConsoleType##type : public ConsoleBaseType \
{ \
public: \
ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { } \
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag); \
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag ); \
virtual const char *getTypeClassName() { return #typeName ; } \
}; \
S32 type = -1; \
ConsoleType##type gConsoleType##type##Instance(size,&type,#type); \
#define ConsolePrepType( typeName, type, size ) \
class ConsoleType##type : public ConsoleBaseType \
{ \
public: \
ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { } \
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag); \
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag ); \
virtual const char *getTypeClassName() { return #typeName; }; \
virtual const char *prepData(const char *data, char *buffer, U32 bufferLen); \
}; \
S32 type = -1; \
ConsoleType##type gConsoleType##type##Instance(size,&type,#type); \
#define ConsoleSetType( type ) \
void ConsoleType##type::setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag)
#define ConsoleGetType( type ) \
const char *ConsoleType##type::getData(void *dptr, EnumTable *tbl, BitSet32 flag)
#define ConsoleProcessData( type ) \
const char *ConsoleType##type::prepData(const char *data, char *buffer, U32 bufferSz)
#define DatablockConsoleType( typeName, type, size, className ) \
class ConsoleType##type : public ConsoleBaseType \
{ \
public: \
ConsoleType##type (const S32 aSize, S32 *idPtr, const char *aTypeName) : ConsoleBaseType(aSize, idPtr, aTypeName) { } \
virtual void setData(void *dptr, S32 argc, const char **argv, EnumTable *tbl, BitSet32 flag); \
virtual const char *getData(void *dptr, EnumTable *tbl, BitSet32 flag ); \
virtual const char *getTypeClassName() { return #className; }; \
virtual const bool isDatablock() { return true; }; \
}; \
S32 type = -1; \
ConsoleType##type gConsoleType##type##Instance(size,&type,#type); \
#endif

299
engine/console/scriptObject.cc Executable file
View File

@ -0,0 +1,299 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/simBase.h"
#include "console/consoleTypes.h"
//-----------------------------------------------------------------------------
// Script object placeholder
//-----------------------------------------------------------------------------
class ScriptObject : public SimObject
{
typedef SimObject Parent;
StringTableEntry mClassName;
StringTableEntry mSuperClassName;
public:
ScriptObject();
bool onAdd();
void onRemove();
DECLARE_CONOBJECT(ScriptObject);
static void initPersistFields();
};
IMPLEMENT_CONOBJECT(ScriptObject);
void ScriptObject::initPersistFields()
{
addGroup("Classes", "Script objects have the ability to inherit and have class information.");
addField("class", TypeString, Offset(mClassName, ScriptObject), "Class of object.");
addField("superClass", TypeString, Offset(mSuperClassName, ScriptObject), "Superclass of object.");
endGroup("Classes");
}
ScriptObject::ScriptObject()
{
mClassName = "";
mSuperClassName = "";
}
bool ScriptObject::onAdd()
{
if (!Parent::onAdd())
return false;
// it's possible that all the namespace links can fail, if
// multiple objects are named the same thing with different script
// hierarchies.
// linkNamespaces will now return false and echo an error message
// rather than asserting.
// superClassName -> ScriptObject
StringTableEntry parent = StringTable->insert("ScriptObject");
if(mSuperClassName[0])
{
if(Con::linkNamespaces(parent, mSuperClassName))
parent = mSuperClassName;
}
// className -> superClassName
if (mClassName[0])
{
if(Con::linkNamespaces(parent, mClassName))
parent = mClassName;
}
// objectName -> className
StringTableEntry objectName = getName();
if (objectName && objectName[0])
{
if(Con::linkNamespaces(parent, objectName))
parent = objectName;
}
// Store our namespace
mNameSpace = Con::lookupNamespace(parent);
// Call onAdd in script!
Con::executef(this, 2, "onAdd", Con::getIntArg(getId()));
return true;
}
void ScriptObject::onRemove()
{
// We call this on this objects namespace so we unlink them after. - jdd
//
// Call onRemove in script!
Con::executef(this, 2, "onRemove", Con::getIntArg(getId()));
// Restore NameSpace's
StringTableEntry child = getName();
if( child && child[0] )
{
if(mClassName && mClassName[0])
{
if(Con::unlinkNamespaces(mClassName, child))
child = mClassName;
}
if(mSuperClassName && mSuperClassName[0])
{
if(Con::unlinkNamespaces(mSuperClassName, child))
child = mSuperClassName;
}
Con::unlinkNamespaces(getClassName(), child);
}
else
{
child = mClassName;
if(child && child[0])
{
if(mSuperClassName && mSuperClassName[0])
{
if(Con::unlinkNamespaces(mSuperClassName, child))
child = mSuperClassName;
}
Con::unlinkNamespaces(getClassName(), child);
}
else
{
if(mSuperClassName && mSuperClassName[0])
Con::unlinkNamespaces(getClassName(), mSuperClassName);
}
}
Parent::onRemove();
}
//-----------------------------------------------------------------------------
// Script group placeholder
//-----------------------------------------------------------------------------
class ScriptGroup : public SimGroup
{
typedef SimGroup Parent;
StringTableEntry mClassName;
StringTableEntry mSuperClassName;
public:
ScriptGroup();
bool onAdd();
void onRemove();
DECLARE_CONOBJECT(ScriptGroup);
static void initPersistFields();
};
IMPLEMENT_CONOBJECT(ScriptGroup);
void ScriptGroup::initPersistFields()
{
addGroup("Classes");
addField("class", TypeString, Offset(mClassName, ScriptGroup));
addField("superClass", TypeString, Offset(mSuperClassName, ScriptGroup));
endGroup("Classes");
}
ScriptGroup::ScriptGroup()
{
mClassName = "";
mSuperClassName = "";
}
bool ScriptGroup::onAdd()
{
if (!Parent::onAdd())
return false;
// superClassName -> ScriptGroup
StringTableEntry parent = StringTable->insert("ScriptGroup");
if(mSuperClassName[0])
{
if(Con::linkNamespaces(parent, mSuperClassName))
parent = mSuperClassName;
}
// className -> superClassName
if(mClassName[0])
{
if(Con::linkNamespaces(parent, mClassName))
parent = mClassName;
}
// objectName -> className
StringTableEntry objectName = getName();
if (objectName && objectName[0])
{
if(Con::linkNamespaces(parent, objectName))
parent = objectName;
}
// Store our namespace
mNameSpace = Con::lookupNamespace(parent);
// Call onAdd in script!
Con::executef(this, 2, "onAdd", Con::getIntArg(getId()));
return true;
}
void ScriptGroup::onRemove()
{
// Call onRemove in script!
Con::executef(this, 2, "onRemove", Con::getIntArg(getId()));
Parent::onRemove();
}
//-----------------------------------------------------------------------------
// Script Class placeholder
//-----------------------------------------------------------------------------
class ScriptClass : public SimObject
{
typedef SimObject Parent;
StringTableEntry mLibraryName;
StringTableEntry mClassName;
public:
ScriptClass();
DECLARE_CONOBJECT(ScriptClass);
bool onAdd();
static void initPersistFields();
};
IMPLEMENT_CONOBJECT(ScriptClass);
void ScriptClass::initPersistFields()
{
Parent::initPersistFields();
addGroup("ClassLibrary", "Script objects have the ability to inherit and have class information.");
addField("libraryName", TypeString, Offset(mLibraryName, ScriptClass), "Class Library This Belongs to.");
addField("className", TypeString, Offset(mClassName, ScriptClass), "Script Class Namespace this object defines.");
endGroup("ClassLibrary");
}
ScriptClass::ScriptClass()
{
mLibraryName = "";
mClassName = "";
}
bool ScriptClass::onAdd()
{
SimGroup *scriptClassGroup = Sim::getScriptClassGroup();
if( !scriptClassGroup )
{
Con::errorf("ScriptClass::onAdd - No ScriptClassGroup found!");
return false;
}
if (!Parent::onAdd())
return false;
// No library goes in root.
if( !mLibraryName || mLibraryName == "" )
{
scriptClassGroup->addObject( this );
return true;
}
SimGroup *libraryGroup = dynamic_cast<SimGroup*>( scriptClassGroup->findObjectByInternalName( mLibraryName ) );
if( libraryGroup != NULL )
{
libraryGroup->addObject( this );
return true;
}
libraryGroup = new SimGroup();
if ( libraryGroup == NULL )
{
Con::errorf("ScriptClass::onAdd - Unable to create non-existent Script ClassLibrary %s!", mLibraryName );
return false;
}
// Register the Script ClassLibrary SimGroup
libraryGroup->registerObject();
// Set Internal Name
libraryGroup->setInternalName( StringTable->insert( mLibraryName ) );
// Add to ScriptClassGroup SimGroup
scriptClassGroup->addObject( libraryGroup );
// Add ourselves to our new Script ClassLibrary
libraryGroup->addObject( this );
// Success!
return true;
}

2033
engine/console/simBase.cc Executable file

File diff suppressed because it is too large Load Diff

1406
engine/console/simBase.h Executable file

File diff suppressed because it is too large Load Diff

301
engine/console/simDictionary.cc Executable file
View File

@ -0,0 +1,301 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/simDictionary.h"
#include "console/simBase.h"
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
extern S32 HashPointer(StringTableEntry e);
SimNameDictionary::SimNameDictionary()
{
hashTable = NULL;
mutex = Mutex::createMutex();
}
SimNameDictionary::~SimNameDictionary()
{
delete[] hashTable;
Mutex::destroyMutex(mutex);
}
void SimNameDictionary::insert(SimObject* obj)
{
if(!obj->objectName)
return;
Mutex::lockMutex(mutex);
if(!hashTable)
{
hashTable = new SimObject *[DefaultTableSize];
hashTableSize = DefaultTableSize;
hashEntryCount = 0;
S32 i;
for(i = 0; i < hashTableSize; i++)
hashTable[i] = NULL;
}
S32 idx = HashPointer(obj->objectName) % hashTableSize;
obj->nextNameObject = hashTable[idx];
hashTable[idx] = obj;
hashEntryCount++;
if(hashEntryCount > hashTableSize)
{
// resize the hash table
S32 i;
SimObject *head = NULL, *walk, *temp;
for(i = 0; i < hashTableSize; i++) {
walk = hashTable[i];
while(walk)
{
temp = walk->nextNameObject;
walk->nextNameObject = head;
head = walk;
walk = temp;
}
}
delete[] hashTable;
hashTableSize = hashTableSize * 2 + 1;
hashTable = new SimObject *[hashTableSize];
for(i = 0; i < hashTableSize;i++)
hashTable[i] = NULL;
while(head)
{
temp = head->nextNameObject;
idx = HashPointer(head->objectName) % hashTableSize;
head->nextNameObject = hashTable[idx];
hashTable[idx] = head;
head = temp;
}
}
Mutex::unlockMutex(mutex);
}
SimObject* SimNameDictionary::find(StringTableEntry name)
{
// NULL is a valid lookup - it will always return NULL
if(!hashTable)
return NULL;
Mutex::lockMutex(mutex);
S32 idx = HashPointer(name) % hashTableSize;
SimObject *walk = hashTable[idx];
while(walk)
{
if(walk->objectName == name)
{
Mutex::unlockMutex(mutex);
return walk;
}
walk = walk->nextNameObject;
}
Mutex::unlockMutex(mutex);
return NULL;
}
void SimNameDictionary::remove(SimObject* obj)
{
if(!obj->objectName)
return;
Mutex::lockMutex(mutex);
SimObject **walk = &hashTable[HashPointer(obj->objectName) % hashTableSize];
while(*walk)
{
if(*walk == obj)
{
*walk = obj->nextNameObject;
obj->nextNameObject = (SimObject*)-1;
hashEntryCount--;
Mutex::unlockMutex(mutex);
return;
}
walk = &((*walk)->nextNameObject);
}
Mutex::unlockMutex(mutex);
}
//----------------------------------------------------------------------------
SimManagerNameDictionary::SimManagerNameDictionary()
{
hashTable = new SimObject *[DefaultTableSize];
hashTableSize = DefaultTableSize;
hashEntryCount = 0;
S32 i;
for(i = 0; i < hashTableSize; i++)
hashTable[i] = NULL;
mutex = Mutex::createMutex();
}
SimManagerNameDictionary::~SimManagerNameDictionary()
{
delete[] hashTable;
Mutex::destroyMutex(mutex);
}
void SimManagerNameDictionary::insert(SimObject* obj)
{
if(!obj->objectName)
return;
Mutex::lockMutex(mutex);
S32 idx = HashPointer(obj->objectName) % hashTableSize;
obj->nextManagerNameObject = hashTable[idx];
hashTable[idx] = obj;
hashEntryCount++;
if(hashEntryCount > hashTableSize)
{
// resize the hash table
S32 i;
SimObject *head = NULL, *walk, *temp;
for(i = 0; i < hashTableSize; i++) {
walk = hashTable[i];
while(walk)
{
temp = walk->nextManagerNameObject;
walk->nextManagerNameObject = head;
head = walk;
walk = temp;
}
}
delete[] hashTable;
hashTableSize = hashTableSize * 2 + 1;
hashTable = new SimObject *[hashTableSize];
for(i = 0; i < hashTableSize;i++)
hashTable[i] = NULL;
while(head)
{
temp = head->nextManagerNameObject;
idx = HashPointer(head->objectName) % hashTableSize;
head->nextManagerNameObject = hashTable[idx];
hashTable[idx] = head;
head = temp;
}
}
Mutex::unlockMutex(mutex);
}
SimObject* SimManagerNameDictionary::find(StringTableEntry name)
{
// NULL is a valid lookup - it will always return NULL
Mutex::lockMutex(mutex);
S32 idx = HashPointer(name) % hashTableSize;
SimObject *walk = hashTable[idx];
while(walk)
{
if(walk->objectName == name)
{
Mutex::unlockMutex(mutex);
return walk;
}
walk = walk->nextManagerNameObject;
}
Mutex::unlockMutex(mutex);
return NULL;
}
void SimManagerNameDictionary::remove(SimObject* obj)
{
if(!obj->objectName)
return;
Mutex::lockMutex(mutex);
SimObject **walk = &hashTable[HashPointer(obj->objectName) % hashTableSize];
while(*walk)
{
if(*walk == obj)
{
*walk = obj->nextManagerNameObject;
obj->nextManagerNameObject = (SimObject*)-1;
hashEntryCount--;
Mutex::unlockMutex(mutex);
return;
}
walk = &((*walk)->nextManagerNameObject);
}
Mutex::unlockMutex(mutex);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
SimIdDictionary::SimIdDictionary()
{
for(S32 i = 0; i < DefaultTableSize; i++)
table[i] = NULL;
mutex = Mutex::createMutex();
}
SimIdDictionary::~SimIdDictionary()
{
Mutex::destroyMutex(mutex);
}
void SimIdDictionary::insert(SimObject* obj)
{
Mutex::lockMutex(mutex);
S32 idx = obj->getId() & TableBitMask;
obj->nextIdObject = table[idx];
AssertFatal( obj->nextIdObject != obj, "SimIdDictionary::insert - Creating Infinite Loop linking to self!" );
table[idx] = obj;
Mutex::unlockMutex(mutex);
}
SimObject* SimIdDictionary::find(S32 id)
{
Mutex::lockMutex(mutex);
S32 idx = id & TableBitMask;
SimObject *walk = table[idx];
while(walk)
{
if(walk->getId() == U32(id))
{
Mutex::unlockMutex(mutex);
return walk;
}
walk = walk->nextIdObject;
}
Mutex::unlockMutex(mutex);
return NULL;
}
void SimIdDictionary::remove(SimObject* obj)
{
Mutex::lockMutex(mutex);
SimObject **walk = &table[obj->getId() & TableBitMask];
while(*walk && *walk != obj)
walk = &((*walk)->nextIdObject);
if(*walk)
*walk = obj->nextIdObject;
Mutex::unlockMutex(mutex);
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------

95
engine/console/simDictionary.h Executable file
View File

@ -0,0 +1,95 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _SIMDICTIONARY_H_
#define _SIMDICTIONARY_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _STRINGTABLE_H_
#include "core/stringTable.h"
#endif
#ifndef _PLATFORMMUTEX_H_
#include "platform/platformMutex.h"
#endif
class SimObject;
//----------------------------------------------------------------------------
/// Map of names to SimObjects
///
/// Provides fast lookup for name->object and
/// for fast removal of an object given object*
class SimNameDictionary
{
enum
{
DefaultTableSize = 29
};
SimObject **hashTable; // hash the pointers of the names...
S32 hashTableSize;
S32 hashEntryCount;
void *mutex;
public:
void insert(SimObject* obj);
void remove(SimObject* obj);
SimObject* find(StringTableEntry name);
SimNameDictionary();
~SimNameDictionary();
};
class SimManagerNameDictionary
{
enum
{
DefaultTableSize = 29
};
SimObject **hashTable; // hash the pointers of the names...
S32 hashTableSize;
S32 hashEntryCount;
void *mutex;
public:
void insert(SimObject* obj);
void remove(SimObject* obj);
SimObject* find(StringTableEntry name);
SimManagerNameDictionary();
~SimManagerNameDictionary();
};
//----------------------------------------------------------------------------
/// Map of ID's to SimObjects.
///
/// Provides fast lookup for ID->object and
/// for fast removal of an object given object*
class SimIdDictionary
{
enum
{
DefaultTableSize = 4096,
TableBitMask = 4095
};
SimObject *table[DefaultTableSize];
void *mutex;
public:
void insert(SimObject* obj);
void remove(SimObject* obj);
SimObject* find(S32 id);
SimIdDictionary();
~SimIdDictionary();
};
#endif //_SIMDICTIONARY_H_

537
engine/console/simManager.cc Executable file
View File

@ -0,0 +1,537 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "platform/platformMutex.h"
#include "console/simBase.h"
#include "core/stringTable.h"
#include "console/console.h"
#include "core/fileStream.h"
#include "core/resManager.h"
#include "core/fileObject.h"
#include "console/consoleInternal.h"
#include "core/idGenerator.h"
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// We comment out the implementation of the Con namespace when doxygenizing because
// otherwise Doxygen decides to ignore our docs in console.h
#ifndef DOXYGENIZING
namespace Sim
{
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// event queue variables:
SimTime gCurrentTime;
SimTime gTargetTime;
void *gEventQueueMutex;
SimEvent *gEventQueue;
U32 gEventSequence;
//---------------------------------------------------------------------------
// event queue init/shutdown
static void initEventQueue()
{
gCurrentTime = 0;
gTargetTime = 0;
gEventSequence = 1;
gEventQueue = NULL;
gEventQueueMutex = Mutex::createMutex();
}
static void shutdownEventQueue()
{
// Delete all pending events
Mutex::lockMutex(gEventQueueMutex);
SimEvent *walk = gEventQueue;
while(walk)
{
SimEvent *temp = walk->nextEvent;
delete walk;
walk = temp;
}
Mutex::unlockMutex(gEventQueueMutex);
Mutex::destroyMutex(gEventQueueMutex);
}
//---------------------------------------------------------------------------
// event post
U32 postEvent(SimObject *destObject, SimEvent* event,U32 time)
{
AssertFatal(time >= getCurrentTime(),
"Sim::postEvent: Cannot go back in time. (flux capacitor unavailable -- BJG)");
AssertFatal(destObject, "Destination object for event doesn't exist.");
Mutex::lockMutex(gEventQueueMutex);
event->time = time;
event->destObject = destObject;
if(!destObject)
{
delete event;
Mutex::unlockMutex(gEventQueueMutex);
return InvalidEventId;
}
event->sequenceCount = gEventSequence++;
SimEvent **walk = &gEventQueue;
SimEvent *current;
while((current = *walk) != NULL && (current->time < event->time))
walk = &(current->nextEvent);
// [tom, 6/24/2005] This ensures that SimEvents are dispatched in the same order that they are posted.
// This is needed to ensure Con::threadSafeExecute() executes script code in the correct order.
while((current = *walk) != NULL && (current->time == event->time))
walk = &(current->nextEvent);
event->nextEvent = current;
*walk = event;
U32 seqCount = event->sequenceCount;
Mutex::unlockMutex(gEventQueueMutex);
return seqCount;
}
//---------------------------------------------------------------------------
// event cancellation
void cancelEvent(U32 eventSequence)
{
Mutex::lockMutex(gEventQueueMutex);
SimEvent **walk = &gEventQueue;
SimEvent *current;
while((current = *walk) != NULL)
{
if(current->sequenceCount == eventSequence)
{
*walk = current->nextEvent;
delete current;
Mutex::unlockMutex(gEventQueueMutex);
return;
}
else
walk = &(current->nextEvent);
}
Mutex::unlockMutex(gEventQueueMutex);
}
static void cancelPendingEvents(SimObject *obj)
{
Mutex::lockMutex(gEventQueueMutex);
SimEvent **walk = &gEventQueue;
SimEvent *current;
while((current = *walk) != NULL)
{
if(current->destObject == obj)
{
*walk = current->nextEvent;
delete current;
}
else
walk = &(current->nextEvent);
}
Mutex::unlockMutex(gEventQueueMutex);
}
//---------------------------------------------------------------------------
// event pending test
bool isEventPending(U32 eventSequence)
{
Mutex::lockMutex(gEventQueueMutex);
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
if(walk->sequenceCount == eventSequence)
{
Mutex::unlockMutex(gEventQueueMutex);
return true;
}
Mutex::unlockMutex(gEventQueueMutex);
return false;
}
U32 getEventTimeLeft(U32 eventSequence)
{
Mutex::lockMutex(gEventQueueMutex);
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
if(walk->sequenceCount == eventSequence)
{
SimTime t = walk->time - getCurrentTime();
Mutex::unlockMutex(gEventQueueMutex);
return t;
}
Mutex::unlockMutex(gEventQueueMutex);
return 0;
}
U32 getScheduleDuration(U32 eventSequence)
{
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
if(walk->sequenceCount == eventSequence)
return (walk->time-walk->startTime);
return 0;
}
U32 getTimeSinceStart(U32 eventSequence)
{
for(SimEvent *walk = gEventQueue; walk; walk = walk->nextEvent)
if(walk->sequenceCount == eventSequence)
return (getCurrentTime()-walk->startTime);
return 0;
}
//---------------------------------------------------------------------------
// event timing
void advanceToTime(SimTime targetTime)
{
AssertFatal(targetTime >= getCurrentTime(), "EventQueue::process: cannot advance to time in the past.");
Mutex::lockMutex(gEventQueueMutex);
gTargetTime = targetTime;
while(gEventQueue && gEventQueue->time <= targetTime)
{
SimEvent *event = gEventQueue;
gEventQueue = gEventQueue->nextEvent;
AssertFatal(event->time >= gCurrentTime,
"SimEventQueue::pop: Cannot go back in time (flux capacitor not installed - BJG).");
gCurrentTime = event->time;
SimObject *obj = event->destObject;
if(!obj->isDeleted())
event->process(obj);
delete event;
}
gCurrentTime = targetTime;
Mutex::unlockMutex(gEventQueueMutex);
}
void advanceTime(SimTime delta)
{
advanceToTime(getCurrentTime() + delta);
}
U32 getCurrentTime()
{
Mutex::lockMutex(gEventQueueMutex);
SimTime t = gCurrentTime;
Mutex::unlockMutex(gEventQueueMutex);
return t;
}
U32 getTargetTime()
{
return gTargetTime;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
SimGroup *gRootGroup = NULL;
SimManagerNameDictionary *gNameDictionary;
SimIdDictionary *gIdDictionary;
U32 gNextObjectId;
static void initRoot()
{
gIdDictionary = new SimIdDictionary;
gNameDictionary = new SimManagerNameDictionary;
gRootGroup = new SimGroup();
gRootGroup->setId(RootGroupId);
gRootGroup->assignName("RootGroup");
gRootGroup->registerObject();
gNextObjectId = DynamicObjectIdFirst;
}
static void shutdownRoot()
{
gRootGroup->deleteObject();
delete gNameDictionary;
delete gIdDictionary;
}
//---------------------------------------------------------------------------
SimObject* findObject(const char* name)
{
// Play nice with bad code - JDD
if( !name )
return NULL;
SimObject *obj;
char c = *name;
if(c == '/')
return gRootGroup->findObject(name + 1 );
if(c >= '0' && c <= '9')
{
// it's an id group
const char* temp = name + 1;
for(;;)
{
c = *temp++;
if(!c)
return findObject(dAtoi(name));
else if(c == '/')
{
obj = findObject(dAtoi(name));
if(!obj)
return NULL;
return obj->findObject(temp);
}
}
}
S32 len;
for(len = 0; name[len] != 0 && name[len] != '/'; len++)
;
StringTableEntry stName = StringTable->lookupn(name, len);
if(!stName)
return NULL;
obj = gNameDictionary->find(stName);
if(!name[len])
return obj;
if(!obj)
return NULL;
return obj->findObject(name + len + 1);
}
SimObject* findObject(SimObjectId id)
{
return gIdDictionary->find(id);
}
SimGroup *getRootGroup()
{
return gRootGroup;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#define InstantiateNamedSet(set) g##set = new SimSet; g##set->registerObject(#set); gRootGroup->addObject(g##set)
#define InstantiateNamedGroup(set) g##set = new SimGroup; g##set->registerObject(#set); gRootGroup->addObject(g##set)
SimDataBlockGroup *gDataBlockGroup;
SimDataBlockGroup *getDataBlockGroup()
{
return gDataBlockGroup;
}
void init()
{
initEventQueue();
initRoot();
InstantiateNamedSet(ActiveActionMapSet);
InstantiateNamedSet(GhostAlwaysSet);
InstantiateNamedSet(LightSet);
InstantiateNamedSet(WayPointSet);
InstantiateNamedSet(fxReplicatorSet);
InstantiateNamedSet(fxFoliageSet);
InstantiateNamedGroup(ActionMapGroup);
InstantiateNamedGroup(ClientGroup);
InstantiateNamedGroup(GuiGroup);
InstantiateNamedGroup(ScriptClassGroup);
InstantiateNamedGroup(GuiDataGroup);
InstantiateNamedGroup(TCPGroup);
InstantiateNamedGroup(ClientConnectionGroup);
InstantiateNamedGroup(ChunkFileGroup);
gDataBlockGroup = new SimDataBlockGroup();
gDataBlockGroup->registerObject("DataBlockGroup");
gRootGroup->addObject(gDataBlockGroup);
InstantiateNamedSet(sgMissionLightingFilterSet);
}
void shutdown()
{
shutdownRoot();
shutdownEventQueue();
}
}
#endif // DOXYGENIZING.
SimDataBlockGroup::SimDataBlockGroup()
{
mLastModifiedKey = 0;
}
S32 QSORT_CALLBACK SimDataBlockGroup::compareModifiedKey(const void* a,const void* b)
{
return (reinterpret_cast<const SimDataBlock* >(a))->getModifiedKey() -
(reinterpret_cast<const SimDataBlock*>(b))->getModifiedKey();
}
void SimDataBlockGroup::sort()
{
if(mLastModifiedKey != SimDataBlock::getNextModifiedKey())
{
mLastModifiedKey = SimDataBlock::getNextModifiedKey();
dQsort(objectList.address(),objectList.size(),sizeof(SimObject *),compareModifiedKey);
}
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
bool SimObject::registerObject()
{
AssertFatal( !mFlags.test( Added ), "reigsterObject - Object already registered!");
mFlags.clear(Deleted | Removed);
if(!mId)
mId = Sim::gNextObjectId++;
AssertFatal(Sim::gIdDictionary && Sim::gNameDictionary,
"SimObject::registerObject - tried to register an object before Sim::init()!");
Sim::gIdDictionary->insert(this);
Sim::gNameDictionary->insert(this);
// Notify object
bool ret = onAdd();
if(!ret)
unregisterObject();
AssertFatal(!ret || isProperlyAdded(), "Object did not call SimObject::onAdd()");
return ret;
}
//---------------------------------------------------------------------------
void SimObject::unregisterObject()
{
mFlags.set(Removed);
// Notify object first
onRemove();
// Clear out any pending notifications before
// we call our own, just in case they delete
// something that we have referenced.
clearAllNotifications();
// Notify all objects that are waiting for delete
// messages
if (getGroup())
getGroup()->removeObject(this);
processDeleteNotifies();
// Do removals from the Sim.
Sim::gNameDictionary->remove(this);
Sim::gIdDictionary->remove(this);
Sim::cancelPendingEvents(this);
}
//---------------------------------------------------------------------------
void SimObject::deleteObject()
{
AssertFatal(mFlags.test(Added),
"SimObject::deleteObject: Object not registered.");
AssertFatal(!isDeleted(),"SimManager::deleteObject: "
"Object has already been deleted");
AssertFatal(!isRemoved(),"SimManager::deleteObject: "
"Object in the process of being removed");
mFlags.set(Deleted);
unregisterObject();
delete this;
}
//---------------------------------------------------------------------------
void SimObject::setId(SimObjectId newId)
{
if(!mFlags.test(Added))
{
mId = newId;
return;
}
// get this object out of the id dictionary if it's in it
Sim::gIdDictionary->remove(this);
// Free current Id.
// Assign new one.
mId = newId ? newId : Sim::gNextObjectId++;
Sim::gIdDictionary->insert(this);
}
void SimObject::assignName(const char *name)
{
StringTableEntry newName = NULL;
if(name[0])
newName = StringTable->insert(name);
if(mGroup)
mGroup->nameDictionary.remove(this);
if(mFlags.test(Added))
Sim::gNameDictionary->remove(this);
objectName = newName;
if(mGroup)
mGroup->nameDictionary.insert(this);
if(mFlags.test(Added))
Sim::gNameDictionary->insert(this);
}
//---------------------------------------------------------------------------
bool SimObject::registerObject(U32 id)
{
setId(id);
return registerObject();
}
bool SimObject::registerObject(const char *name)
{
assignName(name);
return registerObject();
}
bool SimObject::registerObject(const char *name, U32 id)
{
setId(id);
assignName(name);
return registerObject();
}

25
engine/console/stringStack.cc Executable file
View File

@ -0,0 +1,25 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "console/stringStack.h"
void StringStack::getArgcArgv(StringTableEntry name, U32 *argc, const char ***in_argv)
{
U32 startStack = mFrameOffsets[--mNumFrames] + 1;
U32 argCount = getMin(mStartStackSize - startStack, (U32)MaxArgs);
*in_argv = mArgV;
mArgV[0] = name;
for(U32 i = 0; i < argCount; i++)
mArgV[i+1] = mBuffer + mStartOffsets[startStack + i];
argCount++;
mStartStackSize = startStack - 1;
*argc = argCount;
mStart = mStartOffsets[mStartStackSize];
mLen = 0;
}

247
engine/console/stringStack.h Executable file
View File

@ -0,0 +1,247 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _STRINGSTACK_H_
#define _STRINGSTACK_H_
#include "platform/platform.h"
#include "console/console.h"
#include "console/compiler.h"
#include "core/stringTable.h"
/// Core stack for interpreter operations.
///
/// This class provides some powerful semantics for working with strings, and is
/// used heavily by the console interpreter.
struct StringStack
{
enum {
MaxStackDepth = 1024,
MaxArgs = 20,
ReturnBufferSpace = 512
};
char *mBuffer;
U32 mBufferSize;
const char *mArgV[MaxArgs];
U32 mFrameOffsets[MaxStackDepth];
U32 mStartOffsets[MaxStackDepth];
U32 mNumFrames;
U32 mArgc;
U32 mStart;
U32 mLen;
U32 mStartStackSize;
U32 mFunctionOffset;
U32 mArgBufferSize;
char *mArgBuffer;
void validateBufferSize(U32 size)
{
if(size > mBufferSize)
{
mBufferSize = size + 2048;
mBuffer = (char *) dRealloc(mBuffer, mBufferSize);
}
}
void validateArgBufferSize(U32 size)
{
if(size > mArgBufferSize)
{
mArgBufferSize = size + 2048;
mArgBuffer = (char *) dRealloc(mArgBuffer, mArgBufferSize);
}
}
StringStack()
{
mBufferSize = 0;
mBuffer = NULL;
mNumFrames = 0;
mStart = 0;
mLen = 0;
mStartStackSize = 0;
mFunctionOffset = 0;
validateBufferSize(8192);
validateArgBufferSize(2048);
}
/// Set the top of the stack to be an integer value.
void setIntValue(U32 i)
{
validateBufferSize(mStart + 32);
dSprintf(mBuffer + mStart, 32, "%d", i);
mLen = dStrlen(mBuffer + mStart);
}
/// Set the top of the stack to be a float value.
void setFloatValue(F64 v)
{
validateBufferSize(mStart + 32);
dSprintf(mBuffer + mStart, 32, "%g", v);
mLen = dStrlen(mBuffer + mStart);
}
/// Return a temporary buffer we can use to return data.
///
/// @note This clobbers anything in our buffers!
char *getReturnBuffer(U32 size)
{
if(size > ReturnBufferSpace)
{
validateArgBufferSize(size);
return mArgBuffer;
}
else
{
validateBufferSize(mStart + size);
return mBuffer + mStart;
}
}
/// Return a buffer we can use for arguments.
///
/// This updates the function offset.
char *getArgBuffer(U32 size)
{
validateBufferSize(mStart + mFunctionOffset + size);
char *ret = mBuffer + mStart + mFunctionOffset;
mFunctionOffset += size;
return ret;
}
/// Clear the function offset.
void clearFunctionOffset()
{
mFunctionOffset = 0;
}
/// Set a string value on the top of the stack.
void setStringValue(const char *s)
{
if(!s)
{
mLen = 0;
mBuffer[mStart] = 0;
return;
}
mLen = dStrlen(s);
validateBufferSize(mStart + mLen + 2);
dStrcpy(mBuffer + mStart, s);
}
/// Get the top of the stack, as a StringTableEntry.
///
/// @note Don't free this memory!
inline StringTableEntry getSTValue()
{
return StringTable->insert(mBuffer + mStart);
}
/// Get an integer representation of the top of the stack.
inline U32 getIntValue()
{
return dAtoi(mBuffer + mStart);
}
/// Get a float representation of the top of the stack.
inline F64 getFloatValue()
{
return dAtof(mBuffer + mStart);
}
/// Get a string representation of the top of the stack.
///
/// @note This returns a pointer to the actual top of the stack, be careful!
inline const char *getStringValue()
{
return mBuffer + mStart;
}
/// Advance the start stack, placing a zero length string on the top.
///
/// @note You should use StringStack::push, not this, if you want to
/// properly push the stack.
void advance()
{
mStartOffsets[mStartStackSize++] = mStart;
mStart += mLen;
mLen = 0;
}
/// Advance the start stack, placing a single character, null-terminated strong
/// on the top.
///
/// @note You should use StringStack::push, not this, if you want to
/// properly push the stack.
void advanceChar(char c)
{
mStartOffsets[mStartStackSize++] = mStart;
mStart += mLen;
mBuffer[mStart] = c;
mBuffer[mStart+1] = 0;
mStart += 1;
mLen = 0;
}
/// Push the stack, placing a zero-length string on the top.
void push()
{
advanceChar(0);
}
inline void setLen(U32 newlen)
{
mLen = newlen;
}
/// Pop the start stack.
void rewind()
{
mStart = mStartOffsets[--mStartStackSize];
mLen = dStrlen(mBuffer + mStart);
}
// Terminate the current string, and pop the start stack.
void rewindTerminate()
{
mBuffer[mStart] = 0;
mStart = mStartOffsets[--mStartStackSize];
mLen = dStrlen(mBuffer + mStart);
}
/// Compare 1st and 2nd items on stack, consuming them in the process,
/// and returning true if they matched, false if they didn't.
U32 compare()
{
// Figure out the 1st and 2nd item offsets.
U32 oldStart = mStart;
mStart = mStartOffsets[--mStartStackSize];
// Compare current and previous strings.
U32 ret = !dStricmp(mBuffer + mStart, mBuffer + oldStart);
// Put an empty string on the top of the stack.
mLen = 0;
mBuffer[mStart] = 0;
return ret;
}
void pushFrame()
{
mFrameOffsets[mNumFrames++] = mStartStackSize;
mStartOffsets[mStartStackSize++] = mStart;
mStart += ReturnBufferSpace;
validateBufferSize(0);
}
/// Get the arguments for a function call from the stack.
void getArgcArgv(StringTableEntry name, U32 *argc, const char ***in_argv);
};
#endif

274
engine/console/telnetConsole.cc Executable file
View File

@ -0,0 +1,274 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "platform/event.h"
#include "console/telnetConsole.h"
#include "platform/gameInterface.h"
TelnetConsole *TelConsole = NULL;
void TelnetConsole::create()
{
TelConsole = new TelnetConsole;
}
void TelnetConsole::destroy()
{
delete TelConsole;
TelConsole = NULL;
}
ConsoleFunction( telnetSetParameters, void, 4, 5, "(int port, string consolePass, string listenPass)"
"Initialize and open the telnet console.\n\n"
"@param port Port to listen on for console connections (0 will shut down listening).\n"
"@param consolePass Password for read/write access to console.\n"
"@param listenPass Password for read access to console."
"@param remoteEcho [optional] Enable echoing back to the client, off by default.")
{
if (TelConsole)
{
bool remoteEcho = false;
if( argc == 5 )
remoteEcho = dAtob( argv[4] );
TelConsole->setTelnetParameters(dAtoi(argv[1]), argv[2], argv[3], remoteEcho);
}
}
static void telnetCallback(ConsoleLogEntry::Level level, const char *consoleLine)
{
level;
if (TelConsole)
TelConsole->processConsoleLine(consoleLine);
}
TelnetConsole::TelnetConsole()
{
Con::addConsumer(telnetCallback);
mAcceptSocket = InvalidSocket;
mAcceptPort = -1;
mClientList = NULL;
mRemoteEchoEnabled = false;
}
TelnetConsole::~TelnetConsole()
{
Con::removeConsumer(telnetCallback);
if(mAcceptSocket != InvalidSocket)
Net::closeSocket(mAcceptSocket);
TelnetClient *walk = mClientList, *temp;
while(walk)
{
temp = walk->nextClient;
if(walk->socket != InvalidSocket)
Net::closeSocket(walk->socket);
delete walk;
walk = temp;
}
}
void TelnetConsole::setTelnetParameters(S32 port, const char *telnetPassword, const char *listenPassword, bool remoteEcho)
{
if(port == mAcceptPort)
return;
mRemoteEchoEnabled = remoteEcho;
if(mAcceptSocket != InvalidSocket)
{
Net::closeSocket(mAcceptSocket);
mAcceptSocket = InvalidSocket;
}
mAcceptPort = port;
if(mAcceptPort != -1 && mAcceptPort != 0)
{
mAcceptSocket = Net::openSocket();
Net::bind(mAcceptSocket, mAcceptPort);
Net::listen(mAcceptSocket, 4);
Net::setBlocking(mAcceptSocket, false);
}
dStrncpy(mTelnetPassword, telnetPassword, PasswordMaxLength);
dStrncpy(mListenPassword, listenPassword, PasswordMaxLength);
}
void TelnetConsole::processConsoleLine(const char *consoleLine)
{
if (mClientList==NULL) return; // just escape early. don't even do another step...
// ok, spew this line out to all our subscribers...
S32 len = dStrlen(consoleLine)+1;
for(TelnetClient *walk = mClientList; walk; walk = walk->nextClient)
{
if(walk->state == FullAccessConnected || walk->state == ReadOnlyConnected)
{
Net::send(walk->socket, (const unsigned char*)consoleLine, len);
Net::send(walk->socket, (const unsigned char*)"\r\n", 2);
}
}
}
void TelnetConsole::process()
{
NetAddress address;
if(mAcceptSocket != InvalidSocket)
{
// ok, see if we have any new connections:
NetSocket newConnection;
newConnection = Net::accept(mAcceptSocket, &address);
if(newConnection != InvalidSocket)
{
Con::printf ("Telnet connection from %i.%i.%i.%i",
address.netNum[0], address.netNum[1], address.netNum[2], address.netNum[3]);
TelnetClient *cl = new TelnetClient;
cl->socket = newConnection;
cl->curPos = 0;
cl->state = PasswordTryOne;
Net::setBlocking(newConnection, false);
char *connectMessage = "Torque Telnet Remote Console\r\n\r\nEnter Password:";
Net::send(cl->socket, (const unsigned char*)connectMessage, dStrlen(connectMessage)+1);
cl->nextClient = mClientList;
mClientList = cl;
}
}
char recvBuf[256];
char reply[1024];
// see if we have any input to process...
for(TelnetClient *client = mClientList; client; client = client->nextClient)
{
S32 numBytes;
Net::Error err = Net::recv(client->socket, (unsigned char*)recvBuf, sizeof(recvBuf), &numBytes);
if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0)
{
Net::closeSocket(client->socket);
client->socket = InvalidSocket;
continue;
}
S32 replyPos = 0;
for(S32 i = 0; i < numBytes;i++)
{
if(recvBuf[i] == '\r')
continue;
// execute the current command
if(recvBuf[i] == '\n')
{
reply[replyPos++] = '\r';
reply[replyPos++] = '\n';
client->curLine[client->curPos] = 0;
client->curPos = 0;
if(client->state == FullAccessConnected)
{
Net::send(client->socket, (const unsigned char*)reply, replyPos);
replyPos = 0;
dStrcpy(mPostEvent.data, client->curLine);
mPostEvent.size = ConsoleEventHeaderSize + dStrlen(client->curLine) + 1;
Game->postEvent(mPostEvent);
// note - send prompt next
const char *prompt = Con::getVariable("Con::Prompt");
Net::send(client->socket, (const unsigned char*)prompt, dStrlen(prompt));
}
else if(client->state == ReadOnlyConnected)
{
Net::send(client->socket, (const unsigned char*)reply, replyPos);
replyPos = 0;
}
else
{
client->state++;
if(!dStrncmp(client->curLine, mTelnetPassword, PasswordMaxLength))
{
Net::send(client->socket, (const unsigned char*)reply, replyPos);
replyPos = 0;
// send prompt
const char *prompt = Con::getVariable("Con::Prompt");
Net::send(client->socket, (const unsigned char*)prompt, dStrlen(prompt));
client->state = FullAccessConnected;
}
else if(!dStrncmp(client->curLine, mListenPassword, PasswordMaxLength))
{
Net::send(client->socket, (const unsigned char*)reply, replyPos);
replyPos = 0;
// send prompt
const char *listenConnected = "Connected.\r\n";
Net::send(client->socket, (const unsigned char*)listenConnected, dStrlen(listenConnected));
client->state = ReadOnlyConnected;
}
else
{
const char *sendStr;
if(client->state == DisconnectThisDude)
sendStr = "Too many tries... cya.";
else
sendStr = "Nope... try agian.\r\nEnter Password:";
Net::send(client->socket, (const unsigned char*)sendStr, dStrlen(sendStr));
if(client->state == DisconnectThisDude)
{
Net::closeSocket(client->socket);
client->socket = InvalidSocket;
}
}
}
}
else if(recvBuf[i] == '\b')
{
// pull the old backspace manuever...
if(client->curPos > 0)
{
client->curPos--;
if(client->state == FullAccessConnected)
{
reply[replyPos++] = '\b';
reply[replyPos++] = ' ';
reply[replyPos++] = '\b';
}
}
}
else if(client->curPos < Con::MaxLineLength-1)
{
client->curLine[client->curPos++] = recvBuf[i];
// don't echo password chars...
if(client->state == FullAccessConnected)
reply[replyPos++] = recvBuf[i];
}
}
// Echo the character back to the user, unless the remote echo
// is disabled (by default)
if(replyPos && mRemoteEchoEnabled)
Net::send(client->socket, (const unsigned char*)reply, replyPos);
}
TelnetClient ** walk = &mClientList;
TelnetClient *cl;
while((cl = *walk) != NULL)
{
if(cl->socket == InvalidSocket)
{
*walk = cl->nextClient;
delete cl;
}
else
walk = &cl->nextClient;
}
}

104
engine/console/telnetConsole.h Executable file
View File

@ -0,0 +1,104 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _TELNETCONSOLE_H_
#define _TELNETCONSOLE_H_
#ifndef _CONSOLE_H_
#include "console/console.h"
#endif
/// Telnet admin console.
///
/// Torque supports remote access to its console. This is most useful when
/// running a dedicated server, as you can remotely administer the game
/// (for instance, kicking people). In the context of a MMORPG, this sort of
/// functionality would be useful for managing a server.
///
/// There are a number of products for Tribes2 which allow remote administration
/// via a nice GUI.
///
/// @section telnetconsole_use Using the Telnet Console
///
/// The TelnetConsole is designed to be used globally, so you don't instantiate
/// it like a normal class. Instead, you allow it to manage itself:
///
/// @code
/// // How to initialize the TelnetConsole.
/// TelnetConsole::create();
///
/// // How to shut down the TelnetConsole.
/// TelnetConsole::destroy();
/// @endcode
///
///
class TelnetConsole
{
NetSocket mAcceptSocket;
S32 mAcceptPort;
enum {
PasswordMaxLength = 32 ///< Maximum length of the telnet and listen passwords.
};
bool mRemoteEchoEnabled;
char mTelnetPassword[PasswordMaxLength+1];
char mListenPassword[PasswordMaxLength+1];
ConsoleEvent mPostEvent;
/// State of a TelnetClient.
enum State
{
PasswordTryOne, ///< Allow three password attempts.
PasswordTryTwo,
PasswordTryThree,
DisconnectThisDude, ///< If they've failed all three, disconnect them.
FullAccessConnected, ///< They presented the telnetPassword, they get full access.
ReadOnlyConnected ///< They presented the listenPassword, they get read only access.
};
/// Represents a connection to the telnet console.
///
/// This is also a linked list.
struct TelnetClient
{
NetSocket socket;
char curLine[Con::MaxLineLength];
S32 curPos;
S32 state; ///< State of the client.
/// @see TelnetConsole::State
TelnetClient *nextClient;
};
TelnetClient *mClientList;
TelnetConsole();
~TelnetConsole();
public:
static void create(); ///< Initialize the telnet console.
static void destroy(); ///< Shut down the telnet console.
void process(); ///< Called by the main loop to let the console process commands
/// and connections.
/// Configure the parameter for the telnet console.
///
/// @param port Port on which to listen for connections.
/// @param telnetPassword Password for full access to the console.
/// @param listenPassword Password for read-only access to the console.
/// @param remoteEcho Enable/disable echoing input back to the client
void setTelnetParameters(S32 port, const char *telnetPassword, const char *listenPassword, bool remoteEcho = false);
/// Callback to handle a line from the console.
///
/// @note This is used internally by the class; you
/// shouldn't need to call it.
///
/// @see Con::addConsumer()
void processConsoleLine(const char *line);
};
extern TelnetConsole *TelConsole;
#endif

876
engine/console/telnetDebugger.cc Executable file
View File

@ -0,0 +1,876 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/console.h"
#include "console/telnetDebugger.h"
#include "platform/event.h"
#include "core/stringTable.h"
#include "console/consoleInternal.h"
#include "console/ast.h"
#include "console/compiler.h"
#include "platform/gameInterface.h"
//
// Enhanced TelnetDebugger for Torsion
// http://www.sickheadgames.com/torsion
//
//
// Debugger commands:
//
// CEVAL console line - evaluate the console line
// output: none
//
// BRKVARSET varName passct expr - NOT IMPLEMENTED!
// output: none
//
// BRKVARCLR varName - NOT IMPLEMENTED!
// output: none
//
// BRKSET file line clear passct expr - set a breakpoint on the file,line
// it must pass passct times for it to break and if clear is true, it
// clears when hit
// output:
//
// BRKNEXT - stop execution at the next breakable line.
// output: none
//
// BRKCLR file line - clear a breakpoint on the file,line
// output: none
//
// BRKCLRALL - clear all breakpoints
// output: none
//
// CONTINUE - continue execution
// output: RUNNING
//
// STEPIN - run until next statement
// output: RUNNING
//
// STEPOVER - run until next break <= current frame
// output: RUNNING
//
// STEPOUT - run until next break <= current frame - 1
// output: RUNNING
//
// EVAL tag frame expr - evaluate the expr in the console, on the frame'th stack frame
// output: EVALOUT tag exprResult
//
// FILELIST - list script files loaded
// output: FILELISTOUT file1 file2 file3 file4 ...
//
// BREAKLIST file - get a list of breakpoint-able lines in the file
// output: BREAKLISTOUT file skipBreakPairs skiplinecount breaklinecount skiplinecount breaklinecount ...
//
//
// Other output:
//
// BREAK file1 line1 function1 file2 line2 function2 ... - Sent when the debugger hits a
// breakpoint. It lists out one file/line/function triplet for each stack level.
// The first one is the top of the stack.
//
// COUT console-output - echo of console output from engine
//
// BRKMOV file line newline - sent when a breakpoint is moved to a breakable line.
//
// BRKCLR file line - sent when a breakpoint cannot be moved to a breakable line on the client.
//
ConsoleFunction( dbgSetParameters, void, 3, 4, "(int port, string password, bool waitForClient)"
"Open a debug server port on the specified port, requiring the specified password, "
"and optionally waiting for the debug client to connect.")
{
if (TelDebugger)
TelDebugger->setDebugParameters(dAtoi(argv[1]), argv[2], argc > 3 ? dAtob(argv[3]) : false );
}
ConsoleFunction( dbgIsConnected, bool, 1, 1, "()"
"Returns true if a script debugging client is connected else return false.")
{
return TelDebugger && TelDebugger->isConnected();
}
ConsoleFunction( dbgDisconnect, void, 1, 1, "()"
"Forcibly disconnects any attached script debugging client.")
{
if (TelDebugger)
TelDebugger->disconnect();
}
static void debuggerConsumer(ConsoleLogEntry::Level level, const char *line)
{
level;
if (TelDebugger)
TelDebugger->processConsoleLine(line);
}
TelnetDebugger::TelnetDebugger()
{
Con::addConsumer(debuggerConsumer);
mAcceptPort = -1;
mAcceptSocket = InvalidSocket;
mDebugSocket = InvalidSocket;
mState = NotConnected;
mCurPos = 0;
mBreakpoints = NULL;
mBreakOnNextStatement = false;
mStackPopBreakIndex = -1;
mProgramPaused = false;
mWaitForClient = false;
// Add the version number in a global so that
// scripts can detect the presence of the
// "enhanced" debugger features.
Con::evaluatef( "$dbgVersion = %d;", Version );
}
TelnetDebugger::Breakpoint **TelnetDebugger::findBreakpoint(StringTableEntry fileName, S32 lineNumber)
{
Breakpoint **walk = &mBreakpoints;
Breakpoint *cur;
while((cur = *walk) != NULL)
{
if(cur->fileName == fileName && cur->lineNumber == U32(lineNumber))
return walk;
walk = &cur->next;
}
return NULL;
}
TelnetDebugger::~TelnetDebugger()
{
Con::removeConsumer(debuggerConsumer);
if(mAcceptSocket != InvalidSocket)
Net::closeSocket(mAcceptSocket);
if(mDebugSocket != InvalidSocket)
Net::closeSocket(mDebugSocket);
}
TelnetDebugger *TelDebugger = NULL;
void TelnetDebugger::create()
{
TelDebugger = new TelnetDebugger;
}
void TelnetDebugger::destroy()
{
delete TelDebugger;
TelDebugger = NULL;
}
void TelnetDebugger::send(const char *str)
{
Net::send(mDebugSocket, (const unsigned char*)str, dStrlen(str));
}
void TelnetDebugger::disconnect()
{
if ( mDebugSocket != InvalidSocket )
{
Net::closeSocket(mDebugSocket);
mDebugSocket = InvalidSocket;
}
removeAllBreakpoints();
mState = NotConnected;
mProgramPaused = false;
}
void TelnetDebugger::setDebugParameters(S32 port, const char *password, bool waitForClient)
{
// Don't bail if same port... we might just be wanting to change
// the password.
// if(port == mAcceptPort)
// return;
if(mAcceptSocket != InvalidSocket)
{
Net::closeSocket(mAcceptSocket);
mAcceptSocket = InvalidSocket;
}
mAcceptPort = port;
if(mAcceptPort != -1 && mAcceptPort != 0)
{
mAcceptSocket = Net::openSocket();
Net::bind(mAcceptSocket, mAcceptPort);
Net::listen(mAcceptSocket, 4);
Net::setBlocking(mAcceptSocket, false);
}
dStrncpy(mDebuggerPassword, password, PasswordMaxLength);
mWaitForClient = waitForClient;
if ( !mWaitForClient )
return;
// Wait for the client to fully connect.
while ( mState != Connected )
{
Platform::sleep(10);
process();
}
}
void TelnetDebugger::processConsoleLine(const char *consoleLine)
{
if(mState != NotConnected)
{
send("COUT ");
send(consoleLine);
send("\r\n");
}
}
void TelnetDebugger::process()
{
NetAddress address;
if(mAcceptSocket != InvalidSocket)
{
// ok, see if we have any new connections:
NetSocket newConnection;
newConnection = Net::accept(mAcceptSocket, &address);
if(newConnection != InvalidSocket && mDebugSocket == InvalidSocket)
{
Con::printf ("Debugger connection from %i.%i.%i.%i",
address.netNum[0], address.netNum[1], address.netNum[2], address.netNum[3]);
mState = PasswordTry;
mDebugSocket = newConnection;
Net::setBlocking(newConnection, false);
}
else if(newConnection != InvalidSocket)
Net::closeSocket(newConnection);
}
// see if we have any input to process...
if(mDebugSocket == InvalidSocket)
return;
checkDebugRecv();
if(mDebugSocket == InvalidSocket)
removeAllBreakpoints();
}
void TelnetDebugger::checkDebugRecv()
{
for (;;)
{
// Process all the complete commands in the buffer.
while ( mCurPos > 0 )
{
// Remove leading whitespace.
while ( mCurPos > 0 && ( mLineBuffer[0] == 0 || mLineBuffer[0] == '\r' || mLineBuffer[0] == '\n' ) )
{
mCurPos--;
dMemmove(mLineBuffer, mLineBuffer + 1, mCurPos);
}
// Look for a complete command.
bool gotCmd = false;
for(S32 i = 0; i < mCurPos; i++)
{
if( mLineBuffer[i] == 0 )
mLineBuffer[i] = '_';
else if ( mLineBuffer[i] == '\r' || mLineBuffer[i] == '\n' )
{
// Send this command to be processed.
mLineBuffer[i] = '\n';
processLineBuffer(i+1);
// Remove the command from the buffer.
mCurPos -= i + 1;
dMemmove(mLineBuffer, mLineBuffer + i + 1, mCurPos);
gotCmd = true;
break;
}
}
// If we didn't find a command in this pass
// then we have an incomplete buffer.
if ( !gotCmd )
break;
}
// found no <CR> or <LF>
if(mCurPos == MaxCommandSize) // this shouldn't happen
{
disconnect();
return;
}
S32 numBytes;
Net::Error err = Net::recv(mDebugSocket, (unsigned char*)(mLineBuffer + mCurPos), MaxCommandSize - mCurPos, &numBytes);
if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0)
{
disconnect();
return;
}
if(err == Net::WouldBlock)
return;
mCurPos += numBytes;
}
}
void TelnetDebugger::executionStopped(CodeBlock *code, U32 lineNumber)
{
if(mProgramPaused)
return;
if(mBreakOnNextStatement)
{
setBreakOnNextStatement( false );
breakProcess();
return;
}
Breakpoint **bp = findBreakpoint(code->name, lineNumber);
if(!bp)
return;
Breakpoint *brk = *bp;
mProgramPaused = true;
Con::evaluatef("$Debug::result = %s;", brk->testExpression);
if(Con::getBoolVariable("$Debug::result"))
{
brk->curCount++;
if(brk->curCount >= brk->passCount)
{
brk->curCount = 0;
if(brk->clearOnHit)
removeBreakpoint(code->name, lineNumber);
breakProcess();
}
}
mProgramPaused = false;
}
void TelnetDebugger::pushStackFrame()
{
if(mState == NotConnected)
return;
if(mBreakOnNextStatement && mStackPopBreakIndex > -1 &&
gEvalState.stack.size() > mStackPopBreakIndex)
setBreakOnNextStatement( false );
}
void TelnetDebugger::popStackFrame()
{
if(mState == NotConnected)
return;
if(mStackPopBreakIndex > -1 && gEvalState.stack.size()-1 <= mStackPopBreakIndex)
setBreakOnNextStatement( true );
}
void TelnetDebugger::breakProcess()
{
// Send out a break with the full stack.
sendBreak();
mProgramPaused = true;
while(mProgramPaused)
{
Platform::sleep(10);
checkDebugRecv();
if(mDebugSocket == InvalidSocket)
{
mProgramPaused = false;
removeAllBreakpoints();
debugContinue();
return;
}
}
}
void TelnetDebugger::sendBreak()
{
// echo out the break
send("BREAK");
char buffer[MaxCommandSize];
char scope[MaxCommandSize];
S32 last = 0;
for(S32 i = (S32) gEvalState.stack.size() - 1; i >= last; i--)
{
CodeBlock *code = gEvalState.stack[i]->code;
const char *file = "<none>";
if (code && code->name && code->name[0])
file = code->name;
Namespace *ns = gEvalState.stack[i]->scopeNamespace;
scope[0] = 0;
if ( ns ) {
if ( ns->mParent && ns->mParent->mPackage && ns->mParent->mPackage[0] ) {
dStrcat( scope, ns->mParent->mPackage );
dStrcat( scope, "::" );
}
if ( ns->mName && ns->mName[0] ) {
dStrcat( scope, ns->mName );
dStrcat( scope, "::" );
}
}
const char *function = gEvalState.stack[i]->scopeName;
if ((!function) || (!function[0]))
function = "<none>";
dStrcat( scope, function );
U32 line=0, inst;
U32 ip = gEvalState.stack[i]->ip;
if (code)
code->findBreakLine(ip, line, inst);
dSprintf(buffer, MaxCommandSize, " %s %d %s", file, line, scope);
send(buffer);
}
send("\r\n");
}
void TelnetDebugger::processLineBuffer(S32 cmdLen)
{
if (mState == PasswordTry)
{
if(dStrncmp(mLineBuffer, mDebuggerPassword, cmdLen-1))
{
// failed password:
send("PASS WrongPassword.\r\n");
disconnect();
}
else
{
send("PASS Connected.\r\n");
mState = mWaitForClient ? Initialize : Connected;
}
return;
}
else
{
char evalBuffer[MaxCommandSize];
char varBuffer[MaxCommandSize];
char fileBuffer[MaxCommandSize];
char clear[MaxCommandSize];
S32 passCount, line, frame;
if(dSscanf(mLineBuffer, "CEVAL %[^\n]", evalBuffer) == 1)
{
ConsoleEvent postEvent;
dStrcpy(postEvent.data, evalBuffer);
postEvent.size = ConsoleEventHeaderSize + dStrlen(evalBuffer) + 1;
Game->postEvent(postEvent);
}
else if(dSscanf(mLineBuffer, "BRKVARSET %s %d %[^\n]", varBuffer, &passCount, evalBuffer) == 3)
addVariableBreakpoint(varBuffer, passCount, evalBuffer);
else if(dSscanf(mLineBuffer, "BRKVARCLR %s", varBuffer) == 1)
removeVariableBreakpoint(varBuffer);
else if(dSscanf(mLineBuffer, "BRKSET %s %d %s %d %[^\n]", fileBuffer,&line,&clear,&passCount,evalBuffer) == 5)
addBreakpoint(fileBuffer, line, dAtob(clear), passCount, evalBuffer);
else if(dSscanf(mLineBuffer, "BRKCLR %s %d", fileBuffer, &line) == 2)
removeBreakpoint(fileBuffer, line);
else if(!dStrncmp(mLineBuffer, "BRKCLRALL\n", cmdLen))
removeAllBreakpoints();
else if(!dStrncmp(mLineBuffer, "BRKNEXT\n", cmdLen))
debugBreakNext();
else if(!dStrncmp(mLineBuffer, "CONTINUE\n", cmdLen))
debugContinue();
else if(!dStrncmp(mLineBuffer, "STEPIN\n", cmdLen))
debugStepIn();
else if(!dStrncmp(mLineBuffer, "STEPOVER\n", cmdLen))
debugStepOver();
else if(!dStrncmp(mLineBuffer, "STEPOUT\n", cmdLen))
debugStepOut();
else if(dSscanf(mLineBuffer, "EVAL %s %d %[^\n]", varBuffer, &frame, evalBuffer) == 3)
evaluateExpression(varBuffer, frame, evalBuffer);
else if(!dStrncmp(mLineBuffer, "FILELIST\n", cmdLen))
dumpFileList();
else if(dSscanf(mLineBuffer, "BREAKLIST %s", fileBuffer) == 1)
dumpBreakableList(fileBuffer);
else
{
// invalid stuff.
send("DBGERR Invalid command!\r\n");
}
}
}
void TelnetDebugger::addVariableBreakpoint(const char*, S32, const char*)
{
send("addVariableBreakpoint\r\n");
}
void TelnetDebugger::removeVariableBreakpoint(const char*)
{
send("removeVariableBreakpoint\r\n");
}
void TelnetDebugger::addAllBreakpoints(CodeBlock *code)
{
if(mState == NotConnected)
return;
char buffer[MaxCommandSize];
dSprintf(buffer, MaxCommandSize, "LOAD %s\r\n", code->name);
send(buffer);
// Find the breakpoints for this code block and attach them.
Breakpoint **walk = &mBreakpoints;
Breakpoint *cur;
while((cur = *walk) != NULL)
{
if(cur->fileName == code->name)
{
cur->code = code;
// Find the fist breakline starting from and
// including the requested breakline.
S32 newLine = code->findFirstBreakLine(cur->lineNumber);
if (newLine <= 0)
{
walk = &cur->next;
dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
send(buffer);
removeBreakpoint(cur->fileName, cur->lineNumber);
continue;
}
// If the requested breakline does not match
// the actual break line we need to inform
// the client.
if (newLine != cur->lineNumber)
{
// If we already have a line at this breapoint then
// tell the client to clear the breakpoint.
if ( findBreakpoint(cur->fileName, newLine) ) {
walk = &cur->next;
dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
send(buffer);
removeBreakpoint(cur->fileName, cur->lineNumber);
continue;
}
// We're moving the breakpoint to new line... inform the
// client so it can update it's view.
dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", cur->fileName, cur->lineNumber, newLine);
send(buffer);
cur->lineNumber = newLine;
}
code->setBreakpoint(cur->lineNumber);
}
walk = &cur->next;
}
// Enable all breaks if a break next was set.
if (mBreakOnNextStatement)
code->setAllBreaks();
}
void TelnetDebugger::clearCodeBlockPointers(CodeBlock *code)
{
Breakpoint **walk = &mBreakpoints;
Breakpoint *cur;
while((cur = *walk) != NULL)
{
if(cur->code == code)
cur->code = NULL;
walk = &cur->next;
}
}
void TelnetDebugger::addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString)
{
fileName = StringTable->insert(fileName);
Breakpoint **bp = findBreakpoint(fileName, line);
if(bp)
{
// trying to add the same breakpoint...
Breakpoint *brk = *bp;
dFree(brk->testExpression);
brk->testExpression = dStrdup(evalString);
brk->passCount = passCount;
brk->clearOnHit = clear;
brk->curCount = 0;
}
else
{
// Note that if the code block is not already
// loaded it is handled by addAllBreakpoints.
CodeBlock* code = CodeBlock::find(fileName);
if (code)
{
// Find the fist breakline starting from and
// including the requested breakline.
S32 newLine = code->findFirstBreakLine(line);
if (newLine <= 0)
{
char buffer[MaxCommandSize];
dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line);
send(buffer);
return;
}
// If the requested breakline does not match
// the actual break line we need to inform
// the client.
if (newLine != line)
{
char buffer[MaxCommandSize];
// If we already have a line at this breapoint then
// tell the client to clear the breakpoint.
if ( findBreakpoint(fileName, newLine) ) {
dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line);
send(buffer);
return;
}
// We're moving the breakpoint to new line... inform the client.
dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", fileName, line, newLine);
send(buffer);
line = newLine;
}
code->setBreakpoint(line);
}
Breakpoint *brk = new Breakpoint;
brk->code = code;
brk->fileName = fileName;
brk->lineNumber = line;
brk->passCount = passCount;
brk->clearOnHit = clear;
brk->curCount = 0;
brk->testExpression = dStrdup(evalString);
brk->next = mBreakpoints;
mBreakpoints = brk;
}
}
void TelnetDebugger::removeBreakpointsFromCode(CodeBlock *code)
{
Breakpoint **walk = &mBreakpoints;
Breakpoint *cur;
while((cur = *walk) != NULL)
{
if(cur->code == code)
{
dFree(cur->testExpression);
*walk = cur->next;
delete walk;
}
else
walk = &cur->next;
}
}
void TelnetDebugger::removeBreakpoint(const char *fileName, S32 line)
{
fileName = StringTable->insert(fileName);
Breakpoint **bp = findBreakpoint(fileName, line);
if(bp)
{
Breakpoint *brk = *bp;
*bp = brk->next;
if ( brk->code )
brk->code->clearBreakpoint(brk->lineNumber);
dFree(brk->testExpression);
delete brk;
}
}
void TelnetDebugger::removeAllBreakpoints()
{
Breakpoint *walk = mBreakpoints;
while(walk)
{
Breakpoint *temp = walk->next;
if ( walk->code )
walk->code->clearBreakpoint(walk->lineNumber);
dFree(walk->testExpression);
delete walk;
walk = temp;
}
mBreakpoints = NULL;
}
void TelnetDebugger::debugContinue()
{
if (mState == Initialize) {
mState = Connected;
return;
}
setBreakOnNextStatement( false );
mStackPopBreakIndex = -1;
mProgramPaused = false;
send("RUNNING\r\n");
}
void TelnetDebugger::setBreakOnNextStatement( bool enabled )
{
if ( enabled )
{
// Apply breaks on all the code blocks.
for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
walk->setAllBreaks();
mBreakOnNextStatement = true;
}
else if ( !enabled )
{
// Clear all the breaks on the codeblocks
// then go reapply the breakpoints.
for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
walk->clearAllBreaks();
for(Breakpoint *w = mBreakpoints; w; w = w->next)
{
if ( w->code )
w->code->setBreakpoint(w->lineNumber);
}
mBreakOnNextStatement = false;
}
}
void TelnetDebugger::debugBreakNext()
{
if (mState != Connected)
return;
if ( !mProgramPaused )
setBreakOnNextStatement( true );
}
void TelnetDebugger::debugStepIn()
{
// Note that step in is allowed during
// the initialize state, so that we can
// break on the first script line executed.
setBreakOnNextStatement( true );
mStackPopBreakIndex = -1;
mProgramPaused = false;
// Don't bother sending this to the client
// if it's in the initialize state. It will
// just be ignored as the client knows it
// is in a running state when it connects.
if (mState != Initialize)
send("RUNNING\r\n");
else
mState = Connected;
}
void TelnetDebugger::debugStepOver()
{
if (mState != Connected)
return;
setBreakOnNextStatement( true );
mStackPopBreakIndex = gEvalState.stack.size();
mProgramPaused = false;
send("RUNNING\r\n");
}
void TelnetDebugger::debugStepOut()
{
if (mState != Connected)
return;
setBreakOnNextStatement( false );
mStackPopBreakIndex = gEvalState.stack.size() - 1;
if ( mStackPopBreakIndex == 0 )
mStackPopBreakIndex = -1;
mProgramPaused = false;
send("RUNNING\r\n");
}
void TelnetDebugger::evaluateExpression(const char *tag, S32 frame, const char *evalBuffer)
{
// Make sure we're passing a valid frame to the eval.
if ( frame > gEvalState.stack.size() )
frame = gEvalState.stack.size() - 1;
if ( frame < 0 )
frame = 0;
// Build a buffer just big enough for this eval.
const char* format = "return %s;";
dsize_t len = dStrlen( format ) + dStrlen( evalBuffer );
char* buffer = new char[ len ];
dSprintf( buffer, len, format, evalBuffer );
// Execute the eval.
CodeBlock *newCodeBlock = new CodeBlock();
const char* result = newCodeBlock->compileExec( NULL, buffer, false, frame );
delete [] buffer;
// Create a new buffer that fits the result.
format = "EVALOUT %s %s\r\n";
len = dStrlen( format ) + dStrlen( tag ) + dStrlen( result );
buffer = new char[ len ];
dSprintf( buffer, len, format, tag, result[0] ? result : "\"\"" );
send( buffer );
delete [] buffer;
}
void TelnetDebugger::dumpFileList()
{
send("FILELISTOUT ");
for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
{
send(walk->name);
if(walk->nextFile)
send(" ");
}
send("\r\n");
}
void TelnetDebugger::dumpBreakableList(const char *fileName)
{
fileName = StringTable->insert(fileName);
CodeBlock *file = CodeBlock::find(fileName);
char buffer[MaxCommandSize];
if(file)
{
dSprintf(buffer, MaxCommandSize, "BREAKLISTOUT %s %d", fileName, file->breakListSize >> 1);
send(buffer);
for(U32 i = 0; i < file->breakListSize; i += 2)
{
dSprintf(buffer, MaxCommandSize, " %d %d", file->breakList[i], file->breakList[i+1]);
send(buffer);
}
send("\r\n");
}
else
send("DBGERR No such file!\r\n");
}

116
engine/console/telnetDebugger.h Executable file
View File

@ -0,0 +1,116 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _TELNETDEBUGGER_H_
#define _TELNETDEBUGGER_H_
class CodeBlock;
/// Telnet debug service implementation.
///
/// This is the C++ side of the built-in Torque debugger.
///
/// To use the debugger, use dbgSetParameters(port, password); in the console
/// of the server to enable debugger connections. Then on some other system,
/// start up the app (you don't have to start a game or connect to the
/// server) and exec("common/debugger/debugger.cs"); in the console. Then use
/// the debugger GUI to connect to the server with the right port and password.
///
/// @see http://www.planettribes.com/tribes2/editing.shtml for more thorough discussion.
class TelnetDebugger
{
S32 mAcceptPort;
NetSocket mAcceptSocket;
NetSocket mDebugSocket;
enum {
// We should only change this if we truely
// break the protocol in a future version.
Version = 2,
PasswordMaxLength = 32,
MaxCommandSize = 2048
};
char mDebuggerPassword[PasswordMaxLength+1];
enum State
{
NotConnected,
PasswordTry,
Initialize,
Connected
};
S32 mState;
char mLineBuffer[MaxCommandSize];
S32 mCurPos;
bool mWaitForClient;
TelnetDebugger();
~TelnetDebugger();
struct Breakpoint
{
StringTableEntry fileName;
CodeBlock *code;
U32 lineNumber;
S32 passCount;
S32 curCount;
char *testExpression;
bool clearOnHit;
Breakpoint *next;
};
Breakpoint *mBreakpoints;
Breakpoint **findBreakpoint(StringTableEntry fileName, S32 lineNumber);
bool mProgramPaused;
bool mBreakOnNextStatement;
S32 mStackPopBreakIndex;
void addVariableBreakpoint(const char *varName, S32 passCount, const char *evalString);
void removeVariableBreakpoint(const char *varName);
void addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString);
void removeBreakpoint(const char *fileName, S32 line);
void removeAllBreakpoints();
void debugBreakNext();
void debugContinue();
void debugStepIn();
void debugStepOver();
void debugStepOut();
void evaluateExpression(const char *tag, S32 frame, const char *evalBuffer);
void dumpFileList();
void dumpBreakableList(const char *fileName);
void removeBreakpointsFromCode(CodeBlock *code);
void sendBreak();
void checkDebugRecv();
void processLineBuffer(S32);
void breakProcess();
void setBreakOnNextStatement( bool enabled );
public:
static void create();
static void destroy();
void disconnect();
bool isConnected() const { return mState == Connected; }
void process();
void popStackFrame();
void pushStackFrame();
void addAllBreakpoints(CodeBlock *code);
void clearCodeBlockPointers(CodeBlock *code);
virtual void executionStopped(CodeBlock *code, U32 lineNumber);
void send(const char *s);
void setDebugParameters(S32 port, const char *password, bool waitForClient);
void processConsoleLine(const char *consoleLine);
};
extern TelnetDebugger *TelDebugger;
#endif

View File

@ -0,0 +1,70 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "console/console.h"
#include "console/consoleObject.h"
#include "console/typeValidators.h"
#include "console/simBase.h"
#include <stdarg.h>
void TypeValidator::consoleError(SimObject *object, const char *format, ...)
{
char buffer[1024];
va_list argptr;
va_start(argptr, format);
dVsprintf(buffer, sizeof(buffer), format, argptr);
va_end(argptr);
AbstractClassRep *rep = object->getClassRep();
AbstractClassRep::Field &fld = rep->mFieldList[fieldIndex];
const char *objectName = object->getName();
if(!objectName)
objectName = "unnamed";
Con::warnf("%s - %s(%d) - invalid value for %s: %s",
rep->getClassName(), objectName, object->getId(), fld.pFieldname, buffer);
}
void FRangeValidator::validateType(SimObject *object, void *typePtr)
{
F32 *v = (F32 *) typePtr;
if(*v < minV || *v > maxV)
{
consoleError(object, "Must be between %g and %g", minV, maxV);
if(*v < minV)
*v = minV;
else if(*v > maxV)
*v = maxV;
}
}
void IRangeValidator::validateType(SimObject *object, void *typePtr)
{
S32 *v = (S32 *) typePtr;
if(*v < minV || *v > maxV)
{
consoleError(object, "Must be between %d and %d", minV, maxV);
if(*v < minV)
*v = minV;
else if(*v > maxV)
*v = maxV;
}
}
void IRangeValidatorScaled::validateType(SimObject *object, void *typePtr)
{
S32 *v = (S32 *) typePtr;
*v /= factor;
if(*v < minV || *v > maxV)
{
consoleError(object, "Scaled value must be between %d and %d", minV, maxV);
if(*v < minV)
*v = minV;
else if(*v > maxV)
*v = maxV;
}
}

72
engine/console/typeValidators.h Executable file
View File

@ -0,0 +1,72 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#ifndef _TYPEVALIDATORS_H_
#define _TYPEVALIDATORS_H_
class TypeValidator
{
public:
S32 fieldIndex;
/// Prints a console error message for the validator.
///
/// The message is prefaced with with:
/// @code
/// className objectName (objectId) - invalid value for fieldName: msg
/// @endcode
void consoleError(SimObject *object, const char *format, ...);
/// validateType is called for each assigned value on the field this
/// validator is attached to.
virtual void validateType(SimObject *object, void *typePtr) = 0;
};
/// Floating point min/max range validator
class FRangeValidator : public TypeValidator
{
F32 minV, maxV;
public:
FRangeValidator(F32 minValue, F32 maxValue)
{
minV = minValue;
maxV = maxValue;
}
void validateType(SimObject *object, void *typePtr);
};
/// Signed integer min/max range validator
class IRangeValidator : public TypeValidator
{
S32 minV, maxV;
public:
IRangeValidator(S32 minValue, S32 maxValue)
{
minV = minValue;
maxV = maxValue;
}
void validateType(SimObject *object, void *typePtr);
};
/// Scaled integer field validator
///
/// @note This should NOT be used on a field that gets exported -
/// the field is only validated once on initial assignment
class IRangeValidatorScaled : public TypeValidator
{
S32 minV, maxV;
S32 factor;
public:
IRangeValidatorScaled(S32 scaleFactor, S32 minValueScaled, S32 maxValueScaled)
{
minV = minValueScaled;
maxV = maxValueScaled;
factor = scaleFactor;
}
void validateType(SimObject *object, void *typePtr);
};
#endif

View File

@ -0,0 +1,742 @@
//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "constructor/constructorSimpleMesh.h"
#ifdef IS_CONSTRUCTOR
#include "constructor/lightingSystem/lightingSystem.h"
#else
#include "interior/interiorLMManager.h"
#endif
#include "console/console.h"
#include "sim/sceneObject.h"
#include "util/triRayCheck.h"
#include "math/mathIO.h"
#ifdef IS_CONSTRUCTOR
ConstructorSimpleMesh *ConstructorSimpleMeshLoader::currentMesh = NULL;
#endif
// *** DAW: Checks for polygon level collision with given planes
U32 _whichSide(PlaneF pln, Point3F* verts)
{
Point3F currv, nextv;
S32 csd, nsd;
// Find out which side the first vert is on
U32 side = PlaneF::On;
currv = verts[0];
csd = pln.whichSide(currv);
if(csd != PlaneF::On)
side = csd;
for(U32 k = 1; k < 3; k++)
{
nextv = verts[k];
nsd = pln.whichSide(nextv);
if((csd == PlaneF::Back && nsd == PlaneF::Front) ||
(csd == PlaneF::Front && nsd == PlaneF::Back))
return 2;
else if (nsd != PlaneF::On)
side = nsd;
currv = nextv;
csd = nsd;
}
// Loop back to the first vert
nextv = verts[0];
nsd = pln.whichSide(nextv);
if((csd == PlaneF::Back && nsd == PlaneF::Front) ||
(csd == PlaneF::Front && nsd == PlaneF::Back))
return 2;
else if(nsd != PlaneF::On)
side = nsd;
return side;
}
bool ConstructorSimpleMesh::castRay(const Point3F &start, const Point3F &end, RayInfo* info)
{
bool found = false;
F32 best_t = F32_MAX;
Point3F best_normal = Point3F(0, 0, 1);
Point3F dir = end - start;
for(U32 p=0; p<primitives.size(); p++)
{
primitive &prim = primitives[p];
for(U32 t=2; t<prim.count; t++)
{
Point3F &v1 = verts[prim.start+t-2];
Point3F &v2 = verts[prim.start+t-1];
Point3F &v3 = verts[prim.start+t];
F32 cur_t = 0;
Point2F b;
if(castRayTriangle(start, dir, v1, v2, v3, cur_t, b))
{
if(cur_t < best_t)
{
best_t = cur_t;
best_normal = norms[prim.start+t];
found = true;
}
}
}
}
if(found && info)
{
info->t = best_t;
info->normal = best_normal;
info->material = 0;
}
return found;
}
bool ConstructorSimpleMesh::castPlanes(PlaneF left, PlaneF right, PlaneF top, PlaneF bottom)
{
for(U32 p=0; p<primitives.size(); p++)
{
primitive &prim = primitives[p];
for(U32 t=2; t<prim.count; t++)
{
Point3F v[3];
v[0] = verts[prim.start+t-2];
v[1] = verts[prim.start+t-1];
v[2] = verts[prim.start+t];
if(_whichSide(left, v) == PlaneF::Front)
continue;
if(_whichSide(right, v) == PlaneF::Front)
continue;
if(_whichSide(top, v) == PlaneF::Front)
continue;
if(_whichSide(bottom, v) == PlaneF::Front)
continue;
return true;
}
}
return false;
}
#ifdef IS_CONSTRUCTOR
void ConstructorSimpleMesh::render(bool transparent, bool texture, bool lightmap)
{
#else
void ConstructorSimpleMesh::render(bool transparent, bool texture, bool lightmap,
U32 interiorlmhandle, U32 instancelmhandle)
{
if(!materialList)
return;
#endif
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, verts.address());
glEnableClientState(GL_NORMAL_ARRAY);
glNormalPointer(GL_FLOAT, 0, norms.address());
glClientActiveTextureARB(GL_TEXTURE0_ARB);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, diffuseUVs.address());
//if (lightmap)
//{
glClientActiveTextureARB(GL_TEXTURE1_ARB);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glTexCoordPointer(2, GL_FLOAT, 0, lightmapUVs.address());
glClientActiveTextureARB(GL_TEXTURE0_ARB);
//}
for(S32 i=0; i<primitives.size(); i++)
{
primitive &draw = primitives[i];
if(draw.alpha != transparent)
continue;
if(texture)
{
#ifdef IS_CONSTRUCTOR
glBindTexture(GL_TEXTURE_2D, draw.diffuseId);
#else
glBindTexture(GL_TEXTURE_2D, materialList->getMaterial(draw.diffuseIndex).getGLName());
#endif
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, draw.texS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, draw.texT);
}
if(lightmap)
{
glActiveTextureARB(GL_TEXTURE1_ARB);
#ifdef IS_CONSTRUCTOR
glBindTexture(GL_TEXTURE_2D, draw.lightMapId);
#else
glBindTexture(GL_TEXTURE_2D, gInteriorLMManager.getHandle(interiorlmhandle, instancelmhandle, draw.lightMapIndex)->getGLName());
#endif
glActiveTextureARB(GL_TEXTURE0_ARB);
}
glDrawElements(GL_TRIANGLE_STRIP, draw.count, GL_UNSIGNED_SHORT,
&indices[draw.start]);
}
glClientActiveTextureARB(GL_TEXTURE1_ARB);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glClientActiveTextureARB(GL_TEXTURE0_ARB);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
}
bool ConstructorSimpleMesh::read(Stream& stream)
{
// Simple serialization
S32 vectorSize = 0;
// Primitives
stream.read(&vectorSize);
primitives.setSize(vectorSize);
for (U32 i = 0; i < primitives.size(); i++)
{
stream.read(&primitives[i].alpha);
stream.read(&primitives[i].texS);
stream.read(&primitives[i].texT);
stream.read(&primitives[i].diffuseIndex);
stream.read(&primitives[i].lightMapIndex);
stream.read(&primitives[i].start);
stream.read(&primitives[i].count);
mathRead(stream, &primitives[i].lightMapEquationX);
mathRead(stream, &primitives[i].lightMapEquationY);
mathRead(stream, &primitives[i].lightMapOffset);
mathRead(stream, &primitives[i].lightMapSize);
}
// Indices
stream.read(&vectorSize);
indices.setSize(vectorSize);
for (U32 i = 0; i < indices.size(); i++)
stream.read(&indices[i]);
// Vertices
stream.read(&vectorSize);
verts.setSize(vectorSize);
for (U32 i = 0; i < verts.size(); i++)
mathRead(stream, &verts[i]);
// Normals
stream.read(&vectorSize);
norms.setSize(vectorSize);
for (U32 i = 0; i < norms.size(); i++)
mathRead(stream, &norms[i]);
// Diffuse UVs
stream.read(&vectorSize);
diffuseUVs.setSize(vectorSize);
for (U32 i = 0; i < diffuseUVs.size(); i++)
mathRead(stream, &diffuseUVs[i]);
// Lightmap UVs
stream.read(&vectorSize);
lightmapUVs.setSize(vectorSize);
for (U32 i = 0; i < lightmapUVs.size(); i++)
mathRead(stream, &lightmapUVs[i]);
// Material list
bool hasMaterialList = false;
stream.read(&hasMaterialList);
if (hasMaterialList)
{
// Since we are doing this externally to a TSShape read we need to
// make sure that our read version is the same as our write version.
// It is possible that it was changed along the way by a loaded TSShape.
TSShape::smReadVersion = TSShape::smVersion;
if (materialList)
delete materialList;
materialList = new TSMaterialList;
materialList->read(stream);
}
else
materialList = NULL;
// Diffuse bitmaps
stream.read(&vectorSize);
for (U32 i = 0; i < vectorSize; i++)
{
TextureHandle& handle = materialList->getMaterial(i);
bool hasBitmap = false;
stream.read(&hasBitmap);
if (hasBitmap)
{
GBitmap* bitMap = new GBitmap;
bitMap->readPNG(stream);
handle.set(materialList->getMaterialName(i), bitMap, BitmapKeepTexture);
}
}
// Misc data
stream.read(&hasSolid);
stream.read(&hasTranslucency);
mathRead(stream, &bounds);
mathRead(stream, &transform);
mathRead(stream, &scale);
calculateBounds();
return true;
}
bool ConstructorSimpleMesh::write(Stream& stream) const
{
// Simple serialization
// Primitives
stream.write(primitives.size());
for (U32 i = 0; i < primitives.size(); i++)
{
stream.write(primitives[i].alpha);
stream.write(primitives[i].texS);
stream.write(primitives[i].texT);
stream.write(primitives[i].diffuseIndex);
stream.write(primitives[i].lightMapIndex);
stream.write(primitives[i].start);
stream.write(primitives[i].count);
mathWrite(stream, primitives[i].lightMapEquationX);
mathWrite(stream, primitives[i].lightMapEquationY);
mathWrite(stream, primitives[i].lightMapOffset);
mathWrite(stream, primitives[i].lightMapSize);
}
// Indices
stream.write(indices.size());
for (U32 i = 0; i < indices.size(); i++)
stream.write(indices[i]);
// Vertices
stream.write(verts.size());
for (U32 i = 0; i < verts.size(); i++)
mathWrite(stream, verts[i]);
// Normals
stream.write(norms.size());
for (U32 i = 0; i < norms.size(); i++)
mathWrite(stream, norms[i]);
// Diffuse UVs
stream.write(diffuseUVs.size());
for (U32 i = 0; i < diffuseUVs.size(); i++)
mathWrite(stream, diffuseUVs[i]);
// Lightmap UVs
stream.write(lightmapUVs.size());
for (U32 i = 0; i < lightmapUVs.size(); i++)
mathWrite(stream, lightmapUVs[i]);
// Material list
if (materialList)
{
stream.write(true);
materialList->write(stream);
}
else
stream.write(false);
// Diffuse bitmaps
if (!materialList)
stream.write(0);
else
{
stream.write(materialList->getMaterialCount());
for (U32 i = 0; i < materialList->getMaterialCount(); i++)
{
TextureHandle& handle = materialList->getMaterial(i);
if (handle.isValid())
{
GBitmap* bitMap = handle.getBitmap();
if (bitMap)
{
stream.write(true);
bitMap->writePNG(stream);
}
else
stream.write(false);
}
else
stream.write(false);
}
}
// Misc data
stream.write(hasSolid);
stream.write(hasTranslucency);
mathWrite(stream, bounds);
mathWrite(stream, transform);
mathWrite(stream, scale);
return true;
}
//-----------------------------------------------------------------------------
#ifdef IS_CONSTRUCTOR
bool ConstructorSimpleMeshLoader::loadSimpleMesh(const char *filename, ConstructorSimpleMesh &mesh)
{
if(!filename)
return false;
const char *ext = dStrrchr(filename, '.');
if(!ext || (ext[0] == 0))
return false;
FileStream file;
if(!file.open(filename, FileStream::Read))
{
Con::errorf("Unable to open file '%s'.", filename);
return false;
}
currentMesh = &mesh;
currentMesh->clear();
bool res = false;
if(dStricmp(ext, ".dts") == 0)
res = loadDTSFile(filename);
//else if(dStricmp(ext, ".x") == 0)
// res = loadDirectXFile(&file);
else
{
ext = &ext[1];
if(ext[0] != 0)
{
char func[256];
func[0] = 0;
dStrcat(func, "SimpleMeshLoader_load");
dStrcat(func, ext);
dStrcat(func, "File");
const char *ret = Con::executef(1, func);
if(!ret || (dStricmp(ret, "1") != 0))
res = false;
else
res = true;
}
}
currentMesh->calculateBounds();
currentMesh = NULL;
return res;
}
bool ConstructorSimpleMeshLoader::loadDTSFile(const char *filename)
{
Resource<TSShape> shape;
shape = ResourceManager->load(filename);
if(shape.isNull())
return false;
TSShapeInstance *shapeinst = new TSShapeInstance(shape, true);
if(!shapeinst)
return false;
// load up...
currentMesh->materialList = new TSMaterialList(shapeinst->getMaterialList());
// only interested in the top detail...
const TSDetail *detail = &shapeinst->getShape()->details[0];
S32 ss = detail->subShapeNum;
S32 od = detail->objectDetailNum;
S32 start = shapeinst->getShape()->subShapeFirstObject[ss];
S32 end = start + shapeinst->getShape()->subShapeNumObjects[ss];
shapeinst->animate(0);
shapeinst->setCurrentDetail(0);
shapeinst->setStatics(0);
currentMesh->hasSolid = true;
for(U32 i=start; i<end; i++)
{
TSMesh *mesh = shapeinst->mMeshObjects[i].getMesh(od);
MatrixF *mat = shapeinst->mMeshObjects[i].getTransform();
if(!mesh)
continue;
MatrixF m;
if(mat)
m = *mat;
else
m.identity();
ToolVector<Point3F> *vertsp;
ToolVector<Point3F> *normsp;
TSSkinMesh *smesh = dynamic_cast<TSSkinMesh *>(mesh);
if(smesh)
{
vertsp = &smesh->initialVerts;
normsp = &smesh->initialNorms;
}
else
{
vertsp = &mesh->verts;
normsp = &mesh->norms;
}
ToolVector<Point3F> &verts = *vertsp;
ToolVector<Point3F> &norms = *normsp;
for(U32 p=0; p<mesh->primitives.size(); p++)
{
TSDrawPrimitive &oldprim = mesh->primitives[p];
currentMesh->primitives.increment();
ConstructorSimpleMesh::primitive &newprim = currentMesh->primitives.last();
U32 flags = currentMesh->materialList->getFlags(oldprim.matIndex & TSDrawPrimitive::MaterialMask);
newprim.alpha = (flags & TSMaterialList::Translucent);
newprim.texS = (flags & TSMaterialList::S_Wrap) ? GL_REPEAT : GL_CLAMP;
newprim.texT = (flags & TSMaterialList::T_Wrap) ? GL_REPEAT : GL_CLAMP;
newprim.start = currentMesh->indices.size();
newprim.count = 0;
newprim.lightMapId = 0;
newprim.lightMapIndex = 0;
if(newprim.alpha)
currentMesh->hasTranslucency = true;
newprim.diffuseIndex = oldprim.matIndex & TSDrawPrimitive::MaterialMask;
TextureHandle &tex = currentMesh->materialList->getMaterial(newprim.diffuseIndex);
newprim.diffuseId = tex.getGLName();
for(U32 v=0; v<oldprim.numElements; v++)
{
AssertFatal(((S32(oldprim.start + v) > -1) &&
(S32(oldprim.start + v) < mesh->indices.size())), "!");
S32 srcindex = mesh->indices[oldprim.start + v];
S32 dstindex = currentMesh->verts.size();
currentMesh->indices.increment();
currentMesh->norms.increment();
currentMesh->verts.increment();
currentMesh->diffuseUVs.increment();
currentMesh->lightmapUVs.increment();
currentMesh->indices.last() = dstindex;
currentMesh->verts[dstindex] = verts[srcindex];
currentMesh->norms[dstindex] = norms[srcindex];
m.mulP(currentMesh->verts[dstindex]);
m.mulV(currentMesh->norms[dstindex]);
ToolVector<Point2F> uvs;
mesh->getUVs(TSMesh::tDiffuse, uvs);
if(uvs.size() > 0)
currentMesh->diffuseUVs[dstindex] = uvs[srcindex];
newprim.count++;
}
}
}
// Now grab the collision meshes and shove them into ConvexBrushes
for (U32 i = 0; i < shapeinst->getShape()->details.size(); i++)
{
const TSDetail * detail = &shapeinst->getShape()->details[i];
char* name = (char*)shapeinst->getShape()->names[detail->nameIndex];
if (dStrstr(dStrlwr(name), "collision-"))
{
shapeinst->animate(i);
shapeinst->setCurrentDetail(i);
shapeinst->setStatics(i);
S32 ss = detail->subShapeNum;
S32 od = detail->objectDetailNum;
S32 start = shapeinst->getShape()->subShapeFirstObject[ss];
S32 end = shapeinst->getShape()->subShapeNumObjects[ss] + start;
if (start < end)
{
// Run through objects and collide
for (S32 j = start; j < end; j++)
{
TSShapeInstance::MeshObjectInstance * mesh = &shapeinst->mMeshObjects[j];
if (od >= mesh->object->numMeshes)
continue;
ConcretePolyList polys;
// Get a valid transform for the polylist
MatrixF* mat = mesh->getTransform();
MatrixF m;
if(mat)
m = *mat;
else
m.identity();
polys.setTransform(&m, Point3F(1.0f, 1.0f, 1.0f));
// collide...
U32 surfaceKey = 0;
mesh->buildPolyList(od, &polys, surfaceKey);
if (polys.mPolyList.size() == 0)
continue;
// Now build our actual ConvexBrush
ConvexBrush* hull = new ConvexBrush;
for (U32 k = 0; k < polys.mPolyList.size(); k++)
{
ConcretePolyList::Poly& srcPoly = polys.mPolyList[k];
OptimizedPolyList::Poly dstPoly;
dstPoly.plane = hull->mFaces.addPlane(srcPoly.plane);
dstPoly.vertexStart = hull->mFaces.mIndexList.size();
dstPoly.vertexCount = srcPoly.vertexCount;
for (U32 m = 0; m < srcPoly.vertexCount; m++)
{
U32 srcIndex = polys.mIndexList[srcPoly.vertexStart + m];
Point3F& pt = polys.mVertexList[srcIndex];
U32 dstIndex = hull->mFaces.addPoint(pt);
hull->mFaces.mIndexList.push_back(dstIndex);
}
dstPoly.textureData.texture = StringTable->insert("null");
dstPoly.isNull = true;
hull->mFaces.setUpTextureMatrix(dstPoly);
hull->mFaces.mPolyList.push_back(dstPoly);
}
// We need to do a merging pass on the polylist since TSMesh's
// buildPolyList returns a list of individual triangles
// Setup the surface id's
for (U32 k = 0; k < hull->mFaces.mPlaneList.size(); k++)
{
for (U32 m = 0; m < hull->mFaces.size(); m++)
{
OptimizedPolyList::Poly& poly = hull->mFaces[m];
if (poly.plane == k)
poly.surfaceID = k;
}
}
// And do the actual merge
hull->mFaces.mergeSurfaces();
// Need to do a couple operations on our ConvexBrush
hull->mFaces.generateEdgelist();
hull->calcBounds();
hull->calcCentroid();
hull->mStatus = ConvexBrush::Good;
hull->mType = InteriorMapResource::Detail;
hull->mBrushScale = 32.0f;
// And now push it back into our collisionHulls list
currentMesh->collisionHulls.push_back(hull);
}
}
}
}
shapeinst->clearStatics();
delete shapeinst;
return true;
}
/*bool ConstructorSimpleMeshLoader::loadDirectXFile(Stream *stream)
{
return false;
}*/
ConsoleFunction(SimpleMeshLoader_createTriStrip, bool, 3, 3, "bool SimpleMeshLoader_createTriStrip(textureFileName, transparent)")
{
ConstructorSimpleMesh *mesh = ConstructorSimpleMeshLoader::currentMesh;
if(!mesh)
return false;
if(!mesh->materialList)
mesh->materialList = new TSMaterialList();
mesh->materialList->push_back(argv[2], 0);
mesh->primitives.increment();
ConstructorSimpleMesh::primitive &prim = mesh->primitives.last();
prim.start = mesh->indices.size();
prim.count = 0;
prim.diffuseIndex = mesh->materialList->size() - 1;
TextureHandle &tex = mesh->materialList->getMaterial(prim.diffuseIndex);
prim.diffuseId = tex.getGLName();
prim.lightMapId = 0;
prim.lightMapIndex = 0;
prim.alpha = ((dAtoi(argv[2]) == 1) || (dStrcmp(argv[2], "true") == 0));
prim.texS = GL_REPEAT;
prim.texT = GL_REPEAT;
return true;
}
ConsoleFunction(SimpleMeshLoader_createVert, bool, 4, 4, "bool SimpleMeshLoader_createVert(pos, norm, uv)")
{
ConstructorSimpleMesh *mesh = ConstructorSimpleMeshLoader::currentMesh;
if(!mesh || (mesh->primitives.size() < 1))
return false;
Point3F pos, norm;
Point2F uv;
dSscanf(argv[1], "%f %f %f", &pos.x, &pos.y, &pos.z);
dSscanf(argv[2], "%f %f %f", &norm.x, &norm.y, &norm.z);
dSscanf(argv[3], "%f %f", &uv.x, &uv.y);
ConstructorSimpleMesh::primitive &prim = mesh->primitives.last();
prim.count++;
mesh->indices.increment();
mesh->indices.last() = mesh->verts.size();
mesh->verts.increment();
mesh->verts.last() = pos;
mesh->norms.increment();
mesh->norms.last() = norm;
mesh->diffuseUVs.increment();
mesh->diffuseUVs.last() = uv;
mesh->lightmapUVs.increment();
return true;
}
#endif

Some files were not shown because too many files have changed in this diff Show More