diff --git a/Engine/source/T3D/aiPlayer.cpp b/Engine/source/T3D/aiPlayer.cpp index 38a3133e6..6e1c39328 100644 --- a/Engine/source/T3D/aiPlayer.cpp +++ b/Engine/source/T3D/aiPlayer.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/aiPlayer.h" @@ -97,6 +102,9 @@ AIPlayer::AIPlayer() mMoveSlowdown = true; mMoveState = ModeStop; + // This new member saves the movement state of the AI so that + // it can be restored after a substituted animation is finished. + mMoveState_saved = -1; mAimObject = 0; mAimLocationSet = false; mTargetInLOS = false; @@ -547,23 +555,27 @@ bool AIPlayer::getAIMove(Move *movePtr) mMoveState = ModeMove; } - if (mMoveStuckTestCountdown > 0) - --mMoveStuckTestCountdown; - else - { - // We should check to see if we are stuck... - F32 locationDelta = (location - mLastLocation).len(); + // Don't check for ai stuckness if animation during + // an anim-clip effect override. + if (mDamageState == Enabled && !(anim_clip_flags & ANIM_OVERRIDDEN) && !isAnimationLocked()) { + if (mMoveStuckTestCountdown > 0) + --mMoveStuckTestCountdown; + else + { + // We should check to see if we are stuck... + F32 locationDelta = (location - mLastLocation).len(); if (locationDelta < mMoveStuckTolerance && mDamageState == Enabled) { // If we are slowing down, then it's likely that our location delta will be less than // our move stuck tolerance. Because we can be both slowing and stuck // we should TRY to check if we've moved. This could use better detection. if ( mMoveState != ModeSlowing || locationDelta == 0 ) - { - mMoveState = ModeStuck; - onStuck(); - } - } + { + mMoveState = ModeStuck; + onStuck(); + } + } + } } } } @@ -626,6 +638,7 @@ bool AIPlayer::getAIMove(Move *movePtr) } #endif // TORQUE_NAVIGATION_ENABLED + if (!(anim_clip_flags & ANIM_OVERRIDDEN) && !isAnimationLocked()) mLastLocation = location; return true; @@ -1415,6 +1428,47 @@ DefineEngineMethod( AIPlayer, clearMoveTriggers, void, ( ),, object->clearMoveTriggers(); } +// These changes coordinate with anim-clip mods to parent class, Player. + +// New method, restartMove(), restores the AIPlayer to its normal move-state +// following animation overrides from AFX. The tag argument is used to match +// the latest override and prevents interruption of overlapping animation +// overrides. See related anim-clip changes in Player.[h,cc]. +void AIPlayer::restartMove(U32 tag) +{ + if (tag != 0 && tag == last_anim_tag) + { + if (mMoveState_saved != -1) + { + mMoveState = (MoveState) mMoveState_saved; + mMoveState_saved = -1; + } + + bool is_death_anim = ((anim_clip_flags & IS_DEATH_ANIM) != 0); + + last_anim_tag = 0; + anim_clip_flags &= ~(ANIM_OVERRIDDEN | IS_DEATH_ANIM); + + if (mDamageState != Enabled) + { + if (!is_death_anim) + { + // this is a bit hardwired and desperate, + // but if he's dead he needs to look like it. + setActionThread("death10", false, false, false); + } + } + } +} + +// New method, saveMoveState(), stores the current movement state +// so that it can be restored when restartMove() is called. +void AIPlayer::saveMoveState() +{ + if (mMoveState_saved == -1) + mMoveState_saved = (S32) mMoveState; +} + F32 AIPlayer::getTargetDistance(GameBase* target, bool _checkEnabled) { if (!isServerObject()) return false; diff --git a/Engine/source/T3D/aiPlayer.h b/Engine/source/T3D/aiPlayer.h index a8430575c..524b0ba16 100644 --- a/Engine/source/T3D/aiPlayer.h +++ b/Engine/source/T3D/aiPlayer.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _AIPLAYER_H_ #define _AIPLAYER_H_ @@ -225,6 +230,18 @@ public: /// @} #endif // TORQUE_NAVIGATION_ENABLED + // New method, restartMove(), restores the AIPlayer to its normal move-state + // following animation overrides from AFX. The tag argument is used to match + // the latest override and prevents interruption of overlapping animation + // overrides. + // New method, saveMoveState(), stores the current movement state + // so that it can be restored when restartMove() is called. + // See related anim-clip changes in Player.[h,cc]. +private: + S32 mMoveState_saved; +public: + void restartMove(U32 tag); + void saveMoveState(); }; #endif diff --git a/Engine/source/T3D/camera.h b/Engine/source/T3D/camera.h index 5e760e61f..484cb2c92 100644 --- a/Engine/source/T3D/camera.h +++ b/Engine/source/T3D/camera.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _CAMERA_H_ #define _CAMERA_H_ @@ -246,6 +251,8 @@ class Camera: public ShapeBase DECLARE_CONOBJECT( Camera ); DECLARE_CATEGORY( "Game" ); DECLARE_DESCRIPTION( "Represents a position, direction and field of view to render a scene from." ); + static F32 getMovementSpeed() { return smMovementSpeed; } + bool isCamera() const { return true; } }; typedef Camera::CameraMotionMode CameraMotionMode; diff --git a/Engine/source/T3D/containerQuery.cpp b/Engine/source/T3D/containerQuery.cpp index 1c3f34609..1c5f1bc90 100644 --- a/Engine/source/T3D/containerQuery.cpp +++ b/Engine/source/T3D/containerQuery.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/containerQuery.h" @@ -91,7 +96,9 @@ void physicalZoneFind(SceneObject* obj, void *key) if (pz->isActive()) { info->gravityScale *= pz->getGravityMod(); - info->appliedForce += pz->getForce(); + Point3F center; + info->box.getCenter(¢er); + info->appliedForce += pz->getForce(¢er); } } diff --git a/Engine/source/T3D/debris.cpp b/Engine/source/T3D/debris.cpp index 61ec9c3b6..57d9a0577 100644 --- a/Engine/source/T3D/debris.cpp +++ b/Engine/source/T3D/debris.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/debris.h" @@ -113,6 +118,85 @@ DebrisData::DebrisData() ignoreWater = true; } +//#define TRACK_DEBRIS_DATA_CLONES + +#ifdef TRACK_DEBRIS_DATA_CLONES +static int debris_data_clones = 0; +#endif + +DebrisData::DebrisData(const DebrisData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ +#ifdef TRACK_DEBRIS_DATA_CLONES + debris_data_clones++; + if (debris_data_clones == 1) + Con::errorf("DebrisData -- Clones are on the loose!"); +#endif + velocity = other.velocity; + velocityVariance = other.velocityVariance; + friction = other.friction; + elasticity = other.elasticity; + lifetime = other.lifetime; + lifetimeVariance = other.lifetimeVariance; + numBounces = other.numBounces; + bounceVariance = other.bounceVariance; + minSpinSpeed = other.minSpinSpeed; + maxSpinSpeed = other.maxSpinSpeed; + explodeOnMaxBounce = other.explodeOnMaxBounce; + staticOnMaxBounce = other.staticOnMaxBounce; + snapOnMaxBounce = other.snapOnMaxBounce; + fade = other.fade; + useRadiusMass = other.useRadiusMass; + baseRadius = other.baseRadius; + gravModifier = other.gravModifier; + terminalVelocity = other.terminalVelocity; + ignoreWater = other.ignoreWater; + shapeName = other.shapeName; + shape = other.shape; // -- TSShape loaded using shapeName + textureName = other.textureName; + explosionId = other.explosionId; // -- for pack/unpack of explosion ptr + explosion = other.explosion; + dMemcpy( emitterList, other.emitterList, sizeof( emitterList ) ); + dMemcpy( emitterIDList, other.emitterIDList, sizeof( emitterIDList ) ); // -- for pack/unpack of emitterList ptrs +} + +DebrisData::~DebrisData() +{ + if (!isTempClone()) + return; + +#ifdef TRACK_DEBRIS_DATA_CLONES + if (debris_data_clones > 0) + { + debris_data_clones--; + if (debris_data_clones == 0) + Con::errorf("DebrisData -- Clones eliminated!"); + } + else + Con::errorf("DebrisData -- Too many clones deleted!"); +#endif +} + +DebrisData* DebrisData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index) +{ + if (!owner || getSubstitutionCount() == 0) + return this; + + DebrisData* sub_debris_db = new DebrisData(*this, true); + performSubstitutions(sub_debris_db, owner, index); + + return sub_debris_db; +} + +void DebrisData::onPerformSubstitutions() +{ + if( shapeName && shapeName[0] != '\0') + { + shape = ResourceManager::get().load(shapeName); + if( bool(shape) == false ) + Con::errorf("DebrisData::onPerformSubstitutions(): failed to load shape \"%s\"", shapeName); + } +} + bool DebrisData::onAdd() { if(!Parent::onAdd()) @@ -269,6 +353,9 @@ void DebrisData::initPersistFields() addField("ignoreWater", TypeBool, Offset(ignoreWater, DebrisData), "If true, this debris object will not collide with water, acting as if the water is not there."); endGroup("Behavior"); + // disallow some field substitutions + onlyKeepClearSubstitutions("emitters"); // subs resolving to "~~", or "~0" are OK + onlyKeepClearSubstitutions("explosion"); Parent::initPersistFields(); } @@ -451,6 +538,8 @@ Debris::Debris() // Only allocated client side. mNetFlags.set( IsGhost ); + ss_object = 0; + ss_index = 0; } Debris::~Debris() @@ -466,6 +555,12 @@ Debris::~Debris() delete mPart; mPart = NULL; } + + if (mDataBlock && mDataBlock->isTempClone()) + { + delete mDataBlock; + mDataBlock = 0; + } } void Debris::initPersistFields() @@ -495,6 +590,8 @@ bool Debris::onNewDataBlock( GameBaseData *dptr, bool reload ) if( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) ) return false; + if (mDataBlock->isTempClone()) + return true; scriptOnNewDataBlock(); return true; @@ -519,7 +616,7 @@ bool Debris::onAdd() if( mDataBlock->emitterList[i] != NULL ) { ParticleEmitter * pEmitter = new ParticleEmitter; - pEmitter->onNewDataBlock( mDataBlock->emitterList[i], false ); + pEmitter->onNewDataBlock(mDataBlock->emitterList[i]->cloneAndPerformSubstitutions(ss_object, ss_index), false); if( !pEmitter->registerObject() ) { Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() ); @@ -537,7 +634,8 @@ bool Debris::onAdd() { sizeList[0] = mSize * 0.5; sizeList[1] = mSize; - sizeList[2] = mSize * 1.5; + for (U32 i = 2; i < ParticleData::PDC_NUM_KEYS; i++) + sizeList[i] = mSize * 1.5; mEmitterList[0]->setSizes( sizeList ); } @@ -546,7 +644,8 @@ bool Debris::onAdd() { sizeList[0] = 0.0; sizeList[1] = mSize * 0.5; - sizeList[2] = mSize; + for (U32 i = 2; i < ParticleData::PDC_NUM_KEYS; i++) + sizeList[i] = mSize; mEmitterList[1]->setSizes( sizeList ); } @@ -798,7 +897,8 @@ void Debris::explode() Point3F explosionPos = getPosition(); Explosion* pExplosion = new Explosion; - pExplosion->onNewDataBlock(mDataBlock->explosion, false); + pExplosion->setSubstitutionData(ss_object, ss_index); + pExplosion->onNewDataBlock(mDataBlock->explosion->cloneAndPerformSubstitutions(ss_object, ss_index), false); MatrixF trans( true ); trans.setPosition( getPosition() ); diff --git a/Engine/source/T3D/debris.h b/Engine/source/T3D/debris.h index 14cbd5e9f..87dcff2e9 100644 --- a/Engine/source/T3D/debris.h +++ b/Engine/source/T3D/debris.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _DEBRIS_H_ #define _DEBRIS_H_ @@ -97,6 +102,12 @@ struct DebrisData : public GameBaseData DECLARE_CONOBJECT(DebrisData); +public: + /*C*/ DebrisData(const DebrisData&, bool = false); + /*D*/ ~DebrisData(); + DebrisData* cloneAndPerformSubstitutions(const SimObject*, S32 index=0); + virtual void onPerformSubstitutions(); + virtual bool allowSubstitutions() const { return true; } }; //************************************************************************** @@ -165,6 +176,11 @@ public: DECLARE_CONOBJECT(Debris); +private: + SimObject* ss_object; + S32 ss_index; +public: + void setSubstitutionData(SimObject* obj, S32 idx=0) { ss_object = obj; ss_index = idx; } }; diff --git a/Engine/source/T3D/fx/explosion.cpp b/Engine/source/T3D/fx/explosion.cpp index e4daaf077..0236da6e9 100644 --- a/Engine/source/T3D/fx/explosion.cpp +++ b/Engine/source/T3D/fx/explosion.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/fx/explosion.h" @@ -50,6 +55,8 @@ #include "renderInstance/renderPassManager.h" #include "console/engineAPI.h" +#include "sfx/sfxProfile.h" + IMPLEMENT_CONOBJECT(Explosion); ConsoleDocClass( Explosion, @@ -281,6 +288,105 @@ ExplosionData::ExplosionData() lightNormalOffset = 0.1f; } +//#define TRACK_EXPLOSION_DATA_CLONES + +#ifdef TRACK_EXPLOSION_DATA_CLONES +static int explosion_data_clones = 0; +#endif + +ExplosionData::ExplosionData(const ExplosionData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ +#ifdef TRACK_EXPLOSION_DATA_CLONES + explosion_data_clones++; + if (explosion_data_clones == 1) + Con::errorf("ExplosionData -- Clones are on the loose!"); +#endif + + dtsFileName = other.dtsFileName; + faceViewer = other.faceViewer; + particleDensity = other.particleDensity; + particleRadius = other.particleRadius; + soundProfile = other.soundProfile; + particleEmitter = other.particleEmitter; + particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr + explosionScale = other.explosionScale; + playSpeed = other.playSpeed; + explosionShape = other.explosionShape; // -- TSShape loaded using dtsFileName + explosionAnimation = other.explosionAnimation; // -- from explosionShape sequence "ambient" + dMemcpy( emitterList, other.emitterList, sizeof( emitterList ) ); + dMemcpy( emitterIDList, other.emitterIDList, sizeof( emitterIDList ) ); // -- for pack/unpack of emitterList ptrs + dMemcpy( debrisList, other.debrisList, sizeof( debrisList ) ); + dMemcpy( debrisIDList, other.debrisIDList, sizeof( debrisIDList ) ); // -- for pack/unpack of debrisList ptrs + debrisThetaMin = other.debrisThetaMin; + debrisThetaMax = other.debrisThetaMax; + debrisPhiMin = other.debrisPhiMin; + debrisPhiMax = other.debrisPhiMax; + debrisNum = other.debrisNum; + debrisNumVariance = other.debrisNumVariance; + debrisVelocity = other.debrisVelocity; + debrisVelocityVariance = other.debrisVelocityVariance; + dMemcpy( explosionList, other.explosionList, sizeof( explosionList ) ); + dMemcpy( explosionIDList, other.explosionIDList, sizeof( explosionIDList ) ); // -- for pack/unpack of explosionList ptrs + delayMS = other.delayMS; + delayVariance = other.delayVariance; + lifetimeMS = other.lifetimeMS; + lifetimeVariance = other.lifetimeVariance; + offset = other.offset; + dMemcpy( sizes, other.times, sizeof( sizes ) ); + dMemcpy( times, other.times, sizeof( times ) ); + shakeCamera = other.shakeCamera; + camShakeFreq = other.camShakeFreq; + camShakeAmp = other.camShakeAmp; + camShakeDuration = other.camShakeDuration; + camShakeRadius = other.camShakeRadius; + camShakeFalloff = other.camShakeFalloff; + lightStartRadius = other.lightStartRadius; + lightEndRadius = other.lightEndRadius; + lightStartColor = other.lightStartColor; + lightEndColor = other.lightEndColor; + lightStartBrightness = other.lightStartBrightness; + lightEndBrightness = other.lightEndBrightness; + lightNormalOffset = other.lightNormalOffset; + // Note - Explosion calls mDataBlock->getName() in warning messages but + // that should be safe. +} + +ExplosionData::~ExplosionData() +{ + if (!isTempClone()) + return; + + if (soundProfile && soundProfile->isTempClone()) + { + delete soundProfile; + soundProfile = 0; + } + + // particleEmitter, emitterList[*], debrisList[*], explosionList[*] will delete themselves + +#ifdef TRACK_EXPLOSION_DATA_CLONES + if (explosion_data_clones > 0) + { + explosion_data_clones--; + if (explosion_data_clones == 0) + Con::errorf("ExplosionData -- Clones eliminated!"); + } + else + Con::errorf("ExplosionData -- Too many clones deleted!"); +#endif +} + +ExplosionData* ExplosionData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index) +{ + if (!owner || getSubstitutionCount() == 0) + return this; + + ExplosionData* sub_explosion_db = new ExplosionData(*this, true); + performSubstitutions(sub_explosion_db, owner, index); + + return sub_explosion_db; +} + void ExplosionData::initPersistFields() { addField( "explosionShape", TypeShapeFilename, Offset(dtsFileName, ExplosionData), @@ -412,6 +518,12 @@ void ExplosionData::initPersistFields() "Distance (in the explosion normal direction) of the PointLight position " "from the explosion center." ); + // disallow some field substitutions + onlyKeepClearSubstitutions("debris"); // subs resolving to "~~", or "~0" are OK + onlyKeepClearSubstitutions("emitter"); + onlyKeepClearSubstitutions("particleEmitter"); + onlyKeepClearSubstitutions("soundProfile"); + onlyKeepClearSubstitutions("subExplosion"); Parent::initPersistFields(); } @@ -808,6 +920,10 @@ Explosion::Explosion() mLight = LIGHTMGR->createLightInfo(); mNetFlags.set( IsGhost ); + ss_object = 0; + ss_index = 0; + mDataBlock = 0; + soundProfile_clone = 0; } Explosion::~Explosion() @@ -820,6 +936,18 @@ Explosion::~Explosion() } SAFE_DELETE(mLight); + + if (soundProfile_clone) + { + delete soundProfile_clone; + soundProfile_clone = 0; + } + + if (mDataBlock && mDataBlock->isTempClone()) + { + delete mDataBlock; + mDataBlock = 0; + } } @@ -978,6 +1106,8 @@ bool Explosion::onNewDataBlock( GameBaseData *dptr, bool reload ) if (!mDataBlock || !Parent::onNewDataBlock( dptr, reload )) return false; + if (mDataBlock->isTempClone()) + return true; scriptOnNewDataBlock(); return true; } @@ -1190,7 +1320,8 @@ void Explosion::launchDebris( Point3F &axis ) launchDir *= debrisVel; Debris *debris = new Debris; - debris->setDataBlock( mDataBlock->debrisList[0] ); + debris->setSubstitutionData(ss_object, ss_index); + debris->setDataBlock(mDataBlock->debrisList[0]->cloneAndPerformSubstitutions(ss_object, ss_index)); debris->setTransform( getTransform() ); debris->init( pos, launchDir ); @@ -1218,7 +1349,8 @@ void Explosion::spawnSubExplosions() { MatrixF trans = getTransform(); Explosion* pExplosion = new Explosion; - pExplosion->setDataBlock( mDataBlock->explosionList[i] ); + pExplosion->setSubstitutionData(ss_object, ss_index); + pExplosion->setDataBlock(mDataBlock->explosionList[i]->cloneAndPerformSubstitutions(ss_object, ss_index)); pExplosion->setTransform( trans ); pExplosion->setInitialState( trans.getPosition(), mInitialNormal, 1); if (!pExplosion->registerObject()) @@ -1256,12 +1388,18 @@ bool Explosion::explode() resetWorldBox(); } - if (mDataBlock->soundProfile) - SFX->playOnce( mDataBlock->soundProfile, &getTransform() ); + SFXProfile* sound_prof = dynamic_cast(mDataBlock->soundProfile); + if (sound_prof) + { + soundProfile_clone = sound_prof->cloneAndPerformSubstitutions(ss_object, ss_index); + SFX->playOnce( soundProfile_clone, &getTransform() ); + if (!soundProfile_clone->isTempClone()) + soundProfile_clone = 0; + } if (mDataBlock->particleEmitter) { mMainEmitter = new ParticleEmitter; - mMainEmitter->setDataBlock(mDataBlock->particleEmitter); + mMainEmitter->setDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index)); mMainEmitter->registerObject(); mMainEmitter->emitParticles(getPosition(), mInitialNormal, mDataBlock->particleRadius, @@ -1273,7 +1411,7 @@ bool Explosion::explode() if( mDataBlock->emitterList[i] != NULL ) { ParticleEmitter * pEmitter = new ParticleEmitter; - pEmitter->setDataBlock( mDataBlock->emitterList[i] ); + pEmitter->setDataBlock(mDataBlock->emitterList[i]->cloneAndPerformSubstitutions(ss_object, ss_index)); if( !pEmitter->registerObject() ) { Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() ); diff --git a/Engine/source/T3D/fx/explosion.h b/Engine/source/T3D/fx/explosion.h index a030776fe..df4396a76 100644 --- a/Engine/source/T3D/fx/explosion.h +++ b/Engine/source/T3D/fx/explosion.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _EXPLOSION_H_ #define _EXPLOSION_H_ @@ -42,6 +47,7 @@ class TSThread; class SFXTrack; struct DebrisData; +class SFXProfile; //-------------------------------------------------------------------------- class ExplosionData : public GameBaseData { public: @@ -126,6 +132,11 @@ class ExplosionData : public GameBaseData { static void initPersistFields(); virtual void packData(BitStream* stream); virtual void unpackData(BitStream* stream); +public: + /*C*/ ExplosionData(const ExplosionData&, bool = false); + /*D*/ ~ExplosionData(); + ExplosionData* cloneAndPerformSubstitutions(const SimObject*, S32 index=0); + virtual bool allowSubstitutions() const { return true; } }; @@ -188,6 +199,12 @@ class Explosion : public GameBase, public ISceneLight DECLARE_CONOBJECT(Explosion); static void initPersistFields(); +private: + SimObject* ss_object; + S32 ss_index; + SFXProfile* soundProfile_clone; +public: + void setSubstitutionData(SimObject* obj, S32 idx=0) { ss_object = obj; ss_index = idx; } }; #endif // _H_EXPLOSION diff --git a/Engine/source/T3D/fx/fxFoliageReplicator.cpp b/Engine/source/T3D/fx/fxFoliageReplicator.cpp index b62644eb0..bd8389486 100644 --- a/Engine/source/T3D/fx/fxFoliageReplicator.cpp +++ b/Engine/source/T3D/fx/fxFoliageReplicator.cpp @@ -43,6 +43,11 @@ // POTENTIAL TODO LIST: // TODO: Clamp item alpha to fog alpha +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/fx/fxFoliageReplicator.h" @@ -402,6 +407,9 @@ void fxFoliageReplicator::initPersistFields() addField( "AllowedTerrainSlope", TypeS32, Offset( mFieldData.mAllowedTerrainSlope, fxFoliageReplicator ), "Maximum surface angle allowed for foliage instances." ); endGroup( "Restrictions" ); // MM: Added Group Footer. + addGroup( "AFX" ); + addField( "AmbientModulationBias", TypeF32, Offset( mFieldData.mAmbientModulationBias,fxFoliageReplicator ), "Multiplier controling amount foliage is modulated by sun's ambient." ); + endGroup( "AFX" ); // Initialise parents' persistent fields. Parent::initPersistFields(); } @@ -1564,7 +1572,12 @@ void fxFoliageReplicator::renderObject(ObjectRenderInst *ri, SceneRenderState *s mFoliageShaderConsts->setSafe(mFoliageShaderGroundAlphaSC, Point4F(mFieldData.mGroundAlpha, mFieldData.mGroundAlpha, mFieldData.mGroundAlpha, mFieldData.mGroundAlpha)); if (mFoliageShaderAmbientColorSC->isValid()) - mFoliageShaderConsts->set(mFoliageShaderAmbientColorSC, state->getAmbientLightColor()); + { + LinearColorF ambient = state->getAmbientLightColor(); + LinearColorF ambient_inv(1.0f-ambient.red, 1.0f-ambient.green, 1.0f-ambient.blue, 0.0f); + ambient += ambient_inv*(1.0f - mFieldData.mAmbientModulationBias); + mFoliageShaderConsts->set(mFoliageShaderAmbientColorSC, ambient); + } GFX->setShaderConstBuffer(mFoliageShaderConsts); @@ -1705,6 +1718,7 @@ U32 fxFoliageReplicator::packUpdate(NetConnection * con, U32 mask, BitStream * s stream->writeFlag(mFieldData.mShowPlacementArea); // Show Placement Area Flag. stream->write(mFieldData.mPlacementBandHeight); // Placement Area Height. stream->write(mFieldData.mPlaceAreaColour); // Placement Area Colour. + stream->write(mFieldData.mAmbientModulationBias); } // Were done ... @@ -1782,6 +1796,7 @@ void fxFoliageReplicator::unpackUpdate(NetConnection * con, BitStream * stream) stream->read(&mFieldData.mPlacementBandHeight); // Placement Area Height. stream->read(&mFieldData.mPlaceAreaColour); + stream->read(&mFieldData.mAmbientModulationBias); // Calculate Fade-In/Out Gradients. mFadeInGradient = 1.0f / mFieldData.mFadeInRegion; mFadeOutGradient = 1.0f / mFieldData.mFadeOutRegion; diff --git a/Engine/source/T3D/fx/fxFoliageReplicator.h b/Engine/source/T3D/fx/fxFoliageReplicator.h index 9b7bad58f..0d8224652 100644 --- a/Engine/source/T3D/fx/fxFoliageReplicator.h +++ b/Engine/source/T3D/fx/fxFoliageReplicator.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _FOLIAGEREPLICATOR_H_ #define _FOLIAGEREPLICATOR_H_ @@ -319,6 +324,7 @@ public: U32 mPlacementBandHeight; LinearColorF mPlaceAreaColour; + F32 mAmbientModulationBias; tagFieldData() { // Set Defaults. @@ -377,6 +383,7 @@ public: mShowPlacementArea = true; mPlacementBandHeight = 25; mPlaceAreaColour .set(0.4f, 0, 0.8f); + mAmbientModulationBias = 1.0f; } } mFieldData; diff --git a/Engine/source/T3D/fx/particle.cpp b/Engine/source/T3D/fx/particle.cpp index cb307ccbb..823376fc7 100644 --- a/Engine/source/T3D/fx/particle.cpp +++ b/Engine/source/T3D/fx/particle.cpp @@ -19,6 +19,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "particle.h" #include "console/consoleTypes.h" #include "console/typeValidators.h" @@ -72,6 +78,8 @@ static const F32 sgDefaultSpinSpeed = 1.f; static const F32 sgDefaultSpinRandomMin = 0.f; static const F32 sgDefaultSpinRandomMax = 0.f; +static const F32 sgDefaultSpinBias = 1.0f; +static const F32 sgDefaultSizeBias = 1.0f; //----------------------------------------------------------------------------- // Constructor @@ -102,9 +110,9 @@ ParticleData::ParticleData() } times[0] = 0.0f; - times[1] = 0.33f; - times[2] = 0.66f; - times[3] = 1.0f; + times[1] = 1.0f; + for (i = 2; i < PDC_NUM_KEYS; i++) + times[i] = -1.0f; texCoords[0].set(0.0,0.0); // texture coords at 4 corners texCoords[1].set(0.0,1.0); // of particle quad @@ -115,18 +123,20 @@ ParticleData::ParticleData() animTexUVs = NULL; // array of tile vertex UVs textureName = NULL; // texture filename textureHandle = NULL; // loaded texture handle + textureExtName = NULL; + textureExtHandle = NULL; + constrain_pos = false; + start_angle = 0.0f; + angle_variance = 0.0f; + sizeBias = sgDefaultSizeBias; + spinBias = sgDefaultSpinBias; + randomizeSpinDir = false; } //----------------------------------------------------------------------------- // Destructor //----------------------------------------------------------------------------- -ParticleData::~ParticleData() -{ - if (animTexUVs) - { - delete [] animTexUVs; - } -} + FRangeValidator dragCoefFValidator(0.f, 5.f); FRangeValidator gravCoefFValidator(-10.f, 10.f); @@ -214,6 +224,15 @@ void ParticleData::initPersistFields() "@brief Time keys used with the colors and sizes keyframes.\n\n" "Values are from 0.0 (particle creation) to 1.0 (end of lifespace)." ); + addGroup("AFX"); + addField("textureExtName", TypeFilename, Offset(textureExtName, ParticleData)); + addField("constrainPos", TypeBool, Offset(constrain_pos, ParticleData)); + addField("angle", TypeF32, Offset(start_angle, ParticleData)); + addField("angleVariance", TypeF32, Offset(angle_variance, ParticleData)); + addField("sizeBias", TypeF32, Offset(sizeBias, ParticleData)); + addField("spinBias", TypeF32, Offset(spinBias, ParticleData)); + addField("randomizeSpinDir", TypeBool, Offset(randomizeSpinDir, ParticleData)); + endGroup("AFX"); Parent::initPersistFields(); } @@ -243,18 +262,22 @@ void ParticleData::packData(BitStream* stream) stream->writeInt((S32)(spinRandomMin + 1000), 11); stream->writeInt((S32)(spinRandomMax + 1000), 11); } + if(stream->writeFlag(spinBias != sgDefaultSpinBias)) + stream->write(spinBias); + stream->writeFlag(randomizeSpinDir); stream->writeFlag(useInvAlpha); S32 i, count; // see how many frames there are: - for(count = 0; count < 3; count++) + for(count = 0; count < ParticleData::PDC_NUM_KEYS-1; count++) if(times[count] >= 1) break; count++; - stream->writeInt(count-1, 2); + // An extra bit is needed for 8 keys. + stream->writeInt(count-1, 3); for( i=0; iwriteFloat( colors[i].green, 7); stream->writeFloat( colors[i].blue, 7); stream->writeFloat( colors[i].alpha, 7); - stream->writeFloat( sizes[i]/MaxParticleSize, 14); + // AFX bits raised from 14 to 16 to allow larger sizes + stream->writeFloat( sizes[i]/MaxParticleSize, 16); stream->writeFloat( times[i], 8); } @@ -279,6 +303,13 @@ void ParticleData::packData(BitStream* stream) mathWrite(*stream, animTexTiling); stream->writeInt(framesPerSec, 8); } + if (stream->writeFlag(textureExtName && textureExtName[0])) + stream->writeString(textureExtName); + stream->writeFlag(constrain_pos); + stream->writeFloat(start_angle/360.0f, 11); + stream->writeFloat(angle_variance/180.0f, 10); + if(stream->writeFlag(sizeBias != sgDefaultSizeBias)) + stream->write(sizeBias); } //----------------------------------------------------------------------------- @@ -322,17 +353,24 @@ void ParticleData::unpackData(BitStream* stream) spinRandomMax = sgDefaultSpinRandomMax; } + if(stream->readFlag()) + stream->read(&spinBias); + else + spinBias = sgDefaultSpinBias; + randomizeSpinDir = stream->readFlag(); useInvAlpha = stream->readFlag(); S32 i; - S32 count = stream->readInt(2) + 1; + // An extra bit is needed for 8 keys. + S32 count = stream->readInt(3) + 1; for(i = 0;i < count; i++) { colors[i].red = stream->readFloat(7); colors[i].green = stream->readFloat(7); colors[i].blue = stream->readFloat(7); colors[i].alpha = stream->readFloat(7); - sizes[i] = stream->readFloat(14) * MaxParticleSize; + // AFX bits raised from 14 to 16 to allow larger sizes + sizes[i] = stream->readFloat(16) * MaxParticleSize; times[i] = stream->readFloat(8); } textureName = (stream->readFlag()) ? stream->readSTString() : 0; @@ -346,6 +384,14 @@ void ParticleData::unpackData(BitStream* stream) mathRead(*stream, &animTexTiling); framesPerSec = stream->readInt(8); } + textureExtName = (stream->readFlag()) ? stream->readSTString() : 0; + constrain_pos = stream->readFlag(); + start_angle = 360.0f*stream->readFloat(11); + angle_variance = 180.0f*stream->readFloat(10); + if(stream->readFlag()) + stream->read(&sizeBias); + else + sizeBias = sgDefaultSizeBias; } bool ParticleData::protectedSetSizes( void *object, const char *index, const char *data) @@ -427,11 +473,33 @@ bool ParticleData::onAdd() } times[0] = 0.0f; - for (U32 i = 1; i < 4; i++) { - if (times[i] < times[i-1]) { - Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) times[%d] < times[%d]", getName(), i, i-1); - times[i] = times[i-1]; - } + for (U32 i = 1; i < PDC_NUM_KEYS; i++) + { + if (times[i] < 0.0f) + break; + if (times[i] < times[i-1]) + { + Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) times[%d] < times[%d]", getName(), i, i-1); + times[i] = times[i-1]; + } + } + + times[0] = 0.0f; + + U32 last_idx = 0; + for (U32 i = 1; i < PDC_NUM_KEYS; i++) + { + if (times[i] < 0.0f) + break; + else + last_idx = i; + } + + for (U32 i = last_idx+1; i < PDC_NUM_KEYS; i++) + { + times[i] = times[last_idx]; + colors[i] = colors[last_idx]; + sizes[i] = sizes[last_idx]; } // Here we validate parameters @@ -470,6 +538,10 @@ bool ParticleData::onAdd() } } + start_angle = mFmod(start_angle, 360.0f); + if (start_angle < 0.0f) + start_angle += 360.0f; + angle_variance = mClampF(angle_variance, -180.0f, 180.0f); return true; } @@ -495,6 +567,15 @@ bool ParticleData::preload(bool server, String &errorStr) error = true; } } + if (textureExtName && textureExtName[0]) + { + textureExtHandle = GFXTexHandle(textureExtName, &GFXStaticTextureSRGBProfile, avar("%s() - textureExtHandle (line %d)", __FUNCTION__, __LINE__)); + if (!textureExtHandle) + { + errorStr = String::ToString("Missing particle texture: %s", textureName); + error = true; + } + } if (animateTexture) { @@ -606,6 +687,11 @@ void ParticleData::initializeParticle(Particle* init, const Point3F& inheritVelo // assign spin amount init->spinSpeed = spinSpeed * gRandGen.randF( spinRandomMin, spinRandomMax ); + // apply spin bias + init->spinSpeed *= spinBias; + // randomize spin direction + if (randomizeSpinDir && (gRandGen.randI( 0, 1 ) == 1)) + init->spinSpeed = -init->spinSpeed; } bool ParticleData::reload(char errorBuffer[256]) @@ -653,3 +739,78 @@ DefineEngineMethod(ParticleData, reload, void, (),, char errorBuffer[256]; object->reload(errorBuffer); } +//#define TRACK_PARTICLE_DATA_CLONES + +#ifdef TRACK_PARTICLE_DATA_CLONES +static int particle_data_clones = 0; +#endif + +ParticleData::ParticleData(const ParticleData& other, bool temp_clone) : SimDataBlock(other, temp_clone) +{ +#ifdef TRACK_PARTICLE_DATA_CLONES + particle_data_clones++; + if (particle_data_clones == 1) + Con::errorf("ParticleData -- Clones are on the loose!"); +#endif + + dragCoefficient = other.dragCoefficient; + windCoefficient = other.windCoefficient; + gravityCoefficient = other.gravityCoefficient; + inheritedVelFactor = other.inheritedVelFactor; + constantAcceleration = other.constantAcceleration; + lifetimeMS = other.lifetimeMS; + lifetimeVarianceMS = other.lifetimeVarianceMS; + spinSpeed = other.spinSpeed; + spinRandomMin = other.spinRandomMin; + spinRandomMax = other.spinRandomMax; + useInvAlpha = other.useInvAlpha; + animateTexture = other.animateTexture; + numFrames = other.numFrames; // -- calc from other fields + framesPerSec = other.framesPerSec; + dMemcpy( colors, other.colors, sizeof( colors ) ); + dMemcpy( sizes, other.sizes, sizeof( sizes ) ); + dMemcpy( times, other.times, sizeof( times ) ); + animTexUVs = other.animTexUVs; // -- calc from other fields + dMemcpy( texCoords, other.texCoords, sizeof( texCoords ) ); + animTexTiling = other.animTexTiling; + animTexFramesString = other.animTexFramesString; + animTexFrames = other.animTexFrames; // -- parsed from animTexFramesString + textureName = other.textureName; + textureHandle = other.textureHandle; + spinBias = other.spinBias; + randomizeSpinDir = other.randomizeSpinDir; + textureExtName = other.textureExtName; + textureExtHandle = other.textureExtHandle; + constrain_pos = other.constrain_pos; + start_angle = other.start_angle; + angle_variance = other.angle_variance; + sizeBias = other.sizeBias; +} + +ParticleData::~ParticleData() +{ + if (animTexUVs) + { + delete [] animTexUVs; + } + + if (!isTempClone()) + return; + +#ifdef TRACK_PARTICLE_DATA_CLONES + if (particle_data_clones > 0) + { + particle_data_clones--; + if (particle_data_clones == 0) + Con::errorf("ParticleData -- Clones eliminated!"); + } + else + Con::errorf("ParticleData -- Too many clones deleted!"); +#endif +} + +void ParticleData::onPerformSubstitutions() +{ + char errorBuffer[256]; + reload(errorBuffer); +} diff --git a/Engine/source/T3D/fx/particle.h b/Engine/source/T3D/fx/particle.h index 35a0c9792..b1c25f7f3 100644 --- a/Engine/source/T3D/fx/particle.h +++ b/Engine/source/T3D/fx/particle.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _PARTICLE_H_ #define _PARTICLE_H_ @@ -44,7 +49,9 @@ class ParticleData : public SimDataBlock public: enum PDConst { - PDC_NUM_KEYS = 4, + // This increase the keyframes from 4 to 8. Especially useful for premult-alpha blended particles + // for which 4 keyframes is often not enough. + PDC_NUM_KEYS = 8, }; F32 dragCoefficient; @@ -97,6 +104,23 @@ class ParticleData : public SimDataBlock static void initPersistFields(); bool reload(char errorBuffer[256]); + public: + /*C*/ ParticleData(const ParticleData&, bool = false); + virtual void onPerformSubstitutions(); + virtual bool allowSubstitutions() const { return true; } + protected: + F32 spinBias; + bool randomizeSpinDir; + StringTableEntry textureExtName; + public: + GFXTexHandle textureExtHandle; + bool constrain_pos; + F32 start_angle; + F32 angle_variance; + F32 sizeBias; + public: + bool loadParameters(); + bool reload(String &errorStr); }; //***************************************************************************** @@ -123,6 +147,10 @@ struct Particle F32 spinSpeed; Particle * next; + Point3F pos_local; + F32 t_last; + Point3F radial_v; // radial vector for concentric effects + // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle. }; diff --git a/Engine/source/T3D/fx/particleEmitter.cpp b/Engine/source/T3D/fx/particleEmitter.cpp index 63d590bad..4005607d7 100644 --- a/Engine/source/T3D/fx/particleEmitter.cpp +++ b/Engine/source/T3D/fx/particleEmitter.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/fx/particleEmitter.h" @@ -38,6 +43,10 @@ #include "lighting/lightInfo.h" #include "console/engineAPI.h" +#if defined(AFX_CAP_PARTICLE_POOLS) +#include "afx/util/afxParticlePool.h" +#endif + Point3F ParticleEmitter::mWindVelocity( 0.0, 0.0, 0.0 ); const F32 ParticleEmitter::AgedSpinToRadians = (1.0f/1000.0f) * (1.0f/360.0f) * M_PI_F * 2.0f; @@ -149,6 +158,21 @@ ParticleEmitterData::ParticleEmitterData() alignParticles = false; alignDirection = Point3F(0.0f, 1.0f, 0.0f); + + ejectionInvert = false; + fade_color = false; + fade_alpha = false; + fade_size = false; + parts_per_eject = 1; + use_emitter_xfm = false; + +#if defined(AFX_CAP_PARTICLE_POOLS) + pool_datablock = 0; + pool_index = 0; + pool_depth_fade = false; + pool_radial_fade = false; + do_pool_id_convert = false; +#endif } @@ -293,6 +317,26 @@ void ParticleEmitterData::initPersistFields() endGroup( "ParticleEmitterData" ); + addGroup("AFX"); + addField("ejectionInvert", TypeBool, Offset(ejectionInvert, ParticleEmitterData)); + addField("fadeColor", TypeBool, Offset(fade_color, ParticleEmitterData)); + addField("fadeAlpha", TypeBool, Offset(fade_alpha, ParticleEmitterData)); + addField("fadeSize", TypeBool, Offset(fade_size, ParticleEmitterData)); + // useEmitterTransform currently does not work in TGEA or T3D + addField("useEmitterTransform", TypeBool, Offset(use_emitter_xfm, ParticleEmitterData)); + endGroup("AFX"); + +#if defined(AFX_CAP_PARTICLE_POOLS) + addGroup("AFX Pooled Particles"); + addField("poolData", TYPEID(), Offset(pool_datablock, ParticleEmitterData)); + addField("poolIndex", TypeS32, Offset(pool_index, ParticleEmitterData)); + addField("poolDepthFade", TypeBool, Offset(pool_depth_fade, ParticleEmitterData)); + addField("poolRadialFade", TypeBool, Offset(pool_radial_fade, ParticleEmitterData)); + endGroup("AFX Pooled Particles"); +#endif + // disallow some field substitutions + disableFieldSubstitutions("particles"); + onlyKeepClearSubstitutions("poolData"); // subs resolving to "~~", or "~0" are OK Parent::initPersistFields(); } @@ -358,6 +402,22 @@ void ParticleEmitterData::packData(BitStream* stream) stream->writeFlag(renderReflection); stream->writeFlag(glow); stream->writeInt( blendStyle, 4 ); + + stream->writeFlag(ejectionInvert); + stream->writeFlag(fade_color); + stream->writeFlag(fade_alpha); + stream->writeFlag(fade_size); + stream->writeFlag(use_emitter_xfm); + +#if defined(AFX_CAP_PARTICLE_POOLS) + if (stream->writeFlag(pool_datablock)) + { + stream->writeRangedU32(packed ? SimObjectId((uintptr_t)pool_datablock) : pool_datablock->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + stream->write(pool_index); + stream->writeFlag(pool_depth_fade); + stream->writeFlag(pool_radial_fade); + } +#endif } //----------------------------------------------------------------------------- @@ -421,6 +481,22 @@ void ParticleEmitterData::unpackData(BitStream* stream) renderReflection = stream->readFlag(); glow = stream->readFlag(); blendStyle = stream->readInt( 4 ); + ejectionInvert = stream->readFlag(); + fade_color = stream->readFlag(); + fade_alpha = stream->readFlag(); + fade_size = stream->readFlag(); + use_emitter_xfm = stream->readFlag(); + +#if defined(AFX_CAP_PARTICLE_POOLS) + if (stream->readFlag()) + { + pool_datablock = (afxParticlePoolData*)stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + stream->read(&pool_index); + pool_depth_fade = stream->readFlag(); + pool_radial_fade = stream->readFlag(); + do_pool_id_convert = true; + } +#endif } //----------------------------------------------------------------------------- @@ -600,6 +676,22 @@ bool ParticleEmitterData::preload(bool server, String &errorStr) if (!server) { +#if defined(AFX_CAP_PARTICLE_POOLS) + if (do_pool_id_convert) + { + SimObjectId db_id = (SimObjectId)(uintptr_t)pool_datablock; + if (db_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(db_id, pool_datablock)) + { + Con::errorf("ParticleEmitterData::reload() -- bad datablockId: 0x%x (poolData)", db_id); + } + } + do_pool_id_convert = false; + } +#endif + // load emitter texture if specified if (textureName && textureName[0]) { @@ -669,6 +761,8 @@ void ParticleEmitterData::allocPrimBuffer( S32 overrideSize ) partListInitSize = maxPartLife / (ejectionPeriodMS - periodVarianceMS); partListInitSize += 8; // add 8 as "fudge factor" to make sure it doesn't realloc if it goes over by 1 + if (parts_per_eject > 1) + partListInitSize *= parts_per_eject; // if override size is specified, then the emitter overran its buffer and needs a larger allocation if( overrideSize != -1 ) @@ -705,6 +799,134 @@ void ParticleEmitterData::allocPrimBuffer( S32 overrideSize ) delete [] indices; } +//#define TRACK_PARTICLE_EMITTER_DATA_CLONES + +#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES +static int emitter_data_clones = 0; +#endif + +ParticleEmitterData::ParticleEmitterData(const ParticleEmitterData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ +#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES + emitter_data_clones++; + if (emitter_data_clones == 1) + Con::errorf("ParticleEmitterData -- Clones are on the loose!"); +#endif + + ejectionPeriodMS = other.ejectionPeriodMS; + periodVarianceMS = other.periodVarianceMS; + ejectionVelocity = other.ejectionVelocity; + velocityVariance = other.velocityVariance; + ejectionOffset = other.ejectionOffset; + ejectionOffsetVariance = other.ejectionOffsetVariance; + thetaMin = other.thetaMin; + thetaMax = other.thetaMax; + phiReferenceVel = other.phiReferenceVel; + phiVariance = other.phiVariance; + softnessDistance = other.softnessDistance; + ambientFactor = other.ambientFactor; + lifetimeMS = other.lifetimeMS; + lifetimeVarianceMS = other.lifetimeVarianceMS; + overrideAdvance = other.overrideAdvance; + orientParticles = other.orientParticles; + orientOnVelocity = other.orientOnVelocity; + useEmitterSizes = other.useEmitterSizes; + useEmitterColors = other.useEmitterColors; + alignParticles = other.alignParticles; + alignDirection = other.alignDirection; + particleString = other.particleString; + particleDataBlocks = other.particleDataBlocks; // -- derived from particleString + dataBlockIds = other.dataBlockIds; // -- derived from particleString + partListInitSize = other.partListInitSize; // -- approx calc from other fields + primBuff = other.primBuff; + blendStyle = other.blendStyle; + sortParticles = other.sortParticles; + reverseOrder = other.reverseOrder; + textureName = other.textureName; + textureHandle = other.textureHandle; // -- TextureHandle loads using textureName + highResOnly = other.highResOnly; + renderReflection = other.renderReflection; + fade_color = other.fade_color; + fade_size = other.fade_size; + fade_alpha = other.fade_alpha; + ejectionInvert = other.ejectionInvert; + parts_per_eject = other.parts_per_eject; // -- set to 1 (used by subclasses) + use_emitter_xfm = other.use_emitter_xfm; +#if defined(AFX_CAP_PARTICLE_POOLS) + pool_datablock = other.pool_datablock; + pool_index = other.pool_index; + pool_depth_fade = other.pool_depth_fade; + pool_radial_fade = other.pool_radial_fade; + do_pool_id_convert = other.do_pool_id_convert; // -- flags pool id conversion need +#endif +} + +ParticleEmitterData::~ParticleEmitterData() +{ + if (!isTempClone()) + return; + + for (S32 i = 0; i < particleDataBlocks.size(); i++) + { + if (particleDataBlocks[i] && particleDataBlocks[i]->isTempClone()) + { + delete particleDataBlocks[i]; + particleDataBlocks[i] = 0; + } + } + +#ifdef TRACK_PARTICLE_EMITTER_DATA_CLONES + if (emitter_data_clones > 0) + { + emitter_data_clones--; + if (emitter_data_clones == 0) + Con::errorf("ParticleEmitterData -- Clones eliminated!"); + } + else + Con::errorf("ParticleEmitterData -- Too many clones deleted!"); +#endif +} + +ParticleEmitterData* ParticleEmitterData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index) +{ + if (!owner) + return this; + + bool clone_parts_db = false; + + // note -- this could be checked when the particle blocks are evaluated + for (S32 i = 0; i < this->particleDataBlocks.size(); i++) + { + if (this->particleDataBlocks[i] && (this->particleDataBlocks[i]->getSubstitutionCount() > 0)) + { + clone_parts_db = true; + break; + } + } + + ParticleEmitterData* sub_emitter_db = this; + + if (this->getSubstitutionCount() > 0 || clone_parts_db) + { + sub_emitter_db = new ParticleEmitterData(*this, true); + performSubstitutions(sub_emitter_db, owner, index); + + if (clone_parts_db) + { + for (S32 i = 0; i < sub_emitter_db->particleDataBlocks.size(); i++) + { + if (sub_emitter_db->particleDataBlocks[i] && (sub_emitter_db->particleDataBlocks[i]->getSubstitutionCount() > 0)) + { + ParticleData* orig_db = sub_emitter_db->particleDataBlocks[i]; + sub_emitter_db->particleDataBlocks[i] = new ParticleData(*orig_db, true); + orig_db->performSubstitutions(sub_emitter_db->particleDataBlocks[i], owner, index); + } + } + } + } + + return sub_emitter_db; +} //----------------------------------------------------------------------------- // ParticleEmitter @@ -736,6 +958,16 @@ ParticleEmitter::ParticleEmitter() // ParticleEmitter should be allocated on the client only. mNetFlags.set( IsGhost ); + fade_amt = 1.0f; + forced_bbox = false; + db_temp_clone = false; + pos_pe.set(0,0,0); + sort_priority = 0; + mDataBlock = 0; + +#if defined(AFX_CAP_PARTICLE_POOLS) + pool = 0; +#endif } //----------------------------------------------------------------------------- @@ -747,6 +979,19 @@ ParticleEmitter::~ParticleEmitter() { delete [] part_store[i]; } + if (db_temp_clone && mDataBlock && mDataBlock->isTempClone()) + { + for (S32 i = 0; i < mDataBlock->particleDataBlocks.size(); i++) + { + if (mDataBlock->particleDataBlocks[i] && mDataBlock->particleDataBlocks[i]->isTempClone()) + { + delete mDataBlock->particleDataBlocks[i]; + mDataBlock->particleDataBlocks[i] = 0; + } + } + delete mDataBlock; + mDataBlock = 0; + } } //----------------------------------------------------------------------------- @@ -771,6 +1016,11 @@ bool ParticleEmitter::onAdd() mObjBox.maxExtents = Point3F(radius, radius, radius); resetWorldBox(); +#if defined(AFX_CAP_PARTICLE_POOLS) + if (pool) + pool->addParticleEmitter(this); +#endif + return true; } @@ -780,6 +1030,14 @@ bool ParticleEmitter::onAdd() //----------------------------------------------------------------------------- void ParticleEmitter::onRemove() { +#if defined(AFX_CAP_PARTICLE_POOLS) + if (pool) + { + pool->removeParticleEmitter(this); + pool = 0; + } +#endif + removeFromScene(); Parent::onRemove(); } @@ -825,6 +1083,11 @@ bool ParticleEmitter::onNewDataBlock( GameBaseData *dptr, bool reload ) part_list_head.next = NULL; n_parts = 0; } + if (mDataBlock->isTempClone()) + { + db_temp_clone = true; + return true; + } scriptOnNewDataBlock(); return true; @@ -861,6 +1124,11 @@ LinearColorF ParticleEmitter::getCollectiveColor() //----------------------------------------------------------------------------- void ParticleEmitter::prepRenderImage(SceneRenderState* state) { +#if defined(AFX_CAP_PARTICLE_POOLS) + if (pool) + return; +#endif + if( state->isReflectPass() && !getDataBlock()->renderReflection ) return; @@ -889,6 +1157,7 @@ void ParticleEmitter::prepRenderImage(SceneRenderState* state) ri->translucentSort = true; ri->type = RenderPassManager::RIT_Particle; ri->sortDistSq = getRenderWorldBox().getSqDistanceToPoint( camPos ); + ri->defaultKey = (-sort_priority*100); // Draw the system offscreen unless the highResOnly flag is set on the datablock ri->systemState = ( getDataBlock()->highResOnly ? PSS_AwaitingHighResDraw : PSS_AwaitingOffscreenDraw ); @@ -992,6 +1261,7 @@ void ParticleEmitter::emitParticles(const Point3F& point, return; } + pos_pe = point; Point3F realStart; if( useLastPosition && mHasLastPosition ) realStart = mLastPosition; @@ -1059,7 +1329,8 @@ void ParticleEmitter::emitParticles(const Point3F& start, // Create particle at the correct position Point3F pos; pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); - addParticle(pos, axis, velocity, axisx); + addParticle(pos, axis, velocity, axisx, numMilliseconds-currTime); + particlesAdded = true; mNextParticleTime = 0; } @@ -1089,7 +1360,7 @@ void ParticleEmitter::emitParticles(const Point3F& start, // Create particle at the correct position Point3F pos; pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); - addParticle(pos, axis, velocity, axisx); + addParticle(pos, axis, velocity, axisx, numMilliseconds-currTime); particlesAdded = true; // This override-advance code is restored in order to correctly adjust @@ -1114,17 +1385,27 @@ void ParticleEmitter::emitParticles(const Point3F& start, { if (advanceMS != 0) { - F32 t = F32(advanceMS) / 1000.0; + F32 t = F32(advanceMS) / 1000.0; - Point3F a = last_part->acc; - a -= last_part->vel * last_part->dataBlock->dragCoefficient; - a -= mWindVelocity * last_part->dataBlock->windCoefficient; - a += Point3F(0.0f, 0.0f, -9.81f) * last_part->dataBlock->gravityCoefficient; + Point3F a = last_part->acc; + a -= last_part->vel * last_part->dataBlock->dragCoefficient; + a -= mWindVelocity * last_part->dataBlock->windCoefficient; + //a += Point3F(0.0f, 0.0f, -9.81f) * last_part->dataBlock->gravityCoefficient; + a.z += -9.81f*last_part->dataBlock->gravityCoefficient; // as long as gravity is a constant, this is faster - last_part->vel += a * t; - last_part->pos += last_part->vel * t; + last_part->vel += a * t; + //last_part->pos += last_part->vel * t; + last_part->pos_local += last_part->vel * t; - updateKeyData( last_part ); + // AFX -- allow subclasses to adjust the particle params here + sub_particleUpdate(last_part); + + if (last_part->dataBlock->constrain_pos) + last_part->pos = last_part->pos_local + this->pos_pe; + else + last_part->pos = last_part->pos_local; + + updateKeyData( last_part ); } } } @@ -1196,7 +1477,7 @@ void ParticleEmitter::emitParticles(const Point3F& rCenter, axis.normalize(); pos += rCenter; - addParticle(pos, axis, velocity, axisz); + addParticle(pos, axis, velocity, axisz, 0); } // Set world bounding box @@ -1219,6 +1500,8 @@ void ParticleEmitter::emitParticles(const Point3F& rCenter, //----------------------------------------------------------------------------- void ParticleEmitter::updateBBox() { + if (forced_bbox) + return; Point3F minPt(1e10, 1e10, 1e10); Point3F maxPt(-1e10, -1e10, -1e10); @@ -1239,15 +1522,18 @@ void ParticleEmitter::updateBBox() boxScale.y = getMax(boxScale.y, 1.0f); boxScale.z = getMax(boxScale.z, 1.0f); mBBObjToWorld.scale(boxScale); + +#if defined(AFX_CAP_PARTICLE_POOLS) + if (pool) + pool->updatePoolBBox(this); +#endif } //----------------------------------------------------------------------------- // addParticle //----------------------------------------------------------------------------- -void ParticleEmitter::addParticle(const Point3F& pos, - const Point3F& axis, - const Point3F& vel, - const Point3F& axisx) +void ParticleEmitter::addParticle(const Point3F& pos, const Point3F& axis, const Point3F& vel, + const Point3F& axisx, const U32 age_offset) { n_parts++; if (n_parts > n_part_capacity || n_parts > mDataBlock->partListInitSize) @@ -1269,6 +1555,16 @@ void ParticleEmitter::addParticle(const Point3F& pos, pNew->next = part_list_head.next; part_list_head.next = pNew; + // for earlier access to constrain_pos, the ParticleData datablock is chosen here instead + // of later in the method. + U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size(); + ParticleData* part_db = mDataBlock->particleDataBlocks[dBlockIndex]; + // set start position to world or local space + Point3F pos_start; + if (part_db->constrain_pos) + pos_start.set(0,0,0); + else + pos_start = pos; Point3F ejectionAxis = axis; F32 theta = (mDataBlock->thetaMax - mDataBlock->thetaMin) * gRandGen.randF() + mDataBlock->thetaMin; @@ -1290,14 +1586,17 @@ void ParticleEmitter::addParticle(const Point3F& pos, F32 initialVel = mDataBlock->ejectionVelocity; initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance; - pNew->pos = pos + (ejectionAxis * (mDataBlock->ejectionOffset + mDataBlock->ejectionOffsetVariance* gRandGen.randF()) ); - pNew->vel = ejectionAxis * initialVel; - pNew->orientDir = ejectionAxis; + pNew->pos = pos_start + (ejectionAxis * (mDataBlock->ejectionOffset + mDataBlock->ejectionOffsetVariance* gRandGen.randF()) ); + pNew->pos_local = pNew->pos; + pNew->vel = mDataBlock->ejectionInvert ? ejectionAxis * -initialVel : ejectionAxis * initialVel; + if (mDataBlock->orientParticles) + pNew->orientDir = ejectionAxis; + else + // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle. + pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance); pNew->acc.set(0, 0, 0); - pNew->currentAge = 0; - - // Choose a new particle datablack randomly from the list - U32 dBlockIndex = gRandGen.randI() % mDataBlock->particleDataBlocks.size(); + pNew->currentAge = age_offset; + pNew->t_last = 0.0f; mDataBlock->particleDataBlocks[dBlockIndex]->initializeParticle(pNew, vel); updateKeyData( pNew ); @@ -1379,8 +1678,10 @@ void ParticleEmitter::updateKeyData( Particle *part ) if( part->totalLifetime < 1 ) part->totalLifetime = 1; - F32 t = F32(part->currentAge) / F32(part->totalLifetime); - AssertFatal(t <= 1.0f, "Out out bounds filter function for particle."); + if (part->currentAge > part->totalLifetime) + part->currentAge = part->totalLifetime; + F32 t = (F32)part->currentAge / (F32)part->totalLifetime; + for( U32 i = 1; i < ParticleData::PDC_NUM_KEYS; i++ ) { @@ -1412,7 +1713,25 @@ void ParticleEmitter::updateKeyData( Particle *part ) { part->size = (part->dataBlock->sizes[i-1] * (1.0 - firstPart)) + (part->dataBlock->sizes[i] * firstPart); + part->size *= part->dataBlock->sizeBias; } + + if (mDataBlock->fade_color) + { + if (mDataBlock->fade_alpha) + part->color *= fade_amt; + else + { + part->color.red *= fade_amt; + part->color.green *= fade_amt; + part->color.blue *= fade_amt; + } + } + else if (mDataBlock->fade_alpha) + part->color.alpha *= fade_amt; + + if (mDataBlock->fade_size) + part->size *= fade_amt; break; } @@ -1424,19 +1743,25 @@ void ParticleEmitter::updateKeyData( Particle *part ) //----------------------------------------------------------------------------- void ParticleEmitter::update( U32 ms ) { - // TODO: Prefetch + F32 t = F32(ms)/1000.0f; // AFX -- moved outside loop, no need to recalculate this for every particle for (Particle* part = part_list_head.next; part != NULL; part = part->next) { - F32 t = F32(ms) / 1000.0; - Point3F a = part->acc; - a -= part->vel * part->dataBlock->dragCoefficient; + a -= part->vel * part->dataBlock->dragCoefficient; a -= mWindVelocity * part->dataBlock->windCoefficient; - a += Point3F(0.0f, 0.0f, -9.81f) * part->dataBlock->gravityCoefficient; + a.z += -9.81f*part->dataBlock->gravityCoefficient; // AFX -- as long as gravity is a constant, this is faster part->vel += a * t; - part->pos += part->vel * t; + part->pos_local += part->vel * t; + + // AFX -- allow subclasses to adjust the particle params here + sub_particleUpdate(part); + + if (part->dataBlock->constrain_pos) + part->pos = part->pos_local + this->pos_pe; + else + part->pos = part->pos_local; updateKeyData( part ); } @@ -1999,3 +2324,43 @@ DefineEngineMethod(ParticleEmitterData, reload, void,(),, { object->reload(); } +void ParticleEmitter::emitParticlesExt(const MatrixF& xfm, const Point3F& point, + const Point3F& velocity, const U32 numMilliseconds) +{ + if (mDataBlock->use_emitter_xfm) + { + Point3F zero_point(0.0f, 0.0f, 0.0f); + this->pos_pe = zero_point; + this->setTransform(xfm); + Point3F axis(0.0,0.0,1.0); + xfm.mulV(axis); + emitParticles(zero_point, true, axis, velocity, numMilliseconds); + } + else + { + this->pos_pe = point; + Point3F axis(0.0,0.0,1.0); + xfm.mulV(axis); + emitParticles(point, true, axis, velocity, numMilliseconds); + } +} + +void ParticleEmitter::setForcedObjBox(Box3F& box) +{ + mObjBox = box; + forced_bbox = true; +#if defined(AFX_CAP_PARTICLE_POOLS) + if (pool) + pool->updatePoolBBox(this); +#endif +} + +void ParticleEmitter::setSortPriority(S8 priority) +{ + sort_priority = (priority == 0) ? 1 : priority; +#if defined(AFX_CAP_PARTICLE_POOLS) + if (pool) + pool->setSortPriority(sort_priority); +#endif +} + diff --git a/Engine/source/T3D/fx/particleEmitter.h b/Engine/source/T3D/fx/particleEmitter.h index 122dbcb00..0750ab864 100644 --- a/Engine/source/T3D/fx/particleEmitter.h +++ b/Engine/source/T3D/fx/particleEmitter.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _H_PARTICLE_EMITTER #define _H_PARTICLE_EMITTER @@ -42,6 +47,12 @@ class RenderPassManager; class ParticleData; +#define AFX_CAP_PARTICLE_POOLS +#if defined(AFX_CAP_PARTICLE_POOLS) +class afxParticlePoolData; +class afxParticlePool; +#endif + //***************************************************************************** // Particle Emitter Data //***************************************************************************** @@ -113,6 +124,26 @@ class ParticleEmitterData : public GameBaseData bool glow; ///< Renders this emitter into the glow buffer. bool reload(); +public: + bool fade_color; + bool fade_size; + bool fade_alpha; + bool ejectionInvert; + U8 parts_per_eject; + bool use_emitter_xfm; +#if defined(AFX_CAP_PARTICLE_POOLS) +public: + afxParticlePoolData* pool_datablock; + U32 pool_index; + bool pool_depth_fade; + bool pool_radial_fade; + bool do_pool_id_convert; +#endif +public: + /*C*/ ParticleEmitterData(const ParticleEmitterData&, bool = false); + /*D*/ ~ParticleEmitterData(); + virtual ParticleEmitterData* cloneAndPerformSubstitutions(const SimObject*, S32 index=0); + virtual bool allowSubstitutions() const { return true; } }; //***************************************************************************** @@ -121,6 +152,9 @@ class ParticleEmitterData : public GameBaseData class ParticleEmitter : public GameBase { typedef GameBase Parent; +#if defined(AFX_CAP_PARTICLE_POOLS) + friend class afxParticlePool; +#endif public: @@ -190,7 +224,7 @@ class ParticleEmitter : public GameBase /// @param axis /// @param vel Initial velocity /// @param axisx - void addParticle(const Point3F &pos, const Point3F &axis, const Point3F &vel, const Point3F &axisx); + void addParticle(const Point3F &pos, const Point3F &axis, const Point3F &vel, const Point3F &axisx, const U32 age_offset); inline void setupBillboard( Particle *part, @@ -227,7 +261,12 @@ class ParticleEmitter : public GameBase // PEngine interface private: + // AFX subclasses to ParticleEmitter require access to some members and methods of + // ParticleEmitter which are normally declared with private scope. In this section, + // protected and private scope statements have been inserted inline with the original + // code to expose the necessary members and methods. void update( U32 ms ); +protected: inline void updateKeyData( Particle *part ); @@ -239,25 +278,30 @@ class ParticleEmitter : public GameBase ParticleEmitterData* mDataBlock; +protected: U32 mInternalClock; U32 mNextParticleTime; Point3F mLastPosition; bool mHasLastPosition; +private: MatrixF mBBObjToWorld; bool mDeleteWhenEmpty; bool mDeleteOnTick; +protected: S32 mLifetimeMS; S32 mElapsedTimeMS; +private: F32 sizes[ ParticleData::PDC_NUM_KEYS ]; LinearColorF colors[ ParticleData::PDC_NUM_KEYS ]; GFXVertexBufferHandle mVertBuff; +protected: // These members are for implementing a link-list of the active emitter // particles. Member part_store contains blocks of particles that can be // chained in a link-list. Usually the first part_store block is large @@ -268,8 +312,28 @@ class ParticleEmitter : public GameBase Particle part_list_head; S32 n_part_capacity; S32 n_parts; +private: S32 mCurBuffSize; + protected: + F32 fade_amt; + bool forced_bbox; + bool db_temp_clone; + Point3F pos_pe; + S8 sort_priority; + virtual void sub_particleUpdate(Particle*) { } + public: + virtual void emitParticlesExt(const MatrixF& xfm, const Point3F& point, const Point3F& velocity, const U32 numMilliseconds); + void setFadeAmount(F32 amt) { fade_amt = amt; } + void setForcedObjBox(Box3F& box); + void setSortPriority(S8 priority); +#if defined(AFX_CAP_PARTICLE_POOLS) + protected: + afxParticlePool* pool; + public: + void clearPool() { pool = 0; } + void setPool(afxParticlePool* p) { pool = p; } +#endif }; #endif // _H_PARTICLE_EMITTER diff --git a/Engine/source/T3D/gameBase/gameBase.cpp b/Engine/source/T3D/gameBase/gameBase.cpp index 5c7e76695..4a98742c1 100644 --- a/Engine/source/T3D/gameBase/gameBase.cpp +++ b/Engine/source/T3D/gameBase/gameBase.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/gameBase/gameBase.h" #include "console/consoleTypes.h" @@ -36,6 +41,7 @@ #include "T3D/aiConnection.h" #endif +#include "afx/arcaneFX.h" //---------------------------------------------------------------------------- // Ghost update relative priority values @@ -122,6 +128,12 @@ GameBaseData::GameBaseData() category = ""; packed = false; } +GameBaseData::GameBaseData(const GameBaseData& other, bool temp_clone) : SimDataBlock(other, temp_clone) +{ + packed = other.packed; + category = other.category; + //mReloadSignal = other.mReloadSignal; // DO NOT copy the mReloadSignal member. +} void GameBaseData::inspectPostApply() { @@ -244,6 +256,8 @@ GameBase::GameBase() GameBase::~GameBase() { + if (scope_registered) + arcaneFX::unregisterScopedObject(this); } @@ -256,8 +270,16 @@ bool GameBase::onAdd() // Datablock must be initialized on the server. // Client datablock are initialized by the initial update. - if ( isServerObject() && mDataBlock && !onNewDataBlock( mDataBlock, false ) ) - return false; + if (isClientObject()) + { + if (scope_id > 0 && !scope_registered) + arcaneFX::registerScopedObject(this); + } + else + { + if ( mDataBlock && !onNewDataBlock( mDataBlock, false ) ) + return false; + } setProcessTick( true ); @@ -266,6 +288,8 @@ bool GameBase::onAdd() void GameBase::onRemove() { + if (scope_registered) + arcaneFX::unregisterScopedObject(this); // EDITOR FEATURE: Remove us from the reload signal of our datablock. if ( mDataBlock ) mDataBlock->mReloadSignal.remove( this, &GameBase::_onDatablockModified ); @@ -290,6 +314,9 @@ bool GameBase::onNewDataBlock( GameBaseData *dptr, bool reload ) if ( !mDataBlock ) return false; + // Don't set mask when new datablock is a temp-clone. + if (mDataBlock->isTempClone()) + return true; setMaskBits(DataBlockMask); return true; @@ -543,6 +570,11 @@ U32 GameBase::packUpdate( NetConnection *connection, U32 mask, BitStream *stream stream->writeFlag(mIsAiControlled); #endif + if (stream->writeFlag(mask & ScopeIdMask)) + { + if (stream->writeFlag(scope_refs > 0)) + stream->writeInt(scope_id, SCOPE_ID_BITS); + } return retMask; } @@ -581,6 +613,11 @@ void GameBase::unpackUpdate(NetConnection *con, BitStream *stream) mTicksSinceLastMove = 0; mIsAiControlled = stream->readFlag(); #endif + if (stream->readFlag()) + { + scope_id = (stream->readFlag()) ? (U16) stream->readInt(SCOPE_ID_BITS) : 0; + scope_refs = 0; + } } void GameBase::onMount( SceneObject *obj, S32 node ) diff --git a/Engine/source/T3D/gameBase/gameBase.h b/Engine/source/T3D/gameBase/gameBase.h index 3623603fa..e6cf5c973 100644 --- a/Engine/source/T3D/gameBase/gameBase.h +++ b/Engine/source/T3D/gameBase/gameBase.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _GAMEBASE_H_ #define _GAMEBASE_H_ @@ -113,6 +118,8 @@ public: DECLARE_CALLBACK( void, onMount, ( SceneObject* obj, SceneObject* mountObj, S32 node ) ); DECLARE_CALLBACK( void, onUnmount, ( SceneObject* obj, SceneObject* mountObj, S32 node ) ); /// @} +public: + GameBaseData(const GameBaseData&, bool = false); }; //---------------------------------------------------------------------------- @@ -229,7 +236,8 @@ public: enum GameBaseMasks { DataBlockMask = Parent::NextFreeMask << 0, ExtendedInfoMask = Parent::NextFreeMask << 1, - NextFreeMask = Parent::NextFreeMask << 2 + ScopeIdMask = Parent::NextFreeMask << 2, + NextFreeMask = Parent::NextFreeMask << 3, }; // net flags added by game base @@ -453,6 +461,8 @@ private: /// within this callback. /// void _onDatablockModified(); +protected: + void onScopeIdChange() { setMaskBits(ScopeIdMask); } }; diff --git a/Engine/source/T3D/gameBase/gameConnection.cpp b/Engine/source/T3D/gameBase/gameConnection.cpp index 08125c261..d39b5c249 100644 --- a/Engine/source/T3D/gameBase/gameConnection.cpp +++ b/Engine/source/T3D/gameBase/gameConnection.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/gameBase/gameConnection.h" @@ -52,6 +57,11 @@ #include "T3D/gameBase/std/stdMoveList.h" #endif +#ifdef AFX_CAP_DATABLOCK_CACHE +#include "core/stream/fileStream.h" +#endif + +#include "afx/arcaneFX.h" //---------------------------------------------------------------------------- #define MAX_MOVE_PACKET_SENDS 4 @@ -173,9 +183,26 @@ IMPLEMENT_CALLBACK( GameConnection, onFlash, void, (bool state), (state), "either is on or both are off. Typically this is used to enable the flash postFx.\n\n" "@param state Set to true if either the damage flash or white out conditions are active.\n\n"); +#ifdef AFX_CAP_DATABLOCK_CACHE +StringTableEntry GameConnection::server_cache_filename = ""; +StringTableEntry GameConnection::client_cache_filename = ""; +bool GameConnection::server_cache_on = false; +bool GameConnection::client_cache_on = false; +#endif //---------------------------------------------------------------------------- GameConnection::GameConnection() { + mRolloverObj = NULL; + mPreSelectedObj = NULL; + mSelectedObj = NULL; + mChangedSelectedObj = false; + mPreSelectTimestamp = 0; + zoned_in = false; + +#ifdef AFX_CAP_DATABLOCK_CACHE + client_db_stream = new InfiniteBitStream; + server_cache_CRC = 0xffffffff; +#endif mLagging = false; mControlObject = NULL; mCameraObject = NULL; @@ -246,6 +273,10 @@ GameConnection::~GameConnection() dFree(mConnectArgv[i]); dFree(mJoinPassword); delete mMoveList; + +#ifdef AFX_CAP_DATABLOCK_CACHE + delete client_db_stream; +#endif } //---------------------------------------------------------------------------- @@ -1144,6 +1175,17 @@ void GameConnection::readPacket(BitStream *bstream) { mMoveList->clientReadMovePacket(bstream); + // selected object - do we have a change in status? + if (bstream->readFlag()) + { + if (bstream->readFlag()) + { + S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize); + setSelectedObj(static_cast(resolveGhost(gIndex))); + } + else + setSelectedObj(NULL); + } bool hadFlash = mDamageFlash > 0 || mWhiteOut > 0; mDamageFlash = 0; mWhiteOut = 0; @@ -1387,6 +1429,35 @@ void GameConnection::writePacket(BitStream *bstream, PacketNotify *note) // all the damage flash & white out S32 gIndex = -1; + if (mChangedSelectedObj) + { + S32 gidx; + // send NULL player + if ((mSelectedObj == NULL) || mSelectedObj.isNull()) + { + bstream->writeFlag(true); + bstream->writeFlag(false); + mChangedSelectedObj = false; + } + // send ghost-idx + else if ((gidx = getGhostIndex(mSelectedObj)) != -1) + { + Con::printf("SEND OBJECT SELECTION"); + bstream->writeFlag(true); + bstream->writeFlag(true); + bstream->writeInt(gidx, NetConnection::GhostIdBitSize); + mChangedSelectedObj = false; + } + // not fully changed yet + else + { + bstream->writeFlag(false); + mChangedSelectedObj = true; + } + } + else + bstream->writeFlag(false); + if (!mControlObject.isNull()) { gIndex = getGhostIndex(mControlObject); @@ -1608,6 +1679,14 @@ void GameConnection::preloadNextDataBlock(bool hadNewFiles) sendConnectionMessage(DataBlocksDownloadDone, mDataBlockSequence); // gResourceManager->setMissingFileLogging(false); + +#ifdef AFX_CAP_DATABLOCK_CACHE + // This should be the last of the datablocks. An argument of false + // indicates that this is a client save. + if (clientCacheEnabled()) + saveDatablockCache(false); +#endif + return; } mFilesWereDownloaded = hadNewFiles; @@ -1771,7 +1850,11 @@ DefineEngineMethod( GameConnection, transmitDataBlocks, void, (S32 sequence),, const U32 iCount = pGroup->size(); // If this is the local client... +#ifdef AFX_CAP_DATABLOCK_CACHE + if (GameConnection::getLocalClientConnection() == object && !GameConnection::serverCacheEnabled()) +#else if (GameConnection::getLocalClientConnection() == object) +#endif { // Set up a pointer to the datablock. SimDataBlock* pDataBlock = 0; @@ -2166,6 +2249,13 @@ void GameConnection::consoleInit() "@ingroup Networking\n"); // Con::addVariable("specialFog", TypeBool, &SceneGraph::useSpecial); + +#ifdef AFX_CAP_DATABLOCK_CACHE + Con::addVariable("$Pref::Server::DatablockCacheFilename", TypeString, &server_cache_filename); + Con::addVariable("$pref::Client::DatablockCacheFilename", TypeString, &client_cache_filename); + Con::addVariable("$Pref::Server::EnableDatablockCache", TypeBool, &server_cache_on); + Con::addVariable("$pref::Client::EnableDatablockCache", TypeBool, &client_cache_on); +#endif } DefineEngineMethod( GameConnection, startRecording, void, (const char* fileName),, @@ -2360,4 +2450,522 @@ DefineEngineMethod( GameConnection, getVisibleGhostDistance, F32, (),, ) { return object->getVisibleGhostDistance(); -} \ No newline at end of file +} + +// The object selection code here is, in part, based, on functionality described +// in the following resource: +// Object Selection in Torque by Dave Myers +// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=7335 + +ConsoleMethod(GameConnection, setSelectedObj, bool, 3, 4, "(object, [propagate_to_client])") +{ + SceneObject* pending_selection; + if (!Sim::findObject(argv[2], pending_selection)) + return false; + + bool propagate_to_client = (argc > 3) ? dAtob(argv[3]) : false; + object->setSelectedObj(pending_selection, propagate_to_client); + + return true; +} + +ConsoleMethod(GameConnection, getSelectedObj, S32, 2, 2, "()") +{ + SimObject* selected = object->getSelectedObj(); + return (selected) ? selected->getId(): -1; +} + +ConsoleMethod(GameConnection, clearSelectedObj, void, 2, 3, "([propagate_to_client])") +{ + bool propagate_to_client = (argc > 2) ? dAtob(argv[2]) : false; + object->setSelectedObj(NULL, propagate_to_client); +} + +ConsoleMethod(GameConnection, setPreSelectedObjFromRollover, void, 2, 2, "()") +{ + object->setPreSelectedObjFromRollover(); +} + +ConsoleMethod(GameConnection, clearPreSelectedObj, void, 2, 2, "()") +{ + object->clearPreSelectedObj(); +} + +ConsoleMethod(GameConnection, setSelectedObjFromPreSelected, void, 2, 2, "()") +{ + object->setSelectedObjFromPreSelected(); +} + +void GameConnection::setSelectedObj(SceneObject* so, bool propagate_to_client) +{ + if (!isConnectionToServer()) + { + // clear previously selected object + if (mSelectedObj) + clearNotify(mSelectedObj); + + // save new selection + mSelectedObj = so; + + // mark selected object + if (mSelectedObj) + deleteNotify(mSelectedObj); + + // mark selection dirty + if (propagate_to_client) + mChangedSelectedObj = true; + + return; + } + + // clear previously selected object + if (mSelectedObj) + { + mSelectedObj->setSelectionFlags(mSelectedObj->getSelectionFlags() & ~SceneObject::SELECTED); + clearNotify(mSelectedObj); + Con::executef(this, "onObjectDeselected", mSelectedObj->getIdString()); + } + + // save new selection + mSelectedObj = so; + + // mark selected object + if (mSelectedObj) + { + mSelectedObj->setSelectionFlags(mSelectedObj->getSelectionFlags() | SceneObject::SELECTED); + deleteNotify(mSelectedObj); + } + + // mark selection dirty + //mChangedSelectedObj = true; + + // notify appropriate script of the change + if (mSelectedObj) + Con::executef(this, "onObjectSelected", mSelectedObj->getIdString()); +} + +void GameConnection::setRolloverObj(SceneObject* so) +{ + // save new selection + mRolloverObj = so; + + // notify appropriate script of the change + Con::executef(this, "onObjectRollover", (mRolloverObj) ? mRolloverObj->getIdString() : ""); +} + +void GameConnection::setPreSelectedObjFromRollover() +{ + mPreSelectedObj = mRolloverObj; + mPreSelectTimestamp = Platform::getRealMilliseconds(); +} + +void GameConnection::clearPreSelectedObj() +{ + mPreSelectedObj = 0; + mPreSelectTimestamp = 0; +} + +void GameConnection::setSelectedObjFromPreSelected() +{ + U32 now = Platform::getRealMilliseconds(); + if (now - mPreSelectTimestamp < arcaneFX::sTargetSelectionTimeoutMS) + setSelectedObj(mPreSelectedObj); + mPreSelectedObj = 0; +} + +void GameConnection::onDeleteNotify(SimObject* obj) +{ + if (obj == mSelectedObj) + setSelectedObj(NULL); + + Parent::onDeleteNotify(obj); +} + +#ifdef AFX_CAP_DATABLOCK_CACHE + +void GameConnection::tempDisableStringBuffering(BitStream* bs) const +{ + bs->setStringBuffer(0); +} + +void GameConnection::restoreStringBuffering(BitStream* bs) const +{ + bs->clearStringBuffer(); +} + +// rewind to stream postion and then move raw bytes into client_db_stream +// for caching purposes. +void GameConnection::repackClientDatablock(BitStream* bstream, S32 start_pos) +{ + static U8 bit_buffer[Net::MaxPacketDataSize]; + + if (!clientCacheEnabled() || !client_db_stream) + return; + + S32 cur_pos = bstream->getCurPos(); + S32 n_bits = cur_pos - start_pos; + if (n_bits <= 0) + return; + + bstream->setCurPos(start_pos); + bstream->readBits(n_bits, bit_buffer); + bstream->setCurPos(cur_pos); + + //S32 start_pos2 = client_db_stream->getCurPos(); + client_db_stream->writeBits(n_bits, bit_buffer); +} + +#define CLIENT_CACHE_VERSION_CODE 47241113 + +void GameConnection::saveDatablockCache(bool on_server) +{ + InfiniteBitStream bit_stream; + BitStream* bstream = 0; + + if (on_server) + { + SimDataBlockGroup *g = Sim::getDataBlockGroup(); + + // find the first one we haven't sent: + U32 i, groupCount = g->size(); + S32 key = this->getDataBlockModifiedKey(); + for (i = 0; i < groupCount; i++) + if (((SimDataBlock*)(*g)[i])->getModifiedKey() > key) + break; + + // nothing to save + if (i == groupCount) + return; + + bstream = &bit_stream; + + for (;i < groupCount; i++) + { + SimDataBlock* obj = (SimDataBlock*)(*g)[i]; + GameConnection* gc = this; + NetConnection* conn = this; + SimObjectId id = obj->getId(); + + if (bstream->writeFlag(gc->getDataBlockModifiedKey() < obj->getModifiedKey())) // A - flag + { + if (obj->getModifiedKey() > gc->getMaxDataBlockModifiedKey()) + gc->setMaxDataBlockModifiedKey(obj->getModifiedKey()); + + bstream->writeInt(id - DataBlockObjectIdFirst,DataBlockObjectIdBitSize); // B - int + + S32 classId = obj->getClassId(conn->getNetClassGroup()); + bstream->writeClassId(classId, NetClassTypeDataBlock, conn->getNetClassGroup()); // C - id + bstream->writeInt(i, DataBlockObjectIdBitSize); // D - int + bstream->writeInt(groupCount, DataBlockObjectIdBitSize + 1); // E - int + obj->packData(bstream); + } + } + } + else + { + bstream = client_db_stream; + } + + if (bstream->getPosition() <= 0) + return; + + // zero out any leftover bits short of an even byte count + U32 n_leftover_bits = (bstream->getPosition()*8) - bstream->getCurPos(); + if (n_leftover_bits >= 0 && n_leftover_bits <= 8) + { + // note - an unusual problem regarding setCurPos() results when there + // are no leftover bytes. Adding a buffer byte in this case avoids the problem. + if (n_leftover_bits == 0) + n_leftover_bits = 8; + U8 bzero = 0; + bstream->writeBits(n_leftover_bits, &bzero); + } + + // this is where we actually save the file + const char* filename = (on_server) ? server_cache_filename : client_cache_filename; + if (filename && filename[0] != '\0') + { + FileStream* f_stream; + if((f_stream = FileStream::createAndOpen(filename, Torque::FS::File::Write )) == NULL) + { + Con::printf("Failed to open file '%s'.", filename); + return; + } + + U32 save_sz = bstream->getPosition(); + + if (!on_server) + { + f_stream->write((U32)CLIENT_CACHE_VERSION_CODE); + f_stream->write(save_sz); + f_stream->write(server_cache_CRC); + f_stream->write((U32)CLIENT_CACHE_VERSION_CODE); + } + + f_stream->write(save_sz, bstream->getBuffer()); + + // zero out any leftover bytes short of a 4-byte multiple + while ((save_sz % 4) != 0) + { + f_stream->write((U8)0); + save_sz++; + } + + delete f_stream; + } + + if (!on_server) + client_db_stream->clear(); +} + +static bool afx_saved_db_cache = false; +static U32 afx_saved_db_cache_CRC = 0xffffffff; + +void GameConnection::resetDatablockCache() +{ + afx_saved_db_cache = false; + afx_saved_db_cache_CRC = 0xffffffff; +} + +ConsoleFunction(resetDatablockCache, void, 1, 1, "resetDatablockCache()") +{ + GameConnection::resetDatablockCache(); +} + +ConsoleFunction(isDatablockCacheSaved, bool, 1, 1, "resetDatablockCache()") +{ + return afx_saved_db_cache; +} + +ConsoleFunction(getDatablockCacheCRC, S32, 1, 1, "getDatablockCacheCRC()") +{ + return (S32)afx_saved_db_cache_CRC; +} + +ConsoleFunction(extractDatablockCacheCRC, S32, 2, 2, "extractDatablockCacheCRC(filename)") +{ + FileStream f_stream; + const char* fileName = argv[1]; + if(!f_stream.open(fileName, Torque::FS::File::Read)) + { + Con::errorf("Failed to open file '%s'.", fileName); + return -1; + } + + U32 stream_sz = f_stream.getStreamSize(); + if (stream_sz < 4*32) + { + Con::errorf("File '%s' is not a valid datablock cache.", fileName); + f_stream.close(); + return -1; + } + + U32 pre_code; f_stream.read(&pre_code); + U32 save_sz; f_stream.read(&save_sz); + U32 crc_code; f_stream.read(&crc_code); + U32 post_code; f_stream.read(&post_code); + + f_stream.close(); + + if (pre_code != post_code) + { + Con::errorf("File '%s' is not a valid datablock cache.", fileName); + return -1; + } + + if (pre_code != (U32)CLIENT_CACHE_VERSION_CODE) + { + Con::errorf("Version of datablock cache file '%s' does not match version of running software.", fileName); + return -1; + } + + return (S32)crc_code; +} + +ConsoleFunction(setDatablockCacheCRC, void, 2, 2, "setDatablockCacheCRC(crc)") +{ + GameConnection *conn = GameConnection::getConnectionToServer(); + if(!conn) + return; + + U32 crc_u = (U32)dAtoi(argv[1]); + conn->setServerCacheCRC(crc_u); +} + +ConsoleMethod( GameConnection, saveDatablockCache, void, 2, 2, "saveDatablockCache()") +{ + if (GameConnection::serverCacheEnabled() && !afx_saved_db_cache) + { + // Save the datablocks to a cache file. An argument + // of true indicates that this is a server save. + object->saveDatablockCache(true); + afx_saved_db_cache = true; + afx_saved_db_cache_CRC = 0xffffffff; + + static char filename_buffer[1024]; + String filename(Torque::Path::CleanSeparators(object->serverCacheFilename())); + Con::expandScriptFilename(filename_buffer, sizeof(filename_buffer), filename.c_str()); + Torque::Path givenPath(Torque::Path::CompressPath(filename_buffer)); + Torque::FS::FileNodeRef fileRef = Torque::FS::GetFileNode(givenPath); + if ( fileRef == NULL ) + Con::errorf("saveDatablockCache() failed to get CRC for file '%s'.", filename.c_str()); + else + afx_saved_db_cache_CRC = (S32)fileRef->getChecksum(); + } +} + +ConsoleMethod( GameConnection, loadDatablockCache, void, 2, 2, "loadDatablockCache()") +{ + if (GameConnection::clientCacheEnabled()) + { + object->loadDatablockCache(); + } +} + +ConsoleMethod( GameConnection, loadDatablockCache_Begin, bool, 2, 2, "loadDatablockCache_Begin()") +{ + if (GameConnection::clientCacheEnabled()) + { + return object->loadDatablockCache_Begin(); + } + + return false; +} + +ConsoleMethod( GameConnection, loadDatablockCache_Continue, bool, 2, 2, "loadDatablockCache_Continue()") +{ + if (GameConnection::clientCacheEnabled()) + { + return object->loadDatablockCache_Continue(); + } + + return false; +} + +static char* afx_db_load_buf = 0; +static U32 afx_db_load_buf_sz = 0; +static BitStream* afx_db_load_bstream = 0; + +void GameConnection::loadDatablockCache() +{ + if (!loadDatablockCache_Begin()) + return; + + while (loadDatablockCache_Continue()) + ; +} + +bool GameConnection::loadDatablockCache_Begin() +{ + if (!client_cache_filename || client_cache_filename[0] == '\0') + { + Con::errorf("No filename was specified for the client datablock cache."); + return false; + } + + // open cache file + FileStream f_stream; + if(!f_stream.open(client_cache_filename, Torque::FS::File::Read)) + { + Con::errorf("Failed to open file '%s'.", client_cache_filename); + return false; + } + + // get file size + U32 stream_sz = f_stream.getStreamSize(); + if (stream_sz <= 4*4) + { + Con::errorf("File '%s' is too small to be a valid datablock cache.", client_cache_filename); + f_stream.close(); + return false; + } + + // load header data + U32 pre_code; f_stream.read(&pre_code); + U32 save_sz; f_stream.read(&save_sz); + U32 crc_code; f_stream.read(&crc_code); + U32 post_code; f_stream.read(&post_code); + + // validate header info + if (pre_code != post_code) + { + Con::errorf("File '%s' is not a valid datablock cache.", client_cache_filename); + f_stream.close(); + return false; + } + if (pre_code != (U32)CLIENT_CACHE_VERSION_CODE) + { + Con::errorf("Version of datablock cache file '%s' does not match version of running software.", client_cache_filename); + f_stream.close(); + return false; + } + + // allocated the in-memory buffer + afx_db_load_buf_sz = stream_sz - (4*4); + afx_db_load_buf = new char[afx_db_load_buf_sz]; + + // load data from file into memory + if (!f_stream.read(stream_sz, afx_db_load_buf)) + { + Con::errorf("Failed to read data from file '%s'.", client_cache_filename); + f_stream.close(); + delete [] afx_db_load_buf; + afx_db_load_buf = 0; + afx_db_load_buf_sz = 0; + return false; + } + + // close file + f_stream.close(); + + // At this point we have the whole cache in memory + + // create a bitstream from the in-memory buffer + afx_db_load_bstream = new BitStream(afx_db_load_buf, afx_db_load_buf_sz); + + return true; +} + +bool GameConnection::loadDatablockCache_Continue() +{ + if (!afx_db_load_bstream) + return false; + + // prevent repacking of datablocks during load + BitStream* save_client_db_stream = client_db_stream; + client_db_stream = 0; + + bool all_finished = false; + + // loop through at most 16 datablocks + BitStream *bstream = afx_db_load_bstream; + for (S32 i = 0; i < 16; i++) + { + S32 save_pos = bstream->getCurPos(); + if (!bstream->readFlag()) + { + all_finished = true; + break; + } + bstream->setCurPos(save_pos); + SimDataBlockEvent evt; + evt.unpack(this, bstream); + evt.process(this); + } + + client_db_stream = save_client_db_stream; + + if (all_finished) + { + delete afx_db_load_bstream; + afx_db_load_bstream = 0; + delete [] afx_db_load_buf; + afx_db_load_buf = 0; + afx_db_load_buf_sz = 0; + return false; + } + + return true; +} + +#endif diff --git a/Engine/source/T3D/gameBase/gameConnection.h b/Engine/source/T3D/gameBase/gameConnection.h index 13084a637..427c8ce8d 100644 --- a/Engine/source/T3D/gameBase/gameConnection.h +++ b/Engine/source/T3D/gameBase/gameConnection.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _GAMECONNECTION_H_ #define _GAMECONNECTION_H_ @@ -55,6 +60,14 @@ class MoveList; struct Move; struct AuthInfo; +// To disable datablock caching, remove or comment out the AFX_CAP_DATABLOCK_CACHE define below. +// Also, at a minimum, the following script preferences should be set to false: +// $pref::Client::EnableDatablockCache = false; (in arcane.fx/client/defaults.cs) +// $Pref::Server::EnableDatablockCache = false; (in arcane.fx/server/defaults.cs) +// Alternatively, all script code marked with "DATABLOCK CACHE CODE" can be removed or +// commented out. +// +#define AFX_CAP_DATABLOCK_CACHE const F32 MinCameraFov = 1.f; ///< min camera FOV const F32 MaxCameraFov = 179.f; ///< max camera FOV @@ -372,6 +385,61 @@ protected: DECLARE_CALLBACK( void, setLagIcon, (bool state) ); DECLARE_CALLBACK( void, onDataBlocksDone, (U32 sequence) ); DECLARE_CALLBACK( void, onFlash, (bool state) ); + + // GameConnection is modified to keep track of object selections which are used in + // spell targeting. This code stores the current object selection as well as the + // current rollover object beneath the cursor. The rollover object is treated as a + // pending object selection and actual object selection is usually made by promoting + // the rollover object to the current object selection. +private: + SimObjectPtr mRolloverObj; + SimObjectPtr mPreSelectedObj; + SimObjectPtr mSelectedObj; + bool mChangedSelectedObj; + U32 mPreSelectTimestamp; +protected: + virtual void onDeleteNotify(SimObject*); +public: + void setRolloverObj(SceneObject*); + SceneObject* getRolloverObj() { return mRolloverObj; } + void setSelectedObj(SceneObject*, bool propagate_to_client=false); + SceneObject* getSelectedObj() { return mSelectedObj; } + void setPreSelectedObjFromRollover(); + void clearPreSelectedObj(); + void setSelectedObjFromPreSelected(); + // Flag is added to indicate when a client is fully connected or "zoned-in". + // This information determines when AFX will startup active effects on a newly + // added client. +private: + bool zoned_in; +public: + bool isZonedIn() const { return zoned_in; } + void setZonedIn() { zoned_in = true; } + +#ifdef AFX_CAP_DATABLOCK_CACHE +private: + static StringTableEntry server_cache_filename; + static StringTableEntry client_cache_filename; + static bool server_cache_on; + static bool client_cache_on; + BitStream* client_db_stream; + U32 server_cache_CRC; +public: + void repackClientDatablock(BitStream*, S32 start_pos); + void saveDatablockCache(bool on_server); + void loadDatablockCache(); + bool loadDatablockCache_Begin(); + bool loadDatablockCache_Continue(); + void tempDisableStringBuffering(BitStream* bs) const; + void restoreStringBuffering(BitStream* bs) const; + void setServerCacheCRC(U32 crc) { server_cache_CRC = crc; } + + static void resetDatablockCache(); + static bool serverCacheEnabled() { return server_cache_on; } + static bool clientCacheEnabled() { return client_cache_on; } + static const char* serverCacheFilename() { return server_cache_filename; } + static const char* clientCacheFilename() { return client_cache_filename; } +#endif }; #endif diff --git a/Engine/source/T3D/gameBase/gameConnectionEvents.cpp b/Engine/source/T3D/gameBase/gameConnectionEvents.cpp index ddc7b1674..e79e55c45 100644 --- a/Engine/source/T3D/gameBase/gameConnectionEvents.cpp +++ b/Engine/source/T3D/gameBase/gameConnectionEvents.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "core/dnet.h" #include "core/stream/bitStream.h" @@ -136,6 +141,9 @@ void SimDataBlockEvent::notifyDelivered(NetConnection *conn, bool ) void SimDataBlockEvent::pack(NetConnection *conn, BitStream *bstream) { +#ifdef AFX_CAP_DATABLOCK_CACHE + ((GameConnection *)conn)->tempDisableStringBuffering(bstream); +#endif SimDataBlock* obj; Sim::findObject(id,obj); GameConnection *gc = (GameConnection *) conn; @@ -157,10 +165,18 @@ void SimDataBlockEvent::pack(NetConnection *conn, BitStream *bstream) bstream->writeInt(classId ^ DebugChecksum, 32); #endif } +#ifdef AFX_CAP_DATABLOCK_CACHE + ((GameConnection *)conn)->restoreStringBuffering(bstream); +#endif } void SimDataBlockEvent::unpack(NetConnection *cptr, BitStream *bstream) { +#ifdef AFX_CAP_DATABLOCK_CACHE + // stash the stream position prior to unpacking + S32 start_pos = bstream->getCurPos(); + ((GameConnection *)cptr)->tempDisableStringBuffering(bstream); +#endif if(bstream->readFlag()) { mProcess = true; @@ -215,6 +231,11 @@ void SimDataBlockEvent::unpack(NetConnection *cptr, BitStream *bstream) #endif } +#ifdef AFX_CAP_DATABLOCK_CACHE + // rewind to stream position and then process raw bytes for caching + ((GameConnection *)cptr)->repackClientDatablock(bstream, start_pos); + ((GameConnection *)cptr)->restoreStringBuffering(bstream); +#endif } void SimDataBlockEvent::write(NetConnection *cptr, BitStream *bstream) diff --git a/Engine/source/T3D/gameBase/processList.cpp b/Engine/source/T3D/gameBase/processList.cpp index 8e524a205..1fef84fc2 100644 --- a/Engine/source/T3D/gameBase/processList.cpp +++ b/Engine/source/T3D/gameBase/processList.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/gameBase/processList.h" @@ -284,5 +289,20 @@ void ProcessList::advanceObjects() PROFILE_END(); } +ProcessObject* ProcessList::findNearestToEnd(Vector& objs) const +{ + if (objs.empty()) + return 0; + for (ProcessObject* obj = mHead.mProcessLink.prev; obj != &mHead; obj = obj->mProcessLink.prev) + { + for (S32 i = 0; i < objs.size(); i++) + { + if (obj == objs[i]) + return obj; + } + } + + return 0; +} diff --git a/Engine/source/T3D/gameBase/processList.h b/Engine/source/T3D/gameBase/processList.h index 67c769e70..0ac4ecb2c 100644 --- a/Engine/source/T3D/gameBase/processList.h +++ b/Engine/source/T3D/gameBase/processList.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _PROCESSLIST_H_ #define _PROCESSLIST_H_ @@ -188,6 +193,9 @@ protected: PreTickSignal mPreTick; PostTickSignal mPostTick; + // JTF: still needed? +public: + ProcessObject* findNearestToEnd(Vector& objs) const; }; #endif // _PROCESSLIST_H_ \ No newline at end of file diff --git a/Engine/source/T3D/groundPlane.cpp b/Engine/source/T3D/groundPlane.cpp index bb8f82503..5e23a28ce 100644 --- a/Engine/source/T3D/groundPlane.cpp +++ b/Engine/source/T3D/groundPlane.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/groundPlane.h" @@ -40,6 +45,7 @@ #include "T3D/physics/physicsBody.h" #include "T3D/physics/physicsCollision.h" +#include "afx/ce/afxZodiacMgr.h" /// Minimum square size allowed. This is a cheap way to limit the amount /// of geometry possibly generated by the GroundPlane (vertex buffers have a @@ -77,6 +83,7 @@ GroundPlane::GroundPlane() mNetFlags.set( Ghostable | ScopeAlways ); mConvexList = new Convex; + mTypeMask |= TerrainLikeObjectType; } GroundPlane::~GroundPlane() @@ -356,6 +363,7 @@ void GroundPlane::prepRenderImage( SceneRenderState* state ) if( mVertexBuffer.isNull() ) return; + afxZodiacMgr::renderGroundPlaneZodiacs(state, this); // Add a render instance. RenderPassManager* pass = state->getRenderPass(); diff --git a/Engine/source/T3D/lightBase.cpp b/Engine/source/T3D/lightBase.cpp index 76c0298c0..6cc739ed3 100644 --- a/Engine/source/T3D/lightBase.cpp +++ b/Engine/source/T3D/lightBase.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/lightBase.h" @@ -73,6 +78,7 @@ LightBase::LightBase() mLight = LightManager::createLightInfo(); mFlareState.clear(); + mLocalRenderViz = false; } LightBase::~LightBase() @@ -206,7 +212,7 @@ void LightBase::prepRenderImage( SceneRenderState *state ) // If the light is selected or light visualization // is enabled then register the callback. - if ( smRenderViz || isSelectedInEditor ) + if ( mLocalRenderViz || smRenderViz || isSelectedInEditor ) { ObjectRenderInst *ri = state->getRenderPass()->allocInst(); ri->renderDelegate.bind( this, &LightBase::_onRenderViz ); diff --git a/Engine/source/T3D/lightBase.h b/Engine/source/T3D/lightBase.h index af2c35071..bdaf38b15 100644 --- a/Engine/source/T3D/lightBase.h +++ b/Engine/source/T3D/lightBase.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _LIGHTBASE_H_ #define _LIGHTBASE_H_ @@ -132,6 +137,8 @@ public: virtual void pauseAnimation( void ); virtual void playAnimation( void ); virtual void playAnimation( LightAnimData *animData ); +protected: + bool mLocalRenderViz; }; #endif // _LIGHTBASE_H_ diff --git a/Engine/source/T3D/objectTypes.h b/Engine/source/T3D/objectTypes.h index 8c9b3c84c..2b27a7171 100644 --- a/Engine/source/T3D/objectTypes.h +++ b/Engine/source/T3D/objectTypes.h @@ -20,11 +20,19 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _OBJECTTYPES_H_ #define _OBJECTTYPES_H_ #include "platform/types.h" +// Uncomment the AFX_CAP_AFXMODEL_TYPE define below to enable a type flag +// for afxModel objects. +//#define AFX_CAP_AFXMODEL_TYPE /// Types used for SceneObject type masks (SceneObject::mTypeMask) /// /// @note If a new object type is added, don't forget to add it to @@ -149,6 +157,11 @@ enum SceneObjectTypes EntityObjectType = BIT(23), /// @} + InteriorLikeObjectType = BIT(24), + TerrainLikeObjectType = BIT(25), +#if defined(AFX_CAP_AFXMODEL_TYPE) + afxModelObjectType = BIT(26) +#endif }; enum SceneObjectTypeMasks : U32 diff --git a/Engine/source/T3D/physicalZone.cpp b/Engine/source/T3D/physicalZone.cpp index f7307ef21..f8454be5b 100644 --- a/Engine/source/T3D/physicalZone.cpp +++ b/Engine/source/T3D/physicalZone.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "T3D/physicalZone.h" #include "core/stream/bitStream.h" #include "collision/boxConvex.h" @@ -33,6 +38,8 @@ #include "gfx/gfxDrawUtil.h" #include "console/engineAPI.h" +//#include "console/engineTypes.h" +#include "sim/netConnection.h" IMPLEMENT_CO_NETOBJECT_V1(PhysicalZone); ConsoleDocClass( PhysicalZone, @@ -103,6 +110,10 @@ PhysicalZone::PhysicalZone() mConvexList = new Convex; mActive = true; + force_type = VECTOR; + force_mag = 0.0f; + orient_force = false; + fade_amt = 1.0f; } PhysicalZone::~PhysicalZone() @@ -111,6 +122,16 @@ PhysicalZone::~PhysicalZone() mConvexList = NULL; } + +ImplementEnumType( PhysicalZone_ForceType, "Possible physical zone force types.\n" "@ingroup PhysicalZone\n\n" ) + { PhysicalZone::VECTOR, "vector", "..." }, + { PhysicalZone::SPHERICAL, "spherical", "..." }, + { PhysicalZone::CYLINDRICAL, "cylindrical", "..." }, + // aliases + { PhysicalZone::SPHERICAL, "sphere", "..." }, + { PhysicalZone::CYLINDRICAL, "cylinder", "..." }, +EndImplementEnumType; + //-------------------------------------------------------------------------- void PhysicalZone::consoleInit() { @@ -129,6 +150,10 @@ void PhysicalZone::initPersistFields() "point followed by three vectors representing the edges extending from the corner." ); endGroup("Misc"); + addGroup("AFX"); + addField("forceType", TYPEID(), Offset(force_type, PhysicalZone)); + addField("orientForce", TypeBool, Offset(orient_force, PhysicalZone)); + endGroup("AFX"); Parent::initPersistFields(); } @@ -158,6 +183,19 @@ bool PhysicalZone::onAdd() Polyhedron temp = mPolyhedron; setPolyhedron(temp); + switch (force_type) + { + case SPHERICAL: + force_mag = mAppliedForce.magnitudeSafe(); + break; + case CYLINDRICAL: + { + Point3F force_vec = mAppliedForce; + force_vec.z = 0.0; + force_mag = force_vec.magnitudeSafe(); + } + break; + } addToScene(); return true; @@ -191,7 +229,7 @@ void PhysicalZone::setTransform(const MatrixF & mat) mClippedList.setBaseTransform(base); if (isServerObject()) - setMaskBits(InitialUpdateMask); + setMaskBits(MoveMask); } @@ -242,12 +280,8 @@ U32 PhysicalZone::packUpdate(NetConnection* con, U32 mask, BitStream* stream) U32 i; U32 retMask = Parent::packUpdate(con, mask, stream); - if (stream->writeFlag((mask & InitialUpdateMask) != 0)) { - // Note that we don't really care about efficiency here, since this is an - // edit-only ghost... - mathWrite(*stream, mObjToWorld); - mathWrite(*stream, mObjScale); - + if (stream->writeFlag(mask & PolyhedronMask)) + { // Write the polyhedron stream->write(mPolyhedron.pointList.size()); for (i = 0; i < mPolyhedron.pointList.size(); i++) @@ -266,32 +300,44 @@ U32 PhysicalZone::packUpdate(NetConnection* con, U32 mask, BitStream* stream) stream->write(rEdge.vertex[0]); stream->write(rEdge.vertex[1]); } + } + if (stream->writeFlag(mask & MoveMask)) + { + stream->writeAffineTransform(mObjToWorld); + mathWrite(*stream, mObjScale); + } + + if (stream->writeFlag(mask & SettingsMask)) + { stream->write(mVelocityMod); stream->write(mGravityMod); mathWrite(*stream, mAppliedForce); - stream->writeFlag(mActive); - } else { - stream->writeFlag(mActive); + stream->writeInt(force_type, FORCE_TYPE_BITS); + stream->writeFlag(orient_force); } - return retMask; + if (stream->writeFlag(mask & FadeMask)) + { + U8 fade_byte = (U8)(fade_amt*255.0f); + stream->write(fade_byte); + } + + stream->writeFlag(mActive); + + return retMask; } void PhysicalZone::unpackUpdate(NetConnection* con, BitStream* stream) { Parent::unpackUpdate(con, stream); - if (stream->readFlag()) { + bool new_ph = false; + if (stream->readFlag()) // PolyhedronMask + { U32 i, size; - MatrixF temp; - Point3F tempScale; Polyhedron tempPH; - // Transform - mathRead(*stream, &temp); - mathRead(*stream, &tempScale); - // Read the polyhedron stream->read(&size); tempPH.pointList.setSize(size); @@ -314,17 +360,46 @@ void PhysicalZone::unpackUpdate(NetConnection* con, BitStream* stream) stream->read(&rEdge.vertex[1]); } + setPolyhedron(tempPH); + new_ph = true; + } + + if (stream->readFlag()) // MoveMask + { + MatrixF temp; + stream->readAffineTransform(&temp); + + Point3F tempScale; + mathRead(*stream, &tempScale); + + //if (!new_ph) + //{ + // Polyhedron rPolyhedron = mPolyhedron; + // setPolyhedron(rPolyhedron); + //} + setScale(tempScale); + setTransform(temp); + } + + if (stream->readFlag()) //SettingsMask + { stream->read(&mVelocityMod); stream->read(&mGravityMod); mathRead(*stream, &mAppliedForce); - - setPolyhedron(tempPH); - setScale(tempScale); - setTransform(temp); - mActive = stream->readFlag(); - } else { - mActive = stream->readFlag(); + force_type = stream->readInt(FORCE_TYPE_BITS); // AFX + orient_force = stream->readFlag(); // AFX } + + if (stream->readFlag()) //FadeMask + { + U8 fade_byte; + stream->read(&fade_byte); + fade_amt = ((F32)fade_byte)/255.0f; + } + else + fade_amt = 1.0f; + + mActive = stream->readFlag(); } @@ -443,3 +518,104 @@ void PhysicalZone::deactivate() mActive = false; } +void PhysicalZone::onStaticModified(const char* slotName, const char*newValue) +{ + if (dStricmp(slotName, "appliedForce") == 0 || dStricmp(slotName, "forceType") == 0) + { + switch (force_type) + { + case SPHERICAL: + force_mag = mAppliedForce.magnitudeSafe(); + break; + case CYLINDRICAL: + { + Point3F force_vec = mAppliedForce; + force_vec.z = 0.0; + force_mag = force_vec.magnitudeSafe(); + } + break; + } + } +} + +const Point3F& PhysicalZone::getForce(const Point3F* center) const +{ + static Point3F force_vec; + + if (force_type == VECTOR) + { + if (orient_force) + { + getTransform().mulV(mAppliedForce, &force_vec); + force_vec *= fade_amt; + return force_vec; + } + force_vec = mAppliedForce; + force_vec *= fade_amt; + return force_vec; + } + + if (!center) + { + force_vec.zero(); + return force_vec; + } + + if (force_type == SPHERICAL) + { + force_vec = *center - getPosition(); + force_vec.normalizeSafe(); + force_vec *= force_mag*fade_amt; + return force_vec; + } + + if (orient_force) + { + force_vec = *center - getPosition(); + getWorldTransform().mulV(force_vec); + force_vec.z = 0.0f; + force_vec.normalizeSafe(); + force_vec *= force_mag; + force_vec.z = mAppliedForce.z; + getTransform().mulV(force_vec); + force_vec *= fade_amt; + return force_vec; + } + + force_vec = *center - getPosition(); + force_vec.z = 0.0f; + force_vec.normalizeSafe(); + force_vec *= force_mag; + force_vec *= fade_amt; + return force_vec; +} + +bool PhysicalZone::isExcludedObject(SceneObject* obj) const +{ + for (S32 i = 0; i < excluded_objects.size(); i++) + if (excluded_objects[i] == obj) + return true; + + return false; +} + +void PhysicalZone::registerExcludedObject(SceneObject* obj) +{ + if (isExcludedObject(obj)) + return; + + excluded_objects.push_back(obj); + setMaskBits(FadeMask); +} + +void PhysicalZone::unregisterExcludedObject(SceneObject* obj) +{ + for (S32 i = 0; i < excluded_objects.size(); i++) + if (excluded_objects[i] == obj) + { + excluded_objects.erase(i); + setMaskBits(FadeMask); + return; + } +} + diff --git a/Engine/source/T3D/physicalZone.h b/Engine/source/T3D/physicalZone.h index 2f1d72593..004ce6cb3 100644 --- a/Engine/source/T3D/physicalZone.h +++ b/Engine/source/T3D/physicalZone.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _H_PHYSICALZONE #define _H_PHYSICALZONE @@ -40,9 +45,14 @@ class PhysicalZone : public SceneObject { typedef SceneObject Parent; - enum UpdateMasks { + enum UpdateMasks { ActiveMask = Parent::NextFreeMask << 0, - NextFreeMask = Parent::NextFreeMask << 1 + SettingsMask = Parent::NextFreeMask << 1, + FadeMask = Parent::NextFreeMask << 2, + PolyhedronMask = Parent::NextFreeMask << 3, + MoveMask = Parent::NextFreeMask << 4, + ExclusionMask = Parent::NextFreeMask << 5, + NextFreeMask = Parent::NextFreeMask << 6 }; protected: @@ -83,7 +93,10 @@ class PhysicalZone : public SceneObject inline F32 getVelocityMod() const { return mVelocityMod; } inline F32 getGravityMod() const { return mGravityMod; } - inline const Point3F& getForce() const { return mAppliedForce; } + // the scene object is now passed in to getForce() where + // it is needed to calculate the applied force when the + // force is radial. + const Point3F& getForce(const Point3F* center=0) const; void setPolyhedron(const Polyhedron&); bool testObject(SceneObject*); @@ -96,7 +109,25 @@ class PhysicalZone : public SceneObject void deactivate(); inline bool isActive() const { return mActive; } +protected: + friend class afxPhysicalZoneData; + friend class afxEA_PhysicalZone; + Vector excluded_objects; + S32 force_type; + F32 force_mag; + bool orient_force; + F32 fade_amt; + void setFadeAmount(F32 amt) { fade_amt = amt; if (fade_amt < 1.0f) setMaskBits(FadeMask); } +public: + enum ForceType { VECTOR, SPHERICAL, CYLINDRICAL }; + enum { FORCE_TYPE_BITS = 2 }; + virtual void onStaticModified(const char* slotName, const char*newValue = NULL); + bool isExcludedObject(SceneObject*) const; + void registerExcludedObject(SceneObject*); + void unregisterExcludedObject(SceneObject*); }; +typedef PhysicalZone::ForceType PhysicalZone_ForceType; +DefineEnumType( PhysicalZone_ForceType ); #endif // _H_PHYSICALZONE diff --git a/Engine/source/T3D/player.cpp b/Engine/source/T3D/player.cpp index 6b4e00aef..15b57046a 100644 --- a/Engine/source/T3D/player.cpp +++ b/Engine/source/T3D/player.cpp @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #include "platform/platform.h" #include "T3D/player.h" @@ -1656,6 +1660,8 @@ Player::Player() mLastAbsoluteYaw = 0.0f; mLastAbsolutePitch = 0.0f; mLastAbsoluteRoll = 0.0f; + + afx_init(); } Player::~Player() @@ -2070,6 +2076,32 @@ void Player::processTick(const Move* move) } Parent::processTick(move); + // Check for state changes in the standard move triggers and + // set bits for any triggers that switched on this tick in + // the fx_s_triggers mask. Flag any changes to be packed to + // clients. + if (isServerObject()) + { + fx_s_triggers = 0; + if (move) + { + U8 on_bits = 0; + for (S32 i = 0; i < MaxTriggerKeys; i++) + if (move->trigger[i]) + on_bits |= BIT(i); + + if (on_bits != move_trigger_states) + { + U8 switched_on_bits = (on_bits & ~move_trigger_states); + if (switched_on_bits) + { + fx_s_triggers |= (U32)switched_on_bits; + setMaskBits(TriggerMask); + } + move_trigger_states = on_bits; + } + } + } // Warp to catch up to server if (delta.warpTicks > 0) { delta.warpTicks--; @@ -2085,7 +2117,10 @@ void Player::processTick(const Move* move) else if (delta.rot.z > M_PI_F) delta.rot.z -= M_2PI_F; - setPosition(delta.pos,delta.rot); + if (!ignore_updates) + { + setPosition(delta.pos,delta.rot); + } updateDeathOffsets(); updateLookAnimation(); @@ -2183,7 +2218,8 @@ void Player::interpolateTick(F32 dt) Point3F pos = delta.pos + delta.posVec * dt; Point3F rot = delta.rot + delta.rotVec * dt; - setRenderPosition(pos,rot,dt); + if (!ignore_updates) + setRenderPosition(pos,rot,dt); /* // apply camera effects - is this the best place? - bramage @@ -2208,6 +2244,9 @@ void Player::advanceTime(F32 dt) { // Client side animations Parent::advanceTime(dt); + // Increment timer for triggering idle events. + if (idle_timer >= 0.0f) + idle_timer += dt; updateActionThread(); updateAnimation(dt); updateSplash(); @@ -2498,6 +2537,30 @@ AngAxisF gPlayerMoveRot; void Player::updateMove(const Move* move) { + struct Move my_move; + if (override_movement && movement_op < 3) + { + my_move = *move; + switch (movement_op) + { + case 0: // add + my_move.x += movement_data.x; + my_move.y += movement_data.y; + my_move.z += movement_data.z; + break; + case 1: // mult + my_move.x *= movement_data.x; + my_move.y *= movement_data.y; + my_move.z *= movement_data.z; + break; + case 2: // replace + my_move.x = movement_data.x; + my_move.y = movement_data.y; + my_move.z = movement_data.z; + break; + } + move = &my_move; + } delta.move = *move; #ifdef TORQUE_OPENVR @@ -2740,7 +2803,12 @@ void Player::updateMove(const Move* move) // Desired move direction & speed VectorF moveVec; F32 moveSpeed; - if ((mState == MoveState || (mState == RecoverState && mDataBlock->recoverRunForceScale > 0.0f)) && mDamageState == Enabled) + // If BLOCK_USER_CONTROL is set in anim_clip_flags, the user won't be able to + // resume control over the player character. This generally happens for + // short periods of time synchronized with script driven animation at places + // where it makes sense that user motion is prohibited, such as when the + // player is lifted off the ground or knocked down. + if ((mState == MoveState || (mState == RecoverState && mDataBlock->recoverRunForceScale > 0.0f)) && mDamageState == Enabled && !isAnimationLocked()) { zRot.getColumn(0,&moveVec); moveVec *= (move->x * (mPose == SprintPose ? mDataBlock->sprintStrafeScale : 1.0f)); @@ -2799,6 +2867,9 @@ void Player::updateMove(const Move* move) moveSpeed = 0.0f; } + // apply speed bias here. + speed_bias = speed_bias + (speed_bias_goal - speed_bias)*0.1f; + moveSpeed *= speed_bias; // Acceleration due to gravity VectorF acc(0.0f, 0.0f, mGravity * mGravityMod * TickSec); @@ -3025,7 +3096,9 @@ void Player::updateMove(const Move* move) mContactTimer++; // Acceleration from Jumping - if (move->trigger[sJumpTrigger] && canJump())// !isMounted() && + // While BLOCK_USER_CONTROL is set in anim_clip_flags, the user won't be able to + // make the player character jump. + if (move->trigger[sJumpTrigger] && canJump() && !isAnimationLocked()) { // Scale the jump impulse base on maxJumpSpeed F32 zSpeedScale = mVelocity.z; @@ -3076,6 +3149,9 @@ void Player::updateMove(const Move* move) setActionThread( seq, true, false, true ); mJumpSurfaceLastContact = JumpSkipContactsMax; + // Flag the jump event trigger. + fx_s_triggers |= PLAYER_JUMP_S_TRIGGER; + setMaskBits(TriggerMask); } } else @@ -3493,6 +3569,19 @@ void Player::updateDamageState() void Player::updateLookAnimation(F32 dt) { + // If the preference setting overrideLookAnimation is true, the player's + // arm and head no longer animate according to the view direction. They + // are instead given fixed positions. + if (overrideLookAnimation) + { + if (mArmAnimation.thread) + mShapeInstance->setPos(mArmAnimation.thread, armLookOverridePos); + if (mHeadVThread) + mShapeInstance->setPos(mHeadVThread, headVLookOverridePos); + if (mHeadHThread) + mShapeInstance->setPos(mHeadHThread, headHLookOverridePos); + return; + } // Calculate our interpolated head position. Point3F renderHead = delta.head + delta.headVec * dt; @@ -3533,6 +3622,8 @@ void Player::updateLookAnimation(F32 dt) bool Player::inDeathAnim() { + if ((anim_clip_flags & ANIM_OVERRIDDEN) != 0 && (anim_clip_flags & IS_DEATH_ANIM) == 0) + return false; if (mActionAnimation.thread && mActionAnimation.action >= 0) if (mActionAnimation.action < mDataBlock->actionCount) return mDataBlock->actionList[mActionAnimation.action].death; @@ -3742,6 +3833,8 @@ bool Player::setArmThread(U32 action) bool Player::setActionThread(const char* sequence,bool hold,bool wait,bool fsp) { + if (anim_clip_flags & ANIM_OVERRIDDEN) + return false; for (U32 i = 1; i < mDataBlock->actionCount; i++) { PlayerData::ActionAnimation &anim = mDataBlock->actionList[i]; @@ -3766,6 +3859,11 @@ void Player::setActionThread(U32 action,bool forward,bool hold,bool wait,bool fs return; } + if (isClientObject()) + { + mark_idle = (action == PlayerData::RootAnim); + idle_timer = (mark_idle) ? 0.0f : -1.0f; + } PlayerData::ActionAnimation &anim = mDataBlock->actionList[action]; if (anim.sequence != -1) { @@ -3858,7 +3956,8 @@ void Player::updateActionThread() offset = mDataBlock->decalOffset * getScale().x; } - if( triggeredLeft || triggeredRight ) + process_client_triggers(triggeredLeft, triggeredRight); + if ((triggeredLeft || triggeredRight) && !noFootfallFX) { Point3F rot, pos; RayInfo rInfo; @@ -3875,7 +3974,7 @@ void Player::updateActionThread() // Put footprints on surface, if appropriate for material. if( material && material->mShowFootprints - && mDataBlock->decalData ) + && mDataBlock->decalData && !footfallDecalOverride ) { Point3F normal; Point3F tangent; @@ -3886,8 +3985,8 @@ void Player::updateActionThread() // Emit footpuffs. - if( rInfo.t <= 0.5 && mWaterCoverage == 0.0 - && material && material->mShowDust ) + if (!footfallDustOverride && rInfo.t <= 0.5f && mWaterCoverage == 0.0f + && material && material->mShowDust ) { // New emitter every time for visibility reasons ParticleEmitter * emitter = new ParticleEmitter; @@ -3920,6 +4019,7 @@ void Player::updateActionThread() // Play footstep sound. + if (footfallSoundOverride <= 0) playFootstepSound( triggeredLeft, material, rInfo.object ); } } @@ -3941,8 +4041,10 @@ void Player::updateActionThread() pickActionAnimation(); } + // prevent scaling of AFX picked actions if ( (mActionAnimation.action != PlayerData::LandAnim) && - (mActionAnimation.action != PlayerData::NullAnimation) ) + (mActionAnimation.action != PlayerData::NullAnimation) && + !(anim_clip_flags & ANIM_OVERRIDDEN)) { // Update action animation time scale to match ground velocity PlayerData::ActionAnimation &anim = @@ -4560,6 +4662,10 @@ void Player::updateAnimation(F32 dt) if (mImageStateThread) mShapeInstance->advanceTime(dt,mImageStateThread); + // update any active blend clips + if (isGhost()) + for (S32 i = 0; i < blend_clips.size(); i++) + mShapeInstance->advanceTime(dt, blend_clips[i].thread); // If we are the client's player on this machine, then we need // to make sure the transforms are up to date as they are used // to setup the camera. @@ -4573,6 +4679,11 @@ void Player::updateAnimation(F32 dt) else { updateAnimationTree(false); + // This addition forces recently visible players to animate their + // skeleton now rather than in pre-render so that constrained effects + // get up-to-date node transforms. + if (didRenderLastRender()) + mShapeInstance->animate(); } } } @@ -4878,6 +4989,11 @@ Point3F Player::_move( const F32 travelTime, Collision *outCol ) // we can use it to do impacts // and query collision. *outCol = *collision; + if (isServerObject() && bd > 6.8f && collision->normal.z > 0.7f) + { + fx_s_triggers |= PLAYER_LANDING_S_TRIGGER; + setMaskBits(TriggerMask); + } // Subtract out velocity VectorF dv = collision->normal * (bd + sNormalElasticity); @@ -5879,7 +5995,12 @@ void Player::applyImpulse(const Point3F&,const VectorF& vec) bool Player::castRay(const Point3F &start, const Point3F &end, RayInfo* info) { - if (getDamageState() != Enabled) + // In standard Torque there's a rather brute force culling of all + // non-enabled players (corpses) from the ray cast. But, to + // demonstrate a resurrection spell, we need corpses to be + // selectable, so this code change allows consideration of corpses + // in the ray cast if corpsesHiddenFromRayCast is set to false. + if (sCorpsesHiddenFromRayCast && getDamageState() != Enabled) return false; // Collide against bounding box. Need at least this for the editor. @@ -6146,7 +6267,8 @@ void Player::readPacketData(GameConnection *connection, BitStream *stream) stream->read(&mHead.z); stream->read(&rot.z); rot.x = rot.y = 0; - setPosition(pos,rot); + if (!ignore_updates) + setPosition(pos,rot); delta.head = mHead; delta.rot = rot; @@ -6188,6 +6310,7 @@ U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream) mArmAnimation.action != mDataBlock->lookAction))) { stream->writeInt(mArmAnimation.action,PlayerData::ActionAnimBits); } + retMask = afx_packUpdate(con, mask, stream, retMask); // The rest of the data is part of the control object packet update. // If we're controlled by this client, we don't need to send it. @@ -6292,6 +6415,7 @@ void Player::unpackUpdate(NetConnection *con, BitStream *stream) mArmAnimation.action = action; } + afx_unpackUpdate(con, stream); // Done if controlled by client ( and not initial update ) if(stream->readFlag()) return; @@ -6391,7 +6515,8 @@ void Player::unpackUpdate(NetConnection *con, BitStream *stream) } delta.pos = pos; delta.rot = rot; - setPosition(pos,rot); + if (!ignore_updates) + setPosition(pos,rot); } } else @@ -6403,7 +6528,8 @@ void Player::unpackUpdate(NetConnection *con, BitStream *stream) delta.rotVec.set(0.0f, 0.0f, 0.0f); delta.warpTicks = 0; delta.dt = 0.0f; - setPosition(pos,rot); + if (!ignore_updates) + setPosition(pos,rot); } } F32 energy = stream->readFloat(EnergyLevelBits) * mDataBlock->maxEnergy; @@ -6819,6 +6945,7 @@ void Player::consoleInit() Con::addVariable("$player::extendedMoveHeadPosRotIndex", TypeS32, &smExtendedMoveHeadPosRotIndex, "@brief The ExtendedMove position/rotation index used for head movements.\n\n" "@ingroup GameObjects\n"); + afx_consoleInit(); } //-------------------------------------------------------------------------- @@ -6863,6 +6990,8 @@ void Player::calcClassRenderData() void Player::playFootstepSound( bool triggeredLeft, Material* contactMaterial, SceneObject* contactObject ) { + if (footfallSoundOverride > 0) + return; MatrixF footMat = getTransform(); if( mWaterCoverage > 0.0 ) { @@ -7172,6 +7301,340 @@ void Player::renderConvex( ObjectRenderInst *ri, SceneRenderState *state, BaseMa GFX->leaveDebugEvent(); } +// static +bool Player::sCorpsesHiddenFromRayCast = true; // this default matches stock Torque behavior. + +// static +void Player::afx_consoleInit() +{ + Con::addVariable("pref::Player::corpsesHiddenFromRayCast", TypeBool, &sCorpsesHiddenFromRayCast); +} + +void Player::afx_init() +{ + overrideLookAnimation = false; + armLookOverridePos = 0.5f; + headVLookOverridePos = 0.5f; + headHLookOverridePos = 0.5f; + ignore_updates = false; + fx_c_triggers = 0; + mark_fx_c_triggers = 0; + fx_s_triggers = 0; + move_trigger_states = 0; + z_velocity = 0.0f; + mark_idle = false; + idle_timer = 0.0f; + mark_s_landing = false; + speed_bias = 1.0f; + speed_bias_goal = 1.0f; + override_movement = 0; + movement_data.zero(); + movement_op = 1; + last_movement_tag = 0; + footfallDecalOverride = 0; + footfallSoundOverride = 0; + footfallDustOverride = 0; + noFootfallFX = false; +} + +U32 Player::afx_packUpdate(NetConnection* con, U32 mask, BitStream* stream, U32 retMask) +{ +#if 0 + if (stream->writeFlag(mask & LookOverrideMask)) +#else + if (stream->writeFlag(mask & ActionMask)) +#endif + stream->writeFlag(overrideLookAnimation); + + if (stream->writeFlag(mask & TriggerMask)) + stream->write(fx_s_triggers); + + return retMask; +} + +void Player::afx_unpackUpdate(NetConnection* con, BitStream* stream) +{ + if (stream->readFlag()) // LookOverrideMask + overrideLookAnimation = stream->readFlag(); + + if (stream->readFlag()) // TriggerMask + { + U32 mask; + stream->read(&mask); + mark_fx_c_triggers = mask; + } +} + +// Code for overriding player's animation with sequences selected by the +// anim-clip component effect. + +void Player::restoreAnimation(U32 tag) +{ + // check if this is a blended clip + if ((tag & BLENDED_CLIP) != 0) + { + restoreBlendAnimation(tag); + return; + } + + if (tag != 0 && tag == last_anim_tag) + { + bool is_death_anim = ((anim_clip_flags & IS_DEATH_ANIM) != 0); + + anim_clip_flags &= ~(ANIM_OVERRIDDEN | IS_DEATH_ANIM); + + if (isClientObject()) + { + if (mDamageState != Enabled) + { + if (!is_death_anim) + { + // this is a bit hardwired and desperate, + // but if he's dead he needs to look like it. + setActionThread("death10", false, false, false); + } + } + else if (mState != MoveState) + { + // not sure what happens here + } + else + { + pickActionAnimation(); + } + } + + last_anim_tag = 0; + last_anim_id = -1; + } +} + +U32 Player::getAnimationID(const char* name) +{ + for (U32 i = 0; i < mDataBlock->actionCount; i++) + { + PlayerData::ActionAnimation &anim = mDataBlock->actionList[i]; + if (dStricmp(anim.name, name) == 0) + return i; + } + + Con::errorf("Player::getAnimationID() -- Player does not contain a sequence that matches the name, %s.", name); + return BAD_ANIM_ID; +} + +U32 Player::playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim) +{ + if (anim_id == BAD_ANIM_ID) + return 0; + + S32 seq_id = mDataBlock->actionList[anim_id].sequence; + if (seq_id == -1) + { + Con::errorf("Player::playAnimation() problem. BAD_SEQ_ID"); + return 0; + } + + if (mShapeInstance->getShape()->sequences[seq_id].isBlend()) + return playBlendAnimation(seq_id, pos, rate); + + if (isClientObject()) + { + PlayerData::ActionAnimation &anim = mDataBlock->actionList[anim_id]; + if (anim.sequence != -1) + { + mActionAnimation.action = anim_id; + mActionAnimation.forward = (rate >= 0); + mActionAnimation.firstPerson = false; + mActionAnimation.holdAtEnd = hold; + mActionAnimation.waitForEnd = hold? true: wait; + mActionAnimation.animateOnServer = false; + mActionAnimation.atEnd = false; + mActionAnimation.delayTicks = (S32)sNewAnimationTickTime; + + F32 transTime = (trans < 0) ? sAnimationTransitionTime : trans; + + mShapeInstance->setTimeScale(mActionAnimation.thread, rate); + mShapeInstance->transitionToSequence(mActionAnimation.thread,anim.sequence, + pos, transTime, true); + } + } + + if (is_death_anim) + anim_clip_flags |= IS_DEATH_ANIM; + else + anim_clip_flags &= ~IS_DEATH_ANIM; + + anim_clip_flags |= ANIM_OVERRIDDEN; + last_anim_tag = unique_anim_tag_counter++; + last_anim_id = anim_id; + + return last_anim_tag; +} + +F32 Player::getAnimationDurationByID(U32 anim_id) +{ + if (anim_id == BAD_ANIM_ID) + return 0.0f; + S32 seq_id = mDataBlock->actionList[anim_id].sequence; + if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size()) + return mDataBlock->mShape->sequences[seq_id].duration; + + return 0.0f; +} + +bool Player::isBlendAnimation(const char* name) +{ + U32 anim_id = getAnimationID(name); + if (anim_id == BAD_ANIM_ID) + return false; + + S32 seq_id = mDataBlock->actionList[anim_id].sequence; + if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size()) + return mDataBlock->mShape->sequences[seq_id].isBlend(); + + return false; +} + +const char* Player::getLastClipName(U32 clip_tag) +{ + if (clip_tag != last_anim_tag || last_anim_id >= PlayerData::NumActionAnims) + return ""; + + return mDataBlock->actionList[last_anim_id].name; +} + +void Player::unlockAnimation(U32 tag, bool force) +{ + if ((tag != 0 && tag == last_anim_lock_tag) || force) + anim_clip_flags &= ~BLOCK_USER_CONTROL; +} + +U32 Player::lockAnimation() +{ + anim_clip_flags |= BLOCK_USER_CONTROL; + last_anim_lock_tag = unique_anim_tag_counter++; + + return last_anim_lock_tag; +} + +ConsoleMethod(Player, isAnimationLocked, bool, 2, 2, "isAnimationLocked()") +{ + return object->isAnimationLocked(); +} + + +void Player::setLookAnimationOverride(bool flag) +{ + overrideLookAnimation = flag; +#if 0 + setMaskBits(LookOverrideMask); +#else + setMaskBits(ActionMask); +#endif +} + +ConsoleMethod(Player, setLookAnimationOverride, void, 3, 3, "setLookAnimationOverride(flag)") +{ + object->setLookAnimationOverride(dAtob(argv[2])); +} + +ConsoleMethod(Player, copyHeadRotation, void, 3, 3, "copyHeadRotation(other_player)") +{ + Player* other_player = dynamic_cast(Sim::findObject(argv[2])); + if (other_player) + object->copyHeadRotation(other_player); +} +void Player::process_client_triggers(bool triggeredLeft, bool triggeredRight) +{ + bool mark_landing = false; + Point3F my_vel = getVelocity(); + if (my_vel.z > 5.0f) + z_velocity = 1; + else if (my_vel.z < -5.0f) + z_velocity = -1; + else + { + if (z_velocity < 0) + mark_landing = true; + z_velocity = 0.0f; + } + + fx_c_triggers = mark_fx_c_triggers; + if (triggeredLeft) + fx_c_triggers |= PLAYER_LF_FOOT_C_TRIGGER; + if (triggeredRight) + fx_c_triggers |= PLAYER_RT_FOOT_C_TRIGGER; + if (mark_landing) + fx_c_triggers |= PLAYER_LANDING_C_TRIGGER; + if (idle_timer > 10.0f) + { + fx_c_triggers |= PLAYER_IDLE_C_TRIGGER; + idle_timer = 0.0f; + } + if (fx_c_triggers & PLAYER_LANDING_S_TRIGGER) + { + fx_c_triggers &= ~(PLAYER_LANDING_S_TRIGGER); + } +} +U32 Player::unique_movement_tag_counter = 1; + +void Player::setMovementSpeedBias(F32 bias) +{ + speed_bias_goal = bias; +} + +U32 Player::setMovementOverride(F32 bias, const Point3F* mov, U32 op) +{ + if (mov) + { + movement_data = *mov; + override_movement = true; + movement_op = (U8)op; + } + else + override_movement = false; + + speed_bias_goal = bias; + + last_movement_tag = unique_movement_tag_counter++; + return last_movement_tag; +} + +void Player::restoreMovement(U32 tag) +{ + if (tag != 0 && tag == last_movement_tag) + { + speed_bias_goal = 1.0; + override_movement = false; + } +} + +ConsoleMethod(Player, setMovementSpeedBias, void, 3, 3, "setMovementSpeedBias(F32 bias)") +{ + object->setMovementSpeedBias(dAtof(argv[2])); +} + +void Player::overrideFootfallFX(bool decals, bool sounds, bool dust) +{ + if (decals) + footfallDecalOverride++; + if (sounds) + footfallSoundOverride++; + if (dust) + footfallDustOverride++; + noFootfallFX = (footfallDecalOverride > 0 && footfallSoundOverride > 0 && footfallDustOverride > 0); +} + +void Player::restoreFootfallFX(bool decals, bool sounds, bool dust) +{ + if (decals && footfallDecalOverride) + footfallDecalOverride--; + if (sounds && footfallSoundOverride) + footfallSoundOverride--; + if (dust && footfallDustOverride) + footfallDustOverride--; + noFootfallFX = (footfallDecalOverride > 0 && footfallSoundOverride > 0 && footfallDustOverride > 0); +} #ifdef TORQUE_OPENVR void Player::setControllers(Vector controllerList) { diff --git a/Engine/source/T3D/player.h b/Engine/source/T3D/player.h index ac10f2db9..0b4545114 100644 --- a/Engine/source/T3D/player.h +++ b/Engine/source/T3D/player.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _PLAYER_H_ #define _PLAYER_H_ @@ -401,7 +406,8 @@ protected: ActionMask = Parent::NextFreeMask << 0, MoveMask = Parent::NextFreeMask << 1, ImpactMask = Parent::NextFreeMask << 2, - NextFreeMask = Parent::NextFreeMask << 3 + TriggerMask = Parent::NextFreeMask << 3, + NextFreeMask = Parent::NextFreeMask << 4 }; SimObjectPtr mSplashEmitter[PlayerData::NUM_SPLASH_EMITTERS]; @@ -780,6 +786,89 @@ public: virtual void prepRenderImage( SceneRenderState* state ); virtual void renderConvex( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat ); virtual void renderMountedImage( U32 imageSlot, TSRenderState &rstate, SceneRenderState *state ); +private: + static void afx_consoleInit(); + void afx_init(); + U32 afx_packUpdate(NetConnection*, U32 mask, BitStream*, U32 retMask); + void afx_unpackUpdate(NetConnection*, BitStream*); +private: + static bool sCorpsesHiddenFromRayCast; + +public: + virtual void restoreAnimation(U32 tag); + virtual U32 getAnimationID(const char* name); + virtual U32 playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim); + virtual F32 getAnimationDurationByID(U32 anim_id); + virtual bool isBlendAnimation(const char* name); + virtual const char* getLastClipName(U32 clip_tag); + virtual void unlockAnimation(U32 tag, bool force=false); + virtual U32 lockAnimation(); + virtual bool isAnimationLocked() const { return ((anim_clip_flags & BLOCK_USER_CONTROL) != 0); } + +protected: + bool overrideLookAnimation; + F32 armLookOverridePos; + F32 headVLookOverridePos; + F32 headHLookOverridePos; +public: + void setLookAnimationOverride(bool flag); + void copyHeadRotation(const Player* p) { mHead = p->mHead; } +public: + bool ignore_updates; + void resetContactTimer() { mContactTimer = 0; } +private: + U8 move_trigger_states; + U32 fx_s_triggers; + U32 mark_fx_c_triggers; + U32 fx_c_triggers; + F32 z_velocity; + bool mark_idle; + F32 idle_timer; + bool mark_s_landing; + void process_client_triggers(bool triggeredLeft, bool triggeredRight); +public: + enum { + // server events + PLAYER_MOVE_TRIGGER_0 = BIT(0), + PLAYER_MOVE_TRIGGER_1 = BIT(1), + PLAYER_MOVE_TRIGGER_2 = BIT(2), + PLAYER_MOVE_TRIGGER_3 = BIT(3), + PLAYER_MOVE_TRIGGER_4 = BIT(4), + PLAYER_MOVE_TRIGGER_5 = BIT(5), + PLAYER_LANDING_S_TRIGGER = BIT(6), + + PLAYER_FIRE_S_TRIGGER = PLAYER_MOVE_TRIGGER_0, + PLAYER_FIRE_ALT_S_TRIGGER = PLAYER_MOVE_TRIGGER_1, + PLAYER_JUMP_S_TRIGGER = BIT(7), + + // client events + PLAYER_LF_FOOT_C_TRIGGER = BIT(16), + PLAYER_RT_FOOT_C_TRIGGER = BIT(17), + PLAYER_LANDING_C_TRIGGER = BIT(18), + PLAYER_IDLE_C_TRIGGER = BIT(19), + }; + U32 getClientEventTriggers() const { return fx_c_triggers; } + U32 getServerEventTriggers() const { return fx_s_triggers; } +private: + F32 speed_bias; + F32 speed_bias_goal; + bool override_movement; + Point3F movement_data; + U8 movement_op; + U32 last_movement_tag; + static U32 unique_movement_tag_counter; +public: + void setMovementSpeedBias(F32 bias); + U32 setMovementOverride(F32 bias, const Point3F* mov=0, U32 op=1); + void restoreMovement(U32 tag); +private: + S32 footfallDecalOverride; + S32 footfallSoundOverride; + S32 footfallDustOverride; + bool noFootfallFX; +public: + void overrideFootfallFX(bool decals=true, bool sounds=true, bool dust=true); + void restoreFootfallFX(bool decals=true, bool sounds=true, bool dust=true); }; typedef Player::Pose PlayerPose; diff --git a/Engine/source/T3D/projectile.cpp b/Engine/source/T3D/projectile.cpp index 61821cbde..7e77385a2 100644 --- a/Engine/source/T3D/projectile.cpp +++ b/Engine/source/T3D/projectile.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/projectile.h" @@ -190,6 +195,40 @@ ProjectileData::ProjectileData() lightDescId = 0; } +ProjectileData::ProjectileData(const ProjectileData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + projectileShapeName = other.projectileShapeName; + faceViewer = other.faceViewer; // -- always set to false + scale = other.scale; + velInheritFactor = other.velInheritFactor; + muzzleVelocity = other.muzzleVelocity; + impactForce = other.impactForce; + isBallistic = other.isBallistic; + bounceElasticity = other.bounceElasticity; + bounceFriction = other.bounceFriction; + gravityMod = other.gravityMod; + lifetime = other.lifetime; + armingDelay = other.armingDelay; + fadeDelay = other.fadeDelay; + explosion = other.explosion; + explosionId = other.explosionId; // -- for pack/unpack of explosion ptr + waterExplosion = other.waterExplosion; + waterExplosionId = other.waterExplosionId; // -- for pack/unpack of waterExplosion ptr + splash = other.splash; + splashId = other.splashId; // -- for pack/unpack of splash ptr + decal = other.decal; + decalId = other.decalId; // -- for pack/unpack of decal ptr + sound = other.sound; + lightDesc = other.lightDesc; + lightDescId = other.lightDescId; // -- for pack/unpack of lightDesc ptr + projectileShape = other.projectileShape; // -- TSShape loads using projectileShapeName + activateSeq = other.activateSeq; // -- from projectileShape sequence "activate" + maintainSeq = other.maintainSeq; // -- from projectileShape sequence "maintain" + particleEmitter = other.particleEmitter; + particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr + particleWaterEmitter = other.particleWaterEmitter; + particleWaterEmitterId = other.particleWaterEmitterId; // -- for pack/unpack of particleWaterEmitter ptr +} //-------------------------------------------------------------------------- void ProjectileData::initPersistFields() @@ -274,6 +313,13 @@ void ProjectileData::initPersistFields() "A value of 1.0 will assume \"normal\" influence upon it.\n" "The magnitude of gravity is assumed to be 9.81 m/s/s\n\n" "@note ProjectileData::isBallistic must be true for this to have any affect."); + // disallow some field substitutions + onlyKeepClearSubstitutions("explosion"); + onlyKeepClearSubstitutions("particleEmitter"); + onlyKeepClearSubstitutions("particleWaterEmitter"); + onlyKeepClearSubstitutions("sound"); + onlyKeepClearSubstitutions("splash"); + onlyKeepClearSubstitutions("waterExplosion"); Parent::initPersistFields(); } @@ -574,6 +620,11 @@ Projectile::Projectile() mLightState.clear(); mLightState.setLightInfo( mLight ); + + mDataBlock = 0; + ignoreSourceTimeout = false; + dynamicCollisionMask = csmDynamicCollisionMask; + staticCollisionMask = csmStaticCollisionMask; } Projectile::~Projectile() @@ -582,6 +633,11 @@ Projectile::~Projectile() delete mProjectileShape; mProjectileShape = NULL; + if (mDataBlock && mDataBlock->isTempClone()) + { + delete mDataBlock; + mDataBlock = 0; + } } //-------------------------------------------------------------------------- @@ -609,6 +665,7 @@ void Projectile::initPersistFields() addField("sourceSlot", TypeS32, Offset(mSourceObjectSlot, Projectile), "@brief The sourceObject's weapon slot that the projectile originates from.\n\n"); + addField("ignoreSourceTimeout", TypeBool, Offset(ignoreSourceTimeout, Projectile)); endGroup("Source"); @@ -1088,7 +1145,7 @@ void Projectile::simulate( F32 dt ) // disable the source objects collision reponse for a short time while we // determine if the projectile is capable of moving from the old position // to the new position, otherwise we'll hit ourself - bool disableSourceObjCollision = (mSourceObject.isValid() && mCurrTick <= SourceIdTimeoutTicks); + bool disableSourceObjCollision = (mSourceObject.isValid() && (ignoreSourceTimeout || mCurrTick <= SourceIdTimeoutTicks)); if ( disableSourceObjCollision ) mSourceObject->disableCollision(); disableCollision(); @@ -1105,12 +1162,12 @@ void Projectile::simulate( F32 dt ) if ( mPhysicsWorld ) hit = mPhysicsWorld->castRay( oldPosition, newPosition, &rInfo, Point3F( newPosition - oldPosition) * mDataBlock->impactForce ); else - hit = getContainer()->castRay(oldPosition, newPosition, csmDynamicCollisionMask | csmStaticCollisionMask, &rInfo); + hit = getContainer()->castRay(oldPosition, newPosition, dynamicCollisionMask | staticCollisionMask, &rInfo); if ( hit ) { // make sure the client knows to bounce - if ( isServerObject() && ( rInfo.object->getTypeMask() & csmStaticCollisionMask ) == 0 ) + if(isServerObject() && (rInfo.object->getTypeMask() & staticCollisionMask) == 0) setMaskBits( BounceMask ); MatrixF xform( true ); @@ -1301,6 +1358,7 @@ U32 Projectile::packUpdate( NetConnection *con, U32 mask, BitStream *stream ) stream->writeRangedU32( U32(mSourceObjectSlot), 0, ShapeBase::MaxMountedImages - 1 ); + stream->writeFlag(ignoreSourceTimeout); } else // have not recieved the ghost for the source object yet, try again later @@ -1344,6 +1402,7 @@ void Projectile::unpackUpdate(NetConnection* con, BitStream* stream) mSourceObjectId = stream->readRangedU32( 0, NetConnection::MaxGhostCount ); mSourceObjectSlot = stream->readRangedU32( 0, ShapeBase::MaxMountedImages - 1 ); + ignoreSourceTimeout = stream->readFlag(); NetObject* pObject = con->resolveGhost( mSourceObjectId ); if ( pObject != NULL ) mSourceObject = dynamic_cast( pObject ); diff --git a/Engine/source/T3D/projectile.h b/Engine/source/T3D/projectile.h index 17c90cfbc..f6c3870c1 100644 --- a/Engine/source/T3D/projectile.h +++ b/Engine/source/T3D/projectile.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _PROJECTILE_H_ #define _PROJECTILE_H_ @@ -144,6 +149,9 @@ public: DECLARE_CALLBACK( void, onExplode, ( Projectile* proj, Point3F pos, F32 fade ) ); DECLARE_CALLBACK( void, onCollision, ( Projectile* proj, SceneObject* col, F32 fade, Point3F pos, Point3F normal ) ); +public: + ProjectileData(const ProjectileData&, bool = false); + virtual bool allowSubstitutions() const { return true; } }; @@ -279,6 +287,10 @@ protected: Point3F mExplosionPosition; Point3F mExplosionNormal; U32 mCollideHitType; +public: + bool ignoreSourceTimeout; + U32 dynamicCollisionMask; + U32 staticCollisionMask; }; #endif // _PROJECTILE_H_ diff --git a/Engine/source/T3D/shapeBase.cpp b/Engine/source/T3D/shapeBase.cpp index e612aa148..5f71a7eb9 100644 --- a/Engine/source/T3D/shapeBase.cpp +++ b/Engine/source/T3D/shapeBase.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/shapeBase.h" @@ -194,6 +199,69 @@ ShapeBaseData::ShapeBaseData() inheritEnergyFromMount( false ) { dMemset( mountPointNode, -1, sizeof( S32 ) * SceneObject::NumMountPoints ); + remap_txr_tags = NULL; + remap_buffer = NULL; + silent_bbox_check = false; +} + +ShapeBaseData::ShapeBaseData(const ShapeBaseData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + shadowEnable = other.shadowEnable; + shadowSize = other.shadowSize; + shadowMaxVisibleDistance = other.shadowMaxVisibleDistance; + shadowProjectionDistance = other.shadowProjectionDistance; + shadowSphereAdjust = other.shadowSphereAdjust; + shapeName = other.shapeName; + cloakTexName = other.cloakTexName; + cubeDescName = other.cubeDescName; + cubeDescId = other.cubeDescId; + reflectorDesc = other.reflectorDesc; + debris = other.debris; + debrisID = other.debrisID; // -- for pack/unpack of debris ptr + debrisShapeName = other.debrisShapeName; + debrisShape = other.debrisShape; // -- TSShape loaded using debrisShapeName + explosion = other.explosion; + explosionID = other.explosionID; // -- for pack/unpack of explosion ptr + underwaterExplosion = other.underwaterExplosion; + underwaterExplosionID = other.underwaterExplosionID; // -- for pack/unpack of underwaterExplosion ptr + mass = other.mass; + drag = other.drag; + density = other.density; + maxEnergy = other.maxEnergy; + maxDamage = other.maxDamage; + repairRate = other.repairRate; + disabledLevel = other.disabledLevel; + destroyedLevel = other.destroyedLevel; + cameraMaxDist = other.cameraMaxDist; + cameraMinDist = other.cameraMinDist; + cameraDefaultFov = other.cameraDefaultFov; + cameraMinFov = other.cameraMinFov; + cameraMaxFov = other.cameraMaxFov; + cameraCanBank = other.cameraCanBank; + mountedImagesBank = other.mountedImagesBank; + mShape = other.mShape; // -- TSShape loaded using shapeName + mCRC = other.mCRC; // -- from shape, used to verify client shape + computeCRC = other.computeCRC; + eyeNode = other.eyeNode; // -- from shape node "eye" + earNode = other.earNode; // -- from shape node "ear" + cameraNode = other.cameraNode; // -- from shape node "cam" + dMemcpy(mountPointNode, other.mountPointNode, sizeof(mountPointNode)); // -- from shape nodes "mount#" 0-31 + debrisDetail = other.debrisDetail; // -- from shape detail "Debris-17" + damageSequence = other.damageSequence; // -- from shape sequence "Damage" + hulkSequence = other.hulkSequence; // -- from shape sequence "Visibility" + observeThroughObject = other.observeThroughObject; + collisionDetails = other.collisionDetails; // -- calc from shape (this is a Vector copy) + collisionBounds = other.collisionBounds; // -- calc from shape (this is a Vector copy) + LOSDetails = other.LOSDetails; // -- calc from shape (this is a Vector copy) + firstPersonOnly = other.firstPersonOnly; + useEyePoint = other.useEyePoint; + isInvincible = other.isInvincible; + renderWhenDestroyed = other.renderWhenDestroyed; + inheritEnergyFromMount = other.inheritEnergyFromMount; + remap_txr_tags = other.remap_txr_tags; + remap_buffer = other.remap_buffer; + txr_tag_remappings = other.txr_tag_remappings; + silent_bbox_check = other.silent_bbox_check; } struct ShapeBaseDataProto @@ -228,6 +296,8 @@ static ShapeBaseDataProto gShapeBaseDataProto; ShapeBaseData::~ShapeBaseData() { + if (remap_buffer && !isTempClone()) + dFree(remap_buffer); } bool ShapeBaseData::preload(bool server, String &errorStr) @@ -337,11 +407,13 @@ bool ShapeBaseData::preload(bool server, String &errorStr) if (!mShape->bounds.isContained(collisionBounds.last())) { + if (!silent_bbox_check) Con::warnf("Warning: shape %s collision detail %d (Collision-%d) bounds exceed that of shape.", shapeName, collisionDetails.size() - 1, collisionDetails.last()); collisionBounds.last() = mShape->bounds; } else if (collisionBounds.last().isValidBox() == false) { + if (!silent_bbox_check) Con::errorf("Error: shape %s-collision detail %d (Collision-%d) bounds box invalid!", shapeName, collisionDetails.size() - 1, collisionDetails.last()); collisionBounds.last() = mShape->bounds; } @@ -413,6 +485,29 @@ bool ShapeBaseData::preload(bool server, String &errorStr) F32 w = mShape->bounds.len_y() / 2; if (cameraMaxDist < w) cameraMaxDist = w; + // just parse up the string and collect the remappings in txr_tag_remappings. + if (!server && remap_txr_tags != NULL && remap_txr_tags != StringTable->insert("")) + { + txr_tag_remappings.clear(); + if (remap_buffer) + dFree(remap_buffer); + + remap_buffer = dStrdup(remap_txr_tags); + + char* remap_token = dStrtok(remap_buffer, " \t"); + while (remap_token != NULL) + { + char* colon = dStrchr(remap_token, ':'); + if (colon) + { + *colon = '\0'; + txr_tag_remappings.increment(); + txr_tag_remappings.last().old_tag = remap_token; + txr_tag_remappings.last().new_tag = colon+1; + } + remap_token = dStrtok(NULL, " \t"); + } + } } if(!server) @@ -586,6 +681,12 @@ void ShapeBaseData::initPersistFields() endGroup( "Reflection" ); + addField("remapTextureTags", TypeString, Offset(remap_txr_tags, ShapeBaseData)); + addField("silentBBoxValidation", TypeBool, Offset(silent_bbox_check, ShapeBaseData)); + // disallow some field substitutions + onlyKeepClearSubstitutions("debris"); // subs resolving to "~~", or "~0" are OK + onlyKeepClearSubstitutions("explosion"); + onlyKeepClearSubstitutions("underwaterExplosion"); Parent::initPersistFields(); } @@ -738,6 +839,8 @@ void ShapeBaseData::packData(BitStream* stream) //stream->write(reflectMinDist); //stream->write(reflectMaxDist); //stream->write(reflectDetailAdjust); + stream->writeString(remap_txr_tags); + stream->writeFlag(silent_bbox_check); } void ShapeBaseData::unpackData(BitStream* stream) @@ -839,6 +942,8 @@ void ShapeBaseData::unpackData(BitStream* stream) //stream->read(&reflectMinDist); //stream->read(&reflectMaxDist); //stream->read(&reflectDetailAdjust); + remap_txr_tags = stream->readSTString(); + silent_bbox_check = stream->readFlag(); } @@ -941,6 +1046,13 @@ ShapeBase::ShapeBase() for (i = 0; i < MaxTriggerKeys; i++) mTrigger[i] = false; + anim_clip_flags = 0; + last_anim_id = -1; + last_anim_tag = 0; + last_anim_lock_tag = 0; + saved_seq_id = -1; + saved_pos = 0.0f; + saved_rate = 1.0f; } @@ -1079,6 +1191,16 @@ void ShapeBase::onSceneRemove() bool ShapeBase::onNewDataBlock( GameBaseData *dptr, bool reload ) { + // need to destroy blend-clips or we crash + if (isGhost()) + { + for (S32 i = 0; i < blend_clips.size(); i++) + { + if (blend_clips[i].thread) + mShapeInstance->destroyThread(blend_clips[i].thread); + blend_clips.erase_fast(i); + } + } ShapeBaseData *prevDB = dynamic_cast( mDataBlock ); bool isInitialDataBlock = ( mDataBlock == 0 ); @@ -1098,10 +1220,61 @@ bool ShapeBase::onNewDataBlock( GameBaseData *dptr, bool reload ) // a shape assigned to this object. if (bool(mDataBlock->mShape)) { delete mShapeInstance; + if (isClientObject() && mDataBlock->txr_tag_remappings.size() > 0) + { + // temporarily substitute material tags with alternates + TSMaterialList* mat_list = mDataBlock->mShape->materialList; + if (mat_list) + { + for (S32 i = 0; i < mDataBlock->txr_tag_remappings.size(); i++) + { + ShapeBaseData::TextureTagRemapping* remap = &mDataBlock->txr_tag_remappings[i]; + Vector & mat_names = (Vector&) mat_list->getMaterialNameList(); + for (S32 j = 0; j < mat_names.size(); j++) + { + if (mat_names[j].compare(remap->old_tag, dStrlen(remap->old_tag), String::NoCase) == 0) + { + mat_names[j] = String(remap->new_tag); + mat_names[j].insert(0,'#'); + break; + } + } + } + } + } mShapeInstance = new TSShapeInstance(mDataBlock->mShape, isClientObject()); if (isClientObject()) + { mShapeInstance->cloneMaterialList(); + // restore the material tags to original form + if (mDataBlock->txr_tag_remappings.size() > 0) + { + TSMaterialList* mat_list = mDataBlock->mShape->materialList; + if (mat_list) + { + for (S32 i = 0; i < mDataBlock->txr_tag_remappings.size(); i++) + { + ShapeBaseData::TextureTagRemapping* remap = &mDataBlock->txr_tag_remappings[i]; + Vector & mat_names = (Vector&) mat_list->getMaterialNameList(); + for (S32 j = 0; j < mat_names.size(); j++) + { + String::SizeType len = mat_names[j].length(); + if (len > 1) + { + String temp_name = mat_names[j].substr(1,len-1); + if (temp_name.compare(remap->new_tag, dStrlen(remap->new_tag)) == 0) + { + mat_names[j] = String(remap->old_tag); + break; + } + } + } + } + } + } + } + mObjBox = mDataBlock->mShape->bounds; resetWorldBox(); @@ -3550,6 +3723,31 @@ void ShapeBase::setCurrentWaterObject( WaterObject *obj ) mCurrentWaterObject = obj; } +void ShapeBase::notifyCollisionCallbacks(SceneObject* obj, const VectorF& vel) +{ + for (S32 i = 0; i < collision_callbacks.size(); i++) + if (collision_callbacks[i]) + collision_callbacks[i]->collisionNotify(this, obj, vel); +} + +void ShapeBase::registerCollisionCallback(CollisionEventCallback* ce_cb) +{ + for (S32 i = 0; i < collision_callbacks.size(); i++) + if (collision_callbacks[i] == ce_cb) + return; + + collision_callbacks.push_back(ce_cb); +} + +void ShapeBase::unregisterCollisionCallback(CollisionEventCallback* ce_cb) +{ + for (S32 i = 0; i < collision_callbacks.size(); i++) + if (collision_callbacks[i] == ce_cb) + { + collision_callbacks.erase(i); + return; + } +} //-------------------------------------------------------------------------- //---------------------------------------------------------------------------- DefineEngineMethod( ShapeBase, setHidden, void, ( bool show ),, @@ -4945,3 +5143,202 @@ DefineEngineMethod( ShapeBase, getModelFile, const char *, (),, const char *fieldName = StringTable->insert( String("shapeFile") ); return datablock->getDataField( fieldName, NULL ); } + + +U32 ShapeBase::unique_anim_tag_counter = 1; + +U32 ShapeBase::playBlendAnimation(S32 seq_id, F32 pos, F32 rate) +{ + BlendThread blend_clip; + blend_clip.tag = ((unique_anim_tag_counter++) | BLENDED_CLIP); + blend_clip.thread = 0; + + if (isClientObject()) + { + blend_clip.thread = mShapeInstance->addThread(); + mShapeInstance->setSequence(blend_clip.thread, seq_id, pos); + mShapeInstance->setTimeScale(blend_clip.thread, rate); + } + + blend_clips.push_back(blend_clip); + + return blend_clip.tag; +} + +void ShapeBase::restoreBlendAnimation(U32 tag) +{ + for (S32 i = 0; i < blend_clips.size(); i++) + { + if (blend_clips[i].tag == tag) + { + if (blend_clips[i].thread) + { + mShapeInstance->destroyThread(blend_clips[i].thread); + } + blend_clips.erase_fast(i); + break; + } + } +} + +// + +void ShapeBase::restoreAnimation(U32 tag) +{ + if (!isClientObject()) + return; + + // check if this is a blended clip + if ((tag & BLENDED_CLIP) != 0) + { + restoreBlendAnimation(tag); + return; + } + + if (tag != 0 && tag == last_anim_tag) + { + anim_clip_flags &= ~(ANIM_OVERRIDDEN | IS_DEATH_ANIM); + + stopThread(0); + + if (saved_seq_id != -1) + { + setThreadSequence(0, saved_seq_id); + setThreadPosition(0, saved_pos); + setThreadTimeScale(0, saved_rate); + setThreadDir(0, (saved_rate >= 0)); + playThread(0); + + saved_seq_id = -1; + saved_pos = 0.0f; + saved_rate = 1.0f; + } + + last_anim_tag = 0; + last_anim_id = -1; + } +} + +U32 ShapeBase::getAnimationID(const char* name) +{ + const TSShape* ts_shape = getShape(); + S32 seq_id = (ts_shape) ? ts_shape->findSequence(name) : -1; + return (seq_id >= 0) ? (U32) seq_id : BAD_ANIM_ID; +} + +U32 ShapeBase::playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim) +{ + if (!isClientObject()) + return 0; + + if (anim_id == BAD_ANIM_ID) + return 0; + + const TSShape* ts_shape = getShape(); + if (!ts_shape) + return 0; + + S32 seq_id = (S32) anim_id; + if (mShapeInstance->getShape()->sequences[seq_id].isBlend()) + return playBlendAnimation(seq_id, pos, rate); + + if (last_anim_tag == 0) + { + // try to save state of playing animation + Thread& st = mScriptThread[0]; + if (st.sequence != -1) + { + saved_seq_id = st.sequence; + saved_pos = st.position; + saved_rate = st.timescale; + } + } + + // START OR TRANSITION TO SEQUENCE HERE + setThreadSequence(0, seq_id); + setThreadPosition(0, pos); + setThreadTimeScale(0, rate); + setThreadDir(0, (rate >= 0)); + playThread(0); + + if (is_death_anim) + anim_clip_flags |= IS_DEATH_ANIM; + else + anim_clip_flags &= ~IS_DEATH_ANIM; + + anim_clip_flags |= ANIM_OVERRIDDEN; + last_anim_tag = unique_anim_tag_counter++; + last_anim_id = anim_id; + + return last_anim_tag; +} + +F32 ShapeBase::getAnimationDurationByID(U32 anim_id) +{ + if (anim_id == BAD_ANIM_ID) + return 0.0f; + + S32 seq_id = (S32) anim_id; + if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size()) + return mDataBlock->mShape->sequences[seq_id].duration; + + return 0.0f; +} + +bool ShapeBase::isBlendAnimation(const char* name) +{ + U32 anim_id = getAnimationID(name); + if (anim_id == BAD_ANIM_ID) + return false; + + S32 seq_id = (S32) anim_id; + if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size()) + return mDataBlock->mShape->sequences[seq_id].isBlend(); + + return false; +} + +const char* ShapeBase::getLastClipName(U32 clip_tag) +{ + if (clip_tag != last_anim_tag) + return ""; + + S32 seq_id = (S32) last_anim_id; + + S32 idx = mDataBlock->mShape->sequences[seq_id].nameIndex; + if (idx < 0 || idx >= mDataBlock->mShape->names.size()) + return 0; + + return mDataBlock->mShape->names[idx]; +} + +// + +U32 ShapeBase::playAnimation(const char* name, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim) +{ + return playAnimationByID(getAnimationID(name), pos, rate, trans, hold, wait, is_death_anim); +} + +F32 ShapeBase::getAnimationDuration(const char* name) +{ + return getAnimationDurationByID(getAnimationID(name)); +} + +void ShapeBase::setSelectionFlags(U8 flags) +{ + Parent::setSelectionFlags(flags); + + if (!mShapeInstance || !isClientObject()) + return; + + if (!mShapeInstance->ownMaterialList()) + return; + + TSMaterialList* pMatList = mShapeInstance->getMaterialList(); + for (S32 j = 0; j < pMatList->size(); j++) + { + BaseMatInstance * bmi = pMatList->getMaterialInst(j); + bmi->setSelectionHighlighting(needsSelectionHighlighting()); + } +} + diff --git a/Engine/source/T3D/shapeBase.h b/Engine/source/T3D/shapeBase.h index 98a42f73b..067ef7cbf 100644 --- a/Engine/source/T3D/shapeBase.h +++ b/Engine/source/T3D/shapeBase.h @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #ifndef _SHAPEBASE_H_ #define _SHAPEBASE_H_ @@ -654,6 +658,17 @@ public: DECLARE_CALLBACK(void, onEndSequence, (ShapeBase* obj, S32 slot, const char* name)); DECLARE_CALLBACK( void, onForceUncloak, ( ShapeBase* obj, const char* reason ) ); /// @} + struct TextureTagRemapping + { + char* old_tag; + char* new_tag; + }; + StringTableEntry remap_txr_tags; + char* remap_buffer; + Vector txr_tag_remappings; + bool silent_bbox_check; +public: + ShapeBaseData(const ShapeBaseData&, bool = false); }; @@ -1845,7 +1860,58 @@ public: protected: DECLARE_CALLBACK( F32, validateCameraFov, (F32 fov) ); +public: + class CollisionEventCallback + { + public: + virtual void collisionNotify(SceneObject* shape0, SceneObject* shape1, const VectorF& vel)=0; + }; +private: + Vector collision_callbacks; + void notifyCollisionCallbacks(SceneObject*, const VectorF& vel); +public: + void registerCollisionCallback(CollisionEventCallback*); + void unregisterCollisionCallback(CollisionEventCallback*); +protected: + enum { + ANIM_OVERRIDDEN = BIT(0), + BLOCK_USER_CONTROL = BIT(1), + IS_DEATH_ANIM = BIT(2), + BAD_ANIM_ID = 999999999, + BLENDED_CLIP = 0x80000000, + }; + struct BlendThread + { + TSThread* thread; + U32 tag; + }; + Vector blend_clips; + static U32 unique_anim_tag_counter; + U8 anim_clip_flags; + S32 last_anim_id; + U32 last_anim_tag; + U32 last_anim_lock_tag; + S32 saved_seq_id; + F32 saved_pos; + F32 saved_rate; + U32 playBlendAnimation(S32 seq_id, F32 pos, F32 rate); + void restoreBlendAnimation(U32 tag); +public: + U32 playAnimation(const char* name, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim); + F32 getAnimationDuration(const char* name); + + virtual void restoreAnimation(U32 tag); + virtual U32 getAnimationID(const char* name); + virtual U32 playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim); + virtual F32 getAnimationDurationByID(U32 anim_id); + virtual bool isBlendAnimation(const char* name); + virtual const char* getLastClipName(U32 clip_tag); + virtual void unlockAnimation(U32 tag, bool force=false) { } + virtual U32 lockAnimation() { return 0; } + virtual bool isAnimationLocked() const { return false; } + + virtual void setSelectionFlags(U8 flags); }; diff --git a/Engine/source/T3D/staticShape.cpp b/Engine/source/T3D/staticShape.cpp index 6548e2161..874e31059 100644 --- a/Engine/source/T3D/staticShape.cpp +++ b/Engine/source/T3D/staticShape.cpp @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #include "platform/platform.h" #include "core/dnet.h" #include "core/stream/bitStream.h" @@ -97,6 +101,14 @@ StaticShapeData::StaticShapeData() noIndividualDamage = false; } +StaticShapeData::StaticShapeData(const StaticShapeData& other, bool temp_clone) : ShapeBaseData(other, temp_clone) +{ + noIndividualDamage = other.noIndividualDamage; + dynamicTypeField = other.dynamicTypeField; + isShielded = other.isShielded; // -- uninitialized, unused + energyPerDamagePoint = other.energyPerDamagePoint; // -- uninitialized, unused +} + void StaticShapeData::initPersistFields() { addField("noIndividualDamage", TypeBool, Offset(noIndividualDamage, StaticShapeData), "Deprecated\n\n @internal"); diff --git a/Engine/source/T3D/staticShape.h b/Engine/source/T3D/staticShape.h index 737a2e335..292260c72 100644 --- a/Engine/source/T3D/staticShape.h +++ b/Engine/source/T3D/staticShape.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _STATICSHAPE_H_ #define _STATICSHAPE_H_ @@ -38,12 +43,16 @@ struct StaticShapeData: public ShapeBaseData { bool noIndividualDamage; S32 dynamicTypeField; bool isShielded; + F32 energyPerDamagePoint; // Re-added for AFX // DECLARE_CONOBJECT(StaticShapeData); static void initPersistFields(); virtual void packData(BitStream* stream); virtual void unpackData(BitStream* stream); +public: + StaticShapeData(const StaticShapeData&, bool = false); + virtual bool allowSubstitutions() const { return true; } }; diff --git a/Engine/source/T3D/tsStatic.cpp b/Engine/source/T3D/tsStatic.cpp index 84b3b0797..b2f33f44a 100644 --- a/Engine/source/T3D/tsStatic.cpp +++ b/Engine/source/T3D/tsStatic.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "T3D/tsStatic.h" @@ -54,6 +59,8 @@ using namespace Torque; extern bool gEditingMission; +#include "afx/ce/afxZodiacMgr.h" + IMPLEMENT_CO_NETOBJECT_V1(TSStatic); ConsoleDocClass( TSStatic, @@ -124,6 +131,12 @@ TSStatic::TSStatic() mCollisionType = CollisionMesh; mDecalType = CollisionMesh; + + mIgnoreZodiacs = false; + mHasGradients = false; + mInvertGradientRange = false; + mGradientRangeUser.set(0.0f, 180.0f); + afxZodiacData::convertGradientRangeFromDegrees(mGradientRange, mGradientRangeUser); } TSStatic::~TSStatic() @@ -222,6 +235,12 @@ void TSStatic::initPersistFields() endGroup("Debug"); + addGroup("AFX"); + addField("ignoreZodiacs", TypeBool, Offset(mIgnoreZodiacs, TSStatic)); + addField("useGradientRange", TypeBool, Offset(mHasGradients, TSStatic)); + addField("gradientRange", TypePoint2F, Offset(mGradientRangeUser, TSStatic)); + addField("invertGradientRange", TypeBool, Offset(mInvertGradientRange, TSStatic)); + endGroup("AFX"); Parent::initPersistFields(); } @@ -323,6 +342,8 @@ bool TSStatic::_createShape() { // Cleanup before we create. mCollisionDetails.clear(); + mDecalDetails.clear(); + mDecalDetailsPtr = 0; mLOSDetails.clear(); SAFE_DELETE( mPhysicsRep ); SAFE_DELETE( mShapeInstance ); @@ -354,6 +375,8 @@ bool TSStatic::_createShape() mShapeInstance = new TSShapeInstance( mShape, isClientObject() ); + if (isClientObject()) + mShapeInstance->cloneMaterialList(); if( isGhost() ) { // Reapply the current skin @@ -396,11 +419,29 @@ void TSStatic::prepCollision() // Cleanup any old collision data mCollisionDetails.clear(); + mDecalDetails.clear(); + mDecalDetailsPtr = 0; mLOSDetails.clear(); mConvexList->nukeList(); if ( mCollisionType == CollisionMesh || mCollisionType == VisibleMesh ) + { mShape->findColDetails( mCollisionType == VisibleMesh, &mCollisionDetails, &mLOSDetails ); + if ( mDecalType == mCollisionType ) + { + mDecalDetailsPtr = &mCollisionDetails; + } + else if ( mDecalType == CollisionMesh || mDecalType == VisibleMesh ) + { + mShape->findColDetails( mDecalType == VisibleMesh, &mDecalDetails, 0 ); + mDecalDetailsPtr = &mDecalDetails; + } + } + else if ( mDecalType == CollisionMesh || mDecalType == VisibleMesh ) + { + mShape->findColDetails( mDecalType == VisibleMesh, &mDecalDetails, 0 ); + mDecalDetailsPtr = &mDecalDetails; + } _updatePhysics(); } @@ -681,6 +722,8 @@ void TSStatic::prepRenderImage( SceneRenderState* state ) } mShapeInstance->render( rdata ); + if (!mIgnoreZodiacs && mDecalDetailsPtr != 0) + afxZodiacMgr::renderPolysoupZodiacs(state, this); if ( mRenderNormalScalar > 0 ) { ObjectRenderInst *ri = state->getRenderPass()->allocInst(); @@ -786,6 +829,13 @@ U32 TSStatic::packUpdate(NetConnection *con, U32 mask, BitStream *stream) stream->write(mInvertAlphaFade); } + stream->writeFlag(mIgnoreZodiacs); + if (stream->writeFlag(mHasGradients)) + { + stream->writeFlag(mInvertGradientRange); + stream->write(mGradientRange.x); + stream->write(mGradientRange.y); + } if ( mLightPlugin ) retMask |= mLightPlugin->packUpdate(this, AdvancedStaticOptionsMask, con, mask, stream); @@ -870,6 +920,14 @@ void TSStatic::unpackUpdate(NetConnection *con, BitStream *stream) stream->read(&mInvertAlphaFade); } + mIgnoreZodiacs = stream->readFlag(); + mHasGradients = stream->readFlag(); + if (mHasGradients) + { + mInvertGradientRange = stream->readFlag(); + stream->read(&mGradientRange.x); + stream->read(&mGradientRange.y); + } if ( mLightPlugin ) { mLightPlugin->unpackUpdate(this, con, stream); @@ -882,6 +940,7 @@ void TSStatic::unpackUpdate(NetConnection *con, BitStream *stream) if ( isProperlyAdded() ) _updateShouldTick(); + set_special_typing(); } //---------------------------------------------------------------------------- @@ -992,6 +1051,11 @@ bool TSStatic::buildPolyList(PolyListContext context, AbstractPolyList* polyList polyList->addBox( mObjBox ); else if ( meshType == VisibleMesh ) mShapeInstance->buildPolyList( polyList, 0 ); + else if (context == PLC_Decal && mDecalDetailsPtr != 0) + { + for ( U32 i = 0; i < mDecalDetailsPtr->size(); i++ ) + mShapeInstance->buildPolyListOpcode( (*mDecalDetailsPtr)[i], polyList, box ); + } else { // Everything else is done from the collision meshes @@ -1324,3 +1388,41 @@ DefineEngineMethod( TSStatic, getModelFile, const char *, (),, { return object->getShapeFileName(); } + +void TSStatic::set_special_typing() +{ + if (mCollisionType == VisibleMesh || mCollisionType == CollisionMesh) + mTypeMask |= InteriorLikeObjectType; + else + mTypeMask &= ~InteriorLikeObjectType; +} + +void TSStatic::onStaticModified(const char* slotName, const char*newValue) +{ + if (slotName == afxZodiacData::GradientRangeSlot) + { + afxZodiacData::convertGradientRangeFromDegrees(mGradientRange, mGradientRangeUser); + return; + } + + set_special_typing(); +} + +void TSStatic::setSelectionFlags(U8 flags) +{ + Parent::setSelectionFlags(flags); + + if (!mShapeInstance || !isClientObject()) + return; + + if (!mShapeInstance->ownMaterialList()) + return; + + TSMaterialList* pMatList = mShapeInstance->getMaterialList(); + for (S32 j = 0; j < pMatList->size(); j++) + { + BaseMatInstance * bmi = pMatList->getMaterialInst(j); + bmi->setSelectionHighlighting(needsSelectionHighlighting()); + } +} + diff --git a/Engine/source/T3D/tsStatic.cpp.orig b/Engine/source/T3D/tsStatic.cpp.orig deleted file mode 100644 index 11848499e..000000000 --- a/Engine/source/T3D/tsStatic.cpp.orig +++ /dev/null @@ -1,1330 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2012 GarageGames, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//----------------------------------------------------------------------------- - -#include "platform/platform.h" -#include "T3D/tsStatic.h" - -#include "core/resourceManager.h" -#include "core/stream/bitStream.h" -#include "scene/sceneRenderState.h" -#include "scene/sceneManager.h" -#include "scene/sceneObjectLightingPlugin.h" -#include "lighting/lightManager.h" -#include "math/mathIO.h" -#include "ts/tsShapeInstance.h" -#include "ts/tsMaterialList.h" -#include "console/consoleTypes.h" -#include "T3D/shapeBase.h" -#include "sim/netConnection.h" -#include "gfx/gfxDevice.h" -#include "gfx/gfxTransformSaver.h" -#include "ts/tsRenderState.h" -#include "collision/boxConvex.h" -#include "T3D/physics/physicsPlugin.h" -#include "T3D/physics/physicsBody.h" -#include "T3D/physics/physicsCollision.h" -#include "materials/materialDefinition.h" -#include "materials/materialManager.h" -#include "materials/matInstance.h" -#include "materials/materialFeatureData.h" -#include "materials/materialFeatureTypes.h" -#include "console/engineAPI.h" -#include "T3D/accumulationVolume.h" - -using namespace Torque; - -extern bool gEditingMission; - -IMPLEMENT_CO_NETOBJECT_V1(TSStatic); - -ConsoleDocClass( TSStatic, - "@brief A static object derived from a 3D model file and placed within the game world.\n\n" - - "TSStatic is the most basic 3D shape in Torque. Unlike StaticShape it doesn't make use of " - "a datablock. It derrives directly from SceneObject. This makes TSStatic extremely light " - "weight, which is why the Tools use this class when you want to drop in a DTS or DAE object.\n\n" - - "While a TSStatic doesn't provide any motion -- it stays were you initally put it -- it does allow for " - "a single ambient animation sequence to play when the object is first added to the scene.\n\n" - - "@tsexample\n" - "new TSStatic(Team1Base) {\n" - " shapeName = \"art/shapes/desertStructures/station01.dts\";\n" - " playAmbient = \"1\";\n" - " receiveSunLight = \"1\";\n" - " receiveLMLighting = \"1\";\n" - " useCustomAmbientLighting = \"0\";\n" - " customAmbientLighting = \"0 0 0 1\";\n" - " collisionType = \"Visible Mesh\";\n" - " decalType = \"Collision Mesh\";\n" - " allowPlayerStep = \"1\";\n" - " renderNormals = \"0\";\n" - " forceDetail = \"-1\";\n" - " position = \"315.18 -180.418 244.313\";\n" - " rotation = \"0 0 1 195.952\";\n" - " scale = \"1 1 1\";\n" - " isRenderEnabled = \"true\";\n" - " canSaveDynamicFields = \"1\";\n" - "};\n" - "@endtsexample\n" - - "@ingroup gameObjects\n" -); - -TSStatic::TSStatic() -: - cubeDescId( 0 ), - reflectorDesc( NULL ) -{ - mNetFlags.set(Ghostable | ScopeAlways); - - mTypeMask |= StaticObjectType | StaticShapeObjectType; - - mShapeName = ""; - mShapeInstance = NULL; - - mPlayAmbient = true; - mAmbientThread = NULL; - - mAllowPlayerStep = false; - - mConvexList = new Convex; - - mRenderNormalScalar = 0; - mForceDetail = -1; - - mMeshCulling = false; - mUseOriginSort = false; - - mUseAlphaFade = false; - mAlphaFadeStart = 100.0f; - mAlphaFadeEnd = 150.0f; - mInvertAlphaFade = false; - mAlphaFade = 1.0f; - mPhysicsRep = NULL; - - mCollisionType = CollisionMesh; - mDecalType = CollisionMesh; -} - -TSStatic::~TSStatic() -{ - delete mConvexList; - mConvexList = NULL; -} - -ImplementEnumType( TSMeshType, - "Type of mesh data available in a shape.\n" - "@ingroup gameObjects" ) - { TSStatic::None, "None", "No mesh data." }, - { TSStatic::Bounds, "Bounds", "Bounding box of the shape." }, - { TSStatic::CollisionMesh, "Collision Mesh", "Specifically desingated \"collision\" meshes." }, - { TSStatic::VisibleMesh, "Visible Mesh", "Rendered mesh polygons." }, -EndImplementEnumType; - - -void TSStatic::initPersistFields() -{ - addGroup("Media"); - - addField("shapeName", TypeShapeFilename, Offset( mShapeName, TSStatic ), - "%Path and filename of the model file (.DTS, .DAE) to use for this TSStatic." ); - - addProtectedField( "skin", TypeRealString, Offset( mAppliedSkinName, TSStatic ), &_setFieldSkin, &_getFieldSkin, - "@brief The skin applied to the shape.\n\n" - - "'Skinning' the shape effectively renames the material targets, allowing " - "different materials to be used on different instances of the same model.\n\n" - - "Any material targets that start with the old skin name have that part " - "of the name replaced with the new skin name. The initial old skin name is " - "\"base\". For example, if a new skin of \"blue\" was applied to a model " - "that had material targets base_body and face, the new targets " - "would be blue_body and face. Note that face was not " - "renamed since it did not start with the old skin name of \"base\".\n\n" - - "To support models that do not use the default \"base\" naming convention, " - "you can also specify the part of the name to replace in the skin field " - "itself. For example, if a model had a material target called shapemat, " - "we could apply a new skin \"shape=blue\", and the material target would be " - "renamed to bluemat (note \"shape\" has been replaced with \"blue\").\n\n" - - "Multiple skin updates can also be applied at the same time by separating " - "them with a semicolon. For example: \"base=blue;face=happy_face\".\n\n" - - "Material targets are only renamed if an existing Material maps to that " - "name, or if there is a diffuse texture in the model folder with the same " - "name as the new target.\n\n" ); - - endGroup("Media"); - - addGroup("Rendering"); - - addField( "playAmbient", TypeBool, Offset( mPlayAmbient, TSStatic ), - "Enables automatic playing of the animation sequence named \"ambient\" (if it exists) when the TSStatic is loaded."); - addField( "meshCulling", TypeBool, Offset( mMeshCulling, TSStatic ), - "Enables detailed culling of meshes within the TSStatic. Should only be used " - "with large complex shapes like buildings which contain many submeshes." ); - addField( "originSort", TypeBool, Offset( mUseOriginSort, TSStatic ), - "Enables translucent sorting of the TSStatic by its origin instead of the bounds." ); - - endGroup("Rendering"); - - addGroup( "Reflection" ); - addField( "cubeReflectorDesc", TypeRealString, Offset( cubeDescName, TSStatic ), - "References a ReflectorDesc datablock that defines performance and quality properties for dynamic reflections.\n"); - endGroup( "Reflection" ); - - addGroup("Collision"); - - addField( "collisionType", TypeTSMeshType, Offset( mCollisionType, TSStatic ), - "The type of mesh data to use for collision queries." ); - addField( "decalType", TypeTSMeshType, Offset( mDecalType, TSStatic ), - "The type of mesh data used to clip decal polygons against." ); - addField( "allowPlayerStep", TypeBool, Offset( mAllowPlayerStep, TSStatic ), - "@brief Allow a Player to walk up sloping polygons in the TSStatic (based on the collisionType).\n\n" - "When set to false, the slightest bump will stop the player from walking on top of the object.\n"); - - endGroup("Collision"); - - addGroup( "AlphaFade" ); - addField( "alphaFadeEnable", TypeBool, Offset(mUseAlphaFade, TSStatic), "Turn on/off Alpha Fade" ); - addField( "alphaFadeStart", TypeF32, Offset(mAlphaFadeStart, TSStatic), "Distance of start Alpha Fade" ); - addField( "alphaFadeEnd", TypeF32, Offset(mAlphaFadeEnd, TSStatic), "Distance of end Alpha Fade" ); - addField( "alphaFadeInverse", TypeBool, Offset(mInvertAlphaFade, TSStatic), "Invert Alpha Fade's Start & End Distance" ); - endGroup( "AlphaFade" ); - - addGroup("Debug"); - - addField( "renderNormals", TypeF32, Offset( mRenderNormalScalar, TSStatic ), - "Debug rendering mode shows the normals for each point in the TSStatic's mesh." ); - addField( "forceDetail", TypeS32, Offset( mForceDetail, TSStatic ), - "Forces rendering to a particular detail level." ); - - endGroup("Debug"); - - Parent::initPersistFields(); -} - -bool TSStatic::_setFieldSkin( void *object, const char *index, const char *data ) -{ - TSStatic *ts = static_cast( object ); - if ( ts ) - ts->setSkinName( data ); - return false; -} - -const char *TSStatic::_getFieldSkin( void *object, const char *data ) -{ - TSStatic *ts = static_cast( object ); - return ts ? ts->mSkinNameHandle.getString() : ""; -} - -void TSStatic::inspectPostApply() -{ - // Apply any transformations set in the editor - Parent::inspectPostApply(); - - if(isServerObject()) - { - setMaskBits(AdvancedStaticOptionsMask); - prepCollision(); - } - - _updateShouldTick(); -} - -bool TSStatic::onAdd() -{ - PROFILE_SCOPE(TSStatic_onAdd); - - if ( isServerObject() ) - { - // Handle the old "usePolysoup" field - SimFieldDictionary* fieldDict = getFieldDictionary(); - - if ( fieldDict ) - { - StringTableEntry slotName = StringTable->insert( "usePolysoup" ); - - SimFieldDictionary::Entry * entry = fieldDict->findDynamicField( slotName ); - - if ( entry ) - { - // Was "usePolysoup" set? - bool usePolysoup = dAtob( entry->value ); - - // "usePolysoup" maps to the new VisibleMesh type - if ( usePolysoup ) - mCollisionType = VisibleMesh; - - // Remove the field in favor on the new "collisionType" field - fieldDict->setFieldValue( slotName, "" ); - } - } - } - - if ( !Parent::onAdd() ) - return false; - - // Setup the shape. - if ( !_createShape() ) - { - Con::errorf( "TSStatic::onAdd() - Shape creation failed!" ); - return false; - } - - setRenderTransform(mObjToWorld); - - // Register for the resource change signal. - ResourceManager::get().getChangedSignal().notify( this, &TSStatic::_onResourceChanged ); - - addToScene(); - - if ( isClientObject() ) - { - mCubeReflector.unregisterReflector(); - - if ( reflectorDesc ) - mCubeReflector.registerReflector( this, reflectorDesc ); - } - - _updateShouldTick(); - - // Accumulation and environment mapping - if (isClientObject() && mShapeInstance) - { - AccumulationVolume::addObject(this); - } - - return true; -} - -bool TSStatic::_createShape() -{ - // Cleanup before we create. - mCollisionDetails.clear(); - mLOSDetails.clear(); - SAFE_DELETE( mPhysicsRep ); - SAFE_DELETE( mShapeInstance ); - mAmbientThread = NULL; - mShape = NULL; - - if (!mShapeName || mShapeName[0] == '\0') - { - Con::errorf( "TSStatic::_createShape() - No shape name!" ); - return false; - } - - mShapeHash = _StringTable::hashString(mShapeName); - - mShape = ResourceManager::get().load(mShapeName); - if ( bool(mShape) == false ) - { - Con::errorf( "TSStatic::_createShape() - Unable to load shape: %s", mShapeName ); - return false; - } - - if ( isClientObject() && - !mShape->preloadMaterialList(mShape.getPath()) && - NetConnection::filesWereDownloaded() ) - return false; - - mObjBox = mShape->bounds; - resetWorldBox(); - - mShapeInstance = new TSShapeInstance( mShape, isClientObject() ); - - if( isGhost() ) - { - // Reapply the current skin - mAppliedSkinName = ""; - reSkin(); - } - - prepCollision(); - - // Find the "ambient" animation if it exists - S32 ambientSeq = mShape->findSequence("ambient"); - - if ( ambientSeq > -1 && !mAmbientThread ) - mAmbientThread = mShapeInstance->addThread(); - - if ( mAmbientThread ) - mShapeInstance->setSequence( mAmbientThread, ambientSeq, 0); - - // Resolve CubeReflectorDesc. - if ( cubeDescName.isNotEmpty() ) - { - Sim::findObject( cubeDescName, reflectorDesc ); - } - else if( cubeDescId > 0 ) - { - Sim::findObject( cubeDescId, reflectorDesc ); - } - - return true; -} - -void TSStatic::prepCollision() -{ - // Let the client know that the collision was updated - setMaskBits( UpdateCollisionMask ); - - // Allow the ShapeInstance to prep its collision if it hasn't already - if ( mShapeInstance ) - mShapeInstance->prepCollision(); - - // Cleanup any old collision data - mCollisionDetails.clear(); - mLOSDetails.clear(); - mConvexList->nukeList(); - - if ( mCollisionType == CollisionMesh || mCollisionType == VisibleMesh ) - mShape->findColDetails( mCollisionType == VisibleMesh, &mCollisionDetails, &mLOSDetails ); - - _updatePhysics(); -} - -void TSStatic::_updatePhysics() -{ - SAFE_DELETE( mPhysicsRep ); - - if ( !PHYSICSMGR || mCollisionType == None ) - return; - - PhysicsCollision *colShape = NULL; - if ( mCollisionType == Bounds ) - { - MatrixF offset( true ); - offset.setPosition( mShape->center ); - colShape = PHYSICSMGR->createCollision(); - colShape->addBox( getObjBox().getExtents() * 0.5f * mObjScale, offset ); - } - else - colShape = mShape->buildColShape( mCollisionType == VisibleMesh, getScale() ); - - if ( colShape ) - { - PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" ); - mPhysicsRep = PHYSICSMGR->createBody(); - mPhysicsRep->init( colShape, 0, 0, this, world ); - mPhysicsRep->setTransform( getTransform() ); - } -} - -void TSStatic::onRemove() -{ - SAFE_DELETE( mPhysicsRep ); - - // Accumulation - if ( isClientObject() && mShapeInstance ) - { - if ( mShapeInstance->hasAccumulation() ) - AccumulationVolume::removeObject(this); - } - - mConvexList->nukeList(); - - removeFromScene(); - - // Remove the resource change signal. - ResourceManager::get().getChangedSignal().remove( this, &TSStatic::_onResourceChanged ); - - delete mShapeInstance; - mShapeInstance = NULL; - - mAmbientThread = NULL; - if ( isClientObject() ) - mCubeReflector.unregisterReflector(); - - Parent::onRemove(); -} - -void TSStatic::_onResourceChanged( const Torque::Path &path ) -{ - if ( path != Path( mShapeName ) ) - return; - - _createShape(); - _updateShouldTick(); -} - -void TSStatic::setSkinName( const char *name ) -{ - if ( !isGhost() ) - { - if ( name[0] != '\0' ) - { - // Use tags for better network performance - // Should be a tag, but we'll convert to one if it isn't. - if ( name[0] == StringTagPrefixByte ) - mSkinNameHandle = NetStringHandle( U32(dAtoi(name + 1)) ); - else - mSkinNameHandle = NetStringHandle( name ); - } - else - mSkinNameHandle = NetStringHandle(); - - setMaskBits( SkinMask ); - } -} - -void TSStatic::reSkin() -{ - if ( isGhost() && mShapeInstance && mSkinNameHandle.isValidString() ) - { - Vector skins; - String(mSkinNameHandle.getString()).split( ";", skins ); - - for (S32 i = 0; i < skins.size(); i++) - { - String oldSkin( mAppliedSkinName.c_str() ); - String newSkin( skins[i] ); - - // Check if the skin handle contains an explicit "old" base string. This - // allows all models to support skinning, even if they don't follow the - // "base_xxx" material naming convention. - S32 split = newSkin.find( '=' ); // "old=new" format skin? - if ( split != String::NPos ) - { - oldSkin = newSkin.substr( 0, split ); - newSkin = newSkin.erase( 0, split+1 ); - } - - mShapeInstance->reSkin( newSkin, oldSkin ); - mAppliedSkinName = newSkin; - } - } -} - -void TSStatic::processTick( const Move *move ) -{ - if ( isServerObject() && mPlayAmbient && mAmbientThread ) - mShapeInstance->advanceTime( TickSec, mAmbientThread ); - - if ( isMounted() ) - { - MatrixF mat( true ); - mMount.object->getMountTransform(mMount.node, mMount.xfm, &mat ); - setTransform( mat ); - } -} - -void TSStatic::interpolateTick( F32 delta ) -{ -} - -void TSStatic::advanceTime( F32 dt ) -{ - if ( mPlayAmbient && mAmbientThread ) - mShapeInstance->advanceTime( dt, mAmbientThread ); - - if ( isMounted() ) - { - MatrixF mat( true ); - mMount.object->getRenderMountTransform( dt, mMount.node, mMount.xfm, &mat ); - setRenderTransform( mat ); - } -} - -void TSStatic::_updateShouldTick() -{ - bool shouldTick = (mPlayAmbient && mAmbientThread) || isMounted(); - - if ( isTicking() != shouldTick ) - setProcessTick( shouldTick ); -} - -void TSStatic::prepRenderImage( SceneRenderState* state ) -{ - if( !mShapeInstance ) - return; - - Point3F cameraOffset; - getRenderTransform().getColumn(3,&cameraOffset); - cameraOffset -= state->getDiffuseCameraPosition(); - F32 dist = cameraOffset.len(); - if (dist < 0.01f) - dist = 0.01f; - - if (mUseAlphaFade) - { - mAlphaFade = 1.0f; - if ((mAlphaFadeStart < mAlphaFadeEnd) && mAlphaFadeStart > 0.1f) - { - if (mInvertAlphaFade) - { - if (dist <= mAlphaFadeStart) - { - return; - } - if (dist < mAlphaFadeEnd) - { - mAlphaFade = ((dist - mAlphaFadeStart) / (mAlphaFadeEnd - mAlphaFadeStart)); - } - } - else - { - if (dist >= mAlphaFadeEnd) - { - return; - } - if (dist > mAlphaFadeStart) - { - mAlphaFade -= ((dist - mAlphaFadeStart) / (mAlphaFadeEnd - mAlphaFadeStart)); - } - } - } - } - - F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z)); - - // If we're currently rendering our own reflection we - // don't want to render ourselves into it. - if ( mCubeReflector.isRendering() ) - return; - - - if ( mForceDetail == -1 ) - mShapeInstance->setDetailFromDistance( state, dist * invScale ); - else - mShapeInstance->setCurrentDetail( mForceDetail ); - - if ( mShapeInstance->getCurrentDetail() < 0 ) - return; - - GFXTransformSaver saver; - - // Set up our TS render state. - TSRenderState rdata; - rdata.setSceneState( state ); - rdata.setFadeOverride( 1.0f ); - rdata.setOriginSort( mUseOriginSort ); - - if ( mCubeReflector.isEnabled() ) - rdata.setCubemap( mCubeReflector.getCubemap() ); - - // Acculumation - rdata.setAccuTex(mAccuTex); - - // If we have submesh culling enabled then prepare - // the object space frustum to pass to the shape. - Frustum culler; - if ( mMeshCulling ) - { - culler = state->getCullingFrustum(); - MatrixF xfm( true ); - xfm.scale( Point3F::One / getScale() ); - xfm.mul( getRenderWorldTransform() ); - xfm.mul( culler.getTransform() ); - culler.setTransform( xfm ); - rdata.setCuller( &culler ); - } - - // We might have some forward lit materials - // so pass down a query to gather lights. - LightQuery query; - query.init( getWorldSphere() ); - rdata.setLightQuery( &query ); - - MatrixF mat = getRenderTransform(); - mat.scale( mObjScale ); - GFX->setWorldMatrix( mat ); - - if ( state->isDiffusePass() && mCubeReflector.isEnabled() && mCubeReflector.getOcclusionQuery() ) - { - RenderPassManager *pass = state->getRenderPass(); - OccluderRenderInst *ri = pass->allocInst(); - - ri->type = RenderPassManager::RIT_Occluder; - ri->query = mCubeReflector.getOcclusionQuery(); - mObjToWorld.mulP( mObjBox.getCenter(), &ri->position ); - ri->scale.set( mObjBox.getExtents() ); - ri->orientation = pass->allocUniqueXform( mObjToWorld ); - ri->isSphere = false; - state->getRenderPass()->addInst( ri ); - } - - mShapeInstance->animate(); - if(mShapeInstance) - { - if (mUseAlphaFade) - { - mShapeInstance->setAlphaAlways(mAlphaFade); - S32 s = mShapeInstance->mMeshObjects.size(); - - for(S32 x = 0; x < s; x++) - { - mShapeInstance->mMeshObjects[x].visible = mAlphaFade; - } - } - } - mShapeInstance->render( rdata ); - - if ( mRenderNormalScalar > 0 ) - { - ObjectRenderInst *ri = state->getRenderPass()->allocInst(); - ri->renderDelegate.bind( this, &TSStatic::_renderNormals ); - ri->type = RenderPassManager::RIT_Editor; - state->getRenderPass()->addInst( ri ); - } -} - -void TSStatic::_renderNormals( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat ) -{ - PROFILE_SCOPE( TSStatic_RenderNormals ); - - GFXTransformSaver saver; - - MatrixF mat = getRenderTransform(); - mat.scale( mObjScale ); - GFX->multWorld( mat ); - - S32 dl = mShapeInstance->getCurrentDetail(); - mShapeInstance->renderDebugNormals( mRenderNormalScalar, dl ); -} - -void TSStatic::onScaleChanged() -{ - Parent::onScaleChanged(); - - if ( mPhysicsRep ) - { - // If the editor is enabled delay the scale operation - // by a few milliseconds so that we're not rebuilding - // during an active scale drag operation. - if ( gEditingMission ) - mPhysicsRep->queueCallback( 500, Delegate( this, &TSStatic::_updatePhysics ) ); - else - _updatePhysics(); - } - - setMaskBits( ScaleMask ); -} - -void TSStatic::setTransform(const MatrixF & mat) -{ - Parent::setTransform(mat); - if ( !isMounted() ) - setMaskBits( TransformMask ); - - if ( mPhysicsRep ) - mPhysicsRep->setTransform( mat ); - - // Accumulation - if ( isClientObject() && mShapeInstance ) - { - if ( mShapeInstance->hasAccumulation() ) - AccumulationVolume::updateObject(this); - } - - // Since this is a static it's render transform changes 1 - // to 1 with it's collision transform... no interpolation. - setRenderTransform(mat); -} - -U32 TSStatic::packUpdate(NetConnection *con, U32 mask, BitStream *stream) -{ - U32 retMask = Parent::packUpdate(con, mask, stream); - - if ( stream->writeFlag( mask & TransformMask ) ) - mathWrite( *stream, getTransform() ); - - if ( stream->writeFlag( mask & ScaleMask ) ) - { - // Only write one bit if the scale is one. - if ( stream->writeFlag( mObjScale != Point3F::One ) ) - mathWrite( *stream, mObjScale ); - } - - if ( stream->writeFlag( mask & UpdateCollisionMask ) ) - stream->write( (U32)mCollisionType ); - - if ( stream->writeFlag( mask & SkinMask ) ) - con->packNetStringHandleU( stream, mSkinNameHandle ); - - if (stream->writeFlag(mask & AdvancedStaticOptionsMask)) - { - stream->writeString(mShapeName); - stream->write((U32)mDecalType); - - stream->writeFlag(mAllowPlayerStep); - stream->writeFlag(mMeshCulling); - stream->writeFlag(mUseOriginSort); - - stream->write(mRenderNormalScalar); - - stream->write(mForceDetail); - - stream->writeFlag(mPlayAmbient); - } - - if ( stream->writeFlag(mUseAlphaFade) ) - { - stream->write(mAlphaFadeStart); - stream->write(mAlphaFadeEnd); - stream->write(mInvertAlphaFade); - } - - if ( mLightPlugin ) - retMask |= mLightPlugin->packUpdate(this, AdvancedStaticOptionsMask, con, mask, stream); - - if( stream->writeFlag( reflectorDesc != NULL ) ) - { - stream->writeRangedU32( reflectorDesc->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast ); - } - return retMask; -} - -void TSStatic::unpackUpdate(NetConnection *con, BitStream *stream) -{ - Parent::unpackUpdate(con, stream); - - if ( stream->readFlag() ) // TransformMask - { - MatrixF mat; - mathRead( *stream, &mat ); - setTransform(mat); - setRenderTransform(mat); - } - - if ( stream->readFlag() ) // ScaleMask - { - if ( stream->readFlag() ) - { - VectorF scale; - mathRead( *stream, &scale ); - setScale( scale ); - } - else - setScale( Point3F::One ); - } - - if ( stream->readFlag() ) // UpdateCollisionMask - { - U32 collisionType = CollisionMesh; - - stream->read( &collisionType ); - - // Handle it if we have changed CollisionType's - if ( (MeshType)collisionType != mCollisionType ) - { - mCollisionType = (MeshType)collisionType; - - if ( isProperlyAdded() && mShapeInstance ) - prepCollision(); - } - } - - if (stream->readFlag()) // SkinMask - { - NetStringHandle skinDesiredNameHandle = con->unpackNetStringHandleU(stream);; - if (mSkinNameHandle != skinDesiredNameHandle) - { - mSkinNameHandle = skinDesiredNameHandle; - reSkin(); - } - } - - if (stream->readFlag()) // AdvancedStaticOptionsMask - { - mShapeName = stream->readSTString(); - - stream->read((U32*)&mDecalType); - - mAllowPlayerStep = stream->readFlag(); - mMeshCulling = stream->readFlag(); - mUseOriginSort = stream->readFlag(); - - stream->read(&mRenderNormalScalar); - - stream->read(&mForceDetail); - mPlayAmbient = stream->readFlag(); - } - - mUseAlphaFade = stream->readFlag(); - if (mUseAlphaFade) - { - stream->read(&mAlphaFadeStart); - stream->read(&mAlphaFadeEnd); - stream->read(&mInvertAlphaFade); - } - - if ( mLightPlugin ) - { - mLightPlugin->unpackUpdate(this, con, stream); - } - - if( stream->readFlag() ) - { - cubeDescId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast ); - } - - if ( isProperlyAdded() ) - _updateShouldTick(); -} - -//---------------------------------------------------------------------------- -bool TSStatic::castRay(const Point3F &start, const Point3F &end, RayInfo* info) -{ - if ( mCollisionType == None ) - return false; - - if ( !mShapeInstance ) - return false; - - if ( mCollisionType == Bounds ) - { - F32 fst; - if (!mObjBox.collideLine(start, end, &fst, &info->normal)) - return false; - - info->t = fst; - info->object = this; - info->point.interpolate( start, end, fst ); - info->material = NULL; - return true; - } - else - { - RayInfo shortest = *info; - RayInfo localInfo; - shortest.t = 1e8f; - localInfo.generateTexCoord = info->generateTexCoord; - - for ( U32 i = 0; i < mLOSDetails.size(); i++ ) - { - mShapeInstance->animate( mLOSDetails[i] ); - - if ( mShapeInstance->castRayOpcode( mLOSDetails[i], start, end, &localInfo ) ) - { - localInfo.object = this; - - if (localInfo.t < shortest.t) - shortest = localInfo; - } - } - - if (shortest.object == this) - { - // Copy out the shortest time... - *info = shortest; - return true; - } - } - - return false; -} - -bool TSStatic::castRayRendered(const Point3F &start, const Point3F &end, RayInfo *info) -{ - if ( !mShapeInstance ) - return false; - - // Cast the ray against the currently visible detail - RayInfo localInfo; - bool res = mShapeInstance->castRayOpcode( mShapeInstance->getCurrentDetail(), start, end, &localInfo ); - - if ( res ) - { - *info = localInfo; - info->object = this; - return true; - } - - return false; -} - -bool TSStatic::buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &) -{ - if ( !mShapeInstance ) - return false; - - // This is safe to set even if we're not outputing - polyList->setTransform( &mObjToWorld, mObjScale ); - polyList->setObject( this ); - - if ( context == PLC_Export ) - { - // Use highest detail level - S32 dl = 0; - - // Try to call on the client so we can export materials - if ( isServerObject() && getClientObject() ) - dynamic_cast(getClientObject())->mShapeInstance->buildPolyList( polyList, dl ); - else - mShapeInstance->buildPolyList( polyList, dl ); - } - else if ( context == PLC_Selection ) - { - // Use the last rendered detail level - S32 dl = mShapeInstance->getCurrentDetail(); - mShapeInstance->buildPolyListOpcode( dl, polyList, box ); - } - else - { - // Figure out the mesh type we're looking for. - MeshType meshType = ( context == PLC_Decal ) ? mDecalType : mCollisionType; - - if ( meshType == None ) - return false; - else if ( meshType == Bounds ) - polyList->addBox( mObjBox ); - else if ( meshType == VisibleMesh ) - mShapeInstance->buildPolyList( polyList, 0 ); - else - { - // Everything else is done from the collision meshes - // which may be built from either the visual mesh or - // special collision geometry. - for ( U32 i = 0; i < mCollisionDetails.size(); i++ ) - mShapeInstance->buildPolyListOpcode( mCollisionDetails[i], polyList, box ); - } - } - - return true; -} - -void TSStatic::buildConvex(const Box3F& box, Convex* convex) -{ - if ( mCollisionType == None ) - return; - - if ( mShapeInstance == NULL ) - return; - - // These should really come out of a pool - mConvexList->collectGarbage(); - - if ( mCollisionType == Bounds ) - { - // Just return a box convex for the entire shape... - Convex* cc = 0; - CollisionWorkingList& wl = convex->getWorkingList(); - for (CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext) - { - if (itr->mConvex->getType() == BoxConvexType && - itr->mConvex->getObject() == this) - { - cc = itr->mConvex; - break; - } - } - if (cc) - return; - - // Create a new convex. - BoxConvex* cp = new BoxConvex; - mConvexList->registerObject(cp); - convex->addToWorkingList(cp); - cp->init(this); - - mObjBox.getCenter(&cp->mCenter); - cp->mSize.x = mObjBox.len_x() / 2.0f; - cp->mSize.y = mObjBox.len_y() / 2.0f; - cp->mSize.z = mObjBox.len_z() / 2.0f; - } - else // CollisionMesh || VisibleMesh - { - TSStaticPolysoupConvex::smCurObject = this; - - for (U32 i = 0; i < mCollisionDetails.size(); i++) - mShapeInstance->buildConvexOpcode( mObjToWorld, mObjScale, mCollisionDetails[i], box, convex, mConvexList ); - - TSStaticPolysoupConvex::smCurObject = NULL; - } -} - -SceneObject* TSStaticPolysoupConvex::smCurObject = NULL; - -TSStaticPolysoupConvex::TSStaticPolysoupConvex() -: box( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ), - normal( 0.0f, 0.0f, 0.0f, 0.0f ), - idx( 0 ), - mesh( NULL ) -{ - mType = TSPolysoupConvexType; - - for ( U32 i = 0; i < 4; ++i ) - { - verts[i].set( 0.0f, 0.0f, 0.0f ); - } -} - -Point3F TSStaticPolysoupConvex::support(const VectorF& vec) const -{ - F32 bestDot = mDot( verts[0], vec ); - - const Point3F *bestP = &verts[0]; - for(S32 i=1; i<4; i++) - { - F32 newD = mDot(verts[i], vec); - if(newD > bestDot) - { - bestDot = newD; - bestP = &verts[i]; - } - } - - return *bestP; -} - -Box3F TSStaticPolysoupConvex::getBoundingBox() const -{ - Box3F wbox = box; - wbox.minExtents.convolve( mObject->getScale() ); - wbox.maxExtents.convolve( mObject->getScale() ); - mObject->getTransform().mul(wbox); - return wbox; -} - -Box3F TSStaticPolysoupConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const -{ - AssertISV(false, "TSStaticPolysoupConvex::getBoundingBox(m,p) - Not implemented. -- XEA"); - return box; -} - -void TSStaticPolysoupConvex::getPolyList(AbstractPolyList *list) -{ - // Transform the list into object space and set the pointer to the object - MatrixF i( mObject->getTransform() ); - Point3F iS( mObject->getScale() ); - list->setTransform(&i, iS); - list->setObject(mObject); - - // Add only the original collision triangle - S32 base = list->addPoint(verts[0]); - list->addPoint(verts[2]); - list->addPoint(verts[1]); - - list->begin(0, (U32)idx ^ (uintptr_t)mesh); - list->vertex(base + 2); - list->vertex(base + 1); - list->vertex(base + 0); - list->plane(base + 0, base + 1, base + 2); - list->end(); -} - -void TSStaticPolysoupConvex::getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf) -{ - cf->material = 0; - cf->object = mObject; - - // For a tetrahedron this is pretty easy... first - // convert everything into world space. - Point3F tverts[4]; - mat.mulP(verts[0], &tverts[0]); - mat.mulP(verts[1], &tverts[1]); - mat.mulP(verts[2], &tverts[2]); - mat.mulP(verts[3], &tverts[3]); - - // points... - S32 firstVert = cf->mVertexList.size(); - cf->mVertexList.increment(); cf->mVertexList.last() = tverts[0]; - cf->mVertexList.increment(); cf->mVertexList.last() = tverts[1]; - cf->mVertexList.increment(); cf->mVertexList.last() = tverts[2]; - cf->mVertexList.increment(); cf->mVertexList.last() = tverts[3]; - - // edges... - cf->mEdgeList.increment(); - cf->mEdgeList.last().vertex[0] = firstVert+0; - cf->mEdgeList.last().vertex[1] = firstVert+1; - - cf->mEdgeList.increment(); - cf->mEdgeList.last().vertex[0] = firstVert+1; - cf->mEdgeList.last().vertex[1] = firstVert+2; - - cf->mEdgeList.increment(); - cf->mEdgeList.last().vertex[0] = firstVert+2; - cf->mEdgeList.last().vertex[1] = firstVert+0; - - cf->mEdgeList.increment(); - cf->mEdgeList.last().vertex[0] = firstVert+3; - cf->mEdgeList.last().vertex[1] = firstVert+0; - - cf->mEdgeList.increment(); - cf->mEdgeList.last().vertex[0] = firstVert+3; - cf->mEdgeList.last().vertex[1] = firstVert+1; - - cf->mEdgeList.increment(); - cf->mEdgeList.last().vertex[0] = firstVert+3; - cf->mEdgeList.last().vertex[1] = firstVert+2; - - // triangles... - cf->mFaceList.increment(); - cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[0]); - cf->mFaceList.last().vertex[0] = firstVert+2; - cf->mFaceList.last().vertex[1] = firstVert+1; - cf->mFaceList.last().vertex[2] = firstVert+0; - - cf->mFaceList.increment(); - cf->mFaceList.last().normal = PlaneF(tverts[1], tverts[0], tverts[3]); - cf->mFaceList.last().vertex[0] = firstVert+1; - cf->mFaceList.last().vertex[1] = firstVert+0; - cf->mFaceList.last().vertex[2] = firstVert+3; - - cf->mFaceList.increment(); - cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[3]); - cf->mFaceList.last().vertex[0] = firstVert+2; - cf->mFaceList.last().vertex[1] = firstVert+1; - cf->mFaceList.last().vertex[2] = firstVert+3; - - cf->mFaceList.increment(); - cf->mFaceList.last().normal = PlaneF(tverts[0], tverts[2], tverts[3]); - cf->mFaceList.last().vertex[0] = firstVert+0; - cf->mFaceList.last().vertex[1] = firstVert+2; - cf->mFaceList.last().vertex[2] = firstVert+3; - - // All done! -} - -void TSStatic::onMount( SceneObject *obj, S32 node ) -{ - Parent::onMount(obj, node); - _updateShouldTick(); -} - -void TSStatic::onUnmount( SceneObject *obj, S32 node ) -{ - Parent::onUnmount( obj, node ); - setMaskBits( TransformMask ); - _updateShouldTick(); -} - -//------------------------------------------------------------------------ -//These functions are duplicated in tsStatic and shapeBase. -//They each function a little differently; but achieve the same purpose of gathering -//target names/counts without polluting simObject. - -DefineEngineMethod( TSStatic, getTargetName, const char*, ( S32 index ),(0), - "Get the name of the indexed shape material.\n" - "@param index index of the material to get (valid range is 0 - getTargetCount()-1).\n" - "@return the name of the indexed material.\n" - "@see getTargetCount()\n") -{ - TSStatic *obj = dynamic_cast< TSStatic* > ( object ); - if(obj) - { - // Try to use the client object (so we get the reskinned targets in the Material Editor) - if ((TSStatic*)obj->getClientObject()) - obj = (TSStatic*)obj->getClientObject(); - - return obj->getShapeInstance()->getTargetName(index); - } - - return ""; -} - -DefineEngineMethod( TSStatic, getTargetCount, S32,(),, - "Get the number of materials in the shape.\n" - "@return the number of materials in the shape.\n" - "@see getTargetName()\n") -{ - TSStatic *obj = dynamic_cast< TSStatic* > ( object ); - if(obj) - { - // Try to use the client object (so we get the reskinned targets in the Material Editor) - if ((TSStatic*)obj->getClientObject()) - obj = (TSStatic*)obj->getClientObject(); - - return obj->getShapeInstance()->getTargetCount(); - } - - return -1; -} - -// This method is able to change materials per map to with others. The material that is being replaced is being mapped to -// unmapped_mat as a part of this transition - -DefineEngineMethod( TSStatic, changeMaterial, void, ( const char* mapTo, Material* oldMat, Material* newMat ),("",nullAsType(),nullAsType()), - "@brief Change one of the materials on the shape.\n\n" - - "This method changes materials per mapTo with others. The material that " - "is being replaced is mapped to unmapped_mat as a part of this transition.\n" - - "@note Warning, right now this only sort of works. It doesn't do a live " - "update like it should.\n" - - "@param mapTo the name of the material target to remap (from getTargetName)\n" - "@param oldMat the old Material that was mapped \n" - "@param newMat the new Material to map\n\n" - - "@tsexample\n" - "// remap the first material in the shape\n" - "%mapTo = %obj.getTargetName( 0 );\n" - "%obj.changeMaterial( %mapTo, 0, MyMaterial );\n" - "@endtsexample\n" ) -{ - // if no valid new material, theres no reason for doing this - if( !newMat ) - { - Con::errorf("TSShape::changeMaterial failed: New material does not exist!"); - return; - } - - TSMaterialList* shapeMaterialList = object->getShape()->materialList; - - // Check the mapTo name exists for this shape - S32 matIndex = shapeMaterialList->getMaterialNameList().find_next(String(mapTo)); - if (matIndex < 0) - { - Con::errorf("TSShape::changeMaterial failed: Invalid mapTo name '%s'", mapTo); - return; - } - - // Lets remap the old material off, so as to let room for our current material room to claim its spot - if( oldMat ) - oldMat->mMapTo = String("unmapped_mat"); - - newMat->mMapTo = mapTo; - - // Map the material by name in the matmgr - MATMGR->mapMaterial( mapTo, newMat->getName() ); - - // Replace instances with the new material being traded in. Lets make sure that we only - // target the specific targets per inst, this is actually doing more than we thought - delete shapeMaterialList->mMatInstList[matIndex]; - shapeMaterialList->mMatInstList[matIndex] = newMat->createMatInstance(); - - // Finish up preparing the material instances for rendering - const GFXVertexFormat *flags = getGFXVertexFormat(); - FeatureSet features = MATMGR->getDefaultFeatures(); - shapeMaterialList->getMaterialInst(matIndex)->init(features, flags); -} - -DefineEngineMethod( TSStatic, getModelFile, const char *, (),, - "@brief Get the model filename used by this shape.\n\n" - - "@return the shape filename\n\n" - "@tsexample\n" - "// Acquire the model filename used on this shape.\n" - "%modelFilename = %obj.getModelFile();\n" - "@endtsexample\n" - ) -{ -<<<<<<< HEAD - return object->getShapeFileName(); -======= - return object->getShapeFileName(); ->>>>>>> garagegames/development -} diff --git a/Engine/source/T3D/tsStatic.h b/Engine/source/T3D/tsStatic.h index db1ff138c..224086c71 100644 --- a/Engine/source/T3D/tsStatic.h +++ b/Engine/source/T3D/tsStatic.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _TSSTATIC_H_ #define _TSSTATIC_H_ @@ -236,6 +241,20 @@ public: const Vector& getLOSDetails() const { return mLOSDetails; } +private: + virtual void onStaticModified(const char* slotName, const char*newValue = NULL); +protected: + Vector mDecalDetails; + Vector* mDecalDetailsPtr; +public: + bool mIgnoreZodiacs; + bool mHasGradients; + bool mInvertGradientRange; + Point2F mGradientRangeUser; + Point2F mGradientRange; +private: + void set_special_typing(); + virtual void setSelectionFlags(U8 flags); }; typedef TSStatic::MeshType TSMeshType; diff --git a/Engine/source/T3D/turret/aiTurretShape.h b/Engine/source/T3D/turret/aiTurretShape.h index 4aa13ba44..138389691 100644 --- a/Engine/source/T3D/turret/aiTurretShape.h +++ b/Engine/source/T3D/turret/aiTurretShape.h @@ -151,16 +151,36 @@ public: //---------------------------------------------------------------------------- +// As shipped, AITurretShape plus the chain of classes it inherits from, consumes +// all 32 mask-bits. AFX uses one additional mask-bit in GameBase, which pushes +// AITurretShape over the mask-bit limit which will cause runtime crashes. As +// a workaround, AFX modifies AITurretShape so that it reuses the TurretUpdateMask +// defined by TurretShape rather than adding a unique TurretStateMask. This will +// make AITurretShape's network updates slightly less efficient, but should be +// acceptable for most uses of AITurretShape. If you plan to populate your levels +// with many AITurretShape objects, consider restoring it to use of a unique +// bit-mask, but if you do that, you will have to eliminate at use of at least one +// bit by one of it's parent classes. (FYI ShapeBase uses 20 bits.) +// +// Comment out this define if you want AITurretShape to define it's own bit-mask. +#define AFX_REUSE_TURRETSHAPE_MASKBITS class AITurretShape: public TurretShape { typedef TurretShape Parent; protected: +#ifdef AFX_REUSE_TURRETSHAPE_MASKBITS + enum MaskBits { + TurretStateMask = Parent::TurretUpdateMask, + NextFreeMask = Parent::NextFreeMask + }; +#else // ORIGINAL CODE enum MaskBits { TurretStateMask = Parent::NextFreeMask, NextFreeMask = Parent::NextFreeMask << 1 }; +#endif struct TargetInfo { diff --git a/Engine/source/afx/afxCamera.cpp b/Engine/source/afx/afxCamera.cpp new file mode 100644 index 000000000..0a914ad11 --- /dev/null +++ b/Engine/source/afx/afxCamera.cpp @@ -0,0 +1,1189 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// afxCamera implements a modified camera for demonstrating a third person camera style +// which is more common to RPG games than the standard FPS style camera. For the most part, +// it is a hybrid of the standard TGE camera and the third person mode of the Advanced Camera +// resource, authored by Thomas "Man of Ice" Lund. This camera implements the bare minimum +// required for demonstrating an RPG style camera and leaves tons of room for improvement. +// It should be replaced with a better camera if possible. +// +// Advanced Camera Resource by Thomas "Man of Ice" Lund: +// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5471 +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathUtils.h" +#include "math/mathIO.h" +#include "T3D/gameBase/gameConnection.h" +#include "T3D/camera.h" +#include "T3D/player.h" +#include "T3D/sfx/sfx3DWorld.h" + +#include "afx/afxCamera.h" + +#define MaxPitch 1.3962f +#define CameraRadius 0.05f; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxCameraData + +IMPLEMENT_CO_DATABLOCK_V1(afxCameraData); + +ConsoleDocClass( afxCameraData, + "@brief A datablock that describes an afxCamera.\n\n" + + "@ingroup afxMisc\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +U32 afxCameraData::sCameraCollisionMask = TerrainObjectType | InteriorLikeObjectType | TerrainLikeObjectType; + +void afxCameraData::initPersistFields() +{ + Con::addVariable("pref::afxCamera::collisionMask", TypeS32, &sCameraCollisionMask); + + Parent::initPersistFields(); +} + +void afxCameraData::packData(BitStream* stream) +{ + Parent::packData(stream); +} + +void afxCameraData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxCamera + +IMPLEMENT_CO_NETOBJECT_V1(afxCamera); + +ConsoleDocClass( afxCamera, + "@brief A 3rd person camera object.\n\n" + + "@ingroup afxMisc\n" + "@ingroup AFX\n" +); + +afxCamera::afxCamera() +{ + mNetFlags.clear(Ghostable); + mTypeMask |= CameraObjectType; + delta.pos = Point3F(0,0,100); + delta.rot = Point3F(0,0,0); + delta.posVec = delta.rotVec = VectorF(0,0,0); + mObjToWorld.setColumn(3,delta.pos); + mRot = delta.rot; + + mMinOrbitDist = 0; + mMaxOrbitDist = 0; + mCurOrbitDist = 0; + mOrbitObject = NULL; + mPosition.set(0.f, 0.f, 0.f); + mObservingClientObject = false; + mode = FlyMode; + + cam_subject = NULL; + coi_offset.set(0, 0, 2); + cam_offset.set(0, 0, 0); + cam_distance = 0.0f; + cam_angle = 0.0f; + cam_dirty = false; + + flymode_saved = false; + third_person_snap_s = 1; + third_person_snap_c = 1; + flymode_saved_pos.zero(); + + mDamageState = Disabled; +} + +afxCamera::~afxCamera() +{ +} + +//---------------------------------------------------------------------------- + +void afxCamera::cam_update(F32 dt, bool on_server) +{ + if (mode == ThirdPersonMode && cam_subject) + cam_update_3pov(dt, on_server); +} + +void afxCamera::set_cam_pos(const Point3F& pos,const Point3F& rot) +{ + MatrixF xRot, zRot; + xRot.set(EulerF(rot.x, 0, 0)); + zRot.set(EulerF(0, 0, rot.z)); + MatrixF temp; + temp.mul(zRot, xRot); + temp.setColumn(3, pos); + Parent::setTransform(temp); + mRot = rot; +} + + +//---------------------------------------------------------------------------- + + + +//---------------------------------------------------------------------------- + +Point3F &afxCamera::getPosition() +{ + static Point3F position; + mObjToWorld.getColumn(3, &position); + return position; +} + +//---------------------------------------------------------------------------- + + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +// NEW Observer Code +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- +void afxCamera::setFlyMode() +{ + mode = FlyMode; + if (flymode_saved) + snapToPosition(flymode_saved_pos); + + if (bool(mOrbitObject)) + { + clearProcessAfter(); + clearNotify(mOrbitObject); + } + mOrbitObject = NULL; +} + +void afxCamera::setOrbitMode(GameBase *obj, Point3F &pos, AngAxisF &rot, F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject) +{ + mObservingClientObject = ownClientObject; + + if(bool(mOrbitObject)) { + clearProcessAfter(); + clearNotify(mOrbitObject); + } + mOrbitObject = obj; + if(bool(mOrbitObject)) + { + processAfter(mOrbitObject); + deleteNotify(mOrbitObject); + mOrbitObject->getWorldBox().getCenter(&mPosition); + mode = OrbitObjectMode; + } + else + { + mode = OrbitPointMode; + mPosition = pos; + } + + QuatF q(rot); + MatrixF tempMat(true); + q.setMatrix(&tempMat); + Point3F dir; + tempMat.getColumn(1, &dir); + + set_cam_pos(mPosition, dir); + + mMinOrbitDist = minDist; + mMaxOrbitDist = maxDist; + mCurOrbitDist = curDist; +} + + +void afxCamera::validateEyePoint(F32 pos, MatrixF *mat) +{ + if (pos != 0) { + // Use the eye transform to orient the camera + Point3F dir; + mat->getColumn(1, &dir); + pos *= mMaxOrbitDist - mMinOrbitDist; + // Use the camera node's pos. + Point3F startPos; + Point3F endPos; + mObjToWorld.getColumn(3,&startPos); + + // Make sure we don't extend the camera into anything solid + if(mOrbitObject) + mOrbitObject->disableCollision(); + disableCollision(); + RayInfo collision; + + SceneContainer* pContainer = isServerObject() ? &gServerContainer : &gClientContainer; + if (!pContainer->castRay(startPos, startPos - dir * 2.5 * pos, afxCameraData::sCameraCollisionMask, &collision)) + endPos = startPos - dir * pos; + else + { + float dot = mDot(dir, collision.normal); + if(dot > 0.01) + { + float colDist = mDot(startPos - collision.point, dir) - (1 / dot) * CameraRadius; + if(colDist > pos) + colDist = pos; + if(colDist < 0) + colDist = 0; + endPos = startPos - dir * colDist; + } + else + endPos = startPos - dir * pos; + } + mat->setColumn(3,endPos); + enableCollision(); + if(mOrbitObject) + mOrbitObject->enableCollision(); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + + + + + +// Sets the position and calculates rotation +void afxCamera::snapToPosition(const Point3F& tPos) +{ + MatrixF transMat; + + if (cam_subject) + { + // get the subject's transform + MatrixF objToWorld = cam_subject->getRenderTransform(); + + // transform the center-of-interest to world-space + Point3F objPos; + objToWorld.mulP(coi_offset, &objPos); + + // find normalized direction vector looking from camera to coi + VectorF dirVec = objPos - tPos; + dirVec.normalize(); + + MathUtils::getAnglesFromVector(dirVec, mRot.z, mRot.x); + mRot.x = 0 - mRot.x; + + transMat = MathUtils::createOrientFromDir(dirVec); + } + else + { + transMat.identity(); + } + + transMat.setColumn(3, tPos); + Parent::setTransform(transMat); +} + +void afxCamera::setCameraSubject(SceneObject* new_subject) +{ + // cleanup any existing chase subject + if (cam_subject) + { + if (dynamic_cast(cam_subject)) + clearProcessAfter(); + clearNotify(cam_subject); + } + + cam_subject = new_subject; + + // set associations with new chase subject + if (cam_subject) + { + if (dynamic_cast(cam_subject)) + processAfter((GameBase*)cam_subject); + deleteNotify(cam_subject); + } + + mode = (cam_subject) ? ThirdPersonMode : FlyMode; + setMaskBits(SubjectMask); +} + +void afxCamera::setThirdPersonOffset(const Point3F& offset) +{ + // new method + if (cam_distance > 0.0f) + { + if (isClientObject()) + { + GameConnection* conn = GameConnection::getConnectionToServer(); + if (conn) + { + // this auto switches to/from first person + if (conn->isFirstPerson()) + { + if (cam_distance >= 1.0f) + conn->setFirstPerson(false); + } + else + { + if (cam_distance < 1.0f) + conn->setFirstPerson(true); + } + } + } + + cam_offset = offset; + cam_dirty = true; + + return; + } + + // old backwards-compatible method + if (offset.y != cam_offset.y && isClientObject()) + { + GameConnection* conn = GameConnection::getConnectionToServer(); + if (conn) + { + // this auto switches to/from first person + if (conn->isFirstPerson()) + { + if (offset.y <= -1.0f) + conn->setFirstPerson(false); + } + else + { + if (offset.y > -1.0f) + conn->setFirstPerson(true); + } + } + } + + cam_offset = offset; + cam_dirty = true; +} + +void afxCamera::setThirdPersonOffset(const Point3F& offset, const Point3F& coi_offset) +{ + this->coi_offset = coi_offset; + setThirdPersonOffset(offset); +} + +void afxCamera::setThirdPersonDistance(F32 distance) +{ + cam_distance = distance; + cam_dirty = true; +} + +F32 afxCamera::getThirdPersonDistance() +{ + return cam_distance; +} + +void afxCamera::setThirdPersonAngle(F32 angle) +{ + cam_angle = angle; + cam_dirty = true; +} + +F32 afxCamera::getThirdPersonAngle() +{ + return cam_angle; +} + +void afxCamera::setThirdPersonMode() +{ + mode = ThirdPersonMode; + flymode_saved_pos = getPosition(); + flymode_saved = true; + cam_dirty = true; + third_person_snap_s++; +} + +void afxCamera::setThirdPersonSnap() +{ + if (mode == ThirdPersonMode) + third_person_snap_s += 2; +} + +void afxCamera::setThirdPersonSnapClient() +{ + if (mode == ThirdPersonMode) + third_person_snap_c++; +} + +const char* afxCamera::getMode() +{ + switch (mode) + { + case ThirdPersonMode: + return "ThirdPerson"; + case FlyMode: + return "Fly"; + case OrbitObjectMode: + return "Orbit"; + } + + return "Unknown"; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Console Methods + +static char buffer[100]; + +ConsoleMethod(afxCamera, setOrbitMode, void, 7, 8, + "(GameBase orbitObject, transform mat, float minDistance, float maxDistance, float curDistance, bool ownClientObject)" + "Set the camera to orbit around some given object.\n\n" + "@param orbitObject Object we want to orbit.\n" + "@param mat A set of fields: posX posY posZ aaX aaY aaZ aaTheta\n" + "@param minDistance Minimum distance to keep from object.\n" + "@param maxDistance Maximum distance to keep from object.\n" + "@param curDistance Distance to set initially from object.\n" + "@param ownClientObj Are we observing an object owned by us?") +{ + Point3F pos; + AngAxisF aa; + F32 minDis, maxDis, curDis; + + GameBase *orbitObject = NULL; + if(Sim::findObject(argv[2],orbitObject) == false) + { + Con::warnf("Cannot orbit non-existing object."); + object->setFlyMode(); + return; + } + + dSscanf(argv[3],"%f %f %f %f %f %f %f", + &pos.x,&pos.y,&pos.z,&aa.axis.x,&aa.axis.y,&aa.axis.z,&aa.angle); + minDis = dAtof(argv[4]); + maxDis = dAtof(argv[5]); + curDis = dAtof(argv[6]); + + object->setOrbitMode(orbitObject, pos, aa, minDis, maxDis, curDis, (argc == 8) ? dAtob(argv[7]) : false); +} + +ConsoleMethod( afxCamera, setFlyMode, void, 2, 2, "()" "Set the camera to be able to fly freely.") +{ + object->setFlyMode(); +} + +ConsoleMethod( afxCamera, getPosition, const char *, 2, 2, "()" + "Get the position of the camera.\n\n" + "@returns A string of form \"x y z\".") +{ + Point3F& pos = object->getPosition(); + dSprintf(buffer, sizeof(buffer),"%f %f %f",pos.x,pos.y,pos.z); + return buffer; +} + +ConsoleMethod(afxCamera, setCameraSubject, bool, 3, 3, "") +{ + SceneObject* subject; + if (!Sim::findObject(argv[2], subject)) + { + Con::errorf("Camera subject \"%s\" not found.", argv[2].getStringValue()); + return false; + } + + object->setCameraSubject(subject); + + return true; +} + +ConsoleMethod(afxCamera, setThirdPersonDistance, bool, 3, 3, "") +{ + F32 distance; + dSscanf(argv[2], "%f", &distance); + + object->setThirdPersonDistance(distance); + + return true; +} + +ConsoleMethod(afxCamera, getThirdPersonDistance, F32, 2, 2, "") +{ + return object->getThirdPersonDistance(); +} + +ConsoleMethod(afxCamera, setThirdPersonAngle, bool, 3, 3, "") +{ + F32 angle; + dSscanf(argv[2], "%f", &angle); + + object->setThirdPersonAngle(angle); + + return true; +} + +ConsoleMethod(afxCamera, getThirdPersonAngle, F32, 2, 2, "") +{ + return object->getThirdPersonAngle(); +} + +ConsoleMethod(afxCamera, setThirdPersonOffset, void, 3, 4, "(Point3F offset [, Point3f coi_offset])") +{ + Point3F offset; + dSscanf(argv[2], "%f %f %f", &offset.x, &offset.y, &offset.z); + if (argc > 3) + { + Point3F coi_offset; + dSscanf(argv[3], "%f %f %f", &coi_offset.x, &coi_offset.y, &coi_offset.z); + object->setThirdPersonOffset(offset, coi_offset); + } + else + object->setThirdPersonOffset(offset); +} + +ConsoleMethod(afxCamera, getThirdPersonOffset, const char *, 2, 2, "()") +{ + const Point3F& pos = object->getThirdPersonOffset(); + dSprintf(buffer, sizeof(buffer),"%f %f %f",pos.x,pos.y,pos.z); + return buffer; +} + +ConsoleMethod(afxCamera, getThirdPersonCOIOffset, const char *, 2, 2, "()") +{ + const Point3F& pos = object->getThirdPersonCOIOffset(); + dSprintf(buffer, sizeof(buffer),"%f %f %f",pos.x,pos.y,pos.z); + return buffer; +} + +ConsoleMethod(afxCamera, setThirdPersonMode, void, 2, 2, "()") +{ + object->setThirdPersonMode(); +} + +ConsoleMethod(afxCamera, setThirdPersonSnap, void, 2, 2, "()") +{ + object->setThirdPersonSnap(); +} + +ConsoleMethod(afxCamera, getMode, const char *, 2, 2, "()") +{ + return object->getMode(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +// 3POV SECTION + +void afxCamera::cam_update_3pov(F32 dt, bool on_server) +{ + Point3F goal_pos; + Point3F curr_pos = getRenderPosition(); + MatrixF xfm = cam_subject->getRenderTransform(); + Point3F coi = cam_subject->getRenderPosition() + coi_offset; + + // for player subjects, pitch is adjusted + Player* player_subj = dynamic_cast(cam_subject); + if (player_subj) + { + if (cam_distance > 0.0f) + { + // rotate xfm by amount of cam_angle + F32 look_yaw = player_subj->getHeadRotation().z + mDegToRad(-cam_angle); + MatrixF look_yaw_mtx(EulerF(0,0,look_yaw)); + xfm.mul(look_yaw_mtx); + + // rotate xfm by amount of head pitch in player + F32 head_pitch = player_subj->getHeadRotation().x; + MatrixF head_pitch_mtx(EulerF(head_pitch,0,0)); + xfm.mul(head_pitch_mtx); + + VectorF behind_vec(0, -cam_distance, 0); + xfm.mulP(behind_vec, &goal_pos); + goal_pos += cam_offset; + } + else // old backwards-compatible method + { + // rotate xfm by amount of head pitch in player + F32 head_pitch = player_subj->getHeadRotation().x; + MatrixF head_pitch_mtx(EulerF(head_pitch,0,0)); + xfm.mul(head_pitch_mtx); + + VectorF behind_vec(0, cam_offset.y, 0); + xfm.mulP(behind_vec, &goal_pos); + goal_pos.z += cam_offset.z; + } + } + // for non-player subjects, camera will follow, but pitch won't adjust. + else + { + xfm.mulP(cam_offset, &goal_pos); + } + + // avoid view occlusion + if (avoid_blocked_view(coi, goal_pos, goal_pos) && !on_server) + { + // snap to final position if path to goal is blocked + if (test_blocked_line(curr_pos, goal_pos)) + third_person_snap_c++; + } + + // place camera into its final position + + // speed factor values + // 15 -- tight + // 10 -- normal + // 5 -- loose + // 1 -- very loose + F32 speed_factor = 8.0f; + F32 time_inc = 1.0f/speed_factor; + + // snap to final position + if (on_server || (third_person_snap_c > 0 || dt > time_inc)) + { + snapToPosition(goal_pos); + if (!on_server && third_person_snap_c > 0) + third_person_snap_c--; + return; + } + // interpolate to final position + else + { + // interpretation: always move a proportion of the distance + // from current location to destination that would cover the + // entire distance in time_inc duration at constant velocity. + F32 t = (dt >= time_inc) ? 1.0f : dt*speed_factor; + snapToPosition(goal_pos*t + curr_pos*(1.0-t)); + } +} + +// See if the camera view is occluded by certain objects, +// and move the camera closer to the subject in that case +bool afxCamera::avoid_blocked_view(const Point3F& startpos, const Point3F& endpos, Point3F& newpos) +{ + // cast ray to check for intersection with potential blocker objects + RayInfo hit_info; + if (!getContainer()->castRay(startpos, endpos, afxCameraData::sCameraCollisionMask, &hit_info)) + { + // no hit: just return original endpos + newpos = endpos; + return false; + } + + // did hit: return the hit location nudged forward slightly + // to avoid seeing clipped portions of blocking object. + Point3F sight_line = startpos - hit_info.point; + sight_line.normalize(); + newpos = hit_info.point + sight_line*0.4f; + + return true; +} + +bool afxCamera::test_blocked_line(const Point3F& startpos, const Point3F& endpos) +{ + RayInfo hit_info; + return getContainer()->castRay(startpos, endpos, afxCameraData::sCameraCollisionMask, &hit_info); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +// STD OVERRIDES SECTION + +bool afxCamera::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox.maxExtents = mObjScale; + mObjBox.minExtents = mObjScale; + mObjBox.minExtents.neg(); + + resetWorldBox(); + + addToScene(); + + return true; +} + +void afxCamera::onRemove() +{ + removeFromScene(); + Parent::onRemove(); +} + +void afxCamera::onDeleteNotify(SimObject *obj) +{ + Parent::onDeleteNotify(obj); + + if (obj == (SimObject*)mOrbitObject) + { + mOrbitObject = NULL; + if (mode == OrbitObjectMode) + mode = OrbitPointMode; + } + + if (obj == cam_subject) + { + cam_subject = NULL; + } +} + +void afxCamera::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if (gSFX3DWorld) + { + if (mode == ThirdPersonMode && cam_subject) + { + if (gSFX3DWorld->getListener() != cam_subject) + gSFX3DWorld->setListener(cam_subject); + } + else if (mode == FlyMode) + { + if (gSFX3DWorld->getListener() != this) + gSFX3DWorld->setListener(this); + } + } + + cam_update(dt, false); +} + +void afxCamera::processTick(const Move* move) +{ + Parent::processTick(move); + Point3F vec,pos; + + // move will be NULL unless camera becomes the control object as in FlyMode + if (move) + { + // UPDATE ORIENTATION // + delta.rotVec = mRot; + mObjToWorld.getColumn(3, &delta.posVec); + mRot.x = mClampF(mRot.x + move->pitch, -MaxPitch, MaxPitch); + mRot.z += move->yaw; + + // ORBIT MODE // + if (mode == OrbitObjectMode || mode == OrbitPointMode) + { + if(mode == OrbitObjectMode && bool(mOrbitObject)) + { + // If this is a shapebase, use its render eye transform + // to avoid jittering. + GameBase *castObj = mOrbitObject; + ShapeBase* shape = dynamic_cast(castObj); + if( shape != NULL ) { + MatrixF ret; + shape->getRenderEyeTransform( &ret ); + mPosition = ret.getPosition(); + } + else + { + // Hopefully this is a static object that doesn't move, + // because the worldbox doesn't get updated between ticks. + mOrbitObject->getWorldBox().getCenter(&mPosition); + } + } + set_cam_pos(mPosition, mRot); + validateEyePoint(1.0f, &mObjToWorld); + pos = mPosition; + } + + // NON-ORBIT MODE (FLY MODE) // + else // if (mode == FlyMode) + { + // Update pos + bool faster = move->trigger[0] || move->trigger[1]; + F32 scale = Camera::getMovementSpeed() * (faster + 1); + + mObjToWorld.getColumn(3,&pos); + mObjToWorld.getColumn(0,&vec); + pos += vec * move->x * TickSec * scale; + mObjToWorld.getColumn(1,&vec); + pos += vec * move->y * TickSec * scale; + mObjToWorld.getColumn(2,&vec); + pos += vec * move->z * TickSec * scale; + set_cam_pos(pos,mRot); + } + + // If on the client, calc delta for backstepping + if (isClientObject()) + { + delta.pos = pos; + delta.rot = mRot; + delta.posVec = delta.posVec - delta.pos; + delta.rotVec = delta.rotVec - delta.rot; + } + else + { + setMaskBits(MoveMask); + } + } + else // if (!move) + { + if (isServerObject()) + cam_update(1.0/32.0, true); + } + + if (getControllingClient() && mContainer) + updateContainer(); +} + +void afxCamera::interpolateTick(F32 dt) +{ + Parent::interpolateTick(dt); + + if (mode == ThirdPersonMode) + return; + + Point3F rot = delta.rot + delta.rotVec * dt; + + if(mode == OrbitObjectMode || mode == OrbitPointMode) + { + if(mode == OrbitObjectMode && bool(mOrbitObject)) + { + // If this is a shapebase, use its render eye transform + // to avoid jittering. + GameBase *castObj = mOrbitObject; + ShapeBase* shape = dynamic_cast(castObj); + if( shape != NULL ) + { + MatrixF ret; + shape->getRenderEyeTransform( &ret ); + mPosition = ret.getPosition(); + } + else + { + // Hopefully this is a static object that doesn't move, + // because the worldbox doesn't get updated between ticks. + mOrbitObject->getWorldBox().getCenter(&mPosition); + } + } + set_cam_pos(mPosition, rot); + validateEyePoint(1.0f, &mObjToWorld); + } + else + { + // NOTE - posVec is 0,0,0 unless cam is control-object and process tick is + // updating the delta + Point3F pos = delta.pos + delta.posVec * dt; + set_cam_pos(pos,rot); + } +} + +void afxCamera::writePacketData(GameConnection *connection, BitStream *bstream) +{ + // Update client regardless of status flags. + Parent::writePacketData(connection, bstream); + + Point3F pos; mObjToWorld.getColumn(3, &pos); + bstream->setCompressionPoint(pos); // SET COMPRESSION POINT + mathWrite(*bstream, pos); // SND POS + bstream->write(mRot.x); // SND X ROT + bstream->write(mRot.z); // SND Z ROT + + if (bstream->writeFlag(cam_dirty)) + { + mathWrite(*bstream, cam_offset); // SND CAM_OFFSET + mathWrite(*bstream, coi_offset); // SND COI_OFFSET + bstream->write(cam_distance); + bstream->write(cam_angle); + cam_dirty = false; + } + + U32 writeMode = mode; + Point3F writePos = mPosition; + S32 gIndex = -1; + if (mode == OrbitObjectMode) + { + gIndex = bool(mOrbitObject) ? connection->getGhostIndex(mOrbitObject): -1; + if(gIndex == -1) + { + writeMode = OrbitPointMode; + mOrbitObject->getWorldBox().getCenter(&writePos); + } + } + + bstream->writeRangedU32(writeMode, CameraFirstMode, CameraLastMode); // SND MODE + if (writeMode == ThirdPersonMode) + { + bstream->write(third_person_snap_s > 0); // SND SNAP + if (third_person_snap_s > 0) + third_person_snap_s--; + } + + if (writeMode == OrbitObjectMode || writeMode == OrbitPointMode) + { + bstream->write(mMinOrbitDist); // SND ORBIT MIN DIST + bstream->write(mMaxOrbitDist); // SND ORBIT MAX DIST + bstream->write(mCurOrbitDist); // SND ORBIT CURR DIST + if(writeMode == OrbitObjectMode) + { + bstream->writeFlag(mObservingClientObject); // SND OBSERVING CLIENT OBJ + bstream->writeInt(gIndex, NetConnection::GhostIdBitSize); // SND ORBIT OBJ + } + if (writeMode == OrbitPointMode) + bstream->writeCompressedPoint(writePos); // WRITE COMPRESSION POINT + } +} + +void afxCamera::readPacketData(GameConnection *connection, BitStream *bstream) +{ + Parent::readPacketData(connection, bstream); + + Point3F pos,rot; + mathRead(*bstream, &pos); // RCV POS + bstream->setCompressionPoint(pos); + bstream->read(&rot.x); // RCV X ROT + bstream->read(&rot.z); // RCV Z ROT + + if (bstream->readFlag()) + { + Point3F new_cam_offset, new_coi_offset; + mathRead(*bstream, &new_cam_offset); // RCV CAM_OFFSET + mathRead(*bstream, &new_coi_offset); // RCV COI_OFFSET + bstream->read(&cam_distance); + bstream->read(&cam_angle); + setThirdPersonOffset(new_cam_offset, new_coi_offset); + } + + GameBase* obj = 0; + mode = bstream->readRangedU32(CameraFirstMode, // RCV MODE + CameraLastMode); + if (mode == ThirdPersonMode) + { + bool snap; bstream->read(&snap); + if (snap) + third_person_snap_c++; + } + + mObservingClientObject = false; + if (mode == OrbitObjectMode || mode == OrbitPointMode) { + bstream->read(&mMinOrbitDist); + bstream->read(&mMaxOrbitDist); + bstream->read(&mCurOrbitDist); + + if(mode == OrbitObjectMode) + { + mObservingClientObject = bstream->readFlag(); + S32 gIndex = bstream->readInt(NetConnection::GhostIdBitSize); + obj = static_cast(connection->resolveGhost(gIndex)); + } + if (mode == OrbitPointMode) + bstream->readCompressedPoint(&mPosition); + } + if (obj != (GameBase*)mOrbitObject) { + if (mOrbitObject) { + clearProcessAfter(); + clearNotify(mOrbitObject); + } + mOrbitObject = obj; + if (mOrbitObject) { + processAfter(mOrbitObject); + deleteNotify(mOrbitObject); + } + } + + if (mode == ThirdPersonMode) + return; + + set_cam_pos(pos,rot); + delta.pos = pos; + delta.rot = rot; + delta.rotVec.set(0,0,0); + delta.posVec.set(0,0,0); +} + +U32 afxCamera::packUpdate(NetConnection* conn, U32 mask, BitStream *bstream) +{ + U32 retMask = Parent::packUpdate(conn,mask,bstream); + + // The rest of the data is part of the control object packet update. + // If we're controlled by this client, we don't need to send it. + //if(bstream->writeFlag(getControllingClient() == conn && !(mask & InitialUpdateMask))) + // return 0; + + if (bstream->writeFlag(mask & MoveMask)) { + Point3F pos; + mObjToWorld.getColumn(3,&pos); + bstream->write(pos.x); + bstream->write(pos.y); + bstream->write(pos.z); + bstream->write(mRot.x); + bstream->write(mRot.z); + } + + if (bstream->writeFlag(mask & SubjectMask)) + { + S32 ghost_id = (cam_subject) ? conn->getGhostIndex(cam_subject) : -1; + if (bstream->writeFlag(ghost_id != -1)) + bstream->writeRangedU32(U32(ghost_id), 0, NetConnection::MaxGhostCount); + else if (cam_subject) + retMask |= SubjectMask; + } + + return retMask; +} + +void afxCamera::unpackUpdate(NetConnection *conn, BitStream *bstream) +{ + Parent::unpackUpdate(conn,bstream); + + // controlled by the client? + //if(bstream->readFlag()) + // return; + + if (bstream->readFlag()) { + Point3F pos,rot; + bstream->read(&pos.x); + bstream->read(&pos.y); + bstream->read(&pos.z); + bstream->read(&rot.x); + bstream->read(&rot.z); + set_cam_pos(pos,rot); + + // New delta for client side interpolation + delta.pos = pos; + delta.rot = rot; + delta.posVec = delta.rotVec = VectorF(0,0,0); + } + + if (bstream->readFlag()) + { + if (bstream->readFlag()) + { + S32 ghost_id = bstream->readRangedU32(0, NetConnection::MaxGhostCount); + cam_subject = dynamic_cast(conn->resolveGhost(ghost_id)); + } + else + cam_subject = NULL; + } +} + +// Override to ensure both are kept in scope +void afxCamera::onCameraScopeQuery(NetConnection* conn, CameraScopeQuery* query) +{ + if (cam_subject) + conn->objectInScope(cam_subject); + Parent::onCameraScopeQuery(conn, query); +} + +//---------------------------------------------------------------------------- +// check if the object needs to be observed through its own camera... +void afxCamera::getCameraTransform(F32* pos, MatrixF* mat) +{ + // The camera doesn't support a third person mode, + // so we want to override the default ShapeBase behavior. + ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); + if (obj && static_cast(obj->getDataBlock())->observeThroughObject) + obj->getCameraTransform(pos, mat); + else + getEyeTransform(mat); +} + +void afxCamera::setTransform(const MatrixF& mat) +{ + // This method should never be called on the client. + + // This currently converts all rotation in the mat into + // rotations around the z and x axis. + Point3F pos,vec; + mat.getColumn(1,&vec); + mat.getColumn(3,&pos); + Point3F rot(-mAtan2(vec.z, mSqrt(vec.x*vec.x + vec.y*vec.y)),0,-mAtan2(-vec.x,vec.y)); + set_cam_pos(pos,rot); +} + +void afxCamera::onEditorEnable() +{ + mNetFlags.set(Ghostable); +} + +void afxCamera::onEditorDisable() +{ + mNetFlags.clear(Ghostable); +} + +F32 afxCamera::getCameraFov() +{ + ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); + if(obj && static_cast(obj->getDataBlock())->observeThroughObject) + return(obj->getCameraFov()); + else + return(Parent::getCameraFov()); +} + +F32 afxCamera::getDefaultCameraFov() +{ + ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); + if(obj && static_cast(obj->getDataBlock())->observeThroughObject) + return(obj->getDefaultCameraFov()); + else + return(Parent::getDefaultCameraFov()); +} + +bool afxCamera::isValidCameraFov(F32 fov) +{ + ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); + if(obj && static_cast(obj->getDataBlock())->observeThroughObject) + return(obj->isValidCameraFov(fov)); + else + return(Parent::isValidCameraFov(fov)); +} + +void afxCamera::setCameraFov(F32 fov) +{ + ShapeBase * obj = dynamic_cast(static_cast(mOrbitObject)); + if(obj && static_cast(obj->getDataBlock())->observeThroughObject) + obj->setCameraFov(fov); + else + Parent::setCameraFov(fov); +} + +F32 afxCamera::getDamageFlash() const +{ + if (mode == OrbitObjectMode && isServerObject() && bool(mOrbitObject)) + { + const GameBase *castObj = mOrbitObject; + const ShapeBase* psb = dynamic_cast(castObj); + if (psb) + return psb->getDamageFlash(); + } + + return mDamageFlash; +} + +F32 afxCamera::getWhiteOut() const +{ + if (mode == OrbitObjectMode && isServerObject() && bool(mOrbitObject)) + { + const GameBase *castObj = mOrbitObject; + const ShapeBase* psb = dynamic_cast(castObj); + if (psb) + return psb->getWhiteOut(); + } + + return mWhiteOut; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxCamera::setControllingClient( GameConnection* client ) +{ + GameBase::setControllingClient( client ); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/afxCamera.h b/Engine/source/afx/afxCamera.h new file mode 100644 index 000000000..934f293cf --- /dev/null +++ b/Engine/source/afx/afxCamera.h @@ -0,0 +1,189 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// afxCamera implements a modified camera for demonstrating a third person camera style +// which is more common to RPG games than the standard FPS style camera. For the most part, +// it is a hybrid of the standard TGE camera and the third person mode of the Advanced Camera +// resource, authored by Thomas "Man of Ice" Lund. This camera implements the bare minimum +// required for demonstrating an RPG style camera and leaves tons of room for improvement. +// It should be replaced with a better camera if possible. +// +// Advanced Camera Resource by Thomas "Man of Ice" Lund: +// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5471 +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_CAMERA_H_ +#define _AFX_CAMERA_H_ + +#ifndef _SHAPEBASE_H_ +#include "game/shapeBase.h" +#endif + +//---------------------------------------------------------------------------- +struct afxCameraData: public ShapeBaseData { + typedef ShapeBaseData Parent; + + static U32 sCameraCollisionMask; + + // + DECLARE_CONOBJECT(afxCameraData); + DECLARE_CATEGORY("AFX"); + static void initPersistFields(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); +}; + + +//---------------------------------------------------------------------------- +// Implements a basic camera object. +class afxCamera: public ShapeBase +{ + typedef ShapeBase Parent; + + enum MaskBits { + MoveMask = Parent::NextFreeMask, + SubjectMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + + struct StateDelta { + Point3F pos; + Point3F rot; + VectorF posVec; + VectorF rotVec; + }; + + enum + { + ThirdPersonMode = 1, + FlyMode = 2, + OrbitObjectMode = 3, + OrbitPointMode = 4, + CameraFirstMode = 0, + CameraLastMode = 4 + }; + +private: + int mode; + Point3F mRot; + StateDelta delta; + + SimObjectPtr mOrbitObject; + F32 mMinOrbitDist; + F32 mMaxOrbitDist; + F32 mCurOrbitDist; + Point3F mPosition; + bool mObservingClientObject; + + SceneObject* cam_subject; + Point3F cam_offset; + Point3F coi_offset; + F32 cam_distance; + F32 cam_angle; + bool cam_dirty; + + bool flymode_saved; + Point3F flymode_saved_pos; + S8 third_person_snap_c; + S8 third_person_snap_s; + + void set_cam_pos(const Point3F& pos, const Point3F& viewRot); + void cam_update(F32 dt, bool on_server); + +public: + /*C*/ afxCamera(); + /*D*/ ~afxCamera(); + + Point3F& getPosition(); + void setFlyMode(); + void setOrbitMode(GameBase* obj, Point3F& pos, AngAxisF& rot, F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject); + void validateEyePoint(F32 pos, MatrixF *mat); + + GameBase* getOrbitObject() { return(mOrbitObject); } + bool isObservingClientObject() { return(mObservingClientObject); } + + void snapToPosition(const Point3F& pos); + void setCameraSubject(SceneObject* subject); + void setThirdPersonOffset(const Point3F& offset); + void setThirdPersonOffset(const Point3F& offset, const Point3F& coi_offset); + const Point3F& getThirdPersonOffset() const { return cam_offset; } + const Point3F& getThirdPersonCOIOffset() const { return coi_offset; } + void setThirdPersonDistance(F32 distance); + F32 getThirdPersonDistance(); + void setThirdPersonAngle(F32 angle); + F32 getThirdPersonAngle(); + void setThirdPersonMode(); + void setThirdPersonSnap(); + void setThirdPersonSnapClient(); + const char* getMode(); + + bool isCamera() const { return true; } + + DECLARE_CONOBJECT(afxCamera); + DECLARE_CATEGORY("AFX"); + +private: // 3POV SECTION + U32 blockers_mask_3pov; + + void cam_update_3pov(F32 dt, bool on_server); + bool avoid_blocked_view(const Point3F& start, const Point3F& end, Point3F& newpos); + bool test_blocked_line(const Point3F& start, const Point3F& end); + +public: // STD OVERRIDES SECTION + virtual bool onAdd(); + virtual void onRemove(); + virtual void onDeleteNotify(SimObject *obj); + + virtual void advanceTime(F32 dt); + virtual void processTick(const Move* move); + virtual void interpolateTick(F32 delta); + + virtual void writePacketData(GameConnection *conn, BitStream *stream); + virtual void readPacketData(GameConnection *conn, BitStream *stream); + virtual U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + virtual void unpackUpdate(NetConnection *conn, BitStream *stream); + + virtual void onCameraScopeQuery(NetConnection* cr, CameraScopeQuery*); + virtual void getCameraTransform(F32* pos,MatrixF* mat); + virtual void setTransform(const MatrixF& mat); + + virtual void onEditorEnable(); + virtual void onEditorDisable(); + + virtual F32 getCameraFov(); + virtual F32 getDefaultCameraFov(); + virtual bool isValidCameraFov(F32 fov); + virtual void setCameraFov(F32 fov); + + virtual F32 getDamageFlash() const; + virtual F32 getWhiteOut() const; + + virtual void setControllingClient( GameConnection* connection ); +}; + + +#endif // _AFX_CAMERA_H_ diff --git a/Engine/source/afx/afxChoreographer.cpp b/Engine/source/afx/afxChoreographer.cpp new file mode 100644 index 000000000..581ba42ba --- /dev/null +++ b/Engine/source/afx/afxChoreographer.cpp @@ -0,0 +1,1084 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" +#include "T3D/gameBase/gameConnection.h" +#include "math/mathIO.h" +#include "console/compiler.h" + +#include "afx/afxConstraint.h" +#include "afx/afxChoreographer.h" +#include "afx/afxEffectWrapper.h" +#include "afx/util/afxParticlePool.h" +#include "afx/forces/afxForceSet.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxChoreographerData); + +ConsoleDocClass( afxChoreographerData, + "@brief Datablock base class used by choreographers.\n\n" + + "@ingroup afxChoreographers\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxChoreographerData::afxChoreographerData() +{ + exec_on_new_clients = false; + echo_packet_usage = 100; + client_script_file = ST_NULLSTRING; + client_init_func = ST_NULLSTRING; +} + +afxChoreographerData::afxChoreographerData(const afxChoreographerData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + exec_on_new_clients = other.exec_on_new_clients; + echo_packet_usage = other.echo_packet_usage; + client_script_file = other.client_script_file; + client_init_func = other.client_init_func; +} + +#define myOffset(field) Offset(field, afxChoreographerData) + +void afxChoreographerData::initPersistFields() +{ + addField("execOnNewClients", TypeBool, myOffset(exec_on_new_clients), + "..."); + addField("echoPacketUsage", TypeS8, myOffset(echo_packet_usage), + "..."); + addField("clientScriptFile", TypeFilename, myOffset(client_script_file), + "..."); + addField("clientInitFunction", TypeString, myOffset(client_init_func), + "..."); + + Parent::initPersistFields(); +} + +void afxChoreographerData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(exec_on_new_clients); + stream->write(echo_packet_usage); + stream->writeString(client_script_file); + stream->writeString(client_init_func); +} + +void afxChoreographerData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&exec_on_new_clients); + stream->read(&echo_packet_usage); + client_script_file = stream->readSTString(); + client_init_func = stream->readSTString(); +} + +bool afxChoreographerData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + if (!server && client_script_file != ST_NULLSTRING) + { + Compiler::gSyntaxError = false; + Con::evaluate(avar("exec(\"%s\");", client_script_file), false, 0); + if (Compiler::gSyntaxError) + { + Con::errorf("afxChoreographerData: failed to exec clientScriptFile \"%s\" -- syntax error", client_script_file); + Compiler::gSyntaxError = false; + } + } + + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxChoreographer); + +ConsoleDocClass( afxChoreographer, + "@brief Base class used by choreographers.\n\n" + + "@ingroup afxChoreographers\n" + "@ingroup AFX\n" +); + +afxChoreographer::afxChoreographer() +{ + datablock = 0; // datablock initializer (union these?) + exeblock = 0; // object to use in executef callouts + + // create the constraint manager + constraint_mgr = new afxConstraintMgr(); + + ranking = 0; + lod = 0; + exec_conds_mask = 0; + choreographer_id = 0; + extra = 0; + started_with_newop = false; + postpone_activation = false; + remapped_cons_sent = false; // CONSTRAINT REMAPPING + + dyn_cons_defs = &dc_defs_a; + dyn_cons_defs2 = &dc_defs_b; + + proc_after_obj = 0; + trigger_mask = 0; + + force_set_mgr = 0; + + mOrderGUID = 0x0FFFFFFF; +} + +afxChoreographer::~afxChoreographer() +{ + explicit_clients.clear(); + + for (S32 i = 0; i < dyn_cons_defs->size(); i++) + { + if ((*dyn_cons_defs)[i].cons_type == POINT_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.point != NULL) + delete (*dyn_cons_defs)[i].cons_obj.point; + else if ((*dyn_cons_defs)[i].cons_type == TRANSFORM_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.xfm != NULL) + delete (*dyn_cons_defs)[i].cons_obj.xfm; + } + + constraint_mgr->clearAllScopeableObjs(); + delete constraint_mgr; + + delete force_set_mgr; +} + +void afxChoreographer::initPersistFields() +{ + // conditionals + addField("extra", TYPEID(), Offset(extra, afxChoreographer), + "..."); + addField("postponeActivation", TypeBool, Offset(postpone_activation, afxChoreographer), + "..."); + + Parent::initPersistFields(); +} + +bool afxChoreographer::onAdd() +{ + if (!Parent::onAdd()) + return(false); + + if (isServerObject()) + choreographer_id = arcaneFX::registerChoreographer(this); + else + { + if (proc_after_obj) + { + processAfter(proc_after_obj); + proc_after_obj = 0; + } + + force_set_mgr = new afxForceSetMgr(); + + if (datablock && datablock->client_init_func != ST_NULLSTRING) + Con::executef(datablock->client_init_func, getIdString()); + } + + return(true); +} + +void afxChoreographer::onRemove() +{ + for (S32 i = 0; i < particle_pools.size(); i++) + if (particle_pools[i]) + particle_pools[i]->setChoreographer(0); + particle_pools.clear(); + + if (isServerObject()) + arcaneFX::unregisterChoreographer(this); + else + arcaneFX::unregisterClientChoreographer(this); + + choreographer_id = 0; + Parent::onRemove(); +} + +void afxChoreographer::onDeleteNotify(SimObject* obj) +{ + if (dynamic_cast(obj)) + { + SceneObject* scn_obj = (SceneObject*)(obj); + for (S32 i = 0; i < dyn_cons_defs->size(); i++) + if ((*dyn_cons_defs)[i].cons_type != OBJECT_CONSTRAINT || (*dyn_cons_defs)[i].cons_obj.object != scn_obj) + dyn_cons_defs2->push_back((*dyn_cons_defs)[i]); + + Vector* tmp = dyn_cons_defs; + dyn_cons_defs = dyn_cons_defs2; + dyn_cons_defs2 = tmp; + dyn_cons_defs2->clear(); + + if (isServerObject() && scn_obj->isScopeable()) + constraint_mgr->removeScopeableObject(scn_obj); + } + else if (dynamic_cast(obj)) + { + removeExplicitClient((NetConnection*)obj); + } + + Parent::onDeleteNotify(obj); +} + +bool afxChoreographer::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + datablock = dynamic_cast(dptr); + if (!datablock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + exeblock = datablock; + + return true; +} + +void afxChoreographer::pack_constraint_info(NetConnection* conn, BitStream* stream) +{ + stream->write(dyn_cons_defs->size()); + for (S32 i = 0; i < dyn_cons_defs->size(); i++) + { + if ((*dyn_cons_defs)[i].cons_type == OBJECT_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.object != NULL) + { + stream->writeString((*dyn_cons_defs)[i].cons_name); + stream->write((*dyn_cons_defs)[i].cons_type); + SceneObject* object = (*dyn_cons_defs)[i].cons_obj.object; + S32 ghost_idx = conn->getGhostIndex(object); + if (stream->writeFlag(ghost_idx != -1)) + { + stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount); + } + else + { + if (stream->writeFlag(object->getScopeId() > 0)) + { + stream->writeInt(object->getScopeId(), NetObject::SCOPE_ID_BITS); + stream->writeFlag(dynamic_cast(object) != NULL); + } + } + } + else if ((*dyn_cons_defs)[i].cons_type == POINT_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.point != NULL) + { + stream->writeString((*dyn_cons_defs)[i].cons_name); + stream->write((*dyn_cons_defs)[i].cons_type); + mathWrite(*stream, *(*dyn_cons_defs)[i].cons_obj.point); + } + else if ((*dyn_cons_defs)[i].cons_type == TRANSFORM_CONSTRAINT && (*dyn_cons_defs)[i].cons_obj.xfm != NULL) + { + stream->writeString((*dyn_cons_defs)[i].cons_name); + stream->write((*dyn_cons_defs)[i].cons_type); + mathWrite(*stream, *(*dyn_cons_defs)[i].cons_obj.xfm); + } + } + + constraint_mgr->packConstraintNames(conn, stream); +} + +void afxChoreographer::unpack_constraint_info(NetConnection* conn, BitStream* stream) +{ + S32 n_defs; + stream->read(&n_defs); + dyn_cons_defs->clear(); + for (S32 i = 0; i < n_defs; i++) + { + StringTableEntry cons_name = stream->readSTString(); + U8 cons_type; stream->read(&cons_type); + if (cons_type == OBJECT_CONSTRAINT) + { + SceneObject* scn_obj = NULL; + if (stream->readFlag()) + { + S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount); + scn_obj = dynamic_cast(conn->resolveGhost(ghost_idx)); + if (scn_obj) + { + addObjectConstraint(scn_obj, cons_name); + } + else + Con::errorf("CANNOT RESOLVE GHOST %d %s", ghost_idx, cons_name); + } + else + { + if (stream->readFlag()) + { + U16 scope_id = stream->readInt(NetObject::SCOPE_ID_BITS); + bool is_shape = stream->readFlag(); + addObjectConstraint(scope_id, cons_name, is_shape); + } + } + } + else if (cons_type == POINT_CONSTRAINT) + { + Point3F point; + mathRead(*stream, &point); + addPointConstraint(point, cons_name); + } + else if (cons_type == TRANSFORM_CONSTRAINT) + { + MatrixF xfm; + mathRead(*stream, &xfm); + addTransformConstraint(xfm, cons_name); + } + } + + constraint_mgr->unpackConstraintNames(stream); +} + +void afxChoreographer::setup_dynamic_constraints() +{ + for (S32 i = 0; i < dyn_cons_defs->size(); i++) + { + switch ((*dyn_cons_defs)[i].cons_type) + { + case OBJECT_CONSTRAINT: + constraint_mgr->setReferenceObject((*dyn_cons_defs)[i].cons_name, (*dyn_cons_defs)[i].cons_obj.object); + break; + case POINT_CONSTRAINT: + constraint_mgr->setReferencePoint((*dyn_cons_defs)[i].cons_name, *(*dyn_cons_defs)[i].cons_obj.point); + break; + case TRANSFORM_CONSTRAINT: + constraint_mgr->setReferenceTransform((*dyn_cons_defs)[i].cons_name, *(*dyn_cons_defs)[i].cons_obj.xfm); + break; + case OBJECT_CONSTRAINT_SANS_OBJ: + constraint_mgr->setReferenceObjectByScopeId((*dyn_cons_defs)[i].cons_name, (*dyn_cons_defs)[i].cons_obj.scope_id, false); + break; + case OBJECT_CONSTRAINT_SANS_SHAPE: + constraint_mgr->setReferenceObjectByScopeId((*dyn_cons_defs)[i].cons_name, (*dyn_cons_defs)[i].cons_obj.scope_id, true); + break; + } + } +} + +afxChoreographer::dynConstraintDef* afxChoreographer::find_cons_def_by_name(const char* cons_name) +{ + StringTableEntry cons_name_ste = StringTable->insert(cons_name); + + for (S32 i = 0; i < dyn_cons_defs->size(); i++) + { + if ((*dyn_cons_defs)[i].cons_name == cons_name_ste) + return &((*dyn_cons_defs)[i]); + } + + return 0; +} + +void afxChoreographer::check_packet_usage(NetConnection* conn, BitStream* stream, S32 mark_stream_pos, const char* msg_tag) +{ + + S32 packed_size = stream->getCurPos() - mark_stream_pos; + S32 current_headroom = stream->getMaxWriteBitNum()-(stream->getStreamSize()<<3); + S32 max_headroom = stream->getMaxWriteBitNum()-(conn->getMaxRatePacketSize()<<3); + + if (packed_size > max_headroom) + { + Con::errorf("%s [%s] WARNING -- packed-bits (%d) > limit (%d) for max PacketSize settings. [%s]", + msg_tag, datablock->getName(), packed_size, max_headroom, "PACKET OVERRUN POSSIBLE"); + Con::errorf("getMaxRatePacketSize()=%d getCurRatePacketSize()=%d", conn->getMaxRatePacketSize(), conn->getCurRatePacketSize()); + } + // JTF Note: this current_headroom > 0 check is odd and occurs when object is created real early + // in the startup, such as when the dead orc gets fatal damage and we try to post a text effect. + else if (packed_size > current_headroom && current_headroom > 0) + { + Con::errorf("%s [%s] WARNING -- packed-bits (%d) > limit (%d) for current PacketSize settings. [%s]", + msg_tag, datablock->getName(), packed_size, current_headroom, "PACKET OVERRUN POSSIBLE"); + } + else + { + F32 percentage = 100.0f*((F32)packed_size/(F32)max_headroom); + if (percentage >= datablock->echo_packet_usage) + { + Con::warnf("%s [%s] -- packed-bits (%d) < limit (%d). [%.1f%% full]", msg_tag, datablock->getName(), + packed_size, max_headroom, percentage); + } + } +} + +SceneObject* afxChoreographer::get_camera(Point3F* cam_pos) const +{ + GameConnection* conn = GameConnection::getConnectionToServer(); + if (!conn) + { + if (cam_pos) + cam_pos->zero(); + return 0; + } + + SceneObject* cam_obj = conn->getCameraObject(); + if (cam_pos) + { + if (cam_obj) + { + MatrixF cam_mtx; + conn->getControlCameraTransform(0, &cam_mtx); + cam_mtx.getColumn(3, cam_pos); + } + else + cam_pos->zero(); + } + + return cam_obj; +} + +U32 afxChoreographer::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(conn, mask, stream); + + if (stream->writeFlag(mask & InitialUpdateMask)) //-- INITIAL UPDATE ? + { + stream->write(ranking); + stream->write(lod); + stream->write(exec_conds_mask); + stream->write(choreographer_id); + + // write dynamic fields beginning with arcaneFX::sParameterFieldPrefix + SimFieldDictionaryIterator itr(getFieldDictionary()); + SimFieldDictionary::Entry* entry; + U32 prefix_len = dStrlen(arcaneFX::sParameterFieldPrefix); + if (prefix_len > 0) + { + while ((entry = *itr) != NULL) + { + if (dStrncmp(entry->slotName, arcaneFX::sParameterFieldPrefix, prefix_len) == 0) + { + stream->writeFlag(true); + stream->writeString(entry->slotName); + stream->writeLongString(1023, entry->value); + } + ++itr; + } + } + stream->writeFlag(false); + } + + if (stream->writeFlag(mask & TriggerMask)) + { + stream->write(trigger_mask); + } + + // CONSTRAINT REMAPPING << + if (stream->writeFlag((mask & RemapConstraintMask) && !remapped_cons_defs.empty())) + { + remapped_cons_sent = true; + //Con::errorf("PACKING CONS REMAP %d conn:%d", remapped_cons_defs.size(), (U32) conn); + + stream->write(remapped_cons_defs.size()); + for (S32 i = 0; i < remapped_cons_defs.size(); i++) + { + if (remapped_cons_defs[i]->cons_type == OBJECT_CONSTRAINT && remapped_cons_defs[i]->cons_obj.object != NULL) + { + //Con::errorf("PACKING CONS REMAP: name %s", remapped_cons_defs[i]->cons_name); + stream->writeString(remapped_cons_defs[i]->cons_name); + //Con::errorf("PACKING CONS REMAP: type %d", remapped_cons_defs[i]->cons_type); + stream->write(remapped_cons_defs[i]->cons_type); + SceneObject* object = remapped_cons_defs[i]->cons_obj.object; + S32 ghost_idx = conn->getGhostIndex(object); + //Con::errorf("PACKING CONS REMAP: ghost %d", ghost_idx); + if (stream->writeFlag(ghost_idx != -1)) + { + stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount); + } + else + { + if (stream->writeFlag(object->getScopeId() > 0)) + { + stream->writeInt(object->getScopeId(), NetObject::SCOPE_ID_BITS); + stream->writeFlag(dynamic_cast(object) != NULL); + } + } + } + else if (remapped_cons_defs[i]->cons_type == POINT_CONSTRAINT && remapped_cons_defs[i]->cons_obj.point != NULL) + { + stream->writeString(remapped_cons_defs[i]->cons_name); + stream->write(remapped_cons_defs[i]->cons_type); + mathWrite(*stream, *remapped_cons_defs[i]->cons_obj.point); + } + else if (remapped_cons_defs[i]->cons_type == TRANSFORM_CONSTRAINT && remapped_cons_defs[i]->cons_obj.xfm != NULL) + { + stream->writeString(remapped_cons_defs[i]->cons_name); + stream->write(remapped_cons_defs[i]->cons_type); + mathWrite(*stream, *remapped_cons_defs[i]->cons_obj.xfm); + } + } + } + // CONSTRAINT REMAPPING >> + + AssertISV(stream->isValid(), "afxChoreographer::packUpdate(): write failure occurred, possibly caused by packet-size overrun."); + + return retMask; +} + +void afxChoreographer::unpackUpdate(NetConnection * conn, BitStream * stream) +{ + Parent::unpackUpdate(conn, stream); + + // InitialUpdate Only + if (stream->readFlag()) + { + stream->read(&ranking); + stream->read(&lod); + stream->read(&exec_conds_mask); + stream->read(&choreographer_id); + + // read dynamic fields + while(stream->readFlag()) + { + char slotName[256]; + char value[1024]; + stream->readString(slotName); + stream->readLongString(1023, value); + setDataField(StringTable->insert(slotName), 0, value); + } + + arcaneFX::registerClientChoreographer(this); + } + + if (stream->readFlag()) // TriggerMask + { + stream->read(&trigger_mask); + } + + // CONSTRAINT REMAPPING << + if (stream->readFlag()) // RemapConstraintMask + { + S32 n_defs; + stream->read(&n_defs); + for (S32 i = 0; i < n_defs; i++) + { + StringTableEntry cons_name = stream->readSTString(); + U8 cons_type; stream->read(&cons_type); + if (cons_type == OBJECT_CONSTRAINT) + { + SceneObject* scn_obj = NULL; + if (stream->readFlag()) + { + S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount); + scn_obj = dynamic_cast(conn->resolveGhost(ghost_idx)); + if (scn_obj) + { + remapObjectConstraint(scn_obj, cons_name); + } + else + Con::errorf("CANNOT RESOLVE GHOST %d %s", ghost_idx, cons_name); + } + else + { + if (stream->readFlag()) + { + U16 scope_id = stream->readInt(NetObject::SCOPE_ID_BITS); + bool is_shape = stream->readFlag(); + remapObjectConstraint(scope_id, cons_name, is_shape); + } + } + } + else if (cons_type == POINT_CONSTRAINT) + { + Point3F point; + mathRead(*stream, &point); + remapPointConstraint(point, cons_name); + } + else if (cons_type == TRANSFORM_CONSTRAINT) + { + MatrixF xfm; + mathRead(*stream, &xfm); + remapTransformConstraint(xfm, cons_name); + } + } + } + // CONSTRAINT REMAPPING >> +} + +void afxChoreographer::executeScriptEvent(const char* method, afxConstraint* cons, + const MatrixF& xfm, const char* data) +{ + SceneObject* cons_obj = (cons) ? cons->getSceneObject() : NULL; + + char *arg_buf = Con::getArgBuffer(256); + Point3F pos; + xfm.getColumn(3,&pos); + AngAxisF aa(xfm); + dSprintf(arg_buf,256,"%g %g %g %g %g %g %g", + pos.x, pos.y, pos.z, + aa.axis.x, aa.axis.y, aa.axis.z, aa.angle); + + // CALL SCRIPT afxChoreographerData::method(%choreographer, %constraint, %transform, %data) + Con::executef(exeblock, method, + getIdString(), + (cons_obj) ? cons_obj->getIdString() : "", + arg_buf, + data); +} + +void afxChoreographer::addObjectConstraint(SceneObject* object, const char* cons_name) +{ + if (!object || !cons_name) + return; + + dynConstraintDef dyn_def; + dyn_def.cons_name = StringTable->insert(cons_name); + dyn_def.cons_type = OBJECT_CONSTRAINT; + dyn_def.cons_obj.object = object; + dyn_cons_defs->push_back(dyn_def); + +#if defined(AFX_CAP_SCOPE_TRACKING) + if (isServerObject() && object->isScopeable()) + constraint_mgr->addScopeableObject(object); +#endif + + constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, dyn_def.cons_name); + deleteNotify(object); +} + +void afxChoreographer::addObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape) +{ + if (!cons_name) + return; + + dynConstraintDef dyn_def; + dyn_def.cons_name = StringTable->insert(cons_name); + dyn_def.cons_type = (is_shape) ? OBJECT_CONSTRAINT_SANS_SHAPE : OBJECT_CONSTRAINT_SANS_OBJ; + dyn_def.cons_obj.scope_id = scope_id; + dyn_cons_defs->push_back(dyn_def); + + constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, dyn_def.cons_name); +} + +void afxChoreographer::addPointConstraint(Point3F& point, const char* cons_name) +{ + dynConstraintDef dyn_def; + dyn_def.cons_name = StringTable->insert(cons_name); + dyn_def.cons_type = POINT_CONSTRAINT; + dyn_def.cons_obj.point = new Point3F(point); + dyn_cons_defs->push_back(dyn_def); + + constraint_mgr->defineConstraint(POINT_CONSTRAINT, dyn_def.cons_name); +} + +void afxChoreographer::addTransformConstraint(MatrixF& xfm, const char* cons_name) +{ + dynConstraintDef dyn_def; + dyn_def.cons_name = StringTable->insert(cons_name); + dyn_def.cons_type = TRANSFORM_CONSTRAINT; + dyn_def.cons_obj.xfm = new MatrixF(xfm); + dyn_cons_defs->push_back(dyn_def); + + constraint_mgr->defineConstraint(TRANSFORM_CONSTRAINT, dyn_def.cons_name); +} + +// CONSTRAINT REMAPPING << +static inline U32 resolve_cons_spec(const char* source_spec, Point3F& pos, MatrixF& xfm, SceneObject** scn_obj) +{ + AngAxisF aa; + + S32 args_n = dSscanf(source_spec, "%g %g %g %g %g %g %g", + &pos.x, &pos.y, &pos.z, + &aa.axis.x, &aa.axis.y, &aa.axis.z, &aa.angle); + + // TRANSFORM CONSTRAINT SRC + if (args_n == 7) + { + aa.setMatrix(&xfm); + xfm.setColumn(3,pos); + return afxEffectDefs::TRANSFORM_CONSTRAINT; + } + + // POINT CONSTRAINT SRC + if (args_n == 3) + { + return afxEffectDefs::POINT_CONSTRAINT; + } + + SimObject* cons_sim_obj = Sim::findObject(source_spec); + *scn_obj = dynamic_cast(cons_sim_obj); + if (*scn_obj) + { + return afxEffectDefs::OBJECT_CONSTRAINT; + } + + return afxEffectDefs::UNDEFINED_CONSTRAINT_TYPE; +} +// CONSTRAINT REMAPPING >> + +bool afxChoreographer::addConstraint(const char* source_spec, const char* cons_name) +{ + VectorF pos; + MatrixF xfm; + SceneObject* scn_obj; + + switch (resolve_cons_spec(source_spec, pos, xfm, &scn_obj)) + { + case TRANSFORM_CONSTRAINT: + addTransformConstraint(xfm, cons_name); + return true; + case POINT_CONSTRAINT: + addPointConstraint(pos, cons_name); + return true; + case OBJECT_CONSTRAINT: + addObjectConstraint(scn_obj, cons_name); + return true; + } + + return false; +} + +void afxChoreographer::addNamedEffect(afxEffectWrapper* ew) +{ + named_effects.addObject(ew); +} + +void afxChoreographer::removeNamedEffect(afxEffectWrapper* ew) +{ + named_effects.removeObject(ew); +} + +afxEffectWrapper* afxChoreographer::findNamedEffect(StringTableEntry name) +{ + return (afxEffectWrapper*) named_effects.findObject(name); +} + +void afxChoreographer::setGhostConstraintObject(SceneObject* obj, StringTableEntry cons_name) +{ + if (constraint_mgr) + constraint_mgr->setReferenceObject(cons_name, obj); +} + +void afxChoreographer::restoreScopedObject(SceneObject* obj) +{ + constraint_mgr->restoreScopedObject(obj, this); + constraint_mgr->adjustProcessOrdering(this); +} + +void afxChoreographer::addExplicitClient(NetConnection* conn) +{ + if (!conn) + return; + + for (S32 i = 0; i < explicit_clients.size(); i++) + { + if (explicit_clients[i] == conn) + return; + } + + explicit_clients.push_back(conn); + deleteNotify(conn); +} + +void afxChoreographer::removeExplicitClient(NetConnection* conn) +{ + if (!conn) + return; + + for (S32 i = 0; i < explicit_clients.size(); i++) + { + if (explicit_clients[i] == conn) + { + clearNotify(conn); + explicit_clients.erase_fast(i); + return; + } + } +} + +void afxChoreographer::postProcessAfterObject(GameBase* obj) +{ + proc_after_obj = obj; +} + +void afxChoreographer::setTriggerMask(U32 mask) +{ + if (mask != trigger_mask) + { + trigger_mask = mask; + setMaskBits(TriggerMask); + } +} + +afxParticlePool* afxChoreographer::findParticlePool(afxParticlePoolData* key_block, U32 key_index) +{ + for (S32 i = 0; i < particle_pools.size(); i++) + if (particle_pools[i] && particle_pools[i]->hasMatchingKeyBlock(key_block, key_index)) + return particle_pools[i]; + + return 0; +} + +void afxChoreographer::registerParticlePool(afxParticlePool* pool) +{ + particle_pools.push_back(pool); +} + +void afxChoreographer::unregisterParticlePool(afxParticlePool* pool) +{ + for (S32 i = 0; i < particle_pools.size(); i++) + if (particle_pools[i] == pool) + { + particle_pools.erase_fast(i); + return; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod( afxChoreographer, setRanking, void, ( unsigned int ranking ),, + "Set a ranking value (0-255) for the choreographer.\n" ) +{ + object->setRanking((U8)ranking); +} + +DefineEngineMethod( afxChoreographer, setLevelOfDetail, void, ( unsigned int lod ),, + "Set a level-of-detail value (0-255) for the choreographer.\n" ) +{ + object->setLevelOfDetail((U8)lod); +} + +DefineEngineMethod( afxChoreographer, setExecConditions, void, ( U32 mask ),, + "Set a bitmask to specifiy the state of exec-conditions.\n" ) +{ + object->setExecConditions(afxChoreographer::USER_EXEC_CONDS_MASK & mask); +} + +DefineEngineMethod( afxChoreographer, addConstraint, void, ( const char* source, const char* name),, + "Add a dynamic constraint consistiing of a source and name. The source can be a SceneObject, a 3-valued position, or a 7-valued transform.\n" ) +{ + if (!object->addConstraint(source, name)) + Con::errorf("afxChoreographer::addConstraint() -- failed to resolve constraint source [%s].", source); +} + +DefineEngineMethod( afxChoreographer, addExplicitClient, void, ( NetConnection* client ),, + "Add an explicit client.\n" ) +{ + if (!client) + { + Con::errorf(ConsoleLogEntry::General, "afxChoreographer::addExplicitClient: Failed to resolve client connection"); + return; + } + + object->addExplicitClient(client); +} + +DefineEngineMethod( afxChoreographer, setTriggerBit, void, ( U32 bit_num ),, + "Set a bit of the trigger-mask.\n" ) +{ + U32 set_bit = 1 << bit_num; + object->setTriggerMask(set_bit | object->getTriggerMask()); +} + +DefineEngineMethod( afxChoreographer, clearTriggerBit, void, ( U32 bit_num ),, + "Unset a bit of the trigger-mask.\n" ) +{ + U32 clear_bit = 1 << bit_num; + object->setTriggerMask(~clear_bit & object->getTriggerMask()); +} + +DefineEngineMethod( afxChoreographer, testTriggerBit, bool, ( U32 bit_num ),, + "Test state of a trigger-mask bit.\n" ) +{ + U32 test_bit = 1 << bit_num; + return ((test_bit & object->getTriggerMask()) != 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +// CONSTRAINT REMAPPING << + +void afxChoreographer::remapObjectConstraint(SceneObject* object, const char* cons_name) +{ + if (!object || !cons_name) + return; + + // must be a constraint-def with a matching name in the list + dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name); + if (!dyn_def) + { + Con::errorf("afxChoreographer::remapObjectConstraint() -- failed to find constraint name [%s].", cons_name); + return; + } + + // constraint-def must have matching name constraint-type + if (dyn_def->cons_type != OBJECT_CONSTRAINT) + { + Con::errorf("afxChoreographer::remapObjectConstraint() -- remapped contraint type does not match existing constraint type."); + return; + } + + // nothing to do if new object is same as old + if (dyn_def->cons_obj.object == object) + { +#ifdef TORQUE_DEBUG + Con::warnf("afxChoreographer::remapObjectConstraint() -- remapped contraint object is same as existing object."); +#endif + return; + } + + dyn_def->cons_obj.object = object; + deleteNotify(object); + + constraint_mgr->setReferenceObject(StringTable->insert(cons_name), object); + +#if defined(AFX_CAP_SCOPE_TRACKING) + if (isServerObject() && object->isScopeable()) + constraint_mgr->addScopeableObject(object); +#endif + + if (isServerObject()) + { + if (remapped_cons_sent) + { + remapped_cons_defs.clear(); + remapped_cons_sent = false; + } + remapped_cons_defs.push_back(dyn_def); + setMaskBits(RemapConstraintMask); + } +} + +void afxChoreographer::remapObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape) +{ + if (!cons_name) + return; + + // must be a constraint-def with a matching name in the list + dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name); + if (!dyn_def) + { + Con::errorf("afxChoreographer::remapObjectConstraint() -- failed to find constraint name [%s].", cons_name); + return; + } + + // constraint-def must have matching name constraint-type + if (dyn_def->cons_type != OBJECT_CONSTRAINT) + { + Con::errorf("afxChoreographer::remapObjectConstraint() -- remapped contraint type does not match existing constraint type."); + return; + } + + constraint_mgr->setReferenceObjectByScopeId(StringTable->insert(cons_name), scope_id, is_shape); +} + +void afxChoreographer::remapPointConstraint(Point3F& point, const char* cons_name) +{ + // must be a constraint-def with a matching name in the list + dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name); + if (!dyn_def) + { + Con::errorf("afxChoreographer::remapPointConstraint() -- failed to find constraint name [%s].", cons_name); + return; + } + + // constraint-def must have matching name constraint-type + if (dyn_def->cons_type != POINT_CONSTRAINT) + { + Con::errorf("afxChoreographer::remapPointConstraint() -- remapped contraint type does not match existing constraint type."); + return; + } + + *dyn_def->cons_obj.point = point; + + constraint_mgr->setReferencePoint(StringTable->insert(cons_name), point); + + if (isServerObject()) + { + if (remapped_cons_sent) + { + remapped_cons_defs.clear(); + remapped_cons_sent = false; + } + remapped_cons_defs.push_back(dyn_def); + setMaskBits(RemapConstraintMask); + } +} + +void afxChoreographer::remapTransformConstraint(MatrixF& xfm, const char* cons_name) +{ + // must be a constraint-def with a matching name in the list + dynConstraintDef* dyn_def = find_cons_def_by_name(cons_name); + if (!dyn_def) + { + Con::errorf("afxChoreographer::remapTransformConstraint() -- failed to find constraint name [%s].", cons_name); + return; + } + + // constraint-def must have matching name constraint-type + if (dyn_def->cons_type != POINT_CONSTRAINT) + { + Con::errorf("afxChoreographer::remapTransformConstraint() -- remapped contraint type does not match existing constraint type."); + return; + } + + *dyn_def->cons_obj.xfm = xfm; + + constraint_mgr->setReferenceTransform(StringTable->insert(cons_name), xfm); + + if (isServerObject()) + { + if (remapped_cons_sent) + { + remapped_cons_defs.clear(); + remapped_cons_sent = false; + } + remapped_cons_defs.push_back(dyn_def); + setMaskBits(RemapConstraintMask); + } +} + +bool afxChoreographer::remapConstraint(const char* source_spec, const char* cons_name) +{ + SceneObject* scn_obj; + Point3F pos; + MatrixF xfm; + + switch (resolve_cons_spec(source_spec, pos, xfm, &scn_obj)) + { + case TRANSFORM_CONSTRAINT: + //addTransformConstraint(xfm, cons_name); + return true; + case POINT_CONSTRAINT: + //addPointConstraint(pos, cons_name); + return true; + case OBJECT_CONSTRAINT: + remapObjectConstraint(scn_obj, cons_name); + return true; + } + + return false; +} + +DefineEngineMethod( afxChoreographer, remapConstraint, void, ( const char* source, const char* name),, + "Remap a dynamic constraint to use a new source. The source can be a SceneObject, a 3-valued position, or a 7-valued transform. but must match type of existing source.\n" ) +{ + if (!object->remapConstraint(source, name)) + Con::errorf("afxChoreographer::remapConstraint() -- failed to resolve constraint source [%s].", source); +} + +// CONSTRAINT REMAPPING >> + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/afxChoreographer.h b/Engine/source/afx/afxChoreographer.h new file mode 100644 index 000000000..3665e1306 --- /dev/null +++ b/Engine/source/afx/afxChoreographer.h @@ -0,0 +1,222 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_CHOREOGRAPHER_H_ +#define _AFX_CHOREOGRAPHER_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afxEffectDefs.h" +#include "afxEffectWrapper.h" +#include "afxMagicMissile.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxChoreographerData + +class afxChoreographerData : public GameBaseData, public afxEffectDefs +{ + typedef GameBaseData Parent; + +public: + bool exec_on_new_clients; + U8 echo_packet_usage; + StringTableEntry client_script_file; + StringTableEntry client_init_func; + +public: + /*C*/ afxChoreographerData(); + /*C*/ afxChoreographerData(const afxChoreographerData&, bool = false); + + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxChoreographerData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxChoreographer + +class afxConstraint; +class afxConstraintMgr; +class afxEffectWrapper; +class afxParticlePool; +class afxParticlePoolData; +class SimSet; +class afxForceSetMgr; + +class afxChoreographer : public GameBase, public afxEffectDefs, public afxMagicMissileCallback +{ + typedef GameBase Parent; + +public: + enum MaskBits + { + TriggerMask = Parent::NextFreeMask << 0, + RemapConstraintMask = Parent::NextFreeMask << 1, // CONSTRAINT REMAPPING + NextFreeMask = Parent::NextFreeMask << 2 + }; + + enum + { + USER_EXEC_CONDS_MASK = 0x00ffffff + }; + +protected: + struct dynConstraintDef + { + StringTableEntry cons_name; + U8 cons_type; + union + { + SceneObject* object; + Point3F* point; + MatrixF* xfm; + U16 scope_id; + } cons_obj; + }; + +private: + afxChoreographerData* datablock; + SimSet named_effects; + SimObject* exeblock; + afxForceSetMgr* force_set_mgr; + Vector particle_pools; + Vector dc_defs_a; + Vector dc_defs_b; + GameBase* proc_after_obj; + U32 trigger_mask; + +protected: + Vector* dyn_cons_defs; + Vector* dyn_cons_defs2; + afxConstraintMgr* constraint_mgr; + U32 choreographer_id; + U8 ranking; + U8 lod; + U32 exec_conds_mask; + SimObject* extra; + Vector explicit_clients; + bool started_with_newop; + bool postpone_activation; + + virtual void pack_constraint_info(NetConnection* conn, BitStream* stream); + virtual void unpack_constraint_info(NetConnection* conn, BitStream* stream); + void setup_dynamic_constraints(); + void check_packet_usage(NetConnection*, BitStream*, S32 mark_stream_pos, const char* msg_tag); + SceneObject* get_camera(Point3F* cam_pos=0) const; + +public: + /*C*/ afxChoreographer(); + virtual ~afxChoreographer(); + + static void initPersistFields(); + + virtual bool onAdd(); + virtual void onRemove(); + virtual void onDeleteNotify(SimObject*); + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + virtual U32 packUpdate(NetConnection*, U32, BitStream*); + virtual void unpackUpdate(NetConnection*, BitStream*); + + virtual void sync_with_clients() { } + + afxConstraintMgr* getConstraintMgr() { return constraint_mgr; } + afxForceSetMgr* getForceSetMgr() { return force_set_mgr; } + + afxParticlePool* findParticlePool(afxParticlePoolData* key_block, U32 key_index); + void registerParticlePool(afxParticlePool*); + void unregisterParticlePool(afxParticlePool*); + + void setRanking(U8 value) { ranking = value; } + U8 getRanking() const { return ranking; } + bool testRanking(U8 low, U8 high) { return (ranking <= high && ranking >= low); } + void setLevelOfDetail(U8 value) { lod = value; } + U8 getLevelOfDetail() const { return lod; } + bool testLevelOfDetail(U8 low, U8 high) { return (lod <= high && lod >= low); } + void setExecConditions(U32 mask) { exec_conds_mask = mask; } + U32 getExecConditions() const { return exec_conds_mask; } + + virtual void executeScriptEvent(const char* method, afxConstraint*, + const MatrixF& xfm, const char* data); + + virtual void inflictDamage(const char * label, const char* flavor, SimObjectId target, + F32 amt, U8 count, F32 ad_amt, F32 rad, Point3F pos, F32 imp) { } + + void addObjectConstraint(SceneObject*, const char* cons_name); + void addObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape); + void addPointConstraint(Point3F&, const char* cons_name); + void addTransformConstraint(MatrixF&, const char* cons_name); + bool addConstraint(const char* source_spec, const char* cons_name); + + void addNamedEffect(afxEffectWrapper*); + void removeNamedEffect(afxEffectWrapper*); + afxEffectWrapper* findNamedEffect(StringTableEntry); + + void clearChoreographerId() { choreographer_id = 0; } + U32 getChoreographerId() { return choreographer_id; } + void setGhostConstraintObject(SceneObject*, StringTableEntry cons_name); + void setExtra(SimObject* extra) { this->extra = extra; } + void addExplicitClient(NetConnection* conn); + void removeExplicitClient(NetConnection* conn); + U32 getExplicitClientCount() { return explicit_clients.size(); } + + void restoreScopedObject(SceneObject* obj); + virtual void restoreObject(SceneObject*) { }; + + void postProcessAfterObject(GameBase* obj); + U32 getTriggerMask() const { return trigger_mask; } + void setTriggerMask(U32 trigger_mask); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// +// missile watcher callbacks +public: + virtual void impactNotify(const Point3F& p, const Point3F& n, SceneObject*) { } + + DECLARE_CONOBJECT(afxChoreographer); + DECLARE_CATEGORY("AFX"); + + // CONSTRAINT REMAPPING << +protected: + Vector remapped_cons_defs; + bool remapped_cons_sent; + virtual bool remap_builtin_constraint(SceneObject*, const char* cons_name) { return false; } + dynConstraintDef* find_cons_def_by_name(const char* cons_name); +public: + void remapObjectConstraint(SceneObject*, const char* cons_name); + void remapObjectConstraint(U16 scope_id, const char* cons_name, bool is_shape); + void remapPointConstraint(Point3F&, const char* cons_name); + void remapTransformConstraint(MatrixF&, const char* cons_name); + bool remapConstraint(const char* source_spec, const char* cons_name); + // CONSTRAINT REMAPPING >> +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_CHOREOGRAPHER_H_ diff --git a/Engine/source/afx/afxConstraint.cpp b/Engine/source/afx/afxConstraint.cpp new file mode 100644 index 000000000..78a506c02 --- /dev/null +++ b/Engine/source/afx/afxConstraint.cpp @@ -0,0 +1,2613 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "arcaneFX.h" + +#include "T3D/aiPlayer.h" +#include "T3D/tsStatic.h" +#include "sim/netConnection.h" +#include "ts/tsShapeInstance.h" + +#include "afxConstraint.h" +#include "afxChoreographer.h" +#include "afxEffectWrapper.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxConstraintDef + +// static +StringTableEntry afxConstraintDef::SCENE_CONS_KEY; +StringTableEntry afxConstraintDef::EFFECT_CONS_KEY; +StringTableEntry afxConstraintDef::GHOST_CONS_KEY; + +afxConstraintDef::afxConstraintDef() +{ + if (SCENE_CONS_KEY == 0) + { + SCENE_CONS_KEY = StringTable->insert("#scene"); + EFFECT_CONS_KEY = StringTable->insert("#effect"); + GHOST_CONS_KEY = StringTable->insert("#ghost"); + } + + reset(); +} + +bool afxConstraintDef::isDefined() +{ + return (def_type != CONS_UNDEFINED); +} + +bool afxConstraintDef::isArbitraryObject() +{ + return ((cons_src_name != ST_NULLSTRING) && (def_type == CONS_SCENE)); +} + +void afxConstraintDef::reset() +{ + cons_src_name = ST_NULLSTRING; + cons_node_name = ST_NULLSTRING; + def_type = CONS_UNDEFINED; + history_time = 0; + sample_rate = 30; + runs_on_server = false; + runs_on_client = false; + pos_at_box_center = false; + treat_as_camera = false; +} + +bool afxConstraintDef::parseSpec(const char* spec, bool runs_on_server, + bool runs_on_client) +{ + reset(); + + if (spec == 0 || spec[0] == '\0') + return false; + + history_time = 0.0f; + sample_rate = 30; + + this->runs_on_server = runs_on_server; + this->runs_on_client = runs_on_client; + + // spec should be in one of these forms: + // CONSTRAINT_NAME (only) + // CONSTRAINT_NAME.NODE (shapeBase objects only) + // CONSTRAINT_NAME.#center + // object.OBJECT_NAME + // object.OBJECT_NAME.NODE (shapeBase objects only) + // object.OBJECT_NAME.#center + // effect.EFFECT_NAME + // effect.EFFECT_NAME.NODE + // effect.EFFECT_NAME.#center + // #ghost.EFFECT_NAME + // #ghost.EFFECT_NAME.NODE + // #ghost.EFFECT_NAME.#center + // + + // create scratch buffer by duplicating spec. + char special = '\b'; + char* buffer = dStrdup(spec); + + // substitute a dots not inside parens with special character + S32 n_nested = 0; + for (char* b = buffer; (*b) != '\0'; b++) + { + if ((*b) == '(') + n_nested++; + else if ((*b) == ')') + n_nested--; + else if ((*b) == '.' && n_nested == 0) + (*b) = special; + } + + // divide name into '.' separated tokens (up to 8) + char* words[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + char* dot = buffer; + int wdx = 0; + while (wdx < 8) + { + words[wdx] = dot; + dot = dStrchr(words[wdx++], special); + if (!dot) + break; + *(dot++) = '\0'; + if ((*dot) == '\0') + break; + } + + int n_words = wdx; + + // at this point the spec has been split into words. + // n_words indicates how many words we have. + + // no words found (must have been all whitespace) + if (n_words < 1) + { + dFree(buffer); + return false; + } + + char* hist_spec = 0; + char* words2[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + int n_words2 = 0; + + // move words to words2 while extracting #center and #history + for (S32 i = 0; i < n_words; i++) + { + if (dStrcmp(words[i], "#center") == 0) + pos_at_box_center = true; + else if (dStrncmp(words[i], "#history(", 9) == 0) + hist_spec = words[i]; + else + words2[n_words2++] = words[i]; + } + + // words2[] now contains just the constraint part + + // no words found (must have been all #center and #history) + if (n_words2 < 1) + { + dFree(buffer); + return false; + } + + if (hist_spec) + { + char* open_paren = dStrchr(hist_spec, '('); + if (open_paren) + { + hist_spec = open_paren+1; + if ((*hist_spec) != '\0') + { + char* close_paren = dStrchr(hist_spec, ')'); + if (close_paren) + (*close_paren) = '\0'; + char* slash = dStrchr(hist_spec, '/'); + if (slash) + (*slash) = ' '; + + F32 hist_age = 0.0; + U32 hist_rate = 30; + S32 args = dSscanf(hist_spec,"%g %d", &hist_age, &hist_rate); + + if (args > 0) + history_time = hist_age; + if (args > 1) + sample_rate = hist_rate; + } + } + } + + StringTableEntry cons_name_key = StringTable->insert(words2[0]); + + // must be in CONSTRAINT_NAME (only) form + if (n_words2 == 1) + { + // arbitrary object/effect constraints must have a name + if (cons_name_key == SCENE_CONS_KEY || cons_name_key == EFFECT_CONS_KEY) + { + dFree(buffer); + return false; + } + + cons_src_name = cons_name_key; + def_type = CONS_PREDEFINED; + dFree(buffer); + return true; + } + + // "#scene.NAME" or "#scene.NAME.NODE"" + if (cons_name_key == SCENE_CONS_KEY) + { + cons_src_name = StringTable->insert(words2[1]); + if (n_words2 > 2) + cons_node_name = StringTable->insert(words2[2]); + def_type = CONS_SCENE; + dFree(buffer); + return true; + } + + // "#effect.NAME" or "#effect.NAME.NODE" + if (cons_name_key == EFFECT_CONS_KEY) + { + cons_src_name = StringTable->insert(words2[1]); + if (n_words2 > 2) + cons_node_name = StringTable->insert(words2[2]); + def_type = CONS_EFFECT; + dFree(buffer); + return true; + } + + // "#ghost.NAME" or "#ghost.NAME.NODE" + if (cons_name_key == GHOST_CONS_KEY) + { + if (runs_on_server) + { + dFree(buffer); + return false; + } + + cons_src_name = StringTable->insert(words2[1]); + if (n_words2 > 2) + cons_node_name = StringTable->insert(words2[2]); + def_type = CONS_GHOST; + dFree(buffer); + return true; + } + + // "CONSTRAINT_NAME.NODE" + if (n_words2 == 2) + { + cons_src_name = cons_name_key; + cons_node_name = StringTable->insert(words2[1]); + def_type = CONS_PREDEFINED; + dFree(buffer); + return true; + } + + // must be in unsupported form + dFree(buffer); + return false; +} + +void afxConstraintDef::gather_cons_defs(Vector& defs, afxEffectList& fx) +{ + for (S32 i = 0; i < fx.size(); i++) + { + if (fx[i]) + fx[i]->gather_cons_defs(defs); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxConstraint + +afxConstraint::afxConstraint(afxConstraintMgr* mgr) +{ + this->mgr = mgr; + is_defined = false; + is_valid = false; + last_pos.zero(); + last_xfm.identity(); + history_time = 0.0f; + is_alive = true; + gone_missing = false; + change_code = 0; +} + +afxConstraint::~afxConstraint() +{ +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +inline afxPointConstraint* newPointCons(afxConstraintMgr* mgr, bool hist) +{ + return (hist) ? new afxPointHistConstraint(mgr) : new afxPointConstraint(mgr); +} + +inline afxTransformConstraint* newTransformCons(afxConstraintMgr* mgr, bool hist) +{ + return (hist) ? new afxTransformHistConstraint(mgr) : new afxTransformConstraint(mgr); +} + +inline afxShapeConstraint* newShapeCons(afxConstraintMgr* mgr, bool hist) +{ + return (hist) ? new afxShapeHistConstraint(mgr) : new afxShapeConstraint(mgr); +} + +inline afxShapeConstraint* newShapeCons(afxConstraintMgr* mgr, StringTableEntry name, bool hist) +{ + return (hist) ? new afxShapeHistConstraint(mgr, name) : new afxShapeConstraint(mgr, name); +} + +inline afxShapeNodeConstraint* newShapeNodeCons(afxConstraintMgr* mgr, StringTableEntry name, StringTableEntry node, bool hist) +{ + return (hist) ? new afxShapeNodeHistConstraint(mgr, name, node) : new afxShapeNodeConstraint(mgr, name, node); +} + +inline afxObjectConstraint* newObjectCons(afxConstraintMgr* mgr, bool hist) +{ + return (hist) ? new afxObjectHistConstraint(mgr) : new afxObjectConstraint(mgr); +} + +inline afxObjectConstraint* newObjectCons(afxConstraintMgr* mgr, StringTableEntry name, bool hist) +{ + return (hist) ? new afxObjectHistConstraint(mgr, name) : new afxObjectConstraint(mgr, name); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxConstraintMgr + +#define CONS_BY_ID(id) ((*constraints_v[(id).index])[(id).sub_index]) +#define CONS_BY_IJ(i,j) ((*constraints_v[(i)])[(j)]) + +afxConstraintMgr::afxConstraintMgr() +{ + starttime = 0; + on_server = false; + initialized = false; + scoping_dist_sq = 1000.0f*1000.0f; + missing_objs = &missing_objs_a; + missing_objs2 = &missing_objs_b; +} + +afxConstraintMgr::~afxConstraintMgr() +{ + for (S32 i = 0; i < constraints_v.size(); i++) + { + for (S32 j = 0; j < (*constraints_v[i]).size(); j++) + delete CONS_BY_IJ(i,j); + delete constraints_v[i]; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +S32 afxConstraintMgr::find_cons_idx_from_name(StringTableEntry which) +{ + for (S32 i = 0; i < constraints_v.size(); i++) + { + afxConstraint* cons = CONS_BY_IJ(i,0); + if (cons && afxConstraintDef::CONS_EFFECT != cons->cons_def.def_type && + which == cons->cons_def.cons_src_name) + { + return i; + } + } + + return -1; +} + +S32 afxConstraintMgr::find_effect_cons_idx_from_name(StringTableEntry which) +{ + for (S32 i = 0; i < constraints_v.size(); i++) + { + afxConstraint* cons = CONS_BY_IJ(i,0); + if (cons && afxConstraintDef::CONS_EFFECT == cons->cons_def.def_type && + which == cons->cons_def.cons_src_name) + { + return i; + } + } + + return -1; +} + +// Defines a predefined constraint with given name and type +void afxConstraintMgr::defineConstraint(U32 type, StringTableEntry name) +{ + preDef predef = { name, type }; + predefs.push_back(predef); +} + +afxConstraintID afxConstraintMgr::setReferencePoint(StringTableEntry which, Point3F point, + Point3F vector) +{ + S32 idx = find_cons_idx_from_name(which); + if (idx < 0) + return afxConstraintID(); + + afxConstraintID id = afxConstraintID(idx); + setReferencePoint(id, point, vector); + + return id; +} + +afxConstraintID afxConstraintMgr::setReferenceTransform(StringTableEntry which, MatrixF& xfm) +{ + S32 idx = find_cons_idx_from_name(which); + if (idx < 0) + return afxConstraintID(); + + afxConstraintID id = afxConstraintID(idx); + setReferenceTransform(id, xfm); + + return id; +} + +// Assigns an existing scene-object to the named constraint +afxConstraintID afxConstraintMgr::setReferenceObject(StringTableEntry which, SceneObject* obj) +{ + S32 idx = find_cons_idx_from_name(which); + if (idx < 0) + return afxConstraintID(); + + afxConstraintID id = afxConstraintID(idx); + setReferenceObject(id, obj); + + return id; +} + +// Assigns an un-scoped scene-object by scope_id to the named constraint +afxConstraintID afxConstraintMgr::setReferenceObjectByScopeId(StringTableEntry which, U16 scope_id, bool is_shape) +{ + S32 idx = find_cons_idx_from_name(which); + if (idx < 0) + return afxConstraintID(); + + afxConstraintID id = afxConstraintID(idx); + setReferenceObjectByScopeId(id, scope_id, is_shape); + + return id; +} + +afxConstraintID afxConstraintMgr::setReferenceEffect(StringTableEntry which, afxEffectWrapper* ew) +{ + S32 idx = find_effect_cons_idx_from_name(which); + + if (idx < 0) + return afxConstraintID(); + + afxConstraintID id = afxConstraintID(idx); + setReferenceEffect(id, ew); + + return id; +} + +afxConstraintID afxConstraintMgr::createReferenceEffect(StringTableEntry which, afxEffectWrapper* ew) +{ + afxEffectConstraint* cons = new afxEffectConstraint(this, which); + //cons->cons_def = def; + cons->cons_def.def_type = afxConstraintDef::CONS_EFFECT; + cons->cons_def.cons_src_name = which; + afxConstraintList* list = new afxConstraintList(); + list->push_back(cons); + constraints_v.push_back(list); + + return setReferenceEffect(which, ew); +} + +void afxConstraintMgr::setReferencePoint(afxConstraintID id, Point3F point, Point3F vector) +{ + afxPointConstraint* pt_cons = dynamic_cast(CONS_BY_ID(id)); + + // need to change type + if (!pt_cons) + { + afxConstraint* cons = CONS_BY_ID(id); + pt_cons = newPointCons(this, cons->cons_def.history_time > 0.0f); + pt_cons->cons_def = cons->cons_def; + CONS_BY_ID(id) = pt_cons; + delete cons; + } + + pt_cons->set(point, vector); + + // nullify all subnodes + for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++) + { + afxConstraint* cons = CONS_BY_IJ(id.index,j); + if (cons) + cons->unset(); + } +} + +void afxConstraintMgr::setReferenceTransform(afxConstraintID id, MatrixF& xfm) +{ + afxTransformConstraint* xfm_cons = dynamic_cast(CONS_BY_ID(id)); + + // need to change type + if (!xfm_cons) + { + afxConstraint* cons = CONS_BY_ID(id); + xfm_cons = newTransformCons(this, cons->cons_def.history_time > 0.0f); + xfm_cons->cons_def = cons->cons_def; + CONS_BY_ID(id) = xfm_cons; + delete cons; + } + + xfm_cons->set(xfm); + + // nullify all subnodes + for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++) + { + afxConstraint* cons = CONS_BY_IJ(id.index,j); + if (cons) + cons->unset(); + } +} + +void afxConstraintMgr::set_ref_shape(afxConstraintID id, ShapeBase* shape) +{ + id.sub_index = 0; + + afxShapeConstraint* shape_cons = dynamic_cast(CONS_BY_ID(id)); + + // need to change type + if (!shape_cons) + { + afxConstraint* cons = CONS_BY_ID(id); + shape_cons = newShapeCons(this, cons->cons_def.history_time > 0.0f); + shape_cons->cons_def = cons->cons_def; + CONS_BY_ID(id) = shape_cons; + delete cons; + } + + // set new shape on root + shape_cons->set(shape); + + // update all subnodes + for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++) + { + afxConstraint* cons = CONS_BY_IJ(id.index,j); + if (cons) + { + if (dynamic_cast(cons)) + ((afxShapeNodeConstraint*)cons)->set(shape); + else if (dynamic_cast(cons)) + ((afxShapeConstraint*)cons)->set(shape); + else if (dynamic_cast(cons)) + ((afxObjectConstraint*)cons)->set(shape); + else + cons->unset(); + } + } +} + +void afxConstraintMgr::set_ref_shape(afxConstraintID id, U16 scope_id) +{ + id.sub_index = 0; + + afxShapeConstraint* shape_cons = dynamic_cast(CONS_BY_ID(id)); + + // need to change type + if (!shape_cons) + { + afxConstraint* cons = CONS_BY_ID(id); + shape_cons = newShapeCons(this, cons->cons_def.history_time > 0.0f); + shape_cons->cons_def = cons->cons_def; + CONS_BY_ID(id) = shape_cons; + delete cons; + } + + // set new shape on root + shape_cons->set_scope_id(scope_id); + + // update all subnodes + for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++) + { + afxConstraint* cons = CONS_BY_IJ(id.index,j); + if (cons) + cons->set_scope_id(scope_id); + } +} + +// Assigns an existing scene-object to the constraint matching the given constraint-id. +void afxConstraintMgr::setReferenceObject(afxConstraintID id, SceneObject* obj) +{ + if (!initialized) + Con::errorf("afxConstraintMgr::setReferenceObject() -- constraint manager not initialized"); + + if (!CONS_BY_ID(id)->cons_def.treat_as_camera) + { + ShapeBase* shape = dynamic_cast(obj); + if (shape) + { + set_ref_shape(id, shape); + return; + } + } + + afxObjectConstraint* obj_cons = dynamic_cast(CONS_BY_ID(id)); + + // need to change type + if (!obj_cons) + { + afxConstraint* cons = CONS_BY_ID(id); + obj_cons = newObjectCons(this, cons->cons_def.history_time > 0.0f); + obj_cons->cons_def = cons->cons_def; + CONS_BY_ID(id) = obj_cons; + delete cons; + } + + obj_cons->set(obj); + + // update all subnodes + for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++) + { + afxConstraint* cons = CONS_BY_IJ(id.index,j); + if (cons) + { + if (dynamic_cast(cons)) + ((afxObjectConstraint*)cons)->set(obj); + else + cons->unset(); + } + } +} + +// Assigns an un-scoped scene-object by scope_id to the constraint matching the +// given constraint-id. +void afxConstraintMgr::setReferenceObjectByScopeId(afxConstraintID id, U16 scope_id, bool is_shape) +{ + if (!initialized) + Con::errorf("afxConstraintMgr::setReferenceObject() -- constraint manager not initialized"); + + if (is_shape) + { + set_ref_shape(id, scope_id); + return; + } + + afxObjectConstraint* obj_cons = dynamic_cast(CONS_BY_ID(id)); + + // need to change type + if (!obj_cons) + { + afxConstraint* cons = CONS_BY_ID(id); + obj_cons = newObjectCons(this, cons->cons_def.history_time > 0.0f); + obj_cons->cons_def = cons->cons_def; + CONS_BY_ID(id) = obj_cons; + delete cons; + } + + obj_cons->set_scope_id(scope_id); + + // update all subnodes + for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++) + { + afxConstraint* cons = CONS_BY_IJ(id.index,j); + if (cons) + cons->set_scope_id(scope_id); + } +} + +void afxConstraintMgr::setReferenceEffect(afxConstraintID id, afxEffectWrapper* ew) +{ + afxEffectConstraint* eff_cons = dynamic_cast(CONS_BY_ID(id)); + if (!eff_cons) + return; + + eff_cons->set(ew); + + // update all subnodes + for (S32 j = 1; j < (*constraints_v[id.index]).size(); j++) + { + afxConstraint* cons = CONS_BY_IJ(id.index,j); + if (cons) + { + if (dynamic_cast(cons)) + ((afxEffectNodeConstraint*)cons)->set(ew); + else if (dynamic_cast(cons)) + ((afxEffectConstraint*)cons)->set(ew); + else + cons->unset(); + } + } +} + +void afxConstraintMgr::invalidateReference(afxConstraintID id) +{ + afxConstraint* cons = CONS_BY_ID(id); + if (cons) + cons->is_valid = false; +} + +void afxConstraintMgr::create_constraint(const afxConstraintDef& def) +{ + if (def.def_type == afxConstraintDef::CONS_UNDEFINED) + return; + + //Con::printf("CON - %s [%s] [%s] h=%g", def.cons_type_name, def.cons_src_name, def.cons_node_name, def.history_time); + + bool want_history = (def.history_time > 0.0f); + + // constraint is an arbitrary named scene object + // + if (def.def_type == afxConstraintDef::CONS_SCENE) + { + if (def.cons_src_name == ST_NULLSTRING) + return; + + // find the arbitrary object by name + SceneObject* arb_obj; + if (on_server) + { + arb_obj = dynamic_cast(Sim::findObject(def.cons_src_name)); + if (!arb_obj) + Con::errorf("afxConstraintMgr -- failed to find scene constraint source, \"%s\" on server.", + def.cons_src_name); + } + else + { + arb_obj = find_object_from_name(def.cons_src_name); + if (!arb_obj) + Con::errorf("afxConstraintMgr -- failed to find scene constraint source, \"%s\" on client.", + def.cons_src_name); + } + + // if it's a shapeBase object, create a Shape or ShapeNode constraint + if (dynamic_cast(arb_obj)) + { + if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center) + { + afxShapeConstraint* cons = newShapeCons(this, def.cons_src_name, want_history); + cons->cons_def = def; + cons->set((ShapeBase*)arb_obj); + afxConstraintList* list = new afxConstraintList(); + list->push_back(cons); + constraints_v.push_back(list); + } + else if (def.pos_at_box_center) + { + afxShapeConstraint* cons = newShapeCons(this, def.cons_src_name, want_history); + cons->cons_def = def; + cons->set((ShapeBase*)arb_obj); + afxConstraintList* list = constraints_v[constraints_v.size()-1]; // SHAPE-NODE CONS-LIST (#scene)(#center) + if (list && (*list)[0]) + list->push_back(cons); + } + else + { + afxShapeNodeConstraint* sub = newShapeNodeCons(this, def.cons_src_name, def.cons_node_name, want_history); + sub->cons_def = def; + sub->set((ShapeBase*)arb_obj); + afxConstraintList* list = constraints_v[constraints_v.size()-1]; + if (list && (*list)[0]) + list->push_back(sub); + } + } + // if it's not a shapeBase object, create an Object constraint + else if (arb_obj) + { + if (!def.pos_at_box_center) + { + afxObjectConstraint* cons = newObjectCons(this, def.cons_src_name, want_history); + cons->cons_def = def; + cons->set(arb_obj); + afxConstraintList* list = new afxConstraintList(); // OBJECT CONS-LIST (#scene) + list->push_back(cons); + constraints_v.push_back(list); + } + else // if (def.pos_at_box_center) + { + afxObjectConstraint* cons = newObjectCons(this, def.cons_src_name, want_history); + cons->cons_def = def; + cons->set(arb_obj); + afxConstraintList* list = constraints_v[constraints_v.size()-1]; // OBJECT CONS-LIST (#scene)(#center) + if (list && (*list)[0]) + list->push_back(cons); + } + } + } + + // constraint is an arbitrary named effect + // + else if (def.def_type == afxConstraintDef::CONS_EFFECT) + { + if (def.cons_src_name == ST_NULLSTRING) + return; + + // create an Effect constraint + if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center) + { + afxEffectConstraint* cons = new afxEffectConstraint(this, def.cons_src_name); + cons->cons_def = def; + afxConstraintList* list = new afxConstraintList(); + list->push_back(cons); + constraints_v.push_back(list); + } + // create an Effect #center constraint + else if (def.pos_at_box_center) + { + afxEffectConstraint* cons = new afxEffectConstraint(this, def.cons_src_name); + cons->cons_def = def; + afxConstraintList* list = constraints_v[constraints_v.size()-1]; // EFFECT-NODE CONS-LIST (#effect) + if (list && (*list)[0]) + list->push_back(cons); + } + // create an EffectNode constraint + else + { + afxEffectNodeConstraint* sub = new afxEffectNodeConstraint(this, def.cons_src_name, def.cons_node_name); + sub->cons_def = def; + afxConstraintList* list = constraints_v[constraints_v.size()-1]; + if (list && (*list)[0]) + list->push_back(sub); + } + } + + // constraint is a predefined constraint + // + else + { + afxConstraint* cons = 0; + afxConstraint* cons_ctr = 0; + afxConstraint* sub = 0; + + if (def.def_type == afxConstraintDef::CONS_GHOST) + { + for (S32 i = 0; i < predefs.size(); i++) + { + if (predefs[i].name == def.cons_src_name) + { + if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center) + { + cons = newShapeCons(this, want_history); + cons->cons_def = def; + } + else if (def.pos_at_box_center) + { + cons_ctr = newShapeCons(this, want_history); + cons_ctr->cons_def = def; + } + else + { + sub = newShapeNodeCons(this, ST_NULLSTRING, def.cons_node_name, want_history); + sub->cons_def = def; + } + break; + } + } + } + else + { + for (S32 i = 0; i < predefs.size(); i++) + { + if (predefs[i].name == def.cons_src_name) + { + switch (predefs[i].type) + { + case POINT_CONSTRAINT: + cons = newPointCons(this, want_history); + cons->cons_def = def; + break; + case TRANSFORM_CONSTRAINT: + cons = newTransformCons(this, want_history); + cons->cons_def = def; + break; + case OBJECT_CONSTRAINT: + if (def.cons_node_name == ST_NULLSTRING && !def.pos_at_box_center) + { + cons = newShapeCons(this, want_history); + cons->cons_def = def; + } + else if (def.pos_at_box_center) + { + cons_ctr = newShapeCons(this, want_history); + cons_ctr->cons_def = def; + } + else + { + sub = newShapeNodeCons(this, ST_NULLSTRING, def.cons_node_name, want_history); + sub->cons_def = def; + } + break; + case CAMERA_CONSTRAINT: + cons = newObjectCons(this, want_history); + cons->cons_def = def; + cons->cons_def.treat_as_camera = true; + break; + } + break; + } + } + } + + if (cons) + { + afxConstraintList* list = new afxConstraintList(); + list->push_back(cons); + constraints_v.push_back(list); + } + else if (cons_ctr && constraints_v.size() > 0) + { + afxConstraintList* list = constraints_v[constraints_v.size()-1]; // PREDEF-NODE CONS-LIST + if (list && (*list)[0]) + list->push_back(cons_ctr); + } + else if (sub && constraints_v.size() > 0) + { + afxConstraintList* list = constraints_v[constraints_v.size()-1]; + if (list && (*list)[0]) + list->push_back(sub); + } + else + Con::printf("predef not found %s", def.cons_src_name); + } +} + +afxConstraintID afxConstraintMgr::getConstraintId(const afxConstraintDef& def) +{ + if (def.def_type == afxConstraintDef::CONS_UNDEFINED) + return afxConstraintID(); + + if (def.cons_src_name != ST_NULLSTRING) + { + for (S32 i = 0; i < constraints_v.size(); i++) + { + afxConstraintList* list = constraints_v[i]; + afxConstraint* cons = (*list)[0]; + if (def.cons_src_name == cons->cons_def.cons_src_name) + { + for (S32 j = 0; j < list->size(); j++) + { + afxConstraint* sub = (*list)[j]; + if (def.cons_node_name == sub->cons_def.cons_node_name && + def.pos_at_box_center == sub->cons_def.pos_at_box_center && + def.cons_src_name == sub->cons_def.cons_src_name) + { + return afxConstraintID(i, j); + } + } + + // if we're here, it means the root object name matched but the node name + // did not. + if (def.def_type == afxConstraintDef::CONS_PREDEFINED && !def.pos_at_box_center) + { + afxShapeConstraint* shape_cons = dynamic_cast(cons); + if (shape_cons) + { + //Con::errorf("Append a Node constraint [%s.%s] [%d,%d]", def.cons_src_name, def.cons_node_name, i, list->size()); + bool want_history = (def.history_time > 0.0f); + afxConstraint* sub = newShapeNodeCons(this, ST_NULLSTRING, def.cons_node_name, want_history); + sub->cons_def = def; + ((afxShapeConstraint*)sub)->set(shape_cons->shape); + list->push_back(sub); + + return afxConstraintID(i, list->size()-1); + } + } + + break; + } + } + } + + return afxConstraintID(); +} + +afxConstraint* afxConstraintMgr::getConstraint(afxConstraintID id) +{ + if (id.undefined()) + return 0; + + afxConstraint* cons = CONS_BY_IJ(id.index,id.sub_index); + if (cons && !cons->isDefined()) + return NULL; + + return cons; +} + +void afxConstraintMgr::sample(F32 dt, U32 now, const Point3F* cam_pos) +{ + U32 elapsed = now - starttime; + + for (S32 i = 0; i < constraints_v.size(); i++) + { + afxConstraintList* list = constraints_v[i]; + for (S32 j = 0; j < list->size(); j++) + (*list)[j]->sample(dt, elapsed, cam_pos); + } +} + +S32 QSORT_CALLBACK cmp_cons_defs(const void* a, const void* b) +{ + afxConstraintDef* def_a = (afxConstraintDef*) a; + afxConstraintDef* def_b = (afxConstraintDef*) b; + + if (def_a->def_type == def_b->def_type) + { + if (def_a->cons_src_name == def_b->cons_src_name) + { + if (def_a->pos_at_box_center == def_b->pos_at_box_center) + return (def_a->cons_node_name - def_b->cons_node_name); + else + return (def_a->pos_at_box_center) ? 1 : -1; + } + return (def_a->cons_src_name - def_b->cons_src_name); + } + + return (def_a->def_type - def_b->def_type); +} + +void afxConstraintMgr::initConstraintDefs(Vector& all_defs, bool on_server, F32 scoping_dist) +{ + initialized = true; + this->on_server = on_server; + + if (scoping_dist > 0.0) + scoping_dist_sq = scoping_dist*scoping_dist; + else + { + SceneManager* sg = (on_server) ? gServerSceneGraph : gClientSceneGraph; + F32 vis_dist = (sg) ? sg->getVisibleDistance() : 1000.0f; + scoping_dist_sq = vis_dist*vis_dist; + } + + if (all_defs.size() < 1) + return; + + // find effect ghost constraints + if (!on_server) + { + Vector ghost_defs; + + for (S32 i = 0; i < all_defs.size(); i++) + if (all_defs[i].def_type == afxConstraintDef::CONS_GHOST && all_defs[i].cons_src_name != ST_NULLSTRING) + ghost_defs.push_back(all_defs[i]); + + if (ghost_defs.size() > 0) + { + // sort the defs + if (ghost_defs.size() > 1) + dQsort(ghost_defs.address(), ghost_defs.size(), sizeof(afxConstraintDef), cmp_cons_defs); + + S32 last = 0; + defineConstraint(OBJECT_CONSTRAINT, ghost_defs[0].cons_src_name); + + for (S32 i = 1; i < ghost_defs.size(); i++) + { + if (ghost_defs[last].cons_src_name != ghost_defs[i].cons_src_name) + { + defineConstraint(OBJECT_CONSTRAINT, ghost_defs[i].cons_src_name); + last++; + } + } + } + } + + Vector defs; + + // collect defs that run here (server or client) + if (on_server) + { + for (S32 i = 0; i < all_defs.size(); i++) + if (all_defs[i].runs_on_server) + defs.push_back(all_defs[i]); + } + else + { + for (S32 i = 0; i < all_defs.size(); i++) + if (all_defs[i].runs_on_client) + defs.push_back(all_defs[i]); + } + + // create unique set of constraints. + // + if (defs.size() > 0) + { + // sort the defs + if (defs.size() > 1) + dQsort(defs.address(), defs.size(), sizeof(afxConstraintDef), cmp_cons_defs); + + Vector unique_defs; + S32 last = 0; + + // manufacture root-object def if absent + if (defs[0].cons_node_name != ST_NULLSTRING) + { + afxConstraintDef root_def = defs[0]; + root_def.cons_node_name = ST_NULLSTRING; + unique_defs.push_back(root_def); + last++; + } + else if (defs[0].pos_at_box_center) + { + afxConstraintDef root_def = defs[0]; + root_def.pos_at_box_center = false; + unique_defs.push_back(root_def); + last++; + } + + unique_defs.push_back(defs[0]); + + for (S32 i = 1; i < defs.size(); i++) + { + if (unique_defs[last].cons_node_name != defs[i].cons_node_name || + unique_defs[last].cons_src_name != defs[i].cons_src_name || + unique_defs[last].pos_at_box_center != defs[i].pos_at_box_center || + unique_defs[last].def_type != defs[i].def_type) + { + // manufacture root-object def if absent + if (defs[i].cons_src_name != ST_NULLSTRING && unique_defs[last].cons_src_name != defs[i].cons_src_name) + { + if (defs[i].cons_node_name != ST_NULLSTRING || defs[i].pos_at_box_center) + { + afxConstraintDef root_def = defs[i]; + root_def.cons_node_name = ST_NULLSTRING; + root_def.pos_at_box_center = false; + unique_defs.push_back(root_def); + last++; + } + } + unique_defs.push_back(defs[i]); + last++; + } + else + { + if (defs[i].history_time > unique_defs[last].history_time) + unique_defs[last].history_time = defs[i].history_time; + if (defs[i].sample_rate > unique_defs[last].sample_rate) + unique_defs[last].sample_rate = defs[i].sample_rate; + } + } + + //Con::printf("\nConstraints on %s", (on_server) ? "server" : "client"); + for (S32 i = 0; i < unique_defs.size(); i++) + create_constraint(unique_defs[i]); + } + + // collect the names of all the arbitrary object constraints + // that run on clients and store in names_on_server array. + // + if (on_server) + { + names_on_server.clear(); + defs.clear(); + + for (S32 i = 0; i < all_defs.size(); i++) + if (all_defs[i].runs_on_client && all_defs[i].isArbitraryObject()) + defs.push_back(all_defs[i]); + + if (defs.size() < 1) + return; + + // sort the defs + if (defs.size() > 1) + dQsort(defs.address(), defs.size(), sizeof(afxConstraintDef), cmp_cons_defs); + + S32 last = 0; + names_on_server.push_back(defs[0].cons_src_name); + + for (S32 i = 1; i < defs.size(); i++) + { + if (names_on_server[last] != defs[i].cons_src_name) + { + names_on_server.push_back(defs[i].cons_src_name); + last++; + } + } + } +} + +void afxConstraintMgr::packConstraintNames(NetConnection* conn, BitStream* stream) +{ + // pack any named constraint names and ghost indices + if (stream->writeFlag(names_on_server.size() > 0)) //-- ANY NAMED CONS_BY_ID? + { + stream->write(names_on_server.size()); + for (S32 i = 0; i < names_on_server.size(); i++) + { + stream->writeString(names_on_server[i]); + NetObject* obj = dynamic_cast(Sim::findObject(names_on_server[i])); + if (!obj) + { + //Con::printf("CONSTRAINT-OBJECT %s does not exist.", names_on_server[i]); + stream->write((S32)-1); + } + else + { + S32 ghost_id = conn->getGhostIndex(obj); + /* + if (ghost_id == -1) + Con::printf("CONSTRAINT-OBJECT %s does not have a ghost.", names_on_server[i]); + else + Con::printf("CONSTRAINT-OBJECT %s name to server.", names_on_server[i]); + */ + stream->write(ghost_id); + } + } + } +} + +void afxConstraintMgr::unpackConstraintNames(BitStream* stream) +{ + if (stream->readFlag()) //-- ANY NAMED CONS_BY_ID? + { + names_on_server.clear(); + S32 sz; stream->read(&sz); + for (S32 i = 0; i < sz; i++) + { + names_on_server.push_back(stream->readSTString()); + S32 ghost_id; stream->read(&ghost_id); + ghost_ids.push_back(ghost_id); + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +SceneObject* afxConstraintMgr::find_object_from_name(StringTableEntry name) +{ + if (names_on_server.size() > 0) + { + for (S32 i = 0; i < names_on_server.size(); i++) + if (names_on_server[i] == name) + { + if (ghost_ids[i] == -1) + return 0; + NetConnection* conn = NetConnection::getConnectionToServer(); + if (!conn) + return 0; + return dynamic_cast(conn->resolveGhost(ghost_ids[i])); + } + } + + return dynamic_cast(Sim::findObject(name)); +} + +void afxConstraintMgr::addScopeableObject(SceneObject* object) +{ + for (S32 i = 0; i < scopeable_objs.size(); i++) + { + if (scopeable_objs[i] == object) + return; + } + + object->addScopeRef(); + scopeable_objs.push_back(object); +} + +void afxConstraintMgr::removeScopeableObject(SceneObject* object) +{ + for (S32 i = 0; i < scopeable_objs.size(); i++) + if (scopeable_objs[i] == object) + { + object->removeScopeRef(); + scopeable_objs.erase_fast(i); + return; + } +} + +void afxConstraintMgr::clearAllScopeableObjs() +{ + for (S32 i = 0; i < scopeable_objs.size(); i++) + scopeable_objs[i]->removeScopeRef(); + scopeable_objs.clear(); +} + +void afxConstraintMgr::postMissingConstraintObject(afxConstraint* cons, bool is_deleting) +{ + if (cons->gone_missing) + return; + + if (!is_deleting) + { + SceneObject* obj = arcaneFX::findScopedObject(cons->getScopeId()); + if (obj) + { + cons->restoreObject(obj); + return; + } + } + + cons->gone_missing = true; + missing_objs->push_back(cons); +} + +void afxConstraintMgr::restoreScopedObject(SceneObject* obj, afxChoreographer* ch) +{ + for (S32 i = 0; i < missing_objs->size(); i++) + { + if ((*missing_objs)[i]->getScopeId() == obj->getScopeId()) + { + (*missing_objs)[i]->gone_missing = false; + (*missing_objs)[i]->restoreObject(obj); + if (ch) + ch->restoreObject(obj); + } + else + missing_objs2->push_back((*missing_objs)[i]); + } + + Vector* tmp = missing_objs; + missing_objs = missing_objs2; + missing_objs2 = tmp; + missing_objs2->clear(); +} + +void afxConstraintMgr::adjustProcessOrdering(afxChoreographer* ch) +{ + Vector cons_sources; + + // add choreographer to the list + cons_sources.push_back(ch); + + // collect all the ProcessObject related constraint sources + for (S32 i = 0; i < constraints_v.size(); i++) + { + afxConstraintList* list = constraints_v[i]; + afxConstraint* cons = (*list)[0]; + if (cons) + { + ProcessObject* pobj = dynamic_cast(cons->getSceneObject()); + if (pobj) + cons_sources.push_back(pobj); + } + } + + ProcessList* proc_list; + if (ch->isServerObject()) + proc_list = ServerProcessList::get(); + else + proc_list = ClientProcessList::get(); + if (!proc_list) + return; + + GameBase* nearest_to_end = dynamic_cast(proc_list->findNearestToEnd(cons_sources)); + GameBase* chor = (GameBase*) ch; + + // choreographer needs to be processed after the latest process object + if (chor != nearest_to_end && nearest_to_end != 0) + { + //Con::printf("Choreographer processing BEFORE some of its constraints... fixing. [%s] -- %s", + // (ch->isServerObject()) ? "S" : "C", nearest_to_end->getClassName()); + if (chor->isProperlyAdded()) + ch->processAfter(nearest_to_end); + else + ch->postProcessAfterObject(nearest_to_end); + } + else + { + //Con::printf("Choreographer processing AFTER its constraints... fine. [%s]", + // (ch->isServerObject()) ? "S" : "C"); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPointConstraint + +afxPointConstraint::afxPointConstraint(afxConstraintMgr* mgr) + : afxConstraint(mgr) +{ + point.zero(); + vector.set(0,0,1); +} + +afxPointConstraint::~afxPointConstraint() +{ +} + +void afxPointConstraint::set(Point3F point, Point3F vector) +{ + this->point = point; + this->vector = vector; + is_defined = true; + is_valid = true; + change_code++; + sample(0.0f, 0, 0); +} + +void afxPointConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) +{ + if (cam_pos) + { + Point3F dir = (*cam_pos) - point; + F32 dist_sq = dir.lenSquared(); + if (dist_sq > mgr->getScopingDistanceSquared()) + { + is_valid = false; + return; + } + is_valid = true; + } + + last_pos = point; + last_xfm.identity(); + last_xfm.setPosition(point); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxTransformConstraint + +afxTransformConstraint::afxTransformConstraint(afxConstraintMgr* mgr) + : afxConstraint(mgr) +{ + xfm.identity(); +} + +afxTransformConstraint::~afxTransformConstraint() +{ +} + +void afxTransformConstraint::set(const MatrixF& xfm) +{ + this->xfm = xfm; + is_defined = true; + is_valid = true; + change_code++; + sample(0.0f, 0, 0); +} + +void afxTransformConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) +{ + if (cam_pos) + { + Point3F dir = (*cam_pos) - xfm.getPosition(); + F32 dist_sq = dir.lenSquared(); + if (dist_sq > mgr->getScopingDistanceSquared()) + { + is_valid = false; + return; + } + is_valid = true; + } + + last_xfm = xfm; + last_pos = xfm.getPosition(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxShapeConstraint + +afxShapeConstraint::afxShapeConstraint(afxConstraintMgr* mgr) + : afxConstraint(mgr) +{ + arb_name = ST_NULLSTRING; + shape = 0; + scope_id = 0; + clip_tag = 0; + lock_tag = 0; +} + +afxShapeConstraint::afxShapeConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name) + : afxConstraint(mgr) +{ + this->arb_name = arb_name; + shape = 0; + scope_id = 0; + clip_tag = 0; + lock_tag = 0; +} + +afxShapeConstraint::~afxShapeConstraint() +{ + if (shape) + clearNotify(shape); +} + +void afxShapeConstraint::set(ShapeBase* shape) +{ + if (this->shape) + { + scope_id = 0; + clearNotify(this->shape); + if (clip_tag > 0) + remapAnimation(clip_tag, shape); + if (lock_tag > 0) + unlockAnimation(lock_tag); + } + + this->shape = shape; + + if (this->shape) + { + deleteNotify(this->shape); + scope_id = this->shape->getScopeId(); + } + + if (this->shape != NULL) + { + is_defined = true; + is_valid = true; + change_code++; + sample(0.0f, 0, 0); + } + else + is_valid = false; +} + +void afxShapeConstraint::set_scope_id(U16 scope_id) +{ + if (shape) + clearNotify(shape); + + shape = 0; + this->scope_id = scope_id; + + is_defined = (this->scope_id > 0); + is_valid = false; + mgr->postMissingConstraintObject(this); +} + +void afxShapeConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) +{ + if (gone_missing) + return; + + if (shape) + { + last_xfm = shape->getRenderTransform(); + if (cons_def.pos_at_box_center) + last_pos = shape->getBoxCenter(); + else + last_pos = shape->getRenderPosition(); + } +} + +void afxShapeConstraint::restoreObject(SceneObject* obj) +{ + if (this->shape) + { + scope_id = 0; + clearNotify(this->shape); + } + + this->shape = (ShapeBase* )obj; + + if (this->shape) + { + deleteNotify(this->shape); + scope_id = this->shape->getScopeId(); + } + + is_valid = (this->shape != NULL); +} + +void afxShapeConstraint::onDeleteNotify(SimObject* obj) +{ + if (shape == dynamic_cast(obj)) + { + shape = 0; + is_valid = false; + if (scope_id > 0) + mgr->postMissingConstraintObject(this, true); + } + + Parent::onDeleteNotify(obj); +} + +U32 afxShapeConstraint::setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim) +{ + if (!shape) + return 0; + + if (shape->isServerObject()) + { + AIPlayer* ai_player = dynamic_cast(shape); + if (ai_player && !ai_player->isBlendAnimation(clip)) + { + ai_player->saveMoveState(); + ai_player->stopMove(); + } + } + + clip_tag = shape->playAnimation(clip, pos, rate, trans, false/*hold*/, true/*wait*/, is_death_anim); + return clip_tag; +} + +void afxShapeConstraint::remapAnimation(U32 tag, ShapeBase* other_shape) +{ + if (clip_tag == 0) + return; + + if (!shape) + return; + + if (!other_shape) + { + resetAnimation(tag); + return; + } + + Con::errorf("remapAnimation -- Clip name, %s.", shape->getLastClipName(tag)); + + if (shape->isClientObject()) + { + shape->restoreAnimation(tag); + } + else + { + AIPlayer* ai_player = dynamic_cast(shape); + if (ai_player) + ai_player->restartMove(tag); + else + shape->restoreAnimation(tag); + } + + clip_tag = 0; +} + +void afxShapeConstraint::resetAnimation(U32 tag) +{ + if (clip_tag == 0) + return; + + if (!shape) + return; + + if (shape->isClientObject()) + { + shape->restoreAnimation(tag); + } + else + { + AIPlayer* ai_player = dynamic_cast(shape); + if (ai_player) + ai_player->restartMove(tag); + else + shape->restoreAnimation(tag); + } + + if ((tag & 0x80000000) == 0 && tag == clip_tag) + clip_tag = 0; +} + +U32 afxShapeConstraint::lockAnimation() +{ + if (!shape) + return 0; + + lock_tag = shape->lockAnimation(); + return lock_tag; +} + +void afxShapeConstraint::unlockAnimation(U32 tag) +{ + if (lock_tag == 0) + return; + + if (!shape) + return; + + shape->unlockAnimation(tag); + lock_tag = 0; +} + +F32 afxShapeConstraint::getAnimClipDuration(const char* clip) +{ + return (shape) ? shape->getAnimationDuration(clip) : 0.0f; +} + +S32 afxShapeConstraint::getDamageState() +{ + return (shape) ? shape->getDamageState() : -1; +} + +U32 afxShapeConstraint::getTriggers() +{ + return (shape) ? shape->getShapeInstance()->getTriggerStateMask() : 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxShapeNodeConstraint + +afxShapeNodeConstraint::afxShapeNodeConstraint(afxConstraintMgr* mgr) + : afxShapeConstraint(mgr) +{ + arb_node = ST_NULLSTRING; + shape_node_ID = -1; +} + +afxShapeNodeConstraint::afxShapeNodeConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name, StringTableEntry arb_node) + : afxShapeConstraint(mgr, arb_name) +{ + this->arb_node = arb_node; + shape_node_ID = -1; +} + +void afxShapeNodeConstraint::set(ShapeBase* shape) +{ + if (shape) + { + shape_node_ID = shape->getShape()->findNode(arb_node); + if (shape_node_ID == -1) + Con::errorf("Failed to find node [%s]", arb_node); + } + else + shape_node_ID = -1; + + Parent::set(shape); +} + +void afxShapeNodeConstraint::set_scope_id(U16 scope_id) +{ + shape_node_ID = -1; + Parent::set_scope_id(scope_id); +} + +void afxShapeNodeConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) +{ + if (shape && shape_node_ID != -1) + { + last_xfm = shape->getRenderTransform(); + last_xfm.scale(shape->getScale()); + last_xfm.mul(shape->getShapeInstance()->mNodeTransforms[shape_node_ID]); + last_pos = last_xfm.getPosition(); + } +} + +void afxShapeNodeConstraint::restoreObject(SceneObject* obj) +{ + ShapeBase* shape = dynamic_cast(obj); + if (shape) + { + shape_node_ID = shape->getShape()->findNode(arb_node); + if (shape_node_ID == -1) + Con::errorf("Failed to find node [%s]", arb_node); + } + else + shape_node_ID = -1; + Parent::restoreObject(obj); +} + +void afxShapeNodeConstraint::onDeleteNotify(SimObject* obj) +{ + Parent::onDeleteNotify(obj); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxObjectConstraint + +afxObjectConstraint::afxObjectConstraint(afxConstraintMgr* mgr) + : afxConstraint(mgr) +{ + arb_name = ST_NULLSTRING; + obj = 0; + scope_id = 0; + is_camera = false; +} + +afxObjectConstraint::afxObjectConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name) + : afxConstraint(mgr) +{ + this->arb_name = arb_name; + obj = 0; + scope_id = 0; + is_camera = false; +} + +afxObjectConstraint::~afxObjectConstraint() +{ + if (obj) + clearNotify(obj); +} + +void afxObjectConstraint::set(SceneObject* obj) +{ + if (this->obj) + { + scope_id = 0; + clearNotify(this->obj); + } + + this->obj = obj; + + if (this->obj) + { + deleteNotify(this->obj); + scope_id = this->obj->getScopeId(); + } + + if (this->obj != NULL) + { + is_camera = this->obj->isCamera(); + + is_defined = true; + is_valid = true; + change_code++; + sample(0.0f, 0, 0); + } + else + is_valid = false; +} + +void afxObjectConstraint::set_scope_id(U16 scope_id) +{ + if (obj) + clearNotify(obj); + + obj = 0; + this->scope_id = scope_id; + + is_defined = (scope_id > 0); + is_valid = false; + mgr->postMissingConstraintObject(this); +} + +void afxObjectConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) +{ + if (gone_missing) + return; + + if (obj) + { + if (!is_camera && cons_def.treat_as_camera && dynamic_cast(obj)) + { + ShapeBase* cam_obj = (ShapeBase*) obj; + F32 pov = 1.0f; + cam_obj->getCameraTransform(&pov, &last_xfm); + last_xfm.getColumn(3, &last_pos); + } + else + { + last_xfm = obj->getRenderTransform(); + if (cons_def.pos_at_box_center) + last_pos = obj->getBoxCenter(); + else + last_pos = obj->getRenderPosition(); + } + } +} + +void afxObjectConstraint::restoreObject(SceneObject* obj) +{ + if (this->obj) + { + scope_id = 0; + clearNotify(this->obj); + } + + this->obj = obj; + + if (this->obj) + { + deleteNotify(this->obj); + scope_id = this->obj->getScopeId(); + } + + is_valid = (this->obj != NULL); +} + +void afxObjectConstraint::onDeleteNotify(SimObject* obj) +{ + if (this->obj == dynamic_cast(obj)) + { + this->obj = 0; + is_valid = false; + if (scope_id > 0) + mgr->postMissingConstraintObject(this, true); + } + + Parent::onDeleteNotify(obj); +} + +U32 afxObjectConstraint::getTriggers() +{ + TSStatic* ts_static = dynamic_cast(obj); + if (ts_static) + { + TSShapeInstance* obj_inst = ts_static->getShapeInstance(); + return (obj_inst) ? obj_inst->getTriggerStateMask() : 0; + } + + ShapeBase* shape_base = dynamic_cast(obj); + if (shape_base) + { + TSShapeInstance* obj_inst = shape_base->getShapeInstance(); + return (obj_inst) ? obj_inst->getTriggerStateMask() : 0; + } + + return 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectConstraint + +afxEffectConstraint::afxEffectConstraint(afxConstraintMgr* mgr) + : afxConstraint(mgr) +{ + effect_name = ST_NULLSTRING; + effect = 0; +} + +afxEffectConstraint::afxEffectConstraint(afxConstraintMgr* mgr, StringTableEntry effect_name) + : afxConstraint(mgr) +{ + this->effect_name = effect_name; + effect = 0; +} + +afxEffectConstraint::~afxEffectConstraint() +{ +} + +bool afxEffectConstraint::getPosition(Point3F& pos, F32 hist) +{ + if (!effect || !effect->inScope()) + return false; + + if (cons_def.pos_at_box_center) + effect->getUpdatedBoxCenter(pos); + else + effect->getUpdatedPosition(pos); + + return true; +} + +bool afxEffectConstraint::getTransform(MatrixF& xfm, F32 hist) +{ + if (!effect || !effect->inScope()) + return false; + + effect->getUpdatedTransform(xfm); + return true; +} + +bool afxEffectConstraint::getAltitudes(F32& terrain_alt, F32& interior_alt) +{ + if (!effect) + return false; + + effect->getAltitudes(terrain_alt, interior_alt); + return true; +} + +void afxEffectConstraint::set(afxEffectWrapper* effect) +{ + this->effect = effect; + + if (this->effect != NULL) + { + is_defined = true; + is_valid = true; + change_code++; + } + else + is_valid = false; +} + +U32 afxEffectConstraint::setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim) +{ + return (effect) ? effect->setAnimClip(clip, pos, rate, trans) : 0; +} + +void afxEffectConstraint::resetAnimation(U32 tag) +{ + if (effect) + effect->resetAnimation(tag); +} + +F32 afxEffectConstraint::getAnimClipDuration(const char* clip) +{ + return (effect) ? getAnimClipDuration(clip) : 0; +} + +U32 afxEffectConstraint::getTriggers() +{ + return (effect) ? effect->getTriggers() : 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectNodeConstraint + +afxEffectNodeConstraint::afxEffectNodeConstraint(afxConstraintMgr* mgr) + : afxEffectConstraint(mgr) +{ + effect_node = ST_NULLSTRING; + effect_node_ID = -1; +} + +afxEffectNodeConstraint::afxEffectNodeConstraint(afxConstraintMgr* mgr, StringTableEntry name, StringTableEntry node) +: afxEffectConstraint(mgr, name) +{ + this->effect_node = node; + effect_node_ID = -1; +} + + + +bool afxEffectNodeConstraint::getPosition(Point3F& pos, F32 hist) +{ + if (!effect || !effect->inScope()) + return false; + + TSShapeInstance* ts_shape_inst = effect->getTSShapeInstance(); + if (!ts_shape_inst) + return false; + + if (effect_node_ID == -1) + { + TSShape* ts_shape = effect->getTSShape(); + effect_node_ID = (ts_shape) ? ts_shape->findNode(effect_node) : -1; + } + + if (effect_node_ID == -1) + return false; + + effect->getUpdatedTransform(last_xfm); + + Point3F scale; + effect->getUpdatedScale(scale); + + MatrixF gag = ts_shape_inst->mNodeTransforms[effect_node_ID]; + gag.setPosition( gag.getPosition()*scale ); + + MatrixF xfm; + xfm.mul(last_xfm, gag); + // + pos = xfm.getPosition(); + + return true; +} + +bool afxEffectNodeConstraint::getTransform(MatrixF& xfm, F32 hist) +{ + if (!effect || !effect->inScope()) + return false; + + TSShapeInstance* ts_shape_inst = effect->getTSShapeInstance(); + if (!ts_shape_inst) + return false; + + if (effect_node_ID == -1) + { + TSShape* ts_shape = effect->getTSShape(); + effect_node_ID = (ts_shape) ? ts_shape->findNode(effect_node) : -1; + } + + if (effect_node_ID == -1) + return false; + + effect->getUpdatedTransform(last_xfm); + + Point3F scale; + effect->getUpdatedScale(scale); + + MatrixF gag = ts_shape_inst->mNodeTransforms[effect_node_ID]; + gag.setPosition( gag.getPosition()*scale ); + + xfm.mul(last_xfm, gag); + + return true; +} + +void afxEffectNodeConstraint::set(afxEffectWrapper* effect) +{ + if (effect) + { + TSShape* ts_shape = effect->getTSShape(); + effect_node_ID = (ts_shape) ? ts_shape->findNode(effect_node) : -1; + } + else + effect_node_ID = -1; + + Parent::set(effect); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxSampleBuffer + +afxSampleBuffer::afxSampleBuffer() +{ + buffer_sz = 0; + buffer_ms = 0; + ms_per_sample = 33; + elapsed_ms = 0; + last_sample_ms = 0; + next_sample_num = 0; + n_samples = 0; +} + +afxSampleBuffer::~afxSampleBuffer() +{ +} + +void afxSampleBuffer::configHistory(F32 hist_len, U8 sample_rate) +{ + buffer_sz = mCeil(hist_len*sample_rate) + 1; + ms_per_sample = mCeil(1000.0f/sample_rate); + buffer_ms = buffer_sz*ms_per_sample; +} + +void afxSampleBuffer::recordSample(F32 dt, U32 elapsed_ms, void* data) +{ + this->elapsed_ms = elapsed_ms; + + if (!data) + return; + + U32 now_sample_num = elapsed_ms/ms_per_sample; + if (next_sample_num <= now_sample_num) + { + last_sample_ms = elapsed_ms; + while (next_sample_num <= now_sample_num) + { + recSample(next_sample_num % buffer_sz, data); + next_sample_num++; + n_samples++; + } + } +} + +inline bool afxSampleBuffer::compute_idx_from_lag(F32 lag, U32& idx) +{ + bool in_bounds = true; + + U32 lag_ms = lag*1000.0f; + U32 rec_ms = (elapsed_ms < buffer_ms) ? elapsed_ms : buffer_ms; + if (lag_ms > rec_ms) + { + // hasn't produced enough history + lag_ms = rec_ms; + in_bounds = false; + } + + U32 latest_sample_num = last_sample_ms/ms_per_sample; + U32 then_sample_num = (elapsed_ms - lag_ms)/ms_per_sample; + + if (then_sample_num > latest_sample_num) + { + // latest sample is older than lag + then_sample_num = latest_sample_num; + in_bounds = false; + } + + idx = then_sample_num % buffer_sz; + return in_bounds; +} + +inline bool afxSampleBuffer::compute_idx_from_lag(F32 lag, U32& idx1, U32& idx2, F32& t) +{ + bool in_bounds = true; + + F32 lag_ms = lag*1000.0f; + F32 rec_ms = (elapsed_ms < buffer_ms) ? elapsed_ms : buffer_ms; + if (lag_ms > rec_ms) + { + // hasn't produced enough history + lag_ms = rec_ms; + in_bounds = false; + } + + F32 per_samp = ms_per_sample; + F32 latest_sample_num = last_sample_ms/per_samp; + F32 then_sample_num = (elapsed_ms - lag_ms)/per_samp; + + U32 latest_sample_num_i = latest_sample_num; + U32 then_sample_num_i = then_sample_num; + + if (then_sample_num_i >= latest_sample_num_i) + { + if (latest_sample_num_i < then_sample_num_i) + in_bounds = false; + t = 0.0; + idx1 = then_sample_num_i % buffer_sz; + idx2 = idx1; + } + else + { + t = then_sample_num - then_sample_num_i; + idx1 = then_sample_num_i % buffer_sz; + idx2 = (then_sample_num_i+1) % buffer_sz; + } + + return in_bounds; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxSampleXfmBuffer + +afxSampleXfmBuffer::afxSampleXfmBuffer() +{ + xfm_buffer = 0; +} + +afxSampleXfmBuffer::~afxSampleXfmBuffer() +{ + delete [] xfm_buffer; +} + +void afxSampleXfmBuffer::configHistory(F32 hist_len, U8 sample_rate) +{ + if (!xfm_buffer) + { + afxSampleBuffer::configHistory(hist_len, sample_rate); + if (buffer_sz > 0) + xfm_buffer = new MatrixF[buffer_sz]; + } +} + +void afxSampleXfmBuffer::recSample(U32 idx, void* data) +{ + xfm_buffer[idx] = *((MatrixF*)data); +} + +void afxSampleXfmBuffer::getSample(F32 lag, void* data, bool& in_bounds) +{ + U32 idx1, idx2; + F32 t; + in_bounds = compute_idx_from_lag(lag, idx1, idx2, t); + + if (idx1 == idx2) + { + MatrixF* m1 = &xfm_buffer[idx1]; + *((MatrixF*)data) = *m1; + } + else + { + MatrixF* m1 = &xfm_buffer[idx1]; + MatrixF* m2 = &xfm_buffer[idx2]; + + Point3F p1 = m1->getPosition(); + Point3F p2 = m2->getPosition(); + Point3F p; p.interpolate(p1, p2, t); + + if (t < 0.5f) + *((MatrixF*)data) = *m1; + else + *((MatrixF*)data) = *m2; + + ((MatrixF*)data)->setPosition(p); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPointHistConstraint + +afxPointHistConstraint::afxPointHistConstraint(afxConstraintMgr* mgr) + : afxPointConstraint(mgr) +{ + samples = 0; +} + +afxPointHistConstraint::~afxPointHistConstraint() +{ + delete samples; +} + +void afxPointHistConstraint::set(Point3F point, Point3F vector) +{ + if (!samples) + { + samples = new afxSampleXfmBuffer; + samples->configHistory(cons_def.history_time, cons_def.sample_rate); + } + + Parent::set(point, vector); +} + +void afxPointHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) +{ + Parent::sample(dt, elapsed_ms, cam_pos); + + if (isDefined()) + { + if (isValid()) + samples->recordSample(dt, elapsed_ms, &last_xfm); + else + samples->recordSample(dt, elapsed_ms, 0); + } +} + +bool afxPointHistConstraint::getPosition(Point3F& pos, F32 hist) +{ + bool in_bounds; + + MatrixF xfm; + samples->getSample(hist, &xfm, in_bounds); + + pos = xfm.getPosition(); + + return in_bounds; +} + +bool afxPointHistConstraint::getTransform(MatrixF& xfm, F32 hist) +{ + bool in_bounds; + + samples->getSample(hist, &xfm, in_bounds); + + return in_bounds; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPointHistConstraint + +afxTransformHistConstraint::afxTransformHistConstraint(afxConstraintMgr* mgr) + : afxTransformConstraint(mgr) +{ + samples = 0; +} + +afxTransformHistConstraint::~afxTransformHistConstraint() +{ + delete samples; +} + +void afxTransformHistConstraint::set(const MatrixF& xfm) +{ + if (!samples) + { + samples = new afxSampleXfmBuffer; + samples->configHistory(cons_def.history_time, cons_def.sample_rate); + } + + Parent::set(xfm); +} + +void afxTransformHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) +{ + Parent::sample(dt, elapsed_ms, cam_pos); + + if (isDefined()) + { + if (isValid()) + samples->recordSample(dt, elapsed_ms, &last_xfm); + else + samples->recordSample(dt, elapsed_ms, 0); + } +} + +bool afxTransformHistConstraint::getPosition(Point3F& pos, F32 hist) +{ + bool in_bounds; + + MatrixF xfm; + samples->getSample(hist, &xfm, in_bounds); + + pos = xfm.getPosition(); + + return in_bounds; +} + +bool afxTransformHistConstraint::getTransform(MatrixF& xfm, F32 hist) +{ + bool in_bounds; + + samples->getSample(hist, &xfm, in_bounds); + + return in_bounds; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxShapeHistConstraint + +afxShapeHistConstraint::afxShapeHistConstraint(afxConstraintMgr* mgr) + : afxShapeConstraint(mgr) +{ + samples = 0; +} + +afxShapeHistConstraint::afxShapeHistConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name) + : afxShapeConstraint(mgr, arb_name) +{ + samples = 0; +} + +afxShapeHistConstraint::~afxShapeHistConstraint() +{ + delete samples; +} + +void afxShapeHistConstraint::set(ShapeBase* shape) +{ + if (shape && !samples) + { + samples = new afxSampleXfmBuffer; + samples->configHistory(cons_def.history_time, cons_def.sample_rate); + } + + Parent::set(shape); +} + +void afxShapeHistConstraint::set_scope_id(U16 scope_id) +{ + if (scope_id > 0 && !samples) + { + samples = new afxSampleXfmBuffer; + samples->configHistory(cons_def.history_time, cons_def.sample_rate); + } + + Parent::set_scope_id(scope_id); +} + +void afxShapeHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) +{ + Parent::sample(dt, elapsed_ms, cam_pos); + + if (isDefined()) + { + if (isValid()) + samples->recordSample(dt, elapsed_ms, &last_xfm); + else + samples->recordSample(dt, elapsed_ms, 0); + } +} + +bool afxShapeHistConstraint::getPosition(Point3F& pos, F32 hist) +{ + bool in_bounds; + + MatrixF xfm; + samples->getSample(hist, &xfm, in_bounds); + + pos = xfm.getPosition(); + + return in_bounds; +} + +bool afxShapeHistConstraint::getTransform(MatrixF& xfm, F32 hist) +{ + bool in_bounds; + + samples->getSample(hist, &xfm, in_bounds); + + return in_bounds; +} + +void afxShapeHistConstraint::onDeleteNotify(SimObject* obj) +{ + Parent::onDeleteNotify(obj); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxShapeNodeHistConstraint + +afxShapeNodeHistConstraint::afxShapeNodeHistConstraint(afxConstraintMgr* mgr) + : afxShapeNodeConstraint(mgr) +{ + samples = 0; +} + +afxShapeNodeHistConstraint::afxShapeNodeHistConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name, + StringTableEntry arb_node) + : afxShapeNodeConstraint(mgr, arb_name, arb_node) +{ + samples = 0; +} + +afxShapeNodeHistConstraint::~afxShapeNodeHistConstraint() +{ + delete samples; +} + +void afxShapeNodeHistConstraint::set(ShapeBase* shape) +{ + if (shape && !samples) + { + samples = new afxSampleXfmBuffer; + samples->configHistory(cons_def.history_time, cons_def.sample_rate); + } + + Parent::set(shape); +} + +void afxShapeNodeHistConstraint::set_scope_id(U16 scope_id) +{ + if (scope_id > 0 && !samples) + { + samples = new afxSampleXfmBuffer; + samples->configHistory(cons_def.history_time, cons_def.sample_rate); + } + + Parent::set_scope_id(scope_id); +} + +void afxShapeNodeHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) +{ + Parent::sample(dt, elapsed_ms, cam_pos); + + if (isDefined()) + { + if (isValid()) + samples->recordSample(dt, elapsed_ms, &last_xfm); + else + samples->recordSample(dt, elapsed_ms, 0); + } +} + +bool afxShapeNodeHistConstraint::getPosition(Point3F& pos, F32 hist) +{ + bool in_bounds; + + MatrixF xfm; + samples->getSample(hist, &xfm, in_bounds); + + pos = xfm.getPosition(); + + return in_bounds; +} + +bool afxShapeNodeHistConstraint::getTransform(MatrixF& xfm, F32 hist) +{ + bool in_bounds; + + samples->getSample(hist, &xfm, in_bounds); + + return in_bounds; +} + +void afxShapeNodeHistConstraint::onDeleteNotify(SimObject* obj) +{ + Parent::onDeleteNotify(obj); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxObjectHistConstraint + +afxObjectHistConstraint::afxObjectHistConstraint(afxConstraintMgr* mgr) + : afxObjectConstraint(mgr) +{ + samples = 0; +} + +afxObjectHistConstraint::afxObjectHistConstraint(afxConstraintMgr* mgr, StringTableEntry arb_name) + : afxObjectConstraint(mgr, arb_name) +{ + samples = 0; +} + +afxObjectHistConstraint::~afxObjectHistConstraint() +{ + delete samples; +} + +void afxObjectHistConstraint::set(SceneObject* obj) +{ + if (obj && !samples) + { + samples = new afxSampleXfmBuffer; + samples->configHistory(cons_def.history_time, cons_def.sample_rate); + } + + Parent::set(obj); +} + +void afxObjectHistConstraint::set_scope_id(U16 scope_id) +{ + if (scope_id > 0 && !samples) + { + samples = new afxSampleXfmBuffer; + samples->configHistory(cons_def.history_time, cons_def.sample_rate); + } + + Parent::set_scope_id(scope_id); +} + +void afxObjectHistConstraint::sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) +{ + Parent::sample(dt, elapsed_ms, cam_pos); + + if (isDefined()) + { + if (isValid()) + samples->recordSample(dt, elapsed_ms, &last_xfm); + else + samples->recordSample(dt, elapsed_ms, 0); + } +} + +bool afxObjectHistConstraint::getPosition(Point3F& pos, F32 hist) +{ + bool in_bounds; + + MatrixF xfm; + samples->getSample(hist, &xfm, in_bounds); + + pos = xfm.getPosition(); + + return in_bounds; +} + +bool afxObjectHistConstraint::getTransform(MatrixF& xfm, F32 hist) +{ + bool in_bounds; + + samples->getSample(hist, &xfm, in_bounds); + + return in_bounds; +} + +void afxObjectHistConstraint::onDeleteNotify(SimObject* obj) +{ + Parent::onDeleteNotify(obj); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/afxConstraint.h b/Engine/source/afx/afxConstraint.h new file mode 100644 index 000000000..d2ea324ae --- /dev/null +++ b/Engine/source/afx/afxConstraint.h @@ -0,0 +1,677 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_CONSTRAINT_H_ +#define _AFX_CONSTRAINT_H_ + +#include "core/util/tVector.h" +#include "T3D/shapeBase.h" + +#include "afxEffectDefs.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxConstraintDef + +class afxEffectBaseData; + +struct afxConstraintDef : public afxEffectDefs +{ + enum DefType + { + CONS_UNDEFINED, + CONS_PREDEFINED, + CONS_SCENE, + CONS_EFFECT, + CONS_GHOST + }; + + DefType def_type; + + StringTableEntry cons_src_name; + StringTableEntry cons_node_name; + F32 history_time; + U8 sample_rate; + + bool runs_on_server; + bool runs_on_client; + bool pos_at_box_center; + bool treat_as_camera; + + /*C*/ afxConstraintDef(); + + bool isDefined(); + + bool isArbitraryObject(); + void reset(); + bool parseSpec(const char* spec, bool runs_on_server, bool runs_on_client); + + static void gather_cons_defs(Vector& defs, Vector& fx); + + static StringTableEntry SCENE_CONS_KEY; + static StringTableEntry EFFECT_CONS_KEY; + static StringTableEntry GHOST_CONS_KEY; +}; + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxConstraint +// Abstract base-class for a simple constraint mechanism used to constrain +// special effects to spell related objects such as the spellcaster, target, +// projectile, or impact location. +// +// note -- the direction vectors don't really fit... should probably consider separate +// constraint types for position, orientation, and possibly a look-at constraint. +// + +class SceneObject; +class afxConstraintMgr; + +class afxConstraint : public SimObject, public afxEffectDefs +{ + friend class afxConstraintMgr; + typedef SimObject Parent; + +protected: + afxConstraintMgr* mgr; + afxConstraintDef cons_def; + bool is_defined; + bool is_valid; + Point3F last_pos; + MatrixF last_xfm; + F32 history_time; + bool is_alive; + bool gone_missing; + U32 change_code; + +public: + /*C*/ afxConstraint(afxConstraintMgr*); + virtual ~afxConstraint(); + + virtual bool getPosition(Point3F& pos, F32 hist=0.0f) + { pos = last_pos; return is_valid; } + virtual bool getTransform(MatrixF& xfm, F32 hist=0.0f) + { xfm = last_xfm; return is_valid;} + virtual bool getAltitudes(F32& terrain_alt, F32& interior_alt) { return false; } + + virtual bool isDefined() { return is_defined; } + virtual bool isValid() { return is_valid; } + virtual U32 getChangeCode() { return change_code; } + + virtual U32 setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim) + { return 0; }; + virtual void resetAnimation(U32 tag) { }; + virtual U32 lockAnimation() { return 0; } + virtual void unlockAnimation(U32 tag) { } + virtual F32 getAnimClipDuration(const char* clip) { return 0.0f; } + + virtual S32 getDamageState() { return -1; } + virtual void setLivingState(bool state) { is_alive = state; }; + virtual bool getLivingState() { return is_alive; }; + + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos)=0; + + virtual SceneObject* getSceneObject()=0; + virtual void restoreObject(SceneObject*)=0; + virtual U16 getScopeId()=0; + + virtual U32 getTriggers()=0; + + virtual void set_scope_id(U16 scope_id) { } + virtual void unset() { } +}; + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxConstraintMgr + +class ShapeBase; +class afxEffectWrapper; +class afxShapeConstraint; +class afxObjectConstraint; +class BitStream; +class NetConnection; + +struct afxConstraintID +{ + S16 index; + S16 sub_index; + + afxConstraintID() { index = -1; sub_index = 0; } + afxConstraintID(S16 idx, S16 sub=0) { index = idx; sub_index = sub; } + bool undefined() const { return (index < 0); } +}; + +typedef Vector afxConstraintList; + +class afxConstraintMgr : public afxEffectDefs +{ + typedef SimObject Parent; + + struct preDef + { + StringTableEntry name; + U32 type; + }; + + Vector constraints_v; + + Vector names_on_server; + Vector ghost_ids; + Vector predefs; + U32 starttime; + bool on_server; + bool initialized; + F32 scoping_dist_sq; + + SceneObject* find_object_from_name(StringTableEntry); + S32 find_cons_idx_from_name(StringTableEntry); + S32 find_effect_cons_idx_from_name(StringTableEntry); + + void create_constraint(const afxConstraintDef&); + void set_ref_shape(afxConstraintID which_id, ShapeBase*); + void set_ref_shape(afxConstraintID which_id, U16 scope_id); + +public: + /*C*/ afxConstraintMgr(); + /*D*/ ~afxConstraintMgr(); + + void defineConstraint(U32 type, StringTableEntry); + + afxConstraintID setReferencePoint(StringTableEntry which, Point3F point); + afxConstraintID setReferencePoint(StringTableEntry which, Point3F point, Point3F vector); + afxConstraintID setReferenceTransform(StringTableEntry which, MatrixF& xfm); + afxConstraintID setReferenceObject(StringTableEntry which, SceneObject*); + afxConstraintID setReferenceObjectByScopeId(StringTableEntry which, U16 scope_id, bool is_shape); + afxConstraintID setReferenceEffect(StringTableEntry which, afxEffectWrapper*); + afxConstraintID createReferenceEffect(StringTableEntry which, afxEffectWrapper*); + + void setReferencePoint(afxConstraintID which_id, Point3F point); + void setReferencePoint(afxConstraintID which_id, Point3F point, Point3F vector); + void setReferenceTransform(afxConstraintID which_id, MatrixF& xfm); + void setReferenceObject(afxConstraintID which_id, SceneObject*); + void setReferenceObjectByScopeId(afxConstraintID which_id, U16 scope_id, bool is_shape); + void setReferenceEffect(afxConstraintID which_id, afxEffectWrapper*); + + void invalidateReference(afxConstraintID which_id); + + afxConstraintID getConstraintId(const afxConstraintDef&); + afxConstraint* getConstraint(afxConstraintID cons_id); + + void sample(F32 dt, U32 now, const Point3F* cam_pos=0); + + void setStartTime(U32 timestamp) { starttime = timestamp; } + void initConstraintDefs(Vector&, bool on_server, F32 scoping_dist=-1.0f); + void packConstraintNames(NetConnection* conn, BitStream* stream); + void unpackConstraintNames(BitStream* stream); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// +// scope-tracking +private: + Vector scopeable_objs; + Vector scopeable_ids; + Vector* missing_objs; + Vector* missing_objs2; + Vector missing_objs_a; + Vector missing_objs_b; + +public: + void addScopeableObject(SceneObject*); + void removeScopeableObject(SceneObject*); + void clearAllScopeableObjs(); + + void postMissingConstraintObject(afxConstraint*, bool is_deleting=false); + void restoreScopedObject(SceneObject*, afxChoreographer* ch); + void adjustProcessOrdering(afxChoreographer*); + + F32 getScopingDistanceSquared() const { return scoping_dist_sq; } +}; + +inline afxConstraintID afxConstraintMgr::setReferencePoint(StringTableEntry which, Point3F point) +{ + return setReferencePoint(which, point, Point3F(0,0,1)); +} + +inline void afxConstraintMgr::setReferencePoint(afxConstraintID which, Point3F point) +{ + setReferencePoint(which, point, Point3F(0,0,1)); +} + + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPointConstraint +// This constrains to a specific 3D position such as an impact location. +// + +class afxPointConstraint : public afxConstraint +{ + typedef afxConstraint Parent; + +protected: + Point3F point; + Point3F vector; + +public: + /*C*/ afxPointConstraint(afxConstraintMgr*); + virtual ~afxPointConstraint(); + + virtual void set(Point3F point, Point3F vector); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos); + + virtual SceneObject* getSceneObject() { return 0; } + virtual void restoreObject(SceneObject*) { } + virtual U16 getScopeId() { return 0; } + virtual U32 getTriggers() { return 0; } + + virtual void unset() { set(Point3F::Zero, Point3F(0,0,1)); } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxTransformConstraint +// This constrains to a specific 3D transformation. +// + +class afxTransformConstraint : public afxConstraint +{ + typedef afxConstraint Parent; + +protected: + MatrixF xfm; + +public: + /*C*/ afxTransformConstraint(afxConstraintMgr*); + virtual ~afxTransformConstraint(); + + virtual void set(const MatrixF& xfm); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos); + + virtual SceneObject* getSceneObject() { return 0; } + virtual void restoreObject(SceneObject*) { } + virtual U16 getScopeId() { return 0; } + virtual U32 getTriggers() { return 0; } + + virtual void unset() { set(MatrixF::Identity); } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxShapeConstraint +// This constrains to a hierarchical shape (subclasses of ShapeBase), such as a +// Player or a Vehicle. You can also constrain to named sub-nodes of a shape. + +class ShapeBase; +class SceneObject; + +class afxShapeConstraint : public afxConstraint +{ + friend class afxConstraintMgr; + typedef afxConstraint Parent; + +protected: + StringTableEntry arb_name; + ShapeBase* shape; + U16 scope_id; + U32 clip_tag; + U32 lock_tag; + +public: + /*C*/ afxShapeConstraint(afxConstraintMgr*); + /*C*/ afxShapeConstraint(afxConstraintMgr*, StringTableEntry arb_name); + virtual ~afxShapeConstraint(); + + virtual void set(ShapeBase* shape); + virtual void set_scope_id(U16 scope_id); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos); + + virtual U32 setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim); + virtual void resetAnimation(U32 tag); + virtual U32 lockAnimation(); + virtual void unlockAnimation(U32 tag); + virtual F32 getAnimClipDuration(const char* clip); + + void remapAnimation(U32 tag, ShapeBase* other_shape); + + virtual S32 getDamageState(); + + virtual SceneObject* getSceneObject() { return shape; } + virtual void restoreObject(SceneObject*); + virtual U16 getScopeId() { return scope_id; } + virtual U32 getTriggers(); + + virtual void onDeleteNotify(SimObject*); + + virtual void unset() { set(0); } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxShapeNodeConstraint + +class afxShapeNodeConstraint : public afxShapeConstraint +{ + friend class afxConstraintMgr; + typedef afxShapeConstraint Parent; + +protected: + StringTableEntry arb_node; + S32 shape_node_ID; + +public: + /*C*/ afxShapeNodeConstraint(afxConstraintMgr*); + /*C*/ afxShapeNodeConstraint(afxConstraintMgr*, StringTableEntry arb_name, StringTableEntry arb_node); + + virtual void set(ShapeBase* shape); + virtual void set_scope_id(U16 scope_id); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos); + virtual void restoreObject(SceneObject*); + + S32 getNodeID() const { return shape_node_ID; } + + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxObjectConstraint +// This constrains to a simple 3D object (subclasses of SceneObject), such as an +// afxMagicMissile or a Projectile. You cannot constrain to sub-nodes with an +// afxObjectConstraint, use afxShapeConstraint instead. + +class SceneObject; + +class afxObjectConstraint : public afxConstraint +{ + friend class afxConstraintMgr; + typedef afxConstraint Parent; + +protected: + StringTableEntry arb_name; + SceneObject* obj; + U16 scope_id; + bool is_camera; + +public: + afxObjectConstraint(afxConstraintMgr*); + afxObjectConstraint(afxConstraintMgr*, StringTableEntry arb_name); + virtual ~afxObjectConstraint(); + + virtual void set(SceneObject* obj); + virtual void set_scope_id(U16 scope_id); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos); + + virtual SceneObject* getSceneObject() { return obj; } + virtual void restoreObject(SceneObject*); + virtual U16 getScopeId() { return scope_id; } + virtual U32 getTriggers(); + + virtual void onDeleteNotify(SimObject*); + + virtual void unset() { set(0); } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectConstraint +// This constrains to a hierarchical shape (subclasses of ShapeBase), such as a +// Player or a Vehicle. You can also constrain to named sub-nodes of a shape. + +class afxEffectWrapper; + +class afxEffectConstraint : public afxConstraint +{ + friend class afxConstraintMgr; + typedef afxConstraint Parent; + +protected: + StringTableEntry effect_name; + afxEffectWrapper* effect; + U32 clip_tag; + bool is_death_clip; + U32 lock_tag; + +public: + /*C*/ afxEffectConstraint(afxConstraintMgr*); + /*C*/ afxEffectConstraint(afxConstraintMgr*, StringTableEntry effect_name); + virtual ~afxEffectConstraint(); + + virtual bool getPosition(Point3F& pos, F32 hist=0.0f); + virtual bool getTransform(MatrixF& xfm, F32 hist=0.0f); + virtual bool getAltitudes(F32& terrain_alt, F32& interior_alt); + + virtual void set(afxEffectWrapper* effect); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos) { } + + virtual U32 setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans, bool is_death_anim); + virtual void resetAnimation(U32 tag); + virtual F32 getAnimClipDuration(const char* clip); + + virtual SceneObject* getSceneObject() { return 0; } + virtual void restoreObject(SceneObject*) { } + virtual U16 getScopeId() { return 0; } + virtual U32 getTriggers(); + + virtual void unset() { set(0); } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectNodeConstraint + +class afxEffectNodeConstraint : public afxEffectConstraint +{ + friend class afxConstraintMgr; + typedef afxEffectConstraint Parent; + +protected: + StringTableEntry effect_node; + S32 effect_node_ID; + +public: + /*C*/ afxEffectNodeConstraint(afxConstraintMgr*); + /*C*/ afxEffectNodeConstraint(afxConstraintMgr*, StringTableEntry name, StringTableEntry node); + + virtual bool getPosition(Point3F& pos, F32 hist=0.0f); + virtual bool getTransform(MatrixF& xfm, F32 hist=0.0f); + + virtual void set(afxEffectWrapper* effect); + + S32 getNodeID() const { return effect_node_ID; } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxSampleBuffer + +class afxSampleBuffer +{ +protected: + U32 buffer_sz; + U32 buffer_ms; + U32 ms_per_sample; + U32 elapsed_ms; + U32 last_sample_ms; + U32 next_sample_num; + U32 n_samples; + + virtual void recSample(U32 idx, void* data) = 0; + bool compute_idx_from_lag(F32 lag, U32& idx); + bool compute_idx_from_lag(F32 lag, U32& idx1, U32& idx2, F32& t); + +public: + /*C*/ afxSampleBuffer(); + virtual ~afxSampleBuffer(); + + virtual void configHistory(F32 hist_len, U8 sample_rate); + void recordSample(F32 dt, U32 elapsed_ms, void* data); + virtual void getSample(F32 lag, void* data, bool& oob) = 0; +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxSampleXfmBuffer + +class afxSampleXfmBuffer : public afxSampleBuffer +{ + typedef afxSampleBuffer Parent; + +protected: + MatrixF* xfm_buffer; + + virtual void recSample(U32 idx, void* data); + +public: + /*C*/ afxSampleXfmBuffer(); + virtual ~afxSampleXfmBuffer(); + + virtual void configHistory(F32 hist_len, U8 sample_rate); + virtual void getSample(F32 lag, void* data, bool& oob); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPointHistConstraint +// This class extends afxPointConstraint to remember its values for a period of time. + +class afxPointHistConstraint : public afxPointConstraint +{ + friend class afxConstraintMgr; + typedef afxPointConstraint Parent; + +protected: + afxSampleBuffer* samples; + +public: + /*C*/ afxPointHistConstraint(afxConstraintMgr*); + virtual ~afxPointHistConstraint(); + + virtual void set(Point3F point, Point3F vector); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos); + + virtual bool getPosition(Point3F& pos, F32 hist=0.0f); + virtual bool getTransform(MatrixF& xfm, F32 hist=0.0f); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPointHistConstraint +// This class extends afxTransformConstraint to remember its values for a period of time. + +class afxTransformHistConstraint : public afxTransformConstraint +{ + friend class afxConstraintMgr; + typedef afxTransformConstraint Parent; + +protected: + afxSampleBuffer* samples; + +public: + /*C*/ afxTransformHistConstraint(afxConstraintMgr*); + virtual ~afxTransformHistConstraint(); + + virtual void set(const MatrixF& xfm); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos); + + virtual bool getPosition(Point3F& pos, F32 hist=0.0f); + virtual bool getTransform(MatrixF& xfm, F32 hist=0.0f); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxShapeHistConstraint +// This class extends afxShapeConstraint to remember its values for a period of time. + +class afxShapeHistConstraint : public afxShapeConstraint +{ + friend class afxConstraintMgr; + typedef afxShapeConstraint Parent; + +protected: + afxSampleBuffer* samples; + +public: + /*C*/ afxShapeHistConstraint(afxConstraintMgr*); + /*C*/ afxShapeHistConstraint(afxConstraintMgr*, StringTableEntry arb_name); + virtual ~afxShapeHistConstraint(); + + virtual void set(ShapeBase* shape); + virtual void set_scope_id(U16 scope_id); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos); + + virtual bool getPosition(Point3F& pos, F32 hist=0.0f); + virtual bool getTransform(MatrixF& xfm, F32 hist=0.0f); + + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxShapeNodeHistConstraint +// This class extends afxShapeConstraint to remember its values for a period of time. + +class afxShapeNodeHistConstraint : public afxShapeNodeConstraint +{ + friend class afxConstraintMgr; + typedef afxShapeNodeConstraint Parent; + +protected: + afxSampleBuffer* samples; + +public: + /*C*/ afxShapeNodeHistConstraint(afxConstraintMgr*); + /*C*/ afxShapeNodeHistConstraint(afxConstraintMgr*, StringTableEntry arb_name, StringTableEntry arb_node); + virtual ~afxShapeNodeHistConstraint(); + + virtual void set(ShapeBase* shape); + virtual void set_scope_id(U16 scope_id); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos); + + virtual bool getPosition(Point3F& pos, F32 hist=0.0f); + virtual bool getTransform(MatrixF& xfm, F32 hist=0.0f); + + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxObjectHistConstraint +// This class extends afxObjectConstraint to remember its values for a period of time. + +class SceneObject; + +class afxObjectHistConstraint : public afxObjectConstraint +{ + friend class afxConstraintMgr; + typedef afxObjectConstraint Parent; + +protected: + afxSampleBuffer* samples; + +public: + afxObjectHistConstraint(afxConstraintMgr*); + afxObjectHistConstraint(afxConstraintMgr*, StringTableEntry arb_name); + virtual ~afxObjectHistConstraint(); + + virtual void set(SceneObject* obj); + virtual void set_scope_id(U16 scope_id); + virtual void sample(F32 dt, U32 elapsed_ms, const Point3F* cam_pos); + + virtual bool getPosition(Point3F& pos, F32 hist=0.0f); + virtual bool getTransform(MatrixF& xfm, F32 hist=0.0f); + + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_CONSTRAINT_H_ + diff --git a/Engine/source/afx/afxEffectDefs.h b/Engine/source/afx/afxEffectDefs.h new file mode 100644 index 000000000..bbcf5cef2 --- /dev/null +++ b/Engine/source/afx/afxEffectDefs.h @@ -0,0 +1,114 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_EFFECT_DEFS_H_ +#define _AFX_EFFECT_DEFS_H_ + +#include "afx/arcaneFX.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectBASE + +class afxEffectDefs +{ +public: + + enum + { + MAX_EFFECTS_PER_PHRASE = 1023, + EFFECTS_PER_PHRASE_BITS = 10 + }; + + // effect networking + enum + { + SERVER_ONLY = BIT(0), + SCOPE_ALWAYS = BIT(1), + GHOSTABLE = BIT(2), + CLIENT_ONLY = BIT(3), + SERVER_AND_CLIENT = BIT(4) + }; + + // effect condititons + enum + { + DISABLED = BIT(0), + ENABLED = BIT(1), + FAILING = BIT(2), + ALIVE = ENABLED, + DEAD = DISABLED, + DYING = FAILING, + // + IMPACTED_SOMETHING = BIT(31), + IMPACTED_TARGET = BIT(30), + IMPACTED_PRIMARY = BIT(29), + IMPACT_IN_WATER = BIT(28), + CASTER_IN_WATER = BIT(27), + }; + + enum + { + REQUIRES_STOP = BIT(0), + RUNS_ON_SERVER = BIT(1), + RUNS_ON_CLIENT = BIT(2), + }; + + enum + { + MAX_XFM_MODIFIERS = 32, + INFINITE_LIFETIME = (24*60*60) + }; + + enum + { + POINT_CONSTRAINT, + TRANSFORM_CONSTRAINT, + OBJECT_CONSTRAINT, + CAMERA_CONSTRAINT, + OBJECT_CONSTRAINT_SANS_OBJ, + OBJECT_CONSTRAINT_SANS_SHAPE, + UNDEFINED_CONSTRAINT_TYPE + }; + + enum + { + DIRECT_DAMAGE, + DAMAGE_OVER_TIME, + AREA_DAMAGE + }; + + enum + { + TIMING_DELAY = BIT(0), + TIMING_LIFETIME = BIT(1), + TIMING_FADE_IN = BIT(2), + TIMING_FADE_OUT = BIT(3), + TIMING_BITS = 2 + }; +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_EFFECT_DEFS_H_ diff --git a/Engine/source/afx/afxEffectGroup.cpp b/Engine/source/afx/afxEffectGroup.cpp new file mode 100644 index 000000000..a7e97f25e --- /dev/null +++ b/Engine/source/afx/afxEffectGroup.cpp @@ -0,0 +1,270 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" + +#include "afx/afxEffectGroup.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectGroupData::egValidator +// +// When an effect is added using "addEffect", this validator intercepts the value +// and adds it to the dynamic effects list. +// +void afxEffectGroupData::egValidator::validateType(SimObject* object, void* typePtr) +{ + afxEffectGroupData* eff_data = dynamic_cast(object); + afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr); + + if (eff_data && ew) + { + eff_data->fx_list.push_back(*ew); + *ew = 0; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectGroupData + +IMPLEMENT_CO_DATABLOCK_V1(afxEffectGroupData); + +ConsoleDocClass( afxEffectGroupData, + "@brief A datablock that describes an Effect Group.\n\n" + + "afxEffectGroupData provides a way for adding several effects to a choreographer as a " + "group and can be used wherever an afxEffectWrapperData is used. Basically, an " + "effect-group is a simple list of effect-wrappers. When an effect-group is added to a " + "choreographer, the end result is almost the same as adding all of the group's " + "effect-wrappers directly to the choreographer. The main difference is that the " + "grouped effects can be turned on and off collectively and created in multiples. " + "Effect-groups can also contain other effect-groups, forming a hierarchy of effects.\n\n" + + "A great strength of effect-groups is that they have a count setting that multiplies " + "the number of times the effects in the group are added to the owning choreographer " + "and this doesn't happen until the choreographer instance is created and launched. " + "This makes a big difference for certain kinds of effects, such as fireworks, that " + "tend to consist of small groupings of effects that are repeated many times with " + "slight variations. With groups, an effect like this has a very compact representation " + "for transmitting from server to clients, that only expands when actually used.\n\n" + + "Effect-groups with a count greater than one are extremely useful when some of the " + "effects use field substitutions. When an effect-group is expanded, it essentially runs " + "through a for-loop from 0 to count-1 and creates a new set of effect instances each " + "time through the loop. For each new set of effects, their group-index is set to the " + "index of this for-loop, which in turn replaces the ## token used in any field " + "substitutions in the child effects. In essence, the for-loop index becomes a parameter " + "of the child effects which can be used to vary the effects created in each loop.\n\n" + + "@see afxEffectBaseData\n\n" + "@see afxEffectWrapperData\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxEffectGroupData::afxEffectGroupData() +{ + group_enabled = true; + group_count = 1; + idx_offset = 0; + assign_idx = false; + + // dummy entry holds effect-wrapper pointer while a special validator + // grabs it and adds it to an appropriate effects list + dummy_fx_entry = NULL; + + // marked true if datablock ids need to + // be converted into pointers + do_id_convert = false; +} + +afxEffectGroupData::afxEffectGroupData(const afxEffectGroupData& other, bool temp_clone) : afxEffectBaseData(other, temp_clone) +{ + group_enabled = other.group_enabled; + group_count = other.group_count; + idx_offset = other.idx_offset; + assign_idx = other.assign_idx; + timing = other.timing; + dummy_fx_entry = other.dummy_fx_entry; + do_id_convert = other.do_id_convert; // -- + fx_list = other.fx_list; // -- +} + +void afxEffectGroupData::reloadReset() +{ + fx_list.clear(); +} + +void afxEffectGroupData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed) +{ + stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS); + for (int i = 0; i < fx.size(); i++) + writeDatablockID(stream, fx[i], packed); +} + +void afxEffectGroupData::unpack_fx(BitStream* stream, afxEffectList& fx) +{ + fx.clear(); + S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS); + for (int i = 0; i < n_fx; i++) + fx.push_back((afxEffectWrapperData*)readDatablockID(stream)); +} + +#define myOffset(field) Offset(field, afxEffectGroupData) + +void afxEffectGroupData::initPersistFields() +{ + addField("groupEnabled", TypeBool, myOffset(group_enabled), + "..."); + addField("count", TypeS32, myOffset(group_count), + "..."); + addField("indexOffset", TypeS8, myOffset(idx_offset), + "..."); + addField("assignIndices", TypeBool, myOffset(assign_idx), + "..."); + + addField("delay", TypeF32, myOffset(timing.delay), + "..."); + addField("lifetime", TypeF32, myOffset(timing.lifetime), + "..."); + addField("fadeInTime", TypeF32, myOffset(timing.fade_in_time), + "..."); + addField("fadeOutTime", TypeF32, myOffset(timing.fade_out_time), + "..."); + + // effect lists + // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list + static egValidator emptyValidator(0); + + addFieldV("addEffect", TYPEID(), myOffset(dummy_fx_entry), &emptyValidator, + "..."); + + Parent::initPersistFields(); + + // disallow some field substitutions + disableFieldSubstitutions("addEffect"); +} + +void afxEffectGroupData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeFlag(group_enabled); + stream->write(group_count); + stream->write(idx_offset); + stream->writeFlag(assign_idx); + stream->write(timing.delay); + stream->write(timing.lifetime); + stream->write(timing.fade_in_time); + stream->write(timing.fade_out_time); + + pack_fx(stream, fx_list, packed); +} + +void afxEffectGroupData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + group_enabled = stream->readFlag(); + stream->read(&group_count); + stream->read(&idx_offset); + assign_idx = stream->readFlag(); + stream->read(&timing.delay); + stream->read(&timing.lifetime); + stream->read(&timing.fade_in_time); + stream->read(&timing.fade_out_time); + + do_id_convert = true; + unpack_fx(stream, fx_list); +} + +bool afxEffectGroupData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) + { + if (do_id_convert) + { + for (S32 i = 0; i < fx_list.size(); i++) + { + SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]); + if (db_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(db_id, fx_list[i])) + { + Con::errorf(ConsoleLogEntry::General, + "afxEffectGroupData::preload() -- bad datablockId: 0x%x", + db_id); + } + } + } + do_id_convert = false; + } + } + + return true; +} + +void afxEffectGroupData::gather_cons_defs(Vector& defs) +{ + for (S32 i = 0; i < fx_list.size(); i++) + { + if (fx_list[i]) + fx_list[i]->gather_cons_defs(defs); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod(afxEffectGroupData, reset, void, (),, + "Resets an effect-group datablock during reload.\n\n" + "@ingroup AFX") +{ + object->reloadReset(); +} + +DefineEngineMethod(afxEffectGroupData, addEffect, void, (afxEffectBaseData* effect),, + "Adds an effect (wrapper or group) to an effect-group.\n\n" + "@ingroup AFX") +{ + if (!effect) + { + Con::errorf("afxEffectGroupData::addEffect() -- missing afxEffectWrapperData."); + return; + } + + object->fx_list.push_back(effect); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/afxEffectGroup.h b/Engine/source/afx/afxEffectGroup.h new file mode 100644 index 000000000..2942bddb0 --- /dev/null +++ b/Engine/source/afx/afxEffectGroup.h @@ -0,0 +1,103 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_EFFECT_GROUP_H_ +#define _AFX_EFFECT_GROUP_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "console/typeValidators.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" + +class afxEffectWrapperData; + +struct afxGroupTimingData +{ + F32 delay; + F32 lifetime; + F32 fade_in_time; + F32 fade_out_time; + + afxGroupTimingData() + { + delay = 0.0f; + lifetime = 0.0f; + fade_in_time = 0.0f; + fade_out_time = 0.0f; + } +}; + +class afxEffectGroupData : public afxEffectBaseData +{ + typedef afxEffectBaseData Parent; + + class egValidator : public TypeValidator + { + U32 id; + public: + egValidator(U32 id) { this->id = id; } + void validateType(SimObject *object, void *typePtr); + }; + + bool do_id_convert; + +public: + afxEffectList fx_list; + bool group_enabled; + S32 group_count; + U8 idx_offset; + bool assign_idx; + afxGroupTimingData timing; + afxEffectBaseData* dummy_fx_entry; + +private: + void pack_fx(BitStream* stream, const afxEffectList& fx, bool packed); + void unpack_fx(BitStream* stream, afxEffectList& fx); + +public: + /*C*/ afxEffectGroupData(); + /*C*/ afxEffectGroupData(const afxEffectGroupData&, bool = false); + + virtual void reloadReset(); + + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + virtual void gather_cons_defs(Vector& defs); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxEffectGroupData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +#endif // _AFX_EFFECT_GROUP_H_ diff --git a/Engine/source/afx/afxEffectVector.cpp b/Engine/source/afx/afxEffectVector.cpp new file mode 100644 index 000000000..b3d03e4c6 --- /dev/null +++ b/Engine/source/afx/afxEffectVector.cpp @@ -0,0 +1,358 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afxChoreographer.h" +#include "afxEffectVector.h" +#include "afxConstraint.h" +#include "afxEffectWrapper.h" +#include "afxEffectGroup.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxEffectVector::filter_client_server() +{ + if (empty()) + return; + + for (S32 i = 0; i < fx_v->size(); i++) + { + if ((*fx_v)[i]->datablock->runsHere(on_server)) + fx_v2->push_back((*fx_v)[i]); + else + { + delete (*fx_v)[i]; + (*fx_v)[i] = 0; + } + } + + swap_vecs(); + + fx_v2->clear(); +} + +void afxEffectVector::calc_fx_dur_and_afterlife() +{ + total_fx_dur = 0.0f; + after_life = 0.0f; + + if (empty()) + return; + + for (S32 i = 0; i < fx_v->size(); i++) + { + afxEffectWrapper* ew = (*fx_v)[i]; + if (ew) + { + F32 ew_dur; + if (ew->ew_timing.lifetime < 0) + { + if (phrase_dur > ew->ew_timing.delay) + ew_dur = phrase_dur + ew->afterStopTime(); + else + ew_dur = ew->ew_timing.delay + ew->afterStopTime(); + } + else + ew_dur = ew->ew_timing.delay + ew->ew_timing.lifetime + ew->ew_timing.fade_out_time; + + if (ew_dur > total_fx_dur) + total_fx_dur = ew_dur; + + F32 after = ew->afterStopTime(); + if (after > after_life) + after_life = after; + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxEffectVector::afxEffectVector() +{ + fx_v = 0; + fx_v2 = 0; + active = false; + on_server = false; + total_fx_dur = 0; + after_life = 0; +} + +afxEffectVector::~afxEffectVector() +{ + stop(true); + delete fx_v; + delete fx_v2; +} + +void afxEffectVector::effects_init(afxChoreographer* chor, afxEffectList& effects, bool will_stop, F32 time_factor, + S32 group_index, const afxGroupTimingData* group_timing) +{ + afxConstraintMgr* cons_mgr = chor->getConstraintMgr(); + + for (S32 i = 0; i < effects.size(); i++) + { + if (dynamic_cast(effects[i])) + { + afxEffectGroupData* eg = (afxEffectGroupData*)effects[i]; + if (eg->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxEffectGroupData* orig_db = eg; + eg = new afxEffectGroupData(*orig_db, true); + orig_db->performSubstitutions(eg, chor, group_index); + } + + if (eg->group_enabled) + { + if (eg->assign_idx) + { + for (S32 j = 0; j < eg->group_count; j++) + effects_init(chor, eg->fx_list, will_stop, time_factor, j+eg->idx_offset, &eg->timing); + } + else + { + for (S32 j = 0; j < eg->group_count; j++) + effects_init(chor, eg->fx_list, will_stop, time_factor, group_index, &eg->timing); + } + } + + if (eg->isTempClone()) + delete eg; + } + else if (dynamic_cast(effects[i])) + { + afxEffectWrapperData* ewd = (afxEffectWrapperData*)effects[i]; + + if (ewd->getSubstitutionCount() > 0) + { + // clone the ewd and perform substitutions + afxEffectWrapperData* orig_db = ewd; + ewd = new afxEffectWrapperData(*orig_db, true); + orig_db->performSubstitutions(ewd, chor, group_index); + } + + if (ewd->effect_enabled) + { + static afxEffectTimingData inherited_timing; + bool use_inherited_timing = false; + if (ewd->inherit_timing != 0) + { + if (group_timing) + { + inherited_timing = ewd->ewd_timing; + if ((ewd->inherit_timing & afxEffectDefs::TIMING_DELAY) != 0) + inherited_timing.delay = group_timing->delay; + if ((ewd->inherit_timing & afxEffectDefs::TIMING_LIFETIME) != 0) + inherited_timing.lifetime = group_timing->lifetime; + if ((ewd->inherit_timing & afxEffectDefs::TIMING_FADE_IN) != 0) + inherited_timing.fade_in_time = group_timing->fade_in_time; + if ((ewd->inherit_timing & afxEffectDefs::TIMING_FADE_OUT) != 0) + inherited_timing.fade_out_time = group_timing->fade_out_time; + } + else + { + Con::warnf("afxEffectVector::effects_init() -- %s::inheritGroupTiming is non-zero but wrapper is not in a group."); + } + } + + const afxEffectTimingData& timing = (use_inherited_timing) ? inherited_timing : ewd->ewd_timing; + + if ( (will_stop || !ewd->requiresStop(timing)) && + (chor->testRanking(ewd->ranking_range.low, ewd->ranking_range.high)) && + (chor->testLevelOfDetail(ewd->lod_range.low, ewd->lod_range.high)) && + (ewd->testExecConditions(chor->getExecConditions())) + ) + { + afxEffectWrapper* effect; + effect = afxEffectWrapper::ew_create(chor, ewd, cons_mgr, time_factor, group_index); + if (effect) + fx_v->push_back(effect); + } + } + else + { + if (ewd->isTempClone()) + delete ewd; + } + + } + } +} + +void afxEffectVector::ev_init(afxChoreographer* chor, afxEffectList& effects, bool on_server, + bool will_stop, F32 time_factor, F32 phrase_dur, S32 group_index) +{ + this->on_server = on_server; + this->phrase_dur = phrase_dur; + + fx_v = new Vector; + + effects_init(chor, effects, will_stop, time_factor, group_index); + + fx_v2 = new Vector(fx_v->size()); +} + +void afxEffectVector::start(F32 timestamp) +{ + if (empty()) + return; + + // At this point both client and server effects are in the list. + // Timing adjustments are made during prestart(). + for (S32 i = 0; i < fx_v->size(); i++) + (*fx_v)[i]->prestart(); + + // duration and afterlife values are pre-calculated here + calc_fx_dur_and_afterlife(); + + // now we filter out client-only or server-only effects that + // don't belong here, + filter_client_server(); + + active = true; + + for (S32 j = 0; j < fx_v->size(); j++) + { + if ((*fx_v)[j]->start(timestamp)) + fx_v2->push_back((*fx_v)[j]); + else + { + delete (*fx_v)[j]; + (*fx_v)[j] = 0; + } + } + + swap_vecs(); + fx_v2->clear(); +} + +void afxEffectVector::update(F32 dt) +{ + if (empty()) + { + active = false; + return; + } + + for (int i = 0; i < fx_v->size(); i++) + { + (*fx_v)[i]->update(dt); + + if ((*fx_v)[i]->isDone() || (*fx_v)[i]->isAborted()) + { + // effect has ended, cleanup and delete + (*fx_v)[i]->cleanup(); + delete (*fx_v)[i]; + (*fx_v)[i] = 0; + } + else + { + // effect is still going, so keep it around + fx_v2->push_back((*fx_v)[i]); + } + } + + swap_vecs(); + + fx_v2->clear(); + + if (empty()) + { + active = false; + delete fx_v; fx_v =0; + delete fx_v2; fx_v2 = 0; + } +} + +void afxEffectVector::stop(bool force_cleanup) +{ + if (empty()) + { + active = false; + return; + } + + for (int i = 0; i < fx_v->size(); i++) + { + (*fx_v)[i]->stop(); + + if (force_cleanup || (*fx_v)[i]->deleteWhenStopped()) + { + // effect is over when stopped, cleanup and delete + (*fx_v)[i]->cleanup(); + delete (*fx_v)[i]; + (*fx_v)[i] = 0; + } + else + { + // effect needs to fadeout or something, so keep it around + fx_v2->push_back((*fx_v)[i]); + } + } + + swap_vecs(); + + fx_v2->clear(); + + if (empty()) + { + active = false; + delete fx_v; fx_v =0; + delete fx_v2; fx_v2 = 0; + } +} + +void afxEffectVector::interrupt() +{ + if (empty()) + { + active = false; + return; + } + + for (int i = 0; i < fx_v->size(); i++) + { + (*fx_v)[i]->stop(); + (*fx_v)[i]->cleanup(); + delete (*fx_v)[i]; + (*fx_v)[i] = 0; + } + + swap_vecs(); + + fx_v2->clear(); + + if (empty()) + { + active = false; + delete fx_v; fx_v =0; + delete fx_v2; fx_v2 = 0; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + + diff --git a/Engine/source/afx/afxEffectVector.h b/Engine/source/afx/afxEffectVector.h new file mode 100644 index 000000000..49dc11908 --- /dev/null +++ b/Engine/source/afx/afxEffectVector.h @@ -0,0 +1,86 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_EFFECT_VECTOR_H_ +#define _AFX_EFFECT_VECTOR_H_ + +#include "afx/afxEffectWrapper.h" +#include "afx/afxEffectGroup.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectVector + +class afxEffectWrapper; +class afxChoreographer; + +class afxEffectVector +{ + Vector* fx_v; + Vector* fx_v2; + + bool active; + bool on_server; + F32 phrase_dur; + F32 total_fx_dur; + F32 after_life; + + void swap_vecs(); + void filter_client_server(); + void calc_fx_dur_and_afterlife(); + + void effects_init(afxChoreographer*, afxEffectList&, bool will_stop, F32 time_factor, + S32 group_index, const afxGroupTimingData* group_timing=0); + +public: + /*C*/ afxEffectVector(); + /*D*/ ~afxEffectVector(); + + void ev_init(afxChoreographer*, afxEffectList&, bool on_server, bool will_stop, + F32 time_factor, F32 phrase_dur, S32 group_index=0); + + void start(F32 timestamp); + void update(F32 dt); + void stop(bool force_cleanup=false); + void interrupt(); + bool empty() { return (!fx_v || fx_v->empty()); } + bool isActive() { return active; } + S32 count() { return (fx_v) ? fx_v->size() : 0; } + + F32 getTotalDur() { return total_fx_dur; } + F32 getAfterLife() { return after_life; } + + Vector* getFX() { return fx_v; } +}; + +inline void afxEffectVector::swap_vecs() +{ + Vector* tmp = fx_v; + fx_v = fx_v2; + fx_v2 = tmp; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_EFFECT_VECTOR_H_ diff --git a/Engine/source/afx/afxEffectWrapper.cpp b/Engine/source/afx/afxEffectWrapper.cpp new file mode 100644 index 000000000..7011b5f06 --- /dev/null +++ b/Engine/source/afx/afxEffectWrapper.cpp @@ -0,0 +1,1193 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" + +#include "afx/ce/afxComponentEffect.h" +#include "afx/afxResidueMgr.h" +#include "afx/afxChoreographer.h" +#include "afx/afxConstraint.h" +#include "afx/xm/afxXfmMod.h" +#include "afx/afxEffectWrapper.h" +#include "afx/util/afxAnimCurve.h" +#include "afx/util/afxEase.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectWrapperData + +IMPLEMENT_CO_DATABLOCK_V1(afxEffectBaseData); + +ConsoleDocClass( afxEffectBaseData, + "@brief A datablock baseclass for afxEffectWrapperData and afxEffectGroupData.\n\n" + + "Not intended to be used directly, afxEffectBaseData exists to provide base member " + "variables and generic functionality for the derived classes afxEffectWrapperData and " + "afxEffectGroupData.\n\n" + + "@see afxEffectWrapperData\n\n" + "@see afxEffectGroupData\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +IMPLEMENT_CO_DATABLOCK_V1(afxEffectWrapperData); + +ConsoleDocClass( afxEffectWrapperData, + "@brief A datablock that describes an Effect Wrapper.\n\n" + + "Conceptually an effect wrapper encloses a building-block effect and acts " + "as a handle for adding the effect to a choreographer. Effect wrapper fields " + "primarily deal with effect timing, constraints, and conditional effect execution.\n\n" + + "@see afxEffectBaseData\n\n" + "@see afxEffectGroupData\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxEffectWrapperData::afxEffectWrapperData() +{ + effect_name = ST_NULLSTRING; + effect_data = 0; + effect_desc = 0; + data_ID = 0; + use_as_cons_obj = false; + use_ghost_as_cons_obj = false; + + // constraint data + cons_spec = ST_NULLSTRING; + pos_cons_spec = ST_NULLSTRING; + orient_cons_spec = ST_NULLSTRING; + aim_cons_spec = StringTable->insert("camera"); + life_cons_spec = ST_NULLSTRING; + + // conditional execution flags + effect_enabled = true; + ranking_range.set(0,255); + lod_range.set(0,255); + life_conds = 0; + for (S32 i = 0; i < MAX_CONDITION_STATES; i++) + { + exec_cond_on_bits[i] = 0; + exec_cond_off_bits[i] = 0; + exec_cond_bitmasks[i] = 0; + } + + ewd_timing.lifetime = -1; + + user_fade_out_time = 0.0; + + is_looping = false; + n_loops = 0; + loop_gap_time = 0.0f; + + ignore_time_factor = false; + propagate_time_factor = false; + + // residue settings + + // scaling factors + rate_factor = 1.0f; + scale_factor = 1.0f; + + dMemset(xfm_modifiers, 0, sizeof(xfm_modifiers)); + + forced_bbox.minExtents.set(1,1,1); + forced_bbox.maxExtents.set(-1,-1,-1); + + update_forced_bbox = false; + + // marked true if datablock ids need to + // be converted into pointers + do_id_convert = false; + + sort_priority = 0; + direction.set(0,1,0); + speed = 0.0f; + mass = 1.0f; + + borrow_altitudes = false; + vis_keys_spec = ST_NULLSTRING; + vis_keys = 0; + + group_index = -1; + inherit_timing = 0; +} + +afxEffectWrapperData::afxEffectWrapperData(const afxEffectWrapperData& other, bool temp_clone) : afxEffectBaseData(other, temp_clone) +{ + effect_name = other.effect_name; + effect_data = other.effect_data; + effect_desc = other.effect_desc; + data_ID = other.data_ID; + use_as_cons_obj = other.use_as_cons_obj; + use_ghost_as_cons_obj = other.use_ghost_as_cons_obj; + cons_spec = other.cons_spec; + pos_cons_spec = other.pos_cons_spec; + orient_cons_spec = other.orient_cons_spec; + aim_cons_spec = other.aim_cons_spec; + life_cons_spec = other.life_cons_spec; + cons_def = other.cons_def; + pos_cons_def = other.pos_cons_def; + orient_cons_def = other.orient_cons_def; + aim_cons_def = other.aim_cons_def; + life_cons_def = other.life_cons_def; + effect_enabled = other.effect_enabled; + ranking_range = other.ranking_range; + lod_range = other.lod_range; + life_conds = other.life_conds; + dMemcpy(exec_cond_on_bits, other.exec_cond_on_bits, sizeof(exec_cond_on_bits)); + dMemcpy(exec_cond_off_bits, other.exec_cond_off_bits, sizeof(exec_cond_off_bits)); + dMemcpy(exec_cond_bitmasks, other.exec_cond_bitmasks, sizeof(exec_cond_bitmasks)); + ewd_timing = other.ewd_timing; + user_fade_out_time = other.user_fade_out_time; + is_looping = other.is_looping; + n_loops = other.n_loops; + loop_gap_time = other.loop_gap_time; + ignore_time_factor = other.ignore_time_factor; + propagate_time_factor = other.propagate_time_factor; + rate_factor = other.rate_factor; + scale_factor = other.scale_factor; + dMemcpy(xfm_modifiers, other.xfm_modifiers, sizeof(xfm_modifiers)); + forced_bbox = other.forced_bbox; + update_forced_bbox = other.update_forced_bbox; + do_id_convert = other.do_id_convert; + sort_priority = other.sort_priority; + direction = other.direction; + speed = other.speed; + mass = other.mass; + borrow_altitudes = other.borrow_altitudes; + vis_keys_spec = other.vis_keys_spec; + vis_keys = other.vis_keys; + if (other.vis_keys) + { + vis_keys = new afxAnimCurve(); + for (S32 i = 0; i < other.vis_keys->numKeys(); i++) + { + F32 when = other.vis_keys->getKeyTime(i); + F32 what = other.vis_keys->getKeyValue(i); + vis_keys->addKey(when, what); + } + } + else + vis_keys = 0; + group_index = other.group_index; + inherit_timing = other.inherit_timing; +} + +afxEffectWrapperData::~afxEffectWrapperData() +{ + if (vis_keys) + delete vis_keys; +} + +#define myOffset(field) Offset(field, afxEffectWrapperData) + +void afxEffectWrapperData::initPersistFields() +{ + // the wrapped effect + addField("effect", TYPEID(), myOffset(effect_data), + "..."); + addField("effectName", TypeString, myOffset(effect_name), + "..."); + + // constraints + addField("constraint", TypeString, myOffset(cons_spec), + "..."); + addField("posConstraint", TypeString, myOffset(pos_cons_spec), + "..."); + addField("posConstraint2", TypeString, myOffset(aim_cons_spec), + "..."); + addField("orientConstraint", TypeString, myOffset(orient_cons_spec), + "..."); + addField("lifeConstraint", TypeString, myOffset(life_cons_spec), + "..."); + // + addField("isConstraintSrc", TypeBool, myOffset(use_as_cons_obj), + "..."); + addField("ghostIsConstraintSrc", TypeBool, myOffset(use_ghost_as_cons_obj), + "..."); + + addField("delay", TypeF32, myOffset(ewd_timing.delay), + "..."); + addField("lifetime", TypeF32, myOffset(ewd_timing.lifetime), + "..."); + addField("fadeInTime", TypeF32, myOffset(ewd_timing.fade_in_time), + "..."); + addField("residueLifetime", TypeF32, myOffset(ewd_timing.residue_lifetime), + "..."); + addField("fadeInEase", TypePoint2F, myOffset(ewd_timing.fadein_ease), + "..."); + addField("fadeOutEase", TypePoint2F, myOffset(ewd_timing.fadeout_ease), + "..."); + addField("lifetimeBias", TypeF32, myOffset(ewd_timing.life_bias), + "..."); + addField("fadeOutTime", TypeF32, myOffset(user_fade_out_time), + "..."); + + addField("rateFactor", TypeF32, myOffset(rate_factor), + "..."); + addField("scaleFactor", TypeF32, myOffset(scale_factor), + "..."); + + addField("isLooping", TypeBool, myOffset(is_looping), + "..."); + addField("loopCount", TypeS32, myOffset(n_loops), + "..."); + addField("loopGapTime", TypeF32, myOffset(loop_gap_time), + "..."); + + addField("ignoreTimeFactor", TypeBool, myOffset(ignore_time_factor), + "..."); + addField("propagateTimeFactor", TypeBool, myOffset(propagate_time_factor), + "..."); + + addField("effectEnabled", TypeBool, myOffset(effect_enabled), + "..."); + addField("rankingRange", TypeByteRange, myOffset(ranking_range), + "..."); + addField("levelOfDetailRange", TypeByteRange, myOffset(lod_range), + "..."); + addField("lifeConditions", TypeS32, myOffset(life_conds), + "..."); + addField("execConditions", TypeS32, myOffset(exec_cond_on_bits), MAX_CONDITION_STATES, + "..."); + addField("execOffConditions", TypeS32, myOffset(exec_cond_off_bits), MAX_CONDITION_STATES, + "..."); + + addField("xfmModifiers", TYPEID(), myOffset(xfm_modifiers), MAX_XFM_MODIFIERS, + "..."); + + addField("forcedBBox", TypeBox3F, myOffset(forced_bbox), + "..."); + addField("updateForcedBBox", TypeBool, myOffset(update_forced_bbox), + "..."); + + addField("sortPriority", TypeS8, myOffset(sort_priority), + "..."); + addField("direction", TypePoint3F, myOffset(direction), + "..."); + addField("speed", TypeF32, myOffset(speed), + "..."); + addField("mass", TypeF32, myOffset(mass), + "..."); + + addField("borrowAltitudes", TypeBool, myOffset(borrow_altitudes), + "..."); + addField("visibilityKeys", TypeString, myOffset(vis_keys_spec), + "..."); + + addField("groupIndex", TypeS32, myOffset(group_index), + "..."); + addField("inheritGroupTiming", TypeS32, myOffset(inherit_timing), + "..."); + + Parent::initPersistFields(); + + // disallow some field substitutions + disableFieldSubstitutions("effect"); + onlyKeepClearSubstitutions("xfmModifiers"); // subs resolving to "~~", or "~0" are OK + + // Conditional Execution Flags + Con::setIntVariable("$afx::DISABLED", DISABLED); + Con::setIntVariable("$afx::ENABLED", ENABLED); + Con::setIntVariable("$afx::FAILING", FAILING); + Con::setIntVariable("$afx::DEAD", DEAD); + Con::setIntVariable("$afx::ALIVE", ALIVE); + Con::setIntVariable("$afx::DYING", DYING); +} + +bool afxEffectWrapperData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + if (!effect_data) + { + if (!Sim::findObject((SimObjectId)data_ID, effect_data)) + { + Con::errorf("afxEffectWrapperData::onAdd() -- bad datablockId: 0x%x", data_ID); + return false; + } + } + + if (effect_data) + { + if (!afxEffectAdapterDesc::identifyEffect(this)) + { + Con::errorf("afxEffectWrapperData::onAdd() -- unknown effect type."); + return false; + } + } + + parse_cons_specs(); + parse_vis_keys(); + + // figure out if fade-out is for effect of residue + if (ewd_timing.residue_lifetime > 0) + { + ewd_timing.residue_fadetime = user_fade_out_time; + ewd_timing.fade_out_time = 0.0f; + } + else + { + ewd_timing.residue_fadetime = 0.0f; + ewd_timing.fade_out_time = user_fade_out_time; + } + + // adjust fade-in time + if (ewd_timing.lifetime >= 0) + { + ewd_timing.fade_in_time = getMin(ewd_timing.lifetime, ewd_timing.fade_in_time); + } + + // adjust exec-conditions + for (S32 i = 0; i < MAX_CONDITION_STATES; i++) + exec_cond_bitmasks[i] = exec_cond_on_bits[i] | exec_cond_off_bits[i]; + + return true; +} + +void afxEffectWrapperData::packData(BitStream* stream) +{ + Parent::packData(stream); + + writeDatablockID(stream, effect_data, packed); + + stream->writeString(effect_name); + + stream->writeString(cons_spec); + stream->writeString(pos_cons_spec); + stream->writeString(orient_cons_spec); + stream->writeString(aim_cons_spec); + stream->writeString(life_cons_spec); + // + stream->write(use_as_cons_obj); + //stream->write(use_ghost_as_cons_obj); + + stream->writeFlag(effect_enabled); + stream->write(ranking_range.low); + stream->write(ranking_range.high); + stream->write(lod_range.low); + stream->write(lod_range.high); + + for (S32 i = 0; i < MAX_CONDITION_STATES; i++) + { + stream->write(exec_cond_on_bits[i]); + stream->write(exec_cond_off_bits[i]); + } + stream->write(life_conds); + stream->write(ewd_timing.delay); + stream->write(ewd_timing.lifetime); + stream->write(ewd_timing.fade_in_time); + stream->write(user_fade_out_time); + stream->write(is_looping); + stream->write(n_loops); + stream->write(loop_gap_time); + stream->write(ignore_time_factor); + stream->write(propagate_time_factor); + stream->write(ewd_timing.residue_lifetime); + stream->write(rate_factor); + stream->write(scale_factor); + + // modifiers + pack_mods(stream, xfm_modifiers, packed); + + mathWrite(*stream, forced_bbox); + stream->write(update_forced_bbox); + + stream->write(sort_priority); + mathWrite(*stream, direction); + stream->write(speed); + stream->write(mass); + + stream->write(borrow_altitudes); + if (stream->writeFlag(vis_keys_spec != ST_NULLSTRING)) + stream->writeLongString(1023, vis_keys_spec); + + if (stream->writeFlag(group_index != -1)) + stream->write(group_index); + + stream->writeInt(inherit_timing, TIMING_BITS); +} + +void afxEffectWrapperData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + data_ID = readDatablockID(stream); + + effect_name = stream->readSTString(); + + cons_spec = stream->readSTString(); + pos_cons_spec = stream->readSTString(); + orient_cons_spec = stream->readSTString(); + aim_cons_spec = stream->readSTString(); + life_cons_spec = stream->readSTString(); + // + stream->read(&use_as_cons_obj); + //stream->read(&use_ghost_as_cons_obj); + + effect_enabled = stream->readFlag(); + stream->read(&ranking_range.low); + stream->read(&ranking_range.high); + stream->read(&lod_range.low); + stream->read(&lod_range.high); + + for (S32 i = 0; i < MAX_CONDITION_STATES; i++) + { + stream->read(&exec_cond_on_bits[i]); + stream->read(&exec_cond_off_bits[i]); + } + stream->read(&life_conds); + stream->read(&ewd_timing.delay); + stream->read(&ewd_timing.lifetime); + stream->read(&ewd_timing.fade_in_time); + stream->read(&user_fade_out_time); + stream->read(&is_looping); + stream->read(&n_loops); + stream->read(&loop_gap_time); + stream->read(&ignore_time_factor); + stream->read(&propagate_time_factor); + stream->read(&ewd_timing.residue_lifetime); + stream->read(&rate_factor); + stream->read(&scale_factor); + + // modifiers + do_id_convert = true; + unpack_mods(stream, xfm_modifiers); + + mathRead(*stream, &forced_bbox); + stream->read(&update_forced_bbox); + + stream->read(&sort_priority); + mathRead(*stream, &direction); + stream->read(&speed); + stream->read(&mass); + + stream->read(&borrow_altitudes); + if (stream->readFlag()) + { + char buf[1024]; + stream->readLongString(1023, buf); + vis_keys_spec = StringTable->insert(buf); + } + else + vis_keys_spec = ST_NULLSTRING; + + if (stream->readFlag()) + stream->read(&group_index); + else + group_index = -1; + + inherit_timing = stream->readInt(TIMING_BITS); +} + +/* static*/ +S32 num_modifiers(afxXM_BaseData* mods[]) +{ + S32 n_mods = 0; + for (int i = 0; i < afxEffectDefs::MAX_XFM_MODIFIERS; i++) + { + if (mods[i]) + { + if (i != n_mods) + { + mods[n_mods] = mods[i]; + mods[i] = 0; + } + n_mods++; + } + } + + return n_mods; +} + +void afxEffectWrapperData::parse_cons_specs() +{ + // parse the constraint specifications + bool runs_on_s = runsOnServer(); + bool runs_on_c = runsOnClient(); + cons_def.parseSpec(cons_spec, runs_on_s, runs_on_c); + pos_cons_def.parseSpec(pos_cons_spec, runs_on_s, runs_on_c); + orient_cons_def.parseSpec(orient_cons_spec, runs_on_s, runs_on_c); + aim_cons_def.parseSpec(aim_cons_spec, runs_on_s, runs_on_c); + life_cons_def.parseSpec(life_cons_spec, runs_on_s, runs_on_c); + if (cons_def.isDefined()) + { + pos_cons_def = cons_def; + if (!orient_cons_def.isDefined()) + orient_cons_def = cons_def; + } +} + +void afxEffectWrapperData::parse_vis_keys() +{ + if (vis_keys_spec != ST_NULLSTRING) + { + if (vis_keys) + delete vis_keys; + vis_keys = new afxAnimCurve(); + + char* keys_buffer = dStrdup(vis_keys_spec); + + char* key_token = dStrtok(keys_buffer, " \t"); + while (key_token != NULL) + { + char* colon = dStrchr(key_token, ':'); + if (colon) + { + *colon = '\0'; + + F32 when = dAtof(key_token); + F32 what = dAtof(colon+1); + + vis_keys->addKey(when, what); + } + key_token = dStrtok(NULL, " \t"); + } + + dFree(keys_buffer); + vis_keys->sort(); + } +} + +void afxEffectWrapperData::gather_cons_defs(Vector& defs) +{ + if (pos_cons_def.isDefined()) + defs.push_back(pos_cons_def); + if (orient_cons_def.isDefined()) + defs.push_back(orient_cons_def); + if (aim_cons_def.isDefined()) + defs.push_back(aim_cons_def); + if (life_cons_def.isDefined()) + defs.push_back(life_cons_def); + + afxComponentEffectData* ce_data = dynamic_cast(effect_data); + if (ce_data) + ce_data->gather_cons_defs(defs); +} + +void afxEffectWrapperData::pack_mods(BitStream* stream, afxXM_BaseData* mods[], bool packed) +{ + S32 n_mods = num_modifiers(mods); + stream->writeInt(n_mods, 6); + for (int i = 0; i < n_mods; i++) + writeDatablockID(stream, mods[i], packed); +} + +void afxEffectWrapperData::unpack_mods(BitStream* stream, afxXM_BaseData* mods[]) +{ + S32 n_mods = stream->readInt(6); + for (int i = 0; i < n_mods; i++) + mods[i] = (afxXM_BaseData*) readDatablockID(stream); +} + +bool afxEffectWrapperData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) + { + if (do_id_convert) + { + for (int i = 0; i < MAX_XFM_MODIFIERS; i++) + { + SimObjectId db_id = SimObjectId((uintptr_t)xfm_modifiers[i]); + if (db_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(db_id, xfm_modifiers[i])) + { + Con::errorf("afxEffectWrapperData::preload() -- bad datablockId: 0x%x (xfm_modifiers[%d])", + db_id, i); + } + } + do_id_convert = false; + } + } + } + + return true; +} + +void afxEffectWrapperData::onPerformSubstitutions() +{ + Parent::onPerformSubstitutions(); + + parse_cons_specs(); + parse_vis_keys(); + + if (ewd_timing.residue_lifetime > 0) + { + ewd_timing.residue_fadetime = user_fade_out_time; + ewd_timing.fade_out_time = 0.0f; + } + else + { + ewd_timing.residue_fadetime = 0.0f; + ewd_timing.fade_out_time = user_fade_out_time; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectWrapper + +IMPLEMENT_CONOBJECT(afxEffectWrapper); + +ConsoleDocClass( afxEffectWrapper, + "@brief An Effect Wrapper as defined by an afxEffectWrapperData datablock.\n\n" + + "Conceptually an effect wrapper encloses a building-block effect and acts " + "as a handle for adding the effect to a choreographer. Effect wrapper fields " + "primarily deal with effect timing, constraints, and conditional effect execution.\n\n" + + "Not intended to be used directly, afxEffectWrapper is an internal baseclass used to " + "implement effect-specific adapter classes.\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" +); + +afxEffectWrapper::afxEffectWrapper() +{ + choreographer = 0; + datablock = 0; + cons_mgr = 0; + + cond_alive = true; + elapsed = 0; + life_end = 0; + life_elapsed = 0; + stopped = false; + n_updates = 0; + fade_value = 1.0f; + last_fade_value = 0.0f; + fade_in_end = 0.0; + fade_out_start = 0.0f; + in_scope = true; + is_aborted = false; + do_fade_inout = false; + do_fades = false; + full_lifetime = 0; + + time_factor = 1.0f; + prop_time_factor = 1.0f; + + live_scale_factor = 1.0f; + live_fade_factor = 1.0f; + terrain_altitude = -1.0f; + interior_altitude = -1.0f; + + group_index = 0; + + dMemset(xfm_modifiers, 0, sizeof(xfm_modifiers)); +} + +afxEffectWrapper::~afxEffectWrapper() +{ + for (S32 i = 0; i < MAX_XFM_MODIFIERS; i++) + if (xfm_modifiers[i]) + delete xfm_modifiers[i]; + + if (datablock && datablock->effect_name != ST_NULLSTRING) + { + choreographer->removeNamedEffect(this); + if (datablock->use_as_cons_obj && !effect_cons_id.undefined()) + cons_mgr->setReferenceEffect(effect_cons_id, 0); + } + + if (datablock && datablock->isTempClone()) + delete datablock; + datablock = 0; +} + +#undef myOffset +#define myOffset(field) Offset(field, afxEffectWrapper) + +void afxEffectWrapper::initPersistFields() +{ + addField("liveScaleFactor", TypeF32, myOffset(live_scale_factor), + "..."); + addField("liveFadeFactor", TypeF32, myOffset(live_fade_factor), + "..."); + + Parent::initPersistFields(); +} + +void afxEffectWrapper::ew_init(afxChoreographer* choreographer, + afxEffectWrapperData* datablock, + afxConstraintMgr* cons_mgr, + F32 time_factor) +{ + AssertFatal(choreographer != NULL, "Choreographer is missing."); + AssertFatal(datablock != NULL, "Datablock is missing."); + AssertFatal(cons_mgr != NULL, "Constraint manager is missing."); + + this->choreographer = choreographer; + this->datablock = datablock; + this->cons_mgr = cons_mgr; + ea_set_datablock(datablock->effect_data); + + ew_timing = datablock->ewd_timing; + if (ew_timing.life_bias != 1.0f) + { + if (ew_timing.lifetime > 0) + ew_timing.lifetime *= ew_timing.life_bias; + ew_timing.fade_in_time *= ew_timing.life_bias; + ew_timing.fade_out_time *= ew_timing.life_bias; + } + + pos_cons_id = cons_mgr->getConstraintId(datablock->pos_cons_def); + orient_cons_id = cons_mgr->getConstraintId(datablock->orient_cons_def); + aim_cons_id = cons_mgr->getConstraintId(datablock->aim_cons_def); + life_cons_id = cons_mgr->getConstraintId(datablock->life_cons_def); + + this->time_factor = (datablock->ignore_time_factor) ? 1.0f : time_factor; + + if (datablock->propagate_time_factor) + prop_time_factor = time_factor; + + if (datablock->runsHere(choreographer->isServerObject())) + { + for (int i = 0; i < MAX_XFM_MODIFIERS && datablock->xfm_modifiers[i] != 0; i++) + { + xfm_modifiers[i] = datablock->xfm_modifiers[i]->create(this, choreographer->isServerObject()); + AssertFatal(xfm_modifiers[i] != 0, avar("Error, creation failed for xfm_modifiers[%d] of %s.", i, datablock->getName())); + if (xfm_modifiers[i] == 0) + Con::errorf("Error, creation failed for xfm_modifiers[%d] of %s.", i, datablock->getName()); + } + } + + if (datablock->effect_name != ST_NULLSTRING) + { + assignName(datablock->effect_name); + choreographer->addNamedEffect(this); + if (datablock->use_as_cons_obj) + { + effect_cons_id = cons_mgr->setReferenceEffect(datablock->effect_name, this); + if (effect_cons_id.undefined() && datablock->isTempClone() && datablock->runsHere(choreographer->isServerObject())) + effect_cons_id = cons_mgr->createReferenceEffect(datablock->effect_name, this); + } + } +} + +void afxEffectWrapper::prestart() +{ + // modify timing values by time_factor + if (ew_timing.lifetime > 0) + ew_timing.lifetime *= time_factor; + ew_timing.delay *= time_factor; + ew_timing.fade_in_time *= time_factor; + ew_timing.fade_out_time *= time_factor; + + if (ew_timing.lifetime < 0) + { + full_lifetime = INFINITE_LIFETIME; + life_end = INFINITE_LIFETIME; + } + else + { + full_lifetime = ew_timing.lifetime + ew_timing.fade_out_time; + life_end = ew_timing.delay + ew_timing.lifetime; + } + + if ((ew_timing.fade_in_time + ew_timing.fade_out_time) > 0.0f) + { + fade_in_end = ew_timing.delay + ew_timing.fade_in_time; + if (full_lifetime == INFINITE_LIFETIME) + fade_out_start = INFINITE_LIFETIME; + else + fade_out_start = ew_timing.delay + ew_timing.lifetime; + do_fade_inout = true; + } + + if (!do_fade_inout && datablock->vis_keys != NULL && datablock->vis_keys->numKeys() > 0) + { + //do_fades = true; + fade_out_start = ew_timing.delay + ew_timing.lifetime; + } +} + +bool afxEffectWrapper::start(F32 timestamp) +{ + if (!ea_is_enabled()) + { + Con::warnf("afxEffectWrapper::start() -- effect type of %s is currently disabled.", datablock->getName()); + return false; + } + + afxConstraint* life_constraint = getLifeConstraint(); + if (life_constraint) + cond_alive = life_constraint->getLivingState(); + + elapsed = timestamp; + + for (S32 i = 0; i < MAX_XFM_MODIFIERS; i++) + { + if (!xfm_modifiers[i]) + break; + else + xfm_modifiers[i]->start(timestamp); + } + + if (!ea_start()) + { + // subclass should print error message if applicable, so no message here. + return false; + } + + update(0.0f); + return !isAborted(); +} + +bool afxEffectWrapper::test_life_conds() +{ + afxConstraint* life_constraint = getLifeConstraint(); + if (!life_constraint || datablock->life_conds == 0) + return true; + + S32 now_state = life_constraint->getDamageState(); + if ((datablock->life_conds & DEAD) != 0 && now_state == ShapeBase::Disabled) + return true; + if ((datablock->life_conds & ALIVE) != 0 && now_state == ShapeBase::Enabled) + return true; + if ((datablock->life_conds & DYING) != 0) + return (cond_alive && now_state == ShapeBase::Disabled); + + return false; +} + +bool afxEffectWrapper::update(F32 dt) +{ + elapsed += dt; + + // life_elapsed won't exceed full_lifetime + life_elapsed = getMin(elapsed - ew_timing.delay, full_lifetime); + + // update() returns early if elapsed is outside of active timing range + // (delay <= elapsed <= delay+lifetime) + // note: execution is always allowed beyond this point at least once, + // even if elapsed exceeds the lifetime. + if (elapsed < ew_timing.delay) + { + setScopeStatus(false); + return false; + } + + if (!datablock->requiresStop(ew_timing) && ew_timing.lifetime < 0) + { + F32 afterlife = elapsed - ew_timing.delay; + if (afterlife > 1.0f || ((afterlife > 0.0f) && (n_updates > 0))) + { + setScopeStatus(ew_timing.residue_lifetime > 0.0f); + return false; + } + } + else + { + F32 afterlife = elapsed - (full_lifetime + ew_timing.delay); + if (afterlife > 1.0f || ((afterlife > 0.0f) && (n_updates > 0))) + { + setScopeStatus(ew_timing.residue_lifetime > 0.0f); + return false; + } + } + + // first time here, test if required conditions for effect are met + if (n_updates == 0) + { + if (!test_life_conds()) + { + elapsed = full_lifetime + ew_timing.delay; + setScopeStatus(false); + n_updates++; + return false; + } + } + + setScopeStatus(true); + n_updates++; + + + // calculate current fade value if enabled + if (do_fade_inout) + { + if (ew_timing.fade_in_time > 0 && elapsed <= fade_in_end) + { + F32 t = mClampF((elapsed-ew_timing.delay)/ew_timing.fade_in_time, 0.0f, 1.0f); + fade_value = afxEase::t(t, ew_timing.fadein_ease.x,ew_timing.fadein_ease.y); + do_fades = true; + } + else if (elapsed > fade_out_start) + { + if (ew_timing.fade_out_time == 0) + fade_value = 0.0f; + else + { + F32 t = mClampF(1.0f-(elapsed-fade_out_start)/ew_timing.fade_out_time, 0.0f, 1.0f); + fade_value = afxEase::t(t, ew_timing.fadeout_ease.x,ew_timing.fadeout_ease.y); + } + do_fades = true; + } + else + { + fade_value = 1.0f; + do_fades = false; + } + } + else + { + fade_value = 1.0; + do_fades = false; + } + + if (datablock->vis_keys && datablock->vis_keys->numKeys() > 0) + { + F32 vis = datablock->vis_keys->evaluate(elapsed-ew_timing.delay); + fade_value *= mClampF(vis, 0.0f, 1.0f); + do_fades = (fade_value < 1.0f); + } + + // DEAL WITH CONSTRAINTS + afxXM_Params params; + Point3F& CONS_POS = params.pos; + MatrixF& CONS_XFM = params.ori; + Point3F& CONS_AIM = params.pos2; + Point3F& CONS_SCALE = params.scale; + LinearColorF& CONS_COLOR = params.color; + + afxConstraint* pos_constraint = getPosConstraint(); + if (pos_constraint) + { + bool valid = pos_constraint->getPosition(CONS_POS, datablock->pos_cons_def.history_time); + if (!valid) + getUnconstrainedPosition(CONS_POS); + setScopeStatus(valid); + if (valid && datablock->borrow_altitudes) + { + F32 terr_alt, inter_alt; + if (pos_constraint->getAltitudes(terr_alt, inter_alt)) + { + terrain_altitude = terr_alt; + interior_altitude = inter_alt; + } + } + } + else + { + getUnconstrainedPosition(CONS_POS); + setScopeStatus(false); + } + + afxConstraint* orient_constraint = getOrientConstraint(); + if (orient_constraint) + { + orient_constraint->getTransform(CONS_XFM, datablock->pos_cons_def.history_time); + } + else + { + getUnconstrainedTransform(CONS_XFM); + } + + afxConstraint* aim_constraint = getAimConstraint(); + if (aim_constraint) + aim_constraint->getPosition(CONS_AIM, datablock->pos_cons_def.history_time); + else + CONS_AIM.zero(); + + CONS_SCALE.set(datablock->scale_factor, datablock->scale_factor, datablock->scale_factor); + + /* + if (datablock->isPositional() && CONS_POS.isZero() && in_scope) + Con::errorf("#EFFECT AT ORIGIN [%s] time=%g", datablock->getName(), dt); + */ + + getBaseColor(CONS_COLOR); + + params.vis = fade_value; + + // apply modifiers + for (int i = 0; i < MAX_XFM_MODIFIERS; i++) + { + if (!xfm_modifiers[i]) + break; + else + xfm_modifiers[i]->updateParams(dt, life_elapsed, params); + } + + // final pos/orient is determined + updated_xfm = CONS_XFM; + updated_pos = CONS_POS; + updated_aim = CONS_AIM; + updated_xfm.setPosition(updated_pos); + updated_scale = CONS_SCALE; + updated_color = CONS_COLOR; + + if (params.vis > 1.0f) + fade_value = 1.0f; + else + fade_value = params.vis; + + if (last_fade_value != fade_value) + { + do_fades = true; + last_fade_value = fade_value; + } + else + { + do_fades = (fade_value < 1.0f); + } + + if (!ea_update(dt)) + { + is_aborted = true; + Con::errorf("afxEffectWrapper::update() -- effect %s ended unexpectedly.", datablock->getName()); + } + + return true; +} + +void afxEffectWrapper::stop() +{ + if (!datablock->requiresStop(ew_timing)) + return; + + stopped = true; + + // this resets full_lifetime so it starts to shrink or fade + if (full_lifetime == INFINITE_LIFETIME) + { + full_lifetime = (elapsed - ew_timing.delay) + afterStopTime(); + life_end = elapsed; + if (ew_timing.fade_out_time > 0) + fade_out_start = elapsed; + } +} + +void afxEffectWrapper::cleanup(bool was_stopped) +{ + ea_finish(was_stopped); + if (!effect_cons_id.undefined()) + { + cons_mgr->setReferenceEffect(effect_cons_id, 0); + effect_cons_id = afxConstraintID(); + } +} + +void afxEffectWrapper::setScopeStatus(bool in_scope) +{ + if (this->in_scope != in_scope) + { + this->in_scope = in_scope; + ea_set_scope_status(in_scope); + } +} + +bool afxEffectWrapper::isDone() +{ + if (!datablock->is_looping) + return (elapsed >= (life_end + ew_timing.fade_out_time)); + + return false; +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +// static +afxEffectWrapper* afxEffectWrapper::ew_create(afxChoreographer* choreographer, + afxEffectWrapperData* datablock, + afxConstraintMgr* cons_mgr, + F32 time_factor, + S32 group_index) +{ + afxEffectWrapper* adapter = datablock->effect_desc->create(); + + if (adapter) + { + adapter->group_index = (datablock->group_index != -1) ? datablock->group_index : group_index; + adapter->ew_init(choreographer, datablock, cons_mgr, time_factor); + } + + return adapter; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +Vector* afxEffectAdapterDesc::adapters = 0; + +afxEffectAdapterDesc::afxEffectAdapterDesc() +{ + if (!adapters) + adapters = new Vector; + + adapters->push_back(this); +} + +bool afxEffectAdapterDesc::identifyEffect(afxEffectWrapperData* ew) +{ + if (!ew || !ew->effect_data) + { + Con::errorf("afxEffectAdapterDesc::identifyEffect() -- effect datablock was not specified."); + return false; + } + + if (!adapters) + { + Con::errorf("afxEffectAdapterDesc::identifyEffect() -- adapter registration list has not been allocated."); + return false; + } + + if (adapters->size() == 0) + { + Con::errorf("afxEffectAdapterDesc::identifyEffect() -- no effect adapters have been registered."); + return false; + } + + for (S32 i = 0; i < adapters->size(); i++) + { + if ((*adapters)[i]->testEffectType(ew->effect_data)) + { + ew->effect_desc = (*adapters)[i]; + (*adapters)[i]->prepEffect(ew); + return true; + } + } + + Con::errorf("afxEffectAdapterDesc::identifyEffect() -- effect %s has an undefined type. -- %d", + ew->effect_data->getName(), adapters->size()); + return false; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/afxEffectWrapper.h b/Engine/source/afx/afxEffectWrapper.h new file mode 100644 index 000000000..8d5ef646c --- /dev/null +++ b/Engine/source/afx/afxEffectWrapper.h @@ -0,0 +1,392 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_EFFECT_WRAPPER_H_ +#define _AFX_EFFECT_WRAPPER_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" +#include "afxEffectDefs.h" +#include "afxConstraint.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +struct afxEffectTimingData +{ + F32 delay; + F32 lifetime; + F32 fade_in_time; + F32 fade_out_time; + F32 residue_lifetime; + F32 residue_fadetime; + F32 life_bias; + Point2F fadein_ease; + Point2F fadeout_ease; + + afxEffectTimingData() + { + delay = 0.0f; + lifetime = 0.0f; + fade_in_time = 0.0f; + fade_out_time = 0.0f; + residue_lifetime = 0.0f; + residue_fadetime = 0.0f; + life_bias = 1.0f; + fadein_ease.set(0.0f, 1.0f); + fadeout_ease.set(0.0f, 1.0f); + } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEffectWrapperData; +class afxAnimCurve; + +class afxEffectAdapterDesc +{ +private: + static Vector* adapters; + +public: + /*C*/ afxEffectAdapterDesc(); + + virtual bool testEffectType(const SimDataBlock*) const=0; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const=0; + virtual bool runsOnServer(const afxEffectWrapperData*) const=0; + virtual bool runsOnClient(const afxEffectWrapperData*) const=0; + virtual bool isPositional(const afxEffectWrapperData*) const { return true; } + virtual void prepEffect(afxEffectWrapperData*) const { } + + virtual afxEffectWrapper* create() const=0; + + static bool identifyEffect(afxEffectWrapperData*); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_BaseData; + +class afxEffectBaseData : public GameBaseData, public afxEffectDefs +{ + typedef GameBaseData Parent; + +public: + /*C*/ afxEffectBaseData() { } + /*C*/ afxEffectBaseData(const afxEffectBaseData& other, bool temp=false) : GameBaseData(other, temp){ } + + virtual void gather_cons_defs(Vector& defs) { }; + + DECLARE_CONOBJECT(afxEffectBaseData); + DECLARE_CATEGORY("AFX"); +}; + +//class afxEffectWrapperData : public GameBaseData, public afxEffectDefs +class afxEffectWrapperData : public afxEffectBaseData +{ + //typedef GameBaseData Parent; + typedef afxEffectBaseData Parent; + + bool do_id_convert; + +public: + enum { MAX_CONDITION_STATES = 4 }; + +public: + StringTableEntry effect_name; + bool use_as_cons_obj; + bool use_ghost_as_cons_obj; + + StringTableEntry cons_spec; + StringTableEntry pos_cons_spec; + StringTableEntry orient_cons_spec; + StringTableEntry aim_cons_spec; + StringTableEntry life_cons_spec; + // + afxConstraintDef cons_def; + afxConstraintDef pos_cons_def; + afxConstraintDef orient_cons_def; + afxConstraintDef aim_cons_def; + afxConstraintDef life_cons_def; + + afxEffectTimingData ewd_timing; + U32 inherit_timing; + + F32 scale_factor; // scale size if applicable + F32 rate_factor; // scale rate if applicable + F32 user_fade_out_time; + + bool is_looping; + U32 n_loops; + F32 loop_gap_time; + + bool ignore_time_factor; + bool propagate_time_factor; + + ByteRange ranking_range; + ByteRange lod_range; + S32 life_conds; + bool effect_enabled; + U32 exec_cond_on_bits[MAX_CONDITION_STATES]; + U32 exec_cond_off_bits[MAX_CONDITION_STATES]; + U32 exec_cond_bitmasks[MAX_CONDITION_STATES]; + + S32 data_ID; + + afxXM_BaseData* xfm_modifiers[MAX_XFM_MODIFIERS]; + + Box3F forced_bbox; + bool update_forced_bbox; + + S8 sort_priority; + Point3F direction; + F32 speed; + F32 mass; + + bool borrow_altitudes; + StringTableEntry vis_keys_spec; + afxAnimCurve* vis_keys; + + SimDataBlock* effect_data; + afxEffectAdapterDesc* effect_desc; + + S32 group_index; + + void parse_cons_specs(); + void parse_vis_keys(); + void gather_cons_defs(Vector& defs); + void pack_mods(BitStream*, afxXM_BaseData* mods[], bool packed); + void unpack_mods(BitStream*, afxXM_BaseData* mods[]); + +public: + /*C*/ afxEffectWrapperData(); + /*C*/ afxEffectWrapperData(const afxEffectWrapperData&, bool = false); + /*D*/ ~afxEffectWrapperData(); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + virtual void onPerformSubstitutions(); + + bool requiresStop(const afxEffectTimingData& timing) { return effect_desc->requiresStop(this, timing); } + bool runsOnServer() { return effect_desc->runsOnServer(this); } + bool runsOnClient() { return effect_desc->runsOnClient(this); } + bool runsHere(bool server_here) { return (server_here) ? runsOnServer() : runsOnClient(); } + bool isPositional() { return effect_desc->isPositional(this); } + bool testExecConditions(U32 conditions); + + F32 afterStopTime() { return ewd_timing.fade_out_time; } + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxEffectWrapperData); + DECLARE_CATEGORY("AFX"); +}; + +inline bool afxEffectWrapperData::testExecConditions(U32 conditions) +{ + if (exec_cond_bitmasks[0] == 0) + return true; + + if ((exec_cond_bitmasks[0] & conditions) == exec_cond_on_bits[0]) + return true; + + for (S32 i = 1; i < MAX_CONDITION_STATES; i++) + { + if (exec_cond_bitmasks[i] == 0) + return false; + if ((exec_cond_bitmasks[i] & conditions) == exec_cond_on_bits[i]) + return true; + } + return false; +} + +typedef Vector afxEffectList; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectWrapper +// +// NOTE -- this not a subclass of GameBase... it is only meant to exist on +// the client-side. + +class ShapeBase; +class GameBase; +class TSShape; +class TSShapeInstance; +class SceneObject; +class afxConstraint; +class afxConstraintMgr; +class afxChoreographer; +class afxXM_Base; + +class afxEffectWrapper : public SimObject, public afxEffectDefs +{ + typedef SimObject Parent; + friend class afxEffectVector; + +private: + bool test_life_conds(); + +protected: + afxEffectWrapperData* datablock; + + afxEffectTimingData ew_timing; + + F32 fade_in_end; + F32 fade_out_start; + F32 full_lifetime; + + F32 time_factor; + F32 prop_time_factor; + + afxChoreographer* choreographer; + afxConstraintMgr* cons_mgr; + + afxConstraintID pos_cons_id; + afxConstraintID orient_cons_id; + afxConstraintID aim_cons_id; + afxConstraintID life_cons_id; + + afxConstraintID effect_cons_id; + + F32 elapsed; + F32 life_elapsed; + F32 life_end; + bool stopped; + bool cond_alive; + + U32 n_updates; + + MatrixF updated_xfm; + Point3F updated_pos; + Point3F updated_aim; + Point3F updated_scale; + LinearColorF updated_color; + + F32 fade_value; + F32 last_fade_value; + + bool do_fade_inout; + bool do_fades; + bool in_scope; + bool is_aborted; + + U8 effect_flags; + + afxXM_Base* xfm_modifiers[MAX_XFM_MODIFIERS]; + + F32 live_scale_factor; + F32 live_fade_factor; + F32 terrain_altitude; + F32 interior_altitude; + + S32 group_index; + +public: + /*C*/ afxEffectWrapper(); + virtual ~afxEffectWrapper(); + + void ew_init(afxChoreographer*, afxEffectWrapperData*, afxConstraintMgr*, + F32 time_factor); + + F32 getFullLifetime() { return ew_timing.lifetime + ew_timing.fade_out_time; } + F32 getTimeFactor() { return time_factor; } + afxConstraint* getPosConstraint() { return cons_mgr->getConstraint(pos_cons_id); } + afxConstraint* getOrientConstraint() { return cons_mgr->getConstraint(orient_cons_id); } + afxConstraint* getAimConstraint() { return cons_mgr->getConstraint(aim_cons_id); } + afxConstraint* getLifeConstraint() { return cons_mgr->getConstraint(life_cons_id); } + afxChoreographer* getChoreographer() { return choreographer; } + + virtual bool isDone(); + virtual bool deleteWhenStopped() { return false; } + F32 afterStopTime() { return ew_timing.fade_out_time; } + bool isAborted() const { return is_aborted; } + + void prestart(); + bool start(F32 timestamp); + bool update(F32 dt); + void stop(); + void cleanup(bool was_stopped=false); + void setScopeStatus(bool flag); + + virtual void ea_set_datablock(SimDataBlock*) { } + virtual bool ea_start() { return true; } + virtual bool ea_update(F32 dt) { return true; } + virtual void ea_finish(bool was_stopped) { } + virtual void ea_set_scope_status(bool flag) { } + virtual bool ea_is_enabled() { return true; } + virtual SceneObject* ea_get_scene_object() const { return 0; } + U32 ea_get_triggers() const { return 0; } + + void getUpdatedPosition(Point3F& pos) { pos = updated_pos;} + void getUpdatedTransform(MatrixF& xfm) { xfm = updated_xfm; } + void getUpdatedScale(Point3F& scale) { scale = updated_scale; } + void getUpdatedColor(LinearColorF& color) { color = updated_color; } + virtual void getUpdatedBoxCenter(Point3F& pos) { pos = updated_pos;} + + virtual void getUnconstrainedPosition(Point3F& pos) { pos.zero();} + virtual void getUnconstrainedTransform(MatrixF& xfm) { xfm.identity(); } + virtual void getBaseColor(LinearColorF& color) { color.set(1.0f, 1.0f, 1.0f, 1.0f); } + + SceneObject* getSceneObject() const { return ea_get_scene_object(); } + U32 getTriggers() const { return ea_get_triggers(); } + + F32 getMass() { return datablock->mass; } + Point3F getDirection() { return datablock->direction; } + F32 getSpeed() { return datablock->speed; } + + virtual TSShape* getTSShape() { return 0; } + virtual TSShapeInstance* getTSShapeInstance() { return 0; } + + virtual U32 setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans) { return 0; } + virtual void resetAnimation(U32 tag) { } + virtual F32 getAnimClipDuration(const char* clip) { return 0.0f; } + + void setTerrainAltitude(F32 alt) { terrain_altitude = alt; } + void setInteriorAltitude(F32 alt) { interior_altitude = alt; } + void getAltitudes(F32& terr_alt, F32& inter_alt) const { terr_alt = terrain_altitude; inter_alt = interior_altitude; } + + void setGroupIndex(S32 idx) { group_index = idx; } + S32 getGroupIndex() const { return group_index; } + + bool inScope() const { return in_scope; } + +public: + static void initPersistFields(); + + static afxEffectWrapper* ew_create(afxChoreographer*, afxEffectWrapperData*, afxConstraintMgr*, F32 time_factor, S32 group_index=0); + + DECLARE_CONOBJECT(afxEffectWrapper); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_EFFECT_WRAPPER_H_ diff --git a/Engine/source/afx/afxEffectron.cpp b/Engine/source/afx/afxEffectron.cpp new file mode 100644 index 000000000..b33fd9d99 --- /dev/null +++ b/Engine/source/afx/afxEffectron.cpp @@ -0,0 +1,1119 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" +#include "T3D/gameBase/gameConnection.h" +#include "sfx/sfxSystem.h" +#include "math/mathIO.h" + +#include "afx/afxChoreographer.h" +#include "afx/afxEffectron.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectronData::ewValidator +// +// When an effect is added using "addEffect", this validator intercepts the value +// and adds it to the dynamic effects list. +// +void afxEffectronData::ewValidator::validateType(SimObject* object, void* typePtr) +{ + afxEffectronData* eff_data = dynamic_cast(object); + afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr); + + if (eff_data && ew) + { + eff_data->fx_list.push_back(*ew); + *ew = 0; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class EffectronFinishStartupEvent : public SimEvent +{ +public: + void process(SimObject* obj) + { + afxEffectron* eff = dynamic_cast(obj); + if (eff) + eff->finish_startup(); + } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectronData + +IMPLEMENT_CO_DATABLOCK_V1(afxEffectronData); + +ConsoleDocClass( afxEffectronData, + "@brief Defines the properties of an afxEffectron.\n\n" + + "@ingroup afxChoreographers\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxEffectronData::afxEffectronData() +{ + duration = 0.0f; + n_loops = 1; + + // dummy entry holds effect-wrapper pointer while a special validator + // grabs it and adds it to an appropriate effects list + dummy_fx_entry = NULL; + + // marked true if datablock ids need to + // be converted into pointers + do_id_convert = false; +} + +afxEffectronData::afxEffectronData(const afxEffectronData& other, bool temp_clone) : afxChoreographerData(other, temp_clone) +{ + duration = other.duration; + n_loops = other.n_loops; + dummy_fx_entry = other.dummy_fx_entry; + do_id_convert = other.do_id_convert; + fx_list = other.fx_list; +} + +void afxEffectronData::reloadReset() +{ + fx_list.clear(); +} + +#define myOffset(field) Offset(field, afxEffectronData) + +void afxEffectronData::initPersistFields() +{ + addField("duration", TypeF32, myOffset(duration), + "..."); + addField("numLoops", TypeS32, myOffset(n_loops), + "..."); + // effect lists + // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list + static ewValidator emptyValidator(0); + + addFieldV("addEffect", TYPEID(), myOffset(dummy_fx_entry), &emptyValidator, + "..."); + + Parent::initPersistFields(); + + // disallow some field substitutions + disableFieldSubstitutions("addEffect"); +} + +bool afxEffectronData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxEffectronData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed) +{ + stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS); + for (int i = 0; i < fx.size(); i++) + writeDatablockID(stream, fx[i], packed); +} + +void afxEffectronData::unpack_fx(BitStream* stream, afxEffectList& fx) +{ + fx.clear(); + S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS); + for (int i = 0; i < n_fx; i++) + fx.push_back((afxEffectWrapperData*)readDatablockID(stream)); +} + +void afxEffectronData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(duration); + stream->write(n_loops); + + pack_fx(stream, fx_list, packed); +} + +void afxEffectronData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&duration); + stream->read(&n_loops); + + do_id_convert = true; + unpack_fx(stream, fx_list); +} + +bool afxEffectronData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) + { + if (do_id_convert) + { + for (S32 i = 0; i < fx_list.size(); i++) + { + SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]); + if (db_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(db_id, fx_list[i])) + { + Con::errorf(ConsoleLogEntry::General, + "afxEffectronData::preload() -- bad datablockId: 0x%x", + db_id); + } + } + } + do_id_convert = false; + } + } + + return true; +} + +void afxEffectronData::gatherConstraintDefs(Vector& defs) +{ + afxConstraintDef::gather_cons_defs(defs, fx_list); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod(afxEffectronData, reset, void, (),, + "Resets an effectron datablock during reload.\n\n" + "@ingroup AFX") +{ + object->reloadReset(); +} + +DefineEngineMethod(afxEffectronData, addEffect, void, (afxEffectBaseData* effect),, + "Adds an effect (wrapper or group) to an effectron's phase.\n\n" + "@ingroup AFX") +{ + if (!effect) + { + Con::errorf("afxEffectronData::addEffect() -- missing afxEffectWrapperData."); + return; + } + + object->fx_list.push_back(effect); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectron + +IMPLEMENT_CO_NETOBJECT_V1(afxEffectron); + +ConsoleDocClass( afxEffectron, + "@brief A basic effects choreographer.\n\n" + + "@ingroup afxChoreographers\n" + "@ingroup AFX\n" +); + +StringTableEntry afxEffectron::CAMERA_CONS; +StringTableEntry afxEffectron::LISTENER_CONS; + +void afxEffectron::init() +{ + // setup static predefined constraint names + if (CAMERA_CONS == 0) + { + CAMERA_CONS = StringTable->insert("camera"); + LISTENER_CONS = StringTable->insert("listener"); + } + + // afxEffectron is always in scope, however the effects used + // do their own scoping in that they will shut off if their + // position constraint leaves scope. + // + // note -- ghosting is delayed until constraint + // initialization is done. + // + //mNetFlags.set(Ghostable | ScopeAlways); + mNetFlags.clear(Ghostable | ScopeAlways); + + datablock = NULL; + exeblock = NULL; + + constraints_initialized = false; + scoping_initialized = false; + + effect_state = (U8) INACTIVE_STATE; + effect_elapsed = 0; + + // define named constraints + constraint_mgr->defineConstraint(CAMERA_CONSTRAINT, CAMERA_CONS); + constraint_mgr->defineConstraint(POINT_CONSTRAINT, LISTENER_CONS); + + active_phrase = NULL; + time_factor = 1.0f; + camera_cons_obj = 0; + + marks_mask = 0; +} + +afxEffectron::afxEffectron() +{ + started_with_newop = true; + init(); +} + +afxEffectron::afxEffectron(bool not_default) +{ + started_with_newop = false; + init(); +} + +afxEffectron::~afxEffectron() +{ + delete active_phrase; + + if (datablock && datablock->isTempClone()) + { + delete datablock; + datablock = 0; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +// STANDARD OVERLOADED METHODS // + +bool afxEffectron::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + datablock = dynamic_cast(dptr); + if (!datablock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + if (isServerObject() && started_with_newop) + { + // copy dynamic fields from the datablock but + // don't replace fields with a value + assignDynamicFieldsFrom(dptr, arcaneFX::sParameterFieldPrefix, true); + } + + exeblock = datablock; + + if (isClientObject()) + { + // make a temp datablock clone if there are substitutions + if (datablock->getSubstitutionCount() > 0) + { + afxEffectronData* orig_db = datablock; + datablock = new afxEffectronData(*orig_db, true); + exeblock = orig_db; + // Don't perform substitutions yet, the effectrons's dynamic fields haven't + // arrived yet and the substitutions may refer to them. Hold off and do + // in in the onAdd() method. + } + } + else if (started_with_newop) + { + // make a temp datablock clone if there are substitutions + if (datablock->getSubstitutionCount() > 0) + { + afxEffectronData* orig_db = datablock; + datablock = new afxEffectronData(*orig_db, true); + exeblock = orig_db; + orig_db->performSubstitutions(datablock, this, ranking); + } + } + + return true; +} + +void afxEffectron::processTick(const Move* m) +{ + Parent::processTick(m); + + // don't process moves or client ticks + if (m != 0 || isClientObject()) + return; + + process_server(); +} + +void afxEffectron::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + process_client(dt); +} + +bool afxEffectron::onAdd() +{ + if (!Parent::onAdd()) + return false; + + if (isClientObject()) + { + if (datablock->isTempClone()) + { + afxEffectronData* orig_db = (afxEffectronData*)exeblock; + orig_db->performSubstitutions(datablock, this, ranking); + } + } + else if (started_with_newop && !postpone_activation) + { + if (!activationCallInit()) + return false; + activate(); + } + + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +U32 afxEffectron::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) +{ + S32 mark_stream_pos = stream->getCurPos(); + + U32 retMask = Parent::packUpdate(conn, mask, stream); + + // InitialUpdate + if (stream->writeFlag(mask & InitialUpdateMask)) + { + // pack extra object's ghost index or scope id if not yet ghosted + if (stream->writeFlag(dynamic_cast(extra) != 0)) + { + NetObject* net_extra = (NetObject*)extra; + S32 ghost_idx = conn->getGhostIndex(net_extra); + if (stream->writeFlag(ghost_idx != -1)) + stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount); + else + { + if (stream->writeFlag(net_extra->getScopeId() > 0)) + { + stream->writeInt(net_extra->getScopeId(), NetObject::SCOPE_ID_BITS); + } + } + } + + stream->write(time_factor); + + GameConnection* gconn = dynamic_cast(conn); + bool zoned_in = (gconn) ? gconn->isZonedIn() : false; + if (stream->writeFlag(zoned_in)) + pack_constraint_info(conn, stream); + } + + // StateEvent or SyncEvent + if (stream->writeFlag((mask & StateEventMask) || (mask & SyncEventMask))) + { + stream->write(marks_mask); + stream->write(effect_state); + stream->write(effect_elapsed); + } + + // SyncEvent + bool do_sync_event = ((mask & SyncEventMask) && !(mask & InitialUpdateMask)); + if (stream->writeFlag(do_sync_event)) + { + pack_constraint_info(conn, stream); + } + + check_packet_usage(conn, stream, mark_stream_pos, "afxEffectron:"); + AssertISV(stream->isValid(), "afxEffectron::packUpdate(): write failure occurred, possibly caused by packet-size overrun."); + + return retMask; +} + +//~~~~~~~~~~~~~~~~~~~~// + +void afxEffectron::unpackUpdate(NetConnection * conn, BitStream * stream) +{ + Parent::unpackUpdate(conn, stream); + + bool initial_update = false; + bool zoned_in = true; + bool do_sync_event = false; + U8 new_marks_mask = 0; + U8 new_state = INACTIVE_STATE; + F32 new_elapsed = 0; + + // InitialUpdate Only + if (stream->readFlag()) + { + initial_update = true; + + // extra sent + if (stream->readFlag()) + { + // cleanup? + if (stream->readFlag()) // is ghost_idx + { + S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount); + extra = dynamic_cast(conn->resolveGhost(ghost_idx)); + } + else + { + if (stream->readFlag()) // has scope_id + { + // JTF NOTE: U16 extra_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS); + stream->readInt(NetObject::SCOPE_ID_BITS); + } + } + } + + stream->read(&time_factor); + + // if client is marked as fully zoned in + if ((zoned_in = stream->readFlag()) == true) + { + unpack_constraint_info(conn, stream); + init_constraints(); + } + } + + // StateEvent or SyncEvent + // this state data is sent for both state-events and + // sync-events + if (stream->readFlag()) + { + stream->read(&new_marks_mask); + stream->read(&new_state); + stream->read(&new_elapsed); + + marks_mask = new_marks_mask; + } + + // SyncEvent + do_sync_event = stream->readFlag(); + if (do_sync_event) + { + unpack_constraint_info(conn, stream); + init_constraints(); + } + + //~~~~~~~~~~~~~~~~~~~~// + + if (!zoned_in) + effect_state = LATE_STATE; + + // need to adjust state info to get all synced up with spell on server + if (do_sync_event && !initial_update) + sync_client(new_marks_mask, new_state, new_elapsed); +} + +void afxEffectron::sync_with_clients() +{ + setMaskBits(SyncEventMask); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private + +bool afxEffectron::state_expired() +{ + afxPhrase* phrase = (effect_state == ACTIVE_STATE) ? active_phrase : NULL; + + if (phrase) + { + if (phrase->expired(effect_elapsed)) + return (!phrase->recycle(effect_elapsed)); + return false; + } + + return true; +} + +void afxEffectron::init_constraints() +{ + if (constraints_initialized) + { + //Con::printf("CONSTRAINTS ALREADY INITIALIZED"); + return; + } + + Vector defs; + datablock->gatherConstraintDefs(defs); + + constraint_mgr->initConstraintDefs(defs, isServerObject()); + + if (isServerObject()) + { + // find local camera + camera_cons_obj = get_camera(); + if (camera_cons_obj) + camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj); + } + else // if (isClientObject()) + { + // find local camera + camera_cons_obj = get_camera(); + if (camera_cons_obj) + camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj); + + // find local listener + Point3F listener_pos; + listener_pos = SFX->getListener().getTransform().getPosition(); + listener_cons_id = constraint_mgr->setReferencePoint(LISTENER_CONS, listener_pos); + } + + constraint_mgr->adjustProcessOrdering(this); + + constraints_initialized = true; +} + +void afxEffectron::init_scoping() +{ + if (scoping_initialized) + { + //Con::printf("SCOPING ALREADY INITIALIZED"); + return; + } + + if (isServerObject()) + { + if (explicit_clients.size() > 0) + { + for (U32 i = 0; i < explicit_clients.size(); i++) + explicit_clients[i]->objectLocalScopeAlways(this); + } + else + { + mNetFlags.set(Ghostable); + setScopeAlways(); + } + scoping_initialized = true; + } +} + +void afxEffectron::setup_active_fx() +{ + active_phrase = new afxPhrase(isServerObject(), /*willStop=*/true); + + if (active_phrase) + { + active_phrase->init(datablock->fx_list, datablock->duration, this, time_factor, datablock->n_loops); + } +} + +bool afxEffectron::cleanup_over() +{ + if (active_phrase && !active_phrase->isEmpty()) + return false; + + return true; +} + +void afxEffectron::inflictDamage(const char * label, const char* flavor, SimObjectId target_id, + F32 amount, U8 n, F32 ad_amount, F32 radius, Point3F pos, F32 impulse) +{ + char *posArg = Con::getArgBuffer(64); + dSprintf(posArg, 64, "%f %f %f", pos.x, pos.y, pos.z); + Con::executef(exeblock, "onDamage", + getIdString(), + label, + flavor, + Con::getIntArg(target_id), + Con::getFloatArg(amount), + Con::getIntArg(n), + posArg, + Con::getFloatArg(ad_amount), + Con::getFloatArg(radius), + Con::getFloatArg(impulse)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private + +void afxEffectron::process_server() +{ + if (effect_state != INACTIVE_STATE) + effect_elapsed += TickSec; + + U8 pending_state = effect_state; + + // check for state changes + switch (effect_state) + { + case INACTIVE_STATE: + if (marks_mask & MARK_ACTIVATE) + pending_state = ACTIVE_STATE; + break; + case ACTIVE_STATE: + if (marks_mask & MARK_INTERRUPT) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_SHUTDOWN) + pending_state = CLEANUP_STATE; + else if (state_expired()) + pending_state = CLEANUP_STATE; + break; + case CLEANUP_STATE: + if (cleanup_over()) + pending_state = DONE_STATE; + break; + } + + if (effect_state != pending_state) + change_state_s(pending_state); + + if (effect_state == INACTIVE_STATE) + return; + + //--------------------------// + + // sample the constraints + constraint_mgr->sample(TickSec, Platform::getVirtualMilliseconds()); + + if (active_phrase) + active_phrase->update(TickSec, effect_elapsed); +} + +void afxEffectron::change_state_s(U8 pending_state) +{ + if (effect_state == pending_state) + return; + + switch (effect_state) + { + case INACTIVE_STATE: + break; + case ACTIVE_STATE: + leave_active_state_s(); + break; + case CLEANUP_STATE: + break; + case DONE_STATE: + break; + } + + effect_state = pending_state; + + switch (pending_state) + { + case INACTIVE_STATE: + break; + case ACTIVE_STATE: + enter_active_state_s(); + break; + case CLEANUP_STATE: + enter_cleanup_state_s(); + break; + case DONE_STATE: + enter_done_state_s(); + break; + } +} + +void afxEffectron::enter_done_state_s() +{ + postEvent(DEACTIVATE_EVENT); + + F32 done_time = effect_elapsed; + + if (active_phrase) + { + F32 phrase_done; + if (active_phrase->willStop() && active_phrase->isInfinite()) + phrase_done = effect_elapsed + active_phrase->calcAfterLife(); + else + phrase_done = active_phrase->calcDoneTime(); + if (phrase_done > done_time) + done_time = phrase_done; + } + + F32 time_left = done_time - effect_elapsed; + if (time_left < 0) + time_left = 0; + + Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500); + + // CALL SCRIPT afxEffectronData::onDeactivate(%eff) + Con::executef(exeblock, "onDeactivate", getIdString()); +} + +void afxEffectron::enter_active_state_s() +{ + // stamp constraint-mgr starting time + constraint_mgr->setStartTime(Platform::getVirtualMilliseconds()); + effect_elapsed = 0; + + setup_dynamic_constraints(); + + // start casting effects + setup_active_fx(); + if (active_phrase) + active_phrase->start(effect_elapsed, effect_elapsed); +} + +void afxEffectron::leave_active_state_s() +{ + if (active_phrase) + active_phrase->stop(effect_elapsed); +} + +void afxEffectron::enter_cleanup_state_s() +{ + postEvent(SHUTDOWN_EVENT); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private + +void afxEffectron::process_client(F32 dt) +{ + effect_elapsed += dt; + + U8 pending_state = effect_state; + + // check for state changes + switch (effect_state) + { + case INACTIVE_STATE: + if (marks_mask & MARK_ACTIVATE) + pending_state = ACTIVE_STATE; + break; + case ACTIVE_STATE: + if (marks_mask & MARK_INTERRUPT) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_SHUTDOWN) + pending_state = CLEANUP_STATE; + else if (state_expired()) + pending_state = CLEANUP_STATE; + break; + case CLEANUP_STATE: + if (cleanup_over()) + pending_state = DONE_STATE; + break; + } + + if (effect_state != pending_state) + change_state_c(pending_state); + + if (effect_state == INACTIVE_STATE) + return; + + //--------------------------// + + // update the listener constraint position + if (!listener_cons_id.undefined()) + { + Point3F listener_pos; + listener_pos = SFX->getListener().getTransform().getPosition(); + constraint_mgr->setReferencePoint(listener_cons_id, listener_pos); + } + + // find local camera position + Point3F cam_pos; + SceneObject* current_cam = get_camera(&cam_pos); + + // detect camera changes + if (!camera_cons_id.undefined() && current_cam != camera_cons_obj) + { + constraint_mgr->setReferenceObject(camera_cons_id, current_cam); + camera_cons_obj = current_cam; + } + + // sample the constraints + constraint_mgr->sample(dt, Platform::getVirtualMilliseconds(), (current_cam) ? &cam_pos : 0); + + // update active effects lists + if (active_phrase) + active_phrase->update(dt, effect_elapsed); +} + +void afxEffectron::change_state_c(U8 pending_state) +{ + if (effect_state == pending_state) + return; + + switch (effect_state) + { + case INACTIVE_STATE: + break; + case ACTIVE_STATE: + leave_active_state_c(); + break; + case CLEANUP_STATE: + break; + case DONE_STATE: + break; + } + + effect_state = pending_state; + + switch (pending_state) + { + case INACTIVE_STATE: + break; + case ACTIVE_STATE: + enter_active_state_c(effect_elapsed); + break; + case CLEANUP_STATE: + break; + case DONE_STATE: + break; + } +} + +void afxEffectron::enter_active_state_c(F32 starttime) +{ + // stamp constraint-mgr starting time + constraint_mgr->setStartTime(Platform::getVirtualMilliseconds() - (U32)(effect_elapsed*1000)); + ///effect_elapsed = 0; + + setup_dynamic_constraints(); + + setup_active_fx(); + if (active_phrase) + active_phrase->start(starttime, effect_elapsed); +} + +void afxEffectron::leave_active_state_c() +{ + if (active_phrase) + active_phrase->stop(effect_elapsed); +} + +void afxEffectron::sync_client(U16 marks, U8 state, F32 elapsed) +{ + //Con::printf("SYNC marks=%d old_state=%d state=%d elapsed=%g", + // marks, effect_state, state, elapsed); + + if (effect_state != LATE_STATE) + return; + + marks_mask = marks; + + // don't want to be started on late zoning clients + if (!datablock->exec_on_new_clients) + { + effect_state = DONE_STATE; + } + + // it looks like we're ghosting pretty late and + // should just return to the inactive state. + else if (marks & (MARK_INTERRUPT | MARK_DEACTIVATE | MARK_SHUTDOWN)) + { + effect_state = DONE_STATE; + } + + // it looks like we should be in the active state. + else if (marks & MARK_ACTIVATE) + { + effect_state = ACTIVE_STATE; + effect_elapsed = elapsed; + enter_active_state_c(0.0); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// public: + +void afxEffectron::postEvent(U8 event) +{ + setMaskBits(StateEventMask); + + switch (event) + { + case ACTIVATE_EVENT: + marks_mask |= MARK_ACTIVATE; + break; + case SHUTDOWN_EVENT: + marks_mask |= MARK_SHUTDOWN; + break; + case DEACTIVATE_EVENT: + marks_mask |= MARK_DEACTIVATE; + break; + case INTERRUPT_EVENT: + marks_mask |= MARK_INTERRUPT; + break; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxEffectron::finish_startup() +{ + init_constraints(); + init_scoping(); + postEvent(afxEffectron::ACTIVATE_EVENT); +} + +// static +afxEffectron* +afxEffectron::start_effect(afxEffectronData* datablock, SimObject* extra) +{ + AssertFatal(datablock != NULL, "Datablock is missing."); + + afxEffectronData* exeblock = datablock; + + SimObject* param_holder = new SimObject(); + if (!param_holder->registerObject()) + { + Con::errorf("afxEffectron: failed to register parameter object."); + delete param_holder; + return 0; + } + + param_holder->assignDynamicFieldsFrom(datablock, arcaneFX::sParameterFieldPrefix); + if (extra) + { + // copy dynamic fields from the extra object to the param holder + param_holder->assignDynamicFieldsFrom(extra, arcaneFX::sParameterFieldPrefix); + } + + // CALL SCRIPT afxEffectronData::onPreactivate(%params, %extra) + const char* result = Con::executef(datablock, "onPreactivate", + Con::getIntArg(param_holder->getId()), + (extra) ? Con::getIntArg(extra->getId()) : ""); + if (result && result[0] != '\0' && !dAtob(result)) + { +#if defined(TORQUE_DEBUG) + Con::warnf("afxEffectron: onPreactivate() returned false, effect aborted."); +#endif + Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime()); + return 0; + } + + // make a temp datablock clone if there are substitutions + if (datablock->getSubstitutionCount() > 0) + { + datablock = new afxEffectronData(*exeblock, true); + exeblock->performSubstitutions(datablock, param_holder); + } + + // create a new effectron instance + afxEffectron* eff = new afxEffectron(true); + eff->setDataBlock(datablock); + eff->exeblock = exeblock; + eff->setExtra(extra); + + // copy dynamic fields from the param holder to the effectron + eff->assignDynamicFieldsFrom(param_holder, arcaneFX::sParameterFieldPrefix); + Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime()); + + // register + if (!eff->registerObject()) + { + Con::errorf("afxEffectron: failed to register effectron instance."); + Sim::postEvent(eff, new ObjectDeleteEvent, Sim::getCurrentTime()); + return 0; + } + registerForCleanup(eff); + + eff->activate(); + + return eff; +} + +bool afxEffectron::activationCallInit(bool postponed) +{ + if (postponed && (!started_with_newop || !postpone_activation)) + { + Con::errorf("afxEffectron::activate() -- activate() is only required when creating an effectron with the \"new\" operator " + "and the postponeActivation field is set to \"true\"."); + return false; + } + + return true; +} + +void afxEffectron::activate() +{ + // separating the final part of startup allows the calling script + // to make certain types of calls on the returned effectron that + // need to happen prior to constraint initialization. + Sim::postEvent(this, new EffectronFinishStartupEvent, Sim::getCurrentTime()); + + // CALL SCRIPT afxEffectronData::onActivate(%eff) + Con::executef(exeblock, "onActivate", getIdString()); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// console functions + +DefineEngineMethod(afxEffectron, setTimeFactor, void, (float factor),, + "Sets the time-factor for the effectron.\n\n" + "@ingroup AFX") +{ + object->setTimeFactor(factor); +} + +DefineEngineMethod(afxEffectron, interrupt, void, (),, + "Interrupts and deletes a running effectron.\n\n" + "@ingroup AFX") +{ + object->postEvent(afxEffectron::INTERRUPT_EVENT); +} + +DefineEngineMethod(afxEffectron, activate, void, (),, + "Activates an effectron that was started with postponeActivation=true.\n\n" + "@ingroup AFX") +{ + if (object->activationCallInit(true)) + object->activate(); +} + +DefineEngineFunction(startEffectron, S32, (afxEffectronData* datablock, const char* constraintSource, const char* constraintName, SimObject* extra), + (nullAsType(), nullAsType(), nullAsType(), nullAsType()), + + "Instantiates the effectron defined by datablock.\n\n" + "@ingroup AFX") +{ + if (!datablock) + { + Con::errorf("startEffectron() -- missing valid datablock."); + return 0; + } + + // + // Start the Effectron + // + afxEffectron* eff = afxEffectron::start_effect(datablock, extra); + + // + // Create a constraint from arguments (if specified). + // + if (eff) + { + if (constraintSource && constraintName) + { + if (!eff->addConstraint(constraintSource, constraintName)) + Con::errorf("startEffectron() -- failed to find constraint object [%s].", constraintSource); + } + } + + // + // Return the ID (or 0 if start failed). + // + return (eff) ? eff->getId() : 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/afxEffectron.h b/Engine/source/afx/afxEffectron.h new file mode 100644 index 000000000..9c05a48b3 --- /dev/null +++ b/Engine/source/afx/afxEffectron.h @@ -0,0 +1,216 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_COMPOSITE_EFFECT_H_ +#define _AFX_COMPOSITE_EFFECT_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "console/typeValidators.h" + +#include "afxChoreographer.h" +#include "afxEffectWrapper.h" +#include "afxPhrase.h" + +class afxChoreographerData; +class afxEffectWrapperData; + +class afxEffectronData : public afxChoreographerData +{ + typedef afxChoreographerData Parent; + + class ewValidator : public TypeValidator + { + U32 id; + public: + ewValidator(U32 id) { this->id = id; } + void validateType(SimObject *object, void *typePtr); + }; + + bool do_id_convert; + +public: + F32 duration; + S32 n_loops; + + afxEffectBaseData* dummy_fx_entry; + + afxEffectList fx_list; + +private: + void pack_fx(BitStream* stream, const afxEffectList& fx, bool packed); + void unpack_fx(BitStream* stream, afxEffectList& fx); + +public: + /*C*/ afxEffectronData(); + /*C*/ afxEffectronData(const afxEffectronData&, bool = false); + + virtual void reloadReset(); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + void gatherConstraintDefs(Vector&); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxEffectronData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEffectron + +class afxEffectron : public afxChoreographer +{ + typedef afxChoreographer Parent; + +public: + enum MaskBits + { + StateEventMask = Parent::NextFreeMask << 0, + SyncEventMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + + enum + { + NULL_EVENT, + ACTIVATE_EVENT, + SHUTDOWN_EVENT, + DEACTIVATE_EVENT, + INTERRUPT_EVENT + }; + + enum + { + INACTIVE_STATE, + ACTIVE_STATE, + CLEANUP_STATE, + DONE_STATE, + LATE_STATE + }; + + enum { + MARK_ACTIVATE = BIT(0), + MARK_SHUTDOWN = BIT(1), + MARK_DEACTIVATE = BIT(2), + MARK_INTERRUPT = BIT(3), + }; + + class ObjectDeleteEvent : public SimEvent + { + public: + void process(SimObject *obj) { if (obj) obj->deleteObject(); } + }; + +private: + static StringTableEntry CAMERA_CONS; + static StringTableEntry LISTENER_CONS; + +private: + afxEffectronData* datablock; + SimObject* exeblock; + + bool constraints_initialized; + bool scoping_initialized; + + U8 effect_state; + F32 effect_elapsed; + U8 marks_mask; + afxConstraintID listener_cons_id; + afxConstraintID camera_cons_id; + SceneObject* camera_cons_obj; + afxPhrase* active_phrase; + F32 time_factor; + +private: + void init(); + bool state_expired(); + void init_constraints(); + void init_scoping(); + void setup_active_fx(); + bool cleanup_over(); + +public: + /*C*/ afxEffectron(); + /*C*/ afxEffectron(bool not_default); + /*D*/ ~afxEffectron(); + + // STANDARD OVERLOADED METHODS // + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + virtual void processTick(const Move*); + virtual void advanceTime(F32 dt); + virtual bool onAdd(); + virtual U32 packUpdate(NetConnection*, U32, BitStream*); + virtual void unpackUpdate(NetConnection*, BitStream*); + + virtual void inflictDamage(const char * label, const char* flavor, SimObjectId target, + F32 amt, U8 count, F32 ad_amt, F32 rad, Point3F pos, F32 imp); + virtual void sync_with_clients(); + void finish_startup(); + + DECLARE_CONOBJECT(afxEffectron); + DECLARE_CATEGORY("AFX"); + +private: + void process_server(); + // + void change_state_s(U8 pending_state); + // + void enter_active_state_s(); + void leave_active_state_s(); + void enter_cleanup_state_s(); + void enter_done_state_s(); + +private: + void process_client(F32 dt); + // + void change_state_c(U8 pending_state); + // + void enter_active_state_c(F32 starttime); + void leave_active_state_c(); + + void sync_client(U16 marks, U8 state, F32 elapsed); + +public: + void postEvent(U8 event); + void setTimeFactor(F32 f) { time_factor = (f > 0) ? f : 1.0f; } + F32 getTimeFactor() { return time_factor; } + + bool activationCallInit(bool postponed=false); + void activate(); + +public: + static afxEffectron* start_effect(afxEffectronData*, SimObject* extra); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +#endif // _AFX_EFFECTRON_H_ diff --git a/Engine/source/afx/afxMagicMissile.cpp b/Engine/source/afx/afxMagicMissile.cpp new file mode 100644 index 000000000..36ecd1777 --- /dev/null +++ b/Engine/source/afx/afxMagicMissile.cpp @@ -0,0 +1,2116 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// afxMagicMissile is a heavily modified variation of the stock Projectile class. In +// addition to numerous AFX customizations, it also incorporates functionality based on +// the following TGE resources: +// +// Guided or Seeker Projectiles by Derk Adams +// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6778 +// +// Projectile Ballistic Coefficients (drag factors) by Mark Owen +// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5128 +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "scene/sceneRenderState.h" +#include "scene/sceneManager.h" +#include "core/resourceManager.h" +#include "ts/tsShapeInstance.h" +#include "sfx/sfxTrack.h" +#include "sfx/sfxSource.h" +#include "sfx/sfxSystem.h" +#include "sfx/sfxTypes.h" +#include "math/mathUtils.h" +#include "math/mathIO.h" +#include "sim/netConnection.h" +#include "T3D/fx/particleEmitter.h" +#include "T3D/fx/splash.h" +#include "T3D/physics/physicsPlugin.h" +#include "T3D/physics/physicsWorld.h" +#include "gfx/gfxTransformSaver.h" +#include "T3D/containerQuery.h" +#include "T3D/lightDescription.h" +#include "console/engineAPI.h" +#include "lighting/lightManager.h" + +#include "afx/util/afxEase.h" +#include "afx/afxMagicMissile.h" +#include "afx/afxMagicSpell.h" +#include "afx/afxChoreographer.h" + +class ObjectDeleteEvent : public SimEvent +{ +public: + void process(SimObject *object) + { + object->deleteObject(); + } +}; + +IMPLEMENT_CO_DATABLOCK_V1(afxMagicMissileData); + +ConsoleDocClass( afxMagicMissileData, + "@brief Defines a particular magic-missile type. (Use with afxMagicSpellData.)\n" + "@tsexample\n" + "datablock afxMagicMissileData(Fireball_MM)\n" + "{\n" + " muzzleVelocity = 50;\n" + " velInheritFactor = 0;\n" + " lifetime = 20000;\n" + " isBallistic = true;\n" + " ballisticCoefficient = 0.85;\n" + " gravityMod = 0.05;\n" + " isGuided = true;\n" + " precision = 30;\n" + " trackDelay = 7;\n" + " launchOffset = \"0 0 43.7965\";\n" + " launchOnServerSignal = true;\n" + "};\n" + "@endtsexample\n" + "@ingroup AFX\n" +); + +IMPLEMENT_CO_NETOBJECT_V1(afxMagicMissile); + +ConsoleDocClass( afxMagicMissile, + "@brief Magic-missile class used internally by afxMagicSpell. Properties of individual missile types are defined using afxMagicMissileData.\n" + "@ingroup AFX\n" +); + +/* From stock Projectile code... +IMPLEMENT_CALLBACK( ProjectileData, onExplode, void, ( Projectile* proj, Point3F pos, F32 fade ), + ( proj, pos, fade ), + "Called when a projectile explodes.\n" + "@param proj The projectile exploding.\n" + "@param pos The position of the explosion.\n" + "@param fade The currently fadeValue of the projectile, affects its visibility.\n" + "@see Projectile, ProjectileData\n" + ); + +IMPLEMENT_CALLBACK( ProjectileData, onCollision, void, ( Projectile* proj, SceneObject* col, F32 fade, Point3F pos, Point3F normal ), + ( proj, col, fade, pos, normal ), + "Called when a projectile collides with another object.\n" + "@param proj The projectile colliding.\n" + "@param col The object hit by the projectile.\n" + "@param fade The current fadeValue of the projectile, affects its visibility.\n" + "@param pos The collision position.\n" + "@param normal The collision normal.\n" + "@see Projectile, ProjectileData\n" + ); + +const U32 Projectile::csmStaticCollisionMask = TerrainObjectType | + InteriorObjectType | + StaticObjectType; + +const U32 Projectile::csmDynamicCollisionMask = PlayerObjectType | + VehicleObjectType | + DamagableItemObjectType; + +const U32 Projectile::csmDamageableMask = Projectile::csmDynamicCollisionMask; + +U32 Projectile::smProjectileWarpTicks = 5; +*/ + +//-------------------------------------------------------------------------- +// +afxMagicMissileData::afxMagicMissileData() +{ + projectileShapeName = ST_NULLSTRING; + + sound = NULL; + + /* From stock Projectile code... + explosion = NULL; + explosionId = 0; + + waterExplosion = NULL; + waterExplosionId = 0; + */ + + /* From stock Projectile code... + faceViewer = false; + */ + scale.set( 1.0f, 1.0f, 1.0f ); + + isBallistic = false; + + /* From stock Projectile code... + velInheritFactor = 1.0f; + */ + muzzleVelocity = 50; + /* From stock Projectile code... + impactForce = 0.0f; + + armingDelay = 0; + fadeDelay = 20000 / 32; + lifetime = 20000 / 32; + + activateSeq = -1; + maintainSeq = -1; + */ + + gravityMod = 1.0; + /* From stock Projectile code... + bounceElasticity = 0.999f; + bounceFriction = 0.3f; + */ + + particleEmitter = NULL; + particleEmitterId = 0; + + particleWaterEmitter = NULL; + particleWaterEmitterId = 0; + + splash = NULL; + splashId = 0; + + /* From stock Projectile code... + decal = NULL; + decalId = 0; + */ + + lightDesc = NULL; + lightDescId = 0; + + starting_vel_vec.zero(); + + isGuided = false; + precision = 0; + trackDelay = 0; + ballisticCoefficient = 1.0f; + + followTerrain = false; + followTerrainHeight = 0.1f; + followTerrainAdjustRate = 20.0f; + followTerrainAdjustDelay = 0; + + lifetime = MaxLifetimeTicks; + collision_mask = arcaneFX::sMissileCollisionMask; + + acceleration = 0; + accelDelay = 0; + accelLifetime = 0; + + launch_node = ST_NULLSTRING; + launch_offset.zero(); + launch_offset_server.zero(); + launch_offset_client.zero(); + launch_node_offset.zero(); + launch_pitch = 0; + launch_pan = 0; + launch_cons_s_spec = ST_NULLSTRING; + launch_cons_c_spec = ST_NULLSTRING; + + echo_launch_offset = false; + + wiggle_axis_string = ST_NULLSTRING; + wiggle_num_axis = 0; + wiggle_axis = 0; + + hover_altitude = 0; + hover_attack_distance = 0; + hover_attack_gradient = 0; + hover_time = 0; + + reverse_targeting = false; + + caster_safety_time = U32_MAX; +} + +afxMagicMissileData::afxMagicMissileData(const afxMagicMissileData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + projectileShapeName = other.projectileShapeName; + projectileShape = other.projectileShape; // -- TSShape loads using projectileShapeName + sound = other.sound; + splash = other.splash; + splashId = other.splashId; // -- for pack/unpack of splash ptr + lightDesc = other.lightDesc; + lightDescId = other.lightDescId; // -- for pack/unpack of lightDesc ptr + scale = other.scale; + isBallistic = other.isBallistic; + muzzleVelocity = other.muzzleVelocity; + gravityMod = other.gravityMod; + particleEmitter = other.particleEmitter; + particleEmitterId = other.particleEmitterId; // -- for pack/unpack of particleEmitter ptr + particleWaterEmitter = other.particleWaterEmitter; + particleWaterEmitterId = other.particleWaterEmitterId; // -- for pack/unpack of particleWaterEmitter ptr + + collision_mask = other.collision_mask; + starting_vel_vec = other.starting_vel_vec; + isGuided = other.isGuided; + precision = other.precision; + trackDelay = other.trackDelay; + ballisticCoefficient = other.ballisticCoefficient; + followTerrain = other.followTerrain; + followTerrainHeight = other.followTerrainHeight; + followTerrainAdjustRate = other.followTerrainAdjustRate; + followTerrainAdjustDelay = other.followTerrainAdjustDelay; + lifetime = other.lifetime; + fadeDelay = other.fadeDelay; + acceleration = other.acceleration; + accelDelay = other.accelDelay; + accelLifetime = other.accelLifetime; + launch_node = other.launch_node; + launch_offset = other.launch_offset; + launch_offset_server = other.launch_offset_server; + launch_offset_client = other.launch_offset_client; + launch_node_offset = other.launch_node_offset; + launch_pitch = other.launch_pitch; + launch_pan = other.launch_pan; + launch_cons_s_spec = other.launch_cons_s_spec; + launch_cons_c_spec = other.launch_cons_c_spec; + launch_cons_s_def = other.launch_cons_s_def; + launch_cons_c_def = other.launch_cons_c_def; + echo_launch_offset = other.echo_launch_offset; + wiggle_magnitudes = other.wiggle_magnitudes; + wiggle_speeds = other.wiggle_speeds; + wiggle_axis_string = other.wiggle_axis_string; + wiggle_num_axis = other.wiggle_num_axis; + wiggle_axis = other.wiggle_axis; + hover_altitude = other.hover_altitude; + hover_attack_distance = other.hover_attack_distance; + hover_attack_gradient = other.hover_attack_gradient; + hover_time = other.hover_time; + reverse_targeting = other.reverse_targeting; + caster_safety_time = other.caster_safety_time; +} + +afxMagicMissileData::~afxMagicMissileData() +{ + if (wiggle_axis) + delete [] wiggle_axis; +} + +afxMagicMissileData* afxMagicMissileData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index) +{ + if (!owner || getSubstitutionCount() == 0) + return this; + + afxMagicMissileData* sub_missile_db = new afxMagicMissileData(*this, true); + performSubstitutions(sub_missile_db, owner, index); + + return sub_missile_db; +} + +//-------------------------------------------------------------------------- + +#define myOffset(field) Offset(field, afxMagicMissileData) + +FRangeValidator muzzleVelocityValidator(0, 10000); +FRangeValidator missilePrecisionValidator(0.f, 100.f); +FRangeValidator missileTrackDelayValidator(0, 100000); +FRangeValidator missileBallisticCoefficientValidator(0, 1); + +void afxMagicMissileData::initPersistFields() +{ + static IRangeValidatorScaled ticksFromMS(TickMs, 0, MaxLifetimeTicks); + + addField("particleEmitter", TYPEID(), Offset(particleEmitter, afxMagicMissileData)); + addField("particleWaterEmitter", TYPEID(), Offset(particleWaterEmitter, afxMagicMissileData)); + + addField("projectileShapeName", TypeFilename, Offset(projectileShapeName, afxMagicMissileData)); + addField("scale", TypePoint3F, Offset(scale, afxMagicMissileData)); + + addField("sound", TypeSFXTrackName, Offset(sound, afxMagicMissileData)); + + /* From stock Projectile code... + addField("explosion", TYPEID< ExplosionData >(), Offset(explosion, ProjectileData)); + addField("waterExplosion", TYPEID< ExplosionData >(), Offset(waterExplosion, ProjectileData)); + */ + + addField("splash", TYPEID(), Offset(splash, afxMagicMissileData)); + /* From stock Projectile code... + addField("decal", TYPEID< DecalData >(), Offset(decal, ProjectileData)); + */ + + addField("lightDesc", TYPEID< LightDescription >(), Offset(lightDesc, afxMagicMissileData)); + + addField("isBallistic", TypeBool, Offset(isBallistic, afxMagicMissileData)); + /* From stock Projectile code... + addField("velInheritFactor", TypeF32, Offset(velInheritFactor, ProjectileData)); + */ + addNamedFieldV(muzzleVelocity, TypeF32, afxMagicMissileData, &muzzleVelocityValidator); + /* From stock Projectile code... + addField("impactForce", TypeF32, Offset(impactForce, ProjectileData)); + */ + addNamedFieldV(lifetime, TypeS32, afxMagicMissileData, &ticksFromMS); + /* From stock Projectile code... + addProtectedField("armingDelay", TypeS32, Offset(armingDelay, ProjectileData), &setArmingDelay, &getScaledValue, + "The time in milliseconds before the projectile is armed and will cause damage or explode on impact." ); + + addProtectedField("fadeDelay", TypeS32, Offset(fadeDelay, ProjectileData), &setFadeDelay, &getScaledValue, + "The time in milliseconds when the projectile begins to fade out. Must be less than the lifetime to have an effect." ); + + addField("bounceElasticity", TypeF32, Offset(bounceElasticity, ProjectileData)); + addField("bounceFriction", TypeF32, Offset(bounceFriction, ProjectileData)); + */ + addField("gravityMod", TypeF32, Offset(gravityMod, afxMagicMissileData)); + + // FIELDS ADDED BY MAGIC-MISSILE + + addField("missileShapeName", TypeFilename, myOffset(projectileShapeName)); + addField("missileShapeScale", TypePoint3F, myOffset(scale)); + + addField("startingVelocityVector",TypePoint3F, myOffset(starting_vel_vec)); + + addNamedField(isGuided, TypeBool, afxMagicMissileData); + addNamedFieldV(precision, TypeF32, afxMagicMissileData, &missilePrecisionValidator); + addNamedFieldV(trackDelay, TypeS32, afxMagicMissileData, &missileTrackDelayValidator); + addNamedFieldV(ballisticCoefficient, TypeF32, afxMagicMissileData, &missileBallisticCoefficientValidator); + + addField("collisionMask", TypeS32, myOffset(collision_mask)); + + addField("followTerrain", TypeBool, myOffset(followTerrain)); + addField("followTerrainHeight", TypeF32, myOffset(followTerrainHeight)); + addField("followTerrainAdjustRate", TypeF32, myOffset(followTerrainAdjustRate)); + addFieldV("followTerrainAdjustDelay", TypeS32, myOffset(followTerrainAdjustDelay), &ticksFromMS); + + addNamedField(acceleration, TypeF32, afxMagicMissileData); + addNamedFieldV(accelDelay, TypeS32, afxMagicMissileData, &ticksFromMS); + addNamedFieldV(accelLifetime, TypeS32, afxMagicMissileData, &ticksFromMS); + + addField("launchNode", TypeString, myOffset(launch_node)); + addField("launchOffset", TypePoint3F, myOffset(launch_offset)); + addField("launchOffsetServer",TypePoint3F, myOffset(launch_offset_server)); + addField("launchOffsetClient",TypePoint3F, myOffset(launch_offset_client)); + addField("launchNodeOffset", TypePoint3F, myOffset(launch_node_offset)); + addField("launchAimPitch", TypeF32, myOffset(launch_pitch)); + addField("launchAimPan", TypeF32, myOffset(launch_pan)); + addField("launchConstraintServer", TypeString, myOffset(launch_cons_s_spec)); + addField("launchConstraintClient", TypeString, myOffset(launch_cons_c_spec)); + // + addField("echoLaunchOffset", TypeBool, myOffset(echo_launch_offset)); + + addField("wiggleMagnitudes", TypeF32Vector, myOffset(wiggle_magnitudes)); + addField("wiggleSpeeds", TypeF32Vector, myOffset(wiggle_speeds)); + addField("wiggleAxis", TypeString, myOffset(wiggle_axis_string)); + + addField("hoverAltitude", TypeF32, myOffset(hover_altitude)); + addField("hoverAttackDistance", TypeF32, myOffset(hover_attack_distance)); + addField("hoverAttackGradient", TypeF32, myOffset(hover_attack_gradient)); + addFieldV("hoverTime", TypeS32, myOffset(hover_time), &ticksFromMS); + + addField("reverseTargeting", TypeBool, myOffset(reverse_targeting)); + + addFieldV("casterSafetyTime", TypeS32, myOffset(caster_safety_time), &ticksFromMS); + + Parent::initPersistFields(); + + // disallow some field substitutions + onlyKeepClearSubstitutions("particleEmitter"); // subs resolving to "~~", or "~0" are OK + onlyKeepClearSubstitutions("particleWaterEmitter"); + onlyKeepClearSubstitutions("sound"); + onlyKeepClearSubstitutions("splash"); +} + + +//-------------------------------------------------------------------------- +bool afxMagicMissileData::onAdd() +{ + if(!Parent::onAdd()) + return false; + + // ADDED BY MAGIC-MISSILE + + // Wiggle axes //////////////////////////////////////////////////////////// + if (wiggle_axis_string != ST_NULLSTRING && wiggle_num_axis == 0) + { + // Tokenize input string and convert to Point3F array + // + Vector dataBlocks(__FILE__, __LINE__); + + // make a copy of points_string + char* tokCopy = new char[dStrlen(wiggle_axis_string) + 1]; + dStrcpy(tokCopy, wiggle_axis_string); + + // extract tokens one by one, adding them to dataBlocks + char* currTok = dStrtok(tokCopy, " \t"); + while (currTok != NULL) + { + dataBlocks.push_back(currTok); + currTok = dStrtok(NULL, " \t"); + } + + // bail if there were no tokens in the string + if (dataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "afxMagicMissileData(%s) invalid wiggle axis string. No tokens found", getName()); + delete [] tokCopy; + return false; + } + + // Find wiggle_num_axis (round up to multiple of 3) // WARNING here if not multiple of 3? + for (U32 i = 0; i < dataBlocks.size()%3; i++) + { + dataBlocks.push_back("0.0"); + } + + wiggle_num_axis = dataBlocks.size()/3; + wiggle_axis = new Point3F[wiggle_num_axis]; + + U32 p_i = 0; + for (U32 i = 0; i < dataBlocks.size(); i+=3, p_i++) + { + F32 x,y,z; + x = dAtof(dataBlocks[i]); // What about overflow? + y = dAtof(dataBlocks[i+1]); + z = dAtof(dataBlocks[i+2]); + wiggle_axis[p_i].set(x,y,z); + + wiggle_axis[p_i].normalizeSafe(); // sufficient???? + } + + delete [] tokCopy; + } + + launch_cons_s_def.parseSpec(launch_cons_s_spec, true, false); + launch_cons_c_def.parseSpec(launch_cons_c_spec, false, true); + + return true; +} + + +bool afxMagicMissileData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + if( !server ) + { + if (!particleEmitter && particleEmitterId != 0) + if (Sim::findObject(particleEmitterId, particleEmitter) == false) + Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(particleEmitter): %d", particleEmitterId); + + if (!particleWaterEmitter && particleWaterEmitterId != 0) + if (Sim::findObject(particleWaterEmitterId, particleWaterEmitter) == false) + Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(particleWaterEmitter): %d", particleWaterEmitterId); + + /* From stock Projectile code... + if (!explosion && explosionId != 0) + if (Sim::findObject(explosionId, explosion) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(explosion): %d", explosionId); + + if (!waterExplosion && waterExplosionId != 0) + if (Sim::findObject(waterExplosionId, waterExplosion) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(waterExplosion): %d", waterExplosionId); + */ + + if (!splash && splashId != 0) + if (Sim::findObject(splashId, splash) == false) + Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockId(splash): %d", splashId); + + /* From stock Projectile code... + if (!decal && decalId != 0) + if (Sim::findObject(decalId, decal) == false) + Con::errorf(ConsoleLogEntry::General, "ProjectileData::preload: Invalid packet, bad datablockId(decal): %d", decalId); + */ + + String errorStr; + if( !sfxResolve( &sound, errorStr ) ) + Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet: %s", errorStr.c_str()); + + if (!lightDesc && lightDescId != 0) + if (Sim::findObject(lightDescId, lightDesc) == false) + Con::errorf(ConsoleLogEntry::General, "afxMagicMissileData::preload: Invalid packet, bad datablockid(lightDesc): %d", lightDescId); + } + + if (projectileShapeName != ST_NULLSTRING) + { + projectileShape = ResourceManager::get().load(projectileShapeName); + if (bool(projectileShape) == false) + { + errorStr = String::ToString("afxMagicMissileData::load: Couldn't load shape \"%s\"", projectileShapeName); + return false; + } + /* From stock Projectile code... + activateSeq = projectileShape->findSequence("activate"); + maintainSeq = projectileShape->findSequence("maintain"); + */ + } + + if (bool(projectileShape)) // create an instance to preload shape data + { + TSShapeInstance* pDummy = new TSShapeInstance(projectileShape, !server); + delete pDummy; + } + + return true; +} + +//-------------------------------------------------------------------------- +// Modified from floorPlanRes.cc +// Read a vector of items +template +bool readVector(Vector & vec, Stream & stream, const char * msg) +{ + U32 num, i; + bool Ok = true; + stream.read( & num ); + vec.setSize( num ); + for( i = 0; i < num && Ok; i++ ){ + Ok = stream.read(& vec[i]); + AssertISV( Ok, avar("math vec read error (%s) on elem %d", msg, i) ); + } + return Ok; +} +// Write a vector of items +template +bool writeVector(const Vector & vec, Stream & stream, const char * msg) +{ + bool Ok = true; + stream.write( vec.size() ); + for( U32 i = 0; i < vec.size() && Ok; i++ ) { + Ok = stream.write(vec[i]); + AssertISV( Ok, avar("vec write error (%s) on elem %d", msg, i) ); + } + return Ok; +} +//-------------------------------------------------------------------------- + +void afxMagicMissileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(projectileShapeName); + /* From stock Projectile code... + stream->writeFlag(faceViewer); + */ + if(stream->writeFlag(scale.x != 1 || scale.y != 1 || scale.z != 1)) + { + stream->write(scale.x); + stream->write(scale.y); + stream->write(scale.z); + } + + stream->write(collision_mask); + + if (stream->writeFlag(particleEmitter != NULL)) + stream->writeRangedU32(particleEmitter->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (stream->writeFlag(particleWaterEmitter != NULL)) + stream->writeRangedU32(particleWaterEmitter->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + /* From stock Projectile code... + if (stream->writeFlag(explosion != NULL)) + stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + if (stream->writeFlag(waterExplosion != NULL)) + stream->writeRangedU32(waterExplosion->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + */ + + if (stream->writeFlag(splash != NULL)) + stream->writeRangedU32(splash->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + /* From stock Projectile code... + if (stream->writeFlag(decal != NULL)) + stream->writeRangedU32(decal->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + */ + + sfxWrite( stream, sound ); + + if ( stream->writeFlag(lightDesc != NULL)) + stream->writeRangedU32(lightDesc->getId(), DataBlockObjectIdFirst, + DataBlockObjectIdLast); + + /* From stock Projectile code... + stream->write(impactForce); + */ + + stream->write(lifetime); + /* From stock Projectile code... + stream->write(armingDelay); + stream->write(fadeDelay); + */ + + if(stream->writeFlag(isBallistic)) + { + stream->write(gravityMod); + /* From stock Projectile code... + stream->write(bounceElasticity); + stream->write(bounceFriction); + */ + stream->write(ballisticCoefficient); + } + + if(stream->writeFlag(isGuided)) + { + stream->write(precision); + stream->write(trackDelay); + } + + stream->write(muzzleVelocity); + mathWrite(*stream, starting_vel_vec); + stream->write(acceleration); + stream->write(accelDelay); + stream->write(accelLifetime); + + stream->writeString(launch_node); + mathWrite(*stream, launch_offset); + mathWrite(*stream, launch_offset_server); + mathWrite(*stream, launch_offset_client); + mathWrite(*stream, launch_node_offset); + stream->write(launch_pitch); + stream->write(launch_pan); + stream->writeString(launch_cons_c_spec); + stream->writeFlag(echo_launch_offset); + + writeVector(wiggle_magnitudes, *stream, "afxMagicMissile: wiggle_magnitudes"); + writeVector(wiggle_speeds, *stream, "afxMagicMissile: wiggle_speeds"); + + stream->write(wiggle_num_axis); + for (U32 i = 0; i < wiggle_num_axis; i++) + mathWrite(*stream, wiggle_axis[i]); + + stream->write(hover_altitude); + stream->write(hover_attack_distance); + stream->write(hover_attack_gradient); + stream->writeRangedU32(hover_time, 0, MaxLifetimeTicks); + + stream->writeFlag(reverse_targeting); + + stream->write(caster_safety_time); +} + +void afxMagicMissileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + projectileShapeName = stream->readSTString(); + /* From stock Projectile code... + faceViewer = stream->readFlag(); + */ + if(stream->readFlag()) + { + stream->read(&scale.x); + stream->read(&scale.y); + stream->read(&scale.z); + } + else + scale.set(1,1,1); + + stream->read(&collision_mask); + + if (stream->readFlag()) + particleEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->readFlag()) + particleWaterEmitterId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + /* From stock Projectile code... + if (stream->readFlag()) + explosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + if (stream->readFlag()) + waterExplosionId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + */ + + if (stream->readFlag()) + splashId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + /* From stock Projectile code... + if (stream->readFlag()) + decalId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + */ + + sfxRead( stream, &sound ); + + if (stream->readFlag()) + lightDescId = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + + /* From stock Projectile code... + stream->read(&impactForce); + */ + + stream->read(&lifetime); + /* From stock Projectile code... + stream->read(&armingDelay); + stream->read(&fadeDelay); + */ + + isBallistic = stream->readFlag(); + if(isBallistic) + { + stream->read(&gravityMod); + /* From stock Projectile code... + stream->read(&bounceElasticity); + stream->read(&bounceFriction); + */ + stream->read(&ballisticCoefficient); + } + + isGuided = stream->readFlag(); + if(isGuided) + { + stream->read(&precision); + stream->read(&trackDelay); + } + + stream->read(&muzzleVelocity); + mathRead(*stream, &starting_vel_vec); + stream->read(&acceleration); + stream->read(&accelDelay); + stream->read(&accelLifetime); + + launch_node = stream->readSTString(); + mathRead(*stream, &launch_offset); + mathRead(*stream, &launch_offset_server); + mathRead(*stream, &launch_offset_client); + mathRead(*stream, &launch_node_offset); + stream->read(&launch_pitch); + stream->read(&launch_pan); + launch_cons_c_spec = stream->readSTString(); + echo_launch_offset = stream->readFlag(); + + readVector(wiggle_magnitudes, *stream, "afxMagicMissile: wiggle_magnitudes"); + readVector(wiggle_speeds, *stream, "afxMagicMissile: wiggle_speeds"); + + if (wiggle_axis) + delete [] wiggle_axis; + wiggle_axis = 0; + wiggle_num_axis = 0; + + stream->read(&wiggle_num_axis); + if (wiggle_num_axis > 0) + { + wiggle_axis = new Point3F[wiggle_num_axis]; + for (U32 i = 0; i < wiggle_num_axis; i++) + mathRead(*stream, &wiggle_axis[i]); + } + + stream->read(&hover_altitude); + stream->read(&hover_attack_distance); + stream->read(&hover_attack_gradient); + hover_time = stream->readRangedU32(0, MaxLifetimeTicks); + + reverse_targeting = stream->readFlag(); + + stream->read(&caster_safety_time); +} + +/* From stock Projectile code... +bool ProjectileData::setLifetime( void *obj, const char *index, const char *data ) +{ + S32 value = dAtoi(data); + value = scaleValue(value); + + ProjectileData *object = static_cast(obj); + object->lifetime = value; + + return false; +} + +bool ProjectileData::setArmingDelay( void *obj, const char *index, const char *data ) +{ + S32 value = dAtoi(data); + value = scaleValue(value); + + ProjectileData *object = static_cast(obj); + object->armingDelay = value; + + return false; +} + +bool ProjectileData::setFadeDelay( void *obj, const char *index, const char *data ) +{ + S32 value = dAtoi(data); + value = scaleValue(value); + + ProjectileData *object = static_cast(obj); + object->fadeDelay = value; + + return false; +} + +const char *ProjectileData::getScaledValue( void *obj, const char *data) +{ + + S32 value = dAtoi(data); + value = scaleValue(value, false); + + String stringData = String::ToString(value); + char *strBuffer = Con::getReturnBuffer(stringData.size()); + dMemcpy( strBuffer, stringData, stringData.size() ); + + return strBuffer; +} + +S32 ProjectileData::scaleValue( S32 value, bool down ) +{ + S32 minV = 0; + S32 maxV = Projectile::MaxLivingTicks; + + // scale down to ticks before we validate + if( down ) + value /= TickMs; + + if(value < minV || value > maxV) + { + Con::errorf("ProjectileData::scaleValue(S32 value = %d, bool down = %b) - Scaled value must be between %d and %d", value, down, minV, maxV); + if(value < minV) + value = minV; + else if(value > maxV) + value = maxV; + } + + // scale up from ticks after we validate + if( !down ) + value *= TickMs; + + return value; +} +*/ + +void afxMagicMissileData::gather_cons_defs(Vector& defs) +{ + if (launch_cons_s_def.isDefined()) + defs.push_back(launch_cons_s_def); + if (launch_cons_c_def.isDefined()) + defs.push_back(launch_cons_c_def); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMagicMissile + +afxMagicMissile::afxMagicMissile() +{ + init(true, true); +} + +afxMagicMissile::afxMagicMissile(bool on_server, bool on_client) +{ + init(on_server, on_client); +} + +//-------------------------------------------------------------------------- +//-------------------------------------- +// +void afxMagicMissile::init(bool on_server, bool on_client) +{ + mPhysicsWorld = NULL; + + mTypeMask |= ProjectileObjectType | LightObjectType; + + mLight = LightManager::createLightInfo(); + mLight->setType( LightInfo::Point ); + + mLightState.clear(); + mLightState.setLightInfo( mLight ); + + mCurrPosition.zero(); + mCurrVelocity.set(0, 0, 1); + + mCurrTick = 0; + + mParticleEmitter = NULL; + mParticleWaterEmitter = NULL; + mSound = 0; + + mProjectileShape = NULL; + + mDataBlock = NULL; + + choreographer = NULL; + + if (on_server != on_client) + { + client_only = on_client; + server_only = on_server; + mNetFlags.clear(Ghostable | ScopeAlways); + if (client_only) + mNetFlags.set(IsGhost); + } + else + { + // note -- setting neither server or client makes no sense so we + // treat as if both are set. + mNetFlags.set(Ghostable | ScopeAlways); + client_only = server_only = false; + } + + mCurrDeltaBase.zero(); + mCurrBackDelta.zero(); + collision_mask = 0; + prec_inc = 0.0f; + + did_launch = false; + did_impact = false; + + missile_target = NULL; + collide_exempt = NULL; + + use_accel = false; + + hover_attack_go = false; + hover_attack_tick = 0; + + starting_velocity = 0.0; + starting_vel_vec.zero(); + + ss_object = 0; + ss_index = 0; +} + +afxMagicMissile::~afxMagicMissile() +{ + SAFE_DELETE(mLight); + + delete mProjectileShape; + mProjectileShape = NULL; +} + +//-------------------------------------------------------------------------- +void afxMagicMissile::initPersistFields() +{ + addGroup("Physics"); + addField("initialPosition", TypePoint3F, Offset(mCurrPosition, afxMagicMissile) , + "Initial starting position for this missile."); + addField("initialVelocity", TypePoint3F, Offset(mCurrVelocity, afxMagicMissile) , + "Initial starting velocity for this missile."); + endGroup("Physics"); + + /* From stock Projectile code... + addGroup("Source"); + addField("sourceObject", TypeS32, Offset(mSourceObjectId, Projectile) ,"The object that fires this projectile. If this projectile was fired by a WeaponImage, it will be the object that owns the WeaponImage, usually the player."); + addField("sourceSlot", TypeS32, Offset(mSourceObjectSlot, Projectile) ,"Which weapon slot on the sourceObject that this projectile originates from."); + endGroup("Source"); + */ + + Parent::initPersistFields(); +} + +/* From stock Projectile code... +bool Projectile::calculateImpact(float, + Point3F& pointOfImpact, + float& impactTime) +{ + Con::warnf(ConsoleLogEntry::General, "Projectile::calculateImpact: Should never be called"); + + impactTime = 0; + pointOfImpact.set(0, 0, 0); + return false; +} + + +//-------------------------------------------------------------------------- +F32 Projectile::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips) +{ + F32 ret = Parent::getUpdatePriority(camInfo, updateMask, updateSkips); + // if the camera "owns" this object, it should have a slightly higher priority + if(mSourceObject == camInfo->camera) + return ret + 0.2; + return ret; +} +*/ + +bool afxMagicMissile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isServerObject()) + { + /* From stock Projectile code... + ShapeBase* ptr; + if (Sim::findObject(mSourceObjectId, ptr)) + { + mSourceObject = ptr; + + // Since we later do processAfter( mSourceObject ) we must clearProcessAfter + // if it is deleted. SceneObject already handles this in onDeleteNotify so + // all we need to do is register for the notification. + deleteNotify( ptr ); + } + else + { + if (mSourceObjectId != -1) + Con::errorf(ConsoleLogEntry::General, "Projectile::onAdd: mSourceObjectId is invalid"); + mSourceObject = NULL; + } + + // If we're on the server, we need to inherit some of our parent's velocity + // + mCurrTick = 0; + */ + } + else + { + if (bool(mDataBlock->projectileShape)) + { + mProjectileShape = new TSShapeInstance(mDataBlock->projectileShape, true); + /* From stock Projectile code... + if (mDataBlock->activateSeq != -1) + { + mActivateThread = mProjectileShape->addThread(); + mProjectileShape->setTimeScale(mActivateThread, 1); + mProjectileShape->setSequence(mActivateThread, mDataBlock->activateSeq, 0); + } + */ + } + if (mDataBlock->particleEmitter != NULL) + { + ParticleEmitter* pEmitter = new ParticleEmitter; + //pEmitter->setDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index)); + pEmitter->onNewDataBlock(mDataBlock->particleEmitter->cloneAndPerformSubstitutions(ss_object, ss_index), false); + if (pEmitter->registerObject() == false) + { + Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName()); + delete pEmitter; + pEmitter = NULL; + } + mParticleEmitter = pEmitter; + } + + if (mDataBlock->particleWaterEmitter != NULL) + { + ParticleEmitter* pEmitter = new ParticleEmitter; + pEmitter->onNewDataBlock(mDataBlock->particleWaterEmitter->cloneAndPerformSubstitutions(ss_object, ss_index), false); + if (pEmitter->registerObject() == false) + { + Con::warnf(ConsoleLogEntry::General, "Could not register particle emitter for particle of class: %s", mDataBlock->getName()); + delete pEmitter; + pEmitter = NULL; + } + mParticleWaterEmitter = pEmitter; + } + } + /* From stock Projectile code... + if (mSourceObject.isValid()) + processAfter(mSourceObject); + */ + + // detect for acceleration + use_accel = (mDataBlock->acceleration != 0 && mDataBlock->accelLifetime > 0); + + // Setup our bounding box + if (bool(mDataBlock->projectileShape) == true) + mObjBox = mDataBlock->projectileShape->bounds; + else + mObjBox = Box3F(Point3F(0, 0, 0), Point3F(0, 0, 0)); + resetWorldBox(); + addToScene(); + + if ( PHYSICSMGR ) + mPhysicsWorld = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" ); + + return true; +} + + +void afxMagicMissile::onRemove() +{ + if(mParticleEmitter) + { + mParticleEmitter->deleteWhenEmpty(); + mParticleEmitter = NULL; + } + + if(mParticleWaterEmitter) + { + mParticleWaterEmitter->deleteWhenEmpty(); + mParticleWaterEmitter = NULL; + } + + SFX_DELETE( mSound ); + + removeFromScene(); + Parent::onRemove(); +} + + +bool afxMagicMissile::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + starting_velocity = mDataBlock->muzzleVelocity; + starting_vel_vec = mDataBlock->starting_vel_vec; + collision_mask = mDataBlock->collision_mask; + + if ( isGhost() ) + { + // Create the sound ahead of time. This reduces runtime + // costs and makes the system easier to understand. + + SFX_DELETE( mSound ); + + if ( mDataBlock->sound ) + mSound = SFX->createSource( mDataBlock->sound ); + } + + return true; +} + + +//-------------------------------------------------------------------------- + +void afxMagicMissile::submitLights( LightManager *lm, bool staticLighting ) +{ + if ( staticLighting || isHidden() || !mDataBlock->lightDesc ) + return; + + mDataBlock->lightDesc->submitLight( &mLightState, getRenderTransform(), lm, this ); +} + +bool afxMagicMissile::pointInWater(const Point3F &point) +{ + // This is pretty much a hack so we can use the existing ContainerQueryInfo + // and findObject router. + + // We only care if we intersect with water at all + // so build a box at the point that has only 1 z extent. + // And test if water coverage is anything other than zero. + + Box3F boundsBox( point, point ); + boundsBox.maxExtents.z += 1.0f; + + ContainerQueryInfo info; + info.box = boundsBox; + info.mass = 0.0f; + + // Find and retreive physics info from intersecting WaterObject(s) + if(mContainer != NULL) + { + mContainer->findObjects( boundsBox, WaterObjectType, findRouter, &info ); + } + else + { + // Handle special case where the projectile has exploded prior to having + // called onAdd() on the client. This occurs when the projectile on the + // server is created and then explodes in the same network update tick. + // On the client end in NetConnection::ghostReadPacket() the ghost is + // created and then Projectile::unpackUpdate() is called prior to the + // projectile being registered. Within unpackUpdate() the explosion + // is triggered, but without being registered onAdd() isn't called and + // the container is not set. As all we're doing is checking if the + // given explosion point is within water, we should be able to use the + // global container here. We could likely always get away with this, + // but using the actual defined container when possible is the right + // thing to do. DAW + AssertFatal(isClientObject(), "Server projectile has not been properly added"); + gClientContainer.findObjects( boundsBox, WaterObjectType, findRouter, &info ); + } + + return ( info.waterCoverage > 0.0f ); +} + +//---------------------------------------------------------------------------- + +void afxMagicMissile::emitParticles(const Point3F& from, const Point3F& to, const Point3F& vel, const U32 ms) +{ + /* From stock Projectile code... + if ( isHidden() ) + return; + */ + + Point3F axis = -vel; + + if( axis.isZero() ) + axis.set( 0.0, 0.0, 1.0 ); + else + axis.normalize(); + + bool fromWater = pointInWater(from); + bool toWater = pointInWater(to); + + if (!fromWater && !toWater && bool(mParticleEmitter)) // not in water + mParticleEmitter->emitParticles(from, to, axis, vel, ms); + else if (fromWater && toWater && bool(mParticleWaterEmitter)) // in water + mParticleWaterEmitter->emitParticles(from, to, axis, vel, ms); + else if (!fromWater && toWater) // entering water + { + // cast the ray to get the surface point of the water + RayInfo rInfo; + if (gClientContainer.castRay(from, to, WaterObjectType, &rInfo)) + { + create_splash(rInfo.point); + + // create an emitter for the particles out of water and the particles in water + if (mParticleEmitter) + mParticleEmitter->emitParticles(from, rInfo.point, axis, vel, ms); + + if (mParticleWaterEmitter) + mParticleWaterEmitter->emitParticles(rInfo.point, to, axis, vel, ms); + } + } + else if (fromWater && !toWater) // leaving water + { + // cast the ray in the opposite direction since that point is out of the water, otherwise + // we hit water immediately and wont get the appropriate surface point + RayInfo rInfo; + if (gClientContainer.castRay(to, from, WaterObjectType, &rInfo)) + { + create_splash(rInfo.point); + + // create an emitter for the particles out of water and the particles in water + if (mParticleEmitter) + mParticleEmitter->emitParticles(rInfo.point, to, axis, vel, ms); + + if (mParticleWaterEmitter) + mParticleWaterEmitter->emitParticles(from, rInfo.point, axis, vel, ms); + } + } +} + +void afxMagicMissile::processTick(const Move* move) +{ + Parent::processTick( move ); + + // only active from launch to impact + if (!is_active()) + return; + + mCurrTick++; + + // missile fizzles out by exceeding lifetime + if ((isServerObject() || client_only) && mCurrTick >= mDataBlock->lifetime) + { + did_impact = true; + setMaskBits(ImpactMask); + if (choreographer) + { + Point3F n = mCurrVelocity; n.normalizeSafe(); + choreographer->impactNotify(mCurrPosition, n, 0); + } + Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500); + return; + } + + static F32 dT = F32(TickMs)*0.001f; + + Point3F old_pos = mCurrPosition; + + // adjust missile velocity from gravity and drag influences + if (mDataBlock->isBallistic) + { + F32 dV = (1 - mDataBlock->ballisticCoefficient)*dT; + Point3F d(mCurrVelocity.x*dV, mCurrVelocity.y*dV, 9.81f*mDataBlock->gravityMod*dT); + mCurrVelocity -= d; + } + + // adjust missile velocity from acceleration + if (use_accel) + { + if (mCurrTick > mDataBlock->accelDelay && + mCurrTick <= mDataBlock->accelDelay + mDataBlock->accelLifetime) + { + Point3F d = mCurrVelocity; d.normalizeSafe(); + mCurrVelocity += d*mDataBlock->acceleration*dT; + } + } + + // adjust mCurrVelocity from guidance system influences + if (mDataBlock->isGuided && missile_target && mCurrTick > mDataBlock->trackDelay) + { + // get the position tracked by the guidance system + Point3F target_pos = missile_target->getBoxCenter(); + + Point3F target_vec = target_pos - mCurrPosition; + + F32 target_dist_sq = target_vec.lenSquared(); + if (target_dist_sq < 4.0f) + prec_inc += 1.0f; + + // hover + if (mDataBlock->hover_altitude > 0.0f) + { + Point3F target_vec_xy(target_vec.x, target_vec.y, 0); + F32 xy_dist = target_vec_xy.len(); + + if (xy_dist > mDataBlock->hover_attack_distance) + { + hover_attack_go = false; + + if (xy_dist > mDataBlock->hover_attack_distance + mDataBlock->hover_attack_gradient) + { + target_pos.z += mDataBlock->hover_altitude; + } + else + { + target_pos.z += afxEase::eq( (xy_dist-mDataBlock->hover_attack_distance)/mDataBlock->hover_attack_gradient, + 0.0f, mDataBlock->hover_altitude, + 0.25f, 0.75f); + } + target_vec = target_pos - mCurrPosition; + } + + else + { + if (!hover_attack_go) + { + hover_attack_go = true; + hover_attack_tick = 0; + } + hover_attack_tick++; + + if (hover_attack_tick < mDataBlock->hover_time) + { + target_pos.z += mDataBlock->hover_altitude; + target_vec = target_pos - mCurrPosition; + } + } + } + + // apply precision + + // extract speed + F32 speed = mCurrVelocity.len(); + + // normalize vectors + target_vec.normalizeSafe(); + mCurrVelocity.normalize(); + + F32 prec = mDataBlock->precision; + + // fade in precision gradually to avoid sudden turn + if (mCurrTick < mDataBlock->trackDelay + 16) + prec *= (mCurrTick - mDataBlock->trackDelay)/16.0f; + + prec += prec_inc; + if (prec > 100) + prec = 100; + + // apply precision weighting + target_vec *= prec; + mCurrVelocity *= (100 - prec); + + mCurrVelocity += target_vec; + mCurrVelocity.normalize(); + mCurrVelocity *= speed; + } + + // wiggle + for (U32 i = 0; i < mDataBlock->wiggle_num_axis; i++) + { + if (i >= mDataBlock->wiggle_magnitudes.size() || i >= mDataBlock->wiggle_speeds.size()) + break; + + F32 wiggle_mag = mDataBlock->wiggle_magnitudes[i]; + F32 wiggle_speed = mDataBlock->wiggle_speeds[i]; + Point3F wiggle_axis = mDataBlock->wiggle_axis[i]; + //wiggle_axis.normalizeSafe(); // sufficient???? + + F32 theta = wiggle_mag * mSin(wiggle_speed*(mCurrTick*TickSec)); + //Con::printf( "theta: %f", theta ); + AngAxisF thetaRot(wiggle_axis, theta); + MatrixF temp(true); + thetaRot.setMatrix(&temp); + temp.mulP(mCurrVelocity); + } + + Point3F new_pos = old_pos + mCurrVelocity*dT; + + // conform to terrain + if (mDataBlock->followTerrain && mCurrTick >= mDataBlock->followTerrainAdjustDelay) + { + U32 mask = TerrainObjectType | TerrainLikeObjectType; // | InteriorObjectType; + + F32 ht = mDataBlock->followTerrainHeight; + F32 ht_rate = mDataBlock->followTerrainAdjustRate; + F32 ht_min = 0.05f; + if (ht < ht_min) + ht = ht_min; + + SceneContainer* container = (isServerObject()) ? &gServerContainer : &gClientContainer; + Point3F above_pos = new_pos; above_pos.z += 10000; + Point3F below_pos = new_pos; below_pos.z -= 10000; + RayInfo rInfo; + if (container && container->castRay(above_pos, below_pos, mask, &rInfo)) + { + F32 terrain_z = rInfo.point.z; + F32 seek_z = terrain_z + ht; + if (new_pos.z < seek_z) + { + new_pos.z += ht_rate*dT; + if (new_pos.z > seek_z) + new_pos.z = seek_z; + } + else if (new_pos.z > seek_z) + { + new_pos.z -= ht_rate*dT; + if (new_pos.z < seek_z) + new_pos.z = seek_z; + } + + if (new_pos.z < terrain_z + ht_min) + new_pos.z = terrain_z + ht_min; + } + } + + // only check for impacts on server + if (isServerObject()) + { + // avoid collision with the spellcaster + if (collide_exempt && mCurrTick <= mDataBlock->caster_safety_time) + collide_exempt->disableCollision(); + + // check for collision along ray from old to new position + RayInfo rInfo; + bool did_hit = false; + + if (mPhysicsWorld) + { + // did_hit = mPhysicsWorld->castRay(old_pos, new_pos, &rInfo, Point3F(new_pos - old_pos) * mDataBlock->impactForce ); + // Impulse currently hardcoded for testing purposes + did_hit = mPhysicsWorld->castRay(old_pos, new_pos, &rInfo, Point3F(new_pos - old_pos) * 1000.0f ); + } + else + { + did_hit = getContainer()->castRay(old_pos, new_pos, collision_mask, &rInfo); + } + + // restore collisions on spellcaster + if (collide_exempt && mCurrTick <= mDataBlock->caster_safety_time) + collide_exempt->enableCollision(); + + // process impact + if (did_hit) + { + MatrixF xform(true); + xform.setColumn(3, rInfo.point); + setTransform(xform); + mCurrPosition = rInfo.point; + mCurrVelocity = Point3F(0, 0, 0); + did_impact = true; + setMaskBits(ImpactMask); + if (choreographer) + { + choreographer->impactNotify(rInfo.point, rInfo.normal, rInfo.object); + Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500); + } + } + } + else // if (isClientObject()) + { + emitParticles(mCurrPosition, new_pos, mCurrVelocity, TickMs); + updateSound(); + } + + // interp values used in interpolateTick() + mCurrDeltaBase = new_pos; + mCurrBackDelta = mCurrPosition - new_pos; + + mCurrPosition = new_pos; + + MatrixF xform(true); + xform.setColumn(3, mCurrPosition); + setTransform(xform); +} + +void afxMagicMissile::interpolateTick(F32 delta) +{ + Parent::interpolateTick(delta); + + // only active from launch to impact + if (!is_active()) + return; + + Point3F interpPos = mCurrDeltaBase + mCurrBackDelta * delta; + Point3F dir = mCurrVelocity; + if(dir.isZero()) + dir.set(0,0,1); + else + dir.normalize(); + + MatrixF xform(true); + xform = MathUtils::createOrientFromDir(dir); + xform.setPosition(interpPos); + setRenderTransform(xform); + + updateSound(); +} + +//-------------------------------------------------------------------------- +U32 afxMagicMissile::packUpdate(NetConnection* con, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate( con, mask, stream ); + + const bool isInitalUpdate = mask & GameBase::InitialUpdateMask; + + // InitialUpdateMask + if ( stream->writeFlag( isInitalUpdate ) ) + { + Point3F pos; + getTransform().getColumn( 3, &pos ); + stream->writeCompressedPoint( pos ); + F32 len = mCurrVelocity.len(); + + if ( stream->writeFlag( len > 0.02 ) ) + { + Point3F outVel = mCurrVelocity; + outVel *= 1 / len; + stream->writeNormalVector( outVel, 10 ); + len *= 32.0; // 5 bits for fraction + + if ( len > 8191 ) + len = 8191; + + stream->writeInt( (S32)len, 13 ); + } + + stream->writeRangedU32( mCurrTick, 0, afxMagicMissileData::MaxLifetimeTicks ); + + if (choreographer) + { + S32 ghostIndex = con->getGhostIndex( choreographer ); + if ( stream->writeFlag( ghostIndex != -1 ) ) + { + stream->writeRangedU32( U32(ghostIndex), + 0, + NetConnection::MaxGhostCount ); + } + else + // have not recieved the ghost for the source object yet, try again later + retMask |= GameBase::InitialUpdateMask; + } + else + stream->writeFlag( false ); + } + + // impact update + if (stream->writeFlag(mask & ImpactMask)) + { + mathWrite(*stream, mCurrPosition); + mathWrite(*stream, mCurrVelocity); + stream->writeFlag(did_impact); + } + + // guided update + if (stream->writeFlag(mask & GuideMask)) + { + mathWrite(*stream, mCurrPosition); + mathWrite(*stream, mCurrVelocity); + } + + return retMask; +} + +void afxMagicMissile::unpackUpdate(NetConnection* con, BitStream* stream) +{ + Parent::unpackUpdate(con, stream); + + if ( stream->readFlag() ) // InitialUpdateMask + { + Point3F pos; + stream->readCompressedPoint( &pos ); + if ( stream->readFlag() ) + { + stream->readNormalVector( &mCurrVelocity, 10 ); + mCurrVelocity *= stream->readInt( 13 ) / 32.0f; + } + else + mCurrVelocity.zero(); + + mCurrDeltaBase = pos; + mCurrBackDelta = mCurrPosition - pos; + mCurrPosition = pos; + setPosition( mCurrPosition ); + + mCurrTick = stream->readRangedU32(0, afxMagicMissileData::MaxLifetimeTicks); + if ( stream->readFlag() ) + { + U32 id = stream->readRangedU32(0, NetConnection::MaxGhostCount); + choreographer = dynamic_cast(con->resolveGhost(id)); + if (choreographer) + { + deleteNotify(choreographer); + } + } + else + { + if (choreographer) + clearNotify(choreographer); + choreographer = 0; + } + } + + // impact update + if (stream->readFlag()) + { + mathRead(*stream, &mCurrPosition); + mathRead(*stream, &mCurrVelocity); + did_impact = stream->readFlag(); + + } + + if (stream->readFlag()) // guided update + { + mathRead( *stream, &mCurrPosition ); + mathRead( *stream, &mCurrVelocity ); + } +} + +//-------------------------------------------------------------------------- +void afxMagicMissile::prepRenderImage(SceneRenderState* state) +{ + if (!is_active()) + return; + + /* + if (isHidden() || mFadeValue <= (1.0/255.0)) + return; + */ + + if ( mDataBlock->lightDesc ) + { + mDataBlock->lightDesc->prepRender( state, &mLightState, getRenderTransform() ); + } + + /* + if ( mFlareData ) + { + mFlareState.fullBrightness = mDataBlock->lightDesc->mBrightness; + mFlareState.scale = mFlareScale; + mFlareState.lightInfo = mLight; + mFlareState.lightMat = getTransform(); + + mFlareData->prepRender( state, &mFlareState ); + } + */ + + prepBatchRender( state ); +} + +void afxMagicMissile::prepBatchRender(SceneRenderState* state) +{ + if ( !mProjectileShape ) + return; + + GFXTransformSaver saver; + + // Set up our TS render state. + TSRenderState rdata; + rdata.setSceneState( state ); + + // We might have some forward lit materials + // so pass down a query to gather lights. + LightQuery query; + query.init( getWorldSphere() ); + rdata.setLightQuery( &query ); + + MatrixF mat = getRenderTransform(); + mat.scale( mObjScale ); + mat.scale( mDataBlock->scale ); + GFX->setWorldMatrix( mat ); + + mProjectileShape->setDetailFromPosAndScale( state, mat.getPosition(), mObjScale ); + mProjectileShape->animate(); + + mProjectileShape->render( rdata ); +} + +void afxMagicMissile::onDeleteNotify(SimObject* obj) +{ + ShapeBase* shape_test = dynamic_cast(obj); + if (shape_test == collide_exempt) + { + collide_exempt = NULL; + Parent::onDeleteNotify(obj); + return; + } + + SceneObject* target_test = dynamic_cast(obj); + if (target_test == missile_target) + { + missile_target = NULL; + Parent::onDeleteNotify(obj); + return; + } + + afxChoreographer* ch = dynamic_cast(obj); + if (ch == choreographer) + { + choreographer = NULL; + Parent::onDeleteNotify(obj); + return; + } + + Parent::onDeleteNotify(obj); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private: + +void afxMagicMissile::create_splash(const Point3F& pos) +{ + if (!mDataBlock || !mDataBlock->splash) + return; + + MatrixF xfm = getTransform(); + xfm.setPosition(pos); + + Splash* splash = new Splash(); + splash->onNewDataBlock(mDataBlock->splash, false); + splash->setTransform(xfm); + splash->setInitialState(xfm.getPosition(), Point3F(0.0, 0.0, 1.0)); + if (!splash->registerObject()) + { + delete splash; + splash = NULL; + } +} + +void afxMagicMissile::get_launch_constraint_data(Point3F& pos, Point3F& vel) +{ + // need a choreographer + if (!choreographer) + { + Con::errorf("afxMagicMissile::get_launch_constraint_data(): missing choreographer."); + return; + } + + // need a constraint manager + afxConstraintMgr* cons_mgr = choreographer->getConstraintMgr(); + if (!cons_mgr) + { + Con::errorf("afxMagicMissile::get_launch_constraint_data(): missing constriant manager."); + return; + } + + // need a valid constraint + afxConstraintID launch_cons_id; + if (isServerObject()) + launch_cons_id = cons_mgr->getConstraintId(mDataBlock->launch_cons_s_def); + else + launch_cons_id = cons_mgr->getConstraintId(mDataBlock->launch_cons_c_def); + + afxConstraint* launch_cons = cons_mgr->getConstraint(launch_cons_id); + if (!launch_cons) + { + Con::errorf("afxMagicMissile::get_launch_constraint_data(): constraint undefined."); + return; + } + + MatrixF launch_xfm; + launch_cons->getTransform(launch_xfm); + + Point3F launch_pos; + launch_cons->getPosition(launch_pos); + + pos = launch_pos; + + // echo the actual launch position to the console + if (mDataBlock->echo_launch_offset) + { + SceneObject* default_launcher = get_default_launcher(); + if (default_launcher) + { + MatrixF launcher_xfm_inv = default_launcher->getWorldTransform(); + VectorF offset = pos - default_launcher->getRenderPosition(); + launcher_xfm_inv.mulV(offset); + if (isServerObject()) + Con::printf("launchOffsetServer = \"%g %g %g\";", offset.x, offset.y, offset.z); + else + Con::printf("launchOffsetClient = \"%g %g %g\";", offset.x, offset.y, offset.z); + } + } + + // setup aiming matrix to straight forward and level + MatrixF aim_mtx; + AngAxisF aim_aa(Point3F(0,1,0),0); + aim_aa.setMatrix(&aim_mtx); + + // calculate final aiming vector + MatrixF aim2_mtx; + aim2_mtx.mul(launch_xfm, aim_mtx); + + VectorF aim_vec; + aim2_mtx.getColumn(1,&aim_vec); + aim_vec.normalizeSafe(); + + // give velocity vector a magnitude + vel = aim_vec*mDataBlock->muzzleVelocity; +} + +// resolve the launch constraint object. normally it's the caster, but for +// reverse_targeting the target object us used. +SceneObject* afxMagicMissile::get_default_launcher() const +{ + SceneObject* launch_cons_obj = 0; + if (mDataBlock->reverse_targeting) + { + if (dynamic_cast(choreographer)) + launch_cons_obj = ((afxMagicSpell*)choreographer)->target; + if (!launch_cons_obj) + { + Con::errorf("afxMagicMissile::get_launch_data(): missing target constraint object for reverse targeted missile."); + return 0; + } + } + else + { + if (dynamic_cast(choreographer)) + launch_cons_obj = ((afxMagicSpell*)choreographer)->caster; + if (!launch_cons_obj) + { + Con::errorf("afxMagicMissile::get_launch_data(): missing launch constraint object missile."); + return 0; + } + } + + return launch_cons_obj; +} + +void afxMagicMissile::get_launch_data(Point3F& pos, Point3F& vel) +{ + bool use_constraint = (isServerObject()) ? mDataBlock->launch_cons_s_def.isDefined() : mDataBlock->launch_cons_c_def.isDefined(); + if (use_constraint) + { + get_launch_constraint_data(pos, vel); + return; + } + + // a choreographer pointer is required + if (!choreographer) + { + Con::errorf("afxMagicMissile::get_launch_data(): missing choreographer."); + return; + } + + SceneObject* launch_cons_obj = get_default_launcher(); + if (!launch_cons_obj) + return; + + MatrixF launch_xfm = launch_cons_obj->getRenderTransform(); + + // calculate launch position + Point3F offset_override = (isClientObject()) ? mDataBlock->launch_offset_client : + mDataBlock->launch_offset_server; + // override + if (!offset_override.isZero()) + { + launch_xfm.mulV(offset_override); + pos = launch_cons_obj->getRenderPosition() + offset_override; + } + // no override + else + { + // get transformed launch offset + VectorF launch_offset = mDataBlock->launch_offset; + launch_xfm.mulV(launch_offset); + + StringTableEntry launch_node = mDataBlock->launch_node; + + // calculate position of missile at launch + if (launch_node != ST_NULLSTRING) + { + ShapeBase* launch_cons_shape = dynamic_cast(launch_cons_obj); + TSShapeInstance* shape_inst = (launch_cons_shape) ? launch_cons_shape->getShapeInstance() : 0; + if (!shape_inst || !shape_inst->getShape()) + launch_node = ST_NULLSTRING; + else + { + S32 node_ID = shape_inst->getShape()->findNode(launch_node); + MatrixF node_xfm = launch_cons_obj->getRenderTransform(); + node_xfm.scale(launch_cons_obj->getScale()); + if (node_ID >= 0) + node_xfm.mul(shape_inst->mNodeTransforms[node_ID]); + + VectorF node_offset = mDataBlock->launch_node_offset; + node_xfm.mulV(node_offset); + + pos = node_xfm.getPosition() + launch_offset + node_offset; + } + } + // calculate launch position without launch node + else + pos = launch_cons_obj->getRenderPosition() + launch_offset; + } + + // echo the actual launch position to the console + if (mDataBlock->echo_launch_offset) + { + VectorF offset = pos - launch_cons_obj->getRenderPosition(); + MatrixF caster_xfm_inv = launch_xfm; + caster_xfm_inv.affineInverse(); + caster_xfm_inv.mulV(offset); + if (isServerObject()) + Con::printf("launchOffsetServer = \"%g %g %g\";", offset.x, offset.y, offset.z); + else + Con::printf("launchOffsetClient = \"%g %g %g\";", offset.x, offset.y, offset.z); + } + + // calculate launch velocity vector + if (starting_vel_vec.isZero()) + { + // setup aiming matrix to straight forward and level + MatrixF aim_mtx; + AngAxisF aim_aa(Point3F(0,1,0),0); + aim_aa.setMatrix(&aim_mtx); + + // setup pitch matrix + MatrixF pitch_mtx; + AngAxisF pitch_aa(Point3F(1,0,0),mDegToRad(mDataBlock->launch_pitch)); + pitch_aa.setMatrix(&pitch_mtx); + + // setup pan matrix + MatrixF pan_mtx; + AngAxisF pan_aa(Point3F(0,0,1),mDegToRad(mDataBlock->launch_pan)); + pan_aa.setMatrix(&pan_mtx); + + // calculate adjusted aiming matrix + aim_mtx.mul(pitch_mtx); + aim_mtx.mul(pan_mtx); + + // calculate final aiming vector + MatrixF aim2_mtx; + aim2_mtx.mul(launch_xfm, aim_mtx); + VectorF aim_vec; + aim2_mtx.getColumn(1,&aim_vec); + aim_vec.normalizeSafe(); + + // give velocity vector a magnitude + vel = aim_vec*mDataBlock->muzzleVelocity; + } + else + { + vel = starting_vel_vec*starting_velocity; + } +} + +void afxMagicMissile::updateSound() +{ + if (!mDataBlock->sound) + return; + + if ( mSound ) + { + if ( !mSound->isPlaying() ) + mSound->play(); + + mSound->setVelocity( getVelocity() ); + mSound->setTransform( getRenderTransform() ); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// public: + +void afxMagicMissile::launch() +{ + get_launch_data(mCurrPosition, mCurrVelocity); + + mCurrDeltaBase = mCurrPosition; + mCurrBackDelta.zero(); + + did_launch = true; + + setPosition(mCurrPosition); + + afxMagicSpell* spell = dynamic_cast(choreographer); + if (spell) + { + if (mDataBlock->reverse_targeting) + { + missile_target = spell->caster; + collide_exempt = spell->target; + } + else + { + missile_target = spell->target; + collide_exempt = spell->caster; + } + + if (spell->caster) + processAfter(spell->caster); + if (missile_target) + deleteNotify(missile_target); + if (collide_exempt) + deleteNotify(collide_exempt); + } + else + { + missile_target = 0; + collide_exempt = 0; + } +} + +void afxMagicMissile::setChoreographer(afxChoreographer* chor) +{ + if (choreographer) + clearNotify(choreographer); + choreographer = chor; + if (choreographer) + deleteNotify(choreographer); +} + +void afxMagicMissile::setStartingVelocityVector(const Point3F& vel_vec) +{ + starting_vel_vec = vel_vec; +} + +void afxMagicMissile::setStartingVelocity(const F32 vel) +{ + starting_velocity = vel; +} + +void afxMagicMissile::getStartingVelocityValues(F32& vel, Point3F& vel_vec) +{ + vel = starting_velocity; + vel_vec = starting_vel_vec; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +/* From stock Projectile code... +DefineEngineMethod(Projectile, presimulate, void, (F32 seconds), (1.0f), "Updates velocity and position, and performs collision testing.\n" + "@param seconds Amount of time, in seconds, since the simulation began, to start the simulation at.\n" + "@tsexample\n" + "// Tell the projectile object to process a simulation event, and provide the amount of time\n" + " in seconds that has passed since the simulation began.\n" + "%seconds = 2000;\n" + "%projectile.presimulate(%seconds);\n" + "@endtsexample\n") +{ + object->simulate( seconds ); +} +*/ + +DefineEngineMethod(afxMagicMissile, setStartingVelocityVector, void, (Point3F velocityVec),, + "Set the starting velocity-vector for a magic-missile.\n\n" + "@ingroup AFX") +{ + object->setStartingVelocityVector(velocityVec); +} + +DefineEngineMethod(afxMagicMissile, setStartingVelocity, void, (float velocity),, + "Set the starting velocity for a magic-missile.\n\n" + "@ingroup AFX") +{ + object->setStartingVelocity(velocity); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/afxMagicMissile.h b/Engine/source/afx/afxMagicMissile.h new file mode 100644 index 000000000..332d69d3c --- /dev/null +++ b/Engine/source/afx/afxMagicMissile.h @@ -0,0 +1,435 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// afxMagicMissile is a heavily modified variation of the stock Projectile class. In +// addition to numerous AFX customizations, it also incorporates functionality based on +// the following TGE resources: +// +// Guided or Seeker Projectiles by Derk Adams +// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6778 +// +// Projectile Ballistic Coefficients (drag factors) by Mark Owen +// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=5128 +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_MAGIC_MISSILE_H_ +#define _AFX_MAGIC_MISSILE_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "T3D/lightDescription.h" +#include "T3D/fx/particleEmitter.h" + +#include "afx/afxConstraint.h" + +class SplashData; +class ShapeBase; +class TSShapeInstance; +class PhysicsWorld; +class SFXTrack; +class SFXSource; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMagicMissileData + +class afxMagicMissileData : public GameBaseData +{ + typedef GameBaseData Parent; + +protected: + bool onAdd(); + +public: + enum { MaxLifetimeTicks = 4095 }; + +public: + // variables set in datablock definition: + // Shape related + StringTableEntry projectileShapeName; + + //bool hasLight; + //F32 lightRadius; + //LinearColorF lightColor; + + //bool hasWaterLight; + //LinearColorF waterLightColor; + + /* + /// Set to true if it is a billboard and want it to always face the viewer, false otherwise + bool faceViewer; + */ + Point3F scale; + + /* + /// [0,1] scale of how much velocity should be inherited from the parent object + F32 velInheritFactor; + /// Speed of the projectile when fired + */ + F32 muzzleVelocity; + + /// Should it arc? + bool isBallistic; + + /* + /// How HIGH should it bounce (parallel to normal), [0,1] + F32 bounceElasticity; + /// How much momentum should be lost when it bounces (perpendicular to normal), [0,1] + F32 bounceFriction; + */ + + /// Should this projectile fall/rise different than a default object? + F32 gravityMod; + + /// How long the projectile should exist before deleting itself + U32 lifetime; // ticks + /* + /// How long it should not detonate on impact + S32 armingDelay; // the values are converted on initialization with + */ + S32 fadeDelay; // ticks + + /* + ExplosionData* explosion; // Explosion Datablock + S32 explosionId; // Explosion ID + ExplosionData* waterExplosion; // Water Explosion Datablock + S32 waterExplosionId; // Water Explosion ID + */ + + SplashData* splash; // Water Splash Datablock + S32 splashId; // Water splash ID + + SFXTrack* sound; // Projectile Sound + + LightDescription *lightDesc; + S32 lightDescId; + + /* + enum DecalConstants { // Number of decals constant + NumDecals = 6, + }; + DecalData* decals[NumDecals]; // Decal Datablocks + S32 decalId[NumDecals]; // Decal IDs + U32 decalCount; // # of loaded Decal Datablocks + */ + + // variables set on preload: + Resource projectileShape; + /* + S32 activateSeq; + S32 maintainSeq; + */ + + ParticleEmitterData* particleEmitter; + S32 particleEmitterId; + ParticleEmitterData* particleWaterEmitter; + S32 particleWaterEmitterId; + + U32 collision_mask; + + Point3F starting_vel_vec; + + // guidance behavior + bool isGuided; + F32 precision; + S32 trackDelay; + + // simple physics + F32 ballisticCoefficient; + + // terrain following + bool followTerrain; + F32 followTerrainHeight; + F32 followTerrainAdjustRate; + S32 followTerrainAdjustDelay; + + F32 acceleration; + S32 accelDelay; + U32 accelLifetime; + + StringTableEntry launch_node; + Point3F launch_offset; + Point3F launch_offset_server; + Point3F launch_offset_client; + Point3F launch_node_offset; + F32 launch_pitch; + F32 launch_pan; + bool echo_launch_offset; + + StringTableEntry launch_cons_s_spec; + afxConstraintDef launch_cons_s_def; + StringTableEntry launch_cons_c_spec; + afxConstraintDef launch_cons_c_def; + + // wiggle behavior + Vector wiggle_magnitudes; + Vector wiggle_speeds; + StringTableEntry wiggle_axis_string; + Point3F* wiggle_axis; + U32 wiggle_num_axis; + + // hover behavior + F32 hover_altitude; + F32 hover_attack_distance; + F32 hover_attack_gradient; + U32 hover_time; + + bool reverse_targeting; + + U32 caster_safety_time; + +public: + /*C*/ afxMagicMissileData(); + /*D*/ ~afxMagicMissileData(); + + void packData(BitStream*); + void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxMagicMissileData); + DECLARE_CATEGORY("AFX"); + +public: + /*C*/ afxMagicMissileData(const afxMagicMissileData&, bool = false); + + afxMagicMissileData* cloneAndPerformSubstitutions(const SimObject*, S32 index=0); + virtual bool allowSubstitutions() const { return true; } + void gather_cons_defs(Vector& defs); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMagicMissile + +//class afxMagicSpell; +class afxChoreographer; + +class afxMagicMissile : public GameBase, public ISceneLight +{ + typedef GameBase Parent; + +public: + /* + // Initial conditions + enum ProjectileConstants { + SourceIdTimeoutTicks = 7, // = 231 ms + DeleteWaitTime = 500, ///< 500 ms delete timeout (for network transmission delays) + ExcessVelDirBits = 7, + MaxLivingTicks = 4095, + }; + */ + enum UpdateMasks { + /* + BounceMask = Parent::NextFreeMask, + ExplosionMask = Parent::NextFreeMask << 1, + */ + GuideMask = Parent::NextFreeMask << 0, + LaunchMask = Parent::NextFreeMask << 1, + ImpactMask = Parent::NextFreeMask << 2, + NextFreeMask = Parent::NextFreeMask << 3 + }; +protected: + PhysicsWorld *mPhysicsWorld; + + afxMagicMissileData* mDataBlock; + + ParticleEmitter* mParticleEmitter; + ParticleEmitter* mParticleWaterEmitter; + SFXSource* mSound; + + Point3F mCurrPosition; + Point3F mCurrVelocity; + /* + S32 mSourceObjectId; + S32 mSourceObjectSlot; + */ + + // Time related variables common to all projectiles, managed by processTick + + U32 mCurrTick; ///< Current time in ticks + /* + SimObjectPtr mSourceObject; ///< Actual pointer to the source object, times out after SourceIdTimeoutTicks + */ + + // Rendering related variables + TSShapeInstance* mProjectileShape; + /* + TSThread* mActivateThread; + TSThread* mMaintainThread; + + Point3F mLastRenderPos; + */ + + // ISceneLight + virtual void submitLights( LightManager *lm, bool staticLighting ); + virtual LightInfo* getLight() { return mLight; } + + LightInfo *mLight; + LightState mLightState; + + /* + bool mHidden; ///< set by the derived class, if true, projectile doesn't render + F32 mFadeValue; ///< set in processTick, interpolation between fadeDelay and lifetime + ///< in data block + */ + + /* + // Warping and back delta variables. Only valid on the client + // + Point3F mWarpStart; + Point3F mWarpEnd; + U32 mWarpTicksRemaining; + */ + + Point3F mCurrDeltaBase; + Point3F mCurrBackDelta; + + /* + Point3F mExplosionPosition; + Point3F mExplosionNormal; + U32 mCollideHitType; + */ + + bool onAdd(); + void onRemove(); + bool onNewDataBlock(GameBaseData *dptr, bool reload); + + // Rendering + virtual void prepRenderImage(SceneRenderState*); + void prepBatchRender( SceneRenderState *state); + + void processTick(const Move *move); + /* + void advanceTime(F32 dt); + */ + void interpolateTick(F32 delta); + + /* + /// What to do once this projectile collides with something + virtual void onCollision(const Point3F& p, const Point3F& n, SceneObject*); + + /// What to do when this projectile explodes + virtual void explode(const Point3F& p, const Point3F& n, const U32 collideType ); + + /// Returns the velocity of the projectile + Point3F getVelocity() const; + */ + + void emitParticles(const Point3F&, const Point3F&, const Point3F&, const U32); + void updateSound(); + + // Rendering + /* + void prepModelView ( SceneRenderState *state); + */ + + // These are stolen from the player class .. + bool pointInWater(const Point3F &point); + + U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream); + void unpackUpdate(NetConnection *conn, BitStream *stream); + + afxChoreographer* choreographer; + + bool client_only; + bool server_only; + bool use_accel; + U32 collision_mask; + F32 prec_inc; + + bool did_launch; + bool did_impact; + + SceneObject* missile_target; + SceneObject* collide_exempt; + + bool hover_attack_go; + U32 hover_attack_tick; + + F32 starting_velocity; + Point3F starting_vel_vec; + + SimObject* ss_object; + S32 ss_index; + +private: + void init(bool on_server, bool on_client); + void create_splash(const Point3F& pos); + SceneObject* get_default_launcher() const; + void get_launch_constraint_data(Point3F& pos, Point3F& vel); + void get_launch_data(Point3F& pos, Point3F& vel); + bool is_active() const { return (did_launch && !did_impact); } + +public: + /* + F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips); + */ + /*C*/ afxMagicMissile(); + /*C*/ afxMagicMissile(bool on_server, bool on_client); + /*D*/ ~afxMagicMissile(); + virtual void onDeleteNotify(SimObject*); + + DECLARE_CONOBJECT(afxMagicMissile); + DECLARE_CATEGORY("AFX"); + + static void initPersistFields(); + + /* + virtual bool calculateImpact(float simTime, + Point3F& pointOfImpact, + float& impactTime); + + static U32 smProjectileWarpTicks; + +protected: + static const U32 csmStaticCollisionMask; + static const U32 csmDynamicCollisionMask; + static const U32 csmDamageableMask; + */ + + void launch(); + void setChoreographer(afxChoreographer*); + void setStartingVelocityVector(const Point3F& vel_vec); + void setStartingVelocity(const F32 vel); + void getStartingVelocityValues(F32& vel, Point3F& vel_vec); + void setSubstitutionData(SimObject* obj, S32 idx=0) { ss_object = obj; ss_index = idx; } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMagicMissileCallback + +class afxMagicMissileCallback +{ +public: + virtual void impactNotify(const Point3F& p, const Point3F& n, SceneObject*)=0; +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_MAGIC_MISSILE_H_ + diff --git a/Engine/source/afx/afxMagicSpell.cpp b/Engine/source/afx/afxMagicSpell.cpp new file mode 100644 index 000000000..7aeb93da8 --- /dev/null +++ b/Engine/source/afx/afxMagicSpell.cpp @@ -0,0 +1,2709 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" +#include "T3D/gameBase/gameConnection.h" +#include "sfx/sfxSystem.h" +#include "math/mathIO.h" +#include "T3D/containerQuery.h" + +#include "afx/afxChoreographer.h" +#include "afx/afxPhrase.h" +#include "afx/afxMagicSpell.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMagicSpellData::ewValidator +// +// When any of the effect list fields (addCastingEffect, etc.) are set, this validator +// intercepts the value and adds it to the appropriate effects list. One validator is +// created for each effect list and an id is used to identify which list to add the effect +// to. +// +void afxMagicSpellData::ewValidator::validateType(SimObject* object, void* typePtr) +{ + afxMagicSpellData* spelldata = dynamic_cast(object); + afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr); + + if (spelldata && ew) + { + switch (id) + { + case CASTING_PHRASE: + spelldata->casting_fx_list.push_back(*ew); + break; + case LAUNCH_PHRASE: + spelldata->launch_fx_list.push_back(*ew); + break; + case DELIVERY_PHRASE: + spelldata->delivery_fx_list.push_back(*ew); + break; + case IMPACT_PHRASE: + spelldata->impact_fx_list.push_back(*ew); + break; + case LINGER_PHRASE: + spelldata->linger_fx_list.push_back(*ew); + break; + } + *ew = 0; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class SpellFinishStartupEvent : public SimEvent +{ +public: + void process(SimObject* obj) + { + afxMagicSpell* spell = dynamic_cast(obj); + if (spell) + spell->finish_startup(); + } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMagicSpellData + +IMPLEMENT_CO_DATABLOCK_V1(afxMagicSpellData); + +ConsoleDocClass( afxMagicSpellData, + "@brief Defines the properties of an afxMagicSpell.\n\n" + + "@ingroup afxChoreographers\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +IMPLEMENT_CALLBACK( afxMagicSpellData, onDamage, void, + (afxMagicSpell* spell, const char* label, const char* flaver, U32 target_id, F32 amount, U8 n, Point3F pos, F32 ad_amount, F32 radius, F32 impulse), + (spell, label, flaver, target_id, amount, n, pos, ad_amount, radius, impulse), + "Called when the spell deals damage.\n" + "@param spell the spell object\n" ); + +IMPLEMENT_CALLBACK( afxMagicSpellData, onDeactivate, void, (afxMagicSpell* spell), (spell), + "Called when the spell ends naturally.\n" + "@param spell the spell object\n" ); + +IMPLEMENT_CALLBACK( afxMagicSpellData, onInterrupt, void, (afxMagicSpell* spell, ShapeBase* caster), (spell, caster), + "Called when the spell ends unnaturally due to an interruption.\n" + "@param spell the spell object\n" ); + +IMPLEMENT_CALLBACK( afxMagicSpellData, onLaunch, void, + (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target, afxMagicMissile* missile), + (spell, caster, target, missile), + "Called when the spell's casting stage ends and the delivery stage begins.\n" + "@param spell the spell object\n" ); + +IMPLEMENT_CALLBACK( afxMagicSpellData, onImpact, void, + (afxMagicSpell* spell, ShapeBase* caster, SceneObject* impacted, Point3F pos, Point3F normal), + (spell, caster, impacted, pos, normal), + "Called at the spell's missile impact marking the end of the deliver stage and the start of the linger stage.\n" + "@param spell the spell object\n" ); + +IMPLEMENT_CALLBACK( afxMagicSpellData, onPreactivate, bool, + (SimObject* param_holder, ShapeBase* caster, SceneObject* target, SimObject* extra), + (param_holder, caster, target, extra), + "Called during spell casting before spell instance is fully created.\n"); + +IMPLEMENT_CALLBACK( afxMagicSpellData, onActivate, void, + (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target), + (spell, caster, target), + "Called when the spell starts.\n" + "@param spell the spell object\n" ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxMagicSpellData::afxMagicSpellData() +{ + casting_dur = 0.0f; + delivery_dur = 0.0f; + linger_dur = 0.0f; + + n_casting_loops = 1; + n_delivery_loops = 1; + n_linger_loops = 1; + + extra_casting_time = 0.0f; + extra_delivery_time = 0.0f; + extra_linger_time = 0.0f; + + // interrupt flags + do_move_interrupts = true; + move_interrupt_speed = 2.0f; + + // delivers projectile spells + missile_db = 0; + launch_on_server_signal = false; + primary_target_types = PlayerObjectType; + + // dummy entry holds effect-wrapper pointer while a special validator + // grabs it and adds it to an appropriate effects list + dummy_fx_entry = NULL; + + // marked true if datablock ids need to + // be converted into pointers + do_id_convert = false; +} + +afxMagicSpellData::afxMagicSpellData(const afxMagicSpellData& other, bool temp_clone) : afxChoreographerData(other, temp_clone) +{ + casting_dur = other.casting_dur; + delivery_dur = other.delivery_dur; + linger_dur = other.linger_dur; + n_casting_loops = other.n_casting_loops; + n_delivery_loops = other.n_delivery_loops; + n_linger_loops = other.n_linger_loops; + extra_casting_time = other.extra_casting_time; + extra_delivery_time = other.extra_delivery_time; + extra_linger_time = other.extra_linger_time; + do_move_interrupts = other.do_move_interrupts; + move_interrupt_speed = other.move_interrupt_speed; + missile_db = other.missile_db; + launch_on_server_signal = other.launch_on_server_signal; + primary_target_types = other.primary_target_types; + + dummy_fx_entry = other.dummy_fx_entry; + do_id_convert = other.do_id_convert; + + casting_fx_list = other.casting_fx_list; + launch_fx_list = other.launch_fx_list; + delivery_fx_list = other.delivery_fx_list; + impact_fx_list = other.impact_fx_list; + linger_fx_list = other.linger_fx_list; +} + +void afxMagicSpellData::reloadReset() +{ + casting_fx_list.clear(); + launch_fx_list.clear(); + delivery_fx_list.clear(); + impact_fx_list.clear(); + linger_fx_list.clear(); +} + +#define myOffset(field) Offset(field, afxMagicSpellData) + +void afxMagicSpellData::initPersistFields() +{ + static ewValidator _castingPhrase(CASTING_PHRASE); + static ewValidator _launchPhrase(LAUNCH_PHRASE); + static ewValidator _deliveryPhrase(DELIVERY_PHRASE); + static ewValidator _impactPhrase(IMPACT_PHRASE); + static ewValidator _lingerPhrase(LINGER_PHRASE); + + // for each effect list, dummy_fx_entry is set and then a validator adds it to the appropriate effects list + + addGroup("Casting Stage"); + addField("castingDur", TypeF32, myOffset(casting_dur), + "..."); + addField("numCastingLoops", TypeS32, myOffset(n_casting_loops), + "..."); + addField("extraCastingTime", TypeF32, myOffset(extra_casting_time), + "..."); + addFieldV("addCastingEffect", TYPEID(), Offset(dummy_fx_entry, afxMagicSpellData), &_castingPhrase, + "..."); + endGroup("Casting Stage"); + + addGroup("Delivery Stage"); + addField("deliveryDur", TypeF32, myOffset(delivery_dur), + "..."); + addField("numDeliveryLoops", TypeS32, myOffset(n_delivery_loops), + "..."); + addField("extraDeliveryTime", TypeF32, myOffset(extra_delivery_time), + "..."); + addFieldV("addLaunchEffect", TYPEID(), Offset(dummy_fx_entry, afxMagicSpellData), &_launchPhrase, + "..."); + addFieldV("addDeliveryEffect", TYPEID(), Offset(dummy_fx_entry, afxMagicSpellData), &_deliveryPhrase, + "..."); + endGroup("Delivery Stage"); + + addGroup("Linger Stage"); + addField("lingerDur", TypeF32, myOffset(linger_dur), + "..."); + addField("numLingerLoops", TypeS32, myOffset(n_linger_loops), + "..."); + addField("extraLingerTime", TypeF32, myOffset(extra_linger_time), + "..."); + addFieldV("addImpactEffect", TYPEID(), Offset(dummy_fx_entry, afxMagicSpellData), &_impactPhrase, + "..."); + addFieldV("addLingerEffect", TYPEID(), Offset(dummy_fx_entry, afxMagicSpellData), &_lingerPhrase, + "..."); + endGroup("Linger Stage"); + + // interrupt flags + addField("allowMovementInterrupts", TypeBool, myOffset(do_move_interrupts), + "..."); + addField("movementInterruptSpeed", TypeF32, myOffset(move_interrupt_speed), + "..."); + + // delivers projectile spells + addField("missile", TYPEID(), myOffset(missile_db), + "..."); + addField("launchOnServerSignal", TypeBool, myOffset(launch_on_server_signal), + "..."); + addField("primaryTargetTypes", TypeS32, myOffset(primary_target_types), + "..."); + + + Parent::initPersistFields(); + + // disallow some field substitutions + onlyKeepClearSubstitutions("missile"); // subs resolving to "~~", or "~0" are OK + disableFieldSubstitutions("addCastingEffect"); + disableFieldSubstitutions("addLaunchEffect"); + disableFieldSubstitutions("addDeliveryEffect"); + disableFieldSubstitutions("addImpactEffect"); + disableFieldSubstitutions("addLingerEffect"); +} + +bool afxMagicSpellData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + if (missile_db != NULL && delivery_dur == 0.0) + delivery_dur = -1; + + return true; +} + +void afxMagicSpellData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed) +{ + stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS); + for (int i = 0; i < fx.size(); i++) + writeDatablockID(stream, fx[i], packed); +} + +void afxMagicSpellData::unpack_fx(BitStream* stream, afxEffectList& fx) +{ + fx.clear(); + S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS); + for (int i = 0; i < n_fx; i++) + fx.push_back((afxEffectWrapperData*)readDatablockID(stream)); +} + +void afxMagicSpellData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(casting_dur); + stream->write(delivery_dur); + stream->write(linger_dur); + // + stream->write(n_casting_loops); + stream->write(n_delivery_loops); + stream->write(n_linger_loops); + // + stream->write(extra_casting_time); + stream->write(extra_delivery_time); + stream->write(extra_linger_time); + + stream->writeFlag(do_move_interrupts); + stream->write(move_interrupt_speed); + + writeDatablockID(stream, missile_db, packed); + stream->write(launch_on_server_signal); + stream->write(primary_target_types); + + pack_fx(stream, casting_fx_list, packed); + pack_fx(stream, launch_fx_list, packed); + pack_fx(stream, delivery_fx_list, packed); + pack_fx(stream, impact_fx_list, packed); + pack_fx(stream, linger_fx_list, packed); +} + +void afxMagicSpellData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&casting_dur); + stream->read(&delivery_dur); + stream->read(&linger_dur); + // + stream->read(&n_casting_loops); + stream->read(&n_delivery_loops); + stream->read(&n_linger_loops); + // + stream->read(&extra_casting_time); + stream->read(&extra_delivery_time); + stream->read(&extra_linger_time); + + do_move_interrupts = stream->readFlag(); + stream->read(&move_interrupt_speed); + + missile_db = (afxMagicMissileData*) readDatablockID(stream); + stream->read(&launch_on_server_signal); + stream->read(&primary_target_types); + + do_id_convert = true; + unpack_fx(stream, casting_fx_list); + unpack_fx(stream, launch_fx_list); + unpack_fx(stream, delivery_fx_list); + unpack_fx(stream, impact_fx_list); + unpack_fx(stream, linger_fx_list); +} + +bool afxMagicSpellData::writeField(StringTableEntry fieldname, const char* value) +{ + if (!Parent::writeField(fieldname, value)) + return false; + + // don't write the dynamic array fields + if( fieldname == StringTable->insert("addCastingEffect") ) + return false; + if( fieldname == StringTable->insert("addLaunchEffect") ) + return false; + if( fieldname == StringTable->insert("addDeliveryEffect") ) + return false; + if( fieldname == StringTable->insert("addImpactEffect") ) + return false; + if( fieldname == StringTable->insert("addLingerEffect") ) + return false; + + return true; +} + +inline void expand_fx_list(afxEffectList& fx_list, const char* tag) +{ + for (S32 i = 0; i < fx_list.size(); i++) + { + SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]); + if (db_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(db_id, fx_list[i])) + { + Con::errorf(ConsoleLogEntry::General, + "afxMagicSpellData::preload() -- bad datablockId: 0x%x (%s)", + db_id, tag); + } + } + } +} + +bool afxMagicSpellData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) + { + if (do_id_convert) + { + SimObjectId missile_id = SimObjectId((uintptr_t)missile_db); + if (missile_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(missile_id, missile_db)) + { + Con::errorf(ConsoleLogEntry::General, + "afxMagicSpellData::preload() -- bad datablockId: 0x%x (missile)", + missile_id); + } + } + expand_fx_list(casting_fx_list, "casting"); + expand_fx_list(launch_fx_list, "launch"); + expand_fx_list(delivery_fx_list, "delivery"); + expand_fx_list(impact_fx_list, "impact"); + expand_fx_list(linger_fx_list, "linger"); + do_id_convert = false; + } + } + + return true; +} + +void afxMagicSpellData::gatherConstraintDefs(Vector& defs) +{ + afxConstraintDef::gather_cons_defs(defs, casting_fx_list); + afxConstraintDef::gather_cons_defs(defs, launch_fx_list); + afxConstraintDef::gather_cons_defs(defs, delivery_fx_list); + afxConstraintDef::gather_cons_defs(defs, impact_fx_list); + afxConstraintDef::gather_cons_defs(defs, linger_fx_list); + + if (missile_db) + missile_db->gather_cons_defs(defs); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod(afxMagicSpellData, reset, void, (),, + "Resets a spell datablock during reload.\n\n" + "@ingroup AFX") +{ + object->reloadReset(); +} + +DefineEngineMethod(afxMagicSpellData, addCastingEffect, void, (afxEffectBaseData* effect),, + "Adds an effect (wrapper or group) to a spell's casting phase.\n\n" + "@ingroup AFX") +{ + if (!effect) + { + Con::errorf(ConsoleLogEntry::General, + "afxMagicSpellData::addCastingEffect() -- " + "missing afxEffectWrapperData."); + return; + } + + object->casting_fx_list.push_back(effect); +} + +DefineEngineMethod(afxMagicSpellData, addLaunchEffect, void, (afxEffectBaseData* effect),, + "Adds an effect (wrapper or group) to a spell's launch phase.\n\n" + "@ingroup AFX") + +{ + if (!effect) + { + Con::errorf(ConsoleLogEntry::General, + "afxMagicSpellData::addLaunchEffect() -- " + "failed to find afxEffectWrapperData."); + return; + } + + object->launch_fx_list.push_back(effect); +} + +DefineEngineMethod(afxMagicSpellData, addDeliveryEffect, void, (afxEffectBaseData* effect),, + "Adds an effect (wrapper or group) to a spell's delivery phase.\n\n" + "@ingroup AFX") + +{ + if (!effect) + { + Con::errorf(ConsoleLogEntry::General, + "afxMagicSpellData::addDeliveryEffect() -- " + "missing afxEffectWrapperData."); + return; + } + + object->delivery_fx_list.push_back(effect); +} + +DefineEngineMethod(afxMagicSpellData, addImpactEffect, void, (afxEffectBaseData* effect),, + "Adds an effect (wrapper or group) to a spell's impact phase.\n\n" + "@ingroup AFX") + +{ + if (!effect) + { + Con::errorf(ConsoleLogEntry::General, + "afxMagicSpellData::addImpactEffect() -- " + "missing afxEffectWrapperData."); + return; + } + + object->impact_fx_list.push_back(effect); +} + +DefineEngineMethod(afxMagicSpellData, addLingerEffect, void, (afxEffectBaseData* effect),, + "Adds an effect (wrapper or group) to a spell's linger phase.\n\n" + "@ingroup AFX") + +{ + if (!effect) + { + Con::errorf(ConsoleLogEntry::General, + "afxMagicSpellData::addLingerEffect() -- " + "missing afxEffectWrapperData."); + return; + } + + object->linger_fx_list.push_back(effect); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMagicSpell + +IMPLEMENT_GLOBAL_CALLBACK( onCastingStart, void, (), (), + "A callout called on clients by spells when the casting stage begins.\n" + "@ingroup AFX\n" ); + +IMPLEMENT_GLOBAL_CALLBACK( onCastingProgressUpdate, void, (F32 frac), (frac), + "A callout called periodically on clients by spells to indicate casting progress.\n" + "@ingroup AFX\n" ); + +IMPLEMENT_GLOBAL_CALLBACK( onCastingEnd, void, (), (), + "A callout called on clients by spells when the casting stage ends.\n" + "@ingroup AFX\n" ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// CastingPhrase_C +// Subclass of afxPhrase for the client casting phrase. +// This subclass adds handling of the casting progress +// bar in cases where the caster is the client's control +// object. +// + +class CastingPhrase_C : public afxPhrase +{ + typedef afxPhrase Parent; + ShapeBase* caster; + bool notify_castbar; + F32 castbar_progress; +public: + /*C*/ CastingPhrase_C(ShapeBase* caster, bool notify_castbar); + virtual void start(F32 startstamp, F32 timestamp); + virtual void update(F32 dt, F32 timestamp); + virtual void stop(F32 timestamp); + virtual void interrupt(F32 timestamp); +}; + +CastingPhrase_C::CastingPhrase_C(ShapeBase* c, bool notify) + : afxPhrase(false, true) +{ + caster = c; + notify_castbar = notify; + castbar_progress = 0.0f; +} + +void CastingPhrase_C::start(F32 startstamp, F32 timestamp) +{ + Parent::start(startstamp, timestamp); //START + if (notify_castbar) + { + castbar_progress = 0.0f; + onCastingStart_callback(); + } +} + +void CastingPhrase_C::update(F32 dt, F32 timestamp) +{ + Parent::update(dt, timestamp); + + if (!notify_castbar) + return; + + if (dur > 0 && n_loops > 0) + { + F32 nfrac = (timestamp - starttime)/(dur*n_loops); + if (nfrac - castbar_progress > 1.0f/200.0f) + { + castbar_progress = (nfrac < 1.0f) ? nfrac : 1.0f; + onCastingProgressUpdate_callback(castbar_progress); + } + } +} + +void CastingPhrase_C::stop(F32 timestamp) +{ + Parent::stop(timestamp); + if (notify_castbar) + { + onCastingEnd_callback(); + notify_castbar = false; + } +} + +void CastingPhrase_C::interrupt(F32 timestamp) +{ + Parent::interrupt(timestamp); + if (notify_castbar) + { + onCastingEnd_callback(); + notify_castbar = false; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// some enum to name converters for debugging purposes + +#ifdef USE_FOR_DEBUG_MESSAGES +static char* name_from_state(U8 s) +{ + switch (s) + { + case afxMagicSpell::INACTIVE_STATE: + return "inactive"; + case afxMagicSpell::CASTING_STATE: + return "casting"; + case afxMagicSpell::DELIVERY_STATE: + return "delivery"; + case afxMagicSpell::LINGER_STATE: + return "linger"; + case afxMagicSpell::CLEANUP_STATE: + return "cleanup"; + case afxMagicSpell::DONE_STATE: + return "done"; + } + + return "unknown"; +} + +static char* name_from_event(U8 e) +{ + switch (e) + { + case afxMagicSpell::NULL_EVENT: + return "null"; + case afxMagicSpell::ACTIVATE_EVENT: + return "activate"; + case afxMagicSpell::LAUNCH_EVENT: + return "launch"; + case afxMagicSpell::IMPACT_EVENT: + return "impact"; + case afxMagicSpell::SHUTDOWN_EVENT: + return "shutdown"; + case afxMagicSpell::DEACTIVATE_EVENT: + return "deactivate"; + case afxMagicSpell::INTERRUPT_PHASE_EVENT: + return "interrupt_phase"; + case afxMagicSpell::INTERRUPT_SPELL_EVENT: + return "interrupt_spell"; + } + + return "unknown"; +} +#endif + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMagicSpell + +IMPLEMENT_CO_NETOBJECT_V1(afxMagicSpell); + +ConsoleDocClass( afxMagicSpell, + "@brief A magic spell effects choreographer.\n\n" + + "@ingroup afxChoreographers\n" + "@ingroup AFX\n" +); + +// static +StringTableEntry afxMagicSpell::CASTER_CONS; +StringTableEntry afxMagicSpell::TARGET_CONS; +StringTableEntry afxMagicSpell::MISSILE_CONS; +StringTableEntry afxMagicSpell::CAMERA_CONS; +StringTableEntry afxMagicSpell::LISTENER_CONS; +StringTableEntry afxMagicSpell::IMPACT_POINT_CONS; +StringTableEntry afxMagicSpell::IMPACTED_OBJECT_CONS; + +void afxMagicSpell::init() +{ + // setup static predefined constraint names + if (CASTER_CONS == 0) + { + CASTER_CONS = StringTable->insert("caster"); + TARGET_CONS = StringTable->insert("target"); + MISSILE_CONS = StringTable->insert("missile"); + CAMERA_CONS = StringTable->insert("camera"); + LISTENER_CONS = StringTable->insert("listener"); + IMPACT_POINT_CONS = StringTable->insert("impactPoint"); + IMPACTED_OBJECT_CONS = StringTable->insert("impactedObject"); + } + + // afxMagicSpell is always in scope, however effects + // do their own scoping in that they will shut off if + // their position constraint leaves scope. + // + // note -- ghosting is delayed until constraint + // initialization is done. + // + //mNetFlags.set(Ghostable | ScopeAlways); + mNetFlags.clear(Ghostable | ScopeAlways); + + datablock = NULL; + exeblock = NULL; + missile_db = NULL; + + caster = NULL; + target = NULL; + + caster_field = NULL; + target_field = NULL; + + caster_scope_id = 0; + target_scope_id = 0; + target_is_shape = false; + + constraints_initialized = false; + scoping_initialized = false; + + spell_state = (U8) INACTIVE_STATE; + spell_elapsed = 0; + + // define named constraints + constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, CASTER_CONS); + constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, TARGET_CONS); + constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, MISSILE_CONS); + constraint_mgr->defineConstraint(CAMERA_CONSTRAINT, CAMERA_CONS); + constraint_mgr->defineConstraint(POINT_CONSTRAINT, LISTENER_CONS); + constraint_mgr->defineConstraint(POINT_CONSTRAINT, IMPACT_POINT_CONS); + constraint_mgr->defineConstraint(OBJECT_CONSTRAINT, IMPACTED_OBJECT_CONS); + + for (S32 i = 0; i < NUM_PHRASES; i++) + { + phrases[i] = NULL; + tfactors[i] = 1.0f; + } + + notify_castbar = false; + overall_time_factor = 1.0f; + + camera_cons_obj = 0; + + marks_mask = 0; + + missile = NULL; + missile_is_armed = false; + impacted_obj = NULL; + impact_pos.zero(); + impact_norm.set(0,0,1); + impacted_scope_id = 0; + impacted_is_shape = false; +} + +afxMagicSpell::afxMagicSpell() +{ + started_with_newop = true; + init(); +} + +afxMagicSpell::afxMagicSpell(ShapeBase* caster, SceneObject* target) +{ + started_with_newop = false; + init(); + + this->caster = caster; + if (caster) + { + caster_field = caster; + deleteNotify(caster); + processAfter(caster); + } + + this->target = target; + if (target) + { + target_field = target; + deleteNotify(target); + } +} + +afxMagicSpell::~afxMagicSpell() +{ + for (S32 i = 0; i < NUM_PHRASES; i++) + { + if (phrases[i]) + { + phrases[i]->interrupt(spell_elapsed); + delete phrases[i]; + } + } + + if (missile) + missile->deleteObject(); + + if (missile_db && missile_db->isTempClone()) + { + delete missile_db; + missile_db = 0; + } + + if (datablock && datablock->isTempClone()) + { + delete datablock; + datablock = 0; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +// STANDARD OVERLOADED METHODS // + +bool afxMagicSpell::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + datablock = dynamic_cast(dptr); + if (!datablock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + if (isServerObject() && started_with_newop) + { + // copy dynamic fields from the datablock but + // don't replace fields with a value + assignDynamicFieldsFrom(dptr, arcaneFX::sParameterFieldPrefix, true); + } + + exeblock = datablock; + missile_db = datablock->missile_db; + + if (isClientObject()) + { + // make a temp datablock clone if there are substitutions + if (datablock->getSubstitutionCount() > 0) + { + afxMagicSpellData* orig_db = datablock; + datablock = new afxMagicSpellData(*orig_db, true); + exeblock = orig_db; + missile_db = datablock->missile_db; + // Don't perform substitutions yet, the spell's dynamic fields haven't + // arrived yet and the substitutions may refer to them. Hold off and do + // in in the onAdd() method. + } + } + else if (started_with_newop) + { + // make a temp datablock clone if there are substitutions + if (datablock->getSubstitutionCount() > 0) + { + afxMagicSpellData* orig_db = datablock; + datablock = new afxMagicSpellData(*orig_db, true); + exeblock = orig_db; + orig_db->performSubstitutions(datablock, this, ranking); + missile_db = datablock->missile_db; + } + } + + return true; +} + +void afxMagicSpell::processTick(const Move* m) +{ + Parent::processTick(m); + + // don't process moves or client ticks + if (m != 0 || isClientObject()) + return; + + process_server(); +} + +void afxMagicSpell::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + process_client(dt); +} + +bool afxMagicSpell::onAdd() +{ + if (!Parent::onAdd()) + return false ; + + if (isClientObject()) + { + if (datablock->isTempClone()) + { + afxMagicSpellData* orig_db = (afxMagicSpellData*)exeblock; + orig_db->performSubstitutions(datablock, this, ranking); + missile_db = datablock->missile_db; + notify_castbar = (notify_castbar && (datablock->casting_dur > 0.0f)); + } + } + else if (started_with_newop && !postpone_activation) + { + if (!activationCallInit()) + return false; + activate(); + } + + return true ; +} + +void afxMagicSpell::onRemove() +{ + Parent::onRemove(); +} + +void afxMagicSpell::onDeleteNotify(SimObject* obj) +{ + // caster deleted? + ShapeBase* shape = dynamic_cast(obj); + if (shape == caster) + { + clearProcessAfter(); + caster = NULL; + caster_field = NULL; + caster_scope_id = 0; + } + + // target deleted? + SceneObject* scene_obj = dynamic_cast(obj); + if (scene_obj == target) + { + target = NULL; + target_field = NULL; + target_scope_id = 0; + target_is_shape = false; + } + + // impacted_obj deleted? + if (scene_obj == impacted_obj) + { + impacted_obj = NULL; + impacted_scope_id = 0; + impacted_is_shape = false; + } + + // missile deleted? + afxMagicMissile* missile = dynamic_cast(obj); + if (missile != NULL && missile == this->missile) + { + this->missile = NULL; + } + + // something else + Parent::onDeleteNotify(obj); +} + +// static +void afxMagicSpell::initPersistFields() +{ + addField("caster", TYPEID(), Offset(caster_field, afxMagicSpell), + "..."); + addField("target", TYPEID(), Offset(target_field, afxMagicSpell), + "..."); + + Parent::initPersistFields(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxMagicSpell::pack_constraint_info(NetConnection* conn, BitStream* stream) +{ + // pack caster's ghost index or scope id if not yet ghosted + if (stream->writeFlag(caster != NULL)) + { + S32 ghost_idx = conn->getGhostIndex(caster); + if (stream->writeFlag(ghost_idx != -1)) + stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount); + else + { + bool bit = (caster) ? (caster->getScopeId() > 0) : false; + if (stream->writeFlag(bit)) + stream->writeInt(caster->getScopeId(), NetObject::SCOPE_ID_BITS); + } + } + + // pack target's ghost index or scope id if not yet ghosted + if (stream->writeFlag(target != NULL)) + { + S32 ghost_idx = conn->getGhostIndex(target); + if (stream->writeFlag(ghost_idx != -1)) + stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount); + else + { + if (stream->writeFlag(target->getScopeId() > 0)) + { + stream->writeInt(target->getScopeId(), NetObject::SCOPE_ID_BITS); + stream->writeFlag(dynamic_cast(target) != NULL); // is shape? + } + } + } + + Parent::pack_constraint_info(conn, stream); +} + +void afxMagicSpell::unpack_constraint_info(NetConnection* conn, BitStream* stream) +{ + caster = NULL; + caster_field = NULL; + caster_scope_id = 0; + if (stream->readFlag()) // has caster + { + if (stream->readFlag()) // has ghost_idx + { + S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount); + caster = dynamic_cast(conn->resolveGhost(ghost_idx)); + if (caster) + { + caster_field = caster; + deleteNotify(caster); + processAfter(caster); + } + } + else + { + if (stream->readFlag()) // has scope_id (is always a shape) + caster_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS); + } + } + + target = NULL; + target_field = NULL; + target_scope_id = 0; + target_is_shape = false; + if (stream->readFlag()) // has target + { + if (stream->readFlag()) // has ghost_idx + { + S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount); + target = dynamic_cast(conn->resolveGhost(ghost_idx)); + if (target) + { + target_field = target; + deleteNotify(target); + } + } + else + { + if (stream->readFlag()) // has scope_id + { + target_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS); + target_is_shape = stream->readFlag(); // is shape? + } + } + } + + Parent::unpack_constraint_info(conn, stream); +} + +U32 afxMagicSpell::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) +{ + S32 mark_stream_pos = stream->getCurPos(); + + U32 retMask = Parent::packUpdate(conn, mask, stream); + + // InitialUpdate + if (stream->writeFlag(mask & InitialUpdateMask)) + { + // pack extra object's ghost index or scope id if not yet ghosted + if (stream->writeFlag(dynamic_cast(extra) != 0)) + { + NetObject* net_extra = (NetObject*)extra; + S32 ghost_idx = conn->getGhostIndex(net_extra); + if (stream->writeFlag(ghost_idx != -1)) + stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount); + else + { + if (stream->writeFlag(net_extra->getScopeId() > 0)) + { + stream->writeInt(net_extra->getScopeId(), NetObject::SCOPE_ID_BITS); + } + } + } + + // pack initial exec conditions + stream->write(exec_conds_mask); + + // flag if this client owns the spellcaster + bool client_owns_caster = is_caster_client(caster, dynamic_cast(conn)); + stream->writeFlag(client_owns_caster); + + // pack per-phrase time-factor values + for (S32 i = 0; i < NUM_PHRASES; i++) + stream->write(tfactors[i]); + + // flag if this conn is zoned-in yet + bool zoned_in = client_owns_caster; + if (!zoned_in) + { + GameConnection* gconn = dynamic_cast(conn); + zoned_in = (gconn) ? gconn->isZonedIn() : false; + } + if (stream->writeFlag(zoned_in)) + pack_constraint_info(conn, stream); + } + + // StateEvent or SyncEvent + if (stream->writeFlag((mask & StateEventMask) || (mask & SyncEventMask))) + { + stream->write(marks_mask); + stream->write(spell_state); + stream->write(state_elapsed()); + stream->write(spell_elapsed); + } + + // SyncEvent + if (stream->writeFlag((mask & SyncEventMask) && !(mask & InitialUpdateMask))) + { + pack_constraint_info(conn, stream); + } + + // LaunchEvent + if (stream->writeFlag((mask & LaunchEventMask) && (marks_mask & MARK_LAUNCH) && missile)) + { + F32 vel; Point3F vel_vec; + missile->getStartingVelocityValues(vel, vel_vec); + // pack launch vector and velocity + stream->write(vel); + mathWrite(*stream, vel_vec); + } + + // ImpactEvent + if (stream->writeFlag(((mask & ImpactEventMask) || (mask & SyncEventMask)) && (marks_mask & MARK_IMPACT))) + { + // pack impact objects's ghost index or scope id if not yet ghosted + if (stream->writeFlag(impacted_obj != NULL)) + { + S32 ghost_idx = conn->getGhostIndex(impacted_obj); + if (stream->writeFlag(ghost_idx != -1)) + stream->writeRangedU32(U32(ghost_idx), 0, NetConnection::MaxGhostCount); + else + { + if (stream->writeFlag(impacted_obj->getScopeId() > 0)) + { + stream->writeInt(impacted_obj->getScopeId(), NetObject::SCOPE_ID_BITS); + stream->writeFlag(dynamic_cast(impacted_obj) != NULL); + } + } + } + + // pack impact position and normal + mathWrite(*stream, impact_pos); + mathWrite(*stream, impact_norm); + stream->write(exec_conds_mask); + + ShapeBase* temp_shape; + stream->writeFlag(caster != 0 && caster->getDamageState() == ShapeBase::Enabled); + temp_shape = dynamic_cast(target); + stream->writeFlag(temp_shape != 0 && temp_shape->getDamageState() == ShapeBase::Enabled); + temp_shape = dynamic_cast(impacted_obj); + stream->writeFlag(temp_shape != 0 && temp_shape->getDamageState() == ShapeBase::Enabled); + } + + check_packet_usage(conn, stream, mark_stream_pos, "afxMagicSpell:"); + + AssertISV(stream->isValid(), "afxMagicSpell::packUpdate(): write failure occurred, possibly caused by packet-size overrun."); + + return retMask; +} + +//~~~~~~~~~~~~~~~~~~~~// + + // CONSTRAINT REMAPPING << +bool afxMagicSpell::remap_builtin_constraint(SceneObject* obj, const char* cons_name) +{ + StringTableEntry cons_name_ste = StringTable->insert(cons_name); + + if (cons_name_ste == CASTER_CONS) + return true; + if (cons_name_ste == TARGET_CONS) + { + if (obj && target && obj != target && !target_cons_id.undefined()) + { + target = obj; + constraint_mgr->setReferenceObject(target_cons_id, target); + if (isServerObject()) + { + if (target->isScopeable()) + constraint_mgr->addScopeableObject(target); + } + } + return true; + } + if (cons_name_ste == MISSILE_CONS) + return true; + if (cons_name_ste == CAMERA_CONS) + return true; + if (cons_name_ste == LISTENER_CONS) + return true; + if (cons_name_ste == IMPACT_POINT_CONS) + return true; + if (cons_name_ste == IMPACTED_OBJECT_CONS) + return true; + + return false; +} + // CONSTRAINT REMAPPING >> + +void afxMagicSpell::unpackUpdate(NetConnection * conn, BitStream * stream) +{ + Parent::unpackUpdate(conn, stream); + + bool initial_update = false; + bool zoned_in = true; + bool do_sync_event = false; + U16 new_marks_mask = 0; + U8 new_spell_state = INACTIVE_STATE; + F32 new_state_elapsed = 0; + F32 new_spell_elapsed = 0;; + + // InitialUpdate + if (stream->readFlag()) + { + initial_update = true; + + // extra sent + if (stream->readFlag()) + { + // cleanup? + if (stream->readFlag()) // is ghost_idx + { + S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount); + extra = dynamic_cast(conn->resolveGhost(ghost_idx)); + } + else + { + if (stream->readFlag()) // has scope_id + { + // JTF NOTE: U16 extra_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS); + stream->readInt(NetObject::SCOPE_ID_BITS); + } + } + } + + // unpack initial exec conditions + stream->read(&exec_conds_mask); + + // if this is controlling client for the caster, + // enable castbar updates + bool client_owns_caster = stream->readFlag(); + if (client_owns_caster) + notify_castbar = Con::isFunction("onCastingStart"); + + // unpack per-phrase time-factor values + for (S32 i = 0; i < NUM_PHRASES; i++) + stream->read(&tfactors[i]); + + // if client is marked as fully zoned in + if ((zoned_in = stream->readFlag()) == true) + { + unpack_constraint_info(conn, stream); + init_constraints(); + } + } + + // StateEvent or SyncEvent + // this state data is sent for both state-events and + // sync-events + if (stream->readFlag()) + { + stream->read(&new_marks_mask); + stream->read(&new_spell_state); + stream->read(&new_state_elapsed); + stream->read(&new_spell_elapsed); + marks_mask = new_marks_mask; + } + + // SyncEvent + if ((do_sync_event = stream->readFlag()) == true) + { + unpack_constraint_info(conn, stream); + init_constraints(); + } + + // LaunchEvent + if (stream->readFlag()) + { + F32 vel; Point3F vel_vec; + stream->read(&vel); + mathRead(*stream, &vel_vec); + if (missile) + { + missile->setStartingVelocity(vel); + missile->setStartingVelocityVector(vel_vec); + } + } + + // ImpactEvent + if (stream->readFlag()) + { + if (impacted_obj) + clearNotify(impacted_obj); + impacted_obj = NULL; + impacted_scope_id = 0; + impacted_is_shape = false; + if (stream->readFlag()) // is impacted_obj + { + if (stream->readFlag()) // is ghost_idx + { + S32 ghost_idx = stream->readRangedU32(0, NetConnection::MaxGhostCount); + impacted_obj = dynamic_cast(conn->resolveGhost(ghost_idx)); + if (impacted_obj) + deleteNotify(impacted_obj); + } + else + { + if (stream->readFlag()) // has scope_id + { + impacted_scope_id = stream->readInt(NetObject::SCOPE_ID_BITS); + impacted_is_shape = stream->readFlag(); // is shape? + } + } + } + + mathRead(*stream, &impact_pos); + mathRead(*stream, &impact_norm); + stream->read(&exec_conds_mask); + + bool caster_alive = stream->readFlag(); + bool target_alive = stream->readFlag(); + bool impacted_alive = stream->readFlag(); + + afxConstraint* cons; + if ((cons = constraint_mgr->getConstraint(caster_cons_id)) != 0) + cons->setLivingState(caster_alive); + if ((cons = constraint_mgr->getConstraint(target_cons_id)) != 0) + cons->setLivingState(target_alive); + if ((cons = constraint_mgr->getConstraint(impacted_cons_id)) != 0) + cons->setLivingState(impacted_alive); + } + + //~~~~~~~~~~~~~~~~~~~~// + + if (!zoned_in) + spell_state = LATE_STATE; + + // need to adjust state info to get all synced up with spell on server + if (do_sync_event && !initial_update) + sync_client(new_marks_mask, new_spell_state, new_state_elapsed, new_spell_elapsed); +} + +void afxMagicSpell::sync_with_clients() +{ + setMaskBits(SyncEventMask); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private + +bool afxMagicSpell::state_expired() +{ + afxPhrase* phrase = NULL; + + switch (spell_state) + { + case CASTING_STATE: + phrase = phrases[CASTING_PHRASE]; + break; + case DELIVERY_STATE: + phrase = phrases[DELIVERY_PHRASE]; + break; + case LINGER_STATE: + phrase = phrases[LINGER_PHRASE]; + break; + } + + if (phrase) + { + if (phrase->expired(spell_elapsed)) + return (!phrase->recycle(spell_elapsed)); + return false; + } + + return true; +} + +F32 afxMagicSpell::state_elapsed() +{ + afxPhrase* phrase = NULL; + + switch (spell_state) + { + case CASTING_STATE: + phrase = phrases[CASTING_PHRASE]; + break; + case DELIVERY_STATE: + phrase = phrases[DELIVERY_PHRASE]; + break; + case LINGER_STATE: + phrase = phrases[LINGER_PHRASE]; + break; + } + + return (phrase) ? phrase->elapsed(spell_elapsed) : 0.0f; +} + +void afxMagicSpell::init_constraints() +{ + if (constraints_initialized) + { + //Con::printf("CONSTRAINTS ALREADY INITIALIZED"); + return; + } + + Vector defs; + datablock->gatherConstraintDefs(defs); + + constraint_mgr->initConstraintDefs(defs, isServerObject()); + + if (isServerObject()) + { + caster_cons_id = constraint_mgr->setReferenceObject(CASTER_CONS, caster); + target_cons_id = constraint_mgr->setReferenceObject(TARGET_CONS, target); +#if defined(AFX_CAP_SCOPE_TRACKING) + if (caster && caster->isScopeable()) + constraint_mgr->addScopeableObject(caster); + + if (target && target->isScopeable()) + constraint_mgr->addScopeableObject(target); +#endif + + // find local camera + camera_cons_obj = get_camera(); + if (camera_cons_obj) + camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj); + } + else // if (isClientObject()) + { + if (caster) + caster_cons_id = constraint_mgr->setReferenceObject(CASTER_CONS, caster); + else if (caster_scope_id > 0) + caster_cons_id = constraint_mgr->setReferenceObjectByScopeId(CASTER_CONS, caster_scope_id, true); + + if (target) + target_cons_id = constraint_mgr->setReferenceObject(TARGET_CONS, target); + else if (target_scope_id > 0) + target_cons_id = constraint_mgr->setReferenceObjectByScopeId(TARGET_CONS, target_scope_id, target_is_shape); + + // find local camera + camera_cons_obj = get_camera(); + if (camera_cons_obj) + camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj); + + // find local listener + Point3F listener_pos; + listener_pos = SFX->getListener().getTransform().getPosition(); + listener_cons_id = constraint_mgr->setReferencePoint(LISTENER_CONS, listener_pos); + } + + constraint_mgr->adjustProcessOrdering(this); + + constraints_initialized = true; +} + +void afxMagicSpell::init_scoping() +{ + if (scoping_initialized) + { + //Con::printf("SCOPING ALREADY INITIALIZED"); + return; + } + + if (isServerObject()) + { + if (explicit_clients.size() > 0) + { + for (U32 i = 0; i < explicit_clients.size(); i++) + explicit_clients[i]->objectLocalScopeAlways(this); + } + else + { + mNetFlags.set(Ghostable); + setScopeAlways(); + } + scoping_initialized = true; + } +} + +void afxMagicSpell::setup_casting_fx() +{ + if (isServerObject()) + phrases[CASTING_PHRASE] = new afxPhrase(isServerObject(), true); + else + phrases[CASTING_PHRASE] = new CastingPhrase_C(caster, notify_castbar); + + if (phrases[CASTING_PHRASE]) + phrases[CASTING_PHRASE]->init(datablock->casting_fx_list, datablock->casting_dur, this, + tfactors[CASTING_PHRASE], datablock->n_casting_loops, 0, + datablock->extra_casting_time); +} + +void afxMagicSpell::setup_launch_fx() +{ + phrases[LAUNCH_PHRASE] = new afxPhrase(isServerObject(), false); + if (phrases[LAUNCH_PHRASE]) + phrases[LAUNCH_PHRASE]->init(datablock->launch_fx_list, -1, this, + tfactors[LAUNCH_PHRASE], 1); +} + +void afxMagicSpell::setup_delivery_fx() +{ + phrases[DELIVERY_PHRASE] = new afxPhrase(isServerObject(), true); + if (phrases[DELIVERY_PHRASE]) + { + phrases[DELIVERY_PHRASE]->init(datablock->delivery_fx_list, datablock->delivery_dur, this, + tfactors[DELIVERY_PHRASE], datablock->n_delivery_loops, 0, + datablock->extra_delivery_time); + } +} + +void afxMagicSpell::setup_impact_fx() +{ + phrases[IMPACT_PHRASE] = new afxPhrase(isServerObject(), false); + if (phrases[IMPACT_PHRASE]) + { + phrases[IMPACT_PHRASE]->init(datablock->impact_fx_list, -1, this, + tfactors[IMPACT_PHRASE], 1); + } +} + +void afxMagicSpell::setup_linger_fx() +{ + phrases[LINGER_PHRASE] = new afxPhrase(isServerObject(), true); + if (phrases[LINGER_PHRASE]) + phrases[LINGER_PHRASE]->init(datablock->linger_fx_list, datablock->linger_dur, this, + tfactors[LINGER_PHRASE], datablock->n_linger_loops, 0, + datablock->extra_linger_time); +} + +bool afxMagicSpell::cleanup_over() +{ + for (S32 i = 0; i < NUM_PHRASES; i++) + if (phrases[i] && !phrases[i]->isEmpty()) + return false; + + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private +// +// MISSILE STUFF +// + +void afxMagicSpell::init_missile_s(afxMagicMissileData* mm_db) +{ + if (missile) + clearNotify(missile); + + // create the missile + missile = new afxMagicMissile(true, false); + missile->setSubstitutionData(this, ranking); + missile->setDataBlock(mm_db); + missile->setChoreographer(this); + if (!missile->registerObject()) + { + Con::errorf("afxMagicSpell: failed to register missile instance."); + delete missile; + missile = NULL; + } + + if (missile) + { + deleteNotify(missile); + registerForCleanup(missile); + } +} + +void afxMagicSpell::launch_missile_s() +{ + if (missile) + { + missile->launch(); + constraint_mgr->setReferenceObject(MISSILE_CONS, missile); + } +} + +void afxMagicSpell::init_missile_c(afxMagicMissileData* mm_db) +{ + if (missile) + clearNotify(missile); + + // create the missile + missile = new afxMagicMissile(false, true); + missile->setSubstitutionData(this, ranking); + missile->setDataBlock(mm_db); + missile->setChoreographer(this); + if (!missile->registerObject()) + { + Con::errorf("afxMagicSpell: failed to register missile instance."); + delete missile; + missile = NULL; + } + + if (missile) + { + deleteNotify(missile); + registerForCleanup(missile); + } +} + +void afxMagicSpell::launch_missile_c() +{ + if (missile) + { + missile->launch(); + constraint_mgr->setReferenceObject(MISSILE_CONS, missile); + } +} + +bool afxMagicSpell::is_impact_in_water(SceneObject* obj, const Point3F& p) +{ + // AFX_T3D_BROKEN -- water impact detection is disabled. Look at projectile. + return false; +} + +void afxMagicSpell::impactNotify(const Point3F& p, const Point3F& n, SceneObject* obj) +{ + if (isClientObject()) + return; + + ///impact_time_ms = spell_elapsed_ms; + if (impacted_obj) + clearNotify(impacted_obj); + impacted_obj = obj; + impact_pos = p; + impact_norm = n; + + if (impacted_obj != NULL) + { + deleteNotify(impacted_obj); + exec_conds_mask |= IMPACTED_SOMETHING; + if (impacted_obj == target) + exec_conds_mask |= IMPACTED_TARGET; + if (impacted_obj->getTypeMask() & datablock->primary_target_types) + exec_conds_mask |= IMPACTED_PRIMARY; + } + + if (is_impact_in_water(obj, p)) + exec_conds_mask |= IMPACT_IN_WATER; + + postSpellEvent(IMPACT_EVENT); + + if (missile) + clearNotify(missile); + missile = NULL; +} + +void afxMagicSpell::executeScriptEvent(const char* method, afxConstraint* cons, + const MatrixF& xfm, const char* data) +{ + SceneObject* cons_obj = (cons) ? cons->getSceneObject() : NULL; + + char *arg_buf = Con::getArgBuffer(256); + Point3F pos; + xfm.getColumn(3,&pos); + AngAxisF aa(xfm); + dSprintf(arg_buf,256,"%g %g %g %g %g %g %g", + pos.x, pos.y, pos.z, + aa.axis.x, aa.axis.y, aa.axis.z, aa.angle); + + // CALL SCRIPT afxChoreographerData::method(%spell, %caster, %constraint, %transform, %data) + Con::executef(exeblock, method, + getIdString(), + (caster) ? caster->getIdString() : "", + (cons_obj) ? cons_obj->getIdString() : "", + arg_buf, + data); +} + +void afxMagicSpell::inflictDamage(const char * label, const char* flavor, SimObjectId target_id, + F32 amount, U8 n, F32 ad_amount, F32 radius, Point3F pos, F32 impulse) +{ + // Con::printf("INFLICT-DAMAGE label=%s flav=%s id=%d amt=%g n=%d rad=%g pos=(%g %g %g) imp=%g", + // label, flavor, target_id, amount, n, radius, pos.x, pos.y, pos.z, impulse); + + // CALL SCRIPT afxMagicSpellData::onDamage() + // onDamage(%spell, %label, %type, %damaged_obj, %amount, %count, %pos, %ad_amount, + // %radius, %impulse) + datablock->onDamage_callback(this, label, flavor, target_id, amount, n, pos, ad_amount, radius, impulse); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private + +void afxMagicSpell::process_server() +{ + if (spell_state != INACTIVE_STATE) + spell_elapsed += TickSec; + + U8 pending_state = spell_state; + + // check for state changes + switch (spell_state) + { + + case INACTIVE_STATE: + if (marks_mask & MARK_ACTIVATE) + pending_state = CASTING_STATE; + break; + + case CASTING_STATE: + if (datablock->casting_dur > 0.0f && datablock->do_move_interrupts && is_caster_moving()) + { + displayScreenMessage(caster, "SPELL INTERRUPTED."); + postSpellEvent(INTERRUPT_SPELL_EVENT); + } + if (marks_mask & MARK_INTERRUPT_CASTING) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_END_CASTING) + pending_state = DELIVERY_STATE; + else if (marks_mask & MARK_LAUNCH) + pending_state = DELIVERY_STATE; + else if (state_expired()) + pending_state = DELIVERY_STATE; + break; + + case DELIVERY_STATE: + if (marks_mask & MARK_INTERRUPT_DELIVERY) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_END_DELIVERY) + pending_state = LINGER_STATE; + else if (marks_mask & MARK_IMPACT) + pending_state = LINGER_STATE; + else if (state_expired()) + pending_state = LINGER_STATE; + break; + + case LINGER_STATE: + if (marks_mask & MARK_INTERRUPT_LINGER) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_END_LINGER) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_SHUTDOWN) + pending_state = CLEANUP_STATE; + else if (state_expired()) + pending_state = CLEANUP_STATE; + break; + + case CLEANUP_STATE: + if ((marks_mask & MARK_INTERRUPT_CLEANUP) || cleanup_over()) + pending_state = DONE_STATE; + break; + } + + if (spell_state != pending_state) + change_state_s(pending_state); + + if (spell_state == INACTIVE_STATE) + return; + + //--------------------------// + + // sample the constraints + constraint_mgr->sample(TickSec, Platform::getVirtualMilliseconds()); + + for (S32 i = 0; i < NUM_PHRASES; i++) + if (phrases[i]) + phrases[i]->update(TickSec, spell_elapsed); + + if (missile_is_armed) + { + launch_missile_s(); + missile_is_armed = false; + } +} + +void afxMagicSpell::change_state_s(U8 pending_state) +{ + if (spell_state == pending_state) + return; + + // LEAVING THIS STATE + switch (spell_state) + { + case INACTIVE_STATE: + break; + case CASTING_STATE: + leave_casting_state_s(); + break; + case DELIVERY_STATE: + leave_delivery_state_s(); + break; + case LINGER_STATE: + leave_linger_state_s(); + break; + case CLEANUP_STATE: + break; + case DONE_STATE: + break; + } + + spell_state = pending_state; + + // ENTERING THIS STATE + switch (pending_state) + { + case INACTIVE_STATE: + break; + case CASTING_STATE: + enter_casting_state_s(); + break; + case DELIVERY_STATE: + enter_delivery_state_s(); + break; + case LINGER_STATE: + enter_linger_state_s(); + break; + case CLEANUP_STATE: + break; + case DONE_STATE: + enter_done_state_s(); + break; + } +} + +void afxMagicSpell::enter_done_state_s() +{ + postSpellEvent(DEACTIVATE_EVENT); + + if (marks_mask & MARK_INTERRUPTS) + { + Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500); + } + else + { + F32 done_time = spell_elapsed; + + for (S32 i = 0; i < NUM_PHRASES; i++) + { + if (phrases[i]) + { + F32 phrase_done; + if (phrases[i]->willStop() && phrases[i]->isInfinite()) + phrase_done = spell_elapsed + phrases[i]->calcAfterLife(); + else + phrase_done = phrases[i]->calcDoneTime(); + if (phrase_done > done_time) + done_time = phrase_done; + } + } + + F32 time_left = done_time - spell_elapsed; + if (time_left < 0) + time_left = 0; + + Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500); + } + + // CALL SCRIPT afxMagicSpellData::onDeactivate(%spell) + datablock->onDeactivate_callback(this); +} + +void afxMagicSpell::enter_casting_state_s() +{ + // note - onActivate() is called in cast_spell() instead of here to make sure any + // new time-factor settings resolve before they are sent off to the clients. + + // stamp constraint-mgr starting time and reset spell timer + constraint_mgr->setStartTime(Platform::getVirtualMilliseconds()); + spell_elapsed = 0; + + setup_dynamic_constraints(); + + // start casting effects + setup_casting_fx(); + if (phrases[CASTING_PHRASE]) + phrases[CASTING_PHRASE]->start(spell_elapsed, spell_elapsed); + + // initialize missile + if (missile_db) + { + missile_db = missile_db->cloneAndPerformSubstitutions(this, ranking); + init_missile_s(missile_db); + } +} + +void afxMagicSpell::leave_casting_state_s() +{ + if (phrases[CASTING_PHRASE]) + { + if (marks_mask & MARK_INTERRUPT_CASTING) + { + //Con::printf("INTERRUPT CASTING (S)"); + phrases[CASTING_PHRASE]->interrupt(spell_elapsed); + } + else + { + //Con::printf("LEAVING CASTING (S)"); + phrases[CASTING_PHRASE]->stop(spell_elapsed); + } + } + + if (marks_mask & MARK_INTERRUPT_CASTING) + { + // CALL SCRIPT afxMagicSpellData::onInterrupt(%spell, %caster) + datablock->onInterrupt_callback(this, caster); + } +} + +void afxMagicSpell::enter_delivery_state_s() +{ + // CALL SCRIPT afxMagicSpellData::onLaunch(%spell, %caster, %target, %missile) + datablock->onLaunch_callback(this, caster, target, missile); + + if (datablock->launch_on_server_signal) + postSpellEvent(LAUNCH_EVENT); + + missile_is_armed = true; + + // start launch effects + setup_launch_fx(); + if (phrases[LAUNCH_PHRASE]) + phrases[LAUNCH_PHRASE]->start(spell_elapsed, spell_elapsed); //START + + // start delivery effects + setup_delivery_fx(); + if (phrases[DELIVERY_PHRASE]) + phrases[DELIVERY_PHRASE]->start(spell_elapsed, spell_elapsed); //START +} + +void afxMagicSpell::leave_delivery_state_s() +{ + if (phrases[DELIVERY_PHRASE]) + { + if (marks_mask & MARK_INTERRUPT_DELIVERY) + { + //Con::printf("INTERRUPT DELIVERY (S)"); + phrases[DELIVERY_PHRASE]->interrupt(spell_elapsed); + } + else + { + //Con::printf("LEAVING DELIVERY (S)"); + phrases[DELIVERY_PHRASE]->stop(spell_elapsed); + } + } + + if (!missile && !(marks_mask & MARK_IMPACT)) + { + if (target) + { + Point3F p = afxMagicSpell::getShapeImpactPos(target); + Point3F n = Point3F(0,0,1); + impactNotify(p, n, target); + } + else + { + Point3F p = Point3F(0,0,0); + Point3F n = Point3F(0,0,1); + impactNotify(p, n, 0); + } + } +} + +void afxMagicSpell::enter_linger_state_s() +{ + if (impacted_obj) + { + impacted_cons_id = constraint_mgr->setReferenceObject(IMPACTED_OBJECT_CONS, impacted_obj); +#if defined(AFX_CAP_SCOPE_TRACKING) + if (impacted_obj->isScopeable()) + constraint_mgr->addScopeableObject(impacted_obj); +#endif + } + else + constraint_mgr->setReferencePoint(IMPACTED_OBJECT_CONS, impact_pos, impact_norm); + constraint_mgr->setReferencePoint(IMPACT_POINT_CONS, impact_pos, impact_norm); + constraint_mgr->setReferenceObject(MISSILE_CONS, 0); + + // start impact effects + setup_impact_fx(); + if (phrases[IMPACT_PHRASE]) + phrases[IMPACT_PHRASE]->start(spell_elapsed, spell_elapsed); //START + + // start linger effects + setup_linger_fx(); + if (phrases[LINGER_PHRASE]) + phrases[LINGER_PHRASE]->start(spell_elapsed, spell_elapsed); //START + +#if 0 // code temporarily replaced with old callback technique in order to avoid engine bug. + // CALL SCRIPT afxMagicSpellData::onImpact(%spell, %caster, %impactedObj, %impactedPos, %impactedNorm) + datablock->onImpact_callback(this, caster, impacted_obj, impact_pos, impact_norm); +#else + char pos_buf[128]; + dSprintf(pos_buf, sizeof(pos_buf), "%g %g %g", impact_pos.x, impact_pos.y, impact_pos.z); + char norm_buf[128]; + dSprintf(norm_buf, sizeof(norm_buf), "%g %g %g", impact_norm.x, impact_norm.y, impact_norm.z); + Con::executef(exeblock, "onImpact", getIdString(), + (caster) ? caster->getIdString(): "", + (impacted_obj) ? impacted_obj->getIdString(): "", + pos_buf, norm_buf); +#endif +} + +void afxMagicSpell::leave_linger_state_s() +{ + if (phrases[LINGER_PHRASE]) + { + if (marks_mask & MARK_INTERRUPT_LINGER) + { + //Con::printf("INTERRUPT LINGER (S)"); + phrases[LINGER_PHRASE]->interrupt(spell_elapsed); + } + else + { + //Con::printf("LEAVING LINGER (S)"); + phrases[LINGER_PHRASE]->stop(spell_elapsed); + } + } +} + + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private + +void afxMagicSpell::process_client(F32 dt) +{ + spell_elapsed += dt; //SPELL_ELAPSED + + U8 pending_state = spell_state; + + // check for state changes + switch (spell_state) + { + case INACTIVE_STATE: + if (marks_mask & MARK_ACTIVATE) + pending_state = CASTING_STATE; + break; + case CASTING_STATE: + if (marks_mask & MARK_INTERRUPT_CASTING) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_END_CASTING) + pending_state = DELIVERY_STATE; + else if (datablock->launch_on_server_signal) + { + if (marks_mask & MARK_LAUNCH) + pending_state = DELIVERY_STATE; + } + else + { + if (state_expired()) + pending_state = DELIVERY_STATE; + } + break; + case DELIVERY_STATE: + if (marks_mask & MARK_INTERRUPT_DELIVERY) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_END_DELIVERY) + pending_state = LINGER_STATE; + else if (marks_mask & MARK_IMPACT) + pending_state = LINGER_STATE; + else + state_expired(); + break; + case LINGER_STATE: + if (marks_mask & MARK_INTERRUPT_LINGER) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_END_LINGER) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_SHUTDOWN) + pending_state = CLEANUP_STATE; + else if (state_expired()) + pending_state = CLEANUP_STATE; + break; + case CLEANUP_STATE: + if ((marks_mask & MARK_INTERRUPT_CLEANUP) || cleanup_over()) + pending_state = DONE_STATE; + break; + } + + if (spell_state != pending_state) + change_state_c(pending_state); + + if (spell_state == INACTIVE_STATE) + return; + + //--------------------------// + + // update the listener constraint position + if (!listener_cons_id.undefined()) + { + Point3F listener_pos; + listener_pos = SFX->getListener().getTransform().getPosition(); + constraint_mgr->setReferencePoint(listener_cons_id, listener_pos); + } + + // find local camera position + Point3F cam_pos; + SceneObject* current_cam = get_camera(&cam_pos); + + // detect camera changes + if (!camera_cons_id.undefined() && current_cam != camera_cons_obj) + { + constraint_mgr->setReferenceObject(camera_cons_id, current_cam); + camera_cons_obj = current_cam; + } + + // sample the constraints + constraint_mgr->sample(dt, Platform::getVirtualMilliseconds(), (current_cam) ? &cam_pos : 0); + + // update active effects lists + for (S32 i = 0; i < NUM_PHRASES; i++) + if (phrases[i]) + phrases[i]->update(dt, spell_elapsed); + + if (missile_is_armed) + { + launch_missile_c(); + missile_is_armed = false; + } +} + +void afxMagicSpell::change_state_c(U8 pending_state) +{ + if (spell_state == pending_state) + return; + + // LEAVING THIS STATE + switch (spell_state) + { + case INACTIVE_STATE: + break; + case CASTING_STATE: + leave_casting_state_c(); + break; + case DELIVERY_STATE: + leave_delivery_state_c(); + break; + case LINGER_STATE: + leave_linger_state_c(); + break; + case CLEANUP_STATE: + break; + case DONE_STATE: + break; + } + + spell_state = pending_state; + + // ENTERING THIS STATE + switch (pending_state) + { + case INACTIVE_STATE: + break; + case CASTING_STATE: + enter_casting_state_c(spell_elapsed); + break; + case DELIVERY_STATE: + enter_delivery_state_c(spell_elapsed); + break; + case LINGER_STATE: + enter_linger_state_c(spell_elapsed); + break; + case CLEANUP_STATE: + break; + case DONE_STATE: + break; + } +} + +void afxMagicSpell::enter_casting_state_c(F32 starttime) +{ + // stamp constraint-mgr starting time + constraint_mgr->setStartTime(Platform::getVirtualMilliseconds() - (U32)(spell_elapsed*1000)); + //spell_elapsed = 0; //SPELL_ELAPSED + + setup_dynamic_constraints(); + + // start casting effects and castbar + setup_casting_fx(); + if (phrases[CASTING_PHRASE]) + phrases[CASTING_PHRASE]->start(starttime, spell_elapsed); //START + + // initialize missile + if (missile_db) + { + missile_db = missile_db->cloneAndPerformSubstitutions(this, ranking); + init_missile_c(missile_db); + } +} + +void afxMagicSpell::leave_casting_state_c() +{ + if (phrases[CASTING_PHRASE]) + { + if (marks_mask & MARK_INTERRUPT_CASTING) + { + //Con::printf("INTERRUPT CASTING (C)"); + phrases[CASTING_PHRASE]->interrupt(spell_elapsed); + } + else + { + //Con::printf("LEAVING CASTING (C)"); + phrases[CASTING_PHRASE]->stop(spell_elapsed); + } + } +} + +void afxMagicSpell::enter_delivery_state_c(F32 starttime) +{ + missile_is_armed = true; + + setup_launch_fx(); + if (phrases[LAUNCH_PHRASE]) + phrases[LAUNCH_PHRASE]->start(starttime, spell_elapsed); //START + + setup_delivery_fx(); + if (phrases[DELIVERY_PHRASE]) + phrases[DELIVERY_PHRASE]->start(starttime, spell_elapsed); //START +} + +void afxMagicSpell::leave_delivery_state_c() +{ + if (missile) + { + clearNotify(missile); + missile->deleteObject(); + missile = NULL; + } + + if (phrases[DELIVERY_PHRASE]) + { + if (marks_mask & MARK_INTERRUPT_DELIVERY) + { + //Con::printf("INTERRUPT DELIVERY (C)"); + phrases[DELIVERY_PHRASE]->interrupt(spell_elapsed); + } + else + { + //Con::printf("LEAVING DELIVERY (C)"); + phrases[DELIVERY_PHRASE]->stop(spell_elapsed); + } + } +} + +void afxMagicSpell::enter_linger_state_c(F32 starttime) +{ + if (impacted_obj) + impacted_cons_id = constraint_mgr->setReferenceObject(IMPACTED_OBJECT_CONS, impacted_obj); + else if (impacted_scope_id > 0) + impacted_cons_id = constraint_mgr->setReferenceObjectByScopeId(IMPACTED_OBJECT_CONS, impacted_scope_id, impacted_is_shape); + else + constraint_mgr->setReferencePoint(IMPACTED_OBJECT_CONS, impact_pos, impact_norm); + constraint_mgr->setReferencePoint(IMPACT_POINT_CONS, impact_pos, impact_norm); + constraint_mgr->setReferenceObject(MISSILE_CONS, 0); + + setup_impact_fx(); + if (phrases[IMPACT_PHRASE]) + phrases[IMPACT_PHRASE]->start(starttime, spell_elapsed); //START + + setup_linger_fx(); + if (phrases[LINGER_PHRASE]) + { + phrases[LINGER_PHRASE]->start(starttime, spell_elapsed); //START + } +} + +void afxMagicSpell::leave_linger_state_c() +{ + if (phrases[LINGER_PHRASE]) + { + if (marks_mask & MARK_INTERRUPT_LINGER) + { + //Con::printf("INTERRUPT LINGER (C)"); + phrases[LINGER_PHRASE]->interrupt(spell_elapsed); + } + else + { + //Con::printf("LEAVING LINGER (C)"); + phrases[LINGER_PHRASE]->stop(spell_elapsed); + } + } +} + +void afxMagicSpell::sync_client(U16 marks, U8 state, F32 elapsed, F32 spell_elapsed) +{ + //Con::printf("SYNC marks=%d old_state=%s state=%s elapsed=%g spell_elapsed=%g", + // marks, name_from_state(spell_state), name_from_state(state), elapsed, + // spell_elapsed); + + if (spell_state != LATE_STATE) + return; + + marks_mask = marks; + + // don't want to be started on late zoning clients + if (!datablock->exec_on_new_clients) + { + spell_state = DONE_STATE; + } + + // it looks like we're ghosting pretty late and + // should just return to the inactive state. + else if ((marks & (MARK_INTERRUPTS | MARK_DEACTIVATE | MARK_SHUTDOWN)) || + (((marks & MARK_IMPACT) || (marks & MARK_END_DELIVERY)) && (marks & MARK_END_LINGER))) + { + spell_state = DONE_STATE; + } + + // it looks like we should be in the linger state. + else if ((marks & MARK_IMPACT) || + (((marks & MARK_LAUNCH) || (marks & MARK_END_CASTING)) && (marks & MARK_END_DELIVERY))) + { + spell_state = LINGER_STATE; + this->spell_elapsed = spell_elapsed; + enter_linger_state_c(spell_elapsed-elapsed); + } + + // it looks like we should be in the delivery state. + else if ((marks & MARK_LAUNCH) || (marks & MARK_END_CASTING)) + { + spell_state = DELIVERY_STATE; + this->spell_elapsed = spell_elapsed; + enter_delivery_state_c(spell_elapsed-elapsed); + } + + // it looks like we should be in the casting state. + else if (marks & MARK_ACTIVATE) + { + spell_state = CASTING_STATE; //SPELL_STATE + this->spell_elapsed = spell_elapsed; + enter_casting_state_c(spell_elapsed-elapsed); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// public: + +void afxMagicSpell::postSpellEvent(U8 event) +{ + setMaskBits(StateEventMask); + + switch (event) + { + case ACTIVATE_EVENT: + marks_mask |= MARK_ACTIVATE; + break; + case LAUNCH_EVENT: + marks_mask |= MARK_LAUNCH; + setMaskBits(LaunchEventMask); + break; + case IMPACT_EVENT: + marks_mask |= MARK_IMPACT; + setMaskBits(ImpactEventMask); + break; + case SHUTDOWN_EVENT: + marks_mask |= MARK_SHUTDOWN; + break; + case DEACTIVATE_EVENT: + marks_mask |= MARK_DEACTIVATE; + break; + case INTERRUPT_PHASE_EVENT: + if (spell_state == CASTING_STATE) + marks_mask |= MARK_END_CASTING; + else if (spell_state == DELIVERY_STATE) + marks_mask |= MARK_END_DELIVERY; + else if (spell_state == LINGER_STATE) + marks_mask |= MARK_END_LINGER; + break; + case INTERRUPT_SPELL_EVENT: + if (spell_state == CASTING_STATE) + marks_mask |= MARK_INTERRUPT_CASTING; + else if (spell_state == DELIVERY_STATE) + marks_mask |= MARK_INTERRUPT_DELIVERY; + else if (spell_state == LINGER_STATE) + marks_mask |= MARK_INTERRUPT_LINGER; + else if (spell_state == CLEANUP_STATE) + marks_mask |= MARK_INTERRUPT_CLEANUP; + break; + } +} + +void afxMagicSpell::resolveTimeFactors() +{ + for (S32 i = 0; i < NUM_PHRASES; i++) + tfactors[i] *= overall_time_factor; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxMagicSpell::finish_startup() +{ +#if !defined(BROKEN_POINT_IN_WATER) + // test if caster is in water + if (caster) + { + Point3F pos = caster->getPosition(); + if (caster->pointInWater(pos)) + exec_conds_mask |= CASTER_IN_WATER; + } +#endif + + resolveTimeFactors(); + + init_constraints(); + init_scoping(); + + postSpellEvent(afxMagicSpell::ACTIVATE_EVENT); +} + +// static +afxMagicSpell* +afxMagicSpell::cast_spell(afxMagicSpellData* datablock, ShapeBase* caster, SceneObject* target, SimObject* extra) +{ + AssertFatal(datablock != NULL, "Datablock is missing."); + AssertFatal(caster != NULL, "Caster is missing."); + + afxMagicSpellData* exeblock = datablock; + + SimObject* param_holder = new SimObject(); + if (!param_holder->registerObject()) + { + Con::errorf("afxMagicSpell: failed to register parameter object."); + delete param_holder; + return 0; + } + + param_holder->assignDynamicFieldsFrom(datablock, arcaneFX::sParameterFieldPrefix); + if (extra) + { + // copy dynamic fields from the extra object to the param holder + param_holder->assignDynamicFieldsFrom(extra, arcaneFX::sParameterFieldPrefix); + } + + if (datablock->isMethod("onPreactivate")) + { + // CALL SCRIPT afxMagicSpellData::onPreactivate(%params, %caster, %target, %extra) + bool result = datablock->onPreactivate_callback(param_holder, caster, target, extra); + if (!result) + { + #if defined(TORQUE_DEBUG) + Con::warnf("afxMagicSpell: onPreactivate() returned false, spell aborted."); + #endif + Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime()); + return 0; + } + } + + // make a temp datablock clone if there are substitutions + if (datablock->getSubstitutionCount() > 0) + { + datablock = new afxMagicSpellData(*exeblock, true); + exeblock->performSubstitutions(datablock, param_holder); + } + + // create a new spell instance + afxMagicSpell* spell = new afxMagicSpell(caster, target); + spell->setDataBlock(datablock); + spell->exeblock = exeblock; + spell->setExtra(extra); + + // copy dynamic fields from the param holder to the spell + spell->assignDynamicFieldsFrom(param_holder, arcaneFX::sParameterFieldPrefix); + Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime()); + + // register + if (!spell->registerObject()) + { + Con::errorf("afxMagicSpell: failed to register spell instance."); + Sim::postEvent(spell, new ObjectDeleteEvent, Sim::getCurrentTime()); + return 0; + } + registerForCleanup(spell); + + spell->activate(); + + return spell; +} + +IMPLEMENT_GLOBAL_CALLBACK( DisplayScreenMessage, void, (GameConnection* client, const char* message), (client, message), + "Called to display a screen message.\n" + "@ingroup AFX\n" ); + +void afxMagicSpell::displayScreenMessage(ShapeBase* caster, const char* msg) +{ + if (!caster) + return; + + GameConnection* client = caster->getControllingClient(); + if (client) + DisplayScreenMessage_callback(client, msg); +} + +Point3F afxMagicSpell::getShapeImpactPos(SceneObject* obj) +{ + Point3F pos = obj->getRenderPosition(); + if (obj->getTypeMask() & CorpseObjectType) + pos.z += 0.5f; + else + pos.z += (obj->getObjBox().len_z()/2); + return pos; +} + +void afxMagicSpell::restoreObject(SceneObject* obj) +{ + if (obj->getScopeId() == caster_scope_id && dynamic_cast(obj) != NULL) + { + caster_scope_id = 0; + caster = (ShapeBase*)obj; + caster_field = caster; + deleteNotify(caster); + processAfter(caster); + } + + if (obj->getScopeId() == target_scope_id) + { + target_scope_id = 0; + target = obj; + target_field = target; + deleteNotify(target); + } + + if (obj->getScopeId() == impacted_scope_id) + { + impacted_scope_id = 0; + impacted_obj = obj; + deleteNotify(impacted_obj); + } +} + +bool afxMagicSpell::activationCallInit(bool postponed) +{ + if (postponed && (!started_with_newop || !postpone_activation)) + { + Con::errorf("afxMagicSpell::activate() -- activate() is only required when creating a spell with the \"new\" operator " + "and the postponeActivation field is set to \"true\"."); + return false; + } + + if (!caster_field) + { + Con::errorf("afxMagicSpell::activate() -- no spellcaster specified."); + return false; + } + + caster = dynamic_cast(caster_field); + if (!caster) + { + Con::errorf("afxMagicSpell::activate() -- spellcaster is not a ShapeBase derived object."); + return false; + } + + if (target_field) + { + target = dynamic_cast(target_field); + if (!target) + Con::warnf("afxMagicSpell::activate() -- target is not a SceneObject derived object."); + } + + return true; +} + +void afxMagicSpell::activate() +{ + // separating the final part of startup allows the calling script + // to make certain types of calls on the returned spell that need + // to happen prior to object registration. + Sim::postEvent(this, new SpellFinishStartupEvent, Sim::getCurrentTime()); + + caster_field = caster; + target_field = target; + + // CALL SCRIPT afxMagicSpellData::onActivate(%spell, %caster, %target) + datablock->onActivate_callback(this, caster, target); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// console methods/functions + +DefineEngineMethod(afxMagicSpell, getCaster, S32, (),, + "Returns ID of the spell's caster object.\n\n" + "@ingroup AFX") +{ + ShapeBase* caster = object->getCaster(); + return (caster) ? caster->getId() : -1; +} + +DefineEngineMethod(afxMagicSpell, getTarget, S32, (),, + "Returns ID of the spell's target object.\n\n" + "@ingroup AFX") +{ + SceneObject* target = object->getTarget(); + return (target) ? target->getId() : -1; +} + +DefineEngineMethod(afxMagicSpell, getMissile, S32, (),, + "Returns ID of the spell's magic-missile object.\n\n" + "@ingroup AFX") +{ + afxMagicMissile* missile = object->getMissile(); + return (missile) ? missile->getId() : -1; +} + +DefineEngineMethod(afxMagicSpell, getImpactedObject, S32, (),, + "Returns ID of impacted-object for the spell.\n\n" + "@ingroup AFX") +{ + SceneObject* imp_obj = object->getImpactedObject(); + return (imp_obj) ? imp_obj->getId() : -1; +} + +ConsoleMethod(afxMagicSpell, setTimeFactor, void, 3, 4, "(F32 factor) or (string phase, F32 factor)" + "Sets the time-factor for the spell, either overall or for a specific phrase.\n\n" + "@ingroup AFX") +{ + if (argc == 3) + object->setTimeFactor(dAtof(argv[2])); + else + { + if (dStricmp(argv[2], "overall") == 0) + object->setTimeFactor(dAtof(argv[3])); + else if (dStricmp(argv[2], "casting") == 0) + object->setTimeFactor(afxMagicSpell::CASTING_PHRASE, dAtof(argv[3])); + else if (dStricmp(argv[2], "launch") == 0) + object->setTimeFactor(afxMagicSpell::LAUNCH_PHRASE, dAtof(argv[3])); + else if (dStricmp(argv[2], "delivery") == 0) + object->setTimeFactor(afxMagicSpell::DELIVERY_PHRASE, dAtof(argv[3])); + else if (dStricmp(argv[2], "impact") == 0) + object->setTimeFactor(afxMagicSpell::IMPACT_PHRASE, dAtof(argv[3])); + else if (dStricmp(argv[2], "linger") == 0) + object->setTimeFactor(afxMagicSpell::LINGER_PHRASE, dAtof(argv[3])); + else + Con::errorf("afxMagicSpell::setTimeFactor() -- unknown spell phrase [%s].", argv[2].getStringValue()); + } +} + +DefineEngineMethod(afxMagicSpell, interruptStage, void, (),, + "Interrupts the current stage of a magic spell causing it to move onto the next one.\n\n" + "@ingroup AFX") +{ + object->postSpellEvent(afxMagicSpell::INTERRUPT_PHASE_EVENT); +} + +DefineEngineMethod(afxMagicSpell, interrupt, void, (),, + "Interrupts and deletes a running magic spell.\n\n" + "@ingroup AFX") +{ + object->postSpellEvent(afxMagicSpell::INTERRUPT_SPELL_EVENT); +} + +DefineEngineMethod(afxMagicSpell, activate, void, (),, + "Activates a magic spell that was started with postponeActivation=true.\n\n" + "@ingroup AFX") +{ + if (object->activationCallInit(true)) + object->activate(); +} + +DefineEngineFunction(castSpell, S32, (afxMagicSpellData* datablock, ShapeBase* caster, SceneObject* target, SimObject* extra), + (nullAsType(), nullAsType(), nullAsType(), nullAsType()), + "Instantiates the magic spell defined by datablock and cast by caster.\n\n" + "@ingroup AFX") +{ + if (!datablock) + { + Con::errorf("castSpell() -- missing valid spell datablock."); + return 0; + } + + if (!caster) + { + Con::errorf("castSpell() -- missing valid spellcaster."); + return 0; + } + + // target is optional (depends on spell) + + // note -- we must examine all arguments prior to calling cast_spell because + // it calls Con::executef() which will overwrite the argument array. + afxMagicSpell* spell = afxMagicSpell::cast_spell(datablock, caster, target, extra); + + return (spell) ? spell->getId() : 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/afxMagicSpell.h b/Engine/source/afx/afxMagicSpell.h new file mode 100644 index 000000000..1f760e48b --- /dev/null +++ b/Engine/source/afx/afxMagicSpell.h @@ -0,0 +1,390 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_MAGIC_SPELL_H_ +#define _AFX_MAGIC_SPELL_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "core/util/tVector.h" +#include "console/typeValidators.h" + +#include "afxChoreographer.h" +#include "afxEffectDefs.h" +#include "afxEffectWrapper.h" +#include "afxMagicMissile.h" + +class afxChoreographerData; +class afxMagicMissileData; +class afxEffectWrapperData; +class SceneObject; +class afxMagicSpell; + +class afxMagicSpellDefs +{ +public: + enum + { + CASTING_PHRASE, + LAUNCH_PHRASE, + DELIVERY_PHRASE, + IMPACT_PHRASE, + LINGER_PHRASE, + NUM_PHRASES + }; +}; + +class afxMagicSpellData : public afxChoreographerData, public afxMagicSpellDefs +{ + typedef afxChoreographerData Parent; + + class ewValidator : public TypeValidator + { + U32 id; + public: + ewValidator(U32 id) { this->id = id; } + void validateType(SimObject *object, void *typePtr); + }; + + bool do_id_convert; + +public: + F32 casting_dur; + F32 delivery_dur; + F32 linger_dur; + // + S32 n_casting_loops; + S32 n_delivery_loops; + S32 n_linger_loops; + // + F32 extra_casting_time; + F32 extra_delivery_time; + F32 extra_linger_time; + // + bool do_move_interrupts; + F32 move_interrupt_speed; + // + afxMagicMissileData* missile_db; + bool launch_on_server_signal; + U32 primary_target_types; + // + afxEffectWrapperData* dummy_fx_entry; + + // various effects lists + afxEffectList casting_fx_list; + afxEffectList launch_fx_list; + afxEffectList delivery_fx_list; + afxEffectList impact_fx_list; + afxEffectList linger_fx_list; + + void pack_fx(BitStream* stream, const afxEffectList& fx, bool packed); + void unpack_fx(BitStream* stream, afxEffectList& fx); + +public: + /*C*/ afxMagicSpellData(); + /*C*/ afxMagicSpellData(const afxMagicSpellData&, bool = false); + + virtual void reloadReset(); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + virtual bool writeField(StringTableEntry fieldname, const char* value); + + bool preload(bool server, String &errorStr); + + void gatherConstraintDefs(Vector&); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxMagicSpellData); + DECLARE_CATEGORY("AFX"); + + /// @name Callbacks + /// @{ + DECLARE_CALLBACK( void, onDamage, (afxMagicSpell* spell, const char* label, const char* flaver, U32 target_id, F32 amount, U8 n, Point3F pos, F32 ad_amount, F32 radius, F32 impulse) ); + DECLARE_CALLBACK( void, onDeactivate, (afxMagicSpell* spell) ); + DECLARE_CALLBACK( void, onInterrupt, (afxMagicSpell* spell, ShapeBase* caster) ); + DECLARE_CALLBACK( void, onLaunch, (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target, afxMagicMissile* missile) ); + DECLARE_CALLBACK( void, onImpact, (afxMagicSpell* spell, ShapeBase* caster, SceneObject* impacted, Point3F pos, Point3F normal) ); + DECLARE_CALLBACK( bool, onPreactivate, (SimObject* param_holder, ShapeBase* caster, SceneObject* target, SimObject* extra) ); + DECLARE_CALLBACK( void, onActivate, (afxMagicSpell* spell, ShapeBase* caster, SceneObject* target) ); + /// @} +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMagicSpell + +class ShapeBase; +class GameConnection; +class afxEffectVector; +class afxConstraint; +class afxConstraintMgr; +class afxMagicMissile; +class afxChoreographer; +class afxPhrase; + +class afxMagicSpell : public afxChoreographer, public afxMagicSpellDefs +{ + typedef afxChoreographer Parent; + friend class afxMagicMissile; + + enum MaskBits + { + MagicMissileMask = Parent::NextFreeMask << 0, + StateEventMask = Parent::NextFreeMask << 1, + LaunchEventMask = Parent::NextFreeMask << 2, + ImpactEventMask = Parent::NextFreeMask << 3, + SyncEventMask = Parent::NextFreeMask << 4, + RemapConstraintMask = Parent::NextFreeMask << 5, // CONSTRAINT REMAPPING + NextFreeMask = Parent::NextFreeMask << 6 + }; + +public: + enum + { + NULL_EVENT, + ACTIVATE_EVENT, + LAUNCH_EVENT, + IMPACT_EVENT, + SHUTDOWN_EVENT, + DEACTIVATE_EVENT, + INTERRUPT_PHASE_EVENT, + INTERRUPT_SPELL_EVENT + }; + + enum + { + INACTIVE_STATE, + CASTING_STATE, + DELIVERY_STATE, + LINGER_STATE, + CLEANUP_STATE, + DONE_STATE, + LATE_STATE + }; + + enum { + MARK_ACTIVATE = BIT(0), + MARK_LAUNCH = BIT(1), + MARK_IMPACT = BIT(2), + MARK_SHUTDOWN = BIT(3), + MARK_DEACTIVATE = BIT(4), + MARK_END_CASTING = BIT(5), + MARK_END_DELIVERY = BIT(6), + MARK_END_LINGER = BIT(7), + MARK_INTERRUPT_CASTING = BIT(8), + MARK_INTERRUPT_DELIVERY = BIT(9), + MARK_INTERRUPT_LINGER = BIT(10), + MARK_INTERRUPT_CLEANUP = BIT(11), + // + MARK_ENDINGS = MARK_END_CASTING | MARK_END_DELIVERY | MARK_END_LINGER, + MARK_INTERRUPTS = MARK_INTERRUPT_CASTING | MARK_INTERRUPT_DELIVERY | MARK_INTERRUPT_LINGER | MARK_INTERRUPT_CLEANUP + }; + + class ObjectDeleteEvent : public SimEvent + { + public: + void process(SimObject *obj) { if (obj) obj->deleteObject(); } + }; + +private: + static StringTableEntry CASTER_CONS; + static StringTableEntry TARGET_CONS; + static StringTableEntry MISSILE_CONS; + static StringTableEntry CAMERA_CONS; + static StringTableEntry LISTENER_CONS; + static StringTableEntry IMPACT_POINT_CONS; + static StringTableEntry IMPACTED_OBJECT_CONS; + +private: + afxMagicSpellData* datablock; + SimObject* exeblock; + afxMagicMissileData* missile_db; + + ShapeBase* caster; + SceneObject* target; + SimObject* caster_field; + SimObject* target_field; + + U16 caster_scope_id; + U16 target_scope_id; + bool target_is_shape; + + bool constraints_initialized; + bool scoping_initialized; + + U8 spell_state; + F32 spell_elapsed; + + afxConstraintID listener_cons_id; + afxConstraintID caster_cons_id; + afxConstraintID target_cons_id; + afxConstraintID impacted_cons_id; + afxConstraintID camera_cons_id; + SceneObject* camera_cons_obj; + + afxPhrase* phrases[NUM_PHRASES]; + F32 tfactors[NUM_PHRASES]; + + bool notify_castbar; + F32 overall_time_factor; + + U16 marks_mask; + +private: + void init(); + bool state_expired(); + F32 state_elapsed(); + void init_constraints(); + void init_scoping(); + void setup_casting_fx(); + void setup_launch_fx(); + void setup_delivery_fx(); + void setup_impact_fx(); + void setup_linger_fx(); + bool cleanup_over(); + bool is_caster_moving(); + bool is_caster_client(ShapeBase* caster, GameConnection* conn); + bool is_impact_in_water(SceneObject* obj, const Point3F& p); + +protected: + virtual bool remap_builtin_constraint(SceneObject*, const char* cons_name); // CONSTRAINT REMAPPING + virtual void pack_constraint_info(NetConnection* conn, BitStream* stream); + virtual void unpack_constraint_info(NetConnection* conn, BitStream* stream); + +private: + afxMagicMissile* missile; + bool missile_is_armed; + SceneObject* impacted_obj; + Point3F impact_pos; + Point3F impact_norm; + U16 impacted_scope_id; + bool impacted_is_shape; + + void init_missile_s(afxMagicMissileData* mm); + void launch_missile_s(); + + void init_missile_c(afxMagicMissileData* mm); + void launch_missile_c(); + +public: + virtual void impactNotify(const Point3F& p, const Point3F& n, SceneObject*); + virtual void executeScriptEvent(const char* method, afxConstraint*, + const MatrixF& pos, const char* data); + virtual void inflictDamage(const char * label, const char* flavor, SimObjectId target, + F32 amt, U8 count, F32 ad_amt, F32 rad, Point3F pos, F32 imp); + +public: + /*C*/ afxMagicSpell(); + /*C*/ afxMagicSpell(ShapeBase* caster, SceneObject* target); + /*D*/ ~afxMagicSpell(); + + // STANDARD OVERLOADED METHODS // + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + virtual void processTick(const Move*); + virtual void advanceTime(F32 dt); + virtual bool onAdd(); + virtual void onRemove(); + virtual void onDeleteNotify(SimObject*); + virtual U32 packUpdate(NetConnection*, U32, BitStream*); + virtual void unpackUpdate(NetConnection*, BitStream*); + + virtual void sync_with_clients(); + void finish_startup(); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxMagicSpell); + DECLARE_CATEGORY("AFX"); + +private: + void process_server(); + // + void change_state_s(U8 pending_state); + // + void enter_casting_state_s(); + void leave_casting_state_s(); + void enter_delivery_state_s(); + void leave_delivery_state_s(); + void enter_linger_state_s(); + void leave_linger_state_s(); + void enter_done_state_s(); + +private: + void process_client(F32 dt); + // + void change_state_c(U8 pending_state); + // + void enter_casting_state_c(F32 starttime); + void leave_casting_state_c(); + void enter_delivery_state_c(F32 starttime); + void leave_delivery_state_c(); + void enter_linger_state_c(F32 starttime); + void leave_linger_state_c(); + // + void sync_client(U16 marks, U8 state, F32 state_elapsed, F32 spell_elapsed); + +public: + void postSpellEvent(U8 event); + void resolveTimeFactors(); + + void setTimeFactor(F32 f) { overall_time_factor = (f > 0) ? f : 1.0f; } + F32 getTimeFactor() { return overall_time_factor; } + void setTimeFactor(U8 phase, F32 f) { tfactors[phase] = (f > 0) ? f : 1.0f; } + F32 getTimeFactor(U8 phase) { return tfactors[phase]; } + + ShapeBase* getCaster() const { return caster; } + SceneObject* getTarget() const { return target; } + afxMagicMissile* getMissile() const { return missile; } + SceneObject* getImpactedObject() const { return impacted_obj; } + + virtual void restoreObject(SceneObject*); + + bool activationCallInit(bool postponed=false); + void activate(); + +public: + static afxMagicSpell* cast_spell(afxMagicSpellData*, ShapeBase* caster, SceneObject* target, SimObject* extra); + + static void displayScreenMessage(ShapeBase* caster, const char* msg); + static Point3F getShapeImpactPos(SceneObject*); +}; + +inline bool afxMagicSpell::is_caster_moving() +{ + return (caster) ? (caster->getVelocity().len() > datablock->move_interrupt_speed) : false; +} + +inline bool afxMagicSpell::is_caster_client(ShapeBase* caster, GameConnection* conn) +{ + return (caster) ? (caster->getControllingClient() == conn) : false; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_MAGIC_SPELL_H_ diff --git a/Engine/source/afx/afxPhrase.cpp b/Engine/source/afx/afxPhrase.cpp new file mode 100644 index 000000000..3e0a2bee0 --- /dev/null +++ b/Engine/source/afx/afxPhrase.cpp @@ -0,0 +1,196 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/afxEffectVector.h" +#include "afx/afxPhrase.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPhrase + +void +afxPhrase::init_fx(S32 group_index) +{ + fx->ev_init(init_chor, *init_fx_list, on_server, will_stop, init_time_factor, init_dur, group_index); +} + +//~~~~~~~~~~~~~~~~~~~~// + +afxPhrase::afxPhrase(bool on_server, bool will_stop) +{ + this->on_server = on_server; + this->will_stop = will_stop; + + init_fx_list = NULL; + init_dur = 0.0f; + init_chor = NULL; + init_time_factor = 1.0f; + + fx = new afxEffectVector; + fx2 = NULL; + starttime = 0; + dur = 0; + + n_loops = 1; + loop_cnt = 1; + + extra_time = 0.0f; + extra_stoptime = 0.0f; +} + +afxPhrase::~afxPhrase() +{ + delete fx; + delete fx2; +}; + +void +afxPhrase::init(afxEffectList& fx_list, F32 dur, afxChoreographer* chor, F32 time_factor, + S32 n_loops, S32 group_index, F32 extra_time) +{ + init_fx_list = &fx_list; + init_dur = dur; + init_chor = chor; + init_time_factor = time_factor; + + this->n_loops = n_loops; + this->extra_time = extra_time; + this->dur = (init_dur < 0) ? init_dur : init_dur*init_time_factor; + + init_fx(group_index); +} + +void +afxPhrase::start(F32 startstamp, F32 timestamp) +{ + starttime = startstamp; + + F32 loopstart = timestamp - startstamp; + + if (dur > 0 && loopstart > dur) + { + loop_cnt += (S32) (loopstart/dur); + loopstart = mFmod(loopstart, dur); + } + + if (!fx->empty()) + fx->start(loopstart); +} + +void +afxPhrase::update(F32 dt, F32 timestamp) +{ + if (fx->isActive()) + fx->update(dt); + + if (fx2 && fx2->isActive()) + fx2->update(dt); + + if (extra_stoptime > 0 && timestamp > extra_stoptime) + { + stop(timestamp); + } +} + +void +afxPhrase::stop(F32 timestamp) +{ + if (extra_time > 0 && !(extra_stoptime > 0)) + { + extra_stoptime = timestamp + extra_time; + return; + } + + if (fx->isActive()) + fx->stop(); + + if (fx2 && fx2->isActive()) + fx2->stop(); +} + +bool +afxPhrase::expired(F32 timestamp) +{ + if (dur < 0) + return false; + + return ((timestamp - starttime) > loop_cnt*dur); +} + +F32 +afxPhrase::elapsed(F32 timestamp) +{ + return (timestamp - starttime); +} + +bool +afxPhrase::recycle(F32 timestamp) +{ + if (n_loops < 0 || loop_cnt < n_loops) + { + if (fx2) + delete fx2; + + fx2 = fx; + + fx = new afxEffectVector; + init_fx(); + + if (fx2 && !fx2->empty()) + fx2->stop(); + + if (!fx->empty()) + fx->start(0.0F); + + loop_cnt++; + return true; + } + + return false; +} + +void +afxPhrase::interrupt(F32 timestamp) +{ + if (fx->isActive()) + fx->interrupt(); + + if (fx2 && fx2->isActive()) + fx2->interrupt(); +} + +F32 afxPhrase::calcDoneTime() +{ + return starttime + fx->getTotalDur(); +} + +F32 afxPhrase::calcAfterLife() +{ + return fx->getAfterLife(); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/afxPhrase.h b/Engine/source/afx/afxPhrase.h new file mode 100644 index 000000000..2525c2656 --- /dev/null +++ b/Engine/source/afx/afxPhrase.h @@ -0,0 +1,87 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_PHRASE_H_ +#define _AFX_PHRASE_H_ + +#include "afxEffectVector.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPhrase + +class afxChoreographer; +class afxConstraintMgr; +class afxEffectVector; + +class afxPhrase +{ +protected: + afxEffectList* init_fx_list; + F32 init_dur; + afxChoreographer* init_chor; + F32 init_time_factor; + F32 extra_time; + + afxEffectVector* fx; + afxEffectVector* fx2; + + bool on_server; + bool will_stop; + + F32 starttime; + F32 dur; + S32 n_loops; + S32 loop_cnt; + F32 extra_stoptime; + + void init_fx(S32 group_index=0); + +public: + /*C*/ afxPhrase(bool on_server, bool will_stop); + virtual ~afxPhrase(); + + virtual void init(afxEffectList&, F32 dur, afxChoreographer*, F32 time_factor, + S32 n_loops, S32 group_index=0, F32 extra_time=0.0f); + + virtual void start(F32 startstamp, F32 timestamp); + virtual void update(F32 dt, F32 timestamp); + virtual void stop(F32 timestamp); + virtual void interrupt(F32 timestamp); + virtual bool expired(F32 timestamp); + virtual bool recycle(F32 timestamp); + virtual F32 elapsed(F32 timestamp); + + bool isEmpty() { return fx->empty(); } + bool isInfinite() { return (init_dur < 0); } + F32 calcDoneTime(); + F32 calcAfterLife(); + bool willStop() { return will_stop; } + bool onServer() { return on_server; } + S32 count() { return fx->count(); } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_PHRASE_H_ diff --git a/Engine/source/afx/afxRenderHighlightMgr.cpp b/Engine/source/afx/afxRenderHighlightMgr.cpp new file mode 100644 index 000000000..28e875eb9 --- /dev/null +++ b/Engine/source/afx/afxRenderHighlightMgr.cpp @@ -0,0 +1,176 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// The afxRenderHighlightMgr class is adapted from the resource, +// "Silhoute selection via postFX for Torque3D" posted by Konrad Kiss. +// http://www.garagegames.com/community/resources/view/17821 +// Supporting code mods in other areas of the engine are marked as +// "(selection-highlight)". +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "platform/platform.h" +#include "afxRenderHighlightMgr.h" + +#include "scene/sceneManager.h" +#include "scene/sceneRenderState.h" +#include "materials/sceneData.h" +#include "materials/matInstance.h" +//#include "materials/materialFeatureTypes.h" +#include "materials/processedMaterial.h" +#include "postFx/postEffect.h" +#include "gfx/gfxTransformSaver.h" +#include "gfx/gfxDebugEvent.h" +#include "math/util/matrixSet.h" + +IMPLEMENT_CONOBJECT( afxRenderHighlightMgr ); + +afxRenderHighlightMgr::afxRenderHighlightMgr() + : RenderTexTargetBinManager( RenderPassManager::RIT_Mesh, + 1.0f, + 1.0f, + GFXFormatR8G8B8A8, + Point2I( 512, 512 ) ) +{ + mNamedTarget.registerWithName( "highlight" ); + mTargetSizeType = WindowSize; +} + +afxRenderHighlightMgr::~afxRenderHighlightMgr() +{ +} + +PostEffect* afxRenderHighlightMgr::getSelectionEffect() +{ + if ( !mSelectionEffect ) + mSelectionEffect = dynamic_cast( Sim::findObject( "afxHighlightPostFX" ) ); + + return mSelectionEffect; +} + +bool afxRenderHighlightMgr::isSelectionEnabled() +{ + return getSelectionEffect() && getSelectionEffect()->isEnabled(); +} + +void afxRenderHighlightMgr::addElement( RenderInst *inst ) +{ + // Skip out if we don't have the selection post + // effect enabled at this time. + if ( !isSelectionEnabled() ) + return; + + // Skip it if we don't have a selection material. + BaseMatInstance *matInst = getMaterial( inst ); + if ( !matInst || !matInst->needsSelectionHighlighting() ) + return; + + internalAddElement(inst); +} + +void afxRenderHighlightMgr::render( SceneRenderState *state ) +{ + PROFILE_SCOPE( RenderSelectionMgr_Render ); + + if ( !isSelectionEnabled() ) + return; + + const U32 binSize = mElementList.size(); + + // If this is a non-diffuse pass or we have no objects to + // render then tell the effect to skip rendering. + if ( !state->isDiffusePass() || binSize == 0 ) + { + getSelectionEffect()->setSkip( true ); + return; + } + + GFXDEBUGEVENT_SCOPE( RenderSelectionMgr_Render, ColorI::GREEN ); + + GFXTransformSaver saver; + + // Tell the superclass we're about to render, preserve contents + const bool isRenderingToTarget = _onPreRender( state, true ); + + // Clear all the buffers to black. + //GFX->clear( GFXClearTarget, ColorI::BLACK, 1.0f, 0); + GFX->clear( GFXClearTarget, ColorI::ZERO, 1.0f, 0); + + // Restore transforms + MatrixSet &matrixSet = getRenderPass()->getMatrixSet(); + matrixSet.restoreSceneViewProjection(); + + // init loop data + SceneData sgData; + sgData.init( state, SceneData::HighlightBin ); + + for( U32 j=0; j(mElementList[j].inst); + + setupSGData( ri, sgData ); + + BaseMatInstance *mat = ri->matInst; + + U32 matListEnd = j; + + while( mat && mat->setupPass( state, sgData ) ) + { + U32 a; + for( a=j; a(mElementList[a].inst); + + if ( newPassNeeded( ri, passRI ) ) + break; + + matrixSet.setWorld(*passRI->objectToWorld); + matrixSet.setView(*passRI->worldToCamera); + matrixSet.setProjection(*passRI->projection); + mat->setTransforms(matrixSet, state); + + mat->setSceneInfo(state, sgData); + mat->setBuffers(passRI->vertBuff, passRI->primBuff); + + if ( passRI->prim ) + GFX->drawPrimitive( *passRI->prim ); + else + GFX->drawPrimitive( passRI->primBuffIndex ); + } + matListEnd = a; + setupSGData( ri, sgData ); + } + + // force increment if none happened, otherwise go to end of batch + j = ( j == matListEnd ) ? j+1 : matListEnd; + } + + // Finish up. + if ( isRenderingToTarget ) + _onPostRender(); + + // Make sure the effect is gonna render. + getSelectionEffect()->setSkip( false ); +} \ No newline at end of file diff --git a/Engine/source/afx/afxRenderHighlightMgr.h b/Engine/source/afx/afxRenderHighlightMgr.h new file mode 100644 index 000000000..406997f90 --- /dev/null +++ b/Engine/source/afx/afxRenderHighlightMgr.h @@ -0,0 +1,76 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// The afxRenderHighlightMgr class is adapted from the resource, +// "Silhoute selection via postFX for Torque3D" posted by Konrad Kiss. +// http://www.garagegames.com/community/resources/view/17821 +// Supporting code mods in other areas of the engine are marked as +// "(selection-highlight)". +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _afxRENDERHIGHLIGHTMGR_H_ +#define _afxRENDERHIGHLIGHTMGR_H_ + +#ifndef _TEXTARGETBIN_MGR_H_ +#include "renderInstance/renderTexTargetBinManager.h" +#endif + + +class PostEffect; + + +/// +class afxRenderHighlightMgr : public RenderTexTargetBinManager +{ + typedef RenderTexTargetBinManager Parent; + +public: + + afxRenderHighlightMgr(); + virtual ~afxRenderHighlightMgr(); + + /// Returns the selection post effect. + PostEffect* getSelectionEffect(); + + /// Returns true if the highlight post effect is + /// enabled and the selection buffer should be updated. + bool isSelectionEnabled(); + + // RenderBinManager + virtual void addElement( RenderInst *inst ); + virtual void render( SceneRenderState *state ); + + // ConsoleObject + DECLARE_CONOBJECT( afxRenderHighlightMgr ); + +protected: + + SimObjectPtr mSelectionEffect; + +}; + + +#endif // _afxRENDERHIGHLIGHTMGR_H_ diff --git a/Engine/source/afx/afxResidueMgr.cpp b/Engine/source/afx/afxResidueMgr.cpp new file mode 100644 index 000000000..2c36a9559 --- /dev/null +++ b/Engine/source/afx/afxResidueMgr.cpp @@ -0,0 +1,469 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "ts/tsShapeInstance.h" + +#include "afx/ce/afxZodiacMgr.h" +#include "afx/ce/afxModel.h" +#include "afx/afxResidueMgr.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +int QSORT_CALLBACK afxResidueMgr::ResidueList::compare_residue(const void* p1, const void* p2) +{ + const afxResidueMgr::Residue** pd1 = (const afxResidueMgr::Residue**)p1; + const afxResidueMgr::Residue** pd2 = (const afxResidueMgr::Residue**)p2; + + return int(((char*)(*pd1)->data.simobject) - ((char*)(*pd2)->data.simobject)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +inline void afxResidueMgr::ResidueList::swap_array_ptrs() +{ + Vector* tmp = m_array; + m_array = m_scratch_array; + m_scratch_array = tmp; +} + +void afxResidueMgr::ResidueList::free_residue(Residue* residue) +{ + if (the_mgr == NULL) + return; + + if (the_mgr->requires_delete_tracking(residue)) + the_mgr->disable_delete_tracking(residue); + the_mgr->free_residue(residue); +} + +afxResidueMgr::ResidueList::ResidueList() +{ + VECTOR_SET_ASSOCIATION(m_array_a); + VECTOR_SET_ASSOCIATION(m_array_b); + + m_array = &m_array_a; + m_scratch_array = &m_array_b ; + + m_dirty = false; + m_pending = -1; +} + +afxResidueMgr::ResidueList::~ResidueList() +{ + clear(); +} + +void afxResidueMgr::ResidueList::clear() +{ + if (the_mgr) + { + for (S32 i = 0; i < m_array->size(); i++) + { + Residue* r = (*m_array)[i]; + the_mgr->free_residue(r); + } + } + + m_array_a.clear(); + m_array_b.clear(); +} + +void afxResidueMgr::ResidueList::sort() +{ + dQsort(m_array->address(), m_array->size(), sizeof(Residue*), compare_residue); + m_dirty = false; +} + +void afxResidueMgr::ResidueList::fadeAndCull(U32 now) +{ + for (S32 i = 0; i < m_array->size(); i++) + { + Residue* r = (*m_array)[i]; + + // done + if (now >= r->stop_time) + { + free_residue(r); + } + // fading + else if (now >= r->fade_time) + { + r->fade = 1.0f - ((F32)(now - r->fade_time))/((F32)(r->stop_time - r->fade_time)); + m_scratch_array->push_back(r); + } + // opaque + else + { + r->fade = 1.0f; + m_scratch_array->push_back(r); + } + } + + m_array->clear(); + swap_array_ptrs(); +} + +// removes all residue with datablock matching obj +void afxResidueMgr::ResidueList::stripMatchingObjects(SimObject* db, bool del_notify) +{ + if (del_notify) + { + for (S32 i = 0; i < m_array->size(); i++) + { + Residue* r = (*m_array)[i]; + if (db == r->data.simobject && the_mgr != NULL) + the_mgr->free_residue(r); + else + m_scratch_array->push_back(r); + } + } + else + { + for (S32 i = 0; i < m_array->size(); i++) + { + Residue* r = (*m_array)[i]; + if (db == r->data.simobject) + free_residue(r); + else + m_scratch_array->push_back(r); + } + } + + m_array->clear(); + swap_array_ptrs(); +} + +void afxResidueMgr::ResidueList::add(Residue* residue) +{ + m_array->push_back(residue); + m_dirty = true; +} + +void afxResidueMgr::manage_residue(const Residue* r) +{ + if (r == NULL || r->fade < 0.01f) + return; + + if (r->type == ZODIAC) + { + LinearColorF zode_color = ColorI(r->params.zodiac.r, r->params.zodiac.g, r->params.zodiac.b, r->params.zodiac.a); + + afxZodiacData* zd = (afxZodiacData*) r->data.zodiac; + if (zd->blend_flags == afxZodiacDefs::BLEND_SUBTRACTIVE) + zode_color *= r->fade; + else + zode_color.alpha *= r->fade; + + Point3F zode_pos(r->params.zodiac.pos_x, r->params.zodiac.pos_y, r->params.zodiac.pos_z); + Point2F zode_vrange(r->params.zodiac.vrange_dn, r->params.zodiac.vrange_dn); + if (r->params.zodiac.on_terrain) + { + afxZodiacMgr::addTerrainZodiac(zode_pos, r->params.zodiac.rad, zode_color, r->params.zodiac.ang, zd); + } + else + { + afxZodiacMgr::addInteriorZodiac(zode_pos, r->params.zodiac.rad, zode_vrange, zode_color, r->params.zodiac.ang, zd); + } + } + else if (r->type == MODEL) + { + r->data.model->setFadeAmount(r->fade); + } +} + +void afxResidueMgr::ResidueList::manage() +{ + if (the_mgr == NULL) + return; + + S32 n_residue = m_array->size(); + + for (S32 x = 0; x < n_residue; x++) + the_mgr->manage_residue((*m_array)[x]); +} + +U32 afxResidueMgr::ResidueList::findPendingBestBump(U32 look_max) +{ + U32 soonest = 1000*60*60*24; + m_pending = -1; + + U32 n = m_array->size(); + for (U32 i = 0; i < n && i < look_max; i++) + { + Residue* r = (*m_array)[i]; + if (r->stop_time < soonest) + { + soonest = r->stop_time; + m_pending = i; + } + } + + return soonest; +} + +void afxResidueMgr::ResidueList::bumpPending() +{ + if (m_pending >= 0 && m_pending < m_array->size()) + { + Residue* r = (*m_array)[m_pending]; + m_array->erase(m_pending); + free_residue(r); + } + + m_pending = -1; +} + +bool afxResidueMgr::requires_delete_tracking(Residue* r) +{ + return (r->type == MODEL); +} + +void afxResidueMgr::enable_delete_tracking(Residue* r) +{ + deleteNotify(r->data.simobject); +} + +void afxResidueMgr::disable_delete_tracking(Residue* r) +{ + clearNotify(r->data.simobject); + r->data.simobject->deleteObject(); + r->data.simobject = 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxResidueMgr* afxResidueMgr::the_mgr = NULL; +U32 afxResidueMgr::m_max_residue_objs = 256; +bool afxResidueMgr::enabled = true; + +IMPLEMENT_CONOBJECT(afxResidueMgr); + +ConsoleDocClass( afxResidueMgr, + "@brief A class that manages certain AFX effects that can persist for long durations.\n\n" + + "A class that manages certain AFX effects that can persist much longer than the duration of choreographers.\n" + + "@ingroup AFX\n" +); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// free-list management + +afxResidueMgr::Residue* afxResidueMgr::alloc_free_pool_block() +{ + // allocate new block for the free-list + m_free_pool_blocks.push_back(new Residue[FREE_POOL_BLOCK_SIZE]); + + // link them onto the free-list + Residue* new_block = m_free_pool_blocks.last(); + for (U32 i = 0; i < FREE_POOL_BLOCK_SIZE - 1; i++) + new_block[i].next = &new_block[i + 1]; + + // tail of free-list points to NULL + new_block[FREE_POOL_BLOCK_SIZE - 1].next = NULL; + + return new_block; +} + +afxResidueMgr::Residue* afxResidueMgr::alloc_residue() +{ + // need new free-list-block if m_next_free is null + if (!m_next_free) + m_next_free = alloc_free_pool_block(); + + // pop new residue from head of free-list + Residue* residue = m_next_free; + m_next_free = residue->next; + residue->next = NULL; + + return residue; +} + +void afxResidueMgr::free_residue(Residue* residue) +{ + if (residue && residue->type == ZODIAC) + { + if (residue->data.zodiac && residue->data.zodiac->isTempClone()) + { + delete residue->data.zodiac; + residue->data.zodiac = 0; + } + } + + // push residue onto head of free-list + residue->next = m_next_free; + m_next_free = residue; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxResidueMgr::deleteResidueObject(SimObject* obj, bool del_notify) +{ + m_managed.stripMatchingObjects(obj, del_notify); +} + +void afxResidueMgr::bump_residue() +{ + if (m_managed.findPendingBestBump()) + m_managed.bumpPending(); +} + +void afxResidueMgr::add_residue(Residue* residue) +{ + AssertFatal(residue != NULL, "residue pointer is NULL."); + + if (m_managed.size() >= m_max_residue_objs) + bump_residue(); + + m_managed.add(residue); + manage_residue(residue); + + if (requires_delete_tracking(residue)) + enable_delete_tracking(residue); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxResidueMgr::afxResidueMgr() +{ + mObjBox.minExtents.set(-1e7, -1e7, -1e7); + mObjBox.maxExtents.set( 1e7, 1e7, 1e7); + mWorldBox.minExtents.set(-1e7, -1e7, -1e7); + mWorldBox.maxExtents.set( 1e7, 1e7, 1e7); + + m_next_free = NULL; + + VECTOR_SET_ASSOCIATION(m_free_pool_blocks); +} + +afxResidueMgr::~afxResidueMgr() +{ + cleanup(); +} + +void afxResidueMgr::cleanup() +{ + m_managed.clear(); + + m_next_free = NULL; + + for (S32 i = 0; i < m_free_pool_blocks.size(); i++) + delete [] m_free_pool_blocks[i]; + + m_free_pool_blocks.clear(); +} + +void afxResidueMgr::onDeleteNotify(SimObject* obj) +{ + deleteResidueObject(obj, true); + Parent::onDeleteNotify(obj); +} + +void afxResidueMgr::residueAdvanceTime() +{ + U32 now = Platform::getVirtualMilliseconds(); + m_managed.fadeAndCull(now); + m_managed.sortIfDirty(); + m_managed.manage(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// +// add ZODIAC residue +void afxResidueMgr::add_interior_zodiac(F32 dur, F32 fade_dur, afxZodiacData* zode, const Point3F& pos, + F32 rad, const Point2F& vrange, const LinearColorF& col, F32 ang) +{ + add_zodiac(dur, fade_dur, zode, pos, rad, vrange, col, ang, false); +} + +void afxResidueMgr::add_terrain_zodiac(F32 dur, F32 fade_dur, afxZodiacData* zode, const Point3F& pos, + F32 rad, const LinearColorF& col, F32 ang) +{ + static Point2F vrange(0.0, 0.0); + add_zodiac(dur, fade_dur, zode, pos, rad, vrange, col, ang, true); +} + +void afxResidueMgr::add_zodiac(F32 dur, F32 fade_dur, afxZodiacData* zode, const Point3F& pos, + F32 rad, const Point2F& vrange, const LinearColorF& col, F32 ang, bool on_terrain) +{ + if (m_max_residue_objs == 0 || dur <= 0 || the_mgr == NULL) + return; + + ColorI col_i = LinearColorF(col).toColorI(); + U32 now = Platform::getVirtualMilliseconds(); + + Residue* residue = the_mgr->alloc_residue(); + // + residue->type = ZODIAC; + residue->data.zodiac = zode; + residue->fade_time = now + (U32)(dur*1000); + residue->stop_time = residue->fade_time + (U32)(fade_dur*1000); + residue->fade = 1.0f; + // + residue->params.zodiac.pos_x = pos.x; + residue->params.zodiac.pos_y = pos.y; + residue->params.zodiac.pos_z = pos.z; + residue->params.zodiac.rad = rad; + residue->params.zodiac.vrange_dn = vrange.x; + residue->params.zodiac.vrange_up = vrange.y; + residue->params.zodiac.r = col_i.red; + residue->params.zodiac.g = col_i.green; + residue->params.zodiac.b = col_i.blue; + residue->params.zodiac.a = col_i.alpha; + residue->params.zodiac.ang = ang; + residue->params.zodiac.on_terrain = on_terrain; + // + residue->next = 0; + + the_mgr->add_residue(residue); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// +// add MODEL residue + +void afxResidueMgr::add(F32 dur, F32 fade_dur, afxModel* model) +{ + if (m_max_residue_objs == 0 || dur <= 0 || the_mgr == NULL) + return; + + U32 now = Platform::getVirtualMilliseconds(); + + Residue* residue = the_mgr->alloc_residue(); + // + residue->type = MODEL; + residue->data.model = model; + residue->fade_time = now + (U32)(dur*1000); + residue->stop_time = residue->fade_time + (U32)(fade_dur*1000); + residue->fade = 1.0f; + // + residue->next = 0; + + the_mgr->add_residue(residue); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/afxResidueMgr.h b/Engine/source/afx/afxResidueMgr.h new file mode 100644 index 000000000..5219f331b --- /dev/null +++ b/Engine/source/afx/afxResidueMgr.h @@ -0,0 +1,179 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_RESIDUE_MGR_H_ +#define _AFX_RESIDUE_MGR_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxZodiacData; +class afxModel; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxResidueMgr +// +// Manage transient objects in the world. + +class afxResidueMgr : public GameBase +{ + + typedef GameBase Parent; + + enum { + ZODIAC, + MODEL + }; + + struct Residue + { + struct ZodiacParams + { + F32 pos_x, pos_y, pos_z; + F32 rad, vrange_dn, vrange_up; + U8 r,g,b,a; + F32 ang; + bool on_terrain; + }; + + union ResidueParams + { + ZodiacParams zodiac; + }; + + union ResidueData + { + afxZodiacData* zodiac; + afxModel* model; + SimObject* simobject; + }; + + + U32 type; + ResidueData data; + ResidueParams params; + U32 fade_time; + U32 stop_time; + F32 fade; + + Residue* next; + }; + + class ResidueList + { + Vector m_array_a; + Vector m_array_b; + + Vector* m_array; + Vector* m_scratch_array; + bool m_dirty; + S32 m_pending; + + void swap_array_ptrs(); + void free_residue(Residue*); + + public: + /*C*/ ResidueList(); + /*D*/ ~ResidueList(); + + void clear(); + S32 size() { return m_array->size(); } + bool empty() { return m_array->empty(); } + void sortIfDirty() { if (m_dirty) sort(); } + + void sort(); + void fadeAndCull(U32 now); + void stripMatchingObjects(SimObject* db, bool del_notify=false); + void add(Residue*); + + void manage(); + + U32 findPendingBestBump(U32 look_max=256); + void bumpPending(); + + static int QSORT_CALLBACK compare_residue(const void* p1, const void* p2); + }; + + friend class ResidueList; + +private: + enum { FREE_POOL_BLOCK_SIZE = 256 }; + + static afxResidueMgr* the_mgr; + + static U32 m_max_residue_objs; + static bool enabled; + + ResidueList m_managed; + + Vector m_free_pool_blocks; + Residue* m_next_free; + + Residue* alloc_free_pool_block(); + Residue* alloc_residue(); + void free_residue(Residue*); + + void bump_residue(); + void add_residue(Residue*); + static void add_zodiac(F32 dur, F32 fade_dur, afxZodiacData*, const Point3F& pos, F32 rad, + const Point2F& vrange, const LinearColorF& col, F32 ang, bool on_terrain); + +protected: + void deleteResidueObject(SimObject* obj, bool del_notify=false); + + void manage_residue(const Residue* r); + + bool requires_delete_tracking(Residue*); + void enable_delete_tracking(Residue*); + void disable_delete_tracking(Residue*); + +public: + /*C*/ afxResidueMgr(); + /*D*/ ~afxResidueMgr(); + + void cleanup(); + virtual void onDeleteNotify(SimObject *obj); + +public: + void residueAdvanceTime(); + + // ZODIAC + static void add_terrain_zodiac(F32 dur, F32 fade_dur, afxZodiacData*, const Point3F& pos, F32 rad, + const LinearColorF& col, F32 ang); + static void add_interior_zodiac(F32 dur, F32 fade_dur, afxZodiacData*, const Point3F& pos, F32 rad, + const Point2F& vrange, const LinearColorF& col, F32 ang); + + // MODEL + static void add(F32 dur, F32 fade_dur, afxModel*); + + static afxResidueMgr* getMaster() { return the_mgr; } + static void setMaster(afxResidueMgr* m) { the_mgr = m; } + + DECLARE_CONOBJECT(afxResidueMgr); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_RESIDUE_MGR_H_ diff --git a/Engine/source/afx/afxSelectron.cpp b/Engine/source/afx/afxSelectron.cpp new file mode 100644 index 000000000..148bcceca --- /dev/null +++ b/Engine/source/afx/afxSelectron.cpp @@ -0,0 +1,1173 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" +#include "T3D/gameBase/gameConnection.h" +#include "sfx/sfxSystem.h" + +#include "afx/afxChoreographer.h" +#include "afx/afxSelectron.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxSelectronData::ewValidator +// +// When an effect is added using "addEffect", this validator intercepts the value +// and adds it to the dynamic effects list. +// +void afxSelectronData::ewValidator::validateType(SimObject* object, void* typePtr) +{ + afxSelectronData* sele_data = dynamic_cast(object); + afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr); + + if (sele_data && ew) + { + switch (id) + { + case MAIN_PHRASE: + sele_data->main_fx_list.push_back(*ew); + break; + case SELECT_PHRASE: + sele_data->select_fx_list.push_back(*ew); + break; + case DESELECT_PHRASE: + sele_data->deselect_fx_list.push_back(*ew); + break; + } + *ew = 0; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class SelectronFinishStartupEvent : public SimEvent +{ +public: + void process(SimObject* obj) + { + afxSelectron* selectron = dynamic_cast(obj); + if (selectron) + selectron->finish_startup(); + } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxSelectronData + +IMPLEMENT_CO_DATABLOCK_V1(afxSelectronData); + +ConsoleDocClass( afxSelectronData, + "@brief Defines the properties of an afxSelectronData.\n\n" + + "@ingroup afxChoreographers\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxSelectronData::afxSelectronData() +{ + main_dur = 0.0f; + select_dur = 0.0f; + deselect_dur = 0.0f; + + n_main_loops = 1; + n_select_loops = 1; + n_deselect_loops = 1; + + registered = false; + + obj_type_style = 0; + obj_type_mask = 0; + + // dummy entry holds effect-wrapper pointer while a special validator + // grabs it and adds it to an appropriate effects list + dummy_fx_entry = NULL; + + // marked true if datablock ids need to + // be converted into pointers + do_id_convert = false; +} + +afxSelectronData::afxSelectronData(const afxSelectronData& other, bool temp_clone) : afxChoreographerData(other, temp_clone) +{ + main_dur = other.main_dur; + select_dur = other.select_dur; + deselect_dur = other.deselect_dur; + n_main_loops = other.n_main_loops; + n_select_loops = other.n_select_loops; + n_deselect_loops = other.n_deselect_loops; + registered = false; + obj_type_style = other.obj_type_style; + obj_type_mask = other.obj_type_mask; + dummy_fx_entry = other.dummy_fx_entry; + do_id_convert = other.do_id_convert; + + main_fx_list = other.main_fx_list; + select_fx_list = other.select_fx_list; + deselect_fx_list = other.deselect_fx_list; +} + +afxSelectronData::~afxSelectronData() +{ + if (registered && !isTempClone()) + arcaneFX::unregisterSelectronData(this); +} + +void afxSelectronData::reloadReset() +{ + main_fx_list.clear(); + select_fx_list.clear(); + deselect_fx_list.clear(); +} + +#define myOffset(field) Offset(field, afxSelectronData) + +void afxSelectronData::initPersistFields() +{ + static ewValidator _mainPhrase(MAIN_PHRASE); + static ewValidator _selectPhrase(SELECT_PHRASE); + static ewValidator _deselectPhrase(DESELECT_PHRASE); + + addField("mainDur", TypeF32, myOffset(main_dur), + "..."); + addField("selectDur", TypeF32, myOffset(select_dur), + "..."); + addField("deselectDur", TypeF32, myOffset(deselect_dur), + "..."); + addField("mainRepeats", TypeS32, myOffset(n_main_loops), + "..."); + addField("selectRepeats", TypeS32, myOffset(n_select_loops), + "..."); + addField("deselectRepeats", TypeS32, myOffset(n_deselect_loops), + "..."); + addField("selectionTypeMask", TypeS32, myOffset(obj_type_mask), + "..."); + addField("selectionTypeStyle", TypeS8, myOffset(obj_type_style), + "..."); + + // effect lists + // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list + addFieldV("addMainEffect", TYPEID(), myOffset(dummy_fx_entry), &_mainPhrase, + "..."); + addFieldV("addSelectEffect", TYPEID(), myOffset(dummy_fx_entry), &_selectPhrase, + "..."); + addFieldV("addDeselectEffect", TYPEID(), myOffset(dummy_fx_entry), &_deselectPhrase, + "..."); + + // deprecated + addField("numMainLoops", TypeS32, myOffset(n_main_loops), + "..."); + addField("numSelectLoops", TypeS32, myOffset(n_select_loops), + "..."); + addField("numDeselectLoops", TypeS32, myOffset(n_deselect_loops), + "..."); + + Parent::initPersistFields(); + + // disallow some field substitutions + disableFieldSubstitutions("addMainEffect"); + disableFieldSubstitutions("addSelectEffect"); + disableFieldSubstitutions("addDeselectEffect"); +} + +bool afxSelectronData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxSelectronData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed) +{ + stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS); + for (int i = 0; i < fx.size(); i++) + writeDatablockID(stream, fx[i], packed); +} + +void afxSelectronData::unpack_fx(BitStream* stream, afxEffectList& fx) +{ + fx.clear(); + S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS); + for (int i = 0; i < n_fx; i++) + fx.push_back((afxEffectWrapperData*)readDatablockID(stream)); +} + +void afxSelectronData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(main_dur); + stream->write(select_dur); + stream->write(deselect_dur); + stream->write(n_main_loops); + stream->write(n_select_loops); + stream->write(n_deselect_loops); + stream->write(obj_type_style); + stream->write(obj_type_mask); + + pack_fx(stream, main_fx_list, packed); + pack_fx(stream, select_fx_list, packed); + pack_fx(stream, deselect_fx_list, packed); +} + +void afxSelectronData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&main_dur); + stream->read(&select_dur); + stream->read(&deselect_dur); + stream->read(&n_main_loops); + stream->read(&n_select_loops); + stream->read(&n_deselect_loops); + stream->read(&obj_type_style); + stream->read(&obj_type_mask); + + do_id_convert = true; + unpack_fx(stream, main_fx_list); + unpack_fx(stream, select_fx_list); + unpack_fx(stream, deselect_fx_list); +} + +inline void expand_fx_list(afxEffectList& fx_list, const char* tag) +{ + for (S32 i = 0; i < fx_list.size(); i++) + { + SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]); + if (db_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(db_id, fx_list[i])) + { + Con::errorf(ConsoleLogEntry::General, + "afxSelectronData::preload() -- bad datablockId: 0x%x (%s)", + db_id, tag); + } + } + } +} + +bool afxSelectronData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) + { + if (do_id_convert) + { + expand_fx_list(main_fx_list, "main"); + expand_fx_list(select_fx_list, "select"); + expand_fx_list(deselect_fx_list, "deselect"); + do_id_convert = false; + } + + // this is where a selectron registers itself with the rest of AFX + if (!registered) + { + arcaneFX::registerSelectronData(this); + registered = true; + } + } + + return true; +} + +void afxSelectronData::gatherConstraintDefs(Vector& defs) +{ + afxConstraintDef::gather_cons_defs(defs, main_fx_list); + afxConstraintDef::gather_cons_defs(defs, select_fx_list); + afxConstraintDef::gather_cons_defs(defs, deselect_fx_list); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod(afxSelectronData, reset, void, (),, + "Resets a selectron datablock during reload.\n\n" + "@ingroup AFX") +{ + object->reloadReset(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxSelectron + +IMPLEMENT_CO_NETOBJECT_V1(afxSelectron); + +ConsoleDocClass( afxSelectron, + "@brief A choreographer for selection effects.\n\n" + + "@ingroup afxChoreographers\n" + "@ingroup AFX\n" +); + +StringTableEntry afxSelectron::CAMERA_CONS; +StringTableEntry afxSelectron::LISTENER_CONS; +StringTableEntry afxSelectron::FREE_TARGET_CONS; + +void afxSelectron::init() +{ + client_only = true; + mNetFlags.clear(Ghostable | ScopeAlways); + mNetFlags.set(IsGhost); + + // setup static predefined constraint names + if (CAMERA_CONS == 0) + { + CAMERA_CONS = StringTable->insert("camera"); + LISTENER_CONS = StringTable->insert("listener"); + FREE_TARGET_CONS = StringTable->insert("freeTarget"); + } + + datablock = NULL; + exeblock = NULL; + + constraints_initialized = false; + + effect_state = (U8) INACTIVE_STATE; + effect_elapsed = 0; + + // define named constraints + constraint_mgr->defineConstraint(CAMERA_CONSTRAINT, CAMERA_CONS); + constraint_mgr->defineConstraint(POINT_CONSTRAINT, LISTENER_CONS); + constraint_mgr->defineConstraint(POINT_CONSTRAINT, FREE_TARGET_CONS); + + for (S32 i = 0; i < NUM_PHRASES; i++) + phrases[i] = NULL; + + time_factor = 1.0f; + camera_cons_obj = 0; + + marks_mask = 0; +} + +afxSelectron::afxSelectron() +{ + started_with_newop = true; + init(); +} + +afxSelectron::afxSelectron(bool not_default) +{ + started_with_newop = false; + init(); +} + +afxSelectron::~afxSelectron() +{ + for (S32 i = 0; i < NUM_PHRASES; i++) + { + if (phrases[i]) + { + phrases[i]->interrupt(effect_elapsed); + delete phrases[i]; + } + } + + if (datablock && datablock->isTempClone()) + { + delete datablock; + datablock = 0; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +// STANDARD OVERLOADED METHODS // + +bool afxSelectron::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + datablock = dynamic_cast(dptr); + if (!datablock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + exeblock = datablock; + + return true; +} + +void afxSelectron::processTick(const Move* m) +{ + Parent::processTick(m); + + // don't process moves or client ticks + if (m != 0 || isClientObject()) + return; + + process_server(); +} + +void afxSelectron::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + process_client(dt); +} + +bool afxSelectron::onAdd() +{ + if (started_with_newop) + { + Con::errorf("afxSelectron::onAdd() -- selectrons cannot be created with the \"new\" operator. Use startSelectron() instead."); + return false; + } + + NetConnection* conn = NetConnection::getConnectionToServer(); + if (!conn || !Parent::onAdd()) + return false; + + conn->addObject(this); + + return true; +} + +void afxSelectron::onRemove() +{ + getContainer()->removeObject(this); + Parent::onRemove(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +U32 afxSelectron::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(conn, mask, stream); + + // InitialUpdate + if (stream->writeFlag(mask & InitialUpdateMask)) + { + stream->write(time_factor); + + GameConnection* gconn = dynamic_cast(conn); + bool zoned_in = (gconn) ? gconn->isZonedIn() : false; + if (stream->writeFlag(zoned_in)) + pack_constraint_info(conn, stream); + } + + // StateEvent or SyncEvent + if (stream->writeFlag((mask & StateEventMask) || (mask & SyncEventMask))) + { + stream->write(marks_mask); + stream->write(effect_state); + stream->write(effect_elapsed); + } + + // SyncEvent + bool do_sync_event = ((mask & SyncEventMask) && !(mask & InitialUpdateMask)); + if (stream->writeFlag(do_sync_event)) + { + pack_constraint_info(conn, stream); + } + + return retMask; +} + +//~~~~~~~~~~~~~~~~~~~~// + +void afxSelectron::unpackUpdate(NetConnection * conn, BitStream * stream) +{ + Parent::unpackUpdate(conn, stream); + + bool initial_update = false; + bool zoned_in = true; + bool do_sync_event = false; + U8 new_marks_mask = 0; + U8 new_state = INACTIVE_STATE; + F32 new_elapsed = 0; + + // InitialUpdate Only + if (stream->readFlag()) + { + initial_update = true; + + stream->read(&time_factor); + + // if client is marked as fully zoned in + if ((zoned_in = stream->readFlag()) == true) + { + unpack_constraint_info(conn, stream); + init_constraints(); + } + } + + // StateEvent or SyncEvent + // this state data is sent for both state-events and + // sync-events + if (stream->readFlag()) + { + stream->read(&new_marks_mask); + stream->read(&new_state); + stream->read(&new_elapsed); + + marks_mask = new_marks_mask; + } + + // SyncEvent + do_sync_event = stream->readFlag(); + if (do_sync_event) + { + unpack_constraint_info(conn, stream); + init_constraints(); + } + + //~~~~~~~~~~~~~~~~~~~~// + + if (!zoned_in) + effect_state = LATE_STATE; + + // need to adjust state info to get all synced up with spell on server + if (do_sync_event && !initial_update) + sync_client(new_marks_mask, new_state, new_elapsed); +} + +void afxSelectron::sync_with_clients() +{ + setMaskBits(SyncEventMask); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private + +bool afxSelectron::state_expired() +{ + afxPhrase* phrase = (effect_state == ACTIVE_STATE) ? phrases[MAIN_PHRASE] : NULL; + + if (phrase) + { + if (phrase->expired(effect_elapsed)) + return (!phrase->recycle(effect_elapsed)); + return false; + } + + return true; +} + +void afxSelectron::init_constraints() +{ + if (constraints_initialized) + { + //Con::printf("CONSTRAINTS ALREADY INITIALIZED"); + return; + } + + Vector defs; + datablock->gatherConstraintDefs(defs); + + constraint_mgr->initConstraintDefs(defs, isServerObject()); + + if (isClientObject()) + { + // find local camera + camera_cons_obj = get_camera(); + if (camera_cons_obj) + camera_cons_id = constraint_mgr->setReferenceObject(CAMERA_CONS, camera_cons_obj); + + // find local listener + Point3F listener_pos; + listener_pos = SFX->getListener().getTransform().getPosition(); + listener_cons_id = constraint_mgr->setReferencePoint(LISTENER_CONS, listener_pos); + + // find free target + free_target_cons_id = constraint_mgr->setReferencePoint(FREE_TARGET_CONS, arcaneFX::sFreeTargetPos); + } + + constraint_mgr->adjustProcessOrdering(this); + + constraints_initialized = true; +} + +void afxSelectron::setup_main_fx() +{ + phrases[MAIN_PHRASE] = new afxPhrase(isServerObject(), true); + + if (phrases[MAIN_PHRASE]) + phrases[MAIN_PHRASE]->init(datablock->main_fx_list, datablock->main_dur, this, time_factor, + datablock->n_main_loops); +} + +void afxSelectron::setup_select_fx() +{ + phrases[SELECT_PHRASE] = new afxPhrase(isServerObject(), true); + + if (phrases[SELECT_PHRASE]) + phrases[SELECT_PHRASE]->init(datablock->select_fx_list, -1, this, time_factor, 1); +} + +void afxSelectron::setup_deselect_fx() +{ + phrases[DESELECT_PHRASE] = new afxPhrase(isServerObject(), true); + + if (phrases[DESELECT_PHRASE]) + phrases[DESELECT_PHRASE]->init(datablock->deselect_fx_list, -1, this, time_factor, 1); +} + +bool afxSelectron::cleanup_over() +{ + for (S32 i = 0; i < NUM_PHRASES; i++) + if (phrases[i] && !phrases[i]->isEmpty()) + return false; + + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private + +void afxSelectron::process_server() +{ + if (effect_state != INACTIVE_STATE) + effect_elapsed += TickSec; + + U8 pending_state = effect_state; + + // check for state changes + switch (effect_state) + { + case INACTIVE_STATE: + if (marks_mask & MARK_ACTIVATE) + pending_state = ACTIVE_STATE; + break; + case ACTIVE_STATE: + if (marks_mask & MARK_INTERRUPT) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_SHUTDOWN) + pending_state = CLEANUP_STATE; + else if (state_expired()) + pending_state = CLEANUP_STATE; + break; + case CLEANUP_STATE: + if (cleanup_over()) + pending_state = DONE_STATE; + break; + } + + if (effect_state != pending_state) + change_state_s(pending_state); + + if (effect_state == INACTIVE_STATE) + return; + + //--------------------------// + + // sample the constraints + constraint_mgr->sample(TickSec, Platform::getVirtualMilliseconds()); + + for (S32 i = 0; i < NUM_PHRASES; i++) + if (phrases[i]) + phrases[i]->update(TickSec, effect_elapsed); +} + +void afxSelectron::change_state_s(U8 pending_state) +{ + if (effect_state == pending_state) + return; + + switch (effect_state) + { + case INACTIVE_STATE: + break; + case ACTIVE_STATE: + leave_active_state_s(); + break; + case CLEANUP_STATE: + break; + case DONE_STATE: + break; + } + + effect_state = pending_state; + + switch (pending_state) + { + case INACTIVE_STATE: + break; + case ACTIVE_STATE: + enter_active_state_s(); + break; + case CLEANUP_STATE: + enter_cleanup_state_s(); + break; + case DONE_STATE: + enter_done_state_s(); + break; + } +} + +void afxSelectron::enter_done_state_s() +{ + postEvent(DEACTIVATE_EVENT); + + F32 done_time = effect_elapsed; + + for (S32 i = 0; i < NUM_PHRASES; i++) + { + if (phrases[i]) + { + F32 phrase_done; + if (phrases[i]->willStop() && phrases[i]->isInfinite()) + phrase_done = effect_elapsed + phrases[i]->calcAfterLife(); + else + phrase_done = phrases[i]->calcDoneTime(); + if (phrase_done > done_time) + done_time = phrase_done; + } + } + + F32 time_left = done_time - effect_elapsed; + if (time_left < 0) + time_left = 0; + + Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500); + + // CALL SCRIPT afxSelectronData::onDeactivate(%sele) + Con::executef(datablock, "onDeactivate", getIdString()); +} + +void afxSelectron::enter_active_state_s() +{ + // stamp constraint-mgr starting time + constraint_mgr->setStartTime(Platform::getVirtualMilliseconds()); + effect_elapsed = 0; + + setup_dynamic_constraints(); + + // start casting effects + setup_main_fx(); + if (phrases[MAIN_PHRASE]) + phrases[MAIN_PHRASE]->start(effect_elapsed, effect_elapsed); + + setup_select_fx(); + if (phrases[SELECT_PHRASE]) + phrases[SELECT_PHRASE]->start(effect_elapsed, effect_elapsed); +} + +void afxSelectron::leave_active_state_s() +{ + if (phrases[MAIN_PHRASE]) + phrases[MAIN_PHRASE]->stop(effect_elapsed); +} + +void afxSelectron::enter_cleanup_state_s() +{ + // start deselect effects + setup_deselect_fx(); + if (phrases[SELECT_PHRASE]) + phrases[SELECT_PHRASE]->interrupt(effect_elapsed); + if (phrases[DESELECT_PHRASE]) + phrases[DESELECT_PHRASE]->start(effect_elapsed, effect_elapsed); + + postEvent(SHUTDOWN_EVENT); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// private + +void afxSelectron::process_client(F32 dt) +{ + effect_elapsed += dt; + + U8 pending_state = effect_state; + + // check for state changes + switch (effect_state) + { + case INACTIVE_STATE: + if (marks_mask & MARK_ACTIVATE) + pending_state = ACTIVE_STATE; + break; + case ACTIVE_STATE: + if (marks_mask & MARK_INTERRUPT) + pending_state = CLEANUP_STATE; + else if (marks_mask & MARK_SHUTDOWN) + pending_state = CLEANUP_STATE; + else if (state_expired()) + pending_state = CLEANUP_STATE; + break; + case CLEANUP_STATE: + if (cleanup_over()) + pending_state = DONE_STATE; + break; + } + + if (effect_state != pending_state) + change_state_c(pending_state); + + if (effect_state == INACTIVE_STATE) + return; + + //--------------------------// + + // update the listener constraint position + if (!listener_cons_id.undefined()) + { + Point3F listener_pos; + listener_pos = SFX->getListener().getTransform().getPosition(); + constraint_mgr->setReferencePoint(listener_cons_id, listener_pos); + } + + // update the free target constraint position + if (!free_target_cons_id.undefined()) + { + if (!arcaneFX::sFreeTargetPosValid) + constraint_mgr->invalidateReference(free_target_cons_id); + else + constraint_mgr->setReferencePoint(free_target_cons_id, arcaneFX::sFreeTargetPos); + } + + // find local camera position + Point3F cam_pos; + SceneObject* current_cam = get_camera(&cam_pos); + + // detect camera changes + if (!camera_cons_id.undefined() && current_cam != camera_cons_obj) + { + constraint_mgr->setReferenceObject(camera_cons_id, current_cam); + camera_cons_obj = current_cam; + } + + // sample the constraints + constraint_mgr->sample(dt, Platform::getVirtualMilliseconds(), (current_cam) ? &cam_pos : 0); + + // update active effects lists + for (S32 i = 0; i < NUM_PHRASES; i++) + if (phrases[i]) + phrases[i]->update(dt, effect_elapsed); + +} + +void afxSelectron::change_state_c(U8 pending_state) +{ + if (effect_state == pending_state) + return; + + switch (effect_state) + { + case INACTIVE_STATE: + break; + case ACTIVE_STATE: + leave_active_state_c(); + break; + case CLEANUP_STATE: + break; + case DONE_STATE: + break; + } + + effect_state = pending_state; + + switch (pending_state) + { + case INACTIVE_STATE: + break; + case ACTIVE_STATE: + enter_active_state_c(effect_elapsed); + break; + case CLEANUP_STATE: + enter_cleanup_state_c(); + break; + case DONE_STATE: + enter_done_state_c(); + break; + } +} + +void afxSelectron::enter_active_state_c(F32 starttime) +{ + // stamp constraint-mgr starting time + constraint_mgr->setStartTime(Platform::getVirtualMilliseconds() - (U32)(effect_elapsed*1000)); + ///effect_elapsed = 0; + + setup_dynamic_constraints(); + + setup_main_fx(); + if (phrases[MAIN_PHRASE]) + phrases[MAIN_PHRASE]->start(starttime, effect_elapsed); + + setup_select_fx(); + if (phrases[SELECT_PHRASE]) + phrases[SELECT_PHRASE]->start(starttime, effect_elapsed); +} + +void afxSelectron::leave_active_state_c() +{ + if (phrases[MAIN_PHRASE]) + { + //if (marks_mask & MARK_INTERRUPT) + // active_phrase->interrupt(effect_elapsed); + //else + phrases[MAIN_PHRASE]->stop(effect_elapsed); + } +} + +void afxSelectron::enter_cleanup_state_c() +{ + if (!client_only) + return; + + // start deselect effects + setup_deselect_fx(); + if (phrases[DESELECT_PHRASE]) + phrases[DESELECT_PHRASE]->start(effect_elapsed, effect_elapsed); + + postEvent(SHUTDOWN_EVENT); +} + +void afxSelectron::enter_done_state_c() +{ + if (!client_only) + return; + + postEvent(DEACTIVATE_EVENT); + + F32 done_time = effect_elapsed; + + for (S32 i = 0; i < NUM_PHRASES; i++) + { + if (phrases[i]) + { + F32 phrase_done; + if (phrases[i]->willStop() && phrases[i]->isInfinite()) + phrase_done = effect_elapsed + phrases[i]->calcAfterLife(); + else + phrase_done = phrases[i]->calcDoneTime(); + if (phrase_done > done_time) + done_time = phrase_done; + } + } + + F32 time_left = done_time - effect_elapsed; + if (time_left < 0) + time_left = 0; + + Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + time_left*1000 + 500); + + // CALL SCRIPT afxSelectronData::onDeactivate(%selectron) + Con::executef(datablock, "onDeactivate", getIdString()); +} + +void afxSelectron::sync_client(U16 marks, U8 state, F32 elapsed) +{ + //Con::printf("SYNC marks=%d old_state=%d state=%d elapsed=%g", + // marks, effect_state, state, elapsed); + + if (effect_state != LATE_STATE) + return; + + marks_mask = marks; + + // don't want to be started on late zoning clients + if (!datablock->exec_on_new_clients) + { + effect_state = DONE_STATE; + } + + // it looks like we're ghosting pretty late and + // should just return to the inactive state. + else if (marks & (MARK_INTERRUPT | MARK_DEACTIVATE | MARK_SHUTDOWN)) + { + effect_state = DONE_STATE; + } + + // it looks like we should be in the active state. + else if (marks & MARK_ACTIVATE) + { + effect_state = ACTIVE_STATE; + effect_elapsed = elapsed; + enter_active_state_c(0.0); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// public: + +void afxSelectron::postEvent(U8 event) +{ + setMaskBits(StateEventMask); + + switch (event) + { + case ACTIVATE_EVENT: + marks_mask |= MARK_ACTIVATE; + break; + case SHUTDOWN_EVENT: + marks_mask |= MARK_SHUTDOWN; + break; + case DEACTIVATE_EVENT: + marks_mask |= MARK_DEACTIVATE; + break; + case INTERRUPT_EVENT: + marks_mask |= MARK_INTERRUPT; + break; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxSelectron::finish_startup() +{ + init_constraints(); + postEvent(afxSelectron::ACTIVATE_EVENT); +} + +// static +afxSelectron* +afxSelectron::start_selectron(SceneObject* picked, U8 subcode, SimObject* extra) +{ + U32 picked_type = (picked) ? picked->getTypeMask() : 0; + + afxSelectronData* datablock = arcaneFX::findSelectronData(picked_type, subcode); + if (!datablock) + { + Con::errorf("startSelectron() -- failed to match object-type (%x/%d) to a selection-effect.", + picked_type, subcode); + return 0; + } + + afxSelectronData* exeblock = datablock; + + SimObject* param_holder = new SimObject(); + if (!param_holder->registerObject()) + { + Con::errorf("afxSelectron: failed to register parameter object."); + delete param_holder; + return 0; + } + + param_holder->assignDynamicFieldsFrom(datablock, arcaneFX::sParameterFieldPrefix); + if (extra) + { + // copy dynamic fields from the extra object to the param holder + param_holder->assignDynamicFieldsFrom(extra, arcaneFX::sParameterFieldPrefix); + } + + // CALL SCRIPT afxSelectronData::onPreactivate(%params, %extra) + const char* result = Con::executef(datablock, "onPreactivate", + Con::getIntArg(param_holder->getId()), + (extra) ? Con::getIntArg(extra->getId()) : ""); + if (result && result[0] != '\0' && !dAtob(result)) + { +#if defined(TORQUE_DEBUG) + Con::warnf("afxSelectron: onPreactivate() returned false, effect aborted."); +#endif + Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime()); + return 0; + } + + // make a temp datablock clone if there are substitutions + if (datablock->getSubstitutionCount() > 0) + { + datablock = new afxSelectronData(*exeblock, true); + exeblock->performSubstitutions(datablock, param_holder); + } + + // create a new selectron instance + afxSelectron* selectron = new afxSelectron(true); + selectron->setDataBlock(datablock); + selectron->exeblock = exeblock; + selectron->setExtra(extra); + + // copy dynamic fields from the param holder to the selectron + selectron->assignDynamicFieldsFrom(param_holder, arcaneFX::sParameterFieldPrefix); + Sim::postEvent(param_holder, new ObjectDeleteEvent, Sim::getCurrentTime()); + + // register + if (!selectron->registerObject()) + { + Con::errorf("afxSelectron: failed to register selectron instance."); + Sim::postEvent(selectron, new ObjectDeleteEvent, Sim::getCurrentTime()); + return 0; + } + + selectron->activate(); + + return selectron; +} + +void afxSelectron::activate() +{ + // separating the final part of startup allows the calling script + // to make certain types of calls on the returned effectron that + // need to happen prior to constraint initialization. + Sim::postEvent(this, new SelectronFinishStartupEvent, Sim::getCurrentTime()); + + // CALL SCRIPT afxEffectronData::onActivate(%eff) + Con::executef(exeblock, "onActivate", getIdString()); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// console functions + +DefineEngineMethod(afxSelectron, setTimeFactor, void, (float factor), (1.0f), + "Sets the time factor of the selectron.\n\n" + "@ingroup AFX") +{ + object->setTimeFactor(factor); +} + +DefineEngineMethod(afxSelectron, interrupt, void, (),, + "Interrupts and deletes a running selectron.\n\n" + "@ingroup AFX") +{ + object->postEvent(afxSelectron::INTERRUPT_EVENT); +} + +DefineEngineMethod(afxSelectron, stopSelectron, void, (),, + "Stops and deletes a running selectron.\n\n" + "@ingroup AFX") +{ + object->postEvent(afxSelectron::INTERRUPT_EVENT); +} + +DefineEngineFunction(startSelectron, S32, (SceneObject* selectedObj, unsigned int subcode, SimObject* extra), + (nullAsType(), 0, nullAsType()), + "Instantiates a selectron.\n\n" + "@ingroup AFX") +{ + // + // Start the Selectron + // + afxSelectron* selectron = afxSelectron::start_selectron(selectedObj, (U8)subcode, extra); + + // + // Return the ID (or 0 if start failed). + // + return (selectron) ? selectron->getId() : 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + + + + + + + + + diff --git a/Engine/source/afx/afxSelectron.h b/Engine/source/afx/afxSelectron.h new file mode 100644 index 000000000..f7b0c4e50 --- /dev/null +++ b/Engine/source/afx/afxSelectron.h @@ -0,0 +1,258 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_SELECTION_EFFECT_H_ +#define _AFX_SELECTION_EFFECT_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "console/typeValidators.h" + +#include "afxChoreographer.h" +#include "afxEffectWrapper.h" +#include "afxPhrase.h" + +class afxChoreographerData; +class afxEffectBaseData; + +class afxSelectronDefs +{ +public: + enum { + MAIN_PHRASE, + SELECT_PHRASE, + DESELECT_PHRASE, + NUM_PHRASES + }; +}; + +class afxSelectronData : public afxChoreographerData, public afxSelectronDefs +{ + typedef afxChoreographerData Parent; + + class ewValidator : public TypeValidator + { + U32 id; + public: + ewValidator(U32 id) { this->id = id; } + void validateType(SimObject *object, void *typePtr); + }; + + bool do_id_convert; + +public: + F32 main_dur; + F32 select_dur; + F32 deselect_dur; + + S32 n_main_loops; + S32 n_select_loops; + S32 n_deselect_loops; + + bool registered; + U8 obj_type_style; + U32 obj_type_mask; + + afxEffectBaseData* dummy_fx_entry; + + afxEffectList main_fx_list; + afxEffectList select_fx_list; + afxEffectList deselect_fx_list; + +private: + void pack_fx(BitStream* stream, const afxEffectList& fx, bool packed); + void unpack_fx(BitStream* stream, afxEffectList& fx); + +public: + /*C*/ afxSelectronData(); + /*C*/ afxSelectronData(const afxSelectronData&, bool = false); + /*D*/ ~afxSelectronData(); + + virtual void reloadReset(); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + bool matches(U32 mask, U8 style); + void gatherConstraintDefs(Vector&); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxSelectronData); + DECLARE_CATEGORY("AFX"); +}; + +inline bool afxSelectronData::matches(U32 mask, U8 style) +{ + if (obj_type_style != style) + return false; + + if (obj_type_mask == 0 && mask == 0) + return true; + + return ((obj_type_mask & mask) != 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxSelectron + +class afxSelectron : public afxChoreographer, public afxSelectronDefs +{ + typedef afxChoreographer Parent; + friend class arcaneFX; + +public: + enum MaskBits + { + StateEventMask = Parent::NextFreeMask << 0, + SyncEventMask = Parent::NextFreeMask << 1, + NextFreeMask = Parent::NextFreeMask << 2 + }; + + enum + { + NULL_EVENT, + ACTIVATE_EVENT, + SHUTDOWN_EVENT, + DEACTIVATE_EVENT, + INTERRUPT_EVENT + }; + + enum + { + INACTIVE_STATE, + ACTIVE_STATE, + CLEANUP_STATE, + DONE_STATE, + LATE_STATE + }; + + enum { + MARK_ACTIVATE = BIT(0), + MARK_SHUTDOWN = BIT(1), + MARK_DEACTIVATE = BIT(2), + MARK_INTERRUPT = BIT(3), + }; + + class ObjectDeleteEvent : public SimEvent + { + public: + void process(SimObject *obj) { if (obj) obj->deleteObject(); } + }; + +private: + static StringTableEntry CAMERA_CONS; + static StringTableEntry LISTENER_CONS; + static StringTableEntry FREE_TARGET_CONS; + +private: + afxSelectronData* datablock; + SimObject* exeblock; + + bool constraints_initialized; + bool client_only; + + U8 effect_state; + F32 effect_elapsed; + + afxConstraintID listener_cons_id; + afxConstraintID free_target_cons_id; + afxConstraintID camera_cons_id; + SceneObject* camera_cons_obj; + + afxPhrase* phrases[NUM_PHRASES]; + + F32 time_factor; + U8 marks_mask; + +private: + void init(); + bool state_expired(); + void init_constraints(); + void setup_main_fx(); + void setup_select_fx(); + void setup_deselect_fx(); + bool cleanup_over(); + +public: + /*C*/ afxSelectron(); + /*C*/ afxSelectron(bool not_default); + /*D*/ ~afxSelectron(); + + // STANDARD OVERLOADED METHODS // + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + virtual void processTick(const Move*); + virtual void advanceTime(F32 dt); + virtual bool onAdd(); + virtual void onRemove(); + virtual U32 packUpdate(NetConnection*, U32, BitStream*); + virtual void unpackUpdate(NetConnection*, BitStream*); + + virtual void sync_with_clients(); + void finish_startup(); + + DECLARE_CONOBJECT(afxSelectron); + DECLARE_CATEGORY("AFX"); + +private: + void process_server(); + // + void change_state_s(U8 pending_state); + // + void enter_active_state_s(); + void leave_active_state_s(); + void enter_cleanup_state_s(); + void enter_done_state_s(); + +private: + void process_client(F32 dt); + // + void change_state_c(U8 pending_state); + // + void enter_active_state_c(F32 starttime); + void enter_cleanup_state_c(); + void enter_done_state_c(); + void leave_active_state_c(); + + void sync_client(U16 marks, U8 state, F32 elapsed); + +public: + void postEvent(U8 event); + void setTimeFactor(F32 f) { time_factor = (f > 0) ? f : 1.0f; } + F32 getTimeFactor() { return time_factor; } + + void activate(); + +public: + static afxSelectron* start_selectron(SceneObject* picked, U8 subcode, SimObject* extra); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +#endif // _AFX_SELECTION_EFFECT_H_ diff --git a/Engine/source/afx/afxSpellBook.cpp b/Engine/source/afx/afxSpellBook.cpp new file mode 100644 index 000000000..42d28d550 --- /dev/null +++ b/Engine/source/afx/afxSpellBook.cpp @@ -0,0 +1,357 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "T3D/gameBase/gameBase.h" + +#include "afx/afxSpellBook.h" +#include "afx/afxMagicSpell.h" +#include "afx/rpg/afxRPGMagicSpell.h" +#include "afx/ui/afxSpellButton.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxSpellBookData + +IMPLEMENT_CO_DATABLOCK_V1(afxSpellBookData); + +ConsoleDocClass( afxSpellBookData, + "@brief A spellbook datablock.\n\n" + + "@ingroup afxMisc\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxSpellBookData::afxSpellBookData() +{ + spells_per_page = 12; + pages_per_book = 12; + dMemset(spells, 0, sizeof(spells)); + dMemset(rpg_spells, 0, sizeof(rpg_spells)); + + // marked true if datablock ids need to + // be converted into pointers + do_id_convert = false; +} + +#define myOffset(field) Offset(field, afxSpellBookData) + +void afxSpellBookData::initPersistFields() +{ + addField("spellsPerPage", TypeS8, myOffset(spells_per_page), + "..."); + addField("pagesPerBook", TypeS8, myOffset(pages_per_book), + "..."); + + addField("spells", TYPEID(), myOffset(spells), MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE, + "..."); + addField("rpgSpells", TYPEID(), myOffset(rpg_spells), MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE, + "..."); + + Parent::initPersistFields(); +} + +bool afxSpellBookData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) + { + if (do_id_convert) + { + for (S32 i = 0; i < pages_per_book*spells_per_page; i++) + { + SimObjectId db_id = SimObjectId((uintptr_t)rpg_spells[i]); + if (db_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(db_id, rpg_spells[i])) + { + Con::errorf(ConsoleLogEntry::General, + "afxSpellBookData::preload() -- bad datablockId: 0x%x (afxRPGMagicSpellData)", + db_id); + } + } + } + do_id_convert = false; + } + } + + return true; +} + +void afxSpellBookData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(spells_per_page); + stream->write(pages_per_book); + + for (S32 i = 0; i < pages_per_book*spells_per_page; i++) + writeDatablockID(stream, rpg_spells[i], packed); +} + +void afxSpellBookData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&spells_per_page); + stream->read(&pages_per_book); + + do_id_convert = true; + for (S32 i = 0; i < pages_per_book*spells_per_page; i++) + rpg_spells[i] = (afxRPGMagicSpellData*) readDatablockID(stream); +} + +DefineEngineMethod(afxSpellBookData, getPageSlotIndex, S32, (Point2I bookSlot),, + "...\n\n" + "@ingroup AFX") +{ + return object->getPageSlotIndex(bookSlot.x, bookSlot.y); +} + +DefineEngineMethod(afxSpellBookData, getCapacity, S32, (),, + "Get the capacity (total number of spell slots) in a spellbook.\n\n" + "@ingroup AFX") +{ + return object->spells_per_page*object->pages_per_book; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxSpellBook + +IMPLEMENT_CO_NETOBJECT_V1(afxSpellBook); + +ConsoleDocClass( afxSpellBook, + "@brief A spellbook object.\n\n" + + "@ingroup afxMisc\n" + "@ingroup AFX\n" +); + +afxSpellBook::afxSpellBook() +{ + mNetFlags.set(Ghostable | ScopeAlways); + mDataBlock = NULL; + all_spell_cooldown = 1.0f; +} + +afxSpellBook::~afxSpellBook() +{ +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +void afxSpellBook::initPersistFields() +{ + Parent::initPersistFields(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +void afxSpellBook::processTick(const Move* m) +{ + Parent::processTick(m); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +void afxSpellBook::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if (all_spell_cooldown < 1.0f) + { + all_spell_cooldown += dt/2.0f; + if (all_spell_cooldown > 1.0f) + all_spell_cooldown = 1.0f; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +bool afxSpellBook::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + scriptOnNewDataBlock(); + + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +bool afxSpellBook::onAdd() +{ + if (!Parent::onAdd()) + return(false); + + return(true); +} + +void afxSpellBook::onRemove() +{ + Parent::onRemove(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +U32 afxSpellBook::packUpdate(NetConnection * con, U32 mask, BitStream * stream) +{ + U32 retMask = Parent::packUpdate(con, mask, stream); + + if (stream->writeFlag(mask & InitialUpdateMask)) + { + } + + // AllSpellCooldown + if (stream->writeFlag(mask & AllSpellCooldownMask)) + { + } + + return(retMask); +} + +void afxSpellBook::unpackUpdate(NetConnection * con, BitStream * stream) +{ + Parent::unpackUpdate(con, stream); + + // InitialUpdate + if (stream->readFlag()) + { + } + + // AllSpellCooldown + if (stream->readFlag()) + { + all_spell_cooldown = 0.0f; + } +} + +#define SPELL_DATA_NOT_FOUND "\n** Spell data not found **\n\n\n\n" + +char* afxSpellBook::formatDesc(char* buffer, int len, S32 page, S32 slot) const +{ + S32 idx = mDataBlock->getPageSlotIndex(page, slot); + if (idx < 0 || !mDataBlock->rpg_spells[idx]) + return SPELL_DATA_NOT_FOUND; + + return mDataBlock->rpg_spells[idx]->formatDesc(buffer, len); +} + +const char* afxSpellBook::getSpellIcon(S32 page, S32 slot) const +{ + S32 idx = mDataBlock->getPageSlotIndex(page, slot); + if (idx < 0 || !mDataBlock->rpg_spells[idx]) + return 0; + + return mDataBlock->rpg_spells[idx]->icon_name; +} + +bool afxSpellBook::isPlaceholder(S32 page, S32 slot) const +{ + S32 idx = mDataBlock->getPageSlotIndex(page, slot); + if (idx < 0 || !mDataBlock->rpg_spells[idx]) + return false; + + return mDataBlock->rpg_spells[idx]->is_placeholder; +} + + +afxMagicSpellData* afxSpellBook::getSpellData(S32 page, S32 slot) +{ + S32 idx = mDataBlock->getPageSlotIndex(page, slot); + if (idx < 0 || !mDataBlock->spells[idx]) + return 0; + + return mDataBlock->spells[idx]; +} + +afxRPGMagicSpellData* afxSpellBook::getSpellRPGData(S32 page, S32 slot) +{ + S32 idx = mDataBlock->getPageSlotIndex(page, slot); + if (idx < 0 || !mDataBlock->rpg_spells[idx]) + return 0; + + return mDataBlock->rpg_spells[idx]; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxSpellBook::startAllSpellCooldown() +{ + //all_spell_cooldown = 0.0f; + setMaskBits(AllSpellCooldownMask); +} + +F32 afxSpellBook::getCooldownFactor(S32 page, S32 slot) +{ + return all_spell_cooldown; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod(afxSpellBook, getPageSlotIndex, S32, (Point2I bookSlot),, + "...\n\n" + "@ingroup AFX") +{ + return object->getPageSlotIndex(bookSlot.x, bookSlot.y); +} + +DefineEngineMethod(afxSpellBook, getSpellData, S32, (Point2I bookSlot),, + "Get spell datablock for spell stored at spellbook index, (page, slot).\n\n" + "@ingroup AFX") +{ + afxMagicSpellData* spell_data = object->getSpellData(bookSlot.x, bookSlot.y); + return (spell_data) ? spell_data->getId() : 0; +} + +DefineEngineMethod(afxSpellBook, getSpellRPGData, S32, (Point2I bookSlot),, + "Get spell RPG datablock for spell stored at spellbook index, (page, slot).\n\n" + "@ingroup AFX") +{ + afxRPGMagicSpellData* spell_data = object->getSpellRPGData(bookSlot.x, bookSlot.y); + return (spell_data) ? spell_data->getId() : 0; +} + +DefineEngineMethod(afxSpellBook, startAllSpellCooldown, void, (),, + "...\n\n" + "@ingroup AFX") +{ + object->startAllSpellCooldown(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + + + diff --git a/Engine/source/afx/afxSpellBook.h b/Engine/source/afx/afxSpellBook.h new file mode 100644 index 000000000..d802c37ce --- /dev/null +++ b/Engine/source/afx/afxSpellBook.h @@ -0,0 +1,142 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_SPELL_BOOK_H_ +#define _AFX_SPELL_BOOK_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "T3D/gameBase/gameBase.h" + +class afxSpellBookDefs +{ +public: + enum { + MAX_SPELLS_PER_PAGE = 12, + MAX_PAGES_PER_BOOK = 12 + }; +}; + +class afxMagicSpellData; +class afxRPGMagicSpellData; + +class afxSpellBookData : public GameBaseData, public afxSpellBookDefs +{ + typedef GameBaseData Parent; + + bool do_id_convert; + +public: + U8 spells_per_page; + U8 pages_per_book; + afxMagicSpellData* spells[MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE]; + afxRPGMagicSpellData* rpg_spells[MAX_PAGES_PER_BOOK*MAX_SPELLS_PER_PAGE]; + +public: + /*C*/ afxSpellBookData(); + + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + bool verifyPageSlot(S32 page, S32 slot); + S32 getPageSlotIndex(S32 page, S32 slot); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxSpellBookData); + DECLARE_CATEGORY("AFX"); +}; + +inline bool afxSpellBookData::verifyPageSlot(S32 page, S32 slot) +{ + return (page >= 0 && page < pages_per_book && slot >= 0 && slot < spells_per_page); +} + +inline S32 afxSpellBookData::getPageSlotIndex(S32 page, S32 slot) +{ + return (verifyPageSlot(page, slot)) ? page*spells_per_page + slot : -1; +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxMagicSpellData; +class afxSpellButton; + +class afxSpellBook : public GameBase, public afxSpellBookDefs +{ + typedef GameBase Parent; + + enum MaskBits + { + AllSpellCooldownMask = Parent::NextFreeMask << 0, + NextFreeMask = Parent::NextFreeMask << 1 + }; + +private: + afxSpellBookData* mDataBlock; + F32 all_spell_cooldown; + +public: + /*C*/ afxSpellBook(); + /*D*/ ~afxSpellBook(); + + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + virtual void processTick(const Move*); + virtual void advanceTime(F32 dt); + + virtual bool onAdd(); + virtual void onRemove(); + + virtual U32 packUpdate(NetConnection*, U32, BitStream*); + virtual void unpackUpdate(NetConnection*, BitStream*); + + static void initPersistFields(); + + S32 getPageSlotIndex(S32 page, S32 slot); + char* formatDesc(char* buffer, int len, S32 page, S32 slot) const; + const char* getSpellIcon(S32 page, S32 slot) const; + bool isPlaceholder(S32 page, S32 slot) const; + afxMagicSpellData* getSpellData(S32 page, S32 slot); + afxRPGMagicSpellData* getSpellRPGData(S32 page, S32 slot); + + void startAllSpellCooldown(); + F32 getCooldownFactor(S32 page, S32 slot); + + DECLARE_CONOBJECT(afxSpellBook); + DECLARE_CATEGORY("AFX"); +}; + +inline S32 afxSpellBook::getPageSlotIndex(S32 page, S32 slot) +{ + return (mDataBlock) ? mDataBlock->getPageSlotIndex(page, slot) : -1; +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_SPELL_BOOK_H_ diff --git a/Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.cpp b/Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.cpp new file mode 100644 index 000000000..442751a1f --- /dev/null +++ b/Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.cpp @@ -0,0 +1,311 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "materials/shaderData.h" +#include "gfx/gfxTransformSaver.h" +#include "scene/sceneRenderState.h" +#include "collision/concretePolyList.h" +#include "T3D/tsStatic.h" +#include "gfx/primBuilder.h" + +#include "afx/ce/afxZodiacMgr.h" +#include "afx/afxZodiacGroundPlaneRenderer_T3D.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +const RenderInstType afxZodiacGroundPlaneRenderer::RIT_GroundPlaneZodiac("GroundPlaneZodiac"); + +afxZodiacGroundPlaneRenderer* afxZodiacGroundPlaneRenderer::master = 0; + +IMPLEMENT_CONOBJECT(afxZodiacGroundPlaneRenderer); + +ConsoleDocClass( afxZodiacGroundPlaneRenderer, + "@brief A render bin for zodiac rendering on GroundPlane objects.\n\n" + + "This bin renders instances of AFX zodiac effects onto GroundPlane surfaces.\n\n" + + "@ingroup RenderBin\n" + "@ingroup AFX\n" +); + +afxZodiacGroundPlaneRenderer::afxZodiacGroundPlaneRenderer() +: RenderBinManager(RIT_GroundPlaneZodiac, 1.0f, 1.0f) +{ + if (!master) + master = this; + shader_initialized = false; +} + +afxZodiacGroundPlaneRenderer::afxZodiacGroundPlaneRenderer(F32 renderOrder, F32 processAddOrder) +: RenderBinManager(RIT_GroundPlaneZodiac, renderOrder, processAddOrder) +{ + if (!master) + master = this; + shader_initialized = false; +} + +afxZodiacGroundPlaneRenderer::~afxZodiacGroundPlaneRenderer() +{ + if (this == master) + master = 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxZodiacGroundPlaneRenderer::initShader() +{ + if (shader_initialized) + return; + + shader_initialized = true; + + shader_consts = 0; + norm_norefl_zb_SB = norm_refl_zb_SB; + add_norefl_zb_SB = add_refl_zb_SB; + sub_norefl_zb_SB = sub_refl_zb_SB; + + zodiac_shader = afxZodiacMgr::getGroundPlaneZodiacShader(); + if (!zodiac_shader) + return; + + GFXStateBlockDesc d; + + d.cullDefined = true; + d.ffLighting = false; + d.blendDefined = true; + d.blendEnable = true; + d.zDefined = false; + d.zEnable = true; + d.zWriteEnable = false; + d.zFunc = GFXCmpLessEqual; + d.zSlopeBias = 0; + d.alphaDefined = true; + d.alphaTestEnable = true; + d.alphaTestRef = 0; + d.alphaTestFunc = GFXCmpGreater; + d.samplersDefined = true; + d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + + // normal + d.blendSrc = GFXBlendSrcAlpha; + d.blendDest = GFXBlendInvSrcAlpha; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + norm_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + norm_refl_zb_SB = GFX->createStateBlock(d); + + // additive + d.blendSrc = GFXBlendSrcAlpha; + d.blendDest = GFXBlendOne; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + add_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + add_refl_zb_SB = GFX->createStateBlock(d); + + // subtractive + d.blendSrc = GFXBlendZero; + d.blendDest = GFXBlendInvSrcColor; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + sub_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + sub_refl_zb_SB = GFX->createStateBlock(d); + + shader_consts = zodiac_shader->getShader()->allocConstBuffer(); + projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView"); + color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor"); +} + +void afxZodiacGroundPlaneRenderer::clear() +{ + Parent::clear(); + groundPlane_zodiacs.clear(); +} + +void afxZodiacGroundPlaneRenderer::addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, const GroundPlane* gp, F32 camDist) +{ + groundPlane_zodiacs.increment(); + GroundPlaneZodiacElem& elem = groundPlane_zodiacs.last(); + + elem.gp = gp; + elem.zode_idx = zode_idx; + elem.ang = ang; + elem.camDist = camDist; +} + +afxZodiacGroundPlaneRenderer* afxZodiacGroundPlaneRenderer::getMaster() +{ + if (!master) + master = new afxZodiacGroundPlaneRenderer; + return master; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +GFXStateBlock* afxZodiacGroundPlaneRenderer::chooseStateBlock(U32 blend, bool isReflectPass) +{ + GFXStateBlock* sb = 0; + + switch (blend) + { + case afxZodiacData::BLEND_ADDITIVE: + sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB; + break; + case afxZodiacData::BLEND_SUBTRACTIVE: + sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB; + break; + default: // afxZodiacData::BLEND_NORMAL: + sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB; + break; + } + + return sb; +} + +void afxZodiacGroundPlaneRenderer::render(SceneRenderState* state) +{ + PROFILE_SCOPE(afxRenderZodiacGroundPlaneMgr_render); + + // Early out if no ground-plane zodiacs to draw. + if (groundPlane_zodiacs.size() == 0) + return; + + initShader(); + if (!zodiac_shader) + return; + + bool is_reflect_pass = state->isReflectPass(); + + // Automagically save & restore our viewport and transforms. + GFXTransformSaver saver; + + MatrixF proj = GFX->getProjectionMatrix(); + + // Set up world transform + MatrixF world = GFX->getWorldMatrix(); + proj.mul(world); + shader_consts->set(projection_sc, proj); + + //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + // RENDER EACH ZODIAC + // + for (S32 zz = 0; zz < groundPlane_zodiacs.size(); zz++) + { + GroundPlaneZodiacElem& elem = groundPlane_zodiacs[zz]; + + afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::terr_zodes[elem.zode_idx]; + if (!zode) + continue; + + if (is_reflect_pass) + { + //if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0) + continue; + } + else + { + if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0) + continue; + } + + F32 fadebias = zode->calcDistanceFadeBias(elem.camDist); + if (fadebias < 0.01f) + continue; + + F32 cos_ang = mCos(elem.ang); + F32 sin_ang = mSin(elem.ang); + + GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass); + + GFX->setShader(zodiac_shader->getShader()); + GFX->setStateBlock(sb); + GFX->setShaderConstBuffer(shader_consts); + + // set the texture + GFX->setTexture(0, *zode->txr); + LinearColorF zode_color = (LinearColorF)zode->color; + zode_color.alpha *= fadebias; + shader_consts->set(color_sc, zode_color); + + F32 rad_xy = zode->radius_xy; + F32 inv_radius = 1.0f/rad_xy; + F32 offset_xy = mSqrt(2*rad_xy*rad_xy); + + F32 zx = zode->pos.x; + F32 zy = zode->pos.y; + F32 z = 0.00001f; + + Point3F verts[4]; + verts[0].set(zx+offset_xy, zy+offset_xy, z); + verts[1].set(zx-offset_xy, zy+offset_xy, z); + verts[2].set(zx-offset_xy, zy-offset_xy, z); + verts[3].set(zx+offset_xy, zy-offset_xy, z); + + S32 vertind[6]; + vertind[0] = 2; + vertind[1] = 1; + vertind[2] = 0; + vertind[3] = 3; + vertind[4] = 2; + vertind[5] = 0; + + PrimBuild::begin(GFXTriangleList, 6); + for (U32 i = 0; i < 2; i++) + { + for (U32 j = 0; j < 3; j++) + { + const Point3F& vtx = verts[vertind[i*3+j]]; + + // compute UV + F32 u1 = (vtx.x - zode->pos.x)*inv_radius; + F32 v1 = (vtx.y - zode->pos.y)*inv_radius; + F32 ru1 = u1*cos_ang - v1*sin_ang; + F32 rv1 = u1*sin_ang + v1*cos_ang; + + F32 uu = (ru1 + 1.0f)/2.0f; + F32 vv = 1.0f - (rv1 + 1.0f)/2.0f; + + PrimBuild::texCoord2f(uu, vv); + PrimBuild::vertex3fv(vtx); + } + } + PrimBuild::end(false); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.h b/Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.h new file mode 100644 index 000000000..862c2bc86 --- /dev/null +++ b/Engine/source/afx/afxZodiacGroundPlaneRenderer_T3D.h @@ -0,0 +1,91 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ZODIAC_GROUNDPLANE_RENDERER_H_ +#define _AFX_ZODIAC_GROUNDPLANE_RENDERER_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "renderInstance/renderBinManager.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class ConcretePolyList; +class GroundPlane; + +class afxZodiacGroundPlaneRenderer : public RenderBinManager +{ + typedef RenderBinManager Parent; + + struct GroundPlaneZodiacElem + { + const GroundPlane* gp; + U32 zode_idx; + F32 ang; + F32 camDist; + }; + + Vector groundPlane_zodiacs; + static afxZodiacGroundPlaneRenderer* master; + + GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB; + GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB; + GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB; + + ShaderData* zodiac_shader; + GFXShaderConstBufferRef shader_consts; + GFXShaderConstHandle* projection_sc; + GFXShaderConstHandle* color_sc; + + bool shader_initialized; + + GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass); + +public: + static const RenderInstType RIT_GroundPlaneZodiac; + + /*C*/ afxZodiacGroundPlaneRenderer(); + /*C*/ afxZodiacGroundPlaneRenderer(F32 renderOrder, F32 processAddOrder); + /*D*/ ~afxZodiacGroundPlaneRenderer(); + + // RenderBinManager + virtual void sort(){} // don't sort them + virtual void clear(); + + void initShader(); + void addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, const GroundPlane*, F32 camDist); + + virtual void render(SceneRenderState* state); + + static afxZodiacGroundPlaneRenderer* getMaster(); + + // ConsoleObject + DECLARE_CONOBJECT(afxZodiacGroundPlaneRenderer); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ZODIAC_GROUNDPLANE_RENDERER_H_ diff --git a/Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.cpp b/Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.cpp new file mode 100644 index 000000000..cafb3dca7 --- /dev/null +++ b/Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.cpp @@ -0,0 +1,302 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "materials/shaderData.h" +#include "gfx/gfxTransformSaver.h" +#include "scene/sceneRenderState.h" +#include "collision/concretePolyList.h" +#include "T3D/tsStatic.h" +#include "gfx/primBuilder.h" + +#include "afx/ce/afxZodiacMgr.h" +#include "afx/afxZodiacMeshRoadRenderer_T3D.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +const RenderInstType afxZodiacMeshRoadRenderer::RIT_MeshRoadZodiac("MeshRoadZodiac"); + +afxZodiacMeshRoadRenderer* afxZodiacMeshRoadRenderer::master = 0; + +IMPLEMENT_CONOBJECT(afxZodiacMeshRoadRenderer); + +ConsoleDocClass( afxZodiacMeshRoadRenderer, + "@brief A render bin for zodiac rendering on MeshRoad objects.\n\n" + + "This bin renders instances of AFX zodiac effects onto MeshRoad surfaces.\n\n" + + "@ingroup RenderBin\n" + "@ingroup AFX\n" +); + +afxZodiacMeshRoadRenderer::afxZodiacMeshRoadRenderer() +: RenderBinManager(RIT_MeshRoadZodiac, 1.0f, 1.0f) +{ + if (!master) + master = this; + shader_initialized = false; +} + +afxZodiacMeshRoadRenderer::afxZodiacMeshRoadRenderer(F32 renderOrder, F32 processAddOrder) +: RenderBinManager(RIT_MeshRoadZodiac, renderOrder, processAddOrder) +{ + if (!master) + master = this; + shader_initialized = false; +} + +afxZodiacMeshRoadRenderer::~afxZodiacMeshRoadRenderer() +{ + if (this == master) + master = 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxZodiacMeshRoadRenderer::initShader() +{ + if (shader_initialized) + return; + + shader_initialized = true; + + shader_consts = 0; + norm_norefl_zb_SB = norm_refl_zb_SB; + add_norefl_zb_SB = add_refl_zb_SB; + sub_norefl_zb_SB = sub_refl_zb_SB; + + zodiac_shader = afxZodiacMgr::getMeshRoadZodiacShader(); + if (!zodiac_shader) + return; + + GFXStateBlockDesc d; + + d.cullDefined = true; + d.ffLighting = false; + d.blendDefined = true; + d.blendEnable = true; + d.zDefined = false; + d.zEnable = true; + d.zWriteEnable = false; + d.zFunc = GFXCmpLessEqual; + d.zSlopeBias = 0; + d.alphaDefined = true; + d.alphaTestEnable = true; + d.alphaTestRef = 0; + d.alphaTestFunc = GFXCmpGreater; + d.samplersDefined = true; + d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + + // normal + d.blendSrc = GFXBlendSrcAlpha; + d.blendDest = GFXBlendInvSrcAlpha; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + norm_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + norm_refl_zb_SB = GFX->createStateBlock(d); + + // additive + d.blendSrc = GFXBlendSrcAlpha; + d.blendDest = GFXBlendOne; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + add_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + add_refl_zb_SB = GFX->createStateBlock(d); + + // subtractive + d.blendSrc = GFXBlendZero; + d.blendDest = GFXBlendInvSrcColor; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + sub_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + sub_refl_zb_SB = GFX->createStateBlock(d); + + shader_consts = zodiac_shader->getShader()->allocConstBuffer(); + projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView"); + color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor"); +} + +void afxZodiacMeshRoadRenderer::clear() +{ + Parent::clear(); + for (S32 i = 0; i < meshRoad_zodiacs.size(); i++) + if (meshRoad_zodiacs[i].polys) + delete meshRoad_zodiacs[i].polys; + meshRoad_zodiacs.clear(); +} + +void afxZodiacMeshRoadRenderer::addZodiac(U32 zode_idx, ConcretePolyList* polys, const Point3F& pos, F32 ang, const MeshRoad* road, F32 camDist) +{ + meshRoad_zodiacs.increment(); + MeshRoadZodiacElem& elem = meshRoad_zodiacs.last(); + + elem.road = road; + elem.polys = polys; + elem.zode_idx = zode_idx; + elem.ang = ang; + elem.camDist = camDist; +} + +afxZodiacMeshRoadRenderer* afxZodiacMeshRoadRenderer::getMaster() +{ + if (!master) + master = new afxZodiacMeshRoadRenderer; + return master; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +GFXStateBlock* afxZodiacMeshRoadRenderer::chooseStateBlock(U32 blend, bool isReflectPass) +{ + GFXStateBlock* sb = 0; + + switch (blend) + { + case afxZodiacData::BLEND_ADDITIVE: + sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB; + break; + case afxZodiacData::BLEND_SUBTRACTIVE: + sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB; + break; + default: // afxZodiacData::BLEND_NORMAL: + sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB; + break; + } + + return sb; +} + +void afxZodiacMeshRoadRenderer::render(SceneRenderState* state) +{ + PROFILE_SCOPE(afxRenderZodiacMeshRoadMgr_render); + + // Early out if no ground-plane zodiacs to draw. + if (meshRoad_zodiacs.size() == 0) + return; + + initShader(); + if (!zodiac_shader) + return; + + bool is_reflect_pass = state->isReflectPass(); + + // Automagically save & restore our viewport and transforms. + GFXTransformSaver saver; + + MatrixF proj = GFX->getProjectionMatrix(); + + // Set up world transform + MatrixF world = GFX->getWorldMatrix(); + proj.mul(world); + shader_consts->set(projection_sc, proj); + + //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + // RENDER EACH ZODIAC + // + for (S32 zz = 0; zz < meshRoad_zodiacs.size(); zz++) + { + MeshRoadZodiacElem& elem = meshRoad_zodiacs[zz]; + + afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::terr_zodes[elem.zode_idx]; + if (!zode) + continue; + + if (is_reflect_pass) + { + if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0) + continue; + } + else + { + if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0) + continue; + } + + F32 fadebias = zode->calcDistanceFadeBias(elem.camDist); + if (fadebias < 0.01f) + continue; + + F32 cos_ang = mCos(elem.ang); + F32 sin_ang = mSin(elem.ang); + + GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass); + + GFX->setShader(zodiac_shader->getShader()); + GFX->setStateBlock(sb); + GFX->setShaderConstBuffer(shader_consts); + + // set the texture + GFX->setTexture(0, *zode->txr); + LinearColorF zode_color = (LinearColorF)zode->color; + zode_color.alpha *= fadebias; + shader_consts->set(color_sc, zode_color); + + F32 inv_radius = 1.0f/zode->radius_xy; + + PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size()); + for (U32 i = 0; i < elem.polys->mPolyList.size(); i++) + { + ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i]; + + S32 vertind[3]; + vertind[0] = elem.polys->mIndexList[poly->vertexStart]; + vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1]; + vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2]; + + for (U32 j = 0; j < 3; j++) + { + Point3F vtx = elem.polys->mVertexList[vertind[j]]; + + // compute UV + F32 u1 = (vtx.x - zode->pos.x)*inv_radius; + F32 v1 = (vtx.y - zode->pos.y)*inv_radius; + F32 ru1 = u1*cos_ang - v1*sin_ang; + F32 rv1 = u1*sin_ang + v1*cos_ang; + + F32 uu = (ru1 + 1.0f)/2.0f; + F32 vv = 1.0f - (rv1 + 1.0f)/2.0f; + + PrimBuild::texCoord2f(uu, vv); + PrimBuild::vertex3fv(vtx); + } + } + PrimBuild::end(false); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.h b/Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.h new file mode 100644 index 000000000..bc8477bef --- /dev/null +++ b/Engine/source/afx/afxZodiacMeshRoadRenderer_T3D.h @@ -0,0 +1,92 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ZODIAC_MESHROAD_RENDERER_H_ +#define _AFX_ZODIAC_MESHROAD_RENDERER_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "renderInstance/renderBinManager.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class ConcretePolyList; +class MeshRoad; + +class afxZodiacMeshRoadRenderer : public RenderBinManager +{ + typedef RenderBinManager Parent; + + struct MeshRoadZodiacElem + { + const MeshRoad* road; + U32 zode_idx; + ConcretePolyList* polys; + F32 ang; + F32 camDist; + }; + + Vector meshRoad_zodiacs; + static afxZodiacMeshRoadRenderer* master; + + GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB; + GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB; + GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB; + + ShaderData* zodiac_shader; + GFXShaderConstBufferRef shader_consts; + GFXShaderConstHandle* projection_sc; + GFXShaderConstHandle* color_sc; + + bool shader_initialized; + + GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass); + +public: + static const RenderInstType RIT_MeshRoadZodiac; + + /*C*/ afxZodiacMeshRoadRenderer(); + /*C*/ afxZodiacMeshRoadRenderer(F32 renderOrder, F32 processAddOrder); + /*D*/ ~afxZodiacMeshRoadRenderer(); + + // RenderBinManager + virtual void sort(){} // don't sort them + virtual void clear(); + + void initShader(); + void addZodiac(U32 zode_idx, ConcretePolyList*, const Point3F& pos, F32 ang, const MeshRoad*, F32 camDist); + + virtual void render(SceneRenderState*); + + static afxZodiacMeshRoadRenderer* getMaster(); + + // ConsoleObject + DECLARE_CONOBJECT(afxZodiacMeshRoadRenderer); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ZODIAC_MESHROAD_RENDERER_H_ diff --git a/Engine/source/afx/afxZodiacPolysoupRenderer_T3D.cpp b/Engine/source/afx/afxZodiacPolysoupRenderer_T3D.cpp new file mode 100644 index 000000000..84d55a092 --- /dev/null +++ b/Engine/source/afx/afxZodiacPolysoupRenderer_T3D.cpp @@ -0,0 +1,420 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "materials/shaderData.h" +#include "gfx/gfxTransformSaver.h" +#include "scene/sceneRenderState.h" +#include "collision/concretePolyList.h" +#include "T3D/tsStatic.h" +#include "gfx/primBuilder.h" + +#include "afx/ce/afxZodiacMgr.h" +#include "afx/afxZodiacPolysoupRenderer_T3D.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +const RenderInstType afxZodiacPolysoupRenderer::RIT_PolysoupZodiac("PolysoupZodiac"); + +afxZodiacPolysoupRenderer* afxZodiacPolysoupRenderer::master = 0; + +IMPLEMENT_CONOBJECT(afxZodiacPolysoupRenderer); + +ConsoleDocClass( afxZodiacPolysoupRenderer, + "@brief A render bin for zodiac rendering on polysoup TSStatic objects.\n\n" + + "This bin renders instances of AFX zodiac effects onto polysoup TSStatic surfaces.\n\n" + + "@ingroup RenderBin\n" + "@ingroup AFX\n" +); + +afxZodiacPolysoupRenderer::afxZodiacPolysoupRenderer() +: RenderBinManager(RIT_PolysoupZodiac, 1.0f, 1.0f) +{ + if (!master) + master = this; + shader_initialized = false; +} + +afxZodiacPolysoupRenderer::afxZodiacPolysoupRenderer(F32 renderOrder, F32 processAddOrder) +: RenderBinManager(RIT_PolysoupZodiac, renderOrder, processAddOrder) +{ + if (!master) + master = this; + shader_initialized = false; +} + +afxZodiacPolysoupRenderer::~afxZodiacPolysoupRenderer() +{ + if (this == master) + master = 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxZodiacPolysoupRenderer::initShader() +{ + if (shader_initialized) + return; + + shader_initialized = true; + + shader_consts = 0; + norm_norefl_zb_SB = norm_refl_zb_SB; + add_norefl_zb_SB = add_refl_zb_SB; + sub_norefl_zb_SB = sub_refl_zb_SB; + + zodiac_shader = afxZodiacMgr::getPolysoupZodiacShader(); + if (!zodiac_shader) + return; + + GFXStateBlockDesc d; + + d.cullDefined = true; + d.ffLighting = false; + d.blendDefined = true; + d.blendEnable = true; + d.zDefined = false; + d.zEnable = true; + d.zWriteEnable = false; + d.zFunc = GFXCmpLessEqual; + d.zSlopeBias = 0; + d.alphaDefined = true; + d.alphaTestEnable = true; + d.alphaTestRef = 0; + d.alphaTestFunc = GFXCmpGreater; + d.samplersDefined = true; + d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + + // normal + d.blendSrc = GFXBlendSrcAlpha; + d.blendDest = GFXBlendInvSrcAlpha; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + norm_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + norm_refl_zb_SB = GFX->createStateBlock(d); + + // additive + d.blendSrc = GFXBlendSrcAlpha; + d.blendDest = GFXBlendOne; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + add_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + add_refl_zb_SB = GFX->createStateBlock(d); + + // subtractive + d.blendSrc = GFXBlendZero; + d.blendDest = GFXBlendInvSrcColor; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + sub_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sPolysoupZodiacZBias; + sub_refl_zb_SB = GFX->createStateBlock(d); + + shader_consts = zodiac_shader->getShader()->allocConstBuffer(); + projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView"); + color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor"); +} + +void afxZodiacPolysoupRenderer::clear() +{ + Parent::clear(); + for (S32 i = 0; i < polysoup_zodes.size(); i++) + if (polysoup_zodes[i].polys) + delete polysoup_zodes[i].polys; + polysoup_zodes.clear(); +} + +void afxZodiacPolysoupRenderer::addZodiac(U32 zode_idx, ConcretePolyList* polys, const Point3F& pos, F32 ang, const TSStatic* tss, F32 camDist) +{ + polysoup_zodes.increment(); + PolysoupZodiacElem& elem = polysoup_zodes.last(); + + elem.tss = tss; + elem.polys = polys; + elem.zode_idx = zode_idx; + elem.ang = ang; + elem.camDist = camDist; +} + +afxZodiacPolysoupRenderer* afxZodiacPolysoupRenderer::getMaster() +{ + if (!master) + master = new afxZodiacPolysoupRenderer; + return master; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +GFXStateBlock* afxZodiacPolysoupRenderer::chooseStateBlock(U32 blend, bool isReflectPass) +{ + GFXStateBlock* sb = 0; + + switch (blend) + { + case afxZodiacData::BLEND_ADDITIVE: + sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB; + break; + case afxZodiacData::BLEND_SUBTRACTIVE: + sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB; + break; + default: // afxZodiacData::BLEND_NORMAL: + sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB; + break; + } + + return sb; +} + +void afxZodiacPolysoupRenderer::render(SceneRenderState* state) +{ + PROFILE_SCOPE(afxRenderZodiacPolysoupMgr_render); + + // Early out if no polysoup zodiacs to draw. + if (polysoup_zodes.size() == 0) + return; + + initShader(); + if (!zodiac_shader) + return; + + bool is_reflect_pass = state->isReflectPass(); + + // Automagically save & restore our viewport and transforms. + GFXTransformSaver saver; + + MatrixF proj = GFX->getProjectionMatrix(); + + // Set up world transform + MatrixF world = GFX->getWorldMatrix(); + proj.mul(world); + shader_consts->set(projection_sc, proj); + + //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + // RENDER EACH ZODIAC + // + for (S32 zz = 0; zz < polysoup_zodes.size(); zz++) + { + PolysoupZodiacElem& elem = polysoup_zodes[zz]; + + afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::inter_zodes[elem.zode_idx]; + if (!zode) + continue; + + if (is_reflect_pass) + { + if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0) + continue; + } + else + { + if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0) + continue; + } + + F32 fadebias = zode->calcDistanceFadeBias(elem.camDist); + if (fadebias < 0.01f) + continue; + + F32 cos_ang = mCos(elem.ang); + F32 sin_ang = mSin(elem.ang); + + GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass); + + GFX->setShader(zodiac_shader->getShader()); + GFX->setStateBlock(sb); + GFX->setShaderConstBuffer(shader_consts); + + // set the texture + GFX->setTexture(0, *zode->txr); + LinearColorF zode_color = (LinearColorF)zode->color; + zode_color.alpha *= fadebias; + shader_consts->set(color_sc, zode_color); + + F32 inv_radius = 1.0f/zode->radius_xy; + + // FILTER USING GRADIENT RANGE + if ((zode->zflags & afxZodiacData::USE_GRADE_RANGE) != 0) + { + bool skip_oob; + F32 grade_min, grade_max; + if (elem.tss->mHasGradients && ((zode->zflags & afxZodiacData::PREFER_DEST_GRADE) != 0)) + { + skip_oob = (elem.tss->mInvertGradientRange == false); + grade_min = elem.tss->mGradientRange.x; + grade_max = elem.tss->mGradientRange.y; + } + else + { + skip_oob = ((zode->zflags & afxZodiacData::INVERT_GRADE_RANGE) == 0); + grade_min = zode->grade_range.x; + grade_max = zode->grade_range.y; + } + + PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size()); + for (U32 i = 0; i < elem.polys->mPolyList.size(); i++) + { + ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i]; + + const PlaneF& plane = poly->plane; + + bool oob = (plane.z > grade_max || plane.z < grade_min); + if (oob == skip_oob) + continue; + + S32 vertind[3]; + vertind[0] = elem.polys->mIndexList[poly->vertexStart]; + vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1]; + vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2]; + + for (U32 j = 0; j < 3; j++) + { + Point3F vtx = elem.polys->mVertexList[vertind[j]]; + + // compute UV + F32 u1 = (vtx.x - zode->pos.x)*inv_radius; + F32 v1 = (vtx.y - zode->pos.y)*inv_radius; + F32 ru1 = u1*cos_ang - v1*sin_ang; + F32 rv1 = u1*sin_ang + v1*cos_ang; + + F32 uu = (ru1 + 1.0f)/2.0f; + F32 vv = 1.0f - (rv1 + 1.0f)/2.0f; + + PrimBuild::texCoord2f(uu, vv); + PrimBuild::vertex3fv(vtx); + } + } + PrimBuild::end(false); + } + + // FILTER USING OTHER FILTERS + else if (zode->zflags & afxZodiacData::INTERIOR_FILTERS) + { + PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size()); + for (U32 i = 0; i < elem.polys->mPolyList.size(); i++) + { + ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i]; + + const PlaneF& plane = poly->plane; + if (zode->zflags & afxZodiacData::INTERIOR_HORIZ_ONLY) + { + if (!plane.isHorizontal()) + continue; + + if (zode->zflags & afxZodiacData::INTERIOR_BACK_IGNORE) + { + if (plane.whichSide(zode->pos) == PlaneF::Back) + continue; + } + } + else + { + if (zode->zflags & afxZodiacData::INTERIOR_VERT_IGNORE) + { + if (plane.isVertical()) + continue; + } + + if (zode->zflags & afxZodiacData::INTERIOR_BACK_IGNORE) + { + if (plane.whichSide(zode->pos) == PlaneF::Back) + continue; + } + } + + S32 vertind[3]; + vertind[0] = elem.polys->mIndexList[poly->vertexStart]; + vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1]; + vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2]; + + for (U32 j = 0; j < 3; j++) + { + Point3F vtx = elem.polys->mVertexList[vertind[j]]; + + // compute UV + F32 u1 = (vtx.x - zode->pos.x)*inv_radius; + F32 v1 = (vtx.y - zode->pos.y)*inv_radius; + F32 ru1 = u1*cos_ang - v1*sin_ang; + F32 rv1 = u1*sin_ang + v1*cos_ang; + + F32 uu = (ru1 + 1.0f)/2.0f; + F32 vv = 1.0f - (rv1 + 1.0f)/2.0f; + + PrimBuild::texCoord2f(uu, vv); + PrimBuild::vertex3fv(vtx); + } + } + PrimBuild::end(false); + } + + // NO FILTERING + else + { + PrimBuild::begin(GFXTriangleList, 3*elem.polys->mPolyList.size()); + for (U32 i = 0; i < elem.polys->mPolyList.size(); i++) + { + ConcretePolyList::Poly* poly = &elem.polys->mPolyList[i]; + + S32 vertind[3]; + vertind[0] = elem.polys->mIndexList[poly->vertexStart]; + vertind[1] = elem.polys->mIndexList[poly->vertexStart + 1]; + vertind[2] = elem.polys->mIndexList[poly->vertexStart + 2]; + + for (U32 j = 0; j < 3; j++) + { + Point3F vtx = elem.polys->mVertexList[vertind[j]]; + + // compute UV + F32 u1 = (vtx.x - zode->pos.x)*inv_radius; + F32 v1 = (vtx.y - zode->pos.y)*inv_radius; + F32 ru1 = u1*cos_ang - v1*sin_ang; + F32 rv1 = u1*sin_ang + v1*cos_ang; + + F32 uu = (ru1 + 1.0f)/2.0f; + F32 vv = 1.0f - (rv1 + 1.0f)/2.0f; + + PrimBuild::texCoord2f(uu, vv); + PrimBuild::vertex3fv(vtx); + } + } + PrimBuild::end(false); + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/afxZodiacPolysoupRenderer_T3D.h b/Engine/source/afx/afxZodiacPolysoupRenderer_T3D.h new file mode 100644 index 000000000..a01dffd34 --- /dev/null +++ b/Engine/source/afx/afxZodiacPolysoupRenderer_T3D.h @@ -0,0 +1,92 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ZODIAC_POLYSOUP_RENDERER_H_ +#define _AFX_ZODIAC_POLYSOUP_RENDERER_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "renderInstance/renderBinManager.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class ConcretePolyList; +class TSStatic; + +class afxZodiacPolysoupRenderer : public RenderBinManager +{ + typedef RenderBinManager Parent; + + struct PolysoupZodiacElem + { + const TSStatic* tss; + U32 zode_idx; + ConcretePolyList* polys; + F32 ang; + F32 camDist; + }; + + Vector polysoup_zodes; + static afxZodiacPolysoupRenderer* master; + + GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB; + GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB; + GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB; + + ShaderData* zodiac_shader; + GFXShaderConstBufferRef shader_consts; + GFXShaderConstHandle* projection_sc; + GFXShaderConstHandle* color_sc; + + bool shader_initialized; + + GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass); + +public: + static const RenderInstType RIT_PolysoupZodiac; + + /*C*/ afxZodiacPolysoupRenderer(); + /*C*/ afxZodiacPolysoupRenderer(F32 renderOrder, F32 processAddOrder); + /*D*/ ~afxZodiacPolysoupRenderer(); + + // RenderBinManager + virtual void sort(){} // don't sort them + virtual void clear(); + + void initShader(); + void addZodiac(U32 zode_idx, ConcretePolyList*, const Point3F& pos, F32 ang, const TSStatic*, F32 camDist); + + virtual void render(SceneRenderState*); + + static afxZodiacPolysoupRenderer* getMaster(); + + // ConsoleObject + DECLARE_CONOBJECT(afxZodiacPolysoupRenderer); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ZODIAC_POLYSOUP_RENDERER_H_ diff --git a/Engine/source/afx/afxZodiacTerrainRenderer_T3D.cpp b/Engine/source/afx/afxZodiacTerrainRenderer_T3D.cpp new file mode 100644 index 000000000..651a0fad0 --- /dev/null +++ b/Engine/source/afx/afxZodiacTerrainRenderer_T3D.cpp @@ -0,0 +1,344 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "materials/shaderData.h" +#include "gfx/gfxTransformSaver.h" +#include "scene/sceneRenderState.h" +#include "terrain/terrData.h" +#include "terrain/terrCell.h" + +#include "gfx/primBuilder.h" + +#include "afx/ce/afxZodiacMgr.h" +#include "afx/afxZodiacTerrainRenderer_T3D.h" +#include "afx/util/afxTriBoxCheck2D_T3D.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class TerrCellSpy : public TerrCell +{ +public: + static const U32 getMinCellSize() { return smMinCellSize; } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +const RenderInstType afxZodiacTerrainRenderer::RIT_TerrainZodiac("TerrainZodiac"); + +afxZodiacTerrainRenderer* afxZodiacTerrainRenderer::master = 0; + +IMPLEMENT_CONOBJECT(afxZodiacTerrainRenderer); + +ConsoleDocClass( afxZodiacTerrainRenderer, + "@brief A render bin for zodiac rendering on Terrain objects.\n\n" + + "This bin renders instances of AFX zodiac effects onto Terrain surfaces.\n\n" + + "@ingroup RenderBin\n" + "@ingroup AFX\n" +); + +afxZodiacTerrainRenderer::afxZodiacTerrainRenderer() +: RenderBinManager(RIT_TerrainZodiac, 1.0f, 1.0f) +{ + if (!master) + master = this; + shader_initialized = false; +} + +afxZodiacTerrainRenderer::afxZodiacTerrainRenderer(F32 renderOrder, F32 processAddOrder) +: RenderBinManager(RIT_TerrainZodiac, renderOrder, processAddOrder) +{ + if (!master) + master = this; + shader_initialized = false; +} + +afxZodiacTerrainRenderer::~afxZodiacTerrainRenderer() +{ + if (this == master) + master = 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxZodiacTerrainRenderer::initShader() +{ + if (shader_initialized) + return; + + shader_initialized = true; + + shader_consts = 0; + norm_norefl_zb_SB = norm_refl_zb_SB; + add_norefl_zb_SB = add_refl_zb_SB; + sub_norefl_zb_SB = sub_refl_zb_SB; + + zodiac_shader = afxZodiacMgr::getTerrainZodiacShader(); + if (!zodiac_shader) + return; + + GFXStateBlockDesc d; + + d.cullDefined = true; + d.ffLighting = false; + d.blendDefined = true; + d.blendEnable = true; + d.zDefined = false; + d.zEnable = true; + d.zWriteEnable = false; + d.zFunc = GFXCmpLessEqual; + d.zSlopeBias = 0; + d.alphaDefined = true; + d.alphaTestEnable = true; + d.alphaTestRef = 0; + d.alphaTestFunc = GFXCmpGreater; + d.samplersDefined = true; + d.samplers[0] = GFXSamplerStateDesc::getClampLinear(); + + // normal + d.blendSrc = GFXBlendSrcAlpha; + d.blendDest = GFXBlendInvSrcAlpha; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sTerrainZodiacZBias; + norm_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sTerrainZodiacZBias; + norm_refl_zb_SB = GFX->createStateBlock(d); + + // additive + d.blendSrc = GFXBlendSrcAlpha; + d.blendDest = GFXBlendOne; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sTerrainZodiacZBias; + add_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sTerrainZodiacZBias; + add_refl_zb_SB = GFX->createStateBlock(d); + + // subtractive + d.blendSrc = GFXBlendZero; + d.blendDest = GFXBlendInvSrcColor; + // + d.cullMode = GFXCullCCW; + d.zBias = arcaneFX::sTerrainZodiacZBias; + sub_norefl_zb_SB = GFX->createStateBlock(d); + // + d.cullMode = GFXCullCW; + d.zBias = arcaneFX::sTerrainZodiacZBias; + sub_refl_zb_SB = GFX->createStateBlock(d); + + shader_consts = zodiac_shader->getShader()->allocConstBuffer(); + projection_sc = zodiac_shader->getShader()->getShaderConstHandle("$modelView"); + color_sc = zodiac_shader->getShader()->getShaderConstHandle("$zodiacColor"); +} + +void afxZodiacTerrainRenderer::clear() +{ + Parent::clear(); + + terrain_zodes.clear(); +} + +void afxZodiacTerrainRenderer::addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, + const TerrainBlock* block, const TerrCell* cell, + const MatrixF& mRenderObjToWorld, F32 camDist) +{ + terrain_zodes.increment(); + TerrainZodiacElem& elem = terrain_zodes.last(); + + elem.block = block; + elem.cell = cell; + elem.zode_idx = zode_idx; + elem.ang = ang; + elem.mRenderObjToWorld = mRenderObjToWorld; + elem.camDist = camDist; +} + +afxZodiacTerrainRenderer* afxZodiacTerrainRenderer::getMaster() +{ + if (!master) + master = new afxZodiacTerrainRenderer; + return master; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +GFXStateBlock* afxZodiacTerrainRenderer::chooseStateBlock(U32 blend, bool isReflectPass) +{ + GFXStateBlock* sb = 0; + + switch (blend) + { + case afxZodiacData::BLEND_ADDITIVE: + sb = (isReflectPass) ? add_refl_zb_SB : add_norefl_zb_SB; + break; + case afxZodiacData::BLEND_SUBTRACTIVE: + sb = (isReflectPass) ? sub_refl_zb_SB : sub_norefl_zb_SB; + break; + default: // afxZodiacData::BLEND_NORMAL: + sb = (isReflectPass) ? norm_refl_zb_SB : norm_norefl_zb_SB; + break; + } + + return sb; +} + +void afxZodiacTerrainRenderer::render(SceneRenderState* state) +{ + PROFILE_SCOPE(afxRenderZodiacTerrainMgr_render); + + // Early out if no terrain zodiacs to draw. + if (terrain_zodes.size() == 0) + return; + + initShader(); + if (!zodiac_shader) + return; + + bool is_reflect_pass = state->isReflectPass(); + + // Automagically save & restore our viewport and transforms. + GFXTransformSaver saver; + + MatrixF proj = GFX->getProjectionMatrix(); + + // Set up world transform + MatrixF world = GFX->getWorldMatrix(); + proj.mul(world); + shader_consts->set(projection_sc, proj); + + //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + // RENDER EACH ZODIAC + // + for (S32 zz = 0; zz < terrain_zodes.size(); zz++) + { + TerrainZodiacElem& elem = terrain_zodes[zz]; + + TerrainBlock* block = (TerrainBlock*) elem.block; + + afxZodiacMgr::ZodiacSpec* zode = &afxZodiacMgr::terr_zodes[elem.zode_idx]; + if (!zode) + continue; + + if (is_reflect_pass) + { + if ((zode->zflags & afxZodiacData::SHOW_IN_REFLECTIONS) == 0) + continue; + } + else + { + if ((zode->zflags & afxZodiacData::SHOW_IN_NON_REFLECTIONS) == 0) + continue; + } + + F32 fadebias = zode->calcDistanceFadeBias(elem.camDist); + if (fadebias < 0.01f) + continue; + + F32 cos_ang = mCos(elem.ang); + F32 sin_ang = mSin(elem.ang); + + GFXStateBlock* sb = chooseStateBlock(zode->zflags & afxZodiacData::BLEND_MASK, is_reflect_pass); + + GFX->setShader(zodiac_shader->getShader()); + GFX->setStateBlock(sb); + GFX->setShaderConstBuffer(shader_consts); + + // set the texture + GFX->setTexture(0, *zode->txr); + LinearColorF zode_color = (LinearColorF)zode->color; + zode_color.alpha *= fadebias; + shader_consts->set(color_sc, zode_color); + + Point3F half_size(zode->radius_xy,zode->radius_xy,zode->radius_xy); + + F32 inv_radius = 1.0f/zode->radius_xy; + + GFXPrimitive cell_prim; + GFXVertexBufferHandle cell_verts; + GFXPrimitiveBufferHandle primBuff; + elem.cell->getRenderPrimitive(&cell_prim, &cell_verts, &primBuff); + + U32 n_nonskirt_tris = TerrCellSpy::getMinCellSize()*TerrCellSpy::getMinCellSize()*2; + + const Point3F* verts = ((TerrCell*)elem.cell)->getZodiacVertexBuffer(); + const U16 *tris = block->getZodiacPrimitiveBuffer(); + if (!tris) + continue; + + PrimBuild::begin(GFXTriangleList, 3*n_nonskirt_tris); + + ///////////////////////////////// + U32 n_overlapping_tris = 0; + U32 idx = 0; + for (U32 i = 0; i < n_nonskirt_tris; i++) + { + Point3F tri_v[3]; + tri_v[0] = verts[tris[idx++]]; + tri_v[1] = verts[tris[idx++]]; + tri_v[2] = verts[tris[idx++]]; + + elem.mRenderObjToWorld.mulP(tri_v[0]); + elem.mRenderObjToWorld.mulP(tri_v[1]); + elem.mRenderObjToWorld.mulP(tri_v[2]); + + if (!afxTriBoxOverlap2D(zode->pos, half_size, tri_v[0], tri_v[1], tri_v[2])) + continue; + + n_overlapping_tris++; + + for (U32 j = 0; j < 3; j++) + { + // compute UV + F32 u1 = (tri_v[j].x - zode->pos.x)*inv_radius; + F32 v1 = (tri_v[j].y - zode->pos.y)*inv_radius; + F32 ru1 = u1*cos_ang - v1*sin_ang; + F32 rv1 = u1*sin_ang + v1*cos_ang; + + F32 uu = (ru1 + 1.0f)/2.0f; + F32 vv = 1.0f - (rv1 + 1.0f)/2.0f; + + PrimBuild::texCoord2f(uu, vv); + PrimBuild::vertex3fv(tri_v[j]); + } + } + + ///////////////////////////////// + + PrimBuild::end(false); + } + // + // RENDER EACH ZODIAC + //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/afxZodiacTerrainRenderer_T3D.h b/Engine/source/afx/afxZodiacTerrainRenderer_T3D.h new file mode 100644 index 000000000..cf93ded16 --- /dev/null +++ b/Engine/source/afx/afxZodiacTerrainRenderer_T3D.h @@ -0,0 +1,92 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ZODIAC_TERRAIN_RENDERER_H_ +#define _AFX_ZODIAC_TERRAIN_RENDERER_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "renderInstance/renderBinManager.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class TerrCell; + +class afxZodiacTerrainRenderer : public RenderBinManager +{ + typedef RenderBinManager Parent; + + struct TerrainZodiacElem + { + const TerrainBlock* block; + const TerrCell* cell; + U32 zode_idx; + F32 ang; + MatrixF mRenderObjToWorld; + F32 camDist; + }; + + Vector terrain_zodes; + static afxZodiacTerrainRenderer* master; + + GFXStateBlockRef norm_norefl_zb_SB, norm_refl_zb_SB; + GFXStateBlockRef add_norefl_zb_SB, add_refl_zb_SB; + GFXStateBlockRef sub_norefl_zb_SB, sub_refl_zb_SB; + + ShaderData* zodiac_shader; + GFXShaderConstBufferRef shader_consts; + GFXShaderConstHandle* projection_sc; + GFXShaderConstHandle* color_sc; + + bool shader_initialized; + + GFXStateBlock* chooseStateBlock(U32 blend, bool isReflectPass); + +public: + static const RenderInstType RIT_TerrainZodiac; + + /*C*/ afxZodiacTerrainRenderer(); + /*C*/ afxZodiacTerrainRenderer(F32 renderOrder, F32 processAddOrder); + /*D*/ ~afxZodiacTerrainRenderer(); + + // RenderBinManager + virtual void sort(){} // don't sort them + virtual void clear(); + + void initShader(); + void addZodiac(U32 zode_idx, const Point3F& pos, F32 ang, const TerrainBlock*, const TerrCell*, const MatrixF& mRenderObjToWorld, F32 camDist); + + virtual void render(SceneRenderState*); + + static afxZodiacTerrainRenderer* getMaster(); + + // ConsoleObject + DECLARE_CONOBJECT(afxZodiacTerrainRenderer); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ZODIAC_TERRAIN_RENDERER_H_ diff --git a/Engine/source/afx/arcaneFX.cpp b/Engine/source/afx/arcaneFX.cpp new file mode 100644 index 000000000..59da040ea --- /dev/null +++ b/Engine/source/afx/arcaneFX.cpp @@ -0,0 +1,959 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "scene/sceneObject.h" +#include "scene/sceneManager.h" +#include "T3D/gameBase/gameConnection.h" +#include "T3D/gameBase/gameProcess.h" +#include "T3D/player.h" +#include "math/mathUtils.h" +#include "console/compiler.h" +#include "console/engineAPI.h" + +#include "afx/afxChoreographer.h" +#include "afx/afxSelectron.h" +#include "afx/afxResidueMgr.h" +#include "afx/ce/afxZodiacMgr.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#define N_LIGHTING_MODELS 6 +// +// "SG - Original Advanced (Lighting Pack)" +// "SG - Original Stock (Lighting Pack)" +// "SG - Inverse Square (Lighting Pack)" +// "SG - Inverse Square Fast Falloff (Lighting Pack)" +// "SG - Near Linear (Lighting Pack)" +// "SG - Near Linear Fast Falloff (Lighting Pack)" +static StringTableEntry lm_old_names[N_LIGHTING_MODELS]; +// +// "Original Advanced" +// "Original Stock" +// "Inverse Square" +// "Inverse Square Fast Falloff" +// "Near Linear" +// "Near Linear Fast Falloff" +static StringTableEntry lm_new_names[N_LIGHTING_MODELS]; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class ClientZoneInEvent : public NetEvent +{ + typedef NetEvent Parent; +public: + ClientZoneInEvent() { mGuaranteeType = Guaranteed; } + ~ClientZoneInEvent() { } + + virtual void pack(NetConnection*, BitStream*bstream) { } + virtual void write(NetConnection*, BitStream *bstream) { } + virtual void unpack(NetConnection* /*ps*/, BitStream *bstream) { } + + virtual void process(NetConnection* conn) + { + GameConnection* game_conn = dynamic_cast(conn); + if (game_conn && !game_conn->isZonedIn()) + { + arcaneFX::syncToNewConnection(game_conn); + } + } + + DECLARE_CONOBJECT(ClientZoneInEvent); + DECLARE_CATEGORY("AFX"); +}; +IMPLEMENT_CO_SERVEREVENT_V1(ClientZoneInEvent); + +ConsoleDocClass( ClientZoneInEvent, + "@brief Event posted when player is fully loaded into the game and ready for interaction.\n\n" + "@internal"); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +Vector arcaneFX::active_choreographers; +Vector arcaneFX::client_choreographers; +Vector arcaneFX::selectrons; +Vector arcaneFX::scoped_objs; + +StringTableEntry arcaneFX::NULLSTRING = 0; +U32 arcaneFX::sTargetSelectionMask = 0; +U32 arcaneFX::sFreeTargetSelectionMask = 0; +bool arcaneFX::sIsFreeTargeting = false; +Point3F arcaneFX::sFreeTargetPos = Point3F(0.0f, 0.0f, 0.0f); +bool arcaneFX::sFreeTargetPosValid = false; +F32 arcaneFX::sTargetSelectionRange = 200.0f; +U32 arcaneFX::sTargetSelectionTimeoutMS = 500; +bool arcaneFX::sClickToTargetSelf = false; +U32 arcaneFX::sMissileCollisionMask = 0; +StringTableEntry arcaneFX::sParameterFieldPrefix = 0; +F32 arcaneFX::sTerrainZodiacZBias = -0.00025f; +F32 arcaneFX::sInteriorZodiacZBias = -0.0001f; +F32 arcaneFX::sPolysoupZodiacZBias = -0.0001f; +U32 arcaneFX::master_choreographer_id = 1; +U16 arcaneFX::master_scope_id = 1; +bool arcaneFX::is_shutdown = true; + +bool arcaneFX::sUsePlayerCentricListener = false; + +void arcaneFX::init() +{ + NULLSTRING = StringTable->insert(""); + sParameterFieldPrefix = StringTable->insert("_"); + +#if defined(TORQUE_OS_MAC) + arcaneFX::sTerrainZodiacZBias = -0.00025f; + arcaneFX::sInteriorZodiacZBias = -0.00025f; + arcaneFX::sPolysoupZodiacZBias = -0.00025f; +#endif + + Con::addVariable( "pref::AFX::targetSelectionMask", TypeS32, &sTargetSelectionMask); + Con::addVariable( "pref::AFX::freeTargetSelectionMask", TypeS32, &sFreeTargetSelectionMask); + Con::addVariable( "pref::AFX::targetSelectionRange", TypeF32, &sTargetSelectionRange); + Con::addVariable( "pref::AFX::targetSelectionTimeoutMS", TypeS32, &sTargetSelectionTimeoutMS); + Con::addVariable( "pref::AFX::missileCollisionMask", TypeS32, &sMissileCollisionMask); + Con::addVariable( "pref::AFX::clickToTargetSelf", TypeBool, &sClickToTargetSelf); + Con::addVariable( "Pref::Server::AFX::parameterFieldPrefix", TypeString, &sParameterFieldPrefix); + + Con::addVariable( "pref::AFX::terrainZodiacZBias", TypeF32, &sTerrainZodiacZBias); + Con::addVariable( "pref::AFX::interiorZodiacZBias", TypeF32, &sInteriorZodiacZBias); + Con::addVariable( "pref::AFX::polysoupZodiacZBias", TypeF32, &sPolysoupZodiacZBias); + + Con::setIntVariable( "$AFX::TARGETING_OFF", TARGETING_OFF); + Con::setIntVariable( "$AFX::TARGETING_STANDARD", TARGETING_STANDARD); + Con::setIntVariable( "$AFX::TARGETING_FREE", TARGETING_FREE); + Con::setIntVariable( "$AFX::TARGET_CHECK_POLL", TARGET_CHECK_POLL); + Con::setIntVariable( "$AFX::TARGET_CHECK_ON_MOUSE_MOVE", TARGET_CHECK_ON_MOUSE_MOVE); + + Con::setIntVariable( "$AFX::IMPACTED_SOMETHING", afxEffectDefs::IMPACTED_SOMETHING); + Con::setIntVariable( "$AFX::IMPACTED_TARGET", afxEffectDefs::IMPACTED_TARGET); + Con::setIntVariable( "$AFX::IMPACTED_PRIMARY", afxEffectDefs::IMPACTED_PRIMARY); + Con::setIntVariable( "$AFX::IMPACT_IN_WATER", afxEffectDefs::IMPACT_IN_WATER); + Con::setIntVariable( "$AFX::CASTER_IN_WATER", afxEffectDefs::CASTER_IN_WATER); + + Con::setIntVariable( "$AFX::SERVER_ONLY", afxEffectDefs::SERVER_ONLY); + Con::setIntVariable( "$AFX::SCOPE_ALWAYS", afxEffectDefs::SCOPE_ALWAYS); + Con::setIntVariable( "$AFX::GHOSTABLE", afxEffectDefs::GHOSTABLE); + Con::setIntVariable( "$AFX::CLIENT_ONLY", afxEffectDefs::CLIENT_ONLY); + Con::setIntVariable( "$AFX::SERVER_AND_CLIENT", afxEffectDefs::SERVER_AND_CLIENT); + + Con::setIntVariable( "$AFX::DELAY", afxEffectDefs::TIMING_DELAY); + Con::setIntVariable( "$AFX::LIFETIME", afxEffectDefs::TIMING_LIFETIME); + Con::setIntVariable( "$AFX::FADE_IN_TIME", afxEffectDefs::TIMING_FADE_IN); + Con::setIntVariable( "$AFX::FADE_OUT_TIME", afxEffectDefs::TIMING_FADE_OUT); + + Con::setFloatVariable( "$AFX::INFINITE_TIME", -1.0f); + Con::setIntVariable( "$AFX::INFINITE_REPEATS", -1); + + Con::setIntVariable( "$AFX::PLAYER_MOVE_TRIGGER_0", Player::PLAYER_MOVE_TRIGGER_0); + Con::setIntVariable( "$AFX::PLAYER_MOVE_TRIGGER_1", Player::PLAYER_MOVE_TRIGGER_1); + Con::setIntVariable( "$AFX::PLAYER_MOVE_TRIGGER_2", Player::PLAYER_MOVE_TRIGGER_2); + Con::setIntVariable( "$AFX::PLAYER_MOVE_TRIGGER_3", Player::PLAYER_MOVE_TRIGGER_3); + Con::setIntVariable( "$AFX::PLAYER_MOVE_TRIGGER_4", Player::PLAYER_MOVE_TRIGGER_4); + Con::setIntVariable( "$AFX::PLAYER_MOVE_TRIGGER_5", Player::PLAYER_MOVE_TRIGGER_5); + + Con::setIntVariable( "$AFX::PLAYER_FIRE_S_TRIGGER", Player::PLAYER_FIRE_S_TRIGGER); + Con::setIntVariable( "$AFX::PLAYER_FIRE_ALT_S_TRIGGER", Player::PLAYER_FIRE_ALT_S_TRIGGER); + Con::setIntVariable( "$AFX::PLAYER_JUMP_S_TRIGGER", Player::PLAYER_JUMP_S_TRIGGER); + Con::setIntVariable( "$AFX::PLAYER_LANDING_S_TRIGGER", Player::PLAYER_LANDING_S_TRIGGER); + + Con::setIntVariable( "$AFX::PLAYER_LF_FOOT_C_TRIGGER", Player::PLAYER_LF_FOOT_C_TRIGGER); + Con::setIntVariable( "$AFX::PLAYER_RT_FOOT_C_TRIGGER", Player::PLAYER_RT_FOOT_C_TRIGGER); + Con::setIntVariable( "$AFX::PLAYER_LANDING_C_TRIGGER", Player::PLAYER_LANDING_C_TRIGGER); + Con::setIntVariable( "$AFX::PLAYER_IDLE_C_TRIGGER", Player::PLAYER_IDLE_C_TRIGGER); + + Con::setIntVariable( "$AFX::ILLUM_TERRAIN", 0); + Con::setIntVariable( "$AFX::ILLUM_ATLAS", 0); + Con::setIntVariable( "$AFX::ILLUM_DIF", 0); + Con::setIntVariable( "$AFX::ILLUM_DTS", 0); + Con::setIntVariable( "$AFX::ILLUM_ALL", 0); + + Con::setIntVariable("$TypeMasks::TerrainLikeObjectType", TerrainLikeObjectType); + Con::setIntVariable("$TypeMasks::InteriorLikeObjectType", InteriorLikeObjectType); + Con::setIntVariable("$TypeMasks::PolysoupObjectType", InteriorLikeObjectType); // deprecated + + Con::addVariable("$pref::Audio::usePlayerCentricListener", TypeBool, &sUsePlayerCentricListener); + + afxResidueMgr* residue_mgr = new afxResidueMgr; + afxResidueMgr::setMaster(residue_mgr); + + master_scope_id = 1; + master_choreographer_id = 1; + is_shutdown = false; + + if (lm_old_names[0] == 0) + { + lm_old_names[0] = StringTable->insert("SG - Original Advanced (Lighting Pack)"); + lm_old_names[1] = StringTable->insert("SG - Original Stock (Lighting Pack)"); + lm_old_names[2] = StringTable->insert("SG - Inverse Square (Lighting Pack)"); + lm_old_names[3] = StringTable->insert("SG - Inverse Square Fast Falloff (Lighting Pack)"); + lm_old_names[4] = StringTable->insert("SG - Near Linear (Lighting Pack)"); + lm_old_names[5] = StringTable->insert("SG - Near Linear Fast Falloff (Lighting Pack)"); + // + lm_new_names[0] = StringTable->insert("Original Advanced"); + lm_new_names[1] = StringTable->insert("Original Stock"); + lm_new_names[2] = StringTable->insert("Inverse Square"); + lm_new_names[3] = StringTable->insert("Inverse Square Fast Falloff"); + lm_new_names[4] = StringTable->insert("Near Linear"); + lm_new_names[5] = StringTable->insert("Near Linear Fast Falloff"); + } +} + +void arcaneFX::shutdown() +{ + is_shutdown = true; + + for (S32 i = 0; i < scoped_objs.size(); i++) + if (scoped_objs[i]) + scoped_objs[i]->setScopeRegistered(false); + scoped_objs.clear(); + + for (S32 i = 0; i < client_choreographers.size(); i++) + if (client_choreographers[i]) + client_choreographers[i]->clearChoreographerId(); + client_choreographers.clear(); + + for (S32 i = 0; i < selectrons.size(); i++) + if (selectrons[i]) + selectrons[i]->registered = false; + selectrons.clear(); + + afxResidueMgr* residue_mgr = afxResidueMgr::getMaster(); + delete residue_mgr; + afxResidueMgr::setMaster(NULL); +} + +MODULE_BEGIN( arcaneFX ) + + MODULE_INIT + { + arcaneFX::init(); + } + + MODULE_SHUTDOWN + { + arcaneFX::shutdown(); + } + +MODULE_END; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void arcaneFX::advanceTime(U32 delta) +{ + GameConnection* conn = GameConnection::getConnectionToServer(); + if (conn && !conn->isZonedIn() && conn->getCameraObject() != 0) + { + conn->setZonedIn(); + conn->postNetEvent(new ClientZoneInEvent()); + } + + afxZodiacMgr::frameReset(); + afxResidueMgr::getMaster()->residueAdvanceTime(); +} + +// + +U32 arcaneFX::registerChoreographer(afxChoreographer* ch) +{ + if (!ch) + return 0; + + active_choreographers.push_back(ch); + + //Con::printf("registerChoreographer() -- size=%d %s", active_choreographers.size(), + // (ch->isServerObject()) ? "server" : "client"); + + return master_choreographer_id++; +} + +void arcaneFX::unregisterChoreographer(afxChoreographer* ch) +{ + if (!ch) + return; + + for (U32 i = 0; i < active_choreographers.size(); i++) + { + if (ch == active_choreographers[i]) + { + active_choreographers.erase_fast(i); + //Con::printf("unregisterChoreographer() -- size=%d %s", active_choreographers.size(), + // (ch->isServerObject()) ? "server" : "client"); + return; + } + } + + Con::errorf("arcaneFX::unregisterChoreographer() -- failed to find choreographer in list."); +} + +void arcaneFX::registerClientChoreographer(afxChoreographer* ch) +{ + if (!ch || ch->getChoreographerId() == 0) + return; + + client_choreographers.push_back(ch); +} + +void arcaneFX::unregisterClientChoreographer(afxChoreographer* ch) +{ + if (!ch || ch->getChoreographerId() == 0) + return; + + for (U32 i = 0; i < client_choreographers.size(); i++) + { + if (ch == client_choreographers[i]) + { + client_choreographers.erase_fast(i); + return; + } + } + + Con::errorf("arcaneFX::unregisterClientChoreographer() -- failed to find choreographer in list."); +} + +afxChoreographer* arcaneFX::findClientChoreographer(U32 id) +{ + for (U32 i = 0; i < client_choreographers.size(); i++) + { + if (id == client_choreographers[i]->getChoreographerId()) + return client_choreographers[i]; + } + + return 0; +} + +// + +void arcaneFX::registerSelectronData(afxSelectronData* selectron) +{ + if (!selectron) + return; + + selectrons.push_back(selectron); +} + +void arcaneFX::unregisterSelectronData(afxSelectronData* selectron) +{ + if (!selectron) + return; + + for (U32 i = 0; i < selectrons.size(); i++) + { + if (selectron == selectrons[i]) + { + selectrons.erase_fast(i); + return; + } + } + + Con::errorf("arcaneFX::unregisterSelectronData() -- failed to find selectron in list."); +} + +afxSelectronData* arcaneFX::findSelectronData(U32 mask, U8 style) +{ + for (U32 i = 0; i < selectrons.size(); i++) + if (selectrons[i]->matches(mask, style)) + return selectrons[i]; + + return 0; +} + +U16 arcaneFX::generateScopeId() +{ + U16 ret_id = master_scope_id++; + if (master_scope_id >= BIT(GameBase::SCOPE_ID_BITS)) + master_scope_id = 1; + return ret_id; +} + +void arcaneFX::registerScopedObject(SceneObject* object) +{ + scoped_objs.push_back(object); + object->setScopeRegistered(true); + + for (S32 i = 0; i < client_choreographers.size(); i++) + if (client_choreographers[i]) + client_choreographers[i]->restoreScopedObject(object); +} + +SceneObject* arcaneFX::findScopedObject(U16 scope_id) +{ + if (scoped_objs.size() > 0) + { + for (S32 i = scoped_objs.size()-1; i >= 0; i--) + if (scoped_objs[i] && scoped_objs[i]->getScopeId() == scope_id) + return scoped_objs[i]; + } + return 0; +} + +void arcaneFX::unregisterScopedObject(SceneObject* object) +{ + if (scoped_objs.size() > 0) + { + for (S32 i = scoped_objs.size()-1; i >= 0; i--) + if (scoped_objs[i] == object) + { + scoped_objs.erase_fast(i); + if (object) + object->setScopeRegistered(false); + return; + } + } +} + +void arcaneFX::syncToNewConnection(GameConnection* conn) +{ + if (conn) + conn->setZonedIn(); + + for (U32 i = 0; i < active_choreographers.size(); i++) + { + if (active_choreographers[i]) + active_choreographers[i]->sync_with_clients(); + } +} + +void arcaneFX::endMissionNotify() +{ + for (S32 i = 0; i < scoped_objs.size(); i++) + if (scoped_objs[i]) + scoped_objs[i]->setScopeRegistered(false); + scoped_objs.clear(); + + for (S32 i = 0; i < client_choreographers.size(); i++) + if (client_choreographers[i]) + client_choreographers[i]->clearChoreographerId(); + client_choreographers.clear(); + + for (S32 i = 0; i < selectrons.size(); i++) + if (selectrons[i]) + selectrons[i]->registered = false; + selectrons.clear(); + + if (afxResidueMgr::getMaster()) + afxResidueMgr::getMaster()->cleanup(); + afxZodiacMgr::missionCleanup(); +} + +S32 arcaneFX::rolloverRayCast(Point3F start, Point3F end, U32 mask) +{ + sIsFreeTargeting = false; +#if !defined(AFX_CAP_ROLLOVER_RAYCASTS) + return -1; +#else + GameConnection* conn = GameConnection::getConnectionToServer(); + SceneObject* ctrl_obj = NULL; + + if (!arcaneFX::sClickToTargetSelf && conn != NULL) + ctrl_obj = conn->getControlObject(); + + if (ctrl_obj) + ctrl_obj->disableCollision(); + + SceneObject* rollover_obj = (conn) ? conn->getRolloverObj() : 0; + SceneObject* picked_obj = 0; + + RayInfo hit_info; + if (gClientContainer.castRay(start, end, mask, &hit_info)) + picked_obj = dynamic_cast(hit_info.object); + + if (ctrl_obj) + ctrl_obj->enableCollision(); + + if (picked_obj != rollover_obj) + { + if (rollover_obj) + rollover_obj->setSelectionFlags(rollover_obj->getSelectionFlags() & ~SceneObject::PRE_SELECTED); + if (picked_obj) + picked_obj->setSelectionFlags(picked_obj->getSelectionFlags() | SceneObject::PRE_SELECTED); + rollover_obj = picked_obj; + + if (conn) + conn->setRolloverObj(rollover_obj); + } + + return (picked_obj) ? picked_obj->getId() : -1; +#endif +} + +bool arcaneFX::freeTargetingRayCast(Point3F start, Point3F end, U32 mask) +{ + sIsFreeTargeting = true; + + RayInfo hit_info; + if (!gClientContainer.castRay(start, end, mask, &hit_info)) + { + sFreeTargetPosValid = false; + return false; + } + + sFreeTargetPosValid = true; + sFreeTargetPos = hit_info.point; + + return true; +} + +StringTableEntry arcaneFX::convertLightingModelName(StringTableEntry lm_name) +{ + for (U32 i = 0; i < N_LIGHTING_MODELS; i++) + { + if (lm_name == lm_old_names[i]) + return lm_new_names[i]; + } + + return lm_name; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Console Functions + +DefineEngineFunction(afxEndMissionNotify, void, (),, + "...\n\n" + "@ingroup AFX") +{ + arcaneFX::endMissionNotify(); +} + +DefineEngineFunction(afxGetVersion, const char*, (),, + "...\n\n" + "@ingroup AFX") +{ + return AFX_VERSION_STRING; +} + +DefineEngineFunction(afxGetEngine, const char*, (),, + "...\n\n" + "@ingroup AFX") +{ + return "T3D"; +} + +#if defined(AFX_CAP_ROLLOVER_RAYCASTS) +DefineEngineFunction(rolloverRayCast, S32, (Point3F start, Point3F end, U32 mask),, + "Performs a raycast from points start to end and returns the ID of nearest " + "intersecting object with a type found in the specified mask. " + "Returns -1 if no object is found.\n\n" + "@ingroup AFX") +{ + return arcaneFX::rolloverRayCast(start, end, mask); +} +#endif + +DefineEngineFunction(getRandomF, F32, (float a, float b), (F32_MAX, F32_MAX), + "Get a random float number between a and b.\n\n" + "@ingroup AFX") +{ + if (b == F32_MAX) + { + if (a == F32_MAX) + return gRandGen.randF(); + + return gRandGen.randF(0.0f, a); + } + + return (a + (b-a)*gRandGen.randF()); +} + +DefineEngineFunction(getRandomDir, Point3F, (Point3F axis, float thetaMin, float thetaMax, float phiMin, float phiMax), + (Point3F(0.0f,0.0f,0.0f), 0.0f, 180.0f, 0.0f, 360.0f), + "Get a random direction vector.\n\n" + "@ingroup AFX") +{ + return MathUtils::randomDir(axis, thetaMin, thetaMax, phiMin, phiMax); +} + +ConsoleFunction( MatrixInverseMulVector, const char*, 3, 3, "(MatrixF xfrm, Point3F vector)" + "@brief Multiply the vector by the affine inverse of the transform.\n\n" + "@ingroup AFX") +{ + Point3F pos1(0.0f,0.0f,0.0f); + AngAxisF aa1(Point3F(0.0f,0.0f,0.0f),0.0f); + dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle); + + MatrixF temp1(true); + aa1.setMatrix(&temp1); + temp1.setColumn(3, pos1); + + Point3F vec1(0.0f,0.0f,0.0f); + dSscanf(argv[2], "%g %g %g", &vec1.x, &vec1.y, &vec1.z); + + temp1.affineInverse(); + + Point3F result; + temp1.mulV(vec1, &result); + + char* ret = Con::getReturnBuffer(256); + dSprintf(ret, 255, "%g %g %g", result.x, result.y, result.z); + return ret; +} + +ConsoleFunction(moveTransformAbs, const char*, 3, 3, "(MatrixF xfrm, Point3F pos)" + "@brief Move the transform to the new absolute position.\n\n" + "@ingroup AFX") +{ + Point3F pos1(0.0f,0.0f,0.0f); + AngAxisF aa1(Point3F(0.0f,0.0f,0.0f),0.0f); + dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle); + + Point3F pos2(0.0f,0.0f,0.0f); + dSscanf(argv[2], "%g %g %g", &pos2.x, &pos2.y, &pos2.z); + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 255, "%g %g %g %g %g %g %g", + pos2.x, pos2.y, pos2.z, + aa1.axis.x, aa1.axis.y, aa1.axis.z, + aa1.angle); + return returnBuffer; +} + +ConsoleFunction(moveTransformRel, const char*, 3, 3, "(MatrixF xfrm, Point3F pos)" + "@brief Move the transform to the new relative position.\n\n" + "@ingroup AFX") +{ + Point3F pos1(0.0f,0.0f,0.0f); + AngAxisF aa1(Point3F(0.0f,0.0f,0.0f),0.0f); + dSscanf(argv[1], "%g %g %g %g %g %g %g", &pos1.x, &pos1.y, &pos1.z, &aa1.axis.x, &aa1.axis.y, &aa1.axis.z, &aa1.angle); + + Point3F pos2(0.0f,0.0f,0.0f); + dSscanf(argv[2], "%g %g %g", &pos2.x, &pos2.y, &pos2.z); + + pos2 += pos1; + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 255, "%g %g %g %g %g %g %g", + pos2.x, pos2.y, pos2.z, + aa1.axis.x, aa1.axis.y, aa1.axis.z, + aa1.angle); + return returnBuffer; +} + +DefineEngineFunction(getFreeTargetPosition, Point3F, (),, + "@brief Returns the current location of the free target.\n\n" + "@ingroup AFX") +{ + if (!arcaneFX::sFreeTargetPosValid) + return Point3F(0.0f, 0.0f, 0.0f); + return arcaneFX::sFreeTargetPos; +} + +DefineEngineMethod(SceneObject, getSpeed, F32, (),, + "Returns the velocity of a scene-object.\n\n" + "@ingroup AFX") +{ + return object->getVelocity().len(); +} + +static S32 mark_modkey = -1; + +DefineEngineFunction(markDataBlocks, void, (),, + "@brief Called before a series of datablocks are reloaded to " + "help distinguish reloaded datablocks from already loaded ones.\n\n" + "@ingroup AFX") +{ + mark_modkey = SimDataBlock::getNextModifiedKey(); +} + +DefineEngineFunction(touchDataBlocks, void, (),, + "@brief Called after a series of datablocks are reloaded to " + "trigger some important actions on the reloaded datablocks.\n\n" + "@ingroup AFX") +{ + if (mark_modkey < 0) + return; + + SimDataBlockGroup* g = Sim::getDataBlockGroup(); + + U32 groupCount = g->size(); + for (S32 i = groupCount-1; i >= 0; i--) + { + SimDataBlock* simdb = (SimDataBlock*)(*g)[i]; + if (simdb->getModifiedKey() > mark_modkey) + { + simdb->unregisterObject(); + simdb->registerObject(); + } + } + + mark_modkey = -1; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// +// Syntax Error Checking +// (for checking eval() and compile() calls) + +DefineEngineFunction(wasSyntaxError, bool, (),, + "@brief Returns true if script compiler had a syntax error. Useful " + "for detecting syntax errors after reloading a script.\n\n" + "@ingroup AFX") +{ + return Compiler::gSyntaxError; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// +// Network Object Identification + +// These useful console methods come from the following code resource: +// +// How to Identify Objects from Client to Server or Server to Client by Nathan Davies +// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=4852 +// + +DefineEngineMethod(NetConnection, GetGhostIndex, S32, (NetObject* obj),, + "Returns the ghost-index for an object.\n\n" + "@ingroup AFX") +{ + if (obj) + return object->getGhostIndex(obj); + return 0; +} + +DefineEngineMethod(NetConnection, ResolveGhost, S32, (int ghostIndex),, + "Resolves a ghost-index into an object ID.\n\n" + "@ingroup AFX") +{ + if (ghostIndex != -1) + { + NetObject* pObject = NULL; + if( object->isGhostingTo()) + pObject = object->resolveGhost(ghostIndex); + else if( object->isGhostingFrom()) + pObject = object->resolveObjectFromGhostIndex(ghostIndex); + if (pObject) + return pObject->getId(); + } + return 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +////////////////////////////////////////////////////////////////////////// +// TypeByteRange +////////////////////////////////////////////////////////////////////////// + +IMPLEMENT_STRUCT( ByteRange, ByteRange,, + "" ) +END_IMPLEMENT_STRUCT; + +ConsoleType( ByteRange, TypeByteRange, ByteRange, "") +ConsoleType( ByteRange, TypeByteRange2, ByteRange, "") + +ConsoleGetType( TypeByteRange ) +{ + ByteRange* pt = (ByteRange *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%u %u", pt->low, pt->high); + return returnBuffer; +} + +ConsoleSetType( TypeByteRange ) +{ + if(argc == 1) + { + ByteRange* range = (ByteRange*) dptr; + U32 lo, hi; + S32 args = dSscanf(argv[0], "%u %u", &lo, &hi); + range->low = (args > 0) ? lo : 0; + range->high = (args > 1) ? hi : 255; + } + else + Con::printf("ByteRange must be set as \"low\" or \"low high\""); +} + +ConsoleGetType( TypeByteRange2 ) +{ + ByteRange* pt = (ByteRange *) dptr; + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%u %u", pt->low, pt->high); + return returnBuffer; +} + +ConsoleSetType( TypeByteRange2 ) +{ + if(argc == 1) + { + ByteRange* range = (ByteRange*) dptr; + U32 lo, hi; + S32 args = dSscanf(argv[0], "%u %u", &lo, &hi); + range->low = (args > 0) ? lo : 0; + range->high = (args > 1) ? hi : lo; + } + else + Con::printf("ByteRange must be set as \"low\" or \"low high\""); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +static void HSVtoRGB(F32 h, F32 s, F32 v, F32& r, F32& g, F32& b) +{ + h = mFmod(h, 360.0f); + + if (v == 0.0f) + r = g = b = 0.0f; + else if (s == 0.0f) + r = g = b = v; + else + { + F32 hf = h/60.0f; + S32 i = (S32) mFloor(hf); + F32 f = hf - i; + + F32 pv = v*(1.0f - s); + F32 qv = v*(1.0f - s*f); + F32 tv = v*(1.0f - s*(1.0f - f)); + + switch (i) + { + case 0: + r = v; g = tv; b = pv; + break; + case 1: + r = qv; g = v; b = pv; + break; + case 2: + r = pv; g = v; b = tv; + break; + case 3: + r = pv; g = qv; b = v; + break; + case 4: + r = tv; g = pv; b = v; + break; + case 5: + r = v; g = pv; b = qv; + break; + default: + r = g = b = 0.0f; + break; + } + } +} + +DefineEngineFunction(getColorFromHSV, const char*, (float hue, float sat, float val, float alpha), (0.0, 0.0, 1.0, 1.0), + "Coverts an HSV formatted color into an RBG color.\n\n" + "@param hue The hue of the color (0-360).\n" + "@param sat The saturation of the color (0-1).\n" + "@param val The value of the color (0-1).\n" + "@param alpha The alpha of the color (0-1).\n" + "@ingroup AFX") +{ + LinearColorF rgb; + HSVtoRGB(hue, sat, val, rgb.red, rgb.green, rgb.blue); + rgb.alpha = alpha; + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g %g %g %g", rgb.red, rgb.green, rgb.blue, rgb.alpha); + + return returnBuffer; +} + +DefineEngineFunction(ColorScale, const char*, ( LinearColorF color, float scalar ),, + "Returns color scaled by scalar (color*scalar).\n\n" + "@param color The color to be scaled.\n" + "@param scalar The amount to scale the color.\n" + "@ingroup AFX") +{ + color *= scalar; + + char* returnBuffer = Con::getReturnBuffer(256); + dSprintf(returnBuffer, 256, "%g %g %g %g", color.red, color.green, color.blue, color.alpha); + + return returnBuffer; +} + +DefineEngineFunction(getMinF, F32, (float a, float b),, + "Returns the lesser of the two arguments.\n\n" + "@ingroup AFX") +{ + return getMin(a, b); +} + +DefineEngineFunction(getMaxF, F32, (float a, float b),, + "Returns the greater of the two arguments.\n\n" + "@ingroup AFX") +{ + return getMax(a, b); +} + +ConsoleFunction(echoThru, const char*, 2, 0, "(string passthru, string text...)" + "Like echo(), but first argument is returned.\n" + "@ingroup AFX") +{ + U32 len = 0; + S32 i; + for(i = 2; i < argc; i++) + len += dStrlen(argv[i]); + + char *ret = Con::getReturnBuffer(len + 1); + ret[0] = 0; + for(i = 2; i < argc; i++) + dStrcat(ret, argv[i]); + + Con::printf("%s -- [%s]", ret, argv[1].getStringValue()); + ret[0] = 0; + + return argv[1]; +} + +ConsoleFunction(warnThru, const char*, 2, 0, "(string passthru, string text...)" + "Like warn(), but first argument is returned.\n" + "@ingroup AFX") +{ + U32 len = 0; + S32 i; + for(i = 2; i < argc; i++) + len += dStrlen(argv[i]); + + char *ret = Con::getReturnBuffer(len + 1); + ret[0] = 0; + for(i = 2; i < argc; i++) + dStrcat(ret, argv[i]); + + Con::warnf("%s -- [%s]", ret, argv[1].getStringValue()); + ret[0] = 0; + + return argv[1]; +} + +ConsoleFunction(errorThru, const char*, 2, 0, "(string passthru, string text...)" + "Like error(), but first argument is returned.\n" + "@ingroup AFX") +{ + U32 len = 0; + S32 i; + for(i = 2; i < argc; i++) + len += dStrlen(argv[i]); + + char *ret = Con::getReturnBuffer(len + 1); + ret[0] = 0; + for(i = 2; i < argc; i++) + dStrcat(ret, argv[i]); + + Con::errorf("%s -- [%s]", ret, argv[1].getStringValue()); + ret[0] = 0; + + return argv[1]; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/arcaneFX.h b/Engine/source/afx/arcaneFX.h new file mode 100644 index 000000000..19e78d29a --- /dev/null +++ b/Engine/source/afx/arcaneFX.h @@ -0,0 +1,214 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _ARCANE_FX_H_ +#define _ARCANE_FX_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _PLATFORM_H_ +#include "platform/platform.h" +#endif + +#define AFX_VERSION_STRING "2.0" +#define AFX_VERSION 2.0 + +// #define AFX_CUSTOMIZED_BRANCH + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// +#if defined(AFX_CUSTOMIZED_BRANCH) + +#elif (TORQUE_GAME_ENGINE == 1100 || TORQUE_GAME_ENGINE >= 3000) + +#define AFX_CAP_SCOPE_TRACKING +#define AFX_CAP_ROLLOVER_RAYCASTS +//#define AFX_CAP_AFXMODEL_TYPE +//#define BROKEN_POINT_IN_WATER +#define BROKEN_DAMAGEFLASH_WHITEOUT_BLACKOUT + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// +#else + +// This version of AFX source only supports T3D 1.1 + +#endif + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +#ifndef _CONSOLETYPES_H_ +#include "console/consoleTypes.h" +#endif + +#ifndef _ENGINEAPI_H_ +#include "console/engineAPI.h" +#endif + +#ifndef _SIMBASE_H_ +#include "console/simBase.h" +#endif + +#ifndef _BITSTREAM_H_ +#include "core/stream/bitStream.h" +#endif + +#ifndef _GAMEBASE_H_ +#include "T3D/gameBase/gameBase.h" +#endif + +#if defined(DGL_GRAPHICS_LAYER) +#ifndef _DGL_H_ +#include "dgl/dgl.h" +#endif +#endif + +class afxChoreographer; +class afxSelectronData; +class GameConnection; +class SceneObject; + +class arcaneFX +{ +public: + enum { + TARGETING_OFF, + TARGETING_STANDARD, + TARGETING_FREE + }; + enum { + TARGET_CHECK_POLL, + TARGET_CHECK_ON_MOUSE_MOVE + }; + +private: + static Vector active_choreographers; + static Vector client_choreographers; + static Vector selectrons; + static Vector scoped_objs; + static bool is_shutdown; + +public: + static StringTableEntry NULLSTRING; + static U32 sTargetSelectionMask; + static U32 sFreeTargetSelectionMask; + static bool sIsFreeTargeting; + static Point3F sFreeTargetPos; + static bool sFreeTargetPosValid; + static F32 sTargetSelectionRange; + static U32 sTargetSelectionTimeoutMS; + static bool sClickToTargetSelf; + static U32 sMissileCollisionMask; + static StringTableEntry sParameterFieldPrefix; + static F32 sTerrainZodiacZBias; + static F32 sInteriorZodiacZBias; + static F32 sPolysoupZodiacZBias; + static U32 master_choreographer_id; + static U16 master_scope_id; + +public: + static void init(); + static void shutdown(); + static void advanceTime(U32 delta); + + static U32 registerChoreographer(afxChoreographer*); + static void unregisterChoreographer(afxChoreographer*); + static void registerClientChoreographer(afxChoreographer*); + static void unregisterClientChoreographer(afxChoreographer*); + static afxChoreographer* findClientChoreographer(U32 id); + + static void registerSelectronData(afxSelectronData*); + static void unregisterSelectronData(afxSelectronData*); + static afxSelectronData* findSelectronData(U32 obj_type_mask, U8 code); + + static U16 generateScopeId(); + static void registerScopedObject(SceneObject*); + static SceneObject* findScopedObject(U16 scope_id); + static void unregisterScopedObject(SceneObject*); + + static void syncToNewConnection(GameConnection* conn); + static void endMissionNotify(); + static S32 rolloverRayCast(Point3F start, Point3F end, U32 mask); + static bool freeTargetingRayCast(Point3F start, Point3F end, U32 mask); + + static bool isShutdown() { return is_shutdown; } + static StringTableEntry convertLightingModelName(StringTableEntry lm_name); + +private: + static bool sUsePlayerCentricListener; +public: + static bool usePlayerCentricListener() { return sUsePlayerCentricListener; } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class ByteRange +{ +public: + U8 low; + U8 high; + +public: + /*C*/ ByteRange() { low = 0; high = 255; } + /*C*/ ByteRange(U8 l, U8 h=255) { low = l; high = h; } + + void set(U8 l, U8 h=255) { low = l; high = h; } + bool outOfRange(U8 v) { return (v < low || v > high); } + bool inRange(U8 v) { return !outOfRange(v); } + S32 getSpan() const { return high - low; } +}; + +DefineConsoleType(TypeByteRange, ByteRange) +DefineConsoleType(TypeByteRange2, ByteRange) + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +inline void writeDatablockID(BitStream* s, SimObject* simobj, bool packed=false) +{ + if (s->writeFlag(simobj)) + s->writeRangedU32(packed ? SimObjectId((uintptr_t)simobj) : simobj->getId(), + DataBlockObjectIdFirst, DataBlockObjectIdLast); +} + +inline S32 readDatablockID(BitStream* s) +{ + return (!s->readFlag()) ? 0 : ((S32)s->readRangedU32(DataBlockObjectIdFirst, + DataBlockObjectIdLast)); +} + +inline void registerForCleanup(SimObject* obj) +{ + SimGroup* cleanup_grp = dynamic_cast(Sim::findObject("MissionCleanup")); + if (cleanup_grp) + cleanup_grp->addObject(obj); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +#define ST_NULLSTRING (arcaneFX::NULLSTRING) + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _ARCANE_FX_H_ + diff --git a/Engine/source/afx/ce/afxAnimClip.cpp b/Engine/source/afx/ce/afxAnimClip.cpp new file mode 100644 index 000000000..ed9561f2b --- /dev/null +++ b/Engine/source/afx/ce/afxAnimClip.cpp @@ -0,0 +1,217 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +#include "afx/ce/afxAnimClip.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxAnimClipData + +IMPLEMENT_CO_DATABLOCK_V1(afxAnimClipData); + +ConsoleDocClass( afxAnimClipData, + "@brief A datablock that specifies an Animation Clip effect.\n\n" + + "An Animation Clip forces a target ShapeBase-derived object, such as Player or AIPlayer, to perform a particular " + "animation sequence. Animation Clip does not supply any new animation data, but simply selects, by name, a " + "sequence that is already defined in the target. Animation Clip can also target afxModel effects within the same " + "choreographer." + "\n\n" + + "The target of an Animation Clip is the constraint source object specified by the posConstraint field of the enclosing " + "effect wrapper. The target must be a ShapeBase-derived object, or an afxModel and it must contain an animation " + "sequence with the same name as the clipName field." + "\n\n" + + "Animation Clip controls the rate of animation playback and can even play a sequence in reverse. When an Animation " + "Clip selects a blended animation sequence, it is mixed with the current animation instead of replacing it. Animation " + "Clips can be used to activate multiple, overlapping blend sequences." + "\n\n" + + "Normally when an Animation Clip is applied to a user-controlled Player, any interactive user actions will override the " + "animation selected by the clip, but Animation Clips can be configured to temporarily block out some user actions for " + "the duration of the clip." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxAnimClipData::afxAnimClipData() +{ + clip_name = ST_NULLSTRING; + rate = 1.0f; + pos_offset = 0.0; + trans = 0.12f; + flags = 0; + + ignore_disabled = false; + ignore_enabled = false; + is_death_anim = false; + lock_anim = false; + ignore_first_person = false; + ignore_third_person = false; +} + +afxAnimClipData::afxAnimClipData(const afxAnimClipData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + clip_name = other.clip_name; + rate = other.rate; + pos_offset = other.pos_offset; + trans = other.trans; + flags = other.flags; + + expand_flags(); +} + +void afxAnimClipData::onStaticModified(const char* slot, const char* newValue) +{ + Parent::onStaticModified(slot, newValue); + merge_flags(); +} + +#define myOffset(field) Offset(field, afxAnimClipData) + +void afxAnimClipData::initPersistFields() +{ + addField("clipName", TYPEID< StringTableEntry >(), myOffset(clip_name), + "The name of an animation sequence to be played by a ShapeBase-derived object to which this effect is " + "constrained. Also works on afxModel effects.\n" + "default: \"\"\n"); + addField("rate", TYPEID< F32 >(), myOffset(rate), + "The desired playback speed for the sequence. A value of 1.0 indicates forward playback at a normal rate. Negative " + "values cause the sequence to play backwards.\n" + "default: 1.0\n"); + addField("posOffset", TYPEID< F32 >(), myOffset(pos_offset), + "Sets a starting offset for the selected animation clip. It directly specifies an animation thread position in the 0.0 to " + "1.0 range as a fraction of the clip's duration.\n" + "default: 1.0\n"); + addField("transitionTime", TYPEID< F32 >(), myOffset(trans), + "The duration in which the active animation overlaps and blends into the sequence selected by the animation clip.\n" + "default: 0.12\n"); + addField("ignoreCorpse", TYPEID< bool >(), myOffset(ignore_disabled), + "Specifies if the animation clip should not be applied to corpses or anything else with a disabled damage state.\n" + "default: false\n"); + addField("ignoreLiving", TYPEID< bool >(), myOffset(ignore_enabled), + "Specifies if the animation clip should not be applied to living objects or anything else with an enabled damage " + "state.\n" + "default: false\n"); + addField("treatAsDeathAnim", TYPEID< bool >(), myOffset(is_death_anim), + "Indicates if the animation clip is a death animation. If the target object dies during the effect, this will prevent " + "the object from playing another standard death animation after this clip finishes.\n" + "default: false\n"); + addField("lockAnimation", TYPEID< bool >(), myOffset(lock_anim), + "Indicates if user control of a Player should be temporarily blocked during the clip. (See afxAnimLockData.)\n" + "default: false\n"); + addField("ignoreFirstPerson", TYPEID< bool >(), myOffset(ignore_first_person), + "If true, the clip will not be played on targets that are the control object and the camera is in first person mode.\n" + "default: false\n"); + addField("ignoreThirdPerson", TYPEID< bool >(), myOffset(ignore_third_person), + "If true, the clip will not be played on targets that are the control object and the camera is in third person mode.\n" + "default: false\n"); + + // synonyms + addField("ignoreDisabled", TYPEID< bool >(), myOffset(ignore_disabled), + "A synonym for ignoreLiving."); + addField("ignoreEnabled", TYPEID< bool >(), myOffset(ignore_enabled), + "A synonym for ignoreCorpse."); + + Parent::initPersistFields(); +} + +bool afxAnimClipData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxAnimClipData::packData(BitStream* stream) +{ + Parent::packData(stream); + + merge_flags(); + + stream->writeString(clip_name); + stream->write(rate); + stream->write(pos_offset); + stream->write(trans); + stream->write(flags); +} + +void afxAnimClipData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + clip_name = stream->readSTString(); + stream->read(&rate); + stream->read(&pos_offset); + stream->read(&trans); + stream->read(&flags); + + expand_flags(); +} + +bool afxAnimClipData::writeField(StringTableEntry fieldname, const char* value) +{ + if (!Parent::writeField(fieldname, value)) + return false; + + // don't write the synonyms + if( fieldname == StringTable->insert("ignoreDisabled") ) + return false; + if( fieldname == StringTable->insert("ignoreEnabled") ) + return false; + + return true; +} + +void afxAnimClipData::expand_flags() +{ + ignore_disabled = ((flags & IGNORE_DISABLED) != 0); + ignore_enabled = ((flags & IGNORE_ENABLED) != 0); + lock_anim = ((flags & BLOCK_USER_CONTROL) != 0); + is_death_anim = ((flags & IS_DEATH_ANIM) != 0); + ignore_first_person = ((flags & IGNORE_FIRST_PERSON) != 0); + ignore_third_person = ((flags & IGNORE_THIRD_PERSON) != 0); +} + +void afxAnimClipData::merge_flags() +{ + flags = (((ignore_disabled) ? IGNORE_DISABLED : 0) | + ((ignore_enabled) ? IGNORE_ENABLED : 0) | + ((lock_anim) ? BLOCK_USER_CONTROL : 0) | + ((ignore_first_person) ? IGNORE_FIRST_PERSON : 0) | + ((ignore_third_person) ? IGNORE_THIRD_PERSON : 0) | + ((is_death_anim) ? IS_DEATH_ANIM : 0)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxAnimClip.h b/Engine/source/afx/ce/afxAnimClip.h new file mode 100644 index 000000000..4b1dbb0f3 --- /dev/null +++ b/Engine/source/afx/ce/afxAnimClip.h @@ -0,0 +1,81 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ANIM_CLIP_H_ +#define _AFX_ANIM_CLIP_H_ + +class afxAnimClipData : public GameBaseData +{ + typedef GameBaseData Parent; + + enum + { + IGNORE_DISABLED = BIT(0), + IGNORE_ENABLED = BIT(1), + IS_DEATH_ANIM = BIT(2), + BLOCK_USER_CONTROL = BIT(3), + IGNORE_FIRST_PERSON = BIT(4), + IGNORE_THIRD_PERSON = BIT(5) + }; + +public: + StringTableEntry clip_name; + F32 rate; + F32 pos_offset; + F32 trans; + U8 flags; + + bool ignore_disabled; + bool ignore_enabled; + bool is_death_anim; + bool lock_anim; + bool ignore_first_person; + bool ignore_third_person; + + void expand_flags(); + void merge_flags(); + +public: + /*C*/ afxAnimClipData(); + /*C*/ afxAnimClipData(const afxAnimClipData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + virtual bool writeField(StringTableEntry fieldname, const char* value); + + virtual void onStaticModified(const char* slotName, const char* newValue = NULL); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxAnimClipData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ANIM_CLIP_H_ diff --git a/Engine/source/afx/ce/afxAnimLock.cpp b/Engine/source/afx/ce/afxAnimLock.cpp new file mode 100644 index 000000000..cd5da6654 --- /dev/null +++ b/Engine/source/afx/ce/afxAnimLock.cpp @@ -0,0 +1,95 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +#include "afx/ce/afxAnimLock.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxAnimLockData + +IMPLEMENT_CO_DATABLOCK_V1(afxAnimLockData); + +ConsoleDocClass( afxAnimLockData, + "@brief A datablock that specifies an Animation Lock effect.\n\n" + + "Animation Lock is used to temporarily lock out user-controlled Player actions, usually while an Animation Clip is " + "concurrently playing. Animation Clips can already do this, but must lock out user actions for the entire clip length. " + "Sometimes you only want to block user actions for a short section of a longer playing animation, such as the part where " + "the Player is thrown into the air from an impact. With Animation Lock, you can set a specific timespan for when user " + "actions are blocked, independent of any Animation Clip timing." + "\n\n" + + "The target of an Animation Lock is the constraint source object specified by the posConstraint field of the enclosing effect " + "wrapper. The target must be a Player, a subclass of Player, or an afxModel." + "\n\n" + + "The timing of the Animation Lock is determined by the timing fields of the enclosing effect wrapper." + "\n\n" + + "Locking behavior timing is set by fields of the enclosing effect wrapper, so afxAnimLockData does not require any fields. " + "However, TorqueScript syntax disallows the declaration of an empty datablock. Therefore, it is recommended that you set " + "a dynamic field named 'priority' to zero in the body of the datablock as a workaround to this limitation." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxAnimLockData::afxAnimLockData() +{ +} + +#define myOffset(field) Offset(field, afxAnimLockData) + +void afxAnimLockData::initPersistFields() +{ + Parent::initPersistFields(); +} + +bool afxAnimLockData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxAnimLockData::packData(BitStream* stream) +{ + Parent::packData(stream); +} + +void afxAnimLockData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxAnimLock.h b/Engine/source/afx/ce/afxAnimLock.h new file mode 100644 index 000000000..fea98194b --- /dev/null +++ b/Engine/source/afx/ce/afxAnimLock.h @@ -0,0 +1,48 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ANIM_LOCK_H_ +#define _AFX_ANIM_LOCK_H_ + +class afxAnimLockData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + /*C*/ afxAnimLockData(); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxAnimLockData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ANIM_LOCK_H_ diff --git a/Engine/source/afx/ce/afxAreaDamage.cpp b/Engine/source/afx/ce/afxAreaDamage.cpp new file mode 100644 index 000000000..bd63045c4 --- /dev/null +++ b/Engine/source/afx/ce/afxAreaDamage.cpp @@ -0,0 +1,121 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/ce/afxAreaDamage.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxAreaDamageData + +IMPLEMENT_CO_DATABLOCK_V1(afxAreaDamageData); + +ConsoleDocClass( afxAreaDamageData, + "@brief A datablock that specifies an Area Damage effect.\n\n" + + "An Area Damage effect is useful for assigning area damage with unusual timing that must be synchronized with other " + "effects. Negative damage amounts can be used for healing effects." + "\n\n" + + "The primary difference between afxAreaDamageData and afxDamageData, which is also capable of inflicting area damage, " + "is that afxAreaDamageData effects calculate the area damage in C++ code rather than calling out to the script function " + "radiusDamage(). In cases where area damage needs to be inflicted repeatedly or in areas crowded with many targets, " + "afxAreaDamageData is likely to get better performance." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxAreaDamageData::afxAreaDamageData() +{ + flavor = ST_NULLSTRING; + amount = 0; + radius = 0; + impulse = 0; + notify_damage_src = false; + exclude_cons_obj = false; +} + +afxAreaDamageData::afxAreaDamageData(const afxAreaDamageData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + flavor = other.flavor; + amount = other.amount; + radius = other.radius; + impulse = other.impulse; + notify_damage_src = other.notify_damage_src; + exclude_cons_obj = other.exclude_cons_obj; +} + +#define myOffset(field) Offset(field, afxAreaDamageData) + +void afxAreaDamageData::initPersistFields() +{ + addField("flavor", TypeString, myOffset(flavor), + "An arbitrary string which is passed as an argument to a spell's onDamage() script " + "method. It is used to classify a type of damage such as 'melee', 'magical', or " + "'fire'."); + addField("damage", TypeF32, myOffset(amount), + "An amount of area damage to inflict on a target. Objects within half the radius " + "receive full damage which then diminishes out to the full distance of the specified " + "radius."); + addField("radius", TypeF32, myOffset(radius), + "Radius centered at the effect position in which damage will be applied."); + addField("impulse", TypeF32, myOffset(impulse), + "Specifies an amount of force to apply to damaged objects. Objects within half the " + "radius receive full impulse which then diminishes out to the full distance of the " + "specified radius."); + addField("notifyDamageSource", TypeBool, myOffset(notify_damage_src), + "When true, the onInflictedAreaDamage() method of the damaged object will be called " + "to notify it of the damage. This is useful for starting some effects or action that " + "responds to the damage."); + addField("excludeConstraintObject", TypeBool, myOffset(exclude_cons_obj), + "When true, the object specified as the effect's primary position constraint will not " + "receive any damage."); + + Parent::initPersistFields(); +} + +bool afxAreaDamageData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxAreaDamageData::packData(BitStream* stream) +{ + Parent::packData(stream); +} + +void afxAreaDamageData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxAreaDamage.h b/Engine/source/afx/ce/afxAreaDamage.h new file mode 100644 index 000000000..4386acd4e --- /dev/null +++ b/Engine/source/afx/ce/afxAreaDamage.h @@ -0,0 +1,62 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_AREA_DAMAGE_H_ +#define _AFX_AREA_DAMAGE_H_ + +#include "afx/afxEffectDefs.h" + +class afxAreaDamageData : public GameBaseData, public afxEffectDefs +{ + typedef GameBaseData Parent; + +public: + StringTableEntry flavor; + + F32 amount; + F32 radius; + F32 impulse; + bool notify_damage_src; + bool exclude_cons_obj; + +public: + /*C*/ afxAreaDamageData(); + /*C*/ afxAreaDamageData(const afxAreaDamageData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxAreaDamageData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_AREA_DAMAGE_H_ diff --git a/Engine/source/afx/ce/afxAudioBank.cpp b/Engine/source/afx/ce/afxAudioBank.cpp new file mode 100644 index 000000000..2aa148474 --- /dev/null +++ b/Engine/source/afx/ce/afxAudioBank.cpp @@ -0,0 +1,246 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "sim/netConnection.h" +#include "sfx/sfxDescription.h" + +#include "afx/ce/afxAudioBank.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxAudioBank); + +ConsoleDocClass( afxAudioBank, + "@brief A datablock that specifies an Audio Bank effect.\n\n" + + "afxAudioBank is very similar to the stock Torque SFXProfile datablock but it allows specification of up to 32 different sound " + "files. The sound that actually plays is determined by the playIndex field." + "\n\n" + + "afxAudioBank is most useful when used in combination with field substitutions, whereby a substitution statement " + "assigned to playIndex selects a different sound (perhaps randomly) each time the effect is used." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxAudioBank::afxAudioBank() +{ + mPath = ST_NULLSTRING; + mDescriptionObjectID = 0; + mDescriptionObject = NULL; + mPreload = false; + play_index = -1; + + for (S32 i = 0; i < 32; i++) + mFilenames[i] = ST_NULLSTRING; +} + +afxAudioBank::afxAudioBank(const afxAudioBank& other, bool temp_clone) : SimDataBlock(other, temp_clone) +{ + mPath = other.mPath; + mDescriptionObject = other.mDescriptionObject; + mDescriptionObjectID = other.mDescriptionObjectID; // -- for pack/unpack of mDescriptionObject ptr + mPreload = other.mPreload; + play_index = other.play_index; + + for (S32 i = 0; i < 32; i++) + mFilenames[i] = other.mFilenames[i]; +} + +afxAudioBank::~afxAudioBank() +{ + if (!isTempClone()) + return; + + if (mDescriptionObject && mDescriptionObject->isTempClone()) + { + delete mDescriptionObject; + mDescriptionObject = 0; + } +} + +afxAudioBank* afxAudioBank::cloneAndPerformSubstitutions(const SimObject* owner, S32 index) +{ + if (!owner) + return this; + + afxAudioBank* sub_profile_db = this; + + SFXDescription* desc_db; + if (mDescriptionObject && mDescriptionObject->getSubstitutionCount() > 0) + { + SFXDescription* orig_db = mDescriptionObject; + desc_db = new SFXDescription(*orig_db, true); + orig_db->performSubstitutions(desc_db, owner, index); + } + else + desc_db = 0; + + if (this->getSubstitutionCount() > 0 || desc_db) + { + sub_profile_db = new afxAudioBank(*this, true); + performSubstitutions(sub_profile_db, owner, index); + if (desc_db) + sub_profile_db->mDescriptionObject = desc_db; + } + + return sub_profile_db; +} + +void afxAudioBank::onPerformSubstitutions() +{ +} + +void afxAudioBank::initPersistFields() +{ + addField("path", TypeFilename, Offset(mPath, afxAudioBank), + "A filesystem path to the folder containing the sound files specified by the " + "filenames[] field. All sound files used in a single AudioBank must be located in " + "the same folder."); + addField("filenames", TypeString, Offset(mFilenames, afxAudioBank), 32, + "Up to 32 names of sound files found in the path folder. The sound that is actually " + "played by an Audio Bank effect is determined by the playIndex field."); + addField("description", TYPEID(), Offset(mDescriptionObject, afxAudioBank), + "SFXDescription datablock to use with this set of sounds."); + addField("preload", TypeBool, Offset(mPreload, afxAudioBank), + "If set to true, file is pre-loaded, otherwise it is loaded on-demand."); + addField("playIndex", TypeS32, Offset(play_index, afxAudioBank), + "An array index that selects a sound to play from the filenames[] field. Values " + "outside of the range of assigned filename[] entries will not play any sound."); + + Parent::initPersistFields(); +} + +bool afxAudioBank::preload(bool server, String &errorStr) +{ + if(!Parent::preload(server, errorStr)) + return false; + + return true; +} + +bool afxAudioBank::onAdd() +{ + if (!Parent::onAdd()) + return false; + + if (!mDescriptionObject && mDescriptionObjectID) + Sim::findObject(mDescriptionObjectID , mDescriptionObject); + + // if this is client side, make sure that description is as well + if(mDescriptionObject) + { // client side dataBlock id's are not in the dataBlock id range + if (getId() >= DataBlockObjectIdFirst && getId() <= DataBlockObjectIdLast) + { + SimObjectId pid = mDescriptionObject->getId(); + if (pid < DataBlockObjectIdFirst || pid > DataBlockObjectIdLast) + { + Con::errorf(ConsoleLogEntry::General,"afxAudioBank: data dataBlock not networkable (use datablock to create)."); + return false; + } + } + } + + return(true); +} + +void afxAudioBank::packData(BitStream* stream) +{ + Parent::packData(stream); + + if (stream->writeFlag(mDescriptionObject)) + stream->writeRangedU32(mDescriptionObject->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + + /* + char buffer[256]; + if(!mFilename) + buffer[0] = 0; + else + dStrcpy(buffer, mFilename); + stream->writeString(buffer); + */ + + stream->writeString(mPath); + + for (S32 i = 0; i < 32; i++) + { + stream->writeString(mFilenames[i]); + if (mFilenames[i] == ST_NULLSTRING) + break; + } + + stream->writeFlag(mPreload); + + if (stream->writeFlag(play_index >= 0 && play_index < 32)) + stream->writeInt(play_index, 5); +} + +void afxAudioBank::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + if (stream->readFlag()) // AudioDescription + { + SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + mDescriptionObjectID = id; + Sim::findObject(id, mDescriptionObject); + } + + // Filename + /* + char buffer[256]; + stream->readString(buffer); + mFilename = StringTable->insert(buffer); + */ + + char buffer[256]; + + stream->readString(buffer); + mPath = StringTable->insert(buffer); + + for (S32 i = 0; i < 32; i++) + { + stream->readString(buffer); + mFilenames[i] = StringTable->insert(buffer); + if (mFilenames[i] == ST_NULLSTRING) + break; + } + + mPreload = stream->readFlag(); // Preload + + if (stream->readFlag()) + play_index = stream->readInt(5); + else + play_index = -1; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/ce/afxAudioBank.h b/Engine/source/afx/ce/afxAudioBank.h new file mode 100644 index 000000000..e7f9ee4d3 --- /dev/null +++ b/Engine/source/afx/ce/afxAudioBank.h @@ -0,0 +1,67 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_AUDIO_BANK_H_ +#define _AFX_AUDIO_BANK_H_ + +class SFXDescription; + +class afxAudioBank: public SimDataBlock +{ +private: + typedef SimDataBlock Parent; + +public: + SFXDescription* mDescriptionObject; + U32 mDescriptionObjectID; + StringTableEntry mPath; + StringTableEntry mFilenames[32]; + bool mPreload; + S32 play_index; + +public: + /*C*/ afxAudioBank(); + /*C*/ afxAudioBank(const afxAudioBank&, bool = false); + /*D*/ ~afxAudioBank(); + + static void initPersistFields(); + + virtual bool onAdd(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + + bool preload(bool server, String &errorStr); + + afxAudioBank* cloneAndPerformSubstitutions(const SimObject*, S32 index=0); + virtual void onPerformSubstitutions(); + virtual bool allowSubstitutions() const { return true; } + + DECLARE_CONOBJECT(afxAudioBank); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_AUDIO_BANK_H_ diff --git a/Engine/source/afx/ce/afxBillboard.cpp b/Engine/source/afx/ce/afxBillboard.cpp new file mode 100644 index 000000000..f7f4db780 --- /dev/null +++ b/Engine/source/afx/ce/afxBillboard.cpp @@ -0,0 +1,294 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "gfx/gfxAPI.h" +#include "math/mathIO.h" + +#include "afx/afxChoreographer.h" +#include "afx/ce/afxBillboard.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxBillboardData + +IMPLEMENT_CO_DATABLOCK_V1(afxBillboardData); + +ConsoleDocClass( afxBillboardData, + "@brief A datablock that specifies a Billboard effect.\n\n" + + "A Billboard effect is a textured quadrangle which is always aligned to face towards the camera. It is much like a single " + "static particle and is rendered in a similar fashion." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxBillboardData::afxBillboardData() +{ + color.set(1.0f, 1.0f, 1.0f, 1.0f); + txr_name = ST_NULLSTRING; + dimensions.set(1.0f, 1.0f); + texCoords[0].set(0.0f, 0.0f); + texCoords[1].set(0.0f, 1.0f); + texCoords[2].set(1.0f, 1.0f); + texCoords[3].set(1.0f, 0.0f); + blendStyle = BlendUndefined; + srcBlendFactor = BLEND_UNDEFINED; + dstBlendFactor = BLEND_UNDEFINED; + texFunc = TexFuncModulate; +} + +afxBillboardData::afxBillboardData(const afxBillboardData& other, bool temp_clone) + : GameBaseData(other, temp_clone) +{ + color = other.color; + txr_name = other.txr_name; + txr = other.txr; + dimensions = other.dimensions; + texCoords[0] = other.texCoords[0]; + texCoords[1] = other.texCoords[1]; + texCoords[2] = other.texCoords[2]; + texCoords[3] = other.texCoords[3]; + blendStyle = other.blendStyle; + srcBlendFactor = other.srcBlendFactor; + dstBlendFactor = other.dstBlendFactor; + texFunc = other.texFunc; +} + +#define myOffset(field) Offset(field, afxBillboardData) + +extern EnumTable srcBlendFactorTable; +extern EnumTable dstBlendFactorTable; + +ImplementEnumType( afxBillboard_BlendStyle, "Possible blending types.\n" "@ingroup afxBillboard\n\n" ) + { afxBillboardData::BlendNormal, "NORMAL", "..." }, + { afxBillboardData::BlendAdditive, "ADDITIVE", "..." }, + { afxBillboardData::BlendSubtractive, "SUBTRACTIVE", "..." }, + { afxBillboardData::BlendPremultAlpha, "PREMULTALPHA", "..." }, +EndImplementEnumType; + +ImplementEnumType( afxBillboard_TexFuncType, "Possible texture function types.\n" "@ingroup afxBillboard\n\n" ) + { afxBillboardData::TexFuncReplace, "replace", "..." }, + { afxBillboardData::TexFuncModulate, "modulate", "..." }, + { afxBillboardData::TexFuncAdd, "add", "..." }, +EndImplementEnumType; + +void afxBillboardData::initPersistFields() +{ + addField("color", TypeColorF, myOffset(color), + "The color assigned to the quadrangle geometry. The way it combines with the given " + "texture varies according to the setting of the textureFunction field."); + addField("texture", TypeFilename, myOffset(txr_name), + "An image to use as the billboard's texture."); + addField("dimensions", TypePoint2F, myOffset(dimensions), + "A value-pair that specifies the horizontal and vertical dimensions of the billboard " + "in scene units."); + addField("textureCoords", TypePoint2F, myOffset(texCoords), 4, + "An array of four value-pairs that specify the UV texture coordinates for the four " + "corners of the billboard's quadrangle."); + + addField("blendStyle", TYPEID(), myOffset(blendStyle), + "Selects a common blend factor preset. When set to 'user', srcBlendFactor and " + "dstBlendFactor can be used to set additional blend factor combinations.\n" + "Possible values: normal, additive, subtractive, premultalpha, or user."); + addField("srcBlendFactor", TYPEID(), myOffset(srcBlendFactor), + "Specifies source blend factor when blendStyle is set to 'user'.\n" + "Possible values: GFXBlendZero, GFXBlendOne, GFXBlendDestColor, GFXBlendInvDestColor, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha, GFXBlendDestAlpha, GFXBlendInvDestAlpha, or GFXBlendSrcAlphaSat"); + addField("dstBlendFactor", TYPEID(), myOffset(dstBlendFactor), + "Specifies destination blend factor when blendStyle is set to 'user'.\n" + "Possible values: GFXBlendZero, GFXBlendOne, GFXBlendSrcColor, GFXBlendInvSrcColor, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha, GFXBlendDestAlpha, or GFXBlendInvDestAlpha"); + + addField("textureFunction", TYPEID(), myOffset(texFunc), + "Selects a texture function that determines how the texture pixels are combined " + "with the shaded color of the billboard's quadrangle geometry.\n" + "Possible values: replace, modulate, or add."); + + Parent::initPersistFields(); +} + +void afxBillboardData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(color); + stream->writeString(txr_name); + mathWrite(*stream, dimensions); + mathWrite(*stream, texCoords[0]); + mathWrite(*stream, texCoords[1]); + mathWrite(*stream, texCoords[2]); + mathWrite(*stream, texCoords[3]); + + stream->writeInt(srcBlendFactor, 4); + stream->writeInt(dstBlendFactor, 4); + stream->writeInt(texFunc, 4); +} + +void afxBillboardData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&color); + txr_name = stream->readSTString(); + txr = GFXTexHandle(); + mathRead(*stream, &dimensions); + mathRead(*stream, &texCoords[0]); + mathRead(*stream, &texCoords[1]); + mathRead(*stream, &texCoords[2]); + mathRead(*stream, &texCoords[3]); + + srcBlendFactor = (GFXBlend) stream->readInt(4); + dstBlendFactor = (GFXBlend) stream->readInt(4); + texFunc = stream->readInt(4); +} + +bool afxBillboardData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + if (!server) + { + if (txr_name && txr_name[0] != '\0') + { + txr.set(txr_name, &GFXStaticTextureSRGBProfile, "Billboard Texture"); + } + } + + // if blend-style is set to User, check for defined blend-factors + if (blendStyle == BlendUser && (srcBlendFactor == BLEND_UNDEFINED || dstBlendFactor == BLEND_UNDEFINED)) + { + blendStyle = BlendUndefined; + Con::warnf(ConsoleLogEntry::General, "afxBillboardData(%s) incomplete blend factor specification.", getName()); + } + + // silently switch Undefined blend-style to User if blend factors are both defined + if (blendStyle == BlendUndefined && srcBlendFactor != BLEND_UNDEFINED && dstBlendFactor != BLEND_UNDEFINED) + { + blendStyle = BlendUser; + } + + // set pre-defined blend-factors + switch (blendStyle) + { + case BlendNormal: + srcBlendFactor = GFXBlendSrcAlpha; + dstBlendFactor = GFXBlendInvSrcAlpha; + break; + case BlendSubtractive: + srcBlendFactor = GFXBlendZero; + dstBlendFactor = GFXBlendInvSrcColor; + break; + case BlendPremultAlpha: + srcBlendFactor = GFXBlendOne; + dstBlendFactor = GFXBlendInvSrcAlpha; + break; + case BlendUser: + break; + case BlendAdditive: + srcBlendFactor = GFXBlendSrcAlpha; + dstBlendFactor = GFXBlendOne; + break; + case BlendUndefined: + default: + blendStyle = BlendNormal; + srcBlendFactor = GFXBlendSrcAlpha; + dstBlendFactor = GFXBlendInvSrcAlpha; + break; + } + + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxBillboard + +IMPLEMENT_CO_NETOBJECT_V1(afxBillboard); + +ConsoleDocClass( afxBillboard, + "@brief A Billboard effect as defined by an afxBillboardData datablock.\n\n" + + "A Billboard effect is a textured quadrangle which is always aligned to " + "face towards the camera. It is much like a single static particle and is rendered " + "in a similar fashion.\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" +); + +afxBillboard::afxBillboard() +{ + mNetFlags.clear(); + mNetFlags.set(IsGhost); + + mDataBlock = 0; + fade_amt = 1.0f; + is_visible = true; + sort_priority = 0; + live_color.set(1.0f, 1.0f, 1.0f, 1.0f); +} + +afxBillboard::~afxBillboard() +{ +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +bool afxBillboard::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + live_color = mDataBlock->color; + + return true; +} + +bool afxBillboard::onAdd() +{ + if(!Parent::onAdd()) + return false; + + F32 width = mDataBlock->dimensions.x * 0.5f; + F32 height = mDataBlock->dimensions.y * 0.5f; + mObjBox = Box3F(Point3F(-width, -0.01f, -height), Point3F(width, 0.01f, height)); + + addToScene(); + + return true; +} + +void afxBillboard::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxBillboard.h b/Engine/source/afx/ce/afxBillboard.h new file mode 100644 index 000000000..1c5d2e36b --- /dev/null +++ b/Engine/source/afx/ce/afxBillboard.h @@ -0,0 +1,131 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_BILLBOARD_H_ +#define _AFX_BILLBOARD_H_ + +#include "afx/afxEffectDefs.h" + +#define BLEND_UNDEFINED GFXBlend_COUNT + +class afxBillboardData : public GameBaseData, public afxEffectDefs +{ + typedef GameBaseData Parent; + +public: + // This enum specifies common blend settings with predefined values + // for src/dst blend factors. + enum BlendStyle { + BlendUndefined, + BlendNormal, + BlendAdditive, + BlendSubtractive, + BlendPremultAlpha, + BlendUser, + }; + + enum TexFuncType { + TexFuncReplace, + TexFuncModulate, + TexFuncAdd, + }; + +public: + StringTableEntry txr_name; + GFXTexHandle txr; + + LinearColorF color; + Point2F texCoords[4]; + Point2F dimensions; + S32 blendStyle; + GFXBlend srcBlendFactor; + GFXBlend dstBlendFactor; + S32 texFunc; + +public: + /*C*/ afxBillboardData(); + /*C*/ afxBillboardData(const afxBillboardData&, bool = false); + + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxBillboardData); + DECLARE_CATEGORY("AFX"); +}; + +typedef afxBillboardData::BlendStyle afxBillboard_BlendStyle; +DefineEnumType( afxBillboard_BlendStyle ); + +typedef afxBillboardData::TexFuncType afxBillboard_TexFuncType; +DefineEnumType( afxBillboard_TexFuncType ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxBillboard + +class afxBillboard : public GameBase, public afxEffectDefs +{ + typedef GameBase Parent; + friend class afxEA_Billboard; + +private: + afxBillboardData* mDataBlock; + + F32 fade_amt; + bool is_visible; + S8 sort_priority; + LinearColorF live_color; + + GFXStateBlockRef normal_sb; + GFXStateBlockRef reflected_sb; + +public: + /*C*/ afxBillboard(); + /*D*/ ~afxBillboard(); + + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + virtual bool onAdd(); + virtual void onRemove(); + + void setFadeAmount(F32 amt) { fade_amt = amt; } + void setSortPriority(S8 priority) { sort_priority = priority; } + void setVisibility(bool flag) { is_visible = flag; } + + virtual void prepRenderImage(SceneRenderState*); + + void _renderBillboard(ObjectRenderInst*, SceneRenderState*, BaseMatInstance*); + + DECLARE_CONOBJECT(afxBillboard); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_BILLBOARD_H_ diff --git a/Engine/source/afx/ce/afxBillboard_T3D.cpp b/Engine/source/afx/ce/afxBillboard_T3D.cpp new file mode 100644 index 000000000..429a4f51a --- /dev/null +++ b/Engine/source/afx/ce/afxBillboard_T3D.cpp @@ -0,0 +1,145 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "gfx/gfxTransformSaver.h" +#include "gfx/primBuilder.h" + +#include "afx/afxChoreographer.h" +#include "afx/ce/afxBillboard.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxBillboard::prepRenderImage(SceneRenderState* state) +{ + if (!is_visible) + return; + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(this, &afxBillboard::_renderBillboard); + ri->type = RenderPassManager::RIT_ObjectTranslucent; + ri->translucentSort = true; + ri->defaultKey = (U32)(dsize_t)mDataBlock; + ri->sortDistSq = getWorldBox().getSqDistanceToPoint( state->getCameraPosition() ); + state->getRenderPass()->addInst(ri); +} + +void afxBillboard::_renderBillboard(ObjectRenderInst *ri, SceneRenderState* state, BaseMatInstance* overrideMat) +{ + if (overrideMat) + return; + + // predraw + if (normal_sb.isNull()) + { + GFXStateBlockDesc desc; + + // Culling -- it's a billboard, so no backfaces + desc.setCullMode(GFXCullCW); + + // Blending + desc.setBlend(true, mDataBlock->srcBlendFactor, mDataBlock->dstBlendFactor); + desc.alphaTestEnable = (desc.blendSrc == GFXBlendSrcAlpha && + (desc.blendDest == GFXBlendInvSrcAlpha || desc.blendDest == GFXBlendOne)); + desc.alphaTestRef = 1; + desc.alphaTestFunc = GFXCmpGreaterEqual; + + desc.setZReadWrite(true); + desc.zFunc = GFXCmpLessEqual; + desc.zWriteEnable = false; + + desc.samplersDefined = true; + switch (mDataBlock->texFunc) + { + case afxBillboardData::TexFuncReplace: + desc.samplers[0].textureColorOp = GFXTOPDisable; + break; + case afxBillboardData::TexFuncModulate: + desc.samplers[0].textureColorOp = GFXTOPModulate; + break; + case afxBillboardData::TexFuncAdd: + desc.samplers[0].textureColorOp = GFXTOPAdd; + break; + } + + desc.samplers[1].textureColorOp = GFXTOPDisable; + + normal_sb = GFX->createStateBlock(desc); + + desc.setCullMode(GFXCullCCW); + reflected_sb = GFX->createStateBlock(desc); + } + + if (state->isReflectPass()) + GFX->setStateBlock(reflected_sb); + else + GFX->setStateBlock(normal_sb); + + GFXTransformSaver saver; + GFX->multWorld(getRenderTransform()); + + GFX->setTexture(0, mDataBlock->txr); + + MatrixF worldmod = GFX->getWorldMatrix(); + MatrixF viewmod = GFX->getViewMatrix(); + + Point4F Position; + MatrixF ModelView; + ModelView.mul(viewmod, worldmod); + ModelView.getColumn(3, &Position); + ModelView.identity(); + ModelView.setColumn(3, Position); + + GFX->setWorldMatrix(ModelView); + MatrixF ident; + ident.identity(); + GFX->setViewMatrix(ident); + + F32 width = mDataBlock->dimensions.x * 0.5f * mObjScale.x; + F32 height = mDataBlock->dimensions.y * 0.5f * mObjScale.z; + + Point3F points[4]; + points[0].set( width, 0.0f, -height); + points[1].set(-width, 0.0f, -height); + points[2].set(-width, 0.0f, height); + points[3].set( width, 0.0f, height); + + PrimBuild::begin(GFXTriangleStrip, 4); + { + PrimBuild::color4f(live_color.red, live_color.green, live_color.blue, live_color.alpha*fade_amt); + PrimBuild::texCoord2f(mDataBlock->texCoords[1].x, mDataBlock->texCoords[1].y); + PrimBuild::vertex3fv(points[1]); + PrimBuild::texCoord2f(mDataBlock->texCoords[2].x, mDataBlock->texCoords[2].y); + PrimBuild::vertex3fv(points[0]); + PrimBuild::texCoord2f(mDataBlock->texCoords[0].x, mDataBlock->texCoords[0].y); + PrimBuild::vertex3fv(points[2]); + PrimBuild::texCoord2f(mDataBlock->texCoords[3].x, mDataBlock->texCoords[3].y); + PrimBuild::vertex3fv(points[3]); + } + PrimBuild::end(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxCameraPuppet.cpp b/Engine/source/afx/ce/afxCameraPuppet.cpp new file mode 100644 index 000000000..14b578494 --- /dev/null +++ b/Engine/source/afx/ce/afxCameraPuppet.cpp @@ -0,0 +1,132 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "scene/sceneRenderState.h" +#include "math/mathIO.h" + +#include "afx/afxChoreographer.h" +#include "afx/ce/afxCameraPuppet.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxCameraPuppetData + +IMPLEMENT_CO_DATABLOCK_V1(afxCameraPuppetData); + +ConsoleDocClass( afxCameraPuppetData, + "@brief A datablock that specifies a Camera Puppet effect.\n\n" + + "A Camera Puppet effect is used to control the position and orientation of the camera using the AFX constraint system. " + "Camera Puppet effects are useful for creating small cut-scenes and can add a lot of visual drama to a spell or effectron " + "effect." + "\n\n" + + "Effective use of Camera Puppet effects require a fairly advanced understanding of how Torque cameras work in a " + "server-client context. Care must be taken to prevent client cameras from drifting too far out of sync from the server camera. " + "Otherwise, obvious discontinuities in the motion will result when the Camera Puppet ends and control is restored to the " + "server camera. Scoping problems can also result if a client camera is moved to a location that is inconsistent with the " + "scene scoping done by the server camera." + "\n\n" + + "Often it is useful to manage camera controlling in an isolated effectron rather than directly incorporated into a magic-spell. " + "This way the camera controlling effectron can target the specific client associated with the spellcaster. The spellcasting " + "player observes the spell in a dramatic cut-scene-like fashion while other players continue to observe from their own " + "viewing locations." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxCameraPuppetData::afxCameraPuppetData() +{ + cam_spec = ST_NULLSTRING; + networking = SERVER_AND_CLIENT; +} + +afxCameraPuppetData::afxCameraPuppetData(const afxCameraPuppetData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + cam_spec = other.cam_spec; + networking = other.networking; +} + +#define myOffset(field) Offset(field, afxCameraPuppetData) + +void afxCameraPuppetData::initPersistFields() +{ + addField("cameraSpec", TypeString, myOffset(cam_spec), + "This field is like the effect-wrapper fields for specifying constraint sources, " + "but here it specifies a target for the camera-puppet effect."); + addField("networking", TypeS8, myOffset(networking), + "Specifies the networking model used for the camerapuppet effect. The effect can " + "puppet just the server camera, just the client camera, or both.\n" + "Possible values: $AFX::SERVER_ONLY, $AFX::CLIENT_ONLY, or $AFX::SERVER_AND_CLIENT."); + + // disallow some field substitutions + disableFieldSubstitutions("cameraSpec"); + disableFieldSubstitutions("networking"); + + Parent::initPersistFields(); +} + +bool afxCameraPuppetData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + bool runs_on_s = ((networking & (SERVER_ONLY | SERVER_AND_CLIENT)) != 0); + bool runs_on_c = ((networking & (CLIENT_ONLY | SERVER_AND_CLIENT)) != 0); + cam_def.parseSpec(cam_spec, runs_on_s, runs_on_c); + + return true; +} + +void afxCameraPuppetData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(cam_spec); + stream->write(networking); +} + +void afxCameraPuppetData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + cam_spec = stream->readSTString(); + stream->read(&networking); +} + +void afxCameraPuppetData::gather_cons_defs(Vector& defs) +{ + if (cam_def.isDefined()) + defs.push_back(cam_def); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxCameraPuppet.h b/Engine/source/afx/ce/afxCameraPuppet.h new file mode 100644 index 000000000..4226e1242 --- /dev/null +++ b/Engine/source/afx/ce/afxCameraPuppet.h @@ -0,0 +1,64 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_CAMERA_PUPPET_H_ +#define _AFX_CAMERA_PUPPET_H_ + +#include "afx/ce/afxComponentEffect.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxConstraint.h" + +class afxCameraPuppetData : public GameBaseData, public afxEffectDefs, public afxComponentEffectData +{ + typedef GameBaseData Parent; + +public: + StringTableEntry cam_spec; + afxConstraintDef cam_def; + + U8 networking; + + virtual void gather_cons_defs(Vector& defs); + +public: + /*C*/ afxCameraPuppetData(); + /*C*/ afxCameraPuppetData(const afxCameraPuppetData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxCameraPuppetData); + DECLARE_CATEGORY("AFX"); +}; + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_CAMERA_PUPPET_H_ diff --git a/Engine/source/afx/ce/afxCameraShake.cpp b/Engine/source/afx/ce/afxCameraShake.cpp new file mode 100644 index 000000000..152deaff2 --- /dev/null +++ b/Engine/source/afx/ce/afxCameraShake.cpp @@ -0,0 +1,118 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +#include "afx/ce/afxCameraShake.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxCameraShakeData + +IMPLEMENT_CO_DATABLOCK_V1(afxCameraShakeData); + +ConsoleDocClass( afxCameraShakeData, + "@brief A datablock that specifies a Camera Shake effect.\n\n" + + "Camera Shake internally utilizes the standard Torque CameraShake class to implement a shaken camera effect." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxCameraShakeData::afxCameraShakeData() +{ + camShakeFreq.set( 10.0, 10.0, 10.0 ); + camShakeAmp.set( 1.0, 1.0, 1.0 ); + camShakeRadius = 10.0; + camShakeFalloff = 10.0; +} + +afxCameraShakeData::afxCameraShakeData(const afxCameraShakeData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + camShakeFreq = other.camShakeFreq; + camShakeAmp = other.camShakeAmp; + camShakeRadius = other.camShakeRadius; + camShakeFalloff = other.camShakeFalloff; +} + +#define myOffset(field) Offset(field, afxCameraShakeData) + +void afxCameraShakeData::initPersistFields() +{ + addField("frequency", TypePoint3F, Offset(camShakeFreq, afxCameraShakeData), + "The camera shake frequencies for all three axes: X, Y, Z."); + addField("amplitude", TypePoint3F, Offset(camShakeAmp, afxCameraShakeData), + "The camera shake amplitudes for all three axes: X, Y, Z."); + addField("radius", TypeF32, Offset(camShakeRadius, afxCameraShakeData), + "Radius about the effect position in which shaking will be applied."); + addField("falloff", TypeF32, Offset(camShakeFalloff, afxCameraShakeData), + "Magnitude by which shaking decreases over distance to radius."); + + Parent::initPersistFields(); +} + +bool afxCameraShakeData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxCameraShakeData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(camShakeFreq.x); + stream->write(camShakeFreq.y); + stream->write(camShakeFreq.z); + stream->write(camShakeAmp.x); + stream->write(camShakeAmp.y); + stream->write(camShakeAmp.z); + stream->write(camShakeRadius); + stream->write(camShakeFalloff); +} + +void afxCameraShakeData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&camShakeFreq.x); + stream->read(&camShakeFreq.y); + stream->read(&camShakeFreq.z); + stream->read(&camShakeAmp.x); + stream->read(&camShakeAmp.y); + stream->read(&camShakeAmp.z); + stream->read(&camShakeRadius); + stream->read(&camShakeFalloff); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxCameraShake.h b/Engine/source/afx/ce/afxCameraShake.h new file mode 100644 index 000000000..ca0bfb439 --- /dev/null +++ b/Engine/source/afx/ce/afxCameraShake.h @@ -0,0 +1,57 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_CAMERA_SHAKE_H_ +#define _AFX_CAMERA_SHAKE_H_ + +class afxCameraShakeData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + VectorF camShakeFreq; + VectorF camShakeAmp; + F32 camShakeRadius; + F32 camShakeFalloff; + +public: + /*C*/ afxCameraShakeData(); + /*C*/ afxCameraShakeData(const afxCameraShakeData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxCameraShakeData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_CAMERA_SHAKE_H_ diff --git a/Engine/source/afx/ce/afxCollisionEvent.cpp b/Engine/source/afx/ce/afxCollisionEvent.cpp new file mode 100644 index 000000000..9b51e2dc7 --- /dev/null +++ b/Engine/source/afx/ce/afxCollisionEvent.cpp @@ -0,0 +1,103 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +#include "afx/ce/afxCollisionEvent.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxCollisionEventData + +IMPLEMENT_CO_DATABLOCK_V1(afxCollisionEventData); + +ConsoleDocClass( afxCollisionEventData, + "@brief A datablock that specifies a Collision Event effect.\n\n" + + "MORE NEEDED HERE.\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxCollisionEventData::afxCollisionEventData() +{ + method_name = ST_NULLSTRING; + script_data = ST_NULLSTRING; + gen_trigger = false; + trigger_bit = 0; +} + +afxCollisionEventData::afxCollisionEventData(const afxCollisionEventData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + method_name = other.method_name; + script_data = other.script_data; + gen_trigger = other.gen_trigger; + trigger_bit = other.trigger_bit; +} + +#define myOffset(field) Offset(field, afxCollisionEventData) + +void afxCollisionEventData::initPersistFields() +{ + addField("methodName", TypeString, myOffset(method_name), + "..."); + addField("scriptData", TypeString, myOffset(script_data), + "..."); + addField("generateTrigger", TypeBool, myOffset(gen_trigger), + "..."); + addField("triggerBit", TypeS8, myOffset(trigger_bit), + "..."); + + Parent::initPersistFields(); +} + +void afxCollisionEventData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(method_name); + stream->writeString(script_data); + if (stream->writeFlag(gen_trigger)) + stream->write(trigger_bit); +} + +void afxCollisionEventData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + method_name = stream->readSTString(); + script_data = stream->readSTString(); + gen_trigger = stream->readFlag(); + if (gen_trigger) + stream->read(&trigger_bit); + else + trigger_bit = 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxCollisionEvent.h b/Engine/source/afx/ce/afxCollisionEvent.h new file mode 100644 index 000000000..31f6e09a1 --- /dev/null +++ b/Engine/source/afx/ce/afxCollisionEvent.h @@ -0,0 +1,59 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_COLLISION_EVENT_H_ +#define _AFX_COLLISION_EVENT_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxCollisionEventData + +struct afxCollisionEventData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + StringTableEntry method_name; + StringTableEntry script_data; + bool gen_trigger; + U8 trigger_bit; + +public: + /*C*/ afxCollisionEventData(); + /*C*/ afxCollisionEventData(const afxCollisionEventData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxCollisionEventData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_COLLISION_EVENT_H_ diff --git a/Engine/source/afx/ce/afxComponentEffect.h b/Engine/source/afx/ce/afxComponentEffect.h new file mode 100644 index 000000000..5491b7891 --- /dev/null +++ b/Engine/source/afx/ce/afxComponentEffect.h @@ -0,0 +1,41 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_COMPONENT_EFFECT_H_ +#define _AFX_COMPONENT_EFFECT_H_ + +#include "core/util/tVector.h" + +#include "afx/afxConstraint.h" + +class afxComponentEffectData +{ +public: + virtual void gather_cons_defs(Vector& defs) { }; +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_COMPONENT_EFFECT_H_ diff --git a/Engine/source/afx/ce/afxConsoleMessage.cpp b/Engine/source/afx/ce/afxConsoleMessage.cpp new file mode 100644 index 000000000..4854eef48 --- /dev/null +++ b/Engine/source/afx/ce/afxConsoleMessage.cpp @@ -0,0 +1,93 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +#include "afx/ce/afxConsoleMessage.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxConsoleMessageData + +IMPLEMENT_CO_DATABLOCK_V1(afxConsoleMessageData); + +ConsoleDocClass( afxConsoleMessageData, + "@brief A datablock that specifies a Console Message effect.\n\n" + + "Console Message effects are useful for debugging purposes when you want to make sure that an effect with a certain kind " + "of timing is actually getting executed and for evaluating some kinds of field substitutions." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxConsoleMessageData::afxConsoleMessageData() +{ + message_str = ST_NULLSTRING; +} + +afxConsoleMessageData::afxConsoleMessageData(const afxConsoleMessageData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + message_str = other.message_str; +} + +#define myOffset(field) Offset(field, afxConsoleMessageData) + +void afxConsoleMessageData::initPersistFields() +{ + addField("message", TypeString, myOffset(message_str), + "A text message to be displayed when the effect is executed."); + + Parent::initPersistFields(); +} + +bool afxConsoleMessageData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxConsoleMessageData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(message_str); +} + +void afxConsoleMessageData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + message_str = stream->readSTString(); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxConsoleMessage.h b/Engine/source/afx/ce/afxConsoleMessage.h new file mode 100644 index 000000000..224107181 --- /dev/null +++ b/Engine/source/afx/ce/afxConsoleMessage.h @@ -0,0 +1,54 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_CONSOLE_MESSAGE_H_ +#define _AFX_CONSOLE_MESSAGE_H_ + +class afxConsoleMessageData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + StringTableEntry message_str; + +public: + /*C*/ afxConsoleMessageData(); + /*C*/ afxConsoleMessageData(const afxConsoleMessageData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxConsoleMessageData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_CONSOLE_MESSAGE_H_ diff --git a/Engine/source/afx/ce/afxDamage.cpp b/Engine/source/afx/ce/afxDamage.cpp new file mode 100644 index 000000000..4d8ca412a --- /dev/null +++ b/Engine/source/afx/ce/afxDamage.cpp @@ -0,0 +1,140 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +#include "afx/ce/afxDamage.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxDamageData + +IMPLEMENT_CO_DATABLOCK_V1(afxDamageData); + +ConsoleDocClass( afxDamageData, + "@brief A datablock that specifies a Damage effect.\n\n" + + "A Damage effect is useful for assigning damage with unusual timing that must be synchronized with other effects. They " + "can be used to deal direct damage, radius damage, and damage over time. Negative damage amounts can be used for " + "healing effects." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxDamageData::afxDamageData() +{ + label = ST_NULLSTRING; + flavor = ST_NULLSTRING; + amount = 0; + repeats = 1; + ad_amount = 0; + radius = 0; + impulse = 0; +} + +afxDamageData::afxDamageData(const afxDamageData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + label = other.label; + flavor = other.flavor; + amount = other.amount; + repeats = other.repeats; + ad_amount = other.ad_amount; + radius = other.radius; + impulse = other.impulse; +} + +#define myOffset(field) Offset(field, afxDamageData) + +void afxDamageData::initPersistFields() +{ + addField("label", TypeString, myOffset(label), + "An arbitrary string which is passed as an argument to a spell's onDamage() script " + "method. It can be used to identify which damage effect the damage came from in " + "cases where more than one damage effect is used in a single spell."); + addField("flavor", TypeString, myOffset(flavor), + "An arbitrary string which is passed as an argument to a spell's onDamage() script " + "method. It is used to classify a type of damage such as 'melee', 'magical', or " + "'fire'."); + addField("directDamage", TypeF32, myOffset(amount), + "An amount of direct damage to inflict on a target."); + addField("directDamageRepeats", TypeS8, myOffset(repeats), + "The number of times to inflict the damage specified by directDamage. Values " + "greater than 1 inflict damage over time, with the amount of directDamage " + "repeatedly dealt at evenly spaced intervals over the lifetime of the effect."); + addField("areaDamage", TypeF32, myOffset(ad_amount), + "An amount of area damage to inflict on a target. Objects within half the radius " + "receive full damage which then diminishes out to the full distance of " + "areaDamageRadius."); + addField("areaDamageRadius", TypeF32, myOffset(radius), + "Radius centered at the effect position in which damage will be applied."); + addField("areaDamageImpulse", TypeF32, myOffset(impulse), + "Specifies an amount of force to apply to damaged objects. Objects within half the " + "radius receive full impulse which then diminishes out to the full distance of " + "areaDamageRadius."); + + Parent::initPersistFields(); +} + +bool afxDamageData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxDamageData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(label); + stream->writeString(flavor); + stream->write(amount); + stream->write(repeats); + stream->write(ad_amount); + stream->write(radius); + stream->write(impulse); +} + +void afxDamageData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + label = stream->readSTString(); + flavor = stream->readSTString(); + stream->read(&amount); + stream->read(&repeats); + stream->read(&ad_amount); + stream->read(&radius); + stream->read(&impulse); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxDamage.h b/Engine/source/afx/ce/afxDamage.h new file mode 100644 index 000000000..19b6e7943 --- /dev/null +++ b/Engine/source/afx/ce/afxDamage.h @@ -0,0 +1,63 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_DAMAGE_H_ +#define _AFX_DAMAGE_H_ + +#include "afx/afxEffectDefs.h" + +class afxDamageData : public GameBaseData, public afxEffectDefs +{ + typedef GameBaseData Parent; + +public: + StringTableEntry label; + StringTableEntry flavor; + + F32 amount; + U8 repeats; + F32 ad_amount; + F32 radius; + F32 impulse; + +public: + /*C*/ afxDamageData(); + /*C*/ afxDamageData(const afxDamageData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxDamageData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_DAMAGE_H_ diff --git a/Engine/source/afx/ce/afxFootSwitch.cpp b/Engine/source/afx/ce/afxFootSwitch.cpp new file mode 100644 index 000000000..45d273b90 --- /dev/null +++ b/Engine/source/afx/ce/afxFootSwitch.cpp @@ -0,0 +1,116 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +#include "afx/ce/afxFootSwitch.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxFootSwitchData + +IMPLEMENT_CO_DATABLOCK_V1(afxFootSwitchData); + +ConsoleDocClass( afxFootSwitchData, + "@brief A datablock that specifies a Foot Switch effect.\n\n" + + "A Foot Switch effect is used to disable some or all of the standard built-in footstep effects generated by Player objects." + "\n\n" + + "Stock Player objects generate footprint decals, footstep sounds, and puffs of particle dust in response to special " + "animation triggers embedded in the Player's dts model. With the help of Phase Effects, AFX can substitute alternatives for " + "these built-in effects. When this is done, it is often preferable to turn off some or all of the built-in footstep effects." + "\n\n" + + "Foot Switch effects are only meaningful when the primary position constraint is a Player or Player-derived object." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxFootSwitchData::afxFootSwitchData() +{ + override_all = false; + override_decals = false; + override_sounds = false; + override_dust = false; +} + +afxFootSwitchData::afxFootSwitchData(const afxFootSwitchData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + override_all = other.override_all; + override_decals = other.override_decals; + override_sounds = other.override_sounds; + override_dust = other.override_dust; +} + +#define myOffset(field) Offset(field, afxFootSwitchData) + +void afxFootSwitchData::initPersistFields() +{ + addField("overrideAll", TypeBool, myOffset(override_all), + "When true, all of a Player's footstep effects are turned off for the duration of " + "the foot-switch effect."); + addField("overrideDecals", TypeBool, myOffset(override_decals), + "Specifically selects whether the Player's footprint decals are enabled."); + addField("overrideSounds", TypeBool, myOffset(override_sounds), + "Specifically selects whether the Player's footstep sounds are enabled."); + addField("overrideDust", TypeBool, myOffset(override_dust), + "Specifically selects whether the Player's footstep puffs of dust are enabled."); + + Parent::initPersistFields(); +} + +void afxFootSwitchData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if (!stream->writeFlag(override_all)) + { + stream->writeFlag(override_decals); + stream->writeFlag(override_sounds); + stream->writeFlag(override_dust); + } +} + +void afxFootSwitchData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + override_all = stream->readFlag(); + if (!override_all) + { + override_decals = stream->readFlag(); + override_sounds = stream->readFlag(); + override_dust = stream->readFlag(); + } +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxFootSwitch.h b/Engine/source/afx/ce/afxFootSwitch.h new file mode 100644 index 000000000..6420756e9 --- /dev/null +++ b/Engine/source/afx/ce/afxFootSwitch.h @@ -0,0 +1,56 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_FOOT_SWITCH_H_ +#define _AFX_FOOT_SWITCH_H_ + +class afxFootSwitchData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + bool override_all; + bool override_decals; + bool override_sounds; + bool override_dust; + +public: + /*C*/ afxFootSwitchData(); + /*C*/ afxFootSwitchData(const afxFootSwitchData&, bool = false); + + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxFootSwitchData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_FOOT_SWITCH_H_ diff --git a/Engine/source/afx/ce/afxGuiController.cpp b/Engine/source/afx/ce/afxGuiController.cpp new file mode 100644 index 000000000..094cd0f5b --- /dev/null +++ b/Engine/source/afx/ce/afxGuiController.cpp @@ -0,0 +1,109 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "core/stream/bitStream.h" + +#include "afx/ce/afxGuiController.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxGuiControllerData + +IMPLEMENT_CO_DATABLOCK_V1(afxGuiControllerData); + +ConsoleDocClass( afxGuiControllerData, + "@brief A datablock that specifies a Gui Controller effect.\n\n" + + "A Gui Controller enables effect manipulation of pre-existing gui controls. With a Gui Controller effect, a regular gui control " + "is located by name, made visible during the lifetime of the effect, and potentially repositioned by projecting 3D constraint " + "positions into 2D screen space. In addition, when used with a progress-bar control, (GuiProgressCtrl, afxSpellCastBar, " + "afxStatusBar), the progress-bar will continuously reflect the elapsed progress of the effect over its lifetime." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxGuiControllerData::afxGuiControllerData() +{ + control_name = ST_NULLSTRING; + preserve_pos = false; + ctrl_client_only = false; +} + +afxGuiControllerData::afxGuiControllerData(const afxGuiControllerData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + control_name = other.control_name; + preserve_pos = other.preserve_pos; + ctrl_client_only = other.ctrl_client_only; +} + +#define myOffset(field) Offset(field, afxGuiControllerData) + +void afxGuiControllerData::initPersistFields() +{ + addField("controlName", TypeString, myOffset(control_name), + "Specifies the name of an existing gui-control."); + addField("preservePosition", TypeBool, myOffset(preserve_pos), + "When true, the gui-control will retain its initial position, otherwise the " + "gui-control position will be continuously updated using a projection of the " + "3D constraint position into 2D screen coordinates."); + addField("controllingClientOnly", TypeBool, myOffset(ctrl_client_only), + "If true, the effect will only be applied to a gui-control on the client that " + "matches the controlling-client of the primary position constraint object."); + + Parent::initPersistFields(); +} + +bool afxGuiControllerData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxGuiControllerData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(control_name); + stream->writeFlag(preserve_pos); + stream->writeFlag(ctrl_client_only); +} + +void afxGuiControllerData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + control_name = stream->readSTString(); + preserve_pos = stream->readFlag(); + ctrl_client_only = stream->readFlag(); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxGuiController.h b/Engine/source/afx/ce/afxGuiController.h new file mode 100644 index 000000000..90db3249c --- /dev/null +++ b/Engine/source/afx/ce/afxGuiController.h @@ -0,0 +1,58 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_GUI_CONTROLLER_H_ +#define _AFX_GUI_CONTROLLER_H_ + +#include "afx/afxEffectDefs.h" + +class afxGuiControllerData : public GameBaseData, public afxEffectDefs +{ + typedef GameBaseData Parent; + +public: + StringTableEntry control_name; + bool preserve_pos; + bool ctrl_client_only; + +public: + /*C*/ afxGuiControllerData(); + /*C*/ afxGuiControllerData(const afxGuiControllerData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxGuiControllerData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_GUI_CONTROLLER_H_ diff --git a/Engine/source/afx/ce/afxGuiText.cpp b/Engine/source/afx/ce/afxGuiText.cpp new file mode 100644 index 000000000..2b733f3f4 --- /dev/null +++ b/Engine/source/afx/ce/afxGuiText.cpp @@ -0,0 +1,103 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "core/stream/bitStream.h" + +#include "afx/ce/afxGuiText.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxGuiTextData + +IMPLEMENT_CO_DATABLOCK_V1(afxGuiTextData); + +ConsoleDocClass( afxGuiTextData, + "@brief A datablock that specifies a Gui Text effect.\n\n" + + "A Gui Text effect, with the help of an existing afxGuiTextHud, can be used to display 2D text effects on the Gui Canvas. " + "Essentially, using Gui Text effects with an afxGuiTextHud is like using the stock GuiShapeNameHud, but with the ability " + "to make additional text elements come and go as effects constrained to the projection of 3D positions onto the 2D screen." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxGuiTextData::afxGuiTextData() +{ + text_str = ST_NULLSTRING; + text_clr.set(1,1,1,1); +} + +afxGuiTextData::afxGuiTextData(const afxGuiTextData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + text_str = other.text_str; + text_clr = other.text_clr; +} + +#define myOffset(field) Offset(field, afxGuiTextData) + +void afxGuiTextData::initPersistFields() +{ + addField("text", TypeString, myOffset(text_str), + "The literal text to display on the afxGuiTextHud. The center of the text will be " + "placed at the projection of the 3D constraint position into 2D screen space.\n" + "If the text field is set to the special string, '#shapeName', the shape name of the " + "primary position constraint object will be used. (This is only meaningful if the " + "constraint source is a ShapeBase-derived object.)"); + addField("color", TypeColorF, myOffset(text_clr), + "A color value for the text label."); + + Parent::initPersistFields(); +} + +bool afxGuiTextData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxGuiTextData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(text_str); + stream->write(text_clr); +} + +void afxGuiTextData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + text_str = stream->readSTString(); + stream->read(&text_clr); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxGuiText.h b/Engine/source/afx/ce/afxGuiText.h new file mode 100644 index 000000000..fa03de46c --- /dev/null +++ b/Engine/source/afx/ce/afxGuiText.h @@ -0,0 +1,57 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_GUI_TEXT_H_ +#define _AFX_GUI_TEXT_H_ + +#include "afx/afxEffectDefs.h" + +class afxGuiTextData : public GameBaseData, public afxEffectDefs +{ + typedef GameBaseData Parent; + +public: + StringTableEntry text_str; + LinearColorF text_clr; + +public: + /*C*/ afxGuiTextData(); + /*C*/ afxGuiTextData(const afxGuiTextData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxGuiTextData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_GUI_TEXT_H_ diff --git a/Engine/source/afx/ce/afxLight.cpp b/Engine/source/afx/ce/afxLight.cpp new file mode 100644 index 000000000..79a0f8d7b --- /dev/null +++ b/Engine/source/afx/ce/afxLight.cpp @@ -0,0 +1,41 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/ce/afxLight.h" +IMPLEMENT_CO_DATABLOCK_V1(afxLightData); + +ConsoleDocClass( afxLightData, + "@brief afxLightData is a legacy datablock which is not supported for T3D.\n\n" + + "In T3D, instead of afxLightData, use afxT3DPointLightData or afxT3DSpotLightData.\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxLight.h b/Engine/source/afx/ce/afxLight.h new file mode 100644 index 000000000..11181fdc5 --- /dev/null +++ b/Engine/source/afx/ce/afxLight.h @@ -0,0 +1,38 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_LIGHT_H_ +#define _AFX_LIGHT_H_ + +struct afxLightData : public GameBaseData +{ + typedef GameBaseData Parent; + DECLARE_CONOBJECT(afxLightData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_LIGHT_H_ diff --git a/Engine/source/afx/ce/afxLightBase_T3D.cpp b/Engine/source/afx/ce/afxLightBase_T3D.cpp new file mode 100644 index 000000000..209bc7426 --- /dev/null +++ b/Engine/source/afx/ce/afxLightBase_T3D.cpp @@ -0,0 +1,243 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "T3D/lightAnimData.h" + +#include "afx/ce/afxLightBase_T3D.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxT3DLightBaseData + +IMPLEMENT_CO_DATABLOCK_V1(afxT3DLightBaseData); + +ConsoleDocClass( afxT3DLightBaseData, + "@brief A datablock baseclass for afxT3DPointLightData and afxT3DSpotLightData.\n\n" + + "Not intended to be used directly, afxT3DLightBaseData exists to provide base member variables and generic functionality " + "for the derived classes afxT3DPointLightData and afxT3DSpotLightData." + "\n\n" + + "@see afxT3DPointLightData\n\n" + "@see afxT3DSpotLightData\n\n" + "@see PointLight\n\n" + "@see SpotLight\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxT3DLightBaseData::afxT3DLightBaseData() + : mIsEnabled( true ), + mColor( LinearColorF::WHITE ), + mBrightness( 1.0f ), + mCastShadows( false ), + mPriority( 1.0f ), + mAnimationData( NULL ), + mFlareData( NULL ), + mFlareScale( 1.0f ) +{ + + mLocalRenderViz = false; + + // marked true if datablock ids need to + // be converted into pointers + do_id_convert = false; +} + +afxT3DLightBaseData::afxT3DLightBaseData(const afxT3DLightBaseData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + mIsEnabled = other.mIsEnabled; + mColor = other.mColor; + mBrightness = other.mBrightness; + mCastShadows = other.mCastShadows; + mPriority = other.mPriority; + mAnimationData = other.mAnimationData; + mAnimState = other.mAnimState; + mFlareData = other.mFlareData; + mFlareScale = other.mFlareScale; + + mLocalRenderViz = other.mLocalRenderViz; + + do_id_convert = other.do_id_convert; +} + +// +// NOTE: keep this as consistent as possible with LightBase::initPersistFields() +// +void afxT3DLightBaseData::initPersistFields() +{ + // We only add the basic lighting options that all lighting + // systems would use... the specific lighting system options + // are injected at runtime by the lighting system itself. + + addGroup( "Light" ); + + addField( "isEnabled", TypeBool, Offset( mIsEnabled, afxT3DLightBaseData ), + "Enables/Disables the object rendering and functionality in the scene."); + addField( "color", TypeColorF, Offset( mColor, afxT3DLightBaseData ), + "Changes the base color hue of the light."); + addField( "brightness", TypeF32, Offset( mBrightness, afxT3DLightBaseData ), + "Adjusts the lights power, 0 being off completely."); + addField( "castShadows", TypeBool, Offset( mCastShadows, afxT3DLightBaseData ), + "Enables/disables shadow casts by this light."); + addField( "priority", TypeF32, Offset( mPriority, afxT3DLightBaseData ), + "Used for sorting of lights by the light manager. Priority determines if a light " + "has a stronger effect than, those with a lower value"); + addField( "localRenderViz", TypeBool, Offset( mLocalRenderViz, afxT3DLightBaseData ), + "Enables/disables a semi-transparent geometry to help visualize the light's " + "range and placement."); + + endGroup( "Light" ); + + addGroup( "Light Animation" ); + + addField( "animate", TypeBool, Offset( mAnimState.active, afxT3DLightBaseData ), + "Toggles animation for the light on and off"); + addField( "animationType", TYPEID(), Offset( mAnimationData, afxT3DLightBaseData ), + "Datablock containing light animation information (LightAnimData)"); + addField( "animationPeriod", TypeF32, Offset( mAnimState.animationPeriod, afxT3DLightBaseData ), + "The length of time in seconds for a single playback of the light animation"); + addField( "animationPhase", TypeF32, Offset( mAnimState.animationPhase, afxT3DLightBaseData ), + "The phase used to offset the animation start time to vary the animation of " + "nearby lights."); + + endGroup( "Light Animation" ); + + addGroup( "Misc" ); + + addField( "flareType", TYPEID(), Offset( mFlareData, afxT3DLightBaseData ), + "Datablock containing light flare information (LightFlareData)"); + addField( "flareScale", TypeF32, Offset( mFlareScale, afxT3DLightBaseData ), + "Globally scales all features of the light flare"); + + endGroup( "Misc" ); + + /* + // Now inject any light manager specific fields. + LightManager::initLightFields(); + */ + + // We do the parent fields at the end so that + // they show up that way in the inspector. + Parent::initPersistFields(); +} + +bool afxT3DLightBaseData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxT3DLightBaseData::packData(BitStream* stream) +{ + Parent::packData(stream); + + // note: BitStream's overloaded write() for LinearColorF will convert + // to ColorI for transfer and then back to LinearColorF. This is fine + // for most color usage but for lighting colors we want to preserve + // "pushed" color values which may be greater than 1.0 so the color + // is instead sent as individual color primaries. + stream->write( mColor.red ); + stream->write( mColor.green ); + stream->write( mColor.blue ); + + stream->write( mBrightness ); + stream->writeFlag( mCastShadows ); + stream->write( mAnimState.animationPeriod ); + stream->write( mAnimState.animationPhase ); + stream->write( mFlareScale ); + + writeDatablockID(stream, mAnimationData, packed); + writeDatablockID(stream, mFlareData, packed); +} + +void afxT3DLightBaseData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read( &mColor.red ); + stream->read( &mColor.green ); + stream->read( &mColor.blue ); + mColor.alpha = 1.0f; + + stream->read( &mBrightness ); + mCastShadows = stream->readFlag(); + stream->read( &mAnimState.animationPeriod ); + stream->read( &mAnimState.animationPhase ); + stream->read( &mFlareScale ); + + mAnimationData = (LightAnimData*) readDatablockID(stream); + mFlareData = (LightFlareData*) readDatablockID(stream); + + do_id_convert = true; +} + +bool afxT3DLightBaseData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) + { + if (do_id_convert) + { + SimObjectId anim_id = SimObjectId((uintptr_t)mAnimationData); + if (anim_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(anim_id, mAnimationData)) + { + Con::errorf(ConsoleLogEntry::General, + "afxT3DLightBaseData::preload() -- bad datablockId: 0x%x (animationType)", + anim_id); + } + } + SimObjectId flare_id = SimObjectId((uintptr_t)mFlareData); + if (flare_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(flare_id, mFlareData)) + { + Con::errorf(ConsoleLogEntry::General, + "afxT3DLightBaseData::preload() -- bad datablockId: 0x%x (flareType)", + flare_id); + } + } + do_id_convert = false; + } + } + + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxLightBase_T3D.h b/Engine/source/afx/ce/afxLightBase_T3D.h new file mode 100644 index 000000000..1a517d02d --- /dev/null +++ b/Engine/source/afx/ce/afxLightBase_T3D.h @@ -0,0 +1,73 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_T3D_LIGHT_BASE_H_ +#define _AFX_T3D_LIGHT_BASE_H_ + +#include "T3D/lightBase.h" + +class LightAnimData; +class LightFlareData; + +class afxT3DLightBaseData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + bool mIsEnabled; + LinearColorF mColor; + F32 mBrightness; + bool mCastShadows; + F32 mPriority; + + LightAnimData* mAnimationData; + LightAnimState mAnimState; + + bool mLocalRenderViz; + + LightFlareData* mFlareData; + F32 mFlareScale; + + bool do_id_convert; + +public: + /*C*/ afxT3DLightBaseData(); + /*C*/ afxT3DLightBaseData(const afxT3DLightBaseData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxT3DLightBaseData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_T3D_LIGHT_BASE_H_ diff --git a/Engine/source/afx/ce/afxMachineGun.cpp b/Engine/source/afx/ce/afxMachineGun.cpp new file mode 100644 index 000000000..6b040ff5b --- /dev/null +++ b/Engine/source/afx/ce/afxMachineGun.cpp @@ -0,0 +1,127 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "lighting/lightInfo.h" +#include "T3D/projectile.h" + +#include "afx/ce/afxMachineGun.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMachineGunData + +IMPLEMENT_CO_DATABLOCK_V1(afxMachineGunData); + +ConsoleDocClass( afxMachineGunData, + "@brief A datablock that specifies a Machine Gun effect.\n\n" + + "Machine Gun is a simple but useful effect for rapidly shooting standard Torque Projectile objects. For performance " + "reasons, keep in mind that each bullet is a separate Projectile object, which is not a very lightweight object." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxMachineGunData::afxMachineGunData() +{ + projectile_data = 0; + rounds_per_minute = 60; +} + +afxMachineGunData::afxMachineGunData(const afxMachineGunData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + projectile_data = other.projectile_data; + rounds_per_minute = other.rounds_per_minute; +} + +#define myOffset(field) Offset(field, afxMachineGunData) + +void afxMachineGunData::initPersistFields() +{ + addField("projectile", TYPEID(), myOffset(projectile_data), + "A ProjectileData datablock describing the projectile to be launched."); + addField("roundsPerMinute", TypeS32, myOffset(rounds_per_minute), + "Specifies the number of projectiles fired over a minute of time. A value of 1200 " + "will create 20 projectiles per second.\n" + "Sample values for real machine guns:\n" + " AK-47 = 600, M16 = 750-900, UZI = 600"); + + Parent::initPersistFields(); + + // disallow some field substitutions + disableFieldSubstitutions("projectile"); +} + +bool afxMachineGunData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + if (projectile_data) + { + if (getId() >= DataBlockObjectIdFirst && getId() <= DataBlockObjectIdLast) + { + SimObjectId pid = projectile_data->getId(); + if (pid < DataBlockObjectIdFirst || pid > DataBlockObjectIdLast) + { + Con::errorf(ConsoleLogEntry::General,"afxMachineGunData: bad ProjectileData datablock."); + return false; + } + } + } + + return true; +} + +void afxMachineGunData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if (stream->writeFlag(projectile_data)) + stream->writeRangedU32(projectile_data->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast); + + stream->write(rounds_per_minute); +} + +void afxMachineGunData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + if (stream->readFlag()) + { + SimObjectId id = stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast); + Sim::findObject(id, projectile_data); + } + + stream->read(&rounds_per_minute); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxMachineGun.h b/Engine/source/afx/ce/afxMachineGun.h new file mode 100644 index 000000000..f01941f55 --- /dev/null +++ b/Engine/source/afx/ce/afxMachineGun.h @@ -0,0 +1,59 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_MACHINE_GUN_H_ +#define _AFX_MACHINE_GUN_H_ + +#include "afx/afxEffectDefs.h" + +class ProjectileData; + +class afxMachineGunData : public GameBaseData, public afxEffectDefs +{ + typedef GameBaseData Parent; + +public: + ProjectileData* projectile_data; + S32 rounds_per_minute; + +public: + /*C*/ afxMachineGunData(); + /*C*/ afxMachineGunData(const afxMachineGunData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxMachineGunData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_MACHINE_GUN_H_ diff --git a/Engine/source/afx/ce/afxModel.cpp b/Engine/source/afx/ce/afxModel.cpp new file mode 100644 index 000000000..688cd08ad --- /dev/null +++ b/Engine/source/afx/ce/afxModel.cpp @@ -0,0 +1,770 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "T3D/objectTypes.h" +#include "T3D/gameBase/gameProcess.h" +#include "core/resourceManager.h" +#include "sim/netConnection.h" +#include "scene/sceneRenderState.h" +#include "scene/sceneManager.h" +#include "ts/tsShapeInstance.h" +#include "ts/tsMaterialList.h" + +#include "afx/ce/afxModel.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxModelData + +IMPLEMENT_CO_DATABLOCK_V1(afxModelData); + +ConsoleDocClass( afxModelData, + "@brief A datablock that specifies a Model effect.\n\n" + + "A Model effect is a lightweight client-only geometry object useful for effect-driven props." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxModelData::afxModelData() +{ + shapeName = ST_NULLSTRING; + sequence = ST_NULLSTRING; + seq_rate = 1.0f; + seq_offset = 0.0f; + alpha_mult = 1.0f; + use_vertex_alpha = false; + force_on_material_flags = 0; + force_off_material_flags = 0; + texture_filtering = true; + fog_mult = 1.0f; + remap_txr_tags = ST_NULLSTRING; + remap_buffer = 0; + + overrideLightingOptions = false; + receiveSunLight = true; + receiveLMLighting = true; + useAdaptiveSelfIllumination = false; + useCustomAmbientLighting = false; + customAmbientForSelfIllumination = false; + customAmbientLighting = LinearColorF(0.0f, 0.0f, 0.0f); + shadowEnable = false; + + shadowSize = 128; + shadowMaxVisibleDistance = 80.0f; + shadowProjectionDistance = 10.0f; + shadowSphereAdjust = 1.0; +} + +afxModelData::afxModelData(const afxModelData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + shapeName = other.shapeName; + shape = other.shape; // -- + sequence = other.sequence; + seq_rate = other.seq_rate; + seq_offset = other.seq_offset; + alpha_mult = other.alpha_mult; + use_vertex_alpha = other.use_vertex_alpha; + force_on_material_flags = other.force_on_material_flags; + force_off_material_flags = other.force_off_material_flags; + texture_filtering = other.texture_filtering; + fog_mult = other.fog_mult; + remap_txr_tags = other.remap_txr_tags; + remap_buffer = other.remap_buffer; + overrideLightingOptions = other.overrideLightingOptions; + receiveSunLight = other.receiveSunLight; + receiveLMLighting = other.receiveLMLighting; + useAdaptiveSelfIllumination = other.useAdaptiveSelfIllumination; + useCustomAmbientLighting = other.useCustomAmbientLighting; + customAmbientForSelfIllumination = other.customAmbientForSelfIllumination; + customAmbientLighting = other.customAmbientLighting; + shadowEnable = other.shadowEnable; +} + +afxModelData::~afxModelData() +{ + if (remap_buffer) + dFree(remap_buffer); +} + +bool afxModelData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + // don't need to do this stuff on the server + if (server) + return true; + + if (shapeName != ST_NULLSTRING && !shape) + { + shape = ResourceManager::get().load(shapeName); + if (!shape) + { + errorStr = String::ToString("afxModelData::load: Failed to load shape \"%s\"", shapeName); + return false; + } + + // just parse up the string and collect the remappings in txr_tag_remappings. + if (remap_txr_tags != ST_NULLSTRING) + { + txr_tag_remappings.clear(); + if (remap_buffer) + dFree(remap_buffer); + + remap_buffer = dStrdup(remap_txr_tags); + + char* remap_token = dStrtok(remap_buffer, " \t"); + while (remap_token != NULL) + { + char* colon = dStrchr(remap_token, ':'); + if (colon) + { + *colon = '\0'; + txr_tag_remappings.increment(); + txr_tag_remappings.last().old_tag = remap_token; + txr_tag_remappings.last().new_tag = colon+1; + } + remap_token = dStrtok(NULL, " \t"); + } + } + + // this little hack messes things up when remapping texture tags + if (txr_tag_remappings.size() == 0) + { + // this little hack forces the textures to preload + TSShapeInstance* pDummy = new TSShapeInstance(shape); + delete pDummy; + } + } + + return true; +} + +#define myOffset(field) Offset(field, afxModelData) + +void afxModelData::initPersistFields() +{ + addField("shapeFile", TypeFilename, myOffset(shapeName), + "The name of a .dts format file to use for the model."); + addField("sequence", TypeFilename, myOffset(sequence), + "The name of an animation sequence to play in the model."); + addField("sequenceRate", TypeF32, myOffset(seq_rate), + "The rate of playback for the sequence."); + addField("sequenceOffset", TypeF32, myOffset(seq_offset), + "An offset in seconds indicating a starting point for the animation sequence " + "specified by the sequence field. A rate of 1.0 (rather than sequenceRate) is used " + "to convert from seconds to the thread offset."); + addField("alphaMult", TypeF32, myOffset(alpha_mult), + "An alpha multiplier used to set maximum opacity of the model."); + + addField("fogMult", TypeF32, myOffset(fog_mult), + ""); + addField("remapTextureTags", TypeString, myOffset(remap_txr_tags), + "Rename one or more texture tags in the model. Texture tags are what link a " + "model's textures to materials.\n" + "Field should be a string containing space-separated remapping tokens. A remapping " + "token is two names separated by a colon, ':'. The first name should be a texture-tag " + "that exists in the model, while the second is a new name to replace it. The string " + "can have any number of remapping tokens as long as the total string length does not " + "exceed 255."); + addField("shadowEnable", TypeBool, myOffset(shadowEnable), + "Sets whether the model casts a shadow."); + + addField("useVertexAlpha", TypeBool, myOffset(use_vertex_alpha), + "deprecated"); + addField("forceOnMaterialFlags", TypeS32, myOffset(force_on_material_flags), + "deprecated"); + addField("forceOffMaterialFlags", TypeS32, myOffset(force_off_material_flags), + "deprecated"); + addField("textureFiltering", TypeBool, myOffset(texture_filtering), + "deprecated"); + addField("overrideLightingOptions", TypeBool, myOffset(overrideLightingOptions), + "deprecated"); + addField("receiveSunLight", TypeBool, myOffset(receiveSunLight), + ""); + addField("receiveLMLighting", TypeBool, myOffset(receiveLMLighting), + "deprecated"); + addField("useAdaptiveSelfIllumination", TypeBool, myOffset(useAdaptiveSelfIllumination), + "deprecated"); + addField("useCustomAmbientLighting", TypeBool, myOffset(useCustomAmbientLighting), + "deprecated"); + addField("customAmbientSelfIllumination", TypeBool, myOffset(customAmbientForSelfIllumination), + "deprecated"); + addField("customAmbientLighting", TypeColorF, myOffset(customAmbientLighting), + "deprecated"); + addField("shadowSize", TypeS32, myOffset(shadowSize), + "deprecated"); + addField("shadowMaxVisibleDistance", TypeF32, myOffset(shadowMaxVisibleDistance), + "deprecated"); + addField("shadowProjectionDistance", TypeF32, myOffset(shadowProjectionDistance), + "deprecated"); + addField("shadowSphereAdjust", TypeF32, myOffset(shadowSphereAdjust), + "deprecated"); + + Parent::initPersistFields(); + + // Material Flags + Con::setIntVariable("$MaterialFlags::S_Wrap", TSMaterialList::S_Wrap); + Con::setIntVariable("$MaterialFlags::T_Wrap", TSMaterialList::T_Wrap); + Con::setIntVariable("$MaterialFlags::Translucent", TSMaterialList::Translucent); + Con::setIntVariable("$MaterialFlags::Additive", TSMaterialList::Additive); + Con::setIntVariable("$MaterialFlags::Subtractive", TSMaterialList::Subtractive); + Con::setIntVariable("$MaterialFlags::SelfIlluminating", TSMaterialList::SelfIlluminating); + Con::setIntVariable("$MaterialFlags::NeverEnvMap", TSMaterialList::NeverEnvMap); + Con::setIntVariable("$MaterialFlags::NoMipMap", TSMaterialList::NoMipMap); + Con::setIntVariable("$MaterialFlags::MipMap_ZeroBorder", TSMaterialList::MipMap_ZeroBorder); + Con::setIntVariable("$MaterialFlags::AuxiliaryMap", TSMaterialList::AuxiliaryMap); + +#if defined(AFX_CAP_AFXMODEL_TYPE) + Con::setIntVariable("$TypeMasks::afxModelObjectType", afxModelObjectType); +#endif +} + +void afxModelData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(shapeName); + stream->writeString(sequence); + stream->write(seq_rate); + stream->write(seq_offset); + stream->write(alpha_mult); + stream->write(use_vertex_alpha); + stream->write(force_on_material_flags); + stream->write(force_off_material_flags); + stream->writeFlag(texture_filtering); + stream->write(fog_mult); + + stream->writeString(remap_txr_tags); + + stream->writeFlag(overrideLightingOptions); + stream->writeFlag(receiveSunLight); + stream->writeFlag(useAdaptiveSelfIllumination); + stream->writeFlag(useCustomAmbientLighting); + stream->writeFlag(customAmbientForSelfIllumination); + stream->write(customAmbientLighting); + stream->writeFlag(receiveLMLighting); + stream->writeFlag(shadowEnable); + + stream->write(shadowSize); + stream->write(shadowMaxVisibleDistance); + stream->write(shadowProjectionDistance); + stream->write(shadowSphereAdjust); +} + +void afxModelData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + shapeName = stream->readSTString(); + sequence = stream->readSTString(); + stream->read(&seq_rate); + stream->read(&seq_offset); + stream->read(&alpha_mult); + stream->read(&use_vertex_alpha); + stream->read(&force_on_material_flags); + stream->read(&force_off_material_flags); + texture_filtering = stream->readFlag(); + stream->read(&fog_mult); + + remap_txr_tags = stream->readSTString(); + + overrideLightingOptions = stream->readFlag(); + receiveSunLight = stream->readFlag(); + useAdaptiveSelfIllumination = stream->readFlag(); + useCustomAmbientLighting = stream->readFlag(); + customAmbientForSelfIllumination = stream->readFlag(); + stream->read(&customAmbientLighting); + receiveLMLighting = stream->readFlag(); + shadowEnable = stream->readFlag(); + + stream->read(&shadowSize); + stream->read(&shadowMaxVisibleDistance); + stream->read(&shadowProjectionDistance); + stream->read(&shadowSphereAdjust); +} + +void afxModelData::onPerformSubstitutions() +{ + if (shapeName != ST_NULLSTRING) + { + shape = ResourceManager::get().load(shapeName); + if (!shape) + { + Con::errorf("afxModelData::onPerformSubstitutions: Failed to load shape \"%s\"", shapeName); + return; + } + + // REMAP-TEXTURE-TAGS ISSUES? + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxModel + +IMPLEMENT_CO_NETOBJECT_V1(afxModel); + +ConsoleDocClass( afxModel, + "@brief A Model effect as defined by an afxModelData datablock.\n\n" + + "A Model effect is a lightweight client-only geometry object useful for effect-driven " + "props.\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" +); + +afxModel::afxModel() +{ + mTypeMask |= DynamicShapeObjectType; +#if defined(AFX_CAP_AFXMODEL_TYPE) + mTypeMask |= afxModelObjectType; +#endif + + shape_inst = 0; + + main_seq_thread = 0; + main_seq_id = -1; + seq_rate_factor = 1.0f; + last_anim_tag = 0; + + seq_animates_vis = false; + fade_amt = 1.0f; + is_visible = true; + sort_priority = 0; + + mNetFlags.set( IsGhost ); +} + +afxModel::~afxModel() +{ + delete shape_inst; +} + +void afxModel::setSequenceRateFactor(F32 factor) +{ + seq_rate_factor = factor; + if (shape_inst != NULL && main_seq_thread != NULL) + shape_inst->setTimeScale(main_seq_thread, seq_rate_factor*mDataBlock->seq_rate); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +bool afxModel::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + return true; +} + +bool afxModel::onAdd() +{ + // first check if we have a server connection, if we don't then this is on the server + // and we should exit, then check if the parent fails to add the object + NetConnection* conn = NetConnection::getConnectionToServer(); + if (!conn || !Parent::onAdd()) + return false; + + // setup our bounding box + if (mDataBlock->shape) + mObjBox = mDataBlock->shape->bounds; + else + mObjBox = Box3F(Point3F(-1, -1, -1), Point3F(1, 1, 1)); + + // setup the shape instance and sequence + if (mDataBlock->shape) + { + if (/*isClientObject() && */mDataBlock->txr_tag_remappings.size() > 0) + { + // temporarily substitute material tags with alternates + TSMaterialList* mat_list = mDataBlock->shape->materialList; + if (mat_list) + { + for (S32 i = 0; i < mDataBlock->txr_tag_remappings.size(); i++) + { + afxModelData::TextureTagRemapping* remap = &mDataBlock->txr_tag_remappings[i]; + Vector & mat_names = (Vector&) mat_list->getMaterialNameList(); + for (S32 j = 0; j < mat_names.size(); j++) + { + if (mat_names[j].compare(remap->old_tag, dStrlen(remap->old_tag), String::NoCase) == 0) + { + //Con::printf("REMAP TEXTURE TAG [%s] TO [%s]", remap->old_tag, remap->new_tag); + mat_names[j] = String(remap->new_tag); + mat_names[j].insert(0,'#'); + break; + } + } + } + } + } + + shape_inst = new TSShapeInstance(mDataBlock->shape); + + if (true) // isClientObject()) + { + shape_inst->cloneMaterialList(); + + // restore the material tags to original form + if (mDataBlock->txr_tag_remappings.size() > 0) + { + TSMaterialList* mat_list = mDataBlock->shape->materialList; + if (mat_list) + { + for (S32 i = 0; i < mDataBlock->txr_tag_remappings.size(); i++) + { + afxModelData::TextureTagRemapping* remap = &mDataBlock->txr_tag_remappings[i]; + Vector & mat_names = (Vector&) mat_list->getMaterialNameList(); + for (S32 j = 0; j < mat_names.size(); j++) + { + if (mat_names[j].compare(remap->new_tag, dStrlen(remap->new_tag)) == 0) + { + //Con::printf("UNREMAP TEXTURE TAG [%s] TO [%s]", remap->new_tag, remap->old_tag); + mat_names[j] = String(remap->old_tag); + break; + } + } + } + } + } + } + + if (mDataBlock->sequence == ST_NULLSTRING) + { + main_seq_thread = 0; + main_seq_id = -1; + } + else + { + // here we start the default animation sequence + TSShape* shape = shape_inst->getShape(); + main_seq_id = shape->findSequence(mDataBlock->sequence); + if (main_seq_id != -1) + { + main_seq_thread = shape_inst->addThread(); + + F32 seq_pos = 0.0f; + if (mDataBlock->seq_offset > 0.0f && mDataBlock->seq_offset < shape_inst->getDuration(main_seq_thread)) + seq_pos = mDataBlock->seq_offset / shape_inst->getDuration(main_seq_thread); + + shape_inst->setTimeScale(main_seq_thread, seq_rate_factor*mDataBlock->seq_rate); + shape_inst->setSequence(main_seq_thread, main_seq_id, seq_pos); + seq_animates_vis = shape->sequences[main_seq_id].visMatters.testAll(); + } + } + + // deal with material changes + if (shape_inst && (mDataBlock->force_on_material_flags | mDataBlock->force_off_material_flags)) + { + shape_inst->cloneMaterialList(); + TSMaterialList* mats = shape_inst->getMaterialList(); + if (mDataBlock->force_on_material_flags != 0) + { + for (U32 i = 0; i < mats->size(); i++) + mats->setFlags(i, mats->getFlags(i) | mDataBlock->force_on_material_flags); + } + + if (mDataBlock->force_off_material_flags != 0) + { + for (U32 i = 0; i < mats->size(); i++) + mats->setFlags(i, mats->getFlags(i) & ~mDataBlock->force_off_material_flags); + } + } + } + + resetWorldBox(); + + if (mDataBlock->shape) + { + // Scan out the collision hulls... + static const String sCollisionStr( "collision-" ); + + for (U32 i = 0; i < mDataBlock->shape->details.size(); i++) + { + const String &name = mDataBlock->shape->names[mDataBlock->shape->details[i].nameIndex]; + + if (name.compare( sCollisionStr, sCollisionStr.length(), String::NoCase ) == 0) + { + mCollisionDetails.push_back(i); + + // The way LOS works is that it will check to see if there is a LOS detail that matches + // the the collision detail + 1 + MaxCollisionShapes (this variable name should change in + // the future). If it can't find a matching LOS it will simply use the collision instead. + // We check for any "unmatched" LOS's further down + mLOSDetails.increment(); + + char buff[128]; + dSprintf(buff, sizeof(buff), "LOS-%d", i + 1 + 8/*MaxCollisionShapes*/); + U32 los = mDataBlock->shape->findDetail(buff); + if (los == -1) + mLOSDetails.last() = i; + else + mLOSDetails.last() = los; + } + } + + // Snag any "unmatched" LOS details + static const String sLOSStr( "LOS-" ); + + for (U32 i = 0; i < mDataBlock->shape->details.size(); i++) + { + const String &name = mDataBlock->shape->names[mDataBlock->shape->details[i].nameIndex]; + + if (name.compare( sLOSStr, sLOSStr.length(), String::NoCase ) == 0) + { + // See if we already have this LOS + bool found = false; + for (U32 j = 0; j < mLOSDetails.size(); j++) + { + if (mLOSDetails[j] == i) + { + found = true; + break; + } + } + + if (!found) + mLOSDetails.push_back(i); + } + } + + // Compute the hull accelerators (actually, just force the shape to compute them) + for (U32 i = 0; i < mCollisionDetails.size(); i++) + shape_inst->getShape()->getAccelerator(mCollisionDetails[i]); + } + + // tell engine the model exists + gClientSceneGraph->addObjectToScene(this); + removeFromProcessList(); + ClientProcessList::get()->addObject(this); + conn->addObject(this); + + return true; +} + +void afxModel::onRemove() +{ + mSceneManager->removeObjectFromScene(this); + getContainer()->removeObject(this); + Parent::onRemove(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +void afxModel::advanceTime(F32 dt) +{ + if (main_seq_thread) + shape_inst->advanceTime(dt, main_seq_thread); + + for (S32 i = 0; i < blend_clips.size(); i++) + shape_inst->advanceTime(dt, blend_clips[i].thread); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +void afxModel::prepRenderImage(SceneRenderState* state) +{ + if (!is_visible || !shape_inst) + return; + + // calculate distance to camera + Point3F cameraOffset; + getRenderTransform().getColumn(3, &cameraOffset); + cameraOffset -= state->getCameraPosition(); + F32 dist = cameraOffset.len(); + if (dist < 0.01f) + dist = 0.01f; + + F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z)); + shape_inst->setDetailFromDistance(state, dist*invScale); + if ( shape_inst->getCurrentDetail() < 0 ) + return; + + renderObject(state); +} + +bool afxModel::castRay(const Point3F &start, const Point3F &end, RayInfo* info) +{ + if (shape_inst) + { + RayInfo shortest; + shortest.t = 1e8; + + info->object = NULL; + if (mLOSDetails.size() > 0) + { + for (U32 i = 0; i < mLOSDetails.size(); i++) + { + shape_inst->animate(mLOSDetails[i]); + if (shape_inst->castRay(start, end, info, mLOSDetails[i])) + { + info->object = this; + if (info->t < shortest.t) + shortest = *info; + } + } + } + else + { + if (mCollisionDetails.size() > 0) + { + for (U32 i = 0; i < mCollisionDetails.size(); i++) + { + shape_inst->animate(mCollisionDetails[i]); + if (shape_inst->castRay(start, end, info, mCollisionDetails[i])) + { + info->object = this; + if (info->t < shortest.t) + shortest = *info; + } + } + } + } + + if (info->object == this) + { + // Copy out the shortest time... + *info = shortest; + return true; + } + } + + return false; +} + +U32 afxModel::unique_anim_tag_counter = 1; +#define BAD_ANIM_ID 999999999 + +U32 afxModel::setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans) +{ + if (!shape_inst) + return 0; + + TSShape* shape = shape_inst->getShape(); + + S32 seq_id = shape->findSequence(clip); + if (seq_id == -1) + { + Con::errorf("afxModel::setAnimClip() -- failed to find a sequence matching the name, \"%s\".", clip); + return 0; + } + + // JTF Note: test if this blend implementation is working + if (shape->sequences[seq_id].isBlend()) + { + BlendThread blend_clip; + blend_clip.tag = ((unique_anim_tag_counter++) | 0x80000000); + + blend_clip.thread = shape_inst->addThread(); + shape_inst->setSequence(blend_clip.thread, seq_id, pos); + shape_inst->setTimeScale(blend_clip.thread, rate); + + blend_clips.push_back(blend_clip); + + return blend_clip.tag; + } + + if (!main_seq_thread) + { + main_seq_thread = shape_inst->addThread(); + shape_inst->setTimeScale(main_seq_thread, seq_rate_factor*rate); + shape_inst->setSequence(main_seq_thread, seq_id, pos); + seq_animates_vis = shape->sequences[seq_id].visMatters.testAll(); + } + else + { + shape_inst->setTimeScale(main_seq_thread, seq_rate_factor*rate); + + F32 transTime = (trans < 0) ? 0.25 : trans; + if (transTime > 0.0f) + shape_inst->transitionToSequence(main_seq_thread, seq_id, pos, transTime, true); + else + shape_inst->setSequence(main_seq_thread, seq_id, pos); + + seq_animates_vis = shape->sequences[seq_id].visMatters.testAll(); + } + + last_anim_tag = unique_anim_tag_counter++; + + return last_anim_tag; +} + +void afxModel::resetAnimation(U32 tag) +{ + // check if this is a blended clip + if ((tag & 0x80000000) != 0) + { + for (S32 i = 0; i < blend_clips.size(); i++) + { + if (blend_clips[i].tag == tag) + { + if (blend_clips[i].thread) + { + //Con::printf("DESTROY THREAD %d of %d tag=%d" , i, blend_clips.size(), tag & 0x7fffffff); + shape_inst->destroyThread(blend_clips[i].thread); + } + blend_clips.erase_fast(i); + break; + } + } + return; + } + + if (tag != 0 && tag == last_anim_tag) + { + // restore original non-animated state + if (main_seq_id == -1) + { + shape_inst->destroyThread(main_seq_thread); + main_seq_thread = 0; + } + // restore original sequence + else + { + shape_inst->setTimeScale(main_seq_thread, seq_rate_factor*mDataBlock->seq_rate); + shape_inst->transitionToSequence(main_seq_thread, main_seq_id , 0.0f, 0.25f, true); + } + last_anim_tag = 0; + } +} + +F32 afxModel::getAnimClipDuration(const char* clip) +{ + if (!shape_inst) + return 0.0f; + + TSShape* shape = shape_inst->getShape(); + S32 seq_id = shape->findSequence(clip); + return (seq_id != -1) ? shape->sequences[seq_id].duration : 0.0f; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxModel.h b/Engine/source/afx/ce/afxModel.h new file mode 100644 index 000000000..931f89287 --- /dev/null +++ b/Engine/source/afx/ce/afxModel.h @@ -0,0 +1,166 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_MODEL_H_ +#define _AFX_MODEL_H_ + +#include "renderInstance/renderPassManager.h" + +class ParticleEmitterData; +class ParticleEmitter; +class ExplosionData; +class TSPartInstance; +class TSShapeInstance; +class TSShape; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxModel Data + +struct afxModelData : public GameBaseData +{ + typedef GameBaseData Parent; + + StringTableEntry shapeName; + StringTableEntry sequence; + F32 seq_rate; + F32 seq_offset; + F32 alpha_mult; + bool use_vertex_alpha; + U32 force_on_material_flags; + U32 force_off_material_flags; + bool texture_filtering; + F32 fog_mult; + + struct TextureTagRemapping + { + char* old_tag; + char* new_tag; + }; + char* remap_buffer; + Vector txr_tag_remappings; + + StringTableEntry remap_txr_tags; + + Resource shape; + + bool overrideLightingOptions; + bool receiveSunLight; + bool receiveLMLighting; + bool useAdaptiveSelfIllumination; + bool useCustomAmbientLighting; + bool customAmbientForSelfIllumination; + LinearColorF customAmbientLighting; + bool shadowEnable; + + U32 shadowSize; + F32 shadowMaxVisibleDistance; + F32 shadowProjectionDistance; + F32 shadowSphereAdjust; + +public: + /*C*/ afxModelData(); + /*C*/ afxModelData(const afxModelData&, bool = false); + /*D*/ ~afxModelData(); + + bool preload(bool server, String &errorStr); + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual void onPerformSubstitutions(); + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxModelData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxModel + +class afxModel : public GameBase +{ + typedef GameBase Parent; + +private: + afxModelData* mDataBlock; + TSShapeInstance* shape_inst; + TSThread* main_seq_thread; + S32 main_seq_id; + F32 seq_rate_factor; + bool seq_animates_vis; + U32 last_anim_tag; + F32 fade_amt; + bool is_visible; + S8 sort_priority; + + struct BlendThread + { + TSThread* thread; + U32 tag; + }; + Vector blend_clips; + static U32 unique_anim_tag_counter; + +protected: + Vector mCollisionDetails; + Vector mLOSDetails; + bool castRay(const Point3F &start, const Point3F &end, RayInfo* info); + + virtual void advanceTime(F32 dt); + + virtual void prepRenderImage(SceneRenderState*); + + void renderObject(SceneRenderState*); + + virtual bool onAdd(); + virtual void onRemove(); + +public: + /*C*/ afxModel(); + /*D*/ ~afxModel(); + + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + + void setFadeAmount(F32 amt) { fade_amt = amt; } + void setSequenceRateFactor(F32 factor); + void setSortPriority(S8 priority) { sort_priority = priority; } + + const char* getShapeFileName() const { return mDataBlock->shapeName; } + void setVisibility(bool flag) { is_visible = flag; } + TSShape* getTSShape() { return mDataBlock->shape; } + TSShapeInstance* getTSShapeInstance() { return shape_inst; } + + U32 setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans); + void resetAnimation(U32 tag); + F32 getAnimClipDuration(const char* clip); + + DECLARE_CONOBJECT(afxModel); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_MODEL_H_ diff --git a/Engine/source/afx/ce/afxModel_T3D.cpp b/Engine/source/afx/ce/afxModel_T3D.cpp new file mode 100644 index 000000000..be804f949 --- /dev/null +++ b/Engine/source/afx/ce/afxModel_T3D.cpp @@ -0,0 +1,71 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "scene/sceneRenderState.h" +#include "scene/sceneManager.h" +#include "ts/tsShapeInstance.h" +#include "lighting/lightQuery.h" + +#include "afx/ce/afxModel.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxModel::renderObject(SceneRenderState* state) +{ + MatrixF proj = GFX->getProjectionMatrix(); + RectI viewport = GFX->getViewport(); + + MatrixF world = GFX->getWorldMatrix(); + + GFX->pushWorldMatrix(); + + TSRenderState rdata; + rdata.setSceneState( state ); + rdata.setFadeOverride(fade_amt*mDataBlock->alpha_mult); + + // We might have some forward lit materials + // so pass down a query to gather lights. + LightQuery query; + query.init( getWorldSphere() ); + rdata.setLightQuery( &query ); + + MatrixF mat = getRenderTransform(); + mat.scale( mObjScale ); + GFX->setWorldMatrix( mat ); + + shape_inst->animate(); + + shape_inst->render(rdata); + + GFX->popWorldMatrix(); + + GFX->setProjectionMatrix( proj ); + GFX->setViewport( viewport ); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxMooring.cpp b/Engine/source/afx/ce/afxMooring.cpp new file mode 100644 index 000000000..b9cda9ec7 --- /dev/null +++ b/Engine/source/afx/ce/afxMooring.cpp @@ -0,0 +1,278 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" + +#include "afx/afxChoreographer.h" +#include "afx/ce/afxMooring.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMooringData + +IMPLEMENT_CO_DATABLOCK_V1(afxMooringData); + +ConsoleDocClass( afxMooringData, + "@brief A datablock that specifies a Mooring effect.\n\n" + + "A Mooring is an invisible effect object which can be positioned and oriented within a scene like other objects. Its main " + "purpose is to serve as a common mount point for other effects within the same choreographer. Typically one uses AFX " + "animation features to create movement for a Mooring and then other effects are bound to it using effect-to-effect " + "constraints (#effect)." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxMooringData::afxMooringData() +{ + track_pos_only = false; + networking = SCOPE_ALWAYS; + display_axis_marker = false; +} + +afxMooringData::afxMooringData(const afxMooringData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + track_pos_only = other.track_pos_only; + networking = other.networking; + display_axis_marker = other.display_axis_marker; +} + +#define myOffset(field) Offset(field, afxMooringData) + +void afxMooringData::initPersistFields() +{ + addField("displayAxisMarker", TypeBool, myOffset(display_axis_marker), + "Specifies whether to display an axis to help visualize the position and orientation " + "of the mooring."); + addField("trackPosOnly", TypeBool, myOffset(track_pos_only), + "This field is only meaningful for networking settings of SCOPE_ALWAYS and GHOSTABLE. " + "In these cases, client moorings are ghosting a mooring on the server, and " + "trackPosOnly determines if the client moorings need to be updated with the server " + "mooring's complete transform or just its position. If only the position needs to be " + "tracked, setting trackPosOnly to true will reduce the network traffic."); + addField("networking", TypeS8, myOffset(networking), + "Specifies the networking model used for the mooring and should be one of: " + "$AFX::SCOPE_ALWAYS, $AFX::GHOSTABLE, $AFX::SERVER_ONLY, or $AFX::CLIENT_ONLY"); + + Parent::initPersistFields(); +} + +bool afxMooringData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxMooringData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(display_axis_marker); + stream->write(track_pos_only); + stream->write(networking); +} + +void afxMooringData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&display_axis_marker); + stream->read(&track_pos_only); + stream->read(&networking); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMooring + +IMPLEMENT_CO_NETOBJECT_V1(afxMooring); + +ConsoleDocClass( afxMooring, + "@brief A Mooring effect as defined by an afxMooringData datablock.\n\n" + + "A Mooring is an invisible effect object which can be positioned and oriented within " + "a scene like other objects. Its main purpose is to serve as a common mount point for " + "other effects within the same choreographer. Typically one uses AFX animation " + "features to create movement for a Mooring and then other effects are bound to it " + "using effect-to-effect constraints (#effect).\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" +); + +afxMooring::afxMooring() +{ + mNetFlags.set(Ghostable | ScopeAlways); + + chor_id = 0; + hookup_with_chor = false; + ghost_cons_name = ST_NULLSTRING; +} + +afxMooring::afxMooring(U32 networking, U32 chor_id, StringTableEntry cons_name) +{ + if (networking & SCOPE_ALWAYS) + { + mNetFlags.clear(); + mNetFlags.set(Ghostable | ScopeAlways); + } + else if (networking & GHOSTABLE) + { + mNetFlags.clear(); + mNetFlags.set(Ghostable); + } + else if (networking & SERVER_ONLY) + { + mNetFlags.clear(); + } + else // if (networking & CLIENT_ONLY) + { + mNetFlags.clear(); + mNetFlags.set(IsGhost); + } + + this->chor_id = chor_id; + hookup_with_chor = false; + this->ghost_cons_name = cons_name; +} + +afxMooring::~afxMooring() +{ +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +bool afxMooring::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + return true; +} + +void afxMooring::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if (hookup_with_chor) + { + afxChoreographer* chor = arcaneFX::findClientChoreographer(chor_id); + if (chor) + { + chor->setGhostConstraintObject(this, ghost_cons_name); + hookup_with_chor = false; + } + } + + Point3F pos = getRenderPosition(); +} + +U32 afxMooring::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(conn, mask, stream); + + // InitialUpdate + if (stream->writeFlag(mask & InitialUpdateMask)) + { + stream->write(chor_id); + stream->writeString(ghost_cons_name); + } + + if (stream->writeFlag(mask & PositionMask)) + { + if (mDataBlock->track_pos_only) + mathWrite(*stream, mObjToWorld.getPosition()); + else + stream->writeAffineTransform(mObjToWorld); + } + + return retMask; +} + +//~~~~~~~~~~~~~~~~~~~~// + +void afxMooring::unpackUpdate(NetConnection * conn, BitStream * stream) +{ + Parent::unpackUpdate(conn, stream); + + // InitialUpdate + if (stream->readFlag()) + { + stream->read(&chor_id); + ghost_cons_name = stream->readSTString(); + + if (chor_id != 0 && ghost_cons_name != ST_NULLSTRING) + hookup_with_chor = true; + } + + if (stream->readFlag()) + { + if (mDataBlock->track_pos_only) + { + Point3F pos; + mathRead(*stream, &pos); + setPosition(pos); + } + else + { + MatrixF mat; + stream->readAffineTransform(&mat); + setTransform(mat); + setRenderTransform(mat); + } + } +} + +void afxMooring::setTransform(const MatrixF& mat) +{ + Parent::setTransform(mat); + setMaskBits(PositionMask); +} + +bool afxMooring::onAdd() +{ + if(!Parent::onAdd()) + return false; + + mObjBox = Box3F(Point3F(-0.5, -0.5, -0.5), Point3F(0.5, 0.5, 0.5)); + + addToScene(); + + return true; +} + +void afxMooring::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxMooring.h b/Engine/source/afx/ce/afxMooring.h new file mode 100644 index 000000000..d6cfb0b77 --- /dev/null +++ b/Engine/source/afx/ce/afxMooring.h @@ -0,0 +1,102 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_MOORING_H_ +#define _AFX_MOORING_H_ + +#include "renderInstance/renderPassManager.h" + +#include "afx/afxEffectDefs.h" + +class afxMooringData : public GameBaseData, public afxEffectDefs +{ + typedef GameBaseData Parent; + +public: + U8 networking; + bool track_pos_only; + bool display_axis_marker; + +public: + /*C*/ afxMooringData(); + /*C*/ afxMooringData(const afxMooringData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxMooringData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxMooring + +class afxMooring : public GameBase, public afxEffectDefs +{ + typedef GameBase Parent; + +private: + afxMooringData* mDataBlock; + U32 chor_id; + bool hookup_with_chor; + StringTableEntry ghost_cons_name; + + GFXStateBlockRef axis_sb; + void _renderAxisLines(ObjectRenderInst*, SceneRenderState*, BaseMatInstance*); + +protected: + enum MaskBits + { + PositionMask = Parent::NextFreeMask, + NextFreeMask = Parent::NextFreeMask << 1 + }; + +public: + /*C*/ afxMooring(); + /*C*/ afxMooring(U32 networking, U32 chor_id, StringTableEntry cons_name); + /*D*/ ~afxMooring(); + + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + virtual void advanceTime(F32 dt); + virtual bool onAdd(); + virtual void onRemove(); + virtual U32 packUpdate(NetConnection*, U32, BitStream*); + virtual void unpackUpdate(NetConnection*, BitStream*); + virtual void setTransform(const MatrixF&); + + virtual void prepRenderImage(SceneRenderState*); + + DECLARE_CONOBJECT(afxMooring); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_MOORING_H_ diff --git a/Engine/source/afx/ce/afxMooring_T3D.cpp b/Engine/source/afx/ce/afxMooring_T3D.cpp new file mode 100644 index 000000000..cf1512cfe --- /dev/null +++ b/Engine/source/afx/ce/afxMooring_T3D.cpp @@ -0,0 +1,88 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "gfx/gfxTransformSaver.h" +#include "gfx/primBuilder.h" + +#include "afx/afxChoreographer.h" +#include "afx/ce/afxMooring.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxMooring::prepRenderImage(SceneRenderState* state) +{ + if (!mDataBlock->display_axis_marker) + return; + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(this, &afxMooring::_renderAxisLines); + ri->type = RenderPassManager::RIT_ObjectTranslucent; + ri->translucentSort = true; + ri->defaultKey = (U32)(dsize_t)mDataBlock; + ri->sortDistSq = getWorldBox().getSqDistanceToPoint( state->getCameraPosition() ); + state->getRenderPass()->addInst(ri); +} + +void afxMooring::_renderAxisLines(ObjectRenderInst *ri, SceneRenderState* state, BaseMatInstance* overrideMat) +{ + if (overrideMat) + return; + + if (axis_sb.isNull()) + { + GFXStateBlockDesc desc; + + desc.blendDefined = true; + desc.blendEnable = false; + desc.cullDefined = true; + desc.cullMode = GFXCullNone; + desc.ffLighting = false; + desc.zDefined = true; + desc.zWriteEnable = false; + + axis_sb = GFX->createStateBlock(desc); + } + + GFX->setStateBlock(axis_sb); + + GFXTransformSaver saver; + GFX->multWorld(getRenderTransform()); + + PrimBuild::begin(GFXLineList, 6); + PrimBuild::color(LinearColorF(1.0, 0.0, 0.0)); + PrimBuild::vertex3f(-0.5, 0.0, 0.0); + PrimBuild::vertex3f( 0.5, 0.0, 0.0); + PrimBuild::color(LinearColorF(0.0, 1.0, 0.0)); + PrimBuild::vertex3f( 0.0, -0.5, 0.0); + PrimBuild::vertex3f( 0.0, 0.5, 0.0); + PrimBuild::color(LinearColorF(0.0, 0.0, 1.0)); + PrimBuild::vertex3f( 0.0, 0.0, -0.5); + PrimBuild::vertex3f( 0.0, 0.0, 0.5); + PrimBuild::end(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxMultiLight.cpp b/Engine/source/afx/ce/afxMultiLight.cpp new file mode 100644 index 000000000..6dc5f3a72 --- /dev/null +++ b/Engine/source/afx/ce/afxMultiLight.cpp @@ -0,0 +1,40 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/ce/afxMultiLight.h" + +IMPLEMENT_CO_DATABLOCK_V1(afxMultiLightData); + +ConsoleDocClass( afxMultiLightData, + "@brief afxMultiLightData is a legacy datablock which is not supported for T3D.\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxMultiLight.h b/Engine/source/afx/ce/afxMultiLight.h new file mode 100644 index 000000000..9a2261909 --- /dev/null +++ b/Engine/source/afx/ce/afxMultiLight.h @@ -0,0 +1,38 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_MULTI_LIGHT_H_ +#define _AFX_MULTI_LIGHT_H_ + +struct afxMultiLightData : public GameBaseData +{ + typedef GameBaseData Parent; + DECLARE_CONOBJECT(afxMultiLightData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_MULTI_LIGHT_H_ diff --git a/Engine/source/afx/ce/afxParticleEmitter.cpp b/Engine/source/afx/ce/afxParticleEmitter.cpp new file mode 100644 index 000000000..3983b27ba --- /dev/null +++ b/Engine/source/afx/ce/afxParticleEmitter.cpp @@ -0,0 +1,1617 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "scene/sceneManager.h" +#include "T3D/gameBase/gameProcess.h" + +#include "afx/util/afxPath.h" +#include "afx/util/afxPath3D.h" +#include "afx/ce/afxParticleEmitter.h" + +IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterData); + +ConsoleDocClass( afxParticleEmitterData, + "@brief A base datablock inherited by AFX Particle Emitter effects.\n\n" + + "A base datablock inherited by AFX Particle Emitter effects." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxParticleEmitterData::afxParticleEmitterData() +{ + fade_velocity = false; // coordinate velocity amount with fade amout + fade_offset = false; // coordinate ejection-offset amount with fade amount + pe_vector.set(0.0,0.0,0.0); + pe_vector_is_world = false; + tpaths_string = ST_NULLSTRING; + tPathDataBlocks.clear(); + tPathDataBlockIds.clear(); +} + +afxParticleEmitterData::afxParticleEmitterData(const afxParticleEmitterData& other, bool temp_clone) : ParticleEmitterData(other, temp_clone) +{ + fade_velocity = other.fade_velocity; + fade_offset = other.fade_offset; + pe_vector = other.pe_vector; + pe_vector_is_world = other.pe_vector_is_world; + tpaths_string = other.tpaths_string; + tPathDataBlocks = other.tPathDataBlocks; + //tPathDataBlockIds = other.tPathDataBlockIds; +} + +void afxParticleEmitterData::initPersistFields() +{ + addField("fadeVelocity", TypeBool, Offset(fade_velocity, afxParticleEmitterData), + "If true, the initial velocity of emitted particles is multiplied by the fade amount " + "of the containing effect wrapper. As the effect fades-in and out, so does the " + "initial velocity of new particles."); + addField("fadeOffset", TypeBool, Offset(fade_offset, afxParticleEmitterData), + "If true, the ejection offset of emitted particles is multiplied by the fade amount " + "of the containing effect wrapper. As the effect fades-in and out, so does the " + "ejection offset of new particles."); + addField("vector", TypePoint3F, Offset(pe_vector, afxParticleEmitterData), + "General direction vector used for emitting particles. Its exact interpretation is " + "determined by the particle emitter subclass."); + addField("vectorIsWorld", TypeBool, Offset(pe_vector_is_world, afxParticleEmitterData), + "Sets whether the vector field should be interpreted as a vector in the world " + "coordinate system."); + addField("pathsTransform", TypeString, Offset(tpaths_string, afxParticleEmitterData), + "A string of paths to be used as transform paths. Each path name must reference an " + "afxPathData datablock. Transform paths are used to translate particles along a given " + "path or series of paths."); + + Parent::initPersistFields(); +} + +void afxParticleEmitterData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeFlag(fade_velocity); + stream->writeFlag(fade_offset); + mathWrite(*stream, pe_vector); + stream->writeFlag(pe_vector_is_world); + + stream->write(tPathDataBlockIds.size()); + for (int i = 0; i < tPathDataBlockIds.size(); i++) + stream->write(tPathDataBlockIds[i]); +} + +void afxParticleEmitterData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + fade_velocity = stream->readFlag(); + fade_offset = stream->readFlag(); + mathRead(*stream, &pe_vector); + pe_vector_is_world = stream->readFlag(); + + U32 n_db; stream->read(&n_db); + tPathDataBlockIds.setSize(n_db); + for (U32 i = 0; i < n_db; i++) + stream->read(&tPathDataBlockIds[i]); +} + +bool afxParticleEmitterData::onAdd() +{ + if( Parent::onAdd() == false ) + return false; + + if (tpaths_string != ST_NULLSTRING && tpaths_string[0] == '\0') + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) empty transform paths string.", getName()); + return false; + } + + if (tpaths_string != ST_NULLSTRING && dStrlen(tpaths_string) > 255) + { + Con::errorf(ConsoleLogEntry::General, "ParticleEmitterData(%s) transform paths string too long [> 255 chars].", getName()); + return false; + } + + if (tpaths_string != ST_NULLSTRING) + { + Vector dataBlocks(__FILE__, __LINE__); + char* tokCopy = new char[dStrlen(tpaths_string) + 1]; + dStrcpy(tokCopy, tpaths_string); + + char* currTok = dStrtok(tokCopy, " \t"); + while (currTok != NULL) + { + dataBlocks.push_back(currTok); + currTok = dStrtok(NULL, " \t"); + } + if (dataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) invalid transform paths string. No datablocks found", getName()); + delete [] tokCopy; + return false; + } + tPathDataBlocks.clear(); + tPathDataBlockIds.clear(); + + for (U32 i = 0; i < dataBlocks.size(); i++) + { + afxPathData* pData = NULL; + if (Sim::findObject(dataBlocks[i], pData) == false) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find transform path datablock: %s", getName(), dataBlocks[i]); + } + else + { + tPathDataBlocks.push_back(pData); + tPathDataBlockIds.push_back(pData->getId()); + } + } + delete [] tokCopy; + if (tPathDataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) unable to find any transform path datablocks", getName()); + return false; + } + } + + return true; +} + +bool afxParticleEmitterData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + tPathDataBlocks.clear(); + for (U32 i = 0; i < tPathDataBlockIds.size(); i++) + { + afxPathData* pData = NULL; + if (Sim::findObject(tPathDataBlockIds[i], pData) == false) + { + Con::warnf(ConsoleLogEntry::General, + "ParticleEmitterData(%s) unable to find transform path datablock: %d", + getName(), tPathDataBlockIds[i]); + } + else + tPathDataBlocks.push_back(pData); + } + + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// VECTOR + +IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterVectorData); + +ConsoleDocClass( afxParticleEmitterVectorData, + "@brief An AFX customized particle emitter that emits particles along a 3D vector.\n\n" + + "An AFX customized particle emitter that emits particles along a 3D vector." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxParticleEmitterVectorData::afxParticleEmitterVectorData() +{ +} + +afxParticleEmitterVectorData::afxParticleEmitterVectorData(const afxParticleEmitterVectorData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone) +{ +} + +void afxParticleEmitterVectorData::initPersistFields() +{ + Parent::initPersistFields(); +} + +void afxParticleEmitterVectorData::packData(BitStream* stream) +{ + Parent::packData(stream); +} + +void afxParticleEmitterVectorData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); +} + +bool afxParticleEmitterVectorData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + return true; +} + +bool afxParticleEmitterVectorData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// CONE + +IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterConeData); + +ConsoleDocClass( afxParticleEmitterConeData, + "@brief An AFX customized particle emitter that emits particles within a cone shape.\n\n" + + "An AFX customized particle emitter that emits particles within a cone shape." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxParticleEmitterConeData::afxParticleEmitterConeData() +{ + spread_min = 0.0f; + spread_max = 90.0f; +} + +afxParticleEmitterConeData::afxParticleEmitterConeData(const afxParticleEmitterConeData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone) +{ + spread_min = other.spread_min; + spread_max = other.spread_max; +} + +void afxParticleEmitterConeData::initPersistFields() +{ + addField("spreadMin", TypeF32, Offset(spread_min, afxParticleEmitterConeData), + "..."); + addField("spreadMax", TypeF32, Offset(spread_max, afxParticleEmitterConeData), + "..."); + + Parent::initPersistFields(); +} + +void afxParticleEmitterConeData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeRangedU32((U32)spread_min, 0, 180); + stream->writeRangedU32((U32)spread_max, 0, 180); +} + +void afxParticleEmitterConeData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + spread_min = stream->readRangedU32(0, 180); + spread_max = stream->readRangedU32(0, 180); +} + +bool afxParticleEmitterConeData::onAdd() +{ + if( Parent::onAdd() == false ) + return false; + + if (spread_min < 0.0f) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) spreadMin < 0.0", getName()); + spread_min = 0.0f; + } + if (spread_max > 180.0f) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) spreadMax > 180.0f", getName()); + spread_max = 180.0f; + } + + if (spread_max > 179.5f) + spread_max = 179.5f; + if (spread_min > 179.5f) + spread_min = 179.5f; + + if (spread_min > spread_max) + { + Con::warnf(ConsoleLogEntry::General, "ParticleEmitterData(%s) spreadMin > spreadMax", getName()); + spread_min = spread_max; + } + + return true; +} + +bool afxParticleEmitterConeData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// PATH + +IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterPathData); + +ConsoleDocClass( afxParticleEmitterPathData, + "@brief An AFX customized particle emitter that emits particles along a path.\n\n" + + "An AFX customized particle emitter that emits particles along a path." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxParticleEmitterPathData::afxParticleEmitterPathData() +{ + epaths_string = ST_NULLSTRING; + epathDataBlocks.clear(); + epathDataBlockIds.clear(); + path_origin_type = PATHEMIT_ORIGIN; + ground_conform = false; + ground_conform_terrain = true; + ground_conform_interiors = true; + ground_conform_height = 0.0f; +} + +afxParticleEmitterPathData::afxParticleEmitterPathData(const afxParticleEmitterPathData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone) +{ + epaths_string = other.epaths_string; + epathDataBlocks = other.epathDataBlocks; + //epathDataBlockIds = other.epathDataBlockIds; + path_origin_type = other.path_origin_type; + ground_conform = other.ground_conform; + ground_conform_terrain = other.ground_conform_terrain; + ground_conform_interiors = other.ground_conform_interiors; + ground_conform_height = other.ground_conform_height; +} + +ImplementEnumType( afxParticleEmitterPath_OriginType, "Possible particle emitter path origin types.\n" "@ingroup afxParticleEmitterPath\n\n" ) + { afxParticleEmitterPathData::PATHEMIT_ORIGIN, "origin", "..." }, + { afxParticleEmitterPathData::PATHEMIT_POINT, "point", "..." }, + { afxParticleEmitterPathData::PATHEMIT_VECTOR, "vector", "..." }, + { afxParticleEmitterPathData::PATHEMIT_TANGENT, "tangent", "..." }, +EndImplementEnumType; + +void afxParticleEmitterPathData::initPersistFields() +{ + addField("paths", TypeString, Offset(epaths_string, afxParticleEmitterPathData), + "..."); + + addField("pathOrigin", TYPEID(), Offset(path_origin_type, afxParticleEmitterPathData), + "..."); + + // JTF Note: take a look at these and make sure they are ok. + addField("groundConform", TypeBool, Offset(ground_conform, afxParticleEmitterPathData), + "..."); + addField("groundConformTerrain", TypeBool, Offset(ground_conform_terrain, afxParticleEmitterPathData), + "..."); + addField("groundConformInteriors", TypeBool, Offset(ground_conform_interiors, afxParticleEmitterPathData), + "..."); + addField("groundConformHeight", TypeF32, Offset(ground_conform_height, afxParticleEmitterPathData), + "..."); + + Parent::initPersistFields(); +} + +void afxParticleEmitterPathData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(epathDataBlockIds.size()); + for (int i = 0; i < epathDataBlockIds.size(); i++) + stream->write(epathDataBlockIds[i]); + stream->write(path_origin_type); + stream->writeFlag(ground_conform); + stream->writeFlag(ground_conform_terrain); + stream->writeFlag(ground_conform_interiors); + stream->write(ground_conform_height); +} + +void afxParticleEmitterPathData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + U32 n_db; stream->read(&n_db); + epathDataBlockIds.setSize(n_db); + for (U32 i = 0; i < n_db; i++) + stream->read(&epathDataBlockIds[i]); + stream->read(&path_origin_type); + ground_conform = stream->readFlag(); + ground_conform_terrain = stream->readFlag(); + ground_conform_interiors = stream->readFlag(); + stream->read(&ground_conform_height); +} + +bool afxParticleEmitterPathData::onAdd() +{ + if( Parent::onAdd() == false ) + return false; + + // path + if (epaths_string != ST_NULLSTRING && epaths_string[0] == '\0') + { + Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) empty paths string.", getName()); + return false; + } + + if (epaths_string != ST_NULLSTRING && dStrlen(epaths_string) > 255) + { + Con::errorf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) paths string too long [> 255 chars].", getName()); + return false; + } + + if (epaths_string != ST_NULLSTRING) + { + Vector dataBlocks(__FILE__, __LINE__); + char* tokCopy = new char[dStrlen(epaths_string) + 1]; + dStrcpy(tokCopy, epaths_string); + + char* currTok = dStrtok(tokCopy, " \t"); + while (currTok != NULL) + { + dataBlocks.push_back(currTok); + currTok = dStrtok(NULL, " \t"); + } + if (dataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) invalid paths string. No datablocks found", getName()); + delete [] tokCopy; + return false; + } + epathDataBlocks.clear(); + epathDataBlockIds.clear(); + + for (U32 i = 0; i < dataBlocks.size(); i++) + { + afxPathData* pData = NULL; + if (Sim::findObject(dataBlocks[i], pData) == false) + { + Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find path datablock: %s", getName(), dataBlocks[i]); + } + else + { + epathDataBlocks.push_back(pData); + epathDataBlockIds.push_back(pData->getId()); + } + } + delete [] tokCopy; + if (epathDataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find any path datablocks", getName()); + return false; + } + } + + return true; +} + +bool afxParticleEmitterPathData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + epathDataBlocks.clear(); + for (U32 i = 0; i < epathDataBlockIds.size(); i++) + { + afxPathData* pData = NULL; + if (Sim::findObject(epathDataBlockIds[i], pData) == false) + { + Con::warnf(ConsoleLogEntry::General, + "afxParticleEmitterPathData(%s) unable to find path datablock: %d", + getName(), epathDataBlockIds[i]); + } + else + epathDataBlocks.push_back(pData); + } + parts_per_eject = epathDataBlocks.size(); + + return true; +} + +void afxParticleEmitterPathData::onPerformSubstitutions() +{ + Parent::onPerformSubstitutions(); + + + if (epaths_string != ST_NULLSTRING && epaths_string[0] == '\0') + { + Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) empty paths string.", getName()); + return;// false; + } + + if (epaths_string != ST_NULLSTRING && dStrlen(epaths_string) > 255) + { + Con::errorf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) paths string too long [> 255 chars].", getName()); + return;// false; + } + + if (epaths_string != ST_NULLSTRING) + { + Vector dataBlocks(__FILE__, __LINE__); + char* tokCopy = new char[dStrlen(epaths_string) + 1]; + dStrcpy(tokCopy, epaths_string); + + char* currTok = dStrtok(tokCopy, " \t"); + while (currTok != NULL) + { + dataBlocks.push_back(currTok); + currTok = dStrtok(NULL, " \t"); + } + if (dataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) invalid paths string. No datablocks found", getName()); + delete [] tokCopy; + return;// false; + } + epathDataBlocks.clear(); + epathDataBlockIds.clear(); + + for (U32 i = 0; i < dataBlocks.size(); i++) + { + afxPathData* pData = NULL; + if (Sim::findObject(dataBlocks[i], pData) == false) + { + Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find path datablock: %s", getName(), dataBlocks[i]); + } + else + { + epathDataBlocks.push_back(pData); + epathDataBlockIds.push_back(pData->getId()); + } + } + delete [] tokCopy; + if (epathDataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "afxParticleEmitterPathData(%s) unable to find any path datablocks", getName()); + return;// false; + } + } + + + /*epathDataBlocks.clear(); + for (U32 i = 0; i < epathDataBlockIds.size(); i++) + { + afxPathData* pData = NULL; + if (Sim::findObject(epathDataBlockIds[i], pData) == false) + { + Con::warnf(ConsoleLogEntry::General, + "afxParticleEmitterPathData(%s) unable to find path datablock: %d", + getName(), epathDataBlockIds[i]); + } + else + epathDataBlocks.push_back(pData); + } + */ + parts_per_eject = epathDataBlocks.size(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// DISC + +IMPLEMENT_CO_DATABLOCK_V1(afxParticleEmitterDiscData); + +ConsoleDocClass( afxParticleEmitterDiscData, + "@brief An AFX customized particle emitter that emits particles within a disc shape.\n\n" + + "An AFX customized particle emitter that emits particles within a disc shape." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxParticleEmitterDiscData::afxParticleEmitterDiscData() +{ + pe_radius_min = 0.0f; + pe_radius_max = 1.0f; +} + +afxParticleEmitterDiscData::afxParticleEmitterDiscData(const afxParticleEmitterDiscData& other, bool temp_clone) : afxParticleEmitterData(other, temp_clone) +{ + pe_radius_min = other.pe_radius_min; + pe_radius_max = other.pe_radius_max; +} + +void afxParticleEmitterDiscData::initPersistFields() +{ + addField("radiusMin", TypeF32, Offset(pe_radius_min, afxParticleEmitterDiscData), + "..."); + addField("radiusMax", TypeF32, Offset(pe_radius_max, afxParticleEmitterDiscData), + "..."); + + Parent::initPersistFields(); +} + +void afxParticleEmitterDiscData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeInt((S32)(pe_radius_min * 100), 16); + stream->writeInt((S32)(pe_radius_max * 100), 16); +} + +void afxParticleEmitterDiscData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + pe_radius_min = stream->readInt(16) / 100.0f; + pe_radius_max = stream->readInt(16) / 100.0f; +} + +bool afxParticleEmitterDiscData::onAdd() +{ + if( Parent::onAdd() == false ) + return false; + + return true; +} + +bool afxParticleEmitterDiscData::preload(bool server, String &errorStr) +{ + if (Parent::preload(server, errorStr) == false) + return false; + + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxParticleEmitter::afxParticleEmitter() +{ + pe_vector.set(0,0,1); + pe_vector_norm.set(0,0,1); + tpaths.clear(); + tpath_mults.clear(); + n_tpath_points = 0; + tpath_points = NULL; + afx_owner = 0; +} + +afxParticleEmitter::~afxParticleEmitter() +{ +} + +bool afxParticleEmitter::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + if (dynamic_cast(mDataBlock)) + init_paths(); + + return true; +} + +void afxParticleEmitter::onRemove() +{ + if (dynamic_cast(mDataBlock)) + cleanup_paths(); + + Parent::onRemove(); +} + +void afxParticleEmitter::init_paths() +{ + if (!mDataBlock) + { + n_tpath_points = 0; + tpath_points = NULL; + return; + } + + if (mDataBlock->tPathDataBlocks.size() < 1) + { + n_tpath_points = 0; + tpath_points = NULL; + } + else + { + n_tpath_points = mDataBlock->tPathDataBlocks.size(); + tpath_points = new Point3F*[n_tpath_points]; + + for (U32 i=0; i < n_tpath_points; i++) + { + afxPathData* pd = mDataBlock->tPathDataBlocks[i]; + if (!pd) + continue; + + if (pd->getSubstitutionCount() > 0 && afx_owner) + { + afxPathData* orig_db = pd; + pd = new afxPathData(*orig_db, true); + orig_db->performSubstitutions(pd, afx_owner); + } + + if (pd->num_points > 0) + { + afxPath3D* path = new afxPath3D(); + if (pd->times) + path->buildPath( pd->num_points, pd->points, pd->times, pd->delay, 1.0f ); + else if (pd->lifetime == 0) + path->buildPath( pd->num_points, pd->points, pd->delay, 1.0f ); + else + path->buildPath( pd->num_points, pd->points, pd->delay, pd->delay+pd->lifetime ); + path->setLoopType( pd->loop_type ); + tpaths.push_back(path); + + tpath_mults.push_back( pd->mult ); + + tpath_points[i] = new Point3F[pd->num_points]; + for (U32 j=0; jnum_points; j++) + tpath_points[i][j] = pd->points[j]; + } + else + { + Con::warnf("afxParticleEmitter::init_paths() -- pathsTransform datablock (%d) has no points.", i); + } + + if (pd->isTempClone()) + delete pd; + } + } +} + +void afxParticleEmitter::cleanup_paths() +{ + if (n_tpath_points < 1) + return; + + for (U32 i=0; i < tpaths.size(); i++) + { + if (tpaths[i]) + delete tpaths[i]; + } + tpaths.clear(); + + if (tpath_points) + { + if (mDataBlock) + { + for (U32 i=0; i < n_tpath_points; i++) + { + if (tpath_points[i]) + delete [] tpath_points[i]; + } + } + + delete [] tpath_points; + tpath_points = 0; + } +} + +void afxParticleEmitter::sub_particleUpdate(Particle* part) +{ + if (tpaths.size() < 1) + return; + + F32 t = ((F32)part->currentAge)/((F32)part->totalLifetime); + for (U32 i=0; i < tpaths.size(); i++) + { + F32 t_last = part->t_last; + Point3F path_delta = (t_last <= 0.0f) ? tpaths[i]->evaluateAtTime(t) : tpaths[i]->evaluateAtTime(t_last, t); + + if (mDataBlock->tPathDataBlocks[i]->concentric) + { + // scale radial vector by x-component of path + part->pos_local += part->radial_v*path_delta.x; + // scale axis vector by z-component of path + part->pos_local += pe_vector_norm*path_delta.z; + // y-component is ignored + } + else + { + part->pos_local += path_delta; + } + } + + part->t_last = t; +} + +void afxParticleEmitter::preCompute(const MatrixF& mat) +{ + // Put vector into the space of the input matrix + pe_vector = mDataBlock->pe_vector; + if (!mDataBlock->pe_vector_is_world) + mat.mulV(pe_vector); + + pe_vector_norm = pe_vector; + pe_vector_norm.normalize(); + + // Transform Paths: rebuild with current matrix + for( U32 i=0; i < tpaths.size(); i++ ) + { + for( U32 j=0; j < tpaths[i]->getNumPoints(); j++ ) + { + Point3F p = tpath_points[i][j]; + mat.mulV(p); + tpaths[i]->setPointPosition(j, p); + } + + tpaths[i]->reBuildPath(); + } + + sub_preCompute(mat); +} + +void afxParticleEmitter::afx_emitParticles(const Point3F& point, const bool useLastPosition, const Point3F& velocity, const U32 numMilliseconds) +{ + if (mDead) return; + + // lifetime over - no more particles + if (mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS) + return; + + Point3F realStart; + if (useLastPosition && mHasLastPosition) + realStart = mLastPosition; + else + realStart = point; + + afx_emitParticles(realStart, point, velocity, numMilliseconds); +} + +void afxParticleEmitter::afx_emitParticles(const Point3F& start, const Point3F& end, const Point3F& velocity, const U32 numMilliseconds) +{ + if (mDead) return; + + // lifetime over - no more particles + if (mLifetimeMS > 0 && mElapsedTimeMS > mLifetimeMS) + return; + + U32 currTime = 0; + bool particlesAdded = false; + + if (mNextParticleTime != 0) + { + // Need to handle next particle + // + if (mNextParticleTime > numMilliseconds) + { + // Defer to next update + // (Note that this introduces a potential spatial irregularity if the owning + // object is accelerating, and updating at a low frequency) + // + mNextParticleTime -= numMilliseconds; + mInternalClock += numMilliseconds; + mLastPosition = end; + mHasLastPosition = true; + return; + } + else + { + currTime += mNextParticleTime; + mInternalClock += mNextParticleTime; + // Emit particle at curr time + + // Create particle at the correct position + Point3F pos; + pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); + + for (S32 p = 0; p < mDataBlock->parts_per_eject; p++) + { + sub_addParticle(pos, velocity, numMilliseconds-currTime, p); + particlesAdded = true; + } + mNextParticleTime = 0; + } + } + + while (currTime < numMilliseconds) + { + S32 nextTime = mDataBlock->ejectionPeriodMS; + if (mDataBlock->periodVarianceMS != 0) + { + nextTime += S32(gRandGen.randI() % (2 * mDataBlock->periodVarianceMS + 1)) - + S32(mDataBlock->periodVarianceMS); + } + AssertFatal(nextTime > 0, "Error, next particle ejection time must always be greater than 0"); + + if (currTime + nextTime > numMilliseconds) + { + mNextParticleTime = (currTime + nextTime) - numMilliseconds; + mInternalClock += numMilliseconds - currTime; + AssertFatal(mNextParticleTime > 0, "Error, should not have deferred this particle!"); + break; + } + + currTime += nextTime; + mInternalClock += nextTime; + + // Create particle at the correct position + Point3F pos; + pos.interpolate(start, end, F32(currTime) / F32(numMilliseconds)); + + U32 advanceMS = numMilliseconds - currTime; + if (mDataBlock->overrideAdvance == false && advanceMS != 0) + { + for (S32 p = 0; p < mDataBlock->parts_per_eject; p++) + { + sub_addParticle(pos, velocity, numMilliseconds-currTime, p); + particlesAdded = true; + + Particle* last_part = part_list_head.next; + if (last_part) + { + if (advanceMS > last_part->totalLifetime) + { + part_list_head.next = last_part->next; + n_parts--; + last_part->next = part_freelist; + part_freelist = last_part; + } + else + { + F32 t = F32(advanceMS) / 1000.0; + + Point3F a = last_part->acc; + a -= last_part->vel*last_part->dataBlock->dragCoefficient; + a -= mWindVelocity*last_part->dataBlock->windCoefficient; + //a += Point3F(0, 0, -9.81) * last_part->dataBlock->gravityCoefficient; + a.z += -9.81f*last_part->dataBlock->gravityCoefficient; // as long as gravity is a constant, this is faster + + last_part->vel += a * t; + last_part->pos_local += last_part->vel * t; + + // allow subclasses to adjust the particle params here + sub_particleUpdate(last_part); + + if (last_part->dataBlock->constrain_pos) + last_part->pos = last_part->pos_local + this->pos_pe; + else + last_part->pos = last_part->pos_local; + + updateKeyData(last_part); + } + } + } + } + else + { + for (S32 p = 0; p < mDataBlock->parts_per_eject; p++) + { + sub_addParticle(pos, velocity, numMilliseconds-currTime, p); + particlesAdded = true; + } + } + } + + if( particlesAdded == true ) + updateBBox(); + + if( n_parts > 0 && mSceneManager == NULL ) + { + gClientSceneGraph->addObjectToScene(this); + ClientProcessList::get()->addObject(this); + } + + mLastPosition = end; + mHasLastPosition = true; +} + +Particle* afxParticleEmitter::alloc_particle() +{ + n_parts++; + + // this should happen rarely + if (n_parts > n_part_capacity) + { + Particle* store_block = new Particle[16]; + part_store.push_back(store_block); + n_part_capacity += 16; + for (S32 i = 0; i < 16; i++) + { + store_block[i].next = part_freelist; + part_freelist = &store_block[i]; + } + mDataBlock->allocPrimBuffer(n_part_capacity); + } + + Particle* pNew = part_freelist; + part_freelist = pNew->next; + pNew->next = part_list_head.next; + part_list_head.next = pNew; + + return pNew; +} + +ParticleData* afxParticleEmitter::pick_particle_type() +{ + U32 dBlockIndex = (U32)(mCeil(gRandGen.randF() * F32(mDataBlock->particleDataBlocks.size())) - 1); + return mDataBlock->particleDataBlocks[dBlockIndex]; +} + +bool afxParticleEmitter::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if( !mDataBlock || !Parent::onNewDataBlock(dptr, reload) ) + return false; + + if (mDataBlock->isTempClone()) + return true; + + scriptOnNewDataBlock(); + return true; +} + +void afxParticleEmitter::emitParticlesExt(const MatrixF& xfm, const Point3F& point, const Point3F& velocity, const U32 numMilliseconds) +{ + if (mDataBlock->use_emitter_xfm) + { + Point3F zero_point(0.0f, 0.0f, 0.0f); + pos_pe = zero_point; + setTransform(xfm); + + preCompute(xfm); + afx_emitParticles(zero_point, true, velocity, numMilliseconds); + } + else + { + pos_pe = point; + preCompute(xfm); + afx_emitParticles(point, true, velocity, numMilliseconds); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// VECTOR + +afxParticleEmitterVector::afxParticleEmitterVector() +{ +} + +afxParticleEmitterVector::~afxParticleEmitterVector() +{ +} + +bool afxParticleEmitterVector::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if( !mDataBlock || !Parent::onNewDataBlock(dptr, reload) ) + return false; + + if (mDataBlock->isTempClone()) + return true; + + scriptOnNewDataBlock(); + return true; +} + +void afxParticleEmitterVector::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx) +{ + Particle* pNew = alloc_particle(); + ParticleData* part_db = pick_particle_type(); + + Point3F pos_start = pos; + if (part_db->constrain_pos) + pos_start.set(0,0,0); + + F32 initialVel = mDataBlock->ejectionVelocity; + initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance; + if(mDataBlock->fade_velocity) + initialVel *= fade_amt; + + F32 ejection_offset = mDataBlock->ejectionOffset; + if(mDataBlock->fade_offset) + ejection_offset *= fade_amt; + + pNew->pos = pos_start + (pe_vector_norm * ejection_offset); + pNew->pos_local = pNew->pos; + + pNew->vel = pe_vector_norm * initialVel; + if (mDataBlock->orientParticles) + pNew->orientDir = pe_vector_norm; + else + // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle. + pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance); + pNew->acc.set(0, 0, 0); + pNew->currentAge = age_offset; + pNew->t_last = 0.0f; + + pNew->radial_v.set(0.0f, 0.0f, 0.0f); + + part_db->initializeParticle(pNew, vel); + updateKeyData( pNew ); +} + +void afxParticleEmitterVector::sub_preCompute(const MatrixF& mat) +{ +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// CONE + +afxParticleEmitterCone::afxParticleEmitterCone() +{ + cone_v.set(0,0,1); + cone_s0.set(0,0,1); + cone_s1.set(0,0,1); +} + +afxParticleEmitterCone::~afxParticleEmitterCone() +{ +} + +bool afxParticleEmitterCone::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if( !mDataBlock || !Parent::onNewDataBlock(dptr, reload) ) + return false; + + if (mDataBlock->isTempClone()) + return true; + + scriptOnNewDataBlock(); + return true; +} + +void afxParticleEmitterCone::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx) +{ + Particle* pNew = alloc_particle(); + ParticleData* part_db = pick_particle_type(); + + Point3F pos_start = pos; + if (part_db->constrain_pos) + pos_start.set(0,0,0); + + F32 initialVel = mDataBlock->ejectionVelocity; + initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance; + if(mDataBlock->fade_velocity) + initialVel *= fade_amt; + + // Randomly choose a vector between cone_s0 and cone_s1 and normalize: + Point3F vec; + F32 t = mRandF(0.0f, 1.0f); + vec.interpolate(cone_s0, cone_s1, t); + vec.normalize(); + + // Randomly rotate about cone_v + F32 theta = mRandF(0.0f, M_2PI_F); + AngAxisF thetaRot(cone_v, theta); + MatrixF temp(true); + thetaRot.setMatrix(&temp); + temp.mulP(vec); + + F32 ejection_offset = mDataBlock->ejectionOffset; + if(mDataBlock->fade_offset) + ejection_offset *= fade_amt; + + pNew->pos = pos_start + (vec * ejection_offset); + pNew->pos_local = pNew->pos; + + pNew->vel = mDataBlock->ejectionInvert ? vec * -initialVel : vec * initialVel; + if (mDataBlock->orientParticles) + pNew->orientDir = vec; + else + // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle. + pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance); + pNew->acc.set(0, 0, 0); + pNew->currentAge = age_offset; + pNew->t_last = 0.0f; + + pNew->radial_v.set(0.0f, 0.0f, 0.0f); + + part_db->initializeParticle(pNew, vel); + updateKeyData( pNew ); +} + +void afxParticleEmitterCone::sub_preCompute(const MatrixF& mat) +{ + // Find vectors on the XZ plane corresponding to the inner and outer spread angles: + // (tan is infinite at PI/4 or 90 degrees) + cone_v.set( 0.0f, 0.0f, 1.0f ); + + cone_s0.x = mTan( mDegToRad( ((afxParticleEmitterConeData*)mDataBlock)->spread_min / 2.0f )); + cone_s0.y = 0.0f; + cone_s0.z = 1.0f; + + cone_s1.x = mTan( mDegToRad(((afxParticleEmitterConeData*)mDataBlock)->spread_max / 2.0f )); + cone_s1.y = 0.0f; + cone_s1.z = 1.0f; + + Point3F axis; + F32 theta = mAcos( mDot(cone_v, pe_vector_norm) ); + + if( M_PI_F-theta < POINT_EPSILON ) + { + cone_v.neg(); + cone_s0.neg(); + cone_s1.neg(); + } + else if( theta > POINT_EPSILON ) + { + mCross(pe_vector_norm, cone_v, &axis); + axis.normalize(); + + AngAxisF thetaRot(axis, theta); + MatrixF temp(true); + thetaRot.setMatrix(&temp); + + temp.mulP(cone_v); + temp.mulP(cone_s0); + temp.mulP(cone_s1); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// PATH + +afxParticleEmitterPath::afxParticleEmitterPath() +{ + epaths.clear(); + epath_mults.clear(); + n_epath_points = 0; + epath_points = NULL; +} + +afxParticleEmitterPath::~afxParticleEmitterPath() +{ +} + +bool afxParticleEmitterPath::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if( !mDataBlock || !Parent::onNewDataBlock(dptr, reload) ) + return false; + + if (mDataBlock->isTempClone()) + return true; + + scriptOnNewDataBlock(); + return true; +} + +bool afxParticleEmitterPath::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + if (dynamic_cast(mDataBlock)) + init_paths(); + + return true; +} + +void afxParticleEmitterPath::onRemove() +{ + if (dynamic_cast(mDataBlock)) + cleanup_paths(); + + Parent::onRemove(); +} + +void afxParticleEmitterPath::init_paths() +{ + if (!mDataBlock || ((afxParticleEmitterPathData*)mDataBlock)->epathDataBlocks.size() < 1) + { + n_epath_points = 0; + epath_points = NULL; + return; + } + + n_epath_points = ((afxParticleEmitterPathData*)mDataBlock)->epathDataBlocks.size(); + epath_points = new Point3F*[n_epath_points]; + dMemset(epath_points, 0, n_epath_points*sizeof(Point3F*)); + + for (U32 i=0; i < n_epath_points; i++) + { + afxPathData* pd = ((afxParticleEmitterPathData*)mDataBlock)->epathDataBlocks[i]; + if (!pd) + continue; + + if (pd->getSubstitutionCount() > 0 && afx_owner) + { + afxPathData* orig_db = pd; + pd = new afxPathData(*orig_db, true); + orig_db->performSubstitutions(pd, afx_owner); + } + + if (pd->num_points > 0) + { + afxPath3D* path = new afxPath3D(); + if (pd->times) + path->buildPath( pd->num_points, pd->points, pd->times, 0.0f, 1.0f ); + else + path->buildPath( pd->num_points, pd->points, 0.0f, 1.0f ); + epaths.push_back(path); + + epath_mults.push_back( pd->mult ); + + epath_points[i] = new Point3F[pd->num_points]; + for (U32 j=0; jnum_points; j++) + epath_points[i][j] = pd->points[j]; + } + else + { + Con::warnf("afxParticleEmitterPath::init_paths() -- paths datablock (%d) has no points.", i); + } + + if (pd->isTempClone()) + delete pd; + } +} + +void afxParticleEmitterPath::cleanup_paths() +{ + if (n_epath_points < 1) + return; + + for (U32 i=0; i < epaths.size(); i++) + { + if (epaths[i]) + delete epaths[i]; + } + epaths.clear(); + + if (epath_points) + { + if (mDataBlock) + { + for (U32 i=0; i < n_epath_points; i++) + { + if (epath_points[i]) + delete [] epath_points[i]; + } + } + + delete [] epath_points; + epath_points = 0; + } +} + +void afxParticleEmitterPath::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx) +{ + if (part_idx >= epaths.size() || !epaths[part_idx]) + return; + + Particle* pNew = alloc_particle(); + ParticleData* part_db = pick_particle_type(); + + Point3F pos_start = pos; + if (part_db->constrain_pos) + pos_start.set(0,0,0); + + F32 initialVel = mDataBlock->ejectionVelocity; + initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance; + if(mDataBlock->fade_velocity) + initialVel *= fade_amt; + + // Randomly choose a curve parameter between [0.0,1.0] and evaluate the curve there + F32 param = mRandF(0.0f, 1.0f); + + Point3F curve_pos = epaths[part_idx]->evaluateAtTime(param); + + Point3F vec; + switch (((afxParticleEmitterPathData*)mDataBlock)->path_origin_type) + { + case afxParticleEmitterPathData::PATHEMIT_ORIGIN : + vec = curve_pos; + vec.normalize(); + break; + case afxParticleEmitterPathData::PATHEMIT_POINT : + vec = curve_pos-pe_vector; + vec.normalize(); + break; + case afxParticleEmitterPathData::PATHEMIT_VECTOR : + vec = pe_vector_norm; + break; + case afxParticleEmitterPathData::PATHEMIT_TANGENT : + vec = epaths[part_idx]->evaluateTangentAtTime(param); + vec.normalize(); + break; + } + + F32 ejection_offset = mDataBlock->ejectionOffset; + if(mDataBlock->fade_offset) + ejection_offset *= fade_amt; + + pNew->pos = pos_start + curve_pos + (vec * ejection_offset); + pNew->pos_local = pNew->pos; + + pNew->vel = mDataBlock->ejectionInvert ? vec * -initialVel : vec * initialVel; + if (mDataBlock->orientParticles) + pNew->orientDir = vec; + else + // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle. + pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance); + pNew->acc.set(0, 0, 0); + pNew->currentAge = age_offset; + pNew->t_last = 0.0f; + + pNew->radial_v.set(0.0f, 0.0f, 0.0f); + + part_db->initializeParticle(pNew, vel); + updateKeyData( pNew ); +} + +void afxParticleEmitterPath::sub_preCompute(const MatrixF& mat) +{ + for( U32 i=0; i < epaths.size(); i++ ) + { + for( U32 j=0; j < epaths[i]->getNumPoints(); j++ ) + { + Point3F p = epath_points[i][j]; + mat.mulV(p); + + p *= epath_mults[i]; + if(mDataBlock->ground_conform) { + groundConformPoint(p, mat); + } + + epaths[i]->setPointPosition(j, p); + } + + epaths[i]->reBuildPath(); + } +} + +void afxParticleEmitterPath::groundConformPoint(Point3F& point, const MatrixF& mat) +{ + point += mat.getPosition(); + + RayInfo rInfo; + bool hit = false; + + if (mDataBlock->ground_conform_interiors) + { + U32 mask = InteriorLikeObjectType; + if (mDataBlock->ground_conform_terrain) + { + mask |= TerrainObjectType | TerrainLikeObjectType; + } + + Point3F above_pos(point); above_pos.z += 0.1f; + Point3F below_pos(point); below_pos.z -= 10000; + hit = gClientContainer.castRay(above_pos, below_pos, mask, &rInfo); + if (!hit) + { + above_pos.z = point.z + 10000; + below_pos.z = point.z - 0.1f; + hit = gClientContainer.castRay(below_pos, above_pos, mask, &rInfo); + } + } + else if (mDataBlock->ground_conform_terrain) + { + U32 mask = TerrainObjectType | TerrainLikeObjectType; + Point3F above_pos(point); above_pos.z += 10000; + Point3F below_pos(point); below_pos.z -= 10000; + hit = gClientContainer.castRay(above_pos, below_pos, mask, &rInfo); + } + + if (hit) + { + F32 terrain_z = rInfo.point.z; + F32 new_z = terrain_z + mDataBlock->ground_conform_height; + point.z = new_z; + } + + point -= mat.getPosition(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// DISC + +afxParticleEmitterDisc::afxParticleEmitterDisc() +{ + disc_v.set(0,0,1); + disc_r.set(1,0,0); +} + +afxParticleEmitterDisc::~afxParticleEmitterDisc() +{ +} + +bool afxParticleEmitterDisc::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if( !mDataBlock || !Parent::onNewDataBlock(dptr, reload) ) + return false; + + if (mDataBlock->isTempClone()) + return true; + + return true; +} + +void afxParticleEmitterDisc::sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx) +{ + Particle* pNew = alloc_particle(); + ParticleData* part_db = pick_particle_type(); + + Point3F pos_start = pos; + if (part_db->constrain_pos) + pos_start.set(0,0,0); + + F32 initialVel = mDataBlock->ejectionVelocity; + initialVel += (mDataBlock->velocityVariance * 2.0f * gRandGen.randF()) - mDataBlock->velocityVariance; + if(mDataBlock->fade_velocity) + initialVel *= fade_amt; + + // Randomly choose a radius vector + Point3F r( disc_r ); + F32 radius = mRandF(((afxParticleEmitterDiscData*)mDataBlock)->pe_radius_min, ((afxParticleEmitterDiscData*)mDataBlock)->pe_radius_max); + r *= radius; + + // Randomly rotate r about disc_v + F32 theta = mRandF(0.0f, M_2PI_F); + AngAxisF thetaRot(disc_v, theta); + MatrixF temp(true); + thetaRot.setMatrix(&temp); + temp.mulP(r); + + F32 ejection_offset = mDataBlock->ejectionOffset; + if(mDataBlock->fade_offset) + ejection_offset *= fade_amt; + + pNew->pos = pos_start + r + (disc_v * ejection_offset); + pNew->pos_local = pNew->pos; + + pNew->vel = (mDataBlock->ejectionInvert) ? (disc_v * -initialVel) : (disc_v * initialVel); + if (mDataBlock->orientParticles) + pNew->orientDir = disc_v; + else + // note -- for non-oriented particles, we use orientDir.x to store the billboard start angle. + pNew->orientDir.x = mDegToRad(part_db->start_angle + part_db->angle_variance*2.0f*gRandGen.randF() - part_db->angle_variance); + pNew->acc.set(0, 0, 0); + pNew->currentAge = age_offset; + pNew->t_last = 0.0f; + + pNew->radial_v = r; + + part_db->initializeParticle(pNew, vel); + updateKeyData( pNew ); +} + +void afxParticleEmitterDisc::sub_preCompute(const MatrixF& mat) +{ + disc_v.set(0.0f, 0.0f, 1.0f); + disc_r.set(1.0f, 0.0f, 0.0f); + + Point3F axis; + F32 theta = mAcos( mDot(disc_v, pe_vector_norm) ); + + if( M_PI_F-theta < POINT_EPSILON ) + { + disc_v.neg(); + } + else if( theta > POINT_EPSILON ) + { + mCross(pe_vector_norm, disc_v, &axis); + axis.normalize(); + + AngAxisF thetaRot(axis, theta); + MatrixF temp(true); + thetaRot.setMatrix(&temp); + + temp.mulP(disc_v); + temp.mulP(disc_r); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/ce/afxParticleEmitter.h b/Engine/source/afx/ce/afxParticleEmitter.h new file mode 100644 index 000000000..a7053a12b --- /dev/null +++ b/Engine/source/afx/ce/afxParticleEmitter.h @@ -0,0 +1,350 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_EMITTER_PARTICLE_H_ +#define _AFX_EMITTER_PARTICLE_H_ + +#include "T3D/fx/particleEmitter.h" + +class afxPathData; +class afxPath3D; + +class afxParticleEmitterData : public ParticleEmitterData +{ + typedef ParticleEmitterData Parent; + +public: + // The afx enhanced particle emitter allows fading + // of particle color, size, velocity, and/or offset. + // Fading is controlled by a common value which is + // set externally using setFadeAmount(). + // + bool fade_velocity; + bool fade_offset; + Point3F pe_vector; + // new -- consider vector in world space? + bool pe_vector_is_world; + + // new -- transform paths? + StringTableEntry tpaths_string; // + Vector tPathDataBlocks; // datablocks for paths + Vector tPathDataBlockIds; // datablock IDs which correspond to the pathDataBlocks + +public: + /*C*/ afxParticleEmitterData(); + /*C*/ afxParticleEmitterData(const afxParticleEmitterData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + + bool preload(bool server, String &errorStr); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxParticleEmitterData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// VECTOR + +class afxParticleEmitterVectorData : public afxParticleEmitterData +{ + typedef afxParticleEmitterData Parent; + +public: + /*C*/ afxParticleEmitterVectorData(); + /*C*/ afxParticleEmitterVectorData(const afxParticleEmitterVectorData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + + bool preload(bool server, String &errorStr); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxParticleEmitterVectorData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// CONE + +class afxParticleEmitterConeData : public afxParticleEmitterData +{ + typedef afxParticleEmitterData Parent; + +public: + F32 spread_min; + F32 spread_max; + +public: + /*C*/ afxParticleEmitterConeData(); + /*C*/ afxParticleEmitterConeData(const afxParticleEmitterConeData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + + bool preload(bool server, String &errorStr); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxParticleEmitterConeData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// PATH + +class afxParticleEmitterPathData : public afxParticleEmitterData +{ + typedef afxParticleEmitterData Parent; + +public: + enum PathOriginType + { + PATHEMIT_ORIGIN, + PATHEMIT_POINT, + PATHEMIT_VECTOR, + PATHEMIT_TANGENT + }; + StringTableEntry epaths_string; // + Vector epathDataBlocks; // datablocks for paths + Vector epathDataBlockIds; // datablock IDs which correspond to the pathDataBlocks + U32 path_origin_type; + + bool ground_conform; + bool ground_conform_terrain; + bool ground_conform_interiors; + F32 ground_conform_height; + +public: + /*C*/ afxParticleEmitterPathData(); + /*C*/ afxParticleEmitterPathData(const afxParticleEmitterPathData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + + bool preload(bool server, String &errorStr); + + virtual void onPerformSubstitutions(); + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxParticleEmitterPathData); + DECLARE_CATEGORY("AFX"); +}; + +typedef afxParticleEmitterPathData::PathOriginType afxParticleEmitterPath_OriginType; +DefineEnumType( afxParticleEmitterPath_OriginType ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// DISC + +class afxParticleEmitterDiscData : public afxParticleEmitterData +{ + typedef afxParticleEmitterData Parent; + +public: + F32 pe_radius_min; + F32 pe_radius_max; + +public: + /*C*/ afxParticleEmitterDiscData(); + /*C*/ afxParticleEmitterDiscData(const afxParticleEmitterDiscData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + + bool preload(bool server, String &errorStr); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxParticleEmitterDiscData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxParticleEmitter : public ParticleEmitter +{ + typedef ParticleEmitter Parent; + +private: + afxParticleEmitterData* mDataBlock; + +protected: + Point3F pe_vector, pe_vector_norm; + + // these go with the "pathsTransform" field + Vector tpaths; + Vector tpath_mults; + U32 n_tpath_points; + Point3F** tpath_points; + + const SimObject* afx_owner; + + void init_paths(); + void cleanup_paths(); + + Particle* alloc_particle(); + ParticleData* pick_particle_type(); + void afx_emitParticles(const Point3F& point, const bool useLastPosition, const Point3F& velocity, const U32 numMilliseconds); + void afx_emitParticles(const Point3F& start, const Point3F& end, const Point3F& velocity, const U32 numMilliseconds); + void preCompute(const MatrixF& mat); + + virtual void sub_particleUpdate(Particle*); + virtual void sub_preCompute(const MatrixF& mat)=0; + virtual void sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx)=0; + +public: + /*C*/ afxParticleEmitter(); + /*D*/ ~afxParticleEmitter(); + + virtual void emitParticlesExt(const MatrixF& xfm, const Point3F& point, const Point3F& velocity, const U32 numMilliseconds); + + afxParticleEmitterData* getDataBlock(){ return mDataBlock; } + void setAFXOwner(const SimObject* owner) { afx_owner = owner; } + bool onNewDataBlock(GameBaseData* dptr, bool reload); + bool onAdd(); + void onRemove(); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// VECTOR + +class afxParticleEmitterVector : public afxParticleEmitter +{ + typedef afxParticleEmitter Parent; + +private: + afxParticleEmitterVectorData* mDataBlock; + +public: + /*C*/ afxParticleEmitterVector(); + /*D*/ ~afxParticleEmitterVector(); + + bool onNewDataBlock(GameBaseData* dptr, bool reload); + +protected: + void sub_preCompute(const MatrixF& mat); + void sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offse, S32 part_idxt); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// CONE + +class afxParticleEmitterCone : public afxParticleEmitter +{ + typedef afxParticleEmitter Parent; + +private: + afxParticleEmitterData* mDataBlock; + Point3F cone_v, cone_s0, cone_s1; + +public: + /*C*/ afxParticleEmitterCone(); + /*D*/ ~afxParticleEmitterCone(); + + bool onNewDataBlock(GameBaseData* dptr, bool reload); + +protected: + void sub_preCompute(const MatrixF& mat); + void sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// PATH + +class afxParticleEmitterPath : public afxParticleEmitter +{ + typedef afxParticleEmitter Parent; + +private: + afxParticleEmitterPathData* mDataBlock; + + Vector epaths; + Vector epath_mults; + U32 n_epath_points; + Point3F** epath_points; + + void init_paths(); + void cleanup_paths(); + + void groundConformPoint(Point3F& point, const MatrixF& mat); + +public: + /*C*/ afxParticleEmitterPath(); + /*D*/ ~afxParticleEmitterPath(); + + bool onNewDataBlock(GameBaseData* dptr, bool reload); + +protected: + bool onAdd(); + void onRemove(); + void sub_preCompute(const MatrixF& mat); + void sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// DISC + +class afxParticleEmitterDisc : public afxParticleEmitter +{ + typedef afxParticleEmitter Parent; + +private: + afxParticleEmitterDiscData* mDataBlock; + Point3F disc_v, disc_r; + +public: + /*C*/ afxParticleEmitterDisc(); + /*D*/ ~afxParticleEmitterDisc(); + + bool onNewDataBlock(GameBaseData* dptr, bool reload); + +protected: + void sub_preCompute(const MatrixF& mat); + void sub_addParticle(const Point3F& pos, const Point3F& vel, const U32 age_offset, S32 part_idx); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_EMITTER_PARTICLE_H_ diff --git a/Engine/source/afx/ce/afxPhraseEffect.cpp b/Engine/source/afx/ce/afxPhraseEffect.cpp new file mode 100644 index 000000000..3710e2fe6 --- /dev/null +++ b/Engine/source/afx/ce/afxPhraseEffect.cpp @@ -0,0 +1,312 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/ce/afxPhraseEffect.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPhraseEffectData::ewValidator +// +// When an effect is added using "addEffect", this validator intercepts the value +// and adds it to the dynamic effects list. +// +void afxPhraseEffectData::ewValidator::validateType(SimObject* object, void* typePtr) +{ + afxPhraseEffectData* eff_data = dynamic_cast(object); + afxEffectBaseData** ew = (afxEffectBaseData**)(typePtr); + + if (eff_data && ew) + { + eff_data->fx_list.push_back(*ew); + *ew = 0; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPhraseEffectData + +IMPLEMENT_CO_DATABLOCK_V1(afxPhraseEffectData); + +ConsoleDocClass( afxPhraseEffectData, + "@brief A datablock that specifies a Phrase Effect, a grouping of other effects.\n\n" + + "A Phrase Effect is a grouping or phrase of effects that do nothing until certain trigger events occur. It's like having a whole " + "Effectron organized as an individual effect." + "\n\n" + + "Phrase effects can respond to a number of different kinds of triggers:\n" + " -- Player triggers such as footsteps, jumps, landings, and idle triggers.\n" + " -- Arbitrary animation triggers on dts-based scene objects.\n" + " -- Arbitrary trigger bits assigned to active choreographer objects." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxPhraseEffectData::afxPhraseEffectData() +{ + duration = 0.0f; + n_loops = 1; + + // dummy entry holds effect-wrapper pointer while a special validator + // grabs it and adds it to an appropriate effects list + dummy_fx_entry = NULL; + + // marked true if datablock ids need to + // be converted into pointers + do_id_convert = false; + + trigger_mask = 0; + match_type = MATCH_ANY; + match_state = STATE_ON; + phrase_type = PHRASE_TRIGGERED; + + no_choreographer_trigs = false; + no_cons_trigs = false; + no_player_trigs = false; + + on_trig_cmd = ST_NULLSTRING; +} + +afxPhraseEffectData::afxPhraseEffectData(const afxPhraseEffectData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + duration = other.duration; + n_loops = other.n_loops; + dummy_fx_entry = other.dummy_fx_entry; + do_id_convert = other.do_id_convert; // -- + trigger_mask = other.trigger_mask; + match_type = other.match_type; + match_state = other.match_state; + phrase_type = other.phrase_type; + no_choreographer_trigs = other.no_choreographer_trigs; + no_cons_trigs = other.no_cons_trigs; + no_player_trigs = other.no_player_trigs; + on_trig_cmd = other.on_trig_cmd; + + // fx_list; // -- ?? +} + +void afxPhraseEffectData::reloadReset() +{ + fx_list.clear(); +} + +ImplementEnumType( afxPhraseEffect_MatchType, "Possible phrase effect match types.\n" "@ingroup afxPhraseEffect\n\n" ) + { afxPhraseEffectData::MATCH_ANY, "any", "..." }, + { afxPhraseEffectData::MATCH_ALL, "all", "..." }, +EndImplementEnumType; + +ImplementEnumType( afxPhraseEffect_StateType, "Possible phrase effect state types.\n" "@ingroup afxPhraseEffect\n\n" ) + { afxPhraseEffectData::STATE_ON, "on", "..." }, + { afxPhraseEffectData::STATE_OFF, "off", "..." }, + { afxPhraseEffectData::STATE_ON_AND_OFF, "both", "..." }, +EndImplementEnumType; + +ImplementEnumType( afxPhraseEffect_PhraseType, "Possible phrase effect types.\n" "@ingroup afxPhraseEffect\n\n" ) + { afxPhraseEffectData::PHRASE_TRIGGERED, "triggered", "..." }, + { afxPhraseEffectData::PHRASE_CONTINUOUS, "continuous", "..." }, +EndImplementEnumType; + +#define myOffset(field) Offset(field, afxPhraseEffectData) + +void afxPhraseEffectData::initPersistFields() +{ + addField("duration", TypeF32, myOffset(duration), + "Specifies a duration for the phrase-effect. If set to infinity, the phrase-effect " + "needs to have a phraseType of continuous. Set infinite duration using " + "$AFX::INFINITE_TIME."); + addField("numLoops", TypeS32, myOffset(n_loops), + "Specifies the number of times the phrase-effect should loop. If set to infinity, " + "the phrase-effect needs to have a phraseType of continuous. Set infinite looping " + "using $AFX::INFINITE_REPEATS."); + addField("triggerMask", TypeS32, myOffset(trigger_mask), + "Sets which bits to consider in the current trigger-state which consists of 32 " + "trigger-bits combined from (possibly overlapping) player trigger bits, constraint " + "trigger bits, and choreographer trigger bits."); + + addField("matchType", TYPEID(), myOffset(match_type), + "Selects what combination of bits in triggerMask lead to a trigger. When set to " + "'any', any bit in triggerMask matching the current trigger-state will cause a " + "trigger. If set to 'all', every bit in triggerMask must match the trigger-state. " + "Possible values: any or all."); + addField("matchState", TYPEID(), myOffset(match_state), + "Selects which bit-state(s) of bits in the triggerMask to consider when comparing to " + "the current trigger-state. Possible values: on, off, or both."); + addField("phraseType", TYPEID(), myOffset(phrase_type), + "Selects between triggered and continuous types of phrases. When set to 'triggered', " + "the phrase-effect is triggered when the relevant trigger-bits change state. When set " + "to 'continuous', the phrase-effect will stay active as long as the trigger-bits " + "remain in a matching state. Possible values: triggered or continuous."); + + addField("ignoreChoreographerTriggers", TypeBool, myOffset(no_choreographer_trigs), + "When true, trigger-bits on the choreographer will be ignored."); + addField("ignoreConstraintTriggers", TypeBool, myOffset(no_cons_trigs), + "When true, animation triggers from dts-based constraint source objects will be " + "ignored."); + addField("ignorePlayerTriggers", TypeBool, myOffset(no_player_trigs), + "When true, Player-specific triggers from Player-derived constraint source objects " + "will be ignored."); + + addField("onTriggerCommand", TypeString, myOffset(on_trig_cmd), + "Like a field substitution statement without the leading '$$' token, this eval " + "statement will be executed when a trigger occurs. Any '%%' and '##' tokens will be " + "substituted."); + + // effect lists + // for each of these, dummy_fx_entry is set and then a validator adds it to the appropriate effects list + static ewValidator emptyValidator(0); + addFieldV("addEffect", TYPEID< afxEffectBaseData >(), myOffset(dummy_fx_entry), &emptyValidator, + "A field macro which adds an effect wrapper datablock to a list of effects associated " + "with the phrase-effect's single phrase. Unlike other fields, addEffect follows an " + "unusual syntax. Order is important since the effects will resolve in the order they " + "are added to each list."); + + Parent::initPersistFields(); + + // disallow some field substitutions + disableFieldSubstitutions("addEffect"); +} + +bool afxPhraseEffectData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxPhraseEffectData::pack_fx(BitStream* stream, const afxEffectList& fx, bool packed) +{ + stream->writeInt(fx.size(), EFFECTS_PER_PHRASE_BITS); + for (int i = 0; i < fx.size(); i++) + writeDatablockID(stream, fx[i], packed); +} + +void afxPhraseEffectData::unpack_fx(BitStream* stream, afxEffectList& fx) +{ + fx.clear(); + S32 n_fx = stream->readInt(EFFECTS_PER_PHRASE_BITS); + for (int i = 0; i < n_fx; i++) + fx.push_back((afxEffectWrapperData*)readDatablockID(stream)); +} + +void afxPhraseEffectData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(duration); + stream->write(n_loops); + stream->write(trigger_mask); + stream->writeInt(match_type, 1); + stream->writeInt(match_state, 2); + stream->writeInt(phrase_type, 1); + + stream->writeFlag(no_choreographer_trigs); + stream->writeFlag(no_cons_trigs); + stream->writeFlag(no_player_trigs); + + stream->writeString(on_trig_cmd); + + pack_fx(stream, fx_list, packed); +} + +void afxPhraseEffectData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&duration); + stream->read(&n_loops); + stream->read(&trigger_mask); + match_type = stream->readInt(1); + match_state = stream->readInt(2); + phrase_type = stream->readInt(1); + + no_choreographer_trigs = stream->readFlag(); + no_cons_trigs = stream->readFlag(); + no_player_trigs = stream->readFlag(); + + on_trig_cmd = stream->readSTString(); + + do_id_convert = true; + unpack_fx(stream, fx_list); +} + +bool afxPhraseEffectData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + // Resolve objects transmitted from server + if (!server) + { + if (do_id_convert) + { + for (S32 i = 0; i < fx_list.size(); i++) + { + SimObjectId db_id = SimObjectId((uintptr_t)fx_list[i]); + if (db_id != 0) + { + // try to convert id to pointer + if (!Sim::findObject(db_id, fx_list[i])) + { + Con::errorf(ConsoleLogEntry::General, + "afxPhraseEffectData::preload() -- bad datablockId: 0x%x", + db_id); + } + } + } + do_id_convert = false; + } + } + + return true; +} + +void afxPhraseEffectData::gather_cons_defs(Vector& defs) +{ + afxConstraintDef::gather_cons_defs(defs, fx_list); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod( afxPhraseEffectData, addEffect, void, ( afxEffectBaseData* effectData ),, + "Add a child effect to a phrase effect datablock. Argument can be an afxEffectWrappperData or an afxEffectGroupData.\n" ) +{ + if (!effectData) + { + Con::errorf("afxPhraseEffectData::addEffect() -- failed to resolve effect datablock."); + return; + } + + object->fx_list.push_back(effectData); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxPhraseEffect.h b/Engine/source/afx/ce/afxPhraseEffect.h new file mode 100644 index 000000000..ad189d787 --- /dev/null +++ b/Engine/source/afx/ce/afxPhraseEffect.h @@ -0,0 +1,120 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_PHRASE_EFFECT_H_ +#define _AFX_PHRASE_EFFECT_H_ + +#include "console/typeValidators.h" + +#include "afx/ce/afxComponentEffect.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxPhrase.h" + +class afxPhraseEffectData : public GameBaseData, public afxEffectDefs, public afxComponentEffectData +{ + typedef GameBaseData Parent; + + class ewValidator : public TypeValidator + { + U32 id; + public: + ewValidator(U32 id) { this->id = id; } + void validateType(SimObject *object, void *typePtr); + }; + + bool do_id_convert; + +public: + enum MatchType { + MATCH_ANY = 0, + MATCH_ALL = 1 + }; + enum StateType { + STATE_ON = 1, + STATE_OFF = 2, + STATE_ON_AND_OFF = STATE_ON | STATE_OFF + }; + enum PhraseType + { + PHRASE_TRIGGERED = 0, + PHRASE_CONTINUOUS = 1 + }; + +public: + afxEffectList fx_list; + F32 duration; + S32 n_loops; + U32 trigger_mask; + U32 match_type; + U32 match_state; + U32 phrase_type; + + bool no_choreographer_trigs; + bool no_cons_trigs; + bool no_player_trigs; + + StringTableEntry on_trig_cmd; + + afxEffectBaseData* dummy_fx_entry; + +private: + void pack_fx(BitStream* stream, const afxEffectList& fx, bool packed); + void unpack_fx(BitStream* stream, afxEffectList& fx); + +public: + /*C*/ afxPhraseEffectData(); + /*C*/ afxPhraseEffectData(const afxPhraseEffectData&, bool = false); + + virtual void reloadReset(); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + virtual void gather_cons_defs(Vector& defs); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxPhraseEffectData); + DECLARE_CATEGORY("AFX"); +}; + +typedef afxPhraseEffectData::MatchType afxPhraseEffect_MatchType; +DefineEnumType( afxPhraseEffect_MatchType ); + +typedef afxPhraseEffectData::StateType afxPhraseEffect_StateType; +DefineEnumType( afxPhraseEffect_StateType ); + +typedef afxPhraseEffectData::PhraseType afxPhraseEffect_PhraseType; +DefineEnumType( afxPhraseEffect_PhraseType ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_PHRASE_EFFECT_H_ diff --git a/Engine/source/afx/ce/afxPhysicalZone.cpp b/Engine/source/afx/ce/afxPhysicalZone.cpp new file mode 100644 index 000000000..2ed7ca6b6 --- /dev/null +++ b/Engine/source/afx/ce/afxPhysicalZone.cpp @@ -0,0 +1,114 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "T3D/physicalZone.h" + +#include "afx/ce/afxPhysicalZone.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPhysicalZoneData + +IMPLEMENT_CO_DATABLOCK_V1(afxPhysicalZoneData); + +ConsoleDocClass( afxPhysicalZoneData, + "@brief A datablock that specifies a PhysicalZone effect.\n\n" + + "A Physical Zone is a Torque effect that applies physical forces to Players and other movable objects that enter a specific " + "region of influence. AFX has enhanced Physical Zones by allowing orientation of vector forces and adding radial forces. " + "AFX has also optimized Physical Zone networking so that they can be constrained to moving objects for a variety of " + "effects including repelling and flying." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxPhysicalZoneData::afxPhysicalZoneData() +{ + mVelocityMod = 1.0f; + mGravityMod = 1.0f; + mAppliedForce.zero(); + mPolyhedron = ST_NULLSTRING; + force_type = PhysicalZone::VECTOR; + orient_force = false; + exclude_cons_obj = false; +} + +afxPhysicalZoneData::afxPhysicalZoneData(const afxPhysicalZoneData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + mVelocityMod = other.mVelocityMod; + mGravityMod = other.mGravityMod; + mAppliedForce = other.mAppliedForce; + mPolyhedron = other.mPolyhedron; + force_type = other.force_type; + orient_force = other.orient_force; + exclude_cons_obj = other.exclude_cons_obj; +} + +#define myOffset(field) Offset(field, afxPhysicalZoneData) + +void afxPhysicalZoneData::initPersistFields() +{ + addField("velocityMod", TypeF32, myOffset(mVelocityMod), + "A multiplier that biases the velocity of an object every tick it is within the " + "zone."); + addField("gravityMod", TypeF32, myOffset(mGravityMod), + "A multiplier that biases the influence of gravity on objects within the zone."); + addField("appliedForce", TypePoint3F, myOffset(mAppliedForce), + "A three-valued vector representing a directional force applied to objects withing " + "the zone."); + addField("polyhedron", TypeString, myOffset(mPolyhedron), + "Floating point values describing the outer bounds of the PhysicalZone's region of " + "influence."); + + addField("forceType", TYPEID(), myOffset(force_type), + "This enumerated attribute defines the type of force used in the PhysicalZone. " + "Possible values: vector, sphere, or cylinder."); + + addField("orientForce", TypeBool, myOffset(orient_force), + "Determines if the force can be oriented by the PhysicalZone's transform matrix."); + addField("excludeConstraintObject", TypeBool, myOffset(exclude_cons_obj), + "When true, an object used as the primary position constraint of a physical-zone " + "effect will not be influenced by the forces of the zone."); + + Parent::initPersistFields(); +} + +void afxPhysicalZoneData::packData(BitStream* stream) +{ + Parent::packData(stream); +} + +void afxPhysicalZoneData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxPhysicalZone.h b/Engine/source/afx/ce/afxPhysicalZone.h new file mode 100644 index 000000000..379e25b12 --- /dev/null +++ b/Engine/source/afx/ce/afxPhysicalZone.h @@ -0,0 +1,65 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_PHYSICAL_ZONE_H_ +#define _AFX_PHYSICAL_ZONE_H_ + +#include "T3D/gameBase/gameBase.h" +#include "T3D/trigger.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPhysicalZoneData + +class afxPhysicalZoneData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + F32 mVelocityMod; + F32 mGravityMod; + Point3F mAppliedForce; + StringTableEntry mPolyhedron; + S32 force_type; + bool orient_force; + bool exclude_cons_obj; + +public: + /*C*/ afxPhysicalZoneData(); + /*C*/ afxPhysicalZoneData(const afxPhysicalZoneData&, bool = false); + + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxPhysicalZoneData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_PHYSICAL_ZONE_H_ diff --git a/Engine/source/afx/ce/afxPlayerMovement.cpp b/Engine/source/afx/ce/afxPlayerMovement.cpp new file mode 100644 index 000000000..bd5161cc8 --- /dev/null +++ b/Engine/source/afx/ce/afxPlayerMovement.cpp @@ -0,0 +1,140 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "scene/sceneRenderState.h" +#include "math/mathIO.h" + +#include "afx/afxChoreographer.h" +#include "afx/ce/afxPlayerMovement.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPlayerMovementData + +IMPLEMENT_CO_DATABLOCK_V1(afxPlayerMovementData); + +ConsoleDocClass( afxPlayerMovementData, + "@brief A datablock that specifies a Player Movement effect.\n\n" + + "Player Movement effects are used to directly alter the speed and/or movement direction of Player objects. The Player " + "Movement effect is similar to the Player Puppet effect, but where puppet effects totally take over a Player's transformation " + "using the AFX constraint system, Player Movement effects 'steer' the player using the same mechanisms that allow " + "Player control from mouse and keyboard input. Another difference is that Player Movement effects only influence the " + "server instance of a Player. Puppet effects can influence both the Player's server instance and its client ghosts." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +static Point3F default_movement(F32_MAX, F32_MAX, F32_MAX); + +afxPlayerMovementData::afxPlayerMovementData() +{ + speed_bias = 1.0f; + movement = default_movement; + movement_op = OP_MULTIPLY; +} + +afxPlayerMovementData::afxPlayerMovementData(const afxPlayerMovementData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + speed_bias = other.speed_bias; + movement = other.movement; + movement_op = other.movement_op; +} + +ImplementEnumType( afxPlayerMovement_OpType, "Possible player movement operation types.\n" "@ingroup afxPlayerMovemen\n\n" ) + { afxPlayerMovementData::OP_ADD, "add", "..." }, + { afxPlayerMovementData::OP_MULTIPLY, "multiply", "..." }, + { afxPlayerMovementData::OP_REPLACE, "replace", "..." }, + { afxPlayerMovementData::OP_MULTIPLY, "mult", "..." }, +EndImplementEnumType; + +#define myOffset(field) Offset(field, afxPlayerMovementData) + +void afxPlayerMovementData::initPersistFields() +{ + addField("speedBias", TypeF32, myOffset(speed_bias), + "A floating-point multiplier that scales the constraint Player's movement speed."); + addField("movement", TypePoint3F, myOffset(movement), + ""); + addField("movementOp", TYPEID(), myOffset(movement_op), + "Possible values: add, multiply, or replace."); + + Parent::initPersistFields(); +} + +bool afxPlayerMovementData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxPlayerMovementData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(speed_bias); + if (stream->writeFlag(movement != default_movement)) + { + stream->write(movement.x); + stream->write(movement.y); + stream->write(movement.z); + stream->writeInt(movement_op, OP_BITS); + } +} + +void afxPlayerMovementData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&speed_bias); + if (stream->readFlag()) + { + stream->read(&movement.x); + stream->read(&movement.y); + stream->read(&movement.z); + movement_op = stream->readInt(OP_BITS); + } + else + { + movement = default_movement; + movement_op = OP_MULTIPLY; + } +} + +bool afxPlayerMovementData::hasMovementOverride() +{ + return (movement != default_movement); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxPlayerMovement.h b/Engine/source/afx/ce/afxPlayerMovement.h new file mode 100644 index 000000000..725f388e3 --- /dev/null +++ b/Engine/source/afx/ce/afxPlayerMovement.h @@ -0,0 +1,73 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_PLAYER_MOVEMENT_H_ +#define _AFX_PLAYER_MOVEMENT_H_ + +#include "afx/ce/afxComponentEffect.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxConstraint.h" + +class afxPlayerMovementData : public GameBaseData, public afxEffectDefs +{ + typedef GameBaseData Parent; +public: + enum OpType + { + OP_ADD = 0, + OP_MULTIPLY, + OP_REPLACE, + OP_BITS = 2, + }; + +public: + F32 speed_bias; + Point3F movement; + U32 movement_op; + +public: + /*C*/ afxPlayerMovementData(); + /*C*/ afxPlayerMovementData(const afxPlayerMovementData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool hasMovementOverride(); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxPlayerMovementData); + DECLARE_CATEGORY("AFX"); +}; + +typedef afxPlayerMovementData::OpType afxPlayerMovement_OpType; +DefineEnumType( afxPlayerMovement_OpType ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_PLAYER_MOVEMENT_H_ diff --git a/Engine/source/afx/ce/afxPlayerPuppet.cpp b/Engine/source/afx/ce/afxPlayerPuppet.cpp new file mode 100644 index 000000000..b9ec4f090 --- /dev/null +++ b/Engine/source/afx/ce/afxPlayerPuppet.cpp @@ -0,0 +1,122 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "scene/sceneRenderState.h" +#include "math/mathIO.h" + +#include "afx/afxChoreographer.h" +#include "afx/ce/afxPlayerPuppet.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPlayerPuppetData + +IMPLEMENT_CO_DATABLOCK_V1(afxPlayerPuppetData); + +ConsoleDocClass( afxPlayerPuppetData, + "@brief A datablock that specifies a Player Puppet effect.\n\n" + + "Player Puppet effects are defined using the afxPlayerPuppetData datablock and are used to control the movement of " + "Player objects with the AFX constraint system. The Player Puppet effect is similar to the Player Movement effect, but " + "where movement effects 'steer' the player using the same mechanisms that allow Player control from mouse and keyboard " + "input, Player Puppet effects totally take over a Player's transformation using the AFX constraint system." + "\n\n" + + "Player Puppet can be configured to directly move a Player's client ghosts as well as its server instance. When doing this, " + "it is important to keep the general motion of the Player object and its ghosts somewhat consistent. Otherwise, obvious " + "discontinuities in the motion will result when the Player Puppet ends and control is restored to the server Player." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxPlayerPuppetData::afxPlayerPuppetData() +{ + obj_spec = ST_NULLSTRING; + networking = SERVER_ONLY; +} + +afxPlayerPuppetData::afxPlayerPuppetData(const afxPlayerPuppetData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + obj_spec = other.obj_spec; + networking = other.networking; +} + +#define myOffset(field) Offset(field, afxPlayerPuppetData) + +void afxPlayerPuppetData::initPersistFields() +{ + addField("objectSpec", TypeString, myOffset(obj_spec), + "..."); + addField("networking", TypeS8, myOffset(networking), + "..."); + + Parent::initPersistFields(); + + // disallow some field substitutions + disableFieldSubstitutions("objectSpec"); + disableFieldSubstitutions("networking"); +} + +bool afxPlayerPuppetData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + bool runs_on_s = ((networking & (SERVER_ONLY | SERVER_AND_CLIENT)) != 0); + bool runs_on_c = ((networking & (CLIENT_ONLY | SERVER_AND_CLIENT)) != 0); + obj_def.parseSpec(obj_spec, runs_on_s, runs_on_c); + + return true; +} + +void afxPlayerPuppetData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(obj_spec); + stream->write(networking); +} + +void afxPlayerPuppetData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + obj_spec = stream->readSTString(); + stream->read(&networking); +} + +void afxPlayerPuppetData::gather_cons_defs(Vector& defs) +{ + if (obj_def.isDefined()) + defs.push_back(obj_def); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxPlayerPuppet.h b/Engine/source/afx/ce/afxPlayerPuppet.h new file mode 100644 index 000000000..ca6624bd9 --- /dev/null +++ b/Engine/source/afx/ce/afxPlayerPuppet.h @@ -0,0 +1,64 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_PLAYER_PUPPET_H_ +#define _AFX_PLAYER_PUPPET_H_ + +#include "afx/ce/afxComponentEffect.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxConstraint.h" + +class afxPlayerPuppetData : public GameBaseData, public afxEffectDefs, public afxComponentEffectData +{ + typedef GameBaseData Parent; + +public: + StringTableEntry obj_spec; + afxConstraintDef obj_def; + + U8 networking; + + virtual void gather_cons_defs(Vector& defs); + +public: + /*C*/ afxPlayerPuppetData(); + /*C*/ afxPlayerPuppetData(const afxPlayerPuppetData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxPlayerPuppetData); + DECLARE_CATEGORY("AFX"); +}; + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_PLAYER_PUPPET_H_ diff --git a/Engine/source/afx/ce/afxPointLight_T3D.cpp b/Engine/source/afx/ce/afxPointLight_T3D.cpp new file mode 100644 index 000000000..748ecf26c --- /dev/null +++ b/Engine/source/afx/ce/afxPointLight_T3D.cpp @@ -0,0 +1,103 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +#include "afx/ce/afxPointLight_T3D.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxT3DPointLightData + +IMPLEMENT_CO_DATABLOCK_V1(afxT3DPointLightData); + +ConsoleDocClass( afxT3DPointLightData, + "@brief A datablock that specifies a dynamic Point Light effect.\n\n" + + "A Point Light effect that uses the T3D PointLight object. afxT3DPointLightData has the same fields found in PointLight but " + "in a datablock structure." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxT3DPointLightData::afxT3DPointLightData() + : mRadius( 5.0f ) +{ +} + +afxT3DPointLightData::afxT3DPointLightData(const afxT3DPointLightData& other, bool temp_clone) : afxT3DLightBaseData(other, temp_clone) +{ + mRadius = other.mRadius; +} + +// +// NOTE: keep this as consistent as possible with PointLight::initPersistFields() +// +void afxT3DPointLightData::initPersistFields() +{ + addGroup( "Light" ); + + addField( "radius", TypeF32, Offset( mRadius, afxT3DPointLightData ), + "Controls the falloff of the light emission"); + + endGroup( "Light" ); + + // We do the parent fields at the end so that + // they show up that way in the inspector. + Parent::initPersistFields(); + + // Remove the scale field... it's already + // defined by the light radius. + removeField( "scale" ); +} + +bool afxT3DPointLightData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxT3DPointLightData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write( mRadius ); +} + +void afxT3DPointLightData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read( &mRadius ); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxPointLight_T3D.h b/Engine/source/afx/ce/afxPointLight_T3D.h new file mode 100644 index 000000000..0f40ba199 --- /dev/null +++ b/Engine/source/afx/ce/afxPointLight_T3D.h @@ -0,0 +1,56 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_T3D_POINT_LIGHT_H_ +#define _AFX_T3D_POINT_LIGHT_H_ + +#include "afx/ce/afxLightBase_T3D.h" + +class afxT3DPointLightData : public afxT3DLightBaseData +{ + typedef afxT3DLightBaseData Parent; + +public: + F32 mRadius; + +public: + /*C*/ afxT3DPointLightData(); + /*C*/ afxT3DPointLightData(const afxT3DPointLightData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxT3DPointLightData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_T3D_POINT_LIGHT_H_ diff --git a/Engine/source/afx/ce/afxProjectile.cpp b/Engine/source/afx/ce/afxProjectile.cpp new file mode 100644 index 000000000..581e16d3c --- /dev/null +++ b/Engine/source/afx/ce/afxProjectile.cpp @@ -0,0 +1,371 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "T3D/shapeBase.h" + +#include "afx/ce/afxProjectile.h" +#include "afx/afxChoreographer.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxProjectileData + +IMPLEMENT_CO_DATABLOCK_V1(afxProjectileData); + +ConsoleDocClass( afxProjectileData, + "@brief A datablock that specifies a Projectile effect.\n\n" + + "afxProjectileData inherits from ProjectileData and adds some AFX specific fields." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxProjectileData::afxProjectileData() +{ + networking = GHOSTABLE; + launch_pos_spec = ST_NULLSTRING; + launch_dir_bias.zero(); + ignore_src_timeout = false; + dynamicCollisionMask = 0; + staticCollisionMask = 0; + override_collision_masks = false; + launch_dir_method = TowardPos2Constraint; +} + +afxProjectileData::afxProjectileData(const afxProjectileData& other, bool temp_clone) : ProjectileData(other, temp_clone) +{ + networking = other.networking; + launch_pos_spec = other.launch_pos_spec; + launch_pos_def = other.launch_pos_def; + launch_dir_bias = other.launch_dir_bias; + ignore_src_timeout = other.ignore_src_timeout; + dynamicCollisionMask = other.dynamicCollisionMask; + staticCollisionMask = other.staticCollisionMask; + override_collision_masks = other.override_collision_masks; + launch_dir_method = other.launch_dir_method; +} + +ImplementEnumType( afxProjectile_LaunchDirType, "Possible projectile launch direction types.\n" "@ingroup afxProjectile\n\n" ) + { afxProjectileData::TowardPos2Constraint, "towardPos2Constraint", "..." }, + { afxProjectileData::OrientConstraint, "orientConstraint", "..." }, + { afxProjectileData::LaunchDirField, "launchDirField", "..." }, +EndImplementEnumType; + +#define myOffset(field) Offset(field, afxProjectileData) + +void afxProjectileData::initPersistFields() +{ + addField("networking", TypeS8, myOffset(networking), + "..."); + addField("launchPosSpec", TypeString, myOffset(launch_pos_spec), + "..."); + addField("launchDirBias", TypePoint3F, myOffset(launch_dir_bias), + "..."); + addField("ignoreSourceTimeout", TypeBool, myOffset(ignore_src_timeout), + "..."); + addField("dynamicCollisionMask", TypeS32, myOffset(dynamicCollisionMask), + "..."); + addField("staticCollisionMask", TypeS32, myOffset(staticCollisionMask), + "..."); + addField("overrideCollisionMasks", TypeBool, myOffset(override_collision_masks), + "..."); + + addField("launchDirMethod", TYPEID(), myOffset(launch_dir_method), + "Possible values: towardPos2Constraint, orientConstraint, or launchDirField."); + + Parent::initPersistFields(); +} + +bool afxProjectileData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + bool runs_on_s = ((networking & (SERVER_ONLY | SERVER_AND_CLIENT)) != 0); + bool runs_on_c = ((networking & (CLIENT_ONLY | SERVER_AND_CLIENT)) != 0); + launch_pos_def.parseSpec(launch_pos_spec, runs_on_s, runs_on_c); + + return true; +} + +void afxProjectileData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(networking); + stream->writeString(launch_pos_spec); + if (stream->writeFlag(!launch_dir_bias.isZero())) + { + stream->write(launch_dir_bias.x); + stream->write(launch_dir_bias.y); + stream->write(launch_dir_bias.z); + } + stream->writeFlag(ignore_src_timeout); + if (stream->writeFlag(override_collision_masks)) + { + stream->write(dynamicCollisionMask); + stream->write(staticCollisionMask); + } + + stream->writeInt(launch_dir_method, 2); +} + +void afxProjectileData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&networking); + launch_pos_spec = stream->readSTString(); + if (stream->readFlag()) + { + stream->read(&launch_dir_bias.x); + stream->read(&launch_dir_bias.y); + stream->read(&launch_dir_bias.z); + } + else + launch_dir_bias.zero(); + ignore_src_timeout = stream->readFlag(); + if ((override_collision_masks = stream->readFlag()) == true) + { + stream->read(&dynamicCollisionMask); + stream->read(&staticCollisionMask); + } + else + { + dynamicCollisionMask = 0; + staticCollisionMask = 0; + } + + launch_dir_method = (U32) stream->readInt(2); +} + +void afxProjectileData::gather_cons_defs(Vector& defs) +{ + if (launch_pos_def.isDefined()) + defs.push_back(launch_pos_def); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxProjectile + +IMPLEMENT_CO_NETOBJECT_V1(afxProjectile); + +ConsoleDocClass( afxProjectile, + "@brief A Projectile effect as defined by an afxProjectileData datablock.\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" +); + +afxProjectile::afxProjectile() +{ + chor_id = 0; + hookup_with_chor = false; + ghost_cons_name = ST_NULLSTRING; + client_only = false; +} + +afxProjectile::afxProjectile(U32 networking, U32 chor_id, StringTableEntry cons_name) +{ + if (networking & SCOPE_ALWAYS) + { + mNetFlags.clear(); + mNetFlags.set(Ghostable | ScopeAlways); + client_only = false; + } + else if (networking & GHOSTABLE) + { + mNetFlags.clear(); + mNetFlags.set(Ghostable); + client_only = false; + } + else if (networking & SERVER_ONLY) + { + mNetFlags.clear(); + client_only = false; + } + else // if (networking & CLIENT_ONLY) + { + mNetFlags.clear(); + mNetFlags.set(IsGhost); + client_only = true; + } + + this->chor_id = chor_id; + hookup_with_chor = false; + this->ghost_cons_name = cons_name; +} + +afxProjectile::~afxProjectile() +{ +} + +void afxProjectile::init(Point3F& pos, Point3F& vel, ShapeBase* src_obj) +{ + mCurrPosition = pos; + mCurrVelocity = vel; + if (src_obj) + { + mSourceObject = src_obj; + mSourceObjectId = src_obj->getId(); + mSourceObjectSlot = 0; + } + + setPosition(mCurrPosition); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +bool afxProjectile::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + return Parent::onNewDataBlock(dptr, reload); +} + +void afxProjectile::processTick(const Move* move) +{ + // note: this deletion test must occur before calling to the parent's + // processTick() because if this is a server projectile, the parent + // might decide to delete it and then client_only will no longer be + // valid after the return. + bool do_delete = (client_only && mCurrTick >= mDataBlock->lifetime); + + Parent::processTick(move); + + if (do_delete) + deleteObject(); +} + +void afxProjectile::interpolateTick(F32 delta) +{ + if (client_only) + return; + + Parent::interpolateTick(delta); +} + +void afxProjectile::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if (hookup_with_chor) + { + afxChoreographer* chor = arcaneFX::findClientChoreographer(chor_id); + if (chor) + { + chor->setGhostConstraintObject(this, ghost_cons_name); + hookup_with_chor = false; + } + } +} + +bool afxProjectile::onAdd() +{ + if(!Parent::onAdd()) + return false; + + if (isClientObject()) + { + // add to client side mission cleanup + SimGroup *cleanup = dynamic_cast( Sim::findObject( "ClientMissionCleanup") ); + if( cleanup != NULL ) + { + cleanup->addObject( this ); + } + else + { + AssertFatal( false, "Error, could not find ClientMissionCleanup group" ); + return false; + } + } + + return true; +} + +void afxProjectile::onRemove() +{ + Parent::onRemove(); +} + +//~~~~~~~~~~~~~~~~~~~~ + +U32 afxProjectile::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(conn, mask, stream); + + // InitialUpdate + if (stream->writeFlag(mask & InitialUpdateMask)) + { + stream->write(chor_id); + stream->writeString(ghost_cons_name); + } + + return retMask; +} + +//~~~~~~~~~~~~~~~~~~~~// + +void afxProjectile::unpackUpdate(NetConnection * conn, BitStream * stream) +{ + Parent::unpackUpdate(conn, stream); + + // InitialUpdate + if (stream->readFlag()) + { + stream->read(&chor_id); + ghost_cons_name = stream->readSTString(); + + if (chor_id != 0 && ghost_cons_name != ST_NULLSTRING) + hookup_with_chor = true; + } +} + +class afxProjectileDeleteEvent : public SimEvent +{ +public: + void process(SimObject *object) + { + object->deleteObject(); + } +}; + +void afxProjectile::explode(const Point3F& p, const Point3F& n, const U32 collideType) +{ + // Make sure we don't explode twice... + if ( isHidden() ) + return; + + Parent::explode(p, n, collideType); + + if (isClientObject() && client_only) + Sim::postEvent(this, new afxProjectileDeleteEvent, Sim::getCurrentTime() + DeleteWaitTime); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxProjectile.h b/Engine/source/afx/ce/afxProjectile.h new file mode 100644 index 000000000..74609a4d3 --- /dev/null +++ b/Engine/source/afx/ce/afxProjectile.h @@ -0,0 +1,117 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_PROJECTILE_H_ +#define _AFX_PROJECTILE_H_ + +#include "lighting/lightInfo.h" +#include "T3D/projectile.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxConstraint.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxProjectileData + +class afxProjectileData : public ProjectileData, public afxEffectDefs +{ + typedef ProjectileData Parent; + +public: + enum LaunchDirType { + TowardPos2Constraint, + OrientConstraint, + LaunchDirField + }; + +public: + U8 networking; + StringTableEntry launch_pos_spec; + afxConstraintDef launch_pos_def; + Point3F launch_dir_bias; + bool ignore_src_timeout; + U32 dynamicCollisionMask; + U32 staticCollisionMask; + bool override_collision_masks; + U32 launch_dir_method; + + virtual void gather_cons_defs(Vector& defs); + +public: + /*C*/ afxProjectileData(); + /*C*/ afxProjectileData(const afxProjectileData&, bool = false); + + virtual bool onAdd(); + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxProjectileData); + DECLARE_CATEGORY("AFX"); +}; + +typedef afxProjectileData::LaunchDirType afxProjectile_LaunchDirType; +DefineEnumType( afxProjectile_LaunchDirType ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxProjectile + +class afxProjectile : public Projectile, public afxEffectDefs +{ + typedef Projectile Parent; + +private: + U32 chor_id; + bool hookup_with_chor; + StringTableEntry ghost_cons_name; + bool client_only; + +public: + /*C*/ afxProjectile(); + /*C*/ afxProjectile(U32 networking, U32 chor_id, StringTableEntry cons_name); + /*D*/ ~afxProjectile(); + + void init(Point3F& pos, Point3F& vel, ShapeBase* src_obj); + + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + virtual void processTick(const Move *move); + virtual void interpolateTick(F32 delta); + virtual void advanceTime(F32 dt); + virtual bool onAdd(); + virtual void onRemove(); + virtual U32 packUpdate(NetConnection*, U32, BitStream*); + virtual void unpackUpdate(NetConnection*, BitStream*); + virtual void explode(const Point3F& p, const Point3F& n, const U32 collideType); + + DECLARE_CONOBJECT(afxProjectile); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_PROJECTILE_H_ diff --git a/Engine/source/afx/ce/afxScriptEvent.cpp b/Engine/source/afx/ce/afxScriptEvent.cpp new file mode 100644 index 000000000..762652119 --- /dev/null +++ b/Engine/source/afx/ce/afxScriptEvent.cpp @@ -0,0 +1,94 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +#include "afx/ce/afxScriptEvent.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxScriptEventData + +IMPLEMENT_CO_DATABLOCK_V1(afxScriptEventData); + +ConsoleDocClass( afxScriptEventData, + "@brief A datablock that specifies a Script Event effect.\n\n" + + "Arbitrary script functions can be called as an AFX effect using afxScriptEventData. They are useful for implementing " + "high-level scripted side-effects such as character resurrection or teleportation." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxScriptEventData::afxScriptEventData() +{ + method_name = ST_NULLSTRING; + script_data = ST_NULLSTRING; +} + +afxScriptEventData::afxScriptEventData(const afxScriptEventData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + method_name = other.method_name; + script_data = other.script_data; +} + +#define myOffset(field) Offset(field, afxScriptEventData) + +void afxScriptEventData::initPersistFields() +{ + addField("methodName", TypeString, myOffset(method_name), + "The name of a script method defined for the instance class of an effects " + "choreographer. The arguments used to call this method are determined by the type " + "of choreographer."); + addField("scriptData", TypeString, myOffset(script_data), + "An arbitrary blind data value which is passed in as an argument of the script event " + "method. The value of scriptData can be used to differentiate uses when handling " + "different script event effects with a single method."); + + Parent::initPersistFields(); +} + +void afxScriptEventData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(method_name); + stream->writeString(script_data); +} + +void afxScriptEventData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + method_name = stream->readSTString(); + script_data = stream->readSTString(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxScriptEvent.h b/Engine/source/afx/ce/afxScriptEvent.h new file mode 100644 index 000000000..234e50cf3 --- /dev/null +++ b/Engine/source/afx/ce/afxScriptEvent.h @@ -0,0 +1,57 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_SCRIPT_EVENT_H_ +#define _AFX_SCRIPT_EVENT_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxScriptEventData + +struct afxScriptEventData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + StringTableEntry method_name; + StringTableEntry script_data; + +public: + /*C*/ afxScriptEventData(); + /*C*/ afxScriptEventData(const afxScriptEventData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxScriptEventData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_SCRIPT_EVENT_H_ diff --git a/Engine/source/afx/ce/afxSpotLight_T3D.cpp b/Engine/source/afx/ce/afxSpotLight_T3D.cpp new file mode 100644 index 000000000..aa21e50a5 --- /dev/null +++ b/Engine/source/afx/ce/afxSpotLight_T3D.cpp @@ -0,0 +1,112 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" + +#include "afx/ce/afxSpotLight_T3D.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxT3DSpotLightData + +IMPLEMENT_CO_DATABLOCK_V1(afxT3DSpotLightData); + +ConsoleDocClass( afxT3DSpotLightData, + "@brief A datablock that specifies a dynamic Spot Light effect.\n\n" + + "A Spot Light effect that uses the T3D SpotLight object. afxT3DSpotLightData has the same fields found in SpotLight but in " + "a datablock structure." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxT3DSpotLightData::afxT3DSpotLightData() + : mRange( 10.0f ), + mInnerConeAngle( 40.0f ), + mOuterConeAngle( 45.0f ) +{ +} + +afxT3DSpotLightData::afxT3DSpotLightData(const afxT3DSpotLightData& other, bool temp_clone) : afxT3DLightBaseData(other, temp_clone) +{ + mRange = other.mRange; + mInnerConeAngle = other.mInnerConeAngle; + mOuterConeAngle = other.mOuterConeAngle; +} + +// +// NOTE: keep this as consistent as possible with PointLight::initPersistFields() +// +void afxT3DSpotLightData::initPersistFields() +{ + addGroup( "Light" ); + + addField( "range", TypeF32, Offset( mRange, afxT3DSpotLightData ), + "..."); + addField( "innerAngle", TypeF32, Offset( mInnerConeAngle, afxT3DSpotLightData ), + "..."); + addField( "outerAngle", TypeF32, Offset( mOuterConeAngle, afxT3DSpotLightData ), + "..."); + + endGroup( "Light" ); + + // We do the parent fields at the end so that + // they show up that way in the inspector. + Parent::initPersistFields(); +} + +bool afxT3DSpotLightData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxT3DSpotLightData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write( mRange ); + stream->write( mInnerConeAngle ); + stream->write( mOuterConeAngle ); +} + +void afxT3DSpotLightData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read( &mRange ); + stream->read( &mInnerConeAngle ); + stream->read( &mOuterConeAngle ); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxSpotLight_T3D.h b/Engine/source/afx/ce/afxSpotLight_T3D.h new file mode 100644 index 000000000..077850784 --- /dev/null +++ b/Engine/source/afx/ce/afxSpotLight_T3D.h @@ -0,0 +1,58 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_T3D_SPOT_LIGHT_H_ +#define _AFX_T3D_SPOT_LIGHT_H_ + +#include "afx/ce/afxLightBase_T3D.h" + +class afxT3DSpotLightData : public afxT3DLightBaseData +{ + typedef afxT3DLightBaseData Parent; + +public: + F32 mRange; + F32 mInnerConeAngle; + F32 mOuterConeAngle; + +public: + /*C*/ afxT3DSpotLightData(); + /*C*/ afxT3DSpotLightData(const afxT3DSpotLightData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxT3DSpotLightData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_T3D_SPOT_LIGHT_H_ diff --git a/Engine/source/afx/ce/afxStaticShape.cpp b/Engine/source/afx/ce/afxStaticShape.cpp new file mode 100644 index 000000000..5eb4fb815 --- /dev/null +++ b/Engine/source/afx/ce/afxStaticShape.cpp @@ -0,0 +1,241 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "ts/tsShapeInstance.h" + +#include "afx/afxChoreographer.h" +#include "afx/ce/afxStaticShape.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxStaticShapeData + +IMPLEMENT_CO_DATABLOCK_V1(afxStaticShapeData); + +ConsoleDocClass( afxStaticShapeData, + "@brief A datablock that specifies a StaticShape effect.\n\n" + + "afxStaticShapeData inherits from StaticShapeData and adds some AFX specific fields. StaticShape effects should be " + "specified using afxStaticShapeData rather than StaticShapeData datablocks." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxStaticShapeData::afxStaticShapeData() +{ + sequence = ST_NULLSTRING; + ignore_scene_amb = false; + use_custom_scene_amb = false; + custom_scene_amb.set(0.5f, 0.5f, 0.5f); + do_spawn = false; +} + +afxStaticShapeData::afxStaticShapeData(const afxStaticShapeData& other, bool temp_clone) : StaticShapeData(other, temp_clone) +{ + sequence = other.sequence; + ignore_scene_amb = other.ignore_scene_amb; + use_custom_scene_amb = other.use_custom_scene_amb; + custom_scene_amb = other.custom_scene_amb; + do_spawn = other.do_spawn; +} + +#define myOffset(field) Offset(field, afxStaticShapeData) + +void afxStaticShapeData::initPersistFields() +{ + addField("sequence", TypeFilename, myOffset(sequence), + "An animation sequence in the StaticShape to play."); + addField("ignoreSceneAmbient", TypeBool, myOffset(ignore_scene_amb), + "..."); + addField("useCustomSceneAmbient", TypeBool, myOffset(use_custom_scene_amb), + "..."); + addField("customSceneAmbient", TypeColorF, myOffset(custom_scene_amb), + "..."); + addField("doSpawn", TypeBool, myOffset(do_spawn), + "When true, the StaticShape effect will leave behind the StaticShape object as a " + "permanent part of the scene."); + + Parent::initPersistFields(); +} + +void afxStaticShapeData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(sequence); + stream->writeFlag(ignore_scene_amb); + if (stream->writeFlag(use_custom_scene_amb)) + stream->write(custom_scene_amb); + stream->writeFlag(do_spawn); +} + +void afxStaticShapeData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + sequence = stream->readSTString(); + ignore_scene_amb = stream->readFlag(); + if ((use_custom_scene_amb = stream->readFlag()) == true) + stream->read(&custom_scene_amb); + do_spawn = stream->readFlag(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxStaticShape + +IMPLEMENT_CO_NETOBJECT_V1(afxStaticShape); + +ConsoleDocClass( afxStaticShape, + "@brief A StaticShape effect as defined by an afxStaticShapeData datablock.\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" +); + +afxStaticShape::afxStaticShape() +{ + afx_data = 0; + is_visible = true; + chor_id = 0; + hookup_with_chor = false; + ghost_cons_name = ST_NULLSTRING; +} + +afxStaticShape::~afxStaticShape() +{ +} + +void afxStaticShape::init(U32 chor_id, StringTableEntry cons_name) +{ + this->chor_id = chor_id; + ghost_cons_name = cons_name; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +bool afxStaticShape::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + afx_data = dynamic_cast(mDataBlock); + + if (!mShapeInstance) + return true; + + const char* seq_name = 0; + + // if datablock is afxStaticShapeData we get the sequence setting + // directly from the datablock on the client-side only + if (afx_data) + { + if (isClientObject()) + seq_name = afx_data->sequence; + } + // otherwise datablock is stock StaticShapeData and we look for + // a sequence name on a dynamic field on the server. + else + { + if (isServerObject()) + seq_name = mDataBlock->getDataField(StringTable->insert("sequence"),0); + } + + // if we have a sequence name, attempt to start a thread + if (seq_name) + { + TSShape* shape = mShapeInstance->getShape(); + if (shape) + { + S32 seq = shape->findSequence(seq_name); + if (seq != -1) + setThreadSequence(0,seq); + } + } + + return true; +} + +void afxStaticShape::advanceTime(F32 dt) +{ + Parent::advanceTime(dt); + + if (hookup_with_chor) + { + afxChoreographer* chor = arcaneFX::findClientChoreographer(chor_id); + if (chor) + { + chor->setGhostConstraintObject(this, ghost_cons_name); + hookup_with_chor = false; + } + } +} + +U32 afxStaticShape::packUpdate(NetConnection* conn, U32 mask, BitStream* stream) +{ + U32 retMask = Parent::packUpdate(conn, mask, stream); + + // InitialUpdate + if (stream->writeFlag(mask & InitialUpdateMask)) + { + stream->write(chor_id); + stream->writeString(ghost_cons_name); + } + + return retMask; +} + +//~~~~~~~~~~~~~~~~~~~~// + +void afxStaticShape::unpackUpdate(NetConnection * conn, BitStream * stream) +{ + Parent::unpackUpdate(conn, stream); + + // InitialUpdate + if (stream->readFlag()) + { + stream->read(&chor_id); + ghost_cons_name = stream->readSTString(); + + if (chor_id != 0 && ghost_cons_name != ST_NULLSTRING) + hookup_with_chor = true; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +void afxStaticShape::prepRenderImage(SceneRenderState* state) +{ + if (is_visible) + Parent::prepRenderImage(state); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxStaticShape.h b/Engine/source/afx/ce/afxStaticShape.h new file mode 100644 index 000000000..ed84e0774 --- /dev/null +++ b/Engine/source/afx/ce/afxStaticShape.h @@ -0,0 +1,98 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_STATIC_SHAPE_H_ +#define _AFX_STATIC_SHAPE_H_ + +#include "T3D/staticShape.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxStaticShapeData + +class afxStaticShapeData : public StaticShapeData +{ + typedef StaticShapeData Parent; + +public: + StringTableEntry sequence; + bool ignore_scene_amb; + bool use_custom_scene_amb; + LinearColorF custom_scene_amb; + bool do_spawn; + +public: + /*C*/ afxStaticShapeData(); + /*C*/ afxStaticShapeData(const afxStaticShapeData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxStaticShapeData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxStaticShape + +class afxStaticShape : public StaticShape +{ + typedef StaticShape Parent; + +private: + StaticShapeData* mDataBlock; + afxStaticShapeData* afx_data; + bool is_visible; + U32 chor_id; + bool hookup_with_chor; + StringTableEntry ghost_cons_name; + +protected: + virtual void prepRenderImage(SceneRenderState*); + +public: + /*C*/ afxStaticShape(); + /*D*/ ~afxStaticShape(); + + void init(U32 chor_id, StringTableEntry cons_name); + + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + virtual void advanceTime(F32 dt); + virtual U32 packUpdate(NetConnection*, U32, BitStream*); + virtual void unpackUpdate(NetConnection*, BitStream*); + + const char* getShapeFileName() const { return mDataBlock->shapeName; } + void setVisibility(bool flag) { is_visible = flag; } + + DECLARE_CONOBJECT(afxStaticShape); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_STATIC_SHAPE_H_ diff --git a/Engine/source/afx/ce/afxVolumeLight.cpp b/Engine/source/afx/ce/afxVolumeLight.cpp new file mode 100644 index 000000000..ace374935 --- /dev/null +++ b/Engine/source/afx/ce/afxVolumeLight.cpp @@ -0,0 +1,40 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/ce/afxVolumeLight.h" + +IMPLEMENT_CO_DATABLOCK_V1(afxVolumeLightData); + +ConsoleDocClass( afxVolumeLightData, + "@brief afxVolumeLightData is a legacy datablock which is not supported for T3D.\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxVolumeLight.h b/Engine/source/afx/ce/afxVolumeLight.h new file mode 100644 index 000000000..9b56c4f27 --- /dev/null +++ b/Engine/source/afx/ce/afxVolumeLight.h @@ -0,0 +1,38 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_VOLUME_LIGHT_H_ +#define _AFX_VOLUME_LIGHT_H_ + +struct afxVolumeLightData : public GameBaseData +{ + typedef GameBaseData Parent; + DECLARE_CONOBJECT(afxVolumeLightData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_VOLUME_LIGHT_H_ diff --git a/Engine/source/afx/ce/afxZodiac.cpp b/Engine/source/afx/ce/afxZodiac.cpp new file mode 100644 index 000000000..3b1e976d2 --- /dev/null +++ b/Engine/source/afx/ce/afxZodiac.cpp @@ -0,0 +1,418 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "core/frameAllocator.h" +#include "terrain/terrRender.h" +#include "gfx/primBuilder.h" + +#include "afx/ce/afxZodiac.h" + +GFX_ImplementTextureProfile(AFX_GFXZodiacTextureProfile, + GFXTextureProfile::DiffuseMap, + GFXTextureProfile::Static | GFXTextureProfile::NoMipmap | GFXTextureProfile::PreserveSize, + GFXTextureProfile::NONE); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxZodiacData::convertGradientRangeFromDegrees(Point2F& gradrange, const Point2F& gradrange_deg) +{ + F32 x = mCos(mDegToRad(gradrange_deg.x)); + F32 y = mCos(mDegToRad(gradrange_deg.y)); + if (y > x) + gradrange.set(x, y); + else + gradrange.set(y, x); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxZodiacData + +IMPLEMENT_CO_DATABLOCK_V1(afxZodiacData); + +ConsoleDocClass( afxZodiacData, + "@brief A datablock that specifies a decal-like Zodiac effect.\n\n" + + "Zodiacs are special-purpose decal textures, often circular, that are always projected vertically onto the ground. Parameters " + "control dynamic rotation and scale as well as texture, color, and blending style." + "\n\n" + + "Zodiacs render on objects of type TerrainBlock, InteriorInstance, GroundPlane, MeshRoad, and TSStatic. They are very " + "effective as spellcasting lighting rings, explosion shockwaves, scorched earth decals, and selection indicators." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +StringTableEntry afxZodiacData::GradientRangeSlot; +bool afxZodiacData::sPreferDestinationGradients = false; + +afxZodiacData::afxZodiacData() +{ + txr_name = ST_NULLSTRING; + radius_xy = 1; + vert_range.set(0.0f, 0.0f); + start_ang = 0; + ang_per_sec = 0; + color.set(1,1,1,1); + grow_in_time = 0.0f; + shrink_out_time = 0.0f; + growth_rate = 0.0f; + blend_flags = BLEND_NORMAL; + terrain_ok = true; + interiors_ok = true; + reflected_ok = false; + non_reflected_ok = true; + respect_ori_cons = false; + scale_vert_range = true; + + interior_h_only = false; + interior_v_ignore = false; + interior_back_ignore = false; + interior_opaque_ignore = false; + interior_transp_ignore = true; + + altitude_max = 0.0f; + altitude_falloff = 0.0f; + altitude_shrinks = false; + altitude_fades = false; + + distance_max = 75.0f; + distance_falloff = 30.0f; + + use_grade_range = false; + prefer_dest_grade = sPreferDestinationGradients; + grade_range_user.set(0.0f, 45.0f); + afxZodiacData::convertGradientRangeFromDegrees(grade_range, grade_range_user); + inv_grade_range = false; +} + +afxZodiacData::afxZodiacData(const afxZodiacData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + txr_name = other.txr_name; + txr = other.txr; + radius_xy = other.radius_xy; + vert_range = other.vert_range; + start_ang = other.start_ang; + ang_per_sec = other.ang_per_sec; + grow_in_time = other.grow_in_time; + shrink_out_time = other.shrink_out_time; + growth_rate = other.growth_rate; + color = other.color; + + altitude_max = other.altitude_max; + altitude_falloff = other.altitude_falloff; + altitude_shrinks = other.altitude_shrinks; + altitude_fades = other.altitude_fades; + + distance_max = other.distance_max; + distance_falloff = other.distance_falloff; + + use_grade_range = other.use_grade_range; + prefer_dest_grade = other.prefer_dest_grade; + grade_range = other.grade_range; + inv_grade_range = other.inv_grade_range; + + zflags = other.zflags; + expand_zflags(); +} + +ImplementEnumType( afxZodiac_BlendType, "Possible zodiac blend types.\n" "@ingroup afxZodiac\n\n" ) + { afxZodiacData::BLEND_NORMAL, "normal", "..." }, + { afxZodiacData::BLEND_ADDITIVE, "additive", "..." }, + { afxZodiacData::BLEND_SUBTRACTIVE, "subtractive", "..." }, +EndImplementEnumType; + +void afxZodiacData::initPersistFields() +{ + addField("texture", TypeFilename, Offset(txr_name, afxZodiacData), + "An image to use as the zodiac's texture."); + addField("radius", TypeF32, Offset(radius_xy, afxZodiacData), + "The zodiac's radius in scene units."); + addField("verticalRange", TypePoint2F, Offset(vert_range, afxZodiacData), + "For interior zodiacs only, verticalRange specifies distances above and below the " + "zodiac's position. If both values are 0.0, the radius is used."); + addField("scaleVerticalRange", TypeBool, Offset(scale_vert_range, afxZodiacData), + "Specifies if the zodiac's verticalRange should scale according to changes in the " + "radius. When a zodiacs is used as an expanding shockwave, this value should be set " + "to false, otherwise the zodiac can expand to cover an entire interior."); + addField("startAngle", TypeF32, Offset(start_ang, afxZodiacData), + "The starting angle in degrees of the zodiac's rotation."); + addField("rotationRate", TypeF32, Offset(ang_per_sec, afxZodiacData), + "The rate of rotation in degrees-per-second. Zodiacs with a positive rotationRate " + "rotate clockwise, while those with negative values turn counter-clockwise."); + addField("growInTime", TypeF32, Offset(grow_in_time, afxZodiacData), + "A duration of time in seconds over which the zodiac grows from a zero size to its " + "full size as specified by the radius."); + addField("shrinkOutTime", TypeF32, Offset(shrink_out_time, afxZodiacData), + "A duration of time in seconds over which the zodiac shrinks from full size to " + "invisible."); + addField("growthRate", TypeF32, Offset(growth_rate, afxZodiacData), + "A rate in meters-per-second at which the zodiac grows in size. A negative value will " + "shrink the zodiac."); + addField("color", TypeColorF, Offset(color, afxZodiacData), + "A color value for the zodiac."); + + addField("blend", TYPEID(), Offset(blend_flags, afxZodiacData), + "A blending style for the zodiac. Possible values: normal, additive, or subtractive."); + + addField("showOnTerrain", TypeBool, Offset(terrain_ok, afxZodiacData), + "Specifies if the zodiac should be rendered on terrain or terrain-like surfaces."); + addField("showOnInteriors", TypeBool, Offset(interiors_ok, afxZodiacData), + "Specifies if the zodiac should be rendered on interior or interior-like surfaces."); + addField("showInReflections", TypeBool, Offset(reflected_ok, afxZodiacData), + "Specifies if the zodiac should be rendered on the reflection rendering pass of the " + "object it will be projected onto."); + addField("showInNonReflections", TypeBool, Offset(non_reflected_ok, afxZodiacData), + "Specifies if the zodiac should be rendered on the non-reflection rendering pass of " + "the object it will be projected onto."); + addField("trackOrientConstraint", TypeBool, Offset(respect_ori_cons, afxZodiacData), + "Specifies if the zodiac's rotation should be defined by its constrained " + "transformation."); + + addField("interiorHorizontalOnly", TypeBool, Offset(interior_h_only, afxZodiacData), + "Specifies if interior zodiacs should be rendered exclusively on perfectly horizontal " + "interior surfaces."); + addField("interiorIgnoreVertical", TypeBool, Offset(interior_v_ignore, afxZodiacData), + "Specifies if interior zodiacs should not be rendered on perfectly vertical interior " + "surfaces."); + addField("interiorIgnoreBackfaces", TypeBool, Offset(interior_back_ignore, afxZodiacData), + "Specifies if interior zodiacs should not be rendered on interior surface which are " + "backfacing to the zodiac's center."); + addField("interiorIgnoreOpaque", TypeBool, Offset(interior_opaque_ignore, afxZodiacData), + ""); + addField("interiorIgnoreTransparent", TypeBool, Offset(interior_transp_ignore, afxZodiacData), + ""); + + addField("altitudeMax", TypeF32, Offset(altitude_max, afxZodiacData), + "The altitude at which zodiac becomes invisible as the result of fading out or " + "becoming too small."); + addField("altitudeFalloff", TypeF32, Offset(altitude_falloff, afxZodiacData), + "The altitude at which zodiac begins to fade and/or shrink."); + addField("altitudeShrinks", TypeBool, Offset(altitude_shrinks, afxZodiacData), + "When true, zodiac becomes smaller as altitude increases."); + addField("altitudeFades", TypeBool, Offset(altitude_fades, afxZodiacData), + "When true, zodiac fades out as altitude increases."); + + addField("distanceMax", TypeF32, Offset(distance_max, afxZodiacData), + "The distance from camera at which the zodiac becomes invisible as the result of " + "fading out."); + addField("distanceFalloff", TypeF32, Offset(distance_falloff, afxZodiacData), + "The distance from camera at which the zodiac begins to fade out."); + + addField("useGradientRange", TypeBool, Offset(use_grade_range, afxZodiacData), + "When true, gradientRange will be used to determine on which polygons the zodiac will " + "render."); + addField("preferDestinationGradients", TypeBool, Offset(prefer_dest_grade, afxZodiacData), + "When true, a gradientRange specified on an InteriorInstance or TSStatic will be used " + "instead of the zodiac's gradientRange."); + addField("gradientRange", TypePoint2F, Offset(grade_range_user, afxZodiacData), + "Zodiac will render on polygons with gradients within the range specified by " + "gradientRange. 0 for floor polys, 90 for wall polys, 180 for ceiling polys."); + addField("invertGradientRange", TypeBool, Offset(inv_grade_range, afxZodiacData), + "When true, the zodiac will render on polygons with gradients outside of the range " + "specified by gradientRange."); + + Parent::initPersistFields(); + + GradientRangeSlot = StringTable->lookup("gradientRange"); + Con::addVariable("pref::afxZodiac::preferDestinationGradients", TypeBool, &sPreferDestinationGradients); +} + +bool afxZodiacData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + if (altitude_falloff > altitude_max) + altitude_falloff = altitude_max; + + if (distance_falloff > distance_max) + distance_falloff = distance_max; + + return true; +} + +void afxZodiacData::packData(BitStream* stream) +{ + Parent::packData(stream); + + merge_zflags(); + + stream->writeString(txr_name); + stream->write(radius_xy); + stream->write(vert_range.x); + stream->write(vert_range.y); + stream->write(grade_range.x); + stream->write(grade_range.y); + stream->write(start_ang); + stream->write(ang_per_sec); + stream->write(grow_in_time); + stream->write(shrink_out_time); + stream->write(growth_rate); + stream->write(color); + stream->write(zflags); + stream->write(altitude_max); + stream->write(altitude_falloff); + stream->writeFlag(altitude_shrinks); + stream->writeFlag(altitude_fades); + stream->write(distance_max); + stream->write(distance_falloff); +} + +void afxZodiacData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + txr_name = stream->readSTString(); + txr = GFXTexHandle(); + stream->read(&radius_xy); + stream->read(&vert_range.x); + stream->read(&vert_range.y); + stream->read(&grade_range.x); + stream->read(&grade_range.y); + stream->read(&start_ang); + stream->read(&ang_per_sec); + stream->read(&grow_in_time); + stream->read(&shrink_out_time); + stream->read(&growth_rate); + stream->read(&color); + stream->read(&zflags); + stream->read(&altitude_max); + stream->read(&altitude_falloff); + altitude_shrinks = stream->readFlag(); + altitude_fades = stream->readFlag(); + stream->read(&distance_max); + stream->read(&distance_falloff); + + expand_zflags(); +} + +bool afxZodiacData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + if (!server) + { + if (txr_name && txr_name[0] != '\0') + { + txr.set(txr_name, &AFX_GFXZodiacTextureProfile, "Zodiac Texture"); + } + } + + if (vert_range.x == 0.0f && vert_range.y == 0.0f) + vert_range.x = vert_range.y = radius_xy; + + return true; +} + +void afxZodiacData::onStaticModified(const char* slot, const char* newValue) +{ + Parent::onStaticModified(slot, newValue); + if (slot == GradientRangeSlot) + { + convertGradientRangeFromDegrees(grade_range, grade_range_user); + return; + } + merge_zflags(); +} + +void afxZodiacData::onPerformSubstitutions() +{ + if (txr_name && txr_name[0] != '\0') + { + txr.set(txr_name, &AFX_GFXZodiacTextureProfile, "Zodiac Texture"); + } +} + +F32 afxZodiacData::calcRotationAngle(F32 elapsed, F32 rate_factor) +{ + F32 angle = start_ang + elapsed*ang_per_sec*rate_factor; + angle = mFmod(angle, 360.0f); + + return angle; +} + +void afxZodiacData::expand_zflags() +{ + blend_flags = (zflags & BLEND_MASK); + terrain_ok = ((zflags & SHOW_ON_TERRAIN) != 0); + interiors_ok = ((zflags & SHOW_ON_INTERIORS) != 0); + reflected_ok = ((zflags & SHOW_IN_REFLECTIONS) != 0); + non_reflected_ok = ((zflags & SHOW_IN_NON_REFLECTIONS) != 0); + respect_ori_cons = ((zflags & RESPECT_ORIENTATION) != 0); + scale_vert_range = ((zflags & SCALE_VERT_RANGE) != 0); + interior_h_only = ((zflags & INTERIOR_HORIZ_ONLY) != 0); + interior_v_ignore = ((zflags & INTERIOR_VERT_IGNORE) != 0); + interior_back_ignore = ((zflags & INTERIOR_BACK_IGNORE) != 0); + interior_opaque_ignore = ((zflags & INTERIOR_OPAQUE_IGNORE) != 0); + interior_transp_ignore = ((zflags & INTERIOR_TRANSP_IGNORE) != 0); + use_grade_range = ((zflags & USE_GRADE_RANGE) != 0); + prefer_dest_grade = ((zflags & PREFER_DEST_GRADE) != 0); + inv_grade_range = ((zflags & INVERT_GRADE_RANGE) != 0); +} + +void afxZodiacData::merge_zflags() +{ + zflags = (blend_flags & BLEND_MASK); + if (terrain_ok) + zflags |= SHOW_ON_TERRAIN; + if (interiors_ok) + zflags |= SHOW_ON_INTERIORS; + if (reflected_ok) + zflags |= SHOW_IN_REFLECTIONS; + if (non_reflected_ok) + zflags |= SHOW_IN_NON_REFLECTIONS; + if (respect_ori_cons) + zflags |= RESPECT_ORIENTATION; + if (scale_vert_range) + zflags |= SCALE_VERT_RANGE; + if (interior_h_only) + zflags |= INTERIOR_HORIZ_ONLY; + if (interior_v_ignore) + zflags |= INTERIOR_VERT_IGNORE; + if (interior_back_ignore) + zflags |= INTERIOR_BACK_IGNORE; + if (interior_opaque_ignore) + zflags |= INTERIOR_OPAQUE_IGNORE; + if (interior_transp_ignore) + zflags |= INTERIOR_TRANSP_IGNORE; + if (use_grade_range) + zflags |= USE_GRADE_RANGE; + if (prefer_dest_grade) + zflags |= PREFER_DEST_GRADE; + if (inv_grade_range) + zflags |= INVERT_GRADE_RANGE; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxZodiac.h b/Engine/source/afx/ce/afxZodiac.h new file mode 100644 index 000000000..c6adb7625 --- /dev/null +++ b/Engine/source/afx/ce/afxZodiac.h @@ -0,0 +1,132 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ZODIAC_H_ +#define _AFX_ZODIAC_H_ + +#ifndef _ARCANE_FX_H_ +#include "afx/arcaneFX.h" +#endif +#ifndef _AFX_ZODIAC_DEFS_H_ +#include "afx/ce/afxZodiacDefs.h" +#endif + +#include "console/typeValidators.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxZodiacData + +class afxZodiacData : public GameBaseData, public afxZodiacDefs +{ + typedef GameBaseData Parent; + +public: + enum BlendType + { + BLEND_NORMAL = 0x0, + BLEND_ADDITIVE = 0x1, + BLEND_SUBTRACTIVE = 0x2, + BLEND_RESERVED = 0x3, + BLEND_MASK = 0x3 + }; + + static void convertGradientRangeFromDegrees(Point2F& gradrange, const Point2F& gradrange_deg); + +public: + StringTableEntry txr_name; + GFXTexHandle txr; + F32 radius_xy; + Point2F vert_range; + F32 start_ang; + F32 ang_per_sec; + F32 grow_in_time; + F32 shrink_out_time; + F32 growth_rate; + LinearColorF color; + U32 blend_flags; + bool terrain_ok; + bool interiors_ok; + bool reflected_ok; + bool non_reflected_ok; + bool respect_ori_cons; + bool scale_vert_range; + bool interior_h_only; + bool interior_v_ignore; + bool interior_back_ignore; + bool interior_opaque_ignore; + bool interior_transp_ignore; + U32 zflags; + + F32 altitude_max; + F32 altitude_falloff; + bool altitude_shrinks; + bool altitude_fades; + + F32 distance_max; + F32 distance_falloff; + + bool use_grade_range; + bool prefer_dest_grade; + Point2F grade_range_user; + Point2F grade_range; + bool inv_grade_range; + + static StringTableEntry GradientRangeSlot; + static bool sPreferDestinationGradients; + + void expand_zflags(); + void merge_zflags(); + +public: + /*C*/ afxZodiacData(); + /*C*/ afxZodiacData(const afxZodiacData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + virtual void onStaticModified(const char* slotName, const char* newValue = NULL); + + virtual void onPerformSubstitutions(); + virtual bool allowSubstitutions() const { return true; } + + F32 calcRotationAngle(F32 elapsed, F32 rate_factor=1.0f); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxZodiacData); + DECLARE_CATEGORY("AFX"); +}; + +typedef afxZodiacData::BlendType afxZodiac_BlendType; +DefineEnumType( afxZodiac_BlendType ); + +GFX_DeclareTextureProfile(AFX_GFXZodiacTextureProfile); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ZODIAC_H_ diff --git a/Engine/source/afx/ce/afxZodiacDefs.h b/Engine/source/afx/ce/afxZodiacDefs.h new file mode 100644 index 000000000..1222ca69e --- /dev/null +++ b/Engine/source/afx/ce/afxZodiacDefs.h @@ -0,0 +1,163 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ZODIAC_DEFS_H_ +#define _AFX_ZODIAC_DEFS_H_ + +class afxZodiacDefs +{ +public: + enum + { + MAX_ZODIACS = 256, + N_ZODIAC_FIELD_INTS = (MAX_ZODIACS - 1) / 32 + 1, + }; + + enum + { + BLEND_NORMAL = 0x0, + BLEND_ADDITIVE = 0x1, + BLEND_SUBTRACTIVE = 0x2, + BLEND_RESERVED = 0x3, + BLEND_MASK = 0x3 + }; + + enum + { + SHOW_ON_TERRAIN = BIT(2), + SHOW_ON_INTERIORS = BIT(3), + SHOW_ON_WATER = BIT(4), + SHOW_ON_MODELS = BIT(5), + // + SHOW_IN_NON_REFLECTIONS = BIT(6), + SHOW_IN_REFLECTIONS = BIT(7), + // + RESPECT_ORIENTATION = BIT(8), + SCALE_VERT_RANGE = BIT(9), + INVERT_GRADE_RANGE = BIT(10), + USE_GRADE_RANGE = BIT(11), + PREFER_DEST_GRADE = BIT(12), + // + INTERIOR_VERT_IGNORE = BIT(13), + INTERIOR_HORIZ_ONLY = BIT(14), + INTERIOR_BACK_IGNORE = BIT(15), + INTERIOR_OPAQUE_IGNORE = BIT(16), + INTERIOR_TRANSP_IGNORE = BIT(17), + // + INTERIOR_FILTERS = INTERIOR_VERT_IGNORE | INTERIOR_HORIZ_ONLY | INTERIOR_BACK_IGNORE + }; +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxZodiacBitmask : public afxZodiacDefs +{ + U32 mBits[N_ZODIAC_FIELD_INTS]; + +public: + afxZodiacBitmask() { clear(); } + afxZodiacBitmask(const afxZodiacBitmask& field) { *this = field; } + + bool test(U32 index) + { + U32 word = index / 32; + U32 bit = index % 32; + return mBits[word] & (1 << bit); + } + + void set(U32 index) + { + U32 word = index / 32; + U32 bit = index % 32; + mBits[word] |= 1 << bit; + } + + void unset(U32 index) + { + U32 word = index / 32; + U32 bit = index % 32; + mBits[word] &= ~(1 << bit); + } + + S32 findLastSetBit(U32 startbit=(MAX_ZODIACS-1)) + { + U32 word = startbit / 32; + U32 bit = startbit % 32; + + if (mBits[word] != 0) + { + U32 mask = mBits[word] << (31-bit); + for (U32 j = bit; j >= 0; j--) + { + if (mask & 0x80000000) + return word*32 + j; + mask <<= 1; + } + } + + for (U32 k = word-1; k >= 0; k--) + { + if (mBits[k] != 0) + { + U32 mask = mBits[k]; + for (U32 j = 31; j >= 0; j--) + { + if (mask & 0x80000000) + return k*32 + j; + mask <<= 1; + } + } + } + + return -1; + } + + void clear() + { + for (U32 k = 0; k < N_ZODIAC_FIELD_INTS; k++) + mBits[k] = 0x00000000; + } + + bool isEmpty() + { + for (U32 k = 0; k < N_ZODIAC_FIELD_INTS; k++) + if (mBits[k] != 0) + return false; + return true; + } + + afxZodiacBitmask& operator=(const afxZodiacBitmask& field) + { + for (U32 k = 0; k < N_ZODIAC_FIELD_INTS; k++) + mBits[k] = field.mBits[k]; + return *this; + } + + operator bool() { return !isEmpty(); } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ZODIAC_DEFS_H_ diff --git a/Engine/source/afx/ce/afxZodiacMgr.cpp b/Engine/source/afx/ce/afxZodiacMgr.cpp new file mode 100644 index 000000000..abc8c38e2 --- /dev/null +++ b/Engine/source/afx/ce/afxZodiacMgr.cpp @@ -0,0 +1,131 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "terrain/terrRender.h" + +#include "afx/ce/afxZodiac.h" +#include "afx/ce/afxZodiacMgr.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxZodiacMgr + +Vector afxZodiacMgr::terr_zodes; +Vector afxZodiacMgr::inter_zodes; + +afxZodiacMgr::ZodiacTriangle* afxZodiacMgr::zode_tris_head = NULL; +afxZodiacMgr::ZodiacTriangle* afxZodiacMgr::zode_tris_tail = NULL; +afxZodiacMgr::ZodiacTriangle* afxZodiacMgr::zode_tris = NULL; +U32 afxZodiacMgr::zode_tris_idx = 0; +U32 afxZodiacMgr::n_zode_tris = 0; + +afxZodiacMgr::ZodiacSpec* afxZodiacMgr::live_zodiac = 0; +ShaderData* afxZodiacMgr::terrain_zode_shader = 0; +ShaderData* afxZodiacMgr::atlas_zode_shader = 0; +ShaderData* afxZodiacMgr::interior_zode_shader = 0; +ShaderData* afxZodiacMgr::polysoup_zode_shader = 0; + +void afxZodiacMgr::addTerrainZodiac(Point3F& pos, F32 radius, LinearColorF& color, F32 angle, afxZodiacData* zode) +{ + if (radius < 0.001f) + return; + + ZodiacSpec z; + z.pos = pos; + z.radius_xy = radius; + z.vert_range.set(0.0f, 0.0f); + z.grade_range.set(0.0f, 1.0f); + z.color = color.toColorI(); + z.angle = mDegToRad(angle); + z.zflags = zode->zflags; + z.txr = &zode->txr; + + z.distance_max = zode->distance_max*zode->distance_max; + z.distance_falloff = zode->distance_falloff*zode->distance_falloff; + z.distance_delta = z.distance_max - z.distance_falloff; + + if (terr_zodes.size() < MAX_ZODIACS) + terr_zodes.push_back(z); +} + +void afxZodiacMgr::addInteriorZodiac(Point3F& pos, F32 radius, Point2F& vert_range, LinearColorF& color, F32 angle, afxZodiacData* zode) +{ + if (radius < 0.001f) + return; + + ZodiacSpec z; + z.pos = pos; + z.radius_xy = radius; + z.vert_range = vert_range; + z.grade_range = zode->grade_range; + z.color = color.toColorI(); + z.angle = mDegToRad(angle); + z.zflags = zode->zflags; + z.txr = &zode->txr; + + z.distance_max = zode->distance_max*zode->distance_max; + z.distance_falloff = zode->distance_falloff*zode->distance_falloff; + z.distance_delta = z.distance_max - z.distance_falloff; + + if (inter_zodes.size() < MAX_ZODIACS) + inter_zodes.push_back(z); +} + +void afxZodiacMgr::frameReset() +{ + terr_zodes.clear(); + inter_zodes.clear(); +} + +void afxZodiacMgr::missionCleanup() +{ + terrain_zode_shader = 0; + atlas_zode_shader = 0; + interior_zode_shader = 0; + polysoup_zode_shader = 0; +} + +// REGULAR TERRAIN ZODIACS // + +void afxZodiacMgr::transformTerrainZodiacs(const MatrixF& world_xfm) +{ + VectorF facing_vec; + world_xfm.getColumn(1, &facing_vec); + F32 yaw = mAtan2(facing_vec.x, facing_vec.y); + while (yaw < 0.0) yaw += M_2PI_F; + + for (S32 i = 0; i < terr_zodes.size(); i++) + { + world_xfm.mulP(terr_zodes[i].pos, &terr_zodes[i].loc_pos); + F32 ang = terr_zodes[i].angle + yaw; + terr_zodes[i].loc_cos_ang = mCos(ang); + terr_zodes[i].loc_sin_ang = mSin(ang); + } + + zode_tris_head = zode_tris_tail = NULL; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ce/afxZodiacMgr.h b/Engine/source/afx/ce/afxZodiacMgr.h new file mode 100644 index 000000000..0141b3ed4 --- /dev/null +++ b/Engine/source/afx/ce/afxZodiacMgr.h @@ -0,0 +1,150 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ZODIAC_MGR_H_ +#define _AFX_ZODIAC_MGR_H_ + +#ifndef _ARCANE_FX_H_ +#include "afx/arcaneFX.h" +#endif +#ifndef _AFX_ZODIAC_DEFS_H_ +#include "afx/ce/afxZodiacDefs.h" +#endif +#ifndef _AFX_ZODIAC_H_ +#include "afx/ce/afxZodiac.h" +#endif + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +struct GridSquare; + +class ShaderData; +class TSStatic; + +class GroundPlane; +class MeshRoad; +class TerrainBlock; +class TerrCell; + +class afxZodiacMgr : public afxZodiacDefs +{ + friend class afxZodiacTerrainRenderer; + friend class afxZodiacPolysoupRenderer; + friend class afxZodiacGroundPlaneRenderer; + friend class afxZodiacMeshRoadRenderer; + + friend struct TerrainRender; + +private: + struct ZodiacSpec + { + Point3F pos; //12// world position + F32 radius_xy; // 4// radius of zodiac + Point2F vert_range; // 8// vertical range + Point2F grade_range; // 8// plane gradient range + ColorI color; // 4// color of zodiac + F32 angle; // 4// angle in radians + U32 zflags; // 4// 0=normal,1=additive,2=subtractive + GFXTexHandle* txr; // 4// zodiac texture + + F32 distance_max; + F32 distance_falloff; + F32 distance_delta; + + Point3F loc_pos; //12// transformed to local position + F32 loc_cos_ang; // 4// cosine of local rotation angle + F32 loc_sin_ang; // 4// sine of local rotation angle + + F32 calcDistanceFadeBias(F32 camDist) const + { + if (camDist < distance_falloff) + return 1.0f; + if (camDist < distance_max) + return (1.0f - (camDist - distance_falloff)/distance_delta); + return 0.0f; + } + }; + + struct ZodiacTriangle + { + ColorI color; + Point2F texco1; + Point3F point1; + Point2F texco2; + Point3F point2; + Point2F texco3; + Point3F point3; + U32 zflags; // 0=normal,1=additive,2=subtractive + GFXTexHandle* txr; + ZodiacTriangle* next; + }; + + static Vector terr_zodes; + static Vector inter_zodes; + + static ZodiacTriangle* zode_tris_head; + static ZodiacTriangle* zode_tris_tail; + static ZodiacTriangle* zode_tris; + static U32 zode_tris_idx; + static U32 n_zode_tris; + +public: + static ZodiacSpec* live_zodiac; +private: + static ShaderData* terrain_zode_shader; + static ShaderData* atlas_zode_shader; + static ShaderData* interior_zode_shader; + static ShaderData* polysoup_zode_shader; +public: + static void addTerrainZodiac(Point3F& pos, F32 rad, LinearColorF&, F32 ang, afxZodiacData*); + static void addInteriorZodiac(Point3F& pos, F32 rad, Point2F& vrange, LinearColorF&, F32 ang, afxZodiacData*); + static void frameReset(); + static void missionCleanup(); + + static S32 numTerrainZodiacs() { return terr_zodes.size(); } + static S32 numInteriorZodiacs() { return inter_zodes.size(); } + + static void transformTerrainZodiacs(const MatrixF& world_xfm); + static void testTerrainOverlap(GridSquare*, S32 level, Point2I sq_pos, afxZodiacBitmask&); + + static bool doesBoxOverlapZodiac(const Box3F& box, const ZodiacSpec& zode); + static bool doesBlockContainZodiacs(SceneRenderState*, TerrainBlock* block_); + + static bool renderTerrainZodiacs(SceneRenderState*, TerrainBlock*, TerrCell*); + static ShaderData* getTerrainZodiacShader(); + + static void renderPolysoupZodiacs(SceneRenderState*, TSStatic*); + static ShaderData* getPolysoupZodiacShader(); + + static void renderGroundPlaneZodiacs(SceneRenderState*, GroundPlane*); + static ShaderData* getGroundPlaneZodiacShader(); + + static void renderMeshRoadZodiacs(SceneRenderState*, MeshRoad*); + static ShaderData* getMeshRoadZodiacShader(); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ZODIAC_MGR_H_ diff --git a/Engine/source/afx/ce/afxZodiacMgr_T3D.cpp b/Engine/source/afx/ce/afxZodiacMgr_T3D.cpp new file mode 100644 index 000000000..f1b7f8ade --- /dev/null +++ b/Engine/source/afx/ce/afxZodiacMgr_T3D.cpp @@ -0,0 +1,319 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "scene/sceneRenderState.h" +#include "materials/shaderData.h" +#include "core/frameAllocator.h" +#include "terrain/terrRender.h" +#include "T3D/tsStatic.h" +#include "T3D/groundPlane.h" +#include "environment/meshRoad.h" +#include "collision/concretePolyList.h" +#include "gfx/primBuilder.h" +#include "terrain/terrCell.h" + +#include "afx/ce/afxZodiac.h" +#include "afx/ce/afxZodiacMgr.h" +#include "afx/afxZodiacTerrainRenderer_T3D.h" +#include "afx/afxZodiacGroundPlaneRenderer_T3D.h" +#include "afx/afxZodiacMeshRoadRenderer_T3D.h" +#include "afx/afxZodiacPolysoupRenderer_T3D.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +// POLYSOUP INTERIOR ZODIACS // + +void afxZodiacMgr::renderPolysoupZodiacs(SceneRenderState* state, TSStatic* tss) +{ + // check for active interior zodiacs + S32 n_zodes = inter_zodes.size(); + if (n_zodes <= 0) + return; + + static SphereF dummy_sphere; + const Box3F& mWorldBox = tss->getWorldBox(); + const Point3F& cam_pos = state->getCameraPosition(); + + // loop through the interior zodiacs + for (U32 i = 0; i < n_zodes; i++) + { + // calculate zodiac extents + F32 radius = inter_zodes[i].radius_xy; + Box3F box_w; box_w.minExtents = box_w.maxExtents = inter_zodes[i].pos; + box_w.minExtents -= Point3F(radius, radius, inter_zodes[i].vert_range.x); + box_w.maxExtents += Point3F(radius, radius, inter_zodes[i].vert_range.y); + + // skip zodiacs not overlapping this object + if (mWorldBox.isOverlapped(box_w) == false) + continue; + + // collect list of zodiac-intersecting polygons + ConcretePolyList* poly_list = new ConcretePolyList(); + ((SceneObject*)tss)->buildPolyList(PLC_Decal, poly_list, box_w, dummy_sphere); + + // render the polys if we get any + if (!poly_list->mPolyList.empty()) + { + // calculate zodiac distance from camera + Point3F cam_vec = cam_pos - inter_zodes[i].pos; + F32 cam_dist = cam_vec.lenSquared(); + + // render zodiacs + afxZodiacPolysoupRenderer::getMaster()->addZodiac(i, poly_list, inter_zodes[i].pos, inter_zodes[i].angle, tss, cam_dist); + // note: poly_list will be deleted by render-manager after rendering is done. + } + else + { + delete poly_list; // avoids crash when overlapping box but not polygons + } + } +} + +ShaderData* afxZodiacMgr::getPolysoupZodiacShader() +{ + if (!polysoup_zode_shader) + { + if( !Sim::findObject("afxZodiacPolysoupShader", polysoup_zode_shader)) + { + Con::errorf("afxZodiacMgr::getPolysoupZodiacShader() - failed to find shader 'afxZodiacPolysoupShader'."); + return 0; + } + } + + return polysoup_zode_shader; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +bool afxZodiacMgr::doesBoxOverlapZodiac(const Box3F& box, const afxZodiacMgr::ZodiacSpec& zode) +{ + if ((zode.pos.x - zode.radius_xy) > box.maxExtents.x || + (zode.pos.y - zode.radius_xy) > box.maxExtents.y) + return false; + if ((zode.pos.x + zode.radius_xy) < box.minExtents.x || + ( zode.pos.y + zode.radius_xy) < box.minExtents.y) + return false; + return true; +} + +static U32 last_terr_zode_idx = 0; + +bool afxZodiacMgr::doesBlockContainZodiacs(SceneRenderState* state, TerrainBlock* block) +{ + // for now, only look at diffuse-passes + if (!state->isDiffusePass()) + return false; + + // check for active terrain zodiacs + S32 n_zodes = terr_zodes.size(); + if (n_zodes <= 0) + return false; + + const Box3F& block_box = block->getWorldBox(); + + last_terr_zode_idx = 0; + for (U32 i = 0; i < n_zodes; i++) + { + if (doesBoxOverlapZodiac(block_box, terr_zodes[i])) + { + last_terr_zode_idx = i; + return true; + } + } + + return false; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +bool afxZodiacMgr::renderTerrainZodiacs(SceneRenderState* state, TerrainBlock* block, TerrCell* cell) +{ + // we assume here that afxZodiacMgr::doesBlockContainZodiacs() was recently called to + // determine that at least one zodiac intersects the block and its index is last_terr_zode_idx. + + bool cell_has_zodiacs = false; + + const Point3F& cam_pos = state->getCameraPosition(); + const Box3F& block_box = block->getWorldBox(); + + S32 n_zodes = terr_zodes.size(); + for (U32 i = last_terr_zode_idx; i < n_zodes; i++) + { + // first test against block's box + if (!doesBoxOverlapZodiac(block_box, terr_zodes[i])) + continue; + + const MatrixF& mRenderObjToWorld = block->getRenderTransform(); + Box3F cell_box = cell->getBounds(); + mRenderObjToWorld.mul(cell_box); + if (!doesBoxOverlapZodiac(cell_box, terr_zodes[i])) + continue; + + // at this point we know the zodiac overlaps AABB of cell... + + // calculate zodiac distance from camera + Point3F cam_vec = cam_pos - terr_zodes[i].pos; + F32 cam_dist = cam_vec.lenSquared(); + //if (cam_dist > 2500) + // continue; + + // render zodiacs + afxZodiacTerrainRenderer::getMaster()->addZodiac(i, terr_zodes[i].pos, terr_zodes[i].angle, block, cell, mRenderObjToWorld, cam_dist); + + cell_has_zodiacs = true; + } + + last_terr_zode_idx = 0; + + return cell_has_zodiacs; +} + +ShaderData* afxZodiacMgr::getTerrainZodiacShader() +{ + if (!terrain_zode_shader) + { + if (!Sim::findObject("afxZodiacTerrainShader", terrain_zode_shader)) + { + Con::errorf("afxZodiacMgr::getTerrainZodiacShader() - failed to find shader 'afxZodiacTerrainShader'."); + return 0; + } + } + + return terrain_zode_shader; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxZodiacMgr::renderGroundPlaneZodiacs(SceneRenderState* state, GroundPlane* ground_plane) +{ + // check for active terrain zodiacs + S32 n_zodes = terr_zodes.size(); + if (n_zodes <= 0) + return; + + // we currently expect gound-plane to be infinite + if (!ground_plane->isGlobalBounds()) + { + return; + } + + static SphereF dummy_sphere; + static Box3F dummy_box; + + const Point3F& cam_pos = state->getCameraPosition(); + + // loop through the terrain zodiacs + for (U32 i = 0; i < n_zodes; i++) + { + // calculate zodiac distance from camera + Point3F cam_vec = cam_pos - terr_zodes[i].pos; + F32 cam_dist = cam_vec.lenSquared(); + + // render zodiacs + afxZodiacGroundPlaneRenderer::getMaster()->addZodiac(i, terr_zodes[i].pos, terr_zodes[i].angle, ground_plane, cam_dist); + } +} + +ShaderData* afxZodiacMgr::getGroundPlaneZodiacShader() +{ + if (!terrain_zode_shader) + { + if (!Sim::findObject("afxZodiacTerrainShader", terrain_zode_shader)) + { + Con::errorf("afxZodiacMgr::getTerrainZodiacShader() - failed to find shader 'afxZodiacTerrainShader'."); + return 0; + } + } + + return terrain_zode_shader; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxZodiacMgr::renderMeshRoadZodiacs(SceneRenderState* state, MeshRoad* mesh_road) +{ + // check for active terrain zodiacs + S32 n_zodes = terr_zodes.size(); + if (n_zodes <= 0) + return; + + const Box3F& mWorldBox = mesh_road->getWorldBox(); + const Point3F& cam_pos = state->getCameraPosition(); + + // loop through the terrain zodiacs + for (U32 i = 0; i < n_zodes; i++) + { + // calculate zodiac extents + F32 radius = terr_zodes[i].radius_xy; + Box3F box_w; box_w.minExtents = box_w.maxExtents = terr_zodes[i].pos; + box_w.minExtents -= Point3F(radius, radius, terr_zodes[i].vert_range.x); + box_w.maxExtents += Point3F(radius, radius, terr_zodes[i].vert_range.y); + + // skip zodiacs not overlapping this object + if (mWorldBox.isOverlapped(box_w) == false) + continue; + + // collect list of zodiac-intersecting polygons + ConcretePolyList* poly_list = new ConcretePolyList(); + + mesh_road->buildTopPolyList(PLC_Decal, poly_list); + + // render the polys if we get any + if (!poly_list->mPolyList.empty()) + { + // calculate zodiac distance from camera + Point3F cam_vec = cam_pos - terr_zodes[i].pos; + F32 cam_dist = cam_vec.lenSquared(); + + // render zodiacs + afxZodiacMeshRoadRenderer::getMaster()->addZodiac(i, poly_list, terr_zodes[i].pos, terr_zodes[i].angle, mesh_road, cam_dist); + // note: poly_list will be deleted by render-manager after rendering is done. + } + else + { + delete poly_list; // avoids crash when overlapping box but not polygons + } + } +} + +ShaderData* afxZodiacMgr::getMeshRoadZodiacShader() +{ + if (!terrain_zode_shader) + { + if (!Sim::findObject("afxZodiacTerrainShader", terrain_zode_shader)) + { + Con::errorf("afxZodiacMgr::getTerrainZodiacShader() - failed to find shader 'afxZodiacTerrainShader'."); + return 0; + } + } + + return terrain_zode_shader; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxZodiacPlane.cpp b/Engine/source/afx/ce/afxZodiacPlane.cpp new file mode 100644 index 000000000..2a4740298 --- /dev/null +++ b/Engine/source/afx/ce/afxZodiacPlane.cpp @@ -0,0 +1,313 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "renderInstance/renderPassManager.h" + +#include "afx/afxChoreographer.h" +#include "afx/ce/afxZodiac.h" +#include "afx/ce/afxZodiacPlane.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxZodiacPlaneData + +IMPLEMENT_CO_DATABLOCK_V1(afxZodiacPlaneData); + +ConsoleDocClass( afxZodiacPlaneData, + "@brief A datablock that specifies a Zodiac Plane effect.\n\n" + + "afxZodiacData describes a zodiac-like effect called a zodiac plane. It reproduces most of the behavior of normal zodiacs " + "but unlike zodiac decals, it is represented as a flat plane of geometry that can be more flexibly positioned and oriented." + "\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxZodiacPlaneData::afxZodiacPlaneData() +{ + txr_name = ST_NULLSTRING; + radius_xy = 1; + start_ang = 0; + ang_per_sec = 0; + grow_in_time = 0.0f; + shrink_out_time = 0.0f; + growth_rate = 0.0f; + color.set(1,1,1,1); + blend_flags = BLEND_NORMAL; + respect_ori_cons = false; + + double_sided = true; + face_dir = FACES_UP; + use_full_xfm = false; +} + +afxZodiacPlaneData::afxZodiacPlaneData(const afxZodiacPlaneData& other, bool temp_clone) + : GameBaseData(other, temp_clone) +{ + txr_name = other.txr_name; + txr = other.txr; + radius_xy = other.radius_xy; + start_ang = other.start_ang; + ang_per_sec = other.ang_per_sec; + grow_in_time = other.grow_in_time; + shrink_out_time = other.shrink_out_time; + growth_rate = other.growth_rate; + color = other.color; + + double_sided = other.double_sided; + face_dir = other.face_dir; + use_full_xfm = other.use_full_xfm; + + zflags = other.zflags; + expand_zflags(); +} + +ImplementEnumType( afxZodiacPlane_BlendType, "Possible zodiac blend types.\n" "@ingroup afxZodiacPlane\n\n" ) + { afxZodiacData::BLEND_NORMAL, "normal", "..." }, + { afxZodiacData::BLEND_ADDITIVE, "additive", "..." }, + { afxZodiacData::BLEND_SUBTRACTIVE, "subtractive", "..." }, +EndImplementEnumType; + +ImplementEnumType( afxZodiacPlane_FacingType, "Possible zodiac plane facing types.\n" "@ingroup afxZodiacPlane\n\n" ) + { afxZodiacPlaneData::FACES_UP, "up", "..." }, + { afxZodiacPlaneData::FACES_DOWN, "down", "..." }, + { afxZodiacPlaneData::FACES_FORWARD, "forward", "..." }, + { afxZodiacPlaneData::FACES_BACK, "backward", "..." }, + { afxZodiacPlaneData::FACES_RIGHT, "right", "..." }, + { afxZodiacPlaneData::FACES_LEFT, "left", "..." }, + + { afxZodiacPlaneData::FACES_FORWARD, "front", "..." }, + { afxZodiacPlaneData::FACES_BACK, "back", "..." }, +EndImplementEnumType; + +#define myOffset(field) Offset(field, afxZodiacPlaneData) + +void afxZodiacPlaneData::initPersistFields() +{ + addField("texture", TypeFilename, myOffset(txr_name), + "An image to use as the zodiac's texture."); + addField("radius", TypeF32, myOffset(radius_xy), + "The zodiac's radius in scene units."); + addField("startAngle", TypeF32, myOffset(start_ang), + "The starting angle in degrees of the zodiac's rotation."); + addField("rotationRate", TypeF32, myOffset(ang_per_sec), + "The rate of rotation in degrees-per-second. Zodiacs with a positive rotationRate " + "rotate clockwise, while those with negative values turn counter-clockwise."); + addField("growInTime", TypeF32, myOffset(grow_in_time), + "A duration of time in seconds over which the zodiac grows from a zero size to its " + "full size as specified by the radius."); + addField("shrinkOutTime", TypeF32, myOffset(shrink_out_time), + "A duration of time in seconds over which the zodiac shrinks from full size to " + "invisible."); + addField("growthRate", TypeF32, myOffset(growth_rate), + "A rate in meters-per-second at which the zodiac grows in size. A negative value will " + "shrink the zodiac."); + addField("color", TypeColorF, myOffset(color), + "A color value for the zodiac."); + + addField("blend", TYPEID(), myOffset(blend_flags), + "A blending style for the zodiac. Possible values: normal, additive, or subtractive."); + + addField("trackOrientConstraint", TypeBool, myOffset(respect_ori_cons), + "Specifies if the zodiac's rotation should be defined by its constrained " + "transformation."); + + addField("doubleSided", TypeBool, myOffset(double_sided), + "Controls whether the zodiac-plane's polygons are rendered when viewed from either " + "side. If set to false, the zodiac-plane will only be seen when viewed from the " + "direction it is facing (according to faceDir)."); + + addField("faceDir", TYPEID(), myOffset(face_dir), + "Specifies which direction the zodiac-plane's polygons face. Possible values: " + "up, down, front, back, right, or left."); + + addField("useFullTransform", TypeBool, myOffset(use_full_xfm), + "Normal zodiacs have only one degree of freedom, a rotation around the z-axis. " + "Depending on the setting for trackOrientConstraint, this means that the effect's " + "orientation is either ignored or is limited to influencing the zodiac's angle of " + "rotation. By default, zodiac-plane reproduces this limited behavior in order to " + "match normal zodiacs. When useFullTransform is set to true, the zodiac can be " + "arbitrarily oriented."); + + Parent::initPersistFields(); +} + +void afxZodiacPlaneData::packData(BitStream* stream) +{ + Parent::packData(stream); + + merge_zflags(); + + stream->writeString(txr_name); + stream->write(radius_xy); + stream->write(start_ang); + stream->write(ang_per_sec); + stream->write(grow_in_time); + stream->write(shrink_out_time); + stream->write(growth_rate); + stream->write(color); + stream->write(zflags); + stream->write(double_sided); + stream->writeFlag(use_full_xfm); + stream->writeInt(face_dir, FACES_BITS); +} + +void afxZodiacPlaneData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + txr_name = stream->readSTString(); + txr = GFXTexHandle(); + stream->read(&radius_xy); + stream->read(&start_ang); + stream->read(&ang_per_sec); + stream->read(&grow_in_time); + stream->read(&shrink_out_time); + stream->read(&growth_rate); + stream->read(&color); + stream->read(&zflags); + stream->read(&double_sided); + use_full_xfm = stream->readFlag(); + face_dir = stream->readInt(FACES_BITS); + + expand_zflags(); +} + +bool afxZodiacPlaneData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + if (!server) + { + if (txr_name && txr_name[0] != '\0') + { + txr.set(txr_name, &AFX_GFXZodiacTextureProfile, "Zodiac Texture"); + } + } + + return true; +} + +F32 afxZodiacPlaneData::calcRotationAngle(F32 elapsed, F32 rate_factor) +{ + F32 angle = start_ang + elapsed*ang_per_sec*rate_factor; + angle = mFmod(angle, 360.0f); + + return angle; +} + +void afxZodiacPlaneData::expand_zflags() +{ + blend_flags = (zflags & BLEND_MASK); + respect_ori_cons = ((zflags & RESPECT_ORIENTATION) != 0); +} + +void afxZodiacPlaneData::merge_zflags() +{ + zflags = (blend_flags & BLEND_MASK); + if (respect_ori_cons) + zflags |= RESPECT_ORIENTATION; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxZodiacPlane + +IMPLEMENT_CO_NETOBJECT_V1(afxZodiacPlane); + +ConsoleDocClass( afxZodiacPlane, + "@brief A ZodiacPlane effect as defined by an afxZodiacPlaneData datablock.\n\n" + + "@ingroup afxEffects\n" + "@ingroup AFX\n" +); + +afxZodiacPlane::afxZodiacPlane() +{ + mNetFlags.clear(); + mNetFlags.set(IsGhost); + + mDataBlock = 0; + color.set(1,1,1,1); + radius = 1; + is_visible = true; +} + +afxZodiacPlane::~afxZodiacPlane() +{ +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +bool afxZodiacPlane::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + return true; +} + +bool afxZodiacPlane::onAdd() +{ + if(!Parent::onAdd()) + return false; + + F32 len = mDataBlock->radius_xy; + + switch (mDataBlock->face_dir) + { + case afxZodiacPlaneData::FACES_UP: + case afxZodiacPlaneData::FACES_DOWN: + mObjBox = Box3F(Point3F(-len, -len, -0.01f), Point3F(len, len, 0.01f)); + break; + case afxZodiacPlaneData::FACES_FORWARD: + case afxZodiacPlaneData::FACES_BACK: + mObjBox = Box3F(Point3F(-len, -0.01f, -len), Point3F(len, 0.01f, len)); + break; + case afxZodiacPlaneData::FACES_RIGHT: + case afxZodiacPlaneData::FACES_LEFT: + mObjBox = Box3F(Point3F(-0.01f, -len, -len), Point3F(0.01f, len, len)); + break; + } + + addToScene(); + + return true; +} + +void afxZodiacPlane::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ce/afxZodiacPlane.h b/Engine/source/afx/ce/afxZodiacPlane.h new file mode 100644 index 000000000..05b7ad56f --- /dev/null +++ b/Engine/source/afx/ce/afxZodiacPlane.h @@ -0,0 +1,143 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ZODIAC_PLANE_H_ +#define _AFX_ZODIAC_PLANE_H_ + +#include "afx/ce/afxZodiacDefs.h" + +class afxZodiacPlaneData : public GameBaseData, public afxZodiacDefs +{ + typedef GameBaseData Parent; + +public: + enum BlendType + { + BLEND_NORMAL = 0x0, + BLEND_ADDITIVE = 0x1, + BLEND_SUBTRACTIVE = 0x2, + BLEND_RESERVED = 0x3, + BLEND_MASK = 0x3 + }; + enum FacingType + { + FACES_UP = 0, + FACES_DOWN, + FACES_FORWARD, + FACES_BACK, + FACES_RIGHT, + FACES_LEFT, + FACES_BITS = 3 + }; + +public: + StringTableEntry txr_name; + GFXTexHandle txr; + F32 radius_xy; + F32 start_ang; + F32 ang_per_sec; + F32 grow_in_time; + F32 shrink_out_time; + F32 growth_rate; + LinearColorF color; + U32 blend_flags; + bool respect_ori_cons; + bool use_full_xfm; + U32 zflags; + U32 face_dir; + + bool double_sided; + + void expand_zflags(); + void merge_zflags(); + +public: + /*C*/ afxZodiacPlaneData(); + /*C*/ afxZodiacPlaneData(const afxZodiacPlaneData&, bool = false); + + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + virtual bool allowSubstitutions() const { return true; } + + F32 calcRotationAngle(F32 elapsed, F32 rate_factor=1.0f); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxZodiacPlaneData); + DECLARE_CATEGORY("AFX"); +}; + +typedef afxZodiacPlaneData::BlendType afxZodiacPlane_BlendType; +DefineEnumType( afxZodiacPlane_BlendType ); + +typedef afxZodiacPlaneData::FacingType afxZodiacPlane_FacingType; +DefineEnumType( afxZodiacPlane_FacingType ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxZodiacPlane + +class afxZodiacPlane : public GameBase, public afxZodiacDefs +{ + typedef GameBase Parent; + +private: + afxZodiacPlaneData* mDataBlock; + LinearColorF color; + F32 radius; + bool is_visible; + + void preDraw(); + void draw(); + void postDraw(); + + GFXStateBlockRef normal_sb; + GFXStateBlockRef reflected_sb; + +public: + /*C*/ afxZodiacPlane(); + /*D*/ ~afxZodiacPlane(); + + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + virtual bool onAdd(); + virtual void onRemove(); + + void setRadius(F32 rad) { radius = rad; } + void setColor(const LinearColorF& clr) { color = clr; } + void setVisibility(bool flag) { is_visible = flag; } + + virtual void prepRenderImage(SceneRenderState*); + + void _renderZodiacPlane(ObjectRenderInst*, SceneRenderState*, BaseMatInstance*); + + DECLARE_CONOBJECT(afxZodiacPlane); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ZODIAC_PLANE_H_ diff --git a/Engine/source/afx/ce/afxZodiacPlane_T3D.cpp b/Engine/source/afx/ce/afxZodiacPlane_T3D.cpp new file mode 100644 index 000000000..2bf0bcfbc --- /dev/null +++ b/Engine/source/afx/ce/afxZodiacPlane_T3D.cpp @@ -0,0 +1,229 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "gfx/gfxTransformSaver.h" +#include "gfx/primBuilder.h" + +#include "afx/afxChoreographer.h" +#include "afx/ce/afxZodiacPlane.h" + +void afxZodiacPlane::prepRenderImage(SceneRenderState* state) +{ + if (!is_visible) + return; + + ObjectRenderInst *ri = state->getRenderPass()->allocInst(); + ri->renderDelegate.bind(this, &afxZodiacPlane::_renderZodiacPlane); + ri->type = RenderPassManager::RIT_ObjectTranslucent; + ri->translucentSort = true; + ri->defaultKey = (U32)(dsize_t)mDataBlock; + + if (false) + { + ri->sortDistSq = getWorldBox().getSqDistanceToPoint( state->getCameraPosition() ); + } + else // (sort by radius distance) + { + Point3F xyz_scale = getScale(); + F32 uni_scalar = getMax(xyz_scale.x, xyz_scale.y); + uni_scalar = getMax(uni_scalar, xyz_scale.z); + Point3F uni_scale(uni_scalar, uni_scalar, uni_scalar); + + Point3F local_cam_pos = state->getCameraPosition(); + getRenderWorldTransform().mulP(local_cam_pos); + local_cam_pos.convolveInverse(uni_scale); + + switch (mDataBlock->face_dir) + { + case afxZodiacPlaneData::FACES_UP: + case afxZodiacPlaneData::FACES_DOWN: + local_cam_pos.z = 0; + break; + case afxZodiacPlaneData::FACES_FORWARD: + case afxZodiacPlaneData::FACES_BACK: + local_cam_pos.y = 0; + break; + case afxZodiacPlaneData::FACES_RIGHT: + case afxZodiacPlaneData::FACES_LEFT: + local_cam_pos.x = 0; + break; + } + + /* AFX_T3D_BROKEN -- enhanced transparency sorting of ZodiacPlanes JTF Note: evaluate this + if (local_cam_pos.lenSquared() <= radius*radius) + { + ri->sortPoint = local_cam_pos; + } + else + { + local_cam_pos.normalize(); + ri->sortPoint = local_cam_pos*radius; + } + + ri->sortPoint.convolve(uni_scale); + getRenderTransform().mulP(ri->sortPoint); + */ + } + state->getRenderPass()->addInst(ri); +} + +void afxZodiacPlane::_renderZodiacPlane(ObjectRenderInst *ri, SceneRenderState* state, BaseMatInstance* overrideMat) +{ + if (overrideMat) + return; + + // projection + + // predraw + if (normal_sb.isNull()) + { + GFXStateBlockDesc desc; + + // Culling + desc.setCullMode((mDataBlock->double_sided) ? GFXCullNone : GFXCullCW); + + // Blending + U32 blend = (mDataBlock->zflags & BLEND_MASK); + switch (blend) + { + case BLEND_ADDITIVE: + desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendOne); + break; + case BLEND_SUBTRACTIVE: + desc.setBlend(true, GFXBlendZero, GFXBlendInvSrcColor); + break; + case BLEND_NORMAL: + desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha); + break; + } + + // JTF Note: check this desc.setAlphaTest((blend != BLEND_SUBTRACTIVE), GFXCmpGreater, 0); + desc.setAlphaTest(true, GFXCmpGreater, 0); + + desc.setZReadWrite(true); + desc.zFunc = GFXCmpLessEqual; + desc.zWriteEnable = false; + desc.samplersDefined = true; + desc.samplers[0].textureColorOp = GFXTOPModulate; + desc.samplers[1].textureColorOp = GFXTOPDisable; + + normal_sb = GFX->createStateBlock(desc); + if (mDataBlock->double_sided) + reflected_sb = normal_sb; + else + { + desc.setCullMode(GFXCullCCW); + reflected_sb = GFX->createStateBlock(desc); + } + } + + if (state->isReflectPass()) + GFX->setStateBlock(reflected_sb); + else + GFX->setStateBlock(normal_sb); + + Point3F basePoints[4]; + + switch (mDataBlock->face_dir) + { + case afxZodiacPlaneData::FACES_UP: + basePoints[0].set( 0.5f, -0.5f, 0.0f); + basePoints[1].set(-0.5f, -0.5f, 0.0f); + basePoints[2].set(-0.5f, 0.5f, 0.0f); + basePoints[3].set( 0.5f, 0.5f, 0.0f); + break; + case afxZodiacPlaneData::FACES_DOWN: + basePoints[3].set(-0.5f, 0.5f, 0.0f); + basePoints[2].set( 0.5f, 0.5f, 0.0f); + basePoints[1].set( 0.5f, -0.5f, 0.0f); + basePoints[0].set(-0.5f, -0.5f, 0.0f); + break; + case afxZodiacPlaneData::FACES_FORWARD: + basePoints[0].set( 0.5f, 0.0f, -0.5f); + basePoints[1].set(-0.5f, 0.0f, -0.5f); + basePoints[2].set(-0.5f, 0.0f, 0.5f); + basePoints[3].set( 0.5f, 0.0f, 0.5f); + break; + case afxZodiacPlaneData::FACES_BACK: + basePoints[3].set(-0.5f, 0.0f, 0.5f); + basePoints[2].set( 0.5f, 0.0f, 0.5f); + basePoints[1].set( 0.5f, 0.0f, -0.5f); + basePoints[0].set(-0.5f, 0.0f, -0.5f); + break; + case afxZodiacPlaneData::FACES_RIGHT: + basePoints[0].set(0.0f, 0.5f, -0.5f); + basePoints[1].set(0.0f, -0.5f, -0.5f); + basePoints[2].set(0.0f, -0.5f, 0.5f); + basePoints[3].set(0.0f, 0.5f, 0.5f); + break; + case afxZodiacPlaneData::FACES_LEFT: + basePoints[3].set(0.0f, -0.5f, 0.5f); + basePoints[2].set(0.0f, 0.5f, 0.5f); + basePoints[1].set(0.0f, 0.5f, -0.5f); + basePoints[0].set(0.0f, -0.5f, -0.5f); + break; + } + + F32 len = 2*radius; + + Point3F points[4]; + + Point2F texCoords[4]; // default: {{0.0,0.0}, {0.0,1.0}, {1.0,1.0}, {1.0,0.0}} + texCoords[0].set(1.0,1.0); + texCoords[1].set(0.0,1.0); + texCoords[2].set(0.0,0.0); + texCoords[3].set(1.0,0.0); + + for( int i=0; i<4; i++ ) + { + points[i].x = basePoints[i].x; + points[i].y = basePoints[i].y; + points[i].z = basePoints[i].z; + points[i] *= len; + } + + GFXTransformSaver saver; + GFX->multWorld(getRenderTransform()); + + GFX->setTexture(0, mDataBlock->txr); + + PrimBuild::begin(GFXTriangleStrip, 4); + { + PrimBuild::color4f(color.red, color.green, color.blue, color.alpha); + PrimBuild::texCoord2f(texCoords[1].x, texCoords[1].y); + PrimBuild::vertex3f(points[1].x, points[1].y, points[1].z); + PrimBuild::texCoord2f(texCoords[0].x, texCoords[0].y); + PrimBuild::vertex3f(points[0].x, points[0].y, points[0].z); + PrimBuild::texCoord2f(texCoords[2].x, texCoords[2].y); + PrimBuild::vertex3f(points[2].x, points[2].y, points[2].z); + PrimBuild::texCoord2f(texCoords[3].x, texCoords[3].y); + PrimBuild::vertex3f(points[3].x, points[3].y, points[3].z); + } + PrimBuild::end(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ea/afxEA_AnimClip.cpp b/Engine/source/afx/ea/afxEA_AnimClip.cpp new file mode 100644 index 000000000..4e635a1db --- /dev/null +++ b/Engine/source/afx/ea/afxEA_AnimClip.cpp @@ -0,0 +1,200 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxAnimClip.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_AnimClip + +class afxEA_AnimClip : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxAnimClipData* clip_data; + bool started; + F32 anim_lifetime; + U32 anim_tag; + U32 lock_tag; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_AnimClip(); + /*C*/ ~afxEA_AnimClip(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_AnimClip::afxEA_AnimClip() +{ + clip_data = 0; + started = false; + anim_lifetime = 0; + anim_tag = 0; + lock_tag = 0; +} + +afxEA_AnimClip::~afxEA_AnimClip() +{ + if (clip_data && clip_data->isTempClone()) + delete clip_data; + clip_data = 0; +} + +void afxEA_AnimClip::ea_set_datablock(SimDataBlock* db) +{ + clip_data = dynamic_cast(db); +} + +bool afxEA_AnimClip::ea_start() +{ + if (!clip_data) + { + Con::errorf("afxEA_AnimClip::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + afxConstraint* pos_constraint = getPosConstraint(); + if (full_lifetime == INFINITE_LIFETIME && pos_constraint != 0) + anim_lifetime = pos_constraint->getAnimClipDuration(clip_data->clip_name); + else + anim_lifetime = full_lifetime; + + anim_tag = 0; + lock_tag = 0; + + return true; +} + +bool afxEA_AnimClip::ea_update(F32 dt) +{ + afxConstraint* pos_constraint = getPosConstraint(); + if (!started && pos_constraint != 0) + { + bool go_for_it = true; + + if (pos_constraint->getDamageState() == ShapeBase::Enabled) + go_for_it = !clip_data->ignore_enabled; + else if (pos_constraint->getDamageState() == ShapeBase::Disabled) + go_for_it = !clip_data->ignore_disabled; + + if (go_for_it && (clip_data->ignore_first_person || clip_data->ignore_third_person)) + { + ShapeBase* shape = dynamic_cast(pos_constraint->getSceneObject()); + if (shape) + { + bool is_first_person = shape->isFirstPerson(); + if (clip_data->ignore_first_person && is_first_person) + go_for_it = false; + if (clip_data->ignore_third_person && !is_first_person) + go_for_it = false; + } + } + + if (go_for_it) + { + F32 rate = clip_data->rate/prop_time_factor; + F32 pos = mFmod(life_elapsed, anim_lifetime)/anim_lifetime; + pos = mFmod(pos + clip_data->pos_offset, 1.0); + if (clip_data->rate < 0) + pos = 1.0f - pos; + anim_tag = pos_constraint->setAnimClip(clip_data->clip_name, pos, rate, clip_data->trans, + clip_data->is_death_anim); + if (clip_data->lock_anim) + lock_tag = pos_constraint->lockAnimation(); + } + started = true; + } + + return true; +} + +void afxEA_AnimClip::ea_finish(bool was_stopped) +{ + afxConstraint* pos_constraint = getPosConstraint(); + if (pos_constraint && anim_tag != 0) + { + pos_constraint->resetAnimation(anim_tag); + } + if (pos_constraint && lock_tag != 0) + pos_constraint->unlockAnimation(lock_tag); + + started = false; +} + +void afxEA_AnimClip::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (clip_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxAnimClipData* orig_db = clip_data; + clip_data = new afxAnimClipData(*orig_db, true); + orig_db->performSubstitutions(clip_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_AnimClipDesc + +class afxEA_AnimClipDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_AnimClipDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + virtual bool isPositional(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_AnimClip; } +}; + +afxEA_AnimClipDesc afxEA_AnimClipDesc::desc; + +bool afxEA_AnimClipDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxAnimClipData) == typeid(*db)); +} + +bool afxEA_AnimClipDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_AnimLock.cpp b/Engine/source/afx/ea/afxEA_AnimLock.cpp new file mode 100644 index 000000000..3f6fd5597 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_AnimLock.cpp @@ -0,0 +1,108 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/ce/afxAnimLock.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_AnimLock + +class afxEA_AnimLock : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + bool started; + U32 lock_tag; + +public: + /*C*/ afxEA_AnimLock(); + + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_AnimLock::afxEA_AnimLock() +{ + started = false; + lock_tag = 0; +} + +bool afxEA_AnimLock::ea_update(F32 dt) +{ + afxConstraint* pos_constraint = getPosConstraint(); + if (!started && pos_constraint != 0) + { + lock_tag = pos_constraint->lockAnimation(); + started = true; + } + + return true; +} + +void afxEA_AnimLock::ea_finish(bool was_stopped) +{ + afxConstraint* pos_constraint = getPosConstraint(); + if (pos_constraint && lock_tag != 0) + { + pos_constraint->unlockAnimation(lock_tag); + } + + started = false; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_AnimLockDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_AnimLockDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + virtual bool isPositional(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_AnimLock; } +}; + +afxEA_AnimLockDesc afxEA_AnimLockDesc::desc; + +bool afxEA_AnimLockDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxAnimLockData) == typeid(*db)); +} + +bool afxEA_AnimLockDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_AreaDamage.cpp b/Engine/source/afx/ea/afxEA_AreaDamage.cpp new file mode 100644 index 000000000..3c5d26aae --- /dev/null +++ b/Engine/source/afx/ea/afxEA_AreaDamage.cpp @@ -0,0 +1,264 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxAreaDamage.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_AreaDamage + +class afxEA_AreaDamage : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxAreaDamageData* damage_data; + Point3F impact_pos; + bool damage_is_done; + SceneObject* cons_obj; + + void do_runtime_substitutions(); + void deal_area_damage(); + void apply_damage(ShapeBase*, F32 damage, const char* flavor, Point3F& pos); + void apply_impulse(ShapeBase*, F32 impulse, Point3F& pos); + void notify_damage_source(ShapeBase* damaged, F32 damage, const char* flavor, Point3F& pos); + +public: + /*C*/ afxEA_AreaDamage(); + /*C*/ ~afxEA_AreaDamage(); + + virtual bool isDone(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_AreaDamage::afxEA_AreaDamage() +{ + damage_data = 0; + impact_pos.zero(); + damage_is_done = false; + cons_obj = 0; +} + +afxEA_AreaDamage::~afxEA_AreaDamage() +{ + if (damage_data && damage_data->isTempClone()) + delete damage_data; + damage_data = 0; +} + +bool afxEA_AreaDamage::isDone() +{ + return damage_is_done; +} + +void afxEA_AreaDamage::ea_set_datablock(SimDataBlock* db) +{ + damage_data = dynamic_cast(db); +} + +bool afxEA_AreaDamage::ea_start() +{ + if (!damage_data) + { + Con::errorf("afxEA_AreaDamage::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + return true; +} + +bool afxEA_AreaDamage::ea_update(F32 dt) +{ + if (!damage_is_done) + { + afxConstraint* pos_cons = getPosConstraint(); + if (pos_cons) + { + pos_cons->getPosition(impact_pos); + cons_obj = pos_cons->getSceneObject(); + } + + deal_area_damage(); + + damage_is_done = true; + } + + return true; +} + +void afxEA_AreaDamage::ea_finish(bool was_stopped) +{ + damage_is_done = false; +} + +void afxEA_AreaDamage::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (damage_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxAreaDamageData* orig_db = damage_data; + damage_data = new afxAreaDamageData(*orig_db, true); + orig_db->performSubstitutions(damage_data, choreographer, group_index); + } +} + +// radiusDamage(%sourceObject, %position, %radius, %damage, %damageType, %impulse, %excluded) + +void afxEA_AreaDamage::deal_area_damage() +{ + // initContainerRadiusSearch -- afterwards Container::mSearchList contains objects within radius sorted by distance + gServerContainer.initRadiusSearch(impact_pos, damage_data->radius, ShapeBaseObjectType); + + F32 halfradius = damage_data->radius*0.5f; + + const Vector*>& list = gServerContainer.getRadiusSearchList(); + for (S32 i = 0; i < list.size(); i++) + { + if (!list[i]->isNull()) + { + ShapeBase* shape = dynamic_cast((SceneObject*)(*list[i])); + if (!shape || (shape->getTypeMask() & CameraObjectType)) + continue; + + if (damage_data->exclude_cons_obj && cons_obj == *list[i]) + continue; + +#if 0 // AFX_T3D_DISABLED -- calcExplosionCoverage() is a script function + // so we currently assign a coverage value of 1.0. + + U32 mask = InteriorObjectType | TerrainObjectType | VehicleObjectType; + F32 coverage = calcExplosionCoverage(impact_pos, shape, mask); + if (coverage == 0.0f) + continue; +#else + F32 coverage = 1.0f; +#endif + + // calulate distance + Point3F pos; + shape->getWorldBox().getCenter(&pos); + F32 dist = (pos - impact_pos).len(); + + F32 min_dist = shape->getWorldBox().len_x(); + if (shape->getWorldBox().len_y() < min_dist) + min_dist = shape->getWorldBox().len_y(); + if (shape->getWorldBox().len_z() < min_dist) + min_dist = shape->getWorldBox().len_z(); + + dist -= min_dist; + if (dist < 0) + dist = 0; + + F32 dist_scale = (dist < halfradius) ? 1.0f : 1.0f - ((dist - halfradius)/halfradius); + + F32 damage = damage_data->amount*coverage*dist_scale; + apply_damage(shape, damage, damage_data->flavor, impact_pos); + apply_impulse(shape, damage_data->impulse*dist_scale, impact_pos); + + if (damage_data->notify_damage_src) + notify_damage_source(shape, damage, damage_data->flavor, impact_pos); + } + } +} + +void afxEA_AreaDamage::notify_damage_source(ShapeBase* damaged, F32 damage, const char* flavor, Point3F& pos) +{ + if (mIsZero(damage)) + return; + + char *posArg = Con::getArgBuffer(64); + dSprintf(posArg, 64, "%f %f %f", pos.x, pos.y, pos.z); + + Con::executef(choreographer->getDataBlock(), "onInflictedAreaDamage", + choreographer->getIdString(), + damaged->getIdString(), + Con::getFloatArg(damage), + flavor, + posArg); +} + +void afxEA_AreaDamage::apply_damage(ShapeBase* shape, F32 damage, const char* flavor, Point3F& pos) +{ + if (mIsZero(damage)) + return; + + char *posArg = Con::getArgBuffer(64); + dSprintf(posArg, 64, "%f %f %f", pos.x, pos.y, pos.z); + + Con::executef(shape, "damage", + choreographer->getIdString(), + posArg, + Con::getFloatArg(damage), + flavor); +} + +void afxEA_AreaDamage::apply_impulse(ShapeBase* shape, F32 impulse, Point3F& pos) +{ + if (impulse <= 0.0f) + return; + + Point3F center; shape->getWorldBox().getCenter(¢er); + VectorF impulse_vec = center - pos; + impulse_vec.normalizeSafe(); + impulse_vec *= impulse; + shape->applyImpulse(pos, impulse_vec); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_AreaDamageDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_AreaDamageDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const { return false; } + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_AreaDamage; } +}; + +afxEA_AreaDamageDesc afxEA_AreaDamageDesc::desc; + +bool afxEA_AreaDamageDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxAreaDamageData) == typeid(*db)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_AudioBank.cpp b/Engine/source/afx/ea/afxEA_AudioBank.cpp new file mode 100644 index 000000000..3cf7bd912 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_AudioBank.cpp @@ -0,0 +1,178 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "sfx/sfxSystem.h" +#include "sfx/sfxSource.h" +#include "sfx/sfxProfile.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxAudioBank.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_AudioBank + +class afxEA_AudioBank : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + SFXSource* sound_handle; + afxAudioBank* sound_bank; + + S32 play_index; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_AudioBank(); + /*D*/ ~afxEA_AudioBank(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_AudioBank::afxEA_AudioBank() +{ + sound_handle = 0; + sound_bank = 0; + play_index = -1; +} + +afxEA_AudioBank::~afxEA_AudioBank() +{ + if (sound_bank && sound_bank->isTempClone()) + delete sound_bank; + sound_bank = 0; + sound_handle = 0; +} + +void afxEA_AudioBank::ea_set_datablock(SimDataBlock* db) +{ + sound_bank = dynamic_cast(db); +} + +bool afxEA_AudioBank::ea_start() +{ + if (!sound_bank) + { + Con::errorf("afxEA_AudioBank::ea_start() -- missing or incompatible afxAudioBank."); + return false; + } + + do_runtime_substitutions(); + + if (sound_bank->mPath == ST_NULLSTRING) + return false; + + play_index = sound_bank->play_index; + + return true; +} + +bool afxEA_AudioBank::ea_update(F32 dt) +{ + if (!sound_handle) + { + if (play_index >= 0 && play_index < 32 && sound_bank->mFilenames[play_index] != ST_NULLSTRING) + { + SFXProfile* temp_prof = new SFXProfile(sound_bank->mDescriptionObject, + avar("%s/%s", sound_bank->mPath, sound_bank->mFilenames[play_index]), + true); + if (!temp_prof->registerObject()) + { + Con::errorf( "afxEA_AudioBank::ea_update() -- unable to create profile"); + delete temp_prof; + } + else + { + sound_handle = SFX->createSource(temp_prof); + if (sound_handle) + sound_handle->play(); + temp_prof->setAutoDelete(true); + } + } + } + + if (sound_handle) + { + sound_handle->setTransform(updated_xfm); + sound_handle->setVolume(updated_scale.x*fade_value); + } + + return true; +} + +void afxEA_AudioBank::ea_finish(bool was_stopped) +{ + if (sound_handle) + { + sound_handle->stop(); + SFX_DELETE(sound_handle); + } +} + +void afxEA_AudioBank::do_runtime_substitutions() +{ + sound_bank = sound_bank->cloneAndPerformSubstitutions(choreographer, group_index); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_SoundBankDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_SoundBankDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + //virtual void prepEffect(afxEffectWrapperData*) const; + + virtual afxEffectWrapper* create() const { return new afxEA_AudioBank; } +}; + +afxEA_SoundBankDesc afxEA_SoundBankDesc::desc; + +bool afxEA_SoundBankDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxAudioBank) == typeid(*db)); +} + +bool afxEA_SoundBankDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + SFXDescription* desc = ((SFXProfile*)ew->effect_data)->getDescription(); + return (desc && desc->mIsLooping) ? (timing.lifetime < 0) : false; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_Billboard.cpp b/Engine/source/afx/ea/afxEA_Billboard.cpp new file mode 100644 index 000000000..254dab1d6 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_Billboard.cpp @@ -0,0 +1,199 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/afxResidueMgr.h" +#include "afx/ce/afxBillboard.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_Billboard -- This is the adapter for geometry primitives. + +class afxEA_Billboard : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxBillboardData* bb_data; + afxBillboard* bb; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_Billboard(); + /*D*/ ~afxEA_Billboard(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + virtual void ea_set_scope_status(bool flag); + virtual void onDeleteNotify(SimObject*); + virtual void getUpdatedBoxCenter(Point3F& pos); + virtual void getBaseColor(LinearColorF& color) { if (bb_data) color = bb_data->color; } +}; + + +afxEA_Billboard::afxEA_Billboard() +{ + bb_data = 0; + bb = 0; +} + +afxEA_Billboard::~afxEA_Billboard() +{ + if (bb) + bb->deleteObject(); + if (bb_data && bb_data->isTempClone()) + delete bb_data; + bb_data = 0; +} + +void afxEA_Billboard::ea_set_datablock(SimDataBlock* db) +{ + bb_data = dynamic_cast(db); +} + +bool afxEA_Billboard::ea_start() +{ + if (!bb_data) + { + Con::errorf("afxEA_Billboard::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + return true; +} + +bool afxEA_Billboard::ea_update(F32 dt) +{ + if (!bb) + { + // create and register effect + bb = new afxBillboard(); + bb->onNewDataBlock(bb_data, false); + if (!bb->registerObject()) + { + delete bb; + bb = 0; + Con::errorf("afxEA_Billboard::ea_update() -- effect failed to register."); + return false; + } + deleteNotify(bb); + + ///bb->setSequenceRateFactor(datablock->rate_factor/prop_time_factor); + bb->setSortPriority(datablock->sort_priority); + } + + if (bb) + { + bb->live_color = updated_color; + if (do_fades) + { + bb->setFadeAmount(fade_value); + } + bb->setTransform(updated_xfm); + bb->setScale(updated_scale); + } + + return true; +} + +void afxEA_Billboard::ea_finish(bool was_stopped) +{ + if (!bb) + return; + + bb->deleteObject(); + bb = 0; +} + +void afxEA_Billboard::ea_set_scope_status(bool in_scope) +{ + if (bb) + bb->setVisibility(in_scope); +} + +void afxEA_Billboard::onDeleteNotify(SimObject* obj) +{ + if (bb == dynamic_cast(obj)) + bb = 0; + + Parent::onDeleteNotify(obj); +} + +void afxEA_Billboard::getUpdatedBoxCenter(Point3F& pos) +{ + if (bb) + pos = bb->getBoxCenter(); +} + +void afxEA_Billboard::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (bb_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxBillboardData* orig_db = bb_data; + bb_data = new afxBillboardData(*orig_db, true); + orig_db->performSubstitutions(bb_data, choreographer, group_index); + } +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_BillboardDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_BillboardDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_Billboard; } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_BillboardDesc afxEA_BillboardDesc::desc; + +bool afxEA_BillboardDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxBillboardData) == typeid(*db)); +} + +bool afxEA_BillboardDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_CameraPuppet.cpp b/Engine/source/afx/ea/afxEA_CameraPuppet.cpp new file mode 100644 index 000000000..b307bcb2f --- /dev/null +++ b/Engine/source/afx/ea/afxEA_CameraPuppet.cpp @@ -0,0 +1,199 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "T3D/gameBase/gameConnection.h" + +#include "afx/afxChoreographer.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxCamera.h" +#include "afx/ce/afxCameraPuppet.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_CameraPuppet + +class afxEA_CameraPuppet : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxCameraPuppetData* puppet_data; + afxConstraint* cam_cons; + bool was_1st_person; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_CameraPuppet(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + + virtual void getUnconstrainedPosition(Point3F& pos); + virtual void getUnconstrainedTransform(MatrixF& xfm); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_CameraPuppet::afxEA_CameraPuppet() +{ + puppet_data = 0; + cam_cons = 0; + was_1st_person = false; +} + +void afxEA_CameraPuppet::ea_set_datablock(SimDataBlock* db) +{ + puppet_data = dynamic_cast(db); +} + +bool afxEA_CameraPuppet::ea_start() +{ + if (!puppet_data) + { + Con::errorf("afxEA_CameraPuppet::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + afxConstraintID obj_id = cons_mgr->getConstraintId(puppet_data->cam_def); + cam_cons = cons_mgr->getConstraint(obj_id); + + SceneObject* obj = (cam_cons) ? cam_cons->getSceneObject() : 0; + if (obj && obj->isClientObject()) + { + GameConnection* conn = GameConnection::getConnectionToServer(); + if (conn) + { + was_1st_person = conn->isFirstPerson(); + if (was_1st_person) + conn->setFirstPerson(false); + } + } + + return true; +} + +bool afxEA_CameraPuppet::ea_update(F32 dt) +{ + SceneObject* obj = (cam_cons) ? cam_cons->getSceneObject() : 0; + + if (obj && in_scope) + { + obj->setTransform(updated_xfm); + } + + return true; +} + +void afxEA_CameraPuppet::ea_finish(bool was_stopped) +{ + afxCamera* afx_cam = dynamic_cast((cam_cons) ? cam_cons->getSceneObject() : 0); + if (afx_cam && afx_cam->isClientObject()) + afx_cam->setThirdPersonSnapClient(); + + if (was_1st_person) + { + GameConnection* conn = GameConnection::getConnectionToServer(); + if (conn) + conn->setFirstPerson(true); + } +} + +void afxEA_CameraPuppet::getUnconstrainedPosition(Point3F& pos) +{ + SceneObject* obj = (cam_cons) ? cam_cons->getSceneObject() : 0; + if (obj) + pos = obj->getRenderPosition(); + else + pos.zero(); +} + +void afxEA_CameraPuppet::getUnconstrainedTransform(MatrixF& xfm) +{ + SceneObject* obj = (cam_cons) ? cam_cons->getSceneObject() : 0; + if (obj) + xfm = obj->getRenderTransform(); + else + xfm.identity(); +} + +void afxEA_CameraPuppet::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (puppet_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxCameraPuppetData* orig_db = puppet_data; + puppet_data = new afxCameraPuppetData(*orig_db, true); + orig_db->performSubstitutions(puppet_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_CameraPuppetDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_CameraPuppetDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const; + virtual bool runsOnClient(const afxEffectWrapperData*) const; + + virtual afxEffectWrapper* create() const { return new afxEA_CameraPuppet; } +}; + +afxEA_CameraPuppetDesc afxEA_CameraPuppetDesc::desc; + +bool afxEA_CameraPuppetDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxCameraPuppetData) == typeid(*db)); +} + +bool afxEA_CameraPuppetDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +bool afxEA_CameraPuppetDesc::runsOnServer(const afxEffectWrapperData* ew) const +{ + U8 networking = ((const afxCameraPuppetData*)ew->effect_data)->networking; + return ((networking & (SERVER_ONLY | SERVER_AND_CLIENT)) != 0); +} + +bool afxEA_CameraPuppetDesc::runsOnClient(const afxEffectWrapperData* ew) const +{ + U8 networking = ((const afxCameraPuppetData*)ew->effect_data)->networking; + return ((networking & (CLIENT_ONLY | SERVER_AND_CLIENT)) != 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_CameraShake.cpp b/Engine/source/afx/ea/afxEA_CameraShake.cpp new file mode 100644 index 000000000..9c26e32b6 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_CameraShake.cpp @@ -0,0 +1,196 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "T3D/fx/cameraFXMgr.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxCameraShake.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_CameraShake + +class afxEA_CameraShake : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxCameraShakeData* shake_data; + CameraShake* camera_shake; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_CameraShake(); + /*D*/ ~afxEA_CameraShake(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_CameraShake::afxEA_CameraShake() +{ + shake_data = 0; + camera_shake = 0; +} + +afxEA_CameraShake::~afxEA_CameraShake() +{ + delete camera_shake; + if (shake_data && shake_data->isTempClone()) + delete shake_data; + shake_data = 0; +} + +void afxEA_CameraShake::ea_set_datablock(SimDataBlock* db) +{ + shake_data = dynamic_cast(db); +} + +bool afxEA_CameraShake::ea_start() +{ + if (!shake_data) + { + Con::errorf("afxEA_CameraShake::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + afxConstraint* pos_constraint = getPosConstraint(); + afxConstraint* aim_constraint = getAimConstraint(); + + if (aim_constraint && pos_constraint) + { + if (full_lifetime <= 0 || full_lifetime == INFINITE_LIFETIME) + { + Con::errorf("afxEA_CameraShake::ea_start() -- effect requires a finite lifetime."); + return false; + } + + SceneObject* shaken = aim_constraint->getSceneObject(); + if (shaken) + { + Point3F pos; pos_constraint->getPosition(pos); + VectorF diff = shaken->getPosition() - pos; + F32 dist = diff.len(); + if (dist < shake_data->camShakeRadius) + { + camera_shake = new CameraShake; + camera_shake->setDuration(full_lifetime); + camera_shake->setFrequency(shake_data->camShakeFreq); + + F32 falloff = dist/shake_data->camShakeRadius; + falloff = 1 + falloff*10.0; + falloff = 1.0 / (falloff*falloff); + + VectorF shakeAmp = shake_data->camShakeAmp*falloff; + camera_shake->setAmplitude(shakeAmp); + camera_shake->setFalloff(shake_data->camShakeFalloff); + camera_shake->init(); + } + } + } + + return true; +} + +bool afxEA_CameraShake::ea_update(F32 dt) +{ + afxConstraint* aim_constraint = getAimConstraint(); + if (camera_shake && aim_constraint) + { + camera_shake->update(dt); + + SceneObject* shaken = aim_constraint->getSceneObject(); + if (shaken) + { + MatrixF fxTrans = camera_shake->getTrans(); + MatrixF curTrans = shaken->getRenderTransform(); + curTrans.mul(fxTrans); + + Point3F cameraPosWorld; + curTrans.getColumn(3,&cameraPosWorld); + shaken->setPosition(cameraPosWorld); + } + } + + return true; +} + +void afxEA_CameraShake::ea_finish(bool was_stopped) +{ + delete camera_shake; + camera_shake = 0; +} + +void afxEA_CameraShake::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (shake_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxCameraShakeData* orig_db = shake_data; + shake_data = new afxCameraShakeData(*orig_db, true); + orig_db->performSubstitutions(shake_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_CameraShakeDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_CameraShakeDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + virtual bool isPositional(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_CameraShake; } +}; + +afxEA_CameraShakeDesc afxEA_CameraShakeDesc::desc; + +bool afxEA_CameraShakeDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxCameraShakeData) == typeid(*db)); +} + +bool afxEA_CameraShakeDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_CollisionEvent.cpp b/Engine/source/afx/ea/afxEA_CollisionEvent.cpp new file mode 100644 index 000000000..b06492acc --- /dev/null +++ b/Engine/source/afx/ea/afxEA_CollisionEvent.cpp @@ -0,0 +1,225 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxCollisionEvent.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_CollisionEvent + +class afxEA_CollisionEvent : public afxEffectWrapper, ShapeBase::CollisionEventCallback +{ + typedef afxEffectWrapper Parent; + + afxCollisionEventData* script_data; + ShapeBase* shape; + U32 trigger_mask; + bool triggered; + + void do_runtime_substitutions(); + void set_shape(ShapeBase*); + +public: + /*C*/ afxEA_CollisionEvent(); + /*D*/ ~afxEA_CollisionEvent(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + + virtual void collisionNotify(SceneObject* obj0, SceneObject* obj1, const VectorF& vel); + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_CollisionEvent::afxEA_CollisionEvent() +{ + script_data = 0; + shape = 0; + trigger_mask = 0; + triggered = false; +} + +afxEA_CollisionEvent::~afxEA_CollisionEvent() +{ + if (shape) + clearNotify(shape); + if (script_data && script_data->isTempClone()) + delete script_data; + script_data = 0; +} + +void afxEA_CollisionEvent::ea_set_datablock(SimDataBlock* db) +{ + script_data = dynamic_cast(db); +} + +bool afxEA_CollisionEvent::ea_start() +{ + if (!script_data) + { + Con::errorf("afxEA_CollisionEvent::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + if (script_data->gen_trigger && script_data->trigger_bit < 32) + trigger_mask = 1 << script_data->trigger_bit; + else + trigger_mask = 0; + + triggered = false; + + return true; +} + +bool afxEA_CollisionEvent::ea_update(F32 dt) +{ + afxConstraint* pos_constraint = getPosConstraint(); + set_shape((pos_constraint) ? dynamic_cast(pos_constraint->getSceneObject()) : 0); + + if (choreographer && trigger_mask != 0) + { + if (triggered) + { + choreographer->setTriggerMask(trigger_mask | choreographer->getTriggerMask()); + triggered = false; + } + else + { + choreographer->setTriggerMask(~trigger_mask & choreographer->getTriggerMask()); + } + } + + return true; +} + +void afxEA_CollisionEvent::ea_finish(bool was_stopped) +{ + set_shape(0); +} + +void afxEA_CollisionEvent::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (script_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxCollisionEventData* orig_db = script_data; + script_data = new afxCollisionEventData(*orig_db, true); + orig_db->performSubstitutions(script_data, choreographer, group_index); + } +} + +void afxEA_CollisionEvent::set_shape(ShapeBase* new_shape) +{ + if (shape == new_shape) + return; + + if (shape) + { + shape->unregisterCollisionCallback(this); + clearNotify(shape); + } + + shape = new_shape; + + if (shape) + { + deleteNotify(shape); + shape->registerCollisionCallback(this); + } +} + +void afxEA_CollisionEvent::collisionNotify(SceneObject* obj0, SceneObject* obj1, const VectorF& vel) +{ + if (obj0 != shape || !choreographer || !choreographer->getDataBlock()) + return; + + if (script_data->method_name != ST_NULLSTRING) + { + char *arg_buf = Con::getArgBuffer(64); + dSprintf(arg_buf, 256, "%g %g %g", vel.x, vel.y, vel.z); + + // CALL SCRIPT afxChoreographerData::method(%spell, %obj0, %obj1, %velocity) + Con::executef(choreographer->getDataBlock(), script_data->method_name, + choreographer->getIdString(), + (obj0) ? obj0->getIdString() : "", + (obj1) ? obj1->getIdString() : "", + arg_buf, + script_data->script_data); + } + + if (!triggered && trigger_mask != 0) + triggered = true; +} + +void afxEA_CollisionEvent::onDeleteNotify(SimObject* obj) +{ + if (obj == shape) + { + shape->unregisterCollisionCallback(this); + shape = 0; + } + + Parent::onDeleteNotify(obj); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_CollisionEventDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_CollisionEventDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return false; } + virtual bool isPositional(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_CollisionEvent; } +}; + +afxEA_CollisionEventDesc afxEA_CollisionEventDesc::desc; + +bool afxEA_CollisionEventDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxCollisionEventData) == typeid(*db)); +} + +bool afxEA_CollisionEventDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_ConsoleMessage.cpp b/Engine/source/afx/ea/afxEA_ConsoleMessage.cpp new file mode 100644 index 000000000..cb08cfa72 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_ConsoleMessage.cpp @@ -0,0 +1,128 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxConsoleMessage.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_ConsoleMessage + +class afxEA_ConsoleMessage : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxConsoleMessageData* message_data; + bool displayed; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_ConsoleMessage(); + + virtual bool isDone() { return displayed; } + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_ConsoleMessage::afxEA_ConsoleMessage() +{ + message_data = 0; + displayed = false; +} + +void afxEA_ConsoleMessage::ea_set_datablock(SimDataBlock* db) +{ + message_data = dynamic_cast(db); +} + +bool afxEA_ConsoleMessage::ea_start() +{ + if (!message_data) + { + Con::errorf("afxEA_ConsoleMessage::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + return true; +} + +bool afxEA_ConsoleMessage::ea_update(F32 dt) +{ + if (!displayed) + { + if (message_data->message_str != ST_NULLSTRING) + Con::printf("ConsoleMessage: \"%s\"", message_data->message_str); + displayed = true; + } + + return true; +} + +void afxEA_ConsoleMessage::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (message_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxConsoleMessageData* orig_db = message_data; + message_data = new afxConsoleMessageData(*orig_db, true); + orig_db->performSubstitutions(message_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_ConsoleMessageDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_ConsoleMessageDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const { return false; } + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + virtual bool isPositional(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_ConsoleMessage; } +}; + +afxEA_ConsoleMessageDesc afxEA_ConsoleMessageDesc::desc; + +bool afxEA_ConsoleMessageDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxConsoleMessageData) == typeid(*db)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_Damage.cpp b/Engine/source/afx/ea/afxEA_Damage.cpp new file mode 100644 index 000000000..e0691eae3 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_Damage.cpp @@ -0,0 +1,201 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxDamage.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_Damage + +class afxEA_Damage : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxDamageData* damage_data; + bool started; + U8 repeat_cnt; + U32 dot_delta_ms; + U32 next_dot_time; + Point3F impact_pos; + SimObjectId impacted_obj_id; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_Damage(); + /*C*/ ~afxEA_Damage(); + + virtual bool isDone(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_Damage::afxEA_Damage() +{ + damage_data = 0; + started = false; + repeat_cnt = 0; + dot_delta_ms = 0; + next_dot_time = 0; + impact_pos.zero(); + impacted_obj_id = 0; +} + +afxEA_Damage::~afxEA_Damage() +{ + if (damage_data && damage_data->isTempClone()) + delete damage_data; + damage_data = 0; +} + +bool afxEA_Damage::isDone() +{ + return (damage_data) ? (repeat_cnt >= damage_data->repeats) : true; +} + +void afxEA_Damage::ea_set_datablock(SimDataBlock* db) +{ + damage_data = dynamic_cast(db); +} + +bool afxEA_Damage::ea_start() +{ + if (!damage_data) + { + Con::errorf("afxEA_Damage::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + if (damage_data->repeats > 1) + { + dot_delta_ms = full_lifetime/(damage_data->repeats - 1); + next_dot_time = dot_delta_ms; + } + + return true; +} + +bool afxEA_Damage::ea_update(F32 dt) +{ + if (!started) + { + started = true; + + afxConstraint* pos_cons = getPosConstraint(); + if (pos_cons) + pos_cons->getPosition(impact_pos); + + afxConstraint* aim_cons = getAimConstraint(); + if (aim_cons && aim_cons->getSceneObject()) + impacted_obj_id = aim_cons->getSceneObject()->getId(); + + if (choreographer) + choreographer->inflictDamage(damage_data->label, damage_data->flavor, impacted_obj_id, damage_data->amount, + repeat_cnt, damage_data->ad_amount, damage_data->radius, impact_pos, + damage_data->impulse); + repeat_cnt++; + } + else if (repeat_cnt < damage_data->repeats) + { + if (next_dot_time <= life_elapsed) + { + // CONSTRAINT REMAPPING << + afxConstraint* aim_cons = getAimConstraint(); + if (aim_cons && aim_cons->getSceneObject()) + impacted_obj_id = aim_cons->getSceneObject()->getId(); + // CONSTRAINT REMAPPING >> + + if (choreographer) + choreographer->inflictDamage(damage_data->label, damage_data->flavor, impacted_obj_id, damage_data->amount, + repeat_cnt, 0, 0, impact_pos, 0); + next_dot_time += dot_delta_ms; + repeat_cnt++; + } + } + + return true; +} + +void afxEA_Damage::ea_finish(bool was_stopped) +{ + if (started && (repeat_cnt < damage_data->repeats)) + { + if (next_dot_time <= life_elapsed) + { + if (choreographer) + choreographer->inflictDamage(damage_data->label, damage_data->flavor, impacted_obj_id, damage_data->amount, + repeat_cnt, 0, 0, impact_pos, 0); + } + } + + started = false; +} + +void afxEA_Damage::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (damage_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxDamageData* orig_db = damage_data; + damage_data = new afxDamageData(*orig_db, true); + orig_db->performSubstitutions(damage_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_DamageDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_DamageDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const { return false; } + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_Damage; } +}; + +afxEA_DamageDesc afxEA_DamageDesc::desc; + +bool afxEA_DamageDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxDamageData) == typeid(*db)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_Debris.cpp b/Engine/source/afx/ea/afxEA_Debris.cpp new file mode 100644 index 000000000..e4a46fc6b --- /dev/null +++ b/Engine/source/afx/ea/afxEA_Debris.cpp @@ -0,0 +1,199 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "T3D/debris.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_Debris + +class afxEA_Debris : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + DebrisData* debris_data; + Debris* debris; + bool exploded; + bool debris_done; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_Debris(); + /*D*/ ~afxEA_Debris(); + + virtual bool isDone(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_Debris::afxEA_Debris() +{ + debris_data = 0; + debris = 0; + exploded = false; + debris_done = false; +} + +afxEA_Debris::~afxEA_Debris() +{ + if (debris) + clearNotify(debris); +} + +bool afxEA_Debris::isDone() +{ + return (datablock->use_as_cons_obj) ? debris_done : exploded; +} + +void afxEA_Debris::ea_set_datablock(SimDataBlock* db) +{ + debris_data = dynamic_cast(db); +} + +bool afxEA_Debris::ea_start() +{ + if (!debris_data) + { + Con::errorf("afxEA_Debris::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + debris = new Debris(); + debris->onNewDataBlock(debris_data, false); + + return true; +} + +bool afxEA_Debris::ea_update(F32 dt) +{ + if (exploded && debris) + { + if (in_scope) + { + updated_xfm = debris->getRenderTransform(); + updated_xfm.getColumn(3, &updated_pos); + } + } + + if (!exploded && debris) + { + if (in_scope) + { + Point3F dir_vec(0,1,0); + updated_xfm.mulV(dir_vec); + + debris->init(updated_pos, dir_vec); + if (!debris->registerObject()) + { + delete debris; + debris = 0; + Con::errorf("afxEA_Debris::ea_update() -- effect failed to register."); + return false; + } + deleteNotify(debris); + } + exploded = true; + } + + return true; +} + +void afxEA_Debris::ea_finish(bool was_stopped) +{ + if (debris) + { + clearNotify(debris); + debris = 0; + } + exploded = false; +} + +void afxEA_Debris::onDeleteNotify(SimObject* obj) +{ + // debris deleted? + Debris* del_debris = dynamic_cast(obj); + if (del_debris == debris) + { + debris = NULL; + debris_done = true; + } +} + +void afxEA_Debris::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (debris_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + DebrisData* orig_db = debris_data; + debris_data = new DebrisData(*orig_db, true); + orig_db->performSubstitutions(debris_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_DebrisDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_DebrisDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_Debris; } +}; + +afxEA_DebrisDesc afxEA_DebrisDesc::desc; + +bool afxEA_DebrisDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(DebrisData) == typeid(*db)); +} + +bool afxEA_DebrisDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (ew->use_as_cons_obj && timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_Explosion.cpp b/Engine/source/afx/ea/afxEA_Explosion.cpp new file mode 100644 index 000000000..f8e424f84 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_Explosion.cpp @@ -0,0 +1,145 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "T3D/fx/explosion.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_Explosion + +class afxEA_Explosion : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + ExplosionData* explosion_data; + Explosion* explosion; + bool exploded; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_Explosion(); + + virtual bool isDone() { return exploded; } + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_Explosion::afxEA_Explosion() +{ + explosion_data = 0; + explosion = 0; + exploded = false; +} + +void afxEA_Explosion::ea_set_datablock(SimDataBlock* db) +{ + explosion_data = dynamic_cast(db); +} + +bool afxEA_Explosion::ea_start() +{ + if (!explosion_data) + { + Con::errorf("afxEA_Explosion::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + explosion = new Explosion(); + explosion->setSubstitutionData(choreographer, group_index); + explosion->setDataBlock(explosion_data); + + return true; +} + +bool afxEA_Explosion::ea_update(F32 dt) +{ + if (!exploded && explosion) + { + if (in_scope) + { + Point3F norm(0,0,1); updated_xfm.mulV(norm); + explosion->setInitialState(updated_pos, norm); + if (!explosion->registerObject()) + { + delete explosion; + explosion = 0; + Con::errorf("afxEA_Explosion::ea_update() -- effect failed to register."); + return false; + } + } + exploded = true; + } + + return true; +} + +void afxEA_Explosion::ea_finish(bool was_stopped) +{ + explosion = 0; + exploded = false; +} + +void afxEA_Explosion::do_runtime_substitutions() +{ + explosion_data = explosion_data->cloneAndPerformSubstitutions(choreographer, group_index); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_ExplosionDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_ExplosionDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const { return false; } + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_Explosion; } +}; + +afxEA_ExplosionDesc afxEA_ExplosionDesc::desc; + +bool afxEA_ExplosionDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(ExplosionData) == typeid(*db)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_FootSwitch.cpp b/Engine/source/afx/ea/afxEA_FootSwitch.cpp new file mode 100644 index 000000000..822dfb1e4 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_FootSwitch.cpp @@ -0,0 +1,180 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "T3D/player.h" + +#include "afx/afxChoreographer.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/ce/afxFootSwitch.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_FootSwitch + +class afxEA_FootSwitch : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxFootSwitchData* footfall_data; + Player* player; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_FootSwitch(); + + void set_overrides(Player*); + void clear_overrides(Player*); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_FootSwitch::afxEA_FootSwitch() +{ + footfall_data = 0; + player = 0; +} + +inline void afxEA_FootSwitch::set_overrides(Player* player) +{ + if (footfall_data->override_all) + player->overrideFootfallFX(); + else + player->overrideFootfallFX(footfall_data->override_decals, + footfall_data->override_sounds, + footfall_data->override_dust); +} + +inline void afxEA_FootSwitch::clear_overrides(Player* player) +{ + if (footfall_data->override_all) + player->restoreFootfallFX(); + else + player->restoreFootfallFX(footfall_data->override_decals, + footfall_data->override_sounds, + footfall_data->override_dust); +} + +void afxEA_FootSwitch::ea_set_datablock(SimDataBlock* db) +{ + footfall_data = dynamic_cast(db); +} + +bool afxEA_FootSwitch::ea_start() +{ + if (!footfall_data) + { + Con::errorf("afxEA_FootSwitch::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + afxConstraint* pos_cons = getPosConstraint(); + player = (pos_cons) ? dynamic_cast(pos_cons->getSceneObject()) : 0; + if (player) + set_overrides(player); + + return true; +} + +bool afxEA_FootSwitch::ea_update(F32 dt) +{ + if (!player) + return true; + + afxConstraint* pos_cons = getPosConstraint(); + Player* temp_player = (pos_cons) ? dynamic_cast(pos_cons->getSceneObject()) : 0; + if (temp_player && temp_player != player) + { + player = temp_player; + if (player) + set_overrides(player); + } + + return true; +} + +void afxEA_FootSwitch::ea_finish(bool was_stopped) +{ + if (!player) + return; + + afxConstraint* pos_cons = getPosConstraint(); + Player* temp_player = (pos_cons) ? dynamic_cast(pos_cons->getSceneObject()) : 0; + if (temp_player == player) + clear_overrides(player); +} + +void afxEA_FootSwitch::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (footfall_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxFootSwitchData* orig_db = footfall_data; + footfall_data = new afxFootSwitchData(*orig_db, true); + orig_db->performSubstitutions(footfall_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_FootfallSwitchDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_FootfallSwitchDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + virtual bool isPositional(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_FootSwitch; } +}; + +afxEA_FootfallSwitchDesc afxEA_FootfallSwitchDesc::desc; + +bool afxEA_FootfallSwitchDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxFootSwitchData) == typeid(*db)); +} + +bool afxEA_FootfallSwitchDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_GuiController.cpp b/Engine/source/afx/ea/afxEA_GuiController.cpp new file mode 100644 index 000000000..9387278e7 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_GuiController.cpp @@ -0,0 +1,216 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "gui/core/guiControl.h" +#include "gui/3d/guiTSControl.h" +#include "T3D/gameBase/gameConnection.h" +#include "gui/game/guiProgressCtrl.h" + +#include "afx/afxChoreographer.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/ce/afxGuiController.h" +#include "afx/ui/afxProgressBase.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_GuiController + +class afxEA_GuiController : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxGuiControllerData* controller_data; + GuiControl* gui_control; + GuiTSCtrl* ts_ctrl; + afxProgressBase* progress_base; + GuiProgressCtrl* progress_ctrl; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_GuiController(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_GuiController::afxEA_GuiController() +{ + controller_data = 0; + gui_control = 0; + ts_ctrl = 0; + progress_base = 0; + progress_ctrl = 0; +} + +void afxEA_GuiController::ea_set_datablock(SimDataBlock* db) +{ + controller_data = dynamic_cast(db); +} + +bool afxEA_GuiController::ea_start() +{ + if (!controller_data) + { + Con::errorf("afxEA_GuiController::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + if (controller_data->ctrl_client_only) + { + afxConstraint* pos_cons = getPosConstraint(); + if (!pos_cons) + return false; // not an error condition + GameBase* gamebase_obj = dynamic_cast(pos_cons->getSceneObject()); + if (!gamebase_obj || !gamebase_obj->getControllingClient()) + return false; // not an error condition + } + + if (controller_data->control_name == ST_NULLSTRING || controller_data->control_name[0] == '\0') + { + Con::errorf("afxEA_GuiController::ea_start() -- empty control name."); + return false; + } + + gui_control = dynamic_cast(Sim::findObject(controller_data->control_name)); + if (!gui_control) + { + Con::errorf("afxEA_GuiController::ea_start() -- failed to find control \"%s\".", controller_data->control_name); + return false; + } + + gui_control->setVisible(true); + + progress_base = dynamic_cast(gui_control); + if (progress_base) + { + progress_base->setProgress(0.0f); + progress_ctrl = 0; + } + else + { + progress_ctrl = (GuiProgressCtrl*)gui_control; + if (progress_ctrl) + { + progress_ctrl->setScriptValue(0); + progress_base = 0; + } + } + + ts_ctrl = 0; + for (GuiControl* ctrl = gui_control->getParent(); ctrl != 0; ctrl->getParent()) + { + if (dynamic_cast(ctrl)) + { + ts_ctrl = (GuiTSCtrl*) ctrl; + break; + } + } + + return true; +} + +bool afxEA_GuiController::ea_update(F32 dt) +{ + if (ts_ctrl && !controller_data->preserve_pos) + { + Point3F screen_pos; + if (ts_ctrl->project(updated_pos, &screen_pos)) + { + const Point2I ext = gui_control->getExtent(); + Point2I newpos(screen_pos.x - ext.x/2, screen_pos.y - ext.y/2); + gui_control->setPosition(newpos); + } + } + + if (progress_base) + progress_base->setProgress((ew_timing.lifetime > 0.0) ? life_elapsed/ew_timing.lifetime : 0.0f); + else if (progress_ctrl) + progress_ctrl->setScriptValue((ew_timing.lifetime > 0.0) ? avar("%g", life_elapsed/ew_timing.lifetime) : 0); + + if (do_fades) + gui_control->setFadeAmount(fade_value); + + return true; +} + +void afxEA_GuiController::ea_finish(bool was_stopped) +{ + if (progress_base) + progress_base->setProgress(1.0f); + else if (progress_ctrl) + progress_ctrl->setScriptValue("1"); + gui_control->setVisible(false); +} + +void afxEA_GuiController::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (controller_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxGuiControllerData* orig_db = controller_data; + controller_data = new afxGuiControllerData(*orig_db, true); + orig_db->performSubstitutions(controller_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_GuiControllerDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_GuiControllerDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_GuiController; } +}; + +afxEA_GuiControllerDesc afxEA_GuiControllerDesc::desc; + +bool afxEA_GuiControllerDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxGuiControllerData) == typeid(*db)); +} + +bool afxEA_GuiControllerDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_GuiText.cpp b/Engine/source/afx/ea/afxEA_GuiText.cpp new file mode 100644 index 000000000..7dfc77422 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_GuiText.cpp @@ -0,0 +1,180 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxChoreographer.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/ce/afxGuiText.h" +#include "afx/ui/afxGuiTextHud.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_GuiText + +class afxEA_GuiText : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + enum { + UNDEFINED, + USER_TEXT, + SHAPE_NAME + }; + + afxGuiTextData* text_data; + S32 text_src; + LinearColorF text_clr; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_GuiText(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_GuiText::afxEA_GuiText() +{ + text_data = 0; + text_src = UNDEFINED; + text_clr.set(1,1,1,1); +} + +void afxEA_GuiText::ea_set_datablock(SimDataBlock* db) +{ + text_data = dynamic_cast(db); +} + +bool afxEA_GuiText::ea_start() +{ + if (!text_data) + { + Con::errorf("afxEA_GuiText::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + if (text_data->text_str == ST_NULLSTRING || text_data->text_str[0] == '\0') + { + Con::errorf("afxEA_GuiText::ea_start() -- empty text string."); + return false; + } + + text_clr = text_data->text_clr; + + if (dStricmp("#shapeName", text_data->text_str) == 0) + text_src = SHAPE_NAME; + else + text_src = USER_TEXT; + + return true; +} + +bool afxEA_GuiText::ea_update(F32 dt) +{ + switch (text_src) + { + case USER_TEXT: + { + LinearColorF temp_clr = text_clr; + if (do_fades) + temp_clr.alpha = fade_value; + afxGuiTextHud::addTextItem(updated_pos, text_data->text_str, temp_clr); + } + break; + case SHAPE_NAME: + { + const char* name = 0; + SceneObject* cons_obj = 0; + afxConstraint* pos_cons = getPosConstraint(); + if (pos_cons) + { + ShapeBase* shape = dynamic_cast(pos_cons->getSceneObject()); + if (shape) + { + name = shape->getShapeName(); + cons_obj = shape; + } + } + if (name && name[0] != '\0') + { + LinearColorF temp_clr = text_clr; + if (do_fades) + temp_clr.alpha = fade_value; + afxGuiTextHud::addTextItem(updated_pos, name, temp_clr, cons_obj); + } + } + break; + } + + return true; +} + +void afxEA_GuiText::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (text_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxGuiTextData* orig_db = text_data; + text_data = new afxGuiTextData(*orig_db, true); + orig_db->performSubstitutions(text_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_GuiTextDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_GuiTextDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_GuiText; } +}; + +afxEA_GuiTextDesc afxEA_GuiTextDesc::desc; + +bool afxEA_GuiTextDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxGuiTextData) == typeid(*db)); +} + +bool afxEA_GuiTextDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_Light.cpp b/Engine/source/afx/ea/afxEA_Light.cpp new file mode 100644 index 000000000..fbb3c4c42 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_Light.cpp @@ -0,0 +1,62 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "afx/ce/afxLight.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_LightDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_LightDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return 0; } +}; + +afxEA_LightDesc afxEA_LightDesc::desc; + +bool afxEA_LightDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxLightData) == typeid(*db)); +} + +bool afxEA_LightDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_MachineGun.cpp b/Engine/source/afx/ea/afxEA_MachineGun.cpp new file mode 100644 index 000000000..0db257b87 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_MachineGun.cpp @@ -0,0 +1,207 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxProjectile.h" +#include "afx/ce/afxMachineGun.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_MachineGun + +class afxEA_MachineGun : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxMachineGunData* gun_data; + ProjectileData* bullet_data; + + bool shooting; + F32 start_time; + F32 shot_gap; + S32 shot_count; + + void launch_projectile(); + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_MachineGun(); + /*D*/ ~afxEA_MachineGun(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_MachineGun::afxEA_MachineGun() +{ + gun_data = 0; + bullet_data = 0; + shooting = false; + start_time = 0.0f; + shot_count = 0; + shot_gap = 0.2f; +} + +afxEA_MachineGun::~afxEA_MachineGun() +{ + if (gun_data && gun_data->isTempClone()) + delete gun_data; + gun_data = 0; +} + +void afxEA_MachineGun::ea_set_datablock(SimDataBlock* db) +{ + gun_data = dynamic_cast(db); + if (gun_data) + bullet_data = gun_data->projectile_data; +} + +bool afxEA_MachineGun::ea_start() +{ + if (!gun_data) + { + Con::errorf("afxEA_MachineGun::ea_start() -- missing or incompatible datablock."); + return false; + } + if (!bullet_data) + { + Con::errorf("afxEA_MachineGun::ea_start() -- missing or incompatible ProjectileData."); + return false; + } + + do_runtime_substitutions(); + + if (gun_data->rounds_per_minute > 0) + shot_gap = 60.0f/gun_data->rounds_per_minute; + + return true; +} + +bool afxEA_MachineGun::ea_update(F32 dt) +{ + if (!shooting) + { + start_time = elapsed; + shooting = true; + } + else + { + F32 next_shot = start_time + (shot_count+1)*shot_gap; + while (next_shot < elapsed) + { + if (in_scope) + launch_projectile(); + next_shot += shot_gap; + shot_count++; + } + } + + return true; +} + +void afxEA_MachineGun::ea_finish(bool was_stopped) +{ +} + +void afxEA_MachineGun::launch_projectile() +{ + afxProjectile* projectile = new afxProjectile(); + + ProjectileData* next_bullet = bullet_data; + + if (bullet_data->getSubstitutionCount() > 0) + { + next_bullet = new ProjectileData(*bullet_data, true); + bullet_data->performSubstitutions(next_bullet, choreographer, group_index); + } + + projectile->onNewDataBlock(next_bullet, false); + + F32 muzzle_vel = next_bullet->muzzleVelocity; + + afxConstraint* pos_cons = getPosConstraint(); + ShapeBase* src_obj = (pos_cons) ? (dynamic_cast(pos_cons->getSceneObject())) : 0; + + Point3F dir_vec = updated_aim - updated_pos; + dir_vec.normalizeSafe(); + dir_vec *= muzzle_vel; + projectile->init(updated_pos, dir_vec, src_obj); + if (!projectile->registerObject()) + { + delete projectile; + projectile = 0; + Con::errorf("afxEA_MachineGun::launch_projectile() -- projectile failed to register."); + } + if (projectile) + projectile->setDataField(StringTable->insert("afxOwner"), 0, choreographer->getIdString()); +} + +void afxEA_MachineGun::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (gun_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxMachineGunData* orig_db = gun_data; + gun_data = new afxMachineGunData(*orig_db, true); + orig_db->performSubstitutions(gun_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_MachineGunDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_MachineGunDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_MachineGun; } +}; + +afxEA_MachineGunDesc afxEA_MachineGunDesc::desc; + +bool afxEA_MachineGunDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxMachineGunData) == typeid(*db)); +} + +bool afxEA_MachineGunDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_Model.cpp b/Engine/source/afx/ea/afxEA_Model.cpp new file mode 100644 index 000000000..23179ebe4 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_Model.cpp @@ -0,0 +1,255 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "ts/tsShapeInstance.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/afxResidueMgr.h" +#include "afx/ce/afxModel.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_Model -- This is the adapter for afxModel, a lightweight animated model effect. + +class afxEA_Model : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxModelData* model_data; + afxModel* model; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_Model(); + /*D*/ ~afxEA_Model(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + virtual void ea_set_scope_status(bool flag); + virtual void onDeleteNotify(SimObject*); + + virtual void getUpdatedBoxCenter(Point3F& pos); + + virtual TSShape* getTSShape(); + virtual TSShapeInstance* getTSShapeInstance(); + virtual SceneObject* ea_get_scene_object() const; + virtual U32 ea_get_triggers() const; + + virtual U32 setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans); + virtual void resetAnimation(U32 tag); + virtual F32 getAnimClipDuration(const char* clip); +}; + + +afxEA_Model::afxEA_Model() +{ + model_data = 0; + model = 0; +} + +afxEA_Model::~afxEA_Model() +{ + if (model) + model->deleteObject(); + if (model_data && model_data->isTempClone()) + delete model_data; + model_data = 0; +} + +void afxEA_Model::ea_set_datablock(SimDataBlock* db) +{ + model_data = dynamic_cast(db); +} + +bool afxEA_Model::ea_start() +{ + if (!model_data) + { + Con::errorf("afxEA_Model::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + return true; +} + +bool afxEA_Model::ea_update(F32 dt) +{ + if (!model) + { + // create and register effect + model = new afxModel(); + model->onNewDataBlock(model_data, false); + if (!model->registerObject()) + { + delete model; + model = 0; + Con::errorf("afxEA_Model::ea_update() -- effect failed to register."); + return false; + } + deleteNotify(model); + + model->setSequenceRateFactor(datablock->rate_factor/prop_time_factor); + model->setSortPriority(datablock->sort_priority); + } + + if (model) + { + if (do_fades) + { + model->setFadeAmount(fade_value); + } + model->setTransform(updated_xfm); + model->setScale(updated_scale); + } + + return true; +} + +void afxEA_Model::ea_finish(bool was_stopped) +{ + if (!model) + return; + + if (in_scope && ew_timing.residue_lifetime > 0) + { + clearNotify(model); + afxResidueMgr::add(ew_timing.residue_lifetime, ew_timing.residue_fadetime, model); + model = 0; + } + else + { + model->deleteObject(); + model = 0; + } +} + +void afxEA_Model::ea_set_scope_status(bool in_scope) +{ + if (model) + model->setVisibility(in_scope); +} + +void afxEA_Model::onDeleteNotify(SimObject* obj) +{ + if (model == dynamic_cast(obj)) + model = 0; + + Parent::onDeleteNotify(obj); +} + +void afxEA_Model::getUpdatedBoxCenter(Point3F& pos) +{ + if (model) + pos = model->getBoxCenter(); +} + +TSShape* afxEA_Model::getTSShape() +{ + return (model) ? model->getTSShape() : 0; +} + +TSShapeInstance* afxEA_Model::getTSShapeInstance() +{ + return (model) ? model->getTSShapeInstance() : 0; +} + +SceneObject* afxEA_Model::ea_get_scene_object() const +{ + return model; +} + +U32 afxEA_Model::ea_get_triggers() const +{ + TSShapeInstance* shape_inst = model->getTSShapeInstance(); + return (shape_inst) ? shape_inst->getTriggerStateMask() : 0; +} + +void afxEA_Model::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (model_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxModelData* orig_db = model_data; + model_data = new afxModelData(*orig_db, true); + orig_db->performSubstitutions(model_data, choreographer, group_index); + } +} + +U32 afxEA_Model::setAnimClip(const char* clip, F32 pos, F32 rate, F32 trans) +{ + return (model) ? model->setAnimClip(clip, pos, rate, trans) : 0; +} + +void afxEA_Model::resetAnimation(U32 tag) +{ + if (model) + model->resetAnimation(tag); +} + +F32 afxEA_Model::getAnimClipDuration(const char* clip) +{ + return (model) ? model->getAnimClipDuration(clip) : 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_ModelDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_ModelDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_Model; } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_ModelDesc afxEA_ModelDesc::desc; + +bool afxEA_ModelDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxModelData) == typeid(*db)); +} + +bool afxEA_ModelDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_Mooring.cpp b/Engine/source/afx/ea/afxEA_Mooring.cpp new file mode 100644 index 000000000..a4e8743ee --- /dev/null +++ b/Engine/source/afx/ea/afxEA_Mooring.cpp @@ -0,0 +1,188 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxMooring.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_Mooring + +class afxEA_Mooring : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxMooringData* mooring_data; + afxMooring* obj; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_Mooring(); + /*D*/ ~afxEA_Mooring(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_Mooring::afxEA_Mooring() +{ + mooring_data = 0; + obj = 0; +} + +afxEA_Mooring::~afxEA_Mooring() +{ + if (obj) + obj->deleteObject(); + if (mooring_data && mooring_data->isTempClone()) + delete mooring_data; + mooring_data = 0; +} + +void afxEA_Mooring::ea_set_datablock(SimDataBlock* db) +{ + mooring_data = dynamic_cast(db); +} + +bool afxEA_Mooring::ea_start() +{ + if (!mooring_data) + { + Con::errorf("afxEA_Mooring::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + return true; +} + +bool afxEA_Mooring::ea_update(F32 dt) +{ + if (!obj) + { + if (datablock->use_ghost_as_cons_obj && datablock->effect_name != ST_NULLSTRING) + { + obj = new afxMooring(mooring_data->networking, + choreographer->getChoreographerId(), + datablock->effect_name); + } + else + { + obj = new afxMooring(mooring_data->networking, 0, ST_NULLSTRING); + } + + obj->onNewDataBlock(mooring_data, false); + if (!obj->registerObject()) + { + delete obj; + obj = 0; + Con::errorf("afxEA_Mooring::ea_update() -- effect failed to register."); + return false; + } + deleteNotify(obj); + } + + if (obj) + { + obj->setTransform(updated_xfm); + } + + return true; +} + +void afxEA_Mooring::ea_finish(bool was_stopped) +{ +} + +void afxEA_Mooring::onDeleteNotify(SimObject* obj) +{ + if (this->obj == obj) + obj = 0; + + Parent::onDeleteNotify(obj); +} + +void afxEA_Mooring::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (mooring_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxMooringData* orig_db = mooring_data; + mooring_data = new afxMooringData(*orig_db, true); + orig_db->performSubstitutions(mooring_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_MooringDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_MooringDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const; + virtual bool runsOnClient(const afxEffectWrapperData*) const; + + virtual afxEffectWrapper* create() const { return new afxEA_Mooring; } +}; + +afxEA_MooringDesc afxEA_MooringDesc::desc; + +bool afxEA_MooringDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxMooringData) == typeid(*db)); +} + +bool afxEA_MooringDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +bool afxEA_MooringDesc::runsOnServer(const afxEffectWrapperData* ew) const +{ + U8 networking = ((const afxMooringData*)ew->effect_data)->networking; + return ((networking & CLIENT_ONLY) == 0); +} + +bool afxEA_MooringDesc::runsOnClient(const afxEffectWrapperData* ew) const +{ + U8 networking = ((const afxMooringData*)ew->effect_data)->networking; + return ((networking & CLIENT_ONLY) != 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_MultiLight.cpp b/Engine/source/afx/ea/afxEA_MultiLight.cpp new file mode 100644 index 000000000..b42a37cd5 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_MultiLight.cpp @@ -0,0 +1,62 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "afx/ce/afxLight.h" +#include "afx/ce/afxMultiLight.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_MultiLightDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_MultiLightDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return 0; } +}; + +afxEA_MultiLightDesc afxEA_MultiLightDesc::desc; + +bool afxEA_MultiLightDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxMultiLightData) == typeid(*db)); +} + +bool afxEA_MultiLightDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_ParticleEmitter.cpp b/Engine/source/afx/ea/afxEA_ParticleEmitter.cpp new file mode 100644 index 000000000..7f289b893 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_ParticleEmitter.cpp @@ -0,0 +1,337 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#if defined(STOCK_TGE_PARTICLES) +#include "game/fx/particleEngine.h" +#else +#include "afx/ce/afxParticleEmitter.h" +#endif + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ea/afxEA_ParticleEmitter.h" +#include "afx/util/afxParticlePool.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_ParticleEmitter + +afxEA_ParticleEmitter::afxEA_ParticleEmitter() +{ + emitter_data = 0; + emitter = 0; + do_bbox_update = false; +} + +afxEA_ParticleEmitter::~afxEA_ParticleEmitter() +{ + if (emitter) + { + clearNotify(emitter); + emitter->deleteWhenEmpty(); + emitter = 0; + } +} + +void afxEA_ParticleEmitter::ea_set_datablock(SimDataBlock* db) +{ + emitter_data = dynamic_cast(db); +} + +bool afxEA_ParticleEmitter::ea_start() +{ + if (!emitter_data) + { + Con::errorf("afxEA_ParticleEmitter::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + +#if defined(STOCK_TGE_PARTICLES) + emitter = new ParticleEmitter(); + emitter->onNewDataBlock(emitter_data); +#else + afxParticleEmitterData* afx_emitter_db = dynamic_cast(emitter_data); + if (afx_emitter_db) + { + if (dynamic_cast(emitter_data)) + { + afxParticleEmitterVector* pe = new afxParticleEmitterVector(); + pe->onNewDataBlock(afx_emitter_db, false); + pe->setAFXOwner(choreographer); + emitter = pe; + } + else if (dynamic_cast(emitter_data)) + { + afxParticleEmitterCone* pe = new afxParticleEmitterCone(); + pe->onNewDataBlock(afx_emitter_db, false); + pe->setAFXOwner(choreographer); + emitter = pe; + } + else if (dynamic_cast(emitter_data)) + { + afxParticleEmitterPath* pe = new afxParticleEmitterPath(); + pe->onNewDataBlock(afx_emitter_db, false); + pe->setAFXOwner(choreographer); + emitter = pe; + } + else if (dynamic_cast(emitter_data)) + { + afxParticleEmitterDisc* pe = new afxParticleEmitterDisc(); + pe->onNewDataBlock(afx_emitter_db, false); + pe->setAFXOwner(choreographer); + emitter = pe; + } + } + else + { + emitter = new ParticleEmitter(); + emitter->onNewDataBlock(emitter_data, false); + } +#endif + +#if defined(AFX_CAP_PARTICLE_POOLS) + // here we find or create any required particle-pools + if (emitter_data->pool_datablock) + { + afxParticlePool* pool = choreographer->findParticlePool(emitter_data->pool_datablock, emitter_data->pool_index); + if (!pool) + { + afxParticlePoolData* pool_data = emitter_data->pool_datablock; + if (pool_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxParticlePoolData* orig_db = pool_data; + pool_data = new afxParticlePoolData(*orig_db, true); + orig_db->performSubstitutions(pool_data, choreographer, group_index); + } + + pool = new afxParticlePool(); + pool->onNewDataBlock(pool_data, false); + pool->setKeyBlock(emitter_data->pool_datablock, emitter_data->pool_index); + if (!pool->registerObject()) + { + Con::errorf("afxEA_ParticleEmitter::ea_start() -- Failed to register Particle Pool."); + delete pool; + pool = 0; + } + if (pool) + { + pool->setChoreographer(choreographer); + choreographer->registerParticlePool(pool); + } + } + if (pool) + emitter->setPool(pool); + } +#endif + + if (!emitter->registerObject()) + { + delete emitter; + emitter = NULL; + Con::errorf("afxEA_ParticleEmitter::ea_start() -- effect failed to register."); + return false; + } + + if (datablock->forced_bbox.isValidBox()) + { + do_bbox_update = true; + } + + emitter->setSortPriority(datablock->sort_priority); + deleteNotify(emitter); + + return true; +} + +bool afxEA_ParticleEmitter::ea_update(F32 dt) +{ + if (emitter && in_scope) + { + if (do_bbox_update) + { + Box3F bbox = emitter->getObjBox(); + + bbox.minExtents = updated_pos + datablock->forced_bbox.minExtents; + bbox.maxExtents = updated_pos + datablock->forced_bbox.maxExtents; + + emitter->setForcedObjBox(bbox); + emitter->setTransform(emitter->getTransform()); + + if (!datablock->update_forced_bbox) + do_bbox_update = false; + } + + if (do_fades) + emitter->setFadeAmount(fade_value); + + emitter->emitParticlesExt(updated_xfm, updated_pos, Point3F(0.0,0.0,0.0), (U32)(dt*1000)); + } + + return true; +} + +void afxEA_ParticleEmitter::ea_finish(bool was_stopped) +{ + if (arcaneFX::isShutdown()) + return; + + if (emitter) + { + // make sure particles are fully faded. + // note - fully faded particles are not always + // invisible, so they are still kept alive and + // deleted via deleteWhenEmpty(). + if (ew_timing.fade_out_time > 0.0f) + emitter->setFadeAmount(0.0f); + if (dynamic_cast(emitter)) + ((afxParticleEmitter*)emitter)->setAFXOwner(0); + clearNotify(emitter); + emitter->deleteWhenEmpty(); + emitter = 0; + } +} + +void afxEA_ParticleEmitter::do_runtime_substitutions() +{ + bool clone_particles = false; + for (S32 i = 0; i < emitter_data->particleDataBlocks.size(); i++) + { + if (emitter_data->particleDataBlocks[i] && (emitter_data->particleDataBlocks[i]->getSubstitutionCount() > 0)) + { + clone_particles = true; + break; + } + } + + if (clone_particles || (emitter_data->getSubstitutionCount() > 0)) + { + afxParticleEmitterData* afx_emitter_db = dynamic_cast(emitter_data); + if (afx_emitter_db) + { + if (dynamic_cast(emitter_data)) + { + afxParticleEmitterVectorData* orig_db = (afxParticleEmitterVectorData*)emitter_data; + emitter_data = new afxParticleEmitterVectorData(*orig_db, true); + orig_db->performSubstitutions(emitter_data, choreographer, group_index); + } + else if (dynamic_cast(emitter_data)) + { + afxParticleEmitterConeData* orig_db = (afxParticleEmitterConeData*)emitter_data; + emitter_data = new afxParticleEmitterConeData(*orig_db, true); + orig_db->performSubstitutions(emitter_data, choreographer, group_index); + } + else if (dynamic_cast(emitter_data)) + { + afxParticleEmitterPathData* orig_db = (afxParticleEmitterPathData*)emitter_data; + emitter_data = new afxParticleEmitterPathData(*orig_db, true); + orig_db->performSubstitutions(emitter_data, choreographer, group_index); + } + else if (dynamic_cast(emitter_data)) + { + afxParticleEmitterDiscData* orig_db = (afxParticleEmitterDiscData*)emitter_data; + emitter_data = new afxParticleEmitterDiscData(*orig_db, true); + orig_db->performSubstitutions(emitter_data, choreographer, group_index); + } + } + else + { + ParticleEmitterData* orig_db = emitter_data; + emitter_data = new ParticleEmitterData(*orig_db, true); + orig_db->performSubstitutions(emitter_data, choreographer, group_index); + } + + if (clone_particles) + { + for (S32 i = 0; i < emitter_data->particleDataBlocks.size(); i++) + { + if (emitter_data->particleDataBlocks[i] && (emitter_data->particleDataBlocks[i]->getSubstitutionCount() > 0)) + { + // clone the datablock and perform substitutions + ParticleData* orig_db = emitter_data->particleDataBlocks[i]; + emitter_data->particleDataBlocks[i] = new ParticleData(*orig_db, true); + orig_db->performSubstitutions(emitter_data->particleDataBlocks[i], choreographer, group_index); + } + } + } + } +} + +void afxEA_ParticleEmitter::onDeleteNotify(SimObject* obj) +{ + if (emitter == dynamic_cast(obj)) + emitter = 0; + + Parent::onDeleteNotify(obj); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_ParticleEmitterDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_ParticleEmitterDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_ParticleEmitter; } +}; + +afxEA_ParticleEmitterDesc afxEA_ParticleEmitterDesc::desc; + +bool afxEA_ParticleEmitterDesc::testEffectType(const SimDataBlock* db) const +{ +#if defined(STOCK_TGE_PARTICLES) + return (typeid(ParticleEmitterData) == typeid(*db)); +#else + if (typeid(ParticleEmitterData) == typeid(*db)) + return true; + if (typeid(afxParticleEmitterVectorData) == typeid(*db)) + return true; + if (typeid(afxParticleEmitterConeData) == typeid(*db)) + return true; + if (typeid(afxParticleEmitterPathData) == typeid(*db)) + return true; + if (typeid(afxParticleEmitterDiscData) == typeid(*db)) + return true; + + return false; +#endif +} + +bool afxEA_ParticleEmitterDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_ParticleEmitter.h b/Engine/source/afx/ea/afxEA_ParticleEmitter.h new file mode 100644 index 000000000..cf16f2c98 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_ParticleEmitter.h @@ -0,0 +1,67 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_EA_PARTICLE_EMITTER_H_ +#define _AFX_EA_PARTICLE_EMITTER_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/afxEffectWrapper.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_ParticleEmitter + +class ParticleEmitter; +class ParticleEmitterData; + +class afxEA_ParticleEmitter : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + bool do_bbox_update; + ParticleEmitterData* emitter_data; + + void do_runtime_substitutions(); + +public: + ParticleEmitter* emitter; + + /*C*/ afxEA_ParticleEmitter(); + /*D*/ ~afxEA_ParticleEmitter(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + + virtual bool ea_is_enabled() { return true; } + + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + + +#endif // _AFX_EA_PARTICLE_EMITTER_H_ diff --git a/Engine/source/afx/ea/afxEA_PhraseEffect.cpp b/Engine/source/afx/ea/afxEA_PhraseEffect.cpp new file mode 100644 index 000000000..4448e1967 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_PhraseEffect.cpp @@ -0,0 +1,385 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "console/compiler.h" +#include "T3D/player.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxPhrase.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxPhraseEffect.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_PhraseEffect + +class afxEA_PhraseEffect : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxPhraseEffectData* phrase_fx_data; + Vector* active_phrases; + U32 last_trigger_mask; + + Vector _phrases_a; + Vector _phrases_b; + + void grab_constraint_triggers(U32& trigger_mask); + void grab_player_triggers(U32& trigger_mask); + + void do_runtime_substitutions(); + void trigger_new_phrase(); + void update_active_phrases(F32 dt); + void cleanup_finished_phrases(); + +public: + /*C*/ afxEA_PhraseEffect(); + /*D*/ ~afxEA_PhraseEffect(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + + virtual bool ea_is_enabled() { return true; } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_PhraseEffect::afxEA_PhraseEffect() +{ + phrase_fx_data = 0; + active_phrases = &_phrases_a; +} + +afxEA_PhraseEffect::~afxEA_PhraseEffect() +{ + if (phrase_fx_data && phrase_fx_data->isTempClone()) + delete phrase_fx_data; + phrase_fx_data = 0; + + for (S32 i = 0; i < _phrases_a.size(); i++) + { + if (_phrases_a[i]) + delete _phrases_a[i]; + } + + for (S32 i = 0; i < _phrases_b.size(); i++) + { + if (_phrases_b[i]) + delete _phrases_b[i]; + } +} + +void afxEA_PhraseEffect::ea_set_datablock(SimDataBlock* db) +{ + phrase_fx_data = dynamic_cast(db); +} + +bool afxEA_PhraseEffect::ea_start() +{ + if (!phrase_fx_data) + { + Con::errorf("afxEA_PhraseEffect::ea_start() -- missing or incompatible datablock."); + return false; + } + + //last_trigger_mask = choreographer->getTriggerMask(); + last_trigger_mask = 0xffffffff; + + return true; +} + +void afxEA_PhraseEffect::grab_constraint_triggers(U32& trigger_mask) +{ + afxConstraint* pos_cons = getPosConstraint(); + if (pos_cons) + trigger_mask |= pos_cons->getTriggers(); +} + +void afxEA_PhraseEffect::grab_player_triggers(U32& trigger_mask) +{ + afxConstraint* pos_cons = getPosConstraint(); + Player* player = (pos_cons) ? dynamic_cast(pos_cons->getSceneObject()) : 0; + if (player) + { + if (player->isClientObject()) + trigger_mask |= player->getClientEventTriggers(); + else + trigger_mask |= player->getServerEventTriggers(); + } +} + +bool afxEA_PhraseEffect::ea_update(F32 dt) +{ + if (fade_value >= 1.0f) + { + // + // Choreographer Triggers: + // These triggers can come from the choreographer owning this effect. + // They must be set explicitly by calls to afxChoreographer + // console-methods, setTriggerBit(), or clearTriggerBit(). + // + U32 trigger_mask = (phrase_fx_data->no_choreographer_trigs) ? 0 : choreographer->getTriggerMask(); + + // + // Constraint Triggers: + // These triggers can come from the position contraint if it is: + // -- a TSStatic or ShapeBase derived object with dts triggers. + // -- a trigger producing effect such as afxModel. + // + if (!phrase_fx_data->no_cons_trigs) + grab_constraint_triggers(trigger_mask); + + // + // Player Triggers: + // These triggers can come from the position contraint if it is + // a Player or Player-derived object. + // + if (!phrase_fx_data->no_player_trigs) + grab_player_triggers(trigger_mask); + + // any change in the triggers? + if (trigger_mask != last_trigger_mask) + { + if (phrase_fx_data->phrase_type == afxPhraseEffectData::PHRASE_CONTINUOUS) + { + bool last_state, new_state; + if (phrase_fx_data->match_type == afxPhraseEffectData::MATCH_ANY) + { + last_state = ((last_trigger_mask & phrase_fx_data->trigger_mask) != 0); + new_state = ((trigger_mask & phrase_fx_data->trigger_mask) != 0); + } + else + { + last_state = ((last_trigger_mask & phrase_fx_data->trigger_mask) == phrase_fx_data->trigger_mask); + new_state = ((trigger_mask & phrase_fx_data->trigger_mask) == phrase_fx_data->trigger_mask); + } + if (new_state != last_state) + { + bool state_on = phrase_fx_data->match_state & afxPhraseEffectData::STATE_ON; + if (new_state == state_on) // start trigger + { + trigger_new_phrase(); + } + else // stop trigger + { + for (S32 i = 0; i < active_phrases->size(); i++) + { + (*active_phrases)[i]->stop(life_elapsed); + } + } + } + } + else // if (phrase_fx_data->phrase_type == afxPhraseEffectData::PHRASE_TRIGGERED) + { + bool did_trigger = false; + U32 changed_bits = (last_trigger_mask ^ trigger_mask); + + if ((phrase_fx_data->match_state & afxPhraseEffectData::STATE_ON) != 0) + { + // check for trigger bits that just switched to on state + U32 changed_on_bits = (changed_bits & trigger_mask); + if (phrase_fx_data->match_type == afxPhraseEffectData::MATCH_ANY) + did_trigger = ((changed_on_bits & phrase_fx_data->trigger_mask) != 0); + else + did_trigger = ((changed_on_bits & phrase_fx_data->trigger_mask) == phrase_fx_data->trigger_mask); + } + + if (!did_trigger && ((phrase_fx_data->match_state & afxPhraseEffectData::STATE_OFF) != 0)) + { + // check for trigger bits that just switched to off state + U32 changed_off_bits = (changed_bits & last_trigger_mask); + if (phrase_fx_data->match_type == afxPhraseEffectData::MATCH_ANY) + did_trigger = ((changed_off_bits & phrase_fx_data->trigger_mask) != 0); + else + did_trigger = ((changed_off_bits & phrase_fx_data->trigger_mask) == phrase_fx_data->trigger_mask); + } + + if (did_trigger) + trigger_new_phrase(); + } + + last_trigger_mask = trigger_mask; + } + } + + update_active_phrases(dt); + + cleanup_finished_phrases(); + + return true; +} + +void afxEA_PhraseEffect::ea_finish(bool was_stopped) +{ + for (S32 i = 0; i < active_phrases->size(); i++) + { + (*active_phrases)[i]->stop(life_elapsed); + } +} + +void afxEA_PhraseEffect::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (phrase_fx_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxPhraseEffectData* orig_db = phrase_fx_data; + phrase_fx_data = new afxPhraseEffectData(*orig_db, true); + orig_db->performSubstitutions(phrase_fx_data, choreographer, group_index); + } +} + +void afxEA_PhraseEffect::trigger_new_phrase() +{ + //afxPhrase* phrase = new afxPhrase(choreographer->isServerObject(), /*willStop=*/false); + bool will_stop = phrase_fx_data->phrase_type == afxPhraseEffectData::PHRASE_CONTINUOUS; + afxPhrase* phrase = new afxPhrase(choreographer->isServerObject(), will_stop); + phrase->init(phrase_fx_data->fx_list, datablock->ewd_timing.lifetime, choreographer, time_factor, phrase_fx_data->n_loops, group_index); + phrase->start(0, 0); + if (phrase->isEmpty()) + { + delete phrase; + return; + } + + if (phrase_fx_data->on_trig_cmd != ST_NULLSTRING) + { + char obj_str[32]; + dStrcpy(obj_str, Con::getIntArg(choreographer->getId())); + + char index_str[32]; + dStrcpy(index_str, Con::getIntArg(group_index)); + + char buffer[1024]; + char* b = buffer; + const char* v = phrase_fx_data->on_trig_cmd; + while (*v != '\0') + { + if (v[0] == '%' && v[1] == '%') + { + const char* s = obj_str; + while (*s != '\0') + { + b[0] = s[0]; + b++; + s++; + } + v += 2; + } + else if (v[0] == '#' && v[1] == '#') + { + const char* s = index_str; + while (*s != '\0') + { + b[0] = s[0]; + b++; + s++; + } + v += 2; + } + else + { + b[0] = v[0]; + b++; + v++; + } + } + b[0] = '\0'; + + Compiler::gSyntaxError = false; + //Con::errorf("EVAL [%s]", avar("%s;", buffer)); + Con::evaluate(avar("%s;", buffer), false, 0); + if (Compiler::gSyntaxError) + { + Con::errorf("onTriggerCommand \"%s\" -- syntax error", phrase_fx_data->on_trig_cmd); + Compiler::gSyntaxError = false; + } + } + + active_phrases->push_back(phrase); +} + +void afxEA_PhraseEffect::update_active_phrases(F32 dt) +{ + for (S32 i = 0; i < active_phrases->size(); i++) + { + afxPhrase* phrase = (*active_phrases)[i]; + if (phrase->expired(life_elapsed)) + phrase->recycle(life_elapsed); + phrase->update(dt, life_elapsed); + } +} + +void afxEA_PhraseEffect::cleanup_finished_phrases() +{ + Vector* surviving_phrases = (active_phrases == &_phrases_a) ? &_phrases_b : &_phrases_a; + + surviving_phrases->clear(); + for (S32 i = 0; i < active_phrases->size(); i++) + { + afxPhrase* phrase = (*active_phrases)[i]; + if (!phrase->isEmpty()) + surviving_phrases->push_back(phrase); + else + delete phrase; + } + + active_phrases->clear(); + active_phrases = surviving_phrases; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_PhraseEffectDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_PhraseEffectDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_PhraseEffect; } +}; + +afxEA_PhraseEffectDesc afxEA_PhraseEffectDesc::desc; + +bool afxEA_PhraseEffectDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxPhraseEffectData) == typeid(*db)); +} + +bool afxEA_PhraseEffectDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_PhysicalZone.cpp b/Engine/source/afx/ea/afxEA_PhysicalZone.cpp new file mode 100644 index 000000000..02012c358 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_PhysicalZone.cpp @@ -0,0 +1,226 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "T3D/physicalZone.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxPhysicalZone.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_PhysicalZone + +class afxEA_PhysicalZone : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxPhysicalZoneData* zone_data; + PhysicalZone* physical_zone; + SceneObject* cons_obj; + + void do_runtime_substitutions(); + void set_cons_object(SceneObject*); + +public: + /*C*/ afxEA_PhysicalZone(); + /*D*/ ~afxEA_PhysicalZone(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + virtual void ea_set_scope_status(bool flag); + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_PhysicalZone::afxEA_PhysicalZone() +{ + zone_data = 0; + physical_zone = 0; + cons_obj = 0; +} + +afxEA_PhysicalZone::~afxEA_PhysicalZone() +{ + if (physical_zone) + physical_zone->deleteObject(); + if (zone_data && zone_data->isTempClone()) + delete zone_data; + zone_data = 0; +} + +void afxEA_PhysicalZone::ea_set_datablock(SimDataBlock* db) +{ + zone_data = dynamic_cast(db); +} + +bool afxEA_PhysicalZone::ea_start() +{ + if (!zone_data) + { + Con::errorf("afxEA_PhysicalZone::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + return true; +} + +bool afxEA_PhysicalZone::ea_update(F32 dt) +{ + if (!physical_zone) + { + // create and register effect + physical_zone = new PhysicalZone(); + physical_zone->mVelocityMod = zone_data->mVelocityMod; + physical_zone->mGravityMod = zone_data->mGravityMod; + physical_zone->mAppliedForce = zone_data->mAppliedForce; + physical_zone->force_type = zone_data->force_type; + physical_zone->orient_force = zone_data->orient_force; + physical_zone->setField("polyhedron", zone_data->mPolyhedron); + + if (!physical_zone->registerObject()) + { + delete physical_zone; + physical_zone = 0; + Con::errorf("afxEA_PhysicalZone::ea_update() -- effect failed to register."); + return false; + } + deleteNotify(physical_zone); + physical_zone->activate(); + } + + if (physical_zone) + { + if (zone_data->exclude_cons_obj) + { + afxConstraint* pos_constraint = getPosConstraint(); + set_cons_object((pos_constraint) ? pos_constraint->getSceneObject() : 0); + } + + if (do_fades) + physical_zone->setFadeAmount(fade_value); + physical_zone->setTransform(updated_xfm); + } + + return true; +} + +void afxEA_PhysicalZone::ea_finish(bool was_stopped) +{ + if (physical_zone) + { + set_cons_object(0); + physical_zone->deleteObject(); + physical_zone = 0; + } +} + +void afxEA_PhysicalZone::ea_set_scope_status(bool in_scope) +{ + if (physical_zone) + { + if (in_scope && !physical_zone->isActive()) + physical_zone->activate(); + else if (!in_scope && physical_zone->isActive()) + physical_zone->deactivate(); + } +} + +void afxEA_PhysicalZone::onDeleteNotify(SimObject* obj) +{ + if (physical_zone == dynamic_cast(obj)) + physical_zone = 0; + + Parent::onDeleteNotify(obj); +} + +void afxEA_PhysicalZone::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (zone_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxPhysicalZoneData* orig_db = zone_data; + zone_data = new afxPhysicalZoneData(*orig_db, true); + orig_db->performSubstitutions(zone_data, choreographer, group_index); + } +} + +void afxEA_PhysicalZone::set_cons_object(SceneObject* new_obj) +{ + if (cons_obj == new_obj) + return; + + if (cons_obj) + { + physical_zone->unregisterExcludedObject(cons_obj); + //clearNotify(shape); + } + + cons_obj = new_obj; + + if (cons_obj) + { + //deleteNotify(shape); + physical_zone->registerExcludedObject(cons_obj); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_PhysicalZoneDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_PhysicalZoneDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_PhysicalZone; } +}; + +afxEA_PhysicalZoneDesc afxEA_PhysicalZoneDesc::desc; + +bool afxEA_PhysicalZoneDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxPhysicalZoneData) == typeid(*db)); +} + +bool afxEA_PhysicalZoneDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_PlayerMovement.cpp b/Engine/source/afx/ea/afxEA_PlayerMovement.cpp new file mode 100644 index 000000000..03aac77b2 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_PlayerMovement.cpp @@ -0,0 +1,170 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "T3D/player.h" + +#include "afx/afxChoreographer.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/ce/afxPlayerMovement.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_PlayerMovement + +class afxEA_PlayerMovement : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxPlayerMovementData* movement_data; + U32 tag; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_PlayerMovement(); + /*C*/ ~afxEA_PlayerMovement(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_PlayerMovement::afxEA_PlayerMovement() +{ + movement_data = 0; + tag = 0; +} + +afxEA_PlayerMovement::~afxEA_PlayerMovement() +{ + if (movement_data && movement_data->isTempClone()) + delete movement_data; + movement_data = 0; +} + +void afxEA_PlayerMovement::ea_set_datablock(SimDataBlock* db) +{ + movement_data = dynamic_cast(db); +} + +bool afxEA_PlayerMovement::ea_start() +{ + if (!movement_data) + { + Con::errorf("afxEA_PlayerMovement::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + tag = 0; + + afxConstraint* pos_cons = getPosConstraint(); + if (!pos_cons) + { + Con::warnf("afxEA_PlayerMovement::ea_start() -- missing position constraint."); + return false; + } + + Player* player = dynamic_cast(pos_cons->getSceneObject()); + if (!player) + { + Con::warnf("afxEA_PlayerMovement::ea_start() -- position constraint is not a Player."); + return false; + } + + // setup player overrides + if (movement_data->hasMovementOverride()) + tag = player->setMovementOverride(movement_data->speed_bias, &movement_data->movement, movement_data->movement_op); + else + tag = player->setMovementOverride(movement_data->speed_bias); + + return true; +} + +bool afxEA_PlayerMovement::ea_update(F32 dt) +{ + return true; +} + +void afxEA_PlayerMovement::ea_finish(bool was_stopped) +{ + afxConstraint* pos_cons = getPosConstraint(); + if (!pos_cons) + return; + + Player* player = dynamic_cast(pos_cons->getSceneObject()); + if (!player) + return; + + // restore player overrides + player->restoreMovement(tag); +} + +void afxEA_PlayerMovement::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (movement_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxPlayerMovementData* orig_db = movement_data; + movement_data = new afxPlayerMovementData(*orig_db, true); + orig_db->performSubstitutions(movement_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_PlayerMovementDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_PlayerMovementDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_PlayerMovement; } +}; + +afxEA_PlayerMovementDesc afxEA_PlayerMovementDesc::desc; + +bool afxEA_PlayerMovementDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxPlayerMovementData) == typeid(*db)); +} + +bool afxEA_PlayerMovementDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_PlayerPuppet.cpp b/Engine/source/afx/ea/afxEA_PlayerPuppet.cpp new file mode 100644 index 000000000..08c446956 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_PlayerPuppet.cpp @@ -0,0 +1,184 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "T3D/player.h" + +#include "afx/afxChoreographer.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/ce/afxPlayerPuppet.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_PlayerPuppet + +class afxEA_PlayerPuppet : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxPlayerPuppetData* mover_data; + afxConstraint* obj_cons; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_PlayerPuppet(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + + virtual void getUnconstrainedPosition(Point3F& pos); + virtual void getUnconstrainedTransform(MatrixF& xfm); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_PlayerPuppet::afxEA_PlayerPuppet() +{ + mover_data = 0; + obj_cons = 0; +} + +void afxEA_PlayerPuppet::ea_set_datablock(SimDataBlock* db) +{ + mover_data = dynamic_cast(db); +} + +bool afxEA_PlayerPuppet::ea_start() +{ + if (!mover_data) + { + Con::errorf("afxEA_PlayerPuppet::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + afxConstraintID obj_id = cons_mgr->getConstraintId(mover_data->obj_def); + obj_cons = cons_mgr->getConstraint(obj_id); + + Player* player = dynamic_cast((obj_cons) ? obj_cons->getSceneObject() : 0); + if (player) + player->ignore_updates = true; + + return true; +} + +bool afxEA_PlayerPuppet::ea_update(F32 dt) +{ + SceneObject* obj = (obj_cons) ? obj_cons->getSceneObject() : 0; + + if (obj && in_scope) + { + obj->setTransform(updated_xfm); + } + + return true; +} + +void afxEA_PlayerPuppet::ea_finish(bool was_stopped) +{ + Player* player = dynamic_cast((obj_cons) ? obj_cons->getSceneObject() : 0); + if (player) + { + player->resetContactTimer(); + player->ignore_updates = false; + } +} + +void afxEA_PlayerPuppet::getUnconstrainedPosition(Point3F& pos) +{ + SceneObject* obj = (obj_cons) ? obj_cons->getSceneObject() : 0; + if (obj) + pos = obj->getRenderPosition(); + else + pos.zero(); +} + +void afxEA_PlayerPuppet::getUnconstrainedTransform(MatrixF& xfm) +{ + SceneObject* obj = (obj_cons) ? obj_cons->getSceneObject() : 0; + if (obj) + xfm = obj->getRenderTransform(); + else + xfm.identity(); +} + +void afxEA_PlayerPuppet::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (mover_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxPlayerPuppetData* orig_db = mover_data; + mover_data = new afxPlayerPuppetData(*orig_db, true); + orig_db->performSubstitutions(mover_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_PlayerPuppetDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_PlayerPuppetDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const; + virtual bool runsOnClient(const afxEffectWrapperData*) const; + + virtual afxEffectWrapper* create() const { return new afxEA_PlayerPuppet; } +}; + +afxEA_PlayerPuppetDesc afxEA_PlayerPuppetDesc::desc; + +bool afxEA_PlayerPuppetDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxPlayerPuppetData) == typeid(*db)); +} + +bool afxEA_PlayerPuppetDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +bool afxEA_PlayerPuppetDesc::runsOnServer(const afxEffectWrapperData* ew) const +{ + U8 networking = ((const afxPlayerPuppetData*)ew->effect_data)->networking; + return ((networking & (SERVER_ONLY | SERVER_AND_CLIENT)) != 0); +} + +bool afxEA_PlayerPuppetDesc::runsOnClient(const afxEffectWrapperData* ew) const +{ + U8 networking = ((const afxPlayerPuppetData*)ew->effect_data)->networking; + return ((networking & (CLIENT_ONLY | SERVER_AND_CLIENT)) != 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_PointLight_T3D.cpp b/Engine/source/afx/ea/afxEA_PointLight_T3D.cpp new file mode 100644 index 000000000..3035da8d3 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_PointLight_T3D.cpp @@ -0,0 +1,289 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "T3D/pointLight.h" + +#include "afx/ce/afxPointLight_T3D.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_T3DPointLight + +class PointLightProxy; + +class afxEA_T3DPointLight : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxT3DPointLightData* light_data; + PointLightProxy* light; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_T3DPointLight(); + /*D*/ ~afxEA_T3DPointLight(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + virtual void ea_set_scope_status(bool flag); + virtual void onDeleteNotify(SimObject*); + virtual void getBaseColor(LinearColorF& color); + + virtual bool ea_is_enabled() { return true; } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class PointLightProxy : public PointLight +{ + F32 fade_amt; + +public: + PointLightProxy() { fade_amt = 1.0f; } + + void force_ghost() + { + mNetFlags.clear(Ghostable | ScopeAlways); + mNetFlags.set(IsGhost); + } + + void setFadeAmount(F32 fade_amt) + { + this->fade_amt = fade_amt; + mLight->setBrightness(mBrightness*fade_amt); + } + + void updateTransform(const MatrixF& xfm) + { + mLight->setTransform(xfm); + LightBase::setTransform(xfm); + } + + void initWithDataBlock(const afxT3DPointLightData* db) + { + mRadius = db->mRadius; + + mColor = db->mColor; + mBrightness = db->mBrightness; + mCastShadows = db->mCastShadows; + mPriority = db->mPriority; + mFlareData = db->mFlareData; + mAnimationData = db->mAnimationData; + mAnimState.active = (mAnimationData != 0); + + mLocalRenderViz = db->mLocalRenderViz; + + mLight->setType( LightInfo::Point ); + mLight->setBrightness( db->mBrightness ); + mLight->setRange( db->mRadius ); + mLight->setColor( db->mColor ); + mLight->setCastShadows( db->mCastShadows ); + mLight->setPriority( db->mPriority ); + + // Update the bounds and scale to fit our light. + mObjBox.minExtents.set( -1, -1, -1 ); + mObjBox.maxExtents.set( 1, 1, 1 ); + mObjScale.set( db->mRadius, db->mRadius, db->mRadius ); + + //_conformLights(); + } + + void setLiveColor(const LinearColorF& live_color) + { + mLight->setColor(live_color); + } + + void submitLights(LightManager* lm, bool staticLighting) + { + if (mAnimState.active && mAnimationData && fade_amt < 1.0f) + { + F32 mBrightness_save = mBrightness; + mBrightness *= fade_amt; + PointLight::submitLights(lm, staticLighting); + mBrightness = mBrightness_save; + return; + } + + PointLight::submitLights(lm, staticLighting); + } + +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_T3DPointLight::afxEA_T3DPointLight() +{ + light_data = 0; + light = 0; +} + +afxEA_T3DPointLight::~afxEA_T3DPointLight() +{ + if (light) + light->deleteObject(); + if (light_data && light_data->isTempClone()) + delete light_data; + light_data = 0; +} + +void afxEA_T3DPointLight::ea_set_datablock(SimDataBlock* db) +{ + light_data = dynamic_cast(db); +} + +bool afxEA_T3DPointLight::ea_start() +{ + if (!light_data) + { + Con::errorf("afxEA_T3DPointLight::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + // create and register effect + light = new PointLightProxy(); + light->force_ghost(); + if (!light->registerObject()) + { + delete light; + light = 0; + Con::errorf("afxEA_T3DPointLight::ea_update() -- effect failed to register."); + return false; + } + deleteNotify(light); + + light->initWithDataBlock(light_data); + + return true; +} + +bool afxEA_T3DPointLight::ea_update(F32 dt) +{ + if (light) + { +#if 0 // AFX_T3D_DISABLED + // With sgLightObject lights, the following code block would hook + // the constraint object up to the light in case the light was + // configured to exclude it from flare occusions. The code remains + // here in case we need to implement the same feature for T3D light. + + afxConstraint* pos_cons = getPosConstraint(); + SceneObject* cons_obj = (pos_cons) ? pos_cons->getSceneObject() : 0; + light->setConstraintObject(cons_obj); +#endif + + light->setLiveColor(updated_color); + + if (do_fades) + light->setFadeAmount(fade_value*updated_scale.x); + + light->updateTransform(updated_xfm); + + // scale should not be updated this way. It messes up the culling. + //light->setScale(updated_scale); + } + + return true; +} + +void afxEA_T3DPointLight::ea_finish(bool was_stopped) +{ + if (light) + { + light->deleteObject(); + light = 0; + } +} + +void afxEA_T3DPointLight::ea_set_scope_status(bool in_scope) +{ + if (light) + light->setLightEnabled(in_scope); +} + +void afxEA_T3DPointLight::onDeleteNotify(SimObject* obj) +{ + if (light == dynamic_cast(obj)) + light = 0; + + Parent::onDeleteNotify(obj); +} + +void afxEA_T3DPointLight::getBaseColor(LinearColorF& color) +{ + if (light_data) + color = light_data->mColor; +} + +void afxEA_T3DPointLight::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (light_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxT3DPointLightData* orig_db = light_data; + light_data = new afxT3DPointLightData(*orig_db, true); + orig_db->performSubstitutions(light_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_T3DPointLightDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_T3DPointLightDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_T3DPointLight; } +}; + +afxEA_T3DPointLightDesc afxEA_T3DPointLightDesc::desc; + +bool afxEA_T3DPointLightDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxT3DPointLightData) == typeid(*db)); +} + +bool afxEA_T3DPointLightDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_Projectile.cpp b/Engine/source/afx/ea/afxEA_Projectile.cpp new file mode 100644 index 000000000..104d5abb2 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_Projectile.cpp @@ -0,0 +1,315 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "lighting/lightInfo.h" +#include "T3D/projectile.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxProjectile.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_Projectile + +class afxEA_Projectile : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + ProjectileData* projectile_data; + afxProjectileData* afx_projectile_data; + afxProjectile* projectile; + bool launched; + bool impacted; + bool projectile_done; + afxConstraint* launch_cons; + Point3F launch_dir_bias; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_Projectile(); + /*D*/ ~afxEA_Projectile(); + + virtual bool isDone(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_Projectile::afxEA_Projectile() +{ + projectile_data = 0; + afx_projectile_data = 0; + projectile = 0; + launched = false; + impacted = false; + projectile_done = false; + launch_cons = 0; + launch_dir_bias.zero(); +} + +afxEA_Projectile::~afxEA_Projectile() +{ + if (projectile) + clearNotify(projectile); + //if (projectile_data && projectile_data->isTempClone()) + // delete projectile_data; + projectile_data = 0; + afx_projectile_data = 0; +} + +bool afxEA_Projectile::isDone() +{ + return (datablock->use_as_cons_obj || datablock->use_ghost_as_cons_obj) ? projectile_done : impacted; +} + +void afxEA_Projectile::ea_set_datablock(SimDataBlock* db) +{ + projectile_data = dynamic_cast(db); + afx_projectile_data = dynamic_cast(projectile_data); +} + +bool afxEA_Projectile::ea_start() +{ + if (!projectile_data) + { + Con::errorf("afxEA_Projectile::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + if (!afx_projectile_data) + { + projectile = new afxProjectile(); + } + else + { + if (datablock->use_ghost_as_cons_obj && datablock->effect_name != ST_NULLSTRING) + projectile = new afxProjectile(afx_projectile_data->networking, choreographer->getChoreographerId(), datablock->effect_name); + else + projectile = new afxProjectile(afx_projectile_data->networking, 0, ST_NULLSTRING); + projectile->ignoreSourceTimeout = afx_projectile_data->ignore_src_timeout; + if (afx_projectile_data->override_collision_masks) + { + projectile->dynamicCollisionMask = afx_projectile_data->dynamicCollisionMask; + projectile->staticCollisionMask = afx_projectile_data->staticCollisionMask; + } + afxConstraintID launch_pos_id = cons_mgr->getConstraintId(afx_projectile_data->launch_pos_def); + launch_cons = cons_mgr->getConstraint(launch_pos_id); + launch_dir_bias = afx_projectile_data->launch_dir_bias; + } + + projectile->onNewDataBlock(projectile_data, false); + + return true; +} + +bool afxEA_Projectile::ea_update(F32 dt) +{ + if (!launched && projectile) + { + if (in_scope) + { + afxConstraint* pos_cons = getPosConstraint(); + ShapeBase* src_obj = (pos_cons) ? (dynamic_cast(pos_cons->getSceneObject())) : 0; + + F32 muzzle_vel = projectile_data->muzzleVelocity; + + Point3F dir_vec; + if (afx_projectile_data) + { + switch (afx_projectile_data->launch_dir_method) + { + case afxProjectileData::OrientConstraint: + dir_vec.set(0,0,1); + updated_xfm.mulV(dir_vec); + break; + case afxProjectileData::LaunchDirField: + dir_vec.set(0,0,1); + break; + case afxProjectileData::TowardPos2Constraint: + default: + dir_vec = updated_aim - updated_pos; + break; + } + } + else + dir_vec = updated_aim - updated_pos; + + dir_vec.normalizeSafe(); + if (!launch_dir_bias.isZero()) + { + dir_vec += launch_dir_bias; + dir_vec.normalizeSafe(); + } + dir_vec *= muzzle_vel; + + Point3F launch_pos; + if (launch_cons && launch_cons->getPosition(launch_pos)) + { + ShapeBase* launch_obj = (launch_cons) ? (dynamic_cast(launch_cons->getSceneObject())) : 0; + projectile->init(launch_pos, dir_vec, (launch_obj) ? launch_obj : src_obj); + } + else + projectile->init(updated_pos, dir_vec, src_obj); + + if (!projectile->registerObject()) + { + delete projectile; + projectile = 0; + Con::errorf("afxEA_Projectile::ea_update() -- effect failed to register."); + return false; + } + + deleteNotify(projectile); + + if (projectile) + projectile->setDataField(StringTable->insert("afxOwner"), 0, choreographer->getIdString()); + + } + launched = true; + } + + if (launched && projectile) + { + if (in_scope) + { + updated_xfm = projectile->getRenderTransform(); + updated_xfm.getColumn(3, &updated_pos); + } + } + + return true; +} + +void afxEA_Projectile::ea_finish(bool was_stopped) +{ + if (projectile) + { + clearNotify(projectile); + projectile = 0; + } + launched = false; + impacted = false; +} + +void afxEA_Projectile::onDeleteNotify(SimObject* obj) +{ + // projectile deleted? + Projectile* del_projectile = dynamic_cast(obj); + if (del_projectile == projectile) + { + projectile = NULL; + projectile_done = true; + } +} + +void afxEA_Projectile::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (projectile_data->getSubstitutionCount() > 0) + { + if (typeid(afxProjectileData) == typeid(*projectile_data)) + { + afxProjectileData* orig_db = (afxProjectileData*)projectile_data; + afx_projectile_data = new afxProjectileData(*orig_db, true); + projectile_data = afx_projectile_data; + orig_db->performSubstitutions(projectile_data, choreographer, group_index); + } + else + { + // clone the datablock and perform substitutions + ProjectileData* orig_db = projectile_data; + afx_projectile_data = 0; + projectile_data = new ProjectileData(*orig_db, true); + orig_db->performSubstitutions(projectile_data, choreographer, group_index); + } + } +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_ProjectileDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_ProjectileDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const; + virtual bool runsOnClient(const afxEffectWrapperData*) const; + + virtual afxEffectWrapper* create() const { return new afxEA_Projectile; } +}; + +afxEA_ProjectileDesc afxEA_ProjectileDesc::desc; + +bool afxEA_ProjectileDesc::testEffectType(const SimDataBlock* db) const +{ + if (typeid(ProjectileData) == typeid(*db)) + return true; + if (typeid(afxProjectileData) == typeid(*db)) + return true; + return false; +} + +bool afxEA_ProjectileDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return ((ew->use_as_cons_obj || ew->use_ghost_as_cons_obj) && timing.lifetime < 0); +} + +bool afxEA_ProjectileDesc::runsOnServer(const afxEffectWrapperData* ew) const +{ + afxProjectileData* afx_projectile_data = dynamic_cast(ew->effect_data); + if (!afx_projectile_data) + return true; + + U8 networking = ((const afxProjectileData*)ew->effect_data)->networking; + return ((networking & CLIENT_ONLY) == 0); +} + +bool afxEA_ProjectileDesc::runsOnClient(const afxEffectWrapperData* ew) const +{ + afxProjectileData* afx_projectile_data = dynamic_cast(ew->effect_data); + if (!afx_projectile_data) + return false; + + U8 networking = ((const afxProjectileData*)ew->effect_data)->networking; + return ((networking & CLIENT_ONLY) != 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_ScriptEvent.cpp b/Engine/source/afx/ea/afxEA_ScriptEvent.cpp new file mode 100644 index 000000000..ca5de9966 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_ScriptEvent.cpp @@ -0,0 +1,145 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxScriptEvent.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_ScriptEvent + +class afxEA_ScriptEvent : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxScriptEventData* script_data; + bool ran_script; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_ScriptEvent(); + /*D*/ ~afxEA_ScriptEvent(); + + virtual bool isDone() { return ran_script; } + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_ScriptEvent::afxEA_ScriptEvent() +{ + script_data = 0; + ran_script = false; +} + +afxEA_ScriptEvent::~afxEA_ScriptEvent() +{ + if (script_data && script_data->isTempClone()) + delete script_data; + script_data = 0; +} + +void afxEA_ScriptEvent::ea_set_datablock(SimDataBlock* db) +{ + script_data = dynamic_cast(db); +} + +bool afxEA_ScriptEvent::ea_start() +{ + if (!script_data) + { + Con::errorf("afxEA_ScriptEvent::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + ran_script = (script_data->method_name == ST_NULLSTRING); + + return true; +} + +bool afxEA_ScriptEvent::ea_update(F32 dt) +{ + if (!ran_script && choreographer != NULL) + { + afxConstraint* pos_constraint = getPosConstraint(); + choreographer->executeScriptEvent(script_data->method_name, pos_constraint, updated_xfm, + script_data->script_data); + ran_script = true; + } + + return true; +} + +void afxEA_ScriptEvent::ea_finish(bool was_stopped) +{ + ran_script = false; +} + +void afxEA_ScriptEvent::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (script_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxScriptEventData* orig_db = script_data; + script_data = new afxScriptEventData(*orig_db, true); + orig_db->performSubstitutions(script_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_ScriptEventDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_ScriptEventDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const { return false; } + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return false; } + virtual bool isPositional(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_ScriptEvent; } +}; + +afxEA_ScriptEventDesc afxEA_ScriptEventDesc::desc; + +bool afxEA_ScriptEventDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxScriptEventData) == typeid(*db)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_Sound.cpp b/Engine/source/afx/ea/afxEA_Sound.cpp new file mode 100644 index 000000000..3b7f69a88 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_Sound.cpp @@ -0,0 +1,214 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "sfx/sfxSystem.h" +#include "sfx/sfxSource.h" +#include "sfx/sfxProfile.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_Sound + +class afxEA_Sound : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + SFXProfile* sound_prof; + SFXDescription* sound_desc; + SFXSource* sound_handle; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_Sound(); + /*D*/ ~afxEA_Sound(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + + virtual bool ea_is_enabled() { return true; } + + virtual void onDeleteNotify(SimObject*); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_Sound::afxEA_Sound() +{ + sound_prof = 0; + sound_desc = 0; + sound_handle = 0; +} + +afxEA_Sound::~afxEA_Sound() +{ + if (sound_prof && sound_prof->isTempClone()) + delete sound_prof; + sound_prof = 0; + sound_desc = 0; + sound_handle = 0; +} + +void afxEA_Sound::ea_set_datablock(SimDataBlock* db) +{ + sound_prof = dynamic_cast(db); + if (sound_prof) + sound_desc = sound_prof->getDescription(); +} + +bool afxEA_Sound::ea_start() +{ + if (!sound_prof) + { + Con::errorf("afxEA_Sound::ea_start() -- missing or incompatible AudioProfile."); + return false; + } + if (!sound_desc) + { + Con::errorf("afxEA_Sound::ea_start() -- missing or incompatible AudioDescriptor."); + return false; + } + + do_runtime_substitutions(); + + return true; +} + +bool afxEA_Sound::ea_update(F32 dt) +{ + if (!sound_handle) + { + sound_handle = SFX->createSource(sound_prof, &updated_xfm, 0); + if (sound_handle) + sound_handle->play(); + } + + if (sound_handle) + { + sound_handle->setTransform(updated_xfm); + sound_handle->setVolume((in_scope) ? updated_scale.x*fade_value : 0.0f); + deleteNotify(sound_handle); + } + + return true; +} + +void afxEA_Sound::ea_finish(bool was_stopped) +{ + if (sound_handle) + { + sound_handle->stop(); + SFX_DELETE(sound_handle); + } +} + +void afxEA_Sound::do_runtime_substitutions() +{ + sound_prof = sound_prof->cloneAndPerformSubstitutions(choreographer, group_index); + sound_desc = sound_prof->getDescription(); +} + +void afxEA_Sound::onDeleteNotify(SimObject* obj) +{ + if (sound_handle == dynamic_cast(obj)) + sound_handle = 0; + + Parent::onDeleteNotify(obj); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_SoundDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_SoundDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + virtual void prepEffect(afxEffectWrapperData*) const; + + virtual afxEffectWrapper* create() const { return new afxEA_Sound; } +}; + +afxEA_SoundDesc afxEA_SoundDesc::desc; + +bool afxEA_SoundDesc::testEffectType(const SimDataBlock* db) const +{ + // dynamic cast used here so that both SFXProfile and AudioProfile match + return (dynamic_cast(db) != 0); +} + +bool afxEA_SoundDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + SFXDescription* desc = ((SFXProfile*)ew->effect_data)->getDescription(); + return (desc && desc->mIsLooping) ? (timing.lifetime < 0) : false; +} + +void afxEA_SoundDesc::prepEffect(afxEffectWrapperData* ew) const +{ + if (ew->ewd_timing.lifetime < 0) + { + SFXProfile* snd = (SFXProfile*) ew->effect_data; + SFXDescription* desc = snd->getDescription(); + if (desc && !desc->mIsLooping) + { + static bool test_for_audio = true; + static bool can_get_audio_len = false; + + if (test_for_audio) + { + can_get_audio_len = true; + test_for_audio = false; + } + + if (can_get_audio_len) + { + SFXResource* sfx_rsrc = snd->getResource(); + if (sfx_rsrc) + { + ew->ewd_timing.lifetime = 0.001f*sfx_rsrc->getDuration(); + //Con::printf("SFX (%s) duration=%g", snd->mFilename, timing.lifetime); + } + } + else + { + ew->ewd_timing.lifetime = 0; + Con::printf("afxEA_SoundDesc::prepEffect() -- cannot get audio length from sound file."); + } + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_SpotLight_T3D.cpp b/Engine/source/afx/ea/afxEA_SpotLight_T3D.cpp new file mode 100644 index 000000000..eebab60c8 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_SpotLight_T3D.cpp @@ -0,0 +1,293 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "T3D/spotLight.h" + +#include "afx/ce/afxSpotLight_T3D.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_T3DSpotLight + +class SpotLightProxy; + +class afxEA_T3DSpotLight : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxT3DSpotLightData* light_data; + SpotLightProxy* light; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_T3DSpotLight(); + /*D*/ ~afxEA_T3DSpotLight(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + virtual void ea_set_scope_status(bool flag); + virtual void onDeleteNotify(SimObject*); + virtual void getBaseColor(LinearColorF& color); + + virtual bool ea_is_enabled() { return true; } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class SpotLightProxy : public SpotLight +{ + F32 fade_amt; + +public: + SpotLightProxy() { fade_amt = 1.0f; } + + void force_ghost() + { + mNetFlags.clear(Ghostable | ScopeAlways); + mNetFlags.set(IsGhost); + } + + void setFadeAmount(F32 fade_amt) + { + this->fade_amt = fade_amt; + mLight->setBrightness(mBrightness*fade_amt); + } + + void updateTransform(const MatrixF& xfm) + { + mLight->setTransform(xfm); + LightBase::setTransform(xfm); + } + + void initWithDataBlock(const afxT3DSpotLightData* db) + { + mRange = getMax(db->mRange, 0.05f); + mInnerConeAngle = db->mInnerConeAngle; + mOuterConeAngle = db->mOuterConeAngle; + + mColor = db->mColor; + mBrightness = db->mBrightness; + mCastShadows = db->mCastShadows; + mPriority = db->mPriority; + mFlareData = db->mFlareData; + mAnimationData = db->mAnimationData; + mAnimState.active = (mAnimationData != 0); + + mLocalRenderViz = db->mLocalRenderViz; + + mLight->setType( LightInfo::Spot ); + mLight->setBrightness( db->mBrightness ); + mLight->setRange( db->mRange ); + mLight->setColor( db->mColor ); + mLight->setCastShadows( db->mCastShadows ); + mLight->setPriority( db->mPriority ); + mLight->setInnerConeAngle( db->mInnerConeAngle ); + mLight->setOuterConeAngle( db->mOuterConeAngle ); + + // Update the bounds and scale to fit our light. + F32 radius = mRange * mSin( mDegToRad( mOuterConeAngle ) * 0.5f ); + mObjBox.minExtents.set( -1, -1, -1 ); + mObjBox.maxExtents.set( 1, 1, 1 ); + mObjScale.set( radius, mRange, radius ); + + //_conformLights(); + } + + void setLiveColor(const LinearColorF& live_color) + { + mLight->setColor(live_color); + } + + void submitLights(LightManager* lm, bool staticLighting) + { + if (mAnimState.active && mAnimationData && fade_amt < 1.0f) + { + F32 mBrightness_save = mBrightness; + mBrightness *= fade_amt; + SpotLight::submitLights(lm, staticLighting); + mBrightness = mBrightness_save; + return; + } + + SpotLight::submitLights(lm, staticLighting); + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_T3DSpotLight::afxEA_T3DSpotLight() +{ + light_data = 0; + light = 0; +} + +afxEA_T3DSpotLight::~afxEA_T3DSpotLight() +{ + if (light) + light->deleteObject(); + if (light_data && light_data->isTempClone()) + delete light_data; + light_data = 0; +} + +void afxEA_T3DSpotLight::ea_set_datablock(SimDataBlock* db) +{ + light_data = dynamic_cast(db); +} + +bool afxEA_T3DSpotLight::ea_start() +{ + if (!light_data) + { + Con::errorf("afxEA_T3DSpotLight::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + // create and register effect + light = new SpotLightProxy(); + light->force_ghost(); + if (!light->registerObject()) + { + delete light; + light = 0; + Con::errorf("afxEA_T3DSpotLight::ea_update() -- effect failed to register."); + return false; + } + deleteNotify(light); + + light->initWithDataBlock(light_data); + + return true; +} + +bool afxEA_T3DSpotLight::ea_update(F32 dt) +{ + if (light) + { +#if 0 // AFX_T3D_DISABLED + // With sgLightObject lights, the following code block would hook + // the constraint object up to the light in case the light was + // configured to exclude it from flare occusions. The code remains + // here in case we need to implement the same feature for T3D light. + + afxConstraint* pos_cons = getPosConstraint(); + SceneObject* cons_obj = (pos_cons) ? pos_cons->getSceneObject() : 0; + light->setConstraintObject(cons_obj); +#endif + + light->setLiveColor(updated_color); + + if (do_fades) + light->setFadeAmount(fade_value); + + light->updateTransform(updated_xfm); + + // scale should not be updated this way. It messes up the culling. + //light->setScale(updated_scale); + } + + return true; +} + +void afxEA_T3DSpotLight::ea_finish(bool was_stopped) +{ + if (light) + { + light->deleteObject(); + light = 0; + } +} + +void afxEA_T3DSpotLight::ea_set_scope_status(bool in_scope) +{ + if (light) + light->setLightEnabled(in_scope); +} + +void afxEA_T3DSpotLight::onDeleteNotify(SimObject* obj) +{ + if (light == dynamic_cast(obj)) + light = 0; + + Parent::onDeleteNotify(obj); +} + +void afxEA_T3DSpotLight::getBaseColor(LinearColorF& color) +{ + if (light_data) + color = light_data->mColor; +} + +void afxEA_T3DSpotLight::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (light_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxT3DSpotLightData* orig_db = light_data; + light_data = new afxT3DSpotLightData(*orig_db, true); + orig_db->performSubstitutions(light_data, choreographer, group_index); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_T3DSpotLightDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_T3DSpotLightDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_T3DSpotLight; } +}; + +afxEA_T3DSpotLightDesc afxEA_T3DSpotLightDesc::desc; + +bool afxEA_T3DSpotLightDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxT3DSpotLightData) == typeid(*db)); +} + +bool afxEA_T3DSpotLightDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_StaticShape.cpp b/Engine/source/afx/ea/afxEA_StaticShape.cpp new file mode 100644 index 000000000..7f0b35351 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_StaticShape.cpp @@ -0,0 +1,249 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxStaticShape.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_StaticShape + +class afxEA_StaticShape : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + StaticShapeData* shape_data; + afxStaticShape* static_shape; + bool fade_out_started; + bool do_spawn; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_StaticShape(); + /*D*/ ~afxEA_StaticShape(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + virtual void ea_set_scope_status(bool flag); + virtual void onDeleteNotify(SimObject*); + + virtual void getUpdatedBoxCenter(Point3F& pos); + virtual TSShape* getTSShape(); + virtual TSShapeInstance* getTSShapeInstance(); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_StaticShape::afxEA_StaticShape() +{ + shape_data = 0; + static_shape = 0; + fade_out_started = false; + do_spawn = true; +} + +afxEA_StaticShape::~afxEA_StaticShape() +{ + if (static_shape) + static_shape->deleteObject(); + if (shape_data && shape_data->isTempClone()) + delete shape_data; + shape_data = 0; +} + +void afxEA_StaticShape::ea_set_datablock(SimDataBlock* db) +{ + shape_data = dynamic_cast(db); + afxStaticShapeData* afx_shape_data = dynamic_cast(shape_data); + do_spawn = (afx_shape_data) ? afx_shape_data->do_spawn : false; +} + +bool afxEA_StaticShape::ea_start() +{ + if (!shape_data) + { + Con::errorf("afxEA_StaticShape::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + // fades are handled using startFade() calls. + do_fades = false; + + return true; +} + +bool afxEA_StaticShape::ea_update(F32 dt) +{ + if (!static_shape) + { + // create and register effect + static_shape = new afxStaticShape(); + if (datablock->use_ghost_as_cons_obj && datablock->effect_name != ST_NULLSTRING) + static_shape->init(choreographer->getChoreographerId(), datablock->effect_name); + + static_shape->onNewDataBlock(shape_data, false); + if (!static_shape->registerObject()) + { + delete static_shape; + static_shape = 0; + Con::errorf("afxEA_StaticShape::ea_update() -- effect failed to register."); + return false; + } + deleteNotify(static_shape); + registerForCleanup(static_shape); + + if (ew_timing.fade_in_time > 0.0f) + static_shape->startFade(ew_timing.fade_in_time, 0, false); + } + + if (static_shape) + { + if (!fade_out_started && elapsed > fade_out_start) + { + if (!do_spawn) + { + if (ew_timing.fade_out_time > 0.0f) + static_shape->startFade(ew_timing.fade_out_time, 0, true); + } + fade_out_started = true; + } + + if (in_scope) + { + static_shape->setTransform(updated_xfm); + static_shape->setScale(updated_scale); + } + } + + return true; +} + +void afxEA_StaticShape::ea_finish(bool was_stopped) +{ + if (!static_shape) + return; + + if (do_spawn) + { + Con::executef(shape_data, "onSpawn", static_shape->getIdString(), datablock->effect_name); + clearNotify(static_shape); + } + else + static_shape->deleteObject(); + + static_shape = 0; +} + +void afxEA_StaticShape::ea_set_scope_status(bool in_scope) +{ + if (static_shape) + static_shape->setVisibility(do_spawn || in_scope); +} + +void afxEA_StaticShape::onDeleteNotify(SimObject* obj) +{ + if (static_shape == dynamic_cast(obj)) + static_shape = 0; + + Parent::onDeleteNotify(obj); +} + +void afxEA_StaticShape::getUpdatedBoxCenter(Point3F& pos) +{ + if (static_shape) + pos = static_shape->getBoxCenter(); +} + + +TSShape* afxEA_StaticShape::getTSShape() +{ + return (static_shape) ? ((TSShape*)static_shape->getShape()) : 0; +} + +TSShapeInstance* afxEA_StaticShape::getTSShapeInstance() +{ + return (static_shape) ? static_shape->getShapeInstance() : 0; +} + +void afxEA_StaticShape::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (shape_data->getSubstitutionCount() > 0) + { + if (typeid(afxStaticShapeData) == typeid(*shape_data)) + { + afxStaticShapeData* orig_db = (afxStaticShapeData*)shape_data; + shape_data = new afxStaticShapeData(*orig_db, true); + orig_db->performSubstitutions(shape_data, choreographer, group_index); + } + else + { + StaticShapeData* orig_db = shape_data; + shape_data = new StaticShapeData(*orig_db, true); + orig_db->performSubstitutions(shape_data, choreographer, group_index); + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_StaticShapeDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_StaticShapeDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return true; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return false; } + + virtual afxEffectWrapper* create() const { return new afxEA_StaticShape; } +}; + +afxEA_StaticShapeDesc afxEA_StaticShapeDesc::desc; + +bool afxEA_StaticShapeDesc::testEffectType(const SimDataBlock* db) const +{ + if (typeid(StaticShapeData) == typeid(*db)) + return true; + if (typeid(afxStaticShapeData) == typeid(*db)) + return true; + return false; +} + +bool afxEA_StaticShapeDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_TLKLight.cpp b/Engine/source/afx/ea/afxEA_TLKLight.cpp new file mode 100644 index 000000000..160558cd9 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_TLKLight.cpp @@ -0,0 +1,79 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" + +struct sgLightObjectData : public GameBaseData +{ + typedef GameBaseData Parent; + DECLARE_CONOBJECT(sgLightObjectData); + DECLARE_CATEGORY("AFX"); +}; + +IMPLEMENT_CO_DATABLOCK_V1(sgLightObjectData); + +ConsoleDocClass( sgLightObjectData, + "@brief Allows legacy sgLightObjectData datablocks to appear in scripts.\n\n" + + "@ingroup afxMisc\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_TLKLightDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_TLKLightDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return 0; } +}; + +afxEA_TLKLightDesc afxEA_TLKLightDesc::desc; + +//class sgUniversalStaticLightData; + +bool afxEA_TLKLightDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(sgLightObjectData) == typeid(*db)); +} + +bool afxEA_TLKLightDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_VolumeLight.cpp b/Engine/source/afx/ea/afxEA_VolumeLight.cpp new file mode 100644 index 000000000..194052f95 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_VolumeLight.cpp @@ -0,0 +1,61 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/ea/afxEA_ParticleEmitter.h" +#include "afx/afxChoreographer.h" +#include "afx/ce/afxVolumeLight.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_VolumeLightDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_VolumeLightDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return 0; } +}; + +afxEA_VolumeLightDesc afxEA_VolumeLightDesc::desc; + +bool afxEA_VolumeLightDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxVolumeLightData) == typeid(*db)); +} + +bool afxEA_VolumeLightDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_Zodiac.cpp b/Engine/source/afx/ea/afxEA_Zodiac.cpp new file mode 100644 index 000000000..d5fb5dd9c --- /dev/null +++ b/Engine/source/afx/ea/afxEA_Zodiac.cpp @@ -0,0 +1,434 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "math/mathUtils.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/afxResidueMgr.h" +#include "afx/util/afxEase.h" +#include "afx/ce/afxZodiacMgr.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_Zodiac + +class afxEA_Zodiac : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxZodiacData* zode_data; + Point3F zode_pos; + F32 zode_radius; + Point2F zode_vrange; + LinearColorF zode_color; + F32 zode_angle; + F32 zode_angle_offset; + + F32 live_color_factor; + LinearColorF live_color; + bool became_residue; + bool do_altitude_bias; + F32 altitude_falloff_range; + + F32 calc_facing_angle(); + F32 calc_terrain_alt_bias(); + F32 calc_interior_alt_bias(); + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_Zodiac(); + /*C*/ ~afxEA_Zodiac(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + + virtual bool ea_is_enabled() { return true; } + + virtual void getBaseColor(LinearColorF& color) { color = zode_data->color; } + + static void initPersistFields(); + + //DECLARE_CONOBJECT(afxEA_Zodiac); + DECLARE_CATEGORY("AFX"); +}; + +//IMPLEMENT_CONOBJECT(afxEA_Zodiac); + +//~~~~~~~~~~~~~~~~~~~~// + +F32 afxEA_Zodiac::calc_facing_angle() +{ + // get direction player is facing + VectorF shape_vec; + MatrixF shape_xfm; + + afxConstraint* orient_constraint = getOrientConstraint(); + if (orient_constraint) + orient_constraint->getTransform(shape_xfm); + else + shape_xfm.identity(); + + shape_xfm.getColumn(1, &shape_vec); + shape_vec.z = 0.0f; + shape_vec.normalize(); + + F32 pitch, yaw; + MathUtils::getAnglesFromVector(shape_vec, yaw, pitch); + + return mRadToDeg(yaw); +} + +inline F32 afxEA_Zodiac::calc_terrain_alt_bias() +{ + if (terrain_altitude >= zode_data->altitude_max) + return 0.0f; + return 1.0f - (terrain_altitude - zode_data->altitude_falloff)/altitude_falloff_range; +} + +inline F32 afxEA_Zodiac::calc_interior_alt_bias() +{ + if (interior_altitude >= zode_data->altitude_max) + return 0.0f; + return 1.0f - (interior_altitude - zode_data->altitude_falloff)/altitude_falloff_range; +} + +afxEA_Zodiac::afxEA_Zodiac() +{ + zode_data = 0; + zode_pos.zero(); + zode_radius = 1; + zode_vrange.set(1,1); + zode_color.set(1,1,1,1); + zode_angle = 0; + zode_angle_offset = 0; + live_color.set(1,1,1,1); + live_color_factor = 0.0f; + do_altitude_bias = false; + altitude_falloff_range = 0.0f; + became_residue = false; +} + +afxEA_Zodiac::~afxEA_Zodiac() +{ + if (!became_residue && zode_data && zode_data->isTempClone()) + delete zode_data; + zode_data = 0; +} + +void afxEA_Zodiac::ea_set_datablock(SimDataBlock* db) +{ + zode_data = dynamic_cast(db); + if (zode_data) + { + do_altitude_bias = (zode_data->altitude_max > 0.0f && (zode_data->altitude_shrinks || zode_data->altitude_fades)); + altitude_falloff_range = zode_data->altitude_max - zode_data->altitude_falloff; + } +} + +bool afxEA_Zodiac::ea_start() +{ + if (!zode_data) + { + Con::errorf("afxEA_Zodiac::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + zode_angle_offset = calc_facing_angle(); + + return true; +} + +bool afxEA_Zodiac::ea_update(F32 dt) +{ + if (!in_scope) + return true; + + //~~~~~~~~~~~~~~~~~~~~// + // Zodiac Color + + zode_color = updated_color; + + if (live_color_factor > 0.0) + { + zode_color.interpolate(zode_color, live_color, live_color_factor); + //Con::printf("LIVE-COLOR %g %g %g %g FACTOR is %g", + // live_color.red, live_color.green, live_color.blue, live_color.alpha, + // live_color_factor); + } + else + { + //Con::printf("LIVE-COLOR-FACTOR is ZERO"); + } + + if (do_fades) + { + if (fade_value < 0.01f) + return true; // too transparent + + if (zode_data->blend_flags == afxZodiacDefs::BLEND_SUBTRACTIVE) + zode_color *= fade_value*live_fade_factor; + else + zode_color.alpha *= fade_value*live_fade_factor; + } + + if (zode_color.alpha < 0.01f) + return true; + + //~~~~~~~~~~~~~~~~~~~~// + // Zodiac + + // scale and grow zode + zode_radius = zode_data->radius_xy*updated_scale.x + life_elapsed*zode_data->growth_rate; + + // zode is growing + if (life_elapsed < zode_data->grow_in_time) + { + F32 t = life_elapsed/zode_data->grow_in_time; + zode_radius = afxEase::eq(t, 0.001f, zode_radius, 0.2f, 0.8f); + } + // zode is shrinking + else if (full_lifetime - life_elapsed < zode_data->shrink_out_time) + { + F32 t = (full_lifetime - life_elapsed)/zode_data->shrink_out_time; + zode_radius = afxEase::eq(t, 0.001f, zode_radius, 0.0f, 0.9f); + } + + zode_radius *= live_scale_factor; + + if (zode_radius < 0.001f) + return true; // too small + + zode_vrange = zode_data->vert_range; + if (zode_data->scale_vert_range) + { + F32 scale_factor = zode_radius/zode_data->radius_xy; + zode_vrange *= scale_factor; + } + + //~~~~~~~~~~~~~~~~~~~~// + // Zodiac Position + + zode_pos = updated_pos; + + //~~~~~~~~~~~~~~~~~~~~// + // Zodiac Rotation + + if (zode_data->respect_ori_cons) + { + afxConstraint* orient_constraint = getOrientConstraint(); + if (orient_constraint) + { + VectorF shape_vec; + updated_xfm.getColumn(1, &shape_vec); + shape_vec.z = 0.0f; + shape_vec.normalize(); + F32 pitch, yaw; + MathUtils::getAnglesFromVector(shape_vec, yaw, pitch); + zode_angle_offset = mRadToDeg(yaw); + } + } + + zode_angle = zode_data->calcRotationAngle(life_elapsed, datablock->rate_factor/prop_time_factor); + zode_angle = mFmod(zode_angle + zode_angle_offset, 360.0f); + + //~~~~~~~~~~~~~~~~~~~~// + // post zodiac + if ((zode_data->zflags & afxZodiacDefs::SHOW_ON_TERRAIN) != 0) + { + if (do_altitude_bias && terrain_altitude > zode_data->altitude_falloff) + { + F32 alt_bias = calc_terrain_alt_bias(); + if (alt_bias > 0.0f) + { + F32 alt_rad = zode_radius; + if (zode_data->altitude_shrinks) + alt_rad *= alt_bias; + LinearColorF alt_clr(zode_color.red, zode_color.green, zode_color.blue, zode_color.alpha); + if (zode_data->altitude_fades) + alt_clr.alpha *= alt_bias; + afxZodiacMgr::addTerrainZodiac(zode_pos, alt_rad, alt_clr, zode_angle, zode_data); + } + } + else + { + afxZodiacMgr::addTerrainZodiac(zode_pos, zode_radius, zode_color, zode_angle, zode_data); + } + } + + if ((zode_data->zflags & afxZodiacDefs::SHOW_ON_INTERIORS) != 0) + { + if (do_altitude_bias && interior_altitude > zode_data->altitude_falloff) + { + F32 alt_bias = calc_interior_alt_bias(); + if (alt_bias > 0.0f) + { + F32 alt_rad = zode_radius; + if (zode_data->altitude_shrinks) + alt_rad *= alt_bias; + LinearColorF alt_clr(zode_color.red, zode_color.green, zode_color.blue, zode_color.alpha); + if (zode_data->altitude_fades) + alt_clr.alpha *= alt_bias; + afxZodiacMgr::addInteriorZodiac(zode_pos, alt_rad, zode_vrange, alt_clr, zode_angle, zode_data); + } + } + else + afxZodiacMgr::addInteriorZodiac(zode_pos, zode_radius, zode_vrange, zode_color, zode_angle, zode_data); + } + + return true; +} + +void afxEA_Zodiac::ea_finish(bool was_stopped) +{ + if (in_scope && ew_timing.residue_lifetime > 0) + { + if (do_fades) + { + if (fade_value < 0.01f) + return; + zode_color.alpha *= fade_value; + } + if ((zode_data->zflags & afxZodiacDefs::SHOW_ON_TERRAIN) != 0) + { + if (do_altitude_bias && terrain_altitude > zode_data->altitude_falloff) + { + F32 alt_bias = calc_terrain_alt_bias(); + if (alt_bias > 0.0f) + { + F32 alt_rad = zode_radius; + if (zode_data->altitude_shrinks) + alt_rad *= alt_bias; + LinearColorF alt_clr(zode_color.red, zode_color.green, zode_color.blue, zode_color.alpha); + if (zode_data->altitude_fades) + zode_color.alpha *= alt_bias; + became_residue = true; + afxResidueMgr::add_terrain_zodiac(ew_timing.residue_lifetime, ew_timing.residue_fadetime, zode_data, zode_pos, alt_rad, + alt_clr, zode_angle); + } + } + else + { + became_residue = true; + afxResidueMgr::add_terrain_zodiac(ew_timing.residue_lifetime, ew_timing.residue_fadetime, zode_data, zode_pos, zode_radius, + zode_color, zode_angle); + } + } + if ((zode_data->zflags & afxZodiacDefs::SHOW_ON_INTERIORS) != 0) + { + if (do_altitude_bias && interior_altitude > zode_data->altitude_falloff) + { + F32 alt_bias = calc_interior_alt_bias(); + if (alt_bias > 0.0f) + { + F32 alt_rad = zode_radius; + if (zode_data->altitude_shrinks) + alt_rad *= alt_bias; + LinearColorF alt_clr(zode_color.red, zode_color.green, zode_color.blue, zode_color.alpha); + if (zode_data->altitude_fades) + zode_color.alpha *= alt_bias; + + afxZodiacData* temp_zode = zode_data; + if (became_residue) + temp_zode = new afxZodiacData(*zode_data, true); + became_residue = true; + afxResidueMgr::add_interior_zodiac(ew_timing.residue_lifetime, ew_timing.residue_fadetime, temp_zode, zode_pos, alt_rad, + zode_vrange, alt_clr, zode_angle); + } + + } + else + { + afxZodiacData* temp_zode = zode_data; + if (became_residue) + temp_zode = new afxZodiacData(*zode_data, true); + became_residue = true; + afxResidueMgr::add_interior_zodiac(ew_timing.residue_lifetime, ew_timing.residue_fadetime, temp_zode, zode_pos, zode_radius, + zode_vrange, zode_color, zode_angle); + } + } + } +} + +void afxEA_Zodiac::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (zode_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxZodiacData* orig_db = zode_data; + zode_data = new afxZodiacData(*orig_db, true); + orig_db->performSubstitutions(zode_data, choreographer, group_index); + } +} + +#undef myOffset +#define myOffset(field) Offset(field, afxEA_Zodiac) + +void afxEA_Zodiac::initPersistFields() +{ + addField("liveColor", TypeColorF, myOffset(live_color), + "..."); + addField("liveColorFactor", TypeF32, myOffset(live_color_factor), + "..."); + + Parent::initPersistFields(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_ZodiacDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_ZodiacDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_Zodiac; } +}; + +afxEA_ZodiacDesc afxEA_ZodiacDesc::desc; + +bool afxEA_ZodiacDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxZodiacData) == typeid(*db)); +} + +bool afxEA_ZodiacDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ea/afxEA_ZodiacPlane.cpp b/Engine/source/afx/ea/afxEA_ZodiacPlane.cpp new file mode 100644 index 000000000..ca4271860 --- /dev/null +++ b/Engine/source/afx/ea/afxEA_ZodiacPlane.cpp @@ -0,0 +1,344 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "math/mathUtils.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/util/afxEase.h" +#include "afx/afxResidueMgr.h" +#include "afx/ce/afxZodiacPlane.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_ZodiacPlane + +class afxEA_ZodiacPlane : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxZodiacPlaneData* zode_data; + afxZodiacPlane* pzode; + + F32 zode_angle_offset; + AngAxisF aa_rot; + + F32 live_color_factor; + LinearColorF live_color; + + F32 calc_facing_angle(); + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_ZodiacPlane(); + /*D*/ ~afxEA_ZodiacPlane(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); + virtual void ea_set_scope_status(bool flag); + virtual void onDeleteNotify(SimObject*); + virtual void getUpdatedBoxCenter(Point3F& pos); + virtual void getBaseColor(LinearColorF& color) { color = zode_data->color; } +}; + +F32 afxEA_ZodiacPlane::calc_facing_angle() +{ + // get direction player is facing + VectorF shape_vec; + MatrixF shape_xfm; + + afxConstraint* orient_constraint = getOrientConstraint(); + if (orient_constraint) + orient_constraint->getTransform(shape_xfm); + else + shape_xfm.identity(); + + shape_xfm.getColumn(1, &shape_vec); + shape_vec.z = 0.0f; + shape_vec.normalize(); + + F32 pitch, yaw; + MathUtils::getAnglesFromVector(shape_vec, yaw, pitch); + + return mRadToDeg(yaw); +} + +afxEA_ZodiacPlane::afxEA_ZodiacPlane() +{ + zode_data = 0; + pzode = 0; + zode_angle_offset = 0; + live_color.set(1,1,1,1); + live_color_factor = 0.0f; +} + +afxEA_ZodiacPlane::~afxEA_ZodiacPlane() +{ + if (pzode) + pzode->deleteObject(); + if (zode_data && zode_data->isTempClone()) + delete zode_data; + zode_data = 0; +} + +void afxEA_ZodiacPlane::ea_set_datablock(SimDataBlock* db) +{ + zode_data = dynamic_cast(db); +} + +bool afxEA_ZodiacPlane::ea_start() +{ + if (!zode_data) + { + Con::errorf("afxEA_ZodiacPlane::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + if (!zode_data->use_full_xfm) + zode_angle_offset = calc_facing_angle(); + + switch (zode_data->face_dir) + { + case afxZodiacPlaneData::FACES_UP: + aa_rot.set(Point3F(0.0f,0.0f,1.0f),0.0f); + break; + case afxZodiacPlaneData::FACES_DOWN: + aa_rot.set(Point3F(0.0f,0.0f,-1.0f),0.0f); + break; + case afxZodiacPlaneData::FACES_FORWARD: + aa_rot.set(Point3F(0.0f,1.0f,0.0f),0.0f); + break; + case afxZodiacPlaneData::FACES_BACK: + aa_rot.set(Point3F(0.0f,-1.0f,0.0f),0.0f); + break; + case afxZodiacPlaneData::FACES_RIGHT: + aa_rot.set(Point3F(1.0f,0.0f,0.0f),0.0f); + break; + case afxZodiacPlaneData::FACES_LEFT: + aa_rot.set(Point3F(-1.0f,0.0f,0.0f),0.0f); + break; + } + + return true; +} + +bool afxEA_ZodiacPlane::ea_update(F32 dt) +{ + if (!pzode) + { + // create and register effect + pzode = new afxZodiacPlane(); + pzode->onNewDataBlock(zode_data, false); + if (!pzode->registerObject()) + { + delete pzode; + pzode = 0; + Con::errorf("afxEA_ZodiacPlane::ea_update() -- effect failed to register."); + return false; + } + deleteNotify(pzode); + + ///pzode->setSequenceRateFactor(datablock->rate_factor/prop_time_factor); + ///pzode->setSortPriority(datablock->sort_priority); + } + + if (pzode) + { + //LinearColorF zode_color = zode_data->color; + LinearColorF zode_color = updated_color; + + if (live_color_factor > 0.0) + zode_color.interpolate(zode_color, live_color, live_color_factor); + + if (do_fades) + { + if (zode_data->blend_flags == afxZodiacDefs::BLEND_SUBTRACTIVE) + zode_color *= fade_value*live_fade_factor; + else + zode_color.alpha *= fade_value*live_fade_factor; + } + + // scale and grow zode + //F32 zode_radius = zode_data->radius_xy*updated_scale.x + life_elapsed*zode_data->growth_rate; + F32 zode_radius = zode_data->radius_xy + life_elapsed*zode_data->growth_rate; + + // zode is growing + if (life_elapsed < zode_data->grow_in_time) + { + F32 t = life_elapsed/zode_data->grow_in_time; + zode_radius = afxEase::eq(t, 0.001f, zode_radius, 0.2f, 0.8f); + } + // zode is shrinking + else if (full_lifetime - life_elapsed < zode_data->shrink_out_time) + { + F32 t = (full_lifetime - life_elapsed)/zode_data->shrink_out_time; + zode_radius = afxEase::eq(t, 0.001f, zode_radius, 0.0f, 0.9f); + } + + zode_radius *= live_scale_factor; + + if (zode_data->respect_ori_cons && !zode_data->use_full_xfm) + { + VectorF shape_vec; + updated_xfm.getColumn(1, &shape_vec); + shape_vec.normalize(); + + F32 ang; + + switch (zode_data->face_dir) + { + case afxZodiacPlaneData::FACES_FORWARD: + case afxZodiacPlaneData::FACES_BACK: + ang = mAtan2(shape_vec.x, shape_vec.z); + break; + case afxZodiacPlaneData::FACES_RIGHT: + case afxZodiacPlaneData::FACES_LEFT: + ang = mAtan2(shape_vec.y, shape_vec.z); + break; + case afxZodiacPlaneData::FACES_UP: + case afxZodiacPlaneData::FACES_DOWN: + default: + ang = mAtan2(shape_vec.x, shape_vec.y); + break; + } + + if (ang < 0.0f) + ang += M_2PI_F; + + switch (zode_data->face_dir) + { + case afxZodiacPlaneData::FACES_DOWN: + case afxZodiacPlaneData::FACES_BACK: + case afxZodiacPlaneData::FACES_LEFT: + ang = -ang; + break; + } + + zode_angle_offset = mRadToDeg(ang); + } + + F32 zode_angle = zode_data->calcRotationAngle(life_elapsed, datablock->rate_factor/prop_time_factor); + zode_angle = mFmod(zode_angle + zode_angle_offset, 360.0f); + aa_rot.angle = mDegToRad(zode_angle); + + MatrixF spin_xfm; + aa_rot.setMatrix(&spin_xfm); + + // set color, radius + pzode->setColor(zode_color); + pzode->setRadius(zode_radius); + if (zode_data->use_full_xfm) + { + updated_xfm.mul(spin_xfm); + pzode->setTransform(updated_xfm); + } + else + pzode->setTransform(spin_xfm); + pzode->setPosition(updated_pos); + pzode->setScale(updated_scale); + } + + return true; +} + +void afxEA_ZodiacPlane::ea_finish(bool was_stopped) +{ + if (!pzode) + return; + + pzode->deleteObject(); + pzode = 0; +} + +void afxEA_ZodiacPlane::ea_set_scope_status(bool in_scope) +{ + if (pzode) + pzode->setVisibility(in_scope); +} + +void afxEA_ZodiacPlane::onDeleteNotify(SimObject* obj) +{ + if (pzode == dynamic_cast(obj)) + pzode = 0; + + Parent::onDeleteNotify(obj); +} + +void afxEA_ZodiacPlane::getUpdatedBoxCenter(Point3F& pos) +{ + if (pzode) + pos = pzode->getBoxCenter(); +} + +void afxEA_ZodiacPlane::do_runtime_substitutions() +{ + // only clone the datablock if there are substitutions + if (zode_data->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxZodiacPlaneData* orig_db = zode_data; + zode_data = new afxZodiacPlaneData(*orig_db, true); + orig_db->performSubstitutions(zode_data, choreographer, group_index); + } +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_ZodiacPlaneDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_ZodiacPlaneDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_ZodiacPlane; } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_ZodiacPlaneDesc afxEA_ZodiacPlaneDesc::desc; + +bool afxEA_ZodiacPlaneDesc::testEffectType(const SimDataBlock* db) const +{ + return (typeid(afxZodiacPlaneData) == typeid(*db)); +} + +bool afxEA_ZodiacPlaneDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/forces/afxEA_Force.cpp b/Engine/source/afx/forces/afxEA_Force.cpp new file mode 100644 index 000000000..7ec2332f1 --- /dev/null +++ b/Engine/source/afx/forces/afxEA_Force.cpp @@ -0,0 +1,183 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" +#include "afx/afxChoreographer.h" +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/forces/afxForce.h" +#include "afx/forces/afxForceSet.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxEA_Force + +class afxEA_Force : public afxEffectWrapper +{ + typedef afxEffectWrapper Parent; + + afxForceData* force_data; + afxForce* force; + afxForceSetMgr* force_set_mgr; + + void do_runtime_substitutions(); + +public: + /*C*/ afxEA_Force(); + /*D*/ ~afxEA_Force(); + + virtual void ea_set_datablock(SimDataBlock*); + virtual bool ea_start(); + virtual bool ea_update(F32 dt); + virtual void ea_finish(bool was_stopped); +}; + +//~~~~~~~~~~~~~~~~~~~~// + +afxEA_Force::afxEA_Force() +{ + force_data = 0; + force = 0; + force_set_mgr = 0; +} + +afxEA_Force::~afxEA_Force() +{ + if (force) + { + if (force_set_mgr) + force_set_mgr->unregisterForce(force_data->force_set_name, force); + delete force; + } + + if (force_data && force_data->isTempClone()) + { + delete force_data; + force_data = 0; + } + + force_set_mgr = 0; +} + +void afxEA_Force::ea_set_datablock(SimDataBlock* db) +{ + force_data = dynamic_cast(db); +} + +bool afxEA_Force::ea_start() +{ + if (!force_data) + { + Con::errorf("afxEA_Force::ea_start() -- missing or incompatible datablock."); + return false; + } + + do_runtime_substitutions(); + + force_set_mgr = choreographer->getForceSetMgr(); + + return true; +} + +bool afxEA_Force::ea_update(F32 dt) +{ + if (!force) + { + force = (force_data->force_desc) ? force_data->force_desc->create() : 0; + if (!force) + { + delete force; + force = 0; + Con::errorf(ConsoleLogEntry::General, "Force effect failed to instantiate. (%s)", datablock->getName()); + return false; + } + force->onNewDataBlock(force_data, false); + + if (force) + { + force_set_mgr->registerForce(force_data->force_set_name, force); + force->start(); + } + } + + if (force) // && in_scope) + { + if (do_fades) + force->setFadeAmount(fade_value); + + force->update(dt); + } + + return true; +} + +void afxEA_Force::ea_finish(bool was_stopped) +{ + if (!force) + return; + + if (force_set_mgr) + force_set_mgr->unregisterForce(force_data->force_set_name, force); + delete force; + force = 0; +} + +void afxEA_Force::do_runtime_substitutions() +{ + force_data = force_data->cloneAndPerformSubstitutions(choreographer, group_index); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxEA_ForceDesc : public afxEffectAdapterDesc, public afxEffectDefs +{ + static afxEA_ForceDesc desc; + +public: + virtual bool testEffectType(const SimDataBlock*) const; + virtual bool requiresStop(const afxEffectWrapperData*, const afxEffectTimingData&) const; + virtual bool runsOnServer(const afxEffectWrapperData*) const { return false; } + virtual bool runsOnClient(const afxEffectWrapperData*) const { return true; } + + virtual afxEffectWrapper* create() const { return new afxEA_Force; } +}; + +afxEA_ForceDesc afxEA_ForceDesc::desc; + +bool afxEA_ForceDesc::testEffectType(const SimDataBlock* db) const +{ + if (dynamic_cast(db) != 0) + return afxForceDesc::identifyForce((afxForceData*) db); + + return false; +} + +bool afxEA_ForceDesc::requiresStop(const afxEffectWrapperData* ew, const afxEffectTimingData& timing) const +{ + return (timing.lifetime < 0); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + + diff --git a/Engine/source/afx/forces/afxF_Drag.cpp b/Engine/source/afx/forces/afxF_Drag.cpp new file mode 100644 index 000000000..7fb52deb1 --- /dev/null +++ b/Engine/source/afx/forces/afxF_Drag.cpp @@ -0,0 +1,209 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "afx/forces/afxForce.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxF_DragData : public afxForceData +{ + typedef afxForceData Parent; + +public: + F32 drag_coefficient; + F32 air_density; + F32 cross_sectional_area; + +public: + /*C*/ afxF_DragData(); + /*C*/ afxF_DragData(const afxF_DragData&, bool = false); + + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + virtual afxForceData* cloneAndPerformSubstitutions(const SimObject*, S32 index=0); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxF_DragData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxF_Drag : public afxForce +{ + typedef afxForce Parent; + +private: + afxF_DragData* datablock; + F32 air_friction_constant; + +public: + /*C*/ afxF_Drag(); + + virtual bool onNewDataBlock(afxForceData* dptr, bool reload); + + virtual void start(); + virtual Point3F evaluate(Point3F pos, Point3F v, F32 mass); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxDragData + +IMPLEMENT_CO_DATABLOCK_V1(afxF_DragData); + +ConsoleDocClass( afxF_DragData, + "@brief A datablock for specifiying AFX drag forces.\n\n" + + "@ingroup afxExperimental\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxF_DragData::afxF_DragData() +{ + air_density = 1.2250f; + cross_sectional_area = 0.75f; // this variable isn't exposed to the user to keep things simple + drag_coefficient = 1.0f; +} + +afxF_DragData::afxF_DragData(const afxF_DragData& other, bool temp_clone) : afxForceData(other, temp_clone) +{ + air_density = other.air_density; + cross_sectional_area = other.cross_sectional_area; + drag_coefficient = other.drag_coefficient; +} + +#define myOffset(field) Offset(field, afxF_DragData) + +void afxF_DragData::initPersistFields() +{ + addField("drag", TypeF32, myOffset(drag_coefficient), + "..."); + addField("airDensity", TypeF32, myOffset(air_density), + "..."); + addField("crossSectionalArea", TypeF32, myOffset(cross_sectional_area), + "..."); + + Parent::initPersistFields(); +} + +void afxF_DragData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(drag_coefficient); + stream->write(air_density); + stream->write(cross_sectional_area); +} + +void afxF_DragData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&drag_coefficient); + stream->read(&air_density); + stream->read(&cross_sectional_area); +} + +afxForceData* afxF_DragData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index) +{ + afxF_DragData* drag_data = this; + + // only clone the datablock if there are substitutions + if (this->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxF_DragData* orig_db = this; + drag_data = new afxF_DragData(*orig_db, true); + orig_db->performSubstitutions(drag_data, owner, index); + } + + return drag_data; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxF_Drag::afxF_Drag() : afxForce() +{ + air_friction_constant = 1.0f; +} + +bool afxF_Drag::onNewDataBlock(afxForceData* dptr, bool reload) +{ + datablock = dynamic_cast(dptr); + if (!datablock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + return true; +} + +void afxF_Drag::start() +{ + air_friction_constant = 0.5f * datablock->drag_coefficient + * datablock->air_density + * datablock->cross_sectional_area; + //Con::printf("Air Friction: %f", air_friction_constant); +} + +Point3F afxF_Drag::evaluate(Point3F pos, Point3F velocity, F32 mass) +{ + // This implements the standard drag equation for object's at high speeds. + // F-drag = 1/2pACv^2 + // p = medium (air) density + // A = cross-sectional area of moving object (plane perpendicular to direction of motion) + // C = coefficient of drag + + // -- Velocity here should actually be relative to the velocity of the fluid... (relative speed) + // (is it already?) + F32 drag = air_friction_constant*velocity.magnitudeSafe(); + + // Here, velocity is normalized just to get a direction vector. + // Drag is in the direction opposite velocity. Is this right? + velocity.normalizeSafe(); + + return (velocity*-drag*mass); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxF_DragDesc : public afxForceDesc +{ + static afxF_DragDesc desc; + +public: + virtual bool testForceType(const SimDataBlock*) const; + virtual afxForce* create() const { return new afxF_Drag; } +}; + +afxF_DragDesc afxF_DragDesc::desc; + +bool afxF_DragDesc::testForceType(const SimDataBlock* db) const +{ + return (typeid(afxF_DragData) == typeid(*db)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/forces/afxF_Gravity.cpp b/Engine/source/afx/forces/afxF_Gravity.cpp new file mode 100644 index 000000000..fc5ed1d80 --- /dev/null +++ b/Engine/source/afx/forces/afxF_Gravity.cpp @@ -0,0 +1,171 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include +#include "afx/arcaneFX.h" + +#include "afx/forces/afxForce.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxF_GravityData : public afxForceData +{ + typedef afxForceData Parent; + +public: + F32 gravity; + +public: + /*C*/ afxF_GravityData(); + /*C*/ afxF_GravityData(const afxF_GravityData&, bool = false); + + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + virtual afxForceData* cloneAndPerformSubstitutions(const SimObject*, S32 index=0); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxF_GravityData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxF_Gravity : public afxForce +{ + typedef afxForce Parent; + +private: + afxF_GravityData* datablock; + +public: + /*C*/ afxF_Gravity(); + + virtual bool onNewDataBlock(afxForceData* dptr, bool reload); + + virtual Point3F evaluate(Point3F pos, Point3F v, F32 mass); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxForceData + +IMPLEMENT_CO_DATABLOCK_V1(afxF_GravityData); + +ConsoleDocClass( afxF_GravityData, + "@brief A datablock for specifiying AFX gravity forces.\n\n" + + "@ingroup afxExperimental\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxF_GravityData::afxF_GravityData() +{ + gravity = 9.807f; +} + +afxF_GravityData::afxF_GravityData(const afxF_GravityData& other, bool temp_clone) : afxForceData(other, temp_clone) +{ + gravity = other.gravity; +} + +#define myOffset(field) Offset(field, afxF_GravityData) + +void afxF_GravityData::initPersistFields() +{ + addField("gravity", TypeF32, myOffset(gravity), + "..."); + + Parent::initPersistFields(); +} + +void afxF_GravityData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(gravity); +} + +void afxF_GravityData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&gravity); +} + +afxForceData* afxF_GravityData::cloneAndPerformSubstitutions(const SimObject* owner, S32 index) +{ + afxF_GravityData* grav_data = this; + + // only clone the datablock if there are substitutions + if (this->getSubstitutionCount() > 0) + { + // clone the datablock and perform substitutions + afxF_GravityData* orig_db = this; + grav_data = new afxF_GravityData(*orig_db, true); + orig_db->performSubstitutions(grav_data, owner, index); + } + + return grav_data; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxF_Gravity::afxF_Gravity() : afxForce() +{ +} + +bool afxF_Gravity::onNewDataBlock(afxForceData* dptr, bool reload) +{ + datablock = dynamic_cast(dptr); + if (!datablock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + return true; +} + +Point3F afxF_Gravity::evaluate(Point3F pos, Point3F v, F32 mass) +{ + return Point3F(0,0,-datablock->gravity)*mass; +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxF_GravityDesc : public afxForceDesc +{ + static afxF_GravityDesc desc; + +public: + virtual bool testForceType(const SimDataBlock*) const; + virtual afxForce* create() const { return new afxF_Gravity; } +}; + +afxF_GravityDesc afxF_GravityDesc::desc; + +bool afxF_GravityDesc::testForceType(const SimDataBlock* db) const +{ + return (typeid(afxF_GravityData) == typeid(*db)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/forces/afxForce.cpp b/Engine/source/afx/forces/afxForce.cpp new file mode 100644 index 000000000..935ef7174 --- /dev/null +++ b/Engine/source/afx/forces/afxForce.cpp @@ -0,0 +1,148 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "scene/sceneRenderState.h" +#include "math/mathIO.h" + +#include "afx/afxChoreographer.h" +#include "afx/forces/afxForce.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxForceData + +afxForceData::afxForceData() +{ + force_set_name = ST_NULLSTRING; + force_desc = 0; +} + +afxForceData::afxForceData(const afxForceData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + force_set_name = other.force_set_name; + force_desc = other.force_desc; +} + +#define myOffset(field) Offset(field, afxForceData) + +void afxForceData::initPersistFields() +{ + addField("forceSetName", TypeString, myOffset(force_set_name), + "..."); + + Parent::initPersistFields(); +} + +bool afxForceData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + return true; +} + +void afxForceData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->writeString(force_set_name); +} + +void afxForceData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + force_set_name = stream->readSTString(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxForce + +afxForce::afxForce() +{ + datablock = 0; + fade_amt = 1.0f; +} + +afxForce::~afxForce() +{ +} + +bool afxForce::onNewDataBlock(afxForceData* dptr, bool reload) +{ + datablock = dptr; + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +Vector* afxForceDesc::forces = 0; + +afxForceDesc::afxForceDesc() +{ + if (!forces) + forces = new Vector; + + forces->push_back(this); +} + +bool afxForceDesc::identifyForce(afxForceData* force_data) +{ + if (!force_data) + { + Con::errorf("afxForceDesc::identifyForce() -- force datablock was not specified."); + return false; + } + + if (!forces) + { + Con::errorf("afxForceDesc::identifyForce() -- force registration list has not been allocated."); + return false; + } + + if (forces->size() == 0) + { + Con::errorf("afxForceDesc::identifyForce() -- no forces have been registered."); + return false; + } + + for (S32 i = 0; i < forces->size(); i++) + { + if ((*forces)[i]->testForceType(force_data)) + { + force_data->force_desc = (*forces)[i]; + return true; + } + } + + Con::errorf("afxForceDesc::identifyForce() -- force %s has an undefined type. -- %d", + force_data, forces->size()); + return false; +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/forces/afxForce.h b/Engine/source/afx/forces/afxForce.h new file mode 100644 index 000000000..5050d17d8 --- /dev/null +++ b/Engine/source/afx/forces/afxForce.h @@ -0,0 +1,97 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_FORCE_H_ +#define _AFX_FORCE_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxForce Data +class afxForceDesc; + +class afxForceData : public GameBaseData +{ + typedef GameBaseData Parent; + +public: + StringTableEntry force_set_name; + afxForceDesc* force_desc; + +public: + /*C*/ afxForceData(); + /*C*/ afxForceData(const afxForceData&, bool = false); + + virtual bool onAdd(); + virtual void packData(BitStream* stream); + virtual void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + virtual afxForceData* cloneAndPerformSubstitutions(const SimObject*, S32 index=0)=0; + + static void initPersistFields(); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxForce + +class afxForce +{ + afxForceData* datablock; + +protected: + F32 fade_amt; + +public: + /*C*/ afxForce(); + /*D*/ ~afxForce(); + + virtual bool onNewDataBlock(afxForceData* dptr, bool reload); + void setFadeAmount(F32 amt) { fade_amt = amt; } + + virtual void start() {}; + virtual void update(F32 dt) {}; + virtual Point3F evaluate(Point3F pos, Point3F v, F32 mass) { return Point3F(0,0,0); }; //=0; +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxForceDesc +{ +private: + static Vector* forces; + +public: + /*C*/ afxForceDesc(); + + virtual bool testForceType(const SimDataBlock*) const=0; + + virtual afxForce* create() const=0; + + static bool identifyForce(afxForceData*); +}; + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_FORCE_H_ diff --git a/Engine/source/afx/forces/afxForceSet.cpp b/Engine/source/afx/forces/afxForceSet.cpp new file mode 100644 index 000000000..49658022f --- /dev/null +++ b/Engine/source/afx/forces/afxForceSet.cpp @@ -0,0 +1,134 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/forces/afxForceSet.h" +#include "afx/forces/afxForce.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxForceSet::afxForceSet(const char* name) +{ + this->name = (name) ? StringTable->insert(name) : ST_NULLSTRING; + update_dt = 10.0f; // seems like an ok maximum, force-xmods will probably lower it. + elapsed_dt = 0.0f; + elapsed_ms = 0; + num_updates = 0; + last_num_updates = 0; +} + +void afxForceSet::remove(afxForce* force) +{ + for (S32 i = 0; i < force_v.size(); i++) + { + if (force_v[i] == force) + { + force_v.erase(i); + return; + } + } +} + +S32 afxForceSet::updateDT(F32 dt) +{ + U32 now = Platform::getVirtualMilliseconds(); + + if (elapsed_ms == now) + return last_num_updates; + + elapsed_ms = now; + elapsed_dt += dt; + + if (elapsed_dt < update_dt) + { + last_num_updates = 0; + return 0; + } + + num_updates = mFloor(elapsed_dt/update_dt); + elapsed_dt -= update_dt*num_updates; + last_num_updates = num_updates; + + return num_updates; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxForceSetMgr::afxForceSetMgr() +{ +} + +afxForceSetMgr::~afxForceSetMgr() +{ + for (S32 i = 0; i < forces_sets.size(); i++) + { + if (forces_sets[i]) + delete forces_sets[i]; + } +} + +afxForceSet* afxForceSetMgr::findForceSet(StringTableEntry forces_set_name) +{ + for (S32 i = 0; i < forces_sets.size(); i++) + { + if (forces_sets[i] && forces_sets[i]->getName() == forces_set_name) + return forces_sets[i]; + } + + return 0; +} + +void afxForceSetMgr::registerForce(StringTableEntry forces_set_name, afxForce* force) +{ + if (!force) + return; + + // find forceSet by name + afxForceSet* fset = findForceSet(forces_set_name); + + // create forceSet if it does not already exist + if (!fset) + { + fset = new afxForceSet(forces_set_name); + forces_sets.push_back(fset); + } + + // add force to set + fset->add(force); +} + +void afxForceSetMgr::unregisterForce(StringTableEntry forces_set_name, afxForce* force) +{ + if (!force) + return; + + afxForceSet* fset = findForceSet(forces_set_name); + if (!fset) + return; + + fset->remove(force); +} + diff --git a/Engine/source/afx/forces/afxForceSet.h b/Engine/source/afx/forces/afxForceSet.h new file mode 100644 index 000000000..8c42a2dd3 --- /dev/null +++ b/Engine/source/afx/forces/afxForceSet.h @@ -0,0 +1,80 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_FORCE_SET_H_ +#define _AFX_FORCE_SET_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxForce; + +class afxForceSet +{ + Vector force_v; + StringTableEntry name; + + // tick-based updating + F32 update_dt; // constant update interval, in seconds + F32 elapsed_dt; // runtime elapsed delta, in seconds + U32 elapsed_ms; + S32 num_updates; + S32 last_num_updates; + +public: + /*C*/ afxForceSet(const char* name=0); + + void add(afxForce* force) { force_v.push_back(force); } + void remove(afxForce* force); + + S32 count() { return force_v.size(); } + afxForce* getForce(S32 idx) { return force_v[idx]; } + const char* getName() const { return name; } + + void setUpdateDT(F32 update_dt) { this->update_dt = update_dt; } + F32 getUpdateDT() { return update_dt; } + + S32 updateDT(F32 dt); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxForceSetMgr +{ + Vector forces_sets; + +public: + /*C*/ afxForceSetMgr(); + /*D*/ ~afxForceSetMgr(); + + afxForceSet* findForceSet(StringTableEntry name); + S32 numForceSets() const { return forces_sets.size(); } + + void registerForce(StringTableEntry forces_set_name, afxForce* force); + void unregisterForce(StringTableEntry forces_set_name, afxForce* force); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_FORCE_SET_H_ diff --git a/Engine/source/afx/forces/afxXM_Force.cpp b/Engine/source/afx/forces/afxXM_Force.cpp new file mode 100644 index 000000000..cfe534dd8 --- /dev/null +++ b/Engine/source/afx/forces/afxXM_Force.cpp @@ -0,0 +1,274 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" + +#include "afx/afxEffectDefs.h" +#include "afx/forces/afxForce.h" +#include "afx/forces/afxForceSet.h" + +//#define ECHO_DEBUG_INFO + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_ForceData : public afxXM_WeightedBaseData, public afxEffectDefs +{ + typedef afxXM_WeightedBaseData Parent; + +public: + StringTableEntry force_set_name; + F32 update_dt; + +public: + /*C*/ afxXM_ForceData(); + /*C*/ afxXM_ForceData(const afxXM_ForceData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + bool preload(bool server, String &errorStr); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_ForceData); + DECLARE_CATEGORY("AFX"); +}; + +class afxXM_Force : public afxXM_WeightedBase, public afxEffectDefs +{ + typedef afxXM_WeightedBase Parent; + + afxForceSet* force_set; + + Point3F pos_local; + Point3F velocity; + + bool first; + + F32 mass; + F32 mass_inverse; + + afxXM_ForceData* db; + +public: + /*C*/ afxXM_Force(afxXM_ForceData*, afxEffectWrapper*); + + virtual void start(F32 timestamp); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_ForceData); + +ConsoleDocClass( afxXM_ForceData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxExperimental\n" + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_ForceData::afxXM_ForceData() +{ + force_set_name = ST_NULLSTRING; + update_dt = -1.0f; +} + +afxXM_ForceData::afxXM_ForceData(const afxXM_ForceData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + force_set_name = other.force_set_name; + update_dt = other.update_dt; +} + + +void afxXM_ForceData::initPersistFields() +{ + addField("forceSetName", TypeString, Offset(force_set_name, afxXM_ForceData), + "..."); + addField("updateDT", TypeF32, Offset(update_dt, afxXM_ForceData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_ForceData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeString(force_set_name); + if (stream->writeFlag(update_dt < 0.0f)) + stream->write(update_dt); +} + +void afxXM_ForceData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + force_set_name = stream->readSTString(); + if (stream->readFlag()) + stream->read(&update_dt); + else + update_dt = -1.0f; +} + +bool afxXM_ForceData::preload(bool server, String &errorStr) +{ + if(!Parent::preload(server, errorStr)) + return false; + + return true; +} + +afxXM_Base* afxXM_ForceData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_ForceData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_ForceData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + return new afxXM_Force(datablock, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Force::afxXM_Force(afxXM_ForceData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + this->db = db; + + force_set = 0; + + pos_local.zero(); + velocity.zero(); + + mass = 1.0f; + mass_inverse = 1.0f; + + first = true; +} + +void afxXM_Force::start(F32 timestamp) +{ + Parent::start(timestamp); + + afxForceSetMgr* force_set_mgr = fx_wrapper->getChoreographer()->getForceSetMgr(); + force_set = (force_set_mgr) ? force_set_mgr->findForceSet(db->force_set_name) : 0; + if (!force_set) + { + Con::errorf(ConsoleLogEntry::General, + "afxXM_Force::start() -- unable to find afxForceSet %s", db->force_set_name); + return; + } + + mass = fx_wrapper->getMass(); + + // compute mass_inverse safely + mass_inverse = (mass > 0.0001f) ? (1.0f/mass) : 1.0f/0.0001f; + + F32 update_dt = (db->update_dt < 0.0f) ? 1.0f/30.0f : db->update_dt; + if (force_set->getUpdateDT() > update_dt) + force_set->setUpdateDT(update_dt); +} + +// JTF Note: answer these questions? +// Can mass be removed from the force and acceleration calculations? +// XFM Weight is not accounted for (yet). +void afxXM_Force::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (!force_set) + return; + +#ifdef ECHO_DEBUG_INFO + Con::printf("afxXM_Force: elapsed=%f (dt=%f)", elapsed,dt); +#endif + + if (first) + { + velocity = fx_wrapper->getDirection(); + velocity.normalizeSafe(); + params.ori.mulP(velocity); + velocity *= fx_wrapper->getSpeed(); + +#ifdef ECHO_DEBUG_INFO + Con::printf("INITIAL VELOCITY : %f %f %f", velocity.x, velocity.y, velocity.z); + Con::printf("MASS : %f %f", mass, mass_inverse ); +#endif + + first = false; + } + + S32 num_updates = force_set->updateDT(dt); + if (num_updates == 0) + { + params.pos += pos_local; + return; + } + + for (S32 j = 0; j < num_updates; j++) + { + Point3F F_net(0,0,0); + for (S32 i = 0; i < force_set->count(); i++) + { + afxForce* force = force_set->getForce(i); +#ifdef ECHO_DEBUG_INFO + Point3F F = force->evaluate(params.pos+pos_local, velocity, mass); + F_net += F; + Con::printf("(%d) F %i: %f %f %f", this, i, F.x, F.y, F.z); +#else + F_net += force->evaluate(params.pos+pos_local, velocity, mass); +#endif + } + + Point3F acceleration = F_net * mass_inverse * force_set->getUpdateDT(); + velocity += acceleration; + + pos_local += velocity; + } + params.pos += pos_local; + +#ifdef ECHO_DEBUG_INFO + Con::printf("velocity : %f %f %f", velocity.x, velocity.y, velocity.z); + Con::printf("pos: %f %f %f", params.pos.x, params.pos.y, params.pos.z); +#endif +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/rpg/afxRPGMagicSpell.cpp b/Engine/source/afx/rpg/afxRPGMagicSpell.cpp new file mode 100644 index 000000000..dae982fa8 --- /dev/null +++ b/Engine/source/afx/rpg/afxRPGMagicSpell.cpp @@ -0,0 +1,274 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" +#include "afxRPGMagicSpell.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxRPGMagicSpellData + +IMPLEMENT_CO_DATABLOCK_V1(afxRPGMagicSpellData); + +ConsoleDocClass( afxRPGMagicSpellData, + "@brief A datablock for defining RPG aspects of a spell.\n\n" + + "@ingroup afxMisc\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxRPGMagicSpellData::afxRPGMagicSpellData() +{ + // spell parameters + spell_name = ST_NULLSTRING; + spell_desc = ST_NULLSTRING; + spell_target = TARGET_NOTHING; + spell_range = 0.0f; + mana_cost = 0; + + n_reagents = 0; + for (S32 i = 0; i < MAX_REAGENTS_PER_SPELL; i++) + { + reagent_cost[i] = 1; + reagent_name[i] = ST_NULLSTRING; + } + + // spell phase timing + casting_dur = 0.0f; + + // interface elements + icon_name = ST_NULLSTRING; + source_pack = ST_NULLSTRING; + + is_placeholder = false; + + free_target_style = 0; + target_optional = false; +} + +ImplementEnumType( afxRPGMagicSpell_TargetType, "Possible RPG spell target types.\n" "@ingroup afxRPGMagicSpell\n\n" ) + { afxRPGMagicSpellData::TARGET_NOTHING, "nothing", "..." }, + { afxRPGMagicSpellData::TARGET_SELF, "self", "..." }, + { afxRPGMagicSpellData::TARGET_FRIEND, "friend", "..." }, + { afxRPGMagicSpellData::TARGET_ENEMY, "enemy", "..." }, + { afxRPGMagicSpellData::TARGET_CORPSE, "corpse", "..." }, + { afxRPGMagicSpellData::TARGET_FREE, "free", "..." }, +EndImplementEnumType; + +#define myOffset(field) Offset(field, afxRPGMagicSpellData) + +void afxRPGMagicSpellData::initPersistFields() +{ + // spell parameters + addField("spellName", TypeString, myOffset(spell_name), + "..."); + addField("desc", TypeString, myOffset(spell_desc), + "..."); + + addField("target", TYPEID< afxRPGMagicSpellData::TargetType >(), myOffset(spell_target), + "..."); + + addField("range", TypeF32, myOffset(spell_range), + "..."); + addField("manaCost", TypeS32, myOffset(mana_cost), + "..."); + addField("reagentCost", TypeS8, myOffset(reagent_cost), MAX_REAGENTS_PER_SPELL, + "..."); + addField("reagentName", TypeString, myOffset(reagent_name), MAX_REAGENTS_PER_SPELL, + "..."); + + // spell phase timing + addField("castingDur", TypeF32, myOffset(casting_dur)); + + // interface elements + addField("iconBitmap", TypeFilename, myOffset(icon_name)); + addField("sourcePack", TypeString, myOffset(source_pack)); + addField("isPlaceholder", TypeBool, myOffset(is_placeholder)); + addField("freeTargetStyle", TypeS8, myOffset(free_target_style)); + addField("targetOptional", TypeBool, myOffset(target_optional)); + + Parent::initPersistFields(); +} + +bool afxRPGMagicSpellData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + n_reagents = 0; + for (S32 i = 0; i < MAX_REAGENTS_PER_SPELL && reagent_name[i] != ST_NULLSTRING; i++) + n_reagents++; + + return true; +} + +void afxRPGMagicSpellData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(spell_target); + stream->write(spell_range); + stream->write(mana_cost); + stream->write(n_reagents); + for (S32 i = 0; i < n_reagents; i++) + { + stream->write(reagent_cost[i]); + stream->writeString(reagent_name[i]); + } + + stream->write(casting_dur); + + stream->writeString(spell_name); + stream->writeLongString(511, spell_desc); + stream->writeString(icon_name); + stream->writeString(source_pack); + stream->writeFlag(is_placeholder); + stream->writeFlag(target_optional); + stream->write(free_target_style); +} + +void afxRPGMagicSpellData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&spell_target); + stream->read(&spell_range); + stream->read(&mana_cost); + stream->read(&n_reagents); + for (S32 i = 0; i < n_reagents; i++) + { + stream->read(&reagent_cost[i]); + reagent_name[i] = stream->readSTString(); + } + + stream->read(&casting_dur); + + spell_name = stream->readSTString(); + { + char value[512]; + stream->readLongString(511, value); + spell_desc = StringTable->insert(value); + } + icon_name = stream->readSTString(); + source_pack = stream->readSTString(); + is_placeholder = stream->readFlag(); + target_optional = stream->readFlag(); + stream->read(&free_target_style); +} + +#define NAME_FMT "" +#define TARGET_FMT "" +#define MANACOST_FMT "" +#define RANGE_FMT "" +#define CASTLEN_FMT "" +#define DESC_FMT "" +#define SMALL_BR "\n" +#define BR "\n" +#define PACK_FMT "" +#define PACK_NAME_FMT "" + + +char* afxRPGMagicSpellData::fmt_placeholder_desc(char* buffer, int len) const +{ + char pack_str[32]; + if (source_pack == ST_NULLSTRING) + dStrcpy(pack_str, "unknown"); + else + dSprintf(pack_str, 32, "%s", source_pack); + + dSprintf(buffer, len, + NAME_FMT "%s" BR + SMALL_BR + DESC_FMT "%s" BR + SMALL_BR SMALL_BR + PACK_FMT "source: " PACK_NAME_FMT "%s", + spell_name, spell_desc, pack_str); + + return buffer; +} + +char* afxRPGMagicSpellData::formatDesc(char* buffer, int len) const +{ + if (is_placeholder) + return fmt_placeholder_desc(buffer, len); + + char target_str[32]; target_str[0] = '\0'; + + // CAUTION: The following block of code is fragile and tricky since it depends + // on the underlying structures defined by ImplementEnumType/EndImplementEnumType. + // This done so the enum strings can be used directly in the spell description text. + for (unsigned int i = 0; i < _afxRPGMagicSpell_TargetType::_sEnumTable.getNumValues(); i++) + { + if (_afxRPGMagicSpell_TargetType::_sEnumTable[i].mInt == spell_target) + { + if (spell_target != TARGET_NOTHING) + { + dStrcpy(target_str, _afxRPGMagicSpell_TargetType::_sEnumTable[i].mName); + if (spell_target != TARGET_FREE && target_optional) + dStrcat(target_str, " (opt)"); + } + break; + } + } + + + char range_str[32]; range_str[0] = '\0'; + if (spell_range > 0) + { + if (spell_range == ((F32)((S32)spell_range))) + dSprintf(range_str, 32, "%d meter range", (S32) spell_range); + else + dSprintf(range_str, 32, "%.1f meter range", spell_range); + } + + char casting_str[32]; + if (casting_dur <= 0) + dStrcpy(casting_str, "instant"); + else + dSprintf(casting_str, 32, "%.1f sec cast", casting_dur); + + char pack_str[32]; + if (source_pack == ST_NULLSTRING) + dStrcpy(pack_str, "unknown"); + else + dSprintf(pack_str, 32, "%s", source_pack); + + dSprintf(buffer, len, + NAME_FMT "%s" TARGET_FMT "%s" BR + SMALL_BR + MANACOST_FMT "%d Mana" RANGE_FMT "%s" BR + CASTLEN_FMT "%s" BR + SMALL_BR SMALL_BR + DESC_FMT "%s" BR SMALL_BR + PACK_FMT "source: " PACK_NAME_FMT "%s", + spell_name, target_str, + mana_cost, range_str, + casting_str, + spell_desc, pack_str); + + return buffer; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/rpg/afxRPGMagicSpell.h b/Engine/source/afx/rpg/afxRPGMagicSpell.h new file mode 100644 index 000000000..832898547 --- /dev/null +++ b/Engine/source/afx/rpg/afxRPGMagicSpell.h @@ -0,0 +1,105 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_RPG_MAGIC_SPELL_H_ +#define _AFX_RPG_MAGIC_SPELL_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "core/util/tVector.h" +#include "T3D/gameBase/gameBase.h" +#include "console/typeValidators.h" + +#include "afx/afxEffectDefs.h" +#include "afx/afxEffectWrapper.h" +#include "afx/afxMagicMissile.h" + +class afxMagicMissileData; +class afxEffectWrapperData; +class SceneObject; + +class afxRPGMagicSpellDefs +{ +public: + // Migrate this stuff to RPG Magic-System + enum TargetType { + TARGET_NOTHING, + TARGET_SELF, + TARGET_FRIEND, + TARGET_ENEMY, + TARGET_CORPSE, + TARGET_FREE, + }; + + enum { + MAX_REAGENTS_PER_SPELL = 8, + }; +}; + +typedef afxRPGMagicSpellDefs::TargetType afxRPGMagicSpell_TargetType; +DefineEnumType( afxRPGMagicSpell_TargetType ); + +class afxRPGMagicSpellData : public GameBaseData, public afxRPGMagicSpellDefs +{ + typedef GameBaseData Parent; + +public: + F32 casting_dur; + StringTableEntry spell_name; + StringTableEntry spell_desc; + S32 spell_target; + F32 spell_range; + S32 mana_cost; + U8 n_reagents; + S8 reagent_cost[MAX_REAGENTS_PER_SPELL]; + StringTableEntry reagent_name[MAX_REAGENTS_PER_SPELL]; + StringTableEntry icon_name; + StringTableEntry source_pack; + bool is_placeholder; + U8 free_target_style; + bool target_optional; + +private: + char* fmt_placeholder_desc(char* buffer, int len) const; + +public: + /*C*/ afxRPGMagicSpellData(); + + char* formatDesc(char* buffer, int len) const; + bool requiresTarget() { return (spell_target == TARGET_ENEMY || spell_target == TARGET_CORPSE || spell_target == TARGET_FRIEND); } + + virtual bool onAdd(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxRPGMagicSpellData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_RPG_MAGIC_SPELL_H_ diff --git a/Engine/source/afx/ui/afxEventCatchAll.cpp b/Engine/source/afx/ui/afxEventCatchAll.cpp new file mode 100644 index 000000000..7f3da32b3 --- /dev/null +++ b/Engine/source/afx/ui/afxEventCatchAll.cpp @@ -0,0 +1,147 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "gui/3d/guiTSControl.h" + +class afxEventCatchAll : public GuiControl +{ + typedef GuiControl Parent; + +public: + /* C */ afxEventCatchAll() { } + + virtual void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + + virtual void onMouseUp(const GuiEvent&); + virtual void onMouseDown(const GuiEvent&); + virtual void onMouseMove(const GuiEvent&); + virtual void onMouseDragged(const GuiEvent&); + virtual void onMouseEnter(const GuiEvent&); + virtual void onMouseLeave(const GuiEvent&); + + virtual bool onMouseWheelUp(const GuiEvent&); + virtual bool onMouseWheelDown(const GuiEvent&); + + virtual void onRightMouseDown(const GuiEvent&); + virtual void onRightMouseUp(const GuiEvent&); + virtual void onRightMouseDragged(const GuiEvent&); + + DECLARE_CONOBJECT(afxEventCatchAll); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CONOBJECT(afxEventCatchAll); + +ConsoleDocClass( afxEventCatchAll, + "@brief A simple but useful GUI control that propagates all events to its parent " + "control.\n\n" + + "afxEventCatchAll is useful when you want certain events to be handled by its parent " + "afxTSCtrl and not get consumed by certain non-interactive controls like a text " + "HUD.\n\n" + + "@ingroup afxGUI\n" + "@ingroup AFX\n" +); + +void afxEventCatchAll::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + if (parent) parent->getCursor(cursor, showCursor, lastGuiEvent); +} + +void afxEventCatchAll::onMouseUp(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + if (parent) parent->onMouseUp(evt); +} + +void afxEventCatchAll::onMouseDown(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + if (parent) parent->onMouseDown(evt); +} + +void afxEventCatchAll::onMouseMove(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + if (parent) parent->onMouseMove(evt); +} + +void afxEventCatchAll::onMouseDragged(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + if (parent) parent->onMouseDragged(evt); +} + +void afxEventCatchAll::onMouseEnter(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + if (parent) parent->onMouseEnter(evt); +} + +void afxEventCatchAll::onMouseLeave(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + if (parent) parent->onMouseLeave(evt); +} + +bool afxEventCatchAll::onMouseWheelUp(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + return (parent) ? parent->onMouseWheelUp(evt) : false; +} + +bool afxEventCatchAll::onMouseWheelDown(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + return (parent) ? parent->onMouseWheelDown(evt) : false; +} + +void afxEventCatchAll::onRightMouseDown(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + if (parent) parent->onRightMouseDown(evt); +} + +void afxEventCatchAll::onRightMouseUp(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + if (parent) parent->onRightMouseUp(evt); +} + +void afxEventCatchAll::onRightMouseDragged(const GuiEvent& evt) +{ + GuiTSCtrl* parent = dynamic_cast(getParent()); + if (parent) parent->onRightMouseDragged(evt); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + + diff --git a/Engine/source/afx/ui/afxGuiSubstitutionField.cpp b/Engine/source/afx/ui/afxGuiSubstitutionField.cpp new file mode 100644 index 000000000..44ec97c84 --- /dev/null +++ b/Engine/source/afx/ui/afxGuiSubstitutionField.cpp @@ -0,0 +1,205 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "gui/editor/inspector/customField.h" +#include "gui/editor/guiInspector.h" + +#include "afx/ui/afxGuiSubstitutionField.h" + +IMPLEMENT_CONOBJECT( afxGuiSubstitutionField ); + +ConsoleDocClass( afxGuiSubstitutionField, + "@brief A customized variation of GuiInspectorField.\n\n" + + "A customized variation of GuiInspectorField.\n" + + "@ingroup AFX\n" +); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxGuiSubstitutionField::afxGuiSubstitutionField( GuiInspector* inspector, + GuiInspectorGroup* parent, + SimFieldDictionary::Entry* field) +{ + mInspector = inspector; + mParent = parent; + setBounds(0,0,100,20); + + subs_string = StringTable->insert(""); +} + +afxGuiSubstitutionField::afxGuiSubstitutionField() +{ + mInspector = NULL; + mParent = NULL; + subs_string = StringTable->insert(""); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxGuiSubstitutionField::setData( const char* data, bool callbacks ) +{ + if ( mField == NULL) + return; + + const U32 numTargets = mInspector->getNumInspectObjects(); + + //if( callbacks && numTargets > 1 ) + // Con::executef( mInspector, "beginCompoundUndo" ); + + if (data[0] != '$' && data[1] != '$') + { + data = StringTable->insert(avar("$$ %s", data), true); + } + else + { + data = StringTable->insert(data, true); + } + + for( U32 i = 0; i < numTargets; ++ i ) + { + SimObject* target = mInspector->getInspectObject( i ); + SimDataBlock* datablock = dynamic_cast(target); + if (datablock) + datablock->addSubstitution(mField->pFieldname, 0, data); + } + + //if( callbacks && numTargets > 1 ) + // Con::executef( mInspector, "endCompoundUndo" ); + + // Force our edit to update + updateValue(); +} + +const char* afxGuiSubstitutionField::getData( U32 inspectObjectIndex ) +{ + if( mField == NULL) + return ""; + + SimObject* target = mInspector->getInspectObject(inspectObjectIndex); + SimDataBlock* datablock = dynamic_cast(target); + if (datablock) + { + const char* current_subs = datablock->getSubstitution(mField->pFieldname, 0); + if (current_subs) + return StringTable->insert(avar("$$ %s", current_subs)); + } + + return StringTable->insert( "" ); +} + +/// Update this controls value to reflect that of the inspected field. +void afxGuiSubstitutionField::updateValue() +{ + if( mInspector->getNumInspectObjects() > 1 ) + { + // ?? + return; + } + + SimObject* target = mInspector->getInspectObject(0); + SimDataBlock* datablock = dynamic_cast(target); + if (datablock) + { + const char* current_subs = datablock->getSubstitution(mField->pFieldname, 0); + if (current_subs) + { + setValue(StringTable->insert(avar("$$ %s", current_subs))); + return; + } + } + + setValue(StringTable->insert("$$ -- undefined --")); +} + +void afxGuiSubstitutionField::setToolTip( StringTableEntry data ) +{ + mEdit->setDataField( StringTable->insert("tooltipprofile"), NULL, "GuiToolTipProfile" ); + mEdit->setDataField( StringTable->insert("hovertime"), NULL, "1000" ); + mEdit->setDataField( StringTable->insert("tooltip"), NULL, data ); +} + +bool afxGuiSubstitutionField::onAdd() +{ + if( !Parent::onAdd() ) + return false; + + return true; +} + +GuiControl* afxGuiSubstitutionField::constructEditControl() +{ + if (mField->doNotSubstitute) + { + GuiTextEditCtrl* retCtrl = new GuiTextEditCtrl(); + + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile" ); + + // Register the object + retCtrl->registerObject(); + + retCtrl->setText( StringTable->insert("n/a") ); + retCtrl->setActive(false); + + return retCtrl; + } + + GuiControl* retCtrl = new GuiTextEditCtrl(); + + retCtrl->setDataField( StringTable->insert("profile"), NULL, "GuiInspectorTextEditProfile" ); + + // Register the object + retCtrl->registerObject(); + + char szBuffer[512]; + dSprintf( szBuffer, 512, "%d.apply(%d.getText());",getId(), retCtrl->getId() ); + retCtrl->setField("AltCommand", szBuffer ); + retCtrl->setField("Validate", szBuffer ); + + return retCtrl; +} + +void afxGuiSubstitutionField::setValue( const char* newValue ) +{ + if (!mField->doNotSubstitute) + { + GuiTextEditCtrl *ctrl = dynamic_cast( mEdit ); + if ( ctrl != NULL ) + ctrl->setText( newValue ); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// protected: + +void afxGuiSubstitutionField::_executeSelectedCallback() +{ + Con::executef( mInspector, "onFieldSelected", mCaption, ConsoleBaseType::getType(mField->type)->getTypeName(), "Substitution Statement" ); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ui/afxGuiSubstitutionField.h b/Engine/source/afx/ui/afxGuiSubstitutionField.h new file mode 100644 index 000000000..a9cd7b141 --- /dev/null +++ b/Engine/source/afx/ui/afxGuiSubstitutionField.h @@ -0,0 +1,66 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_GUI_SUBSTITUTION_FIELD_H_ +#define _AFX_GUI_SUBSTITUTION_FIELD_H_ + +#include "console/simFieldDictionary.h" +#include "gui/editor/inspector/field.h" + +class afxGuiSubstitutionField : public GuiInspectorField +{ + typedef GuiInspectorField Parent; + +public: + + afxGuiSubstitutionField(GuiInspector *inspector, GuiInspectorGroup* parent, SimFieldDictionary::Entry* field); + afxGuiSubstitutionField(); + ~afxGuiSubstitutionField() {}; + + DECLARE_CONOBJECT(afxGuiSubstitutionField); + DECLARE_CATEGORY("AFX"); + + virtual void setData( const char* data, bool callbacks = true ); + virtual const char* getData( U32 inspectObjectIndex = 0 ); + virtual void updateValue(); + + virtual void setToolTip( StringTableEntry data ); + + virtual bool onAdd(); + + virtual GuiControl* constructEditControl(); + + virtual void setValue( const char* newValue ); + +protected: + + virtual void _executeSelectedCallback(); + +protected: + + String subs_string; +}; + +#endif // _AFX_SUBSTITUTION_FIELD_H_ diff --git a/Engine/source/afx/ui/afxGuiTextHud.cpp b/Engine/source/afx/ui/afxGuiTextHud.cpp new file mode 100644 index 000000000..90bc24b00 --- /dev/null +++ b/Engine/source/afx/ui/afxGuiTextHud.cpp @@ -0,0 +1,328 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "gui/3d/guiTSControl.h" +#include "gfx/gfxDrawUtil.h" +#include "T3D/gameBase/gameConnection.h" +#include "T3D/shapeBase.h" + +#include "afx/ui/afxGuiTextHud.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +Vector afxGuiTextHud::text_items; + +IMPLEMENT_CONOBJECT(afxGuiTextHud); + +ConsoleDocClass( afxGuiTextHud, + "@brief Similar to GuiShapeNameHud, afxGuiTextHud displays ShapeBase object names but " + "also allows Gui Text effects.\n\n" + + "@ingroup GuiGame\n" +); + +afxGuiTextHud::afxGuiTextHud() +{ + mFillColor.set( 0.25f, 0.25f, 0.25f, 0.25f ); + mFrameColor.set( 0, 0, 0, 1 ); + mTextColor.set( 0, 1, 0, 1 ); + mShowFill = false; + mShowFrame = true; + mVerticalOffset = 0.5f; + mDistanceFade = 0.1f; + mLabelAllShapes = false; + mEnableControlObjectOcclusion = false; +} + +void afxGuiTextHud::initPersistFields() +{ + addGroup("Colors"); + addField( "fillColor", TypeColorF, Offset( mFillColor, afxGuiTextHud ), + "..."); + addField( "frameColor", TypeColorF, Offset( mFrameColor, afxGuiTextHud ), + "..."); + addField( "textColor", TypeColorF, Offset( mTextColor, afxGuiTextHud ), + "..."); + endGroup("Colors"); + + addGroup("Misc"); + addField( "showFill", TypeBool, Offset( mShowFill, afxGuiTextHud ), + "..."); + addField( "showFrame", TypeBool, Offset( mShowFrame, afxGuiTextHud ), + "..."); + addField( "verticalOffset", TypeF32, Offset( mVerticalOffset, afxGuiTextHud ), + "..."); + addField( "distanceFade", TypeF32, Offset( mDistanceFade, afxGuiTextHud ), + "..."); + addField( "labelAllShapes", TypeBool, Offset( mLabelAllShapes, afxGuiTextHud ), + "..."); + addField( "enableControlObjectOcclusion", TypeBool, Offset( mEnableControlObjectOcclusion, afxGuiTextHud ), + "..."); + endGroup("Misc"); + + Parent::initPersistFields(); +} + +//---------------------------------------------------------------------------- +/// Core rendering method for this control. +/// +/// This method scans through all the current client ShapeBase objects. +/// If one is named, it displays the name and damage information for it. +/// +/// Information is offset from the center of the object's bounding box, +/// unless the object is a PlayerObjectType, in which case the eye point +/// is used. +/// +/// @param updateRect Extents of control. +void afxGuiTextHud::onRender( Point2I, const RectI &updateRect) +{ + // Background fill first + if (mShowFill) + GFX->getDrawUtil()->drawRectFill(updateRect, mFillColor.toColorI()); + + // Must be in a TS Control + GuiTSCtrl *parent = dynamic_cast(getParent()); + if (!parent) return; + + // Must have a connection and control object + GameConnection* conn = GameConnection::getConnectionToServer(); + if (!conn) + return; + + GameBase * control = dynamic_cast(conn->getControlObject()); + if (!control) + return; + + // Get control camera info + MatrixF cam; + Point3F camPos; + VectorF camDir; + conn->getControlCameraTransform(0,&cam); + cam.getColumn(3, &camPos); + cam.getColumn(1, &camDir); + + F32 camFovCos; + conn->getControlCameraFov(&camFovCos); + camFovCos = mCos(mDegToRad(camFovCos) / 2); + + // Visible distance info & name fading + F32 visDistance = gClientSceneGraph->getVisibleDistance(); + F32 visDistanceSqr = visDistance * visDistance; + F32 fadeDistance = visDistance * mDistanceFade; + + // Collision info. We're going to be running LOS tests and we + // don't want to collide with the control object. + static U32 losMask = TerrainObjectType | TerrainLikeObjectType | ShapeBaseObjectType; + + if (!mEnableControlObjectOcclusion) + control->disableCollision(); + + if (mLabelAllShapes) + { + // This section works just like GuiShapeNameHud and renders labels for + // all the shapes. + + // All ghosted objects are added to the server connection group, + // so we can find all the shape base objects by iterating through + // our current connection. + for (SimSetIterator itr(conn); *itr; ++itr) + { + ///if ((*itr)->getTypeMask() & ShapeBaseObjectType) + ///{ + ShapeBase* shape = dynamic_cast(*itr); + if ( shape ) { + if (shape != control && shape->getShapeName()) + { + + // Target pos to test, if it's a player run the LOS to his eye + // point, otherwise we'll grab the generic box center. + Point3F shapePos; + if (shape->getTypeMask() & PlayerObjectType) + { + MatrixF eye; + + // Use the render eye transform, otherwise we'll see jittering + shape->getRenderEyeTransform(&eye); + eye.getColumn(3, &shapePos); + } + else + { + // Use the render transform instead of the box center + // otherwise it'll jitter. + MatrixF srtMat = shape->getRenderTransform(); + srtMat.getColumn(3, &shapePos); + } + + VectorF shapeDir = shapePos - camPos; + + // Test to see if it's in range + F32 shapeDist = shapeDir.lenSquared(); + if (shapeDist == 0 || shapeDist > visDistanceSqr) + continue; + shapeDist = mSqrt(shapeDist); + + // Test to see if it's within our viewcone, this test doesn't + // actually match the viewport very well, should consider + // projection and box test. + shapeDir.normalize(); + F32 dot = mDot(shapeDir, camDir); + if (dot < camFovCos) + continue; + + // Test to see if it's behind something, and we want to + // ignore anything it's mounted on when we run the LOS. + RayInfo info; + shape->disableCollision(); + SceneObject *mount = shape->getObjectMount(); + if (mount) + mount->disableCollision(); + bool los = !gClientContainer.castRay(camPos, shapePos,losMask, &info); + shape->enableCollision(); + if (mount) + mount->enableCollision(); + + if (!los) + continue; + + // Project the shape pos into screen space and calculate + // the distance opacity used to fade the labels into the + // distance. + Point3F projPnt; + shapePos.z += mVerticalOffset; + if (!parent->project(shapePos, &projPnt)) + continue; + F32 opacity = (shapeDist < fadeDistance)? 1.0: + 1.0 - (shapeDist - fadeDistance) / (visDistance - fadeDistance); + + // Render the shape's name + drawName(Point2I((S32)projPnt.x, (S32)projPnt.y),shape->getShapeName(),opacity); + } + } + } + } + + // This section renders all text added by afxGuiText effects. + for (S32 i = 0; i < text_items.size(); i++) + { + HudTextSpec* spec = &text_items[i]; + if (spec->text && spec->text[0] != '\0') + { + VectorF shapeDir = spec->pos - camPos; + + // do range test + F32 shapeDist = shapeDir.lenSquared(); + if (shapeDist == 0 || shapeDist > visDistanceSqr) + continue; + shapeDist = mSqrt(shapeDist); + + // Test to see if it's within our viewcone, this test doesn't + // actually match the viewport very well, should consider + // projection and box test. + shapeDir.normalize(); + F32 dot = mDot(shapeDir, camDir); + if (dot < camFovCos) + continue; + + // Test to see if it's behind something, and we want to + // ignore anything it's mounted on when we run the LOS. + RayInfo info; + if (spec->obj) + spec->obj->disableCollision(); + bool los = !gClientContainer.castRay(camPos, spec->pos, losMask, &info); + if (spec->obj) + spec->obj->enableCollision(); + if (!los) + continue; + + // Project the shape pos into screen space. + Point3F projPnt; + if (!parent->project(spec->pos, &projPnt)) + continue; + + // Calculate the distance opacity used to fade text into the distance. + F32 opacity = (shapeDist < fadeDistance)? 1.0 : 1.0 - (shapeDist - fadeDistance) / (25.0f); + if (opacity > 0.01f) + drawName(Point2I((S32)projPnt.x, (S32)projPnt.y), spec->text, opacity, &spec->text_clr); + } + } + + // Restore control object collision + if (!mEnableControlObjectOcclusion) + control->enableCollision(); + + // Border last + if (mShowFrame) + GFX->getDrawUtil()->drawRect(updateRect, mFrameColor.toColorI()); + + reset(); +} + +//---------------------------------------------------------------------------- +/// Render object names. +/// +/// Helper function for GuiShapeNameHud::onRender +/// +/// @param offset Screen coordinates to render name label. (Text is centered +/// horizontally about this location, with bottom of text at +/// specified y position.) +/// @param name String name to display. +/// @param opacity Opacity of name (a fraction). +void afxGuiTextHud::drawName(Point2I offset, const char *name, F32 opacity, LinearColorF* color) +{ + // Center the name + offset.x -= mProfile->mFont->getStrWidth((const UTF8 *)name) / 2; + offset.y -= mProfile->mFont->getHeight(); + + // Deal with opacity and draw. + LinearColorF draw_color = (color) ? *color : mTextColor; + draw_color.alpha *= opacity; + GFX->getDrawUtil()->setBitmapModulation(draw_color.toColorI()); + GFX->getDrawUtil()->drawText(mProfile->mFont, offset, name); + GFX->getDrawUtil()->clearBitmapModulation(); +} + +void afxGuiTextHud::addTextItem(const Point3F& pos, const char* text, LinearColorF& color, SceneObject* obj) +{ + if (!text || text[0] == '\0') + return; + + HudTextSpec spec; + spec.pos = pos; + spec.text = text; + spec.text_clr = color; + spec.obj = obj; + + text_items.push_back(spec); +} + +void afxGuiTextHud::reset() +{ + text_items.clear(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/ui/afxGuiTextHud.h b/Engine/source/afx/ui/afxGuiTextHud.h new file mode 100644 index 000000000..285f74193 --- /dev/null +++ b/Engine/source/afx/ui/afxGuiTextHud.h @@ -0,0 +1,87 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_GUI_TEXT_HUD_H_ +#define _AFX_GUI_TEXT_HUD_H_ + +#include "gui/core/guiControl.h" + +//---------------------------------------------------------------------------- +/// Displays name & damage above shape objects. +/// +/// This control displays the name and damage value of all named +/// ShapeBase objects on the client. The name and damage of objects +/// within the control's display area are overlayed above the object. +/// +/// This GUI control must be a child of a TSControl, and a server connection +/// and control object must be present. +/// +/// This is a stand-alone control and relies only on the standard base GuiControl. +class afxGuiTextHud : public GuiControl +{ + typedef GuiControl Parent; + + struct HudTextSpec + { + Point3F pos; + const char* text; + LinearColorF text_clr; + SceneObject* obj; + }; + + static Vector text_items; + + // field data + LinearColorF mFillColor; + LinearColorF mFrameColor; + LinearColorF mTextColor; + + F32 mVerticalOffset; + F32 mDistanceFade; + bool mShowFrame; + bool mShowFill; + bool mLabelAllShapes; + bool mEnableControlObjectOcclusion; + +protected: + void drawName( Point2I offset, const char *buf, F32 opacity, LinearColorF* color=0); + +public: + afxGuiTextHud(); + + // GuiControl + virtual void onRender(Point2I offset, const RectI &updateRect); + + static void initPersistFields(); + static void addTextItem(const Point3F& pos, const char* text, LinearColorF& color, SceneObject* obj=0); + static void reset(); + + DECLARE_CONOBJECT( afxGuiTextHud ); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif //_AFX_GUI_TEXT_HUD_H_ diff --git a/Engine/source/afx/ui/afxProgressBase.h b/Engine/source/afx/ui/afxProgressBase.h new file mode 100644 index 000000000..70f777002 --- /dev/null +++ b/Engine/source/afx/ui/afxProgressBase.h @@ -0,0 +1,37 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_PROGRESS_BASE_H_ +#define _AFX_PROGRESS_BASE_H_ + +class afxProgressBase +{ +public: + virtual void setProgress(F32 value)=0; +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif //_AFX_PROGRESS_BASE_H_ diff --git a/Engine/source/afx/ui/afxSpellButton.cpp b/Engine/source/afx/ui/afxSpellButton.cpp new file mode 100644 index 000000000..149423dd0 --- /dev/null +++ b/Engine/source/afx/ui/afxSpellButton.cpp @@ -0,0 +1,393 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +//------------------------------------- +// +// Bitmap Button Contrl +// Set 'bitmap' comsole field to base name of bitmaps to use. This control will +// append '_n' for normal +// append '_h' for hilighted +// append '_d' for depressed +// +// if bitmap cannot be found it will use the default bitmap to render. +// +// if the extent is set to (0,0) in the gui editor and appy hit, this control will +// set it's extent to be exactly the size of the normal bitmap (if present) +// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" +#include "gfx/gfxDrawUtil.h" + +#include "afx/ui/afxSpellButton.h" +#include "afx/afxSpellBook.h" +#include "afx/afxMagicSpell.h" +#include "afx/rpg/afxRPGMagicSpell.h" + +IMPLEMENT_CONOBJECT(afxSpellButton); + +ConsoleDocClass( afxSpellButton, + "@brief A GUI button with some special features that are useful for casting AFX spells.\n\n" + + "@ingroup afxGUI\n" + "@ingroup AFX\n" +); + +#define COOLDOWN_PROFILE &GFXDefaultGUIProfile, avar("%s() - Cooldown Texture (line %d)", __FUNCTION__, __LINE__) + +StringTableEntry afxSpellButton::sUnknownSpellBitmap = ""; +StringTableEntry afxSpellButton::sSpellCooldownBitmaps = ""; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxSpellButton::afxSpellButton() +{ + if (sUnknownSpellBitmap == NULL) + sUnknownSpellBitmap = ST_NULLSTRING; + if (sSpellCooldownBitmaps == NULL) + sSpellCooldownBitmaps = ST_NULLSTRING; + mBitmapName = ST_NULLSTRING; + setExtent(140, 30); + spellbook = NULL; + book_slot.set(0, 0); +} + +afxSpellButton::~afxSpellButton() +{ +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxSpellButton::initPersistFields() +{ + addField("bitmap", TypeFilename, Offset(mBitmapName, afxSpellButton), + "..."); + addField("book_slot", TypePoint2I, Offset(book_slot, afxSpellButton), + "..."); + + Parent::initPersistFields(); + + Con::addVariable("pref::afxSpellButton::unknownSpellBitmap", TypeFilename, &sUnknownSpellBitmap); + Con::addVariable("pref::afxSpellButton::spellCooldownBitmaps", TypeFilename, &sSpellCooldownBitmaps); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +bool afxSpellButton::onAdd() +{ + if (!Parent::onAdd()) + return false; + + if (sSpellCooldownBitmaps != NULL) + { + char buffer[256]; + for (int i = 0; i < NUM_COOLDOWN_FRAMES; i++) + { + dSprintf(buffer, 256, "%s_%.2d", sSpellCooldownBitmaps, i); + cooldown_txrs[i].set(buffer, COOLDOWN_PROFILE); + } + } + + return true; +} + +bool afxSpellButton::onWake() +{ + if (! Parent::onWake()) + return false; + + setActive(true); + + update_bitmap(); + + return true; +} + +void afxSpellButton::onSleep() +{ + mTextureNormal = NULL; + mTextureHilight = NULL; + mTextureDepressed = NULL; + Parent::onSleep(); +} + +void afxSpellButton::onMouseEnter(const GuiEvent &event) +{ + Parent::onMouseEnter(event); + Con::executef(this, "onMouseEnter"); +} + +void afxSpellButton::onMouseLeave(const GuiEvent &event) +{ + Parent::onMouseLeave(event); + Con::executef(this, "onMouseLeave"); +} + +void afxSpellButton::inspectPostApply() +{ + // if the extent is set to (0,0) in the gui editor and apply hit, this control will + // set it's extent to be exactly the size of the normal bitmap (if present) + Parent::inspectPostApply(); + + if ((getWidth() == 0) && (getHeight() == 0) && mTextureNormal) + { + setExtent(mTextureNormal->getWidth(), mTextureNormal->getHeight()); + } +} + +void afxSpellButton::setBitmap(const char *name, bool placeholder) +{ + mBitmapName = (name) ? StringTable->insert(name) : ST_NULLSTRING; + if (!isAwake()) + return; + + if (mBitmapName != ST_NULLSTRING) + { + char buffer[1024]; + char *p; + + if (placeholder) + { + dStrcpy(buffer, name); + p = buffer + dStrlen(buffer); + + dStrcpy(p, "_i"); + mTextureInactive.set(buffer, COOLDOWN_PROFILE); + mTextureNormal = mTextureInactive; + mTextureHilight = mTextureInactive; + mTextureDepressed = mTextureInactive; + setActive(false); + } + else + { + dStrcpy(buffer, name); + p = buffer + dStrlen(buffer); + dStrcpy(p, "_n"); + mTextureNormal.set(buffer, COOLDOWN_PROFILE); + dStrcpy(p, "_h"); + mTextureHilight.set(buffer, COOLDOWN_PROFILE); + if (!mTextureHilight) + mTextureHilight = mTextureNormal; + dStrcpy(p, "_d"); + mTextureDepressed.set(buffer, COOLDOWN_PROFILE); + if (!mTextureDepressed) + mTextureDepressed = mTextureHilight; + dStrcpy(p, "_i"); + mTextureInactive.set(buffer, COOLDOWN_PROFILE); + if (!mTextureInactive) + mTextureInactive = mTextureNormal; + setActive(true); + } + } + else + { + mTextureNormal = NULL; + mTextureHilight = NULL; + mTextureDepressed = NULL; + mTextureInactive = NULL; + } + + setUpdate(); +} + +void afxSpellButton::onRender(Point2I offset, const RectI& updateRect) +{ + enum { NORMAL, HILIGHT, DEPRESSED, INACTIVE } state = NORMAL; + + if (mActive) + { + if (mMouseOver) state = HILIGHT; + if (mDepressed || mStateOn) state = DEPRESSED; + } + else + state = INACTIVE; + + switch (state) + { + case NORMAL: renderButton(mTextureNormal, offset, updateRect); break; + case HILIGHT: renderButton(mTextureHilight ? mTextureHilight : mTextureNormal, offset, updateRect); break; + case DEPRESSED: renderButton(mTextureDepressed, offset, updateRect); break; + case INACTIVE: renderButton(mTextureInactive ? mTextureInactive : mTextureNormal, offset, updateRect); break; + } +} + +void afxSpellButton::onDeleteNotify(SimObject* obj) +{ + // Handle Shape Deletion + afxSpellBook* book = dynamic_cast(obj); + if (book != NULL) + { + if (book == spellbook) + { + spellbook = NULL; + setBitmap(""); + setVisible(false); + return; + } + } + + Parent::onDeleteNotify(obj); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// protected: + +void afxSpellButton::renderButton(GFXTexHandle &texture, Point2I &offset, + const RectI& updateRect) +{ + if (texture) + { + RectI rect(offset, getExtent()); + GFX->getDrawUtil()->clearBitmapModulation(); + GFX->getDrawUtil()->drawBitmapStretch(texture, rect); + + if (spellbook) + { + F32 cooldown = spellbook->getCooldownFactor(book_slot.x, book_slot.y); + if (cooldown < 1.0f) + { + + if (cooldown_txrs[(int)(36.0f*cooldown)]) + GFX->getDrawUtil()->drawBitmapStretch(cooldown_txrs[(int)(36.0f*cooldown)], rect); + } + } + + renderChildControls( offset, updateRect); + } + else + Parent::onRender(offset, updateRect); +} + +void afxSpellButton::update_bitmap() +{ + const char* icon_name = 0; + + bool is_placeholder = false; + if (spellbook) + { + icon_name = spellbook->getSpellIcon(book_slot.x, book_slot.y); + is_placeholder = spellbook->isPlaceholder(book_slot.x, book_slot.y); + if (icon_name && icon_name[0] == '\0') + icon_name = sUnknownSpellBitmap; + } + + if (icon_name) + { + setBitmap(icon_name, is_placeholder); + setVisible(true); + } + else + { + setBitmap(""); + setVisible(false); + } +} + +void afxSpellButton::setSpellBook(afxSpellBook* book, U8 page) +{ + book_slot.x = page; + + if (spellbook) + clearNotify(spellbook); + + spellbook = book; + update_bitmap(); + + if (spellbook) + deleteNotify(spellbook); +} + +void afxSpellButton::setPage(U8 page) +{ + book_slot.x = page; + update_bitmap(); +} + +char* afxSpellButton::formatDesc(char* buffer, int len) const +{ + return (spellbook) ? spellbook->formatDesc(buffer, len, book_slot.x, book_slot.y) : (char*)""; +} + +afxMagicSpellData* afxSpellButton::getSpellDataBlock() const +{ + return (spellbook) ? spellbook->getSpellData(book_slot.x, book_slot.y) : 0; +} + +afxRPGMagicSpellData* afxSpellButton::getSpellRPGDataBlock() const +{ + return (spellbook) ? spellbook->getSpellRPGData(book_slot.x, book_slot.y) : 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod( afxSpellButton, onSpellbookChange, void, ( afxSpellBook* spellbook, unsigned int page ),, + "Notify an afxSpellButton when its associated spellbook has changed.\n" ) +{ + object->setSpellBook(spellbook, (U8)page); +} + +DefineEngineMethod( afxSpellButton, onTurnPage, void, ( unsigned int page ),, + "Notify an afxSpellButton when the spellbook turns to a new page.\n" ) +{ + object->setPage((U8)page); +} + +DefineEngineMethod( afxSpellButton, getSpellDescription, const char*, (),, + "Get the text description of a spell.\n" ) +{ + char buf[1024]; + return object->formatDesc(buf, 1024); +} + +DefineEngineMethod( afxSpellButton, getSpellDataBlock, S32, (),, + "Get the spell's datablock.\n" ) +{ + afxMagicSpellData* spell_data = object->getSpellDataBlock(); + return (spell_data) ? spell_data->getId() : -1; +} + +DefineEngineMethod( afxSpellButton, getSpellRPGDataBlock, S32, (),, + "Get the spell's RPG datablock.\n" ) +{ + afxRPGMagicSpellData* spell_rpg_data = object->getSpellRPGDataBlock(); + return (spell_rpg_data) ? spell_rpg_data->getId() : -1; +} + +DefineEngineMethod( afxSpellButton, useFreeTargeting, bool, (),, + "Test if spell uses free targeting.\n" ) +{ + afxRPGMagicSpellData* spell_rpg_data = object->getSpellRPGDataBlock(); + return (spell_rpg_data) ? (spell_rpg_data->spell_target == afxRPGMagicSpellData::TARGET_FREE) : false; +} + +DefineEngineMethod( afxSpellButton, getFreeTargetStyle, S32, (),, + "Get the free targeting style used by the spell.\n" ) +{ + afxRPGMagicSpellData* spell_rpg_data = object->getSpellRPGDataBlock(); + return (spell_rpg_data) ? spell_rpg_data->free_target_style : 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ui/afxSpellButton.h b/Engine/source/afx/ui/afxSpellButton.h new file mode 100644 index 000000000..02db1e3cc --- /dev/null +++ b/Engine/source/afx/ui/afxSpellButton.h @@ -0,0 +1,103 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_SPELL_BUTTON_H_ +#define _AFX_SPELL_BUTTON_H_ + +#include "gui/buttons/guiButtonCtrl.h" + +///------------------------------------- +/// Bitmap Button Contrl +/// Set 'bitmap' comsole field to base name of bitmaps to use. This control will +/// append '_n' for normal +/// append '_h' for hilighted +/// append '_d' for depressed +/// +/// if bitmap cannot be found it will use the default bitmap to render. +/// +/// if the extent is set to (0,0) in the gui editor and appy hit, this control will +/// set it's extent to be exactly the size of the normal bitmap (if present) +/// + +class afxSpellBook; +class afxMagicSpellData; +class afxRPGMagicSpellData; + +class afxSpellButton : public GuiButtonCtrl +{ +private: + typedef GuiButtonCtrl Parent; + + enum { NUM_COOLDOWN_FRAMES = 36 }; + +protected: + static StringTableEntry sUnknownSpellBitmap; + static StringTableEntry sSpellCooldownBitmaps; + + StringTableEntry mBitmapName; + GFXTexHandle mTextureNormal; + GFXTexHandle mTextureHilight; + GFXTexHandle mTextureDepressed; + GFXTexHandle mTextureInactive; + + afxSpellBook* spellbook; + Point2I book_slot; + + GFXTexHandle cooldown_txrs[NUM_COOLDOWN_FRAMES]; + + void update_bitmap(); + void renderButton(GFXTexHandle &texture, Point2I &offset, const RectI& updateRect); + +public: + /*C*/ afxSpellButton(); + /*D*/ ~afxSpellButton(); + + void setBitmap(const char *name, bool placholder=false); + void setSpellBook(afxSpellBook*, U8 page); + void setPage(U8 page); + char* formatDesc(char* buffer, int len) const; + + afxMagicSpellData* getSpellDataBlock() const; + afxRPGMagicSpellData* getSpellRPGDataBlock() const; + + virtual bool onAdd(); + virtual bool onWake(); + virtual void onSleep(); + virtual void inspectPostApply(); + virtual void onMouseEnter(const GuiEvent &event); + virtual void onMouseLeave(const GuiEvent &event); + virtual void onRender(Point2I offset, const RectI &updateRect); + + virtual void onDeleteNotify(SimObject*); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxSpellButton); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif //_GUI_BITMAP_BUTTON_CTRL_H diff --git a/Engine/source/afx/ui/afxSpellCastBar.cpp b/Engine/source/afx/ui/afxSpellCastBar.cpp new file mode 100644 index 000000000..f08740743 --- /dev/null +++ b/Engine/source/afx/ui/afxSpellCastBar.cpp @@ -0,0 +1,162 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" +#include "gui/core/guiControl.h" +#include "gfx/gfxDrawUtil.h" + +#include "afx/ui/afxProgressBase.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxSpellCastBar : public GuiControl, public afxProgressBase +{ + typedef GuiControl Parent; + + bool want_border; + bool want_background; + bool use_alt_final_color; + LinearColorF rgba_background; + LinearColorF rgba_border; + LinearColorF rgba_fill; + LinearColorF rgba_fill_final; + + F32 fraction; + +public: + /*C*/ afxSpellCastBar(); + + virtual void onRender(Point2I, const RectI&); + + void setFraction(F32 frac); + F32 getFraction() const { return fraction; } + + virtual void setProgress(F32 value) { setFraction(value); } + virtual void onStaticModified(const char* slotName, const char* newValue = NULL); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxSpellCastBar); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CONOBJECT(afxSpellCastBar); + +ConsoleDocClass( afxSpellCastBar, + "@brief A GUI progress bar useful as a spell casting bar.\n\n" + + "@ingroup afxGUI\n" + "@ingroup AFX\n" +); + +afxSpellCastBar::afxSpellCastBar() +{ + want_border = true; + want_background = true; + use_alt_final_color = false; + rgba_background.set(0.0f, 0.0f, 0.0f, 0.5f); + rgba_border.set(0.5f, 0.5f, 0.5f, 1.0f); + rgba_fill.set(0.0f, 1.0f, 1.0f, 1.0f); + rgba_fill_final.set(0.0f, 1.0f, 1.0f, 1.0f); + + fraction = 0.5f; +} + +void afxSpellCastBar::setFraction(F32 frac) +{ + fraction = mClampF(frac, 0.0f, 1.0f); +} + +void afxSpellCastBar::onStaticModified(const char* slotName, const char* newValue) +{ + Parent::onStaticModified(slotName, newValue); + if (dStricmp(slotName, "fillColorFinal") == 0) + use_alt_final_color = true; +} + +// STATIC +void afxSpellCastBar::initPersistFields() +{ + addGroup("Colors"); + addField( "backgroundColor", TypeColorF, Offset(rgba_background, afxSpellCastBar), + "..."); + addField( "borderColor", TypeColorF, Offset(rgba_border, afxSpellCastBar), + "..."); + addField( "fillColor", TypeColorF, Offset(rgba_fill, afxSpellCastBar), + "..."); + addField( "fillColorFinal", TypeColorF, Offset(rgba_fill_final, afxSpellCastBar), + "..."); + endGroup("Colors"); + + Parent::initPersistFields(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +void afxSpellCastBar::onRender(Point2I offset, const RectI &updateRect) +{ + LinearColorF color; + + // draw the background + if (want_background) + { + color.set(rgba_background.red, rgba_background.green, rgba_background.blue, rgba_background.alpha*fade_amt); + GFX->getDrawUtil()->drawRectFill(updateRect, color.toColorI()); + } + + // calculate the rectangle dimensions + RectI rect(updateRect); + rect.extent.x = (S32)(rect.extent.x * fraction); + + // draw the filled part of bar + if (fraction >= 1.0f && use_alt_final_color) + color.set(rgba_fill_final.red, rgba_fill_final.green, rgba_fill_final.blue, rgba_fill_final.alpha*fade_amt); + else + color.set(rgba_fill.red, rgba_fill.green, rgba_fill.blue, rgba_fill.alpha*fade_amt); + + GFX->getDrawUtil()->drawRectFill(rect, color.toColorI()); + + // draw the border + if (want_border) + { + color.set(rgba_border.red, rgba_border.green, rgba_border.blue, rgba_border.alpha*fade_amt); + GFX->getDrawUtil()->drawRect(updateRect, color.toColorI()); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod(afxSpellCastBar, setProgress, void, (float percentDone),, + "Set the progress percentage on the progress-bar.\n\n" + "@ingroup AFX") +{ + object->setFraction(percentDone); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ui/afxSpellCastBar.h b/Engine/source/afx/ui/afxSpellCastBar.h new file mode 100644 index 000000000..c4e948cd8 --- /dev/null +++ b/Engine/source/afx/ui/afxSpellCastBar.h @@ -0,0 +1,24 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ui/afxStatusBar.cpp b/Engine/source/afx/ui/afxStatusBar.cpp new file mode 100644 index 000000000..b2e79e69c --- /dev/null +++ b/Engine/source/afx/ui/afxStatusBar.cpp @@ -0,0 +1,202 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" +#include "gui/core/guiControl.h" +#include "T3D/gameBase/gameConnection.h" +#include "T3D/shapeBase.h" +#include "gfx/gfxDrawUtil.h" + +#include "afx/ui/afxProgressBase.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxStatusBar : public GuiControl, public afxProgressBase +{ + typedef GuiControl Parent; + + LinearColorF rgba_fill; + + F32 fraction; + ShapeBase* shape; + bool show_energy; + bool monitor_player; + +public: + /*C*/ afxStatusBar(); + + virtual void onRender(Point2I, const RectI&); + + void setFraction(F32 frac); + F32 getFraction() const { return fraction; } + + virtual void setProgress(F32 value) { setFraction(value); } + + void setShape(ShapeBase* s); + void clearShape() { setShape(NULL); } + + virtual bool onWake(); + virtual void onSleep(); + virtual void onMouseDown(const GuiEvent &event); + virtual void onDeleteNotify(SimObject*); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxStatusBar); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CONOBJECT(afxStatusBar); + +ConsoleDocClass( afxStatusBar, + "@brief A GUI status bar for tracking and displaying health and energy of ShapeBase " + "objects.\n\n" + + "@ingroup afxGUI\n" + "@ingroup AFX\n" +); + +afxStatusBar::afxStatusBar() +{ + rgba_fill.set(0.0f, 1.0f, 1.0f, 1.0f); + + fraction = 1.0f; + shape = 0; + show_energy = false; + monitor_player = false; +} + +void afxStatusBar::setFraction(F32 frac) +{ + fraction = mClampF(frac, 0.0f, 1.0f); +} + +void afxStatusBar::setShape(ShapeBase* s) +{ + if (shape) + clearNotify(shape); + shape = s; + if (shape) + deleteNotify(shape); +} + +void afxStatusBar::onDeleteNotify(SimObject* obj) +{ + if (shape == (ShapeBase*)obj) + { + shape = NULL; + return; + } + + Parent::onDeleteNotify(obj); +} + +bool afxStatusBar::onWake() +{ + if (!Parent::onWake()) + return false; + + return true; +} + +void afxStatusBar::onSleep() +{ + //clearShape(); + Parent::onSleep(); +} + +// STATIC +void afxStatusBar::initPersistFields() +{ + addField("fillColor", TypeColorF, Offset(rgba_fill, afxStatusBar), + "..."); + addField("displayEnergy", TypeBool, Offset(show_energy, afxStatusBar), + "..."); + addField("monitorPlayer", TypeBool, Offset(monitor_player, afxStatusBar), + "..."); + + Parent::initPersistFields(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + + +void afxStatusBar::onRender(Point2I offset, const RectI &updateRect) +{ + if (!shape) + return; + + if (shape->getDamageState() != ShapeBase::Enabled) + fraction = 0.0f; + else + fraction = (show_energy) ? shape->getEnergyValue() : (1.0f - shape->getDamageValue()); + + // set alpha value for the fill area + rgba_fill.alpha = 1.0f; + + // calculate the rectangle dimensions + RectI rect(updateRect); + rect.extent.x = (S32)(rect.extent.x*fraction); + + // draw the filled part of bar + GFX->getDrawUtil()->drawRectFill(rect, rgba_fill.toColorI()); +} + +void afxStatusBar::onMouseDown(const GuiEvent &event) +{ + GuiControl *parent = getParent(); + if (parent) + parent->onMouseDown(event); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod(afxStatusBar, setProgress, void, (float percentDone),, + "Set the progress percentage on the status-bar.\n\n" + "@ingroup AFX") +{ + object->setFraction(percentDone); +} + +DefineEngineMethod(afxStatusBar, setShape, void, (ShapeBase* shape),, + "Associate a ShapeBase-derived object with the status-bar.\n\n" + "@ingroup AFX") +{ + object->setShape(shape); +} + +DefineEngineMethod(afxStatusBar, clearShape, void, (),, + "Clear out any ShapeBase-derived object associated with the status-bar.\n\n" + "@ingroup AFX") +{ + object->clearShape(); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ui/afxStatusBar.h b/Engine/source/afx/ui/afxStatusBar.h new file mode 100644 index 000000000..c4e948cd8 --- /dev/null +++ b/Engine/source/afx/ui/afxStatusBar.h @@ -0,0 +1,24 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/ui/afxStatusBox.cpp b/Engine/source/afx/ui/afxStatusBox.cpp new file mode 100644 index 000000000..80344e8f7 --- /dev/null +++ b/Engine/source/afx/ui/afxStatusBox.cpp @@ -0,0 +1,52 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/ui/afxStatusBox.h" + +IMPLEMENT_CONOBJECT(afxStatusBox); + +ConsoleDocClass( afxStatusBox, + "@brief A simple GUI control used to contain player status bars and a label.\n\n" + + "@ingroup afxGUI\n" + "@ingroup AFX\n" +); + +afxStatusBox::afxStatusBox() +{ +} + +void afxStatusBox::onMouseDown(const GuiEvent &event) +{ + Parent::onMouseDown(event); + Con::executef(this, "onMouseDown"); +} + +void afxStatusBox::onSleep() +{ + Parent::onSleep(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ui/afxStatusBox.h b/Engine/source/afx/ui/afxStatusBox.h new file mode 100644 index 000000000..f048cef2f --- /dev/null +++ b/Engine/source/afx/ui/afxStatusBox.h @@ -0,0 +1,50 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_STATUS_BOX_H_ +#define _AFX_STATUS_BOX_H_ + +#include "afx/arcaneFX.h" + +#include "gui/controls/guiBitmapCtrl.h" + +class afxStatusBox : public GuiBitmapCtrl +{ +private: + typedef GuiBitmapCtrl Parent; + +public: + /*C*/ afxStatusBox(); + + virtual void onMouseDown(const GuiEvent &event); + virtual void onSleep(); + + DECLARE_CONOBJECT(afxStatusBox); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif //_AFX_STATUS_BOX_H_ diff --git a/Engine/source/afx/ui/afxStatusLabel.cpp b/Engine/source/afx/ui/afxStatusLabel.cpp new file mode 100644 index 000000000..bc3628f2e --- /dev/null +++ b/Engine/source/afx/ui/afxStatusLabel.cpp @@ -0,0 +1,49 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/ui/afxStatusLabel.h" + +IMPLEMENT_CONOBJECT(afxStatusLabel); + +ConsoleDocClass( afxStatusLabel, + "@brief A simple GUI control used for a player status label.\n\n" + + "@ingroup afxGUI\n" + "@ingroup AFX\n" +); + +afxStatusLabel::afxStatusLabel() +{ +} + +void afxStatusLabel::onMouseDown(const GuiEvent &event) +{ + GuiControl *parent = getParent(); + if (parent) + parent->onMouseDown(event); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ui/afxStatusLabel.h b/Engine/source/afx/ui/afxStatusLabel.h new file mode 100644 index 000000000..a81c92d86 --- /dev/null +++ b/Engine/source/afx/ui/afxStatusLabel.h @@ -0,0 +1,49 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_STATUS_LABEL_H_ +#define _AFX_STATUS_LABEL_H_ + +#include "afx/arcaneFX.h" + +#include "gui/controls/guiMLTextCtrl.h" + +class afxStatusLabel : public GuiMLTextCtrl +{ +private: + typedef GuiMLTextCtrl Parent; + +public: + /*C*/ afxStatusLabel(); + + virtual void onMouseDown(const GuiEvent &event); + + DECLARE_CONOBJECT(afxStatusLabel); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif //_AFX_STATUS_LABEL_H_ diff --git a/Engine/source/afx/ui/afxTSCtrl.cpp b/Engine/source/afx/ui/afxTSCtrl.cpp new file mode 100644 index 000000000..5766b43a4 --- /dev/null +++ b/Engine/source/afx/ui/afxTSCtrl.cpp @@ -0,0 +1,367 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Some of the object selection code in this file is based on functionality described +// in the following resource: +// +// Object Selection in Torque by Dave Myers +// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=7335 +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/engineAPI.h" +#include "gui/core/guiCanvas.h" +#include "T3D/gameBase/gameConnection.h" +#include "T3D/gameFunctions.h" + +#include "afx/ui/afxTSCtrl.h" +#include "afx/afxSpellBook.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CONOBJECT(afxTSCtrl); + +ConsoleDocClass( afxTSCtrl, + "@brief A customized variation of GameTSCtrl.\n\n" + + "@ingroup afxGUI\n" + "@ingroup AFX\n" +); + +afxTSCtrl::afxTSCtrl() +{ + mMouse3DVec.zero(); + mMouse3DPos.zero(); + mouse_dn_timestamp = 0; + spellbook = NULL; + + clearTargetingMode(); +} + +bool afxTSCtrl::processCameraQuery(CameraQuery *camq) +{ + GameUpdateCameraFov(); + return GameProcessCameraQuery(camq); +} + +void afxTSCtrl::renderWorld(const RectI &updateRect) +{ + GameRenderWorld(); +} + +void afxTSCtrl::getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent) +{ + Parent::getCursor(cursor, showCursor, lastGuiEvent); + + GameConnection* conn = GameConnection::getConnectionToServer(); + if (!conn || !conn->getRolloverObj()) + return; + + GuiCanvas *pRoot = getRoot(); + if( !pRoot ) + return; + + PlatformWindow* pWindow = pRoot->getPlatformWindow(); + AssertFatal(pWindow != NULL, "GuiControl without owning platform window! This should not be possible."); + PlatformCursorController* pController = pWindow->getCursorController(); + AssertFatal(pController != NULL, "PlatformWindow without an owned CursorController!"); + + if(pRoot->mCursorChanged != PlatformCursorController::curHand) + { + // We've already changed the cursor, so set it back before we change it again. + if(pRoot->mCursorChanged != -1) + pController->popCursor(); + + // Now change the cursor shape + pController->pushCursor(PlatformCursorController::curHand); + pRoot->mCursorChanged = PlatformCursorController::curHand; + } + else if(pRoot->mCursorChanged != -1) + { + // Restore the cursor we changed + pController->popCursor(); + pRoot->mCursorChanged = -1; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxTSCtrl::onMouseDown(const GuiEvent &evt) +{ + //Con::printf("#### afxTSCtrl::onLeftMouseDown() ####"); + + // save a timestamp so we can measure how long the button is down + mouse_dn_timestamp = Platform::getRealMilliseconds(); + + GuiCanvas* Canvas = getRoot(); + + // clear button down status because the ActionMap is going to capture + // the mouse and the button up will never happen + Canvas->clearMouseButtonDown(); + + // indicate that processing of event should continue (pass down to ActionMap) + Canvas->setConsumeLastInputEvent(false); +} + +void afxTSCtrl::onRightMouseDown(const GuiEvent& evt) +{ + //Con::printf("#### afxTSCtrl::onRightMouseDown() ####"); + + GuiCanvas* Canvas = getRoot(); + + // clear right button down status because the ActionMap is going to capture + // the mouse and the right button up will never happen + Canvas->clearMouseRightButtonDown(); + + // indicate that processing of event should continue (pass down to ActionMap) + Canvas->setConsumeLastInputEvent(false); +} + +void afxTSCtrl::onMouseMove(const GuiEvent& evt) +{ + AssertFatal(!targeting_mode.empty(), "Error, undefined targeting mode."); + + Targeting targeting = targeting_mode.last(); + if (targeting.mode == arcaneFX::TARGETING_OFF || targeting.check != arcaneFX::TARGET_CHECK_ON_MOUSE_MOVE) + return; + + performTargeting(evt.mousePoint, targeting.mode); +} + +void afxTSCtrl::onRender(Point2I offset, const RectI &updateRect) +{ + GameConnection* con = GameConnection::getConnectionToServer(); +#if defined(BROKEN_DAMAGEFLASH_WHITEOUT_BLACKOUT) + bool skipRender = (!con); +#else + bool skipRender = (!con || + (con->getWhiteOut() >= 1.f) || + (con->getDamageFlash() >= 1.f) || + (con->getBlackOut() >= 1.f)); +#endif + + if (!skipRender) + Parent::onRender(offset, updateRect); + + GFX->setViewport(updateRect); +} + +void afxTSCtrl::advanceTime(F32 dt) +{ + AssertFatal(!targeting_mode.empty(), "Error, undefined targeting mode."); + + Targeting targeting = targeting_mode.last(); + if (targeting.mode == arcaneFX::TARGETING_OFF || targeting.check != arcaneFX::TARGET_CHECK_POLL) + return; + + GuiCanvas* Canvas = getRoot(); + + Point2I cursor_pos; + if (Canvas && Canvas->getLastCursorPoint(cursor_pos)) + { + performTargeting(cursor_pos, targeting.mode); + } +}; + +void afxTSCtrl::performTargeting(const Point2I& mousePoint, U8 mode) +{ + GuiCanvas* Canvas = getRoot(); + + if (mode != arcaneFX::TARGETING_FREE && !Canvas->isCursorON()) + return; + + MatrixF cam_xfm; + Point3F dummy_pt; + if (GameGetCameraTransform(&cam_xfm, &dummy_pt)) + { + // get cam pos + Point3F cameraPoint; cam_xfm.getColumn(3,&cameraPoint); + + // construct 3D screen point from mouse coords + Point3F screen_pt((F32)mousePoint.x, (F32)mousePoint.y, 1.0f); + + // convert screen point to world point + bool bad_cam = mIsZero(mLastCameraQuery.farPlane); + Point3F world_pt; + if (!bad_cam && unproject(screen_pt, &world_pt)) + { + Point3F mouseVec = world_pt - cameraPoint; + mouseVec.normalizeSafe(); + + mMouse3DPos = cameraPoint; + mMouse3DVec = mouseVec; + + F32 selectRange = arcaneFX::sTargetSelectionRange; + Point3F mouseScaled = mouseVec*selectRange; + Point3F rangeEnd = cameraPoint + mouseScaled; + + if (mode == arcaneFX::TARGETING_STANDARD) + arcaneFX::rolloverRayCast(cameraPoint, rangeEnd, arcaneFX::sTargetSelectionMask); + else if (mode == arcaneFX::TARGETING_FREE) + arcaneFX::freeTargetingRayCast(cameraPoint, rangeEnd, arcaneFX::sFreeTargetSelectionMask); + } + } +} + +void afxTSCtrl::onMouseEnter(const GuiEvent& evt) +{ + //Con::printf("#### afxTSCtrl::onMouseEnter() ####"); +} + +void afxTSCtrl::onMouseDragged(const GuiEvent& evt) +{ + //Con::printf("#### afxTSCtrl::onMouseDragged() ####"); +} + + +void afxTSCtrl::onMouseLeave(const GuiEvent& evt) +{ + //Con::printf("#### afxTSCtrl::onMouseLeave() ####"); +} + +bool afxTSCtrl::onMouseWheelUp(const GuiEvent& evt) +{ + //Con::printf("#### afxTSCtrl::onMouseWheelUp() ####"); + Con::executef(this, "onMouseWheelUp"); + return true; +} + +bool afxTSCtrl::onMouseWheelDown(const GuiEvent& evt) +{ + //Con::printf("#### afxTSCtrl::onMouseWheelDown() ####"); + Con::executef(this, "onMouseWheelDown"); + return true; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxTSCtrl::setSpellBook(afxSpellBook* book) +{ + if (book != spellbook) + { + spellbook = book; + Con::executef(this, "onSpellbookChange", (spellbook) ? spellbook->getIdString() : ""); + } +} + +void afxTSCtrl::clearTargetingMode() +{ + targeting_mode.clear(); + pushTargetingMode(arcaneFX::TARGETING_OFF, arcaneFX::TARGET_CHECK_POLL); +} + +void afxTSCtrl::pushTargetingMode(U8 mode, U8 check) +{ + switch (mode) + { + case arcaneFX::TARGETING_OFF: + case arcaneFX::TARGETING_STANDARD: + case arcaneFX::TARGETING_FREE: + break; + default: + Con::errorf("afxTSCtrl::setTargetingMode() -- unknown targeting mode [%d].", mode); + return; + } + + switch (check) + { + case arcaneFX::TARGET_CHECK_POLL: + case arcaneFX::TARGET_CHECK_ON_MOUSE_MOVE: + break; + default: + Con::errorf("afxTSCtrl::setTargetingMode() -- unknown targeting check method [%d].", check); + return; + } + + Targeting targeting = { mode, check }; + targeting_mode.push_back(targeting); +} + +void afxTSCtrl::popTargetingMode() +{ + if (targeting_mode.size() <= 1) + return ; + + targeting_mode.pop_back(); +} + +U8 afxTSCtrl::getTargetingMode() +{ + return (targeting_mode.size() > 0) ? targeting_mode.last().mode : arcaneFX::TARGETING_OFF; +} + +U8 afxTSCtrl::getTargetingCheckMethod() +{ + return (targeting_mode.size() > 0) ? targeting_mode.last().check : arcaneFX::TARGET_CHECK_POLL; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +DefineEngineMethod(afxTSCtrl, setSpellBook, void, (afxSpellBook* spellbook),, + "Associate a spellbook with an afxTSCtrl.\n\n" + "@ingroup AFX") +{ + object->setSpellBook(spellbook); +} + +DefineEngineMethod(afxTSCtrl, pushTargetingMode, void, (unsigned int mode, unsigned int checkMethod), ((U32)arcaneFX::TARGETING_OFF, (U32)arcaneFX::TARGET_CHECK_POLL), + "Push a new targeting-mode onto a statck of modes.\n\n" + "@ingroup AFX") +{ + object->pushTargetingMode((U8)mode, (U8)checkMethod); +} + +DefineEngineMethod(afxTSCtrl, popTargetingMode, void, (),, + "Pop the targeting-mode off a statck of modes.\n\n" + "@ingroup AFX") +{ + object->popTargetingMode(); +} + +DefineEngineMethod(afxTSCtrl, getTargetingMode, S32, (),, + "Get the current targeting-mode.\n\n" + "@ingroup AFX") +{ + return object->getTargetingMode(); +} + +DefineEngineMethod(afxTSCtrl, getMouse3DVec, Point3F, (),, + "Get the 3D direction vector for the mouse cursor.\n\n" + "@ingroup AFX") +{ + return object->getMouse3DVec(); +} + +DefineEngineMethod(afxTSCtrl, getMouse3DPos, Point3F, (),, + "Get the 3D position of the mouse cursor.\n\n" + "@ingroup AFX") +{ + return object->getMouse3DPos(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/ui/afxTSCtrl.h b/Engine/source/afx/ui/afxTSCtrl.h new file mode 100644 index 000000000..b45322b70 --- /dev/null +++ b/Engine/source/afx/ui/afxTSCtrl.h @@ -0,0 +1,96 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_TS_CTRL_H_ +#define _AFX_TS_CTRL_H_ + +#include "app/game.h" +#include "gui/3d/guiTSControl.h" +#include "core/iTickable.h" + +class GameBase; +class afxSpellBook; + +//---------------------------------------------------------------------------- +class afxTSCtrl : public GuiTSCtrl, public virtual ITickable +{ +private: + struct Targeting + { + U8 mode; + U8 check; + }; + + typedef GuiTSCtrl Parent; + + Point3F mMouse3DVec; + Point3F mMouse3DPos; + + U32 mouse_dn_timestamp; + afxSpellBook* spellbook; + Vector targeting_mode; + +public: + /*C*/ afxTSCtrl(); + + virtual bool processCameraQuery(CameraQuery *query); + virtual void renderWorld(const RectI &updateRect); + virtual void onRender(Point2I offset, const RectI &updateRect); + + virtual void getCursor(GuiCursor *&cursor, bool &showCursor, const GuiEvent &lastGuiEvent); + + virtual void onMouseDown(const GuiEvent&); + virtual void onMouseMove(const GuiEvent&); + virtual void onMouseDragged(const GuiEvent&); + virtual void onMouseEnter(const GuiEvent&); + virtual void onMouseLeave(const GuiEvent&); + + virtual bool onMouseWheelUp(const GuiEvent&); + virtual bool onMouseWheelDown(const GuiEvent&); + + virtual void onRightMouseDown(const GuiEvent&); + + Point3F getMouse3DVec() {return mMouse3DVec;}; + Point3F getMouse3DPos() {return mMouse3DPos;}; + + void setSpellBook(afxSpellBook* book); + void clearTargetingMode(); + void pushTargetingMode(U8 mode, U8 check); + void popTargetingMode(); + U8 getTargetingMode(); + U8 getTargetingCheckMethod(); + void performTargeting(const Point2I& mousePoint, U8 mode); + + virtual void interpolateTick( F32 delta ) {}; + virtual void processTick() {}; + virtual void advanceTime( F32 timeDelta ); + + DECLARE_CONOBJECT(afxTSCtrl); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_TS_CTRL_H_ diff --git a/Engine/source/afx/util/afxAnimCurve.cpp b/Engine/source/afx/util/afxAnimCurve.cpp new file mode 100644 index 000000000..4074c15d7 --- /dev/null +++ b/Engine/source/afx/util/afxAnimCurve.cpp @@ -0,0 +1,280 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" +#include "afx/util/afxAnimCurve.h" + +afxAnimCurve::afxAnimCurve() : usable( false ), final_value( 0.0f ), start_value( 0.0f ) +{ + evaluator = new afxHermiteEval(); +} + +afxAnimCurve::~afxAnimCurve() +{ + delete evaluator; +} + +void afxAnimCurve::addKey( Point2F &v ) +{ + Key k; + k.time = v.x; + k.value = v.y; + + keys.push_back( k ); + + usable = false; +} + +void afxAnimCurve::addKey( F32 time, F32 value ) +{ + Key k; + k.time = time; + k.value = value; + + keys.push_back( k ); + + usable = false; +} + +void afxAnimCurve::setKeyTime( int index, F32 t ) +{ + if( ( index < 0 ) || ( index >= keys.size() ) ) + return; + + Key &k = keys[index]; + k.time = t; + + usable = false; +} + +void afxAnimCurve::setKeyValue( int index, F32 v ) +{ + if( ( index < 0 ) || ( index >= keys.size() ) ) + return; + + Key &k = keys[index]; + k.value = v; + + if( index == 0 ) + start_value = v; + else if( index == keys.size()-1 ) + final_value = v; +} + +//bool afxAnimCurve::compare_Key( const afxAnimCurve::Key &a, const afxAnimCurve::Key &b ) +//{ +// return a.time < b.time; +//} + +S32 QSORT_CALLBACK afxAnimCurve::compare_Key( const void* a, const void* b ) +{ + const Key *key_a = (Key *)a; + const Key *key_b = (Key *)b; + + //Con::printf( "*** %f %f", key_a->time, key_b->time ); + + //return key_a->time < key_b->time; + + + if (key_a->time > key_b->time) + return 1; + else if (key_a->time < key_b->time) + return -1; + else + return 0; +} + +void afxAnimCurve::sort( ) +{ + if( keys.size() == 0 ) + return; + + //std::sort( keys.begin(), keys.end(), afxAnimCurve::compare_Key ); + dQsort( keys.address(), keys.size(), sizeof(Key), afxAnimCurve::compare_Key ); + + start_value = keys[0].value; + final_value = keys[keys.size()-1].value; + + start_time = keys[0].time; + final_time = keys[keys.size()-1].time; + + usable = true; +} + +int afxAnimCurve::numKeys() +{ + return keys.size(); +} + +F32 afxAnimCurve::getKeyTime( int index ) +{ + if( ( index < 0 ) || ( index >= keys.size() ) ) + return 0.0f; + + Key &k = keys[index]; + return k.time; +} + +F32 afxAnimCurve::getKeyValue( int index ) +{ + if( ( index < 0 ) || ( index >= keys.size() ) ) + return 0.0f; + + Key &k = keys[index]; + return k.value; +} + +Point2F afxAnimCurve::getSegment( F32 time ) +{ + Point2F segment( 0, 0 ); + + if( keys.size() == 0 ) + return segment; + + int start_index = 0; + for( ; start_index < keys.size()-1; start_index++ ) + { + if( time < keys[start_index+1].time ) + break; + } + int end_index = start_index+1; + + segment.x = (F32)start_index; + segment.y = (F32)end_index; + + return segment; +} + +F32 afxAnimCurve::evaluate( F32 time ) +{ + if( !usable ) + return 0.0f; + + if( time <= start_time ) + return start_value; + + if( time >= final_time ) + return final_value; + + if( keys.size() == 1 ) + return start_value; + + int start_index = 0; + for( ; start_index < keys.size()-1; start_index++ ) + { + if( time < keys[start_index+1].time ) + break; + } + int end_index = start_index+1; + + Key k0 = keys[start_index]; + Key k1 = keys[end_index]; + + Point2F v0( (F32) k0.time, k0.value ); + Point2F v1( (F32) k1.time, k1.value ); + + // Compute tangents + Point2F tan0 = computeTangentK0( v0, v1, start_index ); + Point2F tan1 = computeTangentK1( v0, v1, end_index ); + + F32 time_perc = (F32)( time - k0.time ) / (F32)( k1.time - k0.time ); + + Point2F vnew = evaluator->evaluateCurve( v0, + v1, + tan0, + tan1, + time_perc ); + + return vnew.y; +} + +Point2F afxAnimCurve::computeTangentK0( Point2F &k0, Point2F &k1, int start_index ) +{ + Point2F tan0; + + Point2F k_prev; + Point2F k_next; + + // tangent for k0 + if( start_index == 0 ) + { + k_prev = k0; // Setting previous point to k0, creating a hidden point in + // the same spot + k_next = k1; + } + else + { + Key &k = keys[start_index-1]; + k_prev.set( k.time, k.value ); + k_next = k1; + } + tan0 = k_next-k_prev; //k_next.subtract( k_prev ); + tan0 *= .5f; + + return tan0; +} + +Point2F afxAnimCurve::computeTangentK1( Point2F &k0, Point2F &k1, int end_index ) +{ + Point2F tan1; + + Point2F k_prev; + Point2F k_next; + + // tangent for k1 + if( end_index == keys.size()-1 ) + { + k_prev = k0; + k_next = k1; // Setting next point to k1, creating a hidden point in + // the same spot + } + else + { + k_prev = k0; + Key &k = keys[end_index+1]; + k_next.set( k.time, k.value ); + } + tan1 = k_next-k_prev; //k_next.subtract( k_prev ); + tan1 *= .5f; + + return tan1; +} + +void afxAnimCurve::print() +{ + Con::printf( "afxAnimCurve -------------------------" ); + for( int i = 0; i < keys.size(); i++ ) + { + Key &k = keys[i]; + Con::printf( "%f: %f", k.time, k.value ); + } + Con::printf( "-----------------------------------" ); +} + +void afxAnimCurve::printKey( int index ) +{ + Key &k = keys[index]; + Con::printf( "%f: %f", k.time, k.value ); +} \ No newline at end of file diff --git a/Engine/source/afx/util/afxAnimCurve.h b/Engine/source/afx/util/afxAnimCurve.h new file mode 100644 index 000000000..158203b44 --- /dev/null +++ b/Engine/source/afx/util/afxAnimCurve.h @@ -0,0 +1,82 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_ANIM_CURVE_H_ +#define _AFX_ANIM_CURVE_H_ + +#include "core/util/tVector.h" + +#include "afx/util/afxCurveEval.h" + +class afxAnimCurve +{ + class Key + { + public: + F32 time; + F32 value; + }; + + private: + afxCurveEval* evaluator; + + F32 final_value; + F32 start_value; + F32 final_time; + F32 start_time; + bool usable; + + //std::vector keys; + Vector keys; + + //static bool compare_Key( const Key &a, const Key &b ); + static S32 QSORT_CALLBACK compare_Key( const void* a, const void* b ); + + public: + afxAnimCurve(); + ~afxAnimCurve(); + + void addKey( Point2F &v ); + void addKey( F32 time, F32 value ); + void setKeyTime( int index, F32 t ); + void setKeyValue( int index, F32 v ); + void sort( ); + int numKeys(); + F32 getKeyTime( int index ); + F32 getKeyValue( int index ); + Point2F getSegment( F32 time ); + F32 evaluate( F32 time ); + + void print(); + void printKey( int index ); + + private: + Point2F computeTangentK0( Point2F &k0, Point2F &k1, int start_index ); + Point2F computeTangentK1( Point2F &k0, Point2F &k1, int end_index ); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_ANIM_CURVE_H_ \ No newline at end of file diff --git a/Engine/source/afx/util/afxCurve3D.cpp b/Engine/source/afx/util/afxCurve3D.cpp new file mode 100644 index 000000000..860cb4c4a --- /dev/null +++ b/Engine/source/afx/util/afxCurve3D.cpp @@ -0,0 +1,332 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" +#include "afx/util/afxCurveEval.h" +#include "afx/util/afxCurve3D.h" + +afxCurve3D::afxCurve3D() : usable( false ), default_vector( 0, 0, 0 ) +{ + evaluator = new afxHermiteEval(); +} + +afxCurve3D::~afxCurve3D() +{ + delete evaluator; +} + +void afxCurve3D::addPoint( F32 param, Point3F &v ) +{ + if( param < 0.0f || param > 1.0f ) + return; + + CurvePoint p; + p.parameter = param; + p.point.set( v ); + + points.push_back( p ); + + usable = false; +} + +void afxCurve3D::setPoint( int index, Point3F &v ) +{ + if( ( index < 0 ) || ( index >= points.size() ) ) + return; + + CurvePoint &p = points[index]; + p.point = v; + + if( index == 0 ) + start_value = v; + else if( index == points.size()-1 ) + final_value = v; +} + +//bool afxCurve3D::compare_CurvePoint( const afxCurve3D::CurvePoint &a, const afxCurve3D::CurvePoint &b ) +//{ +// return a.parameter < b.parameter; +//} + +S32 QSORT_CALLBACK afxCurve3D::compare_CurvePoint( const void* a, const void* b ) +{ + //CurvePoint *cp_a = *((CurvePoint **)a); + //CurvePoint *cp_b = *((CurvePoint **)b); + + const CurvePoint *cp_a = (CurvePoint *)a; + const CurvePoint *cp_b = (CurvePoint *)b; + + //Con::printf( "*** %f %f", cp_a->parameter, cp_b->parameter ); + + //return cp_a->parameter < cp_b->parameter; + //return 1; + + if (cp_a->parameter > cp_b->parameter) + return 1; + else if (cp_a->parameter < cp_b->parameter) + return -1; + else + return 0; +} + +void afxCurve3D::sort( ) +{ + if( points.size() == 0 ) + return; + + if( points.size() == 1 ) + { + start_value = points[0].point; + final_value = start_value; + usable = true; + return; + } + + //Con::printf( "*** pre-sort" ); + //std::sort( points.begin(), points.end(), afxCurve3D::compare_CurvePoint ); + dQsort( points.address(), points.size(), sizeof(CurvePoint), afxCurve3D::compare_CurvePoint ); + //Con::printf( "*** post-sort" ); + + start_value = points[0].point; + final_value = points[points.size()-1].point; + + usable = true; + + start_tangent = evaluateTangent( 0.0f ); + final_tangent = evaluateTangent( 1.0f ); +} + +int afxCurve3D::numPoints() +{ + return points.size(); +} + +F32 afxCurve3D::getParameter( int index ) +{ + if( ( index < 0 ) || ( index >= points.size() ) ) + return 0.0f; + + return points[index].parameter; +} + +Point3F afxCurve3D::getPoint( int index ) +{ + if( ( index < 0 ) || ( index >= points.size() ) ) + return default_vector; + + return points[index].point; +} + +Point3F afxCurve3D::evaluate( F32 param ) +{ + if( !usable ) + return default_vector; + + if( param <= 0.0f ) + return start_value; + + if( param >= 1.0f ) + return final_value; + + if( points.size() == 1 ) + return start_value; + + int start_index = 0; + for( ; start_index < points.size()-1; start_index++ ) + { + if( param < points[start_index+1].parameter ) + break; + } + int end_index = start_index+1; + + CurvePoint p0 = points[start_index]; + CurvePoint p1 = points[end_index]; + + // Compute tangents + //Point3F tan0 = computeTangentP0( p0.point, p1.point, start_index ); + //Point3F tan1 = computeTangentP1( p0.point, p1.point, end_index ); + + + F32 local_param = ( param - p0.parameter ) / ( p1.parameter - p0.parameter ); + + //Point3F vnew = evaluator->evaluateCurve( p0.point, + // p1.point, + // tan0, + // tan1, + // local_param ); + + Point3F vnew = evaluator->evaluateCurve( p0.point, p1.point, + p0.tangent, p1.tangent, + local_param ); + return vnew; +} + +Point3F afxCurve3D::evaluateTangent( F32 param ) +{ + if( !usable ) + return default_vector; + + if( param < 0.0f ) + return start_tangent; + + if( param > 1.0f ) + return final_tangent; + + if( points.size() == 1 ) + return start_tangent; + + int start_index = 0; + for( ; start_index < points.size()-1; start_index++ ) + { + if( param < points[start_index+1].parameter ) + break; + } + int end_index = start_index+1; + + if( param == 1.0f ) + { + end_index = points.size()-1; + start_index = end_index - 1; + } + + CurvePoint p0 = points[start_index]; + CurvePoint p1 = points[end_index]; + + // Compute tangents + //Point3F tan0 = computeTangentP0( p0.point, p1.point, start_index ); + //Point3F tan1 = computeTangentP1( p0.point, p1.point, end_index ); + + F32 local_param = ( param - p0.parameter ) / ( p1.parameter - p0.parameter ); + + //Point3F vnew = evaluator->evaluateCurveTangent( p0.point, + // p1.point, + // tan0, + // tan1, + // local_param ); + Point3F vnew = evaluator->evaluateCurveTangent( p0.point, p1.point, + p0.tangent, p1.tangent, + local_param ); + + return vnew; +} + +Point3F afxCurve3D::computeTangentP0( Point3F &p0, Point3F &p1, int start_index ) +{ + Point3F tan0; + + Point3F p_prev; + Point3F p_next; + + // tangent for p0 + if( start_index == 0 ) + { + p_prev = p0; // Setting previous point to p0, creating a hidden point in + // the same spot + p_next = p1; + } + else + { + CurvePoint &p = points[start_index-1]; + p_prev = p.point; + p_next = p1; + } + tan0 = p_next-p_prev; //p_next.subtract( p_prev ); + tan0 *= .5f; //= tan0.scale( .5f ); + + return tan0; +} + +Point3F afxCurve3D::computeTangentP1( Point3F &p0, Point3F &p1, int end_index ) +{ + Point3F tan1; + + Point3F p_prev; + Point3F p_next; + + // tangent for p1 + if( end_index == points.size()-1 ) + { + p_prev = p0; + p_next = p1; // Setting next point to p1, creating a hidden point in + // the same spot + } + else + { + p_prev = p0; + CurvePoint &p = points[end_index+1]; + p_next = p.point; + } + tan1 = p_next-p_prev; //p_next.subtract( p_prev ); + tan1 *= .5f; //= tan1.scale( .5f ); + + //Con::printf("UPDATE"); + return tan1; +} + +void afxCurve3D::computeTangents() +{ + CurvePoint *p_prev; + CurvePoint *p_next; + + for( int i = 0; i < points.size(); i++ ) + { + CurvePoint *p = &points[i]; + + if( i == 0 ) + { + p_prev = p; // Setting previous point to p0, creating a hidden point in + // the same spot + p_next = &points[i+1]; + } + else if( i == points.size()-1 ) + { + p_prev = &points[i-1]; + p_next = p; // Setting next point to p1, creating a hidden point in + // the same spot + } + else + { + p_prev = &points[i-1]; + p_next = &points[i+1]; + } + + p->tangent = p_next->point - p_prev->point; + //(p->tangent).normalize(); + p->tangent *= .5f; + + //Con::printf( "%d: %f %f %f", i, p->tangent.x, p->tangent.y, p->tangent.z ); + } +} + +void afxCurve3D::print() +{ + Con::printf( "afxCurve3D -------------------------" ); + for( int i = 0; i < points.size(); i++ ) + { + CurvePoint &p = points[i]; + Con::printf( "%f: %f %f %f", p.parameter, p.point.x, p.point.y, p.point.z ); + } + Con::printf( "---------------------------------" ); +} \ No newline at end of file diff --git a/Engine/source/afx/util/afxCurve3D.h b/Engine/source/afx/util/afxCurve3D.h new file mode 100644 index 000000000..5ddfdfecf --- /dev/null +++ b/Engine/source/afx/util/afxCurve3D.h @@ -0,0 +1,92 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_CURVE_3D_H_ +#define _AFX_CURVE_3D_H_ + +#include "core/util/tVector.h" +#include "math/mPoint3.h" + +class afxCurveEval; + +class afxCurve3D +{ + class CurvePoint + { + public: + F32 parameter; + Point3F point; + + // new: + Point3F tangent; + }; + + private: + afxCurveEval* evaluator; + Point3F start_value; + Point3F final_value; + Point3F start_tangent; + Point3F final_tangent; + bool usable; + + //std::vector points; + Vector points; + + Point3F default_vector; + + //static bool compare_CurvePoint( const CurvePoint &a, const CurvePoint &b ); + static S32 QSORT_CALLBACK compare_CurvePoint( const void* a, const void* b ); + + // new + Point3F last_tangent; + bool flip; + + public: + afxCurve3D(); + ~afxCurve3D(); + + void addPoint( F32 param, Point3F &v ); + void setPoint( int index, Point3F &v ); + void sort( ); + int numPoints(); + F32 getParameter( int index ); + Point3F getPoint( int index ); + Point3F evaluate( F32 param ); + Point3F evaluateTangent( F32 param ); + + void print(); + + void computeTangents(); + + //MatrixF createOrientFromDir( Point3F &direction ); + + private: + Point3F computeTangentP0( Point3F &p0, Point3F &p1, int start_index ); + Point3F computeTangentP1( Point3F &p0, Point3F &p1, int end_index ); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_CURVE_3D_H_ \ No newline at end of file diff --git a/Engine/source/afx/util/afxCurveEval.cpp b/Engine/source/afx/util/afxCurveEval.cpp new file mode 100644 index 000000000..d45b646f9 --- /dev/null +++ b/Engine/source/afx/util/afxCurveEval.cpp @@ -0,0 +1,122 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" +#include "afx/util/afxCurveEval.h" + +Point2F afxHermiteEval::evaluateCurve( Point2F &v0, Point2F &v1, + Point2F &t0, Point2F &t1, F32 t ) +{ + F32 t_3 = t*t*t; + F32 t_2 = t*t; + F32 h1 = ( 2.0f * t_3 ) - ( 3.0f * t_2 ) + 1; + F32 h2 = (-2.0f * t_3 ) + ( 3.0f * t_2 ); + F32 h3 = t_3 - ( 2.0f * t_2 ) + t; + F32 h4 = t_3 - t_2; + + Point2F v( + (h1*v0.x)+(h2*v1.x)+(h3*t0.x)+(h4*t1.x), + (h1*v0.y)+(h2*v1.y)+(h3*t0.y)+(h4*t1.y) ); + + return v; +} + +Point2F afxHermiteEval::evaluateCurve( Point2F &v0, Point2F &v1, F32 t ) +{ + Point2F tangent( 1, 0 ); + return( evaluateCurve( v0, v1, tangent, tangent, t ) ); +} + +Point2F afxHermiteEval::evaluateCurveTangent( Point2F &v0, Point2F &v1, + Point2F &t0, Point2F &t1, F32 t ) +{ + F32 t_2 = t*t; + F32 h1_der = ( 6.0f * t_2 ) - ( 6.0f * t ); + F32 h2_der = (-6.0f * t_2 ) + ( 6.0f * t ); + F32 h3_der = ( 3.0f * t_2 ) - ( 4.0f * t ) + 1; + F32 h4_der = ( 3.0f * t_2 ) - ( 2.0f * t ); + + Point2F tangent( + (h1_der*v0.x)+(h2_der*v1.x)+(h3_der*t0.x)+(h4_der*t1.x), + (h1_der*v0.y)+(h2_der*v1.y)+(h3_der*t0.y)+(h4_der*t1.y) ); + + return tangent; +} + +Point2F afxHermiteEval::evaluateCurveTangent( Point2F &v0, Point2F &v1, F32 t ) +{ + Point2F tangent( 1, 0 ); + return( evaluateCurveTangent( v0, v1, tangent, tangent, t ) ); +} + +Point3F afxHermiteEval::evaluateCurve( Point3F &v0, Point3F &v1, + Point3F &t0, Point3F &t1, F32 t ) +{ + F32 t_3 = t*t*t; + F32 t_2 = t*t; + F32 h1 = ( 2.0f * t_3 ) - ( 3.0f * t_2 ) + 1; + F32 h2 = (-2.0f * t_3 ) + ( 3.0f * t_2 ); + F32 h3 = t_3 - ( 2.0f * t_2 ) + t; + F32 h4 = t_3 - t_2; + + Point3F v( + (h1*v0.x)+(h2*v1.x)+(h3*t0.x)+(h4*t1.x), + (h1*v0.y)+(h2*v1.y)+(h3*t0.y)+(h4*t1.y), + (h1*v0.z)+(h2*v1.z)+(h3*t0.z)+(h4*t1.z) ); + + return v; +} + +Point3F afxHermiteEval::evaluateCurve( Point3F &v0, Point3F &v1, F32 t ) +{ + Point3F tangent( 1, 0, 0 ); + return( evaluateCurve( v0, v1, tangent, tangent, t ) ); +} + +Point3F afxHermiteEval::evaluateCurveTangent( Point3F &v0, Point3F &v1, + Point3F &t0, Point3F &t1, F32 t ) +{ + F32 t_2 = t*t; + F32 h1_der = ( 6.0f * t_2 ) - ( 6.0f * t ); + F32 h2_der = (-6.0f * t_2 ) + ( 6.0f * t ); + F32 h3_der = ( 3.0f * t_2 ) - ( 4.0f * t ) + 1; + F32 h4_der = ( 3.0f * t_2 ) - ( 2.0f * t ); + + Point3F tangent( + (h1_der*v0.x)+(h2_der*v1.x)+(h3_der*t0.x)+(h4_der*t1.x), + (h1_der*v0.y)+(h2_der*v1.y)+(h3_der*t0.y)+(h4_der*t1.y), + (h1_der*v0.z)+(h2_der*v1.z)+(h3_der*t0.z)+(h4_der*t1.z) ); + + return tangent; +} + +Point3F afxHermiteEval::evaluateCurveTangent( Point3F &v0, Point3F &v1, F32 t ) +{ + Point3F tangent( 1, 0, 0 ); + return( evaluateCurveTangent( v0, v1, tangent, tangent, t ) ); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/util/afxCurveEval.h b/Engine/source/afx/util/afxCurveEval.h new file mode 100644 index 000000000..0a8765c19 --- /dev/null +++ b/Engine/source/afx/util/afxCurveEval.h @@ -0,0 +1,62 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_CURVE_EVAL_BASE_H_ +#define _AFX_CURVE_EVAL_BASE_H_ + +#include "math/mPoint2.h" +#include "math/mPoint3.h" + +class afxCurveEval +{ +public: + virtual Point2F evaluateCurve(Point2F& v0, Point2F& v1, F32 t)=0; + virtual Point2F evaluateCurve(Point2F& v0, Point2F& v1, Point2F& t0, Point2F& t1, F32 t)=0; + virtual Point2F evaluateCurveTangent(Point2F& v0, Point2F& v1, F32 t)=0; + virtual Point2F evaluateCurveTangent(Point2F& v0, Point2F& v1, Point2F& t0, Point2F& t1, F32 t)=0; + + virtual Point3F evaluateCurve(Point3F& v0, Point3F& v1, F32 t)=0; + virtual Point3F evaluateCurve(Point3F& v0, Point3F& v1, Point3F& t0, Point3F& t1, F32 t)=0; + virtual Point3F evaluateCurveTangent(Point3F& v0, Point3F& v1, F32 t)=0; + virtual Point3F evaluateCurveTangent(Point3F& v0, Point3F& v1, Point3F& t0, Point3F& t1, F32 t)=0; +}; + +class afxHermiteEval : public afxCurveEval +{ +public: + Point2F evaluateCurve(Point2F& v0, Point2F& v1, F32 t); + Point2F evaluateCurve(Point2F& v0, Point2F& v1, Point2F& t0, Point2F& t1, F32 t); + Point2F evaluateCurveTangent(Point2F& v0, Point2F& v1, F32 t); + Point2F evaluateCurveTangent(Point2F& v0, Point2F& v1, Point2F& t0, Point2F& t1, F32 t); + + Point3F evaluateCurve(Point3F& v0, Point3F& v1, F32 t); + Point3F evaluateCurve(Point3F& v0, Point3F& v1, Point3F& t0, Point3F& t1, F32 t); + Point3F evaluateCurveTangent(Point3F& v0, Point3F& v1, F32 t); + Point3F evaluateCurveTangent(Point3F& v0, Point3F& v1, Point3F& t0, Point3F& t1, F32 t); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_CURVE_EVAL_BASE_H_ diff --git a/Engine/source/afx/util/afxEase.cpp b/Engine/source/afx/util/afxEase.cpp new file mode 100644 index 000000000..7c7e1341e --- /dev/null +++ b/Engine/source/afx/util/afxEase.cpp @@ -0,0 +1,98 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" +#include "afxEase.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +F32 +afxEase::t(F32 t, F32 ein, F32 eout) +{ + if (t == 0.0) + return 0.0; + + if (t == 1.0) + return 1.0; + + F32 ee = eout - ein + 1.0; + + // ease in section + if (t <= ein) + { + F32 tin = t/ein; + return (mSin(M_PI_F*(tin - 1.0)) + M_PI_F*tin)*ein*(1.0/M_PI_F)/ee; + } + + // middle linear section + else if (t <= eout) + { + return (2.0*t - ein)/ee; + } + + // ease out section + else + { + F32 iout = 1.0 - eout; + F32 g = (t - eout)*M_PI_F/iout; + return ((mSin(g) + g)*(iout)/M_PI_F + 2.0*eout - ein)*1.0/ee + 0.0; + } +} + +F32 +afxEase::eq(F32 t, F32 a, F32 b, F32 ein, F32 eout) +{ + if (t == 0.0) + return a; + + if (t == 1.0) + return b; + + F32 ab = b - a; + F32 ee = eout - ein + 1.0; + + // ease in section + if (t <= ein) + { + F32 tin = t/ein; + return a + (mSin(M_PI_F*(tin - 1.0)) + M_PI_F*tin)*ab*ein*(1.0/M_PI_F)/ee; + } + + // middle linear section + else if (t <= eout) + { + return a + ab*(2.0*t - ein)/ee; + } + + // ease out section + else + { + F32 iout = 1.0 - eout; + F32 g = (t - eout)*M_PI_F/iout; + return ((mSin(g) + g)*(iout)/M_PI_F + 2.0*eout - ein)*ab/ee + a; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/util/afxEase.h b/Engine/source/afx/util/afxEase.h new file mode 100644 index 000000000..80b6d95af --- /dev/null +++ b/Engine/source/afx/util/afxEase.h @@ -0,0 +1,41 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_EASE_H_ +#define _AFX_EASE_H_ + +#include "platform/platform.h" + +class afxEase +{ +public: + static F32 t(F32 t, F32 ein, F32 eout); + static F32 eq(F32 t, F32 a, F32 b, F32 ein, F32 eout); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_EASE_H_ + diff --git a/Engine/source/afx/util/afxParticlePool.cpp b/Engine/source/afx/util/afxParticlePool.cpp new file mode 100644 index 000000000..37647f294 --- /dev/null +++ b/Engine/source/afx/util/afxParticlePool.cpp @@ -0,0 +1,233 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "T3D/fx/particleEmitter.h" + +#include "afx/afxChoreographer.h" +#include "afx/util/afxParticlePool.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxParticlePoolData); + +ConsoleDocClass( afxParticlePoolData, + "@brief A ParticlePool datablock.\n\n" + + "@ingroup afxUtil\n" + "@ingroup AFX\n" +); + +Vector afxParticlePool::orderedVector; + +afxParticlePoolData::afxParticlePoolData() +{ + pool_type = POOL_NORMAL; + base_color.set(0.0f, 0.0f, 0.0f, 1.0f); + blend_weight = 1.0f; +} + +afxParticlePoolData::afxParticlePoolData(const afxParticlePoolData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + pool_type = other.pool_type; + base_color = other.base_color; + blend_weight = other.blend_weight; +} + +ImplementEnumType( afxParticlePool_PoolType, "Possible particle pool types.\n" "@ingroup afxParticlePool\n\n" ) + { afxParticlePoolData::POOL_NORMAL, "normal", "..." }, + { afxParticlePoolData::POOL_TWOPASS, "two-pass", "..." }, +EndImplementEnumType; + +afxParticlePoolData::~afxParticlePoolData() +{ +} + +void afxParticlePoolData::initPersistFields() +{ + addField("poolType", TYPEID< afxParticlePoolData::PoolType >(), Offset(pool_type, afxParticlePoolData), + "..."); + addField("baseColor", TypeColorF, Offset(base_color, afxParticlePoolData), + "..."); + addField("blendWeight", TypeF32, Offset(blend_weight, afxParticlePoolData), + "..."); + + Parent::initPersistFields(); +}; + +void afxParticlePoolData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(pool_type); + stream->write(base_color); + stream->write(blend_weight); +}; + +void afxParticlePoolData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&pool_type); + stream->read(&base_color); + stream->read(&blend_weight); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_NETOBJECT_V1(afxParticlePool); + +ConsoleDocClass( afxParticlePool, + "@brief A ParticlePool object as defined by an afxParticlePoolData datablock.\n\n" + + "@ingroup afxUtil\n" + "@ingroup AFX\n" +); + +afxParticlePool::afxParticlePool() +{ + mDataBlock = 0; + key_block = 0; + key_index = 0; + choreographer = 0; + sort_priority = S8_MAX; + + mNetFlags.set(IsGhost); + mTypeMask |= StaticObjectType; + + mCurBuffSize = mCurBuffSize2 = 0; +}; + +afxParticlePool::~afxParticlePool() +{ + for (S32 i = 0; i < emitters.size(); i++) + if (emitters[i]) + emitters[i]->clearPool(); + + if (choreographer) + choreographer->unregisterParticlePool(this); + + if (mDataBlock && mDataBlock->isTempClone()) + { + delete mDataBlock; + mDataBlock = 0; + } +} + +bool afxParticlePool::onNewDataBlock(GameBaseData* dptr, bool reload) +{ + mDataBlock = dynamic_cast(dptr); + if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload)) + return false; + + return true; +} + +bool afxParticlePool::onAdd() +{ + if (!Parent::onAdd()) + return false; + + mObjBox.minExtents.set(-0.5, -0.5, -0.5); + mObjBox.maxExtents.set( 0.5, 0.5, 0.5); + + resetWorldBox(); + + addToScene(); + + return true; +}; + +void afxParticlePool::onRemove() +{ + removeFromScene(); + + Parent::onRemove(); +}; + +void afxParticlePool::addParticleEmitter(ParticleEmitter* emitter) +{ + emitters.push_back(emitter); +} + +void afxParticlePool::removeParticleEmitter(ParticleEmitter* emitter) +{ + for (U32 i=0; i < emitters.size(); i++) + if (emitters[i] == emitter) + { + emitters.erase(i); + break; + } + + if (emitters.empty()) + { + if (choreographer) + { + choreographer->unregisterParticlePool(this); + choreographer = 0; + } + Sim::postEvent(this, new ObjectDeleteEvent, Sim::getCurrentTime() + 500); + } +} + +void afxParticlePool::updatePoolBBox(ParticleEmitter* emitter) +{ + if (emitter->mObjBox.minExtents.x < mObjBox.minExtents.x) + mObjBox.minExtents.x = emitter->mObjBox.minExtents.x; + if (emitter->mObjBox.minExtents.y < mObjBox.minExtents.y) + mObjBox.minExtents.y = emitter->mObjBox.minExtents.y; + if (emitter->mObjBox.minExtents.z < mObjBox.minExtents.z) + mObjBox.minExtents.z = emitter->mObjBox.minExtents.z; + if (emitter->mObjBox.maxExtents.x > mObjBox.maxExtents.x) + mObjBox.maxExtents.x = emitter->mObjBox.maxExtents.x; + if (emitter->mObjBox.maxExtents.y > mObjBox.maxExtents.y) + mObjBox.maxExtents.y = emitter->mObjBox.maxExtents.y; + if (emitter->mObjBox.maxExtents.z > mObjBox.maxExtents.z) + mObjBox.maxExtents.z = emitter->mObjBox.maxExtents.z; + + resetWorldBox(); +} + +void afxParticlePool::setSortPriority(S8 priority) +{ + if (priority < sort_priority) + sort_priority = (priority == 0) ? 1 : priority; +} + +int QSORT_CALLBACK afxParticlePool::cmpSortParticlePool(const void* p1, const void* p2) +{ + const SortParticlePool* sp1 = (const SortParticlePool*)p1; + const SortParticlePool* sp2 = (const SortParticlePool*)p2; + if (sp2->k > sp1->k) + return 1; + else if (sp2->k == sp1->k) + return 0; + else + return -1; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/util/afxParticlePool.h b/Engine/source/afx/util/afxParticlePool.h new file mode 100644 index 000000000..d09b0dcd7 --- /dev/null +++ b/Engine/source/afx/util/afxParticlePool.h @@ -0,0 +1,143 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_PARTICLE_POOL_H_ +#define _AFX_PARTICLE_POOL_H_ + +class afxParticlePoolData : public GameBaseData +{ +private: + typedef GameBaseData Parent; + +public: + enum PoolType + { + POOL_NORMAL, + POOL_TWOPASS + }; + + U32 pool_type; + LinearColorF base_color; + F32 blend_weight; + +public: + /*C*/ afxParticlePoolData(); + /*C*/ afxParticlePoolData(const afxParticlePoolData&, bool = false); + /*D*/ ~afxParticlePoolData(); + + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxParticlePoolData); + DECLARE_CATEGORY("AFX"); +}; + +typedef afxParticlePoolData::PoolType afxParticlePool_PoolType; +DefineEnumType( afxParticlePool_PoolType ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +struct Particle; +class ParticleEmitter; +class afxChoreographer; + +typedef Vector ParticleEmitterList; + +class afxParticlePool : public GameBase +{ + typedef GameBase Parent; + + class ObjectDeleteEvent : public SimEvent + { + public: + void process(SimObject *obj) { if (obj) obj->deleteObject(); } + }; + + struct SortParticlePool + { + Particle* p; + F32 k; // interpreted differently depending on rendering method + ParticleEmitter* emitter; + }; + +private: + afxParticlePoolData* mDataBlock; + afxParticlePoolData* key_block; + U32 key_index; + ParticleEmitterList emitters; + afxChoreographer* choreographer; + S8 sort_priority; + + static Vector orderedVector; + static int QSORT_CALLBACK cmpSortParticlePool(const void* p1, const void* p2); + + S32 mCurBuffSize; + GFXVertexBufferHandle mVertBuff; + S32 mCurBuffSize2; + GFXVertexBufferHandle mVertBuff2; + +protected: + virtual void prepRenderImage(SceneRenderState*); + + void pool_prepBatchRender(RenderPassManager*, const Point3F &camPos, const LinearColorF &ambientColor); + void pool_renderObject_Normal(RenderPassManager*, const Point3F &camPos, const LinearColorF &ambientColor); + void pool_renderObject_TwoPass(RenderPassManager*, const Point3F &camPos, const LinearColorF &ambientColor); + + virtual bool onAdd(); + virtual void onRemove(); + + void renderBillboardParticle_blend(Particle&, const Point3F* basePnts, const MatrixF& camView, const F32 spinFactor, + const F32 blend_factor, ParticleEmitter*); + void renderBillboardParticle_color(Particle&, const Point3F* basePnts, const MatrixF& camView, const F32 spinFactor, + const LinearColorF& color, ParticleEmitter*); + +public: + /*C*/ afxParticlePool(); + /*D*/ ~afxParticlePool(); + + virtual bool onNewDataBlock(GameBaseData* dptr, bool reload); + + void addParticleEmitter(ParticleEmitter*); + void removeParticleEmitter(ParticleEmitter*); + + void setChoreographer(afxChoreographer* ch) { choreographer = ch; } + void setKeyBlock(afxParticlePoolData* db, U32 idx) { key_block = db; key_index = idx; } + bool hasMatchingKeyBlock(const afxParticlePoolData* db, U32 idx) const { return (db == key_block && idx == key_index); } + + void updatePoolBBox(ParticleEmitter*); + + void setSortPriority(S8 priority); + + DECLARE_CONOBJECT(afxParticlePool); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_PARTICLE_POOL_H_ diff --git a/Engine/source/afx/util/afxParticlePool_T3D.cpp b/Engine/source/afx/util/afxParticlePool_T3D.cpp new file mode 100644 index 000000000..6689326cd --- /dev/null +++ b/Engine/source/afx/util/afxParticlePool_T3D.cpp @@ -0,0 +1,491 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "scene/sceneRenderState.h" +#include "T3D/fx/particleEmitter.h" +#include "renderInstance/renderPassManager.h" +#include "lighting/lightinfo.h" +#include "lighting/lightManager.h" + +#include "afx/util/afxParticlePool.h" + +void afxParticlePool::prepRenderImage(SceneRenderState* state) +{ + const LightInfo *sunlight = LIGHTMGR->getSpecialLight( LightManager::slSunLightType ); + pool_prepBatchRender(state->getRenderPass(), state->getCameraPosition(), sunlight->getAmbient()); +}; + +void afxParticlePool::pool_prepBatchRender(RenderPassManager *renderManager, const Point3F &camPos, const LinearColorF &ambientColor) +{ + if (emitters.empty()) + return; + + switch (mDataBlock->pool_type) + { + case afxParticlePoolData::POOL_TWOPASS : + pool_renderObject_TwoPass(renderManager, camPos, ambientColor); + break; + case afxParticlePoolData::POOL_NORMAL : + default: + pool_renderObject_Normal(renderManager, camPos, ambientColor); + } +} + +void afxParticlePool::pool_renderObject_Normal(RenderPassManager *renderManager, const Point3F &camPos, const LinearColorF &ambientColor) +{ + S32 n_parts = 0; + for (S32 i = 0; i < emitters.size(); i++) + n_parts += emitters[i]->n_parts; + + if (n_parts == 0) + return; + + ParticleEmitterData* main_emitter_data = emitters[0]->mDataBlock; + + main_emitter_data->allocPrimBuffer(n_parts); + + orderedVector.clear(); + + MatrixF modelview = GFX->getWorldMatrix(); + Point3F viewvec; modelview.getRow(1, &viewvec); + + for (U32 i=0; i < emitters.size(); i++) + { + // add each particle and a distance based sort key to orderedVector + for (Particle* pp = emitters[i]->part_list_head.next; pp != NULL; pp = pp->next) + { + orderedVector.increment(); + orderedVector.last().p = pp; + orderedVector.last().k = mDot(pp->pos, viewvec); + orderedVector.last().emitter = emitters[i]; + } + } + + // qsort the list into far to near ordering + dQsort(orderedVector.address(), orderedVector.size(), sizeof(SortParticlePool), cmpSortParticlePool); + + static Vector tempBuff(2048); + tempBuff.reserve(n_parts*4 + 64); // make sure tempBuff is big enough + GFXVertexPCT *buffPtr = tempBuff.address(); // use direct pointer (faster) + + Point3F basePoints[4]; + basePoints[0] = Point3F(-1.0, 0.0, -1.0); + basePoints[1] = Point3F( 1.0, 0.0, -1.0); + basePoints[2] = Point3F( 1.0, 0.0, 1.0); + basePoints[3] = Point3F(-1.0, 0.0, 1.0); + + MatrixF camView = GFX->getWorldMatrix(); + camView.transpose(); // inverse - this gets the particles facing camera + + for (U32 i = 0; i < orderedVector.size(); i++) + { + Particle* particle = orderedVector[i].p; + ParticleEmitter* emitter = orderedVector[i].emitter; + + if (emitter->mDataBlock->orientParticles) + emitter->setupOriented(particle, camPos, ambientColor, buffPtr); + else + emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr); + buffPtr+=4; + } + + // create new VB if emitter size grows + if( !mVertBuff || n_parts > mCurBuffSize ) + { + mCurBuffSize = n_parts; + mVertBuff.set(GFX, n_parts*4, GFXBufferTypeDynamic); + } + // lock and copy tempBuff to video RAM + GFXVertexPCT *verts = mVertBuff.lock(); + dMemcpy( verts, tempBuff.address(), n_parts * 4 * sizeof(GFXVertexPCT) ); + mVertBuff.unlock(); + + //MeshRenderInst *ri = gRenderInstManager->allocInst(); + ParticleRenderInst *ri = renderManager->allocInst(); + ri->vertBuff = &mVertBuff; + ri->primBuff = &main_emitter_data->primBuff; + ri->translucentSort = true; + ri->type = RenderPassManager::RIT_Particle; + ri->sortDistSq = getWorldBox().getSqDistanceToPoint( camPos ); + + ri->defaultKey = (-sort_priority*100); + + ri->modelViewProj = renderManager->allocUniqueXform( GFX->getProjectionMatrix() * + GFX->getViewMatrix() * + GFX->getWorldMatrix() ); + + ri->count = n_parts; + + ri->blendStyle = main_emitter_data->blendStyle; + + // use first particle's texture unless there is an emitter texture to override it + if (main_emitter_data->textureHandle) + ri->diffuseTex = &*(main_emitter_data->textureHandle); + else + ri->diffuseTex = &*(main_emitter_data->particleDataBlocks[0]->textureHandle); + + ri->softnessDistance = main_emitter_data->softnessDistance; + + // Sort by texture too. + //ri->defaultKey = ri->diffuseTex ? (U32)ri->diffuseTex : (U32)ri->vertBuff; + + renderManager->addInst( ri ); +} + +void afxParticlePool::pool_renderObject_TwoPass(RenderPassManager *renderManager, const Point3F &camPos, const LinearColorF &ambientColor) +{ + S32 n_parts = 0; + for (S32 i = 0; i < emitters.size(); i++) + n_parts += emitters[i]->n_parts; + + if (n_parts == 0) + return; + + ParticleEmitterData* main_emitter_data = emitters[0]->mDataBlock; + + main_emitter_data->allocPrimBuffer(n_parts); + + orderedVector.clear(); + + F32 min_d=0.0f, max_d=0.0f; + + for (U32 i=0; i < emitters.size(); i++) + { + if (!emitters[i]->mDataBlock->pool_depth_fade || !emitters[i]->mDataBlock->pool_radial_fade) + continue; + + // add particles to orderedVector and calc distance and min/max distance + for (Particle* pp = emitters[i]->part_list_head.next; pp != NULL; pp = pp->next) + { + F32 dist = (pp->pos-camPos).len(); + if (dist > max_d) + max_d = dist; + else if (dist < min_d) + min_d = dist; + + orderedVector.increment(); + orderedVector.last().p = pp; + orderedVector.last().k = dist; + orderedVector.last().emitter = emitters[i]; + } + } + + // Add remaining emitters particles to the orderedVector that do not participate in the + // above depth computations: + for (U32 i=0; i < emitters.size(); i++) + { + if (emitters[i]->mDataBlock->pool_depth_fade || emitters[i]->mDataBlock->pool_radial_fade) + continue; + + for (Particle* pp = emitters[i]->part_list_head.next; pp != NULL; pp = pp->next) + { + orderedVector.increment(); + orderedVector.last().p = pp; + orderedVector.last().k = 0; // no need to compute depth here + orderedVector.last().emitter = emitters[i]; + } + } + + static Vector tempBuff(2048); + tempBuff.reserve(n_parts*4 + 64); // make sure tempBuff is big enough + GFXVertexPCT *buffPtr = tempBuff.address(); // use direct pointer (faster) + + Point3F basePoints[4]; + basePoints[0] = Point3F(-1.0, 0.0, -1.0); + basePoints[1] = Point3F( 1.0, 0.0, -1.0); + basePoints[2] = Point3F( 1.0, 0.0, 1.0); + basePoints[3] = Point3F(-1.0, 0.0, 1.0); + + MatrixF camView = GFX->getWorldMatrix(); + camView.transpose(); // inverse - this gets the particles facing camera + + //~~~~~~~~~~~~~~~~~~~~// + + Point3F bbox_center; mObjBox.getCenter(&bbox_center); + F32 d_range = max_d - min_d; + bool d_safe = (d_range>0.0001); + F32 d_half = min_d + (d_range*0.5f); + + //~~~~~~~~~~~~~~~~~~~~// + + for (U32 i = 0; i < orderedVector.size(); i++) + { + Particle* particle = orderedVector[i].p; + ParticleEmitter* emitter = orderedVector[i].emitter; + + LinearColorF color_save = particle->color; + particle->color.set(mDataBlock->base_color.red, mDataBlock->base_color.green, mDataBlock->base_color.blue, mDataBlock->base_color.alpha*particle->color.alpha); + emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr); + particle->color = color_save; + + buffPtr+=4; + } + + // create new VB if emitter size grows + if( !mVertBuff || n_parts > mCurBuffSize ) + { + mCurBuffSize = n_parts; + mVertBuff.set(GFX, n_parts*4, GFXBufferTypeDynamic); + } + // lock and copy tempBuff to video RAM + GFXVertexPCT *verts = mVertBuff.lock(); + dMemcpy( verts, tempBuff.address(), n_parts * 4 * sizeof(GFXVertexPCT) ); + mVertBuff.unlock(); + + ParticleRenderInst *ri = renderManager->allocInst(); + ri->vertBuff = &mVertBuff; + ri->primBuff = &main_emitter_data->primBuff; + ri->translucentSort = true; + ri->type = RenderPassManager::RIT_Particle; + ri->sortDistSq = getWorldBox().getSqDistanceToPoint( camPos ); + + ri->defaultKey = (-sort_priority*100); + + ri->modelViewProj = renderManager->allocUniqueXform( GFX->getProjectionMatrix() * + GFX->getViewMatrix() * + GFX->getWorldMatrix() ); + + ri->count = n_parts; + + ri->blendStyle = ParticleRenderInst::BlendNormal; + + // use first particle's texture unless there is an emitter texture to override it + //if (main_emitter_data->textureHandle) + // ri->diffuseTex = &*(main_emitter_data->textureHandle); + //else + ri->diffuseTex = &*(main_emitter_data->particleDataBlocks[0]->textureExtHandle); + + F32 save_sort_dist = ri->sortDistSq; + + ri->softnessDistance = main_emitter_data->softnessDistance; + + renderManager->addInst( ri ); + + //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + //~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + // 2nd-pass + + buffPtr = tempBuff.address(); + + bbox_center.z = mObjBox.minExtents.z; + F32 max_radius = (max_d-min_d)*0.5f; + + // gather fade settings + bool do_mixed_fades = false; + bool do_radial_fades = (emitters[0]->mDataBlock->pool_radial_fade && (max_radius>0.0001f)); + bool do_depth_fades = (emitters[0]->mDataBlock->pool_depth_fade && d_safe); + for (U32 i = 1; i < emitters.size(); i++) + { + if ( (do_radial_fades != (emitters[i]->mDataBlock->pool_radial_fade && (max_radius>0.0001f))) || + (do_depth_fades != (emitters[i]->mDataBlock->pool_depth_fade && d_safe))) + { + do_mixed_fades = true; + break; + } + } + + if (do_mixed_fades) + { + for (U32 i = 0; i < orderedVector.size(); i++) + { + Particle* particle = orderedVector[i].p; + ParticleEmitter* emitter = orderedVector[i].emitter; + + F32 bf = 1.0; // blend factor + + // blend factor due to radius + if (emitter->mDataBlock->pool_radial_fade && (max_radius>0.0001f)) + { + F32 p_radius = (particle->pos-bbox_center).len(); + F32 bf_radius = p_radius/max_radius; + if (bf_radius>1.0f) bf_radius = 1.0f; + bf *= bf_radius*bf_radius; // quadratic for faster falloff + } + + // blend factor, depth based + if (emitter->mDataBlock->pool_depth_fade && d_safe && (orderedVector[i].k > d_half)) + { + F32 bf_depth = ((max_d-orderedVector[i].k) / (d_range*0.5f)); + bf *= bf_depth; + } + + // overall blend factor weight + bf *= mDataBlock->blend_weight; + + LinearColorF color_save = particle->color; + particle->color = particle->color*bf; + emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr); + particle->color = color_save; + + buffPtr+=4; + } + } + else if (do_radial_fades && do_depth_fades) + { + for (U32 i = 0; i < orderedVector.size(); i++) + { + Particle* particle = orderedVector[i].p; + ParticleEmitter* emitter = orderedVector[i].emitter; + + F32 bf = 1.0; // blend factor + + // blend factor due to radius + F32 p_radius = (particle->pos-bbox_center).len(); + F32 bf_radius = p_radius/max_radius; + if (bf_radius>1.0f) bf_radius = 1.0f; + bf *= bf_radius*bf_radius; // quadratic for faster falloff + + // blend factor, depth based + if (orderedVector[i].k > d_half) + { + F32 bf_depth = ((max_d-orderedVector[i].k) / (d_range*0.5f)); + bf *= bf_depth; + } + + // overall blend factor weight + bf *= mDataBlock->blend_weight; + + LinearColorF color_save = particle->color; + particle->color = particle->color*bf; + emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr); + particle->color = color_save; + + buffPtr+=4; + } + } + else if (do_radial_fades) // && !do_depth_fades + { + for (U32 i = 0; i < orderedVector.size(); i++) + { + Particle* particle = orderedVector[i].p; + ParticleEmitter* emitter = orderedVector[i].emitter; + + F32 bf = 1.0; // blend factor + + // blend factor due to radius + F32 p_radius = (particle->pos-bbox_center).len(); + F32 bf_radius = p_radius/max_radius; + if (bf_radius>1.0f) bf_radius = 1.0f; + bf *= bf_radius*bf_radius; // quadratic for faster falloff + + // overall blend factor weight + bf *= mDataBlock->blend_weight; + + LinearColorF color_save = particle->color; + particle->color = particle->color*bf; + emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr); + particle->color = color_save; + + buffPtr+=4; + } + } + else if (do_depth_fades) // && !do_radial_fades + { + for (U32 i = 0; i < orderedVector.size(); i++) + { + Particle* particle = orderedVector[i].p; + ParticleEmitter* emitter = orderedVector[i].emitter; + + F32 bf = 1.0; // blend factor + + // blend factor, depth based + if (orderedVector[i].k > d_half) + { + F32 bf_depth = ((max_d-orderedVector[i].k) / (d_range*0.5f)); + bf *= bf_depth; + } + + // overall blend factor weight + bf *= mDataBlock->blend_weight; + + LinearColorF color_save = particle->color; + particle->color = particle->color*bf; + emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr); + particle->color = color_save; + + buffPtr+=4; + } + } + else // (no fades) + { + for (U32 i = 0; i < orderedVector.size(); i++) + { + Particle* particle = orderedVector[i].p; + ParticleEmitter* emitter = orderedVector[i].emitter; + + F32 bf = mDataBlock->blend_weight; // blend factor + + LinearColorF color_save = particle->color; + particle->color = particle->color*bf; + emitter->setupBillboard(particle, basePoints, camView, ambientColor, buffPtr); + particle->color = color_save; + + buffPtr+=4; + } + } + + // create new VB if emitter size grows + if( !mVertBuff2 || n_parts > mCurBuffSize2 ) + { + mCurBuffSize2 = n_parts; + mVertBuff2.set(GFX, n_parts*4, GFXBufferTypeDynamic); + } + + // lock and copy tempBuff to video RAM + verts = mVertBuff2.lock(); + dMemcpy( verts, tempBuff.address(), n_parts * 4 * sizeof(GFXVertexPCT) ); + mVertBuff2.unlock(); + + ri = renderManager->allocInst(); + ri->vertBuff = &mVertBuff2; + ri->primBuff = &main_emitter_data->primBuff; + ri->translucentSort = true; + ri->type = RenderPassManager::RIT_Particle; + ri->sortDistSq = save_sort_dist; + + ri->defaultKey = (-sort_priority*100) + 1; + + ri->modelViewProj = renderManager->allocUniqueXform( GFX->getProjectionMatrix() * + GFX->getViewMatrix() * + GFX->getWorldMatrix() ); + + ri->count = n_parts; + + ri->blendStyle = ParticleRenderInst::BlendAdditive; + + // use first particle's texture unless there is an emitter texture to override it + if (main_emitter_data->textureHandle) + ri->diffuseTex = &*(main_emitter_data->textureHandle); + else + ri->diffuseTex = &*(main_emitter_data->particleDataBlocks[0]->textureHandle); + + ri->softnessDistance = main_emitter_data->softnessDistance; + + renderManager->addInst( ri ); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/util/afxPath.cpp b/Engine/source/afx/util/afxPath.cpp new file mode 100644 index 000000000..1a9ecc4af --- /dev/null +++ b/Engine/source/afx/util/afxPath.cpp @@ -0,0 +1,530 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/consoleTypes.h" +#include "core/stream/bitStream.h" +#include "math/mathIO.h" + +#include "afx/util/afxPath.h" +#include "afx/util/afxPath3D.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// afxPathData + +IMPLEMENT_CO_DATABLOCK_V1(afxPathData); + +ConsoleDocClass( afxPathData, + "@brief A datablock for specifiying a 3D path for use with AFX.\n\n" + + "@ingroup afxUtil\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +StringTableEntry afxPathData::POINTS_FIELD; +StringTableEntry afxPathData::ROLL_FIELD; +StringTableEntry afxPathData::TIMES_FIELD; + +ImplementEnumType( afxPath3DLoopType, "Possible loop types for an afxPath.\n" "@ingroup afxPath\n\n" ) + { afxPath3D::LOOP_CONSTANT, "constant", "..." }, + { afxPath3D::LOOP_CYCLE, "cycle", "..." }, + { afxPath3D::LOOP_OSCILLATE, "oscillate", "..." }, +EndImplementEnumType; + +afxPathData::afxPathData() +{ + if (POINTS_FIELD == 0) + { + POINTS_FIELD = StringTable->insert("points"); + ROLL_FIELD = StringTable->insert("roll"); + TIMES_FIELD = StringTable->insert("times"); + } + + loop_string = ST_NULLSTRING; + delay = 0; + lifetime = 0; + loop_type = 0; + mult = 1.0f; + time_offset = 0.0f; + resolved = false; + reverse = false; + offset.zero(); + echo = false; + concentric = false; + + points_string = ST_NULLSTRING; + points = 0; + num_points = 0; + update_points = true; + + roll_string = ST_NULLSTRING; + rolls = 0; + update_rolls = true; + + times_string = ST_NULLSTRING; + times = 0; + update_times = true; +} + +afxPathData::afxPathData(const afxPathData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + points_string = other.points_string; + roll_string = other.roll_string; + loop_string = other.loop_string; + delay = other.delay; + lifetime = other.lifetime; + loop_type = other.loop_type; // -- + mult = other.mult; + time_offset = other.time_offset; + resolved = other.resolved; // -- + reverse = other.reverse; + offset = other.offset; + echo = other.echo; + concentric = other.concentric; + times_string = other.times_string; + + num_points = other.num_points; // -- + if (other.points && num_points > 0) + { + points = new Point3F[num_points]; + dMemcpy(points, other.points, sizeof(Point3F)*num_points); // -- + } + else + points = 0; + if (other.rolls && num_points > 0) + { + rolls = new F32[num_points]; + dMemcpy(rolls, other.rolls, sizeof(F32)*num_points); // -- + } + else + rolls = 0; + if (other.times && num_points > 0) + { + times = new F32[num_points]; + dMemcpy(times, other.times, sizeof(F32)*num_points); // -- + } + else + times = 0; + + update_points = other.update_points; // -- + update_rolls = other.update_rolls; // -- + update_times = other.update_times; // -- +} + +afxPathData::~afxPathData() +{ + clear_arrays(); +} + +void afxPathData::initPersistFields() +{ + addField("points", TypeString, Offset(points_string, afxPathData), + "..."); + addField("roll", TypeString, Offset(roll_string, afxPathData), + "..."); + addField("times", TypeString, Offset(times_string, afxPathData), + "..."); + addField("loop", TypeString, Offset(loop_string, afxPathData), + "..."); + addField("mult", TypeF32, Offset(mult, afxPathData), + "..."); + addField("delay", TypeF32, Offset(delay, afxPathData), + "..."); + addField("lifetime", TypeF32, Offset(lifetime, afxPathData), + "..."); + addField("timeOffset", TypeF32, Offset(time_offset, afxPathData), + "..."); + addField("reverse", TypeBool, Offset(reverse, afxPathData), + "..."); + addField("offset", TypePoint3F, Offset(offset, afxPathData), + "..."); + addField("echo", TypeBool, Offset(echo, afxPathData), + "..."); + addField("concentric", TypeBool, Offset(concentric, afxPathData), + "..."); + + Parent::initPersistFields(); +} + +bool afxPathData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + update_derived_values(); + + return true; +} + +void afxPathData::onRemove() +{ + clear_arrays(); + loop_type = 0; + + Parent::onRemove(); +} + +void afxPathData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(num_points); + if (num_points > 0) + { + for (U32 i = 0; i < num_points; i++) + mathWrite(*stream, points[i]); + if (stream->writeFlag(rolls != 0)) + { + for (U32 i = 0; i < num_points; i++) + stream->write(rolls[i]); + } + if (stream->writeFlag(times != 0)) + { + for (U32 i = 0; i < num_points; i++) + stream->write(times[i]); + } + } + + stream->writeString(loop_string); + stream->write(delay); + stream->write(lifetime); + stream->write(time_offset); + stream->write(mult); + stream->writeFlag(reverse); + mathWrite(*stream, offset); + stream->writeFlag(echo); + stream->writeFlag(concentric); +} + +void afxPathData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + clear_arrays(); + + // read the points and rolls + stream->read(&num_points); + if (num_points > 0) + { + points = new Point3F[num_points]; + for (U32 i = 0; i < num_points; i++) + mathRead(*stream, &points[i]); + update_points = false; + if (stream->readFlag()) + { + rolls = new F32[num_points]; + for (U32 i = 0; i < num_points; i++) + stream->read(&rolls[i]); + update_rolls = false; + } + if (stream->readFlag()) + { + times = new F32[num_points]; + for (U32 i = 0; i < num_points; i++) + stream->read(×[i]); + update_times = false; + } + } + + loop_string = stream->readSTString(); + stream->read(&delay); + stream->read(&lifetime); + stream->read(&time_offset); + stream->read(&mult); + reverse = stream->readFlag(); + mathRead(*stream, &offset); + echo = stream->readFlag(); + concentric = stream->readFlag(); +} + +void afxPathData::update_derived_values() +{ + U32 num_rolls = (rolls != 0) ? num_points : 0; + U32 num_times = (times != 0) ? num_points : 0; + + if (update_points) + { + derive_points_array(); + update_points = false; + } + + if (update_rolls || num_rolls != num_points) + { + derive_rolls_array(); + update_rolls = false; + } + + if (update_times || num_times != num_points) + { + derive_times_array(); + update_times = false; + } + + // CAUTION: The following block of code is fragile and tricky since it depends + // on the underlying structures defined by ImplementEnumType/EndImplementEnumType. + // This done because the enum text is parsed from a longer string. + if (loop_string != ST_NULLSTRING) + { + for (unsigned int i = 0; i < _afxPath3DLoopType::_sEnumTable.getNumValues(); i++) + { + if (dStricmp(_afxPath3DLoopType::_sEnumTable[i].mName, loop_string) == 0) + { + loop_type = _afxPath3DLoopType::_sEnumTable[i].mInt; + break; + } + } + } + else + { + loop_string = _afxPath3DLoopType::_sEnumTable[0].mName; + loop_type = _afxPath3DLoopType::_sEnumTable[0].mInt; + } +} + +bool afxPathData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + update_derived_values(); + + return true; +} + +void afxPathData::onStaticModified(const char* slot, const char* newValue) +{ + Parent::onStaticModified(slot, newValue); + + if (slot == POINTS_FIELD) + { + update_points = true; + return; + } + if (slot == ROLL_FIELD) + { + update_rolls = true; + return; + } + if (slot == TIMES_FIELD) + { + update_times = true; + return; + } +} + +void afxPathData::extract_floats_from_string(Vector& values, const char* points_str) +{ + values.clear(); + + if (!points_str) + return; + + // make a copy of points_str for tokenizing + char* tokCopy = dStrdup(points_str); + + // extract each token, convert to float, add to values[] + char* currTok = dStrtok(tokCopy, " \t"); + while (currTok != NULL) + { + F32 value = dAtof(currTok); + values.push_back(value); + currTok = dStrtok(NULL, " \t"); + } + + dFree(tokCopy); +} + +Point3F* afxPathData::build_points_array(Vector& values, U32& n_points, bool reverse) +{ + AssertFatal(values.size() > 0, "Values array is empty."); + AssertFatal(values.size()%3 == 0, "Values array is not a multiple of 3."); + + n_points = values.size()/3; + Point3F* points = new Point3F[n_points]; + + if (reverse) + { + U32 p_i = 0; + for (S32 i = values.size()-1; i > 1; i-=3) + points[p_i++].set(values[i-2], values[i-1], values[i]); + } + else + { + U32 p_i = 0; + for (U32 i = 0; i < values.size(); i+=3) + points[p_i++].set(values[i], values[i+1], values[i+2]); + } + + return points; +} + +F32* afxPathData::build_floats_array(Vector& values, U32& n_floats, bool reverse) +{ + AssertFatal(values.size() > 0, "Values array is empty."); + + n_floats = values.size(); + F32* floats = new F32[n_floats]; + + if (reverse) + { + F32* f = floats; + for (S32 i = values.size()-1; i >= 0; i--) + { + *f = values[i]; + f++; + } + } + else + { + for (U32 i = 0; i < values.size(); i++) + floats[i] = values[i]; + } + + return floats; +} + +void afxPathData::clear_arrays() +{ + num_points = 0; + if (points) + { + delete [] points; + points = 0; + } + if (rolls) + { + delete [] rolls; + rolls = 0; + } + if (times) + { + delete [] times; + times = 0; + } + update_points = true; + update_rolls = true; + update_times = true; +} + +void afxPathData::derive_points_array() +{ + if (points_string == ST_NULLSTRING) + return; + + if (points) + { + delete [] points; + points = 0; + } + num_points = 0; + + Vector values; + extract_floats_from_string(values, points_string); + if (values.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "afxPathData(%s) empty points field, datablock is invalid.", getName()); + return; + } + if (values.size()%3 != 0) + { + Con::warnf(ConsoleLogEntry::General, "afxPathData(%s) total points values is not a multiple of 3, datablock is invalid.", getName()); + return; + } + + points = build_points_array(values, num_points, reverse); + + if (offset.x != 0.0f || offset.y != 0.0f || offset.z != 0.0f) + { + // add offset here for efficiency (saves an addition from afxXM_PathConform) + for (U32 i = 0; i < num_points; i++) + points[i] += offset; + } +} + +void afxPathData::derive_rolls_array() +{ + if (roll_string == ST_NULLSTRING) + return; + + if (rolls) + { + delete [] rolls; + rolls = 0; + } + + Vector values; + extract_floats_from_string(values, roll_string); + if (values.size() == 0) + return; + + if (values.size() != num_points) + { + Con::warnf(ConsoleLogEntry::General, "afxPathData(%s) total roll values is not equal to total points, rolls ignored.", getName()); + return; + } + + U32 num_rolls = 0; + rolls = build_floats_array(values, num_rolls, reverse); + + AssertFatal(num_rolls == num_points, "Unexpected error: num_rolls disagrees with num_points."); +} + +void afxPathData::derive_times_array() +{ + if (times_string == ST_NULLSTRING) + return; + + if (times) + { + delete [] times; + times = 0; + } + + Vector values; + extract_floats_from_string(values, times_string); + if (values.size() == 0) + return; + + if (values.size() != num_points) + { + Con::warnf(ConsoleLogEntry::General, "afxPathData(%s) total time values is not equal to total points, times ignored", getName()); + return; + } + + U32 num_times = 0; + times = build_floats_array(values, num_times, reverse); + + AssertFatal(num_times == num_points, "Unexpected error: num_times disagrees with num_points."); +} + +void afxPathData::onPerformSubstitutions() +{ + Parent::onPerformSubstitutions(); + update_derived_values(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/util/afxPath.h b/Engine/source/afx/util/afxPath.h new file mode 100644 index 000000000..50ce218e8 --- /dev/null +++ b/Engine/source/afx/util/afxPath.h @@ -0,0 +1,102 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_PATH_H_ +#define _AFX_PATH_H_ + +#include "core/util/tVector.h" + +class afxPathData : public GameBaseData +{ + typedef GameBaseData Parent; + + static StringTableEntry POINTS_FIELD; + static StringTableEntry ROLL_FIELD; + static StringTableEntry TIMES_FIELD; + + bool resolved; + bool update_points; + bool update_rolls; + bool update_times; + + void update_derived_values(); + + void clear_arrays(); + void derive_points_array(); + void derive_rolls_array(); + void derive_times_array(); + + static void extract_floats_from_string(Vector& values, const char* points_str); + static Point3F* build_points_array(Vector& values, U32& n_points, bool reverse=false); + static F32* build_floats_array(Vector& values, U32& n_floats, bool reverse=false); + +public: + U32 num_points; + StringTableEntry points_string; + Point3F* points; + + StringTableEntry roll_string; + F32* rolls; + + StringTableEntry times_string; + F32* times; + + StringTableEntry loop_string; + F32 delay; + F32 lifetime; + + U32 loop_type; + F32 mult; + F32 time_offset; + bool reverse; + Point3F offset; + bool echo; + bool concentric; + +public: + /*C*/ afxPathData(); + /*C*/ afxPathData(const afxPathData&, bool = false); + /*D*/ ~afxPathData(); + + virtual bool onAdd(); + virtual void onRemove(); + virtual void packData(BitStream*); + virtual void unpackData(BitStream*); + + bool preload(bool server, String &errorStr); + + virtual void onStaticModified(const char* slotName, const char* newValue = NULL); + virtual void onPerformSubstitutions(); + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxPathData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_PATH_H_ diff --git a/Engine/source/afx/util/afxPath3D.cpp b/Engine/source/afx/util/afxPath3D.cpp new file mode 100644 index 000000000..f37b141d9 --- /dev/null +++ b/Engine/source/afx/util/afxPath3D.cpp @@ -0,0 +1,342 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "console/console.h" + +#include "afx/util/afxPath3D.h" + +afxPath3D::afxPath3D() : start_time(0), num_points(0), loop_type(LOOP_CONSTANT) +{ +} + +afxPath3D::~afxPath3D() +{ +} + +void afxPath3D::sortAll() +{ + curve.sort(); + curve_parameters.sort(); +} + +void afxPath3D::setStartTime( F32 time ) +{ + start_time = time; +} + +void afxPath3D::setLoopType( U32 loop_type ) +{ + this->loop_type = loop_type; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +F32 afxPath3D::getEndTime() +{ + return end_time; +} + +int afxPath3D::getNumPoints() +{ + return num_points; +} + +Point3F afxPath3D::getPointPosition( int index ) +{ + if (index < 0 || index >= num_points) + return Point3F(0.0f, 0.0f, 0.0f); + + return curve.getPoint(index); +} + +F32 afxPath3D::getPointTime( int index ) +{ + if (index < 0 || index >= num_points) + return 0.0f; + + return curve_parameters.getKeyTime(index); +} + +F32 afxPath3D::getPointParameter( int index ) +{ + if (index < 0 || index >= num_points) + return 0.0f; + + return curve_parameters.getKeyValue(index); +} + +Point2F afxPath3D::getParameterSegment( F32 time ) +{ + return curve_parameters.getSegment(time); +} + +void afxPath3D::setPointPosition( int index, Point3F &p ) +{ + if (index < 0 || index >= num_points) + return; + + curve.setPoint(index, p); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +F32 afxPath3D::calcCurveTime( F32 time ) +{ + if( time <= start_time ) + return 0.0f; + if( time <= end_time ) + return time-start_time; + + switch( loop_type ) + { + case LOOP_CYCLE : + { + return mFmod( time-start_time, end_time-start_time ); + } + case LOOP_OSCILLATE : + { + F32 t1 = time-start_time; + F32 t2 = end_time-start_time; + + if( (int)(t1/t2) % 2 ) // odd segment + return t2 - mFmod( t1, t2 ); + else // even segment + return mFmod( t1, t2 ); + } + case LOOP_CONSTANT : + default: + return end_time; + } +} + +Point3F afxPath3D::evaluateAtTime( F32 time ) +{ + F32 ctime = calcCurveTime( time ); + F32 param = curve_parameters.evaluate( ctime ); + return curve.evaluate(param); +} + +Point3F afxPath3D::evaluateAtTime(F32 t0, F32 t1) +{ + F32 ctime = calcCurveTime(t0); + F32 param = curve_parameters.evaluate( ctime ); + Point3F p0 = curve.evaluate(param); + + ctime = calcCurveTime(t1); + param = curve_parameters.evaluate( ctime ); + Point3F p1 = curve.evaluate(param); + + return p1-p0; +} + +Point3F afxPath3D::evaluateTangentAtTime( F32 time ) +{ + F32 ctime = calcCurveTime( time ); + F32 param = curve_parameters.evaluate( ctime ); + return curve.evaluateTangent(param); +} + +Point3F afxPath3D::evaluateTangentAtPoint( int index ) +{ + F32 param = curve_parameters.getKeyValue(index); + return curve.evaluateTangent(param); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +void afxPath3D::buildPath( int num_points, Point3F curve_points[], F32 start_time, F32 end_time ) +{ + this->num_points = num_points; + + // Add points to path + F32 param_inc = 1.0f / (F32)(num_points - 1); + F32 param = 0.0f; + for( int i = 0; i < num_points; i++, param += param_inc ) + { + if( i == num_points-1 ) + param = 1.0f; + curve.addPoint( param, curve_points[i] ); + } + + curve.computeTangents(); + + initPathParametersNEW( curve_points, start_time, end_time ); + + sortAll(); +} + +void afxPath3D::buildPath( int num_points, Point3F curve_points[], F32 speed ) +{ + this->num_points = num_points; + + // Add points to path + F32 param_inc = 1.0f / (F32)(num_points - 1); + F32 param = 0.0f; + for( int i = 0; i < num_points; i++, param += param_inc ) + { + if( i == num_points-1 ) + param = 1.0f; + curve.addPoint( param, curve_points[i] ); + } + + initPathParameters( curve_points, speed ); + + sortAll(); +} + +void afxPath3D::buildPath( int num_points, Point3F curve_points[], + F32 point_times[], F32 time_offset, F32 time_factor ) +{ + this->num_points = num_points; + + // Add points to path + F32 param_inc = 1.0f / (F32)(num_points - 1); + F32 param = 0.0f; + for( int i = 0; i < num_points; i++, param += param_inc ) + { + if( i == num_points-1 ) + param = 1.0f; + curve.addPoint( param, curve_points[i] ); + + curve_parameters.addKey( (point_times[i]+time_offset)*time_factor, param ); + } + + // Set end time + end_time = (point_times[num_points-1]+time_offset)*time_factor; + + sortAll(); +} + +void afxPath3D::buildPath( int num_points, Point3F curve_points[], Point2F curve_params[] ) +{ + this->num_points = num_points; + + // Add points to path + F32 param_inc = 1.0f / (F32)(num_points - 1); + F32 param = 0.0f; + for( int i = 0; i < num_points; i++, param += param_inc ) + { + if( i == num_points-1 ) + param = 1.0f; + curve.addPoint( param, curve_points[i] ); + } + + // + for (int i = 0; i < num_points; i++) + curve_parameters.addKey( curve_params[i] ); + + // Set end time + end_time = curve_params[num_points - 1].x; + + sortAll(); +} + +void afxPath3D::reBuildPath() +{ + curve.computeTangents(); + sortAll(); +} + +void afxPath3D::initPathParameters( Point3F curve_points[], F32 speed ) +{ + // Compute the time for each point dependent on the speed of the character and the + // distance it must travel (approximately!) + int num_segments = num_points - 1; + F32 *point_distances = new F32[num_segments]; + for( int i = 0; i < num_segments; i++ ) + { + Point3F p1 = curve_points[i+1]; + Point3F p0 = curve_points[i]; + + point_distances[i] = (p1-p0).len(); + } + + F32 *times = new F32[num_segments]; + F32 last_time = 0;//start_time; + for( int i = 0; i < num_segments; i++ ) + { + times[i] = last_time + (point_distances[i] / speed); + last_time = times[i]; + } + + curve_parameters.addKey( 0, 0.0f );//start_time, 0.0f ); + F32 param_inc = 1.0f / (F32)(num_points - 1); + F32 param = 0.0f + param_inc; + for( int i = 0; i < num_segments; i++, param += param_inc ) + curve_parameters.addKey( times[i], param ); + + // Set end time + end_time = times[num_segments-1]; + + if (point_distances) + delete [] point_distances; + if (times) + delete [] times; +} + +void afxPath3D::initPathParametersNEW( Point3F curve_points[], F32 start_time, F32 end_time ) +{ + int num_segments = num_points - 1; + F32 *point_distances = new F32[num_segments]; + F32 total_distance = 0.0f; + for( int i = 0; i < num_segments; i++ ) + { + Point3F p1 = curve_points[i+1]; + Point3F p0 = curve_points[i]; + + point_distances[i] = (p1-p0).len(); + total_distance += point_distances[i]; + } + + F32 duration = end_time - start_time; + + F32 time = 0.0f; //start_time; + curve_parameters.addKey( time, 0.0f ); + F32 param_inc = 1.0f / (F32)(num_points - 1); + F32 param = 0.0f + param_inc; + for( int i=0; i < num_segments; i++, param += param_inc ) + { + time += (point_distances[i]/total_distance) * duration; + curve_parameters.addKey( time, param ); + } + + // Set end time ???? + //end_time = time; + this->start_time = start_time; + this->end_time = end_time; + + if (point_distances) + delete [] point_distances; +} + +void afxPath3D::print() +{ + // curve.print(); + curve_parameters.print(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/util/afxPath3D.h b/Engine/source/afx/util/afxPath3D.h new file mode 100644 index 000000000..77d2e2ad2 --- /dev/null +++ b/Engine/source/afx/util/afxPath3D.h @@ -0,0 +1,101 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_PATH3D_UTIL_H_ +#define _AFX_PATH3D_UTIL_H_ + +#include "afx/util/afxCurve3D.h" +#include "afx/util/afxAnimCurve.h" + +#include "math/mMatrix.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxPath3D : public EngineObject +{ +private: + // Path-related data + afxCurve3D curve; + afxAnimCurve curve_parameters; + int num_points; + + // Time data + F32 start_time; + F32 end_time; + +public: + /*C*/ afxPath3D( ); + /*D*/ ~afxPath3D(); + + void sortAll(); + + void setStartTime(F32 time); + + F32 getEndTime(); + int getNumPoints(); + Point3F getPointPosition(int index); + F32 getPointTime(int index); + F32 getPointParameter(int index); + Point2F getParameterSegment(F32 time); + + void setPointPosition(int index, Point3F &p); + + Point3F evaluateAtTime(F32 time); + Point3F evaluateAtTime(F32 t0, F32 t1); // returns delta + Point3F evaluateTangentAtTime(F32 time); + Point3F evaluateTangentAtPoint(int index); + + void buildPath(int num_points, Point3F curve_points[], F32 start_time, F32 end_time); + void buildPath(int num_points, Point3F curve_points[], F32 speed); + void buildPath(int num_points, Point3F curve_points[], F32 point_times[], F32 time_offset, F32 time_factor); + void buildPath(int num_points, Point3F curve_points[], Point2F curve_params[]); + + void reBuildPath(); + + void print(); + + enum LoopType + { + LOOP_CONSTANT, + LOOP_CYCLE, + LOOP_OSCILLATE + }; + + U32 loop_type; + void setLoopType(U32); + +private: + void initPathParameters(Point3F curve_points[], F32 speed); + void initPathParametersNEW(Point3F curve_points[], F32 start_time, F32 end_time); + + F32 calcCurveTime(F32 time); +}; + +typedef afxPath3D::LoopType afxPath3DLoopType; +DefineEnumType( afxPath3DLoopType ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_PATH3D_UTIL_H_ diff --git a/Engine/source/afx/util/afxTriBoxCheck2D_T3D.cpp b/Engine/source/afx/util/afxTriBoxCheck2D_T3D.cpp new file mode 100644 index 000000000..1802fe9b6 --- /dev/null +++ b/Engine/source/afx/util/afxTriBoxCheck2D_T3D.cpp @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Adapted to a 2D test for intersecting atlas triangles with zodiacs. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +//----------------------------------------------------------------------------- +// AABB-triangle overlap test code originally by Tomas Akenine-Möller +// Assisted by Pierre Terdiman and David Hunt +// http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/ +// Ported to TSE by BJG, 2005-4-14 +//----------------------------------------------------------------------------- + +#include "afx/arcaneFX.h" +#include "afx/util/afxTriBoxCheck2D_T3D.h" + +#define FINDMINMAX(x0,x1,x2,min,max) \ + min = max = x0; \ + if(x1max) max=x1;\ + if(x2max) max=x2; + +/*======================== Z-tests ========================*/ + +#define AXISTEST_Z12(a, b, fa, fb) \ + p1 = a*v1.x - b*v1.y; \ + p2 = a*v2.x - b*v2.y; \ + if(p2rad || max<-rad) return false; + +#define AXISTEST_Z0(a, b, fa, fb) \ + p0 = a*v0.x - b*v0.y; \ + p1 = a*v1.x - b*v1.y; \ + if(p0rad || max<-rad) return false; + +bool afxTriBoxOverlap2D(const Point3F& boxcenter, const Point3F& boxhalfsize, const Point3F& a, const Point3F& b, const Point3F& c) +{ + /* use separating axis theorem to test overlap between triangle and box */ + /* need to test for overlap in these directions: */ + /* 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle */ + /* we do not even need to test these) */ + /* 2) normal of the triangle */ + /* 3) crossproduct(edge from tri, {x,y,z}-directin) */ + /* this gives 3x3=9 more tests */ + + F32 min,max,p0,p1,p2,rad; + + /* move everything so that the boxcenter is in (0,0,0) */ + Point3F v0 = a - boxcenter; + Point3F v1 = b - boxcenter; + Point3F v2 = c - boxcenter; + + /* compute triangle edges */ + Point3F e0 = v1 - v0; /* tri edge 0 */ + Point3F e1 = v2 - v1; /* tri edge 1 */ + Point3F e2 = v0 - v2; /* tri edge 2 */ + + /* Bullet 3: */ + /* test the 3 tests first */ + F32 fex = mFabs(e0.x); + F32 fey = mFabs(e0.y); + AXISTEST_Z12(e0.y, e0.x, fey, fex); + + fex = mFabs(e1.x); + fey = mFabs(e1.y); + AXISTEST_Z0(e1.y, e1.x, fey, fex); + + fex = mFabs(e2.x); + fey = mFabs(e2.y); + AXISTEST_Z12(e2.y, e2.x, fey, fex); + + /* Bullet 1: */ + /* first test overlap in the {x,y,z}-directions */ + /* find min, max of the triangle each direction, and test for overlap in */ + /* that direction -- this is equivalent to testing a minimal AABB around */ + /* the triangle against the AABB */ + + /* test in X-direction */ + FINDMINMAX(v0.x,v1.x,v2.x,min,max); + if(min>boxhalfsize.x || max<-boxhalfsize.x) return false; + + /* test in Y-direction */ + FINDMINMAX(v0.y,v1.y,v2.y,min,max); + if(min>boxhalfsize.y || max<-boxhalfsize.y) return false; + + return true; /* box and triangle overlaps */ +} + diff --git a/Engine/source/afx/util/afxTriBoxCheck2D_T3D.h b/Engine/source/afx/util/afxTriBoxCheck2D_T3D.h new file mode 100644 index 000000000..34b64adea --- /dev/null +++ b/Engine/source/afx/util/afxTriBoxCheck2D_T3D.h @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------------- +// Copyright (c) 2012 GarageGames, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +//----------------------------------------------------------------------------- + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Adapted to a 2D test for intersecting atlas triangles with zodiacs. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +//----------------------------------------------------------------------------- +// AABB-triangle overlap test code originally by Tomas Akenine-Möller +// Assisted by Pierre Terdiman and David Hunt +// http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/ +// Ported to TSE by BJG, 2005-4-14 +//----------------------------------------------------------------------------- + +#ifndef _AFX_TRIBOXCHECK_2D_H_ +#define _AFX_TRIBOXCHECK_2D_H_ + +#include "math/mPoint3.h" +#include "math/mBox.h" + +bool afxTriBoxOverlap2D(const Point3F& boxcenter, const Point3F& boxhalfsize, const Point3F& a, const Point3F& b, const Point3F& c); + +#endif // _AFX_TRIBOXCHECK_2D_H_ \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_Aim.cpp b/Engine/source/afx/xm/afxXM_Aim.cpp new file mode 100644 index 000000000..5e52823b7 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_Aim.cpp @@ -0,0 +1,261 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_AimData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + bool aim_z_only; + +public: + /*C*/ afxXM_AimData(); + /*C*/ afxXM_AimData(const afxXM_AimData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_AimData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxConstraint; + +class afxXM_Aim_weighted : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + +public: + /*C*/ afxXM_Aim_weighted(afxXM_AimData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Aim_weighted_z : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + +public: + /*C*/ afxXM_Aim_weighted_z(afxXM_AimData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Aim_fixed : public afxXM_Base +{ + typedef afxXM_Base Parent; + +public: + /*C*/ afxXM_Aim_fixed(afxXM_AimData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Aim_fixed_z : public afxXM_Base +{ + typedef afxXM_Base Parent; + +public: + /*C*/ afxXM_Aim_fixed_z(afxXM_AimData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_AimData); + +ConsoleDocClass( afxXM_AimData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_AimData::afxXM_AimData() +{ + aim_z_only = false; +} + +afxXM_AimData::afxXM_AimData(const afxXM_AimData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + aim_z_only = other.aim_z_only; +} + +void afxXM_AimData::initPersistFields() +{ + addField("aimZOnly", TypeBool, Offset(aim_z_only, afxXM_AimData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_AimData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->writeFlag(aim_z_only); +} + +void afxXM_AimData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + aim_z_only = stream->readFlag(); +} + +afxXM_Base* afxXM_AimData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_AimData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_AimData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + if (datablock->aim_z_only) + { + if (datablock->hasFixedWeight()) + return new afxXM_Aim_fixed_z(datablock, fx); + else + return new afxXM_Aim_weighted_z(datablock, fx); + } + else + { + if (datablock->hasFixedWeight()) + return new afxXM_Aim_fixed(datablock, fx); + else + return new afxXM_Aim_weighted(datablock, fx); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Aim_weighted::afxXM_Aim_weighted(afxXM_AimData* db, afxEffectWrapper* fxw) + : afxXM_WeightedBase(db, fxw) +{ +} + +void afxXM_Aim_weighted::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + VectorF line_of_sight = params.pos2 - params.pos; + line_of_sight.normalize(); + + F32 wt_factor = calc_weight_factor(elapsed); + + QuatF qt_ori_incoming(params.ori); + + MatrixF ori_outgoing = MathUtils::createOrientFromDir(line_of_sight); + QuatF qt_ori_outgoing(ori_outgoing); + + QuatF qt_ori = qt_ori_incoming.slerp(qt_ori_outgoing, wt_factor); + + qt_ori.setMatrix(¶ms.ori); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Aim_weighted_z::afxXM_Aim_weighted_z(afxXM_AimData* db, afxEffectWrapper* fxw) + : afxXM_WeightedBase(db, fxw) +{ +} + +void afxXM_Aim_weighted_z::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + Point3F aim_at_pos = params.pos2; + aim_at_pos.z = params.pos.z; + + VectorF line_of_sight = aim_at_pos - params.pos; + line_of_sight.normalize(); + + F32 wt_factor = calc_weight_factor(elapsed); + + QuatF qt_ori_incoming(params.ori); + + MatrixF ori_outgoing = MathUtils::createOrientFromDir(line_of_sight); + QuatF qt_ori_outgoing( ori_outgoing ); + + QuatF qt_ori = qt_ori_incoming.slerp(qt_ori_outgoing, wt_factor); + + qt_ori.setMatrix(¶ms.ori); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Aim_fixed::afxXM_Aim_fixed(afxXM_AimData* db, afxEffectWrapper* fxw) + : afxXM_Base(db, fxw) +{ +} + +void afxXM_Aim_fixed::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + VectorF line_of_sight = params.pos2 - params.pos; + line_of_sight.normalize(); + params.ori = MathUtils::createOrientFromDir(line_of_sight); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Aim_fixed_z::afxXM_Aim_fixed_z(afxXM_AimData* db, afxEffectWrapper* fxw) + : afxXM_Base(db, fxw) +{ +} + +void afxXM_Aim_fixed_z::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + Point3F aim_at_pos = params.pos2; + aim_at_pos.z = params.pos.z; + + VectorF line_of_sight = aim_at_pos - params.pos; + line_of_sight.normalize(); + + params.ori = MathUtils::createOrientFromDir(line_of_sight); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_AltitudeConform.cpp b/Engine/source/afx/xm/afxXM_AltitudeConform.cpp new file mode 100644 index 000000000..02ce4634b --- /dev/null +++ b/Engine/source/afx/xm/afxXM_AltitudeConform.cpp @@ -0,0 +1,262 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_AltitudeConformData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + F32 height; + bool do_terrain; + bool do_interiors; + U32 interior_types; + U32 terrain_types; + bool do_freeze; + +public: + /*C*/ afxXM_AltitudeConformData(); + /*C*/ afxXM_AltitudeConformData(const afxXM_AltitudeConformData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_AltitudeConformData); + DECLARE_CATEGORY("AFX"); +}; + +class afxXM_AltitudeConform : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + afxXM_AltitudeConformData* db; + SceneContainer* container; + bool do_freeze; + bool is_frozen; + F32 terrain_alt; + F32 interior_alt; + Point3F conformed_pos; + +public: + /*C*/ afxXM_AltitudeConform(afxXM_AltitudeConformData*, afxEffectWrapper*, bool on_server); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_AltitudeConformData); + +ConsoleDocClass( afxXM_AltitudeConformData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_AltitudeConformData::afxXM_AltitudeConformData() +{ + height = 0.0f; + do_terrain = false; + do_interiors = true; + do_freeze = false; + interior_types = InteriorLikeObjectType; + terrain_types = TerrainObjectType | TerrainLikeObjectType; +} + +afxXM_AltitudeConformData::afxXM_AltitudeConformData(const afxXM_AltitudeConformData& other, bool temp_clone) + : afxXM_WeightedBaseData(other, temp_clone) +{ + height = other.height; + do_terrain = other.do_terrain; + do_interiors = other.do_interiors; + do_freeze = other.do_freeze; + interior_types = other.interior_types; + terrain_types = other.terrain_types; +} + +void afxXM_AltitudeConformData::initPersistFields() +{ + addField("height", TypeF32, Offset(height, afxXM_AltitudeConformData), + "..."); + addField("conformToTerrain", TypeBool, Offset(do_terrain, afxXM_AltitudeConformData), + "..."); + addField("conformToInteriors", TypeBool, Offset(do_interiors, afxXM_AltitudeConformData), + "..."); + addField("freeze", TypeBool, Offset(do_freeze, afxXM_AltitudeConformData), + "..."); + addField("interiorTypes", TypeS32, Offset(interior_types, afxXM_AltitudeConformData), + "..."); + addField("terrainTypes", TypeS32, Offset(terrain_types, afxXM_AltitudeConformData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_AltitudeConformData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(height); + stream->writeFlag(do_terrain); + stream->writeFlag(do_interiors); + stream->writeFlag(do_freeze); + stream->write(interior_types); + stream->write(terrain_types); +} + +void afxXM_AltitudeConformData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&height); + do_terrain = stream->readFlag(); + do_interiors = stream->readFlag(); + do_freeze = stream->readFlag(); + stream->read(&interior_types); + stream->read(&terrain_types); +} + +afxXM_Base* afxXM_AltitudeConformData::create(afxEffectWrapper* fx, bool on_server) +{ + return new afxXM_AltitudeConform(this, fx, on_server); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_AltitudeConform::afxXM_AltitudeConform(afxXM_AltitudeConformData* db, afxEffectWrapper* fxw, bool on_server) +: afxXM_WeightedBase(db, fxw) +{ + this->db = db; + container = (on_server) ? &gServerContainer : &gClientContainer; + do_freeze = db->do_freeze; + is_frozen = false; + terrain_alt = -1.0f; + interior_alt = -1.0f; + conformed_pos.zero(); +} + +void afxXM_AltitudeConform::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (is_frozen) + { + if (terrain_alt >= 0.0f) + fx_wrapper->setTerrainAltitude(terrain_alt); + if (interior_alt >= 0.0f) + fx_wrapper->setInteriorAltitude(interior_alt); + params.pos = conformed_pos; + return; + } + + RayInfo rInfo1, rInfo2; + bool hit1 = false, hit2 = false; + bool hit1_is_interior = false; + + // find primary ground + Point3F above_pos(params.pos); above_pos.z += 0.1f; + Point3F below_pos(params.pos); below_pos.z -= 10000; + hit1 = container->castRay(above_pos, below_pos, db->interior_types | db->terrain_types, &rInfo1); + + // find secondary ground + if (hit1 && rInfo1.object) + { + hit1_is_interior = ((rInfo1.object->getTypeMask() & db->interior_types) != 0); + U32 mask = (hit1_is_interior) ? db->terrain_types : db->interior_types; + Point3F above_pos(params.pos); above_pos.z += 0.1f; + Point3F below_pos(params.pos); below_pos.z -= 10000; + hit2 = container->castRay(above_pos, below_pos, mask, &rInfo2); + } + + if (hit1) + { + F32 wt_factor = calc_weight_factor(elapsed); + F32 incoming_z = params.pos.z; + F32 ground1_z = rInfo1.point.z + db->height; + F32 pos_z = ground1_z + (1.0f - wt_factor)*(incoming_z - ground1_z); + + if (hit1_is_interior) + { + interior_alt = incoming_z - pos_z; + fx_wrapper->setInteriorAltitude(interior_alt); + if (db->do_interiors) + params.pos.z = pos_z; + } + else + { + terrain_alt = incoming_z - pos_z; + fx_wrapper->setTerrainAltitude(terrain_alt); + if (db->do_terrain) + params.pos.z = pos_z; + } + + if (hit2) + { + F32 ground2_z = rInfo2.point.z + db->height; + F32 z2 = ground2_z + (1.0f - wt_factor)*(incoming_z - ground2_z); + if (hit1_is_interior) + { + terrain_alt = incoming_z - z2; + fx_wrapper->setTerrainAltitude(terrain_alt); + } + else + { + interior_alt = incoming_z - z2; + fx_wrapper->setInteriorAltitude(interior_alt); + } + } + + // check for case where interior is underground + else if (hit1_is_interior) + { + RayInfo rInfo0; + Point3F lookup_from_pos(params.pos); lookup_from_pos.z -= 0.1f; + Point3F lookup_to_pos(params.pos); lookup_to_pos.z += 10000; + if (container->castRay(lookup_from_pos, lookup_to_pos, TerrainObjectType, &rInfo0)) + { + F32 ground2_z = rInfo0.point.z + db->height; + F32 z2 = ground2_z + (1.0f - wt_factor)*(incoming_z - ground2_z); + terrain_alt = z2 - incoming_z; + fx_wrapper->setTerrainAltitude(terrain_alt); + } + } + + if (do_freeze) + { + conformed_pos = params.pos; + is_frozen = true; + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/xm/afxXM_BoxAdapt.cpp b/Engine/source/afx/xm/afxXM_BoxAdapt.cpp new file mode 100644 index 000000000..14df4b399 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_BoxAdapt.cpp @@ -0,0 +1,175 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_BoxAdaptData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + F32 scale_factor; + Point2F dim_range; + +public: + /*C*/ afxXM_BoxAdaptData(); + /*C*/ afxXM_BoxAdaptData(const afxXM_BoxAdaptData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_BoxAdaptData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxConstraint; + +class afxXM_BoxAdapt : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + F32 scale_factor; + Point2F dim_range; + +public: + /*C*/ afxXM_BoxAdapt(afxXM_BoxAdaptData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_BoxAdaptData); + +ConsoleDocClass( afxXM_BoxAdaptData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_BoxAdaptData::afxXM_BoxAdaptData() +{ + scale_factor = 1.0f; + dim_range.set(0.1f, 1000.0f); +} + +afxXM_BoxAdaptData::afxXM_BoxAdaptData(const afxXM_BoxAdaptData& other, bool temp_clone) + : afxXM_WeightedBaseData(other, temp_clone) +{ + scale_factor = other.scale_factor; + dim_range = other.dim_range; +} + +void afxXM_BoxAdaptData::initPersistFields() +{ + addField("scaleFactor", TypeF32, Offset(scale_factor, afxXM_BoxAdaptData), + "..."); + addField("dimensionRange", TypePoint2F, Offset(dim_range, afxXM_BoxAdaptData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_BoxAdaptData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(scale_factor); + mathWrite(*stream, dim_range); +} + +void afxXM_BoxAdaptData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&scale_factor); + mathRead(*stream, &dim_range); +} + +afxXM_Base* afxXM_BoxAdaptData::create(afxEffectWrapper* fx, bool on_server) +{ + return new afxXM_BoxAdapt(this, fx); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_BoxAdapt::afxXM_BoxAdapt(afxXM_BoxAdaptData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + scale_factor = db->scale_factor; + + dim_range = db->dim_range; + + dim_range.x = getMax(0.001f, dim_range.x); + dim_range.y = getMax(0.001f, dim_range.y); + + if (dim_range.x > dim_range.y) + { + F32 tmp = dim_range.y; + dim_range.y = dim_range.x; + dim_range.x = tmp; + } +} + +void afxXM_BoxAdapt::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + afxConstraint* pos_cons = fx_wrapper->getPosConstraint(); + if (!pos_cons) + return; + + SceneObject* obj = pos_cons->getSceneObject(); + if (!obj) + return; + + F32 wt_factor = calc_weight_factor(elapsed); + + const Box3F& obj_box = obj->getObjBox(); + const VectorF obj_scale = obj->getScale(); + + F32 x_dim = obj_box.len_x()*obj_scale.x; + F32 y_dim = obj_box.len_y()*obj_scale.y; + + F32 dim = mClampF(getMax(x_dim, y_dim), dim_range.x, dim_range.y); + dim *= scale_factor*wt_factor*0.5f; + + //Con::printf("SET liveScaleFactor=%g x_dim=%g, y_dim=%g", dim, obj_box.len_x(), obj_box.len_y()); + fx_wrapper->setField("liveScaleFactor", avar("%g", dim)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_BoxConform.cpp b/Engine/source/afx/xm/afxXM_BoxConform.cpp new file mode 100644 index 000000000..3ae897446 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_BoxConform.cpp @@ -0,0 +1,175 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +enum afxXM_BoxConformType +{ + X_POS, X_NEG, Y_POS, Y_NEG, Z_POS, Z_NEG +}; +DefineEnumType( afxXM_BoxConformType ); + +class afxXM_BoxConformData : public afxXM_BaseData +{ + typedef afxXM_BaseData Parent; + +public: + S32 aabb_alignment; + +public: + /*C*/ afxXM_BoxConformData(); + /*C*/ afxXM_BoxConformData(const afxXM_BoxConformData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_BoxConformData); + DECLARE_CATEGORY("AFX"); +}; + +class afxXM_BoxConform : public afxXM_Base +{ + typedef afxXM_Base Parent; + + afxXM_BoxConformData* db; + +public: + /*C*/ afxXM_BoxConform(afxXM_BoxConformData*, afxEffectWrapper*, bool on_server); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_BoxConformData); + +ConsoleDocClass( afxXM_BoxConformData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_BoxConformData::afxXM_BoxConformData() +{ + aabb_alignment = Z_NEG; +} + +afxXM_BoxConformData::afxXM_BoxConformData(const afxXM_BoxConformData& other, bool temp_clone) + : afxXM_BaseData(other, temp_clone) +{ + aabb_alignment = other.aabb_alignment; +} + +ImplementEnumType( afxXM_BoxConformType, "Possible box conform alignment types.\n" "@ingroup afxXM_BoxConform\n\n" ) + { X_POS, "+x", "..." }, + { X_NEG, "-x", "..." }, + { Y_POS, "+y", "..." }, + { Y_NEG, "-y", "..." }, + { Z_POS, "+z", "..." }, + { Z_NEG, "-z", "..." }, + { X_POS, "x", "..." }, + { Y_POS, "y", "..." }, + { Z_POS, "z", "..." }, +EndImplementEnumType; + +void afxXM_BoxConformData::initPersistFields() +{ + addField("boxAlignment", TYPEID< afxXM_BoxConformType >(), Offset(aabb_alignment, afxXM_BoxConformData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_BoxConformData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(aabb_alignment); +} + +void afxXM_BoxConformData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&aabb_alignment); +} + +afxXM_Base* afxXM_BoxConformData::create(afxEffectWrapper* fx, bool on_server) +{ + return new afxXM_BoxConform(this, fx, on_server); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_BoxConform::afxXM_BoxConform(afxXM_BoxConformData* db, afxEffectWrapper* fxw, bool on_server) +: afxXM_Base(db, fxw) +{ + this->db = db; +} + +void afxXM_BoxConform::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + afxConstraint* pos_cons = fx_wrapper->getPosConstraint(); + if (!pos_cons) + return; + + SceneObject* obj = pos_cons->getSceneObject(); + if (!obj) + return; + + const Box3F& box = obj->getWorldBox(); + + switch (db->aabb_alignment) + { + case X_POS: + params.pos.x = box.maxExtents.x; + break; + case X_NEG: + params.pos.x = box.minExtents.x; + break; + case Y_POS: + params.pos.y = box.maxExtents.y; + break; + case Y_NEG: + params.pos.y = box.minExtents.y; + break; + case Z_POS: + params.pos.z = box.maxExtents.z; + break; + case Z_NEG: + params.pos.z = box.minExtents.z; + break; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/xm/afxXM_BoxHeightOffset.cpp b/Engine/source/afx/xm/afxXM_BoxHeightOffset.cpp new file mode 100644 index 000000000..d8fccb013 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_BoxHeightOffset.cpp @@ -0,0 +1,145 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// BOX HEIGHT OFFSET + +class afxXM_BoxHeightOffsetData : public afxXM_BaseData +{ + typedef afxXM_BaseData Parent; + +public: + Point3F offset; + +public: + /*C*/ afxXM_BoxHeightOffsetData(); + /*C*/ afxXM_BoxHeightOffsetData(const afxXM_BoxHeightOffsetData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + static void initPersistFields(); + +#if defined(AFX_VERSION) + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); +#else + afxXM_Base* create(afxEffectWrapper* fx); +#endif + + DECLARE_CONOBJECT(afxXM_BoxHeightOffsetData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_BoxHeightOffset : public afxXM_Base +{ + typedef afxXM_Base Parent; + Point3F offset; + +public: + /*C*/ afxXM_BoxHeightOffset(afxXM_BoxHeightOffsetData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// BOX HEIGHT OFFSET + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_BoxHeightOffsetData); + +ConsoleDocClass( afxXM_BoxHeightOffsetData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_BoxHeightOffsetData::afxXM_BoxHeightOffsetData() +{ + offset.zero(); +} + +afxXM_BoxHeightOffsetData::afxXM_BoxHeightOffsetData(const afxXM_BoxHeightOffsetData& other, bool temp_clone) + : afxXM_BaseData(other, temp_clone) +{ + offset = other.offset; +} + +void afxXM_BoxHeightOffsetData::initPersistFields() +{ + addField("offset", TypePoint3F, Offset(offset, afxXM_BoxHeightOffsetData)); + + Parent::initPersistFields(); +} + +void afxXM_BoxHeightOffsetData::packData(BitStream* stream) +{ + Parent::packData(stream); + mathWrite(*stream, offset); +} + +void afxXM_BoxHeightOffsetData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + mathRead(*stream, &offset); +} + +#if defined(AFX_VERSION) +afxXM_Base* afxXM_BoxHeightOffsetData::create(afxEffectWrapper* fx, bool on_server) +#else +afxXM_Base* afxXM_BoxHeightOffsetData::create(afxEffectWrapper* fx) +#endif +{ + return new afxXM_BoxHeightOffset(this, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_BoxHeightOffset::afxXM_BoxHeightOffset(afxXM_BoxHeightOffsetData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + offset = db->offset; +} + +void afxXM_BoxHeightOffset::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + afxConstraint* pos_cons = fx_wrapper->getPosConstraint(); + SceneObject* scn_obj = (pos_cons) ? pos_cons->getSceneObject() : 0; + + if (scn_obj) + params.pos.z += scn_obj->getWorldBox().maxExtents.z - scn_obj->getWorldBox().minExtents.z; + + params.pos += offset; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_Freeze.cpp b/Engine/source/afx/xm/afxXM_Freeze.cpp new file mode 100644 index 000000000..4476c6090 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_Freeze.cpp @@ -0,0 +1,336 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_FreezeData : public afxXM_BaseData +{ + typedef afxXM_BaseData Parent; + +public: + U32 mask; + F32 delay; + +public: + /*C*/ afxXM_FreezeData() : mask(POSITION | ORIENTATION | POSITION2), delay(0.0f) { } + /*C*/ afxXM_FreezeData(const afxXM_FreezeData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_FreezeData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Freeze : public afxXM_Base +{ + typedef afxXM_Base Parent; + + U32 mask; + bool first; + Point3F frozen_pos; + MatrixF frozen_ori; + Point3F frozen_aim; + F32 delay; + +public: + /*C*/ afxXM_Freeze(afxXM_FreezeData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Freeze_all_but_scale : public afxXM_Base +{ + typedef afxXM_Base Parent; + + bool first; + Point3F frozen_pos; + MatrixF frozen_ori; + Point3F frozen_aim; + F32 delay; + +public: + /*C*/ afxXM_Freeze_all_but_scale(afxXM_FreezeData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Freeze_pos : public afxXM_Base +{ + typedef afxXM_Base Parent; + + bool first; + Point3F frozen_pos; + F32 delay; + +public: + /*C*/ afxXM_Freeze_pos(afxXM_FreezeData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Freeze_pos2 : public afxXM_Base +{ + typedef afxXM_Base Parent; + + bool first; + Point3F frozen_pos2; + F32 delay; + +public: + /*C*/ afxXM_Freeze_pos2(afxXM_FreezeData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Freeze_ori : public afxXM_Base +{ + typedef afxXM_Base Parent; + + bool first; + MatrixF frozen_ori; + F32 delay; + +public: + /*C*/ afxXM_Freeze_ori(afxXM_FreezeData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_FreezeData); + +ConsoleDocClass( afxXM_FreezeData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_FreezeData::afxXM_FreezeData(const afxXM_FreezeData& other, bool temp_clone) : afxXM_BaseData(other, temp_clone) +{ + mask = other.mask; + delay = other.delay; +} + +void afxXM_FreezeData::initPersistFields() +{ + addField("mask", TypeS32, Offset(mask, afxXM_FreezeData), + "..."); + addField("delay", TypeF32, Offset(delay, afxXM_FreezeData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_FreezeData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(mask); + stream->write(delay); +} + +void afxXM_FreezeData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&mask); + stream->read(&delay); +} + +afxXM_Base* afxXM_FreezeData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_FreezeData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_FreezeData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + if (datablock->mask == ALL_BUT_SCALE) + return new afxXM_Freeze_all_but_scale(datablock, fx); + if (datablock->mask == POSITION) + return new afxXM_Freeze_pos(datablock, fx); + if (datablock->mask == ORIENTATION) + return new afxXM_Freeze_ori(datablock, fx); + if (datablock->mask == POSITION2) + return new afxXM_Freeze_pos2(datablock, fx); + return new afxXM_Freeze(datablock, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Freeze::afxXM_Freeze(afxXM_FreezeData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + mask = db->mask; + first = true; + delay = db->delay; +} + +void afxXM_Freeze::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (elapsed < delay) return; + + if (first) + { + if (mask & POSITION) + frozen_pos = params.pos; + if (mask & ORIENTATION) + frozen_ori = params.ori; + if (mask & POSITION2) + frozen_aim = params.pos2; + first = false; + } + else + { + if (mask & POSITION) + params.pos = frozen_pos; + if (mask & ORIENTATION) + params.ori = frozen_ori; + if (mask & POSITION2) + params.pos2 = frozen_aim; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Freeze_all_but_scale::afxXM_Freeze_all_but_scale(afxXM_FreezeData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + first = true; + delay = db->delay; +} + +void afxXM_Freeze_all_but_scale::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (elapsed < delay) return; + + if (first) + { + frozen_pos = params.pos; + frozen_ori = params.ori; + frozen_aim = params.pos2; + first = false; + } + else + { + params.pos = frozen_pos; + params.ori = frozen_ori; + params.pos2 = frozen_aim; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Freeze_pos::afxXM_Freeze_pos(afxXM_FreezeData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + first = true; + delay = db->delay; +} + +void afxXM_Freeze_pos::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (elapsed < delay) return; + + if (first) + { + frozen_pos = params.pos; + first = false; + } + else + params.pos = frozen_pos; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Freeze_pos2::afxXM_Freeze_pos2(afxXM_FreezeData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + first = true; + delay = db->delay; +} + +void afxXM_Freeze_pos2::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (elapsed < delay) return; + + if (first) + { + frozen_pos2 = params.pos2; + first = false; + } + else + params.pos2 = frozen_pos2; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Freeze_ori::afxXM_Freeze_ori(afxXM_FreezeData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + first = true; + delay = db->delay; +} + +void afxXM_Freeze_ori::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (elapsed < delay) return; + + if (first) + { + frozen_ori = params.ori; + first = false; + } + else + params.ori = frozen_ori; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_GroundConform.cpp b/Engine/source/afx/xm/afxXM_GroundConform.cpp new file mode 100644 index 000000000..f31509b41 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_GroundConform.cpp @@ -0,0 +1,212 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_GroundConformData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + F32 height; + bool do_terrain; + bool do_interiors; + bool do_orientation; + +public: + /*C*/ afxXM_GroundConformData(); + /*C*/ afxXM_GroundConformData(const afxXM_GroundConformData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_GroundConformData); + DECLARE_CATEGORY("AFX"); +}; + +class afxXM_GroundConform : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + afxXM_GroundConformData* db; + SceneContainer* container; + +public: + /*C*/ afxXM_GroundConform(afxXM_GroundConformData*, afxEffectWrapper*, bool on_server); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_GroundConformData); + +ConsoleDocClass( afxXM_GroundConformData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_GroundConformData::afxXM_GroundConformData() +{ + height = 0.0f; + do_terrain = true; + do_interiors = true; + do_orientation = false; +} + +afxXM_GroundConformData::afxXM_GroundConformData(const afxXM_GroundConformData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + height = other.height; + do_terrain = other.do_terrain; + do_interiors = other.do_interiors; + do_orientation = other.do_orientation; +} + +void afxXM_GroundConformData::initPersistFields() +{ + addField("height", TypeF32, Offset(height, afxXM_GroundConformData), + "..."); + addField("conformToTerrain", TypeBool, Offset(do_terrain, afxXM_GroundConformData), + "..."); + addField("conformToInteriors", TypeBool, Offset(do_interiors, afxXM_GroundConformData), + "..."); + addField("conformOrientation", TypeBool, Offset(do_orientation, afxXM_GroundConformData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_GroundConformData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(height); + stream->write(do_terrain); + stream->write(do_interiors); + stream->write(do_orientation); +} + +void afxXM_GroundConformData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&height); + stream->read(&do_terrain); + stream->read(&do_interiors); + stream->read(&do_orientation); +} + +afxXM_Base* afxXM_GroundConformData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_GroundConformData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_GroundConformData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + return new afxXM_GroundConform(datablock, fx, on_server); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_GroundConform::afxXM_GroundConform(afxXM_GroundConformData* db, afxEffectWrapper* fxw, bool on_server) +: afxXM_WeightedBase(db, fxw) +{ + this->db = db; + this->container = (on_server) ? &gServerContainer : &gClientContainer; +} + +void afxXM_GroundConform::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + RayInfo rInfo; + bool hit = false; + + if (db->do_interiors) + { + U32 mask = InteriorLikeObjectType; + if (db->do_terrain) + { + mask |= TerrainObjectType | TerrainLikeObjectType; + } + + Point3F above_pos(params.pos); above_pos.z += 0.1f; + Point3F below_pos(params.pos); below_pos.z -= 10000; + hit = container->castRay(above_pos, below_pos, mask, &rInfo); + if (!hit) + { + above_pos.z = params.pos.z + 10000; + below_pos.z = params.pos.z - 0.1f; + hit = container->castRay(below_pos, above_pos, mask, &rInfo); + } + } + else if (db->do_terrain) + { + U32 mask = TerrainObjectType | TerrainLikeObjectType; + Point3F above_pos(params.pos); above_pos.z += 10000; + Point3F below_pos(params.pos); below_pos.z -= 10000; + hit = container->castRay(above_pos, below_pos, mask, &rInfo); + } + + if (hit) + { + F32 terrain_z = rInfo.point.z; + F32 wt_factor = calc_weight_factor(elapsed); + F32 old_z = params.pos.z; + F32 new_z = terrain_z + db->height; + params.pos.z = ((1-wt_factor)*old_z) + ((wt_factor)*new_z); + + if (db->do_orientation) + { + Point3F x,y,z; + z = rInfo.normal; + z.normalize(); + params.ori.getColumn(1,&y); + mCross(y,z,&x); + x.normalize(); + mCross(z,x,&y); + params.ori.setColumn(0,x); + params.ori.setColumn(1,y); + params.ori.setColumn(2,z); + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/xm/afxXM_HeightSampler.cpp b/Engine/source/afx/xm/afxXM_HeightSampler.cpp new file mode 100644 index 000000000..2a627bb12 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_HeightSampler.cpp @@ -0,0 +1,146 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_HeightSamplerData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + /*C*/ afxXM_HeightSamplerData(); + /*C*/ afxXM_HeightSamplerData(const afxXM_HeightSamplerData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_HeightSamplerData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxConstraint; + +class afxXM_HeightSampler : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + +public: + /*C*/ afxXM_HeightSampler(afxXM_HeightSamplerData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_HeightSamplerData); + +ConsoleDocClass( afxXM_HeightSamplerData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_HeightSamplerData::afxXM_HeightSamplerData() +{ +} + +afxXM_HeightSamplerData::afxXM_HeightSamplerData(const afxXM_HeightSamplerData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ +} + +void afxXM_HeightSamplerData::initPersistFields() +{ + Parent::initPersistFields(); +} + +void afxXM_HeightSamplerData::packData(BitStream* stream) +{ + Parent::packData(stream); +} + +void afxXM_HeightSamplerData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); +} + +afxXM_Base* afxXM_HeightSamplerData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_HeightSamplerData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_HeightSamplerData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + return new afxXM_HeightSampler(datablock, fx); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_HeightSampler::afxXM_HeightSampler(afxXM_HeightSamplerData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ +} + +void afxXM_HeightSampler::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + afxConstraint* pos_cons = fx_wrapper->getPosConstraint(); + if (!pos_cons) + return; + + Point3F base_pos; + pos_cons->getPosition(base_pos); + + F32 range = 0.5f; + F32 height = (base_pos.z > params.pos.z) ? (base_pos.z - params.pos.z) : 0.0f; + F32 factor = mClampF(1.0f - (height/range), 0.0f, 1.0f); + + //Con::printf("SET height=%g liveScaleFactor=%g", height, factor); + fx_wrapper->setField("liveScaleFactor", avar("%g", factor)); + fx_wrapper->setField("liveFadeFactor", avar("%g", factor)); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_MountedImageNode.cpp b/Engine/source/afx/xm/afxXM_MountedImageNode.cpp new file mode 100644 index 000000000..8f54f2d9d --- /dev/null +++ b/Engine/source/afx/xm/afxXM_MountedImageNode.cpp @@ -0,0 +1,231 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_MountedImageNodeData : public afxXM_BaseData +{ + typedef afxXM_BaseData Parent; + +public: + StringTableEntry node_name; // name of a mounted-image node + U32 image_slot; // which mounted-image? + // (0 <= image_slot < MaxMountedImages) +public: + /*C*/ afxXM_MountedImageNodeData(); + /*C*/ afxXM_MountedImageNodeData(const afxXM_MountedImageNodeData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_MountedImageNodeData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_MountedImageNode : public afxXM_Base +{ + typedef afxXM_Base Parent; + + StringTableEntry node_name; + U32 image_slot; + S32 node_ID; + ShapeBase* shape; + afxConstraint* cons; + + afxConstraint* find_constraint(); + +public: + /*C*/ afxXM_MountedImageNode(afxXM_MountedImageNodeData*, afxEffectWrapper*); + + virtual void start(F32 timestamp); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_MountedImageNodeData); + +ConsoleDocClass( afxXM_MountedImageNodeData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_MountedImageNodeData::afxXM_MountedImageNodeData() +{ + image_slot = 0; + node_name = ST_NULLSTRING; +} + +afxXM_MountedImageNodeData::afxXM_MountedImageNodeData(const afxXM_MountedImageNodeData& other, bool temp_clone) : afxXM_BaseData(other, temp_clone) +{ + image_slot = other.image_slot; + node_name = other.node_name; +} + +void afxXM_MountedImageNodeData::initPersistFields() +{ + addField("imageSlot", TypeS32, Offset(image_slot, afxXM_MountedImageNodeData), + "..."); + addField("nodeName", TypeString, Offset(node_name, afxXM_MountedImageNodeData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_MountedImageNodeData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(image_slot); + stream->writeString(node_name); +} + +void afxXM_MountedImageNodeData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&image_slot); + node_name = stream->readSTString(); +} + +bool afxXM_MountedImageNodeData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + if (image_slot >= ShapeBase::MaxMountedImages) + { + // datablock will not be added if imageSlot is out-of-bounds + Con::errorf(ConsoleLogEntry::General, + "afxXM_MountedImageNodeData(%s): imageSlot (%u) >= MaxMountedImages (%d)", + getName(), + image_slot, + ShapeBase::MaxMountedImages); + return false; + } + + return true; +} + +afxXM_Base* afxXM_MountedImageNodeData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_MountedImageNodeData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_MountedImageNodeData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + return new afxXM_MountedImageNode(datablock, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_MountedImageNode::afxXM_MountedImageNode(afxXM_MountedImageNodeData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + image_slot = db->image_slot; + node_name = db->node_name; + cons = 0; + node_ID = -1; + shape = 0; +} + +// find the first constraint with a shape by checking pos +// then orient constraints in that order. +afxConstraint* afxXM_MountedImageNode::find_constraint() +{ + afxConstraint* cons = fx_wrapper->getOrientConstraint(); + if (cons && dynamic_cast(cons->getSceneObject())) + return cons; + + cons = fx_wrapper->getPosConstraint(); + if (cons && dynamic_cast(cons->getSceneObject())) + return cons; + + return 0; +} + +void afxXM_MountedImageNode::start(F32 timestamp) +{ + // constraint won't change over the modifier's + // lifetime so we find it here in start(). + cons = find_constraint(); + if (!cons) + Con::errorf(ConsoleLogEntry::General, + "afxXM_MountedImageNode: failed to find a ShapeBase derived constraint source."); +} + +void afxXM_MountedImageNode::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (!cons) + return; + + // validate shape + // The shape must be validated in case it gets deleted + // of goes out scope. + SceneObject* scene_object = cons->getSceneObject(); + if (scene_object != (SceneObject*)shape) + { + shape = dynamic_cast(scene_object); + if (shape && node_name != ST_NULLSTRING) + { + node_ID = shape->getNodeIndex(image_slot, node_name); + if (node_ID < 0) + { + Con::errorf(ConsoleLogEntry::General, + "afxXM_MountedImageNode: failed to find nodeName, \"%s\".", + node_name); + } + } + else + node_ID = -1; + } + + if (shape) + { + shape->getImageTransform(image_slot, node_ID, ¶ms.ori); + params.pos = params.ori.getPosition(); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_Offset.cpp b/Engine/source/afx/xm/afxXM_Offset.cpp new file mode 100644 index 000000000..d4547c05b --- /dev/null +++ b/Engine/source/afx/xm/afxXM_Offset.cpp @@ -0,0 +1,489 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" +#include "afx/util/afxPath3D.h" +#include "afx/util/afxPath.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// LOCAL OFFSET + +class afxXM_LocalOffsetData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + Point3F local_offset; + bool offset_pos2; + +public: + /*C*/ afxXM_LocalOffsetData(); + /*C*/ afxXM_LocalOffsetData(const afxXM_LocalOffsetData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_LocalOffsetData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_LocalOffset_weighted : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + Point3F local_offset; + +public: + /*C*/ afxXM_LocalOffset_weighted(afxXM_LocalOffsetData*, afxEffectWrapper*); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +// this fixed variation is used when +// the weight factors are constant. + +class afxXM_LocalOffset_fixed : public afxXM_Base +{ + typedef afxXM_Base Parent; + + Point3F local_offset; + +public: + /*C*/ afxXM_LocalOffset_fixed(afxXM_LocalOffsetData*, afxEffectWrapper*); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_LocalOffset2_weighted : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + Point3F local_offset; + +public: + /*C*/ afxXM_LocalOffset2_weighted(afxXM_LocalOffsetData*, afxEffectWrapper*); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +// this fixed variation is used when +// the weight factors are constant. + +class afxXM_LocalOffset2_fixed : public afxXM_Base +{ + typedef afxXM_Base Parent; + + Point3F local_offset; + +public: + /*C*/ afxXM_LocalOffset2_fixed(afxXM_LocalOffsetData*, afxEffectWrapper*); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WORLD OFFSET + +class afxXM_WorldOffsetData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + Point3F world_offset; + bool offset_pos2; + +public: + /*C*/ afxXM_WorldOffsetData(); + /*C*/ afxXM_WorldOffsetData(const afxXM_WorldOffsetData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_WorldOffsetData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WorldOffset_weighted : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + Point3F world_offset; + +public: + /*C*/ afxXM_WorldOffset_weighted(afxXM_WorldOffsetData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +// this fixed variation is used when +// the weight factors are constant. + +class afxXM_WorldOffset_fixed : public afxXM_Base +{ + typedef afxXM_WeightedBase Parent; + Point3F world_offset; + +public: + /*C*/ afxXM_WorldOffset_fixed(afxXM_WorldOffsetData*, afxEffectWrapper*); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WorldOffset2_weighted : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + Point3F world_offset; + +public: + /*C*/ afxXM_WorldOffset2_weighted(afxXM_WorldOffsetData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +// this fixed variation is used when +// the weight factors are constant. + +class afxXM_WorldOffset2_fixed : public afxXM_Base +{ + typedef afxXM_WeightedBase Parent; + Point3F world_offset; + +public: + /*C*/ afxXM_WorldOffset2_fixed(afxXM_WorldOffsetData*, afxEffectWrapper*); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// LOCAL OFFSET + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_LocalOffsetData); + +ConsoleDocClass( afxXM_LocalOffsetData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_LocalOffsetData::afxXM_LocalOffsetData() +{ + local_offset.zero(); + offset_pos2 = false; +} + +afxXM_LocalOffsetData::afxXM_LocalOffsetData(const afxXM_LocalOffsetData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + local_offset = other.local_offset; + offset_pos2 = other.offset_pos2; +} + +void afxXM_LocalOffsetData::initPersistFields() +{ + addField("localOffset", TypePoint3F, Offset(local_offset, afxXM_LocalOffsetData), + "..."); + addField("offsetPos2", TypeBool, Offset(offset_pos2, afxXM_LocalOffsetData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_LocalOffsetData::packData(BitStream* stream) +{ + Parent::packData(stream); + mathWrite(*stream, local_offset); + stream->writeFlag(offset_pos2); +} + +void afxXM_LocalOffsetData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + mathRead(*stream, &local_offset); + offset_pos2 = stream->readFlag(); +} + +afxXM_Base* afxXM_LocalOffsetData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_LocalOffsetData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_LocalOffsetData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + if (datablock->offset_pos2) + { + if (datablock->hasFixedWeight()) + return new afxXM_LocalOffset2_fixed(datablock, fx); + else + return new afxXM_LocalOffset2_weighted(datablock, fx); + } + else + { + if (datablock->hasFixedWeight()) + return new afxXM_LocalOffset_fixed(datablock, fx); + else + return new afxXM_LocalOffset_weighted(datablock, fx); + } +} + +//~~~~~~~~~~~~~~~~~~~~// + +afxXM_LocalOffset_weighted::afxXM_LocalOffset_weighted(afxXM_LocalOffsetData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + local_offset = db->local_offset*db->getWeightFactor(); +} + +void afxXM_LocalOffset_weighted::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + Point3F offset(local_offset*wt_factor); + params.ori.mulV(offset); + params.pos += offset; +} + +//~~~~~~~~~~~~~~~~~~~~// + +afxXM_LocalOffset_fixed::afxXM_LocalOffset_fixed(afxXM_LocalOffsetData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + local_offset = db->local_offset*db->getWeightFactor(); +} + +void afxXM_LocalOffset_fixed::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + Point3F offset(local_offset); + params.ori.mulV(offset); + params.pos += offset; +} + +//~~~~~~~~~~~~~~~~~~~~// + +afxXM_LocalOffset2_weighted::afxXM_LocalOffset2_weighted(afxXM_LocalOffsetData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + local_offset = db->local_offset*db->getWeightFactor(); +} + +void afxXM_LocalOffset2_weighted::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + Point3F offset(local_offset*wt_factor); + + afxConstraint* pos2_cons = fx_wrapper->getAimConstraint(); + if (pos2_cons) + { + MatrixF ori2; + pos2_cons->getTransform(ori2); + ori2.mulV(offset); + } + + params.pos2 += offset; +} + +//~~~~~~~~~~~~~~~~~~~~// + +afxXM_LocalOffset2_fixed::afxXM_LocalOffset2_fixed(afxXM_LocalOffsetData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + local_offset = db->local_offset*db->getWeightFactor(); +} + +void afxXM_LocalOffset2_fixed::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + Point3F offset(local_offset); + + afxConstraint* pos2_cons = fx_wrapper->getAimConstraint(); + if (pos2_cons) + { + MatrixF ori2; + pos2_cons->getTransform(ori2); + ori2.mulV(offset); + } + + params.pos2 += offset; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WORLD OFFSET + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_WorldOffsetData); + +ConsoleDocClass( afxXM_WorldOffsetData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_WorldOffsetData::afxXM_WorldOffsetData() +{ + world_offset.zero(); + offset_pos2 = false; +} + +afxXM_WorldOffsetData::afxXM_WorldOffsetData(const afxXM_WorldOffsetData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + world_offset = other.world_offset; + offset_pos2 = other.offset_pos2; +} + +void afxXM_WorldOffsetData::initPersistFields() +{ + addField("worldOffset", TypePoint3F, Offset(world_offset, afxXM_WorldOffsetData), + "..."); + addField("offsetPos2", TypeBool, Offset(offset_pos2, afxXM_WorldOffsetData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_WorldOffsetData::packData(BitStream* stream) +{ + Parent::packData(stream); + mathWrite(*stream, world_offset); + stream->writeFlag(offset_pos2); +} + +void afxXM_WorldOffsetData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + mathRead(*stream, &world_offset); + offset_pos2 = stream->readFlag(); +} + +afxXM_Base* afxXM_WorldOffsetData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_WorldOffsetData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_WorldOffsetData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + if (datablock->offset_pos2) + { + if (datablock->hasFixedWeight()) + return new afxXM_WorldOffset2_fixed(datablock, fx); + else + return new afxXM_WorldOffset2_weighted(datablock, fx); + } + else + { + if (datablock->hasFixedWeight()) + return new afxXM_WorldOffset_fixed(datablock, fx); + else + return new afxXM_WorldOffset_weighted(datablock, fx); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_WorldOffset_weighted::afxXM_WorldOffset_weighted(afxXM_WorldOffsetData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + world_offset = db->world_offset*db->getWeightFactor(); +} + +void afxXM_WorldOffset_weighted::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + params.pos += world_offset*wt_factor; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_WorldOffset_fixed::afxXM_WorldOffset_fixed(afxXM_WorldOffsetData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + world_offset = db->world_offset*db->getWeightFactor(); +} + +void afxXM_WorldOffset_fixed::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + params.pos += world_offset; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_WorldOffset2_weighted::afxXM_WorldOffset2_weighted(afxXM_WorldOffsetData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + world_offset = db->world_offset*db->getWeightFactor(); +} + +void afxXM_WorldOffset2_weighted::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + params.pos2 += world_offset*wt_factor; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_WorldOffset2_fixed::afxXM_WorldOffset2_fixed(afxXM_WorldOffsetData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + world_offset = db->world_offset*db->getWeightFactor(); +} + +void afxXM_WorldOffset2_fixed::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + params.pos2 += world_offset; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_Oscillate.cpp b/Engine/source/afx/xm/afxXM_Oscillate.cpp new file mode 100644 index 000000000..049d84b12 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_Oscillate.cpp @@ -0,0 +1,379 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_OscillateData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + U32 mask; + Point3F min; + Point3F max; + F32 speed; + Point3F axis; + bool additive_scale; + bool local_offset; + +public: + /*C*/ afxXM_OscillateData(); + /*C*/ afxXM_OscillateData(const afxXM_OscillateData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_OscillateData); + DECLARE_CATEGORY("AFX"); +}; + +class afxXM_Oscillate_rot : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + afxXM_OscillateData* db; + +public: + /*C*/ afxXM_Oscillate_rot(afxXM_OscillateData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +class afxXM_Oscillate_scale : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + afxXM_OscillateData* db; + +public: + /*C*/ afxXM_Oscillate_scale(afxXM_OscillateData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +class afxXM_Oscillate_position : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + afxXM_OscillateData* db; + +public: + /*C*/ afxXM_Oscillate_position(afxXM_OscillateData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +class afxXM_Oscillate_position2 : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + afxXM_OscillateData* db; + +public: + /*C*/ afxXM_Oscillate_position2(afxXM_OscillateData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +class afxXM_Oscillate : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + afxXM_OscillateData* db; + +public: + /*C*/ afxXM_Oscillate(afxXM_OscillateData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_OscillateData); + +ConsoleDocClass( afxXM_OscillateData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_OscillateData::afxXM_OscillateData() +{ + mask = POSITION; + min.set(0,0,0); + max.set(1,1,1); + speed = 1.0f; + axis.set(0,0,1); + additive_scale = false; + local_offset = true; +} + +afxXM_OscillateData::afxXM_OscillateData(const afxXM_OscillateData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + mask = other.mask; + min = other.min; + max = other.max; + speed = other.speed; + axis = other.axis; + additive_scale = other.additive_scale; + local_offset = other.local_offset; +} + +void afxXM_OscillateData::initPersistFields() +{ + addField("mask", TypeS32, Offset(mask, afxXM_OscillateData), + "..."); + addField("min", TypePoint3F, Offset(min, afxXM_OscillateData), + "..."); + addField("max", TypePoint3F, Offset(max, afxXM_OscillateData), + "..."); + addField("speed", TypeF32, Offset(speed, afxXM_OscillateData), + "..."); + addField("axis", TypePoint3F, Offset(axis, afxXM_OscillateData), + "..."); + addField("additiveScale", TypeBool, Offset(additive_scale, afxXM_OscillateData), + "..."); + addField("localOffset", TypeBool, Offset(local_offset, afxXM_OscillateData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_OscillateData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(mask); + mathWrite(*stream, min); + mathWrite(*stream, max); + stream->write(speed); + mathWrite(*stream, axis); + stream->writeFlag(additive_scale); + stream->writeFlag(local_offset); +} + +void afxXM_OscillateData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&mask); + mathRead(*stream, &min); + mathRead(*stream, &max); + stream->read(&speed); + mathRead(*stream, &axis); + additive_scale = stream->readFlag(); + local_offset = stream->readFlag(); +} + +afxXM_Base* afxXM_OscillateData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_OscillateData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_OscillateData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + if (datablock->mask == ORIENTATION) + return new afxXM_Oscillate_rot(datablock, fx); + if (datablock->mask == SCALE) + return new afxXM_Oscillate_scale(datablock, fx); + if (datablock->mask == POSITION) + return new afxXM_Oscillate_position(datablock, fx); + if (datablock->mask == POSITION2) + return new afxXM_Oscillate_position2(datablock, fx); + return new afxXM_Oscillate(datablock, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +inline F32 lerp(F32 t, F32 a, F32 b) +{ + return a + t * (b - a); +} + +inline Point3F lerpV(F32 t, const Point3F& a, const Point3F& b) +{ + return Point3F( a.x + t * (b.x - a.x), + a.y + t * (b.y - a.y), + a.z + t * (b.z - a.z) ); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Oscillate_rot::afxXM_Oscillate_rot(afxXM_OscillateData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + this->db = db; +} + +void afxXM_Oscillate_rot::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + + F32 t = mSin(db->speed*elapsed); // [-1,1] + F32 theta = lerp((t+1)/2, db->min.x*wt_factor, db->max.x*wt_factor); + theta = mDegToRad(theta); + + AngAxisF rot_aa(db->axis, theta); + MatrixF rot_xfm; rot_aa.setMatrix(&rot_xfm); + + params.ori.mul(rot_xfm); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Oscillate_scale::afxXM_Oscillate_scale(afxXM_OscillateData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + this->db = db; +} + +void afxXM_Oscillate_scale::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + + F32 t = mSin(db->speed*elapsed); // [-1,1] + F32 s = lerp((t+1)/2, db->min.x*wt_factor, db->max.x*wt_factor); + Point3F xm_scale = db->axis*s; + + if (db->additive_scale) + params.scale += xm_scale; + else + params.scale *= xm_scale; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Oscillate_position::afxXM_Oscillate_position(afxXM_OscillateData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + this->db = db; +} + +void afxXM_Oscillate_position::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + + F32 t = mSin(db->speed*elapsed); // [-1,1] + Point3F offset = lerpV(t, db->min*wt_factor, db->max*wt_factor); + + if (db->local_offset) + { + params.ori.mulV(offset); + params.pos += offset; + } + else + params.pos += offset; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Oscillate_position2::afxXM_Oscillate_position2(afxXM_OscillateData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + this->db = db; +} + +void afxXM_Oscillate_position2::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + + F32 t = mSin(db->speed*elapsed); // [-1,1] + Point3F offset = lerpV(t, db->min*wt_factor, db->max*wt_factor); + + params.pos2 += offset; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Oscillate::afxXM_Oscillate(afxXM_OscillateData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + this->db = db; +} + +void afxXM_Oscillate::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + + F32 t = mSin(db->speed*elapsed); // [-1,1] + + if (db->mask & POSITION) + { + Point3F offset = lerpV(t, db->min*wt_factor, db->max*wt_factor); + if (db->local_offset) + { + params.ori.mulV(offset); + params.pos += offset; + } + else + params.pos += offset; + } + + if (db->mask & POSITION2) + { + Point3F offset = lerpV(t, db->min*wt_factor, db->max*wt_factor); + params.pos2 += offset; + } + + if (db->mask & SCALE) + { + F32 s = lerp((t+1)/2, db->min.x*wt_factor, db->max.x*wt_factor); + Point3F xm_scale = db->axis*s; + if (db->additive_scale) + params.scale += xm_scale; + else + params.scale *= xm_scale; + } + + if (db->mask & ORIENTATION) + { + F32 theta = lerp((t+1)/2, db->min.x*wt_factor, db->max.x*wt_factor); + theta = mDegToRad(theta); + AngAxisF rot_aa(db->axis, theta); + MatrixF rot_xfm; rot_aa.setMatrix(&rot_xfm); + params.ori.mul(rot_xfm); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + diff --git a/Engine/source/afx/xm/afxXM_OscillateZodiacColor.cpp b/Engine/source/afx/xm/afxXM_OscillateZodiacColor.cpp new file mode 100644 index 000000000..28fc98eb6 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_OscillateZodiacColor.cpp @@ -0,0 +1,161 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" +#include "afx/afxEffectWrapper.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_OscillateZodiacColorData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + LinearColorF color_a; + LinearColorF color_b; + F32 speed; + +public: + /*C*/ afxXM_OscillateZodiacColorData(); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_OscillateZodiacColorData); + DECLARE_CATEGORY("AFX"); +}; + +class afxXM_OscillateZodiacColor : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + LinearColorF color_a; + LinearColorF color_b; + F32 speed; + + LinearColorF* liveColor_ptr; + F32* liveColorFactor_ptr; + +public: + /*C*/ afxXM_OscillateZodiacColor(afxXM_OscillateZodiacColorData*, afxEffectWrapper*); + + virtual void update(F32 dt, F32 elapsed, Point3F& pos, MatrixF& ori, Point3F& pos2, + Point3F& scale); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_OscillateZodiacColorData); + +ConsoleDocClass( afxXM_OscillateZodiacColorData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_OscillateZodiacColorData::afxXM_OscillateZodiacColorData() +{ + color_a.set(0.0f, 0.0f, 0.0f, 0.0f); + color_b.set(1.0f, 1.0f, 1.0f, 1.0f); + speed = 1.0f; +} + +void afxXM_OscillateZodiacColorData::initPersistFields() +{ + addField("colorA", TypeColorF, Offset(color_a, afxXM_OscillateZodiacColorData), + "..."); + addField("colorB", TypeColorF, Offset(color_b, afxXM_OscillateZodiacColorData), + "..."); + addField("speed", TypeF32, Offset(speed, afxXM_OscillateZodiacColorData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_OscillateZodiacColorData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(color_a); + stream->write(color_b); + stream->write(speed); +} + +void afxXM_OscillateZodiacColorData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&color_a); + stream->read(&color_b); + stream->read(&speed); +} + +afxXM_Base* afxXM_OscillateZodiacColorData::create(afxEffectWrapper* fx, bool on_server) +{ + return new afxXM_OscillateZodiacColor(this, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_OscillateZodiacColor::afxXM_OscillateZodiacColor(afxXM_OscillateZodiacColorData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + color_a = db->color_a; + color_b = db->color_b; + speed = db->speed; + + const AbstractClassRep::Field* field; + field = fxw->getClassRep()->findField(StringTable->insert("liveColor")); + if (field && field->type == TypeColorF) + liveColor_ptr = (LinearColorF*)(((const char *)(fxw)) + field->offset); + else + liveColor_ptr = 0; + + field = fxw->getClassRep()->findField(StringTable->insert("liveColorFactor")); + if (field && field->type == TypeF32) + liveColorFactor_ptr = (F32*)(((const char *)(fxw)) + field->offset); + else + liveColorFactor_ptr = 0; +} + +void afxXM_OscillateZodiacColor::update(F32 dt, F32 elapsed, Point3F& pos, MatrixF& ori, Point3F& pos2, Point3F& scale) +{ + F32 wt_factor = calc_weight_factor(elapsed); + + if (liveColor_ptr) + { + F32 t = (1.0f + mSin((3.0f*M_PI_F)/2.0f + speed*elapsed*M_2PI_F))*0.5f; + liveColor_ptr->interpolate(color_a, color_b, t); + } + + if (liveColorFactor_ptr) + *liveColorFactor_ptr = wt_factor; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/xm/afxXM_PathConform.cpp b/Engine/source/afx/xm/afxXM_PathConform.cpp new file mode 100644 index 000000000..af9b7869f --- /dev/null +++ b/Engine/source/afx/xm/afxXM_PathConform.cpp @@ -0,0 +1,412 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" +#include "afx/util/afxPath3D.h" +#include "afx/util/afxPath.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// PATH CONFORM + +class afxPathData; + +class afxXM_PathConformData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + StringTableEntry paths_string; // + Vector pathDataBlocks; // datablocks for paths + Vector pathDataBlockIds; // datablock IDs which correspond to the pathDataBlocks + F32 path_mult; + F32 path_time_offset; + bool orient_to_path; + +public: + /*C*/ afxXM_PathConformData(); + /*C*/ afxXM_PathConformData(const afxXM_PathConformData&, bool = false); + + bool onAdd(); + bool preload(bool server, String &errorStr); + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_PathConformData); + DECLARE_CATEGORY("AFX"); +}; + +class afxPath3D; +class afxAnimCurve; + +class afxXM_PathConform : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + afxXM_PathConformData* db; + + Vector paths; + Vector path_mults; + Vector path_time_offsets; + Vector rolls; + + void init_paths(F32 lifetime); + +public: + /*C*/ afxXM_PathConform(afxXM_PathConformData*, afxEffectWrapper*); + /*D*/ ~afxXM_PathConform(); + + virtual void start(F32 timestamp); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_PathConformData); + +ConsoleDocClass( afxXM_PathConformData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_PathConformData::afxXM_PathConformData() +{ + paths_string = ST_NULLSTRING; + pathDataBlocks.clear(); + pathDataBlockIds.clear(); + path_mult = 1.0f; + path_time_offset = 0.0f; + orient_to_path = false; +} + +afxXM_PathConformData::afxXM_PathConformData(const afxXM_PathConformData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + paths_string = other.paths_string; + pathDataBlocks = other.pathDataBlocks; + pathDataBlockIds = other.pathDataBlockIds; + path_mult = other.path_mult; + path_time_offset = other.path_time_offset; + orient_to_path = other.orient_to_path; +} + +void afxXM_PathConformData::initPersistFields() +{ + addField("paths", TypeString, Offset(paths_string, afxXM_PathConformData), + "..."); + addField("pathMult", TypeF32, Offset(path_mult, afxXM_PathConformData), + "..."); + addField("pathTimeOffset", TypeF32, Offset(path_time_offset, afxXM_PathConformData), + "..."); + addField("orientToPath", TypeBool, Offset(orient_to_path, afxXM_PathConformData), + "..."); + + Parent::initPersistFields(); + + // disallow some field substitutions + disableFieldSubstitutions("paths"); +} + +void afxXM_PathConformData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(pathDataBlockIds.size()); + for (int i = 0; i < pathDataBlockIds.size(); i++) + stream->write(pathDataBlockIds[i]); + stream->write(path_mult); + stream->write(path_time_offset); + stream->write(orient_to_path); +} + +void afxXM_PathConformData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + U32 n_db; + stream->read(&n_db); + pathDataBlockIds.setSize(n_db); + for (U32 i = 0; i < n_db; i++) + stream->read(&pathDataBlockIds[i]); + stream->read(&path_mult); + stream->read(&path_time_offset); + stream->read(&orient_to_path); +} + +bool afxXM_PathConformData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + if (paths_string != ST_NULLSTRING) + { + //Con::printf("afxEffectWrapperData: Path string found: %s", paths_string); + } + + if (paths_string != ST_NULLSTRING && paths_string[0] == '\0') + { + Con::warnf(ConsoleLogEntry::General, "afxEffectWrapperData(%s) empty paths string, invalid datablock", getName()); + return false; + } + + if (paths_string != ST_NULLSTRING && dStrlen(paths_string) > 255) + { + Con::errorf(ConsoleLogEntry::General, "afxEffectWrapperData(%s) paths string too long [> 255 chars]", getName()); + return false; + } + + if (paths_string != ST_NULLSTRING) + { + Vector dataBlocks(__FILE__, __LINE__); + char* tokCopy = new char[dStrlen(paths_string) + 1]; + dStrcpy(tokCopy, paths_string); + + char* currTok = dStrtok(tokCopy, " \t"); + while (currTok != NULL) + { + dataBlocks.push_back(currTok); + currTok = dStrtok(NULL, " \t"); + } + if (dataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "afxEffectWrapperData(%s) invalid paths string. No datablocks found", getName()); + delete [] tokCopy; + return false; + } + pathDataBlocks.clear(); + pathDataBlockIds.clear(); + + for (U32 i = 0; i < dataBlocks.size(); i++) + { + afxPathData* pData = NULL; + if (Sim::findObject(dataBlocks[i], pData) == false) + { + Con::warnf(ConsoleLogEntry::General, "afxEffectWrapperData(%s) unable to find path datablock: %s", getName(), dataBlocks[i]); + } + else + { + pathDataBlocks.push_back(pData); + pathDataBlockIds.push_back(pData->getId()); + } + } + delete [] tokCopy; + if (pathDataBlocks.size() == 0) + { + Con::warnf(ConsoleLogEntry::General, "afxEffectWrapperData(%s) unable to find any path datablocks", getName()); + return false; + } + } + + + return true; +} + +bool afxXM_PathConformData::preload(bool server, String &errorStr) +{ + if (!Parent::preload(server, errorStr)) + return false; + + pathDataBlocks.clear(); + for (U32 i = 0; i < pathDataBlockIds.size(); i++) + { + afxPathData* pData = NULL; + if (Sim::findObject(pathDataBlockIds[i], pData) == false) + { + Con::warnf(ConsoleLogEntry::General, + "afxEffectWrapperData(%s) unable to find path datablock: %d", + getName(), pathDataBlockIds[i]); + } + else + pathDataBlocks.push_back(pData); + } + + return true; +} + +afxXM_Base* afxXM_PathConformData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_PathConformData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_PathConformData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + return new afxXM_PathConform(datablock, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_PathConform::afxXM_PathConform(afxXM_PathConformData* db, afxEffectWrapper* fxw) + : afxXM_WeightedBase(db, fxw) +{ + this->db = db; +} + +afxXM_PathConform::~afxXM_PathConform() +{ + for (S32 i = 0; i < paths.size(); i++) + if (paths[i]) + delete paths[i]; + + for (S32 j = 0; j < paths.size(); j++) + if (rolls[j]) + delete rolls[j]; +} + +void afxXM_PathConform::start(F32 timestamp) +{ + init_paths(fx_wrapper->getFullLifetime()); +} + +void afxXM_PathConform::init_paths(F32 lifetime) +{ + for( U32 i=0; i < db->pathDataBlocks.size(); i++ ) + { + afxPathData* pd = db->pathDataBlocks[i]; + if (!pd) + continue; + + if (pd->getSubstitutionCount() > 0) + { + afxPathData* orig_db = pd; + pd = new afxPathData(*orig_db, true); + orig_db->performSubstitutions(pd, fx_wrapper->getChoreographer(), fx_wrapper->getGroupIndex()); + } + + if (pd->num_points > 0) + { + if (pd->lifetime == 0) + { + if (lifetime <= 0) + { + // Is this possible or is the lifetime always inherited properly??? + } + else + { + pd->lifetime = lifetime; + } + } + + F32 pd_delay = pd->delay*time_factor; + F32 pd_lifetime = pd->lifetime*time_factor; + F32 pd_time_offset = pd->time_offset*time_factor; + + afxPath3D* path = new afxPath3D(); + if (pd->times) + path->buildPath( pd->num_points, pd->points, pd->times, pd_delay, time_factor ); + else + path->buildPath( pd->num_points, pd->points, pd_delay, pd_delay+pd_lifetime ); + + path->setLoopType( pd->loop_type ); + + paths.push_back(path); + // path->print(); + + path_mults.push_back( db->path_mult * pd->mult ); + + path_time_offsets.push_back( db->path_time_offset + pd_time_offset ); + + if (pd->roll_string != ST_NULLSTRING && pd->rolls != NULL) + { + afxAnimCurve* roll_curve = new afxAnimCurve(); + for (U32 j=0; jnum_points; j++ ) + { + roll_curve->addKey( path->getPointTime(j), pd->rolls[j] ); + } + roll_curve->sort(); + // roll_curve->print(); + + rolls.push_back(roll_curve); + } + else + { + rolls.push_back( NULL ); + } + } + else + { + Con::warnf("afxXM_PathConform::init_paths() -- paths datablock (%d) has no points.", i); + } + + if (pd->isTempClone()) + delete pd; + } +} + +void afxXM_PathConform::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + + // compute path offset + VectorF path_offset(0,0,0); + for( U32 i=0; i < paths.size(); i++ ) + path_offset += path_mults[i]*paths[i]->evaluateAtTime(elapsed + path_time_offsets[i])*wt_factor; + + params.ori.mulV(path_offset); + params.pos += path_offset; + + if (db->orient_to_path && paths.size()) + { + VectorF path_v = paths[0]->evaluateTangentAtTime(elapsed+path_time_offsets[0]); + path_v.normalize(); + + MatrixF mat(true); + + if( rolls[0] ) + { + F32 roll_angle = rolls[0]->evaluate(elapsed+path_time_offsets[0]); + roll_angle = mDegToRad( roll_angle ); + //Con::printf( "roll: %f", roll_angle ); + + Point3F roll_axis = path_v; + AngAxisF rollRot(roll_axis, roll_angle); + MatrixF roll_mat(true); + rollRot.setMatrix(&roll_mat); + + mat.mul(roll_mat); + } + + mat.mul(MathUtils::createOrientFromDir(path_v)); + params.ori.mul(mat); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/xm/afxXM_PivotNodeOffset.cpp b/Engine/source/afx/xm/afxXM_PivotNodeOffset.cpp new file mode 100644 index 000000000..9d1c73270 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_PivotNodeOffset.cpp @@ -0,0 +1,180 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "ts/tsShapeInstance.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_PivotNodeOffsetData : public afxXM_BaseData +{ + typedef afxXM_BaseData Parent; + +public: + StringTableEntry node_name; + bool node_is_static; + +public: + /*C*/ afxXM_PivotNodeOffsetData(); + /*C*/ afxXM_PivotNodeOffsetData(const afxXM_PivotNodeOffsetData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_PivotNodeOffsetData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_PivotNodeOffset : public afxXM_Base +{ + typedef afxXM_Base Parent; + + StringTableEntry node_name; + bool node_is_static; + S32 node_ID; + Point3F pivot_offset; + bool offset_calculated; + +public: + /*C*/ afxXM_PivotNodeOffset(afxXM_PivotNodeOffsetData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_PivotNodeOffsetData); + +ConsoleDocClass( afxXM_PivotNodeOffsetData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_PivotNodeOffsetData::afxXM_PivotNodeOffsetData() +{ + node_name = ST_NULLSTRING; + node_is_static = true; +} + +afxXM_PivotNodeOffsetData::afxXM_PivotNodeOffsetData(const afxXM_PivotNodeOffsetData& other, bool temp_clone) : afxXM_BaseData(other, temp_clone) +{ + node_name = other.node_name; + node_is_static = other.node_is_static; +} + +void afxXM_PivotNodeOffsetData::initPersistFields() +{ + addField("nodeName", TypeString, Offset(node_name, afxXM_PivotNodeOffsetData), + "..."); + addField("nodeIsStatic", TypeBool, Offset(node_is_static, afxXM_PivotNodeOffsetData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_PivotNodeOffsetData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->writeString(node_name); + stream->writeFlag(node_is_static); +} + +void afxXM_PivotNodeOffsetData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + node_name = stream->readSTString(); + node_is_static = stream->readFlag(); +} + +afxXM_Base* afxXM_PivotNodeOffsetData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_PivotNodeOffsetData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_PivotNodeOffsetData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + return new afxXM_PivotNodeOffset(datablock, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_PivotNodeOffset::afxXM_PivotNodeOffset(afxXM_PivotNodeOffsetData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + node_name = db->node_name; + node_is_static = db->node_is_static; + node_ID = -1; + pivot_offset.set(0,0,0); + offset_calculated = false; +} + +void afxXM_PivotNodeOffset::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (node_ID < 0) + { + TSShape* ts_shape = fx_wrapper->getTSShape(); + node_ID = (ts_shape) ? ts_shape->findNode(node_name) : -1; + } + + if (node_ID >= 0) + { + if (!node_is_static || !offset_calculated) + { + TSShapeInstance* ts_shape_inst = fx_wrapper->getTSShapeInstance(); + if (ts_shape_inst) + { + const MatrixF& pivot_xfm = ts_shape_inst->mNodeTransforms[node_ID]; + pivot_offset = -pivot_xfm.getPosition(); + offset_calculated = true; + } + } + } + + // re-orient pivot offset then add to position + Point3F pivot_offset_temp; + params.ori.mulV(pivot_offset, &pivot_offset_temp); + params.pos += pivot_offset_temp; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_RandomRot.cpp b/Engine/source/afx/xm/afxXM_RandomRot.cpp new file mode 100644 index 000000000..f7534a214 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_RandomRot.cpp @@ -0,0 +1,181 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_RandomRotData : public afxXM_BaseData +{ + typedef afxXM_BaseData Parent; + +public: + Point3F axis; + F32 theta_min; + F32 theta_max; + F32 phi_min; + F32 phi_max; + +public: + /*C*/ afxXM_RandomRotData(); + /*C*/ afxXM_RandomRotData(const afxXM_RandomRotData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_RandomRotData); + DECLARE_CATEGORY("AFX"); +}; + +class afxXM_RandomRot : public afxXM_Base +{ + typedef afxXM_Base Parent; + + MatrixF rand_ori; + +public: + /*C*/ afxXM_RandomRot(afxXM_RandomRotData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_RandomRotData); + +ConsoleDocClass( afxXM_RandomRotData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_RandomRotData::afxXM_RandomRotData() +{ + axis.set(0,0,1); + theta_min = 0.0f; + theta_max = 360.0f; + phi_min = 0.0f; + phi_max = 360.0f; +} + +afxXM_RandomRotData::afxXM_RandomRotData(const afxXM_RandomRotData& other, bool temp_clone) : afxXM_BaseData(other, temp_clone) +{ + axis = other.axis; + theta_min = other.theta_min; + theta_max = other.theta_max; + phi_min = other.phi_min; + phi_max = other.phi_max; +} + +void afxXM_RandomRotData::initPersistFields() +{ + addField("axis", TypePoint3F, Offset(axis, afxXM_RandomRotData), + "..."); + addField("thetaMin", TypeF32, Offset(theta_min, afxXM_RandomRotData), + "..."); + addField("thetaMax", TypeF32, Offset(theta_max, afxXM_RandomRotData), + "..."); + addField("phiMin", TypeF32, Offset(phi_min, afxXM_RandomRotData), + "..."); + addField("phiMax", TypeF32, Offset(phi_max, afxXM_RandomRotData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_RandomRotData::packData(BitStream* stream) +{ + Parent::packData(stream); + mathWrite(*stream, axis); + stream->write(theta_min); + stream->write(theta_max); + stream->write(phi_min); + stream->write(phi_max); +} + +void afxXM_RandomRotData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + mathRead(*stream, &axis); + stream->read(&theta_min); + stream->read(&theta_max); + stream->read(&phi_min); + stream->read(&phi_max); +} + +bool afxXM_RandomRotData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + axis.normalizeSafe(); + + return true; +} + +afxXM_Base* afxXM_RandomRotData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_RandomRotData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_RandomRotData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + return new afxXM_RandomRot(datablock, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_RandomRot::afxXM_RandomRot(afxXM_RandomRotData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + Point3F rand_dir = MathUtils::randomDir(db->axis, db->theta_min, db->theta_max, db->phi_min, db->phi_max); + rand_ori = MathUtils::createOrientFromDir(rand_dir); +} + +void afxXM_RandomRot::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + params.ori = rand_ori; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_Scale.cpp b/Engine/source/afx/xm/afxXM_Scale.cpp new file mode 100644 index 000000000..109051087 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_Scale.cpp @@ -0,0 +1,155 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" +#include "afx/util/afxPath3D.h" +#include "afx/util/afxPath.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_ScaleData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + Point3F scale; + +public: + /*C*/ afxXM_ScaleData(); + /*C*/ afxXM_ScaleData(const afxXM_ScaleData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_ScaleData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Scale_weighted : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + Point3F xm_scale; + +public: + /*C*/ afxXM_Scale_weighted(afxXM_ScaleData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_ScaleData); + +ConsoleDocClass( afxXM_ScaleData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_ScaleData::afxXM_ScaleData() +{ + scale.set(0,0,0); +} + +afxXM_ScaleData::afxXM_ScaleData(const afxXM_ScaleData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + scale = other.scale; +} + +void afxXM_ScaleData::initPersistFields() +{ + addField("scale", TypePoint3F, Offset(scale, afxXM_ScaleData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_ScaleData::packData(BitStream* stream) +{ + Parent::packData(stream); + mathWrite(*stream, scale); +} + +void afxXM_ScaleData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + mathRead(*stream, &scale); +} + +afxXM_Base* afxXM_ScaleData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_ScaleData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_ScaleData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + if (datablock->hasFixedWeight()) + return new afxXM_Scale_weighted(datablock, fx); + else + return new afxXM_Scale_weighted(datablock, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Scale_weighted::afxXM_Scale_weighted(afxXM_ScaleData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + xm_scale = db->scale; +} + +void afxXM_Scale_weighted::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + params.scale += xm_scale*wt_factor; + if (params.scale.x < 0.00001f) + params.scale.x = 0.00001f; + if (params.scale.y < 0.00001f) + params.scale.y = 0.00001f; + if (params.scale.z < 0.00001f) + params.scale.z = 0.00001f; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_Shockwave.cpp b/Engine/source/afx/xm/afxXM_Shockwave.cpp new file mode 100644 index 000000000..47c6e5eb2 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_Shockwave.cpp @@ -0,0 +1,170 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" +#include "afx/util/afxPath3D.h" +#include "afx/util/afxPath.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_ShockwaveData : public afxXM_BaseData +{ + typedef afxXM_BaseData Parent; + +public: + F32 rate; + bool aim_z_only; + +public: + /*C*/ afxXM_ShockwaveData(); + /*C*/ afxXM_ShockwaveData(const afxXM_ShockwaveData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_ShockwaveData); + DECLARE_CATEGORY("AFX"); +}; + +class afxConstraint; + +class afxXM_Shockwave : public afxXM_Base +{ + typedef afxXM_Base Parent; + + afxXM_ShockwaveData* db; + bool first; + Point3F fixed_pos; + +public: + /*C*/ afxXM_Shockwave(afxXM_ShockwaveData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_ShockwaveData); + +ConsoleDocClass( afxXM_ShockwaveData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_ShockwaveData::afxXM_ShockwaveData() +{ + rate = 1.0f; + aim_z_only = false; +} + +afxXM_ShockwaveData::afxXM_ShockwaveData(const afxXM_ShockwaveData& other, bool temp_clone) : afxXM_BaseData(other, temp_clone) +{ + rate = other.rate; + aim_z_only = other.aim_z_only; +} + +void afxXM_ShockwaveData::initPersistFields() +{ + addField("rate", TypeF32, Offset(rate, afxXM_ShockwaveData), + "..."); + addField("aimZOnly", TypeBool, Offset(aim_z_only, afxXM_ShockwaveData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_ShockwaveData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(rate); + stream->write(aim_z_only); +} + +void afxXM_ShockwaveData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&rate); + stream->read(&aim_z_only); +} + +afxXM_Base* afxXM_ShockwaveData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_ShockwaveData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_ShockwaveData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + return new afxXM_Shockwave(datablock, fx); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Shockwave::afxXM_Shockwave(afxXM_ShockwaveData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + this->db = db; + first = true; + fixed_pos.zero(); +} + +void afxXM_Shockwave::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (first) + { + fixed_pos = params.pos; + first = false; + } + + Point3F aim_at_pos = params.pos2; + if (db->aim_z_only) + aim_at_pos.z = fixed_pos.z; + + VectorF line_of_sight = aim_at_pos - fixed_pos; + line_of_sight.normalize(); + + params.pos = fixed_pos + line_of_sight*(elapsed*db->rate); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_Spin.cpp b/Engine/source/afx/xm/afxXM_Spin.cpp new file mode 100644 index 000000000..753835c1a --- /dev/null +++ b/Engine/source/afx/xm/afxXM_Spin.cpp @@ -0,0 +1,241 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" +#include "afx/util/afxPath3D.h" +#include "afx/util/afxPath.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_SpinData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + Point3F spin_axis; + F32 spin_angle; + F32 spin_angle_var; + F32 spin_rate; + F32 spin_rate_var; + +public: + /*C*/ afxXM_SpinData() : spin_axis(0,0,1), spin_angle(0), spin_angle_var(0), spin_rate(0), spin_rate_var(0) { } + /*C*/ afxXM_SpinData(const afxXM_SpinData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + bool onAdd(); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_SpinData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Spin_weighted : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + Point3F spin_axis; + F32 spin_rate; + F32 theta; + +public: + /*C*/ afxXM_Spin_weighted(afxXM_SpinData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_Spin_fixed : public afxXM_Base +{ + typedef afxXM_Base Parent; + + Point3F spin_axis; + F32 spin_rate; + F32 theta; + +public: + /*C*/ afxXM_Spin_fixed(afxXM_SpinData*, afxEffectWrapper*); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_SpinData); + +ConsoleDocClass( afxXM_SpinData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_SpinData::afxXM_SpinData(const afxXM_SpinData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + spin_axis = other.spin_axis; + spin_angle = other.spin_angle; + spin_angle_var = other.spin_angle_var; + spin_rate = other.spin_rate; + spin_rate_var = other.spin_rate_var; +} + +void afxXM_SpinData::initPersistFields() +{ + addField("spinAxis", TypePoint3F, Offset(spin_axis, afxXM_SpinData), + "..."); + addField("spinAngle", TypeF32, Offset(spin_angle, afxXM_SpinData), + "..."); + addField("spinAngleVariance", TypeF32, Offset(spin_angle_var, afxXM_SpinData), + "..."); + addField("spinRate", TypeF32, Offset(spin_rate, afxXM_SpinData), + "..."); + addField("spinRateVariance", TypeF32, Offset(spin_rate_var, afxXM_SpinData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_SpinData::packData(BitStream* stream) +{ + Parent::packData(stream); + mathWrite(*stream, spin_axis); + stream->write(spin_angle); + stream->write(spin_angle_var); + stream->write(spin_rate); + stream->write(spin_rate_var); +} + +void afxXM_SpinData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + mathRead(*stream, &spin_axis); + stream->read(&spin_angle); + stream->read(&spin_angle_var); + stream->read(&spin_rate); + stream->read(&spin_rate_var); +} + +bool afxXM_SpinData::onAdd() +{ + if (Parent::onAdd() == false) + return false; + + spin_axis.normalizeSafe(); + + return true; +} + +afxXM_Base* afxXM_SpinData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_SpinData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_SpinData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + if (datablock->hasFixedWeight()) + return new afxXM_Spin_fixed(datablock, fx); + else + return new afxXM_Spin_weighted(datablock, fx); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Spin_weighted::afxXM_Spin_weighted(afxXM_SpinData* db, afxEffectWrapper* fxw) + : afxXM_WeightedBase(db, fxw) +{ + spin_axis = db->spin_axis; + + spin_rate = db->spin_rate; + if (db->spin_rate_var != 0.0f) + spin_rate += gRandGen.randF()*2.0f*db->spin_rate_var - db->spin_rate_var; + spin_rate *= db->getWeightFactor()/time_factor; + + F32 spin_angle = db->spin_angle; + if (db->spin_angle_var != 0.0f) + spin_angle += gRandGen.randF()*2.0f*db->spin_angle_var - db->spin_angle_var; + theta = mDegToRad(spin_angle); +} + +void afxXM_Spin_weighted::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + F32 wt_factor = calc_weight_factor(elapsed); + F32 rate = spin_rate*wt_factor; + theta += mDegToRad(dt*rate); + + AngAxisF spin_aa(spin_axis, theta); + MatrixF spin_xfm; spin_aa.setMatrix(&spin_xfm); + + params.ori.mul(spin_xfm); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Spin_fixed::afxXM_Spin_fixed(afxXM_SpinData* db, afxEffectWrapper* fxw) + : afxXM_Base(db, fxw) +{ + spin_axis = db->spin_axis; + + spin_rate = db->spin_rate; + if (db->spin_rate_var != 0.0f) + spin_rate += gRandGen.randF()*2.0f*db->spin_rate_var - db->spin_rate_var; + spin_rate *= db->getWeightFactor()/time_factor; + + F32 spin_angle = db->spin_angle; + if (db->spin_angle_var != 0.0f) + spin_angle += gRandGen.randF()*2.0f*db->spin_angle_var - db->spin_angle_var; + theta = mDegToRad(spin_angle); +} + +void afxXM_Spin_fixed::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + theta += mDegToRad(dt*spin_rate); + + AngAxisF spin_aa(spin_axis, theta); + MatrixF spin_xfm; spin_aa.setMatrix(&spin_xfm); + + params.ori.mul(spin_xfm); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_VelocityOffset.cpp b/Engine/source/afx/xm/afxXM_VelocityOffset.cpp new file mode 100644 index 000000000..38fce9e7d --- /dev/null +++ b/Engine/source/afx/xm/afxXM_VelocityOffset.cpp @@ -0,0 +1,373 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" +#include "math/mathUtils.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXfmMod.h" +#include "afx/util/afxPath3D.h" +#include "afx/util/afxPath.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// VELOCITY OFFSET + +class afxXM_VelocityOffsetData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + F32 offset_factor; + bool offset_pos2; + bool normalize; + +public: + /*C*/ afxXM_VelocityOffsetData(); + /*C*/ afxXM_VelocityOffsetData(const afxXM_VelocityOffsetData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_VelocityOffsetData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_VelocityOffset_weighted : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + afxConstraint* cons; + F32 offset_factor; + bool normalize; + +public: + /*C*/ afxXM_VelocityOffset_weighted(afxXM_VelocityOffsetData*, afxEffectWrapper*); + + virtual void start(F32 timestamp); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +// this fixed variation is used when +// the weight factors are constant. + +class afxXM_VelocityOffset_fixed : public afxXM_Base +{ + typedef afxXM_Base Parent; + + afxConstraint* cons; + F32 offset_factor; + bool normalize; + +public: + /*C*/ afxXM_VelocityOffset_fixed(afxXM_VelocityOffsetData*, afxEffectWrapper*); + + virtual void start(F32 timestamp); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_VelocityOffset2_weighted : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + + afxConstraint* cons; + F32 offset_factor; + bool normalize; + +public: + /*C*/ afxXM_VelocityOffset2_weighted(afxXM_VelocityOffsetData*, afxEffectWrapper*); + + virtual void start(F32 timestamp); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +// this fixed variation is used when +// the weight factors are constant. + +class afxXM_VelocityOffset2_fixed : public afxXM_Base +{ + typedef afxXM_Base Parent; + + afxConstraint* cons; + F32 offset_factor; + bool normalize; + +public: + /*C*/ afxXM_VelocityOffset2_fixed(afxXM_VelocityOffsetData*, afxEffectWrapper*); + + virtual void start(F32 timestamp); + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// VELOCITY OFFSET + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_VelocityOffsetData); + +ConsoleDocClass( afxXM_VelocityOffsetData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_VelocityOffsetData::afxXM_VelocityOffsetData() +{ + offset_factor = 1.0f; + offset_pos2 = false; + normalize = false; +} + +afxXM_VelocityOffsetData::afxXM_VelocityOffsetData(const afxXM_VelocityOffsetData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + offset_factor = other.offset_factor; + offset_pos2 = other.offset_pos2; + normalize = other.normalize; +} + +void afxXM_VelocityOffsetData::initPersistFields() +{ + addField("offsetFactor", TypeF32, Offset(offset_factor, afxXM_VelocityOffsetData), + "..."); + addField("offsetPos2", TypeBool, Offset(offset_pos2, afxXM_VelocityOffsetData), + "..."); + addField("normalize", TypeBool, Offset(normalize, afxXM_VelocityOffsetData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_VelocityOffsetData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(offset_factor); + stream->write(offset_pos2); +} + +void afxXM_VelocityOffsetData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&offset_factor); + stream->read(&offset_pos2); +} + +afxXM_Base* afxXM_VelocityOffsetData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_VelocityOffsetData* datablock = this; + + if (getSubstitutionCount() > 0) + { + datablock = new afxXM_VelocityOffsetData(*this, true); + this->performSubstitutions(datablock, fx->getChoreographer(), fx->getGroupIndex()); + } + + if (datablock->offset_pos2) + { + if (datablock->hasFixedWeight()) + return new afxXM_VelocityOffset2_fixed(datablock, fx); + else + return new afxXM_VelocityOffset2_weighted(datablock, fx); + } + else + { + if (datablock->hasFixedWeight()) + return new afxXM_VelocityOffset_fixed(datablock, fx); + else + return new afxXM_VelocityOffset_weighted(datablock, fx); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_VelocityOffset_weighted::afxXM_VelocityOffset_weighted(afxXM_VelocityOffsetData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + cons = 0; + offset_factor = db->offset_factor*db->getWeightFactor(); + normalize = db->normalize; +} + +void afxXM_VelocityOffset_weighted::start(F32 timestamp) +{ + Parent::start(timestamp); + + cons = fx_wrapper->getPosConstraint(); + if (!cons) + Con::errorf(ConsoleLogEntry::General, + "afxXM_VelocityOffset: failed to find a SceneObject derived constraint source."); +} + +void afxXM_VelocityOffset_weighted::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (!cons) + return; + + SceneObject* scene_obj = cons->getSceneObject(); + if (scene_obj) + { + Point3F vel_vec = scene_obj->getVelocity(); + if (!vel_vec.isZero()) + { + if (normalize) + vel_vec.normalize(); + F32 wt_factor = calc_weight_factor(elapsed); + params.pos += vel_vec*offset_factor*wt_factor; + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_VelocityOffset_fixed::afxXM_VelocityOffset_fixed(afxXM_VelocityOffsetData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + cons = 0; + offset_factor = db->offset_factor*db->getWeightFactor(); + normalize = db->normalize; +} + +void afxXM_VelocityOffset_fixed::start(F32 timestamp) +{ + Parent::start(timestamp); + + cons = fx_wrapper->getPosConstraint(); + if (!cons) + Con::errorf(ConsoleLogEntry::General, + "afxXM_VelocityOffset: failed to find a SceneObject derived constraint source."); +} + +void afxXM_VelocityOffset_fixed::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (!cons) + return; + + SceneObject* scene_obj = cons->getSceneObject(); + if (scene_obj) + { + Point3F vel_vec = scene_obj->getVelocity(); + if (!vel_vec.isZero()) + { + if (normalize) + vel_vec.normalize(offset_factor); + params.pos += vel_vec*offset_factor; + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_VelocityOffset2_weighted::afxXM_VelocityOffset2_weighted(afxXM_VelocityOffsetData* db, afxEffectWrapper* fxw) +: afxXM_WeightedBase(db, fxw) +{ + cons = 0; + offset_factor = db->offset_factor*db->getWeightFactor(); + normalize = db->normalize; +} + +void afxXM_VelocityOffset2_weighted::start(F32 timestamp) +{ + Parent::start(timestamp); + + cons = fx_wrapper->getAimConstraint(); + if (!cons) + Con::errorf(ConsoleLogEntry::General, + "afxXM_VelocityOffset: failed to find a SceneObject derived constraint source."); +} + +void afxXM_VelocityOffset2_weighted::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (!cons) + return; + + SceneObject* scene_obj = cons->getSceneObject(); + if (scene_obj) + { + Point3F vel_vec = scene_obj->getVelocity(); + if (!vel_vec.isZero()) + { + if (normalize) + vel_vec.normalize(); + F32 wt_factor = calc_weight_factor(elapsed); + params.pos2 += vel_vec*offset_factor*wt_factor; + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_VelocityOffset2_fixed::afxXM_VelocityOffset2_fixed(afxXM_VelocityOffsetData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + cons = 0; + offset_factor = db->offset_factor*db->getWeightFactor(); + normalize = db->normalize; +} + +void afxXM_VelocityOffset2_fixed::start(F32 timestamp) +{ + Parent::start(timestamp); + + cons = fx_wrapper->getAimConstraint(); + if (!cons) + Con::errorf(ConsoleLogEntry::General, + "afxXM_VelocityOffset: failed to find a SceneObject derived constraint source."); +} + +void afxXM_VelocityOffset2_fixed::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (!cons) + return; + + SceneObject* scene_obj = cons->getSceneObject(); + if (scene_obj) + { + Point3F vel_vec = scene_obj->getVelocity(); + if (!vel_vec.isZero()) + { + if (normalize) + vel_vec.normalize(offset_factor); + params.pos2 += vel_vec*offset_factor; + } + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_WaveBase.cpp b/Engine/source/afx/xm/afxXM_WaveBase.cpp new file mode 100644 index 000000000..514daf641 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_WaveBase.cpp @@ -0,0 +1,610 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "math/mathIO.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXM_WaveBase.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_WaveBaseData); + +ConsoleDocClass( afxXM_WaveBaseData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_WaveBaseData::afxXM_WaveBaseData() +{ + waveform_type = WAVEFORM_SINE; + parameter = PARAM_NONE; + op = OP_ADD; + speed = 1.0f; + speed_vari = 0.0; + accel = 0.0f; + phase_shift = 0.0f; + duty_cycle = 1.0f; + duty_shift = 0.0f; + off_duty_t = 0.0f; + + waves_per_pulse.set(1,1); + waves_per_rest.set(0,0); + rest_dur = 0.0f; + rest_dur_vari = 0.0f; + + axis.zero(); + local_axis = true; +} + +afxXM_WaveBaseData::afxXM_WaveBaseData(const afxXM_WaveBaseData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + waveform_type = other.waveform_type; + parameter = other.parameter; + op = other.op; + speed = other.speed; + speed_vari = other.speed; + accel = other.accel; + phase_shift = other.phase_shift; + duty_cycle = other.duty_cycle; + duty_shift = other.duty_shift; + off_duty_t = other.off_duty_t; + + waves_per_pulse = other.waves_per_pulse; + waves_per_rest = other.waves_per_rest; + rest_dur = other.rest_dur; + rest_dur_vari = other.rest_dur_vari; + + axis = other.axis; + local_axis = true; +} + +ImplementEnumType( afxXM_WaveFormType, "Possible waveform types.\n" "@ingroup afxXM_WaveBase\n\n" ) + { afxXM_WaveBaseData::WAVEFORM_NONE, "none", "..." }, + { afxXM_WaveBaseData::WAVEFORM_SINE, "sine", "..." }, + { afxXM_WaveBaseData::WAVEFORM_SQUARE, "square", "..." }, + { afxXM_WaveBaseData::WAVEFORM_TRIANGLE, "triangle", "..." }, + { afxXM_WaveBaseData::WAVEFORM_SAWTOOTH, "sawtooth", "..." }, + { afxXM_WaveBaseData::WAVEFORM_NOISE, "noise", "..." }, + { afxXM_WaveBaseData::WAVEFORM_ONE, "one", "..." }, +EndImplementEnumType; + +ImplementEnumType( afxXM_WaveParamType, "Possible wave parameter types.\n" "@ingroup afxXM_WaveBase\n\n" ) + { afxXM_WaveBaseData::PARAM_NONE, "none", "..." }, + { afxXM_WaveBaseData::PARAM_POS, "pos", "..." }, + { afxXM_WaveBaseData::PARAM_POS_X, "pos.x", "..." }, + { afxXM_WaveBaseData::PARAM_POS_Y, "pos.y", "..." }, + { afxXM_WaveBaseData::PARAM_POS_Z, "pos.z", "..." }, + { afxXM_WaveBaseData::PARAM_ORI, "ori", "..." }, + { afxXM_WaveBaseData::PARAM_POS2, "pos2", "..." }, + { afxXM_WaveBaseData::PARAM_POS2_X, "pos2.x", "..." }, + { afxXM_WaveBaseData::PARAM_POS2_Y, "pos2.y", "..." }, + { afxXM_WaveBaseData::PARAM_POS2_Z, "pos2.z", "..." }, + { afxXM_WaveBaseData::PARAM_SCALE, "scale", "..." }, + { afxXM_WaveBaseData::PARAM_SCALE_X, "scale.x", "..." }, + { afxXM_WaveBaseData::PARAM_SCALE_Y, "scale.y", "..." }, + { afxXM_WaveBaseData::PARAM_SCALE_Z, "scale.z", "..." }, + { afxXM_WaveBaseData::PARAM_COLOR, "color", "..." }, + { afxXM_WaveBaseData::PARAM_COLOR_R, "color.red", "..." }, + { afxXM_WaveBaseData::PARAM_COLOR_G, "color.green", "..." }, + { afxXM_WaveBaseData::PARAM_COLOR_B, "color.blue", "..." }, + { afxXM_WaveBaseData::PARAM_COLOR_A, "color.alpha", "..." }, + { afxXM_WaveBaseData::PARAM_VIS, "vis", "..." }, + { afxXM_WaveBaseData::PARAM_POS, "position", "..." }, + { afxXM_WaveBaseData::PARAM_POS_X, "position.x", "..." }, + { afxXM_WaveBaseData::PARAM_POS_Y, "position.y", "..." }, + { afxXM_WaveBaseData::PARAM_POS_Z, "position.z", "..." }, + { afxXM_WaveBaseData::PARAM_ORI, "orientation", "..." }, + { afxXM_WaveBaseData::PARAM_POS2, "position2", "..." }, + { afxXM_WaveBaseData::PARAM_POS2_X, "position2.x", "..." }, + { afxXM_WaveBaseData::PARAM_POS2_Y, "position2.y", "..." }, + { afxXM_WaveBaseData::PARAM_POS2_Z, "position2.z", "..." }, + { afxXM_WaveBaseData::PARAM_COLOR_R, "color.r", "..." }, + { afxXM_WaveBaseData::PARAM_COLOR_G, "color.g", "..." }, + { afxXM_WaveBaseData::PARAM_COLOR_B, "color.b", "..." }, + { afxXM_WaveBaseData::PARAM_COLOR_A, "color.a", "..." }, + { afxXM_WaveBaseData::PARAM_VIS, "visibility", "..." }, +EndImplementEnumType; + +ImplementEnumType( afxXM_WaveOpType, "Possible wave operation types.\n" "@ingroup afxXM_WaveBase\n\n" ) + { afxXM_WaveBaseData::OP_ADD, "add", "..." }, + { afxXM_WaveBaseData::OP_MULTIPLY, "multiply", "..." }, + { afxXM_WaveBaseData::OP_REPLACE, "replace", "..." }, + { afxXM_WaveBaseData::OP_MULTIPLY, "mult", "..." }, +EndImplementEnumType; + +void afxXM_WaveBaseData::initPersistFields() +{ + addField("waveform", TYPEID< afxXM_WaveBaseData::WaveFormType >(), Offset(waveform_type, afxXM_WaveBaseData), + "..."); + addField("parameter", TYPEID< afxXM_WaveBaseData::WaveParamType >(), Offset(parameter, afxXM_WaveBaseData), + "..."); + addField("op", TYPEID< afxXM_WaveBaseData::WaveOpType >(), Offset(op, afxXM_WaveBaseData), + "..."); + + addField("speed", TypeF32, Offset(speed, afxXM_WaveBaseData), + "waves per second"); + addField("speedVariance", TypeF32, Offset(speed_vari, afxXM_WaveBaseData), + "..."); + addField("acceleration", TypeF32, Offset(accel, afxXM_WaveBaseData), + "..."); + addField("phaseShift", TypeF32, Offset(phase_shift, afxXM_WaveBaseData), + "..."); + addField("dutyCycle", TypeF32, Offset(duty_cycle, afxXM_WaveBaseData), + "..."); + addField("dutyShift", TypeF32, Offset(duty_shift, afxXM_WaveBaseData), + "..."); + addField("offDutyT", TypeF32, Offset(off_duty_t, afxXM_WaveBaseData), + "..."); + + addField("wavesPerPulse", TypeByteRange2, Offset(waves_per_pulse, afxXM_WaveBaseData), + "..."); + addField("wavesPerRest", TypeByteRange2, Offset(waves_per_rest, afxXM_WaveBaseData), + "..."); + addField("restDuration", TypeF32, Offset(rest_dur, afxXM_WaveBaseData), + "..."); + addField("restDurationVariance", TypeF32, Offset(rest_dur_vari, afxXM_WaveBaseData), + "..."); + + addField("axis", TypePoint3F, Offset(axis, afxXM_WaveBaseData), + "..."); + addField("axisIsLocal", TypeBool, Offset(local_axis, afxXM_WaveBaseData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_WaveBaseData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeInt(waveform_type, WAVEFORM_BITS); + stream->writeInt(parameter, PARAM_BITS); + stream->writeInt(op, OP_BITS); + stream->write(speed); + stream->write(speed_vari); + stream->write(accel); + stream->write(phase_shift); + stream->write(duty_cycle); + stream->write(duty_shift); + stream->write(off_duty_t); + + stream->write(waves_per_pulse.low); + stream->write(waves_per_pulse.high); + stream->write(waves_per_rest.low); + stream->write(waves_per_rest.high); + + stream->write(rest_dur); + stream->write(rest_dur_vari); + + mathWrite(*stream, axis); + stream->writeFlag(local_axis); +} + +void afxXM_WaveBaseData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + waveform_type = stream->readInt(WAVEFORM_BITS); + parameter = stream->readInt(PARAM_BITS); + op = stream->readInt(OP_BITS); + stream->read(&speed); + stream->read(&speed_vari); + stream->read(&accel); + stream->read(&phase_shift); + stream->read(&duty_cycle); + stream->read(&duty_shift); + stream->read(&off_duty_t); + + stream->read(&waves_per_pulse.low); + stream->read(&waves_per_pulse.high); + stream->read(&waves_per_rest.low); + stream->read(&waves_per_rest.high); + + stream->read(&rest_dur); + stream->read(&rest_dur_vari); + + mathRead(*stream, &axis); + local_axis = stream->readFlag(); +} + +void afxXM_WaveBaseData::initParamInfo(U32 parameter, U32& parambit, S32& component) +{ + switch (parameter) + { + case PARAM_POS: + parambit = POSITION; + component = -1; + break; + case PARAM_POS_X: + parambit = POSITION; + component = 0; + break; + case PARAM_POS_Y: + parambit = POSITION; + component = 1; + break; + case PARAM_POS_Z: + parambit = POSITION; + component = 2; + break; + + case PARAM_ORI: + parambit = ORIENTATION; + component = -1; + break; + + case PARAM_POS2: + parambit = POSITION2; + component = -1; + break; + case PARAM_POS2_X: + parambit = POSITION2; + component = 0; + break; + case PARAM_POS2_Y: + parambit = POSITION2; + component = 1; + break; + case PARAM_POS2_Z: + parambit = POSITION2; + component = 2; + break; + + case PARAM_SCALE: + parambit = SCALE; + component = -1; + break; + case PARAM_SCALE_X: + parambit = SCALE; + component = 0; + break; + case PARAM_SCALE_Y: + parambit = SCALE; + component = 1; + break; + case PARAM_SCALE_Z: + parambit = SCALE; + component = 2; + break; + + case PARAM_COLOR: + parambit = COLOR; + component = -1; + break; + case PARAM_COLOR_R: + parambit = COLOR; + component = 0; + break; + case PARAM_COLOR_G: + parambit = COLOR; + component = 1; + break; + case PARAM_COLOR_B: + parambit = COLOR; + component = 2; + break; + case PARAM_COLOR_A: + parambit = COLOR; + component = 3; + break; + + case PARAM_VIS: + parambit = VISIBILITY; + component = -1; + break; + + default: + parambit = 0; + component = -1; + break; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_WaveRiderBaseData); + +ConsoleDocClass( afxXM_WaveRiderBaseData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_WaveRiderBaseData::afxXM_WaveRiderBaseData() +{ + waveform_type = afxXM_WaveBaseData::WAVEFORM_NONE; + parameter = afxXM_WaveBaseData::PARAM_NONE; + op = afxXM_WaveBaseData::OP_ADD; + off_duty_t = 0.0f; + + axis.zero(); + local_axis = true; +} + +afxXM_WaveRiderBaseData::afxXM_WaveRiderBaseData(const afxXM_WaveRiderBaseData& other, bool temp_clone) : afxXM_WeightedBaseData(other, temp_clone) +{ + waveform_type = other.waveform_type; + parameter = other.parameter; + op = other.op; + off_duty_t = other.off_duty_t; + + axis = other.axis; + local_axis = true; +} + +void afxXM_WaveRiderBaseData::initPersistFields() +{ + addField("waveform", TYPEID< afxXM_WaveBaseData::WaveFormType >(), Offset(waveform_type, afxXM_WaveRiderBaseData), + "..."); + addField("parameter", TYPEID< afxXM_WaveBaseData::WaveParamType >(), Offset(parameter, afxXM_WaveRiderBaseData), + "..."); + addField("op", TYPEID< afxXM_WaveBaseData::WaveOpType >(), Offset(op, afxXM_WaveRiderBaseData), + "..."); + + addField("offDutyT", TypeF32, Offset(off_duty_t, afxXM_WaveRiderBaseData), + "..."); + + addField("axis", TypePoint3F, Offset(axis, afxXM_WaveRiderBaseData), + "..."); + addField("axisIsLocal", TypeBool, Offset(local_axis, afxXM_WaveRiderBaseData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_WaveRiderBaseData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->writeInt(waveform_type, afxXM_WaveBaseData::WAVEFORM_BITS); + stream->writeInt(parameter, afxXM_WaveBaseData::PARAM_BITS); + stream->writeInt(op, afxXM_WaveBaseData::OP_BITS); + stream->write(off_duty_t); + + mathWrite(*stream, axis); + stream->writeFlag(local_axis); +} + +void afxXM_WaveRiderBaseData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + waveform_type = stream->readInt(afxXM_WaveBaseData::WAVEFORM_BITS); + parameter = stream->readInt(afxXM_WaveBaseData::PARAM_BITS); + op = stream->readInt(afxXM_WaveBaseData::OP_BITS); + stream->read(&off_duty_t); + + mathRead(*stream, &axis); + local_axis = stream->readFlag(); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// +// WAVEFORMS + +F32 afxXM_WaveformSine::evaluate(F32 t) +{ + t = (0.75f + t)*Float_2Pi; + return 0.5f*(1.0f + mSin(t)); +} + +F32 afxXM_WaveformSquare::evaluate(F32 t) +{ + return (t < 0.25f || t >= 0.75) ? 0.0f : 1.0f; +} + +F32 afxXM_WaveformTriangle::evaluate(F32 t) +{ + return (t < 0.5f) ? 2.0f*t : 2.0f*(1.0f - t); +} + +//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Waveform* afxXM_WaveBaseData::getWaveform(U32 waveform_type) +{ + static afxXM_WaveformSine sine; + static afxXM_WaveformSquare square; + static afxXM_WaveformTriangle triangle; + static afxXM_WaveformSawtooth sawtooth; + static afxXM_WaveformNoise noise; + static afxXM_WaveformOne one; + + switch (waveform_type) + { + case WAVEFORM_SINE: + return &sine; + case WAVEFORM_SQUARE: + return □ + case WAVEFORM_TRIANGLE: + return ▵ + case WAVEFORM_SAWTOOTH: + return &sawtooth; + case WAVEFORM_NOISE: + return &noise; + case WAVEFORM_ONE: + return &one; + default: + // error condition + return &sine; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +bool afxXM_WaveBase::last_was_pulsed = false; +bool afxXM_WaveBase::last_was_off_duty = true; +F32 afxXM_WaveBase::last_t = 0.0f; +F32 afxXM_WaveBase::last_wave_t = 0.0f; + +afxXM_WaveBase::afxXM_WaveBase(afxXM_WaveBaseData* db, afxEffectWrapper* fxw, afxXM_WaveInterp* interp) + : afxXM_WeightedBase(db, fxw) +{ + this->db = db; + interpolator = interp; + waveform = afxXM_WaveBaseData::getWaveform(db->waveform_type); + + speed_is_randomized = !mIsZero(db->speed_vari); + speed = calc_initial_speed(); + + fixed_weight = db->hasFixedWeight(); + + is_resting = false; + cur_pulse_time = db->delay; + next_pulse_time = cur_pulse_time + ((F32)calc_new_wavesPerPulse())/speed; + interpolator->pulse(); + + last_was_pulsed = false; + last_was_off_duty = true; + last_t = 0.0f; + last_wave_t = 0.0f; +} + +afxXM_WaveBase::~afxXM_WaveBase() +{ + delete interpolator; +} + +void afxXM_WaveBase::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + elapsed -= db->delay; + + if (elapsed > next_pulse_time) + { + is_resting = !is_resting; + cur_pulse_time = next_pulse_time; + + if (is_resting) + { + F32 rest_dt = ((F32)(calc_new_wavesPerRest())/speed) + calc_new_restDur(); + if (rest_dt < 0.01) + is_resting = false; + else + next_pulse_time = cur_pulse_time + rest_dt; + } + + if (!is_resting) + { + speed = calc_new_speed(); + next_pulse_time = cur_pulse_time + ((F32)calc_new_wavesPerPulse())/speed; + interpolator->pulse(); + last_was_pulsed = true; + } + } + + if (is_resting) + { + last_was_off_duty = true; + interpolator->interpolate(db->off_duty_t, params); + return; + } + + F32 n_waves = db->phase_shift + (elapsed - cur_pulse_time)*speed; + F32 wave_t = (n_waves - mFloor(n_waves))/db->duty_cycle; + + // we are beyond the duty portion of the wave, use off_duty_t + if (wave_t > 1.0f) + { + last_was_off_duty = true; + interpolator->interpolate(db->off_duty_t, params); + return; + } + + if (db->duty_shift > 0.0f) + { + wave_t += db->duty_shift; + if (wave_t > 1.0) + wave_t -= 1.0f; + } + + last_was_off_duty = false; + last_wave_t = wave_t; + + last_t = waveform->evaluate(wave_t); + + if (fixed_weight) + { + interpolator->interpolate(last_t, params); + } + else + { + F32 wt_factor = calc_weight_factor(elapsed); + F32 final_t = afxXM_WaveInterp::lerp(wt_factor, db->off_duty_t, last_t); + interpolator->interpolate(final_t, params); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxXM_WaveRiderBase::afxXM_WaveRiderBase(afxXM_WaveRiderBaseData* db, afxEffectWrapper* fxw, afxXM_WaveInterp* interp) + : afxXM_WeightedBase(db, fxw) +{ + this->db = db; + interpolator = interp; + waveform = afxXM_WaveBaseData::getWaveform(db->waveform_type); + fixed_weight = db->hasFixedWeight(); + interpolator->pulse(); +} + +afxXM_WaveRiderBase::~afxXM_WaveRiderBase() +{ + delete interpolator; +} + +void afxXM_WaveRiderBase::updateParams(F32 dt, F32 elapsed, afxXM_Params& params) +{ + if (afxXM_WaveBase::last_was_pulsed) + interpolator->pulse(); + + if (afxXM_WaveBase::last_was_off_duty) + { + interpolator->interpolate(db->off_duty_t, params); + return; + } + + F32 t; + if (db->waveform_type != afxXM_WaveBaseData::WAVEFORM_NONE) + t = waveform->evaluate(afxXM_WaveBase::last_wave_t); + else + t = afxXM_WaveBase::last_t; + + if (fixed_weight) + interpolator->interpolate(t, params); + else + { + F32 wt_factor = calc_weight_factor(elapsed); + F32 final_t = afxXM_WaveInterp::lerp(wt_factor, db->off_duty_t, t); + interpolator->interpolate(final_t, params); + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// \ No newline at end of file diff --git a/Engine/source/afx/xm/afxXM_WaveBase.h b/Engine/source/afx/xm/afxXM_WaveBase.h new file mode 100644 index 000000000..f7dd26f31 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_WaveBase.h @@ -0,0 +1,314 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_XFM_WAVE_BASE_H_ +#define _AFX_XFM_WAVE_BASE_H_ + +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVEFORM + +class afxXM_Waveform +{ +public: + virtual F32 evaluate(F32 t) = 0; +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveformSine : public afxXM_Waveform +{ +public: + virtual F32 evaluate(F32 t); +}; + +class afxXM_WaveformSquare : public afxXM_Waveform +{ +public: + virtual F32 evaluate(F32 t); +}; + +class afxXM_WaveformTriangle : public afxXM_Waveform +{ +public: + virtual F32 evaluate(F32 t); +}; + +class afxXM_WaveformSawtooth : public afxXM_Waveform +{ +public: + virtual F32 evaluate(F32 t) { return t; } +}; + +class afxXM_WaveformNoise : public afxXM_Waveform +{ +public: + virtual F32 evaluate(F32 t) { return gRandGen.randF(); }; +}; + +class afxXM_WaveformOne : public afxXM_Waveform +{ +public: + virtual F32 evaluate(F32 t) { return 1.0f; }; +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE INTERPOLATOR + +class afxXM_WaveInterp +{ +public: + virtual void interpolate(F32 t, afxXM_Params& params)=0; + virtual void pulse()=0; + + static F32 lerp(F32 t, F32 a, F32 b) { return a + t * (b - a); } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE BASE DATABLOCK + +class afxXM_WaveBaseData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + friend class afxXM_WaveRiderBaseData; + +public: + enum WaveFormType + { + WAVEFORM_NONE = 0, + WAVEFORM_SINE, + WAVEFORM_SQUARE, + WAVEFORM_TRIANGLE, + WAVEFORM_SAWTOOTH, + WAVEFORM_NOISE, + WAVEFORM_ONE, + WAVEFORM_BITS = 3 + }; + + enum WaveOpType + { + OP_ADD = 0, + OP_MULTIPLY, + OP_REPLACE, + OP_BITS = 2 + }; + + enum WaveParamType + { + PARAM_NONE = 0, + PARAM_POS, + PARAM_POS_X, + PARAM_POS_Y, + PARAM_POS_Z, + PARAM_ORI, + PARAM_POS2, + PARAM_POS2_X, + PARAM_POS2_Y, + PARAM_POS2_Z, + PARAM_SCALE, + PARAM_SCALE_X, + PARAM_SCALE_Y, + PARAM_SCALE_Z, + PARAM_COLOR, + PARAM_COLOR_R, + PARAM_COLOR_G, + PARAM_COLOR_B, + PARAM_COLOR_A, + PARAM_VIS, + PARAM_BITS = 5, + }; + + U32 waveform_type; + U32 parameter; + U32 op; + F32 speed; + F32 speed_vari; + F32 accel; + F32 phase_shift; + F32 duty_cycle; + F32 duty_shift; + F32 off_duty_t; + + ByteRange waves_per_pulse; + ByteRange waves_per_rest; + F32 rest_dur; + F32 rest_dur_vari; + + Point3F axis; + bool local_axis; + +public: + /*C*/ afxXM_WaveBaseData(); + /*C*/ afxXM_WaveBaseData(const afxXM_WaveBaseData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + static void initPersistFields(); + + static void initParamInfo(U32 parameter, U32& parambit, S32& component); + static afxXM_Waveform* getWaveform(U32 waveform_type); + + DECLARE_CONOBJECT(afxXM_WaveBaseData); + DECLARE_CATEGORY("AFX"); +}; + +typedef afxXM_WaveBaseData::WaveFormType afxXM_WaveFormType; +DefineEnumType( afxXM_WaveFormType ); + +typedef afxXM_WaveBaseData::WaveOpType afxXM_WaveOpType; +DefineEnumType( afxXM_WaveOpType ); + +typedef afxXM_WaveBaseData::WaveParamType afxXM_WaveParamType; +DefineEnumType( afxXM_WaveParamType ); + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE RIDER BASE DATABLOCK + +class afxXM_WaveRiderBaseData : public afxXM_WeightedBaseData +{ + typedef afxXM_WeightedBaseData Parent; + +public: + U32 waveform_type; + F32 off_duty_t; + U32 parameter; + U32 op; + + Point3F axis; + bool local_axis; + +public: + /*C*/ afxXM_WaveRiderBaseData(); + /*C*/ afxXM_WaveRiderBaseData(const afxXM_WaveRiderBaseData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxXM_WaveRiderBaseData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE BASE + +class afxXM_WaveBase : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + friend class afxXM_WaveRiderBase; + +protected: + static bool last_was_pulsed; + static bool last_was_off_duty; + static F32 last_t; + static F32 last_wave_t; + + afxXM_WaveInterp* interpolator; + +protected: + afxXM_Waveform* waveform; + + afxXM_WaveBaseData* db; + F32 speed; + bool fixed_weight; + + bool speed_is_randomized; + bool is_resting; + F32 cur_pulse_time; + F32 next_pulse_time; + + F32 calc_initial_speed(); + F32 calc_new_speed(); + F32 calc_new_restDur(); + S32 calc_new_wavesPerPulse(); + S32 calc_new_wavesPerRest(); + +public: + /*C*/ afxXM_WaveBase(afxXM_WaveBaseData*, afxEffectWrapper*, afxXM_WaveInterp*); + /*D*/ ~afxXM_WaveBase(); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +inline F32 afxXM_WaveBase::calc_initial_speed() +{ + return (!speed_is_randomized) ? + db->speed : + (db->speed + gRandGen.randF()*2.0f*db->speed_vari - db->speed_vari); +} + +inline F32 afxXM_WaveBase::calc_new_speed() +{ + return mClampF((!speed_is_randomized) ? + (speed + speed*db->accel) : + (db->speed + gRandGen.randF()*2.0f*db->speed_vari - db->speed_vari), 0.001f, 200.0f); +} + +inline F32 afxXM_WaveBase::calc_new_restDur() +{ + return db->rest_dur + gRandGen.randF()*2.0f*db->rest_dur_vari - db->rest_dur_vari; +} + +inline S32 afxXM_WaveBase::calc_new_wavesPerPulse() +{ + return (db->waves_per_pulse.getSpan() == 0) ? + db->waves_per_pulse.low : + gRandGen.randI(db->waves_per_pulse.low, db->waves_per_pulse.high); +} + +inline S32 afxXM_WaveBase::calc_new_wavesPerRest() +{ + return (db->waves_per_rest.getSpan() == 0) ? + db->waves_per_rest.low : + gRandGen.randI(db->waves_per_rest.low, db->waves_per_rest.high); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE RIDER BASE + +class afxXM_WaveRiderBase : public afxXM_WeightedBase +{ + typedef afxXM_WeightedBase Parent; + +protected: + afxXM_WaveInterp* interpolator; + afxXM_WaveRiderBaseData* db; + afxXM_Waveform* waveform; + bool fixed_weight; + +public: + /*C*/ afxXM_WaveRiderBase(afxXM_WaveRiderBaseData*, afxEffectWrapper*, afxXM_WaveInterp*); + /*D*/ ~afxXM_WaveRiderBase(); + + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& params); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_XFM_WAVE_BASE_H_ + diff --git a/Engine/source/afx/xm/afxXM_WaveColor.cpp b/Engine/source/afx/xm/afxXM_WaveColor.cpp new file mode 100644 index 000000000..851ccfc4d --- /dev/null +++ b/Engine/source/afx/xm/afxXM_WaveColor.cpp @@ -0,0 +1,388 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXM_WaveBase.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE COLOR INTERPOLATORS + +class afxXM_WaveInterp_Color : public afxXM_WaveInterp +{ +protected: + LinearColorF a_set, b_set; + LinearColorF a_var, b_var; + LinearColorF a, b; + bool sync_var; + +public: + afxXM_WaveInterp_Color(); + + void set(LinearColorF& a, LinearColorF& b, LinearColorF& a_var, LinearColorF& b_var, bool sync_var); + + virtual void interpolate(F32 t, afxXM_Params& params)=0; + virtual void pulse(); +}; + +afxXM_WaveInterp_Color::afxXM_WaveInterp_Color() +{ + a_set.set(0.0f, 0.0f, 0.0f, 0.0f); + b_set.set(1.0f, 1.0f, 1.0f, 1.0f); + a_var.set(0.0f, 0.0f, 0.0f, 0.0f); + b_var.set(0.0f, 0.0f, 0.0f, 0.0f); + sync_var = false; + a.set(0.0f, 0.0f, 0.0f, 0.0f); + b.set(1.0f, 1.0f, 1.0f, 1.0f); +} + +void afxXM_WaveInterp_Color::set(LinearColorF& a, LinearColorF& b, LinearColorF& a_var, LinearColorF& b_var, bool sync_var) +{ + a_set = a; + b_set = b; + this->a_var = a_var; + this->b_var = b_var; + this->sync_var = sync_var; + this->a = a; + this->b = b; +} + +inline void afxXM_WaveInterp_Color::pulse() +{ + LinearColorF temp_color; + F32 rand_t = gRandGen.randF()*2.0f; + temp_color.interpolate(-a_var, a_var, rand_t); + a = a_set + temp_color; + if (!sync_var) + rand_t = gRandGen.randF()*2.0f; + temp_color.interpolate(-b_var, b_var, rand_t); + b = b_set + temp_color; +} + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Color_Add : public afxXM_WaveInterp_Color +{ +public: + virtual void interpolate(F32 t, afxXM_Params& params) + { + LinearColorF temp_color; + temp_color.interpolate(a, b, t); + params.color += temp_color; + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Color_Mul : public afxXM_WaveInterp_Color +{ +public: + virtual void interpolate(F32 t, afxXM_Params& params) + { + LinearColorF temp_color; + temp_color.interpolate(a, b, t); + params.color *= temp_color; + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Color_Rep : public afxXM_WaveInterp_Color +{ +public: + virtual void interpolate(F32 t, afxXM_Params& params) + { + params.color.interpolate(a, b, t); + } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE SCALAR BASE DATABLOCK + +class afxXM_WaveColorData_Common : public virtual afxXM_Defs +{ +protected: + static afxXM_WaveInterp_Color* createInterp(U32 op, afxXM_BaseData*); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxXM_WaveInterp_Color* +afxXM_WaveColorData_Common::createInterp(U32 op, afxXM_BaseData* db) +{ + afxXM_WaveInterp_Color* interpolator = 0; + + switch (op) + { + case afxXM_WaveBaseData::OP_ADD: + interpolator = new afxXM_WaveInterp_Color_Add(); + return 0; + case afxXM_WaveBaseData::OP_MULTIPLY: + interpolator = new afxXM_WaveInterp_Color_Mul(); + break; + case afxXM_WaveBaseData::OP_REPLACE: + interpolator = new afxXM_WaveInterp_Color_Rep(); + break; + } + + if (!interpolator) + Con::errorf("%s::%s -- failed to allocate wave color interpolator.", db->getClassName(), db->getName()); + + return interpolator; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE COLOR DATABLOCK + +class afxXM_WaveColorData : public afxXM_WaveBaseData, afxXM_WaveColorData_Common +{ + typedef afxXM_WaveBaseData Parent; + +public: + LinearColorF a, b; + LinearColorF a_var, b_var; + bool sync_var; + +public: + /*C*/ afxXM_WaveColorData(); + /*C*/ afxXM_WaveColorData(const afxXM_WaveColorData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_WaveColorData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_WaveColorData); + +ConsoleDocClass( afxXM_WaveColorData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_WaveColorData::afxXM_WaveColorData() +{ + a.set(0.0f, 0.0f, 0.0f, 0.0f); + b.set(1.0f, 1.0f, 1.0f, 1.0f); + a_var.set(0.0f, 0.0f, 0.0f, 0.0f); + b_var.set(0.0f, 0.0f, 0.0f, 0.0f); + sync_var = false; +} + +afxXM_WaveColorData::afxXM_WaveColorData(const afxXM_WaveColorData& other, bool temp_clone) : afxXM_WaveBaseData(other, temp_clone) +{ + a = other.a; + b = other.b; + a_var = other.a_var; + b_var = other.b_var; + sync_var = other.sync_var; +} + +void afxXM_WaveColorData::initPersistFields() +{ + addField("a", TypeColorF, Offset(a, afxXM_WaveColorData), + "..."); + addField("b", TypeColorF, Offset(b, afxXM_WaveColorData), + "..."); + addField("aVariance", TypeColorF, Offset(a_var, afxXM_WaveColorData), + "..."); + addField("bVariance", TypeColorF, Offset(b_var, afxXM_WaveColorData), + "..."); + addField("syncVariances", TypeBool, Offset(sync_var, afxXM_WaveColorData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_WaveColorData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(a); + stream->write(b); + stream->write(a_var); + stream->write(b_var); + stream->writeFlag(sync_var); +} + +void afxXM_WaveColorData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&a); + stream->read(&b); + stream->read(&a_var); + stream->read(&b_var); + sync_var = stream->readFlag(); +} + +afxXM_Base* afxXM_WaveColorData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_WaveColorData* dblock = this; + + if (getSubstitutionCount() > 0) + { + dblock = new afxXM_WaveColorData(*this, true); + this->performSubstitutions(dblock, fx->getChoreographer(), fx->getGroupIndex()); + } + + afxXM_WaveInterp_Color* interp; + interp = createInterp(dblock->op, dblock); + if (!interp) + return 0; + + interp->set(dblock->a, dblock->b, dblock->a_var, dblock->b_var, dblock->sync_var); + + return new afxXM_WaveBase(dblock, fx, interp); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE RIDER COLOR DATABLOCK + +class afxXM_WaveRiderColorData : public afxXM_WaveRiderBaseData, afxXM_WaveColorData_Common +{ + typedef afxXM_WaveRiderBaseData Parent; + +public: + LinearColorF a, b; + LinearColorF a_var, b_var; + bool sync_var; + +public: + /*C*/ afxXM_WaveRiderColorData(); + /*C*/ afxXM_WaveRiderColorData(const afxXM_WaveRiderColorData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_WaveRiderColorData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_WaveRiderColorData); + +ConsoleDocClass( afxXM_WaveRiderColorData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_WaveRiderColorData::afxXM_WaveRiderColorData() +{ + a.set(0.0f, 0.0f, 0.0f, 0.0f); + b.set(1.0f, 1.0f, 1.0f, 1.0f); + a_var.set(0.0f, 0.0f, 0.0f, 0.0f); + b_var.set(0.0f, 0.0f, 0.0f, 0.0f); + sync_var = false; +} + +afxXM_WaveRiderColorData::afxXM_WaveRiderColorData(const afxXM_WaveRiderColorData& other, bool temp_clone) : afxXM_WaveRiderBaseData(other, temp_clone) +{ + a = other.a; + b = other.b; + a_var = other.a_var; + b_var = other.b_var; + sync_var = other.sync_var; +} + +void afxXM_WaveRiderColorData::initPersistFields() +{ + addField("a", TypeColorF, Offset(a, afxXM_WaveRiderColorData), + "..."); + addField("b", TypeColorF, Offset(b, afxXM_WaveRiderColorData), + "..."); + addField("aVariance", TypeColorF, Offset(a_var, afxXM_WaveRiderColorData), + "..."); + addField("bVariance", TypeColorF, Offset(b_var, afxXM_WaveRiderColorData), + "..."); + addField("syncVariances", TypeBool, Offset(sync_var, afxXM_WaveRiderColorData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_WaveRiderColorData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(a); + stream->write(b); + stream->write(a_var); + stream->write(b_var); + stream->writeFlag(sync_var); +} + +void afxXM_WaveRiderColorData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&a); + stream->read(&b); + stream->read(&a_var); + stream->read(&b_var); + sync_var = stream->readFlag(); +} + +afxXM_Base* afxXM_WaveRiderColorData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_WaveRiderColorData* dblock = this; + + if (getSubstitutionCount() > 0) + { + dblock = new afxXM_WaveRiderColorData(*this, true); + this->performSubstitutions(dblock, fx->getChoreographer(), fx->getGroupIndex()); + } + + afxXM_WaveInterp_Color* interp; + interp = createInterp(dblock->op, dblock); + if (!interp) + return 0; + + interp->set(dblock->a, dblock->b, dblock->a_var, dblock->b_var, dblock->sync_var); + + return new afxXM_WaveRiderBase(dblock, fx, interp); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/xm/afxXM_WaveScalar.cpp b/Engine/source/afx/xm/afxXM_WaveScalar.cpp new file mode 100644 index 000000000..d05b1d579 --- /dev/null +++ b/Engine/source/afx/xm/afxXM_WaveScalar.cpp @@ -0,0 +1,825 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/afxChoreographer.h" +#include "afx/xm/afxXM_WaveBase.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE SCALAR INTERPOLATORS + +class afxXM_WaveInterp_Scalar : public afxXM_WaveInterp +{ +protected: + F32 a_set, b_set; + F32 a_var, b_var; + F32 a, b; + bool sync_var; + +public: + afxXM_WaveInterp_Scalar(); + + void set(F32 a, F32 b, F32 a_var, F32 b_var, bool sync_var); + + virtual void interpolate(F32 t, afxXM_Params& params)=0; + virtual void pulse(); +}; + +afxXM_WaveInterp_Scalar::afxXM_WaveInterp_Scalar() +{ + a_set = 0.0f; + b_set = 1.0f; + a_var = 0.0f; + b_var = 0.0; + sync_var = false; + a = 0.0f; + b = 1.0f; +} + +void afxXM_WaveInterp_Scalar::set(F32 a, F32 b, F32 a_var, F32 b_var, bool sync_var) +{ + a_set = a; + b_set = b; + this->a_var = a_var; + this->b_var = b_var; + this->sync_var = sync_var; + this->a = a; + this->b = b; +} + +inline void afxXM_WaveInterp_Scalar::pulse() +{ + F32 rand_t = gRandGen.randF()*2.0f; + a = a_set + rand_t*a_var - a_var; + if (!sync_var) + rand_t = gRandGen.randF()*2.0f; + b = b_set + rand_t*b_var - b_var; +} + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_Add : public afxXM_WaveInterp_Scalar +{ +protected: + U32 offset; +public: + afxXM_WaveInterp_Scalar_Add(U32 o) : afxXM_WaveInterp_Scalar() { offset = o; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + *((F32*)(((char*)(¶ms)) + offset)) += lerp(t, a, b); + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_Mul : public afxXM_WaveInterp_Scalar +{ +protected: + U32 offset; +public: + afxXM_WaveInterp_Scalar_Mul(U32 o) : afxXM_WaveInterp_Scalar() { offset = o; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + *((F32*)(((char*)(¶ms)) + offset)) *= lerp(t, a, b); + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_Rep : public afxXM_WaveInterp_Scalar +{ +protected: + U32 offset; +public: + afxXM_WaveInterp_Scalar_Rep(U32 o) : afxXM_WaveInterp_Scalar() { offset = o; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + *((F32*)(((char*)(¶ms)) + offset)) = lerp(t, a, b); + } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_PointAdd : public afxXM_WaveInterp_Scalar +{ +protected: + U32 offset; +public: + afxXM_WaveInterp_Scalar_PointAdd(U32 o) : afxXM_WaveInterp_Scalar() { offset = o; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + F32 scalar_at_t = lerp(t, a, b); + Point3F point_at_t(scalar_at_t, scalar_at_t, scalar_at_t); + *((Point3F*)(((char*)(¶ms)) + offset)) += point_at_t; + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_PointMul : public afxXM_WaveInterp_Scalar +{ +protected: + U32 offset; +public: + afxXM_WaveInterp_Scalar_PointMul(U32 o) : afxXM_WaveInterp_Scalar() { offset = o; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + *((Point3F*)(((char*)(¶ms)) + offset)) *= lerp(t, a, b); + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_PointRep : public afxXM_WaveInterp_Scalar +{ +protected: + U32 offset; +public: + afxXM_WaveInterp_Scalar_PointRep(U32 o) : afxXM_WaveInterp_Scalar() { offset = o; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + F32 scalar_at_t = lerp(t, a, b); + Point3F point_at_t(scalar_at_t, scalar_at_t, scalar_at_t); + *((Point3F*)(((char*)(¶ms)) + offset)) = point_at_t; + } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_Axis_PointAdd : public afxXM_WaveInterp_Scalar +{ +protected: + Point3F axis; + U32 offset; +public: + afxXM_WaveInterp_Scalar_Axis_PointAdd(U32 o, Point3F ax) : afxXM_WaveInterp_Scalar() { offset = o; axis = ax; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + Point3F point_at_t = axis*lerp(t, a, b); + *((Point3F*)(((char*)(¶ms)) + offset)) += point_at_t; + } +}; + +class afxXM_WaveInterp_Scalar_LocalAxis_PointAdd : public afxXM_WaveInterp_Scalar +{ +protected: + Point3F axis; + U32 offset; +public: + afxXM_WaveInterp_Scalar_LocalAxis_PointAdd(U32 o, Point3F ax) : afxXM_WaveInterp_Scalar() { offset = o; axis = ax; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + Point3F local_axis(axis); + params.ori.mulV(local_axis); + Point3F point_at_t = local_axis*lerp(t, a, b); + *((Point3F*)(((char*)(¶ms)) + offset)) += point_at_t; + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_Axis_PointMul : public afxXM_WaveInterp_Scalar +{ +protected: + Point3F axis; + U32 offset; +public: + afxXM_WaveInterp_Scalar_Axis_PointMul(U32 o, Point3F ax) : afxXM_WaveInterp_Scalar() { offset = o; axis = ax; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + Point3F point_at_t = axis*lerp(t, a, b); + *((Point3F*)(((char*)(¶ms)) + offset)) *= point_at_t; + } +}; + +class afxXM_WaveInterp_Scalar_LocalAxis_PointMul : public afxXM_WaveInterp_Scalar +{ +protected: + Point3F axis; + U32 offset; +public: + afxXM_WaveInterp_Scalar_LocalAxis_PointMul(U32 o, Point3F ax) : afxXM_WaveInterp_Scalar() { offset = o; axis = ax; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + Point3F local_axis(axis); + params.ori.mulV(local_axis); + Point3F point_at_t = local_axis*lerp(t, a, b); + *((Point3F*)(((char*)(¶ms)) + offset)) *= point_at_t; + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_Axis_PointRep : public afxXM_WaveInterp_Scalar +{ +protected: + Point3F axis; + U32 offset; +public: + afxXM_WaveInterp_Scalar_Axis_PointRep(U32 o, Point3F ax) : afxXM_WaveInterp_Scalar() { offset = o; axis = ax; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + Point3F point_at_t = axis*lerp(t, a, b); + *((Point3F*)(((char*)(¶ms)) + offset)) = point_at_t; + } +}; + +class afxXM_WaveInterp_Scalar_LocalAxis_PointRep : public afxXM_WaveInterp_Scalar +{ +protected: + Point3F axis; + U32 offset; +public: + afxXM_WaveInterp_Scalar_LocalAxis_PointRep(U32 o, Point3F ax) : afxXM_WaveInterp_Scalar() { offset = o; axis = ax; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + Point3F local_axis(axis); + params.ori.mulV(local_axis); + Point3F point_at_t = local_axis*lerp(t, a, b); + *((Point3F*)(((char*)(¶ms)) + offset)) = point_at_t; + } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_ColorAdd : public afxXM_WaveInterp_Scalar +{ +public: + afxXM_WaveInterp_Scalar_ColorAdd() : afxXM_WaveInterp_Scalar() { } + virtual void interpolate(F32 t, afxXM_Params& params) + { + F32 scalar_at_t = lerp(t, a, b); + LinearColorF color_at_t(scalar_at_t, scalar_at_t, scalar_at_t, scalar_at_t); + params.color += color_at_t; + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_ColorMul : public afxXM_WaveInterp_Scalar +{ +public: + afxXM_WaveInterp_Scalar_ColorMul() : afxXM_WaveInterp_Scalar() { } + virtual void interpolate(F32 t, afxXM_Params& params) + { + params.color *= lerp(t, a, b); + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_ColorRep : public afxXM_WaveInterp_Scalar +{ +public: + afxXM_WaveInterp_Scalar_ColorRep() : afxXM_WaveInterp_Scalar() { } + virtual void interpolate(F32 t, afxXM_Params& params) + { + F32 scalar_at_t = lerp(t, a, b); + params.color.set(scalar_at_t, scalar_at_t, scalar_at_t, scalar_at_t); + } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_OriMul : public afxXM_WaveInterp_Scalar +{ +protected: + Point3F axis; +public: + afxXM_WaveInterp_Scalar_OriMul(Point3F& ax) : afxXM_WaveInterp_Scalar() { axis = ax; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + F32 theta = mDegToRad(lerp(t, a, b)); + AngAxisF rot_aa(axis, theta); + MatrixF rot_xfm; rot_aa.setMatrix(&rot_xfm); + params.ori.mul(rot_xfm); + } +}; + +//~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WaveInterp_Scalar_OriRep : public afxXM_WaveInterp_Scalar +{ +protected: + Point3F axis; +public: + afxXM_WaveInterp_Scalar_OriRep(Point3F& ax) : afxXM_WaveInterp_Scalar() { axis = ax; } + virtual void interpolate(F32 t, afxXM_Params& params) + { + F32 theta = mDegToRad(lerp(t, a, b)); + AngAxisF rot_aa(axis, theta); + rot_aa.setMatrix(¶ms.ori); + } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE SCALAR BASE DATABLOCK + +class afxXM_WaveScalarData_Common : public virtual afxXM_Defs +{ + static afxXM_WaveInterp_Scalar* alloc_interp(U32 param, S32 comp, U32 op, U32 off, + Point3F& axis, bool loc_axis, afxXM_BaseData*); + static bool needs_offset(U32 param, S32 component); + static bool needs_axis(U32 param, S32 component); + +protected: + static afxXM_WaveInterp_Scalar* createInterp(U32 param, U32 op, Point3F axis, bool loc_axis, + afxXM_BaseData*); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +bool afxXM_WaveScalarData_Common::needs_offset(U32 param, S32 component) +{ + switch (param) + { + case ORIENTATION: + return false; + + case POSITION: + case POSITION2: + case SCALE: + case VISIBILITY: + return true; + + case COLOR: + return (component != -1); + } + + return false; +} + +bool afxXM_WaveScalarData_Common::needs_axis(U32 param, S32 component) +{ + switch (param) + { + case ORIENTATION: + return true; + + case POSITION: + case POSITION2: + case SCALE: + case COLOR: + case VISIBILITY: + return false; + } + + return true; +} + +afxXM_WaveInterp_Scalar* +afxXM_WaveScalarData_Common::alloc_interp(U32 param, S32 component, U32 op, U32 offset, Point3F& axis, bool loc_axis, afxXM_BaseData* db) +{ + afxXM_WaveInterp_Scalar* interpolator = 0; + + switch (param) + { + case ORIENTATION: + switch (op) + { + case afxXM_WaveBaseData::OP_ADD: + Con::errorf("%s::%s -- invalid orientation op.", db->getClassName(), db->getName()); + return 0; + case afxXM_WaveBaseData::OP_MULTIPLY: + interpolator = new afxXM_WaveInterp_Scalar_OriMul(axis); + break; + case afxXM_WaveBaseData::OP_REPLACE: + interpolator = new afxXM_WaveInterp_Scalar_OriRep(axis); + break; + } + break; + case POSITION: + case POSITION2: + case SCALE: + if (component == -1) + { + if (axis.isZero()) + { + switch (op) + { + case afxXM_WaveBaseData::OP_ADD: + interpolator = new afxXM_WaveInterp_Scalar_PointAdd(offset); + break; + case afxXM_WaveBaseData::OP_MULTIPLY: + interpolator = new afxXM_WaveInterp_Scalar_PointMul(offset); + break; + case afxXM_WaveBaseData::OP_REPLACE: + interpolator = new afxXM_WaveInterp_Scalar_PointRep(offset); + break; + } + } + else if (loc_axis) + { + switch (op) + { + case afxXM_WaveBaseData::OP_ADD: + interpolator = new afxXM_WaveInterp_Scalar_LocalAxis_PointAdd(offset, axis); + break; + case afxXM_WaveBaseData::OP_MULTIPLY: + interpolator = new afxXM_WaveInterp_Scalar_LocalAxis_PointMul(offset, axis); + break; + case afxXM_WaveBaseData::OP_REPLACE: + interpolator = new afxXM_WaveInterp_Scalar_LocalAxis_PointRep(offset, axis); + break; + } + } + else + { + switch (op) + { + case afxXM_WaveBaseData::OP_ADD: + interpolator = new afxXM_WaveInterp_Scalar_Axis_PointAdd(offset, axis); + break; + case afxXM_WaveBaseData::OP_MULTIPLY: + interpolator = new afxXM_WaveInterp_Scalar_Axis_PointMul(offset, axis); + break; + case afxXM_WaveBaseData::OP_REPLACE: + interpolator = new afxXM_WaveInterp_Scalar_Axis_PointRep(offset, axis); + break; + } + } + } + else + { + switch (op) + { + case afxXM_WaveBaseData::OP_ADD: + interpolator = new afxXM_WaveInterp_Scalar_Add(offset); + break; + case afxXM_WaveBaseData::OP_MULTIPLY: + interpolator = new afxXM_WaveInterp_Scalar_Mul(offset); + break; + case afxXM_WaveBaseData::OP_REPLACE: + interpolator = new afxXM_WaveInterp_Scalar_Rep(offset); + break; + } + } + break; + + case COLOR: + if (component == -1) + { + switch (op) + { + case afxXM_WaveBaseData::OP_ADD: + interpolator = new afxXM_WaveInterp_Scalar_ColorAdd(); + break; + case afxXM_WaveBaseData::OP_MULTIPLY: + interpolator = new afxXM_WaveInterp_Scalar_ColorMul(); + break; + case afxXM_WaveBaseData::OP_REPLACE: + interpolator = new afxXM_WaveInterp_Scalar_ColorRep(); + break; + } + } + else + { + switch (op) + { + case afxXM_WaveBaseData::OP_ADD: + interpolator = new afxXM_WaveInterp_Scalar_Add(offset); + break; + case afxXM_WaveBaseData::OP_MULTIPLY: + interpolator = new afxXM_WaveInterp_Scalar_Mul(offset); + break; + case afxXM_WaveBaseData::OP_REPLACE: + interpolator = new afxXM_WaveInterp_Scalar_Rep(offset); + break; + } + } + break; + + case VISIBILITY: + switch (op) + { + case afxXM_WaveBaseData::OP_ADD: + interpolator = new afxXM_WaveInterp_Scalar_Add(offset); + break; + case afxXM_WaveBaseData::OP_MULTIPLY: + interpolator = new afxXM_WaveInterp_Scalar_Mul(offset); + break; + case afxXM_WaveBaseData::OP_REPLACE: + interpolator = new afxXM_WaveInterp_Scalar_Rep(offset); + break; + } + } + + if (!interpolator) + Con::errorf("%s::%s -- failed to allocate wave interpolator.", db->getClassName(), db->getName()); + + return interpolator; +} + +afxXM_WaveInterp_Scalar* +afxXM_WaveScalarData_Common::createInterp(U32 parameter, U32 op, Point3F axis, bool loc_axis, afxXM_BaseData* db) +{ + S32 component; U32 param_bit; + afxXM_WaveBaseData::initParamInfo(parameter, param_bit, component); + + if (param_bit == 0) + { + Con::errorf("%s::%s -- unknown parameter specified.", db->getClassName(), db->getName()); + return 0; + } + + if (axis.isZero() && needs_axis(param_bit, component)) + { + Con::errorf("%s::%s -- axis required.", db->getClassName(), db->getName()); + return 0; + } + + if (!axis.isZero()) + axis.normalize(); + + U32 offset = afxXM_Params::BAD_OFFSET; + if (needs_offset(param_bit, component)) + { + offset = afxXM_Params::getParameterOffset(param_bit, component); + if (offset == afxXM_Params::BAD_OFFSET) + { + Con::errorf("%s::%s -- bad component offset.", db->getClassName(), db->getName()); + return 0; + } + } + + return alloc_interp(param_bit, component, op, offset, axis, loc_axis, db); +} + + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE SCALAR DATABLOCK + +class afxXM_WaveScalarData : public afxXM_WaveBaseData, afxXM_WaveScalarData_Common +{ + typedef afxXM_WaveBaseData Parent; + +public: + F32 a, b; + F32 a_var, b_var; + bool sync_var; + +public: + /*C*/ afxXM_WaveScalarData(); + /*C*/ afxXM_WaveScalarData(const afxXM_WaveScalarData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_WaveScalarData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_WaveScalarData); + +ConsoleDocClass( afxXM_WaveScalarData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_WaveScalarData::afxXM_WaveScalarData() +{ + a = 0.0f; + b = 1.0f; + a_var = 0.0f; + b_var = 0.0f; + sync_var = false; +} + +afxXM_WaveScalarData::afxXM_WaveScalarData(const afxXM_WaveScalarData& other, bool temp_clone) : afxXM_WaveBaseData(other, temp_clone) +{ + a = other.a; + b = other.b; + a_var = other.a_var; + b_var = other.b_var; + sync_var = other.sync_var; +} + +void afxXM_WaveScalarData::initPersistFields() +{ + addField("a", TypeF32, Offset(a, afxXM_WaveScalarData), + "..."); + addField("b", TypeF32, Offset(b, afxXM_WaveScalarData), + "..."); + addField("aVariance", TypeF32, Offset(a_var, afxXM_WaveScalarData), + "..."); + addField("bVariance", TypeF32, Offset(b_var, afxXM_WaveScalarData), + "..."); + addField("syncVariances", TypeBool, Offset(sync_var, afxXM_WaveScalarData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_WaveScalarData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(a); + stream->write(b); + if (stream->writeFlag(a_var != 0.0f || b_var != 0.0f)) + { + stream->write(a_var); + stream->write(b_var); + stream->writeFlag(sync_var); + } +} + +void afxXM_WaveScalarData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&a); + stream->read(&b); + if (stream->readFlag()) + { + stream->read(&a_var); + stream->read(&b_var); + sync_var = stream->readFlag(); + } + else + { + a_var = b_var = 0.0f; + sync_var = false; + } +} + +afxXM_Base* afxXM_WaveScalarData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_WaveScalarData* dblock = this; + + if (getSubstitutionCount() > 0) + { + dblock = new afxXM_WaveScalarData(*this, true); + this->performSubstitutions(dblock, fx->getChoreographer(), fx->getGroupIndex()); + } + + afxXM_WaveInterp_Scalar* interp; + interp = createInterp(dblock->parameter, dblock->op, dblock->axis, dblock->local_axis, dblock); + if (!interp) + return 0; + + interp->set(dblock->a, dblock->b, dblock->a_var, dblock->b_var, dblock->sync_var); + + return new afxXM_WaveBase(dblock, fx, interp); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// WAVE RIDER SCALAR DATABLOCK + +class afxXM_WaveRiderScalarData : public afxXM_WaveRiderBaseData, afxXM_WaveScalarData_Common +{ + typedef afxXM_WaveRiderBaseData Parent; + +public: + F32 a, b; + F32 a_var, b_var; + bool sync_var; + +public: + /*C*/ afxXM_WaveRiderScalarData(); + /*C*/ afxXM_WaveRiderScalarData(const afxXM_WaveRiderScalarData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + virtual bool allowSubstitutions() const { return true; } + + static void initPersistFields(); + + afxXM_Base* create(afxEffectWrapper* fx, bool on_server); + + DECLARE_CONOBJECT(afxXM_WaveRiderScalarData); + DECLARE_CATEGORY("AFX"); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_WaveRiderScalarData); + +ConsoleDocClass( afxXM_WaveRiderScalarData, + "@brief An xmod datablock.\n\n" + + "@ingroup afxXMods\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + +afxXM_WaveRiderScalarData::afxXM_WaveRiderScalarData() +{ + a = 0.0f; + b = 1.0f; + a_var = 0.0f; + b_var = 0.0f; + sync_var = false; +} + +afxXM_WaveRiderScalarData::afxXM_WaveRiderScalarData(const afxXM_WaveRiderScalarData& other, bool temp_clone) : afxXM_WaveRiderBaseData(other, temp_clone) +{ + a = other.a; + b = other.b; + a_var = other.a_var; + b_var = other.b_var; + sync_var = other.sync_var; +} + +void afxXM_WaveRiderScalarData::initPersistFields() +{ + addField("a", TypeF32, Offset(a, afxXM_WaveRiderScalarData), + "..."); + addField("b", TypeF32, Offset(b, afxXM_WaveRiderScalarData), + "..."); + addField("aVariance", TypeF32, Offset(a_var, afxXM_WaveRiderScalarData), + "..."); + addField("bVariance", TypeF32, Offset(b_var, afxXM_WaveRiderScalarData), + "..."); + addField("syncVariances", TypeBool, Offset(sync_var, afxXM_WaveRiderScalarData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_WaveRiderScalarData::packData(BitStream* stream) +{ + Parent::packData(stream); + stream->write(a); + stream->write(b); + if (stream->writeFlag(a_var != 0.0f || b_var != 0.0f)) + { + stream->write(a_var); + stream->write(b_var); + stream->writeFlag(sync_var); + } +} + +void afxXM_WaveRiderScalarData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + stream->read(&a); + stream->read(&b); + if (stream->readFlag()) + { + stream->read(&a_var); + stream->read(&b_var); + sync_var = stream->readFlag(); + } + else + { + a_var = b_var = 0.0f; + sync_var = false; + } +} + +afxXM_Base* afxXM_WaveRiderScalarData::create(afxEffectWrapper* fx, bool on_server) +{ + afxXM_WaveRiderScalarData* dblock = this; + + if (getSubstitutionCount() > 0) + { + dblock = new afxXM_WaveRiderScalarData(*this, true); + this->performSubstitutions(dblock, fx->getChoreographer(), fx->getGroupIndex()); + } + + afxXM_WaveInterp_Scalar* interp; + interp = createInterp(dblock->parameter, dblock->op, dblock->axis, dblock->local_axis, dblock); + if (!interp) + return 0; + + interp->set(dblock->a, dblock->b, dblock->a_var, dblock->b_var, dblock->sync_var); + + return new afxXM_WaveRiderBase(dblock, fx, interp); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// diff --git a/Engine/source/afx/xm/afxXfmMod.cpp b/Engine/source/afx/xm/afxXfmMod.cpp new file mode 100644 index 000000000..896cc06d0 --- /dev/null +++ b/Engine/source/afx/xm/afxXfmMod.cpp @@ -0,0 +1,333 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "afx/arcaneFX.h" + +#include "afx/afxEffectWrapper.h" +#include "afx/util/afxEase.h" +#include "afx/xm/afxXfmMod.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +afxXM_Params::afxXM_Params() +{ + pos.zero(); + ori.identity(); + scale.set(1.0f,1.0f,1.0f); + pos2.zero(); + color.set(0.0f,0.0f,0.0f,0.0f); + vis = 0.0; +} + +U32 afxXM_Params::getParameterOffset(U32 param, S32 component) +{ + switch (param) + { + + case POSITION: + switch (component) + { + case 0: + return Offset(pos.x, afxXM_Params); + case 1: + return Offset(pos.y, afxXM_Params); + case 2: + return Offset(pos.z, afxXM_Params); + default: + return Offset(pos, afxXM_Params); + } + break; + + case ORIENTATION: + return Offset(ori, afxXM_Params); + + case POSITION2: + switch (component) + { + case 0: + return Offset(pos2.x, afxXM_Params); + case 1: + return Offset(pos2.y, afxXM_Params); + case 2: + return Offset(pos2.z, afxXM_Params); + default: + return Offset(pos2, afxXM_Params); + } + break; + + case SCALE: + switch (component) + { + case 0: + return Offset(scale.x, afxXM_Params); + case 1: + return Offset(scale.y, afxXM_Params); + case 2: + return Offset(scale.z, afxXM_Params); + default: + return Offset(scale, afxXM_Params); + } + break; + + case COLOR: + switch (component) + { + case 0: + return Offset(color.red, afxXM_Params); + case 1: + return Offset(color.green, afxXM_Params); + case 2: + return Offset(color.blue, afxXM_Params); + case 3: + return Offset(color.alpha, afxXM_Params); + default: + return Offset(color, afxXM_Params); + } + break; + + case VISIBILITY: + return Offset(vis, afxXM_Params); + } + + return BAD_OFFSET; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// BASE CLASSES +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_BaseData); + +afxXM_BaseData::afxXM_BaseData() +{ + ignore_time_factor = false; +} + +afxXM_BaseData::afxXM_BaseData(const afxXM_BaseData& other, bool temp_clone) : GameBaseData(other, temp_clone) +{ + ignore_time_factor = other.ignore_time_factor; +} + +void afxXM_BaseData::initPersistFields() +{ + addField("ignoreTimeFactor", TypeBool, Offset(ignore_time_factor, afxXM_BaseData), + "..."); + + Parent::initPersistFields(); + + Con::setIntVariable("$afxXfmMod::POS", POSITION); + Con::setIntVariable("$afxXfmMod::ORI", ORIENTATION); + Con::setIntVariable("$afxXfmMod::POS2", POSITION2); + Con::setIntVariable("$afxXfmMod::SCALE", SCALE); + Con::setIntVariable("$afxXfmMod::ALL_BUT_SCALE", ALL_BUT_SCALE); + Con::setIntVariable("$afxXfmMod::ALL", ALL); +} + +void afxXM_BaseData::packData(BitStream* stream) +{ + Parent::packData(stream); + + stream->write(ignore_time_factor); +} + +void afxXM_BaseData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + stream->read(&ignore_time_factor); +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_Base::afxXM_Base(afxXM_BaseData* db, afxEffectWrapper* fxw) +{ + fx_wrapper = fxw; + time_factor = (db->ignore_time_factor) ? 1.0f : fxw->getTimeFactor(); + datablock = db; +} + +afxXM_Base::~afxXM_Base() +{ + if (datablock && datablock->isTempClone()) + delete datablock; + datablock = 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +IMPLEMENT_CO_DATABLOCK_V1(afxXM_WeightedBaseData); + +afxXM_WeightedBaseData::afxXM_WeightedBaseData() +{ + delay = 0; + lifetime = afxEffectDefs::INFINITE_LIFETIME; + fade_in_time = 0; + fade_out_time = 0; + fadein_ease.set(0.0f, 1.0f); + fadeout_ease.set(0.0f, 1.0f); + life_bias = 1.0f; +} + +afxXM_WeightedBaseData::afxXM_WeightedBaseData(const afxXM_WeightedBaseData& other, bool temp_clone) : afxXM_BaseData(other, temp_clone) +{ + delay = other.delay; + lifetime = other.lifetime; + fade_in_time = other.fade_in_time; + fade_out_time = other.fade_out_time; + fadein_ease = other.fadein_ease; + fadeout_ease = other.fadeout_ease; + life_bias = other.life_bias; +} + +bool afxXM_WeightedBaseData::hasFixedWeight() const +{ + return (delay == 0.0f && lifetime == afxEffectDefs::INFINITE_LIFETIME && fade_in_time == 0.0f && fade_out_time == 0.0f); +} + +F32 afxXM_WeightedBaseData::getWeightFactor() const +{ + return 1.0f; +} + +void afxXM_WeightedBaseData::initPersistFields() +{ + addField("delay", TypeF32, Offset(delay, afxXM_WeightedBaseData), + "..."); + addField("lifetime", TypeF32, Offset(lifetime, afxXM_WeightedBaseData), + "..."); + addField("fadeInTime", TypeF32, Offset(fade_in_time, afxXM_WeightedBaseData), + "..."); + addField("fadeOutTime", TypeF32, Offset(fade_out_time, afxXM_WeightedBaseData), + "..."); + addField("fadeInEase", TypePoint2F, Offset(fadein_ease, afxXM_WeightedBaseData), + "..."); + addField("fadeOutEase", TypePoint2F, Offset(fadeout_ease, afxXM_WeightedBaseData), + "..."); + addField("lifetimeBias", TypeF32, Offset(life_bias, afxXM_WeightedBaseData), + "..."); + + Parent::initPersistFields(); +} + +void afxXM_WeightedBaseData::packData(BitStream* stream) +{ + Parent::packData(stream); + + if (stream->writeFlag(!hasFixedWeight())) + { + stream->write(delay); + stream->write(lifetime); + stream->write(fade_in_time); + stream->write(fade_out_time); + if (stream->writeFlag(fadein_ease.x != 0.0f || fadein_ease.y != 1.0f)) + { + stream->writeFloat(fadein_ease.x, 16); + stream->writeFloat(fadein_ease.y, 16); + } + if (stream->writeFlag(fadeout_ease.x != 0.0f || fadeout_ease.y != 1.0f)) + { + stream->writeFloat(fadeout_ease.x, 16); + stream->writeFloat(fadeout_ease.y, 16); + } + stream->write(life_bias); + } +} + +void afxXM_WeightedBaseData::unpackData(BitStream* stream) +{ + Parent::unpackData(stream); + + if (stream->readFlag()) // WEIGHTED? + { + stream->read(&delay); + stream->read(&lifetime); + stream->read(&fade_in_time); + stream->read(&fade_out_time); + if (stream->readFlag()) // FADE-IN EASED? + { + fadein_ease.x = stream->readFloat(16); + fadein_ease.y = stream->readFloat(16); + } + else + fadein_ease.set(0.0f, 1.0f); + if (stream->readFlag()) // FADE-OUT EASED? + { + fadeout_ease.x = stream->readFloat(16); + fadeout_ease.y = stream->readFloat(16); + } + else + fadeout_ease.set(0.0f, 1.0f); + stream->read(&life_bias); + } + else + { + delay = 0.0f; + lifetime = afxEffectDefs::INFINITE_LIFETIME; + fade_in_time = 0.0f; + fade_out_time = 0.0f; + fadein_ease.set(0.0f, 1.0f); + fadeout_ease.set(0.0f, 1.0f); + life_bias = 1.0f; + } +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~// + +afxXM_WeightedBase::afxXM_WeightedBase(afxXM_WeightedBaseData* db, afxEffectWrapper* fxw) +: afxXM_Base(db, fxw) +{ + wt_fadein = db->fade_in_time*db->life_bias; + wt_fadeout = db->fade_out_time*db->life_bias; + wt_fadein_ease = db->fadein_ease; + wt_fadeout_ease = db->fadeout_ease; + wt_start_time = db->delay; + wt_full_time = wt_start_time + wt_fadein; + wt_fade_time = wt_start_time + db->lifetime*db->life_bias; + wt_done_time = wt_fade_time + wt_fadeout; +} + +F32 afxXM_WeightedBase::calc_weight_factor(F32 elapsed) +{ + if (elapsed < wt_start_time) // pre + return 0.0f; + else if (elapsed < wt_full_time) // fade-in + { + F32 t = (elapsed - wt_start_time)/wt_fadein; + return afxEase::t(t, wt_fadein_ease.x, wt_fadein_ease.y); + } + else if (elapsed < wt_fade_time) // full + return 1.0f; + else if (elapsed < wt_done_time) // fade-out + { + F32 t = (wt_done_time - elapsed)/wt_fadeout; + return afxEase::t(t, wt_fadeout_ease.x, wt_fadeout_ease.y); + } + else // post + return 0; +} + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + + diff --git a/Engine/source/afx/xm/afxXfmMod.h b/Engine/source/afx/xm/afxXfmMod.h new file mode 100644 index 000000000..7c7eaa41d --- /dev/null +++ b/Engine/source/afx/xm/afxXfmMod.h @@ -0,0 +1,176 @@ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. +// +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#ifndef _AFX_XFM_MOD_BASE_H_ +#define _AFX_XFM_MOD_BASE_H_ + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#include "math/mPoint3.h" +#include "math/mMatrix.h" +#include "math/mMathFn.h" + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// BASE CLASSES +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class BitStream; +class afxEffectWrapper; + +class afxXM_Defs +{ +public: + enum + { + POSITION = BIT(0), + ORIENTATION = BIT(1), + POSITION2 = BIT(2), + SCALE = BIT(3), + COLOR = BIT(4), + VISIBILITY = BIT(5), + ALL_BUT_SCALE = (POSITION | ORIENTATION | POSITION2), + ALL = (ALL_BUT_SCALE | SCALE), + }; +}; + +struct afxXM_Params : public afxXM_Defs +{ + Point3F pos; + MatrixF ori; + Point3F scale; + Point3F pos2; + LinearColorF color; + F32 vis; + + enum { BAD_OFFSET = S32_MAX }; + + static U32 getParameterOffset(U32 param, S32 component=-1); + afxXM_Params(); +}; + +class afxXM_Base; + +class afxXM_BaseData : public GameBaseData, public afxXM_Defs +{ + typedef GameBaseData Parent; + +public: + bool ignore_time_factor; + +public: + /*C*/ afxXM_BaseData(); + /*C*/ afxXM_BaseData(const afxXM_BaseData&, bool = false); + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + static void initPersistFields(); + + virtual afxXM_Base* create(afxEffectWrapper* fx, bool on_server) { return 0; } + + DECLARE_CONOBJECT(afxXM_BaseData); + DECLARE_CATEGORY("AFX"); +}; + +class afxXM_Base : public afxXM_Defs +{ +protected: + afxEffectWrapper* fx_wrapper; + afxXM_BaseData* datablock; + F32 time_factor; + +public: + /*C*/ afxXM_Base(afxXM_BaseData*, afxEffectWrapper*); + virtual ~afxXM_Base(); + + virtual void start(F32 timestamp) { } + virtual void updateParams(F32 dt, F32 elapsed, afxXM_Params& p); + + // old update form for backwards compatibility (deprecated) + virtual void update(F32 dt, F32 elapsed, Point3F& pos, MatrixF& ori, Point3F& pos2, Point3F& scale) { }; +}; + +// New subclasses should define own updateParams() and should *not* call this thru Parent. +// This calls old form of update() for backwards compatibility. +inline void afxXM_Base::updateParams(F32 dt, F32 elapsed, afxXM_Params& p) +{ + update(dt, elapsed, p.pos, p.ori, p.pos2, p.scale); +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +class afxXM_WeightedBaseData : public afxXM_BaseData +{ + typedef afxXM_BaseData Parent; + +public: + F32 lifetime; + F32 delay; + F32 fade_in_time; + F32 fade_out_time; + Point2F fadein_ease; + Point2F fadeout_ease; + F32 life_bias; + +public: + /*C*/ afxXM_WeightedBaseData(); + /*C*/ afxXM_WeightedBaseData(const afxXM_WeightedBaseData&, bool = false); + + bool hasFixedWeight() const; + F32 getWeightFactor() const; + + void packData(BitStream* stream); + void unpackData(BitStream* stream); + + static void initPersistFields(); + + DECLARE_CONOBJECT(afxXM_WeightedBaseData); + DECLARE_CATEGORY("AFX"); +}; + +class afxXM_WeightedBase : public afxXM_Base +{ + typedef afxXM_Base Parent; + +protected: + F32 wt_fadein; + F32 wt_fadeout; + Point2F wt_fadein_ease; + Point2F wt_fadeout_ease; + F32 wt_start_time; + F32 wt_full_time; + F32 wt_fade_time; + F32 wt_done_time; + + F32 calc_weight_factor(F32 elapsed); + +public: + /*C*/ afxXM_WeightedBase(afxXM_WeightedBaseData*, afxEffectWrapper*); + virtual ~afxXM_WeightedBase() { } +}; + +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + +#endif // _AFX_XFM_MOD_BASE_H_ diff --git a/Engine/source/app/game.cpp b/Engine/source/app/game.cpp index 5b2070887..b2b107d8c 100644 --- a/Engine/source/app/game.cpp +++ b/Engine/source/app/game.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "platform/platformInput.h" @@ -49,6 +54,8 @@ #include "gfx/gfxTextureManager.h" #include "sfx/sfxSystem.h" +// Including this header provides access to certain system-level AFX methods. +#include "afx/arcaneFX.h" #ifdef TORQUE_PLAYER // See matching #ifdef in editor/editor.cpp bool gEditingMission = false; @@ -235,6 +242,9 @@ ConsoleFunctionGroupEnd(Platform); bool clientProcess(U32 timeDelta) { + // Required heartbeat call on the client side which must come + // before the advanceTime() calls are made to the scene objects. + arcaneFX::advanceTime(timeDelta); bool ret = true; #ifndef TORQUE_TGB_ONLY diff --git a/Engine/source/console/compiledEval.cpp b/Engine/source/console/compiledEval.cpp index f189d6268..1d872df8a 100644 --- a/Engine/source/console/compiledEval.cpp +++ b/Engine/source/console/compiledEval.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "console/console.h" @@ -858,6 +863,7 @@ breakContinue: Con::errorf(ConsoleLogEntry::General, "%s: Unable to instantiate non-datablock class %s.", getFileLine(ip), (const char*)callArgv[1]); // Clean up... delete object; + currentNewObject = NULL; ip = failJump; break; } @@ -893,6 +899,14 @@ breakContinue: currentNewObject->setCopySource( parent ); currentNewObject->assignFieldsFrom( parent ); + // copy any substitution statements + SimDataBlock* parent_db = dynamic_cast(parent); + if (parent_db) + { + SimDataBlock* currentNewObject_db = dynamic_cast(currentNewObject); + if (currentNewObject_db) + currentNewObject_db->copySubstitutionsFrom(parent_db); + } } else { @@ -952,6 +966,38 @@ breakContinue: currentNewObject->setModDynamicFields(true); } } + else + { + currentNewObject->reloadReset(); // AFX (reload-reset) + // Does it have a parent object? (ie, the copy constructor : syntax, not inheriance) + if(*objParent) + { + // Find it! + SimObject *parent; + if(Sim::findObject(objParent, parent)) + { + // Con::printf(" - Parent object found: %s", parent->getClassName()); + + // temporarily block name change + SimObject::preventNameChanging = true; + currentNewObject->setCopySource( parent ); + currentNewObject->assignFieldsFrom(parent); + // restore name changing + SimObject::preventNameChanging = false; + + // copy any substitution statements + SimDataBlock* parent_db = dynamic_cast(parent); + if (parent_db) + { + SimDataBlock* currentNewObject_db = dynamic_cast(currentNewObject); + if (currentNewObject_db) + currentNewObject_db->copySubstitutionsFrom(parent_db); + } + } + else + Con::errorf(ConsoleLogEntry::General, "%d: Unable to find parent object %s for %s.", lineNumber, objParent, (const char*)callArgv[1]); + } + } // Advance the IP past the create info... ip += 7; diff --git a/Engine/source/console/consoleObject.cpp b/Engine/source/console/consoleObject.cpp index 2d9ba2a1e..d75405376 100644 --- a/Engine/source/console/consoleObject.cpp +++ b/Engine/source/console/consoleObject.cpp @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #include "platform/platform.h" #include "console/consoleObject.h" @@ -681,6 +685,39 @@ AbstractClassRep* ConsoleObject::getClassRep() const return NULL; } +bool ConsoleObject::disableFieldSubstitutions(const char* fieldname) +{ + StringTableEntry slotname = StringTable->insert(fieldname); + + for (U32 i = 0; i < sg_tempFieldList.size(); i++) + { + if (sg_tempFieldList[i].pFieldname == slotname) + { + sg_tempFieldList[i].doNotSubstitute = true; + sg_tempFieldList[i].keepClearSubsOnly = false; + return true; + } + } + + return false; +} + +bool ConsoleObject::onlyKeepClearSubstitutions(const char* fieldname) +{ + StringTableEntry slotname = StringTable->insert(fieldname); + + for (U32 i = 0; i < sg_tempFieldList.size(); i++) + { + if (sg_tempFieldList[i].pFieldname == slotname) + { + sg_tempFieldList[i].doNotSubstitute = false; + sg_tempFieldList[i].keepClearSubsOnly = true; + return true; + } + } + + return false; +} String ConsoleObject::_getLogMessage(const char* fmt, va_list args) const { String objClass = "UnknownClass"; diff --git a/Engine/source/console/consoleObject.h b/Engine/source/console/consoleObject.h index b5253ced6..2ff2bc5a3 100644 --- a/Engine/source/console/consoleObject.h +++ b/Engine/source/console/consoleObject.h @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #ifndef _CONSOLEOBJECT_H_ #define _CONSOLEOBJECT_H_ @@ -493,6 +497,7 @@ public: setDataFn( NULL ), getDataFn( NULL ) { + doNotSubstitute = keepClearSubsOnly = false; } StringTableEntry pFieldname; ///< Name of the field. @@ -510,6 +515,8 @@ public: TypeValidator *validator; ///< Validator, if any. SetDataNotify setDataFn; ///< Set data notify Fn GetDataNotify getDataFn; ///< Get data notify Fn + bool doNotSubstitute; + bool keepClearSubsOnly; WriteDataNotify writeDataFn; ///< Function to determine whether data should be written or not. }; typedef Vector FieldList; @@ -1055,6 +1062,9 @@ public: static ConsoleObject* __findObject( const char* ) { return NULL; } static const char* __getObjectId( ConsoleObject* ) { return ""; } +protected: + static bool disableFieldSubstitutions(const char* in_pFieldname); + static bool onlyKeepClearSubstitutions(const char* in_pFieldname); }; #define addNamedField(fieldName,type,className) addField(#fieldName, type, Offset(fieldName,className)) diff --git a/Engine/source/console/simDatablock.cpp b/Engine/source/console/simDatablock.cpp index a10ba1761..ce689c054 100644 --- a/Engine/source/console/simDatablock.cpp +++ b/Engine/source/console/simDatablock.cpp @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #include "platform/platform.h" #include "console/simDatablock.h" @@ -29,6 +33,8 @@ #include "T3D/gameBase/gameConnectionEvents.h" #include "T3D/gameBase/gameConnection.h" +#include "core/stream/bitStream.h" +#include "console/compiler.h" IMPLEMENT_CO_DATABLOCK_V1(SimDataBlock); SimObjectId SimDataBlock::sNextObjectId = DataBlockObjectIdFirst; @@ -52,6 +58,260 @@ SimDataBlock::SimDataBlock() setModDynamicFields(true); setModStaticFields(true); } +// this implements a simple structure for managing substitution statements. + +SimDataBlock::SubstitutionStatement::SubstitutionStatement(StringTableEntry slot, S32 idx, const char* value) +{ + this->slot = slot; + this->idx = idx; + this->value = dStrdup(value); +} + +SimDataBlock::SubstitutionStatement::~SubstitutionStatement() +{ + dFree(value); +} + +void SimDataBlock::SubstitutionStatement::replaceValue(const char* value) +{ + dFree(this->value); + this->value = dStrdup(value); +} + +// this is the copy-constructor for creating temp-clones. +SimDataBlock::SimDataBlock(const SimDataBlock& other, bool temp_clone) : SimObject(other, temp_clone) +{ + modifiedKey = other.modifiedKey; +} + +// a destructor is added to SimDataBlock so that we can delete any substitutions. +SimDataBlock::~SimDataBlock() +{ + clear_substitutions(); +} + +void SimDataBlock::clear_substitutions() +{ + for (S32 i = 0; i < substitutions.size(); i++) + delete substitutions[i]; + substitutions.clear(); +} + +void SimDataBlock::addSubstitution(StringTableEntry slot, S32 idx, const char* subst) +{ + AssertFatal(subst != 0 && subst[0] == '$' && subst[1] == '$', "Bad substition statement string added"); + + subst += 2; + while (dIsspace(*subst)) + subst++; + + bool empty_subs = (*subst == '\0'); + + for (S32 i = 0; i < substitutions.size(); i++) + { + if (substitutions[i] && substitutions[i]->slot == slot && substitutions[i]->idx == idx) + { + if (empty_subs) + { + delete substitutions[i]; + substitutions[i] = 0; + onRemoveSubstitution(slot, idx); + } + else + { + substitutions[i]->replaceValue(subst); + onAddSubstitution(slot, idx, subst); + } + return; + } + } + + if (!empty_subs) + { + substitutions.push_back(new SubstitutionStatement(slot, idx, subst)); + onAddSubstitution(slot, idx, subst); + } +} + +const char* SimDataBlock::getSubstitution(StringTableEntry slot, S32 idx) +{ + for (S32 i = 0; i < substitutions.size(); i++) + { + if (substitutions[i] && substitutions[i]->slot == slot && substitutions[i]->idx == idx) + return substitutions[i]->value; + } + + return 0; +} + +bool SimDataBlock::fieldHasSubstitution(StringTableEntry slot) +{ + for (S32 i = 0; i < substitutions.size(); i++) + if (substitutions[i] && substitutions[i]->slot == slot) + return true; + return false; +} + +void SimDataBlock::printSubstitutions() +{ + for (S32 i = 0; i < substitutions.size(); i++) + if (substitutions[i]) + Con::errorf("SubstitutionStatement[%s] = \"%s\" -- %d", substitutions[i]->slot, substitutions[i]->value, i); +} + +void SimDataBlock::copySubstitutionsFrom(SimDataBlock* other) +{ + clear_substitutions(); + if (!other) + return; + + for (S32 i = 0; i < other->substitutions.size(); i++) + { + if (other->substitutions[i]) + { + SubstitutionStatement* subs = other->substitutions[i]; + substitutions.push_back(new SubstitutionStatement(subs->slot, subs->idx, subs->value)); + } + } +} + + +// This is the method that evaluates any substitution statements on a datablock and does the +// actual replacement of substituted datablock fields. +// +// Much of the work is done by passing the statement to Con::evaluate() but first there are +// some key operations performed on the statement. +// -- Instances of "%%" in the statement are replaced with the id of the object. +// -- Instances of "##" are replaced with the value of . +// +// There are also some return values that get special treatment. +// -- An empty result will produce a realtime error message. +// -- A result of "~~" will leave the original field value unchanged. +// -- A result of "~0" will clear the original field to "" without producing an error message. +// +void SimDataBlock::performSubstitutions(SimDataBlock* dblock, const SimObject* obj, S32 index) +{ + if (!dblock || !dblock->getClassRep()) + { + // error message + return; + } + + char obj_str[32]; + dStrcpy(obj_str, Con::getIntArg(obj->getId())); + + char index_str[32]; + dStrcpy(index_str, Con::getIntArg(index)); + + for (S32 i = 0; i < substitutions.size(); i++) + { + if (substitutions[i]) + { + static char buffer[1024]; + static char* b_oob = &buffer[1024]; + char* b = buffer; + + // perform special token expansion (%% and ##) + const char* v = substitutions[i]->value; + while (*v != '\0') + { + // identify "%%" tokens and replace with id + if (v[0] == '%' && v[1] == '%') + { + const char* s = obj_str; + while (*s != '\0') + { + b[0] = s[0]; + b++; + s++; + } + v += 2; + } + // identify "##" tokens and replace with value + else if (v[0] == '#' && v[1] == '#') + { + const char* s = index_str; + while (*s != '\0') + { + b[0] = s[0]; + b++; + s++; + } + v += 2; + } + else + { + b[0] = v[0]; + b++; + v++; + } + } + + AssertFatal((uintptr_t)b < (uintptr_t)b_oob, "Substitution buffer overflowed"); + + b[0] = '\0'; + + // perform the statement evaluation + Compiler::gSyntaxError = false; + //Con::errorf("EVAL [%s]", avar("return %s;", buffer)); + const char *result = Con::evaluate(avar("return %s;", buffer), false, 0); + if (Compiler::gSyntaxError) + { + Con::errorf("Field Substitution Failed: field=\"%s\" substitution=\"%s\" -- syntax error", + substitutions[i]->slot, substitutions[i]->value); + Compiler::gSyntaxError = false; + return; + } + + // output a runtime console error when a substitution produces and empty result. + if (result == 0 || result[0] == '\0') + { + Con::errorf("Field Substitution Failed: field=\"%s\" substitution=\"%s\" -- empty result", + substitutions[i]->slot, substitutions[i]->value); + return; + } + + // handle special return values + if (result[0] == '~') + { + // if value is "~~" then keep the existing value + if (result[1] == '~' && result[2] == '\0') + continue; + // if "~0" then clear it + if (result[1] == '0' && result[2] == '\0') + result = ""; + } + + const AbstractClassRep::Field* field = dblock->getClassRep()->findField(substitutions[i]->slot); + if (!field) + { + // this should be very unlikely... + Con::errorf("Field Substitution Failed: unknown field, \"%s\".", substitutions[i]->slot); + continue; + } + + if (field->keepClearSubsOnly && result[0] != '\0') + { + Con::errorf("Field Substitution Failed: field \"%s\" of datablock %s only allows \"$$ ~~\" (keep) and \"$$ ~0\" (clear) field substitutions. [%s]", + substitutions[i]->slot, this->getClassName(), this->getName()); + continue; + } + + // substitute the field value with its replacement + Con::setData(field->type, (void*)(((const char*)(dblock)) + field->offset), substitutions[i]->idx, 1, &result, field->table, field->flag); + + //dStrncpy(buffer, result, 255); + //Con::errorf("SUBSTITUTION %s.%s[%d] = %s idx=%s", Con::getIntArg(getId()), substitutions[i]->slot, substitutions[i]->idx, buffer, index_str); + + // notify subclasses of a field modification + dblock->onStaticModified(substitutions[i]->slot); + } + } + + // notify subclasses of substitution operation + if (substitutions.size() > 0) + dblock->onPerformSubstitutions(); +} //----------------------------------------------------------------------------- @@ -96,14 +356,37 @@ void SimDataBlock::onStaticModified(const char* slotName, const char* newValue) //----------------------------------------------------------------------------- -void SimDataBlock::packData(BitStream*) +// packData() and unpackData() do nothing in the stock implementation, but here +// they've been modified to pack and unpack any substitution statements. +// +void SimDataBlock::packData(BitStream* stream) { + for (S32 i = 0; i < substitutions.size(); i++) + { + if (substitutions[i]) + { + stream->writeFlag(true); + stream->writeString(substitutions[i]->slot); + stream->write(substitutions[i]->idx); + stream->writeString(substitutions[i]->value); + } + } + stream->writeFlag(false); } -//----------------------------------------------------------------------------- - -void SimDataBlock::unpackData(BitStream*) +void SimDataBlock::unpackData(BitStream* stream) { + clear_substitutions(); + while(stream->readFlag()) + { + char slotName[256]; + S32 idx; + char value[256]; + stream->readString(slotName); + stream->read(&idx); + stream->readString(value); + substitutions.push_back(new SubstitutionStatement(StringTable->insert(slotName), idx, value)); + } } //----------------------------------------------------------------------------- diff --git a/Engine/source/console/simDatablock.h b/Engine/source/console/simDatablock.h index 3d7acc777..d6a7dcd52 100644 --- a/Engine/source/console/simDatablock.h +++ b/Engine/source/console/simDatablock.h @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #ifndef _SIMDATABLOCK_H_ #define _SIMDATABLOCK_H_ @@ -172,6 +176,32 @@ public: /// Used by the console system to automatically tell datablock classes apart /// from non-datablock classes. static const bool __smIsDatablock = true; +protected: + struct SubstitutionStatement + { + StringTableEntry slot; + S32 idx; + char* value; + SubstitutionStatement(StringTableEntry slot, S32 idx, const char* value); + ~SubstitutionStatement(); + void replaceValue(const char* value); + }; + Vector substitutions; + void clear_substitutions(); +public: + /*C*/ SimDataBlock(const SimDataBlock&, bool = false); + /*D*/ ~SimDataBlock(); + + void addSubstitution(StringTableEntry field, S32 idx, const char* subst); + const char* getSubstitution(StringTableEntry field, S32 idx); + S32 getSubstitutionCount() { return substitutions.size(); } + void performSubstitutions(SimDataBlock*, const SimObject*, S32 index=0); + void copySubstitutionsFrom(SimDataBlock* other); + void printSubstitutions(); + bool fieldHasSubstitution(StringTableEntry slot); + virtual void onAddSubstitution(StringTableEntry, S32 idx, const char* subst) { } + virtual void onRemoveSubstitution(StringTableEntry, S32 idx) { } + virtual void onPerformSubstitutions() { } }; //--------------------------------------------------------------------------- diff --git a/Engine/source/console/simFieldDictionary.cpp b/Engine/source/console/simFieldDictionary.cpp index 9615c59d0..7cf3deb94 100644 --- a/Engine/source/console/simFieldDictionary.cpp +++ b/Engine/source/console/simFieldDictionary.cpp @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #include "platform/platform.h" #include "console/simFieldDictionary.h" @@ -361,4 +365,62 @@ SimFieldDictionary::Entry* SimFieldDictionaryIterator::operator++() SimFieldDictionary::Entry* SimFieldDictionaryIterator::operator*() { return(mEntry); -} \ No newline at end of file +} +// A variation of the stock SimFieldDictionary::setFieldValue(), this method adds the +// argument which, when true, prohibits the replacement of fields that +// already have a value. +// +// AFX uses this when an effects-choreographer (afxMagicSpell, afxEffectron) is created +// using the new operator. It prevents any in-line effect parameters from being overwritten +// by default parameters that are copied over later. +void SimFieldDictionary::setFieldValue(StringTableEntry slotName, const char *value, ConsoleBaseType *type, bool no_replace) +{ + if (!no_replace) + { + setFieldValue(slotName, value); + return; + } + + if (!value || !*value) + return; + + U32 bucket = getHashValue(slotName); + Entry **walk = &mHashTable[bucket]; + while(*walk && (*walk)->slotName != slotName) + walk = &((*walk)->next); + + Entry *field = *walk; + if (field) + return; + + addEntry( bucket, slotName, type, dStrdup( value ) ); +} +// A variation of the stock SimFieldDictionary::assignFrom(), this method adds +// and arguments. When true, prohibits the replacement of fields that already +// have a value. When is specified, only fields with leading characters that exactly match +// the characters in are copied. +void SimFieldDictionary::assignFrom(SimFieldDictionary *dict, const char* filter, bool no_replace) +{ + dsize_t filter_len = (filter) ? dStrlen(filter) : 0; + if (filter_len == 0 && !no_replace) + { + assignFrom(dict); + return; + } + + mVersion++; + + if (filter_len == 0) + { + for(U32 i = 0; i < HashTableSize; i++) + for(Entry *walk = dict->mHashTable[i];walk; walk = walk->next) + setFieldValue(walk->slotName, walk->value, walk->type, no_replace); + } + else + { + for(U32 i = 0; i < HashTableSize; i++) + for(Entry *walk = dict->mHashTable[i];walk; walk = walk->next) + if (dStrncmp(walk->slotName, filter, filter_len) == 0) + setFieldValue(walk->slotName, walk->value, walk->type, no_replace); + } +} diff --git a/Engine/source/console/simFieldDictionary.h b/Engine/source/console/simFieldDictionary.h index bc865398c..4849be563 100644 --- a/Engine/source/console/simFieldDictionary.h +++ b/Engine/source/console/simFieldDictionary.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _SIMFIELDDICTIONARY_H_ #define _SIMFIELDDICTIONARY_H_ @@ -90,6 +95,8 @@ public: U32 getNumFields() const { return mNumFields; } Entry *operator[](U32 index); + void setFieldValue(StringTableEntry slotName, const char *value, ConsoleBaseType *type, bool no_replace); + void assignFrom(SimFieldDictionary *dict, const char* filter, bool no_replace); }; class SimFieldDictionaryIterator diff --git a/Engine/source/console/simObject.cpp b/Engine/source/console/simObject.cpp index 73a1ffa3d..c12a12f19 100644 --- a/Engine/source/console/simObject.cpp +++ b/Engine/source/console/simObject.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "platform/platformMemory.h" #include "console/simObject.h" @@ -48,6 +53,7 @@ ConsoleDocClass( SimObject, bool SimObject::smForceId = false; SimObjectId SimObject::smForcedId = 0; +bool SimObject::preventNameChanging = false; namespace Sim { @@ -88,12 +94,17 @@ SimObject::SimObject() mCopySource = NULL; mPersistentId = NULL; + is_temp_clone = false; } //----------------------------------------------------------------------------- SimObject::~SimObject() { + // if this is a temp-clone, we don't delete any members that were shallow-copied + // over from the source datablock. + if (is_temp_clone) + return; if( mFieldDictionary ) { delete mFieldDictionary; @@ -212,6 +223,19 @@ String SimObject::describeSelf() const return desc; } +// Copies dynamic fields from one object to another, optionally limited by the settings for +// and . When true, prohibits the replacement of fields that +// already have a value. When is specified, only fields with leading characters that +// exactly match the characters in are copied. +void SimObject::assignDynamicFieldsFrom(SimObject* from, const char* filter, bool no_replace) +{ + if (from->mFieldDictionary) + { + if( mFieldDictionary == NULL ) + mFieldDictionary = new SimFieldDictionary; + mFieldDictionary->assignFrom(from->mFieldDictionary, filter, no_replace); + } +} //============================================================================= // Persistence. //============================================================================= @@ -915,6 +939,29 @@ void SimObject::setDataField(StringTableEntry slotName, const char *array, const S32 array1 = array ? dAtoi(array) : 0; + // Here we check to see if is a datablock and if + // starts with "$$". If both true than save value as a runtime substitution. + if (dynamic_cast(this) && value[0] == '$' && value[1] == '$') + { + if (!this->allowSubstitutions()) + { + Con::errorf("Substitution Error: %s datablocks do not allow \"$$\" field substitutions. [%s]", + this->getClassName(), this->getName()); + return; + } + + if (fld->doNotSubstitute) + { + Con::errorf("Substitution Error: field \"%s\" of datablock %s prohibits \"$$\" field substitutions. [%s]", + slotName, this->getClassName(), this->getName()); + return; + } + + // add the substitution + ((SimDataBlock*)this)->addSubstitution(slotName, array1, value); + return; + } + if(array1 >= 0 && array1 < fld->elementCount && fld->elementCount >= 1) { // If the set data notify callback returns true, then go ahead and @@ -1300,6 +1347,43 @@ void SimObject::setDataFieldType(const char *typeName, StringTableEntry slotName } } +// This is the copy-constructor used to create temporary datablock clones. +// The argument is added to distinguish this copy-constructor +// from any general-purpose copy-constructor that might be needed in the +// future. should always be true when creating temporary +// datablock clones. +// +SimObject::SimObject(const SimObject& other, bool temp_clone) +{ + is_temp_clone = temp_clone; + + objectName = other.objectName; + mOriginalName = other.mOriginalName; + nextNameObject = other.nextNameObject; + nextManagerNameObject = other.nextManagerNameObject; + nextIdObject = other.nextIdObject; + mGroup = other.mGroup; + mFlags = other.mFlags; + mCopySource = other.mCopySource; + mFieldDictionary = other.mFieldDictionary; + //mIdString = other.mIdString; // special treatment (see below) + mFilename = other.mFilename; + mDeclarationLine = other.mDeclarationLine; + mNotifyList = other.mNotifyList; + mId = other.mId; + mInternalName = other.mInternalName; + mCanSaveFieldDictionary = other.mCanSaveFieldDictionary; + mPersistentId = other.mPersistentId; + mNameSpace = other.mNameSpace; + mClassName = other.mClassName; + mSuperClassName = other.mSuperClassName; + preventNameChanging = other.preventNameChanging; + + if (mId) + dSprintf( mIdString, sizeof( mIdString ), "%u", mId ); + else + mIdString[ 0 ] = '\0'; +} //----------------------------------------------------------------------------- void SimObject::dumpClassHierarchy() @@ -2116,6 +2200,8 @@ bool SimObject::setProtectedParent( void *obj, const char *index, const char *da bool SimObject::setProtectedName(void *obj, const char *index, const char *data) { + if (preventNameChanging) + return false; SimObject *object = static_cast(obj); if ( object->isProperlyAdded() ) @@ -2612,6 +2698,16 @@ DefineEngineMethod( SimObject, dump, void, ( bool detailed ), ( false ), } } + // If the object is a datablock with substitution statements, + // they get printed out as part of the dump. + if (dynamic_cast(object)) + { + if (((SimDataBlock*)object)->getSubstitutionCount() > 0) + { + Con::printf("Substitution Fields:"); + ((SimDataBlock*)object)->printSubstitutions(); + } + } Con::printf( "Dynamic Fields:" ); if(object->getFieldDictionary()) object->getFieldDictionary()->printFields(object); diff --git a/Engine/source/console/simObject.h b/Engine/source/console/simObject.h index 6cba1beff..2119ea6a9 100644 --- a/Engine/source/console/simObject.h +++ b/Engine/source/console/simObject.h @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #ifndef _SIMOBJECT_H_ #define _SIMOBJECT_H_ @@ -970,6 +974,19 @@ class SimObject: public ConsoleObject, public TamlCallbacks // EngineObject. virtual void destroySelf(); +protected: + bool is_temp_clone; +public: + /*C*/ SimObject(const SimObject&, bool = false); + bool isTempClone() const { return is_temp_clone; } + virtual bool allowSubstitutions() const { return false; } + +public: + static bool preventNameChanging; + void assignDynamicFieldsFrom(SimObject*, const char* filter, bool no_replace=false); + +public: + virtual void reloadReset() { } }; diff --git a/Engine/source/core/stream/bitStream.h b/Engine/source/core/stream/bitStream.h index 7f6fe82ca..93287b938 100644 --- a/Engine/source/core/stream/bitStream.h +++ b/Engine/source/core/stream/bitStream.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _BITSTREAM_H_ #define _BITSTREAM_H_ @@ -254,6 +259,7 @@ public: U32 getPosition() const; bool setPosition(const U32 in_newPosition); U32 getStreamSize(); + S32 getMaxWriteBitNum() const { return maxWriteBitNum; } }; class ResizeBitStream : public BitStream diff --git a/Engine/source/environment/meshRoad.cpp b/Engine/source/environment/meshRoad.cpp index 7fef4a217..fc1619afb 100644 --- a/Engine/source/environment/meshRoad.cpp +++ b/Engine/source/environment/meshRoad.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "environment/meshRoad.h" @@ -52,6 +57,8 @@ #include "T3D/physics/physicsCollision.h" #include "environment/nodeListManager.h" +#include "afx/ce/afxZodiacMgr.h" + #define MIN_METERS_PER_SEGMENT 1.0f #define MIN_NODE_DEPTH 0.25f #define MAX_NODE_DEPTH 50.0f @@ -620,6 +627,7 @@ MeshRoad::MeshRoad() mMatInst[Top] = NULL; mMatInst[Bottom] = NULL; mMatInst[Side] = NULL; + mTypeMask |= TerrainLikeObjectType; } MeshRoad::~MeshRoad() @@ -821,6 +829,7 @@ void MeshRoad::prepRenderImage( SceneRenderState* state ) // otherwise obey the smShowRoad flag if ( smShowRoad || !smEditorOpen ) { + afxZodiacMgr::renderMeshRoadZodiacs(state, this); MeshRenderInst coreRI; coreRI.clear(); coreRI.objectToWorld = &MatrixF::Identity; @@ -1379,6 +1388,11 @@ bool MeshRoad::buildSegmentPolyList( AbstractPolyList* polyList, U32 startSegIdx ddraw->setLastTTL( 0 ); } + if (buildPolyList_TopSurfaceOnly) + { + offset += 4; + continue; + } // Left Face polyList->begin( 0,0 ); @@ -2454,3 +2468,16 @@ DefineEngineMethod( MeshRoad, postApply, void, (),, { object->inspectPostApply(); } +bool MeshRoad::buildPolyList_TopSurfaceOnly = false; + +bool MeshRoad::buildTopPolyList(PolyListContext plc, AbstractPolyList* polyList) +{ + static Box3F box_prox; static SphereF ball_prox; + + buildPolyList_TopSurfaceOnly = true; + bool result = buildPolyList(plc, polyList, box_prox, ball_prox); + buildPolyList_TopSurfaceOnly = false; + + return result; +} + diff --git a/Engine/source/environment/meshRoad.h b/Engine/source/environment/meshRoad.h index ed47984b1..06157b343 100644 --- a/Engine/source/environment/meshRoad.h +++ b/Engine/source/environment/meshRoad.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _MESHROAD_H_ #define _MESHROAD_H_ @@ -560,6 +565,10 @@ protected: Convex* mConvexList; Vector mDebugConvex; PhysicsBody *mPhysicsRep; +private: + static bool buildPolyList_TopSurfaceOnly; +public: + bool buildTopPolyList(PolyListContext, AbstractPolyList*); }; diff --git a/Engine/source/gui/core/guiCanvas.cpp b/Engine/source/gui/core/guiCanvas.cpp index f00cf6cca..82c0475f0 100644 --- a/Engine/source/gui/core/guiCanvas.cpp +++ b/Engine/source/gui/core/guiCanvas.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "gui/core/guiCanvas.h" @@ -605,10 +610,11 @@ bool GuiCanvas::tabPrev(void) bool GuiCanvas::processInputEvent(InputEventInfo &inputEvent) { + mConsumeLastInputEvent = true; // First call the general input handler (on the extremely off-chance that it will be handled): if (mFirstResponder && mFirstResponder->onInputEvent(inputEvent)) { - return(true); + return mConsumeLastInputEvent; } switch (inputEvent.deviceType) @@ -664,7 +670,7 @@ bool GuiCanvas::processKeyboardEvent(InputEventInfo &inputEvent) if (mFirstResponder) { if(mFirstResponder->onKeyDown(mLastEvent)) - return true; + return mConsumeLastInputEvent; } //see if we should tab next/prev @@ -675,12 +681,12 @@ bool GuiCanvas::processKeyboardEvent(InputEventInfo &inputEvent) if (inputEvent.modifier & SI_SHIFT) { if(tabPrev()) - return true; + return mConsumeLastInputEvent; } else if (inputEvent.modifier == 0) { if(tabNext()) - return true; + return mConsumeLastInputEvent; } } } @@ -691,14 +697,14 @@ bool GuiCanvas::processKeyboardEvent(InputEventInfo &inputEvent) if ((U32)mAcceleratorMap[i].keyCode == (U32)inputEvent.objInst && (U32)mAcceleratorMap[i].modifier == eventModifier) { mAcceleratorMap[i].ctrl->acceleratorKeyPress(mAcceleratorMap[i].index); - return true; + return mConsumeLastInputEvent; } } } else if(inputEvent.action == SI_BREAK) { if(mFirstResponder && mFirstResponder->onKeyUp(mLastEvent)) - return true; + return mConsumeLastInputEvent; //see if there's an accelerator for (U32 i = 0; i < mAcceleratorMap.size(); i++) @@ -706,7 +712,7 @@ bool GuiCanvas::processKeyboardEvent(InputEventInfo &inputEvent) if ((U32)mAcceleratorMap[i].keyCode == (U32)inputEvent.objInst && (U32)mAcceleratorMap[i].modifier == eventModifier) { mAcceleratorMap[i].ctrl->acceleratorKeyRelease(mAcceleratorMap[i].index); - return true; + return mConsumeLastInputEvent; } } } @@ -718,13 +724,14 @@ bool GuiCanvas::processKeyboardEvent(InputEventInfo &inputEvent) if ((U32)mAcceleratorMap[i].keyCode == (U32)inputEvent.objInst && (U32)mAcceleratorMap[i].modifier == eventModifier) { mAcceleratorMap[i].ctrl->acceleratorKeyPress(mAcceleratorMap[i].index); - return true; + return mConsumeLastInputEvent; } } if(mFirstResponder) { - return mFirstResponder->onKeyRepeat(mLastEvent); + bool ret = mFirstResponder->onKeyRepeat(mLastEvent); + return ret && mConsumeLastInputEvent; } } return false; @@ -801,7 +808,7 @@ bool GuiCanvas::processMouseEvent(InputEventInfo &inputEvent) rootMiddleMouseDragged(mLastEvent); else rootMouseMove(mLastEvent); - return true; + return mConsumeLastInputEvent; } else if ( inputEvent.objInst == SI_ZAXIS || inputEvent.objInst == SI_RZAXIS ) @@ -860,7 +867,7 @@ bool GuiCanvas::processMouseEvent(InputEventInfo &inputEvent) rootMouseUp(mLastEvent); } - return true; + return mConsumeLastInputEvent; } else if(inputEvent.objInst == KEY_BUTTON1) // right button { @@ -891,7 +898,7 @@ bool GuiCanvas::processMouseEvent(InputEventInfo &inputEvent) else // it was a mouse up rootRightMouseUp(mLastEvent); - return true; + return mConsumeLastInputEvent; } else if(inputEvent.objInst == KEY_BUTTON2) // middle button { @@ -922,7 +929,7 @@ bool GuiCanvas::processMouseEvent(InputEventInfo &inputEvent) else // it was a mouse up rootMiddleMouseUp(mLastEvent); - return true; + return mConsumeLastInputEvent; } } return false; @@ -1801,7 +1808,7 @@ void GuiCanvas::renderFrame(bool preRenderOnly, bool bufferSwap /* = true */) if (GuiOffscreenCanvas::sList.size() != 0) { // Reset the entire state since oculus shit will have barfed it. - GFX->disableShaders(true); + //GFX->disableShaders(true); GFX->updateStates(true); for (Vector::iterator itr = GuiOffscreenCanvas::sList.begin(); itr != GuiOffscreenCanvas::sList.end(); itr++) @@ -2815,3 +2822,23 @@ ConsoleMethod( GuiCanvas, cursorNudge, void, 4, 4, "x, y" ) { object->cursorNudge(dAtof(argv[2]), dAtof(argv[3])); } +// This function allows resetting of the video-mode from script. It was motivated by +// the need to temporarily disable vsync during datablock cache load to avoid a +// significant slowdown. +bool AFX_forceVideoReset = false; + +ConsoleMethod( GuiCanvas, resetVideoMode, void, 2,2, "()") +{ + PlatformWindow* window = object->getPlatformWindow(); + if( window ) + { + GFXWindowTarget* gfx_target = window->getGFXTarget(); + if ( gfx_target ) + { + AFX_forceVideoReset = true; + gfx_target->resetMode(); + AFX_forceVideoReset = false; + } + } +} + diff --git a/Engine/source/gui/core/guiCanvas.h b/Engine/source/gui/core/guiCanvas.h index fa213f56a..7775b8af0 100644 --- a/Engine/source/gui/core/guiCanvas.h +++ b/Engine/source/gui/core/guiCanvas.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _GUICANVAS_H_ #define _GUICANVAS_H_ @@ -446,6 +451,13 @@ public: private: static const U32 MAX_GAMEPADS = 4; ///< The maximum number of supported gamepads + protected: + bool mConsumeLastInputEvent; + public: + void clearMouseRightButtonDown(void) { mMouseRightButtonDown = false; } + void clearMouseButtonDown(void) { mMouseButtonDown = false; } + void setConsumeLastInputEvent(bool flag) { mConsumeLastInputEvent = flag; } + bool getLastCursorPoint(Point2I& pt) const { pt = mLastCursorPt; return mLastCursorEnabled; } }; #endif diff --git a/Engine/source/gui/core/guiControl.cpp b/Engine/source/gui/core/guiControl.cpp index fb833ca7b..968fc9a16 100644 --- a/Engine/source/gui/core/guiControl.cpp +++ b/Engine/source/gui/core/guiControl.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "gui/core/guiControl.h" @@ -224,6 +229,7 @@ GuiControl::GuiControl() : mAddGroup( NULL ), mCanSaveFieldDictionary = false; mNotifyChildrenResized = true; + fade_amt = 1.0f; } //----------------------------------------------------------------------------- diff --git a/Engine/source/gui/core/guiControl.h b/Engine/source/gui/core/guiControl.h index fa3a327dd..eae37a8df 100644 --- a/Engine/source/gui/core/guiControl.h +++ b/Engine/source/gui/core/guiControl.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _GUICONTROL_H_ #define _GUICONTROL_H_ @@ -822,6 +827,10 @@ class GuiControl : public SimGroup void inspectPostApply(); void inspectPreApply(); +protected: + F32 fade_amt; +public: + void setFadeAmount(F32 amt) { fade_amt = amt; } }; typedef GuiControl::horizSizingOptions GuiHorizontalSizing; diff --git a/Engine/source/materials/baseMatInstance.h b/Engine/source/materials/baseMatInstance.h index 6a2bee0a1..bca878dc2 100644 --- a/Engine/source/materials/baseMatInstance.h +++ b/Engine/source/materials/baseMatInstance.h @@ -252,6 +252,11 @@ public: virtual const GFXStateBlockDesc &getUserStateBlock() const = 0; +protected: + bool needsHighlighting; +public: + bool needsSelectionHighlighting() { return needsHighlighting; }; + void setSelectionHighlighting(bool flag) { needsHighlighting = flag; }; }; #endif /// _BASEMATINSTANCE_H_ diff --git a/Engine/source/materials/matInstance.cpp b/Engine/source/materials/matInstance.cpp index 33b6357c9..e242a2059 100644 --- a/Engine/source/materials/matInstance.cpp +++ b/Engine/source/materials/matInstance.cpp @@ -253,6 +253,7 @@ void MatInstance::construct() mIsForwardLit = false; mIsValid = false; mIsHardwareSkinned = false; + needsHighlighting = false; MATMGR->_track(this); } diff --git a/Engine/source/materials/materialList.cpp b/Engine/source/materials/materialList.cpp index 65aade9e5..4ddb75bbb 100644 --- a/Engine/source/materials/materialList.cpp +++ b/Engine/source/materials/materialList.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "materials/materialList.h" @@ -309,7 +314,12 @@ void MaterialList::mapMaterial( U32 i ) return; } - String materialName = MATMGR->getMapEntry(matName); + String materialName; + // Skip past a leading '#' marker. + if (matName.compare("#", 1) == 0) + materialName = MATMGR->getMapEntry(matName.substr(1, matName.length()-1)); + else + materialName = MATMGR->getMapEntry(matName); // IF we didn't find it, then look for a PolyStatic generated Material // [a little cheesy, but we need to allow for user override of generated Materials] diff --git a/Engine/source/materials/sceneData.h b/Engine/source/materials/sceneData.h index 99fc8ef52..fd8b01633 100644 --- a/Engine/source/materials/sceneData.h +++ b/Engine/source/materials/sceneData.h @@ -52,6 +52,9 @@ struct SceneData /// The deferred render bin. /// @RenderDeferredMgr DeferredBin, + /// The selection-highlight render bin. + /// @afxRenderHighlightMgr + HighlightBin, }; /// This defines when we're rendering a special bin diff --git a/Engine/source/scene/sceneContainer.h b/Engine/source/scene/sceneContainer.h index 0033d7cab..a98548964 100644 --- a/Engine/source/scene/sceneContainer.h +++ b/Engine/source/scene/sceneContainer.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _SCENECONTAINER_H_ #define _SCENECONTAINER_H_ @@ -304,6 +309,8 @@ class SceneContainer void _findSpecialObjects( const Vector< SceneObject* >& vector, const Box3F &box, U32 mask, FindCallback callback, void *key = NULL ); static void getBinRange( const F32 min, const F32 max, U32& minBin, U32& maxBin ); +public: + Vector*>& getRadiusSearchList() { return mSearchList; } }; //----------------------------------------------------------------------------- diff --git a/Engine/source/scene/sceneObject.cpp b/Engine/source/scene/sceneObject.cpp index 6bd796607..550ee80b4 100644 --- a/Engine/source/scene/sceneObject.cpp +++ b/Engine/source/scene/sceneObject.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "scene/sceneObject.h" @@ -144,6 +149,7 @@ SceneObject::SceneObject() mIsScopeAlways = false; mAccuTex = NULL; + mSelectionFlags = 0; mPathfindingIgnore = false; } diff --git a/Engine/source/scene/sceneObject.h b/Engine/source/scene/sceneObject.h index 3985d372c..bf04ea4c4 100644 --- a/Engine/source/scene/sceneObject.h +++ b/Engine/source/scene/sceneObject.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _SCENEOBJECT_H_ #define _SCENEOBJECT_H_ @@ -778,6 +783,23 @@ class SceneObject : public NetObject, private SceneContainer::Link, public Proce // Note: This was placed in SceneObject to both ShapeBase and TSStatic could support it. public: GFXTextureObject* mAccuTex; + // mSelectionFlags field keeps track of flags related to object selection. + // PRE_SELECTED marks an object as pre-selected (object under cursor) + // SELECTED marks an object as selected (a target) + protected: + U8 mSelectionFlags; + public: + enum { + SELECTED = BIT(0), + PRE_SELECTED = BIT(1), + }; + virtual void setSelectionFlags(U8 flags) { mSelectionFlags = flags; } + U8 getSelectionFlags() const { return mSelectionFlags; } + bool needsSelectionHighlighting() const { return (mSelectionFlags != 0); } + // This should only return true if the object represents an independent camera + // as opposed to something like a Player that has a built-in camera that requires + // special calculations to determine the view transform. + virtual bool isCamera() const { return false; } }; #endif // _SCENEOBJECT_H_ diff --git a/Engine/source/sfx/sfxDescription.cpp b/Engine/source/sfx/sfxDescription.cpp index 1b4e0dead..1c82ba6a5 100644 --- a/Engine/source/sfx/sfxDescription.cpp +++ b/Engine/source/sfx/sfxDescription.cpp @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #include "platform/platform.h" #include "sfx/sfxDescription.h" @@ -176,6 +180,37 @@ SFXDescription::SFXDescription( const SFXDescription& desc ) //----------------------------------------------------------------------------- +SFXDescription::SFXDescription(const SFXDescription& other, bool temp_clone) + : SimDataBlock(other, temp_clone), + mVolume( other.mVolume ), + mPitch( other.mPitch ), + mIsLooping( other.mIsLooping ), + mIsStreaming( other.mIsStreaming ), + mIs3D( other.mIs3D ), + mUseHardware( other.mUseHardware ), + mMinDistance( other.mMinDistance ), + mMaxDistance( other.mMaxDistance ), + mConeInsideAngle( other.mConeInsideAngle ), + mConeOutsideAngle( other.mConeOutsideAngle ), + mConeOutsideVolume( other.mConeOutsideVolume ), + mRolloffFactor( other.mRolloffFactor ), + mSourceGroup( other.mSourceGroup ), + mFadeInTime( other.mFadeInTime ), + mFadeOutTime( other.mFadeOutTime ), + mFadeInEase( other.mFadeInEase ), + mFadeOutEase( other.mFadeOutEase ), + mFadeLoops( other.mFadeLoops ), + mStreamPacketSize( other.mStreamPacketSize ), + mStreamReadAhead( other.mStreamReadAhead ), + mUseReverb( other.mUseReverb ), + mReverb( other.mReverb ), + mPriority( other.mPriority ), + mScatterDistance( other.mScatterDistance ) +{ + for( U32 i = 0; i < MaxNumParameters; ++ i ) + mParameters[ i ] = other.mParameters[ i ]; +} + void SFXDescription::initPersistFields() { addGroup( "Playback" ); @@ -652,3 +687,24 @@ void SFXDescription::inspectPostApply() if( SFX ) SFX->notifyDescriptionChanged( this ); } +// This allows legacy AudioDescription datablocks to be recognized as an alias +// for SFXDescription. It is intended to ease the transition from older scripts +// especially those that still need to support pre-1.7 applications. +// (This maybe removed in future releases so treat as deprecated.) +class AudioDescription : public SFXDescription +{ + typedef SFXDescription Parent; +public: + DECLARE_CONOBJECT(AudioDescription); +}; + +IMPLEMENT_CO_DATABLOCK_V1(AudioDescription); + +ConsoleDocClass( AudioDescription, + "@brief Allows legacy AudioDescription datablocks to be treated as SFXDescription datablocks.\n\n" + + "@ingroup afxMisc\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + diff --git a/Engine/source/sfx/sfxDescription.h b/Engine/source/sfx/sfxDescription.h index 8bac79467..003faee27 100644 --- a/Engine/source/sfx/sfxDescription.h +++ b/Engine/source/sfx/sfxDescription.h @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #ifndef _SFXDESCRIPTION_H_ #define _SFXDESCRIPTION_H_ @@ -192,6 +196,9 @@ class SFXDescription : public SimDataBlock /// Validates the description fixing any /// parameters that are out of range. void validate(); + public: + SFXDescription(const SFXDescription&, bool); + virtual bool allowSubstitutions() const { return true; } }; diff --git a/Engine/source/sfx/sfxPlayList.h b/Engine/source/sfx/sfxPlayList.h index 0d9acf5c3..6bde33734 100644 --- a/Engine/source/sfx/sfxPlayList.h +++ b/Engine/source/sfx/sfxPlayList.h @@ -81,7 +81,7 @@ class SFXPlayList : public SFXTrack /// /// @note To have longer playlists, simply cascade playlists and use /// wait behaviors. - NUM_SLOTS = 16, + NUM_SLOTS = 12, // AFX (was 16) NUM_TRANSITION_MODE_BITS = 3, NUM_LOOP_MODE_BITS = 1, diff --git a/Engine/source/sfx/sfxProfile.cpp b/Engine/source/sfx/sfxProfile.cpp index bbb40c249..d9cc3d56f 100644 --- a/Engine/source/sfx/sfxProfile.cpp +++ b/Engine/source/sfx/sfxProfile.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "sfx/sfxProfile.h" @@ -92,9 +97,6 @@ SFXProfile::SFXProfile( SFXDescription* desc, const String& filename, bool prelo //----------------------------------------------------------------------------- -SFXProfile::~SFXProfile() -{ -} //----------------------------------------------------------------------------- @@ -114,6 +116,9 @@ void SFXProfile::initPersistFields() endGroup( "Sound" ); + // disallow some field substitutions + disableFieldSubstitutions("description"); + Parent::initPersistFields(); } @@ -373,3 +378,116 @@ DefineEngineMethod( SFXProfile, getSoundDuration, F32, (),, { return ( F32 ) object->getSoundDuration() * 0.001f; } + +// enable this to help verify that temp-clones of AudioProfile are being deleted +//#define TRACK_AUDIO_PROFILE_CLONES + +#ifdef TRACK_AUDIO_PROFILE_CLONES +static int audio_prof_clones = 0; +#endif + +SFXProfile::SFXProfile(const SFXProfile& other, bool temp_clone) : SFXTrack(other, temp_clone) +{ +#ifdef TRACK_AUDIO_PROFILE_CLONES + audio_prof_clones++; + if (audio_prof_clones == 1) + Con::errorf("SFXProfile -- Clones are on the loose!"); +#endif + mResource = other.mResource; + mFilename = other.mFilename; + mPreload = other.mPreload; + mBuffer = other.mBuffer; // -- AudioBuffer loaded using mFilename + mChangedSignal = other.mChangedSignal; +} + +SFXProfile::~SFXProfile() +{ + if (!isTempClone()) + return; + + // cleanup after a temp-clone + + if (mDescription && mDescription->isTempClone()) + { + delete mDescription; + mDescription = 0; + } + +#ifdef TRACK_AUDIO_PROFILE_CLONES + if (audio_prof_clones > 0) + { + audio_prof_clones--; + if (audio_prof_clones == 0) + Con::errorf("SFXProfile -- Clones eliminated!"); + } + else + Con::errorf("SFXProfile -- Too many clones deleted!"); +#endif +} + +// Clone and perform substitutions on the SFXProfile and on any SFXDescription +// it references. +SFXProfile* SFXProfile::cloneAndPerformSubstitutions(const SimObject* owner, S32 index) +{ + if (!owner) + return this; + + SFXProfile* sub_profile_db = this; + + // look for mDescriptionObject subs + SFXDescription* desc_db; + if (mDescription && mDescription->getSubstitutionCount() > 0) + { + SFXDescription* orig_db = mDescription; + desc_db = new SFXDescription(*orig_db, true); + orig_db->performSubstitutions(desc_db, owner, index); + } + else + desc_db = 0; + + if (this->getSubstitutionCount() > 0 || desc_db) + { + sub_profile_db = new SFXProfile(*this, true); + performSubstitutions(sub_profile_db, owner, index); + if (desc_db) + sub_profile_db->mDescription = desc_db; + } + + return sub_profile_db; +} + +void SFXProfile::onPerformSubstitutions() +{ + if ( SFX ) + { + // If preload is enabled we load the resource + // and device buffer now to avoid a delay on + // first playback. + if ( mPreload && !_preloadBuffer() ) + Con::errorf( "SFXProfile(%s)::onPerformSubstitutions: The preload failed!", getName() ); + + // We need to get device change notifications. + SFX->getEventSignal().notify( this, &SFXProfile::_onDeviceEvent ); + } +} +// This allows legacy AudioProfile datablocks to be recognized as an alias +// for SFXProfile. It is intended to ease the transition from older scripts +// especially those that still need to support pre-1.7 applications. +// (This maybe removed in future releases so treat as deprecated.) +class AudioProfile : public SFXProfile +{ + typedef SFXProfile Parent; +public: + DECLARE_CONOBJECT(AudioProfile); +}; + +IMPLEMENT_CO_DATABLOCK_V1(AudioProfile); + +ConsoleDocClass( AudioProfile, + "@brief Allows legacy AudioProfile datablocks to be treated as SFXProfile datablocks.\n\n" + + "@ingroup afxMisc\n" + "@ingroup AFX\n" + "@ingroup Datablocks\n" +); + diff --git a/Engine/source/sfx/sfxProfile.h b/Engine/source/sfx/sfxProfile.h index 70ac4f097..ab1a4b1f0 100644 --- a/Engine/source/sfx/sfxProfile.h +++ b/Engine/source/sfx/sfxProfile.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _SFXPROFILE_H_ #define _SFXPROFILE_H_ @@ -175,6 +180,11 @@ class SFXProfile : public SFXTrack /// ChangedSignal& getChangedSignal() { return mChangedSignal; } + public: + /*C*/ SFXProfile(const SFXProfile&, bool = false); + SFXProfile* cloneAndPerformSubstitutions(const SimObject*, S32 index=0); + virtual void onPerformSubstitutions(); + virtual bool allowSubstitutions() const { return true; } }; diff --git a/Engine/source/sfx/sfxTrack.cpp b/Engine/source/sfx/sfxTrack.cpp index dde11bb26..067a202ca 100644 --- a/Engine/source/sfx/sfxTrack.cpp +++ b/Engine/source/sfx/sfxTrack.cpp @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #include "sfx/sfxTrack.h" #include "sfx/sfxTypes.h" #include "sfx/sfxDescription.h" @@ -65,6 +69,11 @@ SFXTrack::SFXTrack( SFXDescription* description ) dMemset( mParameters, 0, sizeof( mParameters ) ); } +SFXTrack::SFXTrack(const SFXTrack& other, bool temp_clone) : SimDataBlock(other, temp_clone) +{ + mDescription = other.mDescription; + dMemcpy(mParameters, other.mParameters, sizeof(mParameters)); +} //----------------------------------------------------------------------------- void SFXTrack::initPersistFields() diff --git a/Engine/source/sfx/sfxTrack.h b/Engine/source/sfx/sfxTrack.h index 13221dbb8..e30acd007 100644 --- a/Engine/source/sfx/sfxTrack.h +++ b/Engine/source/sfx/sfxTrack.h @@ -20,6 +20,10 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// #ifndef _SFXTRACK_H_ #define _SFXTRACK_H_ @@ -92,6 +96,8 @@ class SFXTrack : public SimDataBlock DECLARE_CONOBJECT( SFXTrack ); DECLARE_CATEGORY( "SFX" ); DECLARE_DESCRIPTION( "Abstract base class for any kind of data that can be turned into SFXSources." ); + public: + /*C*/ SFXTrack(const SFXTrack&, bool = false); }; #endif // !_SFXTRACK_H_ diff --git a/Engine/source/sim/netConnection.cpp b/Engine/source/sim/netConnection.cpp index f12c8cde6..c952a0258 100644 --- a/Engine/source/sim/netConnection.cpp +++ b/Engine/source/sim/netConnection.cpp @@ -316,7 +316,10 @@ void NetConnection::checkMaxRate() { packetRateToServer = 128; packetRateToClient = 128; - packetSize = 1024; + // These changes introduced in T3D 1.1 Preview reduce the packet headroom which leads + // to some spells and effects running out of room when dynamic variables are used + // to send launch-time parameters to clients. + packetSize = 512; } gPacketUpdateDelayToServer = 1024 / packetRateToServer; diff --git a/Engine/source/sim/netConnection.h b/Engine/source/sim/netConnection.h index c2e106fa3..55422f9eb 100644 --- a/Engine/source/sim/netConnection.h +++ b/Engine/source/sim/netConnection.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _NETCONNECTION_H_ #define _NETCONNECTION_H_ @@ -1050,6 +1055,9 @@ public: virtual bool readDemoStartBlock(BitStream *stream); virtual void demoPlaybackComplete(); /// @} +public: + S32 getCurRatePacketSize() const { return mCurRate.packetSize; } + S32 getMaxRatePacketSize() const { return mMaxRate.packetSize; } }; diff --git a/Engine/source/sim/netObject.cpp b/Engine/source/sim/netObject.cpp index cd432d717..76564fb16 100644 --- a/Engine/source/sim/netObject.cpp +++ b/Engine/source/sim/netObject.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "console/simBase.h" #include "core/dnet.h" @@ -28,6 +33,8 @@ #include "console/consoleTypes.h" #include "console/engineAPI.h" +#include "afx/arcaneFX.h" + IMPLEMENT_CONOBJECT(NetObject); // More information can be found in the Torque Manual (CHM) @@ -46,6 +53,9 @@ NetObject::NetObject() mPrevDirtyList = NULL; mNextDirtyList = NULL; mDirtyMaskBits = 0; + scope_id = 0; + scope_refs = 0; + scope_registered = false; } NetObject::~NetObject() @@ -460,3 +470,26 @@ DefineEngineMethod( NetObject, isServerObject, bool, (),, //{ // return object->isServerObject(); //} +U16 NetObject::addScopeRef() +{ + if (scope_refs == 0) + { + scope_id = arcaneFX::generateScopeId(); + onScopeIdChange(); + } + scope_refs++; + return scope_id; +} + +void NetObject::removeScopeRef() +{ + if (scope_refs == 0) + return; + scope_refs--; + if (scope_refs == 0) + { + scope_id = 0; + onScopeIdChange(); + } +} + diff --git a/Engine/source/sim/netObject.h b/Engine/source/sim/netObject.h index ced8a2cf3..a486936be 100644 --- a/Engine/source/sim/netObject.h +++ b/Engine/source/sim/netObject.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _NETOBJECT_H_ #define _NETOBJECT_H_ @@ -405,6 +410,18 @@ public: static T* getClientObject( T *netObj ) { return static_cast( netObj->getClientObject() ); } /// @} +protected: + U16 scope_id; + U16 scope_refs; + bool scope_registered; + virtual void onScopeIdChange() { } +public: + enum { SCOPE_ID_BITS = 14 }; + U16 getScopeId() const { return scope_id; } + U16 addScopeRef(); + void removeScopeRef(); + void setScopeRegistered(bool flag) { scope_registered = flag; } + bool getScopeRegistered() const { return scope_registered; } }; //----------------------------------------------------------------------------- diff --git a/Engine/source/terrain/terrCell.cpp b/Engine/source/terrain/terrCell.cpp index d96811f45..ba1262df1 100644 --- a/Engine/source/terrain/terrCell.cpp +++ b/Engine/source/terrain/terrCell.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "terrain/terrCell.h" @@ -56,6 +61,7 @@ TerrCell::TerrCell() mIsInteriorOnly( false ) { dMemset( mChildren, 0, sizeof( mChildren ) ); + zode_vertexBuffer = 0; } TerrCell::~TerrCell() @@ -64,6 +70,7 @@ TerrCell::~TerrCell() for ( U32 i=0; i < 4; i++ ) SAFE_DELETE( mChildren[i] ); + deleteZodiacVertexBuffer(); } void TerrCell::createPrimBuffer( GFXPrimitiveBufferHandle *primBuffer ) @@ -582,6 +589,7 @@ void TerrCell::_updateVertexBuffer() AssertFatal( vbcounter == smVBSize, "bad" ); mVertexBuffer.unlock(); + deleteZodiacVertexBuffer(); } void TerrCell::_updatePrimitiveBuffer() @@ -1089,3 +1097,114 @@ void TerrCell::deleteMaterials() if ( mChildren[i] ) mChildren[i]->deleteMaterials(); } + +const Point3F* TerrCell::getZodiacVertexBuffer() +{ + if (!zode_vertexBuffer) + createZodiacVertexBuffer(); + return zode_vertexBuffer; +} + +void TerrCell::createZodiacPrimBuffer(U16** zode_primBuffer) +{ + if (*zode_primBuffer != 0) + delete [] *zode_primBuffer; + + *zode_primBuffer = new U16[TerrCell::smMinCellSize*TerrCell::smMinCellSize*6]; + + // Lock and fill it up! + U16* idxBuff = *zode_primBuffer; + U32 counter = 0; + U32 maxIndex = 0; + + for ( U32 y = 0; y < smMinCellSize; y++ ) + { + const U32 yTess = y % 2; + + for ( U32 x = 0; x < smMinCellSize; x++ ) + { + U32 index = ( y * smVBStride ) + x; + + const U32 xTess = x % 2; + + if ( ( xTess == 0 && yTess == 0 ) || + ( xTess != 0 && yTess != 0 ) ) + { + idxBuff[0] = index + 0; + idxBuff[1] = index + smVBStride; + idxBuff[2] = index + smVBStride + 1; + + idxBuff[3] = index + 0; + idxBuff[4] = index + smVBStride + 1; + idxBuff[5] = index + 1; + } + else + { + idxBuff[0] = index + 1; + idxBuff[1] = index; + idxBuff[2] = index + smVBStride; + + idxBuff[3] = index + 1; + idxBuff[4] = index + smVBStride; + idxBuff[5] = index + smVBStride + 1; + } + + idxBuff += 6; + maxIndex = index + 1 + smVBStride; + counter += 6; + } + } +} + +void TerrCell::createZodiacVertexBuffer() +{ + const F32 squareSize = mTerrain->getSquareSize(); + const U32 blockSize = mTerrain->getBlockSize(); + const U32 stepSize = mSize / smMinCellSize; + + if (zode_vertexBuffer) + delete [] zode_vertexBuffer; + + zode_vertexBuffer = new Point3F[smVBStride*smVBStride]; + + Point3F* vert = zode_vertexBuffer; + + Point2I gridPt; + Point2F point; + F32 height; + + const TerrainFile *file = mTerrain->getFile(); + + for ( U32 y = 0; y < smVBStride; y++ ) + { + for ( U32 x = 0; x < smVBStride; x++ ) + { + // We clamp here to keep the geometry from reading across + // one side of the height map to the other causing walls + // around the edges of the terrain. + gridPt.x = mClamp( mPoint.x + x * stepSize, 0, blockSize - 1 ); + gridPt.y = mClamp( mPoint.y + y * stepSize, 0, blockSize - 1 ); + + // Setup this point. + point.x = (F32)gridPt.x * squareSize; + point.y = (F32)gridPt.y * squareSize; + height = fixedToFloat( file->getHeight( gridPt.x, gridPt.y ) ); + + vert->x = point.x; + vert->y = point.y; + vert->z = height; + + ++vert; + } + } +} + +void TerrCell::deleteZodiacVertexBuffer() +{ + if (zode_vertexBuffer) + { + delete [] zode_vertexBuffer; + zode_vertexBuffer = 0; + } +} + diff --git a/Engine/source/terrain/terrCell.h b/Engine/source/terrain/terrCell.h index f736c2915..0b948cedd 100644 --- a/Engine/source/terrain/terrCell.h +++ b/Engine/source/terrain/terrCell.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _TERRCELL_H_ #define _TERRCELL_H_ @@ -226,6 +231,13 @@ public: void renderBounds() const; /// @} +protected: + Point3F* zode_vertexBuffer; + void createZodiacVertexBuffer(); +public: + const Point3F* getZodiacVertexBuffer(); + void deleteZodiacVertexBuffer(); + static void createZodiacPrimBuffer(U16** primBuffer); }; inline F32 TerrCell::getDistanceTo( const Point3F &pt ) const diff --git a/Engine/source/terrain/terrData.cpp b/Engine/source/terrain/terrData.cpp index eff266385..a97ec7e98 100644 --- a/Engine/source/terrain/terrData.cpp +++ b/Engine/source/terrain/terrData.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "terrain/terrData.h" @@ -200,6 +205,8 @@ TerrainBlock::TerrainBlock() { mTypeMask = TerrainObjectType | StaticObjectType | StaticShapeObjectType; mNetFlags.set(Ghostable | ScopeAlways); + mIgnoreZodiacs = false; + zode_primBuffer = 0; } @@ -218,6 +225,7 @@ TerrainBlock::~TerrainBlock() if (editor) editor->detachTerrain(this); #endif + deleteZodiacPrimitiveBuffer(); } void TerrainBlock::_onTextureEvent( GFXTexCallbackCode code ) @@ -1006,6 +1014,7 @@ void TerrainBlock::_rebuildQuadtree() // Build the shared PrimitiveBuffer. mCell->createPrimBuffer( &mPrimBuffer ); + deleteZodiacPrimitiveBuffer(); } void TerrainBlock::_updatePhysics() @@ -1148,6 +1157,9 @@ void TerrainBlock::initPersistFields() endGroup( "Misc" ); + addGroup("AFX"); + addField("ignoreZodiacs", TypeBool, Offset(mIgnoreZodiacs, TerrainBlock)); + endGroup("AFX"); Parent::initPersistFields(); removeField( "scale" ); @@ -1198,6 +1210,7 @@ U32 TerrainBlock::packUpdate(NetConnection* con, U32 mask, BitStream *stream) stream->write( mScreenError ); stream->writeInt(mBaseTexFormat, 32); + stream->writeFlag(mIgnoreZodiacs); return retMask; } @@ -1267,6 +1280,7 @@ void TerrainBlock::unpackUpdate(NetConnection* con, BitStream *stream) stream->read( &mScreenError ); mBaseTexFormat = (BaseTexFormat)stream->readInt(32); + mIgnoreZodiacs = stream->readFlag(); } void TerrainBlock::getMinMaxHeight( F32 *minHeight, F32 *maxHeight ) const @@ -1405,3 +1419,19 @@ DefineConsoleFunction( getTerrainHeightBelowPosition, F32, (const char* ptOrX, c return height; } +const U16* TerrainBlock::getZodiacPrimitiveBuffer() +{ + if (!zode_primBuffer && !mIgnoreZodiacs) + TerrCell::createZodiacPrimBuffer(&zode_primBuffer); + return zode_primBuffer; +} + +void TerrainBlock::deleteZodiacPrimitiveBuffer() +{ + if (zode_primBuffer != 0) + { + delete [] zode_primBuffer; + zode_primBuffer = 0; + } +} + diff --git a/Engine/source/terrain/terrData.h b/Engine/source/terrain/terrData.h index f7b3fb179..87f994a03 100644 --- a/Engine/source/terrain/terrData.h +++ b/Engine/source/terrain/terrData.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _TERRDATA_H_ #define _TERRDATA_H_ @@ -457,6 +462,13 @@ public: U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream); void unpackUpdate(NetConnection *conn, BitStream *stream); void inspectPostApply(); + +protected: + bool mIgnoreZodiacs; + U16* zode_primBuffer; + void deleteZodiacPrimitiveBuffer(); +public: + const U16* getZodiacPrimitiveBuffer(); }; #endif // _TERRDATA_H_ diff --git a/Engine/source/terrain/terrRender.cpp b/Engine/source/terrain/terrRender.cpp index be67d58eb..15c294611 100644 --- a/Engine/source/terrain/terrRender.cpp +++ b/Engine/source/terrain/terrRender.cpp @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #include "platform/platform.h" #include "terrain/terrRender.h" @@ -45,6 +50,8 @@ #include "gfx/gfxDrawUtil.h" +#include "afx/arcaneFX.h" +#include "afx/ce/afxZodiacMgr.h" #include "gfx/gfxTransformSaver.h" #include "gfx/bitmap/gBitmap.h" #include "gfx/bitmap/ddsFile.h" @@ -421,6 +428,7 @@ void TerrainBlock::_renderBlock( SceneRenderState *state ) if ( isColorDrawPass ) lm = LIGHTMGR; + bool has_zodiacs = afxZodiacMgr::doesBlockContainZodiacs(state, this); for ( U32 i=0; i < renderCells.size(); i++ ) { TerrCell *cell = renderCells[i]; @@ -483,6 +491,8 @@ void TerrainBlock::_renderBlock( SceneRenderState *state ) inst->defaultKey = (U32)cell->getMaterials(); + if (has_zodiacs) + afxZodiacMgr::renderTerrainZodiacs(state, this, cell); // Submit it for rendering. renderPass->addInst( inst ); } diff --git a/Engine/source/terrain/terrRender.h b/Engine/source/terrain/terrRender.h index 0f4bfbc9a..ea67c0ca2 100644 --- a/Engine/source/terrain/terrRender.h +++ b/Engine/source/terrain/terrRender.h @@ -20,6 +20,16 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +// +// The terrain implementation of zodiacs is largely contained in +// afxZodiac.[h,cpp], however, some changes are required to the terrain +// code. Structures EmitChunk and SquareStackNode now contain an +// afxZodiacBitmask for keeping track of zodiac intersections. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _TERRRENDER_H_ #define _TERRRENDER_H_ @@ -27,6 +37,7 @@ #include "terrain/terrData.h" #endif +#include "afx/ce/afxZodiacDefs.h" enum TerrConstants : U32 { MaxClipPlanes = 8, ///< left, right, top, bottom - don't need far tho... diff --git a/Engine/source/ts/tsShapeInstance.h b/Engine/source/ts/tsShapeInstance.h index 292eb9302..c483c8792 100644 --- a/Engine/source/ts/tsShapeInstance.h +++ b/Engine/source/ts/tsShapeInstance.h @@ -20,6 +20,11 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// +// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames +// Copyright (C) 2015 Faust Logic, Inc. +//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~// + #ifndef _TSSHAPEINSTANCE_H_ #define _TSSHAPEINSTANCE_H_ @@ -690,6 +695,8 @@ protected: //------------------------------------------------------------------------------------- bool hasAccumulation(); + // provides access to full mTriggerStates mask. + U32 getTriggerStateMask() const { return mTriggerStates; } }; diff --git a/Engine/source/windowManager/mac/macCursorController.mm b/Engine/source/windowManager/mac/macCursorController.mm index 9e92c0f33..14754191b 100644 --- a/Engine/source/windowManager/mac/macCursorController.mm +++ b/Engine/source/windowManager/mac/macCursorController.mm @@ -127,6 +127,11 @@ void MacCursorController::setCursorShape(U32 cursorID) case PlatformCursorController::curResizeHorz: [[NSCursor resizeUpDownCursor] set]; break; + // This sets an appropriate value for the standard hand cursor. + // In AFX this is used for rollover feedback. + case PlatformCursorController::curHand: + [[NSCursor pointingHandCursor] set]; + break; } } diff --git a/Tools/CMake/modules/module_afx.cmake b/Tools/CMake/modules/module_afx.cmake new file mode 100644 index 000000000..177af6e6d --- /dev/null +++ b/Tools/CMake/modules/module_afx.cmake @@ -0,0 +1,27 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2014 GarageGames, LLC +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. +# ----------------------------------------------------------------------------- + +option(TORQUE_AFX "Enable AFX module" ON) +if(TORQUE_AFX) +# files +addPathRec( "${srcDir}/afx" ) +endif() \ No newline at end of file