From c47caa1440f0adc8768f389dfc9c9868dfe1650f Mon Sep 17 00:00:00 2001 From: hjw Date: Thu, 23 Jan 2014 10:49:10 +0000 Subject: [PATCH] Adding particle system - probably needs work, should try to get the version from the NS1 svn repository --- main/source/dlls/extdll.h | 10 +- main/source/linux/Makefile | 36 +- main/source/mod/AnimationUtil.cpp | 3 +- main/source/mod/AvHEntities.cpp | 6 +- main/source/mod/AvHNexusServer.cpp | 30 +- main/source/mod/AvHParticleSystem.cpp | 4 +- main/source/mod/AvHParticleSystem.h | 2 +- main/source/mod/AvHParticleSystemManager.h | 3 +- main/source/mod/AvHParticleTemplate.cpp | 1 + main/source/mod/AvHPlayer.cpp | 4 +- main/source/mod/AvHScriptClient.cpp | 9 +- main/source/mod/AvHScriptManager.cpp | 18 + main/source/mod/AvHScriptManager.h | 8 +- main/source/mod/AvHScriptServer.cpp | 8 +- main/source/mod/AvHScriptShared.cpp | 2 + main/source/particle/Makefile | 43 + main/source/particle/ParticleDLL.dsp | 132 ++ main/source/particle/Readme.txt | 4 + main/source/particle/action_api.cpp | 390 +++++ main/source/particle/actions.cpp | 1840 ++++++++++++++++++++ main/source/particle/general.h | 452 +++++ main/source/particle/libparticleMP.a | Bin 0 -> 84110 bytes main/source/particle/opengl.cpp | 149 ++ main/source/particle/p_vector.h | 141 ++ main/source/particle/papi.h | 229 +++ main/source/particle/system.cpp | 883 ++++++++++ main/source/util/Balance.cpp | 4 +- main/source/util/CString.h | 1 + main/source/util/STLUtil.h | 3 +- 29 files changed, 4366 insertions(+), 49 deletions(-) create mode 100644 main/source/particle/Makefile create mode 100644 main/source/particle/ParticleDLL.dsp create mode 100644 main/source/particle/Readme.txt create mode 100644 main/source/particle/action_api.cpp create mode 100644 main/source/particle/actions.cpp create mode 100644 main/source/particle/general.h create mode 100644 main/source/particle/libparticleMP.a create mode 100644 main/source/particle/opengl.cpp create mode 100644 main/source/particle/p_vector.h create mode 100644 main/source/particle/papi.h create mode 100644 main/source/particle/system.cpp diff --git a/main/source/dlls/extdll.h b/main/source/dlls/extdll.h index abd24e3e..8467177e 100644 --- a/main/source/dlls/extdll.h +++ b/main/source/dlls/extdll.h @@ -49,14 +49,20 @@ typedef int BOOL; #define MAX_PATH PATH_MAX #include #include +#ifdef PUZL #ifndef min -#define min(a,b) (((a) < (b)) ? (a) : (b)) +#define min(a,b) (((a) < (b)) ? (a) : (b)) #endif #ifndef max -#define max(a,b) (((a) > (b)) ? (a) : (b)) +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef _vsnprintf #define _vsnprintf(a,b,c,d) vsnprintf(a,b,c,d) +#endif +#ifndef _snprintf #define _snprintf(a,b,c,d,e) #endif +#endif #endif //_WIN32 // Misc C-runtime library headers diff --git a/main/source/linux/Makefile b/main/source/linux/Makefile index fc6f21e3..7fa25540 100644 --- a/main/source/linux/Makefile +++ b/main/source/linux/Makefile @@ -6,18 +6,18 @@ DLLNAME=ns -ARCH=i386 +ARCH=i686 #make sure this is the correct compiler for your system CC=gcc LD=gcc -LIBBASE=../../../libraries/current +LIBBASE=.. DLL_SRCDIR=../dlls GAME_SHARED_SRCDIR = ../game_shared MOD_SRCDIR = ../mod -PARTICLES_SRCDIR = ../particles +PARTICLES_SRCDIR = ../Particle PM_SHARED_SRCDIR=../pm_shared TEXT_SRCDIR = ../textrep UTIL_SRCDIR = ../util @@ -36,15 +36,15 @@ BASE_CFLAGS=-Dstricmp=strcasecmp -D_strnicmp=strncasecmp -Dstrnicmp=strncasecmp # -DAVH_SECURE_PRERELEASE_BUILD for CM testing #full optimization -CFLAGS=$(BASE_CFLAGS) -w -Wall -nostdinc++ -ffor-scope -fPIC -mcpu=i486 -O3 -pipe -funroll-loops -fdelayed-branch -malign-loops=4 -malign-jumps=4 -malign-functions=4 +CFLAGS=$(BASE_CFLAGS) -w -Wall -nostdinc++ -fpermissive -ffor-scope -fPIC -mtune=$(ARCH) -O3 -pipe -funroll-loops -fdelayed-branch -falign-loops=4 -falign-jumps=4 -falign-functions=4 #use these when debugging #CFLAGS=$(BASE_CFLAGS) -fPIC -fno-for-scope # add base directory (CGC) -INCLUDEDIRS=-I$(LIBBASE) -I. -I$(LIBBASE)/include -I$(LIBBASE)/include/stlport -I$(LIBBASE)/include/particle -I$(LIBBASE)/include/lua -I../dlls -I../engine -I../common -I../mod -I../game_shared -I../pm_shared -I.. -I/usr/include/c++/3.3/i486-linux -I/usr/include/c++/3.3 -I/usr/include/c++ -LINKDIRS= -L$(LIBBASE)/release +INCLUDEDIRS=-I$(LIBBASE) -I. -I$(LIBBASE)/include -I$(LIBBASE)/include/stlport -I$(LIBBASE)/particle -I$(LIBBASE)/include/lua -I../dlls -I../engine -I../common -I../mod -I../game_shared -I../pm_shared -I.. -I/usr/include/x86_64-linux-gnu/c++/4.8 -I/usr/include/c++/4.8 -I/usr/include/c++ +LINKDIRS= -L$(LIBBASE)/particle SHLIBEXT=so @@ -52,7 +52,7 @@ LDPRELIBS= $(CFLAGS) -shared \ -Wl,-Map,ns_map.txt \ $(LINKDIRS) -LDPOSTLIBS= -Wl,-Bstatic -lstlport_gcc -lstdc++ -lsupc++ -llua -llualib -lparticleMP -lcurl \ +LDPOSTLIBS= -Wl,-Bstatic -lstdc++ -lsupc++ -lparticleMP -lcurl \ -Wl,-Bdynamic -lm -lgcc -lgcc_eh # -lgcc -lgcc_eh -lelf DO_CC=$(CC) $(CFLAGS) $(INCLUDEDIRS) -o $@ -c $< @@ -257,18 +257,18 @@ release: $(DLLNAME)_$(ARCH).$(SHLIBEXT) curl: ./build-curl.sh neat: - -mkdir $(CURL_OBJDIR) - -mkdir $(DLL_OBJDIR) - -mkdir $(GAME_SHARED_OBJDIR) - -mkdir $(MOD_OBJDIR) - -mkdir $(PARTICLES_OBJDIR) - -mkdir $(PM_SHARED_OBJDIR) - -mkdir $(TEXT_OBJDIR) - -mkdir $(UTIL_OBJDIR) - -mkdir $(OUTPUT_DIR) + @-mkdir -p $(CURL_OBJDIR) + @-mkdir -p $(DLL_OBJDIR) + @-mkdir -p $(GAME_SHARED_OBJDIR) + @-mkdir -p $(MOD_OBJDIR) + @-mkdir -p $(PARTICLES_OBJDIR) + @-mkdir -p $(PM_SHARED_OBJDIR) + @-mkdir -p $(TEXT_OBJDIR) + @-mkdir -p $(UTIL_OBJDIR) + @-mkdir -p $(OUTPUT_DIR) clean: - -rm -f $(OBJ) - -rm -f $(DLLNAME)_$(ARCH).$(SHLIBEXT) + -rm $(OBJ) + -rm $(DLLNAME)_$(ARCH).$(SHLIBEXT) spotless: clean -rm -r $(CURL_OBJDIR) -rm -r $(DLL_OBJDIR) diff --git a/main/source/mod/AnimationUtil.cpp b/main/source/mod/AnimationUtil.cpp index c978a467..e2ee7e5f 100644 --- a/main/source/mod/AnimationUtil.cpp +++ b/main/source/mod/AnimationUtil.cpp @@ -1,4 +1,5 @@ #include +#include #include "types.h" #ifdef AVH_CLIENT @@ -665,4 +666,4 @@ void NS_GetBoneMatrices(const NS_AnimationData& inAnimationData, float time, NS_ } -} \ No newline at end of file +} diff --git a/main/source/mod/AvHEntities.cpp b/main/source/mod/AvHEntities.cpp index ce37c058..291206a7 100644 --- a/main/source/mod/AvHEntities.cpp +++ b/main/source/mod/AvHEntities.cpp @@ -1211,17 +1211,17 @@ void TriggerPresence::KeyValue(KeyValueData* pkvd) } else if(FStrEq(pkvd->szKeyName, "timebeforeleave")) { - this->mTimeBeforeLeave = max(atof(pkvd->szValue), 0.0f); + this->mTimeBeforeLeave = max(atof(pkvd->szValue), (double)0.0f); pkvd->fHandled = TRUE; } else if(FStrEq(pkvd->szKeyName, "momentaryopentime")) { - this->mMomentaryOpenTime = max(atof(pkvd->szValue), 0.01f); + this->mMomentaryOpenTime = max(atof(pkvd->szValue), (double)0.01f); pkvd->fHandled = TRUE; } else if(FStrEq(pkvd->szKeyName, "momentaryclosetime")) { - this->mMomentaryCloseTime = max(atof(pkvd->szValue), 0.01f); + this->mMomentaryCloseTime = max(atof(pkvd->szValue), (double)0.01f); pkvd->fHandled = TRUE; } else if(FStrEq(pkvd->szKeyName, "spawnflags")) diff --git a/main/source/mod/AvHNexusServer.cpp b/main/source/mod/AvHNexusServer.cpp index 01d0c795..5de648f1 100644 --- a/main/source/mod/AvHNexusServer.cpp +++ b/main/source/mod/AvHNexusServer.cpp @@ -4,20 +4,22 @@ using std::string; #include "AvHNexusServer.h" #include "AvHServerUtil.h" - bool AvHNexus::send(entvars_t* const pev, const unsigned char* data, const unsigned int length) { return false; } - bool AvHNexus::recv(entvars_t* const pev, const char* data, const unsigned int length) { return false; } - string AvHNexus::getNetworkID(const edict_t* edict) { return AvHSUGetPlayerAuthIDString((edict_t *)edict); } - void AvHNexus::handleUnauthorizedJoinTeamAttempt(const edict_t* edict, const unsigned char team_index) {} - void AvHNexus::performSpeedTest(void) {} - void AvHNexus::processResponses(void) {} - void AvHNexus::setGeneratePerformanceData(const edict_t* edict, const bool generate) {} - bool AvHNexus::getGeneratePerformanceData(void) { return false; } - bool AvHNexus::isRecordingGame(void) { return false; } - void AvHNexus::cancelGame(void) {} - void AvHNexus::finishGame(void) {} - void AvHNexus::startGame(void) {} - void AvHNexus::startup(void) {} - void AvHNexus::shutdown(void) {} +namespace AvHNexus { + bool send(entvars_t* const pev, const unsigned char* data, const unsigned int length) { return false; } + bool recv(entvars_t* const pev, const char* data, const unsigned int length) { return false; } + string getNetworkID(const edict_t* edict) { return AvHSUGetPlayerAuthIDString((edict_t *)edict); } + void handleUnauthorizedJoinTeamAttempt(const edict_t* edict, const unsigned char team_index) {} + void performSpeedTest(void) {} + void processResponses(void) {} + void setGeneratePerformanceData(const edict_t* edict, const bool generate) {} + bool getGeneratePerformanceData(void) { return false; } + bool isRecordingGame(void) { return false; } + void cancelGame(void) {} + void finishGame(void) {} + void startGame(void) {} + void startup(void) {} + void shutdown(void) {} +} #else #include #include "AvHNexusServer.h" diff --git a/main/source/mod/AvHParticleSystem.cpp b/main/source/mod/AvHParticleSystem.cpp index b463d335..29563386 100644 --- a/main/source/mod/AvHParticleSystem.cpp +++ b/main/source/mod/AvHParticleSystem.cpp @@ -688,9 +688,9 @@ AvHParticleSystem::GenerateParticles(int inNumberParticles) else if(theDomain == PS_Sphere) { float theRadius1 = fabs(this->mGenerationEntityAbsMax[0] - this->mGenerationEntityAbsMin[0])/2.0f; - theRadius1 = max(theRadius1 - theEdgeInset, 0); + theRadius1 = max(theRadius1 - theEdgeInset, 0.0f); float theRadius2 = this->mGenerationEntityParam; - theRadius2 = max(theRadius2 - theEdgeInset, 0); + theRadius2 = max(theRadius2 - theEdgeInset, 0.0f); theParams[3] = theRadius1; theParams[4] = theRadius2; } diff --git a/main/source/mod/AvHParticleSystem.h b/main/source/mod/AvHParticleSystem.h index d9428b32..aef5a83a 100644 --- a/main/source/mod/AvHParticleSystem.h +++ b/main/source/mod/AvHParticleSystem.h @@ -205,4 +205,4 @@ private: }; -#endif \ No newline at end of file +#endif diff --git a/main/source/mod/AvHParticleSystemManager.h b/main/source/mod/AvHParticleSystemManager.h index 3e6d0563..1a7802e1 100644 --- a/main/source/mod/AvHParticleSystemManager.h +++ b/main/source/mod/AvHParticleSystemManager.h @@ -19,6 +19,7 @@ #ifndef AVH_PSMANAGER_H #define AVH_PSMANAGER_H +#include #include "util/nowarnings.h" #include "types.h" #include "mod/AvHParticleSystem.h" @@ -169,4 +170,4 @@ private: }; -#endif \ No newline at end of file +#endif diff --git a/main/source/mod/AvHParticleTemplate.cpp b/main/source/mod/AvHParticleTemplate.cpp index 2ee9cbf9..e97ed170 100644 --- a/main/source/mod/AvHParticleTemplate.cpp +++ b/main/source/mod/AvHParticleTemplate.cpp @@ -1,3 +1,4 @@ +#include #include "util/nowarnings.h" #include "mod/AvHParticleTemplate.h" #include "common/renderingconst.h" diff --git a/main/source/mod/AvHPlayer.cpp b/main/source/mod/AvHPlayer.cpp index 3ac61825..c50acaab 100644 --- a/main/source/mod/AvHPlayer.cpp +++ b/main/source/mod/AvHPlayer.cpp @@ -6878,7 +6878,7 @@ void AvHPlayer::InternalMovementThink() float factor = pushbackfactor / (radius / distance); float weigthFactor=1.0f; - int veriticalLimit=110; + float veriticalLimit=110.0f; switch(theEntity->pev->iuser3) { case AVH_USER3_ALIEN_PLAYER4: weigthFactor=0.70f; @@ -9227,7 +9227,7 @@ bool AvHPlayer::RunClientScript(const string& inScriptName) void AvHPlayer::PrintWeaponListToClient(CBaseEntity *theAvHPlayer) { char msg[1024]; - sprintf(msg, "Weapons for %s:\n", this->GetPlayerName()); + sprintf(msg, "Weapons for %s:\n", this->GetPlayerName().c_str()); ClientPrint(theAvHPlayer->pev, HUD_PRINTNOTIFY, msg); for(int i = 0; i < MAX_ITEM_TYPES; i++) diff --git a/main/source/mod/AvHScriptClient.cpp b/main/source/mod/AvHScriptClient.cpp index 9d4b8d3c..9b5bbae3 100644 --- a/main/source/mod/AvHScriptClient.cpp +++ b/main/source/mod/AvHScriptClient.cpp @@ -46,13 +46,16 @@ #include "common/event_api.h" #include "mod/AvHScriptManager.h" +#ifdef USE_LUA extern "C" { #include } +#endif extern void DrawScaledHUDSprite(int inSpriteHandle, int inMode, int inRowsInSprite = 1, int inX = 0, int inY = 0, int inWidth = ScreenWidth(), int inHeight = ScreenHeight(), int inForceSpriteFrame = -1, float inStartU = 0.0f, float inStartV = 0.0f, float inEndU = 1.0f, float inEndV = 1.0f, float inRotateUVRadians = 0.0f, bool inUVWrapsOverFrames = false); extern vec3_t v_origin; +#ifdef USE_LUA static int errormessage(lua_State* inState) { const char *s = lua_tostring(inState, 1); @@ -93,6 +96,7 @@ static int print(lua_State* inState) return 0; } + // Runs a client command, returns success static int clientCommand(lua_State* inState) { @@ -438,9 +442,11 @@ static int triFog(lua_State* inState) return 1; } +#endif void AvHScriptInstance::InitClient() { +#ifdef USE_LUA //lua_register(this->mState, LUA_ERRORMESSAGE, errormessage); lua_register(this->mState, "print", print); lua_register(this->mState, "clientCommand", clientCommand); @@ -469,4 +475,5 @@ void AvHScriptInstance::InitClient() //lua_register(this->mState, "drawScaledHUDSprite", drawScaledHUDSpriteUV); //lua_register(this->mState, "drawScaledTiledHUDSprite", drawScaledTiledHUDSprite); -} \ No newline at end of file +#endif +} diff --git a/main/source/mod/AvHScriptManager.cpp b/main/source/mod/AvHScriptManager.cpp index 77f86075..0f5ae8d4 100644 --- a/main/source/mod/AvHScriptManager.cpp +++ b/main/source/mod/AvHScriptManager.cpp @@ -28,17 +28,21 @@ #include "util/STLUtil.h" #include "mod/AvHConstants.h" +#ifdef USE_LUA extern "C" { #include #include #include } +#endif AvHScriptInstance* gRunningScript = NULL; AvHScriptInstance::AvHScriptInstance(string inScriptName) { +#ifdef USE_LUA this->mState = NULL; +#endif this->Init(); @@ -70,10 +74,12 @@ void AvHScriptInstance::CallSimpleFunction(const string& inFunctionName) { gRunningScript = this; +#ifdef USE_LUA // Execute callback lua_getglobal(this->mState, inFunctionName.c_str()); //lua_pushstring(this->mState, inFunctionName.c_str()); lua_call(this->mState, 0, 0); +#endif gRunningScript = NULL; } @@ -83,16 +89,21 @@ void AvHScriptInstance::Cleanup() ASSERT(!this->CallbacksPending()); ASSERT(this->mState); +#ifdef USE_LUA lua_close(this->mState); +#endif } +#ifdef USE_LUA lua_State* AvHScriptInstance::GetState() { return this->mState; } +#endif void AvHScriptInstance::Init() { +#ifdef USE_LUA this->mState = lua_open(); lua_baselibopen(this->mState); @@ -107,6 +118,7 @@ void AvHScriptInstance::Init() #else this->InitClient(); #endif +#endif } void AvHScriptInstance::Reset() @@ -127,7 +139,9 @@ void AvHScriptInstance::Run() // Set global current script so it's accessible statically (needed for setting callbacks) gRunningScript = this; +#ifdef USE_LUA lua_dofile(this->mState, this->mScriptName.c_str()); +#endif gRunningScript = NULL; } @@ -235,6 +249,7 @@ void AvHScriptManager::Update(float inTime) #ifdef AVH_CLIENT void AvHScriptManager::ClientUpdate(float inTimePassed) { +#ifdef USE_LUA // For all scripts for(AvHScriptInstanceListType::iterator theIter = this->mScriptList.begin(); theIter != this->mScriptList.end(); /* no increment */) { @@ -269,6 +284,7 @@ void AvHScriptManager::ClientUpdate(float inTimePassed) theIter++; } } +#endif } void AvHScriptManager::DrawNormal() @@ -301,6 +317,7 @@ void AvHScriptManager::DrawNoZBuffering() bool AvHScriptManager::GetClientMove(int& outButtonBits, int& outImpulse) { bool theSuccess = false; +#ifdef USE_LUA #ifdef DEBUG @@ -335,6 +352,7 @@ bool AvHScriptManager::GetClientMove(int& outButtonBits, int& outImpulse) #endif +#endif return theSuccess; } diff --git a/main/source/mod/AvHScriptManager.h b/main/source/mod/AvHScriptManager.h index b8c8a793..9afd94e5 100644 --- a/main/source/mod/AvHScriptManager.h +++ b/main/source/mod/AvHScriptManager.h @@ -23,7 +23,9 @@ #include "util/Checksum.h" #include "util/STLUtil.h" +#ifdef USE_LUA extern "C" struct lua_State; +#endif class AvHScriptInstance { @@ -38,7 +40,9 @@ public: void Cleanup(); +#ifdef USE_LUA lua_State* GetState(); +#endif void Reset(); @@ -58,7 +62,9 @@ private: #endif // This gets copied around, so make sure elements can be shallow-copied or write a copy constructor +#ifdef USE_LUA lua_State* mState; +#endif string mScriptName; typedef pair CallbackType; @@ -96,4 +102,4 @@ private: }; -#endif \ No newline at end of file +#endif diff --git a/main/source/mod/AvHScriptServer.cpp b/main/source/mod/AvHScriptServer.cpp index c1adc5eb..d920b29e 100644 --- a/main/source/mod/AvHScriptServer.cpp +++ b/main/source/mod/AvHScriptServer.cpp @@ -33,6 +33,7 @@ #include "util/Checksum.h" #include "util/STLUtil.h" +#ifdef USE_LUA extern "C" { #include } @@ -545,8 +546,11 @@ static int setServerVariable(lua_State* inState) return 0; } +#endif + void AvHScriptInstance::InitServer() { +#ifdef USE_LUA //lua_register(this->mState, LUA_ERRORMESSAGE, errormessage); //lua_register(this->mState, "execute", execute); @@ -579,4 +583,6 @@ void AvHScriptInstance::InitServer() lua_register(this->mState, "runClientScript", runClientScript); lua_register(this->mState, "setServerVariable", setServerVariable); -} \ No newline at end of file +#endif +} + diff --git a/main/source/mod/AvHScriptShared.cpp b/main/source/mod/AvHScriptShared.cpp index dfd29ab5..617aabe1 100644 --- a/main/source/mod/AvHScriptShared.cpp +++ b/main/source/mod/AvHScriptShared.cpp @@ -37,6 +37,7 @@ #include "mod/AvHSharedUtil.h" +#ifdef USE_LUA extern "C" { #include } @@ -93,3 +94,4 @@ void AvHScriptInstance::InitShared() lua_register(this->mState, "getTime", getTime); } +#endif diff --git a/main/source/particle/Makefile b/main/source/particle/Makefile new file mode 100644 index 00000000..3b05842f --- /dev/null +++ b/main/source/particle/Makefile @@ -0,0 +1,43 @@ +###################################################################### +# Particle System API +# +# Copyright 1998 by David K. McAllister. +# +###################################################################### + +C++ = g++ + +GLUT_HOME =/usr/local/contrib/unmoderated + +MP = #-mp -DPARTICLE_MP + +# Make it real fast on an Origin 2000. +#LNO =-LNO:opt=1:fusion=2:fission=2:fusion_peeling_limit=2048:cs1=32K:cs2=8M +COPT = $(MP) -O3 $(LNO) -fPIC + +CFLAGS = $(COPT) $(COMPFLAGS) -I. -I.. -I$(GLUT_HOME)/include -I/usr/include/c++/4.8/ + +POBJS =action_api.o actions.o opengl.o system.o + +ALL = libparticleMP.a SPDir + +all: $(ALL) + +# following line needed for c++ .cc files +.SUFFIXES : .cpp + +.cpp.o: + $(C++) $(CFLAGS) -c $< + +libparticleMP.a: $(POBJS) + rm -f $@ + ar clq $@ $(POBJS) + +SPDir: + (cd SP ; smake) + +clean: + (cd SP ; smake clean) + rm -f libpar* + rm -f *.o + rm -rf *ii_files diff --git a/main/source/particle/ParticleDLL.dsp b/main/source/particle/ParticleDLL.dsp new file mode 100644 index 00000000..38049cc9 --- /dev/null +++ b/main/source/particle/ParticleDLL.dsp @@ -0,0 +1,132 @@ +# Microsoft Developer Studio Project File - Name="ParticleDLL" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=ParticleDLL - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "ParticleDLL.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "ParticleDLL.mak" CFG="ParticleDLL - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "ParticleDLL - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "ParticleDLL - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "ParticleDLL - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /I ".." /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "PARTICLEDLL_EXPORTS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 opengl32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 +# SUBTRACT LINK32 /profile + +!ELSEIF "$(CFG)" == "ParticleDLL - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /I ".." /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "PARTICLEDLL_EXPORTS" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 opengl32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "ParticleDLL - Win32 Release" +# Name "ParticleDLL - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\action_api.cpp +# End Source File +# Begin Source File + +SOURCE=.\actions.cpp +# End Source File +# Begin Source File + +SOURCE=.\opengl.cpp +# End Source File +# Begin Source File + +SOURCE=.\system.cpp +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter ".h" +# Begin Source File + +SOURCE=.\general.h +# End Source File +# Begin Source File + +SOURCE=.\p_vector.h +# End Source File +# Begin Source File + +SOURCE=.\papi.h +# End Source File +# End Group +# End Target +# End Project diff --git a/main/source/particle/Readme.txt b/main/source/particle/Readme.txt new file mode 100644 index 00000000..9e1847b9 --- /dev/null +++ b/main/source/particle/Readme.txt @@ -0,0 +1,4 @@ +For SGI we make a version of the library that's made for +multiprocessing in this directory and a version that's made for single +processing in the SP directory. Typing make in this directory will make both. + diff --git a/main/source/particle/action_api.cpp b/main/source/particle/action_api.cpp new file mode 100644 index 00000000..c7ecc7e2 --- /dev/null +++ b/main/source/particle/action_api.cpp @@ -0,0 +1,390 @@ +// action_api.cpp +// +// Copyright 1997-1998 by David K. McAllister +// +// This file implements the action API calls by creating +// action class instances, which are either executed or +// added to an action list. + +#include "general.h" + +extern void _pAddActionToList(ParticleAction *S, int size); +extern void _pCallActionList(ParticleAction *pa, int num_actions, + ParticleGroup *pg); + +// Do not call this function. +void _pSendAction(ParticleAction *S, PActionEnum type, int size) +{ + _ParticleState &_ps = _GetPState(); + + S->type = type; + + if(_ps.in_new_list) + { + _pAddActionToList(S, size); + } + else + { + // Immediate mode. Execute it. + // This is a hack to give them local access to dt. + S->dt = _ps.dt; + _pCallActionList(S, 1, _ps.pgrp); + } +} + +PARTICLEDLL_API void pAvoid(float magnitude, float epsilon, float look_ahead, + PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + PAAvoid S; + + S.position = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); + S.magnitude = magnitude; + S.epsilon = epsilon; + S.look_ahead = look_ahead; + + _pSendAction(&S, PAAvoidID, sizeof(PAAvoid)); +} + +PARTICLEDLL_API void pBounce(float friction, float resilience, float cutoff, + PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + PABounce S; + + S.position = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); + S.oneMinusFriction = 1.0f - friction; + S.resilience = resilience; + S.cutoffSqr = fsqr(cutoff); + + _pSendAction(&S, PABounceID, sizeof(PABounce)); +} + +PARTICLEDLL_API void pCopyVertexB(bool copy_pos, bool copy_vel) +{ + PACopyVertexB S; + + S.copy_pos = copy_pos; + S.copy_vel = copy_vel; + + _pSendAction(&S, PACopyVertexBID, sizeof(PACopyVertexB)); +} + +PARTICLEDLL_API void pDamping(float damping_x, float damping_y, float damping_z, + float vlow, float vhigh) +{ + PADamping S; + + S.damping = pVector(damping_x, damping_y, damping_z); + S.vlowSqr = fsqr(vlow); + S.vhighSqr = fsqr(vhigh); + + _pSendAction(&S, PADampingID, sizeof(PADamping)); +} + +PARTICLEDLL_API void pExplosion(float center_x, float center_y, float center_z, float velocity, + float magnitude, float stdev, float epsilon, float age) +{ + PAExplosion S; + + S.center = pVector(center_x, center_y, center_z); + S.velocity = velocity; + S.magnitude = magnitude; + S.stdev = stdev; + S.epsilon = epsilon; + S.age = age; + + if(S.epsilon < 0.0f) + S.epsilon = P_EPS; + + _pSendAction(&S, PAExplosionID, sizeof(PAExplosion)); +} + +PARTICLEDLL_API void pFollow(float magnitude, float epsilon, float max_radius) +{ + PAFollow S; + + S.magnitude = magnitude; + S.epsilon = epsilon; + S.max_radius = max_radius; + + _pSendAction(&S, PAFollowID, sizeof(PAFollow)); +} + +PARTICLEDLL_API void pGravitate(float magnitude, float epsilon, float max_radius) +{ + PAGravitate S; + + S.magnitude = magnitude; + S.epsilon = epsilon; + S.max_radius = max_radius; + + _pSendAction(&S, PAGravitateID, sizeof(PAGravitate)); +} + +PARTICLEDLL_API void pGravity(float dir_x, float dir_y, float dir_z) +{ + PAGravity S; + + S.direction = pVector(dir_x, dir_y, dir_z); + + _pSendAction(&S, PAGravityID, sizeof(PAGravity)); +} + +PARTICLEDLL_API void pJet(float center_x, float center_y, float center_z, + float magnitude, float epsilon, float max_radius) +{ + _ParticleState &_ps = _GetPState(); + + PAJet S; + + S.center = pVector(center_x, center_y, center_z); + S.acc = _ps.Vel; + S.magnitude = magnitude; + S.epsilon = epsilon; + S.max_radius = max_radius; + + _pSendAction(&S, PAJetID, sizeof(PAJet)); +} + +PARTICLEDLL_API void pKillOld(float age_limit, bool kill_less_than) +{ + PAKillOld S; + + S.age_limit = age_limit; + S.kill_less_than = kill_less_than; + + _pSendAction(&S, PAKillOldID, sizeof(PAKillOld)); +} + +PARTICLEDLL_API void pMatchVelocity(float magnitude, float epsilon, float max_radius) +{ + PAMatchVelocity S; + + S.magnitude = magnitude; + S.epsilon = epsilon; + S.max_radius = max_radius; + + _pSendAction(&S, PAMatchVelocityID, sizeof(PAMatchVelocity)); +} + +PARTICLEDLL_API void pMove() +{ + PAMove S; + + _pSendAction(&S, PAMoveID, sizeof(PAMove)); +} + +PARTICLEDLL_API void pOrbitLine(float p_x, float p_y, float p_z, + float axis_x, float axis_y, float axis_z, + float magnitude, float epsilon, float max_radius) +{ + PAOrbitLine S; + + S.p = pVector(p_x, p_y, p_z); + S.axis = pVector(axis_x, axis_y, axis_z); + S.axis.normalize(); + S.magnitude = magnitude; + S.epsilon = epsilon; + S.max_radius = max_radius; + + _pSendAction(&S, PAOrbitLineID, sizeof(PAOrbitLine)); +} + +PARTICLEDLL_API void pOrbitPoint(float center_x, float center_y, float center_z, + float magnitude, float epsilon, float max_radius) +{ + PAOrbitPoint S; + + S.center = pVector(center_x, center_y, center_z); + S.magnitude = magnitude; + S.epsilon = epsilon; + S.max_radius = max_radius; + + _pSendAction(&S, PAOrbitPointID, sizeof(PAOrbitPoint)); +} + +PARTICLEDLL_API void pRandomAccel(PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + PARandomAccel S; + + S.gen_acc = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); + + _pSendAction(&S, PARandomAccelID, sizeof(PARandomAccel)); +} + +PARTICLEDLL_API void pRandomDisplace(PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + PARandomDisplace S; + + S.gen_disp = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); + + _pSendAction(&S, PARandomDisplaceID, sizeof(PARandomDisplace)); +} + +PARTICLEDLL_API void pRandomVelocity(PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + PARandomVelocity S; + + S.gen_vel = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); + + _pSendAction(&S, PARandomVelocityID, sizeof(PARandomVelocity)); +} + +PARTICLEDLL_API void pRestore(float time_left) +{ + PARestore S; + + S.time_left = time_left; + + _pSendAction(&S, PARestoreID, sizeof(PARestore)); +} + +PARTICLEDLL_API void pSink(bool kill_inside, PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + PASink S; + + S.kill_inside = kill_inside; + S.position = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); + + _pSendAction(&S, PASinkID, sizeof(PASink)); +} + +PARTICLEDLL_API void pSinkVelocity(bool kill_inside, PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + PASinkVelocity S; + + S.kill_inside = kill_inside; + S.velocity = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); + + _pSendAction(&S, PASinkVelocityID, sizeof(PASinkVelocity)); +} + +PARTICLEDLL_API void pSource(float particle_rate, PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + _ParticleState &_ps = _GetPState(); + + PASource S; + + S.particle_rate = particle_rate; + S.position = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); + S.positionB = _ps.VertexB; + S.size = _ps.Size; + S.velocity = _ps.Vel; + S.color = _ps.Color; + S.alpha = _ps.Alpha; + S.age = _ps.Age; + S.age_sigma = _ps.AgeSigma; + S.vertexB_tracks = _ps.vertexB_tracks; + + _pSendAction(&S, PASourceID, sizeof(PASource)); +} + +PARTICLEDLL_API void pSpeedLimit(float min_speed, float max_speed) +{ + PASpeedLimit S; + + S.min_speed = min_speed; + S.max_speed = max_speed; + + _pSendAction(&S, PASpeedLimitID, sizeof(PASpeedLimit)); +} + +PARTICLEDLL_API void pTargetColor(float color_x, float color_y, float color_z, + float alpha, float scale) +{ + PATargetColor S; + + S.color = pVector(color_x, color_y, color_z); + S.alpha = alpha; + S.scale = scale; + + _pSendAction(&S, PATargetColorID, sizeof(PATargetColor)); +} + +PARTICLEDLL_API void pTargetSize(float size_x, float size_y, float size_z, + float scale_x, float scale_y, float scale_z) +{ + PATargetSize S; + + S.size = pVector(size_x, size_y, size_z); + S.scale = pVector(scale_x, scale_y, scale_z); + + _pSendAction(&S, PATargetSizeID, sizeof(PATargetSize)); +} + +PARTICLEDLL_API void pTargetVelocity(float vel_x, float vel_y, float vel_z, float scale) +{ + PATargetVelocity S; + + S.velocity = pVector(vel_x, vel_y, vel_z); + S.scale = scale; + + _pSendAction(&S, PATargetVelocityID, sizeof(PATargetVelocity)); +} + +// If in immediate mode, quickly add a vertex. +// If building an action list, call pSource. +PARTICLEDLL_API void pVertex(float x, float y, float z) +{ + _ParticleState &_ps = _GetPState(); + + if(_ps.in_new_list) + { + pSource(1, PDPoint, x, y, z); + return; + } + + // Immediate mode. Quickly add the vertex. + if(_ps.pgrp == NULL) + return; + + pVector pos(x, y, z); + pVector siz, vel, col, posB; + if(_ps.vertexB_tracks) + posB = pos; + else + _ps.VertexB.Generate(posB); + _ps.Size.Generate(siz); + _ps.Vel.Generate(vel); + _ps.Color.Generate(col); + _ps.pgrp->Add(pos, posB, siz, vel, col, _ps.Alpha, _ps.Age); +} + +PARTICLEDLL_API void pVortex(float center_x, float center_y, float center_z, + float axis_x, float axis_y, float axis_z, + float magnitude, float epsilon, float max_radius) +{ + PAVortex S; + + S.center = pVector(center_x, center_y, center_z); + S.axis = pVector(axis_x, axis_y, axis_z); + S.axis.normalize(); + S.magnitude = magnitude; + S.epsilon = epsilon; + S.max_radius = max_radius; + + _pSendAction(&S, PAVortexID, sizeof(PAVortex)); +} diff --git a/main/source/particle/actions.cpp b/main/source/particle/actions.cpp new file mode 100644 index 00000000..ce053e12 --- /dev/null +++ b/main/source/particle/actions.cpp @@ -0,0 +1,1840 @@ +// actions.cpp +// +// Copyright 1997-1998 by David K. McAllister +// +// I used code Copyright 1997 by Jonathan P. Leech +// as an example in implenting this. +// +// This file implements the dynamics of particle actions. + +#include "general.h" +#include +#include + +#define SQRT2PI 2.506628274631000502415765284811045253006 +#define ONEOVERSQRT2PI (1. / SQRT2PI) + +// To offset [0 .. 1] vectors to [-.5 .. .5] +static pVector vHalf(0.5, 0.5, 0.5); + +static inline pVector RandVec() +{ + return pVector(drand48(), drand48(), drand48()); +} + +// Return a random number with a normal distribution. +static inline float NRand(float sigma = 1.0f) +{ +#define ONE_OVER_SIGMA_EXP (1.0f / 0.7975f) + + if(sigma == 0) return 0; + + float y; + do + { + y = -logf(drand48()); + } + while(drand48() > expf(-fsqr(y - 1.0f)*0.5f)); + + if(rand() & 0x1) + return y * sigma * ONE_OVER_SIGMA_EXP; + else + return -y * sigma * ONE_OVER_SIGMA_EXP; +} + +void PAAvoid::Execute(ParticleGroup *group) +{ + float magdt = magnitude * dt; + + switch(position.type) + { + case PDPlane: + { + if(look_ahead < P_MAXFLOAT) + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // p2 stores the plane normal (the a,b,c of the plane eqn). + // Old and new distances: dist(p,plane) = n * p + d + // radius1 stores -n*p, which is d. + float dist = m.pos * position.p2 + position.radius1; + + if(dist < look_ahead) + { + float vm = m.vel.length(); + pVector Vn = m.vel / vm; + // float dot = Vn * position.p2; + + pVector tmp = (position.p2 * (magdt / (dist*dist+epsilon))) + Vn; + m.vel = tmp * (vm / tmp.length()); + } + } + } + else + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // p2 stores the plane normal (the a,b,c of the plane eqn). + // Old and new distances: dist(p,plane) = n * p + d + // radius1 stores -n*p, which is d. + float dist = m.pos * position.p2 + position.radius1; + + float vm = m.vel.length(); + pVector Vn = m.vel / vm; + // float dot = Vn * position.p2; + + pVector tmp = (position.p2 * (magdt / (dist*dist+epsilon))) + Vn; + m.vel = tmp * (vm / tmp.length()); + } + } + } + break; + case PDRectangle: + { + // Compute the inverse matrix of the plane basis. + pVector &u = position.u; + pVector &v = position.v; + + // The normalized bases are needed inside the loop. + pVector un = u / position.radius1Sqr; + pVector vn = v / position.radius2Sqr; + + // w = u cross v + float wx = u.y*v.z-u.z*v.y; + float wy = u.z*v.x-u.x*v.z; + float wz = u.x*v.y-u.y*v.x; + + float det = 1/(wz*u.x*v.y-wz*u.y*v.x-u.z*wx*v.y-u.x*v.z*wy+v.z*wx*u.y+u.z*v.x*wy); + + pVector s1((v.y*wz-v.z*wy), (v.z*wx-v.x*wz), (v.x*wy-v.y*wx)); + s1 *= det; + pVector s2((u.y*wz-u.z*wy), (u.z*wx-u.x*wz), (u.x*wy-u.y*wx)); + s2 *= -det; + + // See which particles bounce. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // See if particle's current and next positions cross plane. + // If not, couldn't bounce, so keep going. + pVector pnext(m.pos + m.vel * dt * look_ahead); + + // p2 stores the plane normal (the a,b,c of the plane eqn). + // Old and new distances: dist(p,plane) = n * p + d + // radius1 stores -n*p, which is d. + float distold = m.pos * position.p2 + position.radius1; + float distnew = pnext * position.p2 + position.radius1; + + // Opposite signs if product < 0 + // There is no faster way to do this. + if(distold * distnew >= 0) + continue; + + float nv = position.p2 * m.vel; + float t = -distold / nv; + + // Actual intersection point p(t) = pos + vel t + pVector phit(m.pos + m.vel * t); + + // Offset from origin in plane, p - origin + pVector offset(phit - position.p1); + + // Dot product with basis vectors of old frame + // in terms of new frame gives position in uv frame. + float upos = offset * s1; + float vpos = offset * s2; + + // Did it cross plane outside triangle? + if(upos < 0 || vpos < 0 || upos > 1 || vpos > 1) + continue; + + // A hit! A most palpable hit! + // Compute distance to the three edges. + pVector uofs = (un * (un * offset)) - offset; + float udistSqr = uofs.length2(); + pVector vofs = (vn * (vn * offset)) - offset; + float vdistSqr = vofs.length2(); + + pVector foffset((u + v) - offset); + pVector fofs = (un * (un * foffset)) - foffset; + float fdistSqr = fofs.length2(); + pVector gofs = (un * (un * foffset)) - foffset; + float gdistSqr = gofs.length2(); + + pVector S; + if(udistSqr <= vdistSqr && udistSqr <= fdistSqr + && udistSqr <= gdistSqr) S = uofs; + else if(vdistSqr <= fdistSqr && vdistSqr <= gdistSqr) S = vofs; + else if(fdistSqr <= gdistSqr) S = fofs; + else S = gofs; + + S.normalize(); + + // We now have a vector to safety. + float vm = m.vel.length(); + pVector Vn = m.vel / vm; + + // Blend S into V. + pVector tmp = (S * (magdt / (t*t+epsilon))) + Vn; + m.vel = tmp * (vm / tmp.length()); + } + } + break; + case PDTriangle: + { + // Compute the inverse matrix of the plane basis. + pVector &u = position.u; + pVector &v = position.v; + + // The normalized bases are needed inside the loop. + pVector un = u / position.radius1Sqr; + pVector vn = v / position.radius2Sqr; + + // f is the third (non-basis) triangle edge. + pVector f = v - u; + pVector fn(f); + fn.normalize(); + + // w = u cross v + float wx = u.y*v.z-u.z*v.y; + float wy = u.z*v.x-u.x*v.z; + float wz = u.x*v.y-u.y*v.x; + + float det = 1/(wz*u.x*v.y-wz*u.y*v.x-u.z*wx*v.y-u.x*v.z*wy+v.z*wx*u.y+u.z*v.x*wy); + + pVector s1((v.y*wz-v.z*wy), (v.z*wx-v.x*wz), (v.x*wy-v.y*wx)); + s1 *= det; + pVector s2((u.y*wz-u.z*wy), (u.z*wx-u.x*wz), (u.x*wy-u.y*wx)); + s2 *= -det; + + // See which particles bounce. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // See if particle's current and next positions cross plane. + // If not, couldn't bounce, so keep going. + pVector pnext(m.pos + m.vel * dt * look_ahead); + + // p2 stores the plane normal (the a,b,c of the plane eqn). + // Old and new distances: dist(p,plane) = n * p + d + // radius1 stores -n*p, which is d. + float distold = m.pos * position.p2 + position.radius1; + float distnew = pnext * position.p2 + position.radius1; + + // Opposite signs if product < 0 + // Is there a faster way to do this? + if(distold * distnew >= 0) + continue; + + float nv = position.p2 * m.vel; + float t = -distold / nv; + + // Actual intersection point p(t) = pos + vel t + pVector phit(m.pos + m.vel * t); + + // Offset from origin in plane, p - origin + pVector offset(phit - position.p1); + + // Dot product with basis vectors of old frame + // in terms of new frame gives position in uv frame. + float upos = offset * s1; + float vpos = offset * s2; + + // Did it cross plane outside triangle? + if(upos < 0 || vpos < 0 || (upos + vpos) > 1) + continue; + + // A hit! A most palpable hit! + // Compute distance to the three edges. + pVector uofs = (un * (un * offset)) - offset; + float udistSqr = uofs.length2(); + pVector vofs = (vn * (vn * offset)) - offset; + float vdistSqr = vofs.length2(); + pVector foffset(offset - u); + pVector fofs = (fn * (fn * foffset)) - foffset; + float fdistSqr = fofs.length2(); + pVector S; + if(udistSqr <= vdistSqr && udistSqr <= fdistSqr) S = uofs; + else if(vdistSqr <= fdistSqr) S = vofs; + else S = fofs; + + S.normalize(); + + // We now have a vector to safety. + float vm = m.vel.length(); + pVector Vn = m.vel / vm; + + // Blend S into V. + pVector tmp = (S * (magdt / (t*t+epsilon))) + Vn; + m.vel = tmp * (vm / tmp.length()); + } + } + break; + case PDDisc: + { + float r1Sqr = fsqr(position.radius1); + float r2Sqr = fsqr(position.radius2); + + // See which particles bounce. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // See if particle's current and next positions cross plane. + // If not, couldn't bounce, so keep going. + pVector pnext(m.pos + m.vel * dt * look_ahead); + + // p2 stores the plane normal (the a,b,c of the plane eqn). + // Old and new distances: dist(p,plane) = n * p + d + // radius1 stores -n*p, which is d. radius1Sqr stores d. + float distold = m.pos * position.p2 + position.radius1Sqr; + float distnew = pnext * position.p2 + position.radius1Sqr; + + // Opposite signs if product < 0 + // Is there a faster way to do this? + if(distold * distnew >= 0) + continue; + + // Find position at the crossing point by parameterizing + // p(t) = pos + vel * t + // Solve dist(p(t),plane) = 0 e.g. + // n * p(t) + D = 0 -> + // n * p + t (n * v) + D = 0 -> + // t = -(n * p + D) / (n * v) + // Could factor n*v into distnew = distold + n*v and save a bit. + // Safe since n*v != 0 assured by quick rejection test. + // This calc is indep. of dt because we have established that it + // will hit before dt. We just want to know when. + float nv = position.p2 * m.vel; + float t = -distold / nv; + + // Actual intersection point p(t) = pos + vel t + pVector phit(m.pos + m.vel * t); + + // Offset from origin in plane, phit - origin + pVector offset(phit - position.p1); + + float rad = offset.length2(); + + if(rad > r1Sqr || rad < r2Sqr) + continue; + + // A hit! A most palpable hit! + pVector S = offset; + S.normalize(); + + // We now have a vector to safety. + float vm = m.vel.length(); + pVector Vn = m.vel / vm; + + // Blend S into V. + pVector tmp = (S * (magdt / (t*t+epsilon))) + Vn; + m.vel = tmp * (vm / tmp.length()); + } + } + break; + case PDSphere: + { + float rSqr = position.radius1 * position.radius1; + + // See which particles are aimed toward the sphere. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // First do a ray-sphere intersection test and + // see if it's soon enough. + // Can I do this faster without t? + float vm = m.vel.length(); + pVector Vn = m.vel / vm; + + pVector L = position.p1 - m.pos; + float v = L * Vn; + + float disc = rSqr - (L * L) + v * v; + if(disc < 0) + continue; // I'm not heading toward it. + + // Compute length for second rejection test. + float t = v - sqrtf(disc); + if(t < 0 || t > (vm * look_ahead)) + continue; + + // Get a vector to safety. + pVector C = Vn ^ L; + C.normalize(); + pVector S = Vn ^ C; + + // Blend S into V. + pVector tmp = (S * (magdt / (t*t+epsilon))) + Vn; + m.vel = tmp * (vm / tmp.length()); + } + } + break; + } +} + +void PABounce::Execute(ParticleGroup *group) +{ + switch(position.type) + { + case PDTriangle: + { + // Compute the inverse matrix of the plane basis. + pVector &u = position.u; + pVector &v = position.v; + + // w = u cross v + float wx = u.y*v.z-u.z*v.y; + float wy = u.z*v.x-u.x*v.z; + float wz = u.x*v.y-u.y*v.x; + + float det = 1/(wz*u.x*v.y-wz*u.y*v.x-u.z*wx*v.y-u.x*v.z*wy+v.z*wx*u.y+u.z*v.x*wy); + + pVector s1((v.y*wz-v.z*wy), (v.z*wx-v.x*wz), (v.x*wy-v.y*wx)); + s1 *= det; + pVector s2((u.y*wz-u.z*wy), (u.z*wx-u.x*wz), (u.x*wy-u.y*wx)); + s2 *= -det; + + // See which particles bounce. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // See if particle's current and next positions cross plane. + // If not, couldn't bounce, so keep going. + pVector pnext(m.pos + m.vel * dt); + + // p2 stores the plane normal (the a,b,c of the plane eqn). + // Old and new distances: dist(p,plane) = n * p + d + // radius1 stores -n*p, which is d. + float distold = m.pos * position.p2 + position.radius1; + float distnew = pnext * position.p2 + position.radius1; + + // Opposite signs if product < 0 + // Is there a faster way to do this? + if(distold * distnew >= 0) + continue; + + // Find position at the crossing point by parameterizing + // p(t) = pos + vel * t + // Solve dist(p(t),plane) = 0 e.g. + // n * p(t) + D = 0 -> + // n * p + t (n * v) + D = 0 -> + // t = -(n * p + D) / (n * v) + // Could factor n*v into distnew = distold + n*v and save a bit. + // Safe since n*v != 0 assured by quick rejection test. + // This calc is indep. of dt because we have established that it + // will hit before dt. We just want to know when. + float nv = position.p2 * m.vel; + float t = -distold / nv; + + // Actual intersection point p(t) = pos + vel t + pVector phit(m.pos + m.vel * t); + + // Offset from origin in plane, p - origin + pVector offset(phit - position.p1); + + // Dot product with basis vectors of old frame + // in terms of new frame gives position in uv frame. + float upos = offset * s1; + float vpos = offset * s2; + + // Did it cross plane outside triangle? + if(upos < 0 || vpos < 0 || (upos + vpos) > 1) + continue; + + // A hit! A most palpable hit! + + // Compute tangential and normal components of velocity + pVector vn(position.p2 * nv); // Normal Vn = (V.N)N + pVector vt(m.vel - vn); // Tangent Vt = V - Vn + + // Compute new velocity heading out: + // Don't apply friction if tangential velocity < cutoff + if(vt.length2() <= cutoffSqr) + m.vel = vt - vn * resilience; + else + m.vel = vt * oneMinusFriction - vn * resilience; + } + } + break; + case PDDisc: + { + float r1Sqr = fsqr(position.radius1); + float r2Sqr = fsqr(position.radius2); + + // See which particles bounce. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // See if particle's current and next positions cross plane. + // If not, couldn't bounce, so keep going. + pVector pnext(m.pos + m.vel * dt); + + // p2 stores the plane normal (the a,b,c of the plane eqn). + // Old and new distances: dist(p,plane) = n * p + d + // radius1 stores -n*p, which is d. radius1Sqr stores d. + float distold = m.pos * position.p2 + position.radius1Sqr; + float distnew = pnext * position.p2 + position.radius1Sqr; + + // Opposite signs if product < 0 + // Is there a faster way to do this? + if(distold * distnew >= 0) + continue; + + // Find position at the crossing point by parameterizing + // p(t) = pos + vel * t + // Solve dist(p(t),plane) = 0 e.g. + // n * p(t) + D = 0 -> + // n * p + t (n * v) + D = 0 -> + // t = -(n * p + D) / (n * v) + // Could factor n*v into distnew = distold + n*v and save a bit. + // Safe since n*v != 0 assured by quick rejection test. + // This calc is indep. of dt because we have established that it + // will hit before dt. We just want to know when. + float nv = position.p2 * m.vel; + float t = -distold / nv; + + // Actual intersection point p(t) = pos + vel t + pVector phit(m.pos + m.vel * t); + + // Offset from origin in plane, phit - origin + pVector offset(phit - position.p1); + + float rad = offset.length2(); + + if(rad > r1Sqr || rad < r2Sqr) + continue; + + // A hit! A most palpable hit! + + // Compute tangential and normal components of velocity + pVector vn(position.p2 * nv); // Normal Vn = (V.N)N + pVector vt(m.vel - vn); // Tangent Vt = V - Vn + + // Compute new velocity heading out: + // Don't apply friction if tangential velocity < cutoff + if(vt.length2() <= cutoffSqr) + m.vel = vt - vn * resilience; + else + m.vel = vt * oneMinusFriction - vn * resilience; + } + } + break; + case PDPlane: + { + // See which particles bounce. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // See if particle's current and next positions cross plane. + // If not, couldn't bounce, so keep going. + pVector pnext(m.pos + m.vel * dt); + + // p2 stores the plane normal (the a,b,c of the plane eqn). + // Old and new distances: dist(p,plane) = n * p + d + // radius1 stores -n*p, which is d. + float distold = m.pos * position.p2 + position.radius1; + float distnew = pnext * position.p2 + position.radius1; + + // Opposite signs if product < 0 + if(distold * distnew >= 0) + continue; + + // Compute tangential and normal components of velocity + float nmag = m.vel * position.p2; + pVector vn(position.p2 * nmag); // Normal Vn = (V.N)N + pVector vt(m.vel - vn); // Tangent Vt = V - Vn + + // Compute new velocity heading out: + // Don't apply friction if tangential velocity < cutoff + if(vt.length2() <= cutoffSqr) + m.vel = vt - vn * resilience; + else + m.vel = vt * oneMinusFriction - vn * resilience; + } + } + break; + case PDRectangle: + { + // Compute the inverse matrix of the plane basis. + pVector &u = position.u; + pVector &v = position.v; + + // w = u cross v + float wx = u.y*v.z-u.z*v.y; + float wy = u.z*v.x-u.x*v.z; + float wz = u.x*v.y-u.y*v.x; + + float det = 1/(wz*u.x*v.y-wz*u.y*v.x-u.z*wx*v.y-u.x*v.z*wy+v.z*wx*u.y+u.z*v.x*wy); + + pVector s1((v.y*wz-v.z*wy), (v.z*wx-v.x*wz), (v.x*wy-v.y*wx)); + s1 *= det; + pVector s2((u.y*wz-u.z*wy), (u.z*wx-u.x*wz), (u.x*wy-u.y*wx)); + s2 *= -det; + + // See which particles bounce. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // See if particle's current and next positions cross plane. + // If not, couldn't bounce, so keep going. + pVector pnext(m.pos + m.vel * dt); + + // p2 stores the plane normal (the a,b,c of the plane eqn). + // Old and new distances: dist(p,plane) = n * p + d + // radius1 stores -n*p, which is d. + float distold = m.pos * position.p2 + position.radius1; + float distnew = pnext * position.p2 + position.radius1; + + // Opposite signs if product < 0 + if(distold * distnew >= 0) + continue; + + // Find position at the crossing point by parameterizing + // p(t) = pos + vel * t + // Solve dist(p(t),plane) = 0 e.g. + // n * p(t) + D = 0 -> + // n * p + t (n * v) + D = 0 -> + // t = -(n * p + D) / (n * v) + float t = -distold / (position.p2 * m.vel); + + // Actual intersection point p(t) = pos + vel t + pVector phit(m.pos + m.vel * t); + + // Offset from origin in plane, p - origin + pVector offset(phit - position.p1); + + // Dot product with basis vectors of old frame + // in terms of new frame gives position in uv frame. + float upos = offset * s1; + float vpos = offset * s2; + + // Crossed plane outside bounce region if !(0<=[uv]pos<=1) + if(upos < 0 || upos > 1 || vpos < 0 || vpos > 1) + continue; + + // A hit! A most palpable hit! + + // Compute tangential and normal components of velocity + float nmag = m.vel * position.p2; + pVector vn(position.p2 * nmag); // Normal Vn = (V.N)N + pVector vt(m.vel - vn); // Tangent Vt = V - Vn + + // Compute new velocity heading out: + // Don't apply friction if tangential velocity < cutoff + if(vt.length2() <= cutoffSqr) + m.vel = vt - vn * resilience; + else + m.vel = vt * oneMinusFriction - vn * resilience; + } + } + break; + case PDSphere: + { + // Sphere that particles bounce off + // The particles are always forced out of the sphere. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // See if particle's next position is inside domain. + // If so, bounce it. + pVector pnext(m.pos + m.vel * dt); + + if(position.Within(pnext)) + { + // See if we were inside on previous timestep. + bool pinside = position.Within(m.pos); + + // Normal to surface. This works for a sphere. Isn't + // computed quite right, should extrapolate particle + // position to surface. + pVector n(m.pos - position.p1); + n.normalize(); + + // Compute tangential and normal components of velocity + float nmag = m.vel * n; + + pVector vn(n * nmag); // Normal Vn = (V.N)N + pVector vt = m.vel - vn; // Tangent Vt = V - Vn + + if(pinside) + { + // Previous position was inside. If normal component of + // velocity points in, reverse it. This effectively + // repels particles which would otherwise be trapped + // in the sphere. + if(nmag < 0) + m.vel = vt - vn; + } + else + { + // Previous position was outside -> particle will cross + // surface boundary. Reverse normal component of velocity, + // and apply friction (if Vt >= cutoff) and resilience. + + // Compute new velocity heading out: + // Don't apply friction if tangential velocity < cutoff + if(vt.length2() <= cutoffSqr) + m.vel = vt - vn * resilience; + else + m.vel = vt * oneMinusFriction - vn * resilience; + } + } + } + } + } +} + +// Set the secondary position of each particle to be its position. +void PACallActionList::Execute(ParticleGroup *group) +{ + pCallActionList(action_list_num); +} + +// Set the secondary position of each particle to be its position. +void PACopyVertexB::Execute(ParticleGroup *group) +{ + int i; + + if(copy_pos) + { + for(i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + m.posB = m.pos; + } + } + + if(copy_vel) + { + for(i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + m.velB = m.vel; + } + } +} + +// Dampen velocities +void PADamping::Execute(ParticleGroup *group) +{ + // This is important if dt is != 1. + pVector one(1,1,1); + pVector scale(one - ((one - damping) * dt)); + + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + float vSqr = m.vel.length2(); + + if(vSqr >= vlowSqr && vSqr <= vhighSqr) + { + m.vel.x *= scale.x; + m.vel.y *= scale.y; + m.vel.z *= scale.z; + } + } +} + +// Exert force on each particle away from explosion center +void PAExplosion::Execute(ParticleGroup *group) +{ + float radius = velocity * age; + float magdt = magnitude * dt; + float oneOverSigma = 1.0f / stdev; + float inexp = -0.5f*fsqr(oneOverSigma); + float outexp = ONEOVERSQRT2PI * oneOverSigma; + + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Figure direction to particle. + pVector dir(m.pos - center); + float distSqr = dir.length2(); + float dist = sqrtf(distSqr); + float DistFromWaveSqr = fsqr(radius - dist); + + float Gd = exp(DistFromWaveSqr * inexp) * outexp; + + m.vel += dir * (Gd * magdt / (dist * (distSqr + epsilon))); + } + + age += dt; +} + +// Follow the next particle in the list +void PAFollow::Execute(ParticleGroup *group) +{ + float magdt = magnitude * dt; + float max_radiusSqr = max_radius * max_radius; + + if(max_radiusSqr < P_MAXFLOAT) + { + for(int i = 0; i < group->p_count - 1; i++) + { + Particle &m = group->list[i]; + + // Accelerate toward the particle after me in the list. + pVector tohim(group->list[i+1].pos - m.pos); // tohim = p1 - p0 + float tohimlenSqr = tohim.length2(); + + if(tohimlenSqr < max_radiusSqr) + { + // Compute force exerted between the two bodies + m.vel += tohim * (magdt / (sqrtf(tohimlenSqr) * (tohimlenSqr + epsilon))); + } + } + } + else + { + for(int i = 0; i < group->p_count - 1; i++) + { + Particle &m = group->list[i]; + + // Accelerate toward the particle after me in the list. + pVector tohim(group->list[i+1].pos - m.pos); // tohim = p1 - p0 + float tohimlenSqr = tohim.length2(); + + // Compute force exerted between the two bodies + m.vel += tohim * (magdt / (sqrtf(tohimlenSqr) * (tohimlenSqr + epsilon))); + } + } +} + +// Inter-particle gravitation +void PAGravitate::Execute(ParticleGroup *group) +{ + float magdt = magnitude * dt; + float max_radiusSqr = max_radius * max_radius; + + if(max_radiusSqr < P_MAXFLOAT) + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Add interactions with other particles + for(int j = i + 1; j < group->p_count; j++) + { + Particle &mj = group->list[j]; + + pVector tohim(mj.pos - m.pos); // tohim = p1 - p0 + float tohimlenSqr = tohim.length2(); + + if(tohimlenSqr < max_radiusSqr) + { + // Compute force exerted between the two bodies + pVector acc(tohim * (magdt / (sqrtf(tohimlenSqr) * (tohimlenSqr + epsilon)))); + + m.vel += acc; + mj.vel -= acc; + } + } + } + } + else + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Add interactions with other particles + for(int j = i + 1; j < group->p_count; j++) + { + Particle &mj = group->list[j]; + + pVector tohim(mj.pos - m.pos); // tohim = p1 - p0 + float tohimlenSqr = tohim.length2(); + + // Compute force exerted between the two bodies + pVector acc(tohim * (magdt / (sqrtf(tohimlenSqr) * (tohimlenSqr + epsilon)))); + + m.vel += acc; + mj.vel -= acc; + } + } + } +} + +// Acceleration in a constant direction +void PAGravity::Execute(ParticleGroup *group) +{ + pVector ddir(direction * dt); + + for(int i = 0; i < group->p_count; i++) + { + // Step velocity with acceleration + group->list[i].vel += ddir; + } +} + +// Accelerate particles along a line +void PAJet::Execute(ParticleGroup *group) +{ + float magdt = magnitude * dt; + float max_radiusSqr = max_radius * max_radius; + + if(max_radiusSqr < P_MAXFLOAT) + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Figure direction to particle. + pVector dir(m.pos - center); + + // Distance to jet (force drops as 1/r^2) + // Soften by epsilon to avoid tight encounters to infinity + float rSqr = dir.length2(); + + if(rSqr < max_radiusSqr) + { + pVector accel; + acc.Generate(accel); + + // Step velocity with acceleration + m.vel += accel * (magdt / (rSqr + epsilon)); + } + } + } + else + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Figure direction to particle. + pVector dir(m.pos - center); + + // Distance to jet (force drops as 1/r^2) + // Soften by epsilon to avoid tight encounters to infinity + float rSqr = dir.length2(); + + pVector accel; + acc.Generate(accel); + + // Step velocity with acceleration + m.vel += accel * (magdt / (rSqr + epsilon)); + } + } +} + +// Get rid of older particles +void PAKillOld::Execute(ParticleGroup *group) +{ + // Must traverse list in reverse order so Remove will work + for(int i = group->p_count-1; i >= 0; i--) + { + Particle &m = group->list[i]; + + if(!((m.age < age_limit) ^ kill_less_than)) + group->Remove(i); + } +} + +// Match velocity to near neighbors +void PAMatchVelocity::Execute(ParticleGroup *group) +{ + float magdt = magnitude * dt; + float max_radiusSqr = max_radius * max_radius; + + if(max_radiusSqr < P_MAXFLOAT) + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Add interactions with other particles + for(int j = i + 1; j < group->p_count; j++) + { + Particle &mj = group->list[j]; + + pVector tohim(mj.pos - m.pos); // tohim = p1 - p0 + float tohimlenSqr = tohim.length2(); + + if(tohimlenSqr < max_radiusSqr) + { + // Compute force exerted between the two bodies + pVector acc(mj.vel * (magdt / (tohimlenSqr + epsilon))); + + m.vel += acc; + mj.vel -= acc; + } + } + } + } + else + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Add interactions with other particles + for(int j = i + 1; j < group->p_count; j++) + { + Particle &mj = group->list[j]; + + pVector tohim(mj.pos - m.pos); // tohim = p1 - p0 + float tohimlenSqr = tohim.length2(); + + // Compute force exerted between the two bodies + pVector acc(mj.vel * (magdt / (tohimlenSqr + epsilon))); + + m.vel += acc; + mj.vel -= acc; + } + } + } +} + +void PAMove::Execute(ParticleGroup *group) +{ + // Step particle positions forward by dt, and age the particles. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + m.age += dt; + m.pos += m.vel * dt; + } +} + +// Accelerate particles towards a line +void PAOrbitLine::Execute(ParticleGroup *group) +{ + float magdt = magnitude * dt; + float max_radiusSqr = max_radius * max_radius; + + if(max_radiusSqr < P_MAXFLOAT) + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Figure direction to particle from base of line. + pVector f(m.pos - p); + + pVector w(axis * (f * axis)); + + // Direction from particle to nearest point on line. + pVector into = w - f; + + // Distance to line (force drops as 1/r^2, normalize by 1/r) + // Soften by epsilon to avoid tight encounters to infinity + float rSqr = into.length2(); + + if(rSqr < max_radiusSqr) + // Step velocity with acceleration + m.vel += into * (magdt / (sqrtf(rSqr) + (rSqr + epsilon))); + } + } + else + { + // Removed because it causes pipeline stalls. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Figure direction to particle from base of line. + pVector f(m.pos - p); + + pVector w(axis * (f * axis)); + + // Direction from particle to nearest point on line. + pVector into = w - f; + + // Distance to line (force drops as 1/r^2, normalize by 1/r) + // Soften by epsilon to avoid tight encounters to infinity + float rSqr = into.length2(); + + // Step velocity with acceleration + m.vel += into * (magdt / (sqrtf(rSqr) + (rSqr + epsilon))); + } + } +} + +// Accelerate particles towards a point +void PAOrbitPoint::Execute(ParticleGroup *group) +{ + float magdt = magnitude * dt; + float max_radiusSqr = max_radius * max_radius; + + if(max_radiusSqr < P_MAXFLOAT) + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Figure direction to particle. + pVector dir(center - m.pos); + + // Distance to gravity well (force drops as 1/r^2, normalize by 1/r) + // Soften by epsilon to avoid tight encounters to infinity + float rSqr = dir.length2(); + + // Step velocity with acceleration + if(rSqr < max_radiusSqr) + m.vel += dir * (magdt / (sqrtf(rSqr) + (rSqr + epsilon))); + } + } + else + { + // Avoids pipeline stalls. + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Figure direction to particle. + pVector dir(center - m.pos); + + // Distance to gravity well (force drops as 1/r^2, normalize by 1/r) + // Soften by epsilon to avoid tight encounters to infinity + float rSqr = dir.length2(); + + // Step velocity with acceleration + m.vel += dir * (magdt / (sqrtf(rSqr) + (rSqr + epsilon))); + } + } +} + +// Accelerate in random direction each time step +void PARandomAccel::Execute(ParticleGroup *group) +{ + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + pVector acceleration; + gen_acc.Generate(acceleration); + + // dt will affect this by making a higher probability of + // being near the original velocity after unit time. Smaller + // dt approach a normal distribution instead of a square wave. + m.vel += acceleration * dt; + } +} + +// Immediately displace position randomly +void PARandomDisplace::Execute(ParticleGroup *group) +{ + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + pVector displacement; + gen_disp.Generate(displacement); + + // dt will affect this by making a higher probability of + // being near the original position after unit time. Smaller + // dt approach a normal distribution instead of a square wave. + m.pos += displacement * dt; + } +} + +// Immediately assign a random velocity +void PARandomVelocity::Execute(ParticleGroup *group) +{ + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + pVector velocity; + gen_vel.Generate(velocity); + + // Shouldn't multiply by dt because velocities are + // invariant of dt. How should dt affect this? + m.vel = velocity; + } +} + +#if 0 +// Produce coefficients of a velocity function v(t)=at^2 + bt + c +// satisfying initial x(0)=x0,v(0)=v0 and desired x(t)=xf,v(t)=vf, +// where x = x(0) + integrate(v(T),0,t) +static inline void _pconstrain(float x0, float v0, float xf, float vf, + float t, float *a, float *b, float *c) +{ + *c = v0; + *b = 2 * (-t*vf - 2*t*v0 + 3*xf - 3*x0) / (t * t); + *a = 3 * (t*vf + t*v0 - 2*xf + 2*x0) / (t * t * t); +} +#endif + +// Over time, restore particles to initial positions +// Put all particles on the surface of a statue, explode the statue, +// and then suck the particles back to the original position. Cool! +void PARestore::Execute(ParticleGroup *group) +{ + if(time_left <= 0) + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Already constrained, keep it there. + m.pos = m.posB; + m.vel = pVector(0,0,0); + } + } + else + { + float t = time_left; + float dtSqr = dt * dt; + float tSqrInv2dt = dt * 2.0f / (t * t); + float tCubInv3dtSqr = dtSqr * 3.0f / (t * t * t); + + for(int i = 0; i < group->p_count; i++) + { +#if 1 + Particle &m = group->list[i]; + + // Solve for a desired-behavior velocity function in each axis + // _pconstrain(m.pos.x, m.vel.x, m.posB.x, 0., timeLeft, &a, &b, &c); + + // Figure new velocity at next timestep + // m.vel.x = a * dtSqr + b * dt + c; + + float b = (-2*t*m.vel.x + 3*m.posB.x - 3*m.pos.x) * tSqrInv2dt; + float a = (t*m.vel.x - m.posB.x - m.posB.x + m.pos.x + m.pos.x) * tCubInv3dtSqr; + + // Figure new velocity at next timestep + m.vel.x += a + b; + + b = (-2*t*m.vel.y + 3*m.posB.y - 3*m.pos.y) * tSqrInv2dt; + a = (t*m.vel.y - m.posB.y - m.posB.y + m.pos.y + m.pos.y) * tCubInv3dtSqr; + + // Figure new velocity at next timestep + m.vel.y += a + b; + + b = (-2*t*m.vel.z + 3*m.posB.z - 3*m.pos.z) * tSqrInv2dt; + a = (t*m.vel.z - m.posB.z - m.posB.z + m.pos.z + m.pos.z) * tCubInv3dtSqr; + + // Figure new velocity at next timestep + m.vel.z += a + b; +#else + Particle &m = group->list[i]; + + // XXX Optimize this. + // Solve for a desired-behavior velocity function in each axis + float a, b, c; // Coefficients of velocity function needed + + _pconstrain(m.pos.x, m.vel.x, m.posB.x, 0., + timeLeft, &a, &b, &c); + + // Figure new velocity at next timestep + m.vel.x = a * dtSqr + b * dt + c; + + _pconstrain(m.pos.y, m.vel.y, m.posB.y, 0., + timeLeft, &a, &b, &c); + + // Figure new velocity at next timestep + m.vel.y = a * dtSqr + b * dt + c; + + _pconstrain(m.pos.z, m.vel.z, m.posB.z, 0., + timeLeft, &a, &b, &c); + + // Figure new velocity at next timestep + m.vel.z = a * dtSqr + b * dt + c; + +#endif + } + } + + time_left -= dt; +} + +// Kill particles with positions on wrong side of the specified domain +void PASink::Execute(ParticleGroup *group) +{ + // Must traverse list in reverse order so Remove will work + for(int i = group->p_count-1; i >= 0; i--) + { + Particle &m = group->list[i]; + + // Remove if inside/outside flag matches object's flag + if(!(position.Within(m.pos) ^ kill_inside)) + group->Remove(i); + } +} + +// Kill particles with velocities on wrong side of the specified domain +void PASinkVelocity::Execute(ParticleGroup *group) +{ + // Must traverse list in reverse order so Remove will work + for(int i = group->p_count-1; i >= 0; i--) + { + Particle &m = group->list[i]; + + // Remove if inside/outside flag matches object's flag + if(!(velocity.Within(m.vel) ^ kill_inside)) + group->Remove(i); + } +} + +// Randomly add particles to the system +void PASource::Execute(ParticleGroup *group) +{ + int rate = int(floor(particle_rate * dt)); + + // Dither the fraction particle in time. + if(drand48() < particle_rate * dt - float(rate)) + rate++; + + // Don't emit more than it can hold. + if(group->p_count + rate > group->max_particles) + rate = group->max_particles - group->p_count; + + pVector pos, posB, vel, col, siz; + + if(vertexB_tracks) + { + for(int i = 0; i < rate; i++) + { + position.Generate(pos); + size.Generate(siz); + velocity.Generate(vel); + color.Generate(col); + float ag = age + NRand(age_sigma); + + group->Add(pos, pos, siz, vel, col, alpha, ag); + } + } + else + { + for(int i = 0; i < rate; i++) + { + position.Generate(pos); + positionB.Generate(posB); + size.Generate(siz); + velocity.Generate(vel); + color.Generate(col); + float ag = age + NRand(age_sigma); + + group->Add(pos, posB, siz, vel, col, alpha, ag); + } + } +} + +void PASpeedLimit::Execute(ParticleGroup *group) +{ + float min_sqr = min_speed*min_speed; + float max_sqr = max_speed*max_speed; + + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + float sSqr = m.vel.length2(); + if(sSqrmax_sqr) + { + float s = sqrtf(sSqr); + m.vel *= (max_speed/s); + } + } +} + +// Change color of all particles toward the specified color +void PATargetColor::Execute(ParticleGroup *group) +{ + float scaleFac = scale * dt; + + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + m.color += (color - m.color) * scaleFac; + m.alpha += (alpha - m.alpha) * scaleFac; + } +} + +// Change sizes of all particles toward the specified size +void PATargetSize::Execute(ParticleGroup *group) +{ + float scaleFac_x = scale.x * dt; + float scaleFac_y = scale.y * dt; + float scaleFac_z = scale.z * dt; + + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + pVector dif(size - m.size); + dif.x *= scaleFac_x; + dif.y *= scaleFac_y; + dif.z *= scaleFac_z; + m.size += dif; + } +} + +// Change velocity of all particles toward the specified velocity +void PATargetVelocity::Execute(ParticleGroup *group) +{ + float scaleFac = scale * dt; + + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + m.vel += (velocity - m.vel) * scaleFac; + } +} + +// Immediately displace position using vortex +// Vortex tip at center, around axis, with magnitude +// and tightness exponent +void PAVortex::Execute(ParticleGroup *group) +{ + float magdt = magnitude * dt; + float max_radiusSqr = max_radius * max_radius; + + if(max_radiusSqr < P_MAXFLOAT) + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Vector from tip of vortex + pVector offset(m.pos - center); + + // Compute distance from particle to tip of vortex. + float rSqr = offset.length2(); + + // Don't do anything to particle if too close or too far. + if(rSqr > max_radiusSqr) + continue; + + float r = sqrtf(rSqr); + + // Compute normalized offset vector. + pVector offnorm(offset / r); + + // Construct orthogonal vector frame in which to rotate + // transformed point around origin + float axisProj = offnorm * axis; // offnorm . axis + + // Components of offset perpendicular and parallel to axis + pVector w(axis * axisProj); // parallel component + pVector u(offnorm - w); // perpendicular component + + // Perpendicular component completing frame: + pVector v(axis ^ u); + + // Figure amount of rotation + // Resultant is (cos theta) u + (sin theta) v + float theta = magdt / (rSqr + epsilon); + float s = sinf(theta); + float c = cosf(theta); + + offset = (u * c + v * s + w) * r; + + // Translate back to object space + m.pos = offset + center; + } + } + else + { + for(int i = 0; i < group->p_count; i++) + { + Particle &m = group->list[i]; + + // Vector from tip of vortex + pVector offset(m.pos - center); + + // Compute distance from particle to tip of vortex. + float rSqr = offset.length2(); + + float r = sqrtf(rSqr); + + // Compute normalized offset vector. + pVector offnorm(offset / r); + + // Construct orthogonal vector frame in which to rotate + // transformed point around origin + float axisProj = offnorm * axis; // offnorm . axis + + // Components of offset perpendicular and parallel to axis + pVector w(axis * axisProj); // parallel component + pVector u(offnorm - w); // perpendicular component + + // Perpendicular component completing frame: + pVector v(axis ^ u); + + // Figure amount of rotation + // Resultant is (cos theta) u + (sin theta) v + float theta = magdt / (rSqr + epsilon); + float s = sinf(theta); + float c = cosf(theta); + + offset = (u * c + v * s + w) * r; + + // Translate back to object space + m.pos = offset + center; + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Stuff for the pDomain. + +pDomain::pDomain(PDomainEnum dtype, float a0, float a1, + float a2, float a3, float a4, float a5, + float a6, float a7, float a8) +{ + type = dtype; + switch(type) + { + case PDPoint: + p1 = pVector(a0, a1, a2); + break; + case PDLine: + { + p1 = pVector(a0, a1, a2); + pVector tmp(a3, a4, a5); + // p2 is vector from p1 to other endpoint. + p2 = tmp - p1; + } + break; + case PDBox: + // p1 is the min corner. p2 is the max corner. + if(a0 < a3) + { + p1.x = a0; p2.x = a3; + } + else + { + p1.x = a3; p2.x = a0; + } + if(a1 < a4) + { + p1.y = a1; p2.y = a4; + } + else + { + p1.y = a4; p2.y = a1; + } + if(a2 < a5) + { + p1.z = a2; p2.z = a5; + } + else + { + p1.z = a5; p2.z = a2; + } + break; + case PDTriangle: + { + p1 = pVector(a0, a1, a2); + pVector tp2 = pVector(a3, a4, a5); + pVector tp3 = pVector(a6, a7, a8); + + u = tp2 - p1; + v = tp3 - p1; + + // The rest of this is needed for bouncing. + radius1Sqr = u.length(); + pVector tu = u / radius1Sqr; + radius2Sqr = v.length(); + pVector tv = v / radius2Sqr; + + p2 = tu ^ tv; // This is the non-unit normal. + p2.normalize(); // Must normalize it. + + // radius1 stores the d of the plane eqn. + radius1 = -(p1 * p2); + } + break; + case PDRectangle: + { + p1 = pVector(a0, a1, a2); + u = pVector(a3, a4, a5); + v = pVector(a6, a7, a8); + + // The rest of this is needed for bouncing. + radius1Sqr = u.length(); + pVector tu = u / radius1Sqr; + radius2Sqr = v.length(); + pVector tv = v / radius2Sqr; + + p2 = tu ^ tv; // This is the non-unit normal. + p2.normalize(); // Must normalize it. + + // radius1 stores the d of the plane eqn. + radius1 = -(p1 * p2); + } + break; + case PDPlane: + { + p1 = pVector(a0, a1, a2); + p2 = pVector(a3, a4, a5); + p2.normalize(); // Must normalize it. + + // radius1 stores the d of the plane eqn. + radius1 = -(p1 * p2); + } + break; + case PDSphere: + p1 = pVector(a0, a1, a2); + if(a3 > a4) + { + radius1 = a3; radius2 = a4; + } + else + { + radius1 = a4; radius2 = a3; + } + radius1Sqr = radius1 * radius1; + radius2Sqr = radius2 * radius2; + break; + case PDCone: + case PDCylinder: + { + // p2 is a vector from p1 to the other end of cylinder. + // p1 is apex of cone. + + p1 = pVector(a0, a1, a2); + pVector tmp(a3, a4, a5); + p2 = tmp - p1; + + if(a6 > a7) + { + radius1 = a6; radius2 = a7; + } + else + { + radius1 = a7; radius2 = a6; + } + radius1Sqr = fsqr(radius1); + + // Given an arbitrary nonzero vector n, make two orthonormal + // vectors u and v forming a frame [u,v,n.normalize()]. + pVector n = p2; + float p2l2 = n.length2(); // Optimize this. + n.normalize(); + + // radius2Sqr stores 1 / (p2.p2) + // XXX Used to have an actual if. + radius2Sqr = p2l2 ? 1.0f / p2l2 : 0.0f; + + // Find a vector orthogonal to n. + pVector basis(1.0f, 0.0f, 0.0f); + if(fabs(basis * n) > 0.999) + basis = pVector(0.0f, 1.0f, 0.0f); + + // Project away N component, normalize and cross to get + // second orthonormal vector. + u = basis - n * (basis * n); + u.normalize(); + v = n ^ u; + } + break; + case PDBlob: + { + p1 = pVector(a0, a1, a2); + radius1 = a3; + float tmp = 1./radius1; + radius2Sqr = -0.5f*fsqr(tmp); + radius2 = ONEOVERSQRT2PI * tmp; + } + break; + case PDDisc: + { + p1 = pVector(a0, a1, a2); // Center point + p2 = pVector(a3, a4, a5); // Normal (not used in Within and Generate) + p2.normalize(); + + if(a6 > a7) + { + radius1 = a6; radius2 = a7; + } + else + { + radius1 = a7; radius2 = a6; + } + + // Find a vector orthogonal to n. + pVector basis(1.0f, 0.0f, 0.0f); + if(fabs(basis * p2) > 0.999) + basis = pVector(0.0f, 1.0f, 0.0f); + + // Project away N component, normalize and cross to get + // second orthonormal vector. + u = basis - p2 * (basis * p2); + u.normalize(); + v = p2 ^ u; + radius1Sqr = -(p1 * p2); // D of the plane eqn. + } + break; + } +} + +// Determines if pos is inside the domain +bool pDomain::Within(const pVector &pos) const +{ + switch (type) + { + case PDBox: + return !((pos.x < p1.x) || (pos.x > p2.x) || + (pos.y < p1.y) || (pos.y > p2.y) || + (pos.z < p1.z) || (pos.z > p2.z)); + case PDPlane: + // Distance from plane = n * p + d + // Inside is the positive half-space. + return pos * p2 >= -radius1; + case PDSphere: + { + pVector rvec(pos - p1); + float rSqr = rvec.length2(); + return rSqr <= radius1Sqr && rSqr >= radius2Sqr; + } + case PDCylinder: + case PDCone: + { + // This is painful and slow. Might be better to do quick + // accept/reject tests. + // Let p2 = vector from base to tip of the cylinder + // x = vector from base to test point + // x . p2 + // dist = ------ = projected distance of x along the axis + // p2. p2 ranging from 0 (base) to 1 (tip) + // + // rad = x - dist * p2 = projected vector of x along the base + // p1 is the apex of the cone. + + pVector x(pos - p1); + + // Check axial distance + // radius2Sqr stores 1 / (p2.p2) + float dist = (p2 * x) * radius2Sqr; + if(dist < 0.0f || dist > 1.0f) + return false; + + // Check radial distance; scale radius along axis for cones + pVector xrad = x - p2 * dist; // Radial component of x + float rSqr = xrad.length2(); + + if(type == PDCone) + return (rSqr <= fsqr(dist * radius1) && + rSqr >= fsqr(dist * radius2)); + else + return (rSqr <= radius1Sqr && rSqr >= fsqr(radius2)); + } + case PDBlob: + { + pVector x(pos - p1); + // return exp(-0.5 * xSq * Sqr(oneOverSigma)) * ONEOVERSQRT2PI * oneOverSigma; + float Gx = expf(x.length2() * radius2Sqr) * radius2; + return (drand48() < Gx); + } + case PDPoint: + case PDLine: + case PDRectangle: + case PDTriangle: + case PDDisc: + default: + return false; // XXX Is there something better? + } +} + +// Generate a random point uniformly distrbuted within the domain +void pDomain::Generate(pVector &pos) const +{ + switch (type) + { + case PDPoint: + pos = p1; + break; + case PDLine: + pos = p1 + p2 * drand48(); + break; + case PDBox: + // Scale and translate [0,1] random to fit box + pos.x = p1.x + (p2.x - p1.x) * drand48(); + pos.y = p1.y + (p2.y - p1.y) * drand48(); + pos.z = p1.z + (p2.z - p1.z) * drand48(); + break; + case PDTriangle: + { + float r1 = drand48(); + float r2 = drand48(); + if(r1 + r2 < 1.0f) + pos = p1 + u * r1 + v * r2; + else + pos = p1 + u * (1.0f-r1) + v * (1.0f-r2); + } + break; + case PDRectangle: + pos = p1 + u * drand48() + v * drand48(); + break; + case PDPlane: // How do I sensibly make a point on an infinite plane? + pos = p1; + break; + case PDSphere: + // Place on [-1..1] sphere + pos = RandVec() - vHalf; + pos.normalize(); + + // Scale unit sphere pos by [0..r] and translate + // (should distribute as r^2 law) + if(radius1 == radius2) + pos = p1 + pos * radius1; + else + pos = p1 + pos * (radius2 + drand48() * (radius1 - radius2)); + break; + case PDCylinder: + case PDCone: + { + // For a cone, p2 is the apex of the cone. + float dist = drand48(); // Distance between base and tip + float theta = drand48() * 2.0f * float(M_PI); // Angle around axis + // Distance from axis + float r = radius2 + drand48() * (radius1 - radius2); + + float x = r * cosf(theta); // Weighting of each frame vector + float y = r * sinf(theta); + + // Scale radius along axis for cones + if(type == PDCone) + { + x *= dist; + y *= dist; + } + + pos = p1 + p2 * dist + u * x + v * y; + } + break; + case PDBlob: + pos.x = p1.x + NRand(radius1); + pos.y = p1.y + NRand(radius1); + pos.z = p1.z + NRand(radius1); + + break; + case PDDisc: + { + float theta = drand48() * 2.0f * float(M_PI); // Angle around normal + // Distance from center + float r = radius2 + drand48() * (radius1 - radius2); + + float x = r * cosf(theta); // Weighting of each frame vector + float y = r * sinf(theta); + + pos = p1 + u * x + v * y; + } + break; + default: + pos = pVector(0,0,0); + } +} diff --git a/main/source/particle/general.h b/main/source/particle/general.h new file mode 100644 index 00000000..a4f661dc --- /dev/null +++ b/main/source/particle/general.h @@ -0,0 +1,452 @@ +// general.h +// +// Copyright 1998 by David K. McAllister. +// +// This file implements the API calls that are not particle actions. + +#ifndef general_h +#define general_h + +#include "papi.h" +#include "p_vector.h" + +#ifdef _WIN32 +#pragma warning (disable:4244) +#endif + +// A single particle +struct Particle +{ + pVector pos; + pVector posB; + pVector size; + pVector vel; + pVector velB; // Used to compute binormal, normal, etc. + pVector color; // Color must be next to alpha so glColor4fv works. + float alpha; // This is both cunning and scary. + float age; +}; + +// A group of particles - Info and an array of Particles +struct ParticleGroup +{ + int p_count; // Number of particles currently existing. + int max_particles; // Max particles allowed in group. + int particles_allocated; // Actual allocated size. + Particle list[1]; // Actually, num_particles in size + + inline void Remove(int i) + { + list[i] = list[--p_count]; + } + + inline bool Add(const pVector &pos, const pVector &posB, + const pVector &size, const pVector &vel, const pVector &color, + const float alpha = 1.0f, + const float age = 0.0f) + { + if(p_count >= max_particles) + return false; + else + { + list[p_count].pos = pos; + list[p_count].posB = posB; + list[p_count].size = size; + list[p_count].vel = vel; + list[p_count].velB = vel; // XXX This should be fixed. + list[p_count].color = color; + list[p_count].alpha = alpha; + list[p_count].age = age; + p_count++; + return true; + } + } +}; + +struct pDomain +{ + PDomainEnum type; // PABoxDomain, PASphereDomain, PAConeDomain... + pVector p1, p2; // Box vertices, Sphere center, Cylinder/Cone ends + pVector u, v; // Orthonormal basis vectors for Cylinder/Cone + float radius1; // Outer radius + float radius2; // Inner radius + float radius1Sqr; // Used for fast Within test of spheres, + float radius2Sqr; // and for mag. of u and v vectors for plane. + + bool Within(const pVector &) const; + void Generate(pVector &) const; + + // This constructor is used when default constructing a + // ParticleAction that has a pDomain. + inline pDomain() + { + } + + // Construct a domain in the standard way. + pDomain(PDomainEnum dtype, + float a0=0.0f, float a1=0.0f, float a2=0.0f, + float a3=0.0f, float a4=0.0f, float a5=0.0f, + float a6=0.0f, float a7=0.0f, float a8=0.0f); +}; + +////////////////////////////////////////////////////////////////////// +// Type codes for all actions +enum PActionEnum +{ + PAHeaderID, // The first action in each list. + PAAvoidID, // Avoid entering the domain of space. + PABounceID, // Bounce particles off a domain of space. + PACallActionListID, // + PACopyVertexBID, // Set the secondary position from current position. + PADampingID, // Dampen particle velocities. + PAExplosionID, // An Explosion. + PAFollowID, // Accelerate toward the previous particle in the group. + PAGravitateID, // Accelerate each particle toward each other particle. + PAGravityID, // Acceleration in the given direction. + PAJetID, // + PAKillOldID, // + PAMatchVelocityID, // + PAMoveID, // + PAOrbitLineID, // + PAOrbitPointID, // + PARandomAccelID, // + PARandomDisplaceID, // + PARandomVelocityID, // + PARestoreID, // + PASinkID, // + PASinkVelocityID, // + PASourceID, // + PASpeedLimitID, // + PATargetColorID, // + PATargetSizeID, // + PATargetVelocityID, // + PAVortexID // +}; + +// This method actually does the particle's action. +#define ExecMethod void Execute(ParticleGroup *pg); + +struct ParticleAction +{ + static float dt; // This is copied to here from global state. + PActionEnum type; // Type field +}; + +/////////////////////////////////////////////////////////////////////////// +// Data types derived from Action. + + +struct PAHeader : public ParticleAction +{ + int actions_allocated; + int count; // Total actions in the list. + float padding[96]; // This must be the largest action. + + ExecMethod +}; + +struct PAAvoid : public ParticleAction +{ + pDomain position; // Avoid region + float look_ahead; // how many time units ahead to look + float magnitude; // what percent of the way to go each time + float epsilon; // add to r^2 for softening + + ExecMethod +}; + +struct PABounce : public ParticleAction +{ + pDomain position; // Bounce region + float oneMinusFriction; // Friction tangent to surface + float resilience; // Resilence perpendicular to surface + float cutoffSqr; // cutoff velocity; friction applies iff v > cutoff + + ExecMethod +}; + +struct PACallActionList : public ParticleAction +{ + int action_list_num; // The action list number to call + + ExecMethod +}; + +struct PACopyVertexB : public ParticleAction +{ + bool copy_pos; // True to copy pos to posB. + bool copy_vel; // True to copy vel to velB. + + ExecMethod +}; + +struct PADamping : public ParticleAction +{ + pVector damping; // Damping constant applied to velocity + float vlowSqr; // Low and high cutoff velocities + float vhighSqr; + + ExecMethod +}; + +struct PAExplosion : public ParticleAction +{ + pVector center; // The center of the explosion + float velocity; // Of shock wave + float magnitude; // At unit radius + float stdev; // Sharpness or width of shock wave + float age; // How long it's been going on + float epsilon; // Softening parameter + + ExecMethod +}; + +struct PAFollow : public ParticleAction +{ + float magnitude; // The grav of each particle + float epsilon; // Softening parameter + float max_radius; // Only influence particles within max_radius + + ExecMethod +}; + +struct PAGravitate : public ParticleAction +{ + float magnitude; // The grav of each particle + float epsilon; // Softening parameter + float max_radius; // Only influence particles within max_radius + + ExecMethod +}; + +struct PAGravity : public ParticleAction +{ + pVector direction; // Amount to increment velocity + + ExecMethod +}; + +struct PAJet : public ParticleAction +{ + pVector center; // Center of the fan + pDomain acc; // Acceleration vector domain + float magnitude; // Scales acceleration + float epsilon; // Softening parameter + float max_radius; // Only influence particles within max_radius + + ExecMethod +}; + +struct PAKillOld : public ParticleAction +{ + float age_limit; // Exact age at which to kill particles. + bool kill_less_than; // True to kill particles less than limit. + + ExecMethod +}; + +struct PAMatchVelocity : public ParticleAction +{ + float magnitude; // The grav of each particle + float epsilon; // Softening parameter + float max_radius; // Only influence particles within max_radius + + ExecMethod +}; + +struct PAMove : public ParticleAction +{ + + ExecMethod +}; + +struct PAOrbitLine : public ParticleAction +{ + pVector p, axis; // Endpoints of line to which particles are attracted + float magnitude; // Scales acceleration + float epsilon; // Softening parameter + float max_radius; // Only influence particles within max_radius + + ExecMethod +}; + +struct PAOrbitPoint : public ParticleAction +{ + pVector center; // Point to which particles are attracted + float magnitude; // Scales acceleration + float epsilon; // Softening parameter + float max_radius; // Only influence particles within max_radius + + ExecMethod +}; + +struct PARandomAccel : public ParticleAction +{ + pDomain gen_acc; // The domain of random accelerations. + + ExecMethod +}; + +struct PARandomDisplace : public ParticleAction +{ + pDomain gen_disp; // The domain of random displacements. + + ExecMethod +}; + +struct PARandomVelocity : public ParticleAction +{ + pDomain gen_vel; // The domain of random velocities. + + ExecMethod +}; + +struct PARestore : public ParticleAction +{ + float time_left; // Time remaining until they should be in position. + + ExecMethod +}; + +struct PASink : public ParticleAction +{ + bool kill_inside; // True to dispose of particles *inside* domain + pDomain position; // Disposal region + + ExecMethod +}; + +struct PASinkVelocity : public ParticleAction +{ + bool kill_inside; // True to dispose of particles with vel *inside* domain + pDomain velocity; // Disposal region + + ExecMethod +}; + +struct PASpeedLimit : public ParticleAction +{ + float min_speed; // Clamp speed to this minimum. + float max_speed; // Clamp speed to this maximum. + + ExecMethod +}; + +struct PASource : public ParticleAction +{ + pDomain position; // Choose a position in this domain. + pDomain positionB; // Choose a positionB in this domain. + pDomain size; // Choose a size in this domain. + pDomain velocity; // Choose a velocity in this domain. + pDomain color; // Choose a color in this domain. + float alpha; // Alpha of all generated particles + float particle_rate; // Particles to generate per unit time + float age; // Initial age of the particles + float age_sigma; // St. dev. of initial age of the particles + bool vertexB_tracks; // True to get positionB from position. + + ExecMethod +}; + +struct PATargetColor : public ParticleAction +{ + pVector color; // Color to shift towards + float alpha; // Alpha value to shift towards + float scale; // Amount to shift by (1 == all the way) + + ExecMethod +}; + +struct PATargetSize : public ParticleAction +{ + pVector size; // Size to shift towards + pVector scale; // Amount to shift by per frame (1 == all the way) + + ExecMethod +}; + +struct PATargetVelocity : public ParticleAction +{ + pVector velocity; // Velocity to shift towards + float scale; // Amount to shift by (1 == all the way) + + ExecMethod +}; + +struct PAVortex : public ParticleAction +{ + pVector center; // Center of vortex + pVector axis; // Axis around which vortex is applied + float magnitude; // Scale for rotation around axis + float epsilon; // Softening parameter + float max_radius; // Only influence particles within max_radius + + ExecMethod +}; + +// Global state vector +struct _ParticleState +{ + float dt; + bool in_call_list; + bool in_new_list; + bool vertexB_tracks; + + int group_id; + int list_id; + ParticleGroup *pgrp; + PAHeader *pact; + int tid; // Only used in the MP case, but always define it. + + // These are static because all threads access the same groups. + // All accesses to these should be locked. + static ParticleGroup **group_list; + static PAHeader **alist_list; + static int group_count; + static int alist_count; + + pDomain Size; + pDomain Vel; + pDomain VertexB; + pDomain Color; + float Alpha; + float Age; + float AgeSigma; + + _ParticleState(); + + // Return an index into the list of particle groups where + // p_group_count groups can be added. + int GenerateGroups(int p_group_count); + int GenerateLists(int alist_count); + ParticleGroup *GetGroupPtr(int p_group_num); + PAHeader *GetListPtr(int action_list_num); +}; + +#ifdef PARTICLE_MP +// All entry points call this to get their particle state. +inline _ParticleState &_GetPState() +{ + // Returns a reference to the appropriate particle state. + extern _ParticleState &_GetPStateWithTID(); + + return _GetPStateWithTID(); +} + +#else + +// All entry points call this to get their particle state. +// For the non-MP case this is practically a no-op. +inline _ParticleState &_GetPState() +{ + // This is the global state. + extern _ParticleState __ps; + + return __ps; +} +#endif + +// Just a silly little function. +static inline float fsqr(float f) { return f * f; } + +#endif diff --git a/main/source/particle/libparticleMP.a b/main/source/particle/libparticleMP.a new file mode 100644 index 0000000000000000000000000000000000000000..a84d4b2e8020c02d89b04a8fa171b3ba37426aab GIT binary patch literal 84110 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 C5MC7k literal 0 HcmV?d00001 diff --git a/main/source/particle/opengl.cpp b/main/source/particle/opengl.cpp new file mode 100644 index 00000000..2f48a566 --- /dev/null +++ b/main/source/particle/opengl.cpp @@ -0,0 +1,149 @@ +// opengl.cpp +// +// Copyright 1998 by David K. McAllister +// +// This file implements the API calls that draw particle groups in OpenGL. + +#include "general.h" + +#ifdef WIN32 +// This is for something in gl.h. +#include +#endif + +#include +// XXX #include + +// Emit OpenGL calls to draw the particles. These are drawn with +// whatever primitive type the user specified(GL_POINTS, for +// example). The color and radius are set per primitive, by default. +// For GL_LINES, the other vertex of the line is the velocity vector. +// XXX const_size is ignored. +PARTICLEDLL_API void pDrawGroupp(int primitive, bool const_size, bool const_color) +{ + _ParticleState &_ps = _GetPState(); + + // Get a pointer to the particles in gp memory + ParticleGroup *pg = _ps.pgrp; + + if(pg == NULL) + return; // ERROR + + if(pg->p_count < 1) + return; + + if(primitive == GL_POINTS) + { + glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT); + glEnableClientState(GL_VERTEX_ARRAY); + if(!const_color) + { + glEnableClientState(GL_COLOR_ARRAY); + glColorPointer(4, GL_FLOAT, sizeof(Particle), &pg->list[0].color); + } + + glVertexPointer(3, GL_FLOAT, sizeof(Particle), &pg->list[0].pos); + glDrawArrays((GLenum)primitive, 0, pg->p_count); + glPopClientAttrib(); + // XXX For E&S + glDisableClientState(GL_COLOR_ARRAY); + } + else + { + // Assume GL_LINES + glBegin((GLenum)primitive); + + if(!const_color) + { + for(int i = 0; i < pg->p_count; i++) + { + Particle &m = pg->list[i]; + + // Warning: this depends on alpha following color in the Particle struct. + glColor4fv((GLfloat *)&m.color); + glVertex3fv((GLfloat *)&m.pos); + + // For lines, make a tail with the velocity vector's direction and + // a length of radius. + pVector tail = m.pos - m.vel; + glVertex3fv((GLfloat *)&tail); + } + } + else + { + for(int i = 0; i < pg->p_count; i++) + { + Particle &m = pg->list[i]; + glVertex3fv((GLfloat *)&m.pos); + + // For lines, make a tail with the velocity vector's direction and + // a length of radius. + pVector tail = m.pos - m.vel; + glVertex3fv((GLfloat *)&tail); + } + } + glEnd(); + } +} + +PARTICLEDLL_API void pDrawGroupl(int dlist, bool const_size, bool const_color, bool const_rotation) +{ + _ParticleState &_ps = _GetPState(); + + // Get a pointer to the particles in gp memory + ParticleGroup *pg = _ps.pgrp; + if(pg == NULL) + return; // ERROR + + if(pg->p_count < 1) + return; + + //if(const_color) + // glColor4fv((GLfloat *)&pg->list[0].color); + + for(int i = 0; i < pg->p_count; i++) + { + Particle &m = pg->list[i]; + + glPushMatrix(); + glTranslatef(m.pos.x, m.pos.y, m.pos.z); + + if(!const_size) + glScalef(m.size.x, m.size.y, m.size.z); + else + glScalef(pg->list[i].size.x, pg->list[i].size.y, pg->list[i].size.z); + + // Expensive! A sqrt, cross prod and acos. Yow. + if(!const_rotation) + { + pVector vN(m.vel); + vN.normalize(); + pVector voN(m.velB); + voN.normalize(); + + pVector biN; + if(voN.x == vN.x && voN.y == vN.y && voN.z == vN.z) + biN = pVector(0, 1, 0); + else + biN = vN ^ voN; + biN.normalize(); + + pVector N(vN ^ biN); + + double M[16]; + M[0] = vN.x; M[4] = biN.x; M[8] = N.x; M[12] = 0; + M[1] = vN.y; M[5] = biN.y; M[9] = N.y; M[13] = 0; + M[2] = vN.z; M[6] = biN.z; M[10] = N.z; M[14] = 0; + M[3] = 0; M[7] = 0; M[11] = 0; M[15] = 1; + glMultMatrixd(M); + } + + // Warning: this depends on alpha following color in the Particle struct. + if(!const_color) + glColor4fv((GLfloat *)&m.color); + + glCallList(dlist); + + glPopMatrix(); + } +} diff --git a/main/source/particle/p_vector.h b/main/source/particle/p_vector.h new file mode 100644 index 00000000..45ef5d7b --- /dev/null +++ b/main/source/particle/p_vector.h @@ -0,0 +1,141 @@ +// p_vector.h - yet another vector class. +// +// Copyright 1997 by Jonathan P. Leech +// Modifications Copyright 1997-1999 by David K. McAllister +// +// A simple 3D float vector class for internal use by the particle systems. + +#ifndef particle_vector_h +#define particle_vector_h + +#include + +#ifndef M_PI +#define M_PI 3.1415926535897932384626433f +#endif + +#ifdef WIN32 +#define drand48() (((float) rand())/((float) RAND_MAX)) +#define srand48(x) srand(x) + +// This is because their stupid compiler thinks it's smart. +#define inline __forceinline +#endif + +class pVector +{ +public: + float x, y, z; + + inline pVector(float ax, float ay, float az) : x(ax), y(ay), z(az) + { + //x = ax; y = ay; z = az; + } + + inline pVector() {} + + inline float length() const + { + return sqrtf(x*x+y*y+z*z); + } + + inline float length2() const + { + return (x*x+y*y+z*z); + } + + inline float normalize() + { + float onel = 1.0f / sqrtf(x*x+y*y+z*z); + x *= onel; + y *= onel; + z *= onel; + + return onel; + } + + inline float operator*(const pVector &a) const + { + return x*a.x + y*a.y + z*a.z; + } + + inline pVector operator*(const float s) const + { + return pVector(x*s, y*s, z*s); + } + + inline pVector operator/(const float s) const + { + float invs = 1.0f / s; + return pVector(x*invs, y*invs, z*invs); + } + + inline pVector operator+(const pVector& a) const + { + return pVector(x+a.x, y+a.y, z+a.z); + } + + inline pVector operator-(const pVector& a) const + { + return pVector(x-a.x, y-a.y, z-a.z); + } + + inline pVector operator-() + { + x = -x; + y = -y; + z = -z; + return *this; + } + + inline pVector& operator+=(const pVector& a) + { + x += a.x; + y += a.y; + z += a.z; + return *this; + } + + inline pVector& operator-=(const pVector& a) + { + x -= a.x; + y -= a.y; + z -= a.z; + return *this; + } + + inline pVector& operator*=(const float a) + { + x *= a; + y *= a; + z *= a; + return *this; + } + + inline pVector& operator/=(const float a) + { + float b = 1.0f / a; + x *= b; + y *= b; + z *= b; + return *this; + } + + inline pVector& operator=(const pVector& a) + { + x = a.x; + y = a.y; + z = a.z; + return *this; + } + + inline pVector operator^(const pVector& b) const + { + return pVector( + y*b.z-z*b.y, + z*b.x-x*b.z, + x*b.y-y*b.x); + } +}; + +#endif diff --git a/main/source/particle/papi.h b/main/source/particle/papi.h new file mode 100644 index 00000000..95ba22c7 --- /dev/null +++ b/main/source/particle/papi.h @@ -0,0 +1,229 @@ +// papi.h +// +// Copyright 1997-1998 by David K. McAllister +// http://www.cs.unc.edu/~davemc/Particle +// +// Include this file in all applications that use the Particle System API. + +#ifndef _particle_api_h +#define _particle_api_h + +#include + +// This is the major and minor version number of this release of the API. +#define P_VERSION 120 + +#ifdef WIN32 +#include + +#ifdef PARTICLEDLL_EXPORTS +#define PARTICLEDLL_API __declspec(dllexport) +#else +#define PARTICLEDLL_API __declspec(dllimport) +#endif + +#else +#define PARTICLEDLL_API +#endif + +// Actually this must be < sqrt(MAXFLOAT) since we store this value squared. +#define P_MAXFLOAT 1.0e16f + +#ifdef MAXINT +#define P_MAXINT MAXINT +#else +#define P_MAXINT 0x7fffffff +#endif + +#define P_EPS 1e-3f + +////////////////////////////////////////////////////////////////////// +// Type codes for domains +PARTICLEDLL_API enum PDomainEnum +{ + PDPoint = 0, // Single point + PDLine = 1, // Line segment + PDTriangle = 2, // Triangle + PDPlane = 3, // Arbitrarily-oriented plane + PDBox = 4, // Axis-aligned box + PDSphere = 5, // Sphere + PDCylinder = 6, // Cylinder + PDCone = 7, // Cone + PDBlob = 8, // Gaussian blob + PDDisc = 9, // Arbitrarily-oriented disc + PDRectangle = 10 // Rhombus-shaped planar region +}; + +// State setting calls + +PARTICLEDLL_API void pColor(float red, float green, float blue, float alpha = 1.0f); + +PARTICLEDLL_API void pColorD(float alpha, PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pSize(float size_x, float size_y = 1.0f, float size_z = 1.0f); + +PARTICLEDLL_API void pSizeD(PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pStartingAge(float age, float sigma = 1.0f); + +PARTICLEDLL_API void pTimeStep(float new_dt); + +PARTICLEDLL_API void pVelocity(float x, float y, float z); + +PARTICLEDLL_API void pVelocityD(PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pVertexB(float x, float y, float z); + +PARTICLEDLL_API void pVertexBD(PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pVertexBTracks(bool track_vertex = true); + + +// Action List Calls + +PARTICLEDLL_API void pCallActionList(int action_list_num); + +PARTICLEDLL_API void pDeleteActionLists(int action_list_num, int action_list_count = 1); + +PARTICLEDLL_API void pEndActionList(); + +PARTICLEDLL_API int pGenActionLists(int action_list_count = 1); + +PARTICLEDLL_API void pNewActionList(int action_list_num); + + +// Particle Group Calls + +PARTICLEDLL_API void pCopyGroup(int p_src_group_num, int index = 0, int copy_count = P_MAXINT); + +PARTICLEDLL_API void pCurrentGroup(int p_group_num); + +PARTICLEDLL_API void pDeleteParticleGroups(int p_group_num, int p_group_count = 1); + +PARTICLEDLL_API void pDrawGroupl(int dlist, bool const_size = false, + bool const_color = false, bool const_rotation = false); + +PARTICLEDLL_API void pDrawGroupp(int primitive, bool const_size = false, + bool const_color = false); + +PARTICLEDLL_API int pGenParticleGroups(int p_group_count = 1, int max_particles = 0); + +PARTICLEDLL_API int pGetGroupCount(); + +PARTICLEDLL_API int pGetParticles(int index, int count, float *position = NULL, float *color = NULL, + float *vel = NULL, float *size = NULL, float *age = NULL); + +PARTICLEDLL_API int pSetMaxParticles(int max_count); + + +// Actions + +PARTICLEDLL_API void pAvoid(float magnitude, float epsilon, float look_ahead, + PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pBounce(float friction, float resilience, float cutoff, + PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pCopyVertexB(bool copy_pos = true, bool copy_vel = false); + +PARTICLEDLL_API void pDamping(float damping_x, float damping_y, float damping_z, + float vlow = 0.0f, float vhigh = P_MAXFLOAT); + +PARTICLEDLL_API void pExplosion(float center_x, float center_y, float center_z, float velocity, + float magnitude, float stdev, float epsilon = P_EPS, float age = 0.0f); + +PARTICLEDLL_API void pFollow(float magnitude = 1.0f, float epsilon = P_EPS, float max_radius = P_MAXFLOAT); + +PARTICLEDLL_API void pGravitate(float magnitude = 1.0f, float epsilon = P_EPS, float max_radius = P_MAXFLOAT); + +PARTICLEDLL_API void pGravity(float dir_x, float dir_y, float dir_z); + +PARTICLEDLL_API void pJet(float center_x, float center_y, float center_z, float magnitude = 1.0f, + float epsilon = P_EPS, float max_radius = P_MAXFLOAT); + +PARTICLEDLL_API void pKillOld(float age_limit, bool kill_less_than = false); + +PARTICLEDLL_API void pMatchVelocity(float magnitude = 1.0f, float epsilon = P_EPS, + float max_radius = P_MAXFLOAT); + +PARTICLEDLL_API void pMove(); + +PARTICLEDLL_API void pOrbitLine(float p_x, float p_y, float p_z, + float axis_x, float axis_y, float axis_z, float magnitude = 1.0f, + float epsilon = P_EPS, float max_radius = P_MAXFLOAT); + +PARTICLEDLL_API void pOrbitPoint(float center_x, float center_y, float center_z, + float magnitude = 1.0f, float epsilon = P_EPS, + float max_radius = P_MAXFLOAT); + +PARTICLEDLL_API void pRandomAccel(PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pRandomDisplace(PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pRandomVelocity(PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pRestore(float time); + +PARTICLEDLL_API void pShade(float color_x, float color_y, float color_z, + float alpha, float scale); + +PARTICLEDLL_API void pSink(bool kill_inside, PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pSinkVelocity(bool kill_inside, PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pSource(float particle_rate, PDomainEnum dtype, + float a0 = 0.0f, float a1 = 0.0f, float a2 = 0.0f, + float a3 = 0.0f, float a4 = 0.0f, float a5 = 0.0f, + float a6 = 0.0f, float a7 = 0.0f, float a8 = 0.0f); + +PARTICLEDLL_API void pSpeedLimit(float min_speed, float max_speed = P_MAXFLOAT); + +PARTICLEDLL_API void pTargetColor(float color_x, float color_y, float color_z, + float alpha, float scale); + +PARTICLEDLL_API void pTargetSize(float size_x, float size_y, float size_z, + float scale_x = 0.0f, float scale_y = 0.0f, float scale_z = 0.0f); + +PARTICLEDLL_API void pTargetVelocity(float vel_x, float vel_y, float vel_z, float scale); + +PARTICLEDLL_API void pVertex(float x, float y, float z); + +PARTICLEDLL_API void pVortex(float center_x, float center_y, float center_z, + float axis_x, float axis_y, float axis_z, + float magnitude = 1.0f, float epsilon = P_EPS, + float max_radius = P_MAXFLOAT); + +#endif diff --git a/main/source/particle/system.cpp b/main/source/particle/system.cpp new file mode 100644 index 00000000..2cc2c712 --- /dev/null +++ b/main/source/particle/system.cpp @@ -0,0 +1,883 @@ +// system.cpp +// +// Copyright 1998 by David K. McAllister. +// +// This file implements the API calls that are not particle actions. + +#include "general.h" + +#include + +// XXX +#include +// using namespace std; + +// For Windows DLL. +#ifdef WIN32 +BOOL APIENTRY DllMain( HANDLE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} +#endif + +float ParticleAction::dt; + +ParticleGroup **_ParticleState::group_list; +PAHeader **_ParticleState::alist_list; +int _ParticleState::group_count; +int _ParticleState::alist_count; + +// This AutoCall struct allows for static initialization of the above shared variables. +struct AutoCall +{ + AutoCall(); +}; + +AutoCall::AutoCall() +{ + // The list of groups, etc. + _ParticleState::group_list = new ParticleGroup *[16]; + _ParticleState::group_count = 16; + _ParticleState::alist_list = new PAHeader *[16]; + _ParticleState::alist_count = 16; + for(int i=0; i<16; i++) + { + _ParticleState::group_list[i] = NULL; + _ParticleState::alist_list[i] = NULL; + } +} + +#ifdef PARTICLE_MP +// This code is defined if we are compiling the library to be used on +// multiple threads. We need to have each API call figure out which +// _ParticleState belongs to it. We hash pointers to contexts in +// _CtxHash. Whenever a TID is asked for but doesn't exist we create +// it. + +#include + +// XXX This hard limit should get fixed. +int _CtxCount = 151; +_ParticleState **_CtxHash = NULL; + +inline int _HashTID(int tid) +{ + return ((tid << 13) ^ ((tid >> 11) ^ tid)) % _CtxCount; +} + +// Returns a reference to the appropriate particle state. +_ParticleState &_GetPStateWithTID() +{ + int tid = mp_my_threadnum(); + + int ind = _HashTID(tid); + + // cerr << tid << "->" << ind << endl; + + // Check through the hash table and find it. + for(int i=ind; i<_CtxCount; i++) + if(_CtxHash[i] && _CtxHash[i]->tid == tid) + { + //#pragma critical + //cerr << tid << " => " << i << endl; + + return *_CtxHash[i]; + } + + for(i=0; itid == tid) + return *_CtxHash[i]; + + // It didn't exist. It's a new context, so create it. + _ParticleState *psp = new _ParticleState(); + psp->tid = tid; + + // Find a place to put it. + for(i=ind; i<_CtxCount; i++) + if(_CtxHash[i] == NULL) + { + // #pragma critical + // cerr << "Stored " << tid << " at " << i << endl; + _CtxHash[i] = psp; + return *psp; + } + + for(i=0; i= group_count) + return NULL; // IERROR + + return group_list[p_group_num]; +} + +PAHeader *_ParticleState::GetListPtr(int a_list_num) +{ + if(a_list_num < 0) + return NULL; // IERROR + + if(a_list_num >= alist_count) + return NULL; // IERROR + + return alist_list[a_list_num]; +} + +// Return an index into the list of particle groups where +// p_group_count groups can be added. +int _ParticleState::GenerateGroups(int p_group_count) +{ + int num_empty = 0; + int first_empty = -1; + + for(int i=0; i= p_group_count) + return first_empty; + } + } + + // Couldn't find a big enough gap. Reallocate. + int new_count = 16 + group_count + p_group_count; + ParticleGroup **glist = new ParticleGroup *[new_count]; + memcpy(glist, group_list, group_count * sizeof(void*)); + for(int i=group_count; i= list_count) + return first_empty; + } + } + + // Couldn't find a big enough gap. Reallocate. + int new_count = 16 + alist_count + list_count; + PAHeader **new_list = new PAHeader *[new_count]; + memcpy(new_list, alist_list, alist_count * sizeof(void*)); + for(int i=list_count; itype) + { + case PAAvoidID: + ((PAAvoid *)pa)->Execute(pg); + break; + case PABounceID: + ((PABounce *)pa)->Execute(pg); + break; + case PACallActionListID: + ((PACallActionList *)pa)->Execute(pg); + break; + case PACopyVertexBID: + ((PACopyVertexB *)pa)->Execute(pg); + break; + case PADampingID: + ((PADamping *)pa)->Execute(pg); + break; + case PAExplosionID: + ((PAExplosion *)pa)->Execute(pg); + break; + case PAFollowID: + ((PAFollow *)pa)->Execute(pg); + break; + case PAGravitateID: + ((PAGravitate *)pa)->Execute(pg); + break; + case PAGravityID: + ((PAGravity *)pa)->Execute(pg); + break; + case PAJetID: + ((PAJet *)pa)->Execute(pg); + break; + case PAKillOldID: + ((PAKillOld *)pa)->Execute(pg); + break; + case PAMatchVelocityID: + ((PAMatchVelocity *)pa)->Execute(pg); + break; + case PAMoveID: + ((PAMove *)pa)->Execute(pg); + break; + case PAOrbitLineID: + ((PAOrbitLine *)pa)->Execute(pg); + break; + case PAOrbitPointID: + ((PAOrbitPoint *)pa)->Execute(pg); + break; + case PARandomAccelID: + ((PARandomAccel *)pa)->Execute(pg); + break; + case PARandomDisplaceID: + ((PARandomDisplace *)pa)->Execute(pg); + break; + case PARandomVelocityID: + ((PARandomVelocity *)pa)->Execute(pg); + break; + case PARestoreID: + ((PARestore *)pa)->Execute(pg); + break; + case PASinkID: + ((PASink *)pa)->Execute(pg); + break; + case PASinkVelocityID: + ((PASinkVelocity *)pa)->Execute(pg); + break; + case PASourceID: + ((PASource *)pa)->Execute(pg); + break; + case PASpeedLimitID: + ((PASpeedLimit *)pa)->Execute(pg); + break; + case PATargetColorID: + ((PATargetColor *)pa)->Execute(pg); + break; + case PATargetSizeID: + ((PATargetSize *)pa)->Execute(pg); + break; + case PATargetVelocityID: + ((PATargetVelocity *)pa)->Execute(pg); + break; + case PAVortexID: + ((PAVortex *)pa)->Execute(pg); + break; + } + } +} + +// Add the incoming action to the end of the current action list. +void _pAddActionToList(ParticleAction *S, int size) +{ + _ParticleState &_ps = _GetPState(); + + if(!_ps.in_new_list) + return; // ERROR + + if(_ps.pact == NULL) + return; // ERROR + + if(_ps.list_id < 0) + return; // ERROR + + PAHeader *alist = _ps.pact; + + if(alist->actions_allocated <= alist->count) + { + // Must reallocate. + int new_alloc = 16 + alist->actions_allocated; + PAHeader *new_alist = new PAHeader[new_alloc]; + memcpy(new_alist, alist, alist->count * sizeof(PAHeader)); + + delete [] alist; + _ps.alist_list[_ps.list_id] = _ps.pact = alist = new_alist; + + alist->actions_allocated = new_alloc; + } + + // Now add it in. + memcpy(&alist[alist->count], S, size); + alist->count++; +} + +//////////////////////////////////////////////////////// +// State setting calls + +PARTICLEDLL_API void pColor(float red, float green, float blue, float alpha) +{ + _ParticleState &_ps = _GetPState(); + + _ps.Alpha = alpha; + _ps.Color = pDomain(PDPoint, red, green, blue); +} + +PARTICLEDLL_API void pColorD(float alpha, PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + _ParticleState &_ps = _GetPState(); + + _ps.Alpha = alpha; + _ps.Color = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); +} + +PARTICLEDLL_API void pVelocity(float x, float y, float z) +{ + _ParticleState &_ps = _GetPState(); + + _ps.Vel = pDomain(PDPoint, x, y, z); +} + +PARTICLEDLL_API void pVelocityD(PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + _ParticleState &_ps = _GetPState(); + + _ps.Vel = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); +} + +PARTICLEDLL_API void pVertexB(float x, float y, float z) +{ + _ParticleState &_ps = _GetPState(); + + _ps.VertexB = pDomain(PDPoint, x, y, z); +} + +PARTICLEDLL_API void pVertexBD(PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + _ParticleState &_ps = _GetPState(); + + _ps.VertexB = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); +} + + +PARTICLEDLL_API void pVertexBTracks(bool trackVertex) +{ + _ParticleState &_ps = _GetPState(); + + _ps.vertexB_tracks = trackVertex; +} + +PARTICLEDLL_API void pSize(float size_x, float size_y, float size_z) +{ + _ParticleState &_ps = _GetPState(); + + _ps.Size = pDomain(PDPoint, size_x, size_y, size_z); +} + +PARTICLEDLL_API void pSizeD(PDomainEnum dtype, + float a0, float a1, float a2, + float a3, float a4, float a5, + float a6, float a7, float a8) +{ + _ParticleState &_ps = _GetPState(); + + _ps.Size = pDomain(dtype, a0, a1, a2, a3, a4, a5, a6, a7, a8); +} + +PARTICLEDLL_API void pStartingAge(float age, float sigma) +{ + _ParticleState &_ps = _GetPState(); + + _ps.Age = age; + _ps.AgeSigma = sigma; +} + +PARTICLEDLL_API void pTimeStep(float newDT) +{ + _ParticleState &_ps = _GetPState(); + + _ps.dt = newDT; +} + +//////////////////////////////////////////////////////// +// Action List Calls + +PARTICLEDLL_API int pGenActionLists(int action_list_count) +{ + _ParticleState &_ps = _GetPState(); + + if(_ps.in_new_list) + return -1; // ERROR + + _PLock(); + + int ind = _ps.GenerateLists(action_list_count); + + for(int i=ind; iactions_allocated = 8; + _ps.alist_list[i]->type = PAHeaderID; + _ps.alist_list[i]->count = 1; + } + + _PUnLock(); + + return ind; +} + +PARTICLEDLL_API void pNewActionList(int action_list_num) +{ + _ParticleState &_ps = _GetPState(); + + if(_ps.in_new_list) + return; // ERROR + + _ps.pact = _ps.GetListPtr(action_list_num); + + if(_ps.pact == NULL) + return; // ERROR + + _ps.list_id = action_list_num; + _ps.in_new_list = true; + + // Remove whatever used to be in the list. + _ps.pact->count = 1; +} + +PARTICLEDLL_API void pEndActionList() +{ + _ParticleState &_ps = _GetPState(); + + if(!_ps.in_new_list) + return; // ERROR + + _ps.in_new_list = false; + + _ps.pact = NULL; + _ps.list_id = -1; +} + +PARTICLEDLL_API void pDeleteActionLists(int action_list_num, int action_list_count) +{ + _ParticleState &_ps = _GetPState(); + + if(_ps.in_new_list) + return; // ERROR + + if(action_list_num < 0) + return; // ERROR + + if(action_list_num + action_list_count > _ps.alist_count) + return; // ERROR + + _PLock(); + + for(int i = action_list_num; i < action_list_num + action_list_count; i++) + { + if(_ps.alist_list[i]) + { + delete [] _ps.alist_list[i]; + _ps.alist_list[i] = NULL; + } + else + { + _PUnLock(); + return; // ERROR + } + } + + _PUnLock(); +} + +PARTICLEDLL_API void pCallActionList(int action_list_num) +{ + _ParticleState &_ps = _GetPState(); + + if(_ps.in_new_list) + { + // Add this call as an action to the current list. + extern void _pSendAction(ParticleAction *S, PActionEnum type, int size); + + PACallActionList S; + S.action_list_num = action_list_num; + + _pSendAction(&S, PACallActionListID, sizeof(PACallActionList)); + } + else + { + // Execute the specified action list. + PAHeader *pa = _ps.GetListPtr(action_list_num); + + if(pa == NULL) + return; // ERRROR + + // XXX A temporary hack. + pa->dt = _ps.dt; + + _ps.in_call_list = true; + + _pCallActionList(pa+1, pa->count-1, _ps.pgrp); + + _ps.in_call_list = false; + } +} + +//////////////////////////////////////////////////////// +// Particle Group Calls + +// Create particle groups, each with max_particles allocated. +PARTICLEDLL_API int pGenParticleGroups(int p_group_count, int max_particles) +{ + _ParticleState &_ps = _GetPState(); + + if(_ps.in_new_list) + return -1; // ERROR + + _PLock(); + // cerr << "Generating pg " << _ps.tid << " cnt= " << max_particles << endl; + + int ind = _ps.GenerateGroups(p_group_count); + + for(int i=ind; imax_particles = max_particles; + _ps.group_list[i]->particles_allocated = max_particles; + _ps.group_list[i]->p_count = 0; + } + + _PUnLock(); + + return ind; +} + +PARTICLEDLL_API void pDeleteParticleGroups(int p_group_num, int p_group_count) +{ + _ParticleState &_ps = _GetPState(); + + if(p_group_num < 0) + return; // ERROR + + if(p_group_num + p_group_count > _ps.group_count) + return; // ERROR + + _PLock(); + + for(int i = p_group_num; i < p_group_num + p_group_count; i++) + { + if(_ps.group_list[i]) + { + delete [] _ps.group_list[i]; + _ps.group_list[i] = NULL; + } + else + { + _PUnLock(); + return; // ERROR + } + } + + _PUnLock(); +} + +// Change which group is current. +PARTICLEDLL_API void pCurrentGroup(int p_group_num) +{ + _ParticleState &_ps = _GetPState(); + + if(_ps.in_new_list) + return; // ERROR + + _ps.pgrp = _ps.GetGroupPtr(p_group_num); + if(_ps.pgrp) + _ps.group_id = p_group_num; + else + _ps.group_id = -1; +} + +// Change the maximum number of particles in the current group. +PARTICLEDLL_API int pSetMaxParticles(int max_count) +{ + _ParticleState &_ps = _GetPState(); + + if(_ps.in_new_list) + return 0; // ERROR + + ParticleGroup *pg = _ps.pgrp; + if(pg == NULL) + return 0; // ERROR + + if(max_count < 0) + return 0; // ERROR + + // Reducing max. + if(pg->particles_allocated >= max_count) + { + pg->max_particles = max_count; + + // May have to kill particles. + if(pg->p_count > pg->max_particles) + pg->p_count = pg->max_particles; + + return max_count; + } + + _PLock(); + + // Allocate particles. + ParticleGroup *pg2 =(ParticleGroup *)new Particle[max_count + 2]; + if(pg2 == NULL) + { + // Not enough memory. Just give all we've got. + // ERROR + pg->max_particles = pg->particles_allocated; + + _PUnLock(); + + return pg->max_particles; + } + + memcpy(pg2, pg, (pg->p_count + 2) * sizeof(Particle)); + + delete [] pg; + + _ps.group_list[_ps.group_id] = _ps.pgrp = pg2; + pg2->max_particles = max_count; + pg2->particles_allocated = max_count; + + _PUnLock(); + + return max_count; +} + +// Copy from the specified group to the current group. +PARTICLEDLL_API void pCopyGroup(int p_src_group_num, int index, int copy_count) +{ + _ParticleState &_ps = _GetPState(); + + if(_ps.in_new_list) + return; // ERROR + + ParticleGroup *srcgrp = _ps.GetGroupPtr(p_src_group_num); + if(srcgrp == NULL) + return; // ERROR + + ParticleGroup *destgrp = _ps.pgrp; + if(destgrp == NULL) + return; // ERROR + + // Find out exactly how many to copy. + int ccount = copy_count; + if(ccount > srcgrp->p_count - index) + ccount = srcgrp->p_count - index; + if(ccount > destgrp->max_particles - destgrp->p_count) + ccount = destgrp->max_particles - destgrp->p_count; + + // #pragma critical + // cerr << p_src_group_num << ": " << ccount << " " << srcgrp->p_count << " " << index << endl; + + if(ccount<0) + ccount = 0; + + // Directly copy the particles to the current list. + for(int i=0; ilist[destgrp->p_count+i] = + srcgrp->list[index+i]; + } + destgrp->p_count += ccount; +} + +// Copy from the current group to application memory. +PARTICLEDLL_API int pGetParticles(int index, int count, float *verts, + float *color, float *vel, float *size, float *age) +{ + _ParticleState &_ps = _GetPState(); + + // XXX I should think about whether color means color3, color4, or what. + // For now, it means color4. + + if(_ps.in_new_list) + return -1; // ERROR + + ParticleGroup *pg = _ps.pgrp; + if(pg == NULL) + return -2; // ERROR + + if(index < 0 || count < 0) + return -3; // ERROR + + if(index + count > pg->p_count) + { + count = pg->p_count - index; + if(count <= 0) + return -4; // ERROR index out of bounds. + } + + int vi = 0, ci = 0, li = 0, si = 0, ai = 0; + + // This could be optimized. + for(int i=0; ilist[index + i]; + + if(verts) + { + verts[vi++] = m.pos.x; + verts[vi++] = m.pos.y; + verts[vi++] = m.pos.z; + } + + if(color) + { + color[ci++] = m.color.x; + color[ci++] = m.color.y; + color[ci++] = m.color.z; + color[ci++] = m.alpha; + } + + if(vel) + { + vel[li++] = m.vel.x; + vel[li++] = m.vel.y; + vel[li++] = m.vel.z; + } + + if(size) + { + size[si++] = m.size.x; + size[si++] = m.size.y; + size[si++] = m.size.z; + } + + if(age) + { + age[ai++] = m.age; + } + } + + return count; +} + +// Returns the number of particles currently in the group. +PARTICLEDLL_API int pGetGroupCount() +{ + _ParticleState &_ps = _GetPState(); + + if(_ps.in_new_list) + return 0; // ERROR + + if(_ps.pgrp == NULL) + return 0; // ERROR + + return _ps.pgrp->p_count; +} diff --git a/main/source/util/Balance.cpp b/main/source/util/Balance.cpp index 5fcbb792..90692dc8 100644 --- a/main/source/util/Balance.cpp +++ b/main/source/util/Balance.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "Balance.h" using std::auto_ptr; @@ -1054,4 +1056,4 @@ bool FileValueContainer::save(void) file.close(); return true; -} \ No newline at end of file +} diff --git a/main/source/util/CString.h b/main/source/util/CString.h index 243e8163..7782d1c4 100644 --- a/main/source/util/CString.h +++ b/main/source/util/CString.h @@ -2,6 +2,7 @@ #define C_STRING_H #include +#include #include #include using std::string; diff --git a/main/source/util/STLUtil.h b/main/source/util/STLUtil.h index dde3e8dd..0fbf1532 100644 --- a/main/source/util/STLUtil.h +++ b/main/source/util/STLUtil.h @@ -29,6 +29,7 @@ #ifndef STL_UTIL_H #define STL_UTIL_H +#include #include "util/nowarnings.h" #include "util/CString.h" #include "types.h" @@ -53,4 +54,4 @@ bool MakeHexPairsFromBytes(const unsigned char* inBytes, string& ioHex, int numB extern int32 sprintf(string& inOutput, const char* inPattern, ...); -#endif \ No newline at end of file +#endif