//----------------------------------------------------------------------------- // 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 "forest/forestWindEmitter.h" #include "forest/forestWindMgr.h" #include "console/consoleInternal.h" #include "core/stream/bitStream.h" #include "core/util/safeDelete.h" #include "platform/profiler.h" #include "math/mathIO.h" #include "math/mRandom.h" #include "scene/sceneManager.h" #include "scene/sceneRenderState.h" #include "renderInstance/renderPassManager.h" #include "gfx/gfxDrawUtil.h" #include "gfx/gfxTransformSaver.h" #include "sim/netConnection.h" #include "T3D/gameBase/processList.h" #include "console/engineAPI.h" #include "T3D/gameBase/gameConnection.h" ConsoleDocClass( ForestWindEmitter, "@brief Object responsible for simulating wind in a level.\n\n" "When placed in the level, a ForestWindEmitter will cause tree branches to bend and sway, leaves " "to flutter, and create vertical bending on the tree's trunk.\n\n" "@tsexample\n" "// The following is a full declaration of a wind emitter\n" "new ForestWindEmitter()\n" "{\n" " position = \"497.739 765.821 102.395\";\n" " windEnabled = \"1\";\n" " radialEmitter = \"1\";\n" " strength = \"1\";\n" " radius = \"3\";\n" " gustStrength = \"0.5\";\n" " gustFrequency = \"1\";\n" " gustYawAngle = \"10\";\n" " gustYawFrequency = \"4\";\n" " gustWobbleStrength = \"2\";\n" " turbulenceStrength = \"1\";\n" " turbulenceFrequency = \"2\";\n" " hasMount = \"0\";\n" " scale = \"3 3 3\";\n" " canSave = \"1\";\n" " canSaveDynamicFields = \"1\";\n" " rotation = \"1 0 0 0\";\n" "};\n" "@endtsexample\n\n" "@ingroup FX\n" "@ingroup Forest\n" "@ingroup Atmosphere\n" ); // We need to know when the mission editor is enabled. extern bool gEditingMission; ForestWind::ForestWind( ForestWindEmitter *emitter ) : mStrength( 0.0f ), mDirection( 1.0f, 0, 0 ), mLastGustTime( 0 ), mLastYawTime( 0 ), mTargetYawAngle( 0 ), mCurrentInterp( 0 ), mCurrentTarget( 0, 0 ), mParent( emitter ), mRandom( Platform::getRealMilliseconds() + 1 ), mIsDirty( false ) { } ForestWind::~ForestWind() { } void ForestWind::processTick() { PROFILE_SCOPE( ForestWind_ProcessTick ); const F32 deltaTime = 0.032f; const U32 simTime = Sim::getCurrentTime(); Point2F finalVec( 0, 0 ); Point2F windDir( mParent->mWindDirection.x, mParent->mWindDirection.y ); if ( mLastGustTime < simTime ) { Point2F turbVec( 0, 0 ); if ( mLastGustTime < simTime + (mParent->mWindTurbulenceFrequency * 1000.0f) ) turbVec = (mRandom.randF() * mParent->mWindTurbulenceStrength) * windDir; mLastGustTime = simTime + (mParent->mWindGustFrequency * 1000.0f); Point2F gustVec = (mRandom.randF() * mParent->mWindGustStrength + mRandom.randF() * mParent->mWindGustWobbleStrength) * windDir; finalVec += gustVec + turbVec; //finalVec.normalizeSafe(); } //bool rotationChange = false; if ( mLastYawTime < simTime ) { mLastYawTime = simTime + (mParent->mWindGustYawFrequency * 1000.0f); F32 rotateAmt = mRandom.randF() * mParent->mWindGustYawAngle + mRandom.randF() * mParent->mWindGustWobbleStrength; if ( mRandom.randF() <= 0.5f ) rotateAmt = -rotateAmt; rotateAmt = mDegToRad( rotateAmt ); if ( rotateAmt > M_2PI_F ) rotateAmt -= M_2PI_F; else if ( rotateAmt < -M_2PI_F ) rotateAmt += M_2PI_F; mTargetYawAngle = rotateAmt; //finalVec.rotate( rotateAmt ); mCurrentTarget.rotate( rotateAmt ); } //mCurrentTarget.normalizeSafe(); if ( mCurrentTarget.isZero() || mCurrentInterp >= 1.0f ) { mCurrentInterp = 0; mCurrentTarget.set( 0, 0 ); Point2F windNorm( mDirection.x, mDirection.y ); windNorm.normalizeSafe(); mCurrentTarget = finalVec + windNorm; } else { mCurrentInterp += deltaTime; mCurrentInterp = mClampF( mCurrentInterp, 0.0f, 1.0f ); mDirection.interpolate( mDirection, Point3F( mCurrentTarget.x, mCurrentTarget.y, 0 ), mCurrentInterp ); //F32 rotateAmt = mLerp( 0, mTargetYawAngle, mCurrentInterp ); //mTargetYawAngle -= rotateAmt; //Point2F dir( mDirection.x, mDirection.y ); //if ( mTargetYawAngle > 0.0f ) // dir.rotate( rotateAmt ); //mDirection.set( dir.x, dir.y, 0 ); } } void ForestWind::setStrengthAndDirection( F32 strength, const VectorF &direction ) { if ( mStrength != strength || mDirection != direction ) { mStrength = strength; mDirection = direction; mCurrentTarget.zero(); mIsDirty = true; } } void ForestWind::setStrength( F32 strength ) { if ( mStrength != strength ) { mStrength = strength; mIsDirty = true; } } void ForestWind::setDirection( const VectorF &direction ) { if ( mDirection != direction ) { mDirection = direction; mIsDirty = true; } } IMPLEMENT_CO_NETOBJECT_V1(ForestWindEmitter); ForestWindEmitter::ForestWindEmitter( bool makeClientObject ) : mAddedToScene( false ), mEnabled( true ), mWind( NULL ), mWindStrength( 1 ), mWindDirection( 1, 0, 0 ), mWindGustFrequency( 3.0f ), mWindGustStrength( 0.25f ), mWindGustYawAngle( 10.0f ), mWindGustYawFrequency( 4.0f ), mWindGustWobbleStrength( 2.0f ), mWindTurbulenceFrequency( 2.0f ), mWindTurbulenceStrength( 0.25f ), mWindRadius( 0 ), mRadialEmitter( false ), mHasMount( false ), mIsMounted( false ), mMountObject( NULL ) { mTypeMask |= StaticObjectType | EnvironmentObjectType; if ( makeClientObject ) mNetFlags.set( IsGhost ); else mNetFlags.set( Ghostable | ScopeAlways ); } ForestWindEmitter::~ForestWindEmitter() { } void ForestWindEmitter::initPersistFields() { // Initialise parents' persistent fields. Parent::initPersistFields(); addGroup( "ForestWind" ); addField( "windEnabled", TypeBool, Offset( mEnabled, ForestWindEmitter ), "Determines if the emitter will be counted in wind calculations." ); addField( "radialEmitter", TypeBool, Offset( mRadialEmitter, ForestWindEmitter ), "Determines if the emitter is a global direction or local radial emitter." ); addField( "strength", TypeF32, Offset( mWindStrength, ForestWindEmitter ), "The strength of the wind force." ); addField( "radius", TypeF32, Offset( mWindRadius, ForestWindEmitter ), "The radius of the emitter for local radial emitters." ); addField( "gustStrength", TypeF32, Offset( mWindGustStrength, ForestWindEmitter ), "The maximum strength of a gust." ); addField( "gustFrequency", TypeF32, Offset( mWindGustFrequency, ForestWindEmitter ), "The frequency of gusting in seconds." ); addField( "gustYawAngle", TypeF32, Offset( mWindGustYawAngle, ForestWindEmitter ), "The amount of degrees the wind direction can drift (both positive and negative)." ); addField( "gustYawFrequency", TypeF32, Offset( mWindGustYawFrequency, ForestWindEmitter ), "The frequency of wind yaw drift, in seconds." ); addField( "gustWobbleStrength", TypeF32, Offset( mWindGustWobbleStrength, ForestWindEmitter ), "The amount of random wobble added to gust and turbulence vectors." ); addField( "turbulenceStrength", TypeF32, Offset( mWindTurbulenceStrength, ForestWindEmitter ), "The strength of gust turbulence." ); addField( "turbulenceFrequency", TypeF32, Offset( mWindTurbulenceFrequency, ForestWindEmitter ), "The frequency of gust turbulence, in seconds." ); addField( "hasMount", TypeBool, Offset( mHasMount, ForestWindEmitter ), "Determines if the emitter is mounted to another object." ); endGroup( "ForestWind" ); } U32 ForestWindEmitter::packUpdate(NetConnection * con, U32 mask, BitStream * stream) { U32 retMask = Parent::packUpdate( con, mask, stream ); mathWrite( *stream, mObjToWorld ); if ( stream->writeFlag( mask & EnabledMask ) ) stream->writeFlag( mEnabled ); if ( stream->writeFlag( mask & WindMask ) ) { stream->write( mWindStrength ); stream->write( mWindRadius ); stream->writeFlag( mRadialEmitter ); stream->write( mWindGustStrength ); stream->write( mWindGustFrequency ); stream->write( mWindGustYawAngle ); stream->write( mWindGustYawFrequency ); stream->write( mWindGustWobbleStrength ); stream->write( mWindTurbulenceStrength ); stream->write( mWindTurbulenceFrequency ); // The wind direction should be normalized! if ( mWindDirection.isZero() ) { VectorF forwardVec( 0, 0, 0 ); mWorldToObj.getColumn( 1, &mWindDirection ); } else mWindDirection.normalize(); stream->writeNormalVector( mWindDirection, 8 ); stream->writeFlag( mHasMount ); } return retMask; } void ForestWindEmitter::unpackUpdate(NetConnection * con, BitStream * stream) { // Unpack Parent. Parent::unpackUpdate( con, stream ); MatrixF xfm; mathRead( *stream, &xfm ); Parent::setTransform( xfm ); U32 windMask = 0; if ( stream->readFlag() ) // EnabledMask mEnabled = stream->readFlag(); if ( stream->readFlag() ) // WindMask { stream->read( &mWindStrength ); stream->read( &mWindRadius ); mRadialEmitter = stream->readFlag(); stream->read( &mWindGustStrength ); stream->read( &mWindGustFrequency ); stream->read( &mWindGustYawAngle ); stream->read( &mWindGustYawFrequency ); stream->read( &mWindGustWobbleStrength ); stream->read( &mWindTurbulenceStrength ); stream->read( &mWindTurbulenceFrequency ); stream->readNormalVector( &mWindDirection, 8 ); windMask |= WindMask; mHasMount = stream->readFlag(); } // This does nothing if the masks are not set! if ( windMask != 0 && isProperlyAdded() ) { Point3F boxRad( 0, 0, 0 ); if ( !isRadialEmitter() ) boxRad.set( 10000.0f, 10000.0f, 10000.0f ); else boxRad.set( mWindRadius, mWindRadius, mWindRadius ); mObjBox.set( -boxRad, boxRad ); resetWorldBox(); _initWind( windMask ); } } bool ForestWindEmitter::onAdd() { if ( !Parent::onAdd() ) return false; // Only the client side actually does wind. if ( isClientObject() ) { // TODO: wasn't this a big hack we already fixed better? //Projectile::getGhostReceivedSignal().notify( this, &ForestWindEmitter::_onMountObjectGhostReceived ); _initWind(); WINDMGR->addEmitter( this ); } Point3F boxRad( 0, 0, 0 ); if ( !isRadialEmitter() ) boxRad.set( 10000.0f, 10000.0f, 10000.0f ); else boxRad.set( mWindRadius, mWindRadius, mWindRadius ); mObjBox.set( -boxRad, boxRad ); resetWorldBox(); enableCollision(); // If we are we editing the mission then // be sure to add us to the scene. if ( gEditingMission || mHasMount ) { addToScene(); mAddedToScene = true; } return true; } void ForestWindEmitter::onRemove() { // Only the client side actually does wind. if ( isClientObject() ) { //Projectile::getGhostReceivedSignal().remove( this, &ForestWindEmitter::_onMountObjectGhostReceived ); WINDMGR->removeEmitter( this ); SAFE_DELETE( mWind ); } // If we are editing the mission then remove // us from the scene graph. if ( gEditingMission || mHasMount ) { removeFromScene(); mAddedToScene = false; } // Do Parent. Parent::onRemove(); } void ForestWindEmitter::_onMountObjectGhostReceived( SceneObject *object ) { if ( !object ) return; attachToObject( object ); } void ForestWindEmitter::inspectPostApply() { // Force the client update! setMaskBits(0xffffffff); } void ForestWindEmitter::onEditorEnable() { if ( !mAddedToScene ) { addToScene(); mAddedToScene = true; } } void ForestWindEmitter::onEditorDisable() { // Remove us from the scene. if ( mAddedToScene ) { removeFromScene(); mAddedToScene = false; } } void ForestWindEmitter::_initWind( U32 mask ) { AssertFatal( !isServerObject(), "SpeedWind is never updated on the server!" ); // If we don't have a wind // object create one now. if ( !mWind ) mWind = new ForestWind( this ); // Do we need to apply a new direction and strength? if ( mask & WindMask ) { mWorldToObj.getColumn( 1, &mWindDirection ); mWind->setStrengthAndDirection( mWindStrength, mWindDirection ); } } void ForestWindEmitter::setTransform( const MatrixF &mat ) { Parent::setTransform( mat ); // Force the client update! setMaskBits(0xffffffff); if ( isClientObject() ) _initWind(); } void ForestWindEmitter::prepRenderImage( SceneRenderState* state ) { PROFILE_SCOPE( ForestWindEmitter_PrepRenderImage ); // Only render the radius and // direction if we're in the editor. // Don't render them if this is a reflect pass. if ( !state->isDiffusePass() || !gEditingMission ) return; // This should be sufficient for most objects that don't manage zones, and // don't need to return a specialized RenderImage... ObjectRenderInst *ri = state->getRenderPass()->allocInst(); ri->renderDelegate.bind( this, &ForestWindEmitter::_renderEmitterInfo ); ri->type = RenderPassManager::RIT_Editor; state->getRenderPass()->addInst( ri ); } void ForestWindEmitter::_renderEmitterInfo( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat ) { if ( overrideMat ) return; GFXTransformSaver saver; GFXDrawUtil *drawer = GFX->getDrawUtil(); AssertFatal( drawer, "Got NULL GFXDrawUtil!" ); const Point3F &pos = getPosition(); const VectorF &windVec = mWind->getDirection(); GFXStateBlockDesc desc; desc.setBlend( true ); desc.setZReadWrite( true, false ); // Draw an arrow pointing // in the wind direction. drawer->drawArrow( desc, pos, pos + (windVec * mWindStrength), ColorI( 0, 0, 255, 255 ) );//Point3F( -235.214, 219.589, 34.0991 ), Point3F( -218.814, 244.731, 37.5587 ), ColorI( 255, 255, 0, 255 ) );// drawer->drawArrow( desc, pos, pos + (mWind->getTarget() * mWindStrength ), ColorI( 255, 0, 0, 85 ) ); S32 useRadius = mWindRadius; // Draw a 2D circle for the wind radius. if ( isRadialEmitter() ) { // If the camera is close to the sphere, shrink the sphere so it remains visible. GameConnection* gc = GameConnection::getConnectionToServer(); GameBase *gb = gc ? gc->getCameraObject() : NULL; if (gb) { F32 camDist = (gb->getPosition() - getPosition()).len(); if ( camDist < mWindRadius ) useRadius = camDist; } drawer->drawSphere( desc, useRadius, pos, ColorI( 255, 0, 0, 80 ) ); } } F32 ForestWindEmitter::getStrength() const { return mWind->getStrength(); } void ForestWindEmitter::setStrength( F32 strength ) { mWindStrength = strength; mWind->setStrength( mWindStrength ); } void ForestWindEmitter::attachToObject( SceneObject *obj ) { if ( !obj ) return; mMountObject = obj; mIsMounted = true; if ( isServerObject() ) deleteNotify( mMountObject ); } void ForestWindEmitter::updateMountPosition() { AssertFatal( isClientObject(), "ForestWindEmitter::updateMountPosition - This should only happen on the client!" ); if ( !mHasMount || !mMountObject ) return; MatrixF mat( true ); mat.setPosition( mMountObject->getPosition() ); Parent::setTransform( mat ); } void ForestWindEmitter::onDeleteNotify(SimObject *object) { AssertFatal( isServerObject(), "ForestWindEmitter::onDeleteNotify - This should never happen on the client!" ); safeDeleteObject(); } DefineEngineMethod( ForestWindEmitter, attachToObject, void, ( U32 objectID ),, "@brief Mounts the wind emitter to another scene object\n\n" "@param objectID Unique ID of the object wind emitter should attach to" "@tsexample\n" "// Wind emitter previously created and named %windEmitter\n" "// Going to attach it to the player, making him a walking wind storm\n" "%windEmitter.attachToObject(%player);\n" "@endtsexample\n\n") { SceneObject *obj = dynamic_cast( Sim::findObject( objectID ) ); if ( !obj ) Con::warnf( "ForestWindEmitter::attachToObject - failed to find object with ID: %d", objectID ); object->attachToObject( obj ); }