Engine directory for ticket #1

This commit is contained in:
DavidWyand-GG 2012-09-19 11:15:01 -04:00
parent 352279af7a
commit 7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions

View file

@ -0,0 +1,582 @@
//-----------------------------------------------------------------------------
// 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/aiClient.h"
#include "math/mMatrix.h"
#include "T3D/shapeBase.h"
#include "T3D/player.h"
#include "T3D/gameBase/moveManager.h"
#include "console/consoleInternal.h"
IMPLEMENT_CONOBJECT( AIClient );
ConsoleDocClass( AIClient,
"@brief Simulated client driven by AI commands.\n\n"
"This object is derived from the AIConnection class. It introduces its own Player object "
"to solidify the purpose of this class: Simulated client connecting as a player\n\n"
"To get more specific, if you want a strong alternative to AIPlayer (and wish to make use "
"of the AIConnection structure), consider AIClient. AIClient inherits from AIConnection, "
"contains quite a bit of functionality you will find in AIPlayer, and has its own Player "
"object.\n\n"
"@note This is a legacy class, which you are discouraged from using as it will "
"most likely be deprecated in a future version. For now it has been left in for "
"backwards compatibility with TGE and the old RTS Kit. Use AIPlayer instead.\n\n"
"@see AIPlayer, AIConnection\n\n"
"@ingroup AI\n"
"@ingroup Networking\n"
);
/**
* Constructor
*/
AIClient::AIClient() {
mMoveMode = ModeStop;
mMoveDestination.set( 0.0f, 0.0f, 0.0f );
mAimLocation.set( 0.0f, 0.0f, 0.0f );
mMoveSpeed = 1.0f;
mMoveTolerance = 0.25f;
// Clear the triggers
for( int i = 0; i < MaxTriggerKeys; i++ )
mTriggers[i] = false;
mAimToDestination = true;
mTargetInLOS = false;
mLocation.set( 0.0f, 0.0f, 0.0f );
mPlayer = NULL;
}
/**
* Destructor
*/
AIClient::~AIClient() {
// Blah
}
/**
* Sets the object the bot is targeting
*
* @param targetObject The object to target
*/
void AIClient::setTargetObject( ShapeBase *targetObject ) {
if ( !targetObject || !bool( mTargetObject ) || targetObject->getId() != mTargetObject->getId() )
mTargetInLOS = false;
mTargetObject = targetObject;
}
/**
* Returns the target object
*
* @return Object bot is targeting
*/
S32 AIClient::getTargetObject() const {
if( bool( mTargetObject ) )
return mTargetObject->getId();
else
return -1;
}
/**
* Sets the speed at which this AI moves
*
* @param speed Speed to move, default player was 10
*/
void AIClient::setMoveSpeed( F32 speed ) {
if( speed <= 0.0f )
mMoveSpeed = 0.0f;
else
mMoveSpeed = getMin( 1.0f, speed );
}
/**
* Sets the movement mode for this AI
*
* @param mode Movement mode, see enum
*/
void AIClient::setMoveMode( S32 mode ) {
if( mode < 0 || mode >= ModeCount )
mode = 0;
if( mode != mMoveMode ) {
switch( mode ) {
case ModeStuck:
throwCallback( "onStuck" );
break;
case ModeMove:
if( mMoveMode == ModeStuck )
throwCallback( "onUnStuck" );
else
throwCallback( "onMove" );
break;
case ModeStop:
throwCallback( "onStop" );
break;
}
}
mMoveMode = mode;
}
/**
* Sets how far away from the move location is considered
* "on target"
*
* @param tolerance Movement tolerance for error
*/
void AIClient::setMoveTolerance( const F32 tolerance ) {
mMoveTolerance = getMax( 0.1f, tolerance );
}
/**
* Sets the location for the bot to run to
*
* @param location Point to run to
*/
void AIClient::setMoveDestination( const Point3F &location ) {
// Ok, here's the story...we're going to aim where we are going UNLESS told otherwise
if( mAimToDestination ) {
mAimLocation = location;
mAimLocation.z = 0.0f;
}
mMoveDestination = location;
}
/**
* Sets the location for the bot to aim at
*
* @param location Point to aim at
*/
void AIClient::setAimLocation( const Point3F &location ) {
mAimLocation = location;
mAimToDestination = false;
}
/**
* Clears the aim location and sets it to the bot's
* current destination so he looks where he's going
*/
void AIClient::clearAim() {
mAimLocation = Point3F( 0.0f, 0.0f, 0.0f );
mAimToDestination = true;
}
/**
* This method gets the move list for an object, in the case
* of the AI, it actually calculates the moves, and then
* sends them down the pipe.
*
* @param movePtr Pointer to move the move list into
* @param numMoves Number of moves in the move list
*/
U32 AIClient::getMoveList( Move **movePtr,U32 *numMoves ) {
//initialize the move structure and return pointers
mMove = NullMove;
*movePtr = &mMove;
*numMoves = 1;
// Check if we got a player
mPlayer = NULL;
mPlayer = dynamic_cast<Player *>( getControlObject() );
// We got a something controling us?
if( !mPlayer )
return 1;
// What is The Matrix?
MatrixF moveMatrix;
moveMatrix.set( EulerF( 0, 0, 0 ) );
moveMatrix.setColumn( 3, Point3F( 0, 0, 0 ) );
moveMatrix.transpose();
// Position / rotation variables
F32 curYaw, curPitch;
F32 newYaw, newPitch;
F32 xDiff, yDiff;
F32 moveSpeed = mMoveSpeed;
switch( mMoveMode ) {
case ModeStop:
return 1; // Stop means no action
break;
case ModeStuck:
// Fall through, so we still try to move
case ModeMove:
// Get my location
MatrixF const& myTransform = mPlayer->getTransform();
myTransform.getColumn( 3, &mLocation );
// Set rotation variables
Point3F rotation = mPlayer->getRotation();
Point3F headRotation = mPlayer->getHeadRotation();
curYaw = rotation.z;
curPitch = headRotation.x;
xDiff = mAimLocation.x - mLocation.x;
yDiff = mAimLocation.y - mLocation.y;
// first do Yaw
if( !mIsZero( xDiff ) || !mIsZero( yDiff ) ) {
// use the cur yaw between -Pi and Pi
while( curYaw > M_2PI_F )
curYaw -= M_2PI_F;
while( curYaw < -M_2PI_F )
curYaw += M_2PI_F;
// find the new yaw
newYaw = mAtan2( xDiff, yDiff );
// find the yaw diff
F32 yawDiff = newYaw - curYaw;
// make it between 0 and 2PI
if( yawDiff < 0.0f )
yawDiff += M_2PI_F;
else if( yawDiff >= M_2PI_F )
yawDiff -= M_2PI_F;
// now make sure we take the short way around the circle
if( yawDiff > M_2PI_F )
yawDiff -= M_2PI_F;
else if( yawDiff < -M_2PI_F )
yawDiff += M_2PI_F;
mMove.yaw = yawDiff;
// set up the movement matrix
moveMatrix.set( EulerF( 0.0f, 0.0f, newYaw ) );
}
else
moveMatrix.set( EulerF( 0.0f, 0.0f, curYaw ) );
// next do pitch
F32 horzDist = Point2F( mAimLocation.x, mAimLocation.y ).len();
if( !mIsZero( horzDist ) ) {
//we shoot from the gun, not the eye...
F32 vertDist = mAimLocation.z;
newPitch = mAtan2( horzDist, vertDist ) - ( M_2PI_F / 2.0f );
F32 pitchDiff = newPitch - curPitch;
mMove.pitch = pitchDiff;
}
// finally, mMove towards mMoveDestination
xDiff = mMoveDestination.x - mLocation.x;
yDiff = mMoveDestination.y - mLocation.y;
// Check if we should mMove, or if we are 'close enough'
if( ( ( mFabs( xDiff ) > mMoveTolerance ) ||
( mFabs( yDiff ) > mMoveTolerance ) ) && ( !mIsZero( mMoveSpeed ) ) )
{
if( mIsZero( xDiff ) )
mMove.y = ( mLocation.y > mMoveDestination.y ? -moveSpeed : moveSpeed );
else if( mIsZero( yDiff ) )
mMove.x = ( mLocation.x > mMoveDestination.x ? -moveSpeed : moveSpeed );
else if( mFabs( xDiff ) > mFabs( yDiff ) ) {
F32 value = mFabs( yDiff / xDiff ) * mMoveSpeed;
mMove.y = ( mLocation.y > mMoveDestination.y ? -value : value );
mMove.x = ( mLocation.x > mMoveDestination.x ? -moveSpeed : moveSpeed );
}
else {
F32 value = mFabs( xDiff / yDiff ) * mMoveSpeed;
mMove.x = ( mLocation.x > mMoveDestination.x ? -value : value );
mMove.y = ( mLocation.y > mMoveDestination.y ? -moveSpeed : moveSpeed );
}
//now multiply the mMove vector by the transpose of the object rotation matrix
moveMatrix.transpose();
Point3F newMove;
moveMatrix.mulP( Point3F( mMove.x, mMove.y, 0.0f ), &newMove );
//and sub the result back in the mMove structure
mMove.x = newMove.x;
mMove.y = newMove.y;
// We should check to see if we are stuck...
if( mLocation.x == mLastLocation.x &&
mLocation.y == mLastLocation.y &&
mLocation.z == mLastLocation.z ) {
// We're stuck...probably
setMoveMode( ModeStuck );
}
else
setMoveMode( ModeMove );
}
else {
// Ok, we are close enough, lets stop
// setMoveMode( ModeStop ); // DON'T use this, it'll throw the wrong callback
mMoveMode = ModeStop;
throwCallback( "onReachDestination" ); // Callback
}
break;
}
// Test for target location in sight
RayInfo dummy;
Point3F targetLoc = mMoveDestination; // Change this
if( mPlayer ) {
if( !mPlayer->getContainer()->castRay( mLocation, targetLoc, InteriorObjectType |
StaticShapeObjectType | StaticObjectType |
TerrainObjectType, &dummy ) ) {
if( !mTargetInLOS )
throwCallback( "onTargetEnterLOS" );
}
else {
if( mTargetInLOS )
throwCallback( "onTargetExitLOS" );
}
}
// Copy over the trigger status
for( int i = 0; i < MaxTriggerKeys; i++ ) {
mMove.trigger[i] = mTriggers[i];
mTriggers[i] = false;
}
return 1;
}
/**
* This method is just called to stop the bots from running amuck
* while the mission cycles
*/
void AIClient::missionCycleCleanup() {
setMoveMode( ModeStop );
}
/**
* Utility function to throw callbacks
*/
void AIClient::throwCallback( const char *name ) {
Con::executef( this, name );
}
/**
* What gets called when this gets created, different from constructor
*/
void AIClient::onAdd( const char *nameSpace ) {
// This doesn't work...
//
if( dStrcmp( nameSpace, mNameSpace->mName ) ) {
Con::linkNamespaces( mNameSpace->mName, nameSpace );
mNameSpace = Con::lookupNamespace( nameSpace );
}
throwCallback( "onAdd" );
}
// --------------------------------------------------------------------------------------------
// Console Functions
// --------------------------------------------------------------------------------------------
/**
* Sets the move speed for an AI object
*/
ConsoleMethod( AIClient, setMoveSpeed, void, 3, 3, "ai.setMoveSpeed( float );" ) {
AIClient *ai = static_cast<AIClient *>( object );
ai->setMoveSpeed( dAtof( argv[2] ) );
}
/**
* Stops all AI movement, halt!
*/
ConsoleMethod( AIClient, stop, void, 2, 2, "ai.stop();" ) {
AIClient *ai = static_cast<AIClient *>( object );
ai->setMoveMode( AIClient::ModeStop );
}
/**
* Tells the AI to aim at the location provided
*/
ConsoleMethod( AIClient, setAimLocation, void, 3, 3, "ai.setAimLocation( x y z );" ) {
AIClient *ai = static_cast<AIClient *>( object );
Point3F v( 0.0f,0.0f,0.0f );
dSscanf( argv[2], "%f %f %f", &v.x, &v.y, &v.z );
ai->setAimLocation( v );
}
/**
* Tells the AI to move to the location provided
*/
ConsoleMethod( AIClient, setMoveDestination, void, 3, 3, "ai.setMoveDestination( x y z );" ) {
AIClient *ai = static_cast<AIClient *>( object );
Point3F v( 0.0f, 0.0f, 0.0f );
dSscanf( argv[2], "%f %f", &v.x, &v.y );
ai->setMoveDestination( v );
}
/**
* Returns the point the AI is aiming at
*/
ConsoleMethod( AIClient, getAimLocation, const char *, 2, 2, "ai.getAimLocation();" ) {
AIClient *ai = static_cast<AIClient *>( object );
Point3F aimPoint = ai->getAimLocation();
char *returnBuffer = Con::getReturnBuffer( 256 );
dSprintf( returnBuffer, 256, "%f %f %f", aimPoint.x, aimPoint.y, aimPoint.z );
return returnBuffer;
}
/**
* Returns the point the AI is set to move to
*/
ConsoleMethod( AIClient, getMoveDestination, const char *, 2, 2, "ai.getMoveDestination();" ) {
AIClient *ai = static_cast<AIClient *>( object );
Point3F movePoint = ai->getMoveDestination();
char *returnBuffer = Con::getReturnBuffer( 256 );
dSprintf( returnBuffer, 256, "%f %f %f", movePoint.x, movePoint.y, movePoint.z );
return returnBuffer;
}
/**
* Sets the bots target object
*/
ConsoleMethod( AIClient, setTargetObject, void, 3, 3, "ai.setTargetObject( obj );" ) {
AIClient *ai = static_cast<AIClient *>( object );
// Find the target
ShapeBase *targetObject;
if( Sim::findObject( argv[2], targetObject ) )
ai->setTargetObject( targetObject );
else
ai->setTargetObject( NULL );
}
/**
* Gets the object the AI is targeting
*/
ConsoleMethod( AIClient, getTargetObject, S32, 2, 2, "ai.getTargetObject();" ) {
AIClient *ai = static_cast<AIClient *>( object );
return ai->getTargetObject();
}
/**
* Tells the bot the mission is cycling
*/
ConsoleMethod( AIClient, missionCycleCleanup, void, 2, 2, "ai.missionCycleCleanup();" ) {
AIClient *ai = static_cast<AIClient*>( object );
ai->missionCycleCleanup();
}
/**
* Sets the AI to run mode
*/
ConsoleMethod( AIClient, move, void, 2, 2, "ai.move();" ) {
AIClient *ai = static_cast<AIClient *>( object );
ai->setMoveMode( AIClient::ModeMove );
}
/**
* Gets the AI's location in the world
*/
ConsoleMethod( AIClient, getLocation, const char *, 2, 2, "ai.getLocation();" ) {
AIClient *ai = static_cast<AIClient *>( object );
Point3F locPoint = ai->getLocation();
char *returnBuffer = Con::getReturnBuffer( 256 );
dSprintf( returnBuffer, 256, "%f %f %f", locPoint.x, locPoint.y, locPoint.z );
return returnBuffer;
}
/**
* Adds an AI Player to the game
*/
ConsoleFunction( aiAddPlayer, S32 , 2, 3, "aiAddPlayer( 'playerName'[, 'AIClassType'] );" ) {
// Create the player
AIClient *aiPlayer = new AIClient();
aiPlayer->registerObject();
aiPlayer->setGhostFrom(false);
aiPlayer->setGhostTo(false);
aiPlayer->setSendingEvents(false);
aiPlayer->setTranslatesStrings(true);
aiPlayer->setEstablished();
// Add the connection to the client group
SimGroup *g = Sim::getClientGroup();
g->addObject( aiPlayer );
char *name = new char[ dStrlen( argv[1] ) + 1];
char *ns = new char[ dStrlen( argv[2] ) + 1];
dStrcpy( name, argv[1] );
dStrcpy( ns, argv[2] );
// Execute the connect console function, this is the same
// onConnect function invoked for normal client connections
Con::executef( aiPlayer, "onConnect", name );
// Now execute the onAdd command and feed it the namespace
if( argc > 2 )
aiPlayer->onAdd( ns );
else
aiPlayer->onAdd( "AIClient" );
return aiPlayer->getId();
}
/**
* Tells the AI to move forward 100 units...TEST FXN
*/
ConsoleMethod( AIClient, moveForward, void, 2, 2, "ai.moveForward();" ) {
AIClient *ai = static_cast<AIClient *>( object );
ShapeBase *player = dynamic_cast<ShapeBase*>(ai->getControlObject());
Point3F location;
MatrixF const &myTransform = player->getTransform();
myTransform.getColumn( 3, &location );
location.y += 100.0f;
ai->setMoveDestination( location );
} // *** /TEST FXN

View file

@ -0,0 +1,114 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _AICLIENT_H_
#define _AICLIENT_H_
#ifndef _AICONNECTION_H_
#include "T3D/aiConnection.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
class ShapeBase;
class Player;
class AIClient : public AIConnection {
typedef AIConnection Parent;
private:
enum {
FireTrigger = 0,
JumpTrigger = 2,
JetTrigger = 3,
GrenadeTrigger = 4,
MineTrigger = 5
};
F32 mMoveSpeed;
S32 mMoveMode;
F32 mMoveTolerance; // How close to the destination before we stop
bool mTriggers[MaxTriggerKeys];
Player *mPlayer;
Point3F mMoveDestination;
Point3F mLocation;
Point3F mLastLocation; // For stuck check
bool mAimToDestination;
Point3F mAimLocation;
bool mTargetInLOS;
SimObjectPtr<ShapeBase> mTargetObject;
// Utility Methods
void throwCallback( const char *name );
public:
DECLARE_CONOBJECT( AIClient );
enum {
ModeStop = 0,
ModeMove,
ModeStuck,
ModeCount // This is in there as a max index value
};
AIClient();
~AIClient();
U32 getMoveList( Move **movePtr,U32 *numMoves );
// ---Targeting and aiming sets/gets
void setTargetObject( ShapeBase *targetObject );
S32 getTargetObject() const;
// ---Movement sets/gets
void setMoveSpeed( const F32 speed );
F32 getMoveSpeed() const { return mMoveSpeed; }
void setMoveMode( S32 mode );
S32 getMoveMode() const { return mMoveMode; }
void setMoveTolerance( const F32 tolerance );
F32 getMoveTolerance() const { return mMoveTolerance; }
void setMoveDestination( const Point3F &location );
Point3F getMoveDestination() const { return mMoveDestination; }
Point3F getLocation() const { return mLocation; }
// ---Facing(Aiming) sets/gets
void setAimLocation( const Point3F &location );
Point3F getAimLocation() const { return mAimLocation; }
void clearAim();
// ---Other
void missionCycleCleanup();
void onAdd( const char *nameSpace );
};
#endif

View file

@ -0,0 +1,258 @@
//-----------------------------------------------------------------------------
// 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 "T3D/aiConnection.h"
IMPLEMENT_CONOBJECT( AIConnection );
ConsoleDocClass( AIConnection,
"@brief Special client connection driven by an AI, rather than a human.\n\n"
"Unlike other net connections, AIConnection is intended to run unmanned. Rather than "
"gathering input from a human using a device, move events, triggers, and look events "
"are driven through functions like AIConnection::setMove.\n\n"
"In addition to having its own set of functions for managing client move events, "
"a member variable inherited by GameConnection is toggle: mAIControlled. This is useful "
"for a server to determine if a connection is AI driven via the function GameConnection::isAIControlled\n\n"
"AIConnection is an alternative to manually creating an AI driven game object. When you "
"want the server to manage AI, you will create a specific one from script using a "
"class like AIPlayer. If you do not want the server managing the AI and wish to simulate "
"a complete client connection, you will use AIConnection\n\n."
"To get more specific, if you want a strong alternative to AIPlayer (and wish to make use "
"of the AIConnection structure), consider AIClient. AIClient inherits from AIConnection, "
"contains quite a bit of functionality you will find in AIPlayer, and has its own Player "
"object.\n\n"
"@tsexample\n"
"// Create a new AI client connection\n"
"%botConnection = aiConnect(\"MasterBlaster\" @ %i, -1, 0.5, false, \"SDF\", 1.0);\n\n"
"// In another area of the code, you can locate this and any other AIConnections\n"
"// using the isAIControlled function\n"
"for(%i = 0; %i < ClientGroup.getCount(); %i++)\n"
"{\n"
" %client = ClientGroup.getObject(%i);\n"
" if(%client.isAIControlled())\n"
" {\n"
" // React to this AI controlled client\n"
" }\n"
"}\n"
"@endtsexample\n\n"
"@note This is a legacy class, which you are discouraged from using as it will "
"most likely be deprecated in a future version. For now it has been left in for "
"backwards compatibility with TGE and the old RTS Kit. Use GameConnection "
"and AIPlayer instead.\n\n"
"@see GameConnection, NetConnection, AIClient\n\n"
"@ingroup AI\n"
"@ingroup Networking\n"
);
//-----------------------------------------------------------------------------
AIConnection::AIConnection() {
mAIControlled = true;
mMove = NullMove;
}
//-----------------------------------------------------------------------------
void AIConnection::clearMoves( U32 )
{
// Clear the pending move list. This connection generates moves
// on the fly, so there are never any pending moves.
}
void AIConnection::setMove(Move* m)
{
mMove = *m;
}
const Move& AIConnection::getMove()
{
return mMove;
}
/// Retrive the pending moves
/**
* The GameConnection base class queues moves for delivery to the
* controll object. This function is normally used to retrieve the
* queued moves recieved from the client. The AI connection does not
* have a connected client and simply generates moves on-the-fly
* base on it's current state.
*/
U32 AIConnection::getMoveList( Move **lngMove, U32 *numMoves )
{
*numMoves = 1;
*lngMove = &mMove;
return *numMoves;
}
//-----------------------------------------------------------------------------
// Console functions & methods
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
static inline F32 moveClamp(F32 v)
{
// Support function to convert/clamp the input into a move rotation
// which only allows 0 -> M_2PI.
F32 a = mClampF(v, -M_2PI_F, M_2PI_F);
return (a < 0) ? a + M_2PI_F : a;
}
//-----------------------------------------------------------------------------
/// Construct and connect an AI connection object
ConsoleFunction(aiConnect, S32 , 2, 20, "(...)"
"@brief Creates a new AIConnection, and passes arguments to its onConnect script callback.\n\n"
"@returns The newly created AIConnection\n"
"@see GameConnection for parameter information\n"
"@ingroup AI")
{
// Create the connection
AIConnection *aiConnection = new AIConnection();
aiConnection->registerObject();
// Add the connection to the client group
SimGroup *g = Sim::getClientGroup();
g->addObject( aiConnection );
// Prep the arguments for the console exec...
// Make sure and leav args[1] empty.
const char* args[21];
args[0] = "onConnect";
for (S32 i = 1; i < argc; i++)
args[i + 1] = argv[i];
// Execute the connect console function, this is the same
// onConnect function invoked for normal client connections
Con::execute(aiConnection, argc + 1, args);
return aiConnection->getId();
}
//-----------------------------------------------------------------------------
ConsoleMethod(AIConnection,setMove,void,4, 4,"(string field, float value)"
"Set a field on the current move.\n\n"
"@param field One of {'x','y','z','yaw','pitch','roll'}\n"
"@param value Value to set field to.")
{
Move move = object->getMove();
// Ok, a little slow for now, but this is just an example..
if (!dStricmp(argv[2],"x"))
move.x = mClampF(dAtof(argv[3]),-1,1);
else
if (!dStricmp(argv[2],"y"))
move.y = mClampF(dAtof(argv[3]),-1,1);
else
if (!dStricmp(argv[2],"z"))
move.z = mClampF(dAtof(argv[3]),-1,1);
else
if (!dStricmp(argv[2],"yaw"))
move.yaw = moveClamp(dAtof(argv[3]));
else
if (!dStricmp(argv[2],"pitch"))
move.pitch = moveClamp(dAtof(argv[3]));
else
if (!dStricmp(argv[2],"roll"))
move.roll = moveClamp(dAtof(argv[3]));
//
object->setMove(&move);
}
ConsoleMethod(AIConnection,getMove,F32,3, 3,"(string field)"
"Get the given field of a move.\n\n"
"@param field One of {'x','y','z','yaw','pitch','roll'}\n"
"@returns The requested field on the current move.")
{
const Move& move = object->getMove();
if (!dStricmp(argv[2],"x"))
return move.x;
if (!dStricmp(argv[2],"y"))
return move.y;
if (!dStricmp(argv[2],"z"))
return move.z;
if (!dStricmp(argv[2],"yaw"))
return move.yaw;
if (!dStricmp(argv[2],"pitch"))
return move.pitch;
if (!dStricmp(argv[2],"roll"))
return move.roll;
return 0;
}
ConsoleMethod(AIConnection,setFreeLook,void,3, 3,"(bool isFreeLook)"
"Enable/disable freelook on the current move.")
{
Move move = object->getMove();
move.freeLook = dAtob(argv[2]);
object->setMove(&move);
}
ConsoleMethod(AIConnection,getFreeLook,bool,2, 2,"getFreeLook()"
"Is freelook on for the current move?")
{
return object->getMove().freeLook;
}
//-----------------------------------------------------------------------------
ConsoleMethod(AIConnection,setTrigger,void,4, 4,"(int trigger, bool set)"
"Set a trigger.")
{
S32 idx = dAtoi(argv[2]);
if (idx >= 0 && idx < MaxTriggerKeys) {
Move move = object->getMove();
move.trigger[idx] = dAtob(argv[3]);
object->setMove(&move);
}
}
ConsoleMethod(AIConnection,getTrigger,bool,4, 4,"(int trigger)"
"Is the given trigger set?")
{
S32 idx = dAtoi(argv[2]);
if (idx >= 0 && idx < MaxTriggerKeys)
return object->getMove().trigger[idx];
return false;
}
//-----------------------------------------------------------------------------
ConsoleMethod(AIConnection,getAddress,const char*,2, 2,"")
{
// Override the netConnection method to return to indicate
// this is an ai connection.
return "ai:local";
}

View file

@ -0,0 +1,56 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _AICONNECTION_H_
#define _AICONNECTION_H_
#ifndef _GAMECONNECTION_H_
#include "T3D/gameBase/gameConnection.h"
#endif
#ifndef _MOVEMANAGER_H_
#include "T3D/gameBase/moveManager.h"
#endif
//-----------------------------------------------------------------------------
class AIConnection : public GameConnection
{
typedef GameConnection Parent;
protected:
Move mMove;
public:
AIConnection();
DECLARE_CONOBJECT( AIConnection );
// Interface
const Move& getMove();
void setMove(Move *m);
// GameConnection overrides
void clearMoves(U32 n);
virtual U32 getMoveList(Move **,U32 *numMoves);
};
#endif

View file

@ -0,0 +1,602 @@
//-----------------------------------------------------------------------------
// 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/aiPlayer.h"
#include "console/consoleInternal.h"
#include "math/mMatrix.h"
#include "T3D/gameBase/moveManager.h"
#include "console/engineAPI.h"
IMPLEMENT_CO_NETOBJECT_V1(AIPlayer);
ConsoleDocClass( AIPlayer,
"@brief A Player object not controlled by conventional input, but by an AI engine.\n\n"
"The AIPlayer provides a Player object that may be controlled from script. You control "
"where the player moves and how fast. You may also set where the AIPlayer is aiming at "
"-- either a location or another game object.\n\n"
"The AIPlayer class does not have a datablock of its own. It makes use of the PlayerData "
"datablock to define how it looks, etc. As the AIPlayer is an extension of the Player class "
"it can mount objects and fire weapons, or mount vehicles and drive them.\n\n"
"While the PlayerData datablock is used, there are a number of additional callbacks that are "
"implemented by AIPlayer on the datablock. These are listed here:\n\n"
"void onReachDestination(AIPlayer obj) \n"
"Called when the player has reached its set destination using the setMoveDestination() method. "
"The actual point at which this callback is called is when the AIPlayer is within the mMoveTolerance "
"of the defined destination.\n\n"
"void onMoveStuck(AIPlayer obj) \n"
"While in motion, if an AIPlayer has moved less than moveStuckTolerance within a single tick, this "
"callback is called. From here you could choose an alternate destination to get the AIPlayer moving "
"again.\n\n"
"void onTargetEnterLOS(AIPlayer obj) \n"
"When an object is being aimed at (following a call to setAimObject()) and the targeted object enters "
"the AIPlayer's line of sight, this callback is called. The LOS test is a ray from the AIPlayer's eye "
"position to the center of the target's bounding box. The LOS ray test only checks against interiors, "
"statis shapes, and terrain.\n\n"
"void onTargetExitLOS(AIPlayer obj) \n"
"When an object is being aimed at (following a call to setAimObject()) and the targeted object leaves "
"the AIPlayer's line of sight, this callback is called. The LOS test is a ray from the AIPlayer's eye "
"position to the center of the target's bounding box. The LOS ray test only checks against interiors, "
"statis shapes, and terrain.\n\n"
"@tsexample\n"
"// Create the demo player object\n"
"%player = new AiPlayer()\n"
"{\n"
" dataBlock = DemoPlayer;\n"
" path = \"\";\n"
"};\n"
"@endtsexample\n\n"
"@see Player for a list of all inherited functions, variables, and base description\n"
"@ingroup AI\n"
"@ingroup gameObjects\n");
/**
* Constructor
*/
AIPlayer::AIPlayer()
{
mMoveDestination.set( 0.0f, 0.0f, 0.0f );
mMoveSpeed = 1.0f;
mMoveTolerance = 0.25f;
mMoveStuckTolerance = 0.01f;
mMoveStuckTestDelay = 30;
mMoveStuckTestCountdown = 0;
mMoveSlowdown = true;
mMoveState = ModeStop;
mAimObject = 0;
mAimLocationSet = false;
mTargetInLOS = false;
mAimOffset = Point3F(0.0f, 0.0f, 0.0f);
mIsAiControlled = true;
}
/**
* Destructor
*/
AIPlayer::~AIPlayer()
{
}
void AIPlayer::initPersistFields()
{
addGroup( "AI" );
addField( "mMoveTolerance", TypeF32, Offset( mMoveTolerance, AIPlayer ),
"@brief Distance from destination before stopping.\n\n"
"When the AIPlayer is moving to a given destination it will move to within "
"this distance of the destination and then stop. By providing this tolerance "
"it helps the AIPlayer from never reaching its destination due to minor obstacles, "
"rounding errors on its position calculation, etc. By default it is set to 0.25.\n");
addField( "moveStuckTolerance", TypeF32, Offset( mMoveStuckTolerance, AIPlayer ),
"@brief Distance tolerance on stuck check.\n\n"
"When the AIPlayer is moving to a given destination, if it ever moves less than "
"this tolerance during a single tick, the AIPlayer is considered stuck. At this point "
"the onMoveStuck() callback is called on the datablock.\n");
addField( "moveStuckTestDelay", TypeS32, Offset( mMoveStuckTestDelay, AIPlayer ),
"@brief The number of ticks to wait before testing if the AIPlayer is stuck.\n\n"
"When the AIPlayer is asked to move, this property is the number of ticks to wait "
"before the AIPlayer starts to check if it is stuck. This delay allows the AIPlayer "
"to accelerate to full speed without its initial slow start being considered as stuck.\n"
"@note Set to zero to have the stuck test start immediately.\n");
endGroup( "AI" );
Parent::initPersistFields();
}
bool AIPlayer::onAdd()
{
if (!Parent::onAdd())
return false;
// Use the eye as the current position (see getAIMove)
MatrixF eye;
getEyeTransform(&eye);
mLastLocation = eye.getPosition();
return true;
}
/**
* Sets the speed at which this AI moves
*
* @param speed Speed to move, default player was 10
*/
void AIPlayer::setMoveSpeed( F32 speed )
{
mMoveSpeed = getMax(0.0f, getMin( 1.0f, speed ));
}
/**
* Stops movement for this AI
*/
void AIPlayer::stopMove()
{
mMoveState = ModeStop;
}
/**
* Sets how far away from the move location is considered
* "on target"
*
* @param tolerance Movement tolerance for error
*/
void AIPlayer::setMoveTolerance( const F32 tolerance )
{
mMoveTolerance = getMax( 0.1f, tolerance );
}
/**
* Sets the location for the bot to run to
*
* @param location Point to run to
*/
void AIPlayer::setMoveDestination( const Point3F &location, bool slowdown )
{
mMoveDestination = location;
mMoveState = ModeMove;
mMoveSlowdown = slowdown;
mMoveStuckTestCountdown = mMoveStuckTestDelay;
}
/**
* Sets the object the bot is targeting
*
* @param targetObject The object to target
*/
void AIPlayer::setAimObject( GameBase *targetObject )
{
mAimObject = targetObject;
mTargetInLOS = false;
mAimOffset = Point3F(0.0f, 0.0f, 0.0f);
}
/**
* Sets the object the bot is targeting and an offset to add to target location
*
* @param targetObject The object to target
* @param offset The offest from the target location to aim at
*/
void AIPlayer::setAimObject( GameBase *targetObject, Point3F offset )
{
mAimObject = targetObject;
mTargetInLOS = false;
mAimOffset = offset;
}
/**
* Sets the location for the bot to aim at
*
* @param location Point to aim at
*/
void AIPlayer::setAimLocation( const Point3F &location )
{
mAimObject = 0;
mAimLocationSet = true;
mAimLocation = location;
mAimOffset = Point3F(0.0f, 0.0f, 0.0f);
}
/**
* Clears the aim location and sets it to the bot's
* current destination so he looks where he's going
*/
void AIPlayer::clearAim()
{
mAimObject = 0;
mAimLocationSet = false;
mAimOffset = Point3F(0.0f, 0.0f, 0.0f);
}
/**
* This method calculates the moves for the AI player
*
* @param movePtr Pointer to move the move list into
*/
bool AIPlayer::getAIMove(Move *movePtr)
{
*movePtr = NullMove;
// Use the eye as the current position.
MatrixF eye;
getEyeTransform(&eye);
Point3F location = eye.getPosition();
Point3F rotation = getRotation();
// Orient towards the aim point, aim object, or towards
// our destination.
if (mAimObject || mAimLocationSet || mMoveState != ModeStop)
{
// Update the aim position if we're aiming for an object
if (mAimObject)
mAimLocation = mAimObject->getPosition() + mAimOffset;
else
if (!mAimLocationSet)
mAimLocation = mMoveDestination;
F32 xDiff = mAimLocation.x - location.x;
F32 yDiff = mAimLocation.y - location.y;
if (!mIsZero(xDiff) || !mIsZero(yDiff))
{
// First do Yaw
// use the cur yaw between -Pi and Pi
F32 curYaw = rotation.z;
while (curYaw > M_2PI_F)
curYaw -= M_2PI_F;
while (curYaw < -M_2PI_F)
curYaw += M_2PI_F;
// find the yaw offset
F32 newYaw = mAtan2( xDiff, yDiff );
F32 yawDiff = newYaw - curYaw;
// make it between 0 and 2PI
if( yawDiff < 0.0f )
yawDiff += M_2PI_F;
else if( yawDiff >= M_2PI_F )
yawDiff -= M_2PI_F;
// now make sure we take the short way around the circle
if( yawDiff > M_PI_F )
yawDiff -= M_2PI_F;
else if( yawDiff < -M_PI_F )
yawDiff += M_2PI_F;
movePtr->yaw = yawDiff;
// Next do pitch.
if (!mAimObject && !mAimLocationSet)
{
// Level out if were just looking at our next way point.
Point3F headRotation = getHeadRotation();
movePtr->pitch = -headRotation.x;
}
else
{
// This should be adjusted to run from the
// eye point to the object's center position. Though this
// works well enough for now.
F32 vertDist = mAimLocation.z - location.z;
F32 horzDist = mSqrt(xDiff * xDiff + yDiff * yDiff);
F32 newPitch = mAtan2( horzDist, vertDist ) - ( M_PI_F / 2.0f );
if (mFabs(newPitch) > 0.01f)
{
Point3F headRotation = getHeadRotation();
movePtr->pitch = newPitch - headRotation.x;
}
}
}
}
else
{
// Level out if we're not doing anything else
Point3F headRotation = getHeadRotation();
movePtr->pitch = -headRotation.x;
}
// Move towards the destination
if (mMoveState != ModeStop)
{
F32 xDiff = mMoveDestination.x - location.x;
F32 yDiff = mMoveDestination.y - location.y;
// Check if we should mMove, or if we are 'close enough'
if (mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance)
{
mMoveState = ModeStop;
throwCallback("onReachDestination");
}
else
{
// Build move direction in world space
if (mIsZero(xDiff))
movePtr->y = (location.y > mMoveDestination.y) ? -1.0f : 1.0f;
else
if (mIsZero(yDiff))
movePtr->x = (location.x > mMoveDestination.x) ? -1.0f : 1.0f;
else
if (mFabs(xDiff) > mFabs(yDiff))
{
F32 value = mFabs(yDiff / xDiff);
movePtr->y = (location.y > mMoveDestination.y) ? -value : value;
movePtr->x = (location.x > mMoveDestination.x) ? -1.0f : 1.0f;
}
else
{
F32 value = mFabs(xDiff / yDiff);
movePtr->x = (location.x > mMoveDestination.x) ? -value : value;
movePtr->y = (location.y > mMoveDestination.y) ? -1.0f : 1.0f;
}
// Rotate the move into object space (this really only needs
// a 2D matrix)
Point3F newMove;
MatrixF moveMatrix;
moveMatrix.set(EulerF(0.0f, 0.0f, -(rotation.z + movePtr->yaw)));
moveMatrix.mulV( Point3F( movePtr->x, movePtr->y, 0.0f ), &newMove );
movePtr->x = newMove.x;
movePtr->y = newMove.y;
// Set movement speed. We'll slow down once we get close
// to try and stop on the spot...
if (mMoveSlowdown)
{
F32 speed = mMoveSpeed;
F32 dist = mSqrt(xDiff*xDiff + yDiff*yDiff);
F32 maxDist = 5.0f;
if (dist < maxDist)
speed *= dist / maxDist;
movePtr->x *= speed;
movePtr->y *= speed;
mMoveState = ModeSlowing;
}
else
{
movePtr->x *= mMoveSpeed;
movePtr->y *= mMoveSpeed;
mMoveState = ModeMove;
}
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;
throwCallback("onMoveStuck");
}
}
}
}
}
// Test for target location in sight if it's an object. The LOS is
// run from the eye position to the center of the object's bounding,
// which is not very accurate.
if (mAimObject) {
MatrixF eyeMat;
getEyeTransform(&eyeMat);
eyeMat.getColumn(3,&location);
Point3F targetLoc = mAimObject->getBoxCenter();
// This ray ignores non-static shapes. Cast Ray returns true
// if it hit something.
RayInfo dummy;
if (getContainer()->castRay( location, targetLoc,
InteriorObjectType | StaticShapeObjectType | StaticObjectType |
TerrainObjectType, &dummy)) {
if (mTargetInLOS) {
throwCallback( "onTargetExitLOS" );
mTargetInLOS = false;
}
}
else
if (!mTargetInLOS) {
throwCallback( "onTargetEnterLOS" );
mTargetInLOS = true;
}
}
// Replicate the trigger state into the move so that
// triggers can be controlled from scripts.
for( int i = 0; i < MaxTriggerKeys; i++ )
movePtr->trigger[i] = getImageTriggerState(i);
mLastLocation = location;
return true;
}
/**
* Utility function to throw callbacks. Callbacks always occure
* on the datablock class.
*
* @param name Name of script function to call
*/
void AIPlayer::throwCallback( const char *name )
{
Con::executef(getDataBlock(), name, getIdString());
}
// --------------------------------------------------------------------------------------------
// Console Functions
// --------------------------------------------------------------------------------------------
DefineEngineMethod( AIPlayer, stop, void, ( ),,
"@brief Tells the AIPlayer to stop moving.\n\n")
{
object->stopMove();
}
DefineEngineMethod( AIPlayer, clearAim, void, ( ),,
"@brief Use this to stop aiming at an object or a point.\n\n"
"@see setAimLocation()\n"
"@see setAimObject()\n")
{
object->clearAim();
}
DefineEngineMethod( AIPlayer, setMoveSpeed, void, ( F32 speed ),,
"@brief Sets the move speed for an AI object.\n\n"
"@param speed A speed multiplier between 0.0 and 1.0. "
"This is multiplied by the AIPlayer's base movement rates (as defined in "
"its PlayerData datablock)\n\n"
"@see getMoveDestination()\n")
{
object->setMoveSpeed(speed);
}
DefineEngineMethod( AIPlayer, setMoveDestination, void, ( Point3F goal, bool slowDown ), ( true ),
"@brief Tells the AI to move to the location provided\n\n"
"@param goal Coordinates in world space representing location to move to.\n"
"@param slowDown A boolean value. If set to true, the bot will slow down "
"when it gets within 5-meters of its move destination. If false, the bot "
"will stop abruptly when it reaches the move destination. By default, this is true.\n\n"
"@note Upon reaching a move destination, the bot will clear its move destination and "
"calls to getMoveDestination will return \"0 0 0\"."
"@see getMoveDestination()\n")
{
object->setMoveDestination( goal, slowDown);
}
DefineEngineMethod( AIPlayer, getMoveDestination, Point3F, (),,
"@brief Get the AIPlayer's current destination.\n\n"
"@return Returns a point containing the \"x y z\" position "
"of the AIPlayer's current move destination. If no move destination "
"has yet been set, this returns \"0 0 0\"."
"@see setMoveDestination()\n")
{
return object->getMoveDestination();
}
DefineEngineMethod( AIPlayer, setAimLocation, void, ( Point3F target ),,
"@brief Tells the AIPlayer to aim at the location provided.\n\n"
"@param target An \"x y z\" position in the game world to target.\n\n"
"@see getAimLocation()\n")
{
object->setAimLocation(target);
}
DefineEngineMethod( AIPlayer, getAimLocation, Point3F, (),,
"@brief Returns the point the AIPlayer is aiming at.\n\n"
"This will reflect the position set by setAimLocation(), "
"or the position of the object that the bot is now aiming at. "
"If the bot is not aiming at anything, this value will "
"change to whatever point the bot's current line-of-sight intercepts."
"@return World space coordinates of the object AI is aiming at. Formatted as \"X Y Z\".\n\n"
"@see setAimLocation()\n"
"@see setAimObject()\n")
{
return object->getAimLocation();
}
ConsoleDocFragment _setAimObject(
"@brief Sets the AIPlayer's target object. May optionally set an offset from target location\n\n"
"@param targetObject The object to target\n"
"@param offset Optional three-element offset vector which will be added to the position of the aim object.\n\n"
"@tsexample\n"
"// Without an offset\n"
"%ai.setAimObject(%target);\n\n"
"// With an offset\n"
"// Cause our AI object to aim at the target\n"
"// offset (0, 0, 1) so you don't aim at the target's feet\n"
"%ai.setAimObject(%target, \"0 0 1\");\n"
"@endtsexample\n\n"
"@see getAimLocation()\n"
"@see getAimObject()\n"
"@see clearAim()\n",
"AIPlayer",
"void setAimObject(GameBase targetObject, Point3F offset);"
);
ConsoleMethod( AIPlayer, setAimObject, void, 3, 4, "( GameBase obj, [Point3F offset] )"
"Sets the bot's target object. Optionally set an offset from target location."
"@hide")
{
Point3F off( 0.0f, 0.0f, 0.0f );
// Find the target
GameBase *targetObject;
if( Sim::findObject( argv[2], targetObject ) )
{
if (argc == 4)
dSscanf( argv[3], "%g %g %g", &off.x, &off.y, &off.z );
object->setAimObject( targetObject, off );
}
else
object->setAimObject( 0, off );
}
DefineEngineMethod( AIPlayer, getAimObject, S32, (),,
"@brief Gets the object the AIPlayer is targeting.\n\n"
"@return Returns -1 if no object is being aimed at, "
"or the SimObjectID of the object the AIPlayer is aiming at.\n\n"
"@see setAimObject()\n")
{
GameBase* obj = object->getAimObject();
return obj? obj->getId(): -1;
}

View file

@ -0,0 +1,94 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _AIPLAYER_H_
#define _AIPLAYER_H_
#ifndef _PLAYER_H_
#include "T3D/player.h"
#endif
class AIPlayer : public Player {
typedef Player Parent;
public:
enum MoveState {
ModeStop, // AI has stopped moving.
ModeMove, // AI is currently moving.
ModeStuck, // AI is stuck, but wants to move.
ModeSlowing, // AI is slowing down as it reaches it's destination.
};
private:
MoveState mMoveState;
F32 mMoveSpeed;
F32 mMoveTolerance; // Distance from destination before we stop
Point3F mMoveDestination; // Destination for movement
Point3F mLastLocation; // For stuck check
F32 mMoveStuckTolerance; // Distance tolerance on stuck check
S32 mMoveStuckTestDelay; // The number of ticks to wait before checking if the AI is stuck
S32 mMoveStuckTestCountdown; // The current countdown until at AI starts to check if it is stuck
bool mMoveSlowdown; // Slowdown as we near the destination
SimObjectPtr<GameBase> mAimObject; // Object to point at, overrides location
bool mAimLocationSet; // Has an aim location been set?
Point3F mAimLocation; // Point to look at
bool mTargetInLOS; // Is target object visible?
Point3F mAimOffset;
// Utility Methods
void throwCallback( const char *name );
public:
DECLARE_CONOBJECT( AIPlayer );
AIPlayer();
~AIPlayer();
static void initPersistFields();
bool onAdd();
virtual bool getAIMove( Move *move );
// Targeting and aiming sets/gets
void setAimObject( GameBase *targetObject );
void setAimObject( GameBase *targetObject, Point3F offset );
GameBase* getAimObject() const { return mAimObject; }
void setAimLocation( const Point3F &location );
Point3F getAimLocation() const { return mAimLocation; }
void clearAim();
// Movement sets/gets
void setMoveSpeed( const F32 speed );
F32 getMoveSpeed() const { return mMoveSpeed; }
void setMoveTolerance( const F32 tolerance );
F32 getMoveTolerance() const { return mMoveTolerance; }
void setMoveDestination( const Point3F &location, bool slowdown );
Point3F getMoveDestination() const { return mMoveDestination; }
void stopMove();
};
#endif

1978
Engine/source/T3D/camera.cpp Normal file

File diff suppressed because it is too large Load diff

243
Engine/source/T3D/camera.h Normal file
View file

@ -0,0 +1,243 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _CAMERA_H_
#define _CAMERA_H_
#ifndef _SHAPEBASE_H_
#include "T3D/shapeBase.h"
#endif
#ifndef _DYNAMIC_CONSOLETYPES_H_
#include "console/dynamicTypes.h"
#endif
class CameraData: public ShapeBaseData
{
public:
typedef ShapeBaseData Parent;
// ShapeBaseData.
DECLARE_CONOBJECT( CameraData );
DECLARE_CATEGORY( "Game" );
DECLARE_DESCRIPTION( "A datablock that describes a camera." );
static void initPersistFields();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
};
/// Implements a basic camera object.
class Camera: public ShapeBase
{
public:
typedef ShapeBase Parent;
/// Movement behavior type for camera.
enum CameraMotionMode
{
StationaryMode = 0,
FreeRotateMode,
FlyMode,
OrbitObjectMode,
OrbitPointMode,
TrackObjectMode,
OverheadMode,
EditOrbitMode, ///< Used by the World Editor
CameraFirstMode = 0,
CameraLastMode = EditOrbitMode
};
protected:
enum MaskBits
{
MoveMask = Parent::NextFreeMask,
UpdateMask = Parent::NextFreeMask << 1,
NewtonCameraMask = Parent::NextFreeMask << 2,
EditOrbitMask = Parent::NextFreeMask << 3,
NextFreeMask = Parent::NextFreeMask << 4
};
struct StateDelta
{
Point3F pos;
Point3F rot;
VectorF posVec;
VectorF rotVec;
};
Point3F mRot;
StateDelta mDelta;
Point3F mOffset;
static F32 smMovementSpeed;
SimObjectPtr<GameBase> mOrbitObject;
F32 mMinOrbitDist;
F32 mMaxOrbitDist;
F32 mCurOrbitDist;
Point3F mPosition;
bool mObservingClientObject;
/// @name NewtonFlyMode
/// @{
VectorF mAngularVelocity;
F32 mAngularForce;
F32 mAngularDrag;
VectorF mVelocity;
bool mNewtonMode;
bool mNewtonRotation;
F32 mMass;
F32 mDrag;
F32 mFlyForce;
F32 mSpeedMultiplier;
F32 mBrakeMultiplier;
/// @}
/// @name EditOrbitMode
/// @{
bool mValidEditOrbitPoint;
Point3F mEditOrbitPoint;
F32 mCurrentEditOrbitDist;
/// @}
bool mLocked;
CameraMotionMode mMode;
void _setPosition(const Point3F& pos,const Point3F& viewRot);
void _setRenderPosition(const Point3F& pos,const Point3F& viewRot);
void _validateEyePoint( F32 pos, MatrixF* mat );
void _calcOrbitPoint( MatrixF* mat, const Point3F& rot );
void _calcEditOrbitPoint( MatrixF *mat, const Point3F& rot );
static bool _setModeField( void *object, const char *index, const char *data );
static bool _setNewtonField( void *object, const char *index, const char *data );
// ShapeBase.
virtual F32 getCameraFov();
virtual void setCameraFov( F32 fov );
virtual F32 getDefaultCameraFov();
virtual bool isValidCameraFov( F32 fov );
virtual F32 getDamageFlash() const;
virtual F32 getWhiteOut() const;
virtual void setTransform( const MatrixF& mat );
virtual void setRenderTransform( const MatrixF& mat );
public:
Camera();
~Camera();
CameraMotionMode getMode() const { return mMode; }
Point3F getPosition();
Point3F getRotation() { return mRot; };
void setRotation( const Point3F& viewRot );
Point3F getOffset() { return mOffset; };
void lookAt( const Point3F& pos);
void setOffset( const Point3F& offset) { if( mOffset != offset ) mOffset = offset; setMaskBits( UpdateMask ); }
void setFlyMode();
void setOrbitMode( GameBase *obj, const Point3F& pos, const Point3F& rot, const Point3F& offset,
F32 minDist, F32 maxDist, F32 curDist, bool ownClientObject, bool locked = false );
void setTrackObject( GameBase *obj, const Point3F& offset);
void onDeleteNotify( SimObject* obj );
GameBase* getOrbitObject() { return(mOrbitObject); }
bool isObservingClientObject() { return(mObservingClientObject); }
/// @name NewtonFlyMode
/// @{
void setNewtonFlyMode();
VectorF getVelocity() const { return mVelocity; }
void setVelocity( const VectorF& vel );
VectorF getAngularVelocity() const { return mAngularVelocity; }
void setAngularVelocity( const VectorF& vel );
bool isRotationDamped() {return mNewtonRotation;}
void setAngularForce( F32 force ) {mAngularForce = force; setMaskBits(NewtonCameraMask);}
void setAngularDrag( F32 drag ) {mAngularDrag = drag; setMaskBits(NewtonCameraMask);}
void setMass( F32 mass ) {mMass = mass; setMaskBits(NewtonCameraMask);}
void setDrag( F32 drag ) {mDrag = drag; setMaskBits(NewtonCameraMask);}
void setFlyForce( F32 force ) {mFlyForce = force; setMaskBits(NewtonCameraMask);}
void setSpeedMultiplier( F32 mul ) {mSpeedMultiplier = mul; setMaskBits(NewtonCameraMask);}
void setBrakeMultiplier( F32 mul ) {mBrakeMultiplier = mul; setMaskBits(NewtonCameraMask);}
/// @}
/// @name EditOrbitMode
/// @{
void setEditOrbitMode();
bool isEditOrbitMode() {return mMode == EditOrbitMode;}
bool getValidEditOrbitPoint() { return mValidEditOrbitPoint; }
void setValidEditOrbitPoint( bool state );
Point3F getEditOrbitPoint() const;
void setEditOrbitPoint( const Point3F& pnt );
/// Orient the camera to view the given radius. Requires that an
/// edit orbit point has been set.
void autoFitRadius( F32 radius );
/// @}
// ShapeBase.
static void initPersistFields();
static void consoleInit();
virtual void onEditorEnable();
virtual void onEditorDisable();
virtual bool onAdd();
virtual void onRemove();
virtual void processTick( const Move* move );
virtual void interpolateTick( F32 delta);
virtual void getCameraTransform( F32* pos,MatrixF* mat );
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 );
DECLARE_CONOBJECT( Camera );
DECLARE_CATEGORY( "Game" );
DECLARE_DESCRIPTION( "Represents a position, direction and field of view to render a scene from." );
};
typedef Camera::CameraMotionMode CameraMotionMode;
DefineEnumType( CameraMotionMode );
#endif

View file

@ -0,0 +1,387 @@
//-----------------------------------------------------------------------------
// 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 "T3D/cameraSpline.h"
#include "console/console.h"
#include "gfx/gfxDevice.h"
//-----------------------------------------------------------------------------
CameraSpline::Knot::Knot(const Knot &k)
{
mPosition = k.mPosition;
mRotation = k.mRotation;
mSpeed = k.mSpeed;
mType = k.mType;
mPath = k.mPath;
prev = NULL; next = NULL;
}
CameraSpline::Knot::Knot(const Point3F &p, const QuatF &r, F32 s, Knot::Type type, Knot::Path path)
{
mPosition = p;
mRotation = r;
mSpeed = s;
mType = type;
mPath = path;
prev = NULL; next = NULL;
}
//-----------------------------------------------------------------------------
CameraSpline::CameraSpline()
{
mFront = NULL;
mSize = 0;
mIsMapDirty = true;
VECTOR_SET_ASSOCIATION(mTimeMap);
}
CameraSpline::~CameraSpline()
{
removeAll();
}
void CameraSpline::push_back(Knot *w)
{
if (!mFront)
{
mFront = w;
w->next = w;
w->prev = w;
}
else
{
Knot *before = back();
Knot *after = before->next;
w->next = before->next;
w->prev = before;
after->prev = w;
before->next = w;
}
++mSize;
mIsMapDirty = true;
}
CameraSpline::Knot* CameraSpline::getKnot(S32 i)
{
Knot *k = mFront;
while(i--)
k = k->next;
return k;
}
CameraSpline::Knot* CameraSpline::remove(Knot *w)
{
if (w->next == mFront && w->prev == mFront)
mFront = NULL;
else
{
w->prev->next = w->next;
w->next->prev = w->prev;
if (mFront == w)
mFront = w->next;
}
--mSize;
mIsMapDirty = true;
return w;
}
void CameraSpline::removeAll()
{
while(front())
delete remove(front());
mSize = 0;
}
//-----------------------------------------------------------------------------
static bool gBuilding = false;
void CameraSpline::buildTimeMap()
{
if (!mIsMapDirty)
return;
gBuilding = true;
mTimeMap.clear();
mTimeMap.reserve(size()*3); // preallocate
// Initial node and knot value..
TimeMap map;
map.mTime = 0;
map.mDistance = 0;
mTimeMap.push_back(map);
Knot ka,kj,ki;
value(0, &kj, true);
F32 length = 0.0f;
ka = kj;
// Loop through the knots and add nodes. Nodes are added for every knot and
// whenever the spline length and segment length deviate by epsilon.
F32 epsilon = Con::getFloatVariable("CameraSpline::epsilon", 0.90f);
const F32 Step = 0.05f;
F32 lt = 0,time = 0;
do
{
if ((time += Step) > F32(mSize - 1))
time = (F32)mSize - 1.0f;
value(time, &ki, true);
length += (ki.mPosition - kj.mPosition).len();
F32 segment = (ki.mPosition - ka.mPosition).len();
if ((segment / length) < epsilon || time == (mSize - 1) || mFloor(lt) != mFloor(time))
{
map.mTime = time;
map.mDistance = length;
mTimeMap.push_back(map);
ka = ki;
}
kj = ki;
lt = time;
}
while (time < mSize - 1);
mIsMapDirty = false;
gBuilding = false;
}
//-----------------------------------------------------------------------------
void CameraSpline::renderTimeMap()
{
buildTimeMap();
gBuilding = true;
// Build vertex buffer
GFXVertexBufferHandle<GFXVertexPC> vb;
vb.set(GFX, mTimeMap.size(), GFXBufferTypeVolatile);
vb.lock();
MRandomLCG random(1376312589 * (U32)this);
int index = 0;
for(Vector<TimeMap>::iterator itr=mTimeMap.begin(); itr != mTimeMap.end(); itr++)
{
Knot a;
value(itr->mTime, &a, true);
S32 cr = random.randI(0,255);
S32 cg = random.randI(0,255);
S32 cb = random.randI(0,255);
vb[index].color.set(cr, cg, cb);
vb[index].point.set(a.mPosition.x, a.mPosition.y, a.mPosition.z);
index++;
}
gBuilding = false;
vb.unlock();
// Render the buffer
GFX->pushWorldMatrix();
GFX->disableShaders();
GFX->setVertexBuffer(vb);
GFX->drawPrimitive(GFXLineStrip,0,index);
GFX->popWorldMatrix();
}
//-----------------------------------------------------------------------------
F32 CameraSpline::advanceTime(F32 t, S32 delta_ms)
{
buildTimeMap();
Knot k;
value(t, &k, false);
F32 dist = getDistance(t) + k.mSpeed * (F32(delta_ms) / 1000.0f);
return getTime(dist);
}
F32 CameraSpline::advanceDist(F32 t, F32 meters)
{
buildTimeMap();
F32 dist = getDistance(t) + meters;
return getTime(dist);
}
F32 CameraSpline::getDistance(F32 t)
{
if (mSize <= 1)
return 0;
// Find the nodes spanning the time
Vector<TimeMap>::iterator end = mTimeMap.begin() + 1, start;
for (; end < (mTimeMap.end() - 1) && end->mTime < t; end++) { }
start = end - 1;
// Interpolate between the two nodes
F32 i = (t - start->mTime) / (end->mTime - start->mTime);
return start->mDistance + (end->mDistance - start->mDistance) * i;
}
F32 CameraSpline::getTime(F32 d)
{
if (mSize <= 1)
return 0;
// Find nodes spanning the distance
Vector<TimeMap>::iterator end = mTimeMap.begin() + 1, start;
for (; end < (mTimeMap.end() - 1) && end->mDistance < d; end++) { }
start = end - 1;
// Check for duplicate points..
F32 seg = end->mDistance - start->mDistance;
if (!seg)
return end->mTime;
// Interpolate between the two nodes
F32 i = (d - start->mDistance) / (end->mDistance - start->mDistance);
return start->mTime + (end->mTime - start->mTime) * i;
}
//-----------------------------------------------------------------------------
void CameraSpline::value(F32 t, CameraSpline::Knot *result, bool skip_rotation)
{
// Do some easing in and out for t.
if(!gBuilding)
{
F32 oldT = t;
if(oldT < 0.5f)
{
t = 0.5f - (mSin( (0.5 - oldT) * M_PI ) / 2.f);
}
if((F32(size()) - 1.5f) > 0.f && oldT - (F32(size()) - 1.5f) > 0.f)
{
oldT -= (F32(size()) - 1.5f);
t = (F32(size()) - 1.5f) + (mCos( (0.5f - oldT) * F32(M_PI) ) / 2.f);
}
}
// Verify that t is in range [0 >= t > size]
// AssertFatal(t >= 0.0f && t < (F32)size(), "t out of range");
Knot *p1 = getKnot((S32)mFloor(t));
Knot *p2 = next(p1);
F32 i = t - mFloor(t); // adjust t to 0 to 1 on p1-p2 interval
if (p1->mPath == Knot::SPLINE)
{
Knot *p0 = (p1->mType == Knot::KINK) ? p1 : prev(p1);
Knot *p3 = (p2->mType == Knot::KINK) ? p2 : next(p2);
result->mPosition.x = mCatmullrom(i, p0->mPosition.x, p1->mPosition.x, p2->mPosition.x, p3->mPosition.x);
result->mPosition.y = mCatmullrom(i, p0->mPosition.y, p1->mPosition.y, p2->mPosition.y, p3->mPosition.y);
result->mPosition.z = mCatmullrom(i, p0->mPosition.z, p1->mPosition.z, p2->mPosition.z, p3->mPosition.z);
}
else
{ // Linear
result->mPosition.interpolate(p1->mPosition, p2->mPosition, i);
}
if (skip_rotation)
return;
buildTimeMap();
// find the two knots to interpolate rotation and velocity through since some
// knots are only positional
S32 start = (S32)mFloor(t);
S32 end = (p2 == p1) ? start : (start + 1);
while (p1->mType == Knot::POSITION_ONLY && p1 != front())
{
p1 = prev(p1);
start--;
}
while (p2->mType == Knot::POSITION_ONLY && p2 != back())
{
p2 = next(p2);
end++;
}
if (start == end)
{
result->mRotation = p1->mRotation;
result->mSpeed = p1->mSpeed;
}
else
{
F32 c = getDistance(t);
F32 d1 = getDistance((F32)start);
F32 d2 = getDistance((F32)end);
if (d1 == d2)
{
result->mRotation = p2->mRotation;
result->mSpeed = p2->mSpeed;
}
else
{
i = (c-d1)/(d2-d1);
if(p1->mPath == Knot::SPLINE)
{
Knot *p0 = (p1->mType == Knot::KINK) ? p1 : prev(p1);
Knot *p3 = (p2->mType == Knot::KINK) ? p2 : next(p2);
F32 q,w,e;
q = mCatmullrom(i, 0, 1, 1, 1);
w = mCatmullrom(i, 0, 0, 0, 1);
e = mCatmullrom(i, 0, 0, 1, 1);
QuatF a; a.interpolate(p0->mRotation, p1->mRotation, q);
QuatF b; b.interpolate(p2->mRotation, p3->mRotation, w);
result->mRotation.interpolate(a, b, e);
result->mSpeed = mCatmullrom(i, p0->mSpeed, p1->mSpeed, p2->mSpeed, p3->mSpeed);
}
else
{
result->mRotation.interpolate(p1->mRotation, p2->mRotation, i);
result->mSpeed = (p1->mSpeed * (1.0f-i)) + (p2->mSpeed * i);
}
}
}
}

View file

@ -0,0 +1,108 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _CAMERASPLINE_H_
#define _CAMERASPLINE_H_
#include "math/mMath.h"
#include "core/util/tVector.h"
//----------------------------------------------------------------------------
class CameraSpline
{
public:
struct Knot
{
public:
Point3F mPosition;
QuatF mRotation;
F32 mSpeed; /// in meters per second
enum Type {
NORMAL,
POSITION_ONLY,
KINK,
NUM_TYPE_BITS = 2
}mType;
enum Path {
LINEAR,
SPLINE,
NUM_PATH_BITS = 1
}mPath;
F32 mDistance;
Knot *prev;
Knot *next;
Knot() {};
Knot(const Knot &k);
Knot(const Point3F &p, const QuatF &r, F32 s, Knot::Type type = NORMAL, Knot::Path path = SPLINE);
};
CameraSpline();
~CameraSpline();
bool isEmpty() { return (mFront == NULL); }
S32 size() { return mSize; }
Knot* remove(Knot *w);
void removeAll();
Knot* front() { return mFront; }
Knot* back() { return (mFront == NULL) ? NULL : mFront->prev; }
void push_back(Knot *w);
void push_front(Knot *w) { push_back(w); mFront = w; mIsMapDirty = true; }
Knot* getKnot(S32 i);
Knot* next(Knot *k) { return (k->next == mFront) ? k : k->next; }
Knot* prev(Knot *k) { return (k == mFront) ? k : k->prev; }
F32 advanceTime(F32 t, S32 delta_ms);
F32 advanceDist(F32 t, F32 meters);
void value(F32 t, Knot *result, bool skip_rotation=false);
F32 getDistance(F32 t);
F32 getTime(F32 d);
void renderTimeMap();
private:
Knot *mFront;
S32 mSize;
bool mIsMapDirty;
struct TimeMap {
F32 mTime;
F32 mDistance;
};
Vector<TimeMap> mTimeMap;
void buildTimeMap();
};
#endif

View file

@ -0,0 +1,97 @@
//-----------------------------------------------------------------------------
// 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/containerQuery.h"
#include "scene/sceneObject.h"
#include "environment/waterObject.h"
#include "T3D/physicalZone.h"
void findRouter( SceneObject *obj, void *key )
{
if (obj->getTypeMask() & WaterObjectType)
waterFind(obj, key);
else if (obj->getTypeMask() & PhysicalZoneObjectType)
physicalZoneFind(obj, key);
else {
AssertFatal(false, "Error, must be either water or physical zone here!");
}
}
void waterFind( SceneObject *obj, void *key )
{
PROFILE_SCOPE( waterFind );
// This is called for each WaterObject the ShapeBase object is overlapping.
ContainerQueryInfo *info = static_cast<ContainerQueryInfo*>(key);
WaterObject *water = dynamic_cast<WaterObject*>(obj);
AssertFatal( water != NULL, "containerQuery - waterFind(), passed object was not of class WaterObject!");
// Get point at the bottom/center of the box.
Point3F testPnt = info->box.getCenter();
testPnt.z = info->box.minExtents.z;
F32 coverage = water->getWaterCoverage(info->box);
// Since a WaterObject can have global bounds we may get this call
// even though we have zero coverage. If so we want to early out and
// not save the water properties.
if ( coverage == 0.0f )
return;
// Add in flow force. Would be appropriate to try scaling it by coverage
// thought. Or perhaps have getFlow do that internally and take
// the box parameter.
info->appliedForce += water->getFlow( testPnt );
// Only save the following properties for the WaterObject with the
// greatest water coverage for this ShapeBase object.
if ( coverage < info->waterCoverage )
return;
info->waterCoverage = coverage;
info->liquidType = water->getLiquidType();
info->waterViscosity = water->getViscosity();
info->waterDensity = water->getDensity();
info->waterHeight = water->getSurfaceHeight( Point2F(testPnt.x,testPnt.y) );
info->waterObject = water;
}
void physicalZoneFind(SceneObject* obj, void *key)
{
PROFILE_SCOPE( physicalZoneFind );
ContainerQueryInfo *info = static_cast<ContainerQueryInfo*>(key);
PhysicalZone* pz = dynamic_cast<PhysicalZone*>(obj);
AssertFatal(pz != NULL, "Error, not a physical zone!");
if (pz == NULL || pz->testBox(info->box) == false)
return;
if (pz->isActive()) {
info->gravityScale *= pz->getGravityMod();
info->appliedForce += pz->getForce();
}
}

View file

@ -0,0 +1,71 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _CONTAINERQUERY_H_
#define _CONTAINERQUERY_H_
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _STRINGTABLE_H_
#include "core/stringTable.h"
#endif
#ifndef _MBOX_H_
#include "math/mBox.h"
#endif
class SceneObject;
class WaterObject;
struct ContainerQueryInfo
{
ContainerQueryInfo()
: waterCoverage(0.0f),
waterHeight(0.0f),
waterDensity(0.0f),
waterViscosity(0.0f),
gravityScale(1.0f),
appliedForce(0,0,0),
box(-1,-1,-1,1,1,1),
mass(1.0f),
waterObject(NULL)
{
}
//SceneObject *sceneObject;
Box3F box;
F32 mass;
F32 waterCoverage;
F32 waterHeight;
F32 waterDensity;
F32 waterViscosity;
String liquidType;
F32 gravityScale;
Point3F appliedForce;
WaterObject *waterObject;
};
extern void findRouter( SceneObject *obj, void *key );
extern void waterFind( SceneObject *obj, void *key );
extern void physicalZoneFind( SceneObject *obj, void *key );
#endif // _CONTAINERQUERY_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,248 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _CONVEXSHAPE_H_
#define _CONVEXSHAPE_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _GFXVERTEXBUFFER_H_
#include "gfx/gfxVertexBuffer.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
#ifndef _CONVEX_H_
#include "collision/convex.h"
#endif
class ConvexShape;
// Crap name, but whatcha gonna do.
class ConvexShapeCollisionConvex : public Convex
{
typedef Convex Parent;
friend class ConvexShape;
protected:
ConvexShape *pShape;
public:
ConvexShapeCollisionConvex() { mType = ConvexShapeCollisionConvexType; }
ConvexShapeCollisionConvex( const ConvexShapeCollisionConvex& cv ) {
mType = ConvexShapeCollisionConvexType;
mObject = cv.mObject;
pShape = cv.pShape;
}
Point3F support(const VectorF& vec) const;
void getFeatures(const MatrixF& mat,const VectorF& n, ConvexFeature* cf);
void getPolyList(AbstractPolyList* list);
};
GFXDeclareVertexFormat( ConvexVert )
{
Point3F point;
GFXVertexColor color;
Point3F normal;
Point3F tangent;
Point2F texCoord;
};
class PhysicsBody;
// Define our vertex format here so we don't have to
// change it in multiple spots later
typedef ConvexVert VertexType;
class ConvexShape : public SceneObject
{
typedef SceneObject Parent;
friend class GuiConvexEditorCtrl;
friend class GuiConvexEditorUndoAction;
friend class ConvexShapeCollisionConvex;
public:
enum NetBits {
TransformMask = Parent::NextFreeMask,
UpdateMask = Parent::NextFreeMask << 1,
NextFreeMask = Parent::NextFreeMask << 2
};
// Declaring these structs directly within ConvexShape to prevent
// the otherwise excessively deep scoping we had.
// eg. ConvexShape::Face::Triangle ...
struct Edge
{
U32 p0;
U32 p1;
};
struct Triangle
{
U32 p0;
U32 p1;
U32 p2;
U32 operator []( U32 index ) const
{
AssertFatal( index >= 0 && index <= 2, "index out of range" );
return *( (&p0) + index );
}
};
struct Face
{
Vector< Edge > edges;
Vector< U32 > points;
Vector< U32 > winding;
Vector< Point2F > texcoords;
Vector< Triangle > triangles;
Point3F tangent;
Point3F normal;
Point3F centroid;
F32 area;
S32 id;
};
struct Geometry
{
void generate( const Vector< PlaneF > &planes, const Vector< Point3F > &tangents );
Vector< Point3F > points;
Vector< Face > faces;
};
static bool smRenderEdges;
public:
ConvexShape();
virtual ~ConvexShape();
DECLARE_CONOBJECT( ConvexShape );
// ConsoleObject
static void initPersistFields();
// SimObject
virtual void inspectPostApply();
virtual bool onAdd();
virtual void onRemove();
virtual void writeFields(Stream &stream, U32 tabStop);
virtual bool writeField( StringTableEntry fieldname, const char *value );
// NetObject
virtual U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream );
virtual void unpackUpdate( NetConnection *conn, BitStream *stream );
// SceneObject
virtual void onScaleChanged();
virtual void setTransform( const MatrixF &mat );
virtual void prepRenderImage( SceneRenderState *state );
virtual void buildConvex( const Box3F &box, Convex *convex );
virtual bool buildPolyList( PolyListContext context, AbstractPolyList *polyList, const Box3F &box, const SphereF &sphere );
virtual bool castRay( const Point3F &start, const Point3F &end, RayInfo *info );
virtual bool collideBox( const Point3F &start, const Point3F &end, RayInfo *info );
void updateBounds( bool recenter );
void recenter();
/// Geometry access.
/// @{
MatrixF getSurfaceWorldMat( S32 faceid, bool scaled = false ) const;
void cullEmptyPlanes( Vector< U32 > *removedPlanes );
void exportToCollada();
void resizePlanes( const Point3F &size );
void getSurfaceLineList( S32 surfId, Vector< Point3F > &lineList );
Geometry& getGeometry() { return mGeometry; }
Vector<MatrixF>& getSurfaces() { return mSurfaces; }
void getSurfaceTriangles( S32 surfId, Vector< Point3F > *outPoints, Vector< Point2F > *outCoords, bool worldSpace );
/// @}
/// Geometry Visualization.
/// @{
void renderFaceEdges( S32 faceid, const ColorI &color = ColorI::WHITE, F32 lineWidth = 1.0f );
/// @}
protected:
void _updateMaterial();
void _updateGeometry( bool updateCollision = false );
void _updateCollision();
void _export( OptimizedPolyList *plist, const Box3F &box, const SphereF &sphere );
void _renderDebug( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *mat );
static S32 QSORT_CALLBACK _comparePlaneDist( const void *a, const void *b );
static bool protectedSetSurface( void *object, const char *index, const char *data );
protected:
// The name of the Material we will use for rendering
String mMaterialName;
// The actual Material instance
BaseMatInstance* mMaterialInst;
// The GFX vertex and primitive buffers
GFXVertexBufferHandle< VertexType > mVertexBuffer;
GFXPrimitiveBufferHandle mPrimitiveBuffer;
U32 mVertCount;
U32 mPrimCount;
Geometry mGeometry;
Vector< PlaneF > mPlanes;
Vector< MatrixF > mSurfaces;
Vector< Point3F > mFaceCenters;
Convex *mConvexList;
PhysicsBody *mPhysicsRep;
/// Geometry visualization
/// @{
F32 mNormalLength;
/// @}
};
#endif // _CONVEXSHAPE_H_

View file

@ -0,0 +1,942 @@
//-----------------------------------------------------------------------------
// 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/debris.h"
#include "core/stream/bitStream.h"
#include "math/mathUtils.h"
#include "console/consoleTypes.h"
#include "console/consoleObject.h"
#include "sim/netConnection.h"
#include "scene/sceneRenderState.h"
#include "scene/sceneManager.h"
#include "ts/tsShapeInstance.h"
#include "ts/tsPartInstance.h"
#include "T3D/fx/particleEmitter.h"
#include "T3D/fx/explosion.h"
#include "T3D/gameBase/gameProcess.h"
#include "core/resourceManager.h"
#include "gfx/gfxTransformSaver.h"
#include "console/engineAPI.h"
#include "lighting/lightQuery.h"
const U32 csmStaticCollisionMask = TerrainObjectType |
InteriorObjectType;
const U32 csmDynamicCollisionMask = StaticShapeObjectType;
IMPLEMENT_CO_DATABLOCK_V1(DebrisData);
ConsoleDocClass( DebrisData,
"@brief Stores properties for an individual debris type.\n\n"
"DebrisData defines the base properties for a Debris object. Typically you'll want a Debris object to consist of "
"a shape and possibly up to two particle emitters. The DebrisData datablock provides the definition for these items, "
"along with physical properties and how a Debris object will react to other game objects, such as water and terrain.\n"
"@tsexample\n"
"datablock DebrisData(GrenadeDebris)\n"
"{\n"
" shapeFile = \"art/shapes/weapons/ramrifle/debris.dts\";\n"
" emitters[0] = GrenadeDebrisFireEmitter;\n"
" elasticity = 0.4;\n"
" friction = 0.25;\n"
" numBounces = 3;\n"
" bounceVariance = 1;\n"
" explodeOnMaxBounce = false;\n"
" staticOnMaxBounce = false;\n"
" snapOnMaxBounce = false;\n"
" minSpinSpeed = 200;\n"
" maxSpinSpeed = 600;\n"
" lifetime = 4;\n"
" lifetimeVariance = 1.5;\n"
" velocity = 15;\n"
" velocityVariance = 5;\n"
" fade = true;\n"
" useRadiusMass = true;\n"
" baseRadius = 0.3;\n"
" gravModifier = 1.0;\n"
" terminalVelocity = 20;\n"
" ignoreWater = false;\n"
"};\n"
"@endtsexample\n\n"
"@see Debris\n\n"
"@ingroup FX\n"
);
DebrisData::DebrisData()
{
dMemset( emitterList, 0, sizeof( emitterList ) );
dMemset( emitterIDList, 0, sizeof( emitterIDList ) );
explosion = NULL;
explosionId = 0;
velocity = 0.0f;
velocityVariance = 0.0;
elasticity = 0.3f;
friction = 0.2f;
numBounces = 0;
bounceVariance = 0;
minSpinSpeed = maxSpinSpeed = 0.0;
staticOnMaxBounce = false;
explodeOnMaxBounce = false;
snapOnMaxBounce = false;
lifetime = 3.0f;
lifetimeVariance = 0.0f;
minSpinSpeed = 0.0f;
maxSpinSpeed = 0.0f;
textureName = NULL;
shapeName = NULL;
fade = true;
useRadiusMass = false;
baseRadius = 1.0f;
gravModifier = 1.0f;
terminalVelocity = 0.0f;
ignoreWater = true;
}
bool DebrisData::onAdd()
{
if(!Parent::onAdd())
return false;
for( int i=0; i<DDC_NUM_EMITTERS; i++ )
{
if( !emitterList[i] && emitterIDList[i] != 0 )
{
if( Sim::findObject( emitterIDList[i], emitterList[i] ) == false)
{
Con::errorf( ConsoleLogEntry::General, "DebrisData::onAdd: Invalid packet, bad datablockId(emitter): 0x%x", emitterIDList[i]);
}
}
}
if (!explosion && explosionId != 0)
{
if (!Sim::findObject( SimObjectId( explosionId ), explosion ))
Con::errorf( ConsoleLogEntry::General, "DebrisData::onAdd: Invalid packet, bad datablockId(particle emitter): 0x%x", explosionId);
}
// validate data
if( velocityVariance > velocity )
{
Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: velocityVariance invalid", getName());
velocityVariance = velocity;
}
if( friction < -10.0f || friction > 10.0f )
{
Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: friction invalid", getName());
friction = 0.2f;
}
if( elasticity < -10.0f || elasticity > 10.0f )
{
Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: elasticity invalid", getName());
elasticity = 0.2f;
}
if( lifetime < 0.0f || lifetime > 1000.0f )
{
Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: lifetime invalid", getName());
lifetime = 3.0f;
}
if( lifetimeVariance < 0.0f || lifetimeVariance > lifetime )
{
Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: lifetimeVariance invalid", getName());
lifetimeVariance = 0.0f;
}
if( numBounces < 0 || numBounces > 10000 )
{
Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: numBounces invalid", getName());
numBounces = 3;
}
if( bounceVariance < 0 || bounceVariance > numBounces )
{
Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: bounceVariance invalid", getName());
bounceVariance = 0;
}
if( minSpinSpeed < -10000.0f || minSpinSpeed > 10000.0f || minSpinSpeed > maxSpinSpeed )
{
Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: minSpinSpeed invalid", getName());
minSpinSpeed = maxSpinSpeed - 1.0f;
}
if( maxSpinSpeed < -10000.0f || maxSpinSpeed > 10000.0f )
{
Con::warnf(ConsoleLogEntry::General, "DebrisData(%s)::onAdd: maxSpinSpeed invalid", getName());
maxSpinSpeed = 0.0f;
}
return true;
}
bool DebrisData::preload(bool server, String &errorStr)
{
if (Parent::preload(server, errorStr) == false)
return false;
if( server ) return true;
if( shapeName && shapeName[0] != '\0' && !bool(shape) )
{
shape = ResourceManager::get().load(shapeName);
if( bool(shape) == false )
{
errorStr = String::ToString("DebrisData::load: Couldn't load shape \"%s\"", shapeName);
return false;
}
else
{
TSShapeInstance* pDummy = new TSShapeInstance(shape, !server);
delete pDummy;
}
}
return true;
}
void DebrisData::initPersistFields()
{
addGroup("Display");
addField("texture", TypeString, Offset(textureName, DebrisData),
"@brief Texture imagemap to use for this debris object.\n\nNot used any more.\n");
addField("shapeFile", TypeShapeFilename, Offset(shapeName, DebrisData),
"@brief Object model to use for this debris object.\n\nThis shape is optional. You could have Debris made up of only particles.\n");
endGroup("Display");
addGroup("Datablocks");
addField("emitters", TYPEID< ParticleEmitterData >(), Offset(emitterList, DebrisData), DDC_NUM_EMITTERS,
"@brief List of particle emitters to spawn along with this debris object.\n\nThese are optional. You could have Debris made up of only a shape.\n");
addField("explosion", TYPEID< ExplosionData >(), Offset(explosion, DebrisData),
"@brief ExplosionData to spawn along with this debris object.\n\nThis is optional as not all Debris explode.\n");
endGroup("Datablocks");
addGroup("Physical Properties");
addField("elasticity", TypeF32, Offset(elasticity, DebrisData),
"@brief A floating-point value specifying how 'bouncy' this object is.\n\nMust be in the range of -10 to 10.\n");
addField("friction", TypeF32, Offset(friction, DebrisData),
"@brief A floating-point value specifying how much velocity is lost to impact and sliding friction.\n\nMust be in the range of -10 to 10.\n");
addField("numBounces", TypeS32, Offset(numBounces, DebrisData),
"@brief How many times to allow this debris object to bounce until it either explodes, becomes static or snaps (defined in explodeOnMaxBounce, staticOnMaxBounce, snapOnMaxBounce).\n\n"
"Must be within the range of 0 to 10000.\n"
"@see bounceVariance\n");
addField("bounceVariance", TypeS32, Offset(bounceVariance, DebrisData),
"@brief Allowed variance in the value of numBounces.\n\nMust be less than numBounces.\n@see numBounces\n");
addField("minSpinSpeed", TypeF32, Offset(minSpinSpeed, DebrisData),
"@brief Minimum speed that this debris object will rotate.\n\nMust be in the range of -10000 to 1000, and must be less than maxSpinSpeed.\n@see maxSpinSpeed\n");
addField("maxSpinSpeed", TypeF32, Offset(maxSpinSpeed, DebrisData),
"@brief Maximum speed that this debris object will rotate.\n\nMust be in the range of -10000 to 10000.\n@see minSpinSpeed\n");
addField("gravModifier", TypeF32, Offset(gravModifier, DebrisData), "How much gravity affects debris.");
addField("terminalVelocity", TypeF32, Offset(terminalVelocity, DebrisData), "Max velocity magnitude.");
addField("velocity", TypeF32, Offset(velocity, DebrisData),
"@brief Speed at which this debris object will move.\n\n@see velocityVariance\n");
addField("velocityVariance", TypeF32, Offset(velocityVariance, DebrisData),
"@brief Allowed variance in the value of velocity\n\nMust be less than velocity.\n@see velocity\n");
addField("lifetime", TypeF32, Offset(lifetime, DebrisData),
"@brief Amount of time until this debris object is destroyed.\n\nMust be in the range of 0 to 1000.\n@see lifetimeVariance");
addField("lifetimeVariance", TypeF32, Offset(lifetimeVariance, DebrisData),
"@brief Allowed variance in the value of lifetime.\n\nMust be less than lifetime.\n@see lifetime\n");
addField("useRadiusMass", TypeBool, Offset(useRadiusMass, DebrisData),
"@brief Use mass calculations based on radius.\n\nAllows for the adjustment of elasticity and friction based on the Debris size.\n@see baseRadius\n");
addField("baseRadius", TypeF32, Offset(baseRadius, DebrisData),
"@brief Radius at which the standard elasticity and friction apply.\n\nOnly used when useRaduisMass is true.\n@see useRadiusMass.\n");
endGroup("Physical Properties");
addGroup("Behavior");
addField("explodeOnMaxBounce", TypeBool, Offset(explodeOnMaxBounce, DebrisData),
"@brief If true, this debris object will explode after it has bounced max times.\n\nBe sure to provide an ExplosionData datablock for this to take effect.\n@see explosion\n");
addField("staticOnMaxBounce", TypeBool, Offset(staticOnMaxBounce, DebrisData), "If true, this debris object becomes static after it has bounced max times.");
addField("snapOnMaxBounce", TypeBool, Offset(snapOnMaxBounce, DebrisData), "If true, this debris object will snap into a resting position on the last bounce.");
addField("fade", TypeBool, Offset(fade, DebrisData),
"@brief If true, this debris object will fade out when destroyed.\n\nThis fade occurs over the last second of the Debris' lifetime.\n");
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");
Parent::initPersistFields();
}
void DebrisData::packData(BitStream* stream)
{
Parent::packData(stream);
stream->write(elasticity);
stream->write(friction);
stream->write(numBounces);
stream->write(bounceVariance);
stream->write(minSpinSpeed);
stream->write(maxSpinSpeed);
stream->write(explodeOnMaxBounce);
stream->write(staticOnMaxBounce);
stream->write(snapOnMaxBounce);
stream->write(lifetime);
stream->write(lifetimeVariance);
stream->write(velocity);
stream->write(velocityVariance);
stream->write(fade);
stream->write(useRadiusMass);
stream->write(baseRadius);
stream->write(gravModifier);
stream->write(terminalVelocity);
stream->write(ignoreWater);
stream->writeString( textureName );
stream->writeString( shapeName );
for( int i=0; i<DDC_NUM_EMITTERS; i++ )
{
if( stream->writeFlag( emitterList[i] != NULL ) )
{
stream->writeRangedU32( emitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
}
}
if( stream->writeFlag( explosion ) )
{
stream->writeRangedU32(packed? SimObjectId(explosion):
explosion->getId(),DataBlockObjectIdFirst,DataBlockObjectIdLast);
}
}
void DebrisData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
stream->read(&elasticity);
stream->read(&friction);
stream->read(&numBounces);
stream->read(&bounceVariance);
stream->read(&minSpinSpeed);
stream->read(&maxSpinSpeed);
stream->read(&explodeOnMaxBounce);
stream->read(&staticOnMaxBounce);
stream->read(&snapOnMaxBounce);
stream->read(&lifetime);
stream->read(&lifetimeVariance);
stream->read(&velocity);
stream->read(&velocityVariance);
stream->read(&fade);
stream->read(&useRadiusMass);
stream->read(&baseRadius);
stream->read(&gravModifier);
stream->read(&terminalVelocity);
stream->read(&ignoreWater);
textureName = stream->readSTString();
shapeName = stream->readSTString();
for( int i=0; i<DDC_NUM_EMITTERS; i++ )
{
if( stream->readFlag() )
{
emitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
}
}
if(stream->readFlag())
{
explosionId = (S32)stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast);
}
else
{
explosionId = 0;
}
}
IMPLEMENT_CO_NETOBJECT_V1(Debris);
ConsoleDocClass( Debris,
"@brief Base debris class. Uses the DebrisData datablock for properties of individual debris objects.\n\n"
"Debris is typically made up of a shape and up to two particle emitters. In most cases Debris objects are "
"not created directly. They are usually produced automatically by other means, such as through the Explosion "
"class. When an explosion goes off, its ExplosionData datablock determines what Debris to emit.\n"
"@tsexample\n"
"datablock ExplosionData(GrenadeLauncherExplosion)\n"
"{\n"
" // Assiging debris data\n"
" debris = GrenadeDebris;\n\n"
" // Adjust how debris is ejected\n"
" debrisThetaMin = 10;\n"
" debrisThetaMax = 60;\n"
" debrisNum = 4;\n"
" debrisNumVariance = 2;\n"
" debrisVelocity = 25;\n"
" debrisVelocityVariance = 5;\n\n"
" // Note: other ExplosionData properties are not listed for this example\n"
"};\n"
"@endtsexample\n\n"
"@note Debris are client side only objects.\n"
"@see DebrisData\n"
"@see ExplosionData\n"
"@see Explosion\n"
"@ingroup FX\n"
);
DefineEngineMethod(Debris, init, bool, (const char* inputPosition, const char* inputVelocity),
("1.0 1.0 1.0", "1.0 0.0 0.0"),
"@brief Manually set this piece of debris at the given position with the given velocity.\n\n"
"Usually you do not manually create Debris objects as they are generated through other means, "
"such as an Explosion. This method exists when you do manually create a Debris object and "
"want to have it start moving.\n"
"@param inputPosition Position to place the debris.\n"
"@param inputVelocity Velocity to move the debris after it has been placed.\n"
"@return Always returns true.\n"
"@tsexample\n"
"// Define the position\n"
"%position = \"1.0 1.0 1.0\";\n\n"
"// Define the velocity\n"
"%velocity = \"1.0 0.0 0.0\";\n\n"
"// Inform the debris object of its new position and velocity\n"
"%debris.init(%position,%velocity);\n"
"@endtsexample\n")
{
Point3F pos;
dSscanf( inputPosition, "%f %f %f", &pos.x, &pos.y, &pos.z );
Point3F vel;
dSscanf( inputVelocity, "%f %f %f", &vel.x, &vel.y, &vel.z );
object->init( pos, vel );
return true;
}
Debris::Debris()
{
mTypeMask |= DebrisObjectType | DynamicShapeObjectType;
mVelocity = Point3F( 0.0f, 0.0f, 4.0f );
mLifetime = gRandGen.randF( 1.0f, 10.0f );
mLastPos = getPosition();
mNumBounces = gRandGen.randI( 0, 1 );
mSize = 2.0f;
mElapsedTime = 0.0f;
mShape = NULL;
mPart = NULL;
mDataBlock = NULL;
mXRotSpeed = 0.0f;
mZRotSpeed = 0.0f;
mInitialTrans.identity();
mRadius = 0.2f;
mStatic = false;
dMemset( mEmitterList, 0, sizeof( mEmitterList ) );
// Only allocated client side.
mNetFlags.set( IsGhost );
}
Debris::~Debris()
{
if( mShape )
{
delete mShape;
mShape = NULL;
}
if( mPart )
{
delete mPart;
mPart = NULL;
}
}
void Debris::initPersistFields()
{
addGroup( "Debris" );
addField( "lifetime", TypeF32, Offset(mLifetime, Debris),
"@brief Length of time for this debris object to exist. When expired, the object will be deleted.\n\n"
"The initial lifetime value comes from the DebrisData datablock.\n"
"@see DebrisData::lifetime\n"
"@see DebrisData::lifetimeVariance\n");
endGroup( "Debris" );
Parent::initPersistFields();
}
void Debris::init( const Point3F &position, const Point3F &velocity )
{
setPosition( position );
setVelocity( velocity );
}
bool Debris::onNewDataBlock( GameBaseData *dptr, bool reload )
{
mDataBlock = dynamic_cast< DebrisData* >( dptr );
if( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
return false;
scriptOnNewDataBlock();
return true;
}
bool Debris::onAdd()
{
if( !Parent::onAdd() )
{
return false;
}
// create emitters
for( int i=0; i<DebrisData::DDC_NUM_EMITTERS; i++ )
{
if( mDataBlock->emitterList[i] != NULL )
{
ParticleEmitter * pEmitter = new ParticleEmitter;
pEmitter->onNewDataBlock( mDataBlock->emitterList[i], false );
if( !pEmitter->registerObject() )
{
Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() );
delete pEmitter;
pEmitter = NULL;
}
mEmitterList[i] = pEmitter;
}
}
// set particle sizes based on debris size
F32 sizeList[ParticleData::PDC_NUM_KEYS];
if( mEmitterList[0] )
{
sizeList[0] = mSize * 0.5;
sizeList[1] = mSize;
sizeList[2] = mSize * 1.5;
mEmitterList[0]->setSizes( sizeList );
}
if( mEmitterList[1] )
{
sizeList[0] = 0.0;
sizeList[1] = mSize * 0.5;
sizeList[2] = mSize;
mEmitterList[1]->setSizes( sizeList );
}
S32 bounceVar = (S32)mDataBlock->bounceVariance;
bounceVar = gRandGen.randI( -bounceVar, bounceVar );
mNumBounces = mDataBlock->numBounces + bounceVar;
F32 lifeVar = (mDataBlock->lifetimeVariance * 2.0f * gRandGen.randF(-1.0,1.0)) - mDataBlock->lifetimeVariance;
mLifetime = mDataBlock->lifetime + lifeVar;
F32 xRotSpeed = gRandGen.randF( mDataBlock->minSpinSpeed, mDataBlock->maxSpinSpeed );
F32 zRotSpeed = gRandGen.randF( mDataBlock->minSpinSpeed, mDataBlock->maxSpinSpeed );
zRotSpeed *= gRandGen.randF( 0.1f, 0.5f );
mRotAngles.set( xRotSpeed, 0.0f, zRotSpeed );
mElasticity = mDataBlock->elasticity;
mFriction = mDataBlock->friction;
// Setup our bounding box
if( mDataBlock->shape )
{
mObjBox = mDataBlock->shape->bounds;
}
else
{
mObjBox = Box3F(Point3F(-1, -1, -1), Point3F(1, 1, 1));
}
if( mDataBlock->shape )
{
mShape = new TSShapeInstance( mDataBlock->shape, true);
}
if( mPart )
{
// use half radius becuase we want debris to stick in ground
mRadius = mPart->getRadius() * 0.5;
mObjBox = mPart->getBounds();
}
resetWorldBox();
mInitialTrans = getTransform();
if( mDataBlock->velocity != 0.0 )
{
F32 velocity = mDataBlock->velocity + gRandGen.randF( -mDataBlock->velocityVariance, mDataBlock->velocityVariance );
mVelocity.normalizeSafe();
mVelocity *= velocity;
}
// mass calculations
if( mDataBlock->useRadiusMass )
{
if( mRadius < mDataBlock->baseRadius )
{
mRadius = mDataBlock->baseRadius;
}
// linear falloff
F32 multFactor = mDataBlock->baseRadius / mRadius;
mElasticity *= multFactor;
mFriction *= multFactor;
mRotAngles *= multFactor;
}
// tell engine the debris exists
gClientSceneGraph->addObjectToScene(this);
removeFromProcessList();
ClientProcessList::get()->addObject(this);
NetConnection* pNC = NetConnection::getConnectionToServer();
AssertFatal(pNC != NULL, "Error, must have a connection to the server!");
pNC->addObject(this);
return true;
}
void Debris::onRemove()
{
for( int i=0; i<DebrisData::DDC_NUM_EMITTERS; i++ )
{
if( mEmitterList[i] )
{
mEmitterList[i]->deleteWhenEmpty();
mEmitterList[i] = NULL;
}
}
if( mPart )
{
TSShapeInstance *ss = mPart->getSourceShapeInstance();
if( ss )
{
ss->decDebrisRefCount();
if( ss->getDebrisRefCount() == 0 )
{
delete ss;
}
}
}
getSceneManager()->removeObjectFromScene(this);
getContainer()->removeObject(this);
Parent::onRemove();
}
void Debris::processTick(const Move*)
{
if (mLifetime <= 0.0)
deleteObject();
}
void Debris::advanceTime( F32 dt )
{
mElapsedTime += dt;
mLifetime -= dt;
if( mLifetime <= 0.0 )
{
mLifetime = 0.0;
return;
}
mLastPos = getPosition();
if( !mStatic )
{
rotate( dt );
Point3F nextPos = getPosition();
computeNewState( nextPos, mVelocity, dt );
if( bounce( nextPos, dt ) )
{
--mNumBounces;
if( mNumBounces <= 0 )
{
if( mDataBlock->explodeOnMaxBounce )
{
explode();
mLifetime = 0.0;
}
if( mDataBlock->snapOnMaxBounce )
{
// orient debris so it's flat
MatrixF stat = getTransform();
Point3F dir;
stat.getColumn( 1, &dir );
dir.z = 0.0;
MatrixF newTrans = MathUtils::createOrientFromDir( dir );
// hack for shell casings to get them above ground. Need something better - bramage
newTrans.setPosition( getPosition() + Point3F( 0.0f, 0.0f, 0.10f ) );
setTransform( newTrans );
}
if( mDataBlock->staticOnMaxBounce )
{
mStatic = true;
}
}
}
else
{
setPosition( nextPos );
}
}
Point3F pos( getPosition( ) );
updateEmitters( pos, mVelocity, (U32)(dt * 1000.0));
}
void Debris::rotate( F32 dt )
{
MatrixF curTrans = getTransform();
curTrans.setPosition( Point3F(0.0f, 0.0f, 0.0f) );
Point3F curAngles = mRotAngles * dt * M_PI_F/180.0f;
MatrixF rotMatrix( EulerF( curAngles.x, curAngles.y, curAngles.z ) );
curTrans.mul( rotMatrix );
curTrans.setPosition( getPosition() );
setTransform( curTrans );
}
bool Debris::bounce( const Point3F &nextPos, F32 dt )
{
Point3F curPos = getPosition();
Point3F dir = nextPos - curPos;
if( dir.magnitudeSafe() == 0.0f ) return false;
dir.normalizeSafe();
Point3F extent = nextPos + dir * mRadius;
F32 totalDist = Point3F( extent - curPos ).magnitudeSafe();
F32 moveDist = Point3F( nextPos - curPos ).magnitudeSafe();
F32 movePercent = (moveDist / totalDist);
RayInfo rayInfo;
U32 collisionMask = csmStaticCollisionMask;
if( !mDataBlock->ignoreWater )
{
collisionMask |= WaterObjectType;
}
if( getContainer()->castRay( curPos, extent, collisionMask, &rayInfo ) )
{
Point3F reflection = mVelocity - rayInfo.normal * (mDot( mVelocity, rayInfo.normal ) * 2.0f);
mVelocity = reflection;
Point3F tangent = reflection - rayInfo.normal * mDot( reflection, rayInfo.normal );
mVelocity -= tangent * mFriction;
Point3F velDir = mVelocity;
velDir.normalizeSafe();
mVelocity *= mElasticity;
Point3F bouncePos = curPos + dir * rayInfo.t * movePercent;
bouncePos += mVelocity * dt;
setPosition( bouncePos );
mRotAngles *= mElasticity;
return true;
}
return false;
}
void Debris::explode()
{
if( !mDataBlock->explosion ) return;
Point3F explosionPos = getPosition();
Explosion* pExplosion = new Explosion;
pExplosion->onNewDataBlock(mDataBlock->explosion, false);
MatrixF trans( true );
trans.setPosition( getPosition() );
pExplosion->setTransform( trans );
pExplosion->setInitialState( explosionPos, VectorF(0,0,1), 1);
if (!pExplosion->registerObject())
delete pExplosion;
}
void Debris::computeNewState( Point3F &newPos, Point3F &newVel, F32 dt )
{
// apply gravity
Point3F force = Point3F(0, 0, -9.81 * mDataBlock->gravModifier );
if( mDataBlock->terminalVelocity > 0.0001 )
{
if( newVel.magnitudeSafe() > mDataBlock->terminalVelocity )
{
newVel.normalizeSafe();
newVel *= mDataBlock->terminalVelocity;
}
else
{
newVel += force * dt;
}
}
else
{
newVel += force * dt;
}
newPos += newVel * dt;
}
void Debris::updateEmitters( Point3F &pos, Point3F &vel, U32 ms )
{
Point3F axis = -vel;
if( axis.magnitudeSafe() == 0.0 )
{
axis = Point3F( 0.0, 0.0, 1.0 );
}
axis.normalizeSafe();
Point3F lastPos = mLastPos;
for( int i=0; i<DebrisData::DDC_NUM_EMITTERS; i++ )
{
if( mEmitterList[i] )
{
mEmitterList[i]->emitParticles( lastPos, pos, axis, vel, ms );
}
}
}
void Debris::prepRenderImage( SceneRenderState *state )
{
if( !mPart && !mShape )
return;
Point3F cameraOffset;
mObjToWorld.getColumn(3,&cameraOffset);
cameraOffset -= state->getDiffuseCameraPosition();
F32 dist = cameraOffset.len();
F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z));
if( mShape )
{
mShape->setDetailFromDistance( state, dist * invScale );
if( mShape->getCurrentDetail() < 0 )
return;
}
if( mPart )
{
// get the shapeInstance that we are using for the debris parts
TSShapeInstance *si = mPart->getSourceShapeInstance();
if ( si )
si->setDetailFromDistance( state, dist * invScale );
}
prepBatchRender( state );
}
void Debris::prepBatchRender( SceneRenderState *state )
{
if ( !mShape && !mPart )
return;
GFXTransformSaver saver;
F32 alpha = 1.0;
if( mDataBlock->fade )
{
if( mLifetime < 1.0 ) alpha = mLifetime;
}
Point3F cameraOffset;
mObjToWorld.getColumn(3,&cameraOffset);
cameraOffset -= state->getCameraPosition();
// 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 );
if( mShape )
{
MatrixF mat = getRenderTransform();
GFX->setWorldMatrix( mat );
rdata.setFadeOverride( alpha );
mShape->render( rdata );
}
else
{
if (mPart->getCurrentObjectDetail() != -1)
{
MatrixF mat = getRenderTransform();
GFX->setWorldMatrix( mat );
rdata.setFadeOverride( alpha );
mPart->render( rdata );
}
}
}
void Debris::setSize( F32 size )
{
mSize = size;
}

173
Engine/source/T3D/debris.h Normal file
View file

@ -0,0 +1,173 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _DEBRIS_H_
#define _DEBRIS_H_
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
#ifndef _GAMEBASE_H_
#include "T3D/gameBase/gameBase.h"
#endif
class ParticleEmitterData;
class ParticleEmitter;
class ExplosionData;
class TSPartInstance;
class TSShapeInstance;
class TSShape;
//**************************************************************************
// Debris Data
//**************************************************************************
struct DebrisData : public GameBaseData
{
typedef GameBaseData Parent;
//-----------------------------------------------------------------------
// Data Decs
//-----------------------------------------------------------------------
enum DebrisDataConst
{
DDC_NUM_EMITTERS = 2,
};
//-----------------------------------------------------------------------
// Debris datablock
//-----------------------------------------------------------------------
F32 velocity;
F32 velocityVariance;
F32 friction;
F32 elasticity;
F32 lifetime;
F32 lifetimeVariance;
U32 numBounces;
U32 bounceVariance;
F32 minSpinSpeed;
F32 maxSpinSpeed;
bool explodeOnMaxBounce; // explodes after it has bounced max times
bool staticOnMaxBounce; // becomes static after bounced max times
bool snapOnMaxBounce; // snap into a "resting" position on last bounce
bool fade;
bool useRadiusMass; // use mass calculations based on radius
F32 baseRadius; // radius at which the standard elasticity and friction apply
F32 gravModifier; // how much gravity affects debris
F32 terminalVelocity; // max velocity magnitude
bool ignoreWater;
const char* shapeName;
Resource<TSShape> shape;
StringTableEntry textureName;
S32 explosionId;
ExplosionData * explosion;
ParticleEmitterData* emitterList[DDC_NUM_EMITTERS];
S32 emitterIDList[DDC_NUM_EMITTERS];
DebrisData();
bool onAdd();
bool preload( bool server, String &errorStr );
static void initPersistFields();
void packData(BitStream* stream);
void unpackData(BitStream* stream);
DECLARE_CONOBJECT(DebrisData);
};
//**************************************************************************
// Debris
//**************************************************************************
class Debris : public GameBase
{
typedef GameBase Parent;
private:
S32 mNumBounces;
F32 mSize;
Point3F mLastPos;
Point3F mVelocity;
F32 mLifetime;
DebrisData * mDataBlock;
F32 mElapsedTime;
TSShapeInstance * mShape;
TSPartInstance * mPart;
MatrixF mInitialTrans;
F32 mXRotSpeed;
F32 mZRotSpeed;
Point3F mRotAngles;
F32 mRadius;
bool mStatic;
F32 mElasticity;
F32 mFriction;
SimObjectPtr<ParticleEmitter> mEmitterList[ DebrisData::DDC_NUM_EMITTERS ];
/// Bounce the debris - returns true if debris bounces.
bool bounce( const Point3F &nextPos, F32 dt );
/// Compute state of debris as if it hasn't collided with anything.
void computeNewState( Point3F &newPos, Point3F &newVel, F32 dt );
void explode();
void rotate( F32 dt );
protected:
virtual void processTick(const Move* move);
virtual void advanceTime( F32 dt );
void prepRenderImage(SceneRenderState *state);
void prepBatchRender(SceneRenderState *state);
bool onAdd();
void onRemove();
void updateEmitters( Point3F &pos, Point3F &vel, U32 ms );
public:
Debris();
~Debris();
static void initPersistFields();
bool onNewDataBlock( GameBaseData *dptr, bool reload );
void init( const Point3F &position, const Point3F &velocity );
void setLifetime( F32 lifetime ){ mLifetime = lifetime; }
void setPartInstance( TSPartInstance *part ){ mPart = part; }
void setSize( F32 size );
void setVelocity( const Point3F &vel ){ mVelocity = vel; }
void setRotAngles( const Point3F &angles ){ mRotAngles = angles; }
DECLARE_CONOBJECT(Debris);
};
#endif

View file

@ -0,0 +1,481 @@
//-----------------------------------------------------------------------------
// 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/decal/decalData.h"
#include "console/consoleTypes.h"
#include "core/stream/bitStream.h"
#include "math/mathIO.h"
#include "materials/materialManager.h"
#include "materials/baseMatInstance.h"
#include "T3D/objectTypes.h"
#include "console/engineAPI.h"
GFXImplementVertexFormat( DecalVertex )
{
addElement( "POSITION", GFXDeclType_Float3 );
addElement( "NORMAL", GFXDeclType_Float3 );
addElement( "TANGENT", GFXDeclType_Float3 );
addElement( "COLOR", GFXDeclType_Color );
addElement( "TEXCOORD", GFXDeclType_Float2, 0 );
}
IMPLEMENT_CO_DATABLOCK_V1( DecalData );
ConsoleDocClass( DecalData,
"@brief A datablock describing an individual decal.\n\n"
"The textures defined by the decal Material can be divided into multiple "
"rectangular sub-textures as shown below, with a different sub-texture "
"selected by all decals using the same DecalData (via #frame) or each decal "
"instance (via #randomize).\n"
"@image html images/decal_example.png \"Example of a Decal imagemap\"\n"
"@tsexample\n"
"datablock DecalData(BulletHoleDecal)\n"
"{\n"
" material = \"DECAL_BulletHole\";\n"
" size = \"5.0\";\n"
" lifeSpan = \"50000\";\n"
" randomize = \"1\";\n"
" texRows = \"2\";\n"
" texCols = \"2\";\n"
" clippingAngle = \"60\";\n"
"};\n"
"@endtsexample\n\n"
"@see Decals\n"
"@ingroup Decals\n"
"@ingroup FX\n"
);
//-------------------------------------------------------------------------
// DecalData
//-------------------------------------------------------------------------
DecalData::DecalData()
{
size = 5;
materialName = "";
lifeSpan = 5000;
fadeTime = 1000;
frame = 0;
randomize = false;
texRows = 1;
texCols = 1;
fadeStartPixelSize = -1.0f;
fadeEndPixelSize = 200.0f;
material = NULL;
matInst = NULL;
renderPriority = 10;
clippingMasks = STATIC_COLLISION_TYPEMASK;
clippingAngle = 89.0f;
texCoordCount = 1;
// TODO: We could in theory calculate if we can skip
// normals on the decal by checking the material features.
skipVertexNormals = false;
for ( S32 i = 0; i < 16; i++ )
{
texRect[i].point.set( 0.0f, 0.0f );
texRect[i].extent.set( 1.0f, 1.0f );
}
}
DecalData::~DecalData()
{
SAFE_DELETE( matInst );
}
bool DecalData::onAdd()
{
if ( !Parent::onAdd() )
return false;
if (size < 0.0) {
Con::warnf("DecalData::onAdd: size < 0");
size = 0;
}
getSet()->addObject( this );
if( texRows > 1 || texCols > 1 )
reloadRects();
return true;
}
void DecalData::onRemove()
{
Parent::onRemove();
}
void DecalData::initPersistFields()
{
addGroup( "Decal" );
addField( "size", TypeF32, Offset( size, DecalData ),
"Width and height of the decal in meters before scale is applied." );
addField( "material", TypeMaterialName, Offset( materialName, DecalData ),
"Material to use for this decal." );
addField( "lifeSpan", TypeS32, Offset( lifeSpan, DecalData ),
"Time (in milliseconds) before this decal will be automatically deleted." );
addField( "fadeTime", TypeS32, Offset( fadeTime, DecalData ),
"@brief Time (in milliseconds) over which to fade out the decal before "
"deleting it at the end of its lifetime.\n\n"
"@see lifeSpan" );
endGroup( "Decal" );
addGroup( "Rendering" );
addField( "fadeStartPixelSize", TypeF32, Offset( fadeStartPixelSize, DecalData ),
"@brief LOD value - size in pixels at which decals of this type begin "
"to fade out.\n\n"
"This should be a larger value than #fadeEndPixelSize. However, you may "
"also set this to a negative value to disable lod-based fading." );
addField( "fadeEndPixelSize", TypeF32, Offset( fadeEndPixelSize, DecalData ),
"@brief LOD value - size in pixels at which decals of this type are "
"fully faded out.\n\n"
"This should be a smaller value than #fadeStartPixelSize." );
addField( "renderPriority", TypeS8, Offset( renderPriority, DecalData ),
"Default renderPriority for decals of this type (determines draw "
"order when decals overlap)." );
addField( "clippingAngle", TypeF32, Offset( clippingAngle, DecalData ),
"The angle in degrees used to clip geometry that faces away from the "
"decal projection direction." );
endGroup( "Rendering" );
addGroup( "Texturing" );
addField( "frame", TypeS32, Offset( frame, DecalData ),
"Index of the texture rectangle within the imagemap to use for this decal." );
addField( "randomize", TypeBool, Offset( randomize, DecalData ),
"If true, a random frame from the imagemap is selected for each "
"instance of the decal." );
addField( "textureCoordCount", TypeS32, Offset( texCoordCount, DecalData ),
"Number of individual frames in the imagemap (maximum 16)." );
addField( "texRows", TypeS32, Offset( texRows, DecalData ),
"@brief Number of rows in the supplied imagemap.\n\n"
"Use #texRows and #texCols if the imagemap frames are arranged in a "
"grid; use #textureCoords to manually specify UV coordinates for "
"irregular sized frames." );
addField( "texCols", TypeS32, Offset( texCols, DecalData ),
"@brief Number of columns in the supplied imagemap.\n\n"
"Use #texRows and #texCols if the imagemap frames are arranged in a "
"grid; use #textureCoords to manually specify UV coordinates for "
"irregular sized frames." );
addField( "textureCoords", TypeRectF, Offset( texRect, DecalData ), MAX_TEXCOORD_COUNT,
"@brief An array of RectFs (topleft.x topleft.y extent.x extent.y) "
"representing the UV coordinates for each frame in the imagemap.\n\n"
"@note This field should only be set if the imagemap frames are "
"irregular in size. Otherwise use the #texRows and #texCols fields "
"and the UV coordinates will be calculated automatically." );
endGroup( "Texturing" );
Parent::initPersistFields();
}
void DecalData::onStaticModified( const char *slotName, const char *newValue )
{
Parent::onStaticModified( slotName, newValue );
if ( !isProperlyAdded() )
return;
// To allow changing materials live.
if ( dStricmp( slotName, "material" ) == 0 )
{
materialName = newValue;
_updateMaterial();
}
// To allow changing name live.
else if ( dStricmp( slotName, "name" ) == 0 )
{
lookupName = getName();
}
else if ( dStricmp( slotName, "renderPriority" ) == 0 )
{
renderPriority = getMax( renderPriority, (U8)1 );
}
}
bool DecalData::preload( bool server, String &errorStr )
{
if (Parent::preload(server, errorStr) == false)
return false;
// Server assigns name to lookupName,
// client assigns lookupName in unpack.
if ( server )
lookupName = getName();
return true;
}
void DecalData::packData( BitStream *stream )
{
Parent::packData( stream );
stream->write( lookupName );
stream->write( size );
stream->write( materialName );
stream->write( lifeSpan );
stream->write( fadeTime );
stream->write( texCoordCount );
for (S32 i = 0; i < texCoordCount; i++)
mathWrite( *stream, texRect[i] );
stream->write( fadeStartPixelSize );
stream->write( fadeEndPixelSize );
stream->write( renderPriority );
stream->write( clippingMasks );
stream->write( clippingAngle );
stream->write( texRows );
stream->write( texCols );
stream->write( frame );
stream->write( randomize );
}
void DecalData::unpackData( BitStream *stream )
{
Parent::unpackData( stream );
stream->read( &lookupName );
stream->read( &size );
stream->read( &materialName );
_updateMaterial();
stream->read( &lifeSpan );
stream->read( &fadeTime );
stream->read( &texCoordCount );
for (S32 i = 0; i < texCoordCount; i++)
mathRead(*stream, &texRect[i]);
stream->read( &fadeStartPixelSize );
stream->read( &fadeEndPixelSize );
stream->read( &renderPriority );
stream->read( &clippingMasks );
stream->read( &clippingAngle );
stream->read( &texRows );
stream->read( &texCols );
stream->read( &frame );
stream->read( &randomize );
}
void DecalData::_initMaterial()
{
SAFE_DELETE( matInst );
if ( material )
matInst = material->createMatInstance();
else
matInst = MATMGR->createMatInstance( "WarningMaterial" );
GFXStateBlockDesc desc;
desc.setZReadWrite( true, false );
//desc.zFunc = GFXCmpLess;
matInst->addStateBlockDesc( desc );
matInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat<DecalVertex>() );
if( !matInst->isValid() )
{
Con::errorf( "DecalData::_initMaterial - failed to create material instance for '%s'", materialName.c_str() );
SAFE_DELETE( matInst );
matInst = MATMGR->createMatInstance( "WarningMaterial" );
matInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat< DecalVertex >() );
}
}
void DecalData::_updateMaterial()
{
if ( materialName.isEmpty() )
return;
Material *pMat = NULL;
if ( !Sim::findObject( materialName, pMat ) )
{
Con::printf( "DecalData::unpackUpdate, failed to find Material of name %s!", materialName.c_str() );
return;
}
material = pMat;
// Only update material instance if we have one allocated.
if ( matInst )
_initMaterial();
}
Material* DecalData::getMaterial()
{
if ( !material )
{
_updateMaterial();
if ( !material )
material = static_cast<Material*>( Sim::findObject("WarningMaterial") );
}
return material;
}
BaseMatInstance* DecalData::getMaterialInstance()
{
if ( !material || !matInst || matInst->getMaterial() != material )
_initMaterial();
return matInst;
}
DecalData* DecalData::findDatablock( String searchName )
{
StringTableEntry className = DecalData::getStaticClassRep()->getClassName();
DecalData *pData;
SimSet *set = getSet();
SimSetIterator iter( set );
for ( ; *iter; ++iter )
{
if ( (*iter)->getClassName() != className )
{
Con::errorf( "DecalData::findDatablock - found a class %s object in DecalDataSet!", (*iter)->getClassName() );
continue;
}
pData = static_cast<DecalData*>( *iter );
if ( pData->lookupName.equal( searchName, String::NoCase ) )
return pData;
}
return NULL;
}
void DecalData::inspectPostApply()
{
reloadRects();
}
void DecalData::reloadRects()
{
F32 rowsBase = 0;
F32 colsBase = 0;
bool canRenderRowsByFrame = false;
bool canRenderColsByFrame = false;
S32 id = 0;
texRect[id].point.x = 0.f;
texRect[id].extent.x = 1.f;
texRect[id].point.y = 0.f;
texRect[id].extent.y = 1.f;
texCoordCount = (texRows * texCols) - 1;
if( texCoordCount > 16 )
{
Con::warnf("Coordinate max must be lower than 16 to be a valid decal !");
texRows = 1;
texCols = 1;
texCoordCount = 1;
}
// use current datablock information in order to build a template to extract
// coordinates from.
if( texRows > 1 )
{
rowsBase = ( 1.f / texRows );
canRenderRowsByFrame = true;
}
if( texCols > 1 )
{
colsBase = ( 1.f / texCols );
canRenderColsByFrame = true;
}
// if were able, lets enter the loop
if( frame >= 0 && (canRenderRowsByFrame || canRenderColsByFrame) )
{
// columns first then rows
for ( S32 colId = 1; colId <= texCols; colId++ )
{
for ( S32 rowId = 1; rowId <= texRows; rowId++, id++ )
{
// if were over the coord count, lets go
if(id > texCoordCount)
return;
// keep our dimensions correct
if(rowId > texRows)
rowId = 1;
if(colId > texCols)
colId = 1;
// start setting our rect values per frame
if( canRenderRowsByFrame )
{
texRect[id].point.x = rowsBase * ( rowId - 1 );
texRect[id].extent.x = rowsBase;
}
if( canRenderColsByFrame )
{
texRect[id].point.y = colsBase * ( colId - 1 );
texRect[id].extent.y = colsBase;
}
}
}
}
}
DefineEngineMethod(DecalData, postApply, void, (),,
"Recompute the imagemap sub-texture rectangles for this DecalData.\n"
"@tsexample\n"
"// Inform the decal object to reload its imagemap and frame data.\n"
"%decalData.texRows = 4;\n"
"%decalData.postApply();\n"
"@endtsexample\n")
{
object->inspectPostApply();
}

View file

@ -0,0 +1,143 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _DECALDATA_H_
#define _DECALDATA_H_
#ifndef _SIMDATABLOCK_H_
#include "console/simDatablock.h"
#endif
#ifndef _MATERIALDEFINITION_H_
#include "materials/materialDefinition.h"
#endif
#ifndef _MRECT_H_
#include "math/mRect.h"
#endif
#ifndef _DYNAMIC_CONSOLETYPES_H_
#include "console/dynamicTypes.h"
#endif
GFXDeclareVertexFormat( DecalVertex )
{
// .xyz = coords
Point3F point;
Point3F normal;
Point3F tangent;
GFXVertexColor color;
Point2F texCoord;
};
/// DataBlock implementation for decals.
class DecalData : public SimDataBlock
{
typedef SimDataBlock Parent;
public:
enum { MAX_TEXCOORD_COUNT = 16 };
F32 size;
/// Milliseconds for decal to expire.
U32 lifeSpan;
/// Milliseconds for decal to fade after expiration.
U32 fadeTime;
S32 texCoordCount;
RectF texRect[MAX_TEXCOORD_COUNT];
///
S32 frame;
bool randomize;
S32 texRows;
S32 texCols;
F32 fadeStartPixelSize;
F32 fadeEndPixelSize;
/// Name of material to use.
String materialName;
/// Render material for decal.
SimObjectPtr<Material> material;
/// Material instance for decal.
BaseMatInstance *matInst;
String lookupName;
U8 renderPriority;
S32 clippingMasks;
/// The angle in degress used to clip geometry
/// that faces away from the decal projection.
F32 clippingAngle;
/// Skip generating and collecting vertex normals for decals.
bool skipVertexNormals;
public:
DecalData();
~DecalData();
DECLARE_CONOBJECT(DecalData);
static void initPersistFields();
virtual void onStaticModified( const char *slotName, const char *newValue = NULL );
virtual bool onAdd();
virtual void onRemove();
virtual bool preload( bool server, String &errorStr );
virtual void packData( BitStream* );
virtual void unpackData( BitStream* );
Material* getMaterial();
BaseMatInstance* getMaterialInstance();
static SimSet* getSet();
static DecalData* findDatablock( String lookupName );
virtual void inspectPostApply();
void reloadRects();
protected:
void _initMaterial();
void _updateMaterial();
};
inline SimSet* DecalData::getSet()
{
SimSet *set = NULL;
if ( !Sim::findObject( "DecalDataSet", set ) )
{
set = new SimSet;
set->registerObject( "DecalDataSet" );
Sim::getRootGroup()->addObject( set );
}
return set;
}
#endif // _DECALDATA_H_

View file

@ -0,0 +1,407 @@
//-----------------------------------------------------------------------------
// 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 "decalDataFile.h"
#include "math/mathIO.h"
#include "core/tAlgorithm.h"
#include "core/stream/fileStream.h"
#include "T3D/decal/decalManager.h"
#include "T3D/decal/decalData.h"
template<>
void* Resource<DecalDataFile>::create( const Torque::Path &path )
{
FileStream stream;
stream.open( path.getFullPath(), Torque::FS::File::Read );
if ( stream.getStatus() != Stream::Ok )
return NULL;
DecalDataFile *file = new DecalDataFile();
if ( !file->read( stream ) )
{
delete file;
return NULL;
}
return file;
}
template<>
ResourceBase::Signature Resource<DecalDataFile>::signature()
{
return MakeFourCC('d','e','c','f');
}
//-----------------------------------------------------------------------------
DecalDataFile::DecalDataFile()
: mIsDirty( false ),
mSphereWithLastInsertion( NULL )
{
VECTOR_SET_ASSOCIATION( mSphereList );
}
//-----------------------------------------------------------------------------
DecalDataFile::~DecalDataFile()
{
clear();
}
//-----------------------------------------------------------------------------
void DecalDataFile::clear()
{
for ( U32 i=0; i < mSphereList.size(); i++ )
delete mSphereList[i];
mSphereList.clear();
mSphereWithLastInsertion = NULL;
mChunker.freeBlocks();
mIsDirty = true;
}
//-----------------------------------------------------------------------------
bool DecalDataFile::write( Stream& stream )
{
// Write our identifier... so we have a better
// idea if we're reading pure garbage.
// This identifier stands for "Torque Decal Data File".
stream.write( 4, "TDDF" );
// Now the version number.
stream.write( (U8)FILE_VERSION );
Vector<DecalInstance*> allDecals;
// Gather all DecalInstances that should be saved.
for ( U32 i = 0; i < mSphereList.size(); i++ )
{
Vector<DecalInstance*>::const_iterator item = mSphereList[i]->mItems.begin();
for ( ; item != mSphereList[i]->mItems.end(); item++ )
{
if ( (*item)->mFlags & SaveDecal )
allDecals.push_back( (*item) );
}
}
// Gather all the DecalData datablocks used.
Vector<const DecalData*> allDatablocks;
for ( U32 i = 0; i < allDecals.size(); i++ )
allDatablocks.push_back_unique( allDecals[i]->mDataBlock );
// Write out datablock lookupNames.
U32 count = allDatablocks.size();
stream.write( count );
for ( U32 i = 0; i < count; i++ )
stream.write( allDatablocks[i]->lookupName );
Vector<const DecalData*>::iterator dataIter;
// Write out the DecalInstance list.
count = allDecals.size();
stream.write( count );
for ( U32 i = 0; i < count; i++ )
{
DecalInstance *inst = allDecals[i];
dataIter = find( allDatablocks.begin(), allDatablocks.end(), inst->mDataBlock );
U8 dataIndex = dataIter - allDatablocks.begin();
stream.write( dataIndex );
mathWrite( stream, inst->mPosition );
mathWrite( stream, inst->mNormal );
mathWrite( stream, inst->mTangent );
stream.write( inst->mTextureRectIdx );
stream.write( inst->mSize );
stream.write( inst->mRenderPriority );
}
// Clear the dirty flag.
mIsDirty = false;
return true;
}
//-----------------------------------------------------------------------------
bool DecalDataFile::read( Stream &stream )
{
// NOTE: we are shortcutting by just saving out the DecalInst and
// using regular addDecal methods to add them, which will end up
// generating the DecalSphere(s) in the process.
// It would be more efficient however to just save out all the data
// and read it all in with no calculation required.
// Read our identifier... so we know we're
// not reading in pure garbage.
char id[4] = { 0 };
stream.read( 4, id );
if ( dMemcmp( id, "TDDF", 4 ) != 0 )
{
Con::errorf( "DecalDataFile::read() - This is not a Decal file!" );
return false;
}
// Empty ourselves before we really begin reading.
clear();
// Now the version number.
U8 version;
stream.read( &version );
if ( version != (U8)FILE_VERSION )
{
Con::errorf( "DecalDataFile::read() - file versions do not match!" );
Con::errorf( "You must manually delete the old .decals file before continuing!" );
return false;
}
// Read in the lookupNames of the DecalData datablocks and recover the datablock.
Vector<DecalData*> allDatablocks;
U32 count;
stream.read( &count );
allDatablocks.setSize( count );
for ( U32 i = 0; i < count; i++ )
{
String lookupName;
stream.read( &lookupName );
DecalData *data = DecalData::findDatablock( lookupName );
if ( !data )
{
char name[512];
dSprintf(name, 512, "%s_missing", lookupName.c_str());
DecalData *stubCheck = DecalData::findDatablock( name );
if( !stubCheck )
{
data = new DecalData;
data->lookupName = name;
data->registerObject(name);
Sim::getRootGroup()->addObject( data );
data->materialName = "WarningMaterial";
data->material = dynamic_cast<Material*>(Sim::findObject("WarningMaterial"));
Con::errorf( "DecalDataFile::read() - DecalData %s does not exist! Temporarily created %s_missing.", lookupName.c_str() );
}
}
allDatablocks[ i ] = data;
}
U8 dataIndex;
DecalData *data;
// Now read all the DecalInstance(s).
stream.read( &count );
for ( U32 i = 0; i < count; i++ )
{
DecalInstance *inst = _allocateInstance();
stream.read( &dataIndex );
mathRead( stream, &inst->mPosition );
mathRead( stream, &inst->mNormal );
mathRead( stream, &inst->mTangent );
stream.read( &inst->mTextureRectIdx );
stream.read( &inst->mSize );
stream.read( &inst->mRenderPriority );
inst->mVisibility = 1.0f;
inst->mFlags = PermanentDecal | SaveDecal | ClipDecal;
inst->mCreateTime = Sim::getCurrentTime();
inst->mVerts = NULL;
inst->mIndices = NULL;
inst->mVertCount = 0;
inst->mIndxCount = 0;
data = allDatablocks[ dataIndex ];
if ( data )
{
inst->mDataBlock = data;
_addDecalToSpheres( inst );
// onload set instances should get added to the appropriate vec
inst->mId = gDecalManager->mDecalInstanceVec.size();
gDecalManager->mDecalInstanceVec.push_back(inst);
}
else
{
_freeInstance( inst );
Con::errorf( "DecalDataFile::read - cannot find DecalData for DecalInstance read from disk." );
}
}
// Clear the dirty flag.
mIsDirty = false;
return true;
}
//-----------------------------------------------------------------------------
DecalInstance* DecalDataFile::addDecal( const Point3F& pos, const Point3F& normal, const Point3F& tangent,
DecalData* decalData, F32 decalScale, S32 decalTexIndex, U8 flags )
{
DecalInstance* newDecal = _allocateInstance();
newDecal->mRenderPriority = 0;
newDecal->mCustomTex = NULL;
newDecal->mId = -1;
newDecal->mPosition = pos;
newDecal->mNormal = normal;
newDecal->mTangent = tangent;
newDecal->mSize = decalData->size * decalScale;
newDecal->mDataBlock = decalData;
S32 frame = newDecal->mDataBlock->frame;
// randomize the frame if the flag is set. this number is used directly below us
// when calculating render coords
if ( decalData->randomize )
frame = gRandGen.randI();
frame %= getMax( decalData->texCoordCount, 0 ) + 1;
newDecal->mTextureRectIdx = frame;
newDecal->mVisibility = 1.0f;
newDecal->mLastAlpha = -1.0f;
newDecal->mCreateTime = Sim::getCurrentTime();
newDecal->mVerts = NULL;
newDecal->mIndices = NULL;
newDecal->mVertCount = 0;
newDecal->mIndxCount = 0;
newDecal->mFlags = flags;
newDecal->mFlags |= ClipDecal;
_addDecalToSpheres( newDecal );
return newDecal;
}
//-----------------------------------------------------------------------------
void DecalDataFile::_addDecalToSpheres( DecalInstance* inst )
{
// First try the sphere we have last inserted an item into, if there is one.
// Given a good spatial locality of insertions, this is a reasonable first
// guess as a good candidate.
if( mSphereWithLastInsertion && mSphereWithLastInsertion->tryAddItem( inst ) )
return;
// Otherwise, go through the list and try to find an existing sphere that meets
// our tolerances.
for( U32 i = 0; i < mSphereList.size(); i++ )
{
DecalSphere* sphere = mSphereList[i];
if( sphere == mSphereWithLastInsertion )
continue;
if( sphere->tryAddItem( inst ) )
{
mSphereWithLastInsertion = sphere;
return;
}
}
// Didn't find a suitable existing sphere, so create a new one.
DecalSphere* sphere = new DecalSphere( inst->mPosition, inst->mSize / 2.f );
mSphereList.push_back( sphere );
mSphereWithLastInsertion = sphere;
sphere->mItems.push_back( inst );
}
//-----------------------------------------------------------------------------
void DecalDataFile::removeDecal( DecalInstance *inst )
{
if( !_removeDecalFromSpheres( inst ) )
return;
_freeInstance( inst );
}
//-----------------------------------------------------------------------------
bool DecalDataFile::_removeDecalFromSpheres( DecalInstance *inst )
{
for( U32 i = 0; i < mSphereList.size(); i++ )
{
DecalSphere* sphere = mSphereList[ i ];
Vector< DecalInstance* > &items = sphere->mItems;
// Try to remove the instance from the list of this sphere.
// If that fails, the instance doesn't belong to this sphere
// so continue.
if( !items.remove( inst ) )
continue;
// If the sphere is now empty, remove it. Otherwise, update
// it's bounds.
if( items.empty() )
{
if( mSphereWithLastInsertion == sphere )
mSphereWithLastInsertion = NULL;
delete sphere;
mSphereList.erase( i );
}
else
sphere->updateWorldSphere();
return true;
}
return false;
}
//-----------------------------------------------------------------------------
void DecalDataFile::notifyDecalModified( DecalInstance *inst )
{
_removeDecalFromSpheres( inst );
_addDecalToSpheres( inst );
}

View file

@ -0,0 +1,133 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _DECALDATAFILE_H_
#define _DECALDATAFILE_H_
#ifndef _DATACHUNKER_H_
#include "core/dataChunker.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _DECALSPHERE_H_
#include "T3D/decal/decalSphere.h"
#endif
class Stream;
class DecalData;
/// This is the data file for decals.
/// Not intended to be used directly, do your work with decals
/// via the DecalManager.
class DecalDataFile
{
protected:
enum { FILE_VERSION = 5 };
/// Set to true if the file is dirty and
/// needs to be saved before being destroyed.
bool mIsDirty;
/// @name Memory Management
/// @{
/// Allocator for DecalInstances.
FreeListChunker< DecalInstance > mChunker;
/// Allocate a new, uninitialized DecalInstance.
DecalInstance* _allocateInstance() { return mChunker.alloc(); }
/// Free the memory of the given DecalInstance.
void _freeInstance( DecalInstance *decal ) { mChunker.free( decal ); }
/// @}
/// @name Instance Management
/// @{
/// The decal sphere that we have last insert an item into. This sphere
/// is most likely to be a good candidate for the next insertion so
/// test this sphere first.
DecalSphere* mSphereWithLastInsertion;
/// List of bounding sphere shapes that contain and organize
/// DecalInstances for optimized culling and lookup.
Vector< DecalSphere* > mSphereList;
/// Add the given decal to the sphere list.
void _addDecalToSpheres( DecalInstance *inst );
/// Remove the decal from the sphere list.
bool _removeDecalFromSpheres( DecalInstance *inst );
/// @}
public:
DecalDataFile();
virtual ~DecalDataFile();
Vector< DecalSphere* >& getSphereList() { return mSphereList; }
const Vector< DecalSphere* >& getSphereList() const { return mSphereList; }
/// Return true if the decal data has been modified since the last save or load.
bool isDirty() const { return mIsDirty; }
/// Deletes all the data and resets the
/// file to an empty state.
void clear();
/// @name I/O
/// @{
/// Write the decal data to the given stream.
bool write( Stream& stream );
/// Read the decal data from the given stream.
bool read( Stream& stream );
/// @}
/// @name Decal Management
/// @{
/// Create a new decal in this file using the given data.
DecalInstance* addDecal( const Point3F& pos, const Point3F& normal, const Point3F& tangent,
DecalData* decalData, F32 decalScale, S32 decalTexIndex, U8 flags );
/// Remove a decal from the file.
void removeDecal( DecalInstance *inst );
/// Let the file know that the data of the given decal has changed.
void notifyDecalModified( DecalInstance *inst );
/// @}
};
#endif // _DECALDATAFILE_H_

View file

@ -0,0 +1,73 @@
//-----------------------------------------------------------------------------
// 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/decal/decalInstance.h"
#include "scene/sceneRenderState.h"
void DecalInstance::getWorldMatrix( MatrixF *outMat, bool flip )
{
outMat->setPosition( mPosition );
Point3F fvec;
mCross( mNormal, mTangent, &fvec );
outMat->setColumn( 0, mTangent );
outMat->setColumn( 1, fvec );
outMat->setColumn( 2, mNormal );
}
F32 DecalInstance::calcPixelSize( U32 viewportHeight, const Point3F &cameraPos, F32 worldToScreenScaleY ) const
{
// If fadeStartPixelSize is set less than zero this is interpreted to mean
// pixelSize based fading is disabled and the decal always renders.
// Returning a value of F32_MAX should be treated by the caller as
// meaning "big enough to render at normal quality, dont LOD me.".
if ( mDataBlock->fadeStartPixelSize < 0.0f )
return F32_MAX;
// This is an approximation since mSize is actually the xyz size of the
// cube we use to clip geometry for the decal. In this case we are assuming
// it is appropriate to use the radius of the sphere 'inscribed' in that
// cube as the equivalent of mShape->radius for purposes of calculating
// the pixelScale ( see TSShapeInstance::setDetailFromDistance ).
const F32 radius = mSize * 0.5f;
// Approximate distance to the decal.
const F32 distance = ( cameraPos - mPosition ).len() - radius;
// We are inside the decal's volume. There is no useful pixelSize for us
// to return, it is essentially the entire screen.
if ( distance <= 0.0f )
return F32_MAX;
// Lod is relative to a viewport with a heigh of 300. Could prescale this into decalSize...
const F32 pixelScale = viewportHeight / 300.0f;
// Inline of SceneState::projectRadius.
const F32 pixelSize = mSize / distance * worldToScreenScaleY * pixelScale;
// Optionally scale pixelSize by a detail adjust value here...
// eg. pixelSize *= TSDetailAdjust...
return pixelSize;
}

View file

@ -0,0 +1,93 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _DECALINSTANCE_H_
#define _DECALINSTANCE_H_
#ifndef _GFXVERTEXBUFFER_H_
#include "gfx/gfxVertexBuffer.h"
#endif
#ifndef _DECALDATA_H_
#include "T3D/decal/decalData.h"
#endif
struct DecalVertex;
class SceneRenderState;
/// DecalInstance represents a rendering decal in the scene.
/// You should not allocate this yourself, add new decals to the scene
/// via the DecalManager.
/// All data is public, change it if you wish, but be sure to inform
/// the DecalManager.
class DecalInstance
{
public:
DecalData *mDataBlock;
Point3F mPosition;
Point3F mNormal;
Point3F mTangent;
F32 mRotAroundNormal;
F32 mSize;
U32 mCreateTime;
F32 mVisibility;
F32 mLastAlpha;
U32 mTextureRectIdx;
DecalVertex *mVerts;
U16 *mIndices;
U32 mVertCount;
U32 mIndxCount;
U8 mFlags;
U8 mRenderPriority;
S32 mId;
GFXTexHandle *mCustomTex;
void getWorldMatrix( MatrixF *outMat, bool flip = false );
Box3F getWorldBox() const
{
return Box3F( mPosition - Point3F( mSize / 2.f ), mPosition + Point3F( mSize / 2.f ) );
}
U8 getRenderPriority() const
{
return mRenderPriority == 0 ? mDataBlock->renderPriority : mRenderPriority;
}
/// Calculates the size of this decal onscreen in pixels, used for LOD.
F32 calcPixelSize( U32 viewportHeight, const Point3F &cameraPos, F32 worldToScreenScaleY ) const;
DecalInstance() : mId(-1) {}
};
#endif // _DECALINSTANCE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,278 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _DECALMANAGER_H_
#define _DECALMANAGER_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _MATHUTIL_FRUSTUM_H_
#include "math/util/frustum.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
#ifndef _GFXDEVICE_H_
#include "gfx/gfxDevice.h"
#endif
#ifndef _CLIPPEDPOLYLIST_H_
#include "collision/clippedPolyList.h"
#endif
#ifndef _DECALDATAFILE_H_
#include "decalDataFile.h"
#endif
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
#ifndef _DECALINSTANCE_H_
#include "decalInstance.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
#ifndef _DATACHUNKER_H_
#include "core/dataChunker.h"
#endif
//#define DECALMANAGER_DEBUG
struct ObjectRenderInst;
class Material;
enum DecalFlags
{
PermanentDecal = 1 << 0,
SaveDecal = 1 << 1,
ClipDecal = 1 << 2,
CustomDecal = 1 << 3 // DecalManager will not attempt to clip or remove this decal
// it is managed by someone else.
};
/// Manage decals in the scene.
class DecalManager : public SceneObject
{
public:
typedef SceneObject Parent;
// [rene, 11-Mar-11] This vector is very poorly managed; the logic is spread all over the place
Vector<DecalInstance *> mDecalInstanceVec;
protected:
/// The clipper we keep around between decal updates
/// to avoid excessive memory allocations.
ClippedPolyList mClipper;
Vector<DecalInstance*> mDecalQueue;
StringTableEntry mDataFileName;
Resource<DecalDataFile> mData;
Signal< void() > mClearDataSignal;
Vector< GFXVertexBufferHandle<DecalVertex>* > mVBs;
Vector< GFXPrimitiveBufferHandle* > mPBs;
Vector< GFXVertexBufferHandle<DecalVertex>* > mVBPool;
Vector< GFXPrimitiveBufferHandle* > mPBPool;
FreeListChunkerUntyped *mChunkers[3];
#ifdef DECALMANAGER_DEBUG
Vector<PlaneF> mDebugPlanes;
#endif
bool mDirty;
struct DecalBatch
{
U32 startDecal;
U32 decalCount;
U32 iCount;
U32 vCount;
U8 priority;
Material *mat;
BaseMatInstance *matInst;
bool dynamic;
};
/// Whether to render visualizations for debugging in the editor.
static bool smDebugRender;
static bool smDecalsOn;
static F32 smDecalLifeTimeScale;
static bool smPoolBuffers;
static const U32 smMaxVerts;
static const U32 smMaxIndices;
// Assume that a class is already given for the object:
// Point with coordinates {float x, y;}
//===================================================================
// isLeft(): tests if a point is Left|On|Right of an infinite line.
// Input: three points P0, P1, and P2
// Return: >0 for P2 left of the line through P0 and P1
// =0 for P2 on the line
// <0 for P2 right of the line
// See: the January 2001 Algorithm on Area of Triangles
inline F32 isLeft( const Point3F &P0, const Point3F &P1, const Point3F &P2 )
{
return (P1.x - P0.x)*(P2.y - P0.y) - (P2.x - P0.x)*(P1.y - P0.y);
}
U32 _generateConvexHull( const Vector<Point3F> &points, Vector<Point3F> *outPoints );
// Rendering
void prepRenderImage( SceneRenderState *state );
void _generateWindingOrder( const Point3F &cornerPoint, Vector<Point3F> *sortPoints );
// Helpers for creating and deleting the vert and index arrays
// held by DecalInstance.
void _allocBuffers( DecalInstance *inst );
void _freeBuffers( DecalInstance *inst );
void _freePools();
/// Returns index used to index into the correct sized FreeListChunker for
/// allocating vertex and index arrays.
S32 _getSizeClass( DecalInstance *inst ) const;
// Hide this from Doxygen
/// @cond
bool _handleGFXEvent(GFXDevice::GFXDeviceEventType event);
/// @endcond
void _renderDecalSpheres( ObjectRenderInst* inst, SceneRenderState* state, BaseMatInstance* overrideMat );
///
void _handleZoningChangedEvent( SceneZoneSpaceManager* zoneManager );
bool _createDataFile();
// SceneObject.
virtual bool onSceneAdd();
virtual void onSceneRemove(); public:
public:
DecalManager();
~DecalManager();
/// @name Decal Addition
/// @{
/// Adds a decal using a normal and a rotation.
///
/// @param pos The 3d position for the decal center.
/// @param normal The decal up vector.
/// @param rotAroundNormal The decal rotation around the normal.
/// @param decalData The datablock which defines this decal.
/// @param decalScale A scalar for adjusting the default size of the decal.
/// @param decalTexIndex Selects the texture coord index within the decal
/// data to use. If it is less than zero then a random
/// coord is selected.
/// @param flags The decal flags. @see DecalFlags
///
DecalInstance* addDecal( const Point3F &pos,
const Point3F &normal,
F32 rotAroundNormal,
DecalData *decalData,
F32 decalScale = 1.0f,
S32 decalTexIndex = 0,
U8 flags = 0x000 );
/// Adds a decal using a normal and a tangent.
///
/// @param pos The 3d position for the decal center.
/// @param normal The decal up vector.
/// @param tanget The decal right vector.
/// @param decalData The datablock which defines this decal.
/// @param decalScale A scalar for adjusting the default size of the decal.
/// @param decalTexIndex Selects the texture coord index within the decal
/// data to use. If it is less than zero then a random
/// coord is selected.
/// @param flags The decal flags. @see DecalFlags
///
DecalInstance* addDecal( const Point3F &pos,
const Point3F &normal,
const Point3F &tangent,
DecalData *decalData,
F32 decalScale = 1.0f,
S32 decalTexIndex = 0,
U8 flags = 0 );
/// @}
/// @name Decal Removal
/// @{
void removeDecal( DecalInstance *inst );
/// @}
DecalInstance* getDecal( S32 id );
DecalInstance* getClosestDecal( const Point3F &pos );
/// Return the closest DecalInstance hit by a ray.
DecalInstance* raycast( const Point3F &start, const Point3F &end, bool savedDecalsOnly = true );
//void dataDeleted( DecalData *data );
void saveDecals( const UTF8 *fileName );
bool loadDecals( const UTF8 *fileName );
void clearData();
/// Returns true if changes have been made since the last load/save
bool isDirty() const { return mDirty; }
bool clipDecal( DecalInstance *decal, Vector<Point3F> *edgeVerts = NULL, const Point2F *clipDepth = NULL );
void notifyDecalModified( DecalInstance *inst );
Signal< void() >& getClearDataSignal() { return mClearDataSignal; }
Resource<DecalDataFile> getDecalDataFile() { return mData; }
// SimObject.
DECLARE_CONOBJECT( DecalManager );
static void consoleInit();
};
extern DecalManager* gDecalManager;
#endif // _DECALMANAGER_H_

View file

@ -0,0 +1,109 @@
//-----------------------------------------------------------------------------
// 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/decal/decalSphere.h"
#include "scene/zones/sceneZoneSpaceManager.h"
#include "T3D/decal/decalInstance.h"
F32 DecalSphere::smDistanceTolerance = 30.0f;
F32 DecalSphere::smRadiusTolerance = 40.0f;
//-----------------------------------------------------------------------------
bool DecalSphere::tryAddItem( DecalInstance* inst )
{
// If we are further away from the center than our tolerance
// allows, don't use this sphere.
//
// Note that we take the distance from the nearest point on the
// bounding sphere rather than the distance to the center of the
// decal. This takes decal sizes into account and generally works
// better.
const F32 distCenterToCenter = ( mWorldSphere.center - inst->mPosition ).len();
const F32 distBoundsToCenter = distCenterToCenter - inst->mSize / 2.f;
if( distBoundsToCenter > smDistanceTolerance )
return false;
// Also, if adding the current decal to the sphere would make
// it larger than our radius tolerance, don't use it.
const F32 newRadius = distCenterToCenter + inst->mSize / 2.f + 0.5f;
if( newRadius > mWorldSphere.radius && newRadius > smRadiusTolerance )
return false;
// Otherwise, go with this sphere and add the item to it.
mItems.push_back( inst );
// Update the sphere bounds, if necessary.
if( newRadius > mWorldSphere.radius )
updateWorldSphere();
return true;
}
//-----------------------------------------------------------------------------
void DecalSphere::updateWorldSphere()
{
AssertFatal( mItems.size() >= 1, "DecalSphere::updateWorldSphere - Sphere is empty!" );
Box3F aabb( mItems[ 0 ]->getWorldBox() );
const U32 numItems = mItems.size();
for( U32 i = 1; i < numItems; ++ i )
aabb.intersect( mItems[ i ]->getWorldBox() );
mWorldSphere = aabb.getBoundingSphere();
// Clear the zoning data so that it gets recomputed.
mZones.clear();
}
//-----------------------------------------------------------------------------
void DecalSphere::updateZoning( SceneZoneSpaceManager* zoneManager )
{
mZones.clear();
// If there's only a single zone in the world, it must be the
// outdoor zone, so there's no point maintaining zoning information
// on all the decalspheres.
if( zoneManager->getNumZones() == 1 )
return;
// Otherwise query the scene graph for all zones that intersect with the
// AABB around our world sphere.
Box3F aabb( mWorldSphere.radius );
aabb.setCenter( mWorldSphere.center );
zoneManager->findZones( aabb, mZones );
}

View file

@ -0,0 +1,83 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _DECALSPHERE_H_
#define _DECALSPHERE_H_
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _MSPHERE_H_
#include "math/mSphere.h"
#endif
class DecalInstance;
class SceneZoneSpaceManager;
/// A bounding sphere in world space and a list of DecalInstance(s)
/// contained by it. DecalInstance(s) are organized/binned in this fashion
/// as a lookup and culling optimization.
class DecalSphere
{
public:
static F32 smDistanceTolerance;
static F32 smRadiusTolerance;
DecalSphere()
{
VECTOR_SET_ASSOCIATION( mItems );
VECTOR_SET_ASSOCIATION( mZones );
}
DecalSphere( const Point3F &position, F32 radius )
{
VECTOR_SET_ASSOCIATION( mItems );
VECTOR_SET_ASSOCIATION( mZones );
mWorldSphere.center = position;
mWorldSphere.radius = radius;
}
/// Recompute #mWorldSphere from the current instance list.
void updateWorldSphere();
/// Recompute the zoning information from the current bounds.
void updateZoning( SceneZoneSpaceManager* zoneManager );
/// Decal instances in this sphere.
Vector< DecalInstance* > mItems;
/// Zones that intersect with this sphere. If this is empty, the zoning
/// state of the sphere is uninitialized.
Vector< U32 > mZones;
/// World-space sphere corresponding to this DecalSphere.
SphereF mWorldSphere;
///
bool tryAddItem( DecalInstance* inst );
};
#endif // !_DECALSPHERE_H_

View file

@ -0,0 +1,346 @@
//-----------------------------------------------------------------------------
// 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/examples/renderMeshExample.h"
#include "math/mathIO.h"
#include "scene/sceneRenderState.h"
#include "console/consoleTypes.h"
#include "core/stream/bitStream.h"
#include "materials/materialManager.h"
#include "materials/baseMatInstance.h"
#include "renderInstance/renderPassManager.h"
#include "lighting/lightQuery.h"
#include "console/engineAPI.h"
IMPLEMENT_CO_NETOBJECT_V1(RenderMeshExample);
ConsoleDocClass( RenderMeshExample,
"@brief An example scene object which renders a mesh.\n\n"
"This class implements a basic SceneObject that can exist in the world at a "
"3D position and render itself. There are several valid ways to render an "
"object in Torque. This class implements the preferred rendering method which "
"is to submit a MeshRenderInst along with a Material, vertex buffer, "
"primitive buffer, and transform and allow the RenderMeshMgr handle the "
"actual setup and rendering for you.\n\n"
"See the C++ code for implementation details.\n\n"
"@ingroup Examples\n" );
//-----------------------------------------------------------------------------
// Object setup and teardown
//-----------------------------------------------------------------------------
RenderMeshExample::RenderMeshExample()
{
// Flag this object so that it will always
// be sent across the network to clients
mNetFlags.set( Ghostable | ScopeAlways );
// Set it as a "static" object that casts shadows
mTypeMask |= StaticObjectType | StaticShapeObjectType;
// Make sure we the Material instance to NULL
// so we don't try to access it incorrectly
mMaterialInst = NULL;
}
RenderMeshExample::~RenderMeshExample()
{
if ( mMaterialInst )
SAFE_DELETE( mMaterialInst );
}
//-----------------------------------------------------------------------------
// Object Editing
//-----------------------------------------------------------------------------
void RenderMeshExample::initPersistFields()
{
addGroup( "Rendering" );
addField( "material", TypeMaterialName, Offset( mMaterialName, RenderMeshExample ),
"The name of the material used to render the mesh." );
endGroup( "Rendering" );
// SceneObject already handles exposing the transform
Parent::initPersistFields();
}
void RenderMeshExample::inspectPostApply()
{
Parent::inspectPostApply();
// Flag the network mask to send the updates
// to the client object
setMaskBits( UpdateMask );
}
bool RenderMeshExample::onAdd()
{
if ( !Parent::onAdd() )
return false;
// Set up a 1x1x1 bounding box
mObjBox.set( Point3F( -0.5f, -0.5f, -0.5f ),
Point3F( 0.5f, 0.5f, 0.5f ) );
resetWorldBox();
// Add this object to the scene
addToScene();
return true;
}
void RenderMeshExample::onRemove()
{
// Remove this object from the scene
removeFromScene();
Parent::onRemove();
}
void RenderMeshExample::setTransform(const MatrixF & mat)
{
// Let SceneObject handle all of the matrix manipulation
Parent::setTransform( mat );
// Dirty our network mask so that the new transform gets
// transmitted to the client object
setMaskBits( TransformMask );
}
U32 RenderMeshExample::packUpdate( NetConnection *conn, U32 mask, BitStream *stream )
{
// Allow the Parent to get a crack at writing its info
U32 retMask = Parent::packUpdate( conn, mask, stream );
// Write our transform information
if ( stream->writeFlag( mask & TransformMask ) )
{
mathWrite(*stream, getTransform());
mathWrite(*stream, getScale());
}
// Write out any of the updated editable properties
if ( stream->writeFlag( mask & UpdateMask ) )
stream->write( mMaterialName );
return retMask;
}
void RenderMeshExample::unpackUpdate(NetConnection *conn, BitStream *stream)
{
// Let the Parent read any info it sent
Parent::unpackUpdate(conn, stream);
if ( stream->readFlag() ) // TransformMask
{
mathRead(*stream, &mObjToWorld);
mathRead(*stream, &mObjScale);
setTransform( mObjToWorld );
}
if ( stream->readFlag() ) // UpdateMask
{
stream->read( &mMaterialName );
if ( isProperlyAdded() )
updateMaterial();
}
}
//-----------------------------------------------------------------------------
// Object Rendering
//-----------------------------------------------------------------------------
void RenderMeshExample::createGeometry()
{
static const Point3F cubePoints[8] =
{
Point3F( 1, -1, -1), Point3F( 1, -1, 1), Point3F( 1, 1, -1), Point3F( 1, 1, 1),
Point3F(-1, -1, -1), Point3F(-1, 1, -1), Point3F(-1, -1, 1), Point3F(-1, 1, 1)
};
static const Point3F cubeNormals[6] =
{
Point3F( 1, 0, 0), Point3F(-1, 0, 0), Point3F( 0, 1, 0),
Point3F( 0, -1, 0), Point3F( 0, 0, 1), Point3F( 0, 0, -1)
};
static const Point2F cubeTexCoords[4] =
{
Point2F( 0, 0), Point2F( 0, -1),
Point2F( 1, 0), Point2F( 1, -1)
};
static const U32 cubeFaces[36][3] =
{
{ 3, 0, 3 }, { 0, 0, 0 }, { 1, 0, 1 },
{ 2, 0, 2 }, { 0, 0, 0 }, { 3, 0, 3 },
{ 7, 1, 1 }, { 4, 1, 2 }, { 5, 1, 0 },
{ 6, 1, 3 }, { 4, 1, 2 }, { 7, 1, 1 },
{ 3, 2, 1 }, { 5, 2, 2 }, { 2, 2, 0 },
{ 7, 2, 3 }, { 5, 2, 2 }, { 3, 2, 1 },
{ 1, 3, 3 }, { 4, 3, 0 }, { 6, 3, 1 },
{ 0, 3, 2 }, { 4, 3, 0 }, { 1, 3, 3 },
{ 3, 4, 3 }, { 6, 4, 0 }, { 7, 4, 1 },
{ 1, 4, 2 }, { 6, 4, 0 }, { 3, 4, 3 },
{ 2, 5, 1 }, { 4, 5, 2 }, { 0, 5, 0 },
{ 5, 5, 3 }, { 4, 5, 2 }, { 2, 5, 1 }
};
// Fill the vertex buffer
VertexType *pVert = NULL;
mVertexBuffer.set( GFX, 36, GFXBufferTypeStatic );
pVert = mVertexBuffer.lock();
Point3F halfSize = getObjBox().getExtents() * 0.5f;
for (U32 i = 0; i < 36; i++)
{
const U32& vdx = cubeFaces[i][0];
const U32& ndx = cubeFaces[i][1];
const U32& tdx = cubeFaces[i][2];
pVert[i].point = cubePoints[vdx] * halfSize;
pVert[i].normal = cubeNormals[ndx];
pVert[i].texCoord = cubeTexCoords[tdx];
}
mVertexBuffer.unlock();
// Fill the primitive buffer
U16 *pIdx = NULL;
mPrimitiveBuffer.set( GFX, 36, 12, GFXBufferTypeStatic );
mPrimitiveBuffer.lock(&pIdx);
for (U16 i = 0; i < 36; i++)
pIdx[i] = i;
mPrimitiveBuffer.unlock();
}
void RenderMeshExample::updateMaterial()
{
if ( mMaterialName.isEmpty() )
return;
// If the material name matches then don't bother updating it.
if ( mMaterialInst && mMaterialName.equal( mMaterialInst->getMaterial()->getName(), String::NoCase ) )
return;
SAFE_DELETE( mMaterialInst );
mMaterialInst = MATMGR->createMatInstance( mMaterialName, getGFXVertexFormat< VertexType >() );
if ( !mMaterialInst )
Con::errorf( "RenderMeshExample::updateMaterial - no Material called '%s'", mMaterialName.c_str() );
}
void RenderMeshExample::prepRenderImage( SceneRenderState *state )
{
// Do a little prep work if needed
if ( mVertexBuffer.isNull() )
createGeometry();
// If we have no material then skip out.
if ( !mMaterialInst )
return;
// If we don't have a material instance after the override then
// we can skip rendering all together.
BaseMatInstance *matInst = state->getOverrideMaterial( mMaterialInst );
if ( !matInst )
return;
// Get a handy pointer to our RenderPassmanager
RenderPassManager *renderPass = state->getRenderPass();
// Allocate an MeshRenderInst so that we can submit it to the RenderPassManager
MeshRenderInst *ri = renderPass->allocInst<MeshRenderInst>();
// Set our RenderInst as a standard mesh render
ri->type = RenderPassManager::RIT_Mesh;
// Calculate our sorting point
if ( state )
{
// Calculate our sort point manually.
const Box3F& rBox = getRenderWorldBox();
ri->sortDistSq = rBox.getSqDistanceToPoint( state->getCameraPosition() );
}
else
ri->sortDistSq = 0.0f;
// Set up our transforms
MatrixF objectToWorld = getRenderTransform();
objectToWorld.scale( getScale() );
ri->objectToWorld = renderPass->allocUniqueXform( objectToWorld );
ri->worldToCamera = renderPass->allocSharedXform(RenderPassManager::View);
ri->projection = renderPass->allocSharedXform(RenderPassManager::Projection);
// If our material needs lights then fill the RIs
// light vector with the best lights.
if ( matInst->isForwardLit() )
{
LightQuery query;
query.init( getWorldSphere() );
query.getLights( ri->lights, 8 );
}
// Make sure we have an up-to-date backbuffer in case
// our Material would like to make use of it
// NOTICE: SFXBB is removed and refraction is disabled!
//ri->backBuffTex = GFX->getSfxBackBuffer();
// Set our Material
ri->matInst = matInst;
// Set up our vertex buffer and primitive buffer
ri->vertBuff = &mVertexBuffer;
ri->primBuff = &mPrimitiveBuffer;
ri->prim = renderPass->allocPrim();
ri->prim->type = GFXTriangleList;
ri->prim->minIndex = 0;
ri->prim->startIndex = 0;
ri->prim->numPrimitives = 12;
ri->prim->startVertex = 0;
ri->prim->numVertices = 36;
// We sort by the material then vertex buffer
ri->defaultKey = matInst->getStateHint();
ri->defaultKey2 = (U32)ri->vertBuff; // Not 64bit safe!
// Submit our RenderInst to the RenderPassManager
state->getRenderPass()->addInst( ri );
}
DefineEngineMethod( RenderMeshExample, postApply, void, (),,
"A utility method for forcing a network update.\n")
{
object->inspectPostApply();
}

View file

@ -0,0 +1,134 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _RENDERMESHEXAMPLE_H_
#define _RENDERMESHEXAMPLE_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _GFXVERTEXBUFFER_H_
#include "gfx/gfxVertexBuffer.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
class BaseMatInstance;
//-----------------------------------------------------------------------------
// This class implements a basic SceneObject that can exist in the world at a
// 3D position and render itself. There are several valid ways to render an
// object in Torque. This class implements the preferred rendering method which
// is to submit a MeshRenderInst along with a Material, vertex buffer,
// primitive buffer, and transform and allow the RenderMeshMgr handle the
// actual setup and rendering for you.
//-----------------------------------------------------------------------------
class RenderMeshExample : public SceneObject
{
typedef SceneObject Parent;
// Networking masks
// We need to implement a mask specifically to handle
// updating our transform from the server object to its
// client-side "ghost". We also need to implement a
// maks for handling editor updates to our properties
// (like material).
enum MaskBits
{
TransformMask = Parent::NextFreeMask << 0,
UpdateMask = Parent::NextFreeMask << 1,
NextFreeMask = Parent::NextFreeMask << 2
};
//--------------------------------------------------------------------------
// Rendering variables
//--------------------------------------------------------------------------
// The name of the Material we will use for rendering
String mMaterialName;
// The actual Material instance
BaseMatInstance* mMaterialInst;
// Define our vertex format here so we don't have to
// change it in multiple spots later
typedef GFXVertexPNT VertexType;
// The GFX vertex and primitive buffers
GFXVertexBufferHandle< VertexType > mVertexBuffer;
GFXPrimitiveBufferHandle mPrimitiveBuffer;
public:
RenderMeshExample();
virtual ~RenderMeshExample();
// Declare this object as a ConsoleObject so that we can
// instantiate it into the world and network it
DECLARE_CONOBJECT(RenderMeshExample);
//--------------------------------------------------------------------------
// Object Editing
// Since there is always a server and a client object in Torque and we
// actually edit the server object we need to implement some basic
// networking functions
//--------------------------------------------------------------------------
// Set up any fields that we want to be editable (like position)
static void initPersistFields();
// Allows the object to update its editable settings
// from the server object to the client
virtual void inspectPostApply();
// Handle when we are added to the scene and removed from the scene
bool onAdd();
void onRemove();
// Override this so that we can dirty the network flag when it is called
void setTransform( const MatrixF &mat );
// This function handles sending the relevant data from the server
// object to the client object
U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream );
// This function handles receiving relevant data from the server
// object and applying it to the client object
void unpackUpdate( NetConnection *conn, BitStream *stream );
//--------------------------------------------------------------------------
// Object Rendering
// Torque utilizes a "batch" rendering system. This means that it builds a
// list of objects that need to render (via RenderInst's) and then renders
// them all in one batch. This allows it to optimized on things like
// minimizing texture, state, and shader switching by grouping objects that
// use the same Materials.
//--------------------------------------------------------------------------
// Create the geometry for rendering
void createGeometry();
// Get the Material instance
void updateMaterial();
// This is the function that allows this object to submit itself for rendering
void prepRenderImage( SceneRenderState *state );
};
#endif // _RENDERMESHEXAMPLE_H_

View file

@ -0,0 +1,280 @@
//-----------------------------------------------------------------------------
// 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 "T3D/examples/renderObjectExample.h"
#include "math/mathIO.h"
#include "scene/sceneRenderState.h"
#include "core/stream/bitStream.h"
#include "materials/sceneData.h"
#include "gfx/gfxDebugEvent.h"
#include "gfx/gfxTransformSaver.h"
#include "renderInstance/renderPassManager.h"
IMPLEMENT_CO_NETOBJECT_V1(RenderObjectExample);
ConsoleDocClass( RenderObjectExample,
"@brief An example scene object which renders using a callback.\n\n"
"This class implements a basic SceneObject that can exist in the world at a "
"3D position and render itself. Note that RenderObjectExample handles its own "
"rendering by submitting itself as an ObjectRenderInst (see "
"renderInstance\renderPassmanager.h) along with a delegate for its render() "
"function. However, the preffered rendering method in the engine is to submit "
"a MeshRenderInst along with a Material, vertex buffer, primitive buffer, and "
"transform and allow the RenderMeshMgr handle the actual rendering. You can "
"see this implemented in RenderMeshExample.\n\n"
"See the C++ code for implementation details.\n\n"
"@ingroup Examples\n" );
//-----------------------------------------------------------------------------
// Object setup and teardown
//-----------------------------------------------------------------------------
RenderObjectExample::RenderObjectExample()
{
// Flag this object so that it will always
// be sent across the network to clients
mNetFlags.set( Ghostable | ScopeAlways );
// Set it as a "static" object
mTypeMask |= StaticObjectType | StaticShapeObjectType;
}
RenderObjectExample::~RenderObjectExample()
{
}
//-----------------------------------------------------------------------------
// Object Editing
//-----------------------------------------------------------------------------
void RenderObjectExample::initPersistFields()
{
// SceneObject already handles exposing the transform
Parent::initPersistFields();
}
bool RenderObjectExample::onAdd()
{
if ( !Parent::onAdd() )
return false;
// Set up a 1x1x1 bounding box
mObjBox.set( Point3F( -0.5f, -0.5f, -0.5f ),
Point3F( 0.5f, 0.5f, 0.5f ) );
resetWorldBox();
// Add this object to the scene
addToScene();
return true;
}
void RenderObjectExample::onRemove()
{
// Remove this object from the scene
removeFromScene();
Parent::onRemove();
}
void RenderObjectExample::setTransform(const MatrixF & mat)
{
// Let SceneObject handle all of the matrix manipulation
Parent::setTransform( mat );
// Dirty our network mask so that the new transform gets
// transmitted to the client object
setMaskBits( TransformMask );
}
U32 RenderObjectExample::packUpdate( NetConnection *conn, U32 mask, BitStream *stream )
{
// Allow the Parent to get a crack at writing its info
U32 retMask = Parent::packUpdate( conn, mask, stream );
// Write our transform information
if ( stream->writeFlag( mask & TransformMask ) )
{
mathWrite(*stream, getTransform());
mathWrite(*stream, getScale());
}
return retMask;
}
void RenderObjectExample::unpackUpdate(NetConnection *conn, BitStream *stream)
{
// Let the Parent read any info it sent
Parent::unpackUpdate(conn, stream);
if ( stream->readFlag() ) // TransformMask
{
mathRead(*stream, &mObjToWorld);
mathRead(*stream, &mObjScale);
setTransform( mObjToWorld );
}
}
//-----------------------------------------------------------------------------
// Object Rendering
//-----------------------------------------------------------------------------
void RenderObjectExample::createGeometry()
{
static const Point3F cubePoints[8] =
{
Point3F( 1.0f, -1.0f, -1.0f), Point3F( 1.0f, -1.0f, 1.0f),
Point3F( 1.0f, 1.0f, -1.0f), Point3F( 1.0f, 1.0f, 1.0f),
Point3F(-1.0f, -1.0f, -1.0f), Point3F(-1.0f, 1.0f, -1.0f),
Point3F(-1.0f, -1.0f, 1.0f), Point3F(-1.0f, 1.0f, 1.0f)
};
static const Point3F cubeNormals[6] =
{
Point3F( 1.0f, 0.0f, 0.0f), Point3F(-1.0f, 0.0f, 0.0f),
Point3F( 0.0f, 1.0f, 0.0f), Point3F( 0.0f, -1.0f, 0.0f),
Point3F( 0.0f, 0.0f, 1.0f), Point3F( 0.0f, 0.0f, -1.0f)
};
static const ColorI cubeColors[3] =
{
ColorI( 255, 0, 0, 255 ),
ColorI( 0, 255, 0, 255 ),
ColorI( 0, 0, 255, 255 )
};
static const U32 cubeFaces[36][3] =
{
{ 3, 0, 0 }, { 0, 0, 0 }, { 1, 0, 0 },
{ 2, 0, 0 }, { 0, 0, 0 }, { 3, 0, 0 },
{ 7, 1, 0 }, { 4, 1, 0 }, { 5, 1, 0 },
{ 6, 1, 0 }, { 4, 1, 0 }, { 7, 1, 0 },
{ 3, 2, 1 }, { 5, 2, 1 }, { 2, 2, 1 },
{ 7, 2, 1 }, { 5, 2, 1 }, { 3, 2, 1 },
{ 1, 3, 1 }, { 4, 3, 1 }, { 6, 3, 1 },
{ 0, 3, 1 }, { 4, 3, 1 }, { 1, 3, 1 },
{ 3, 4, 2 }, { 6, 4, 2 }, { 7, 4, 2 },
{ 1, 4, 2 }, { 6, 4, 2 }, { 3, 4, 2 },
{ 2, 5, 2 }, { 4, 5, 2 }, { 0, 5, 2 },
{ 5, 5, 2 }, { 4, 5, 2 }, { 2, 5, 2 }
};
// Fill the vertex buffer
VertexType *pVert = NULL;
mVertexBuffer.set( GFX, 36, GFXBufferTypeStatic );
pVert = mVertexBuffer.lock();
Point3F halfSize = getObjBox().getExtents() * 0.5f;
for (U32 i = 0; i < 36; i++)
{
const U32& vdx = cubeFaces[i][0];
const U32& ndx = cubeFaces[i][1];
const U32& cdx = cubeFaces[i][2];
pVert[i].point = cubePoints[vdx] * halfSize;
pVert[i].normal = cubeNormals[ndx];
pVert[i].color = cubeColors[cdx];
}
mVertexBuffer.unlock();
// Set up our normal and reflection StateBlocks
GFXStateBlockDesc desc;
// The normal StateBlock only needs a default StateBlock
mNormalSB = GFX->createStateBlock( desc );
// The reflection needs its culling reversed
desc.cullDefined = true;
desc.cullMode = GFXCullCW;
mReflectSB = GFX->createStateBlock( desc );
}
void RenderObjectExample::prepRenderImage( SceneRenderState *state )
{
// Do a little prep work if needed
if ( mVertexBuffer.isNull() )
createGeometry();
// Allocate an ObjectRenderInst so that we can submit it to the RenderPassManager
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
// Now bind our rendering function so that it will get called
ri->renderDelegate.bind( this, &RenderObjectExample::render );
// Set our RenderInst as a standard object render
ri->type = RenderPassManager::RIT_Object;
// Set our sorting keys to a default value
ri->defaultKey = 0;
ri->defaultKey2 = 0;
// Submit our RenderInst to the RenderPassManager
state->getRenderPass()->addInst( ri );
}
void RenderObjectExample::render( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
{
if ( overrideMat )
return;
if ( mVertexBuffer.isNull() )
return;
PROFILE_SCOPE(RenderObjectExample_Render);
// Set up a GFX debug event (this helps with debugging rendering events in external tools)
GFXDEBUGEVENT_SCOPE( RenderObjectExample_Render, ColorI::RED );
// GFXTransformSaver is a handy helper class that restores
// the current GFX matrices to their original values when
// it goes out of scope at the end of the function
GFXTransformSaver saver;
// Calculate our object to world transform matrix
MatrixF objectToWorld = getRenderTransform();
objectToWorld.scale( getScale() );
// Apply our object transform
GFX->multWorld( objectToWorld );
// Deal with reflect pass otherwise
// set the normal StateBlock
if ( state->isReflectPass() )
GFX->setStateBlock( mReflectSB );
else
GFX->setStateBlock( mNormalSB );
// Set up the "generic" shaders
// These handle rendering on GFX layers that don't support
// fixed function. Otherwise they disable shaders.
GFX->setupGenericShaders( GFXDevice::GSModColorTexture );
// Set the vertex buffer
GFX->setVertexBuffer( mVertexBuffer );
// Draw our triangles
GFX->drawPrimitive( GFXTriangleList, 0, 12 );
}

View file

@ -0,0 +1,136 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _RENDEROBJECTEXAMPLE_H_
#define _RENDEROBJECTEXAMPLE_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _GFXSTATEBLOCK_H_
#include "gfx/gfxStateBlock.h"
#endif
#ifndef _GFXVERTEXBUFFER_H_
#include "gfx/gfxVertexBuffer.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
class BaseMatInstance;
//-----------------------------------------------------------------------------
// This class implements a basic SceneObject that can exist in the world at a
// 3D position and render itself. Note that RenderObjectExample handles its own
// rendering by submitting itself as an ObjectRenderInst (see
// renderInstance\renderPassmanager.h) along with a delegate for its render()
// function. However, the preffered rendering method in the engine is to submit
// a MeshRenderInst along with a Material, vertex buffer, primitive buffer, and
// transform and allow the RenderMeshMgr handle the actual rendering. You can
// see this implemented in RenderMeshExample.
//-----------------------------------------------------------------------------
class RenderObjectExample : public SceneObject
{
typedef SceneObject Parent;
// Networking masks
// We need to implement at least one of these to allow
// the client version of the object to receive updates
// from the server version (like if it has been moved
// or edited)
enum MaskBits
{
TransformMask = Parent::NextFreeMask << 0,
NextFreeMask = Parent::NextFreeMask << 1
};
//--------------------------------------------------------------------------
// Rendering variables
//--------------------------------------------------------------------------
// Define our vertex format here so we don't have to
// change it in multiple spots later
typedef GFXVertexPCN VertexType;
// The handles for our StateBlocks
GFXStateBlockRef mNormalSB;
GFXStateBlockRef mReflectSB;
// The GFX vertex and primitive buffers
GFXVertexBufferHandle< VertexType > mVertexBuffer;
public:
RenderObjectExample();
virtual ~RenderObjectExample();
// Declare this object as a ConsoleObject so that we can
// instantiate it into the world and network it
DECLARE_CONOBJECT(RenderObjectExample);
//--------------------------------------------------------------------------
// Object Editing
// Since there is always a server and a client object in Torque and we
// actually edit the server object we need to implement some basic
// networking functions
//--------------------------------------------------------------------------
// Set up any fields that we want to be editable (like position)
static void initPersistFields();
// Handle when we are added to the scene and removed from the scene
bool onAdd();
void onRemove();
// Override this so that we can dirty the network flag when it is called
void setTransform( const MatrixF &mat );
// This function handles sending the relevant data from the server
// object to the client object
U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream );
// This function handles receiving relevant data from the server
// object and applying it to the client object
void unpackUpdate( NetConnection *conn, BitStream *stream );
//--------------------------------------------------------------------------
// Object Rendering
// Torque utilizes a "batch" rendering system. This means that it builds a
// list of objects that need to render (via RenderInst's) and then renders
// them all in one batch. This allows it to optimized on things like
// minimizing texture, state, and shader switching by grouping objects that
// use the same Materials. For this example, however, we are just going to
// get this object added to the list of objects that handle their own
// rendering.
//--------------------------------------------------------------------------
// Create the geometry for rendering
void createGeometry();
// This is the function that allows this object to submit itself for rendering
void prepRenderImage( SceneRenderState *state );
// This is the function that actually gets called to do the rendering
// Note that there is no longer a predefined name for this function.
// Instead, when we submit our ObjectRenderInst in prepRenderImage(),
// we bind this function as our rendering delegate function
void render( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );
};
#endif // _RENDEROBJECTEXAMPLE_H_

View file

@ -0,0 +1,273 @@
//-----------------------------------------------------------------------------
// 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 "T3D/examples/renderShapeExample.h"
#include "math/mathIO.h"
#include "sim/netConnection.h"
#include "scene/sceneRenderState.h"
#include "console/consoleTypes.h"
#include "core/resourceManager.h"
#include "core/stream/bitStream.h"
#include "gfx/gfxTransformSaver.h"
#include "renderInstance/renderPassManager.h"
#include "lighting/lightQuery.h"
IMPLEMENT_CO_NETOBJECT_V1(RenderShapeExample);
ConsoleDocClass( RenderShapeExample,
"@brief An example scene object which renders a DTS.\n\n"
"This class implements a basic SceneObject that can exist in the world at a "
"3D position and render itself. There are several valid ways to render an "
"object in Torque. This class makes use of the 'TS' (three space) shape "
"system. TS manages loading the various mesh formats supported by Torque as "
"well was rendering those meshes (including LOD and animation...though this "
"example doesn't include any animation over time).\n\n"
"See the C++ code for implementation details.\n\n"
"@ingroup Examples\n" );
//-----------------------------------------------------------------------------
// Object setup and teardown
//-----------------------------------------------------------------------------
RenderShapeExample::RenderShapeExample()
{
// Flag this object so that it will always
// be sent across the network to clients
mNetFlags.set( Ghostable | ScopeAlways );
// Set it as a "static" object.
mTypeMask |= StaticObjectType | StaticShapeObjectType;
// Make sure to initialize our TSShapeInstance to NULL
mShapeInstance = NULL;
}
RenderShapeExample::~RenderShapeExample()
{
}
//-----------------------------------------------------------------------------
// Object Editing
//-----------------------------------------------------------------------------
void RenderShapeExample::initPersistFields()
{
addGroup( "Rendering" );
addField( "shapeFile", TypeStringFilename, Offset( mShapeFile, RenderShapeExample ),
"The path to the DTS shape file." );
endGroup( "Rendering" );
// SceneObject already handles exposing the transform
Parent::initPersistFields();
}
void RenderShapeExample::inspectPostApply()
{
Parent::inspectPostApply();
// Flag the network mask to send the updates
// to the client object
setMaskBits( UpdateMask );
}
bool RenderShapeExample::onAdd()
{
if ( !Parent::onAdd() )
return false;
// Set up a 1x1x1 bounding box
mObjBox.set( Point3F( -0.5f, -0.5f, -0.5f ),
Point3F( 0.5f, 0.5f, 0.5f ) );
resetWorldBox();
// Add this object to the scene
addToScene();
// Setup the shape.
createShape();
return true;
}
void RenderShapeExample::onRemove()
{
// Remove this object from the scene
removeFromScene();
// Remove our TSShapeInstance
if ( mShapeInstance )
SAFE_DELETE( mShapeInstance );
Parent::onRemove();
}
void RenderShapeExample::setTransform(const MatrixF & mat)
{
// Let SceneObject handle all of the matrix manipulation
Parent::setTransform( mat );
// Dirty our network mask so that the new transform gets
// transmitted to the client object
setMaskBits( TransformMask );
}
U32 RenderShapeExample::packUpdate( NetConnection *conn, U32 mask, BitStream *stream )
{
// Allow the Parent to get a crack at writing its info
U32 retMask = Parent::packUpdate( conn, mask, stream );
// Write our transform information
if ( stream->writeFlag( mask & TransformMask ) )
{
mathWrite(*stream, getTransform());
mathWrite(*stream, getScale());
}
// Write out any of the updated editable properties
if ( stream->writeFlag( mask & UpdateMask ) )
{
stream->write( mShapeFile );
// Allow the server object a chance to handle a new shape
createShape();
}
return retMask;
}
void RenderShapeExample::unpackUpdate(NetConnection *conn, BitStream *stream)
{
// Let the Parent read any info it sent
Parent::unpackUpdate(conn, stream);
if ( stream->readFlag() ) // TransformMask
{
mathRead(*stream, &mObjToWorld);
mathRead(*stream, &mObjScale);
setTransform( mObjToWorld );
}
if ( stream->readFlag() ) // UpdateMask
{
stream->read( &mShapeFile );
if ( isProperlyAdded() )
createShape();
}
}
//-----------------------------------------------------------------------------
// Object Rendering
//-----------------------------------------------------------------------------
void RenderShapeExample::createShape()
{
if ( mShapeFile.isEmpty() )
return;
// If this is the same shape then no reason to update it
if ( mShapeInstance && mShapeFile.equal( mShape.getPath().getFullPath(), String::NoCase ) )
return;
// Clean up our previous shape
if ( mShapeInstance )
SAFE_DELETE( mShapeInstance );
mShape = NULL;
// Attempt to get the resource from the ResourceManager
mShape = ResourceManager::get().load( mShapeFile );
if ( !mShape )
{
Con::errorf( "RenderShapeExample::createShape() - Unable to load shape: %s", mShapeFile.c_str() );
return;
}
// Attempt to preload the Materials for this shape
if ( isClientObject() &&
!mShape->preloadMaterialList( mShape.getPath() ) &&
NetConnection::filesWereDownloaded() )
{
mShape = NULL;
return;
}
// Update the bounding box
mObjBox = mShape->bounds;
resetWorldBox();
setRenderTransform(mObjToWorld);
// Create the TSShapeInstance
mShapeInstance = new TSShapeInstance( mShape, isClientObject() );
}
void RenderShapeExample::prepRenderImage( SceneRenderState *state )
{
// Make sure we have a TSShapeInstance
if ( !mShapeInstance )
return;
// Calculate the distance of this object from the camera
Point3F cameraOffset;
getRenderTransform().getColumn( 3, &cameraOffset );
cameraOffset -= state->getDiffuseCameraPosition();
F32 dist = cameraOffset.len();
if ( dist < 0.01f )
dist = 0.01f;
// Set up the LOD for the shape
F32 invScale = ( 1.0f / getMax( getMax( mObjScale.x, mObjScale.y ), mObjScale.z ) );
mShapeInstance->setDetailFromDistance( state, dist * invScale );
// Make sure we have a valid level of detail
if ( mShapeInstance->getCurrentDetail() < 0 )
return;
// GFXTransformSaver is a handy helper class that restores
// the current GFX matrices to their original values when
// it goes out of scope at the end of the function
GFXTransformSaver saver;
// Set up our TS render state
TSRenderState rdata;
rdata.setSceneState( state );
rdata.setFadeOverride( 1.0f );
// We might have some forward lit materials
// so pass down a query to gather lights.
LightQuery query;
query.init( getWorldSphere() );
rdata.setLightQuery( &query );
// Set the world matrix to the objects render transform
MatrixF mat = getRenderTransform();
mat.scale( mObjScale );
GFX->setWorldMatrix( mat );
// Animate the the shape
mShapeInstance->animate();
// Allow the shape to submit the RenderInst(s) for itself
mShapeInstance->render( rdata );
}

View file

@ -0,0 +1,119 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _RENDERSHAPEEXAMPLE_H_
#define _RENDERSHAPEEXAMPLE_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _TSSHAPEINSTANCE_H_
#include "ts/tsShapeInstance.h"
#endif
//-----------------------------------------------------------------------------
// This class implements a basic SceneObject that can exist in the world at a
// 3D position and render itself. There are several valid ways to render an
// object in Torque. This class makes use of the "TS" (three space) shape
// system. TS manages loading the various mesh formats supported by Torque as
// well was rendering those meshes (including LOD and animation...though this
// example doesn't include any animation over time).
//-----------------------------------------------------------------------------
class RenderShapeExample : public SceneObject
{
typedef SceneObject Parent;
// Networking masks
// We need to implement a mask specifically to handle
// updating our transform from the server object to its
// client-side "ghost". We also need to implement a
// maks for handling editor updates to our properties
// (like material).
enum MaskBits
{
TransformMask = Parent::NextFreeMask << 0,
UpdateMask = Parent::NextFreeMask << 1,
NextFreeMask = Parent::NextFreeMask << 2
};
//--------------------------------------------------------------------------
// Rendering variables
//--------------------------------------------------------------------------
// The name of the shape file we will use for rendering
String mShapeFile;
// The actual shape instance
TSShapeInstance* mShapeInstance;
// Store the resource so we can access the filename later
Resource<TSShape> mShape;
public:
RenderShapeExample();
virtual ~RenderShapeExample();
// Declare this object as a ConsoleObject so that we can
// instantiate it into the world and network it
DECLARE_CONOBJECT(RenderShapeExample);
//--------------------------------------------------------------------------
// Object Editing
// Since there is always a server and a client object in Torque and we
// actually edit the server object we need to implement some basic
// networking functions
//--------------------------------------------------------------------------
// Set up any fields that we want to be editable (like position)
static void initPersistFields();
// Allows the object to update its editable settings
// from the server object to the client
virtual void inspectPostApply();
// Handle when we are added to the scene and removed from the scene
bool onAdd();
void onRemove();
// Override this so that we can dirty the network flag when it is called
void setTransform( const MatrixF &mat );
// This function handles sending the relevant data from the server
// object to the client object
U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream );
// This function handles receiving relevant data from the server
// object and applying it to the client object
void unpackUpdate( NetConnection *conn, BitStream *stream );
//--------------------------------------------------------------------------
// Object Rendering
// Torque utilizes a "batch" rendering system. This means that it builds a
// list of objects that need to render (via RenderInst's) and then renders
// them all in one batch. This allows it to optimized on things like
// minimizing texture, state, and shader switching by grouping objects that
// use the same Materials.
//--------------------------------------------------------------------------
// Create the geometry for rendering
void createShape();
// This is the function that allows this object to submit itself for rendering
void prepRenderImage( SceneRenderState *state );
};
#endif // _RENDERSHAPEEXAMPLE_H_

View file

@ -0,0 +1,197 @@
//-----------------------------------------------------------------------------
// 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 "gui/core/guiControl.h"
#include "console/consoleTypes.h"
#include "T3D/shapeBase.h"
#include "gfx/gfxDrawUtil.h"
#include "console/engineAPI.h"
//-----------------------------------------------------------------------------
/// Vary basic HUD clock.
/// Displays the current simulation time offset from some base. The base time
/// is usually synchronized with the server as mission start time. This hud
/// currently only displays minutes:seconds.
class GuiClockHud : public GuiControl
{
typedef GuiControl Parent;
bool mShowFrame;
bool mShowFill;
bool mTimeReversed;
ColorF mFillColor;
ColorF mFrameColor;
ColorF mTextColor;
S32 mTimeOffset;
public:
GuiClockHud();
void setTime(F32 newTime);
void setReverseTime(F32 reverseTime);
F32 getTime();
void onRender( Point2I, const RectI &);
static void initPersistFields();
DECLARE_CONOBJECT( GuiClockHud );
DECLARE_CATEGORY( "Gui Game" );
DECLARE_DESCRIPTION( "Basic HUD clock. Displays the current simulation time offset from some base." );
};
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT( GuiClockHud );
ConsoleDocClass( GuiClockHud,
"@brief Basic HUD clock. Displays the current simulation time offset from some base.\n"
"@tsexample\n"
"\n new GuiClockHud()"
"{\n"
" fillColor = \"0.0 1.0 0.0 1.0\"; // Fills with a solid green color\n"
" frameColor = \"1.0 1.0 1.0 1.0\"; // Solid white frame color\n"
" textColor = \"1.0 1.0 1.0 1.0\"; // Solid white text Color\n"
" showFill = \"true\";\n"
" showFrame = \"true\";\n"
"};\n"
"@endtsexample\n\n"
"@ingroup GuiGame\n"
);
GuiClockHud::GuiClockHud()
{
mShowFrame = mShowFill = true;
mFillColor.set(0, 0, 0, 0.5);
mFrameColor.set(0, 1, 0, 1);
mTextColor.set( 0, 1, 0, 1 );
mTimeOffset = 0;
}
void GuiClockHud::initPersistFields()
{
addGroup("Misc");
addField( "showFill", TypeBool, Offset( mShowFill, GuiClockHud ), "If true, draws a background color behind the control.");
addField( "showFrame", TypeBool, Offset( mShowFrame, GuiClockHud ), "If true, draws a frame around the control." );
addField( "fillColor", TypeColorF, Offset( mFillColor, GuiClockHud ), "Standard color for the background of the control." );
addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiClockHud ), "Color for the control's frame." );
addField( "textColor", TypeColorF, Offset( mTextColor, GuiClockHud ), "Color for the text on this control." );
endGroup("Misc");
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
void GuiClockHud::onRender(Point2I offset, const RectI &updateRect)
{
// Background first
if (mShowFill)
GFX->getDrawUtil()->drawRectFill(updateRect, mFillColor);
// Convert ms time into hours, minutes and seconds.
S32 time = S32(getTime());
S32 secs = time % 60;
S32 mins = (time % 3600) / 60;
// Currently only displays min/sec
char buf[256];
dSprintf(buf,sizeof(buf), "%02d:%02d",mins,secs);
// Center the text
offset.x += (getWidth() - mProfile->mFont->getStrWidth((const UTF8 *)buf)) / 2;
offset.y += (getHeight() - mProfile->mFont->getHeight()) / 2;
GFX->getDrawUtil()->setBitmapModulation(mTextColor);
GFX->getDrawUtil()->drawText(mProfile->mFont, offset, buf);
GFX->getDrawUtil()->clearBitmapModulation();
// Border last
if (mShowFrame)
GFX->getDrawUtil()->drawRect(updateRect, mFrameColor);
}
//-----------------------------------------------------------------------------
void GuiClockHud::setReverseTime(F32 time)
{
// Set the current time in seconds.
mTimeReversed = true;
mTimeOffset = S32(time * 1000) + Platform::getVirtualMilliseconds();
}
void GuiClockHud::setTime(F32 time)
{
// Set the current time in seconds.
mTimeReversed = false;
mTimeOffset = S32(time * 1000) - Platform::getVirtualMilliseconds();
}
F32 GuiClockHud::getTime()
{
// Return elapsed time in seconds.
if(mTimeReversed)
return F32(mTimeOffset - Platform::getVirtualMilliseconds()) / 1000;
else
return F32(mTimeOffset + Platform::getVirtualMilliseconds()) / 1000;
}
DefineEngineMethod(GuiClockHud, setTime, void, (F32 timeInSeconds),(60), "Sets the current base time for the clock.\n"
"@param timeInSeconds Time to set the clock, in seconds (IE: 00:02 would be 120)\n"
"@tsexample\n"
"// Define the time, in seconds\n"
"%timeInSeconds = 120;\n\n"
"// Change the time on the GuiClockHud control\n"
"%guiClockHud.setTime(%timeInSeconds);\n"
"@endtsexample\n"
)
{
object->setTime(timeInSeconds);
}
DefineEngineMethod(GuiClockHud, setReverseTime, void, (F32 timeInSeconds),(60), "@brief Sets a time for a countdown clock.\n\n"
"Setting the time like this will cause the clock to count backwards from the specified time.\n\n"
"@param timeInSeconds Time to set the clock, in seconds (IE: 00:02 would be 120)\n\n"
"@see setTime\n"
)
{
object->setReverseTime(timeInSeconds);
}
DefineEngineMethod(GuiClockHud, getTime, F32, (),, "Returns the current time, in seconds.\n"
"@return timeInseconds Current time, in seconds\n"
"@tsexample\n"
"// Get the current time from the GuiClockHud control\n"
"%timeInSeconds = %guiClockHud.getTime();\n"
"@endtsexample\n"
)
{
return object->getTime();
}

View file

@ -0,0 +1,191 @@
//-----------------------------------------------------------------------------
// 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 "gui/core/guiControl.h"
#include "gui/controls/guiBitmapCtrl.h"
#include "console/consoleTypes.h"
#include "scene/sceneManager.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/shapeBase.h"
#include "gfx/gfxDrawUtil.h"
#include "console/engineAPI.h"
//-----------------------------------------------------------------------------
/// Vary basic cross hair hud.
/// Uses the base bitmap control to render a bitmap, and decides whether
/// to draw or not depending on the current control object and it's state.
/// If there is ShapeBase object under the cross hair and it's named,
/// then a small health bar is displayed.
class GuiCrossHairHud : public GuiBitmapCtrl
{
typedef GuiBitmapCtrl Parent;
ColorF mDamageFillColor;
ColorF mDamageFrameColor;
Point2I mDamageRectSize;
Point2I mDamageOffset;
protected:
void drawDamage(Point2I offset, F32 damage, F32 opacity);
public:
GuiCrossHairHud();
void onRender( Point2I, const RectI &);
static void initPersistFields();
DECLARE_CONOBJECT( GuiCrossHairHud );
DECLARE_CATEGORY( "Gui Game" );
DECLARE_DESCRIPTION( "Basic cross hair hud. Reacts to state of control object.\n"
"Also displays health bar for named objects under the cross hair." );
};
/// Valid object types for which the cross hair will render, this
/// should really all be script controlled.
static const U32 ObjectMask = PlayerObjectType | VehicleObjectType;
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT( GuiCrossHairHud );
ConsoleDocClass( GuiCrossHairHud,
"@brief Basic cross hair hud. Reacts to state of control object. Also displays health bar for named objects under the cross hair.\n\n"
"Uses the base bitmap control to render a bitmap, and decides whether to draw or not depending "
"on the current control object and it's state. If there is ShapeBase object under the cross hair "
"and it's named, then a small health bar is displayed.\n\n"
"@tsexample\n"
"\n new GuiCrossHairHud()"
"{\n"
" damageFillColor = \"1.0 0.0 0.0 1.0\"; // Fills with a solid red color\n"
" damageFrameColor = \"1.0 1.0 1.0 1.0\"; // Solid white frame color\n"
" damageRect = \"15 5\";\n"
" damageOffset = \"0 -10\";\n"
"};\n"
"@endtsexample\n"
"@ingroup GuiGame\n"
);
GuiCrossHairHud::GuiCrossHairHud()
{
mDamageFillColor.set( 0.0f, 1.0f, 0.0f, 1.0f );
mDamageFrameColor.set( 1.0f, 0.6f, 0.0f, 1.0f );
mDamageRectSize.set(50, 4);
mDamageOffset.set(0,32);
}
void GuiCrossHairHud::initPersistFields()
{
addGroup("Damage");
addField( "damageFillColor", TypeColorF, Offset( mDamageFillColor, GuiCrossHairHud ), "As the health bar depletes, this color will represent the health loss amount." );
addField( "damageFrameColor", TypeColorF, Offset( mDamageFrameColor, GuiCrossHairHud ), "Color for the health bar's frame." );
addField( "damageRect", TypePoint2I, Offset( mDamageRectSize, GuiCrossHairHud ), "Size for the health bar portion of the control." );
addField( "damageOffset", TypePoint2I, Offset( mDamageOffset, GuiCrossHairHud ), "Offset for drawing the damage portion of the health control." );
endGroup("Damage");
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
void GuiCrossHairHud::onRender(Point2I offset, const RectI &updateRect)
{
// Must have a connection and player control object
GameConnection* conn = GameConnection::getConnectionToServer();
if (!conn)
return;
ShapeBase* control = dynamic_cast<ShapeBase*>(conn->getControlObject());
if (!control || !(control->getTypeMask() & ObjectMask) || !conn->isFirstPerson())
return;
// Parent render.
Parent::onRender(offset,updateRect);
// Get control camera info
MatrixF cam;
Point3F camPos;
conn->getControlCameraTransform(0,&cam);
cam.getColumn(3, &camPos);
// Extend the camera vector to create an endpoint for our ray
Point3F endPos;
cam.getColumn(1, &endPos);
endPos *= gClientSceneGraph->getVisibleDistance();
endPos += camPos;
// 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 | InteriorObjectType | ShapeBaseObjectType;
control->disableCollision();
RayInfo info;
if (gClientContainer.castRay(camPos, endPos, losMask, &info)) {
// Hit something... but we'll only display health for named
// ShapeBase objects. Could mask against the object type here
// and do a static cast if it's a ShapeBaseObjectType, but this
// isn't a performance situation, so I'll just use dynamic_cast.
if (ShapeBase* obj = dynamic_cast<ShapeBase*>(info.object))
if (obj->getShapeName()) {
offset.x = updateRect.point.x + updateRect.extent.x / 2;
offset.y = updateRect.point.y + updateRect.extent.y / 2;
drawDamage(offset + mDamageOffset, obj->getDamageValue(), 1);
}
}
// Restore control object collision
control->enableCollision();
}
//-----------------------------------------------------------------------------
/**
Display a damage bar ubove the shape.
This is a support funtion, called by onRender.
*/
void GuiCrossHairHud::drawDamage(Point2I offset, F32 damage, F32 opacity)
{
mDamageFillColor.alpha = mDamageFrameColor.alpha = opacity;
// Damage should be 0->1 (0 being no damage,or healthy), but
// we'll just make sure here as we flip it.
damage = mClampF(1 - damage, 0, 1);
// Center the bar
RectI rect(offset, mDamageRectSize);
rect.point.x -= mDamageRectSize.x / 2;
// Draw the border
GFX->getDrawUtil()->drawRect(rect, mDamageFrameColor);
// Draw the damage % fill
rect.point += Point2I(1, 1);
rect.extent -= Point2I(1, 1);
rect.extent.x = (S32)(rect.extent.x * damage);
if (rect.extent.x == 1)
rect.extent.x = 2;
if (rect.extent.x > 0)
GFX->getDrawUtil()->drawRectFill(rect, mDamageFillColor);
}

View file

@ -0,0 +1,190 @@
//-----------------------------------------------------------------------------
// 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 "gui/core/guiControl.h"
#include "console/consoleTypes.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/shapeBase.h"
#include "gfx/gfxDrawUtil.h"
//-----------------------------------------------------------------------------
/// A basic health bar control.
/// This gui displays the damage value of the current PlayerObjectType
/// control object. The gui can be set to pulse if the health value
/// drops below a set value. This control only works if a server
/// connection exists and it's control object is a PlayerObjectType. If
/// either of these requirements is false, the control is not rendered.
class GuiHealthBarHud : public GuiControl
{
typedef GuiControl Parent;
bool mShowFrame;
bool mShowFill;
bool mDisplayEnergy;
ColorF mFillColor;
ColorF mFrameColor;
ColorF mDamageFillColor;
S32 mPulseRate;
F32 mPulseThreshold;
F32 mValue;
public:
GuiHealthBarHud();
void onRender( Point2I, const RectI &);
static void initPersistFields();
DECLARE_CONOBJECT( GuiHealthBarHud );
DECLARE_CATEGORY( "Gui Game" );
DECLARE_DESCRIPTION( "A basic health bar. Shows the damage value of the current\n"
"PlayerObjectType control object." );
};
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT( GuiHealthBarHud );
ConsoleDocClass( GuiHealthBarHud,
"@brief A basic health bar. Shows the damage value of the current PlayerObjectType control object.\n\n"
"This gui displays the damage value of the current PlayerObjectType control object. "
"The gui can be set to pulse if the health value drops below a set value. "
"This control only works if a server connection exists and it's control object "
"is a PlayerObjectType. If either of these requirements is false, the control is not rendered.\n\n"
"@tsexample\n"
"\n new GuiHealthBarHud()"
"{\n"
" fillColor = \"0.0 1.0 0.0 1.0\"; // Fills with a solid green color\n"
" frameColor = \"1.0 1.0 1.0 1.0\"; // Solid white frame color\n"
" damageFillColor = \"1.0 0.0 0.0 1.0\"; // Fills with a solid red color\n"
" pulseRate = \"500\";\n"
" pulseThreshold = \"0.25\";\n"
" showFill = \"true\";\n"
" showFrame = \"true\";\n"
" displayEnergy = \"false\";\n"
"};\n"
"@endtsexample\n\n"
"@ingroup GuiGame\n"
);
GuiHealthBarHud::GuiHealthBarHud()
{
mShowFrame = mShowFill = true;
mDisplayEnergy = false;
mFillColor.set(0, 0, 0, 0.5);
mFrameColor.set(0, 1, 0, 1);
mDamageFillColor.set(0, 1, 0, 1);
mPulseRate = 0;
mPulseThreshold = 0.3f;
mValue = 0.2f;
}
void GuiHealthBarHud::initPersistFields()
{
addGroup("Colors");
addField( "fillColor", TypeColorF, Offset( mFillColor, GuiHealthBarHud ), "Standard color for the background of the control." );
addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiHealthBarHud ), "Color for the control's frame." );
addField( "damageFillColor", TypeColorF, Offset( mDamageFillColor, GuiHealthBarHud ), "As the health bar depletes, this color will represent the health loss amount." );
endGroup("Colors");
addGroup("Pulse");
addField( "pulseRate", TypeS32, Offset( mPulseRate, GuiHealthBarHud ), "Speed at which the control will pulse." );
addField( "pulseThreshold", TypeF32, Offset( mPulseThreshold, GuiHealthBarHud ), "Health level the control must be under before the control will pulse." );
endGroup("Pulse");
addGroup("Misc");
addField( "showFill", TypeBool, Offset( mShowFill, GuiHealthBarHud ), "If true, we draw the background color of the control." );
addField( "showFrame", TypeBool, Offset( mShowFrame, GuiHealthBarHud ), "If true, we draw the frame of the control." );
addField( "displayEnergy", TypeBool, Offset( mDisplayEnergy, GuiHealthBarHud ), "If true, display the energy value rather than the damage value." );
endGroup("Misc");
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
/**
Gui onRender method.
Renders a health bar with filled background and border.
*/
void GuiHealthBarHud::onRender(Point2I offset, const RectI &updateRect)
{
// Must have a connection and player control object
GameConnection* conn = GameConnection::getConnectionToServer();
if (!conn)
return;
ShapeBase* control = dynamic_cast<ShapeBase*>(conn->getControlObject());
if (!control || !(control->getTypeMask() & PlayerObjectType))
return;
if(mDisplayEnergy)
{
mValue = control->getEnergyValue();
}
else
{
// We'll just grab the damage right off the control object.
// Damage value 0 = no damage.
mValue = 1 - control->getDamageValue();
}
// Background first
if (mShowFill)
GFX->getDrawUtil()->drawRectFill(updateRect, mFillColor);
// Pulse the damage fill if it's below the threshold
if (mPulseRate != 0)
if (mValue < mPulseThreshold)
{
U32 time = Platform::getVirtualMilliseconds();
F32 alpha = 2.0f * F32(time % mPulseRate) / F32(mPulseRate);
mDamageFillColor.alpha = (alpha > 1.0f)? 2.0f - alpha: alpha;
}
else
mDamageFillColor.alpha = 1;
// Render damage fill %
RectI rect(updateRect);
if(getWidth() > getHeight())
rect.extent.x = (S32)(rect.extent.x * mValue);
else
{
S32 bottomY = rect.point.y + rect.extent.y;
rect.extent.y = (S32)(rect.extent.y * mValue);
rect.point.y = bottomY - rect.extent.y;
}
GFX->getDrawUtil()->drawRectFill(rect, mDamageFillColor);
// Border last
if (mShowFrame)
GFX->getDrawUtil()->drawRect(updateRect, mFrameColor);
}

View file

@ -0,0 +1,287 @@
//-----------------------------------------------------------------------------
// 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 "gui/core/guiControl.h"
#include "gui/3d/guiTSControl.h"
#include "console/consoleTypes.h"
#include "scene/sceneManager.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/shapeBase.h"
#include "gfx/gfxDrawUtil.h"
#include "console/engineAPI.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 GuiShapeNameHud : public GuiControl {
typedef GuiControl Parent;
// field data
ColorF mFillColor;
ColorF mFrameColor;
ColorF mTextColor;
F32 mVerticalOffset;
F32 mDistanceFade;
bool mShowFrame;
bool mShowFill;
protected:
void drawName( Point2I offset, const char *buf, F32 opacity);
public:
GuiShapeNameHud();
// GuiControl
virtual void onRender(Point2I offset, const RectI &updateRect);
static void initPersistFields();
DECLARE_CONOBJECT( GuiShapeNameHud );
DECLARE_CATEGORY( "Gui Game" );
DECLARE_DESCRIPTION( "Displays name and damage of ShapeBase objects in its bounds.\n"
"Must be a child of a GuiTSCtrl and a server connection must be present." );
};
//-----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(GuiShapeNameHud);
ConsoleDocClass( GuiShapeNameHud,
"@brief Displays name and damage of ShapeBase objects in its bounds. Must be a child of a GuiTSCtrl and a server connection must be present.\n\n"
"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.\n\n"
"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.\n\n"
"@tsexample\n"
"\n new GuiShapeNameHud()"
"{\n"
" fillColor = \"0.0 1.0 0.0 1.0\"; // Fills with a solid green color\n"
" frameColor = \"1.0 1.0 1.0 1.0\"; // Solid white frame color\n"
" textColor = \"1.0 1.0 1.0 1.0\"; // Solid white text Color\n"
" showFill = \"true\";\n"
" showFrame = \"true\";\n"
" verticalOffset = \"0.15\";\n"
" distanceFade = \"15.0\";\n"
"};\n"
"@endtsexample\n\n"
"@ingroup GuiGame\n"
);
/// Default distance for object's information to be displayed.
static const F32 cDefaultVisibleDistance = 500.0f;
GuiShapeNameHud::GuiShapeNameHud()
{
mFillColor.set( 0.25f, 0.25f, 0.25f, 0.25f );
mFrameColor.set( 0, 1, 0, 1 );
mTextColor.set( 0, 1, 0, 1 );
mShowFrame = mShowFill = true;
mVerticalOffset = 0.5f;
mDistanceFade = 0.1f;
}
void GuiShapeNameHud::initPersistFields()
{
addGroup("Colors");
addField( "fillColor", TypeColorF, Offset( mFillColor, GuiShapeNameHud ), "Standard color for the background of the control." );
addField( "frameColor", TypeColorF, Offset( mFrameColor, GuiShapeNameHud ), "Color for the control's frame." );
addField( "textColor", TypeColorF, Offset( mTextColor, GuiShapeNameHud ), "Color for the text on this control." );
endGroup("Colors");
addGroup("Misc");
addField( "showFill", TypeBool, Offset( mShowFill, GuiShapeNameHud ), "If true, we draw the background color of the control." );
addField( "showFrame", TypeBool, Offset( mShowFrame, GuiShapeNameHud ), "If true, we draw the frame of the control." );
addField( "verticalOffset", TypeF32, Offset( mVerticalOffset, GuiShapeNameHud ), "Amount to vertically offset the control in relation to the ShapeBase object in focus." );
addField( "distanceFade", TypeF32, Offset( mDistanceFade, GuiShapeNameHud ), "Visibility distance (how far the player must be from the ShapeBase object in focus) for this control to render." );
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 GuiShapeNameHud::onRender( Point2I, const RectI &updateRect)
{
// Background fill first
if (mShowFill)
GFX->getDrawUtil()->drawRectFill(updateRect, mFillColor);
// Must be in a TS Control
GuiTSCtrl *parent = dynamic_cast<GuiTSCtrl*>(getParent());
if (!parent) return;
// Must have a connection and control object
GameConnection* conn = GameConnection::getConnectionToServer();
if (!conn) return;
GameBase * control = dynamic_cast<GameBase*>(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 camFov;
conn->getControlCameraFov(&camFov);
camFov = mDegToRad(camFov) / 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 | InteriorObjectType | ShapeBaseObjectType;
control->disableCollision();
// 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) {
ShapeBase* shape = dynamic_cast< ShapeBase* >(*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 < camFov)
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);
}
}
}
// Restore control object collision
control->enableCollision();
// Border last
if (mShowFrame)
GFX->getDrawUtil()->drawRect(updateRect, mFrameColor);
}
//----------------------------------------------------------------------------
/// 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 GuiShapeNameHud::drawName(Point2I offset, const char *name, F32 opacity)
{
// Center the name
offset.x -= mProfile->mFont->getStrWidth((const UTF8 *)name) / 2;
offset.y -= mProfile->mFont->getHeight();
// Deal with opacity and draw.
mTextColor.alpha = opacity;
GFX->getDrawUtil()->setBitmapModulation(mTextColor);
GFX->getDrawUtil()->drawText(mProfile->mFont, offset, name);
GFX->getDrawUtil()->clearBitmapModulation();
}

View file

@ -0,0 +1,208 @@
//-----------------------------------------------------------------------------
// 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/fx/cameraFXMgr.h"
#include "platform/profiler.h"
#include "math/mRandom.h"
#include "math/mMatrix.h"
// global cam fx
CameraFXManager gCamFXMgr;
//**************************************************************************
// Camera effect
//**************************************************************************
CameraFX::CameraFX()
{
mElapsedTime = 0.0;
mDuration = 1.0;
}
//--------------------------------------------------------------------------
// Update
//--------------------------------------------------------------------------
void CameraFX::update( F32 dt )
{
mElapsedTime += dt;
}
//**************************************************************************
// Camera shake effect
//**************************************************************************
CameraShake::CameraShake()
{
mFreq.zero();
mAmp.zero();
mStartAmp.zero();
mTimeOffset.zero();
mCamFXTrans.identity();
mFalloff = 10.0;
remoteControlled = false;
isAdded = false;
}
bool CameraShake::isExpired()
{
if ( remoteControlled )
return false;
return Parent::isExpired();
}
//--------------------------------------------------------------------------
// Update
//--------------------------------------------------------------------------
void CameraShake::update( F32 dt )
{
Parent::update( dt );
if ( !remoteControlled )
fadeAmplitude();
else
mAmp = mStartAmp;
VectorF camOffset;
camOffset.x = mAmp.x * sin( M_2PI * (mTimeOffset.x + mElapsedTime) * mFreq.x );
camOffset.y = mAmp.y * sin( M_2PI * (mTimeOffset.y + mElapsedTime) * mFreq.y );
camOffset.z = mAmp.z * sin( M_2PI * (mTimeOffset.z + mElapsedTime) * mFreq.z );
VectorF rotAngles;
rotAngles.x = camOffset.x * 10.0 * M_PI/180.0;
rotAngles.y = camOffset.y * 10.0 * M_PI/180.0;
rotAngles.z = camOffset.z * 10.0 * M_PI/180.0;
MatrixF rotMatrix( EulerF( rotAngles.x, rotAngles.y, rotAngles.z ) );
mCamFXTrans = rotMatrix;
mCamFXTrans.setPosition( camOffset );
}
//--------------------------------------------------------------------------
// Fade out the amplitude over time
//--------------------------------------------------------------------------
void CameraShake::fadeAmplitude()
{
F32 percentDone = (mElapsedTime / mDuration);
if( percentDone > 1.0 ) percentDone = 1.0;
F32 time = 1 + percentDone * mFalloff;
time = 1 / (time * time);
mAmp = mStartAmp * time;
}
//--------------------------------------------------------------------------
// Initialize
//--------------------------------------------------------------------------
void CameraShake::init()
{
mTimeOffset.x = 0.0;
mTimeOffset.y = gRandGen.randF();
mTimeOffset.z = gRandGen.randF();
}
//**************************************************************************
// CameraFXManager
//**************************************************************************
CameraFXManager::CameraFXManager()
{
mCamFXTrans.identity();
}
//--------------------------------------------------------------------------
// Destructor
//--------------------------------------------------------------------------
CameraFXManager::~CameraFXManager()
{
clear();
}
//--------------------------------------------------------------------------
// Add new effect to currently running list
//--------------------------------------------------------------------------
void CameraFXManager::addFX( CameraFX *newFX )
{
mFXList.pushFront( newFX );
}
void CameraFXManager::removeFX( CameraFX *fx )
{
CamFXList::Iterator itr = mFXList.begin();
for ( ; itr != mFXList.end(); itr++ )
{
if ( *itr == fx )
{
mFXList.erase( itr );
return;
}
}
return;
}
//--------------------------------------------------------------------------
// Clear all currently running camera effects
//--------------------------------------------------------------------------
void CameraFXManager::clear()
{
for(CamFXList::Iterator i = mFXList.begin(); i != mFXList.end(); ++i)
{
delete *i;
}
mFXList.clear();
}
//--------------------------------------------------------------------------
// Update camera effects
//--------------------------------------------------------------------------
void CameraFXManager::update( F32 dt )
{
PROFILE_SCOPE( CameraFXManager_update );
mCamFXTrans.identity();
CamFXList::Iterator cur;
for(CamFXList::Iterator i = mFXList.begin(); i != mFXList.end(); /*Trickiness*/)
{
// Store previous iterator and increment while iterator is still valid.
cur = i;
++i;
CameraFX * curFX = *cur;
curFX->update( dt );
MatrixF fxTrans = curFX->getTrans();
mCamFXTrans.mul( fxTrans );
if( curFX->isExpired() )
{
delete curFX;
mFXList.erase( cur );
}
}
}

View file

@ -0,0 +1,113 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _CAMERAFXMGR_H_
#define _CAMERAFXMGR_H_
#ifndef _TORQUE_LIST_
#include "core/util/tList.h"
#endif
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _MMATRIX_H_
#include "math/mMatrix.h"
#endif
//**************************************************************************
// Abstract camera effect template
//**************************************************************************
class CameraFX
{
protected:
MatrixF mCamFXTrans;
F32 mElapsedTime;
F32 mDuration;
public:
CameraFX();
MatrixF & getTrans(){ return mCamFXTrans; }
virtual bool isExpired(){ return mElapsedTime >= mDuration; }
void setDuration( F32 duration ){ mDuration = duration; }
virtual void update( F32 dt );
};
//--------------------------------------------------------------------------
// Camera shake effect
//--------------------------------------------------------------------------
class CameraShake : public CameraFX
{
typedef CameraFX Parent;
VectorF mFreq; // these are vectors to represent these values in 3D
VectorF mStartAmp;
VectorF mAmp;
VectorF mTimeOffset;
F32 mFalloff;
public:
/// Is controlled by someone else, ignore duration and do not delete.
bool remoteControlled;
bool isAdded;
CameraShake();
void init();
void fadeAmplitude();
void setFalloff( F32 falloff ){ mFalloff = falloff; }
void setFrequency( VectorF &freq ){ mFreq = freq; }
void setAmplitude( VectorF &amp ){ mStartAmp = amp; }
bool isExpired();
virtual void update( F32 dt );
};
//**************************************************************************
// CameraFXManager
//**************************************************************************
class CameraFXManager
{
typedef CameraFX * CameraFXPtr;
MatrixF mCamFXTrans;
typedef Torque::List<CameraFXPtr> CamFXList;
CamFXList mFXList;
public:
void addFX( CameraFX *newFX );
void removeFX( CameraFX *fx );
void clear();
MatrixF & getTrans(){ return mCamFXTrans; }
void update( F32 dt );
CameraFXManager();
~CameraFXManager();
};
extern CameraFXManager gCamFXMgr;
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,199 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _EXPLOSION_H_
#define _EXPLOSION_H_
#ifndef _GAMEBASE_H_
#include "T3D/gameBase/gameBase.h"
#endif
#ifndef _TSSHAPE_H_
#include "ts/tsShape.h"
#endif
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
#ifndef _LIGHTINFO_H_
#include "lighting/lightInfo.h"
#endif
class ParticleEmitter;
class ParticleEmitterData;
class TSThread;
class SFXTrack;
struct DebrisData;
class ShockwaveData;
//--------------------------------------------------------------------------
class ExplosionData : public GameBaseData {
public:
typedef GameBaseData Parent;
enum ExplosionConsts
{
EC_NUM_DEBRIS_TYPES = 1,
EC_NUM_EMITTERS = 4,
EC_MAX_SUB_EXPLOSIONS = 5,
EC_NUM_TIME_KEYS = 4,
};
public:
StringTableEntry dtsFileName;
bool faceViewer;
S32 particleDensity;
F32 particleRadius;
SFXTrack* soundProfile;
ParticleEmitterData* particleEmitter;
S32 particleEmitterId;
Point3F explosionScale;
F32 playSpeed;
Resource<TSShape> explosionShape;
S32 explosionAnimation;
ParticleEmitterData* emitterList[EC_NUM_EMITTERS];
S32 emitterIDList[EC_NUM_EMITTERS];
ShockwaveData * shockwave;
S32 shockwaveID;
bool shockwaveOnTerrain;
DebrisData * debrisList[EC_NUM_DEBRIS_TYPES];
S32 debrisIDList[EC_NUM_DEBRIS_TYPES];
F32 debrisThetaMin;
F32 debrisThetaMax;
F32 debrisPhiMin;
F32 debrisPhiMax;
S32 debrisNum;
S32 debrisNumVariance;
F32 debrisVelocity;
F32 debrisVelocityVariance;
// sub - explosions
ExplosionData* explosionList[EC_MAX_SUB_EXPLOSIONS];
S32 explosionIDList[EC_MAX_SUB_EXPLOSIONS];
S32 delayMS;
S32 delayVariance;
S32 lifetimeMS;
S32 lifetimeVariance;
F32 offset;
Point3F sizes[ EC_NUM_TIME_KEYS ];
F32 times[ EC_NUM_TIME_KEYS ];
// camera shake data
bool shakeCamera;
VectorF camShakeFreq;
VectorF camShakeAmp;
F32 camShakeDuration;
F32 camShakeRadius;
F32 camShakeFalloff;
// Dynamic Lighting. The light is smoothly
// interpolated from start to end time.
F32 lightStartRadius;
F32 lightEndRadius;
ColorF lightStartColor;
ColorF lightEndColor;
F32 lightStartBrightness;
F32 lightEndBrightness;
F32 lightNormalOffset;
ExplosionData();
DECLARE_CONOBJECT(ExplosionData);
bool onAdd();
bool preload(bool server, String &errorStr);
static void initPersistFields();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
};
//--------------------------------------------------------------------------
class Explosion : public GameBase, public ISceneLight
{
typedef GameBase Parent;
private:
ExplosionData* mDataBlock;
TSShapeInstance* mExplosionInstance;
TSThread* mExplosionThread;
SimObjectPtr<ParticleEmitter> mEmitterList[ ExplosionData::EC_NUM_EMITTERS ];
SimObjectPtr<ParticleEmitter> mMainEmitter;
U32 mCurrMS;
U32 mEndingMS;
F32 mRandAngle;
LightInfo* mLight;
protected:
Point3F mInitialNormal;
F32 mFade;
bool mActive;
S32 mDelayMS;
F32 mRandomVal;
U32 mCollideType;
protected:
bool onAdd();
void onRemove();
bool explode();
void processTick(const Move *move);
void advanceTime(F32 dt);
void updateEmitters( F32 dt );
void launchDebris( Point3F &axis );
void spawnSubExplosions();
void setCurrentScale();
// Rendering
protected:
void prepRenderImage( SceneRenderState *state );
void prepBatchRender(SceneRenderState *state);
void prepModelView(SceneRenderState*);
public:
Explosion();
~Explosion();
void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0);
// ISceneLight
virtual void submitLights( LightManager *lm, bool staticLighting );
virtual LightInfo* getLight() { return mLight; }
bool onNewDataBlock( GameBaseData *dptr, bool reload );
void setCollideType( U32 cType ){ mCollideType = cType; }
DECLARE_CONOBJECT(Explosion);
static void initPersistFields();
};
#endif // _H_EXPLOSION

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,392 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _FOLIAGEREPLICATOR_H_
#define _FOLIAGEREPLICATOR_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _GFXTEXTUREHANDLE_H_
#include "gfx/gfxTextureHandle.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
#ifndef _GBITMAP_H_
#include "gfx/bitmap/gBitmap.h"
#endif
#ifndef _RENDERPASSMANAGER_H_
#include "renderInstance/renderPassManager.h"
#endif
#ifndef _MATHUTIL_FRUSTUM_H_
#include "math/util/frustum.h"
#endif
#pragma warning( push, 4 )
#define AREA_ANIMATION_ARC (1.0f / 360.0f)
#define FXFOLIAGEREPLICATOR_COLLISION_MASK ( TerrainObjectType | \
InteriorObjectType | \
StaticShapeObjectType | \
WaterObjectType )
#define FXFOLIAGEREPLICATOR_NOWATER_COLLISION_MASK ( TerrainObjectType | \
InteriorObjectType | \
StaticShapeObjectType )
#define FXFOLIAGE_ALPHA_EPSILON 1e-4
//------------------------------------------------------------------------------
// Class: fxFoliageItem
//------------------------------------------------------------------------------
class fxFoliageItem
{
public:
MatrixF Transform;
F32 Width;
F32 Height;
Box3F FoliageBox;
bool Flipped;
F32 SwayPhase;
F32 SwayTimeRatio;
F32 LightPhase;
F32 LightTimeRatio;
U32 LastFrameSerialID;
};
//------------------------------------------------------------------------------
// Class: fxFoliageCulledList
//------------------------------------------------------------------------------
class fxFoliageCulledList
{
public:
fxFoliageCulledList() {};
fxFoliageCulledList(Box3F SearchBox, fxFoliageCulledList* InVec);
~fxFoliageCulledList() {};
void FindCandidates(Box3F SearchBox, fxFoliageCulledList* InVec);
U32 GetListCount(void) { return mCulledObjectSet.size(); };
fxFoliageItem* GetElement(U32 index) { return mCulledObjectSet[index]; };
Vector<fxFoliageItem*> mCulledObjectSet; // Culled Object Set.
};
//------------------------------------------------------------------------------
// Class: fxFoliageQuadNode
//------------------------------------------------------------------------------
class fxFoliageQuadrantNode {
public:
U32 Level;
Box3F QuadrantBox;
fxFoliageQuadrantNode* QuadrantChildNode[4];
Vector<fxFoliageItem*> RenderList;
// Used in DrawIndexPrimitive call.
U32 startIndex;
U32 primitiveCount;
};
//------------------------------------------------------------------------------
// Class: fxFoliageRenderList
//------------------------------------------------------------------------------
class fxFoliageRenderList
{
public:
Box3F mBox; // Clipping Box.
Frustum mFrustum; // View frustum.
Vector<fxFoliageItem*> mVisObjectSet; // Visible Object Set.
F32 mHeightLerp; // Height Lerp.
public:
bool IsQuadrantVisible(const Box3F VisBox, const MatrixF& RenderTransform);
void SetupClipPlanes(SceneRenderState* state, const F32 FarClipPlane);
void DrawQuadBox(const Box3F& QuadBox, const ColorF Colour);
};
// Define a vertex
GFXDeclareVertexFormat( GFXVertexFoliage )
{
Point3F point;
Point3F normal;
Point2F texCoord;
Point2F texCoord2;
};
//------------------------------------------------------------------------------
// Class: fxFoliageReplicator
//------------------------------------------------------------------------------
class fxFoliageReplicator : public SceneObject
{
private:
typedef SceneObject Parent;
protected:
void CreateFoliage(void);
void DestroyFoliage(void);
void DestroyFoliageItems();
void SyncFoliageReplicators(void);
Box3F FetchQuadrant(Box3F Box, U32 Quadrant);
void ProcessQuadrant(fxFoliageQuadrantNode* pParentNode, fxFoliageCulledList* pCullList, U32 Quadrant);
void ProcessNodeChildren(fxFoliageQuadrantNode* pParentNode, fxFoliageCulledList* pCullList);
enum { FoliageReplicationMask = (1 << 0) };
U32 mCreationAreaAngle;
bool mClientReplicationStarted;
U32 mCurrentFoliageCount;
Vector<fxFoliageQuadrantNode*> mFoliageQuadTree;
Vector<fxFoliageItem*> mReplicatedFoliage;
fxFoliageRenderList mFrustumRenderSet;
GFXVertexBufferHandle<GFXVertexFoliage> mVertexBuffer;
GFXPrimitiveBufferHandle mPrimBuffer;
GFXShaderRef mShader;
ShaderData* mShaderData;
GBitmap* mAlphaLookup;
MRandomLCG RandomGen;
F32 mFadeInGradient;
F32 mFadeOutGradient;
S32 mLastRenderTime;
F32 mGlobalSwayPhase;
F32 mGlobalSwayTimeRatio;
F32 mGlobalLightPhase;
F32 mGlobalLightTimeRatio;
U32 mFrameSerialID;
U32 mQuadTreeLevels; // Quad-Tree Levels.
U32 mPotentialFoliageNodes; // Potential Foliage Nodes.
U32 mNextAllocatedNodeIdx; // Next Allocated Node Index.
U32 mBillboardsAcquired; // Billboards Acquired.
// Used for alpha lookup in the pixel shader
GFXTexHandle mAlphaTexture;
GFXStateBlockRef mPlacementSB;
GFXStateBlockRef mRenderSB;
GFXStateBlockRef mDebugSB;
GFXShaderConstBufferRef mFoliageShaderConsts;
GFXShaderConstHandle* mFoliageShaderProjectionSC;
GFXShaderConstHandle* mFoliageShaderWorldSC;
GFXShaderConstHandle* mFoliageShaderGlobalSwayPhaseSC;
GFXShaderConstHandle* mFoliageShaderSwayMagnitudeSideSC;
GFXShaderConstHandle* mFoliageShaderSwayMagnitudeFrontSC;
GFXShaderConstHandle* mFoliageShaderGlobalLightPhaseSC;
GFXShaderConstHandle* mFoliageShaderLuminanceMagnitudeSC;
GFXShaderConstHandle* mFoliageShaderLuminanceMidpointSC;
GFXShaderConstHandle* mFoliageShaderDistanceRangeSC;
GFXShaderConstHandle* mFoliageShaderCameraPosSC;
GFXShaderConstHandle* mFoliageShaderTrueBillboardSC;
//pixel shader
GFXShaderConstHandle* mFoliageShaderGroundAlphaSC;
GFXShaderConstHandle* mFoliageShaderAmbientColorSC;
GFXShaderConstHandle* mDiffuseTextureSC;
GFXShaderConstHandle* mAlphaMapTextureSC;
bool mDirty;
void SetupShader();
void SetupBuffers();
void renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance*);
void renderBuffers(SceneRenderState* state);
void renderArc(const F32 fRadiusX, const F32 fRadiusY);
void renderPlacementArea(const F32 ElapsedTime);
void renderQuad(fxFoliageQuadrantNode* quadNode, const MatrixF& RenderTransform, const bool UseDebug);
void computeAlphaTex();
public:
fxFoliageReplicator();
~fxFoliageReplicator();
void StartUp(void);
void ShowReplication(void);
void HideReplication(void);
// SceneObject
virtual void prepRenderImage( SceneRenderState *state );
// SimObject
bool onAdd();
void onRemove();
void inspectPostApply();
// NetObject
U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream);
void unpackUpdate(NetConnection *conn, BitStream *stream);
// Editor
void onGhostAlwaysDone();
// ConObject.
static void initPersistFields();
// Field Data.
class tagFieldData
{
public:
bool mUseDebugInfo;
F32 mDebugBoxHeight;
U32 mSeed;
StringTableEntry mFoliageFile;
GFXTexHandle mFoliageTexture;
U32 mFoliageCount;
U32 mFoliageRetries;
U32 mInnerRadiusX;
U32 mInnerRadiusY;
U32 mOuterRadiusX;
U32 mOuterRadiusY;
F32 mMinWidth;
F32 mMaxWidth;
F32 mMinHeight;
F32 mMaxHeight;
bool mFixAspectRatio;
bool mFixSizeToMax;
F32 mOffsetZ;
bool mRandomFlip;
bool mUseTrueBillboards;
bool mUseCulling;
U32 mCullResolution;
F32 mViewDistance;
F32 mViewClosest;
F32 mFadeInRegion;
F32 mFadeOutRegion;
F32 mAlphaCutoff;
F32 mGroundAlpha;
bool mSwayOn;
bool mSwaySync;
F32 mSwayMagnitudeSide;
F32 mSwayMagnitudeFront;
F32 mMinSwayTime;
F32 mMaxSwayTime;
bool mLightOn;
bool mLightSync;
F32 mMinLuminance;
F32 mMaxLuminance;
F32 mLightTime;
bool mAllowOnTerrain;
bool mAllowOnInteriors;
bool mAllowStatics;
bool mAllowOnWater;
bool mAllowWaterSurface;
S32 mAllowedTerrainSlope;
bool mHideFoliage;
bool mShowPlacementArea;
U32 mPlacementBandHeight;
ColorF mPlaceAreaColour;
tagFieldData()
{
// Set Defaults.
mUseDebugInfo = false;
mDebugBoxHeight = 1.0f;
mSeed = 1376312589;
mFoliageFile = StringTable->insert("");
mFoliageTexture = GFXTexHandle();
mFoliageCount = 10;
mFoliageRetries = 100;
mInnerRadiusX = 0;
mInnerRadiusY = 0;
mOuterRadiusX = 128;
mOuterRadiusY = 128;
mMinWidth = 1;
mMaxWidth = 3;
mMinHeight = 1;
mMaxHeight = 5;
mFixAspectRatio = true;
mFixSizeToMax = false;
mOffsetZ = 0;
mRandomFlip = true;
mUseTrueBillboards = false;
mUseCulling = true;
mCullResolution = 64;
mViewDistance = 50.0f;
mViewClosest = 1.0f;
mFadeInRegion = 10.0f;
mFadeOutRegion = 1.0f;
mAlphaCutoff = 0.2f;
mGroundAlpha = 1.0f;
mSwayOn = false;
mSwaySync = false;
mSwayMagnitudeSide = 0.1f;
mSwayMagnitudeFront = 0.2f;
mMinSwayTime = 3.0f;
mMaxSwayTime = 10.0f;
mLightOn = false;
mLightSync = false;
mMinLuminance = 0.7f;
mMaxLuminance = 1.0f;
mLightTime = 5.0f;
mAllowOnTerrain = true;
mAllowOnInteriors = true;
mAllowStatics = true;
mAllowOnWater = false;
mAllowWaterSurface = false;
mAllowedTerrainSlope = 90;
mHideFoliage = false;
mShowPlacementArea = true;
mPlacementBandHeight = 25;
mPlaceAreaColour .set(0.4f, 0, 0.8f);
}
} mFieldData;
// Declare Console Object.
DECLARE_CONOBJECT(fxFoliageReplicator);
};
#pragma warning( pop )
#endif // _FOLIAGEREPLICATOR_H_

View file

@ -0,0 +1,764 @@
//-----------------------------------------------------------------------------
// 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/fx/fxShapeReplicator.h"
#include "gfx/gfxDevice.h"
#include "gfx/primBuilder.h"
#include "console/consoleTypes.h"
#include "core/stream/bitStream.h"
#include "math/mRandom.h"
#include "math/mathIO.h"
#include "T3D/gameBase/gameConnection.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
#include "renderInstance/renderPassManager.h"
#include "console/engineAPI.h"
//------------------------------------------------------------------------------
//
// Put this in /example/common/editor/editor.cs in function [Editor::create()] (around line 66).
//
// // Ignore Replicated fxStatic Instances.
// EWorldEditor.ignoreObjClass("fxShapeReplicatedStatic");
//
//------------------------------------------------------------------------------
//
// Put this in /example/common/editor/EditorGui.cs in [function Creator::init( %this )]
//
// %Environment_Item[8] = "fxShapeReplicator"; <-- ADD THIS.
//
//------------------------------------------------------------------------------
//
// Put the function in /example/common/editor/ObjectBuilderGui.gui [around line 458] ...
//
// function ObjectBuilderGui::buildfxShapeReplicator(%this)
// {
// %this.className = "fxShapeReplicator";
// %this.process();
// }
//
//------------------------------------------------------------------------------
//
// Put this in /example/common/client/missionDownload.cs in [function clientCmdMissionStartPhase3(%seq,%missionName)] (line 65)
// after codeline 'onPhase2Complete();'.
//
// StartClientReplication();
//
//------------------------------------------------------------------------------
//
// Put this in /engine/console/simBase.h (around line 509) in
//
// namespace Sim
// {
// DeclareNamedSet(fxReplicatorSet) <-- ADD THIS (Note no semi-colon).
//
//------------------------------------------------------------------------------
//
// Put this in /engine/console/simBase.cc (around line 19) in
//
// ImplementNamedSet(fxReplicatorSet) <-- ADD THIS
//
//------------------------------------------------------------------------------
//
// Put this in /engine/console/simManager.cc [function void init()] (around line 269).
//
// namespace Sim
// {
// InstantiateNamedSet(fxReplicatorSet); <-- ADD THIS
//
//------------------------------------------------------------------------------
extern bool gEditingMission;
//------------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1(fxShapeReplicator);
IMPLEMENT_CO_NETOBJECT_V1(fxShapeReplicatedStatic);
ConsoleDocClass( fxShapeReplicator,
"@brief An emitter for objects to replicate across an area.\n"
"@ingroup Foliage\n"
);
ConsoleDocClass( fxShapeReplicatedStatic,
"@brief The object definition for shapes that will be replicated across an area using an fxShapeReplicator.\n"
"@ingroup Foliage\n"
);
//------------------------------------------------------------------------------
// Class: fxShapeReplicator
//------------------------------------------------------------------------------
fxShapeReplicator::fxShapeReplicator()
{
// Setup NetObject.
mTypeMask |= StaticObjectType;
mNetFlags.set(Ghostable | ScopeAlways);
// Reset Shape Count.
mCurrentShapeCount = 0;
// Reset Creation Area Angle Animation.
mCreationAreaAngle = 0;
// Reset Last Render Time.
mLastRenderTime = 0;
mPlacementSB = NULL;
}
//------------------------------------------------------------------------------
fxShapeReplicator::~fxShapeReplicator()
{
mPlacementSB = NULL;
}
//------------------------------------------------------------------------------
void fxShapeReplicator::initPersistFields()
{
// Add out own persistent fields.
addGroup( "Debugging" ); // MM: Added Group Header.
addField( "HideReplications", TypeBool, Offset( mFieldData.mHideReplications, fxShapeReplicator ), "Replicated shapes are hidden when set to true." );
addField( "ShowPlacementArea", TypeBool, Offset( mFieldData.mShowPlacementArea, fxShapeReplicator ), "Draw placement rings when set to true." );
addField( "PlacementAreaHeight", TypeS32, Offset( mFieldData.mPlacementBandHeight, fxShapeReplicator ), "Height of the placement ring in world units." );
addField( "PlacementColour", TypeColorF, Offset( mFieldData.mPlaceAreaColour, fxShapeReplicator ), "Color of the placement ring." );
endGroup( "Debugging" ); // MM: Added Group Footer.
addGroup( "Media" ); // MM: Added Group Header.
addField( "ShapeFile", TypeShapeFilename, Offset( mFieldData.mShapeFile, fxShapeReplicator ), "Filename of shape to replicate." );
endGroup( "Media" ); // MM: Added Group Footer.
addGroup( "Replications" ); // MM: Added Group Header.
addField( "Seed", TypeS32, Offset( mFieldData.mSeed, fxShapeReplicator ), "Random seed for shape placement." );
addField( "ShapeCount", TypeS32, Offset( mFieldData.mShapeCount, fxShapeReplicator ), "Maximum shape instance count." );
addField( "ShapeRetries", TypeS32, Offset( mFieldData.mShapeRetries, fxShapeReplicator ), "Number of times to try placing a shape instance before giving up." );
endGroup( "Replications" ); // MM: Added Group Footer.
addGroup( "Placement Radius" ); // MM: Added Group Header.
addField( "InnerRadiusX", TypeS32, Offset( mFieldData.mInnerRadiusX, fxShapeReplicator ), "Placement area inner radius on the X axis" );
addField( "InnerRadiusY", TypeS32, Offset( mFieldData.mInnerRadiusY, fxShapeReplicator ), "Placement area inner radius on the Y axis" );
addField( "OuterRadiusX", TypeS32, Offset( mFieldData.mOuterRadiusX, fxShapeReplicator ), "Placement area outer radius on the X axis" );
addField( "OuterRadiusY", TypeS32, Offset( mFieldData.mOuterRadiusY, fxShapeReplicator ), "Placement area outer radius on the Y axis" );
endGroup( "Placement Radius" ); // MM: Added Group Footer.
addGroup( "Restraints" ); // MM: Added Group Header.
addField( "AllowOnTerrain", TypeBool, Offset( mFieldData.mAllowOnTerrain, fxShapeReplicator ), "Shapes will be placed on terrain when set." );
addField( "AllowOnInteriors", TypeBool, Offset( mFieldData.mAllowOnInteriors, fxShapeReplicator ), "Shapes will be placed on InteriorInstances when set." );
addField( "AllowOnStatics", TypeBool, Offset( mFieldData.mAllowStatics, fxShapeReplicator ), "Shapes will be placed on Static shapes when set." );
addField( "AllowOnWater", TypeBool, Offset( mFieldData.mAllowOnWater, fxShapeReplicator ), "Shapes will be placed on/under water when set." );
addField( "AllowWaterSurface", TypeBool, Offset( mFieldData.mAllowWaterSurface, fxShapeReplicator ), "Shapes will be placed on water when set. Requires AllowOnWater." );
addField( "AlignToTerrain", TypeBool, Offset( mFieldData.mAlignToTerrain, fxShapeReplicator ), "Align shapes to surface normal when set." );
addField( "Interactions", TypeBool, Offset( mFieldData.mInteractions, fxShapeReplicator ), "Allow physics interactions with shapes." );
addField( "AllowedTerrainSlope", TypeS32, Offset( mFieldData.mAllowedTerrainSlope, fxShapeReplicator ), "Maximum surface angle allowed for shape instances." );
addField( "TerrainAlignment", TypePoint3F, Offset( mFieldData.mTerrainAlignment, fxShapeReplicator ), "Surface normals will be multiplied by these values when AlignToTerrain is enabled." );
endGroup( "Restraints" ); // MM: Added Group Footer.
addGroup( "Object Transforms" ); // MM: Added Group Header.
addField( "ShapeScaleMin", TypePoint3F, Offset( mFieldData.mShapeScaleMin, fxShapeReplicator ), "Minimum shape scale." );
addField( "ShapeScaleMax", TypePoint3F, Offset( mFieldData.mShapeScaleMax, fxShapeReplicator ), "Maximum shape scale." );
addField( "ShapeRotateMin", TypePoint3F, Offset( mFieldData.mShapeRotateMin, fxShapeReplicator ), "Minimum shape rotation angles.");
addField( "ShapeRotateMax", TypePoint3F, Offset( mFieldData.mShapeRotateMax, fxShapeReplicator ), "Maximum shape rotation angles." );
addField( "OffsetZ", TypeS32, Offset( mFieldData.mOffsetZ, fxShapeReplicator ), "Offset shapes by this amount vertically." );
endGroup( "Object Transforms" ); // MM: Added Group Footer.
// Initialise parents' persistent fields.
Parent::initPersistFields();
}
//------------------------------------------------------------------------------
void fxShapeReplicator::CreateShapes(void)
{
F32 HypX, HypY;
F32 Angle;
U32 RelocationRetry;
Point3F ShapePosition;
Point3F ShapeStart;
Point3F ShapeEnd;
Point3F ShapeScale;
EulerF ShapeRotation;
QuatF QRotation;
bool CollisionResult;
RayInfo RayEvent;
TSShape* pShape;
// Don't create shapes if we are hiding replications.
if (mFieldData.mHideReplications) return;
// Cannot continue without shapes!
if (dStrcmp(mFieldData.mShapeFile, "") == 0) return;
// Check that we can position somewhere!
if (!( mFieldData.mAllowOnTerrain ||
mFieldData.mAllowOnInteriors ||
mFieldData.mAllowStatics ||
mFieldData.mAllowOnWater))
{
// Problem ...
Con::warnf(ConsoleLogEntry::General, "[%s] - Could not place object, All alloweds are off!", getName());
// Return here.
return;
}
// Check Shapes.
AssertFatal(mCurrentShapeCount==0,"Shapes already present, this should not be possible!")
// Check that we have a shape...
if (!mFieldData.mShapeFile) return;
// Set Seed.
RandomGen.setSeed(mFieldData.mSeed);
// Set shape vector.
mReplicatedShapes.clear();
// Add shapes.
for (U32 idx = 0; idx < mFieldData.mShapeCount; idx++)
{
fxShapeReplicatedStatic* fxStatic;
// Create our static shape.
fxStatic = new fxShapeReplicatedStatic();
// Set the 'shapeName' field.
fxStatic->setField("shapeName", mFieldData.mShapeFile);
// Is this Replicator on the Server?
if (isServerObject())
// Yes, so stop it from Ghosting. (Hack, Hack, Hack!)
fxStatic->touchNetFlags(Ghostable, false);
else
// No, so flag as ghost object. (Another damn Hack!)
fxStatic->touchNetFlags(IsGhost, true);
// Register the Object.
if (!fxStatic->registerObject())
{
// Problem ...
Con::warnf(ConsoleLogEntry::General, "[%s] - Could not load shape file '%s'!", getName(), mFieldData.mShapeFile);
// Destroy Shape.
delete fxStatic;
// Destroy existing hapes.
DestroyShapes();
// Quit.
return;
}
// Get Allocated Shape.
pShape = fxStatic->getShape();
// Reset Relocation Retry.
RelocationRetry = mFieldData.mShapeRetries;
// Find it a home ...
do
{
// Get the Replicator Position.
ShapePosition = getPosition();
// Calculate a random offset
HypX = RandomGen.randF(mFieldData.mInnerRadiusX, mFieldData.mOuterRadiusX);
HypY = RandomGen.randF(mFieldData.mInnerRadiusY, mFieldData.mOuterRadiusY);
Angle = RandomGen.randF(0, (F32)M_2PI);
// Calcualte the new position.
ShapePosition.x += HypX * mCos(Angle);
ShapePosition.y += HypY * mSin(Angle);
// Initialise RayCast Search Start/End Positions.
ShapeStart = ShapeEnd = ShapePosition;
ShapeStart.z = 2000.f;
ShapeEnd.z= -2000.f;
// Is this the Server?
if (isServerObject())
// Perform Ray Cast Collision on Server Terrain.
CollisionResult = gServerContainer.castRay(ShapeStart, ShapeEnd, FXREPLICATOR_COLLISION_MASK, &RayEvent);
else
// Perform Ray Cast Collision on Client Terrain.
CollisionResult = gClientContainer.castRay( ShapeStart, ShapeEnd, FXREPLICATOR_COLLISION_MASK, &RayEvent);
// Did we hit anything?
if (CollisionResult)
{
// For now, let's pretend we didn't get a collision.
CollisionResult = false;
// Yes, so get it's type.
U32 CollisionType = RayEvent.object->getTypeMask();
// Check Illegal Placements.
if (((CollisionType & TerrainObjectType) && !mFieldData.mAllowOnTerrain) ||
((CollisionType & InteriorObjectType) && !mFieldData.mAllowOnInteriors) ||
((CollisionType & StaticShapeObjectType) && !mFieldData.mAllowStatics) ||
((CollisionType & WaterObjectType) && !mFieldData.mAllowOnWater) ) continue;
// If we collided with water and are not allowing on the water surface then let's find the
// terrain underneath and pass this on as the original collision else fail.
//
// NOTE:- We need to do this on the server/client as appropriate.
if ((CollisionType & WaterObjectType) && !mFieldData.mAllowWaterSurface)
{
// Is this the Server?
if (isServerObject())
{
// Yes, so do it on the server container.
if (!gServerContainer.castRay( ShapeStart, ShapeEnd, FXREPLICATOR_NOWATER_COLLISION_MASK, &RayEvent)) continue;
}
else
{
// No, so do it on the client container.
if (!gClientContainer.castRay( ShapeStart, ShapeEnd, FXREPLICATOR_NOWATER_COLLISION_MASK, &RayEvent)) continue;
}
}
// We passed with flying colours so carry on.
CollisionResult = true;
}
// Invalidate if we are below Allowed Terrain Angle.
if (RayEvent.normal.z < mSin(mDegToRad(90.0f-mFieldData.mAllowedTerrainSlope))) CollisionResult = false;
// Wait until we get a collision.
} while(!CollisionResult && --RelocationRetry);
// Check for Relocation Problem.
if (RelocationRetry > 0)
{
// Adjust Impact point.
RayEvent.point.z += mFieldData.mOffsetZ;
// Set New Position.
ShapePosition = RayEvent.point;
}
else
{
// Warning.
Con::warnf(ConsoleLogEntry::General, "[%s] - Could not find satisfactory position for shape '%s' on %s!", getName(), mFieldData.mShapeFile,isServerObject()?"Server":"Client");
// Unregister Object.
fxStatic->unregisterObject();
// Destroy Shape.
delete fxStatic;
// Skip to next.
continue;
}
// Get Shape Transform.
MatrixF XForm = fxStatic->getTransform();
// Are we aligning to Terrain?
if (mFieldData.mAlignToTerrain)
{
// Yes, so set rotation to Terrain Impact Normal.
ShapeRotation = RayEvent.normal * mFieldData.mTerrainAlignment;
}
else
{
// No, so choose a new Rotation (in Radians).
ShapeRotation.set( mDegToRad(RandomGen.randF(mFieldData.mShapeRotateMin.x, mFieldData.mShapeRotateMax.x)),
mDegToRad(RandomGen.randF(mFieldData.mShapeRotateMin.y, mFieldData.mShapeRotateMax.y)),
mDegToRad(RandomGen.randF(mFieldData.mShapeRotateMin.z, mFieldData.mShapeRotateMax.z)));
}
// Set Quaternion Roation.
QRotation.set(ShapeRotation);
// Set Transform Rotation.
QRotation.setMatrix(&XForm);
// Set Position.
XForm.setColumn(3, ShapePosition);
// Set Shape Position / Rotation.
fxStatic->setTransform(XForm);
// Choose a new Scale.
ShapeScale.set( RandomGen.randF(mFieldData.mShapeScaleMin.x, mFieldData.mShapeScaleMax.x),
RandomGen.randF(mFieldData.mShapeScaleMin.y, mFieldData.mShapeScaleMax.y),
RandomGen.randF(mFieldData.mShapeScaleMin.z, mFieldData.mShapeScaleMax.z));
// Set Shape Scale.
fxStatic->setScale(ShapeScale);
// Lock it.
fxStatic->setLocked(true);
// Store Shape in Replicated Shapes Vector.
//mReplicatedShapes[mCurrentShapeCount++] = fxStatic;
mReplicatedShapes.push_back(fxStatic);
}
mCurrentShapeCount = mReplicatedShapes.size();
// Take first Timestamp.
mLastRenderTime = Platform::getVirtualMilliseconds();
}
//------------------------------------------------------------------------------
void fxShapeReplicator::DestroyShapes(void)
{
// Finish if we didn't create any shapes.
if (mCurrentShapeCount == 0) return;
// Remove shapes.
for (U32 idx = 0; idx < mCurrentShapeCount; idx++)
{
fxShapeReplicatedStatic* fxStatic;
// Fetch the Shape Object.
fxStatic = mReplicatedShapes[idx];
// Got a Shape?
if (fxStatic)
{
// Unlock it.
fxStatic->setLocked(false);
// Unregister the object.
fxStatic->unregisterObject();
// Delete it.
delete fxStatic;
}
}
// Empty the Replicated Shapes Vector.
mReplicatedShapes.clear();
// Reset Shape Count.
mCurrentShapeCount = 0;
}
//------------------------------------------------------------------------------
void fxShapeReplicator::RenewShapes(void)
{
// Destroy any shapes.
DestroyShapes();
// Don't create shapes on the Server if we don't need interactions.
if (isServerObject() && !mFieldData.mInteractions) return;
// Create Shapes.
CreateShapes();
}
//------------------------------------------------------------------------------
void fxShapeReplicator::StartUp(void)
{
RenewShapes();
}
//------------------------------------------------------------------------------
bool fxShapeReplicator::onAdd()
{
if(!Parent::onAdd())
return(false);
// Add the Replicator to the Replicator Set.
dynamic_cast<SimSet*>(Sim::findObject("fxReplicatorSet"))->addObject(this);
// Set Default Object Box.
mObjBox.minExtents.set( -0.5, -0.5, -0.5 );
mObjBox.maxExtents.set( 0.5, 0.5, 0.5 );
resetWorldBox();
// Add to Scene.
setRenderTransform(mObjToWorld);
addToScene();
// Register for notification when GhostAlways objects are done loading
NetConnection::smGhostAlwaysDone.notify( this, &fxShapeReplicator::onGhostAlwaysDone );
return true;
}
//------------------------------------------------------------------------------
void fxShapeReplicator::onRemove()
{
// Remove the Replicator from the Replicator Set.
dynamic_cast<SimSet*>(Sim::findObject("fxReplicatorSet"))->removeObject(this);
NetConnection::smGhostAlwaysDone.remove( this, &fxShapeReplicator::onGhostAlwaysDone );
removeFromScene();
// Destroy Shapes.
DestroyShapes();
// Do Parent.
Parent::onRemove();
}
//------------------------------------------------------------------------------
void fxShapeReplicator::onGhostAlwaysDone()
{
RenewShapes();
}
//------------------------------------------------------------------------------
void fxShapeReplicator::inspectPostApply()
{
// Set Parent.
Parent::inspectPostApply();
// Renew Shapes.
RenewShapes();
// Set Replication Mask.
setMaskBits(ReplicationMask);
}
//------------------------------------------------------------------------------
DefineEngineFunction(StartClientReplication, void, (),, "Activates the shape replicator.\n"
"@tsexample\n"
"// Call the function\n"
"StartClientReplication()\n"
"@endtsexample\n"
"@ingroup Foliage"
)
{
// Find the Replicator Set.
SimSet *fxReplicatorSet = dynamic_cast<SimSet*>(Sim::findObject("fxReplicatorSet"));
// Return if Error.
if (!fxReplicatorSet) return;
// StartUp Replication Object.
for (SimSetIterator itr(fxReplicatorSet); *itr; ++itr)
{
// Fetch the Replicator Object.
fxShapeReplicator* Replicator = static_cast<fxShapeReplicator*>(*itr);
// Start Client Objects Only.
if (Replicator->isClientObject()) Replicator->StartUp();
}
// Info ...
Con::printf("Client Replication Startup has Happened!");
}
//------------------------------------------------------------------------------
void fxShapeReplicator::prepRenderImage( SceneRenderState* state )
{
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
ri->renderDelegate.bind(this, &fxShapeReplicator::renderObject);
// The fxShapeReplicator isn't technically foliage but our debug
// effect seems to render best as a Foliage type (translucent,
// renders itself, no sorting)
ri->type = RenderPassManager::RIT_Foliage;
state->getRenderPass()->addInst( ri );
}
//------------------------------------------------------------------------------
// Renders a triangle stripped oval
void fxShapeReplicator::renderArc(const F32 fRadiusX, const F32 fRadiusY)
{
PrimBuild::begin(GFXTriangleStrip, 720);
for (U32 Angle = mCreationAreaAngle; Angle < (mCreationAreaAngle+360); Angle++)
{
F32 XPos, YPos;
// Calculate Position.
XPos = fRadiusX * mCos(mDegToRad(-(F32)Angle));
YPos = fRadiusY * mSin(mDegToRad(-(F32)Angle));
// Set Colour.
PrimBuild::color4f(mFieldData.mPlaceAreaColour.red,
mFieldData.mPlaceAreaColour.green,
mFieldData.mPlaceAreaColour.blue,
AREA_ANIMATION_ARC * (Angle-mCreationAreaAngle));
PrimBuild::vertex3f(XPos, YPos, -(F32)mFieldData.mPlacementBandHeight/2.0f);
PrimBuild::vertex3f(XPos, YPos, +(F32)mFieldData.mPlacementBandHeight/2.0f);
}
PrimBuild::end();
}
// This currently uses the primbuilder, could convert out, but why allocate the buffer if we
// never edit the misison?
void fxShapeReplicator::renderPlacementArea(const F32 ElapsedTime)
{
if (gEditingMission && mFieldData.mShowPlacementArea)
{
GFX->pushWorldMatrix();
GFX->multWorld(getTransform());
if (!mPlacementSB)
{
GFXStateBlockDesc transparent;
transparent.setCullMode(GFXCullNone);
transparent.alphaTestEnable = true;
transparent.setZReadWrite(true);
transparent.zWriteEnable = false;
transparent.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
mPlacementSB = GFX->createStateBlock( transparent );
}
GFX->setStateBlock(mPlacementSB);
// Do we need to draw the Outer Radius?
if (mFieldData.mOuterRadiusX || mFieldData.mOuterRadiusY)
renderArc((F32) mFieldData.mOuterRadiusX, (F32) mFieldData.mOuterRadiusY);
// Inner radius?
if (mFieldData.mInnerRadiusX || mFieldData.mInnerRadiusY)
renderArc((F32) mFieldData.mInnerRadiusX, (F32) mFieldData.mInnerRadiusY);
GFX->popWorldMatrix();
mCreationAreaAngle = (U32)(mCreationAreaAngle + (1000 * ElapsedTime));
mCreationAreaAngle = mCreationAreaAngle % 360;
}
}
//------------------------------------------------------------------------------
void fxShapeReplicator::renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* overrideMat)
{
if (overrideMat)
return;
// Return if placement area not needed.
if (!mFieldData.mShowPlacementArea)
return;
// Calculate Elapsed Time and take new Timestamp.
S32 Time = Platform::getVirtualMilliseconds();
F32 ElapsedTime = (Time - mLastRenderTime) * 0.001f;
mLastRenderTime = Time;
renderPlacementArea(ElapsedTime);
}
//------------------------------------------------------------------------------
U32 fxShapeReplicator::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
{
// Pack Parent.
U32 retMask = Parent::packUpdate(con, mask, stream);
// Write Replication Flag.
if (stream->writeFlag(mask & ReplicationMask))
{
stream->writeAffineTransform(mObjToWorld); // Replicator Position.
stream->writeInt(mFieldData.mSeed, 32); // Replicator Seed.
stream->writeInt(mFieldData.mShapeCount, 32); // Shapes Count.
stream->writeInt(mFieldData.mShapeRetries, 32); // Shapes Retries.
stream->writeString(mFieldData.mShapeFile);
stream->writeInt(mFieldData.mInnerRadiusX, 32); // Shapes Inner Radius X.
stream->writeInt(mFieldData.mInnerRadiusY, 32); // Shapes Inner Radius Y.
stream->writeInt(mFieldData.mOuterRadiusX, 32); // Shapes Outer Radius X.
stream->writeInt(mFieldData.mOuterRadiusY, 32); // Shapes Outer Radius Y.
mathWrite(*stream, mFieldData.mShapeScaleMin); // Shapes Scale Min.
mathWrite(*stream, mFieldData.mShapeScaleMax); // Shapes Scale Max.
mathWrite(*stream, mFieldData.mShapeRotateMin); // Shapes Rotate Min.
mathWrite(*stream, mFieldData.mShapeRotateMax); // Shapes Rotate Max.
stream->writeSignedInt(mFieldData.mOffsetZ, 32); // Shapes Offset Z.
stream->writeFlag(mFieldData.mAllowOnTerrain); // Allow on Terrain.
stream->writeFlag(mFieldData.mAllowOnInteriors); // Allow on Interiors.
stream->writeFlag(mFieldData.mAllowStatics); // Allow on Statics.
stream->writeFlag(mFieldData.mAllowOnWater); // Allow on Water.
stream->writeFlag(mFieldData.mAllowWaterSurface); // Allow on Water Surface.
stream->writeSignedInt(mFieldData.mAllowedTerrainSlope, 32); // Shapes Offset Z.
stream->writeFlag(mFieldData.mAlignToTerrain); // Shapes AlignToTerrain.
mathWrite(*stream, mFieldData.mTerrainAlignment); // Write Terrain Alignment.
stream->writeFlag(mFieldData.mHideReplications); // Hide Replications.
stream->writeFlag(mFieldData.mInteractions); // Shape Interactions.
stream->writeFlag(mFieldData.mShowPlacementArea); // Show Placement Area Flag.
stream->writeInt(mFieldData.mPlacementBandHeight, 32); // Placement Area Height.
stream->write(mFieldData.mPlaceAreaColour);
}
// Were done ...
return(retMask);
}
//------------------------------------------------------------------------------
void fxShapeReplicator::unpackUpdate(NetConnection * con, BitStream * stream)
{
// Unpack Parent.
Parent::unpackUpdate(con, stream);
// Read Replication Details.
if(stream->readFlag())
{
MatrixF ReplicatorObjectMatrix;
stream->readAffineTransform(&ReplicatorObjectMatrix); // Replication Position.
mFieldData.mSeed = stream->readInt(32); // Replicator Seed.
mFieldData.mShapeCount = stream->readInt(32); // Shapes Count.
mFieldData.mShapeRetries = stream->readInt(32); // Shapes Retries.
mFieldData.mShapeFile = stream->readSTString(); // Shape File.
mFieldData.mInnerRadiusX = stream->readInt(32); // Shapes Inner Radius X.
mFieldData.mInnerRadiusY = stream->readInt(32); // Shapes Inner Radius Y.
mFieldData.mOuterRadiusX = stream->readInt(32); // Shapes Outer Radius X.
mFieldData.mOuterRadiusY = stream->readInt(32); // Shapes Outer Radius Y.
mathRead(*stream, &mFieldData.mShapeScaleMin); // Shapes Scale Min.
mathRead(*stream, &mFieldData.mShapeScaleMax); // Shapes Scale Max.
mathRead(*stream, &mFieldData.mShapeRotateMin); // Shapes Rotate Min.
mathRead(*stream, &mFieldData.mShapeRotateMax); // Shapes Rotate Max.
mFieldData.mOffsetZ = stream->readSignedInt(32); // Shapes Offset Z.
mFieldData.mAllowOnTerrain = stream->readFlag(); // Allow on Terrain.
mFieldData.mAllowOnInteriors = stream->readFlag(); // Allow on Interiors.
mFieldData.mAllowStatics = stream->readFlag(); // Allow on Statics.
mFieldData.mAllowOnWater = stream->readFlag(); // Allow on Water.
mFieldData.mAllowWaterSurface = stream->readFlag(); // Allow on Water Surface.
mFieldData.mAllowedTerrainSlope = stream->readSignedInt(32); // Allowed Terrain Slope.
mFieldData.mAlignToTerrain = stream->readFlag(); // Read AlignToTerrain.
mathRead(*stream, &mFieldData.mTerrainAlignment); // Read Terrain Alignment.
mFieldData.mHideReplications = stream->readFlag(); // Hide Replications.
mFieldData.mInteractions = stream->readFlag(); // Read Interactions.
mFieldData.mShowPlacementArea = stream->readFlag(); // Show Placement Area Flag.
mFieldData.mPlacementBandHeight = stream->readInt(32); // Placement Area Height.
stream->read(&mFieldData.mPlaceAreaColour);
// Set Transform.
setTransform(ReplicatorObjectMatrix);
RenewShapes();
}
}

View file

@ -0,0 +1,196 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _SHAPEREPLICATOR_H_
#define _SHAPEREPLICATOR_H_
#ifndef _TSSTATIC_H_
#include "T3D/tsStatic.h"
#endif
#ifndef _TSSHAPEINSTANCE_H_
#include "ts/tsShapeInstance.h"
#endif
#ifndef _RENDERPASSMANAGER_H_
#include "renderInstance/renderPassManager.h"
#endif
#define AREA_ANIMATION_ARC (1.0f / 360.0f)
#define FXREPLICATOR_COLLISION_MASK ( TerrainObjectType | \
InteriorObjectType | \
StaticShapeObjectType | \
WaterObjectType )
#define FXREPLICATOR_NOWATER_COLLISION_MASK ( TerrainObjectType | \
InteriorObjectType | \
StaticShapeObjectType )
//------------------------------------------------------------------------------
// Class: fxShapeReplicatedStatic
//------------------------------------------------------------------------------
class fxShapeReplicatedStatic : public TSStatic
{
private:
typedef SceneObject Parent;
public:
fxShapeReplicatedStatic() {};
~fxShapeReplicatedStatic() {};
void touchNetFlags(const U32 m, bool setflag = true) { if (setflag) mNetFlags.set(m); else mNetFlags.clear(m); };
TSShape* getShape(void) { return mShapeInstance->getShape(); };
void setTransform(const MatrixF & mat) { Parent::setTransform(mat); setRenderTransform(mat); };
DECLARE_CONOBJECT(fxShapeReplicatedStatic);
};
//------------------------------------------------------------------------------
// Class: fxShapeReplicator
//------------------------------------------------------------------------------
class fxShapeReplicator : public SceneObject
{
private:
typedef SceneObject Parent;
protected:
void CreateShapes(void);
void DestroyShapes(void);
void RenewShapes(void);
enum { ReplicationMask = (1 << 0) };
U32 mCreationAreaAngle;
U32 mCurrentShapeCount;
Vector<fxShapeReplicatedStatic*> mReplicatedShapes;
MRandomLCG RandomGen;
S32 mLastRenderTime;
public:
fxShapeReplicator();
~fxShapeReplicator();
void StartUp(void);
void ShowReplication(void);
void HideReplication(void);
GFXStateBlockRef mPlacementSB;
void renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance*);
void renderArc(const F32 fRadiusX, const F32 fRadiusY);
void renderPlacementArea(const F32 ElapsedTime);
// SceneObject
virtual void prepRenderImage( SceneRenderState *state );
// SimObject
bool onAdd();
void onRemove();
void inspectPostApply();
// NetObject
U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream);
void unpackUpdate(NetConnection *conn, BitStream *stream);
// Editor
void onGhostAlwaysDone();
// ConObject.
static void initPersistFields();
// Field Data.
class tagFieldData
{
public:
U32 mSeed;
StringTableEntry mShapeFile;
U32 mShapeCount;
U32 mShapeRetries;
Point3F mShapeScaleMin;
Point3F mShapeScaleMax;
Point3F mShapeRotateMin;
Point3F mShapeRotateMax;
U32 mInnerRadiusX;
U32 mInnerRadiusY;
U32 mOuterRadiusX;
U32 mOuterRadiusY;
S32 mOffsetZ;
bool mAllowOnTerrain;
bool mAllowOnInteriors;
bool mAllowStatics;
bool mAllowOnWater;
S32 mAllowedTerrainSlope;
bool mAlignToTerrain;
bool mAllowWaterSurface;
Point3F mTerrainAlignment;
bool mInteractions;
bool mHideReplications;
bool mShowPlacementArea;
U32 mPlacementBandHeight;
ColorF mPlaceAreaColour;
tagFieldData()
{
// Set Defaults.
mSeed = 1376312589;
mShapeFile = StringTable->insert("");
mShapeCount = 10;
mShapeRetries = 100;
mInnerRadiusX = 0;
mInnerRadiusY = 0;
mOuterRadiusX = 100;
mOuterRadiusY = 100;
mOffsetZ = 0;
mAllowOnTerrain = true;
mAllowOnInteriors = true;
mAllowStatics = true;
mAllowOnWater = false;
mAllowWaterSurface = false;
mAllowedTerrainSlope= 90;
mAlignToTerrain = false;
mInteractions = true;
mHideReplications = false;
mShowPlacementArea = true;
mPlacementBandHeight = 25;
mPlaceAreaColour .set(0.4f, 0, 0.8f);
mShapeScaleMin .set(1, 1, 1);
mShapeScaleMax .set(1, 1, 1);
mShapeRotateMin .set(0, 0, 0);
mShapeRotateMax .set(0, 0, 0);
mTerrainAlignment .set(1, 1, 1);
}
} mFieldData;
// Declare Console Object.
DECLARE_CONOBJECT(fxShapeReplicator);
};
#endif // _SHAPEREPLICATOR_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,391 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GROUNDCOVER_H_
#define _GROUNDCOVER_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _MATHUTIL_FRUSTUM_H_
#include "math/util/frustum.h"
#endif
#ifndef _GFXTEXTUREHANDLE_H_
#include "gfx/gfxTextureHandle.h"
#endif
#ifndef _GFX_GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
#ifndef _RENDERPASSMANAGER_H_
#include "renderInstance/renderPassManager.h"
#endif
#ifndef _MATTEXTURETARGET_H_
#include "materials/matTextureTarget.h"
#endif
#ifndef _SHADERFEATURE_H_
#include "shaderGen/shaderFeature.h"
#endif
class TerrainBlock;
class GroundCoverCell;
class TSShapeInstance;
class Material;
class MaterialParameters;
class MaterialParameterHandle;
///
#define MAX_COVERTYPES 8
GFXDeclareVertexFormat( GCVertex )
{
Point3F point;
Point3F normal;
// .rgb = ambient
// .a = corner index
GFXVertexColor ambient;
// .x = size x
// .y = size y
// .z = type
// .w = wind amplitude
Point4F params;
};
struct GroundCoverShaderConstData
{
Point2F fadeInfo;
Point3F gustInfo;
Point2F turbInfo;
Point3F camRight;
Point3F camUp;
};
class GroundCover;
class GroundCoverShaderConstHandles : public ShaderFeatureConstHandles
{
public:
GroundCoverShaderConstHandles();
virtual void init( GFXShader *shader );
virtual void setConsts( SceneRenderState *state,
const SceneData &sgData,
GFXShaderConstBuffer *buffer );
GroundCover *mGroundCover;
GFXShaderConstHandle *mTypeRectsSC;
GFXShaderConstHandle *mFadeSC;
GFXShaderConstHandle *mWindDirSC;
GFXShaderConstHandle *mGustInfoSC;
GFXShaderConstHandle *mTurbInfoSC;
GFXShaderConstHandle *mCamRightSC;
GFXShaderConstHandle *mCamUpSC;
};
class GroundCover : public SceneObject
{
friend class GroundCoverShaderConstHandles;
friend class GroundCoverCell;
typedef SceneObject Parent;
public:
GroundCover();
~GroundCover();
DECLARE_CONOBJECT(GroundCover);
static void consoleInit();
static void initPersistFields();
bool onAdd();
void onRemove();
void inspectPostApply();
// Network
U32 packUpdate( NetConnection *, U32 mask, BitStream *stream );
void unpackUpdate( NetConnection *, BitStream *stream );
// Rendering
void prepRenderImage( SceneRenderState *state );
// Editor
void onTerrainUpdated( U32 flags, TerrainBlock *tblock, const Point2I& min, const Point2I& max );
// Misc
const GroundCoverShaderConstData& getShaderConstData() const { return mShaderConstData; }
/// Sets the global ground cover LOD scalar which controls
/// the percentage of the maximum designed cover to put down.
/// It scales both rendering cost and placement CPU performance.
/// Returns the actual value set.
static F32 setQualityScale( F32 scale ) { return smDensityScale = mClampF( scale, 0.0f, 1.0f ); }
/// Returns the current quality scale... see above.
static F32 getQualityScale() { return smDensityScale; }
protected:
enum MaskBits
{
TerrainBlockMask = Parent::NextFreeMask << 0,
NextFreeMask = Parent::NextFreeMask << 1
};
MaterialParameters *mMatParams;
MaterialParameterHandle *mTypeRectsParam;
MaterialParameterHandle *mFadeParams;
MaterialParameterHandle *mWindDirParam;
MaterialParameterHandle *mGustInfoParam;
MaterialParameterHandle *mTurbInfoParam;
MaterialParameterHandle *mCamRightParam;
MaterialParameterHandle *mCamUpParam;
/// This RNG seed is saved and sent to clients
/// for generating the same cover.
S32 mRandomSeed;
/// This is the outer generation radius from
/// the current camera position.
F32 mRadius;
// Offset along the Z axis to render the ground cover.
F32 mZOffset;
/// This is less than or equal to mRadius and
/// defines when fading of cover elements begins.
F32 mFadeRadius;
/// This is the distance at which DTS elements are
/// completely culled out.
F32 mShapeCullRadius;
/// Whether shapes rendered by the GroundCover should cast shadows.
bool mShapesCastShadows;
/// This is used to scale the various culling radii
/// when rendering a reflection... typically for water.
F32 mReflectRadiusScale;
/// This is the number of cells per axis in the grid.
U32 mGridSize;
typedef Vector<GroundCoverCell*> CellVector;
/// This is the allocator for GridCell chunks.
CellVector mAllocCellList;
CellVector mFreeCellList;
/// This is the grid of active cells.
CellVector mCellGrid;
/// This is a scratch grid used while updating
/// the cell grid.
CellVector mScratchGrid;
/// This is the index to the first grid cell.
Point2I mGridIndex;
/// The maximum amount of cover elements to include in
/// the grid at any one time. The actual amount may be
/// less than this based on randomization.
S32 mMaxPlacement;
/// Used to detect changes in cell placement count from
/// the global quality scale so we can regen the cells.
S32 mLastPlacementCount;
/// Used for culling cells to update and render.
Frustum mCuller;
/// Debug parameter for displaying the grid cells.
bool mDebugRenderCells;
/// Debug parameter for turning off billboard rendering.
bool mDebugNoBillboards;
/// Debug parameter for turning off shape rendering.
bool mDebugNoShapes;
/// Debug parameter for locking the culling frustum which
/// will freeze the cover generation.
bool mDebugLockFrustum;
/// Stat for number of rendered cells.
static U32 smStatRenderedCells;
/// Stat for number of rendered billboards.
static U32 smStatRenderedBillboards;
/// Stat for number of rendered billboard batches.
static U32 smStatRenderedBatches;
/// Stat for number of rendered shapes.
static U32 smStatRenderedShapes;
/// The global ground cover LOD scalar which controls
/// the percentage of the maximum amount of cover to put
/// down. It scales both rendering cost and placement
/// CPU performance.
static F32 smDensityScale;
String mMaterialName;
Material *mMaterial;
BaseMatInstance *mMatInst;
GroundCoverShaderConstData mShaderConstData;
/// This is the maximum amout of degrees the billboard will
/// tilt down to match the camera.
F32 mMaxBillboardTiltAngle;
/// The probability of one cover type verses another.
F32 mProbability[MAX_COVERTYPES];
/// The minimum random size for each cover type.
F32 mSizeMin[MAX_COVERTYPES];
/// The maximum random size of this cover type.
F32 mSizeMax[MAX_COVERTYPES];
/// An exponent used to bias between the minimum
/// and maximum random sizes.
F32 mSizeExponent[MAX_COVERTYPES];
/// The wind effect scale.
F32 mWindScale[MAX_COVERTYPES];
/// The maximum slope angle in degrees for placement.
F32 mMaxSlope[MAX_COVERTYPES];
/// The minimum world space elevation for placement.
F32 mMinElevation[MAX_COVERTYPES];
/// The maximum world space elevation for placement.
F32 mMaxElevation[MAX_COVERTYPES];
/// Terrain material name to limit coverage to, or
/// left empty to cover entire terrain.
StringTableEntry mLayer[MAX_COVERTYPES];
/// Inverts the data layer test making the
/// layer an exclusion mask.
bool mInvertLayer[MAX_COVERTYPES];
/// The minimum amount of elements in a clump.
S32 mMinClumpCount[MAX_COVERTYPES];
/// The maximum amount of elements in a clump.
S32 mMaxClumpCount[MAX_COVERTYPES];
/// An exponent used to bias between the minimum
/// and maximum clump counts for a particular clump.
F32 mClumpCountExponent[MAX_COVERTYPES];
/// The maximum clump radius.
F32 mClumpRadius[MAX_COVERTYPES];
/// This is a cached array of billboard aspect scales
/// used to avoid some calculations when generating cells.
F32 mBillboardAspectScales[MAX_COVERTYPES];
RectF mBillboardRects[MAX_COVERTYPES];
/// The cover shape filenames.
StringTableEntry mShapeFilenames[MAX_COVERTYPES];
/// The cover shape instances.
TSShapeInstance* mShapeInstances[MAX_COVERTYPES];
/// This is the same as mProbability, but normalized for use
/// during the cover placement process.
F32 mNormalizedProbability[MAX_COVERTYPES];
/// A shared primitive buffer setup for drawing the maximum amount
/// of billboards you could possibly have in a single cell.
GFXPrimitiveBufferHandle mPrimBuffer;
/// The length in meters between peaks in the wind gust.
F32 mWindGustLength;
/// Controls how often the wind gust peaks per second.
F32 mWindGustFrequency;
/// The maximum distance in meters that the peak wind
/// gust will displace an element.
F32 mWindGustStrength;
/// The direction of the wind.
Point2F mWindDirection;
/// Controls the overall rapidity of the wind turbulence.
F32 mWindTurbulenceFrequency;
/// The maximum distance in meters that the turbulence can
/// displace a ground cover element.
F32 mWindTurbulenceStrength;
void _initMaterial();
bool _initShader();
void _initShapes();
void _deleteShapes();
/// Called when GroundCover parameters are changed and
/// things need to be reinitialized to continue.
void _initialize( U32 cellCount, U32 cellPlacementCount );
/// Updates the cover grid by removing cells that
/// have fallen outside of mRadius and adding new
/// ones that have come into view.
void _updateCoverGrid( const Frustum &culler );
/// Clears the cell grid, moves all the allocated cells to
/// the free list, and deletes excess free cells.
void _freeCells();
/// Clears the cell grid and deletes all the free cells.
void _deleteCells();
/// Returns a cell to the free list.
void _recycleCell( GroundCoverCell* cell );
/// Generates a new cell using the recycle list when possible.
GroundCoverCell* _generateCell( const Point2I& index,
const Box3F& bounds,
U32 placementCount,
S32 randSeed );
void _debugRender( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );
};
#endif // _GROUNDCOVER_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,241 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _LIGHTNING_H_
#define _LIGHTNING_H_
#ifndef _GAMEBASE_H_
#include "T3D/gameBase/gameBase.h"
#endif
#ifndef _TORQUE_LIST_
#include "core/util/tList.h"
#endif
#ifndef _COLOR_H_
#include "core/color.h"
#endif
#ifndef _RENDERPASSMANAGER_H_
#include "renderInstance/renderPassManager.h"
#endif
#ifndef _ENGINEAPI_H_
#include "console/engineAPI.h"
#endif
#include "gfx/gfxTextureHandle.h"
class ShapeBase;
class LightningStrikeEvent;
class SFXTrack;
// -------------------------------------------------------------------------
class LightningData : public GameBaseData
{
typedef GameBaseData Parent;
public:
enum Constants {
MaxThunders = 8,
MaxTextures = 8
};
//-------------------------------------- Console set variables
public:
SFXTrack* thunderSounds[MaxThunders];
SFXTrack* strikeSound;
StringTableEntry strikeTextureNames[MaxTextures];
//-------------------------------------- load set variables
public:
GFXTexHandle strikeTextures[MaxTextures];
U32 numThunders;
protected:
bool onAdd();
public:
LightningData();
~LightningData();
void packData(BitStream*);
void unpackData(BitStream*);
bool preload(bool server, String &errorStr);
DECLARE_CONOBJECT(LightningData);
static void initPersistFields();
};
// -------------------------------------------------------------------------
struct LightningBolt
{
struct Node
{
Point3F point;
VectorF dirToMainLine;
};
struct NodeManager
{
Node nodeList[10];
Point3F startPoint;
Point3F endPoint;
U32 numNodes;
F32 maxAngle;
void generateNodes();
};
NodeManager mMajorNodes;
Vector< NodeManager > mMinorNodes;
typedef Torque::List<LightningBolt> LightingBoltList;
LightingBoltList splitList;
F32 lifetime;
F32 elapsedTime;
F32 fadeTime;
bool isFading;
F32 percentFade;
bool startRender;
F32 renderTime;
F32 width;
F32 chanceOfSplit;
Point3F startPoint;
Point3F endPoint;
U32 numMajorNodes;
F32 maxMajorAngle;
U32 numMinorNodes;
F32 maxMinorAngle;
LightningBolt();
~LightningBolt();
void createSplit( const Point3F &startPoint, const Point3F &endPoint, U32 depth, F32 width );
F32 findHeight( Point3F &point, SceneManager* sceneManager );
void render( const Point3F &camPos );
void renderSegment( NodeManager &segment, const Point3F &camPos, bool renderLastPoint );
void generate();
void generateMinorNodes();
void startSplits();
void update( F32 dt );
};
// -------------------------------------------------------------------------
class Lightning : public GameBase
{
typedef GameBase Parent;
protected:
bool onAdd();
void onRemove();
bool onNewDataBlock( GameBaseData *dptr, bool reload );
DECLARE_CALLBACK( void, applyDamage, ( const Point3F& hitPosition, const Point3F& hitNormal, SceneObject* hitObject ));
struct Strike {
F32 xVal; // Position in cloud layer of strike
F32 yVal; // top
bool targetedStrike; // Is this a targeted strike?
U32 targetGID;
F32 deathAge; // Age at which this strike expires
F32 currentAge; // Current age of this strike (updated by advanceTime)
LightningBolt bolt[3];
Strike* next;
};
struct Thunder {
F32 tRemaining;
Thunder* next;
};
public:
//-------------------------------------- Console set variables
public:
U32 strikesPerMinute;
F32 strikeWidth;
F32 chanceToHitTarget;
F32 strikeRadius;
F32 boltStartRadius;
ColorF color;
ColorF fadeColor;
bool useFog;
GFXStateBlockRef mLightningSB;
protected:
// Rendering
void prepRenderImage(SceneRenderState *state);
void renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* );
// Time management
void processTick(const Move *move);
void interpolateTick(F32 delta);
void advanceTime(F32 dt);
// Strike management
void scheduleThunder(Strike*);
// Data members
private:
LightningData* mDataBlock;
protected:
U32 mLastThink; // Valid only on server
Strike* mStrikeListHead; // Valid on on the client
Thunder* mThunderListHead;
static const U32 csmTargetMask;
public:
Lightning();
~Lightning();
void warningFlashes();
void strikeRandomPoint();
void strikeObject(ShapeBase*);
void processEvent(LightningStrikeEvent*);
DECLARE_CONOBJECT(Lightning);
static void initPersistFields();
U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream);
void unpackUpdate(NetConnection *conn, BitStream *stream);
};
#endif // _H_LIGHTNING

View file

@ -0,0 +1,633 @@
//-----------------------------------------------------------------------------
// 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 "particle.h"
#include "console/consoleTypes.h"
#include "core/stream/bitStream.h"
#include "math/mRandom.h"
#include "math/mathIO.h"
#include "console/engineAPI.h"
IMPLEMENT_CO_DATABLOCK_V1( ParticleData );
ConsoleDocClass( ParticleData,
"@brief Contains information for how specific particles should look and react "
"including particle colors, particle imagemap, acceleration value for individual "
"particles and spin information.\n"
"@tsexample\n"
"datablock ParticleData( GLWaterExpSmoke )\n"
"{\n"
" textureName = \"art/shapes/particles/smoke\";\n"
" dragCoefficient = 0.4;\n"
" gravityCoefficient = -0.25;\n"
" inheritedVelFactor = 0.025;\n"
" constantAcceleration = -1.1;\n"
" lifetimeMS = 1250;\n"
" lifetimeVarianceMS = 0;\n"
" useInvAlpha = false;\n"
" spinSpeed = 1;\n"
" spinRandomMin = -200.0;\n"
" spinRandomMax = 200.0;\n\n"
" colors[0] = \"0.1 0.1 1.0 1.0\";\n"
" colors[1] = \"0.4 0.4 1.0 1.0\";\n"
" colors[2] = \"0.4 0.4 1.0 0.0\";\n\n"
" sizes[0] = 2.0;\n"
" sizes[1] = 6.0;\n"
" sizes[2] = 2.0;\n\n"
" times[0] = 0.0;\n"
" times[1] = 0.5;\n"
" times[2] = 1.0;\n"
"};\n"
"@endtsexample\n"
"@ingroup FX\n"
"@see ParticleEmitter\n"
"@see ParticleEmitterData\n"
"@see ParticleEmitterNode\n"
);
static const float sgDefaultWindCoefficient = 0.0f;
static const float sgDefaultConstantAcceleration = 0.f;
static const float sgDefaultSpinSpeed = 1.f;
static const float sgDefaultSpinRandomMin = 0.f;
static const float sgDefaultSpinRandomMax = 0.f;
//-----------------------------------------------------------------------------
// Constructor
//-----------------------------------------------------------------------------
ParticleData::ParticleData()
{
dragCoefficient = 0.0f;
windCoefficient = sgDefaultWindCoefficient;
gravityCoefficient = 0.0f;
inheritedVelFactor = 0.0f;
constantAcceleration = sgDefaultConstantAcceleration;
lifetimeMS = 1000;
lifetimeVarianceMS = 0;
spinSpeed = sgDefaultSpinSpeed;
spinRandomMin = sgDefaultSpinRandomMin;
spinRandomMax = sgDefaultSpinRandomMax;
useInvAlpha = false;
animateTexture = false;
numFrames = 1;
framesPerSec = numFrames;
S32 i;
for( i=0; i<PDC_NUM_KEYS; i++ )
{
colors[i].set( 1.0, 1.0, 1.0, 1.0 );
sizes[i] = 1.0;
}
times[0] = 0.0f;
times[1] = 0.33f;
times[2] = 0.66f;
times[3] = 1.0f;
texCoords[0].set(0.0,0.0); // texture coords at 4 corners
texCoords[1].set(0.0,1.0); // of particle quad
texCoords[2].set(1.0,1.0); // (defaults to entire particle)
texCoords[3].set(1.0,0.0);
animTexTiling.set(0,0); // tiling dimensions
animTexFramesString = NULL; // string of animation frame indices
animTexUVs = NULL; // array of tile vertex UVs
textureName = NULL; // texture filename
textureHandle = NULL; // loaded texture handle
}
//-----------------------------------------------------------------------------
// Destructor
//-----------------------------------------------------------------------------
ParticleData::~ParticleData()
{
if (animTexUVs)
{
delete [] animTexUVs;
}
}
//-----------------------------------------------------------------------------
// initPersistFields
//-----------------------------------------------------------------------------
void ParticleData::initPersistFields()
{
addField( "dragCoefficient", TYPEID< F32 >(), Offset(dragCoefficient, ParticleData),
"Particle physics drag amount." );
addField( "windCoefficient", TYPEID< F32 >(), Offset(windCoefficient, ParticleData),
"Strength of wind on the particles." );
addField( "gravityCoefficient", TYPEID< F32 >(), Offset(gravityCoefficient, ParticleData),
"Strength of gravity on the particles." );
addField( "inheritedVelFactor", TYPEID< F32 >(), Offset(inheritedVelFactor, ParticleData),
"Amount of emitter velocity to add to particle initial velocity." );
addField( "constantAcceleration", TYPEID< F32 >(), Offset(constantAcceleration, ParticleData),
"Constant acceleration to apply to this particle." );
addField( "lifetimeMS", TYPEID< S32 >(), Offset(lifetimeMS, ParticleData),
"Time in milliseconds before this particle is destroyed." );
addField( "lifetimeVarianceMS", TYPEID< S32 >(), Offset(lifetimeVarianceMS, ParticleData),
"Variance in lifetime of particle, from 0 - lifetimeMS." );
addField( "spinSpeed", TYPEID< F32 >(), Offset(spinSpeed, ParticleData),
"Speed at which to spin the particle." );
addField( "spinRandomMin", TYPEID< F32 >(), Offset(spinRandomMin, ParticleData),
"Minimum allowed spin speed of this particle, between -10000 and spinRandomMax." );
addField( "spinRandomMax", TYPEID< F32 >(), Offset(spinRandomMax, ParticleData),
"Maximum allowed spin speed of this particle, between spinRandomMin and 10000." );
addField( "useInvAlpha", TYPEID< bool >(), Offset(useInvAlpha, ParticleData),
"@brief Controls how particles blend with the scene.\n\n"
"If true, particles blend like ParticleBlendStyle NORMAL, if false, "
"blend like ParticleBlendStyle ADDITIVE.\n"
"@note If ParticleEmitterData::blendStyle is set, it will override this value." );
addField( "animateTexture", TYPEID< bool >(), Offset(animateTexture, ParticleData),
"If true, allow the particle texture to be an animated sprite." );
addField( "framesPerSec", TYPEID< S32 >(), Offset(framesPerSec, ParticleData),
"If animateTexture is true, this defines the frames per second of the "
"sprite animation." );
addField( "textureCoords", TYPEID< Point2F >(), Offset(texCoords, ParticleData), 4,
"@brief 4 element array defining the UV coords into textureName to use "
"for this particle.\n\n"
"Coords should be set for the first tile only when using animTexTiling; "
"coordinates for other tiles will be calculated automatically. \"0 0\" is "
"top left and \"1 1\" is bottom right." );
addField( "animTexTiling", TYPEID< Point2I >(), Offset(animTexTiling, ParticleData),
"@brief The number of frames, in rows and columns stored in textureName "
"(when animateTexture is true).\n\n"
"A maximum of 256 frames can be stored in a single texture when using "
"animTexTiling. Value should be \"NumColumns NumRows\", for example \"4 4\"." );
addField( "animTexFrames", TYPEID< StringTableEntry >(), Offset(animTexFramesString,ParticleData),
"@brief A list of frames and/or frame ranges to use for particle "
"animation if animateTexture is true.\n\n"
"Each frame token must be separated by whitespace. A frame token must be "
"a positive integer frame number or a range of frame numbers separated "
"with a '-'. The range separator, '-', cannot have any whitspace around "
"it.\n\n"
"Ranges can be specified to move through the frames in reverse as well "
"as forward (eg. 19-14). Frame numbers exceeding the number of tiles will "
"wrap.\n"
"@tsexample\n"
"animTexFrames = \"0-16 20 19 18 17 31-21\";\n"
"@endtsexample\n" );
addField( "textureName", TYPEID< StringTableEntry >(), Offset(textureName, ParticleData),
"Texture file to use for this particle." );
addField( "animTexName", TYPEID< StringTableEntry >(), Offset(textureName, ParticleData),
"@brief Texture file to use for this particle if animateTexture is true.\n\n"
"Deprecated. Use textureName instead." );
// Interpolation variables
addField( "colors", TYPEID< ColorF >(), Offset(colors, ParticleData), PDC_NUM_KEYS,
"@brief Particle RGBA color keyframe values.\n\n"
"The particle color will linearly interpolate between the color/time keys "
"over the lifetime of the particle." );
addField( "sizes", TYPEID< F32 >(), Offset(sizes, ParticleData), PDC_NUM_KEYS,
"@brief Particle size keyframe values.\n\n"
"The particle size will linearly interpolate between the size/time keys "
"over the lifetime of the particle." );
addProtectedField( "times", TYPEID< F32 >(), Offset(times, ParticleData), &protectedSetTimes,
&defaultProtectedGetFn, PDC_NUM_KEYS,
"@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)." );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
// Pack data
//-----------------------------------------------------------------------------
void ParticleData::packData(BitStream* stream)
{
Parent::packData(stream);
stream->writeFloat(dragCoefficient / 5, 10);
if( stream->writeFlag(windCoefficient != sgDefaultWindCoefficient ) )
stream->write(windCoefficient);
if (stream->writeFlag(gravityCoefficient != 0.0f))
stream->writeSignedFloat(gravityCoefficient / 10, 12);
stream->writeFloat(inheritedVelFactor, 9);
if( stream->writeFlag( constantAcceleration != sgDefaultConstantAcceleration ) )
stream->write(constantAcceleration);
stream->write( lifetimeMS );
stream->write( lifetimeVarianceMS );
if( stream->writeFlag( spinSpeed != sgDefaultSpinSpeed ) )
stream->write(spinSpeed);
if(stream->writeFlag(spinRandomMin != sgDefaultSpinRandomMin || spinRandomMax != sgDefaultSpinRandomMax))
{
stream->writeInt((S32)(spinRandomMin + 1000), 11);
stream->writeInt((S32)(spinRandomMax + 1000), 11);
}
stream->writeFlag(useInvAlpha);
S32 i, count;
// see how many frames there are:
for(count = 0; count < 3; count++)
if(times[count] >= 1)
break;
count++;
stream->writeInt(count-1, 2);
for( i=0; i<count; i++ )
{
stream->writeFloat( colors[i].red, 7);
stream->writeFloat( colors[i].green, 7);
stream->writeFloat( colors[i].blue, 7);
stream->writeFloat( colors[i].alpha, 7);
stream->writeFloat( sizes[i]/MaxParticleSize, 14);
stream->writeFloat( times[i], 8);
}
if (stream->writeFlag(textureName && textureName[0]))
stream->writeString(textureName);
for (i = 0; i < 4; i++)
mathWrite(*stream, texCoords[i]);
if (stream->writeFlag(animateTexture))
{
if (stream->writeFlag(animTexFramesString && animTexFramesString[0]))
{
stream->writeString(animTexFramesString);
}
mathWrite(*stream, animTexTiling);
stream->writeInt(framesPerSec, 8);
}
}
//-----------------------------------------------------------------------------
// Unpack data
//-----------------------------------------------------------------------------
void ParticleData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
dragCoefficient = stream->readFloat(10) * 5;
if(stream->readFlag())
stream->read(&windCoefficient);
else
windCoefficient = sgDefaultWindCoefficient;
if (stream->readFlag())
gravityCoefficient = stream->readSignedFloat(12)*10;
else
gravityCoefficient = 0.0f;
inheritedVelFactor = stream->readFloat(9);
if(stream->readFlag())
stream->read(&constantAcceleration);
else
constantAcceleration = sgDefaultConstantAcceleration;
stream->read( &lifetimeMS );
stream->read( &lifetimeVarianceMS );
if(stream->readFlag())
stream->read(&spinSpeed);
else
spinSpeed = sgDefaultSpinSpeed;
if(stream->readFlag())
{
spinRandomMin = (F32)(stream->readInt(11) - 1000);
spinRandomMax = (F32)(stream->readInt(11) - 1000);
}
else
{
spinRandomMin = sgDefaultSpinRandomMin;
spinRandomMax = sgDefaultSpinRandomMax;
}
useInvAlpha = stream->readFlag();
S32 i;
S32 count = stream->readInt(2) + 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;
times[i] = stream->readFloat(8);
}
textureName = (stream->readFlag()) ? stream->readSTString() : 0;
for (i = 0; i < 4; i++)
mathRead(*stream, &texCoords[i]);
animateTexture = stream->readFlag();
if (animateTexture)
{
animTexFramesString = (stream->readFlag()) ? stream->readSTString() : 0;
mathRead(*stream, &animTexTiling);
framesPerSec = stream->readInt(8);
}
}
bool ParticleData::protectedSetTimes( void *object, const char *index, const char *data)
{
ParticleData *pData = static_cast<ParticleData*>( object );
F32 val = dAtof(data);
U32 i;
if (!index)
i = 0;
else
i = dAtoui(index);
pData->times[i] = mClampF( val, 0.f, 1.f );
return true;
}
//-----------------------------------------------------------------------------
// onAdd
//-----------------------------------------------------------------------------
bool ParticleData::onAdd()
{
if (Parent::onAdd() == false)
return false;
if (dragCoefficient < 0.0) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) drag coeff less than 0", getName());
dragCoefficient = 0.0f;
}
if (lifetimeMS < 1) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) lifetime < 1 ms", getName());
lifetimeMS = 1;
}
if (lifetimeVarianceMS >= lifetimeMS) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) lifetimeVariance >= lifetime", getName());
lifetimeVarianceMS = lifetimeMS - 1;
}
if (spinSpeed > 10000.0 || spinSpeed < -10000.0) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinSpeed invalid", getName());
return false;
}
if (spinRandomMin > 10000.0 || spinRandomMin < -10000.0) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMin invalid", getName());
spinRandomMin = -360.0;
return false;
}
if (spinRandomMin > spinRandomMax) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMin greater than spinRandomMax", getName());
spinRandomMin = spinRandomMax - (spinRandomMin - spinRandomMax );
return false;
}
if (spinRandomMax > 10000.0 || spinRandomMax < -10000.0) {
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) spinRandomMax invalid", getName());
spinRandomMax = 360.0;
return false;
}
if (framesPerSec > 255)
{
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) framesPerSec > 255, too high", getName());
framesPerSec = 255;
return false;
}
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];
}
}
// Here we validate parameters
if (animateTexture)
{
// Tiling dimensions must be positive and non-zero
if (animTexTiling.x <= 0 || animTexTiling.y <= 0)
{
Con::warnf(ConsoleLogEntry::General,
"ParticleData(%s) bad value(s) for animTexTiling [%d or %d <= 0], invalid datablock",
animTexTiling.x, animTexTiling.y, getName());
return false;
}
// Indices must fit into a byte so these are also bad
if (animTexTiling.x * animTexTiling.y > 256)
{
Con::warnf(ConsoleLogEntry::General,
"ParticleData(%s) bad values for animTexTiling [%d*%d > %d], invalid datablock",
animTexTiling.x, animTexTiling.y, 256, getName());
return false;
}
// A list of frames is required
if (!animTexFramesString || !animTexFramesString[0])
{
Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) no animTexFrames, invalid datablock", getName());
return false;
}
// The frame list cannot be too long.
if (animTexFramesString && dStrlen(animTexFramesString) > 255)
{
Con::errorf(ConsoleLogEntry::General, "ParticleData(%s) animTexFrames string too long [> 255 chars]", getName());
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
// preload
//-----------------------------------------------------------------------------
bool ParticleData::preload(bool server, String &errorStr)
{
if (Parent::preload(server, errorStr) == false)
return false;
bool error = false;
if(!server)
{
// Here we attempt to load the particle's texture if specified. An undefined
// texture is *not* an error since the emitter may provide one.
if (textureName && textureName[0])
{
textureHandle = GFXTexHandle(textureName, &GFXDefaultStaticDiffuseProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__));
if (!textureHandle)
{
errorStr = String::ToString("Missing particle texture: %s", textureName);
error = true;
}
}
if (animateTexture)
{
// Here we parse animTexFramesString into byte-size frame numbers in animTexFrames.
// Each frame token must be separated by whitespace.
// A frame token must be a positive integer frame number or a range of frame numbers
// separated with a '-'.
// The range separator, '-', cannot have any whitspace around it.
// Ranges can be specified to move through the frames in reverse as well as forward.
// Frame numbers exceeding the number of tiles will wrap.
// example:
// "0-16 20 19 18 17 31-21"
S32 n_tiles = animTexTiling.x * animTexTiling.y;
AssertFatal(n_tiles > 0 && n_tiles <= 256, "Error, bad animTexTiling setting." );
animTexFrames.clear();
char* tokCopy = new char[dStrlen(animTexFramesString) + 1];
dStrcpy(tokCopy, animTexFramesString);
char* currTok = dStrtok(tokCopy, " \t");
while (currTok != NULL)
{
char* minus = dStrchr(currTok, '-');
if (minus)
{
// add a range of frames
*minus = '\0';
S32 range_a = dAtoi(currTok);
S32 range_b = dAtoi(minus+1);
if (range_b < range_a)
{
// reverse frame range
for (S32 i = range_a; i >= range_b; i--)
animTexFrames.push_back((U8)(i % n_tiles));
}
else
{
// forward frame range
for (S32 i = range_a; i <= range_b; i++)
animTexFrames.push_back((U8)(i % n_tiles));
}
}
else
{
// add one frame
animTexFrames.push_back((U8)(dAtoi(currTok) % n_tiles));
}
currTok = dStrtok(NULL, " \t");
}
// Here we pre-calculate the UVs for each frame tile, which are
// tiled inside the UV region specified by texCoords. Since the
// UVs are calculated using bilinear interpolation, the texCoords
// region does *not* have to be an axis-aligned rectangle.
if (animTexUVs)
delete [] animTexUVs;
animTexUVs = new Point2F[(animTexTiling.x+1)*(animTexTiling.y+1)];
// interpolate points on the left and right edge of the uv quadrangle
Point2F lf_pt = texCoords[0];
Point2F rt_pt = texCoords[3];
// per-row delta for left and right interpolated points
Point2F lf_d = (texCoords[1] - texCoords[0])/(F32)animTexTiling.y;
Point2F rt_d = (texCoords[2] - texCoords[3])/(F32)animTexTiling.y;
S32 idx = 0;
for (S32 yy = 0; yy <= animTexTiling.y; yy++)
{
Point2F p = lf_pt;
Point2F dp = (rt_pt - lf_pt)/(F32)animTexTiling.x;
for (S32 xx = 0; xx <= animTexTiling.x; xx++)
{
animTexUVs[idx++] = p;
p += dp;
}
lf_pt += lf_d;
rt_pt += rt_d;
}
// cleanup
delete [] tokCopy;
numFrames = animTexFrames.size();
}
}
return !error;
}
//-----------------------------------------------------------------------------
// Initialize particle
//-----------------------------------------------------------------------------
void ParticleData::initializeParticle(Particle* init, const Point3F& inheritVelocity)
{
init->dataBlock = this;
// Calculate the constant accleration...
init->vel += inheritVelocity * inheritedVelFactor;
init->acc = init->vel * constantAcceleration;
// Calculate this instance's lifetime...
init->totalLifetime = lifetimeMS;
if (lifetimeVarianceMS != 0)
init->totalLifetime += S32(gRandGen.randI() % (2 * lifetimeVarianceMS + 1)) - S32(lifetimeVarianceMS);
// assign spin amount
init->spinSpeed = spinSpeed * gRandGen.randF( spinRandomMin, spinRandomMax );
}
bool ParticleData::reload(char errorBuffer[256])
{
bool error = false;
if (textureName && textureName[0])
{
textureHandle = GFXTexHandle(textureName, &GFXDefaultStaticDiffuseProfile, avar("%s() - textureHandle (line %d)", __FUNCTION__, __LINE__));
if (!textureHandle)
{
dSprintf(errorBuffer, 256, "Missing particle texture: %s", textureName);
error = true;
}
}
/*
numFrames = 0;
for( int i=0; i<PDC_MAX_TEX; i++ )
{
if( textureNameList[i] && textureNameList[i][0] )
{
textureList[i] = TextureHandle( textureNameList[i], MeshTexture );
if (!textureList[i].getName())
{
dSprintf(errorBuffer, 256, "Missing particle texture: %s", textureNameList[i]);
error = true;
}
numFrames++;
}
}
*/
return !error;
}
DefineEngineMethod(ParticleData, reload, void, (),,
"Reloads this particle.\n"
"@tsexample\n"
"// Get the editor's current particle\n"
"%particle = PE_ParticleEditor.currParticle\n\n"
"// Change a particle value\n"
"%particle.setFieldValue( %propertyField, %value );\n\n"
"// Reload it\n"
"%particle.reload();\n"
"@endtsexample\n" )
{
char errorBuffer[256];
object->reload(errorBuffer);
}

View file

@ -0,0 +1,128 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _PARTICLE_H_
#define _PARTICLE_H_
#ifndef _GAMEBASE_H_
#include "T3D/gameBase/gameBase.h"
#endif
#ifndef _GFXTEXTUREHANDLE_H_
#include "gfx/gfxTextureHandle.h"
#endif
#define MaxParticleSize 50.0
struct Particle;
//*****************************************************************************
// Particle Data
//*****************************************************************************
class ParticleData : public SimDataBlock
{
typedef SimDataBlock Parent;
public:
enum PDConst
{
PDC_NUM_KEYS = 4,
};
F32 dragCoefficient;
F32 windCoefficient;
F32 gravityCoefficient;
F32 inheritedVelFactor;
F32 constantAcceleration;
S32 lifetimeMS;
S32 lifetimeVarianceMS;
F32 spinSpeed; // degrees per second
F32 spinRandomMin;
F32 spinRandomMax;
bool useInvAlpha;
bool animateTexture;
U32 numFrames;
U32 framesPerSec;
ColorF colors[ PDC_NUM_KEYS ];
F32 sizes[ PDC_NUM_KEYS ];
F32 times[ PDC_NUM_KEYS ];
Point2F* animTexUVs;
Point2F texCoords[4]; // default: {{0.0,0.0}, {0.0,1.0}, {1.0,1.0}, {1.0,0.0}}
Point2I animTexTiling;
StringTableEntry animTexFramesString;
Vector<U8> animTexFrames;
StringTableEntry textureName;
GFXTexHandle textureHandle;
static bool protectedSetTimes( void *object, const char *index, const char *data );
public:
ParticleData();
~ParticleData();
// move this procedure to Particle
void initializeParticle(Particle*, const Point3F&);
void packData(BitStream* stream);
void unpackData(BitStream* stream);
bool onAdd();
bool preload(bool server, String &errorStr);
DECLARE_CONOBJECT(ParticleData);
static void initPersistFields();
bool reload(char errorBuffer[256]);
};
//*****************************************************************************
// Particle
//
// This structure should be as small as possible.
//*****************************************************************************
struct Particle
{
Point3F pos; // current instantaneous position
Point3F vel; // " " velocity
Point3F acc; // Constant acceleration
Point3F orientDir; // direction particle should go if using oriented particles
U32 totalLifetime; // Total ms that this instance should be "live"
ParticleData* dataBlock; // datablock that contains global parameters for
// this instance
U32 currentAge;
// are these necessary to store here? - they are interpolated in real time
ColorF color;
F32 size;
F32 spinSpeed;
Particle * next;
};
#endif // _PARTICLE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,287 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _H_PARTICLE_EMITTER
#define _H_PARTICLE_EMITTER
#ifndef _GAMEBASE_H_
#include "T3D/gameBase/gameBase.h"
#endif
#ifndef _COLOR_H_
#include "core/color.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
#ifndef _GFXVERTEXBUFFER_H_
#include "gfx/gfxVertexBuffer.h"
#endif
#ifndef _PARTICLE_H_
#include "T3D/fx/particle.h"
#endif
#if defined(TORQUE_OS_XENON)
#include "gfx/D3D9/360/gfx360MemVertexBuffer.h"
#endif
class RenderPassManager;
class ParticleData;
//*****************************************************************************
// Particle Emitter Data
//*****************************************************************************
class ParticleEmitterData : public GameBaseData
{
typedef GameBaseData Parent;
static bool _setAlignDirection( void *object, const char *index, const char *data );
public:
ParticleEmitterData();
DECLARE_CONOBJECT(ParticleEmitterData);
static void initPersistFields();
void packData(BitStream* stream);
void unpackData(BitStream* stream);
bool preload(bool server, String &errorStr);
bool onAdd();
void allocPrimBuffer( S32 overrideSize = -1 );
public:
S32 ejectionPeriodMS; ///< Time, in Milliseconds, between particle ejection
S32 periodVarianceMS; ///< Varience in ejection peroid between 0 and n
F32 ejectionVelocity; ///< Ejection velocity
F32 velocityVariance; ///< Variance for velocity between 0 and n
F32 ejectionOffset; ///< Z offset from emitter point to eject from
F32 thetaMin; ///< Minimum angle, from the horizontal plane, to eject from
F32 thetaMax; ///< Maximum angle, from the horizontal plane, to eject from
F32 phiReferenceVel; ///< Reference angle, from the verticle plane, to eject from
F32 phiVariance; ///< Varience from the reference angle, from 0 to n
F32 softnessDistance; ///< For soft particles, the distance (in meters) where particles will be faded
///< based on the difference in depth between the particle and the scene geometry.
/// A scalar value used to influence the effect
/// of the ambient color on the particle.
F32 ambientFactor;
U32 lifetimeMS; ///< Lifetime of particles
U32 lifetimeVarianceMS; ///< Varience in lifetime from 0 to n
bool overrideAdvance; ///<
bool orientParticles; ///< Particles always face the screen
bool orientOnVelocity; ///< Particles face the screen at the start
bool useEmitterSizes; ///< Use emitter specified sizes instead of datablock sizes
bool useEmitterColors; ///< Use emitter specified colors instead of datablock colors
bool alignParticles; ///< Particles always face along a particular axis
Point3F alignDirection; ///< The direction aligned particles should face
StringTableEntry particleString; ///< Used to load particle data directly from a string
Vector<ParticleData*> particleDataBlocks; ///< Particle Datablocks
Vector<U32> dataBlockIds; ///< Datablock IDs (parellel array to particleDataBlocks)
U32 partListInitSize; /// initial size of particle list calc'd from datablock info
GFXPrimitiveBufferHandle primBuff;
S32 blendStyle; ///< Pre-define blend factor setting
bool sortParticles; ///< Particles are sorted back-to-front
bool reverseOrder; ///< reverses draw order
StringTableEntry textureName; ///< Emitter texture file to override particle textures
GFXTexHandle textureHandle; ///< Emitter texture handle from txrName
bool highResOnly; ///< This particle system should not use the mixed-resolution particle rendering
bool renderReflection; ///< Enables this emitter to render into reflection passes.
bool reload();
};
//*****************************************************************************
// Particle Emitter
//*****************************************************************************
class ParticleEmitter : public GameBase
{
typedef GameBase Parent;
public:
#if defined(TORQUE_OS_XENON)
typedef GFXVertexPCTT ParticleVertexType;
#else
typedef GFXVertexPCT ParticleVertexType;
#endif
ParticleEmitter();
~ParticleEmitter();
DECLARE_CONOBJECT(ParticleEmitter);
static Point3F mWindVelocity;
static void setWindVelocity( const Point3F &vel ){ mWindVelocity = vel; }
ColorF getCollectiveColor();
/// Sets sizes of particles based on sizelist provided
/// @param sizeList List of sizes
void setSizes( F32 *sizeList );
/// Sets colors for particles based on color list provided
/// @param colorList List of colors
void setColors( ColorF *colorList );
ParticleEmitterData *getDataBlock(){ return mDataBlock; }
bool onNewDataBlock( GameBaseData *dptr, bool reload );
/// By default, a particle renderer will wait for it's owner to delete it. When this
/// is turned on, it will delete itself as soon as it's particle count drops to zero.
void deleteWhenEmpty();
/// @name Particle Emission
/// Main interface for creating particles. The emitter does _not_ track changes
/// in axis or velocity over the course of a single update, so this should be called
/// at a fairly fine grain. The emitter will potentially track the last particle
/// to be created into the next call to this function in order to create a uniformly
/// random time distribution of the particles. If the object to which the emitter is
/// attached is in motion, it should try to ensure that for call (n+1) to this
/// function, start is equal to the end from call (n). This will ensure a uniform
/// spatial distribution.
/// @{
void emitParticles(const Point3F& start,
const Point3F& end,
const Point3F& axis,
const Point3F& velocity,
const U32 numMilliseconds);
void emitParticles(const Point3F& point,
const bool useLastPosition,
const Point3F& axis,
const Point3F& velocity,
const U32 numMilliseconds);
void emitParticles(const Point3F& rCenter,
const Point3F& rNormal,
const F32 radius,
const Point3F& velocity,
S32 count);
/// @}
bool mDead;
protected:
/// @name Internal interface
/// @{
/// Adds a particle
/// @param pos Initial position of particle
/// @param axis
/// @param vel Initial velocity
/// @param axisx
void addParticle(const Point3F &pos, const Point3F &axis, const Point3F &vel, const Point3F &axisx);
inline void setupBillboard( Particle *part,
Point3F *basePts,
const MatrixF &camView,
const ColorF &ambientColor,
ParticleVertexType *lVerts );
inline void setupOriented( Particle *part,
const Point3F &camPos,
const ColorF &ambientColor,
ParticleVertexType *lVerts );
inline void setupAligned( const Particle *part,
const ColorF &ambientColor,
ParticleVertexType *lVerts );
/// Updates the bounding box for the particle system
void updateBBox();
/// @}
protected:
bool onAdd();
void onRemove();
void processTick(const Move *move);
void advanceTime(F32 dt);
// Rendering
protected:
void prepRenderImage( SceneRenderState *state );
void copyToVB( const Point3F &camPos, const ColorF &ambientColor );
// PEngine interface
private:
void update( U32 ms );
inline void updateKeyData( Particle *part );
private:
/// Constant used to calculate particle
/// rotation from spin and age.
static const F32 AgedSpinToRadians;
ParticleEmitterData* mDataBlock;
U32 mInternalClock;
U32 mNextParticleTime;
Point3F mLastPosition;
bool mHasLastPosition;
MatrixF mBBObjToWorld;
bool mDeleteWhenEmpty;
bool mDeleteOnTick;
S32 mLifetimeMS;
S32 mElapsedTimeMS;
F32 sizes[ ParticleData::PDC_NUM_KEYS ];
ColorF colors[ ParticleData::PDC_NUM_KEYS ];
#if defined(TORQUE_OS_XENON)
GFX360MemVertexBufferHandle<ParticleVertexType> mVertBuff;
#else
GFXVertexBufferHandle<ParticleVertexType> mVertBuff;
#endif
// 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
// enough to contain all the particles but it can be expanded in emergency
// circumstances.
Vector <Particle*> part_store;
Particle* part_freelist;
Particle part_list_head;
S32 n_part_capacity;
S32 n_parts;
S32 mCurBuffSize;
};
#endif // _H_PARTICLE_EMITTER

View file

@ -0,0 +1,419 @@
//-----------------------------------------------------------------------------
// 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 "particleEmitterNode.h"
#include "console/consoleTypes.h"
#include "core/stream/bitStream.h"
#include "T3D/fx/particleEmitter.h"
#include "math/mathIO.h"
#include "sim/netConnection.h"
#include "console/engineAPI.h"
IMPLEMENT_CO_DATABLOCK_V1(ParticleEmitterNodeData);
IMPLEMENT_CO_NETOBJECT_V1(ParticleEmitterNode);
ConsoleDocClass( ParticleEmitterNodeData,
"@brief Contains additional data to be associated with a ParticleEmitterNode."
"@ingroup FX\n"
);
ConsoleDocClass( ParticleEmitterNode,
"@brief A particle emitter object that can be positioned in the world and "
"dynamically enabled or disabled.\n\n"
"@tsexample\n"
"datablock ParticleEmitterNodeData( SimpleEmitterNodeData )\n"
"{\n"
" timeMultiple = 1.0;\n"
"};\n\n"
"%emitter = new ParticleEmitterNode()\n"
"{\n"
" datablock = SimpleEmitterNodeData;\n"
" active = true;\n"
" emitter = FireEmitterData;\n"
" velocity = 3.5;\n"
"};\n\n"
"// Dynamically change emitter datablock\n"
"%emitter.setEmitterDataBlock( DustEmitterData );\n"
"@endtsexample\n"
"@note To change the emitter field dynamically (after the ParticleEmitterNode "
"object has been created) you must use the setEmitterDataBlock() method or the "
"change will not be replicated to other clients in the game.\n"
"Similarly, use the setActive() method instead of changing the active field "
"directly. When changing velocity, you need to toggle setActive() on and off "
"to force the state change to be transmitted to other clients.\n\n"
"@ingroup FX\n"
"@see ParticleEmitterNodeData\n"
"@see ParticleEmitterData\n"
);
//-----------------------------------------------------------------------------
// ParticleEmitterNodeData
//-----------------------------------------------------------------------------
ParticleEmitterNodeData::ParticleEmitterNodeData()
{
timeMultiple = 1.0;
}
ParticleEmitterNodeData::~ParticleEmitterNodeData()
{
}
//-----------------------------------------------------------------------------
// initPersistFields
//-----------------------------------------------------------------------------
void ParticleEmitterNodeData::initPersistFields()
{
addField( "timeMultiple", TYPEID< F32 >(), Offset(timeMultiple, ParticleEmitterNodeData),
"@brief Time multiplier for particle emitter nodes.\n\n"
"Increasing timeMultiple is like running the emitter at a faster rate - single-shot "
"emitters will complete in a shorter time, and continuous emitters will generate "
"particles more quickly.\n\n"
"Valid range is 0.01 - 100." );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
// onAdd
//-----------------------------------------------------------------------------
bool ParticleEmitterNodeData::onAdd()
{
if( !Parent::onAdd() )
return false;
if( timeMultiple < 0.01 || timeMultiple > 100 )
{
Con::warnf("ParticleEmitterNodeData::onAdd(%s): timeMultiple must be between 0.01 and 100", getName());
timeMultiple = timeMultiple < 0.01 ? 0.01 : 100;
}
return true;
}
//-----------------------------------------------------------------------------
// preload
//-----------------------------------------------------------------------------
bool ParticleEmitterNodeData::preload(bool server, String &errorStr)
{
if( Parent::preload(server, errorStr) == false )
return false;
return true;
}
//-----------------------------------------------------------------------------
// packData
//-----------------------------------------------------------------------------
void ParticleEmitterNodeData::packData(BitStream* stream)
{
Parent::packData(stream);
stream->write(timeMultiple);
}
//-----------------------------------------------------------------------------
// unpackData
//-----------------------------------------------------------------------------
void ParticleEmitterNodeData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
stream->read(&timeMultiple);
}
//-----------------------------------------------------------------------------
// ParticleEmitterNode
//-----------------------------------------------------------------------------
ParticleEmitterNode::ParticleEmitterNode()
{
// Todo: ScopeAlways?
mNetFlags.set(Ghostable);
mTypeMask |= EnvironmentObjectType;
mActive = true;
mDataBlock = NULL;
mEmitterDatablock = NULL;
mEmitterDatablockId = 0;
mEmitter = NULL;
mVelocity = 1.0;
}
//-----------------------------------------------------------------------------
// Destructor
//-----------------------------------------------------------------------------
ParticleEmitterNode::~ParticleEmitterNode()
{
//
}
//-----------------------------------------------------------------------------
// initPersistFields
//-----------------------------------------------------------------------------
void ParticleEmitterNode::initPersistFields()
{
addField( "active", TYPEID< bool >(), Offset(mActive,ParticleEmitterNode),
"Controls whether particles are emitted from this node." );
addField( "emitter", TYPEID< ParticleEmitterData >(), Offset(mEmitterDatablock, ParticleEmitterNode),
"Datablock to use when emitting particles." );
addField( "velocity", TYPEID< F32 >(), Offset(mVelocity, ParticleEmitterNode),
"Velocity to use when emitting particles (in the direction of the "
"ParticleEmitterNode object's up (Z) axis)." );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
// onAdd
//-----------------------------------------------------------------------------
bool ParticleEmitterNode::onAdd()
{
if( !Parent::onAdd() )
return false;
if( !mEmitterDatablock && mEmitterDatablockId != 0 )
{
if( Sim::findObject(mEmitterDatablockId, mEmitterDatablock) == false )
Con::errorf(ConsoleLogEntry::General, "ParticleEmitterNode::onAdd: Invalid packet, bad datablockId(mEmitterDatablock): %d", mEmitterDatablockId);
}
if( isClientObject() )
{
setEmitterDataBlock( mEmitterDatablock );
}
else
{
setMaskBits( StateMask | EmitterDBMask );
}
mObjBox.minExtents.set(-0.5, -0.5, -0.5);
mObjBox.maxExtents.set( 0.5, 0.5, 0.5);
resetWorldBox();
addToScene();
return true;
}
//-----------------------------------------------------------------------------
// onRemove
//-----------------------------------------------------------------------------
void ParticleEmitterNode::onRemove()
{
removeFromScene();
if( isClientObject() )
{
if( mEmitter )
{
mEmitter->deleteWhenEmpty();
mEmitter = NULL;
}
}
Parent::onRemove();
}
//-----------------------------------------------------------------------------
// onNewDataBlock
//-----------------------------------------------------------------------------
bool ParticleEmitterNode::onNewDataBlock( GameBaseData *dptr, bool reload )
{
mDataBlock = dynamic_cast<ParticleEmitterNodeData*>( dptr );
if ( !mDataBlock || !Parent::onNewDataBlock( dptr, reload ) )
return false;
// Todo: Uncomment if this is a "leaf" class
scriptOnNewDataBlock();
return true;
}
//-----------------------------------------------------------------------------
void ParticleEmitterNode::inspectPostApply()
{
Parent::inspectPostApply();
setMaskBits(StateMask | EmitterDBMask);
}
//-----------------------------------------------------------------------------
// advanceTime
//-----------------------------------------------------------------------------
void ParticleEmitterNode::processTick(const Move* move)
{
Parent::processTick(move);
if ( isMounted() )
{
MatrixF mat;
mMount.object->getMountTransform( mMount.node, mMount.xfm, &mat );
setTransform( mat );
}
}
void ParticleEmitterNode::advanceTime(F32 dt)
{
Parent::advanceTime(dt);
if(!mActive || mEmitter.isNull() || !mDataBlock)
return;
Point3F emitPoint, emitVelocity;
Point3F emitAxis(0, 0, 1);
getTransform().mulV(emitAxis);
getTransform().getColumn(3, &emitPoint);
emitVelocity = emitAxis * mVelocity;
mEmitter->emitParticles(emitPoint, emitPoint,
emitAxis,
emitVelocity, (U32)(dt * mDataBlock->timeMultiple * 1000.0f));
}
//-----------------------------------------------------------------------------
// packUpdate
//-----------------------------------------------------------------------------
U32 ParticleEmitterNode::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
if ( stream->writeFlag( mask & InitialUpdateMask ) )
{
mathWrite(*stream, getTransform());
mathWrite(*stream, getScale());
}
if ( stream->writeFlag( mask & EmitterDBMask ) )
{
if( stream->writeFlag(mEmitterDatablock != NULL) )
{
stream->writeRangedU32(mEmitterDatablock->getId(), DataBlockObjectIdFirst,
DataBlockObjectIdLast);
}
}
if ( stream->writeFlag( mask & StateMask ) )
{
stream->writeFlag( mActive );
stream->write( mVelocity );
}
return retMask;
}
//-----------------------------------------------------------------------------
// unpackUpdate
//-----------------------------------------------------------------------------
void ParticleEmitterNode::unpackUpdate(NetConnection* con, BitStream* stream)
{
Parent::unpackUpdate(con, stream);
if ( stream->readFlag() )
{
MatrixF temp;
Point3F tempScale;
mathRead(*stream, &temp);
mathRead(*stream, &tempScale);
setScale(tempScale);
setTransform(temp);
}
if ( stream->readFlag() )
{
mEmitterDatablockId = stream->readFlag() ?
stream->readRangedU32(DataBlockObjectIdFirst, DataBlockObjectIdLast) : 0;
ParticleEmitterData *emitterDB = NULL;
Sim::findObject( mEmitterDatablockId, emitterDB );
if ( isProperlyAdded() )
setEmitterDataBlock( emitterDB );
}
if ( stream->readFlag() )
{
mActive = stream->readFlag();
stream->read( &mVelocity );
}
}
void ParticleEmitterNode::setEmitterDataBlock(ParticleEmitterData* data)
{
if ( isServerObject() )
{
setMaskBits( EmitterDBMask );
}
else
{
ParticleEmitter* pEmitter = NULL;
if ( data )
{
// Create emitter with new datablock
pEmitter = new ParticleEmitter;
pEmitter->onNewDataBlock( data, false );
if( pEmitter->registerObject() == false )
{
Con::warnf(ConsoleLogEntry::General, "Could not register base emitter for particle of class: %s", data->getName() ? data->getName() : data->getIdString() );
delete pEmitter;
return;
}
}
// Replace emitter
if ( mEmitter )
mEmitter->deleteWhenEmpty();
mEmitter = pEmitter;
}
mEmitterDatablock = data;
}
DefineEngineMethod(ParticleEmitterNode, setEmitterDataBlock, void, (ParticleEmitterData* emitterDatablock), (0),
"Assigns the datablock for this emitter node.\n"
"@param emitterDatablock ParticleEmitterData datablock to assign\n"
"@tsexample\n"
"// Assign a new emitter datablock\n"
"%emitter.setEmitterDatablock( %emitterDatablock );\n"
"@endtsexample\n" )
{
if ( !emitterDatablock )
{
Con::errorf("ParticleEmitterData datablock could not be found when calling setEmitterDataBlock in particleEmitterNode.");
return;
}
object->setEmitterDataBlock(emitterDatablock);
}
DefineEngineMethod(ParticleEmitterNode, setActive, void, (bool active),,
"Turns the emitter on or off.\n"
"@param active New emitter state\n" )
{
object->setActive( active );
}

View file

@ -0,0 +1,118 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _PARTICLEEMITTERDUMMY_H_
#define _PARTICLEEMITTERDUMMY_H_
#ifndef _GAMEBASE_H_
#include "T3D/gameBase/gameBase.h"
#endif
class ParticleEmitterData;
class ParticleEmitter;
//*****************************************************************************
// ParticleEmitterNodeData
//*****************************************************************************
class ParticleEmitterNodeData : public GameBaseData
{
typedef GameBaseData Parent;
protected:
bool onAdd();
//-------------------------------------- Console set variables
public:
F32 timeMultiple;
//-------------------------------------- load set variables
public:
public:
ParticleEmitterNodeData();
~ParticleEmitterNodeData();
void packData(BitStream*);
void unpackData(BitStream*);
bool preload(bool server, String &errorStr);
DECLARE_CONOBJECT(ParticleEmitterNodeData);
static void initPersistFields();
};
//*****************************************************************************
// ParticleEmitterNode
//*****************************************************************************
class ParticleEmitterNode : public GameBase
{
typedef GameBase Parent;
enum MaskBits
{
StateMask = Parent::NextFreeMask << 0,
EmitterDBMask = Parent::NextFreeMask << 1,
NextFreeMask = Parent::NextFreeMask << 2,
};
private:
ParticleEmitterNodeData* mDataBlock;
protected:
bool onAdd();
void onRemove();
bool onNewDataBlock( GameBaseData *dptr, bool reload );
void inspectPostApply();
ParticleEmitterData* mEmitterDatablock;
S32 mEmitterDatablockId;
bool mActive;
SimObjectPtr<ParticleEmitter> mEmitter;
F32 mVelocity;
public:
ParticleEmitterNode();
~ParticleEmitterNode();
ParticleEmitter *getParticleEmitter() {return mEmitter;}
// Time/Move Management
public:
void processTick(const Move* move);
void advanceTime(F32 dt);
DECLARE_CONOBJECT(ParticleEmitterNode);
static void initPersistFields();
U32 packUpdate (NetConnection *conn, U32 mask, BitStream* stream);
void unpackUpdate(NetConnection *conn, BitStream* stream);
inline bool getActive( void ) { return mActive; };
inline void setActive( bool active ) { mActive = active; setMaskBits( StateMask ); };
void setEmitterDataBlock(ParticleEmitterData* data);
};
#endif // _H_PARTICLEEMISSIONDUMMY

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,287 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _PRECIPITATION_H_
#define _PRECIPITATION_H_
#include "gfx/gfxDevice.h"
#include "T3D/gameBase/gameBase.h"
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
#ifndef _RENDERPASSMANAGER_H_
#include "renderInstance/renderPassManager.h"
#endif
class SFXTrack;
class SFXSource;
//--------------------------------------------------------------------------
/// Precipitation datablock.
class PrecipitationData : public GameBaseData
{
typedef GameBaseData Parent;
public:
SFXTrack* soundProfile;
StringTableEntry mDropName; ///< Texture filename for drop particles
StringTableEntry mDropShaderName; ///< The name of the shader used for raindrops
StringTableEntry mSplashName; ///< Texture filename for splash particles
StringTableEntry mSplashShaderName; ///< The name of the shader used for raindrops
S32 mDropsPerSide; ///< How many drops are on a side of the raindrop texture.
S32 mSplashesPerSide; ///< How many splash are on a side of the splash texture.
PrecipitationData();
DECLARE_CONOBJECT(PrecipitationData);
bool preload( bool server, String& errorStr );
static void initPersistFields();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
};
struct Raindrop
{
F32 velocity; ///< How fast the drop is falling downwards
Point3F position; ///< Position of the drop
Point3F renderPosition; ///< Interpolated render-position of the drop
F32 time; ///< Time into the turbulence function
F32 mass; ///< Mass of drop used for how much turbulence/wind effects the drop
U32 texCoordIndex; ///< Which piece of the material will be used
bool toRender; ///< Don't want to render all drops, just the ones that pass a few tests
bool valid; ///< Drop becomes invalid after hitting something. Just keep updating
///< the position of it, but don't render until it hits the bottom
///< of the renderbox and respawns
Point3F hitPos; ///< Point at which the drop will collide with something
U32 hitType; ///< What kind of object the drop will hit
Raindrop *nextSplashDrop; ///< Linked list cruft for easily adding/removing stuff from the splash list
Raindrop *prevSplashDrop; ///< Same as next but previous!
SimTime animStartTime; ///< Animation time tracker
Raindrop* next; ///< linked list cruft
Raindrop()
{
velocity = 0;
time = 0;
mass = 1;
texCoordIndex = 0;
next = NULL;
toRender = false;
valid = true;
nextSplashDrop = NULL;
prevSplashDrop = NULL;
animStartTime = 0;
hitType = 0;
hitPos = Point3F(0,0,0);
}
};
//--------------------------------------------------------------------------
class Precipitation : public GameBase
{
protected:
typedef GameBase Parent;
PrecipitationData* mDataBlock;
Raindrop *mDropHead; ///< Drop linked list head
Raindrop *mSplashHead; ///< Splash linked list head
Point2F* mTexCoords; ///< texture coords for rain texture
Point2F* mSplashCoords; ///< texture coordinates for splash texture
SFXSource* mAmbientSound; ///< Ambient sound
GFXShaderRef mDropShader; ///< The shader used for raindrops
GFXTexHandle mDropHandle; ///< Texture handle for raindrop
GFXShaderRef mSplashShader; ///< The shader used for splashes
GFXTexHandle mSplashHandle; ///< Texture handle for splash
U32 mLastRenderFrame; ///< Used to skip processTick when we haven't been visible.
U32 mDropHitMask; ///< Stores the current drop hit mask.
//console exposed variables
bool mFollowCam; ///< Does the system follow the camera or stay where it's placed.
F32 mDropSize; ///< Droplet billboard size
F32 mSplashSize; ///< Splash billboard size
bool mUseTrueBillboards; ///< True to use true billboards, false for axis-aligned billboards
S32 mSplashMS; ///< How long in milliseconds a splash will last
bool mAnimateSplashes; ///< Animate the splashes using the frames in the texture.
S32 mDropAnimateMS; ///< If greater than zero, will animate the drops from
///< the frames in the texture
S32 mNumDrops; ///< Number of drops in the scene
F32 mPercentage; ///< Server-side set var (NOT exposed to console)
///< which controls how many drops are present [0,1]
F32 mMinSpeed; ///< Minimum downward speed of drops
F32 mMaxSpeed; ///< Maximum downward speed of drops
F32 mMinMass; ///< Minimum mass of drops
F32 mMaxMass; ///< Maximum mass of drops
F32 mBoxWidth; ///< How far away in the x and y directions drops will render
F32 mBoxHeight; ///< How high drops will render
F32 mMaxTurbulence; ///< Coefficient to sin/cos for adding turbulence
F32 mTurbulenceSpeed; ///< How fast the turbulence wraps in a circle
bool mUseTurbulence; ///< Whether to use turbulence or not (MAY EFFECT PERFORMANCE)
bool mUseLighting; ///< This enables shading of the drops and splashes
///< by the sun color.
ColorF mGlowIntensity; ///< Set it to 0 to disable the glow or use it to control
///< the intensity of each channel.
bool mReflect; ///< This enables the precipitation to be rendered
///< during reflection passes. This is expensive.
bool mUseWind; ///< This enables the wind from the sky SceneObject
///< to effect the velocitiy of the drops.
bool mRotateWithCamVel; ///< Rotate the drops relative to the camera velocity
///< This is useful for "streak" type drops
bool mDoCollision; ///< Whether or not to do collision
bool mDropHitPlayers; ///< Should drops collide with players
bool mDropHitVehicles; ///< Should drops collide with vehicles
F32 mFadeDistance; ///< The distance at which fading of the particles begins.
F32 mFadeDistanceEnd; ///< The distance at which fading of the particles ends.
U32 mMaxVBDrops; ///< The maximum drops allowed in one render batch.
GFXStateBlockRef mDefaultSB;
GFXStateBlockRef mDistantSB;
GFXShaderConstBufferRef mDropShaderConsts;
GFXShaderConstHandle* mDropShaderModelViewSC;
GFXShaderConstHandle* mDropShaderFadeStartEndSC;
GFXShaderConstHandle* mDropShaderCameraPosSC;
GFXShaderConstHandle* mDropShaderAmbientSC;
GFXShaderConstBufferRef mSplashShaderConsts;
GFXShaderConstHandle* mSplashShaderModelViewSC;
GFXShaderConstHandle* mSplashShaderFadeStartEndSC;
GFXShaderConstHandle* mSplashShaderCameraPosSC;
GFXShaderConstHandle* mSplashShaderAmbientSC;
struct
{
bool valid;
U32 startTime;
U32 totalTime;
F32 startPct;
F32 endPct;
} mStormData;
struct
{
bool valid;
U32 startTime;
U32 totalTime;
F32 startMax;
F32 startSpeed;
F32 endMax;
F32 endSpeed;
} mTurbulenceData;
//other functions...
void processTick(const Move*);
void interpolateTick(F32 delta);
VectorF getWindVelocity();
void fillDropList(); ///< Adds/removes drops from the list to have the right # of drops
void killDropList(); ///< Deletes the entire drop list
void initRenderObjects(); ///< Re-inits the texture coord lookup tables
void initMaterials(); ///< Re-inits the textures and shaders
void spawnDrop(Raindrop *drop); ///< Fills drop info with random velocity, x/y positions, and mass
void spawnNewDrop(Raindrop *drop); ///< Same as spawnDrop except also does z position
void findDropCutoff(Raindrop *drop, const Box3F &box, const VectorF &windVel); ///< Casts a ray to see if/when a drop will collide
void wrapDrop(Raindrop *drop, const Box3F &box, const U32 currTime, const VectorF &windVel); ///< Wraps a drop within the specified box
void createSplash(Raindrop *drop); ///< Adds a drop to the splash list
void destroySplash(Raindrop *drop); ///< Removes a drop from the splash list
GFXPrimitiveBufferHandle mRainIB;
GFXVertexBufferHandle<GFXVertexPT> mRainVB;
bool onAdd();
void onRemove();
// Rendering
void prepRenderImage( SceneRenderState* state );
void renderObject(ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* );
void setTransform(const MatrixF &mat);
public:
Precipitation();
~Precipitation();
void inspectPostApply();
enum
{
DataMask = Parent::NextFreeMask << 0,
PercentageMask = Parent::NextFreeMask << 1,
StormMask = Parent::NextFreeMask << 2,
TransformMask = Parent::NextFreeMask << 3,
TurbulenceMask = Parent::NextFreeMask << 4,
NextFreeMask = Parent::NextFreeMask << 5
};
bool onNewDataBlock( GameBaseData *dptr, bool reload );
DECLARE_CONOBJECT(Precipitation);
static void initPersistFields();
U32 packUpdate(NetConnection*, U32 mask, BitStream* stream);
void unpackUpdate(NetConnection*, BitStream* stream);
void setPercentage(F32 pct);
void modifyStorm(F32 pct, U32 ms);
/// This is used to smoothly change the turbulence
/// over a desired time period. Setting ms to zero
/// will cause the change to be instantaneous. Setting
/// max zero will disable turbulence.
void setTurbulence(F32 max, F32 speed, U32 ms);
};
#endif // PRECIPITATION_H_

View file

@ -0,0 +1,698 @@
//-----------------------------------------------------------------------------
// 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 "T3D/fx/splash.h"
#include "console/consoleTypes.h"
#include "gfx/primBuilder.h"
#include "gfx/gfxDrawUtil.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxProfile.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
#include "core/stream/bitStream.h"
#include "math/mathIO.h"
#include "T3D/fx/explosion.h"
#include "T3D/fx/particle.h"
#include "T3D/fx/particleEmitter.h"
#include "T3D/fx/particleEmitterNode.h"
#include "T3D/gameBase/gameProcess.h"
#include "sim/netConnection.h"
#include "renderInstance/renderPassManager.h"
#include "console/engineAPI.h"
namespace
{
MRandomLCG sgRandom(0xdeadbeef);
} // namespace {}
//----------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(SplashData);
IMPLEMENT_CO_NETOBJECT_V1(Splash);
ConsoleDocClass( SplashData,
"@brief Acts as the physical point in space in white a Splash is created from.\n"
"@ingroup FX\n"
);
ConsoleDocClass( Splash,
"@brief Manages the ring used for a Splash effect.\n"
"@ingroup FX\n"
);
//--------------------------------------------------------------------------
// Splash Data
//--------------------------------------------------------------------------
SplashData::SplashData()
{
soundProfile = NULL;
soundProfileId = 0;
scale.set(1, 1, 1);
dMemset( emitterList, 0, sizeof( emitterList ) );
dMemset( emitterIDList, 0, sizeof( emitterIDList ) );
delayMS = 0;
delayVariance = 0;
lifetimeMS = 1000;
lifetimeVariance = 0;
width = 4.0;
numSegments = 10;
velocity = 5.0;
height = 0.0;
acceleration = 0.0;
texWrap = 1.0;
texFactor = 3.0;
ejectionFreq = 5;
ejectionAngle = 45.0;
ringLifetime = 1.0;
startRadius = 0.5;
explosion = NULL;
explosionId = 0;
dMemset( textureName, 0, sizeof( textureName ) );
U32 i;
for( i=0; i<NUM_TIME_KEYS; i++ )
times[i] = 1.0;
times[0] = 0.0;
for( i=0; i<NUM_TIME_KEYS; i++ )
colors[i].set( 1.0, 1.0, 1.0, 1.0 );
}
//--------------------------------------------------------------------------
// Init fields
//--------------------------------------------------------------------------
void SplashData::initPersistFields()
{
addField("soundProfile", TYPEID< SFXProfile >(), Offset(soundProfile, SplashData), "SFXProfile effect to play.\n");
addField("scale", TypePoint3F, Offset(scale, SplashData), "The scale of this splashing effect, defined as the F32 points X, Y, Z.\n");
addField("emitter", TYPEID< ParticleEmitterData >(), Offset(emitterList, SplashData), NUM_EMITTERS, "List of particle emitters to create at the point of this Splash effect.\n");
addField("delayMS", TypeS32, Offset(delayMS, SplashData), "Time to delay, in milliseconds, before actually starting this effect.\n");
addField("delayVariance", TypeS32, Offset(delayVariance, SplashData), "Time variance for delayMS.\n");
addField("lifetimeMS", TypeS32, Offset(lifetimeMS, SplashData), "Lifetime for this effect, in milliseconds.\n");
addField("lifetimeVariance", TypeS32, Offset(lifetimeVariance, SplashData), "Time variance for lifetimeMS.\n");
addField("width", TypeF32, Offset(width, SplashData), "Width for the X and Y coordinates to create this effect within.");
addField("numSegments", TypeS32, Offset(numSegments, SplashData), "Number of ejection points in the splash ring.\n");
addField("velocity", TypeF32, Offset(velocity, SplashData), "Velocity for the splash effect to travel.\n");
addField("height", TypeF32, Offset(height, SplashData), "Height for the splash to reach.\n");
addField("acceleration", TypeF32, Offset(acceleration, SplashData), "Constant acceleration value to place upon the splash effect.\n");
addField("times", TypeF32, Offset(times, SplashData), NUM_TIME_KEYS, "Times to transition through the splash effect. Up to 4 allowed. Values are 0.0 - 1.0, and corrispond to the life of the particle where 0 is first created and 1 is end of lifespace.\n" );
addField("colors", TypeColorF, Offset(colors, SplashData), NUM_TIME_KEYS, "Color values to set the splash effect, rgba. Up to 4 allowed. Will transition through colors based on values set in the times value. Example: colors[0] = \"0.6 1.0 1.0 0.5\".\n" );
addField("texture", TypeFilename, Offset(textureName, SplashData), NUM_TEX, "Imagemap file to use as the texture for the splash effect.\n");
addField("texWrap", TypeF32, Offset(texWrap, SplashData), "Amount to wrap the texture around the splash ring, 0.0f - 1.0f.\n");
addField("texFactor", TypeF32, Offset(texFactor, SplashData), "Factor in which to apply the texture to the splash ring, 0.0f - 1.0f.\n");
addField("ejectionFreq", TypeF32, Offset(ejectionFreq, SplashData), "Frequency in which to emit splash rings.\n");
addField("ejectionAngle", TypeF32, Offset(ejectionAngle, SplashData), "Rotational angle to create a splash ring.\n");
addField("ringLifetime", TypeF32, Offset(ringLifetime, SplashData), "Lifetime, in milliseconds, for a splash ring.\n");
addField("startRadius", TypeF32, Offset(startRadius, SplashData), "Starting radius size of a splash ring.\n");
addField("explosion", TYPEID< ExplosionData >(), Offset(explosion, SplashData), "ExplosionData object to create at the creation position of this splash effect.\n");
Parent::initPersistFields();
}
//--------------------------------------------------------------------------
// On add - verify data settings
//--------------------------------------------------------------------------
bool SplashData::onAdd()
{
if (Parent::onAdd() == false)
return false;
return true;
}
//--------------------------------------------------------------------------
// Pack data
//--------------------------------------------------------------------------
void SplashData::packData(BitStream* stream)
{
Parent::packData(stream);
mathWrite(*stream, scale);
stream->write(delayMS);
stream->write(delayVariance);
stream->write(lifetimeMS);
stream->write(lifetimeVariance);
stream->write(width);
stream->write(numSegments);
stream->write(velocity);
stream->write(height);
stream->write(acceleration);
stream->write(texWrap);
stream->write(texFactor);
stream->write(ejectionFreq);
stream->write(ejectionAngle);
stream->write(ringLifetime);
stream->write(startRadius);
if( stream->writeFlag( explosion ) )
{
stream->writeRangedU32(explosion->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast);
}
S32 i;
for( i=0; i<NUM_EMITTERS; i++ )
{
if( stream->writeFlag( emitterList[i] != NULL ) )
{
stream->writeRangedU32( emitterList[i]->getId(), DataBlockObjectIdFirst, DataBlockObjectIdLast );
}
}
for( i=0; i<NUM_TIME_KEYS; i++ )
{
stream->write( colors[i] );
}
for( i=0; i<NUM_TIME_KEYS; i++ )
{
stream->write( times[i] );
}
for( i=0; i<NUM_TEX; i++ )
{
stream->writeString(textureName[i]);
}
}
//--------------------------------------------------------------------------
// Unpack data
//--------------------------------------------------------------------------
void SplashData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
mathRead(*stream, &scale);
stream->read(&delayMS);
stream->read(&delayVariance);
stream->read(&lifetimeMS);
stream->read(&lifetimeVariance);
stream->read(&width);
stream->read(&numSegments);
stream->read(&velocity);
stream->read(&height);
stream->read(&acceleration);
stream->read(&texWrap);
stream->read(&texFactor);
stream->read(&ejectionFreq);
stream->read(&ejectionAngle);
stream->read(&ringLifetime);
stream->read(&startRadius);
if( stream->readFlag() )
{
explosionId = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
}
U32 i;
for( i=0; i<NUM_EMITTERS; i++ )
{
if( stream->readFlag() )
{
emitterIDList[i] = stream->readRangedU32( DataBlockObjectIdFirst, DataBlockObjectIdLast );
}
}
for( i=0; i<NUM_TIME_KEYS; i++ )
{
stream->read( &colors[i] );
}
for( i=0; i<NUM_TIME_KEYS; i++ )
{
stream->read( &times[i] );
}
for( i=0; i<NUM_TEX; i++ )
{
textureName[i] = stream->readSTString();
}
}
//--------------------------------------------------------------------------
// Preload data - load resources
//--------------------------------------------------------------------------
bool SplashData::preload(bool server, String &errorStr)
{
if (Parent::preload(server, errorStr) == false)
return false;
if (!server)
{
S32 i;
for( i=0; i<NUM_EMITTERS; i++ )
{
if( !emitterList[i] && emitterIDList[i] != 0 )
{
if( Sim::findObject( emitterIDList[i], emitterList[i] ) == false)
{
Con::errorf( ConsoleLogEntry::General, "SplashData::onAdd: Invalid packet, bad datablockId(particle emitter): 0x%x", emitterIDList[i] );
}
}
}
for( i=0; i<NUM_TEX; i++ )
{
if (textureName[i] && textureName[i][0])
{
textureHandle[i] = GFXTexHandle(textureName[i], &GFXDefaultStaticDiffuseProfile, avar("%s() - textureHandle[%d] (line %d)", __FUNCTION__, i, __LINE__) );
}
}
}
if( !explosion && explosionId != 0 )
{
if( !Sim::findObject(explosionId, explosion) )
{
Con::errorf(ConsoleLogEntry::General, "SplashData::preload: Invalid packet, bad datablockId(explosion): %d", explosionId);
}
}
return true;
}
//--------------------------------------------------------------------------
// Splash
//--------------------------------------------------------------------------
Splash::Splash()
{
dMemset( mEmitterList, 0, sizeof( mEmitterList ) );
mDelayMS = 0;
mCurrMS = 0;
mEndingMS = 1000;
mActive = false;
mRadius = 0.0;
mVelocity = 1.0;
mHeight = 0.0;
mTimeSinceLastRing = 0.0;
mDead = false;
mElapsedTime = 0.0;
mInitialNormal.set( 0.0, 0.0, 1.0 );
// Only allocated client side.
mNetFlags.set( IsGhost );
}
//--------------------------------------------------------------------------
// Destructor
//--------------------------------------------------------------------------
Splash::~Splash()
{
}
//--------------------------------------------------------------------------
// Set initial state
//--------------------------------------------------------------------------
void Splash::setInitialState(const Point3F& point, const Point3F& normal, const F32 fade)
{
mInitialPosition = point;
mInitialNormal = normal;
mFade = fade;
mFog = 0.0f;
}
//--------------------------------------------------------------------------
// OnAdd
//--------------------------------------------------------------------------
bool Splash::onAdd()
{
// first check if we have a server connection, if we dont 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;
mDelayMS = mDataBlock->delayMS + sgRandom.randI( -mDataBlock->delayVariance, mDataBlock->delayVariance );
mEndingMS = mDataBlock->lifetimeMS + sgRandom.randI( -mDataBlock->lifetimeVariance, mDataBlock->lifetimeVariance );
mVelocity = mDataBlock->velocity;
mHeight = mDataBlock->height;
mTimeSinceLastRing = 1.0 / mDataBlock->ejectionFreq;
for( U32 i=0; i<SplashData::NUM_EMITTERS; i++ )
{
if( mDataBlock->emitterList[i] != NULL )
{
ParticleEmitter * pEmitter = new ParticleEmitter;
pEmitter->onNewDataBlock( mDataBlock->emitterList[i], false );
if( !pEmitter->registerObject() )
{
Con::warnf( ConsoleLogEntry::General, "Could not register emitter for particle of class: %s", mDataBlock->getName() );
delete pEmitter;
pEmitter = NULL;
}
mEmitterList[i] = pEmitter;
}
}
spawnExplosion();
mObjBox.minExtents = Point3F( -1, -1, -1 );
mObjBox.maxExtents = Point3F( 1, 1, 1 );
resetWorldBox();
gClientSceneGraph->addObjectToScene(this);
removeFromProcessList();
ClientProcessList::get()->addObject(this);
conn->addObject(this);
return true;
}
//--------------------------------------------------------------------------
// OnRemove
//--------------------------------------------------------------------------
void Splash::onRemove()
{
for( U32 i=0; i<SplashData::NUM_EMITTERS; i++ )
{
if( mEmitterList[i] )
{
mEmitterList[i]->deleteWhenEmpty();
mEmitterList[i] = NULL;
}
}
ringList.clear();
getSceneManager()->removeObjectFromScene(this);
getContainer()->removeObject(this);
Parent::onRemove();
}
//--------------------------------------------------------------------------
// On New Data Block
//--------------------------------------------------------------------------
bool Splash::onNewDataBlock( GameBaseData *dptr, bool reload )
{
mDataBlock = dynamic_cast<SplashData*>(dptr);
if (!mDataBlock || !Parent::onNewDataBlock(dptr, reload))
return false;
scriptOnNewDataBlock();
return true;
}
//--------------------------------------------------------------------------
// Process tick
//--------------------------------------------------------------------------
void Splash::processTick(const Move*)
{
mCurrMS += TickMs;
if( isServerObject() )
{
if( mCurrMS >= mEndingMS )
{
mDead = true;
if( mCurrMS >= (mEndingMS + mDataBlock->ringLifetime * 1000) )
{
deleteObject();
}
}
}
else
{
if( mCurrMS >= mEndingMS )
{
mDead = true;
}
}
}
//--------------------------------------------------------------------------
// Advance time
//--------------------------------------------------------------------------
void Splash::advanceTime(F32 dt)
{
if (dt == 0.0)
return;
mElapsedTime += dt;
updateColor();
updateWave( dt );
updateEmitters( dt );
updateRings( dt );
if( !mDead )
{
emitRings( dt );
}
}
//----------------------------------------------------------------------------
// Update emitters
//----------------------------------------------------------------------------
void Splash::updateEmitters( F32 dt )
{
Point3F pos = getPosition();
for( U32 i=0; i<SplashData::NUM_EMITTERS; i++ )
{
if( mEmitterList[i] )
{
mEmitterList[i]->emitParticles( pos, pos, mInitialNormal, Point3F( 0.0, 0.0, 0.0 ), (S32) (dt * 1000) );
}
}
}
//----------------------------------------------------------------------------
// Update wave
//----------------------------------------------------------------------------
void Splash::updateWave( F32 dt )
{
mVelocity += mDataBlock->acceleration * dt;
mRadius += mVelocity * dt;
}
//----------------------------------------------------------------------------
// Update color
//----------------------------------------------------------------------------
void Splash::updateColor()
{
for(SplashRingList::Iterator ring = ringList.begin(); ring != ringList.end(); ++ring)
{
F32 t = F32(ring->elapsedTime) / F32(ring->lifetime);
for( U32 i = 1; i < SplashData::NUM_TIME_KEYS; i++ )
{
if( mDataBlock->times[i] >= t )
{
F32 firstPart = t - mDataBlock->times[i-1];
F32 total = (mDataBlock->times[i] -
mDataBlock->times[i-1]);
firstPart /= total;
ring->color.interpolate( mDataBlock->colors[i-1],
mDataBlock->colors[i],
firstPart);
break;
}
}
}
}
//----------------------------------------------------------------------------
// Create ring
//----------------------------------------------------------------------------
SplashRing Splash::createRing()
{
SplashRing ring;
U32 numPoints = mDataBlock->numSegments + 1;
Point3F ejectionAxis( 0.0, 0.0, 1.0 );
Point3F axisx;
if (mFabs(ejectionAxis.z) < 0.999f)
mCross(ejectionAxis, Point3F(0, 0, 1), &axisx);
else
mCross(ejectionAxis, Point3F(0, 1, 0), &axisx);
axisx.normalize();
for( U32 i=0; i<numPoints; i++ )
{
F32 t = F32(i) / F32(numPoints);
AngAxisF thetaRot( axisx, mDataBlock->ejectionAngle * (M_PI / 180.0));
AngAxisF phiRot( ejectionAxis, t * (M_PI * 2.0));
Point3F pointAxis = ejectionAxis;
MatrixF temp;
thetaRot.setMatrix(&temp);
temp.mulP(pointAxis);
phiRot.setMatrix(&temp);
temp.mulP(pointAxis);
Point3F startOffset = axisx;
temp.mulV( startOffset );
startOffset *= mDataBlock->startRadius;
SplashRingPoint point;
point.position = getPosition() + startOffset;
point.velocity = pointAxis * mDataBlock->velocity;
ring.points.push_back( point );
}
ring.color = mDataBlock->colors[0];
ring.lifetime = mDataBlock->ringLifetime;
ring.elapsedTime = 0.0;
ring.v = mDataBlock->texFactor * mFmod( mElapsedTime, 1.0 );
return ring;
}
//----------------------------------------------------------------------------
// Emit rings
//----------------------------------------------------------------------------
void Splash::emitRings( F32 dt )
{
mTimeSinceLastRing += dt;
S32 numNewRings = (S32) (mTimeSinceLastRing * F32(mDataBlock->ejectionFreq));
mTimeSinceLastRing -= numNewRings / mDataBlock->ejectionFreq;
for( S32 i=numNewRings-1; i>=0; i-- )
{
F32 t = F32(i) / F32(numNewRings);
t *= dt;
t += mTimeSinceLastRing;
SplashRing ring = createRing();
updateRing( ring, t );
ringList.pushBack( ring );
}
}
//----------------------------------------------------------------------------
// Update rings
//----------------------------------------------------------------------------
void Splash::updateRings( F32 dt )
{
SplashRingList::Iterator ring;
for(SplashRingList::Iterator i = ringList.begin(); i != ringList.end(); /*Trickiness*/)
{
ring = i++;
ring->elapsedTime += dt;
if( !ring->isActive() )
{
ringList.erase( ring );
}
else
{
updateRing( *ring, dt );
}
}
}
//----------------------------------------------------------------------------
// Update ring
//----------------------------------------------------------------------------
void Splash::updateRing( SplashRing& ring, F32 dt )
{
for( U32 i=0; i<ring.points.size(); i++ )
{
if( mDead )
{
Point3F vel = ring.points[i].velocity;
vel.normalize();
vel *= mDataBlock->acceleration;
ring.points[i].velocity += vel * dt;
}
ring.points[i].velocity += Point3F( 0.0f, 0.0f, -9.8f ) * dt;
ring.points[i].position += ring.points[i].velocity * dt;
}
}
//----------------------------------------------------------------------------
// Explode
//----------------------------------------------------------------------------
void Splash::spawnExplosion()
{
if( !mDataBlock->explosion ) return;
Explosion* pExplosion = new Explosion;
pExplosion->onNewDataBlock(mDataBlock->explosion, false);
MatrixF trans = getTransform();
trans.setPosition( getPosition() );
pExplosion->setTransform( trans );
pExplosion->setInitialState( trans.getPosition(), VectorF(0,0,1), 1);
if (!pExplosion->registerObject())
delete pExplosion;
}
//--------------------------------------------------------------------------
// packUpdate
//--------------------------------------------------------------------------
U32 Splash::packUpdate(NetConnection* con, U32 mask, BitStream* stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
if( stream->writeFlag(mask & GameBase::InitialUpdateMask) )
{
mathWrite(*stream, mInitialPosition);
}
return retMask;
}
//--------------------------------------------------------------------------
// unpackUpdate
//--------------------------------------------------------------------------
void Splash::unpackUpdate(NetConnection* con, BitStream* stream)
{
Parent::unpackUpdate(con, stream);
if( stream->readFlag() )
{
mathRead(*stream, &mInitialPosition);
setPosition( mInitialPosition );
}
}

View file

@ -0,0 +1,195 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _SPLASH_H_
#define _SPLASH_H_
#ifndef _GAMEBASE_H_
#include "T3D/gameBase/gameBase.h"
#endif
#ifndef _TORQUE_LIST_
#include "core/util/tList.h"
#endif
#include "gfx/gfxTextureHandle.h"
class ParticleEmitter;
class ParticleEmitterData;
class AudioProfile;
class ExplosionData;
//--------------------------------------------------------------------------
// Ring Point
//--------------------------------------------------------------------------
struct SplashRingPoint
{
Point3F position;
Point3F velocity;
};
//--------------------------------------------------------------------------
// Splash Ring
//--------------------------------------------------------------------------
struct SplashRing
{
Vector <SplashRingPoint> points;
ColorF color;
F32 lifetime;
F32 elapsedTime;
F32 v;
SplashRing()
{
color.set( 0.0, 0.0, 0.0, 1.0 );
lifetime = 0.0;
elapsedTime = 0.0;
v = 0.0;
}
bool isActive()
{
return elapsedTime < lifetime;
}
};
//--------------------------------------------------------------------------
// Splash Data
//--------------------------------------------------------------------------
class SplashData : public GameBaseData
{
public:
typedef GameBaseData Parent;
enum Constants
{
NUM_EMITTERS = 3,
NUM_TIME_KEYS = 4,
NUM_TEX = 2,
};
public:
AudioProfile* soundProfile;
S32 soundProfileId;
ParticleEmitterData* emitterList[NUM_EMITTERS];
S32 emitterIDList[NUM_EMITTERS];
S32 delayMS;
S32 delayVariance;
S32 lifetimeMS;
S32 lifetimeVariance;
Point3F scale;
F32 width;
F32 height;
U32 numSegments;
F32 velocity;
F32 acceleration;
F32 texWrap;
F32 texFactor;
F32 ejectionFreq;
F32 ejectionAngle;
F32 ringLifetime;
F32 startRadius;
F32 times[ NUM_TIME_KEYS ];
ColorF colors[ NUM_TIME_KEYS ];
StringTableEntry textureName[NUM_TEX];
GFXTexHandle textureHandle[NUM_TEX];
ExplosionData* explosion;
S32 explosionId;
SplashData();
DECLARE_CONOBJECT(SplashData);
bool onAdd();
bool preload(bool server, String &errorStr);
static void initPersistFields();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
};
//--------------------------------------------------------------------------
// Splash
//--------------------------------------------------------------------------
class Splash : public GameBase
{
typedef GameBase Parent;
private:
SplashData* mDataBlock;
SimObjectPtr<ParticleEmitter> mEmitterList[ SplashData::NUM_EMITTERS ];
typedef Torque::List<SplashRing> SplashRingList;
SplashRingList ringList;
U32 mCurrMS;
U32 mEndingMS;
F32 mRandAngle;
F32 mRadius;
F32 mVelocity;
F32 mHeight;
ColorF mColor;
F32 mTimeSinceLastRing;
bool mDead;
F32 mElapsedTime;
protected:
Point3F mInitialPosition;
Point3F mInitialNormal;
F32 mFade;
F32 mFog;
bool mActive;
S32 mDelayMS;
protected:
bool onAdd();
void onRemove();
void processTick(const Move *move);
void advanceTime(F32 dt);
void updateEmitters( F32 dt );
void updateWave( F32 dt );
void updateColor();
SplashRing createRing();
void updateRings( F32 dt );
void updateRing( SplashRing& ring, F32 dt );
void emitRings( F32 dt );
void spawnExplosion();
public:
Splash();
~Splash();
void setInitialState(const Point3F& point, const Point3F& normal, const F32 fade = 1.0);
U32 packUpdate (NetConnection *conn, U32 mask, BitStream* stream);
void unpackUpdate(NetConnection *conn, BitStream* stream);
bool onNewDataBlock( GameBaseData *dptr, bool reload );
DECLARE_CONOBJECT(Splash);
};
#endif // _H_SPLASH

View file

@ -0,0 +1,136 @@
//-----------------------------------------------------------------------------
// 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/fx/windEmitter.h"
#include "math/mBox.h"
#include "core/tAlgorithm.h"
#include "platform/profiler.h"
Vector<WindEmitter*> WindEmitter::smAllEmitters;
WindEmitter::WindEmitter()
{
smAllEmitters.push_back( this );
mEnabled = true;
mScore = 0.0f;
mSphere.center.zero();
mSphere.radius = 0.0f;
mStrength = 0.0f;
mTurbulenceFrequency = 0.0f;
mTurbulenceStrength = 0.0f;
mVelocity.zero();
}
WindEmitter::~WindEmitter()
{
WindEmitterList::iterator iter = find( smAllEmitters.begin(), smAllEmitters.end(), this );
smAllEmitters.erase( iter );
}
void WindEmitter::setPosition( const Point3F& pos )
{
mSphere.center = pos;
}
void WindEmitter::update( const Point3F& pos, const VectorF& velocity )
{
mSphere.center = pos;
mVelocity = velocity;
}
void WindEmitter::setRadius( F32 radius )
{
mSphere.radius = radius;
}
void WindEmitter::setStrength( F32 strength )
{
mStrength = strength;
}
void WindEmitter::setTurbulency( F32 frequency, F32 strength )
{
mTurbulenceFrequency = frequency;
mTurbulenceStrength = strength;
}
S32 QSORT_CALLBACK WindEmitter::_sortByScore(const void* a, const void* b)
{
return mSign((*(WindEmitter**)b)->mScore - (*(WindEmitter**)a)->mScore);
}
bool WindEmitter::findBest( const Point3F& cameraPos,
const VectorF& cameraDir,
F32 viewDistance,
U32 maxResults,
WindEmitterList* results )
{
PROFILE_START(WindEmitter_findBest);
// Build a sphere from the camera point.
SphereF cameraSphere;
cameraSphere.center = cameraPos;
cameraSphere.radius = viewDistance;
// Collect the active spheres within the camera space and score them.
WindEmitterList best;
WindEmitterList::iterator iter = smAllEmitters.begin();
for ( ; iter != smAllEmitters.end(); iter++ )
{
const SphereF& sphere = *(*iter);
// Skip any spheres outside of our camera range or that are disabled.
if ( !(*iter)->mEnabled || !cameraSphere.isIntersecting( sphere ) )
continue;
// Simple score calculation...
//
// score = ( radius / distance to camera ) * dot( cameraDir, vector from camera to sphere )
//
Point3F vect = sphere.center - cameraSphere.center;
F32 dist = vect.len();
(*iter)->mScore = dist * sphere.radius;
vect /= getMax( dist, 0.001f );
(*iter)->mScore *= mDot( vect, cameraDir );
best.push_back( *iter );
}
// Sort the results by score!
dQsort( best.address(), best.size(), sizeof(WindEmitter*), &WindEmitter::_sortByScore );
// Clip the results to the max requested.
if ( best.size() > maxResults )
best.setSize( maxResults );
// Merge the results and return.
results->merge( best );
PROFILE_END(); // WindEmitter_findBest
return best.size() > 0;
}

View file

@ -0,0 +1,96 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _WINDEMITTER_H_
#define _WINDEMITTER_H_
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _MSPHERE_H_
#include "math/mSphere.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
class WindEmitter;
/// A vector of WindEmitter pointers.
typedef Vector<WindEmitter*> WindEmitterList;
class WindEmitter
{
public:
WindEmitter();
~WindEmitter();
operator const SphereF&() const { return mSphere; }
void update( const Point3F& pos, const VectorF& velocity );
void setPosition( const Point3F& pos );
void setRadius( F32 radius );
void setStrength( F32 strength );
void setTurbulency( F32 frequency, F32 strength );
const Point3F& getCenter() const { return mSphere.center; }
F32 getRadius() const { return mSphere.radius; }
F32 getStrength() const { return mStrength; }
F32 getTurbulenceFrequency() const { return mTurbulenceFrequency; }
F32 getTurbulenceStrength() const { return mTurbulenceStrength; }
const VectorF& getVelocity() const { return mVelocity; }
static bool findBest( const Point3F& cameraPos,
const VectorF& cameraDir,
F32 viewDistance,
U32 maxResults,
WindEmitterList* results );
protected:
SphereF mSphere;
VectorF mVelocity;
F32 mStrength;
F32 mTurbulenceFrequency;
F32 mTurbulenceStrength;
F32 mScore;
bool mEnabled;
static WindEmitterList smAllEmitters;
static S32 QSORT_CALLBACK _sortByScore( const void* a, const void* b );
};
#endif // _WINDEMITTER_H_

View file

@ -0,0 +1,703 @@
//-----------------------------------------------------------------------------
// 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/gameBase/gameBase.h"
#include "console/consoleTypes.h"
#include "console/engineAPI.h"
#include "console/consoleInternal.h"
#include "core/stream/bitStream.h"
#include "sim/netConnection.h"
#include "T3D/gameBase/gameConnection.h"
#include "math/mathIO.h"
#include "T3D/gameBase/moveManager.h"
#include "T3D/gameBase/gameProcess.h"
#ifdef TORQUE_DEBUG_NET_MOVES
#include "T3D/aiConnection.h"
#endif
//----------------------------------------------------------------------------
// Ghost update relative priority values
static F32 sUpFov = 1.0;
static F32 sUpDistance = 0.4f;
static F32 sUpVelocity = 0.4f;
static F32 sUpSkips = 0.2f;
static F32 sUpInterest = 0.2f;
//----------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(GameBaseData);
ConsoleDocClass( GameBaseData,
"@brief Scriptable, demo-able datablock. Used by GameBase objects.\n\n"
"@see GameBase\n"
"@ingroup gameObjects\n"
);
IMPLEMENT_CALLBACK( GameBaseData, onAdd, void, ( GameBase* obj ), ( obj ),
"@brief Called when the object is added to the scene.\n\n"
"@param obj the GameBase object\n\n"
"@tsexample\n"
"datablock GameBaseData(MyObjectData)\n"
"{\n"
" category = \"Misc\";\n"
"};\n\n"
"function MyObjectData::onAdd( %this, %obj )\n"
"{\n"
" echo( \"Added \" @ %obj.getName() @ \" to the scene.\" );\n"
"}\n\n"
"function MyObjectData::onNewDataBlock( %this, %obj )\n"
"{\n"
" echo( \"Assign \" @ %this.getName() @ \" datablock to \" %obj.getName() );\n"
"}\n\n"
"function MyObjectData::onRemove( %this, %obj )\n"
"{\n"
" echo( \"Removed \" @ %obj.getName() @ \" to the scene.\" );\n"
"}\n\n"
"function MyObjectData::onMount( %this, %obj, %mountObj, %node )\n"
"{\n"
" echo( %obj.getName() @ \" mounted to \" @ %mountObj.getName() );\n"
"}\n\n"
"function MyObjectData::onUnmount( %this, %obj, %mountObj, %node )\n"
"{\n"
" echo( %obj.getName() @ \" unmounted from \" @ %mountObj.getName() );\n"
"}\n\n"
"@endtsexample\n" );
IMPLEMENT_CALLBACK( GameBaseData, onNewDataBlock, void, ( GameBase* obj ), ( obj ),
"@brief Called when the object has a new datablock assigned.\n\n"
"@param obj the GameBase object\n\n"
"@see onAdd for an example\n" );
IMPLEMENT_CALLBACK( GameBaseData, onRemove, void, ( GameBase* obj ), ( obj ),
"@brief Called when the object is removed from the scene.\n\n"
"@param obj the GameBase object\n\n"
"@see onAdd for an example\n" );
IMPLEMENT_CALLBACK( GameBaseData, onMount, void, ( GameBase* obj, SceneObject* mountObj, S32 node ), ( obj, mountObj, node ),
"@brief Called when the object is mounted to another object in the scene.\n\n"
"@param obj the GameBase object being mounted\n"
"@param mountObj the object we are mounted to\n"
"@param node the mountObj node we are mounted to\n\n"
"@see onAdd for an example\n" );
IMPLEMENT_CALLBACK( GameBaseData, onUnmount, void, ( GameBase* obj, SceneObject* mountObj, S32 node ), ( obj, mountObj, node ),
"@brief Called when the object is unmounted from another object in the scene.\n\n"
"@param obj the GameBase object being unmounted\n"
"@param mountObj the object we are unmounted from\n"
"@param node the mountObj node we are unmounted from\n\n"
"@see onAdd for an example\n" );
IMPLEMENT_CALLBACK( GameBase, setControl, void, ( bool controlled ), ( controlled ),
"@brief Called when the client controlling the object changes.\n\n"
"@param controlled true if a client now controls this object, false if no "
"client controls this object.\n" );
GameBaseData::GameBaseData()
{
category = "";
packed = false;
}
void GameBaseData::inspectPostApply()
{
Parent::inspectPostApply();
// Tell interested parties ( like objects referencing this datablock )
// that we have been modified and they might want to rebuild...
mReloadSignal.trigger();
}
bool GameBaseData::onAdd()
{
if (!Parent::onAdd())
return false;
return true;
}
void GameBaseData::initPersistFields()
{
addGroup("Scripting");
addField( "category", TypeCaseString, Offset( category, GameBaseData ),
"The group that this datablock will show up in under the \"Scripted\" "
"tab in the World Editor Library." );
endGroup("Scripting");
Parent::initPersistFields();
}
bool GameBaseData::preload(bool server, String &errorStr)
{
if (!Parent::preload(server, errorStr))
return false;
packed = false;
return true;
}
void GameBaseData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
packed = true;
}
//----------------------------------------------------------------------------
bool UNPACK_DB_ID(BitStream * stream, U32 & id)
{
if (stream->readFlag())
{
id = stream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast);
return true;
}
return false;
}
bool PACK_DB_ID(BitStream * stream, U32 id)
{
if (stream->writeFlag(id))
{
stream->writeRangedU32(id,DataBlockObjectIdFirst,DataBlockObjectIdLast);
return true;
}
return false;
}
bool PRELOAD_DB(U32 & id, SimDataBlock ** data, bool server, const char * clientMissing, const char * serverMissing)
{
if (server)
{
if (*data)
id = (*data)->getId();
else if (server && serverMissing)
{
Con::errorf(ConsoleLogEntry::General,serverMissing);
return false;
}
}
else
{
if (id && !Sim::findObject(id,*data) && clientMissing)
{
Con::errorf(ConsoleLogEntry::General,clientMissing);
return false;
}
}
return true;
}
//----------------------------------------------------------------------------
bool GameBase::gShowBoundingBox = false;
//----------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1(GameBase);
ConsoleDocClass( GameBase,
"@brief Base class for game objects which use datablocks, networking, are "
"editable, and need to process ticks.\n\n"
"@ingroup gameObjects\n"
);
GameBase::GameBase()
: mDataBlock( NULL ),
mControllingClient( NULL ),
mCurrentWaterObject( NULL )
{
mNetFlags.set(Ghostable);
mTypeMask |= GameBaseObjectType;
mProcessTag = 0;
// From ProcessObject
mIsGameBase = true;
#ifdef TORQUE_DEBUG_NET_MOVES
mLastMoveId = 0;
mTicksSinceLastMove = 0;
mIsAiControlled = false;
#endif
}
GameBase::~GameBase()
{
}
//----------------------------------------------------------------------------
bool GameBase::onAdd()
{
if ( !Parent::onAdd() )
return false;
// Datablock must be initialized on the server.
// Client datablock are initialized by the initial update.
if ( isServerObject() && mDataBlock && !onNewDataBlock( mDataBlock, false ) )
return false;
setProcessTick( true );
return true;
}
void GameBase::onRemove()
{
// EDITOR FEATURE: Remove us from the reload signal of our datablock.
if ( mDataBlock )
mDataBlock->mReloadSignal.remove( this, &GameBase::_onDatablockModified );
Parent::onRemove();
}
bool GameBase::onNewDataBlock( GameBaseData *dptr, bool reload )
{
// EDITOR FEATURE: Remove us from old datablock's reload signal and
// add us to the new one.
if ( !reload )
{
if ( mDataBlock )
mDataBlock->mReloadSignal.remove( this, &GameBase::_onDatablockModified );
if ( dptr )
dptr->mReloadSignal.notify( this, &GameBase::_onDatablockModified );
}
mDataBlock = dptr;
if ( !mDataBlock )
return false;
setMaskBits(DataBlockMask);
return true;
}
void GameBase::_onDatablockModified()
{
AssertFatal( mDataBlock, "GameBase::onDatablockModified - mDataBlock is NULL." );
onNewDataBlock( mDataBlock, true );
}
void GameBase::inspectPostApply()
{
Parent::inspectPostApply();
setMaskBits(ExtendedInfoMask);
}
//----------------------------------------------------------------------------
void GameBase::processTick(const Move * move)
{
#ifdef TORQUE_DEBUG_NET_MOVES
if (!move)
mTicksSinceLastMove++;
const char * srv = isClientObject() ? "client" : "server";
const char * who = "";
if (isClientObject())
{
if (this == (GameBase*)GameConnection::getConnectionToServer()->getControlObject())
who = " player";
else
who = " ghost";
if (mIsAiControlled)
who = " ai";
}
if (isServerObject())
{
if (dynamic_cast<AIConnection*>(getControllingClient()))
{
who = " ai";
mIsAiControlled = true;
}
else if (getControllingClient())
{
who = " player";
mIsAiControlled = false;
}
else
{
who = "";
mIsAiControlled = false;
}
}
U32 moveid = mLastMoveId+mTicksSinceLastMove;
if (move)
moveid = move->id;
if (getTypeMask() & GameBaseHiFiObjectType)
{
if (move)
Con::printf("Processing (%s%s id %i) move %i",srv,who,getId(), move->id);
else
Con::printf("Processing (%s%s id %i) move %i (%i)",srv,who,getId(),mLastMoveId+mTicksSinceLastMove,mTicksSinceLastMove);
}
if (move)
{
mLastMoveId = move->id;
mTicksSinceLastMove=0;
}
#endif
}
//----------------------------------------------------------------------------
F32 GameBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips)
{
TORQUE_UNUSED(updateMask);
// Calculate a priority used to decide if this object
// will be updated on the client. All the weights
// are calculated 0 -> 1 Then weighted together at the
// end to produce a priority.
Point3F pos;
getWorldBox().getCenter(&pos);
pos -= camInfo->pos;
F32 dist = pos.len();
if (dist == 0.0f) dist = 0.001f;
pos *= 1.0f / dist;
// Weight based on linear distance, the basic stuff.
F32 wDistance = (dist < camInfo->visibleDistance)?
1.0f - (dist / camInfo->visibleDistance): 0.0f;
// Weight by field of view, objects directly in front
// will be weighted 1, objects behind will be 0
F32 dot = mDot(pos,camInfo->orientation);
bool inFov = dot > camInfo->cosFov;
F32 wFov = inFov? 1.0f: 0;
// Weight by linear velocity parallel to the viewing plane
// (if it's the field of view, 0 if it's not).
F32 wVelocity = 0.0f;
if (inFov)
{
Point3F vec;
mCross(camInfo->orientation,getVelocity(),&vec);
wVelocity = (vec.len() * camInfo->fov) /
(camInfo->fov * camInfo->visibleDistance);
if (wVelocity > 1.0f)
wVelocity = 1.0f;
}
// Weight by interest.
F32 wInterest;
if (getTypeMask() & PlayerObjectType)
wInterest = 0.75f;
else if (getTypeMask() & ProjectileObjectType)
{
// Projectiles are more interesting if they
// are heading for us.
wInterest = 0.30f;
F32 dot = -mDot(pos,getVelocity());
if (dot > 0.0f)
wInterest += 0.20 * dot;
}
else
{
if (getTypeMask() & ItemObjectType)
wInterest = 0.25f;
else
// Everything else is less interesting.
wInterest = 0.0f;
}
// Weight by updateSkips
F32 wSkips = updateSkips * 0.5;
// Calculate final priority, should total to about 1.0f
//
return
wFov * sUpFov +
wDistance * sUpDistance +
wVelocity * sUpVelocity +
wSkips * sUpSkips +
wInterest * sUpInterest;
}
//----------------------------------------------------------------------------
bool GameBase::setDataBlock(GameBaseData* dptr)
{
if (isGhost() || isProperlyAdded()) {
if (mDataBlock != dptr)
return onNewDataBlock(dptr,false);
}
else
mDataBlock = dptr;
return true;
}
//--------------------------------------------------------------------------
void GameBase::scriptOnAdd()
{
// Script onAdd() must be called by the leaf class after
// everything is ready.
if (mDataBlock && !isGhost())
mDataBlock->onAdd_callback( this );
}
void GameBase::scriptOnNewDataBlock()
{
// Script onNewDataBlock() must be called by the leaf class
// after everything is loaded.
if (mDataBlock && !isGhost())
mDataBlock->onNewDataBlock_callback( this );
}
void GameBase::scriptOnRemove()
{
// Script onRemove() must be called by leaf class while
// the object state is still valid.
if (!isGhost() && mDataBlock)
mDataBlock->onRemove_callback( this );
}
//----------------------------------------------------------------------------
void GameBase::setControllingClient(GameConnection* client)
{
if (isClientObject())
{
if (mControllingClient)
setControl_callback( 0 );
if (client)
setControl_callback( 1 );
}
mControllingClient = client;
}
U32 GameBase::getPacketDataChecksum(GameConnection * connection)
{
// just write the packet data into a buffer
// then we can CRC the buffer. This should always let us
// know when there is a checksum problem.
static U8 buffer[1500] = { 0, };
BitStream stream(buffer, sizeof(buffer));
writePacketData(connection, &stream);
U32 byteCount = stream.getPosition();
U32 ret = CRC::calculateCRC(buffer, byteCount, 0xFFFFFFFF);
dMemset(buffer, 0, byteCount);
return ret;
}
void GameBase::writePacketData(GameConnection*, BitStream*)
{
}
void GameBase::readPacketData(GameConnection*, BitStream*)
{
}
U32 GameBase::packUpdate( NetConnection *connection, U32 mask, BitStream *stream )
{
U32 retMask = Parent::packUpdate( connection, mask, stream );
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 & DataBlockMask ) && mDataBlock != NULL ) )
{
stream->writeRangedU32( mDataBlock->getId(),
DataBlockObjectIdFirst,
DataBlockObjectIdLast );
if ( stream->writeFlag( mNetFlags.test( NetOrdered ) ) )
stream->writeInt( mOrderGUID, 16 );
}
#ifdef TORQUE_DEBUG_NET_MOVES
stream->write(mLastMoveId);
stream->writeFlag(mIsAiControlled);
#endif
return retMask;
}
void GameBase::unpackUpdate(NetConnection *con, BitStream *stream)
{
Parent::unpackUpdate( con, stream );
// ScaleMask
if ( stream->readFlag() )
{
if ( stream->readFlag() )
{
VectorF scale;
mathRead( *stream, &scale );
setScale( scale );
}
else
setScale( Point3F::One );
}
// DataBlockMask
if ( stream->readFlag() )
{
GameBaseData *dptr = 0;
SimObjectId id = stream->readRangedU32( DataBlockObjectIdFirst,
DataBlockObjectIdLast );
if ( stream->readFlag() )
mOrderGUID = stream->readInt( 16 );
if ( !Sim::findObject( id, dptr ) || !setDataBlock( dptr ) )
con->setLastError( "Invalid packet GameBase::unpackUpdate()" );
}
#ifdef TORQUE_DEBUG_NET_MOVES
stream->read(&mLastMoveId);
mTicksSinceLastMove = 0;
mIsAiControlled = stream->readFlag();
#endif
}
void GameBase::onMount( SceneObject *obj, S32 node )
{
deleteNotify( obj );
// Are we mounting to a GameBase object?
GameBase *gbaseObj = dynamic_cast<GameBase*>( obj );
if ( gbaseObj && gbaseObj->getControlObject() != this )
processAfter( gbaseObj );
if (!isGhost()) {
setMaskBits(MountedMask);
mDataBlock->onMount_callback( this, obj, node );
}
}
void GameBase::onUnmount( SceneObject *obj, S32 node )
{
clearNotify(obj);
GameBase *gbaseObj = dynamic_cast<GameBase*>( obj );
if ( gbaseObj && gbaseObj->getControlObject() != this )
clearProcessAfter();
if (!isGhost()) {
setMaskBits(MountedMask);
mDataBlock->onUnmount_callback( this, obj, node );
}
}
bool GameBase::setDataBlockProperty( void *obj, const char *index, const char *db)
{
if( db == NULL || !db || !db[ 0 ] )
{
Con::errorf( "GameBase::setDataBlockProperty - Can't unset datablock on GameBase objects" );
return false;
}
GameBase* object = static_cast< GameBase* >( obj );
GameBaseData* data;
if( Sim::findObject( db, data ) )
return object->setDataBlock( data );
Con::errorf( "GameBase::setDatablockProperty - Could not find data block \"%s\"", db );
return false;
}
MoveList* GameBase::getMoveList()
{
return mControllingClient ? mControllingClient->mMoveList : NULL;
}
//----------------------------------------------------------------------------
DefineEngineMethod( GameBase, getDataBlock, S32, (),,
"@brief Get the datablock used by this object.\n\n"
"@return the datablock this GameBase is using."
"@see setDataBlock()\n")
{
return object->getDataBlock()? object->getDataBlock()->getId(): 0;
}
//----------------------------------------------------------------------------
DefineEngineMethod( GameBase, setDataBlock, bool, ( GameBaseData* data ),,
"@brief Assign this GameBase to use the specified datablock.\n\n"
"@param data new datablock to use\n"
"@return true if successful, false if failed."
"@see getDataBlock()\n")
{
return ( data && object->setDataBlock(data) );
}
//----------------------------------------------------------------------------
void GameBase::initPersistFields()
{
addGroup( "Game" );
addProtectedField( "dataBlock", TYPEID< GameBaseData >(), Offset(mDataBlock, GameBase),
&setDataBlockProperty, &defaultProtectedGetFn,
"Script datablock used for game objects." );
endGroup( "Game" );
Parent::initPersistFields();
}
void GameBase::consoleInit()
{
#ifdef TORQUE_DEBUG
Con::addVariable( "GameBase::boundingBox", TypeBool, &gShowBoundingBox,
"@brief Toggles on the rendering of the bounding boxes for certain types of objects in scene.\n\n"
"@ingroup GameBase" );
#endif
}
DefineEngineMethod( GameBase, applyImpulse, bool, ( Point3F pos, VectorF vel ),,
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
"@param pos impulse world position\n"
"@param vel impulse velocity (impulse force F = m * v)\n"
"@return Always true\n"
"@note Not all objects that derrive from GameBase have this defined.\n")
{
object->applyImpulse(pos,vel);
return true;
}
DefineEngineMethod( GameBase, applyRadialImpulse, void, ( Point3F origin, F32 radius, F32 magnitude ),,
"@brief Applies a radial impulse to the object using the given origin and force.\n\n"
"@param origin World point of origin of the radial impulse.\n"
"@param radius The radius of the impulse area.\n"
"@param magnitude The strength of the impulse.\n"
"@note Not all objects that derrive from GameBase have this defined.\n")
{
object->applyRadialImpulse( origin, radius, magnitude );
}

View file

@ -0,0 +1,451 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GAMEBASE_H_
#define _GAMEBASE_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _PROCESSLIST_H_
#include "T3D/gameBase/processList.h"
#endif
#ifndef _TICKCACHE_H_
#include "T3D/gameBase/tickCache.h"
#endif
#ifndef _DYNAMIC_CONSOLETYPES_H_
#include "console/dynamicTypes.h"
#endif
class NetConnection;
class ProcessList;
class GameBase;
struct Move;
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
/// Scriptable, demo-able datablock.
///
/// This variant of SimDataBlock performs these additional tasks:
/// - Linking datablock's namepsaces to the namespace of their C++ class, so
/// that datablocks can expose script functionality.
/// - Linking datablocks to a user defined scripting namespace, by setting the
/// 'class' field at datablock definition time.
/// - Adds a category field; this is used by the world creator in the editor to
/// classify creatable shapes. Creatable shapes are placed under the Shapes
/// node in the treeview for this; additional levels are created, named after
/// the category fields.
/// - Adds support for demo stream recording. This support takes the form
/// of the member variable packed. When a demo is being recorded by a client,
/// data is unpacked, then packed again to the data stream, then, in the case
/// of datablocks, preload() is called to process the data. It is occasionally
/// the case that certain references in the datablock stream cannot be resolved
/// until preload is called, in which case a raw ID field is stored in the variable
/// which will eventually be used to store a pointer to the object. However, if
/// packData() is called before we resolve this ID, trying to call getID() on the
/// objecct ID would be a fatal error. Therefore, in these cases, we test packed;
/// if it is true, then we know we have to write the raw data, instead of trying
/// to resolve an ID.
///
/// @see SimDataBlock for further details about datablocks.
/// @see http://hosted.tribalwar.com/t2faq/datablocks.shtml for an excellent
/// explanation of the basics of datablocks from a scripting perspective.
/// @nosubgrouping
struct GameBaseData : public SimDataBlock
{
private:
typedef SimDataBlock Parent;
public:
bool packed;
StringTableEntry category;
// Signal triggered when this datablock is modified.
// GameBase objects referencing this datablock notify with this signal.
Signal<void(void)> mReloadSignal;
// Triggers the reload signal.
void inspectPostApply();
bool onAdd();
// The derived class should provide the following:
DECLARE_CONOBJECT(GameBaseData);
GameBaseData();
static void initPersistFields();
bool preload(bool server, String &errorStr);
void unpackData(BitStream* stream);
/// @name Callbacks
/// @{
DECLARE_CALLBACK( void, onAdd, ( GameBase* obj ) );
DECLARE_CALLBACK( void, onRemove, ( GameBase* obj ) );
DECLARE_CALLBACK( void, onNewDataBlock, ( GameBase* obj ) );
DECLARE_CALLBACK( void, onMount, ( GameBase* obj, SceneObject* mountObj, S32 node ) );
DECLARE_CALLBACK( void, onUnmount, ( GameBase* obj, SceneObject* mountObj, S32 node ) );
/// @}
};
//----------------------------------------------------------------------------
// A few utility methods for sending datablocks over the net
//----------------------------------------------------------------------------
bool UNPACK_DB_ID(BitStream *, U32 & id);
bool PACK_DB_ID(BitStream *, U32 id);
bool PRELOAD_DB(U32 & id, SimDataBlock **, bool server, const char * clientMissing = NULL, const char * serverMissing = NULL);
//----------------------------------------------------------------------------
class GameConnection;
class WaterObject;
class MoveList;
// For truly it is written: "The wise man extends GameBase for his purposes,
// while the fool has the ability to eject shell casings from the belly of his
// dragon." -- KillerBunny
/// Base class for game objects which use datablocks, networking, are editable,
/// and need to process ticks.
///
/// @section GameBase_process GameBase and ProcessList
///
/// GameBase adds two kinds of time-based updates. Torque works off of a concept
/// of ticks. Ticks are slices of time 32 milliseconds in length. There are three
/// methods which are used to update GameBase objects that are registered with
/// the ProcessLists:
/// - processTick(Move*) is called on each object once for every tick, regardless
/// of the "real" framerate.
/// - interpolateTick(float) is called on client objects when they need to interpolate
/// to match the next tick.
/// - advanceTime(float) is called on client objects so they can do time-based behaviour,
/// like updating animations.
///
/// Torque maintains a server and a client processing list; in a local game, both
/// are populated, while in multiplayer situations, either one or the other is
/// populated.
///
/// You can control whether an object is considered for ticking by means of the
/// setProcessTick() method.
///
/// @section GameBase_datablock GameBase and Datablocks
///
/// GameBase adds support for datablocks. Datablocks are secondary classes which store
/// static data for types of game elements. For instance, this means that all "light human
/// male armor" type Players share the same datablock. Datablocks typically store not only
/// raw data, but perform precalculations, like finding nodes in the game model, or
/// validating movement parameters.
///
/// There are three parts to the datablock interface implemented in GameBase:
/// - <b>getDataBlock()</b>, which gets a pointer to the current datablock. This is
/// mostly for external use; for in-class use, it's better to directly access the
/// mDataBlock member.
/// - <b>setDataBlock()</b>, which sets mDataBlock to point to a new datablock; it
/// uses the next part of the interface to inform subclasses of this.
/// - <b>onNewDataBlock()</b> is called whenever a new datablock is assigned to a GameBase.
///
/// Datablocks are also usable through the scripting language.
///
/// @see SimDataBlock for more details.
///
/// @section GameBase_networking GameBase and Networking
///
/// writePacketData() and readPacketData() are called to transfer information needed for client
/// side prediction. They are usually used when updating a client of its control object state.
///
/// Subclasses of GameBase usually transmit positional and basic status data in the packUpdate()
/// functions, while giving velocity, momentum, and similar state information in the writePacketData().
///
/// writePacketData()/readPacketData() are called <i>in addition</i> to packUpdate/unpackUpdate().
///
/// @nosubgrouping
class GameBase : public SceneObject
{
typedef SceneObject Parent;
/// @name Datablock
/// @{
GameBaseData* mDataBlock;
/// @}
TickCache mTickCache;
// Control interface
GameConnection* mControllingClient;
public:
static bool gShowBoundingBox; ///< Should we render bounding boxes?
protected:
F32 mCameraFov;
/// The WaterObject we are currently within.
WaterObject *mCurrentWaterObject;
static bool setDataBlockProperty( void *object, const char *index, const char *data );
#ifdef TORQUE_DEBUG_NET_MOVES
U32 mLastMoveId;
U32 mTicksSinceLastMove;
bool mIsAiControlled;
#endif
public:
GameBase();
~GameBase();
enum GameBaseMasks {
DataBlockMask = Parent::NextFreeMask << 0,
ExtendedInfoMask = Parent::NextFreeMask << 1,
NextFreeMask = Parent::NextFreeMask << 2
};
// net flags added by game base
enum
{
NetOrdered = BIT(Parent::MaxNetFlagBit+1), /// Process in same order on client and server.
NetNearbyAdded = BIT(Parent::MaxNetFlagBit+2), /// Is set during client catchup when neighbors have been checked.
GhostUpdated = BIT(Parent::MaxNetFlagBit+3), /// Is set whenever ghost updated (and reset) on the client, for hifi objects.
TickLast = BIT(Parent::MaxNetFlagBit+4), /// Tick this object after all others.
NewGhost = BIT(Parent::MaxNetFlagBit+5), /// This ghost was just added during the last update.
HiFiPassive = BIT(Parent::MaxNetFlagBit+6), /// Do not interact with other hifi passive objects.
MaxNetFlagBit = Parent::MaxNetFlagBit+6
};
/// @name Inherited Functionality.
/// @{
bool onAdd();
void onRemove();
void inspectPostApply();
static void initPersistFields();
static void consoleInit();
/// @}
///@name Datablock
///@{
/// Assigns this object a datablock and loads attributes with onNewDataBlock.
///
/// @see onNewDataBlock
/// @param dptr Datablock
bool setDataBlock( GameBaseData *dptr );
/// Returns the datablock for this object.
GameBaseData* getDataBlock() { return mDataBlock; }
/// Called when a new datablock is set. This allows subclasses to
/// appropriately handle new datablocks.
///
/// @see setDataBlock()
/// @param dptr New datablock
/// @param reload Is this a new datablock or are we reloading one
/// we already had.
virtual bool onNewDataBlock( GameBaseData *dptr, bool reload );
///@}
/// @name Script
/// The scriptOnXX methods are invoked by the leaf classes
/// @{
/// Executes the 'onAdd' script function for this object.
/// @note This must be called after everything is ready
void scriptOnAdd();
/// Executes the 'onNewDataBlock' script function for this object.
///
/// @note This must be called after everything is loaded.
void scriptOnNewDataBlock();
/// Executes the 'onRemove' script function for this object.
/// @note This must be called while the object is still valid
void scriptOnRemove();
/// @}
// ProcessObject override
void processTick( const Move *move );
/// @name GameBase NetFlags & Hifi-Net Interface
/// @{
/// Set or clear the GhostUpdated bit in our NetFlags.
/// @see GhostUpdated
void setGhostUpdated( bool b ) { if (b) mNetFlags.set(GhostUpdated); else mNetFlags.clear(GhostUpdated); }
/// Returns true if the GhostUpdated bit in our NetFlags is set.
/// @see GhostUpdated
bool isGhostUpdated() const { return mNetFlags.test(GhostUpdated); }
/// Set or clear the NewGhost bit in our NetFlags.
/// @see NewGhost
void setNewGhost( bool n ) { if (n) mNetFlags.set(NewGhost); else mNetFlags.clear(NewGhost); }
/// Returns true if the NewGhost bit in out NetFlags is set.
/// @see NewGhost
bool isNewGhost() const { return mNetFlags.test(NewGhost); }
/// Set or clear the NetNearbyAdded bit in our NetFlags.
/// @see NetNearbyAdded
void setNetNearbyAdded( bool b ) { if (b) mNetFlags.set(NetNearbyAdded); else mNetFlags.clear(NetNearbyAdded); }
/// Returns true if the NetNearby bit in our NetFlags is set.
/// @see NetNearbyAdded
bool isNetNearbyAdded() const { return mNetFlags.test(NetNearbyAdded); }
/// Returns true if the HiFiPassive bit in our NetFlags is set.
/// @see HiFiPassive
bool isHifiPassive() const { return mNetFlags.test(HiFiPassive); }
/// Returns true if the TickLast bit in our NetFlags is set.
/// @see TickLast
bool isTickLast() const { return mNetFlags.test(TickLast); }
/// Returns true if the NetOrdered bit in our NetFlags is set.
/// @see NetOrdered
bool isNetOrdered() const { return mNetFlags.test(NetOrdered); }
/// Called during client catchup under the hifi-net model.
virtual void computeNetSmooth( F32 backDelta ) {}
/// Returns TickCache used under the hifi-net model.
TickCache& getTickCache() { return mTickCache; }
/// @}
/// @name Network
/// @see NetObject, NetConnection
/// @{
F32 getUpdatePriority( CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips );
U32 packUpdate ( NetConnection *conn, U32 mask, BitStream *stream );
void unpackUpdate( NetConnection *conn, BitStream *stream );
/// Write state information necessary to perform client side prediction of an object.
///
/// This information is sent only to the controlling object. For example, if you are a client
/// controlling a Player, the server uses writePacketData() instead of packUpdate() to
/// generate the data you receive.
///
/// @param conn Connection for which we're generating this data.
/// @param stream Bitstream for output.
virtual void writePacketData( GameConnection *conn, BitStream *stream );
/// Read data written with writePacketData() and update the object state.
///
/// @param conn Connection for which we're generating this data.
/// @param stream Bitstream to read.
virtual void readPacketData( GameConnection *conn, BitStream *stream );
/// Gets the checksum for packet data.
///
/// Basically writes a packet, does a CRC check on it, and returns
/// that CRC.
///
/// @see writePacketData
/// @param conn Game connection
virtual U32 getPacketDataChecksum( GameConnection *conn );
///@}
/// @name Mounted objects ( overrides )
/// @{
public:
virtual void onMount( SceneObject *obj, S32 node );
virtual void onUnmount( SceneObject *obj,S32 node );
/// @}
/// @name User control
/// @{
/// Returns the client controlling this object
GameConnection *getControllingClient() { return mControllingClient; }
const GameConnection *getControllingClient() const { return mControllingClient; }
/// Returns the MoveList of the client controlling this object.
/// If there is no client it returns NULL;
MoveList* getMoveList();
/// Sets the client controlling this object
/// @param client Client that is now controlling this object
virtual void setControllingClient( GameConnection *client );
virtual GameBase * getControllingObject() { return NULL; }
virtual GameBase * getControlObject() { return NULL; }
virtual void setControlObject( GameBase * ) { }
/// @}
virtual F32 getDefaultCameraFov() { return 90.f; }
virtual F32 getCameraFov() { return 90.f; }
virtual void setCameraFov( F32 fov ) { }
virtual bool isValidCameraFov( F32 fov ) { return true; }
virtual bool useObjsEyePoint() const { return false; }
virtual bool onlyFirstPerson() const { return false; }
virtual F32 getDamageFlash() const { return 1.0f; }
virtual F32 getWhiteOut() const { return 1.0f; }
// Not implemented here, but should return the Camera to world transformation matrix
virtual void getCameraTransform (F32 *pos, MatrixF *mat ) { *mat = MatrixF::Identity; }
/// Returns the water object we are colliding with, it is up to derived
/// classes to actually set this object.
virtual WaterObject* getCurrentWaterObject() { return mCurrentWaterObject; }
#ifdef TORQUE_DEBUG_NET_MOVES
bool isAIControlled() const { return mIsAiControlled; }
#endif
DECLARE_CONOBJECT (GameBase );
/// @name Callbacks
/// @{
DECLARE_CALLBACK( void, setControl, ( bool controlled ) );
/// @}
private:
/// This is called by the reload signal in our datablock when it is
/// modified in the editor.
///
/// This method is private and is not virtual. To handle a datablock-modified
/// even in a child-class specific way you should override onNewDatablock
/// and handle the reload( true ) case.
///
/// Warning: For local-client, editor situations only.
///
/// Warning: Do not attempt to call .remove or .notify on mDataBlock->mReloadSignal
/// within this callback.
///
void _onDatablockModified();
};
#endif // _GAMEBASE_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,345 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GAMECONNECTION_H_
#define _GAMECONNECTION_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _GAMEBASE_H_
#include "T3D/gameBase/gameBase.h"
#endif
#ifndef _NETCONNECTION_H_
#include "sim/netConnection.h"
#endif
#ifndef _MOVEMANAGER_H_
#include "T3D/gameBase/moveManager.h"
#endif
#ifndef _BITVECTOR_H_
#include "core/bitVector.h"
#endif
enum GameConnectionConstants
{
MaxClients = 126,
DataBlockQueueCount = 16
};
class SFXProfile;
class MatrixF;
class MatrixF;
class Point3F;
class MoveManager;
class MoveList;
struct Move;
struct AuthInfo;
#define GameString TORQUE_APP_NAME
const F32 MinCameraFov = 1.f; ///< min camera FOV
const F32 MaxCameraFov = 179.f; ///< max camera FOV
class GameConnection : public NetConnection
{
private:
typedef NetConnection Parent;
SimObjectPtr<GameBase> mControlObject;
SimObjectPtr<GameBase> mCameraObject;
U32 mDataBlockSequence;
char mDisconnectReason[256];
U32 mMissionCRC; // crc of the current mission file from the server
private:
U32 mLastControlRequestTime;
S32 mDataBlockModifiedKey;
S32 mMaxDataBlockModifiedKey;
/// @name Client side first/third person
/// @{
///
bool mFirstPerson; ///< Are we currently first person or not.
bool mUpdateFirstPerson; ///< Set to notify client or server of first person change.
bool mUpdateCameraFov; ///< Set to notify server of camera FOV change.
F32 mCameraFov; ///< Current camera fov (in degrees).
F32 mCameraPos; ///< Current camera pos (0-1).
F32 mCameraSpeed; ///< Camera in/out speed.
/// @}
public:
/// @name Protocol Versions
///
/// Protocol versions are used to indicated changes in network traffic.
/// These could be changes in how any object transmits or processes
/// network information. You can specify backwards compatibility by
/// specifying a MinRequireProtocolVersion. If the client
/// protocol is >= this min value, the connection is accepted.
///
/// Torque (V12) SDK 1.0 uses protocol = 1
///
/// Torque SDK 1.1 uses protocol = 2
/// Torque SDK 1.4 uses protocol = 12
/// @{
static const U32 CurrentProtocolVersion;
static const U32 MinRequiredProtocolVersion;
/// @}
/// Configuration
enum Constants {
BlockTypeMove = NetConnectionBlockTypeCount,
GameConnectionBlockTypeCount,
MaxConnectArgs = 16,
DataBlocksDone = NumConnectionMessages,
DataBlocksDownloadDone,
};
/// Set connection arguments; these are passed to the server when we connect.
void setConnectArgs(U32 argc, const char **argv);
/// Set the server password to use when we join.
void setJoinPassword(const char *password);
/// @name Event Handling
/// @{
virtual void onTimedOut();
virtual void onConnectTimedOut();
virtual void onDisconnect(const char *reason);
virtual void onConnectionRejected(const char *reason);
virtual void onConnectionEstablished(bool isInitiator);
virtual void handleStartupError(const char *errorString);
/// @}
/// @name Packet I/O
/// @{
virtual void writeConnectRequest(BitStream *stream);
virtual bool readConnectRequest(BitStream *stream, const char **errorString);
virtual void writeConnectAccept(BitStream *stream);
virtual bool readConnectAccept(BitStream *stream, const char **errorString);
/// @}
bool canRemoteCreate();
private:
/// @name Connection State
/// This data is set with setConnectArgs() and setJoinPassword(), and
/// sent across the wire when we connect.
/// @{
U32 mConnectArgc;
char *mConnectArgv[MaxConnectArgs];
char *mJoinPassword;
/// @}
protected:
struct GamePacketNotify : public NetConnection::PacketNotify
{
S32 cameraFov;
GamePacketNotify();
};
PacketNotify *allocNotify();
bool mControlForceMismatch;
Vector<SimDataBlock *> mDataBlockLoadList;
public:
MoveList *mMoveList;
protected:
bool mAIControlled;
AuthInfo * mAuthInfo;
static S32 mLagThresholdMS;
S32 mLastPacketTime;
bool mLagging;
/// @name Flashing
////
/// Note, these variables are not networked, they are for the local connection only.
/// @{
F32 mDamageFlash;
F32 mWhiteOut;
F32 mBlackOut;
S32 mBlackOutTimeMS;
S32 mBlackOutStartTimeMS;
bool mFadeToBlack;
/// @}
/// @name Packet I/O
/// @{
void readPacket (BitStream *bstream);
void writePacket (BitStream *bstream, PacketNotify *note);
void packetReceived (PacketNotify *note);
void packetDropped (PacketNotify *note);
void connectionError (const char *errorString);
void writeDemoStartBlock (ResizeBitStream *stream);
bool readDemoStartBlock (BitStream *stream);
void handleRecordedBlock (U32 type, U32 size, void *data);
/// @}
void ghostWriteExtra(NetObject *,BitStream *);
void ghostReadExtra(NetObject *,BitStream *, bool newGhost);
void ghostPreRead(NetObject *, bool newGhost);
virtual void onEndGhosting();
public:
DECLARE_CONOBJECT(GameConnection);
void handleConnectionMessage(U32 message, U32 sequence, U32 ghostCount);
void preloadDataBlock(SimDataBlock *block);
void fileDownloadSegmentComplete();
void preloadNextDataBlock(bool hadNew);
static void consoleInit();
void setDisconnectReason(const char *reason);
GameConnection();
~GameConnection();
bool onAdd();
void onRemove();
static GameConnection *getConnectionToServer()
{
return dynamic_cast<GameConnection*>((NetConnection *) mServerConnection);
}
static GameConnection *getLocalClientConnection()
{
return dynamic_cast<GameConnection*>((NetConnection *) mLocalClientConnection);
}
/// @name Control object
/// @{
///
void setControlObject(GameBase *);
GameBase* getControlObject() { return mControlObject; }
const GameBase* getControlObject() const { return mControlObject; }
void setCameraObject(GameBase *);
GameBase* getCameraObject();
bool getControlCameraTransform(F32 dt,MatrixF* mat);
bool getControlCameraVelocity(Point3F *vel);
bool getControlCameraDefaultFov(F32 *fov);
bool getControlCameraFov(F32 *fov);
bool setControlCameraFov(F32 fov);
bool isValidControlCameraFov(F32 fov);
// Used by editor
bool isControlObjectRotDampedCamera();
void setFirstPerson(bool firstPerson);
/// @}
void detectLag();
/// @name Datablock management
/// @{
S32 getDataBlockModifiedKey () { return mDataBlockModifiedKey; }
void setDataBlockModifiedKey (S32 key) { mDataBlockModifiedKey = key; }
S32 getMaxDataBlockModifiedKey () { return mMaxDataBlockModifiedKey; }
void setMaxDataBlockModifiedKey (S32 key) { mMaxDataBlockModifiedKey = key; }
/// Return the datablock sequence number that this game connection is on.
/// The datablock sequence number is synchronized to the mission sequence number
/// on each datablock transmission.
U32 getDataBlockSequence() { return mDataBlockSequence; }
/// Set the datablock sequence number.
void setDataBlockSequence(U32 seq) { mDataBlockSequence = seq; }
/// @}
/// @name Fade control
/// @{
F32 getDamageFlash() const { return mDamageFlash; }
F32 getWhiteOut() const { return mWhiteOut; }
void setBlackOut(bool fadeToBlack, S32 timeMS);
F32 getBlackOut();
/// @}
/// @name Authentication
///
/// This is remnant code from Tribes 2.
/// @{
void setAuthInfo(const AuthInfo *info);
const AuthInfo *getAuthInfo();
/// @}
/// @name Sound
/// @{
void play2D(SFXProfile *profile);
void play3D(SFXProfile *profile, const MatrixF *transform);
/// @}
/// @name Misc.
/// @{
bool isFirstPerson() const { return mCameraPos == 0; }
bool isAIControlled() { return mAIControlled; }
void doneScopingScene();
void demoPlaybackComplete();
void setMissionCRC(U32 crc) { mMissionCRC = crc; }
U32 getMissionCRC() { return(mMissionCRC); }
/// @}
static Signal<void(F32)> smFovUpdate;
static Signal<void()> smPlayingDemo;
protected:
DECLARE_CALLBACK( void, onConnectionTimedOut, () );
DECLARE_CALLBACK( void, onConnectionAccepted, () );
DECLARE_CALLBACK( void, onConnectRequestTimedOut, () );
DECLARE_CALLBACK( void, onConnectionDropped, (const char* reason) );
DECLARE_CALLBACK( void, onConnectRequestRejected, (const char* reason) );
DECLARE_CALLBACK( void, onConnectionError, (const char* errorString) );
DECLARE_CALLBACK( void, onDrop, (const char* disconnectReason) );
DECLARE_CALLBACK( void, initialControlSet, () );
DECLARE_CALLBACK( void, onControlObjectChange, () );
DECLARE_CALLBACK( void, setLagIcon, (bool state) );
DECLARE_CALLBACK( void, onDataBlocksDone, (U32 sequence) );
DECLARE_CALLBACK( void, onFlash, (bool state) );
};
#endif

View file

@ -0,0 +1,382 @@
//-----------------------------------------------------------------------------
// 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 "core/dnet.h"
#include "core/stream/bitStream.h"
#include "console/consoleTypes.h"
#include "console/simBase.h"
#include "scene/pathManager.h"
#include "scene/sceneManager.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxDescription.h"
#include "app/game.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/gameBase/gameConnectionEvents.h"
#define DebugChecksum 0xF00DBAAD
//#define DEBUG_SPEW
//--------------------------------------------------------------------------
IMPLEMENT_CO_CLIENTEVENT_V1(SimDataBlockEvent);
IMPLEMENT_CO_CLIENTEVENT_V1(Sim2DAudioEvent);
IMPLEMENT_CO_CLIENTEVENT_V1(Sim3DAudioEvent);
IMPLEMENT_CO_CLIENTEVENT_V1(SetMissionCRCEvent);
ConsoleDocClass( SimDataBlockEvent,
"@brief Use by GameConnection to process incoming datablocks.\n\n"
"Not intended for game development, internal use only, but does expose onDataBlockObjectReceived.\n\n "
"@internal");
ConsoleDocClass( Sim2DAudioEvent,
"@brief Use by GameConnection to send a 2D sound event over the network.\n\n"
"Not intended for game development, internal use only, but does expose GameConnection::play2D.\n\n "
"@internal");
ConsoleDocClass( Sim3DAudioEvent,
"@brief Use by GameConnection to send a 3D sound event over the network.\n\n"
"Not intended for game development, internal use only, but does expose GameConnection::play3D.\n\n "
"@internal");
ConsoleDocClass( SetMissionCRCEvent,
"@brief Use by GameConnection to send a 3D sound event over the network.\n\n"
"Not intended for game development, internal use only, but does expose GameConnection::setMissionCRC.\n\n "
"@internal");
//----------------------------------------------------------------------------
SimDataBlockEvent::SimDataBlockEvent(SimDataBlock* obj, U32 index, U32 total, U32 missionSequence)
{
mObj = NULL;
mIndex = index;
mTotal = total;
mMissionSequence = missionSequence;
mProcess = false;
if(obj)
{
id = obj->getId();
AssertFatal(id >= DataBlockObjectIdFirst && id <= DataBlockObjectIdLast,
"Out of range event data block id... check simBase.h");
#ifdef DEBUG_SPEW
Con::printf("queuing data block: %d", mIndex);
#endif
}
}
SimDataBlockEvent::~SimDataBlockEvent()
{
if( mObj )
delete mObj;
}
#ifdef TORQUE_DEBUG_NET
const char *SimDataBlockEvent::getDebugName()
{
SimObject *obj = Sim::findObject(id);
static char buffer[256];
dSprintf(buffer, sizeof(buffer), "%s [%s - %s]",
getClassName(),
obj ? obj->getName() : "",
obj ? obj->getClassName() : "NONE");
return buffer;
}
#endif // TORQUE_DEBUG_NET
void SimDataBlockEvent::notifyDelivered(NetConnection *conn, bool )
{
// if the modified key for this event is not the current one,
// we've already resorted and resent some blocks, so fall out.
if(conn->isRemoved())
return;
GameConnection *gc = (GameConnection *) conn;
if(gc->getDataBlockSequence() != mMissionSequence)
return;
U32 nextIndex = mIndex + DataBlockQueueCount;
SimDataBlockGroup *g = Sim::getDataBlockGroup();
if(mIndex == g->size() - 1)
{
gc->setDataBlockModifiedKey(gc->getMaxDataBlockModifiedKey());
gc->sendConnectionMessage(GameConnection::DataBlocksDone, mMissionSequence);
}
if(g->size() <= nextIndex)
return;
SimDataBlock *blk = (SimDataBlock *) (*g)[nextIndex];
gc->postNetEvent(new SimDataBlockEvent(blk, nextIndex, g->size(), mMissionSequence));
}
void SimDataBlockEvent::pack(NetConnection *conn, BitStream *bstream)
{
SimDataBlock* obj;
Sim::findObject(id,obj);
GameConnection *gc = (GameConnection *) conn;
if(bstream->writeFlag(gc->getDataBlockModifiedKey() < obj->getModifiedKey()))
{
if(obj->getModifiedKey() > gc->getMaxDataBlockModifiedKey())
gc->setMaxDataBlockModifiedKey(obj->getModifiedKey());
AssertFatal(obj,
"SimDataBlockEvent:: Data blocks cannot be deleted");
bstream->writeInt(id - DataBlockObjectIdFirst,DataBlockObjectIdBitSize);
S32 classId = obj->getClassId(conn->getNetClassGroup());
bstream->writeClassId(classId, NetClassTypeDataBlock, conn->getNetClassGroup());
bstream->writeInt(mIndex, DataBlockObjectIdBitSize);
bstream->writeInt(mTotal, DataBlockObjectIdBitSize + 1);
obj->packData(bstream);
#ifdef TORQUE_DEBUG_NET
bstream->writeInt(classId ^ DebugChecksum, 32);
#endif
}
}
void SimDataBlockEvent::unpack(NetConnection *cptr, BitStream *bstream)
{
if(bstream->readFlag())
{
mProcess = true;
id = bstream->readInt(DataBlockObjectIdBitSize) + DataBlockObjectIdFirst;
S32 classId = bstream->readClassId(NetClassTypeDataBlock, cptr->getNetClassGroup());
mIndex = bstream->readInt(DataBlockObjectIdBitSize);
mTotal = bstream->readInt(DataBlockObjectIdBitSize + 1);
SimObject* ptr;
if( Sim::findObject( id, ptr ) )
{
// An object with the given ID already exists. Make sure it has the right class.
AbstractClassRep* classRep = AbstractClassRep::findClassRep( cptr->getNetClassGroup(), NetClassTypeDataBlock, classId );
if( classRep && dStrcmp( classRep->getClassName(), ptr->getClassName() ) != 0 )
{
Con::warnf( "A '%s' datablock with id: %d already existed. "
"Clobbering it with new '%s' datablock from server.",
ptr->getClassName(), id, classRep->getClassName() );
ptr->deleteObject();
ptr = NULL;
}
}
if( !ptr )
ptr = ( SimObject* ) ConsoleObject::create( cptr->getNetClassGroup(), NetClassTypeDataBlock, classId );
mObj = dynamic_cast< SimDataBlock* >( ptr );
if( mObj != NULL )
{
#ifdef DEBUG_SPEW
Con::printf(" - SimDataBlockEvent: unpacking event of type: %s", mObj->getClassName());
#endif
mObj->unpackData( bstream );
}
else
{
#ifdef DEBUG_SPEW
Con::printf(" - SimDataBlockEvent: INVALID PACKET! Could not create class with classID: %d", classId);
#endif
delete ptr;
cptr->setLastError("Invalid packet in SimDataBlockEvent::unpack()");
}
#ifdef TORQUE_DEBUG_NET
U32 checksum = bstream->readInt(32);
AssertISV( (checksum ^ DebugChecksum) == (U32)classId,
avar("unpack did not match pack for event of class %s.",
mObj->getClassName()) );
#endif
}
}
void SimDataBlockEvent::write(NetConnection *cptr, BitStream *bstream)
{
if(bstream->writeFlag(mProcess))
{
bstream->writeInt(id - DataBlockObjectIdFirst,DataBlockObjectIdBitSize);
S32 classId = mObj->getClassId(cptr->getNetClassGroup());
bstream->writeClassId(classId, NetClassTypeDataBlock, cptr->getNetClassGroup());
bstream->writeInt(mIndex, DataBlockObjectIdBitSize);
bstream->writeInt(mTotal, DataBlockObjectIdBitSize + 1);
mObj->packData(bstream);
}
}
void SimDataBlockEvent::process(NetConnection *cptr)
{
if(mProcess)
{
//call the console function to set the number of blocks to be sent
Con::executef("onDataBlockObjectReceived", Con::getIntArg(mIndex), Con::getIntArg(mTotal));
String &errorBuffer = NetConnection::getErrorBuffer();
// Register the datablock object if this is a new DB
// and not for a modified datablock event.
if( !mObj->isProperlyAdded() )
{
// This is a fresh datablock object.
// Perform preload on datablock and register
// the object.
GameConnection* conn = dynamic_cast< GameConnection* >( cptr );
if( conn )
conn->preloadDataBlock( mObj );
if( mObj->registerObject(id) )
{
cptr->addObject( mObj );
mObj = NULL;
}
}
else
{
// This is an update to an existing datablock. Preload
// to finish this.
mObj->preload( false, errorBuffer );
mObj = NULL;
}
}
}
//----------------------------------------------------------------------------
Sim2DAudioEvent::Sim2DAudioEvent(SFXProfile *profile)
{
mProfile = profile;
}
void Sim2DAudioEvent::pack(NetConnection *, BitStream *bstream)
{
bstream->writeInt( mProfile->getId() - DataBlockObjectIdFirst, DataBlockObjectIdBitSize);
}
void Sim2DAudioEvent::write(NetConnection *, BitStream *bstream)
{
bstream->writeInt( mProfile->getId() - DataBlockObjectIdFirst, DataBlockObjectIdBitSize);
}
void Sim2DAudioEvent::unpack(NetConnection *, BitStream *bstream)
{
SimObjectId id = bstream->readInt(DataBlockObjectIdBitSize) + DataBlockObjectIdFirst;
Sim::findObject(id, mProfile);
}
void Sim2DAudioEvent::process(NetConnection *)
{
if (mProfile)
SFX->playOnce( mProfile );
}
//----------------------------------------------------------------------------
static F32 SoundPosAccuracy = 0.5;
static S32 SoundRotBits = 8;
Sim3DAudioEvent::Sim3DAudioEvent(SFXProfile *profile,const MatrixF* mat)
{
mProfile = profile;
if (mat)
mTransform = *mat;
}
void Sim3DAudioEvent::pack(NetConnection *con, BitStream *bstream)
{
bstream->writeInt(mProfile->getId() - DataBlockObjectIdFirst, DataBlockObjectIdBitSize);
// If the sound has cone parameters, the orientation is
// transmitted as well.
SFXDescription* ad = mProfile->getDescription();
if ( bstream->writeFlag( ad->mConeInsideAngle || ad->mConeOutsideAngle ) )
{
QuatF q(mTransform);
q.normalize();
// LH - we can get a valid quat that's very slightly over 1 in and so
// this fails (barely) check against zero. So use some error-
AssertFatal((1.0 - ((q.x * q.x) + (q.y * q.y) + (q.z * q.z))) >= (0.0 - 0.001),
"QuatF::normalize() is broken in Sim3DAudioEvent");
bstream->writeFloat(q.x,SoundRotBits);
bstream->writeFloat(q.y,SoundRotBits);
bstream->writeFloat(q.z,SoundRotBits);
bstream->writeFlag(q.w < 0.0);
}
Point3F pos;
mTransform.getColumn(3,&pos);
bstream->writeCompressedPoint(pos,SoundPosAccuracy);
}
void Sim3DAudioEvent::write(NetConnection *con, BitStream *bstream)
{
// Just do the normal pack...
pack(con,bstream);
}
void Sim3DAudioEvent::unpack(NetConnection *con, BitStream *bstream)
{
SimObjectId id = bstream->readInt(DataBlockObjectIdBitSize) + DataBlockObjectIdFirst;
Sim::findObject(id, mProfile);
if (bstream->readFlag()) {
QuatF q;
q.x = bstream->readFloat(SoundRotBits);
q.y = bstream->readFloat(SoundRotBits);
q.z = bstream->readFloat(SoundRotBits);
F32 value = ((q.x * q.x) + (q.y * q.y) + (q.z * q.z));
// #ifdef __linux
// Hmm, this should never happen, but it does...
if ( value > 1.f )
value = 1.f;
// #endif
q.w = mSqrt(1.f - value);
if (bstream->readFlag())
q.w = -q.w;
q.setMatrix(&mTransform);
}
else
mTransform.identity();
Point3F pos;
bstream->readCompressedPoint(&pos,SoundPosAccuracy);
mTransform.setColumn(3, pos);
}
void Sim3DAudioEvent::process(NetConnection *)
{
if (mProfile)
SFX->playOnce( mProfile, &mTransform );
}

View file

@ -0,0 +1,161 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GAMECONNECTIONEVENTS_H_
#define _GAMECONNECTIONEVENTS_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _GAMECONNECTION_H_
#include "T3D/gameBase/gameConnection.h"
#endif
#ifndef _SFXPROFILE_H_
#include "sfx/sfxProfile.h"
#endif
#ifndef _BITSTREAM_H_
#include "core/stream/bitStream.h"
#endif
class QuitEvent : public SimEvent
{
void process(SimObject *object)
{
Platform::postQuitMessage(0);
}
};
/// Event for sending a datablock over the net from the server to the client.
///
/// Datablock events are GuaranteedOrdered client events.
///
class SimDataBlockEvent : public NetEvent
{
public:
typedef NetEvent Parent;
protected:
/// Id of the datablock object to be sent. This must be a datablock ID
/// (as opposed to a normal object ID).
SimObjectId id;
///
U32 mIndex;
/// Total number of datablocks that are part of this datablock transmission.
/// Each datablock is transmitted in an independent datablock event.
U32 mTotal;
/// The mission sequence number to which this datablock transmission
/// belongs.
///
/// @see GameConnection::getDataBlockSequence
U32 mMissionSequence;
/// Datablock object constructed on the client side.
SimDataBlock *mObj;
///
bool mProcess;
public:
SimDataBlockEvent(SimDataBlock* obj = NULL, U32 index = 0, U32 total = 0, U32 missionSequence = 0);
~SimDataBlockEvent();
void pack(NetConnection *, BitStream *bstream);
void write(NetConnection *, BitStream *bstream);
void unpack(NetConnection *cptr, BitStream *bstream);
void process(NetConnection*);
void notifyDelivered(NetConnection *, bool);
#ifdef TORQUE_DEBUG_NET
const char *getDebugName();
#endif
DECLARE_CONOBJECT( SimDataBlockEvent );
DECLARE_CATEGORY( "Game Networking" );
};
class Sim2DAudioEvent: public NetEvent
{
private:
SFXProfile *mProfile;
public:
typedef NetEvent Parent;
Sim2DAudioEvent(SFXProfile *profile=NULL);
void pack(NetConnection *, BitStream *bstream);
void write(NetConnection *, BitStream *bstream);
void unpack(NetConnection *, BitStream *bstream);
void process(NetConnection *);
DECLARE_CONOBJECT(Sim2DAudioEvent);
};
class Sim3DAudioEvent: public NetEvent
{
private:
SFXProfile *mProfile;
MatrixF mTransform;
public:
typedef NetEvent Parent;
Sim3DAudioEvent(SFXProfile *profile=NULL,const MatrixF* mat=NULL);
void pack(NetConnection *, BitStream *bstream);
void write(NetConnection *, BitStream *bstream);
void unpack(NetConnection *, BitStream *bstream);
void process(NetConnection *);
DECLARE_CONOBJECT(Sim3DAudioEvent);
};
//----------------------------------------------------------------------------
// used to set the crc for the current mission (mission lighting)
//----------------------------------------------------------------------------
class SetMissionCRCEvent : public NetEvent
{
private:
U32 mCrc;
public:
typedef NetEvent Parent;
SetMissionCRCEvent(U32 crc = 0xffffffff)
{ mCrc = crc; }
void pack(NetConnection *, BitStream * bstream)
{ bstream->write(mCrc); }
void write(NetConnection * con, BitStream * bstream)
{ pack(con, bstream); }
void unpack(NetConnection *, BitStream * bstream)
{ bstream->read(&mCrc); }
void process(NetConnection * con)
{ static_cast<GameConnection*>(con)->setMissionCRC(mCrc); }
DECLARE_CONOBJECT(SetMissionCRCEvent);
};
#endif

View file

@ -0,0 +1,184 @@
//-----------------------------------------------------------------------------
// 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/gameBase/gameProcess.h"
#include "T3D/gameBase/gameBase.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/gameBase/moveList.h"
//----------------------------------------------------------------------------
ClientProcessList* ClientProcessList::smClientProcessList = NULL;
ServerProcessList* ServerProcessList::smServerProcessList = NULL;
static U32 gNetOrderNextId = 0;
ConsoleFunction( dumpProcessList, void, 1, 1,
"Dumps all ProcessObjects in ServerProcessList and ClientProcessList to the console." )
{
Con::printf( "client process list:" );
ClientProcessList::get()->dumpToConsole();
Con::printf( "server process list:" );
ServerProcessList::get()->dumpToConsole();
}
//--------------------------------------------------------------------------
// ClientProcessList
//--------------------------------------------------------------------------
ClientProcessList::ClientProcessList()
{
}
void ClientProcessList::addObject( ProcessObject *pobj )
{
AssertFatal( static_cast<SceneObject*>( pobj )->isClientObject(), "Tried to add server object to ClientProcessList." );
GameBase *obj = getGameBase( pobj );
if ( obj && obj->isNetOrdered() )
{
if ( ( gNetOrderNextId & 0xFFFF ) == 0 )
// don't let it be zero
gNetOrderNextId++;
pobj->mOrderGUID = ( gNetOrderNextId++ ) & 0xFFFF; // 16 bits should be enough
pobj->plLinkBefore( &mHead );
mDirty = true;
}
else if ( obj && obj->isTickLast() )
{
pobj->mOrderGUID = 0xFFFFFFFF;
pobj->plLinkBefore( &mHead );
// not dirty
}
else
{
pobj->plLinkAfter( &mHead );
// not dirty
}
}
bool ClientProcessList::doBacklogged( SimTime timeDelta )
{
#ifdef TORQUE_DEBUG
static bool backlogged = false;
static U32 backloggedTime = 0;
#endif
// See if the control object has pending moves.
GameConnection *connection = GameConnection::getConnectionToServer();
if ( connection )
{
// If the connection to the server is backlogged
// the simulation is frozen.
if ( connection->mMoveList->isBacklogged() )
{
#ifdef TORQUE_DEBUG
if ( !backlogged )
{
Con::printf( "client is backlogged, time is frozen" );
backlogged = true;
}
backloggedTime += timeDelta;
#endif
return true;
}
}
#ifdef TORQUE_DEBUG
if ( backlogged )
{
Con::printf( "client is no longer backlogged, time is unfrozen (%i ms elapsed)", backloggedTime );
backlogged = false;
backloggedTime = 0;
}
#endif
return false;
}
void ClientProcessList::onPreTickObject( ProcessObject *pobj )
{
// reset to tick boundary
pobj->interpolateTick( 0.0f );
}
//--------------------------------------------------------------------------
// ServerProcessList
//--------------------------------------------------------------------------
ServerProcessList::ServerProcessList()
{
}
void ServerProcessList::addObject( ProcessObject *pobj )
{
AssertFatal( static_cast<SceneObject*>( pobj )->isServerObject(), "Tried to add client object to ServerProcessList." );
GameBase *obj = getGameBase( pobj );
if ( obj && obj->isNetOrdered() )
{
if ( ( gNetOrderNextId & 0xFFFF ) == 0)
// don't let it be zero
gNetOrderNextId++;
pobj->mOrderGUID = ( gNetOrderNextId++ ) & 0xFFFF; // 16 bits should be enough
pobj->plLinkBefore( &mHead );
mDirty = true;
}
else if ( obj && obj->isTickLast() )
{
pobj->mOrderGUID = 0xFFFFFFFF;
pobj->plLinkBefore( &mHead );
// not dirty
}
else
{
pobj->plLinkAfter( &mHead );
// not dirty
}
}
void ServerProcessList::advanceObjects()
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("Advance server time...");
#endif
Parent::advanceObjects();
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("---------");
#endif
}
void ServerProcessList::onPreTickObject( ProcessObject *pobj )
{
}

View file

@ -0,0 +1,96 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GAMEPROCESS_H_
#define _GAMEPROCESS_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _PROCESSLIST_H_
#include "T3D/gameBase/processList.h"
#endif
class GameBase;
class GameConnection;
struct Move;
class ClientProcessList : public ProcessList
{
typedef ProcessList Parent;
public:
ClientProcessList();
// ProcessList
void addObject( ProcessObject *pobj );
/// Called after a correction packet is received from the server.
/// If the control object was corrected it will now play back any moves
/// which were rolled back.
virtual void clientCatchup( GameConnection *conn ) {}
static ClientProcessList* get() { return smClientProcessList; }
protected:
// ProcessList
void onPreTickObject( ProcessObject *pobj );
/// Returns true if backlogged.
bool doBacklogged( SimTime timeDelta );
protected:
static ClientProcessList* smClientProcessList;
};
class ServerProcessList : public ProcessList
{
typedef ProcessList Parent;
public:
ServerProcessList();
// ProcessList
void addObject( ProcessObject *pobj );
static ServerProcessList* get() { return smServerProcessList; }
protected:
// ProcessList
void onPreTickObject( ProcessObject *pobj );
void advanceObjects();
protected:
static ServerProcessList* smServerProcessList;
};
#endif // _GAMEPROCESS_H_

View file

@ -0,0 +1,607 @@
//-----------------------------------------------------------------------------
// 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/gameBase/hifi/hifiGameProcess.h"
#include "platform/profiler.h"
#include "core/frameAllocator.h"
#include "core/stream/bitStream.h"
#include "math/mathUtils.h"
#include "T3D/gameBase/hifi/hifiMoveList.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/gameFunctions.h"
MODULE_BEGIN( ProcessList )
MODULE_INIT
{
HifiServerProcessList::init();
HifiClientProcessList::init();
}
MODULE_SHUTDOWN
{
HifiServerProcessList::shutdown();
HifiClientProcessList::shutdown();
}
MODULE_END;
void HifiServerProcessList::init()
{
smServerProcessList = new HifiServerProcessList();
}
void HifiServerProcessList::shutdown()
{
delete smServerProcessList;
}
void HifiClientProcessList::init()
{
smClientProcessList = new HifiClientProcessList();
}
void HifiClientProcessList::shutdown()
{
delete smClientProcessList;
}
//----------------------------------------------------------------------------
F32 gMaxHiFiVelSq = 100 * 100;
namespace
{
inline GameBase * GetGameBase(ProcessObject * obj)
{
return static_cast<GameBase*>(obj);
}
// local work class
struct GameBaseListNode
{
GameBaseListNode()
{
mPrev=this;
mNext=this;
mObject=NULL;
}
GameBaseListNode * mPrev;
GameBaseListNode * mNext;
GameBase * mObject;
void linkBefore(GameBaseListNode * obj)
{
// Link this before obj
mNext = obj;
mPrev = obj->mPrev;
obj->mPrev = this;
mPrev->mNext = this;
}
};
// Structure used for synchronizing move lists on client/server
struct MoveSync
{
enum { ActionCount = 4 };
S32 moveDiff;
S32 moveDiffSteadyCount;
S32 moveDiffSameSignCount;
bool doAction() { return moveDiffSteadyCount>=ActionCount || moveDiffSameSignCount>=4*ActionCount; }
void reset() { moveDiff=0; moveDiffSteadyCount=0; moveDiffSameSignCount=0; }
void update(S32 diff);
} moveSync;
void MoveSync::update(S32 diff)
{
if (diff && diff==moveDiff)
{
moveDiffSteadyCount++;
moveDiffSameSignCount++;
}
else if (diff*moveDiff>0)
{
moveDiffSteadyCount = 0;
moveDiffSameSignCount++;
}
else
reset();
moveDiff = diff;
}
} // namespace
//--------------------------------------------------------------------------
// HifiClientProcessList
//--------------------------------------------------------------------------
HifiClientProcessList::HifiClientProcessList()
{
mSkipAdvanceObjectsMs = 0;
mForceHifiReset = false;
mCatchup = 0;
}
bool HifiClientProcessList::advanceTime( SimTime timeDelta )
{
PROFILE_SCOPE( AdvanceClientTime );
if ( mSkipAdvanceObjectsMs && timeDelta > mSkipAdvanceObjectsMs )
{
timeDelta -= mSkipAdvanceObjectsMs;
advanceTime( mSkipAdvanceObjectsMs );
AssertFatal( !mSkipAdvanceObjectsMs, "mSkipAdvanceObjectsMs must always be positive." );
}
if ( doBacklogged( timeDelta ) )
return false;
// remember interpolation value because we might need to set it back
F32 oldLastDelta = mLastDelta;
bool ret = Parent::advanceTime( timeDelta );
if ( !mSkipAdvanceObjectsMs )
{
AssertFatal( mLastDelta >= 0.0f && mLastDelta <= 1.0f, "mLastDelta must always be zero to one." );
for ( ProcessObject *pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next )
{
if ( pobj->isTicking() )
pobj->interpolateTick( mLastDelta );
}
// Inform objects of total elapsed delta so they can advance
// client side animations.
F32 dt = F32( timeDelta ) / 1000;
for ( ProcessObject *pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next)
{
pobj->advanceTime( dt );
}
}
else
{
mSkipAdvanceObjectsMs -= timeDelta;
mLastDelta = oldLastDelta;
}
return ret;
}
void HifiClientProcessList::onAdvanceObjects()
{
GameConnection* connection = GameConnection::getConnectionToServer();
if(connection)
{
// process any demo blocks that are NOT moves, and exactly one move
// we advance time in the demo stream by a move inserted on
// each tick. So before doing the tick processing we advance
// the demo stream until a move is ready
if(connection->isPlayingBack())
{
U32 blockType;
do
{
blockType = connection->getNextBlockType();
bool res = connection->processNextBlock();
// if there are no more blocks, exit out of this function,
// as no more client time needs to process right now - we'll
// get it all on the next advanceClientTime()
if(!res)
return;
}
while(blockType != GameConnection::BlockTypeMove);
}
if (!mSkipAdvanceObjectsMs)
{
connection->mMoveList->collectMove();
advanceObjects();
}
connection->mMoveList->onAdvanceObjects();
}
}
void HifiClientProcessList::onTickObject(ProcessObject * pobj)
{
// Each object is advanced a single tick
// If it's controlled by a client, tick using a move.
Move *movePtr;
U32 numMoves;
GameConnection *con = pobj->getControllingClient();
SimObjectPtr<GameBase> obj = getGameBase( pobj );
if ( obj && con && con->getControlObject() == obj && con->mMoveList->getMoves( &movePtr, &numMoves) )
{
#ifdef TORQUE_DEBUG_NET_MOVES
U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
#endif
obj->processTick( movePtr );
if ( bool(obj) && obj->getControllingClient() )
{
U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
// set checksum if not set or check against stored value if set
movePtr->checksum = newsum;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf( "move checksum: %i, (start %i), (move %f %f %f)",
movePtr->checksum,sum,movePtr->yaw,movePtr->y,movePtr->z );
#endif
}
con->mMoveList->clearMoves( 1 );
}
else if ( pobj->isTicking() )
pobj->processTick( 0 );
if ( obj && ( obj->getTypeMask() & GameBaseHiFiObjectType ) )
{
GameConnection * serverConnection = GameConnection::getConnectionToServer();
TickCacheEntry * tce = obj->getTickCache().addCacheEntry();
BitStream bs( tce->packetData, TickCacheEntry::MaxPacketSize );
obj->writePacketData( serverConnection, &bs );
Point3F vel = obj->getVelocity();
F32 velSq = mDot( vel, vel );
gMaxHiFiVelSq = getMax( gMaxHiFiVelSq, velSq );
}
}
void HifiClientProcessList::advanceObjects()
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("Advance client time...");
#endif
// client re-computes this each time objects are advanced
gMaxHiFiVelSq = 0;
Parent::advanceObjects();
// We need to consume a move on the connections whether
// there is a control object to consume the move or not,
// otherwise client and server can get out of sync move-wise
// during startup. If there is a control object, we cleared
// a move above. Handle case where no control object here.
// Note that we might consume an extra move here and there when
// we had a control object in above loop but lost it during tick.
// That is no big deal so we don't bother trying to carefully
// track it.
GameConnection * client = GameConnection::getConnectionToServer();
if (client && client->getControlObject() == NULL)
client->mMoveList->clearMoves(1);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("---------");
#endif
}
void HifiClientProcessList::ageTickCache(S32 numToAge, S32 len)
{
for (ProcessObject * pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next)
{
GameBase *obj = getGameBase(pobj);
if ( obj && obj->getTypeMask() & GameBaseHiFiObjectType )
obj->getTickCache().ageCache(numToAge,len);
}
}
void HifiClientProcessList::updateMoveSync(S32 moveDiff)
{
moveSync.update(moveDiff);
if (moveSync.doAction() && moveDiff<0)
{
skipAdvanceObjects(TickMs * -moveDiff);
moveSync.reset();
}
}
void HifiClientProcessList::clientCatchup(GameConnection * connection)
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("client catching up... (%i)%s", mCatchup, mForceHifiReset ? " reset" : "");
#endif
if (connection->getControlObject() && connection->getControlObject()->isGhostUpdated())
// if control object is reset, make sure moves are reset too
connection->mMoveList->resetCatchup();
const F32 maxVel = mSqrt(gMaxHiFiVelSq) * 1.25f;
F32 dt = F32(mCatchup+1) * TickSec;
Point3F bigDelta(maxVel*dt,maxVel*dt,maxVel*dt);
// walk through all process objects looking for ones which were updated
// -- during first pass merely collect neighbors which need to be reset and updated in unison
ProcessObject * pobj;
if (mCatchup && !mForceHifiReset)
{
for (pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next)
{
GameBase *obj = getGameBase( pobj );
static SimpleQueryList nearby;
nearby.mList.clear();
// check for nearby objects which need to be reset and then caught up
// note the funky loop logic -- first time through obj is us, then
// we start iterating through nearby list (to look for objects nearby
// the nearby objects), which is why index starts at -1
// [objects nearby the nearby objects also get added to the nearby list]
for (S32 i=-1; obj; obj = ++i<nearby.mList.size() ? (GameBase*)nearby.mList[i] : NULL)
{
if (obj->isGhostUpdated() && (obj->getTypeMask() & GameBaseHiFiObjectType) && !obj->isNetNearbyAdded())
{
Point3F start = obj->getWorldSphere().center;
Point3F end = start + 1.1f * dt * obj->getVelocity();
F32 rad = 1.5f * obj->getWorldSphere().radius;
// find nearby items not updated but are hi fi, mark them as updated (and restore old loc)
// check to see if added items have neighbors that need updating
Box3F box;
Point3F rads(rad,rad,rad);
box.minExtents = box.maxExtents = start;
box.minExtents -= bigDelta + rads;
box.maxExtents += bigDelta + rads;
// CodeReview - this is left in for MBU, but also so we can deal with the issue later.
// add marble blast hack so hifi networking can see hidden objects
// (since hidden is under control of hifi networking)
// gForceNotHidden = true;
S32 j = nearby.mList.size();
gClientContainer.findObjects(box, GameBaseHiFiObjectType, SimpleQueryList::insertionCallback, &nearby);
// CodeReview - this is left in for MBU, but also so we can deal with the issue later.
// disable above hack
// gForceNotHidden = false;
// drop anyone not heading toward us or already checked
for (; j<nearby.mList.size(); j++)
{
GameBase * obj2 = (GameBase*)nearby.mList[j];
// if both passive, these guys don't interact with each other
bool passive = obj->isHifiPassive() && obj2->isHifiPassive();
if (!obj2->isGhostUpdated() && !passive)
{
// compare swept spheres of obj and obj2
// if collide, reset obj2, setGhostUpdated(true), and continue
Point3F end2 = obj2->getWorldSphere().center;
Point3F start2 = end2 - 1.1f * dt * obj2->getVelocity();
F32 rad2 = 1.5f * obj->getWorldSphere().radius;
if (MathUtils::capsuleCapsuleOverlap(start,end,rad,start2,end2,rad2))
{
// better add obj2
obj2->getTickCache().beginCacheList();
TickCacheEntry * tce = obj2->getTickCache().incCacheList();
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj2->readPacketData(connection,&bs);
obj2->setGhostUpdated(true);
// continue so we later add the neighbors too
continue;
}
}
// didn't pass above test...so don't add it or nearby objects
nearby.mList[j] = nearby.mList.last();
nearby.mList.decrement();
j--;
}
obj->setNetNearbyAdded(true);
}
}
}
}
// save water mark -- for game base list
FrameAllocatorMarker mark;
// build ordered list of client objects which need to be caught up
GameBaseListNode list;
for (pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next)
{
GameBase *obj = getGameBase( pobj );
//GameBase *obj = dynamic_cast<GameBase*>( pobj );
//GameBase *obj = (GameBase*)pobj;
// Not a GameBase object so nothing to do.
if ( !obj )
continue;
if (obj->isGhostUpdated() && (obj->getTypeMask() & GameBaseHiFiObjectType))
{
// construct process object and add it to the list
// hold pointer to our object in mAfterObject
GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode));
po->mObject = obj;
po->linkBefore(&list);
// begin iterating through tick list (skip first tick since that is the state we've been reset to)
obj->getTickCache().beginCacheList();
obj->getTickCache().incCacheList();
}
else if (mForceHifiReset && (obj->getTypeMask() & GameBaseHiFiObjectType))
{
// add all hifi objects
obj->getTickCache().beginCacheList();
TickCacheEntry * tce = obj->getTickCache().incCacheList();
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj->readPacketData(connection,&bs);
obj->setGhostUpdated(true);
// construct process object and add it to the list
// hold pointer to our object in mAfterObject
GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode));
po->mObject = obj;
po->linkBefore(&list);
}
else if (obj == connection->getControlObject() && obj->isGhostUpdated())
{
// construct process object and add it to the list
// hold pointer to our object in mAfterObject
// .. but this is not a hi fi object, so don't mess with tick cache
GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode));
po->mObject = obj;
po->linkBefore(&list);
}
else if (obj->isGhostUpdated())
{
// not hifi but we were updated, so perform net smooth now
obj->computeNetSmooth(mLastDelta);
}
// clear out work flags
obj->setNetNearbyAdded(false);
obj->setGhostUpdated(false);
}
// run through all the moves in the move list so we can play them with our control object
Move* movePtr;
U32 numMoves;
connection->mMoveList->resetClientMoves();
connection->mMoveList->getMoves(&movePtr, &numMoves);
AssertFatal(mCatchup<=numMoves,"doh");
// tick catchup time
for (U32 m=0; m<mCatchup; m++)
{
for (GameBaseListNode * walk = list.mNext; walk != &list; walk = walk->mNext)
{
// note that we get object from after object not getGameBase function
// this is because we are an on the fly linked list which uses mAfterObject
// rather than the linked list embedded in GameBase (clean this up?)
GameBase * obj = walk->mObject;
// it's possible for a non-hifi object to get in here, but
// only if it is a control object...make sure we don't do any
// of the tick cache stuff if we are not hifi.
bool hifi = obj->getTypeMask() & GameBaseHiFiObjectType;
TickCacheEntry * tce = hifi ? obj->getTickCache().incCacheList() : NULL;
// tick object
if (obj==connection->getControlObject())
{
obj->processTick(movePtr);
movePtr->checksum = obj->getPacketDataChecksum(connection);
movePtr++;
}
else
{
AssertFatal(tce && hifi,"Should not get in here unless a hi fi object!!!");
obj->processTick(tce->move);
}
if (hifi)
{
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj->writePacketData(connection,&bs);
}
}
if (connection->getControlObject() == NULL)
movePtr++;
}
connection->mMoveList->clearMoves(mCatchup);
// Handle network error smoothing here...but only for control object
GameBase * control = connection->getControlObject();
if (control && !control->isNewGhost())
{
control->computeNetSmooth(mLastDelta);
control->setNewGhost(false);
}
if (moveSync.doAction() && moveSync.moveDiff>0)
{
S32 moveDiff = moveSync.moveDiff;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("client timewarping to catchup %i moves",moveDiff);
#endif
while (moveDiff--)
advanceObjects();
moveSync.reset();
}
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("---------");
#endif
// all caught up
mCatchup = 0;
}
//--------------------------------------------------------------------------
// HifiServerProcessList
//--------------------------------------------------------------------------
void HifiServerProcessList::onTickObject(ProcessObject * pobj)
{
// Each object is advanced a single tick
// If it's controlled by a client, tick using a move.
Move *movePtr;
U32 numMoves;
GameConnection *con = pobj->getControllingClient();
SimObjectPtr<GameBase> obj = getGameBase( pobj );
if ( obj && con && con->getControlObject() == obj && con->mMoveList->getMoves( &movePtr, &numMoves ) )
{
#ifdef TORQUE_DEBUG_NET_MOVES
U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
#endif
obj->processTick(movePtr);
if ( bool(obj) && obj->getControllingClient() )
{
U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
// check move checksum
if ( movePtr->checksum != newsum )
{
#ifdef TORQUE_DEBUG_NET_MOVES
if ( !obj->mIsAiControlled )
Con::printf( "move %i checksum disagree: %i != %i, (start %i), (move %f %f %f)",
movePtr->id, movePtr->checksum, newsum, sum, movePtr->yaw, movePtr->y, movePtr->z );
#endif
movePtr->checksum = Move::ChecksumMismatch;
}
else
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf( "move %i checksum agree: %i == %i, (start %i), (move %f %f %f)",
movePtr->id, movePtr->checksum, newsum, sum, movePtr->yaw, movePtr->y, movePtr->z );
#endif
}
// Adding this seems to fix constant corrections, but is it
// really a sound fix?
con->mMoveList->clearMoves( 1 );
}
}
else if ( pobj->isTicking() )
pobj->processTick( 0 );
}

View file

@ -0,0 +1,91 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GAMEPROCESS_HIFI_H_
#define _GAMEPROCESS_HIFI_H_
#ifndef _GAMEPROCESS_H_
#include "T3D/gameBase/gameProcess.h"
#endif
/// List to keep track of GameBases to process.
class HifiClientProcessList : public ClientProcessList
{
typedef ClientProcessList Parent;
friend class HifiMoveList;
public:
HifiClientProcessList();
// ProcessList
bool advanceTime(SimTime timeDelta);
// ClientProcessList
void clientCatchup(GameConnection*);
static void init();
static void shutdown();
protected:
// tick cache functions -- client only
void ageTickCache(S32 numToAge, S32 len);
void forceHifiReset(bool reset) { mForceHifiReset=reset; }
U32 getTotalTicks() { return mTotalTicks; }
void updateMoveSync(S32 moveDiff);
void skipAdvanceObjects(U32 ms) { mSkipAdvanceObjectsMs += ms; }
// ProcessList
void onTickObject(ProcessObject *);
void advanceObjects();
void onAdvanceObjects();
void setCatchup(U32 catchup) { mCatchup = catchup; }
protected:
U32 mSkipAdvanceObjectsMs;
bool mForceHifiReset;
U32 mCatchup;
};
class HifiServerProcessList : public ServerProcessList
{
typedef ServerProcessList Parent;
public:
HifiServerProcessList() {}
static void init();
static void shutdown();
protected:
// ProcessList
void onTickObject(ProcessObject *);
};
#endif // _GAMEPROCESS_HIFI_H_

View file

@ -0,0 +1,443 @@
//-----------------------------------------------------------------------------
// 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 "T3D/gameBase/hifi/hifiMoveList.h"
#include "T3D/gameBase/hifi/hifiGameProcess.h"
#include "T3D/gameBase/gameConnection.h"
#include "core/stream/bitStream.h"
#define MAX_MOVE_PACKET_SENDS 4
const U32 DefaultTargetMoveListSize = 3;
const U32 DefaultMaxMoveSizeList = 5;
const F32 DefaultSmoothMoveAvg = 0.15f;
const F32 DefaultMoveListSizeSlack = 1.0f;
HifiMoveList::HifiMoveList()
{
mLastSentMove = 0;
mAvgMoveQueueSize = DefaultTargetMoveListSize ;
mTargetMoveListSize = DefaultTargetMoveListSize;
mMaxMoveListSize = DefaultMaxMoveSizeList;
mSmoothMoveAvg = DefaultSmoothMoveAvg;
mMoveListSizeSlack = DefaultMoveListSizeSlack;
mTotalServerTicks = ServerTicksUninitialized;
mSuppressMove = false;
}
void HifiMoveList::updateClientServerTickDiff(S32 & tickDiff)
{
if (mLastMoveAck==0)
tickDiff=0;
// Make adjustments to move list to account for tick mis-matches between client and server.
if (tickDiff>0)
{
// Server ticked more than client. Adjust for this by reseting all hifi objects
// to a later position in the tick cache (see ageTickCache below) and at the same
// time pulling back some moves we thought we had made (so that time on client
// doesn't change).
S32 dropTicks = tickDiff;
while (dropTicks)
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("dropping move%s",mLastClientMove>mFirstMoveIndex ? "" : " but none there");
#endif
if (mLastClientMove>mFirstMoveIndex)
mLastClientMove--;
else
tickDiff--;
dropTicks--;
}
AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request");
AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveVec.size(), "Desynched first and last move.");
}
else
{
// Client ticked more than server. Adjust for this by taking extra moves
// (either adding back moves that were dropped above, or taking new ones).
for (S32 i=0; i<-tickDiff; i++)
{
if (mMoveVec.size() > mLastClientMove - mFirstMoveIndex)
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("add back move");
#endif
mLastClientMove++;
}
else
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("add back move -- create one");
#endif
collectMove();
mLastClientMove++;
}
}
}
// drop moves that are not made yet (because we rolled them back) and not yet sent
U32 len = getMax(mLastClientMove-mFirstMoveIndex,mLastSentMove-mFirstMoveIndex);
mMoveVec.setSize(len);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("move list size: %i, last move: %i, last sent: %i",mMoveVec.size(),mLastClientMove-mFirstMoveIndex,mLastSentMove-mFirstMoveIndex);
#endif
}
S32 HifiMoveList::getServerTicks(U32 serverTickNum)
{
S32 serverTicks=0;
if (serverTicksInitialized())
{
// handle tick wrapping...
const S32 MaxTickCount = (1<<TotalTicksBits);
const S32 HalfMaxTickCount = MaxTickCount>>1;
U32 prevTickNum = mTotalServerTicks & TotalTicksMask;
serverTicks = serverTickNum-prevTickNum;
if (serverTicks>HalfMaxTickCount)
serverTicks -= MaxTickCount;
else if (-serverTicks>HalfMaxTickCount)
serverTicks += MaxTickCount;
AssertFatal(serverTicks>=0,"Server can't tick backwards!!!");
if (serverTicks<0)
serverTicks=0;
}
mTotalServerTicks = serverTickNum;
return serverTicks;
}
void HifiMoveList::markControlDirty()
{
mLastClientMove = mLastMoveAck;
// save state for future update
GameBase *obj = mConnection->getControlObject();
AssertFatal(obj,"ClientProcessList::markControlDirty: no control object");
obj->setGhostUpdated(true);
obj->getTickCache().beginCacheList();
TickCacheEntry * tce = obj->getTickCache().incCacheList();
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj->writePacketData( mConnection, &bs );
}
void HifiMoveList::resetMoveList()
{
mMoveVec.clear();
mLastMoveAck = 0;
mLastClientMove = 0;
mFirstMoveIndex = 0;
mLastSentMove = 0;
}
U32 HifiMoveList::getMoves(Move** movePtr,U32* numMoves)
{
if (mConnection->isConnectionToServer())
// give back moves starting at the last client move...
return Parent::getMoves(movePtr,numMoves);
if (mSuppressMove || mMoveVec.size()==0)
{
*numMoves=0;
*movePtr=NULL;
}
else
{
*numMoves=1;
*movePtr=mMoveVec.begin();
}
return *numMoves;
}
void HifiMoveList::advanceMove()
{
S32 numMoves = mMoveVec.size();
mAvgMoveQueueSize *= (1.0f-mSmoothMoveAvg);
mAvgMoveQueueSize += mSmoothMoveAvg * F32(numMoves);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("moves remaining: %i, running avg: %f",numMoves,mAvgMoveQueueSize);
#endif
if (mAvgMoveQueueSize<mTargetMoveListSize-mMoveListSizeSlack && numMoves<mTargetMoveListSize && numMoves)
{
numMoves=0;
mAvgMoveQueueSize = (F32)getMax(S32(mAvgMoveQueueSize + mMoveListSizeSlack + 0.5f),numMoves);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("too few moves on server, padding with null move");
#endif
}
if (numMoves)
numMoves=1;
if ( mMoveVec.size()>mMaxMoveListSize || (mAvgMoveQueueSize>mTargetMoveListSize+mMoveListSizeSlack && mMoveVec.size()>mTargetMoveListSize) )
{
U32 drop = mMoveVec.size()-mTargetMoveListSize;
clearMoves(drop);
mAvgMoveQueueSize = (F32)mTargetMoveListSize;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("too many moves on server, dropping moves (%i)",drop);
#endif
}
mSuppressMove = numMoves == 0;
// now clear move
if (areMovesPending())
clearMoves(1);
}
void HifiMoveList::clientWriteMovePacket(BitStream *bstream)
{
if (!serverTicksInitialized())
resetMoveList();
AssertFatal(mLastMoveAck == mFirstMoveIndex, "Invalid move index.");
// enforce limit on number of moves sent
if (mLastSentMove<mFirstMoveIndex)
mLastSentMove=mFirstMoveIndex;
U32 count = mLastSentMove-mFirstMoveIndex;
Move * move = mMoveVec.address();
U32 start = mLastMoveAck;
U32 offset;
for(offset = 0; offset < count; offset++)
if(move[offset].sendCount < MAX_MOVE_PACKET_SENDS)
break;
if(offset == count && count != 0)
offset--;
start += offset;
count -= offset;
if (count > MaxMoveCount)
count = MaxMoveCount;
bstream->writeInt(start,32);
bstream->writeInt(count,MoveCountBits);
Move * prevMove = NULL;
for (int i = 0; i < count; i++)
{
move[offset + i].sendCount++;
move[offset + i].pack(bstream,prevMove);
bstream->writeInt(move[offset + i].checksum,Move::ChecksumBits);
prevMove = &move[offset+i];
}
}
void HifiMoveList::serverReadMovePacket(BitStream *bstream)
{
// Server side packet read.
U32 start = bstream->readInt(32);
U32 count = bstream->readInt(MoveCountBits);
Move * prevMove = NULL;
Move prevMoveHolder;
// Skip forward (must be starting up), or over the moves
// we already have.
int skip = mLastMoveAck - start;
if (skip < 0)
{
mLastMoveAck = start;
}
else
{
if (skip > count)
skip = count;
for (int i = 0; i < skip; i++)
{
prevMoveHolder.unpack(bstream,prevMove);
prevMoveHolder.checksum = bstream->readInt(Move::ChecksumBits);
prevMove = &prevMoveHolder;
S32 idx = mMoveVec.size()-skip+i;
if (idx>=0)
{
#ifdef TORQUE_DEBUG_NET_MOVES
if (mMoveVec[idx].checksum != prevMoveHolder.checksum)
Con::printf("updated checksum on move %i from %i to %i",mMoveVec[idx].id,mMoveVec[idx].checksum,prevMoveHolder.checksum);
#endif
mMoveVec[idx].checksum = prevMoveHolder.checksum;
}
}
start += skip;
count = count - skip;
}
// Put the rest on the move list.
int index = mMoveVec.size();
mMoveVec.increment(count);
while (index < mMoveVec.size())
{
mMoveVec[index].unpack(bstream,prevMove);
mMoveVec[index].checksum = bstream->readInt(Move::ChecksumBits);
prevMove = &mMoveVec[index];
mMoveVec[index].id = start++;
index ++;
}
mLastMoveAck += count;
if (mMoveVec.size()>mMaxMoveListSize)
{
U32 drop = mMoveVec.size()-mTargetMoveListSize;
clearMoves(drop);
mAvgMoveQueueSize = (F32)mTargetMoveListSize;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("too many moves on server, dropping moves (%i)",drop);
#endif
}
}
void HifiMoveList::serverWriteMovePacket(BitStream * bstream)
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("ack %i minus %i",mLastMoveAck,mMoveVec.size());
#endif
// acknowledge only those moves that have been ticked
bstream->writeInt(mLastMoveAck - mMoveVec.size(),32);
// send over the current tick count on the server...
bstream->writeInt(ServerProcessList::get()->getTotalTicks() & TotalTicksMask, TotalTicksBits);
}
void HifiMoveList::clientReadMovePacket(BitStream * bstream)
{
if (!serverTicksInitialized())
resetMoveList();
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("pre move ack: %i", mLastMoveAck);
#endif
mLastMoveAck = bstream->readInt(32);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("post move ack %i, first move %i, last move %i", mLastMoveAck, mFirstMoveIndex, mLastClientMove);
#endif
// This is how many times we've ticked since last ack -- before adjustments below
S32 ourTicks = mLastMoveAck - mFirstMoveIndex;
if (mLastMoveAck < mFirstMoveIndex)
mLastMoveAck = mFirstMoveIndex;
if(mLastMoveAck > mLastClientMove)
{
ourTicks -= mLastMoveAck-mLastClientMove;
mLastClientMove = mLastMoveAck;
}
while(mFirstMoveIndex < mLastMoveAck)
{
if (mMoveVec.size())
{
mMoveVec.pop_front();
mFirstMoveIndex++;
}
else
{
AssertWarn(1, "Popping off too many moves!");
mFirstMoveIndex = mLastMoveAck;
}
}
// get server ticks using total number of ticks on server to date...
U32 serverTickNum = bstream->readInt(TotalTicksBits);
S32 serverTicks = getServerTicks(serverTickNum);
S32 tickDiff = serverTicks - ourTicks;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("server ticks: %i, client ticks: %i, diff: %i%s", serverTicks, ourTicks, tickDiff, !tickDiff ? "" : " (ticks mis-match)");
#endif
// Apply the first (of two) client-side synchronization mechanisms. Key is that
// we need to both synchronize client/server move streams (so first move in list is made
// at same "time" on both client and server) and maintain the "time" at which the most
// recent move was made on the server. In both cases, "time" is the number of ticks
// it took to get to that move.
updateClientServerTickDiff(tickDiff);
// Apply the second (and final) client-side synchronization mechanism. The tickDiff adjustments above
// make sure time is preserved on client. But that assumes that a future (or previous) update will adjust
// time in the other direction, so that we don't get too far behind or ahead of the server. The updateMoveSync
// mechanism tracks us over time to make sure we eventually return to be in sync, and makes adjustments
// if we don't after a certain time period (number of updates). Unlike the tickDiff mechanism, when
// the updateMoveSync acts time is not preserved on the client.
HifiClientProcessList * processList = dynamic_cast<HifiClientProcessList*>(ClientProcessList::get());
if (processList)
{
processList->updateMoveSync(mLastSentMove-mLastClientMove);
// set catchup parameters...
U32 totalCatchup = mLastClientMove - mFirstMoveIndex;
processList->ageTickCache(ourTicks + (tickDiff>0 ? tickDiff : 0), totalCatchup+1);
processList->forceHifiReset(tickDiff!=0);
processList->setCatchup(totalCatchup);
}
}
void HifiMoveList::ghostPreRead(NetObject * nobj, bool newGhost)
{
GameBase* obj = dynamic_cast<GameBase*>(nobj);
if ( obj && ( obj->getTypeMask() & GameBaseHiFiObjectType ) && !newGhost )
{
// set next cache entry to start
obj->getTickCache().beginCacheList();
// reset to old state because we are about to unpack (and then tick forward)
TickCacheEntry * tce = obj->getTickCache().incCacheList(false);
if (tce)
{
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj->readPacketData(mConnection, &bs);
}
}
}
void HifiMoveList::ghostReadExtra(NetObject * nobj, BitStream * bstream, bool newGhost)
{
// Receive additional per ghost information.
// Get pending moves for ghosts that have them and add the moves to
// the tick cache.
GameBase* obj = dynamic_cast<GameBase*>(nobj);
if ( obj && ( obj->getTypeMask() & GameBaseHiFiObjectType ) )
{
// mark ghost so that it updates correctly
obj->setGhostUpdated(true);
obj->setNewGhost(newGhost);
// set next cache entry to start
obj->getTickCache().beginCacheList();
// save state for future update
TickCacheEntry * tce = obj->getTickCache().incCacheList();
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj->writePacketData(mConnection, &bs);
}
}

View file

@ -0,0 +1,76 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _MOVELIST_HIFI_H_
#define _MOVELIST_HIFI_H_
#ifndef _MOVELIST_H_
#include "T3D/gameBase/moveList.h"
#endif
class HifiMoveList : public MoveList
{
typedef MoveList Parent;
public:
HifiMoveList();
void init() { mTotalServerTicks = ServerTicksUninitialized; }
void ghostReadExtra(NetObject *,BitStream *, bool newGhost);
void ghostPreRead(NetObject *, bool newGhost);
void clientWriteMovePacket(BitStream *bstream);
void clientReadMovePacket(BitStream *);
void serverWriteMovePacket(BitStream *);
void serverReadMovePacket(BitStream *bstream);
void markControlDirty();
U32 getMoves(Move**,U32* numMoves);
void onAdvanceObjects() { if (mMoveVec.size() > mLastSentMove-mFirstMoveIndex) mLastSentMove++; }
void advanceMove();
protected:
void resetMoveList();
S32 getServerTicks(U32 serverTickNum);
void updateClientServerTickDiff(S32 & tickDiff);
bool serverTicksInitialized() { return mTotalServerTicks!=ServerTicksUninitialized; }
protected:
U32 mLastSentMove;
F32 mAvgMoveQueueSize;
// server side move list management
U32 mTargetMoveListSize; // Target size of move buffer on server
U32 mMaxMoveListSize; // Max size move buffer allowed to grow to
F32 mSmoothMoveAvg; // Smoothing parameter for move list size running average
F32 mMoveListSizeSlack; // Amount above/below target size move list running average allowed to diverge
bool mSuppressMove; // If true, don't return move on server
// client side tracking of server ticks
enum { TotalTicksBits=10, TotalTicksMask = (1<<TotalTicksBits)-1, ServerTicksUninitialized=0xFFFFFFFF };
U32 mTotalServerTicks;
};
#endif // _MOVELIST_HIFI_H_

View file

@ -0,0 +1,223 @@
//-----------------------------------------------------------------------------
// 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 "T3D/gameBase/moveList.h"
#include "T3D/gameBase/gameConnection.h"
#include "core/stream/bitStream.h"
MoveList::MoveList()
{
mControlMismatch = false;
reset();
}
void MoveList::reset()
{
mLastMoveAck = 0;
mLastClientMove = 0;
mFirstMoveIndex = 0;
mMoveVec.clear();
}
bool MoveList::getNextMove(Move &curMove)
{
if ( mMoveVec.size() > MaxMoveQueueSize )
return false;
F32 pitchAdd = MoveManager::mPitchUpSpeed - MoveManager::mPitchDownSpeed;
F32 yawAdd = MoveManager::mYawLeftSpeed - MoveManager::mYawRightSpeed;
F32 rollAdd = MoveManager::mRollRightSpeed - MoveManager::mRollLeftSpeed;
curMove.pitch = MoveManager::mPitch + pitchAdd;
curMove.yaw = MoveManager::mYaw + yawAdd;
curMove.roll = MoveManager::mRoll + rollAdd;
MoveManager::mPitch = 0;
MoveManager::mYaw = 0;
MoveManager::mRoll = 0;
curMove.x = MoveManager::mRightAction - MoveManager::mLeftAction + MoveManager::mXAxis_L;
curMove.y = MoveManager::mForwardAction - MoveManager::mBackwardAction + MoveManager::mYAxis_L;
curMove.z = MoveManager::mUpAction - MoveManager::mDownAction;
curMove.freeLook = MoveManager::mFreeLook;
curMove.deviceIsKeyboardMouse = MoveManager::mDeviceIsKeyboardMouse;
for(U32 i = 0; i < MaxTriggerKeys; i++)
{
curMove.trigger[i] = false;
if(MoveManager::mTriggerCount[i] & 1)
curMove.trigger[i] = true;
else if(!(MoveManager::mPrevTriggerCount[i] & 1) && MoveManager::mPrevTriggerCount[i] != MoveManager::mTriggerCount[i])
curMove.trigger[i] = true;
MoveManager::mPrevTriggerCount[i] = MoveManager::mTriggerCount[i];
}
if (mConnection->getControlObject())
mConnection->getControlObject()->preprocessMove(&curMove);
curMove.clamp(); // clamp for net traffic
return true;
}
void MoveList::pushMove(const Move &mv)
{
U32 id = mFirstMoveIndex + mMoveVec.size();
U32 sz = mMoveVec.size();
mMoveVec.push_back(mv);
mMoveVec[sz].id = id;
mMoveVec[sz].sendCount = 0;
}
U32 MoveList::getMoves(Move** movePtr,U32* numMoves)
{
if (mConnection->isConnectionToServer())
{
// give back moves starting at the last client move...
AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request");
AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveVec.size(), "Desynched first and last move.");
*numMoves = mMoveVec.size() - mLastClientMove + mFirstMoveIndex;
*movePtr = mMoveVec.address() + mLastClientMove - mFirstMoveIndex;
}
else
{
// return the full list
*numMoves = mMoveVec.size();
*movePtr = mMoveVec.begin();
}
return *numMoves;
}
void MoveList::collectMove()
{
Move mv;
if (mConnection)
{
if(!mConnection->isPlayingBack() && getNextMove(mv))
{
mv.checksum=Move::ChecksumMismatch;
pushMove(mv);
mConnection->recordBlock(GameConnection::BlockTypeMove, sizeof(Move), &mv);
}
}
else
{
if(getNextMove(mv))
{
mv.checksum=Move::ChecksumMismatch;
pushMove(mv);
}
}
}
void MoveList::clearMoves(U32 count)
{
if (mConnection->isConnectionToServer())
{
mLastClientMove += count;
AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request");
AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveVec.size(), "Desynched first and last move.");
if (!mConnection)
// drop right away if no connection
ackMoves(count);
}
else
{
AssertFatal(count <= mMoveVec.size(),"GameConnection: Clearing too many moves");
for (S32 i=0; i<count; i++)
if (mMoveVec[i].checksum == Move::ChecksumMismatch)
mControlMismatch = true;
else
mControlMismatch = false;
if (count == mMoveVec.size())
mMoveVec.clear();
else
while (count--)
mMoveVec.pop_front();
}
}
bool MoveList::areMovesPending()
{
return mConnection->isConnectionToServer() ?
mMoveVec.size() - mLastClientMove + mFirstMoveIndex :
mMoveVec.size();
}
bool MoveList::isBacklogged()
{
if ( !mConnection->isConnectionToServer() )
return false;
return mLastClientMove - mFirstMoveIndex == mMoveVec.size() &&
mMoveVec.size() >= MaxMoveCount;
}
void MoveList::ackMoves(U32 count)
{
mLastMoveAck += count;
if(mLastMoveAck > mLastClientMove)
mLastClientMove = mLastMoveAck;
while(mFirstMoveIndex < mLastMoveAck)
{
if (mMoveVec.size())
{
mMoveVec.pop_front();
mFirstMoveIndex++;
}
else
{
AssertWarn(1, "Popping off too many moves!");
mFirstMoveIndex = mLastMoveAck;
}
}
}
void MoveList::writeDemoStartBlock(ResizeBitStream *stream)
{
stream->write(mLastMoveAck);
stream->write(mLastClientMove);
stream->write(mFirstMoveIndex);
stream->write(U32(mMoveVec.size()));
for(U32 j = 0; j < mMoveVec.size(); j++)
mMoveVec[j].pack(stream);
}
void MoveList::readDemoStartBlock(BitStream *stream)
{
stream->read(&mLastMoveAck);
stream->read(&mLastClientMove);
stream->read(&mFirstMoveIndex);
U32 size;
Move mv;
stream->read(&size);
mMoveVec.clear();
while(size--)
{
mv.unpack(stream);
pushMove(mv);
}
}

View file

@ -0,0 +1,122 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _MOVELIST_H_
#define _MOVELIST_H_
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _MOVEMANAGER_H_
#include "T3D/gameBase/moveManager.h"
#endif
class BitStream;
class ResizeBitStream;
class NetObject;
class GameConnection;
class PlayerRep;
class ProcessList;
class MoveList
{
public:
MoveList();
virtual ~MoveList() {}
virtual void init() {}
void setConnection( GameConnection *connection) { mConnection = connection; }
/// @name Move Packets
/// Write/read move data to the packet.
/// @{
virtual void ghostReadExtra( NetObject *, BitStream *, bool newGhost) {};
virtual void ghostWriteExtra( NetObject *,BitStream * ) {};
virtual void ghostPreRead( NetObject *, bool newGhost ) {};
virtual void clientWriteMovePacket( BitStream *bstream ) = 0;
virtual void clientReadMovePacket( BitStream * ) = 0;
virtual void serverWriteMovePacket( BitStream * ) = 0;
virtual void serverReadMovePacket( BitStream *bstream ) = 0;
virtual void writeDemoStartBlock( ResizeBitStream *stream );
virtual void readDemoStartBlock( BitStream *stream );
/// @}
virtual void advanceMove() = 0;
virtual void onAdvanceObjects() = 0;
virtual U32 getMoves( Move**, U32 *numMoves );
/// Reset to beginning of client move list.
void resetClientMoves() { mLastClientMove = mFirstMoveIndex; }
/// Reset move list back to last acknowledged move.
void resetCatchup() { mLastClientMove = mLastMoveAck; }
void collectMove();
void pushMove( const Move &mv );
virtual void clearMoves( U32 count );
virtual void markControlDirty() { mLastClientMove = mLastMoveAck; }
bool isMismatch() { return mControlMismatch; }
void clearMismatch() { mControlMismatch = false; }
/// Clear out all moves in the list and reset to initial state.
void reset();
/// If there are no pending moves and the input queue is full,
/// then the connection to the server must be clogged.
bool isBacklogged();
bool areMovesPending();
void ackMoves( U32 count );
protected:
bool getNextMove( Move &curMove );
protected:
enum
{
MoveCountBits = 5,
/// MaxMoveCount should not exceed the MoveManager's
/// own maximum (MaxMoveQueueSize)
MaxMoveCount = 30,
};
U32 mLastMoveAck;
U32 mLastClientMove;
U32 mFirstMoveIndex;
bool mControlMismatch;
GameConnection *mConnection;
Vector<Move> mMoveVec;
};
#endif // _MOVELIST_H_

View file

@ -0,0 +1,302 @@
//-----------------------------------------------------------------------------
// 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 "T3D/gameBase/moveManager.h"
#include "core/stream/bitStream.h"
#include "core/module.h"
#include "console/consoleTypes.h"
#include "core/strings/stringFunctions.h"
#include "math/mConstants.h"
MODULE_BEGIN( MoveManager )
MODULE_INIT
{
MoveManager::init();
}
MODULE_END;
bool MoveManager::mDeviceIsKeyboardMouse = false;
F32 MoveManager::mForwardAction = 0;
F32 MoveManager::mBackwardAction = 0;
F32 MoveManager::mUpAction = 0;
F32 MoveManager::mDownAction = 0;
F32 MoveManager::mLeftAction = 0;
F32 MoveManager::mRightAction = 0;
bool MoveManager::mFreeLook = false;
F32 MoveManager::mPitch = 0;
F32 MoveManager::mYaw = 0;
F32 MoveManager::mRoll = 0;
F32 MoveManager::mPitchUpSpeed = 0;
F32 MoveManager::mPitchDownSpeed = 0;
F32 MoveManager::mYawLeftSpeed = 0;
F32 MoveManager::mYawRightSpeed = 0;
F32 MoveManager::mRollLeftSpeed = 0;
F32 MoveManager::mRollRightSpeed = 0;
F32 MoveManager::mXAxis_L = 0;
F32 MoveManager::mYAxis_L = 0;
F32 MoveManager::mXAxis_R = 0;
F32 MoveManager::mYAxis_R = 0;
U32 MoveManager::mTriggerCount[MaxTriggerKeys] = { 0, };
U32 MoveManager::mPrevTriggerCount[MaxTriggerKeys] = { 0, };
const Move NullMove =
{
/*px=*/16, /*py=*/16, /*pz=*/16,
/*pyaw=*/0, /*ppitch=*/0, /*proll=*/0,
/*x=*/0, /*y=*/0,/*z=*/0,
/*yaw=*/0, /*pitch=*/0, /*roll=*/0,
/*id=*/0,
/*sendCount=*/0,
/*checksum=*/false,
/*deviceIsKeyboardMouse=*/false,
/*freeLook=*/false,
/*triggers=*/{false,false,false,false,false,false}
};
void MoveManager::init()
{
Con::addVariable("mvForwardAction", TypeF32, &mForwardAction,
"Forwards movement speed for the active player.\n"
"@ingroup Game");
Con::addVariable("mvBackwardAction", TypeF32, &mBackwardAction,
"Backwards movement speed for the active player.\n"
"@ingroup Game");
Con::addVariable("mvUpAction", TypeF32, &mUpAction,
"Upwards movement speed for the active player.\n"
"@ingroup Game");
Con::addVariable("mvDownAction", TypeF32, &mDownAction,
"Downwards movement speed for the active player.\n"
"@ingroup Game");
Con::addVariable("mvLeftAction", TypeF32, &mLeftAction,
"Left movement speed for the active player.\n"
"@ingroup Game");
Con::addVariable("mvRightAction", TypeF32, &mRightAction,
"Right movement speed for the active player.\n"
"@ingroup Game");
Con::addVariable("mvFreeLook", TypeBool, &mFreeLook,
"Boolean state for if freelook is active or not.\n"
"@ingroup Game");
Con::addVariable("mvDeviceIsKeyboardMouse", TypeBool, &mDeviceIsKeyboardMouse,
"Boolean state for it the system is using a keyboard and mouse or not.\n"
"@ingroup Game");
Con::addVariable("mvPitch", TypeF32, &mPitch,
"Current pitch value, typically applied through input devices, such as a mouse.\n"
"@ingroup Game");
Con::addVariable("mvYaw", TypeF32, &mYaw,
"Current yaw value, typically applied through input devices, such as a mouse.\n"
"@ingroup Game");
Con::addVariable("mvRoll", TypeF32, &mRoll,
"Current roll value, typically applied through input devices, such as a mouse.\n"
"@ingroup Game");
Con::addVariable("mvPitchUpSpeed", TypeF32, &mPitchUpSpeed,
"Upwards pitch speed.\n"
"@ingroup Game");
Con::addVariable("mvPitchDownSpeed", TypeF32, &mPitchDownSpeed,
"Downwards pitch speed.\n"
"@ingroup Game");
Con::addVariable("mvYawLeftSpeed", TypeF32, &mYawLeftSpeed,
"Left Yaw speed.\n"
"@ingroup Game");
Con::addVariable("mvYawRightSpeed", TypeF32, &mYawRightSpeed,
"Right Yaw speed.\n"
"@ingroup Game");
Con::addVariable("mvRollLeftSpeed", TypeF32, &mRollLeftSpeed,
"Left roll speed.\n"
"@ingroup Game");
Con::addVariable("mvRollRightSpeed", TypeF32, &mRollRightSpeed,
"Right roll speed.\n"
"@ingroup Game");
// Dual-analog
Con::addVariable( "mvXAxis_L", TypeF32, &mXAxis_L,
"Left thumbstick X axis position on a dual-analog gamepad.\n"
"@ingroup Game" );
Con::addVariable( "mvYAxis_L", TypeF32, &mYAxis_L,
"Left thumbstick Y axis position on a dual-analog gamepad.\n"
"@ingroup Game" );
Con::addVariable( "mvXAxis_R", TypeF32, &mXAxis_R,
"Right thumbstick X axis position on a dual-analog gamepad.\n"
"@ingroup Game" );
Con::addVariable( "mvYAxis_R", TypeF32, &mYAxis_R,
"Right thumbstick Y axis position on a dual-analog gamepad.\n"
"@ingroup Game");
for(U32 i = 0; i < MaxTriggerKeys; i++)
{
char varName[256];
dSprintf(varName, sizeof(varName), "mvTriggerCount%d", i);
Con::addVariable(varName, TypeS32, &mTriggerCount[i],
"Used to determine the trigger counts of buttons. Namely used for input actions such as jumping and weapons firing.\n"
"@ingroup Game");
}
}
static inline F32 clampFloatWrap(F32 val)
{
return val - F32(S32(val));
}
static inline S32 clampRangeClamp(F32 val)
{
if(val < -1)
return 0;
if(val > 1)
return 32;
// 0.5 / 16 = 0.03125 ... this forces a round up to
// make the precision near zero equal in the negative
// and positive directions. See...
//
// http://www.garagegames.com/community/forums/viewthread/49714
return (S32)((val + 1.03125) * 16);
}
#define FANG2IANG(x) ((U32)((S16)((F32(0x10000) / M_2PI) * x)) & 0xFFFF)
#define IANG2FANG(x) (F32)((M_2PI / F32(0x10000)) * (F32)((S16)x))
void Move::unclamp()
{
yaw = IANG2FANG(pyaw);
pitch = IANG2FANG(ppitch);
roll = IANG2FANG(proll);
x = (px - 16) / F32(16);
y = (py - 16) / F32(16);
z = (pz - 16) / F32(16);
}
static inline F32 clampAngleClamp( F32 angle )
{
const F32 limit = ( M_PI_F / 180.0f ) * 179.999f;
if ( angle < -limit )
return -limit;
if ( angle > limit )
return limit;
return angle;
}
void Move::clamp()
{
// If yaw/pitch/roll goes equal or greater than -PI/+PI it
// flips the direction of the rotation... we protect against
// that by clamping before the conversion.
yaw = clampAngleClamp( yaw );
pitch = clampAngleClamp( pitch );
roll = clampAngleClamp( roll );
// angles are all 16 bit.
pyaw = FANG2IANG(yaw);
ppitch = FANG2IANG(pitch);
proll = FANG2IANG(roll);
px = clampRangeClamp(x);
py = clampRangeClamp(y);
pz = clampRangeClamp(z);
unclamp();
}
void Move::pack(BitStream *stream, const Move * basemove)
{
bool alwaysWriteAll = basemove!=NULL;
if (!basemove)
basemove = &NullMove;
S32 i;
bool triggerDifferent = false;
for (i=0; i < MaxTriggerKeys; i++)
if (trigger[i] != basemove->trigger[i])
triggerDifferent = true;
bool somethingDifferent = (pyaw!=basemove->pyaw) ||
(ppitch!=basemove->ppitch) ||
(proll!=basemove->proll) ||
(px!=basemove->px) ||
(py!=basemove->py) ||
(pz!=basemove->pz) ||
(deviceIsKeyboardMouse!=basemove->deviceIsKeyboardMouse) ||
(freeLook!=basemove->freeLook) ||
triggerDifferent;
if (alwaysWriteAll || stream->writeFlag(somethingDifferent))
{
if(stream->writeFlag(pyaw != basemove->pyaw))
stream->writeInt(pyaw, 16);
if(stream->writeFlag(ppitch != basemove->ppitch))
stream->writeInt(ppitch, 16);
if(stream->writeFlag(proll != basemove->proll))
stream->writeInt(proll, 16);
if (stream->writeFlag(px != basemove->px))
stream->writeInt(px, 6);
if (stream->writeFlag(py != basemove->py))
stream->writeInt(py, 6);
if (stream->writeFlag(pz != basemove->pz))
stream->writeInt(pz, 6);
stream->writeFlag(freeLook);
stream->writeFlag(deviceIsKeyboardMouse);
if (stream->writeFlag(triggerDifferent))
for(i = 0; i < MaxTriggerKeys; i++)
stream->writeFlag(trigger[i]);
}
}
void Move::unpack(BitStream *stream, const Move * basemove)
{
bool alwaysReadAll = basemove!=NULL;
if (!basemove)
basemove=&NullMove;
if (alwaysReadAll || stream->readFlag())
{
pyaw = stream->readFlag() ? stream->readInt(16) : basemove->pyaw;
ppitch = stream->readFlag() ? stream->readInt(16) : basemove->ppitch;
proll = stream->readFlag() ? stream->readInt(16) : basemove->proll;
px = stream->readFlag() ? stream->readInt(6) : basemove->px;
py = stream->readFlag() ? stream->readInt(6) : basemove->py;
pz = stream->readFlag() ? stream->readInt(6) : basemove->pz;
freeLook = stream->readFlag();
deviceIsKeyboardMouse = stream->readFlag();
bool triggersDiffer = stream->readFlag();
for (S32 i = 0; i< MaxTriggerKeys; i++)
trigger[i] = triggersDiffer ? stream->readFlag() : basemove->trigger[i];
unclamp();
}
else
*this = *basemove;
}

View file

@ -0,0 +1,95 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _MOVEMANAGER_H_
#define _MOVEMANAGER_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
enum MoveConstants {
MaxTriggerKeys = 6,
MaxMoveQueueSize = 45,
};
class BitStream;
struct Move
{
enum { ChecksumBits = 16, ChecksumMask = ((1<<ChecksumBits)-1), ChecksumMismatch = U32(-1) };
// packed storage rep, set in clamp
S32 px, py, pz;
U32 pyaw, ppitch, proll;
F32 x, y, z; // float -1 to 1
F32 yaw, pitch, roll; // 0-2PI
U32 id; // sync'd between server & client - debugging tool.
U32 sendCount;
U32 checksum;
bool deviceIsKeyboardMouse;
bool freeLook;
bool trigger[MaxTriggerKeys];
void pack(BitStream *stream, const Move * move = NULL);
void unpack(BitStream *stream, const Move * move = NULL);
void clamp();
void unclamp();
};
extern const Move NullMove;
class MoveManager
{
public:
static bool mDeviceIsKeyboardMouse;
static F32 mForwardAction;
static F32 mBackwardAction;
static F32 mUpAction;
static F32 mDownAction;
static F32 mLeftAction;
static F32 mRightAction;
static bool mFreeLook;
static F32 mPitch;
static F32 mYaw;
static F32 mRoll;
static F32 mPitchUpSpeed;
static F32 mPitchDownSpeed;
static F32 mYawLeftSpeed;
static F32 mYawRightSpeed;
static F32 mRollLeftSpeed;
static F32 mRollRightSpeed;
static F32 mXAxis_L;
static F32 mYAxis_L;
static F32 mXAxis_R;
static F32 mYAxis_R;
static U32 mTriggerCount[MaxTriggerKeys];
static U32 mPrevTriggerCount[MaxTriggerKeys];
static void init();
};
#endif

View file

@ -0,0 +1,277 @@
//-----------------------------------------------------------------------------
// 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/gameBase/processList.h"
#include "T3D/gameBase/gameBase.h"
#include "platform/profiler.h"
#include "console/consoleTypes.h"
//----------------------------------------------------------------------------
ProcessObject::ProcessObject()
: mProcessTag( 0 ),
mOrderGUID( 0 ),
mProcessTick( false ),
mIsGameBase( false )
{
mProcessLink.next = mProcessLink.prev = this;
}
void ProcessObject::plUnlink()
{
mProcessLink.next->mProcessLink.prev = mProcessLink.prev;
mProcessLink.prev->mProcessLink.next = mProcessLink.next;
mProcessLink.next = mProcessLink.prev = this;
}
void ProcessObject::plLinkAfter(ProcessObject * obj)
{
AssertFatal(mProcessLink.next == this && mProcessLink.prev == this,"ProcessObject::plLinkAfter: must be unlinked before calling this");
#ifdef TORQUE_DEBUG
ProcessObject * test1 = obj;
ProcessObject * test2 = obj->mProcessLink.next;
ProcessObject * test3 = obj->mProcessLink.prev;
ProcessObject * test4 = this;
#endif
// Link this after obj
mProcessLink.next = obj->mProcessLink.next;
mProcessLink.prev = obj;
obj->mProcessLink.next = this;
mProcessLink.next->mProcessLink.prev = this;
#ifdef TORQUE_DEBUG
AssertFatal(test1->mProcessLink.next->mProcessLink.prev==test1 && test1->mProcessLink.prev->mProcessLink.next==test1,"Doh!");
AssertFatal(test2->mProcessLink.next->mProcessLink.prev==test2 && test2->mProcessLink.prev->mProcessLink.next==test2,"Doh!");
AssertFatal(test3->mProcessLink.next->mProcessLink.prev==test3 && test3->mProcessLink.prev->mProcessLink.next==test3,"Doh!");
AssertFatal(test4->mProcessLink.next->mProcessLink.prev==test4 && test4->mProcessLink.prev->mProcessLink.next==test4,"Doh!");
#endif
}
void ProcessObject::plLinkBefore(ProcessObject * obj)
{
AssertFatal(mProcessLink.next == this && mProcessLink.prev == this,"ProcessObject::plLinkBefore: must be unlinked before calling this");
#ifdef TORQUE_DEBUG
ProcessObject * test1 = obj;
ProcessObject * test2 = obj->mProcessLink.next;
ProcessObject * test3 = obj->mProcessLink.prev;
ProcessObject * test4 = this;
#endif
// Link this before obj
mProcessLink.next = obj;
mProcessLink.prev = obj->mProcessLink.prev;
obj->mProcessLink.prev = this;
mProcessLink.prev->mProcessLink.next = this;
#ifdef TORQUE_DEBUG
AssertFatal(test1->mProcessLink.next->mProcessLink.prev==test1 && test1->mProcessLink.prev->mProcessLink.next==test1,"Doh!");
AssertFatal(test2->mProcessLink.next->mProcessLink.prev==test2 && test2->mProcessLink.prev->mProcessLink.next==test2,"Doh!");
AssertFatal(test3->mProcessLink.next->mProcessLink.prev==test3 && test3->mProcessLink.prev->mProcessLink.next==test3,"Doh!");
AssertFatal(test4->mProcessLink.next->mProcessLink.prev==test4 && test4->mProcessLink.prev->mProcessLink.next==test4,"Doh!");
#endif
}
void ProcessObject::plJoin(ProcessObject * head)
{
ProcessObject * tail1 = head->mProcessLink.prev;
ProcessObject * tail2 = mProcessLink.prev;
tail1->mProcessLink.next = this;
mProcessLink.prev = tail1;
tail2->mProcessLink.next = head;
head->mProcessLink.prev = tail2;
}
//--------------------------------------------------------------------------
ProcessList::ProcessList()
{
mCurrentTag = 0;
mDirty = false;
mTotalTicks = 0;
mLastTick = 0;
mLastTime = 0;
mLastDelta = 0.0f;
}
void ProcessList::addObject( ProcessObject *obj )
{
obj->plLinkAfter(&mHead);
}
//----------------------------------------------------------------------------
void ProcessList::orderList()
{
// ProcessObject tags are initialized to 0, so current tag should never be 0.
if (++mCurrentTag == 0)
mCurrentTag++;
// Install a temporary head node
ProcessObject list;
list.plLinkBefore(mHead.mProcessLink.next);
mHead.plUnlink();
// start out by (bubble) sorting list by GUID
for (ProcessObject * cur = list.mProcessLink.next; cur != &list; cur = cur->mProcessLink.next)
{
if (cur->mOrderGUID == 0)
// special case -- can be no lower, so accept as lowest (this is also
// a common value since it is what non ordered objects have)
continue;
for (ProcessObject * walk = cur->mProcessLink.next; walk != &list; walk = walk->mProcessLink.next)
{
if (walk->mOrderGUID < cur->mOrderGUID)
{
// swap walk and cur -- need to be careful because walk might be just after cur
// so insert after item before cur and before item after walk
ProcessObject * before = cur->mProcessLink.prev;
ProcessObject * after = walk->mProcessLink.next;
cur->plUnlink();
walk->plUnlink();
cur->plLinkBefore(after);
walk->plLinkAfter(before);
ProcessObject * swap = walk;
walk = cur;
cur = swap;
}
}
}
// Reverse topological sort into the original head node
while (list.mProcessLink.next != &list)
{
ProcessObject * ptr = list.mProcessLink.next;
ProcessObject * afterObject = ptr->getAfterObject();
ptr->mProcessTag = mCurrentTag;
ptr->plUnlink();
if (afterObject)
{
// Build chain "stack" of dependent objects and patch
// it to the end of the current list.
while (afterObject && afterObject->mProcessTag != mCurrentTag)
{
afterObject->mProcessTag = mCurrentTag;
afterObject->plUnlink();
afterObject->plLinkBefore(ptr);
ptr = afterObject;
afterObject = ptr->getAfterObject();
}
ptr->plJoin(&mHead);
}
else
ptr->plLinkBefore(&mHead);
}
mDirty = false;
}
GameBase* ProcessList::getGameBase( ProcessObject *obj )
{
if ( !obj->mIsGameBase )
return NULL;
return static_cast< GameBase* >( obj );
}
void ProcessList::dumpToConsole()
{
for (ProcessObject * pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next)
{
SimObject * obj = dynamic_cast<SimObject*>(pobj);
if (obj)
Con::printf("id %i, order guid %i, type %s", obj->getId(), pobj->mOrderGUID, obj->getClassName());
else
Con::printf("---unknown object type, order guid %i", pobj->mOrderGUID);
}
}
//----------------------------------------------------------------------------
bool ProcessList::advanceTime(SimTime timeDelta)
{
PROFILE_START(ProcessList_AdvanceTime);
// some drivers change the FPU control state, which will break our control object simulation
// (leading to packet mismatch errors due to small FP differences). So set it to the known
// state before advancing.
U32 mathState = Platform::getMathControlState();
Platform::setMathControlStateKnown();
if (mDirty)
orderList();
SimTime targetTime = mLastTime + timeDelta;
SimTime targetTick = targetTime - (targetTime % TickMs);
SimTime tickDelta = targetTick - mLastTick;
bool tickPass = mLastTick != targetTick;
if ( tickPass )
mPreTick.trigger();
// Advance all the objects.
for (; mLastTick != targetTick; mLastTick += TickMs)
onAdvanceObjects();
mLastTime = targetTime;
mLastDelta = ((TickMs - ((targetTime+1) % TickMs)) % TickMs) / F32(TickMs);
if ( tickPass )
mPostTick.trigger( tickDelta );
// restore math control state in case others are relying on it being a certain value
Platform::setMathControlState(mathState);
PROFILE_END();
return tickPass;
}
//----------------------------------------------------------------------------
void ProcessList::advanceObjects()
{
PROFILE_START(ProcessList_AdvanceObjects);
// A little link list shuffling is done here to avoid problems
// with objects being deleted from within the process method.
ProcessObject list;
list.plLinkBefore(mHead.mProcessLink.next);
mHead.plUnlink();
for (ProcessObject * pobj = list.mProcessLink.next; pobj != &list; pobj = list.mProcessLink.next)
{
pobj->plUnlink();
pobj->plLinkBefore(&mHead);
onTickObject(pobj);
}
mTotalTicks++;
PROFILE_END();
}

View file

@ -0,0 +1,193 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _PROCESSLIST_H_
#define _PROCESSLIST_H_
#ifndef _SIM_H_
#include "console/sim.h"
#endif
#ifndef _TSIGNAL_H_
#include "core/util/tSignal.h"
#endif
//----------------------------------------------------------------------------
#define TickMs 32
#define TickSec (F32(TickMs) / 1000.0f)
//----------------------------------------------------------------------------
class GameConnection;
struct Move;
class ProcessObject
{
public:
ProcessObject();
virtual ~ProcessObject() { removeFromProcessList(); }
/// Removes this object from the tick-processing list
void removeFromProcessList() { plUnlink(); }
/// Set the status of tick processing.
///
/// Set true to receive processTick, advanceTime, and interpolateTick calls.
///
/// @see processTick
/// @param t If true, tick processing is enabled.
virtual void setProcessTick( bool t ) { mProcessTick = t; }
/// Returns true if this object processes ticks.
bool isTicking() const { return mProcessTick; }
/// This is really implemented in GameBase and is only here to avoid
/// casts within ProcessList.
virtual GameConnection* getControllingClient() { return NULL; }
/// This is really implemented in GameBase and is only here to avoid
/// casts within ProcessList.
virtual U32 getPacketDataChecksum( GameConnection *conn ) { return -1; }
/// Force this object to process after some other object.
///
/// For example, a player mounted to a vehicle would want to process after
/// the vehicle to prevent a visible 'lagging' from occurring when the
/// vehicle moves. So the player would be set to processAfter(theVehicle).
///
/// @param obj Object to process after
virtual void processAfter( ProcessObject *obj ) {}
/// Clears the effects of a call to processAfter()
virtual void clearProcessAfter() {}
/// Returns the object that this processes after.
///
/// @see processAfter
virtual ProcessObject* getAfterObject() const { return NULL; }
/// Processes a move event and updates object state once every 32 milliseconds.
///
/// This takes place both on the client and server, every 32 milliseconds (1 tick).
///
/// @see ProcessList
/// @param move Move event corresponding to this tick, or NULL.
virtual void processTick( const Move *move ) {}
/// Interpolates between tick events. This takes place on the CLIENT ONLY.
///
/// @param delta Time since last call to interpolate
virtual void interpolateTick( F32 delta ) {}
/// Advances simulation time for animations. This is called every frame.
///
/// @param dt Time since last advance call
virtual void advanceTime( F32 dt ) {}
/// Allow object to modify the Move before it is ticked or sent to the server.
/// This is only called for the control object on the client-side.
virtual void preprocessMove( Move *move ) {}
//protected:
struct Link
{
ProcessObject *next;
ProcessObject *prev;
};
// Processing interface
void plUnlink();
void plLinkAfter(ProcessObject*);
void plLinkBefore(ProcessObject*);
void plJoin(ProcessObject*);
U32 mProcessTag; // Tag used during sort
U32 mOrderGUID; // UID for keeping order synced (e.g., across network or runs of sim)
Link mProcessLink; // Ordered process queue
bool mProcessTick;
bool mIsGameBase;
};
//----------------------------------------------------------------------------
typedef Signal<void()> PreTickSignal;
typedef Signal<void(SimTime)> PostTickSignal;
class GameBase;
/// List of ProcessObjects.
class ProcessList
{
public:
ProcessList();
virtual ~ProcessList() {}
void markDirty() { mDirty = true; }
bool isDirty() { return mDirty; }
SimTime getLastTime() { return mLastTime; }
F32 getLastDelta() { return mLastDelta; }
F32 getLastInterpDelta() { return mLastDelta / F32(TickMs); }
U32 getTotalTicks() { return mTotalTicks; }
void dumpToConsole();
PreTickSignal& preTickSignal() { return mPreTick; }
PostTickSignal& postTickSignal() { return mPostTick; }
virtual void addObject( ProcessObject *obj );
/// Returns true if a tick was processed.
virtual bool advanceTime( SimTime timeDelta );
protected:
void orderList();
GameBase* getGameBase( ProcessObject *obj );
virtual void advanceObjects();
virtual void onAdvanceObjects() { advanceObjects(); }
virtual void onPreTickObject( ProcessObject* ) {}
virtual void onTickObject( ProcessObject* ) {}
protected:
ProcessObject mHead;
U32 mCurrentTag;
bool mDirty;
U32 mTotalTicks;
SimTime mLastTick;
SimTime mLastTime;
F32 mLastDelta;
PreTickSignal mPreTick;
PostTickSignal mPostTick;
};
#endif // _PROCESSLIST_H_

View file

@ -0,0 +1,400 @@
//-----------------------------------------------------------------------------
// 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/gameBase/std/stdGameProcess.h"
#include "platform/profiler.h"
#include "console/consoleTypes.h"
#include "core/dnet.h"
#include "core/stream/bitStream.h"
#include "core/frameAllocator.h"
#include "core/util/refBase.h"
#include "math/mPoint3.h"
#include "math/mMatrix.h"
#include "math/mathUtils.h"
#include "T3D/gameBase/gameBase.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/gameBase/std/stdMoveList.h"
#include "T3D/fx/cameraFXMgr.h"
MODULE_BEGIN( ProcessList )
MODULE_INIT
{
StdServerProcessList::init();
StdClientProcessList::init();
}
MODULE_SHUTDOWN
{
StdServerProcessList::shutdown();
StdClientProcessList::shutdown();
}
MODULE_END;
void StdServerProcessList::init()
{
smServerProcessList = new StdServerProcessList();
}
void StdServerProcessList::shutdown()
{
delete smServerProcessList;
}
void StdClientProcessList::init()
{
smClientProcessList = new StdClientProcessList();
}
void StdClientProcessList::shutdown()
{
delete smClientProcessList;
}
//----------------------------------------------------------------------------
namespace
{
// local work class
struct GameBaseListNode
{
GameBaseListNode()
{
mPrev=this;
mNext=this;
mObject=NULL;
}
GameBaseListNode * mPrev;
GameBaseListNode * mNext;
GameBase * mObject;
void linkBefore(GameBaseListNode * obj)
{
// Link this before obj
mNext = obj;
mPrev = obj->mPrev;
obj->mPrev = this;
mPrev->mNext = this;
}
};
} // namespace
//--------------------------------------------------------------------------
// ClientProcessList
//--------------------------------------------------------------------------
StdClientProcessList::StdClientProcessList()
{
}
bool StdClientProcessList::advanceTime( SimTime timeDelta )
{
PROFILE_SCOPE( StdClientProcessList_AdvanceTime );
if ( doBacklogged( timeDelta ) )
return false;
bool ret = Parent::advanceTime( timeDelta );
ProcessObject *obj = NULL;
AssertFatal( mLastDelta >= 0.0f && mLastDelta <= 1.0f, "mLastDelta is not zero to one.");
obj = mHead.mProcessLink.next;
while ( obj != &mHead )
{
if ( obj->isTicking() )
obj->interpolateTick( mLastDelta );
obj = obj->mProcessLink.next;
}
// Inform objects of total elapsed delta so they can advance
// client side animations.
F32 dt = F32(timeDelta) / 1000;
// Update camera FX.
gCamFXMgr.update( dt );
obj = mHead.mProcessLink.next;
while ( obj != &mHead )
{
obj->advanceTime( dt );
obj = obj->mProcessLink.next;
}
return ret;
}
//----------------------------------------------------------------------------
void StdClientProcessList::onAdvanceObjects()
{
PROFILE_SCOPE( StdClientProcessList_OnAdvanceObjects );
GameConnection* connection = GameConnection::getConnectionToServer();
if ( connection )
{
// process any demo blocks that are NOT moves, and exactly one move
// we advance time in the demo stream by a move inserted on
// each tick. So before doing the tick processing we advance
// the demo stream until a move is ready
if ( connection->isPlayingBack() )
{
U32 blockType;
do
{
blockType = connection->getNextBlockType();
bool res = connection->processNextBlock();
// if there are no more blocks, exit out of this function,
// as no more client time needs to process right now - we'll
// get it all on the next advanceClientTime()
if(!res)
return;
}
while ( blockType != GameConnection::BlockTypeMove );
}
connection->mMoveList->collectMove();
advanceObjects();
}
else
advanceObjects();
}
void StdClientProcessList::onTickObject( ProcessObject *obj )
{
PROFILE_SCOPE( StdClientProcessList_OnTickObject );
// In case the object deletes itself during its processTick.
SimObjectPtr<SceneObject> safePtr = static_cast<SceneObject*>( obj );
// Each object is either advanced a single tick, or if it's
// being controlled by a client, ticked once for each pending move.
Move* movePtr;
U32 numMoves;
GameConnection* con = obj->getControllingClient();
if ( con && con->getControlObject() == obj )
{
con->mMoveList->getMoves( &movePtr, &numMoves );
if ( numMoves )
{
// Note: should only have a single move at this point
AssertFatal(numMoves==1,"ClientProccessList::onTickObject: more than one move in queue");
#ifdef TORQUE_DEBUG_NET_MOVES
U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient());
#endif
if ( obj->isTicking() )
obj->processTick( movePtr );
if ( bool(safePtr) && obj->getControllingClient() )
{
U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
// set checksum if not set or check against stored value if set
movePtr->checksum = newsum;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("move checksum: %i, (start %i), (move %f %f %f)",
movePtr->checksum,sum,movePtr->yaw,movePtr->y,movePtr->z);
#endif
}
con->mMoveList->clearMoves( 1 );
}
}
else if ( obj->isTicking() )
obj->processTick( 0 );
}
void StdClientProcessList::advanceObjects()
{
PROFILE_SCOPE( StdClientProcessList_AdvanceObjects );
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("Advance client time...");
#endif
Parent::advanceObjects();
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("---------");
#endif
}
void StdClientProcessList::clientCatchup( GameConnection * connection )
{
SimObjectPtr<GameBase> control = connection->getControlObject();
if ( control )
{
Move * movePtr;
U32 numMoves;
U32 m = 0;
connection->mMoveList->getMoves( &movePtr, &numMoves );
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("client catching up... (%i)", numMoves);
#endif
preTickSignal().trigger();
if ( control->isTicking() )
for ( m = 0; m < numMoves; m++ )
control->processTick( movePtr++ );
connection->mMoveList->clearMoves( m );
}
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("---------");
#endif
}
//--------------------------------------------------------------------------
// ServerProcessList
//--------------------------------------------------------------------------
StdServerProcessList::StdServerProcessList()
{
}
void StdServerProcessList::onPreTickObject( ProcessObject *pobj )
{
if ( pobj->mIsGameBase )
{
SimObjectPtr<GameBase> obj = getGameBase( pobj );
// Each object is either advanced a single tick, or if it's
// being controlled by a client, ticked once for each pending move.
GameConnection *con = obj->getControllingClient();
if ( con && con->getControlObject() == obj )
{
Move* movePtr;
U32 numMoves;
con->mMoveList->getMoves( &movePtr, &numMoves );
if ( numMoves == 0 )
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("no moves on object %i, skip tick",obj->getId());
#endif
return;
}
}
}
Parent::onPreTickObject (pobj );
}
void StdServerProcessList::onTickObject( ProcessObject *pobj )
{
PROFILE_SCOPE( StdServerProcessList_OnTickObject );
// Each object is either advanced a single tick, or if it's
// being controlled by a client, ticked once for each pending move.
GameConnection *con = pobj->getControllingClient();
if ( pobj->mIsGameBase && con && con->getControlObject() == pobj )
{
// In case the object is deleted during its own tick.
SimObjectPtr<GameBase> obj = getGameBase( pobj );
Move* movePtr;
U32 m, numMoves;
con->mMoveList->getMoves( &movePtr, &numMoves );
// For debugging it can be useful to know when this happens.
//if ( numMoves > 1 )
// Con::printf( "numMoves: %i", numMoves );
// Do we really need to test the control object each iteration? Does it change?
for ( m = 0; m < numMoves && con && con->getControlObject() == obj; m++, movePtr++ )
{
#ifdef TORQUE_DEBUG_NET_MOVES
U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient());
#endif
if ( obj->isTicking() )
obj->processTick( movePtr );
if ( con && con->getControlObject() == obj )
{
U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
// check move checksum
if ( movePtr->checksum != newsum )
{
#ifdef TORQUE_DEBUG_NET_MOVES
if( !obj->isAIControlled() )
Con::printf("move %i checksum disagree: %i != %i, (start %i), (move %f %f %f)",
movePtr->id, movePtr->checksum,newsum,sum,movePtr->yaw,movePtr->y,movePtr->z);
#endif
movePtr->checksum = Move::ChecksumMismatch;
}
else
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("move %i checksum agree: %i == %i, (start %i), (move %f %f %f)",
movePtr->id, movePtr->checksum,newsum,sum,movePtr->yaw,movePtr->y,movePtr->z);
#endif
}
}
}
con->mMoveList->clearMoves( m );
}
else if ( pobj->isTicking() )
pobj->processTick( 0 );
}
void StdServerProcessList::advanceObjects()
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("Advance server time...");
#endif
Parent::advanceObjects();
// Credit all connections with the elapsed tick
SimGroup *clientGroup = Sim::getClientGroup();
for(SimGroup::iterator i = clientGroup->begin(); i != clientGroup->end(); i++)
{
if (GameConnection *con = dynamic_cast<GameConnection *>(*i))
{
con->mMoveList->advanceMove();
}
}
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("---------");
#endif
}

View file

@ -0,0 +1,82 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GAMEPROCESS_STD_H_
#define _GAMEPROCESS_STD_H_
//#include "T3D/gameBase/processList.h"
#ifndef _GAMEPROCESS_H_
#include "T3D/gameBase/gameProcess.h"
#endif
class GameBase;
class GameConnection;
struct Move;
//----------------------------------------------------------------------------
/// List to keep track of GameBases to process.
class StdClientProcessList : public ClientProcessList
{
typedef ClientProcessList Parent;
protected:
// ProcessList
void onTickObject(ProcessObject *);
void advanceObjects();
void onAdvanceObjects();
public:
StdClientProcessList();
// ProcessList
bool advanceTime( SimTime timeDelta );
// ClientProcessList
void clientCatchup( GameConnection *conn );
static void init();
static void shutdown();
};
class StdServerProcessList : public ServerProcessList
{
typedef ServerProcessList Parent;
protected:
// ProcessList
void onPreTickObject( ProcessObject *pobj );
void onTickObject( ProcessObject *pobj );
void advanceObjects();
public:
StdServerProcessList();
static void init();
static void shutdown();
};
#endif // _GAMEPROCESS_STD_H_

View file

@ -0,0 +1,198 @@
//-----------------------------------------------------------------------------
// 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/gameBase/std/stdMoveList.h"
#include "T3D/gameBase/gameConnection.h"
#include "core/stream/bitStream.h"
#define MAX_MOVE_PACKET_SENDS 4
StdMoveList::StdMoveList()
{
mMoveCredit = MaxMoveCount;
}
U32 StdMoveList::getMoves(Move** movePtr,U32* numMoves)
{
if (!mConnection->isConnectionToServer())
{
if (mMoveVec.size() > mMoveCredit)
mMoveVec.setSize(mMoveCredit);
}
return Parent::getMoves(movePtr,numMoves);
}
void StdMoveList::clearMoves(U32 count)
{
if (!mConnection->isConnectionToServer())
{
count = mClamp(count,0,mMoveCredit);
mMoveCredit -= count;
}
Parent::clearMoves(count);
}
void StdMoveList::advanceMove()
{
AssertFatal(!mConnection->isConnectionToServer(), "Cannot inc move credit on the client.");
// Game tick increment
mMoveCredit++;
if (mMoveCredit > MaxMoveCount)
mMoveCredit = MaxMoveCount;
// Clear pending moves for the elapsed time if there
// is no control object.
if ( mConnection->getControlObject() == NULL )
mMoveVec.clear();
}
void StdMoveList::clientWriteMovePacket(BitStream *bstream)
{
AssertFatal(mLastMoveAck == mFirstMoveIndex, "Invalid move index.");
U32 count = mMoveVec.size();
Move * move = mMoveVec.address();
U32 start = mLastMoveAck;
U32 offset;
for(offset = 0; offset < count; offset++)
if(move[offset].sendCount < MAX_MOVE_PACKET_SENDS)
break;
if(offset == count && count != 0)
offset--;
start += offset;
count -= offset;
if (count > MaxMoveCount)
count = MaxMoveCount;
bstream->writeInt(start,32);
bstream->writeInt(count,MoveCountBits);
Move * prevMove = NULL;
for (int i = 0; i < count; i++)
{
move[offset + i].sendCount++;
move[offset + i].pack(bstream,prevMove);
bstream->writeInt(move[offset + i].checksum,Move::ChecksumBits);
prevMove = &move[offset+i];
}
}
void StdMoveList::serverReadMovePacket(BitStream *bstream)
{
// Server side packet read.
U32 start = bstream->readInt(32);
U32 count = bstream->readInt(MoveCountBits);
Move * prevMove = NULL;
Move prevMoveHolder;
// Skip forward (must be starting up), or over the moves
// we already have.
int skip = mLastMoveAck - start;
if (skip < 0)
{
mLastMoveAck = start;
}
else
{
if (skip > count)
skip = count;
for (int i = 0; i < skip; i++)
{
prevMoveHolder.unpack(bstream,prevMove);
prevMoveHolder.checksum = bstream->readInt(Move::ChecksumBits);
prevMove = &prevMoveHolder;
S32 idx = mMoveVec.size()-skip+i;
if (idx>=0)
{
#ifdef TORQUE_DEBUG_NET_MOVES
if (mMoveVec[idx].checksum != prevMoveHolder.checksum)
Con::printf("updated checksum on move %i from %i to %i",mMoveVec[idx].id,mMoveVec[idx].checksum,prevMoveHolder.checksum);
#endif
mMoveVec[idx].checksum = prevMoveHolder.checksum;
}
}
start += skip;
count = count - skip;
}
// Put the rest on the move list.
int index = mMoveVec.size();
mMoveVec.increment(count);
while (index < mMoveVec.size())
{
mMoveVec[index].unpack(bstream,prevMove);
mMoveVec[index].checksum = bstream->readInt(Move::ChecksumBits);
prevMove = &mMoveVec[index];
mMoveVec[index].id = start++;
index ++;
}
mLastMoveAck += count;
}
void StdMoveList::serverWriteMovePacket(BitStream * bstream)
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("ack %i minus %i",mLastMoveAck,mMoveVec.size());
#endif
// acknowledge only those moves that have been ticked
bstream->writeInt(mLastMoveAck - mMoveVec.size(),32);
}
void StdMoveList::clientReadMovePacket(BitStream * bstream)
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("pre move ack: %i", mLastMoveAck);
#endif
mLastMoveAck = bstream->readInt(32);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("post move ack %i, first move %i, last move %i", mLastMoveAck, mFirstMoveIndex, mLastClientMove);
#endif
if (mLastMoveAck < mFirstMoveIndex)
mLastMoveAck = mFirstMoveIndex;
if(mLastMoveAck > mLastClientMove)
mLastClientMove = mLastMoveAck;
while(mFirstMoveIndex < mLastMoveAck)
{
if (mMoveVec.size())
{
mMoveVec.pop_front();
mFirstMoveIndex++;
}
else
{
AssertWarn(1, "Popping off too many moves!");
mFirstMoveIndex = mLastMoveAck;
}
}
}

View file

@ -0,0 +1,55 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _MOVELIST_STD_H_
#define _MOVELIST_STD_H_
#ifndef _MOVELIST_H_
#include "T3D/gameBase/moveList.h"
#endif
class StdMoveList : public MoveList
{
typedef MoveList Parent;
public:
StdMoveList();
void clientWriteMovePacket(BitStream *);
void clientReadMovePacket(BitStream *);
void serverWriteMovePacket(BitStream *);
void serverReadMovePacket(BitStream *);
U32 getMoves(Move**,U32* numMoves);
void clearMoves(U32 count);
void advanceMove();
void onAdvanceObjects() {}
protected:
U32 mMoveCredit;
};
#endif // _MOVELIST_STD_H_

View file

@ -0,0 +1,174 @@
//-----------------------------------------------------------------------------
// 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/gameBase/gameBase.h"
#include "console/consoleTypes.h"
#include "console/consoleInternal.h"
#include "core/stream/bitStream.h"
#include "sim/netConnection.h"
#include "T3D/gameBase/gameConnection.h"
#include "math/mathIO.h"
#include "T3D/gameBase/moveManager.h"
#include "T3D/gameBase/gameProcess.h"
struct TickCacheHead
{
TickCacheEntry * oldest;
TickCacheEntry * newest;
TickCacheEntry * next;
U32 numEntry;
};
namespace
{
FreeListChunker<TickCacheHead> sgTickCacheHeadStore;
FreeListChunker<TickCacheEntry> sgTickCacheEntryStore;
FreeListChunker<Move> sgMoveStore;
static TickCacheHead * allocHead() { return sgTickCacheHeadStore.alloc(); }
static void freeHead(TickCacheHead * head) { sgTickCacheHeadStore.free(head); }
static TickCacheEntry * allocEntry() { return sgTickCacheEntryStore.alloc(); }
static void freeEntry(TickCacheEntry * entry) { sgTickCacheEntryStore.free(entry); }
static Move * allocMove() { return sgMoveStore.alloc(); }
static void freeMove(Move * move) { sgMoveStore.free(move); }
}
//----------------------------------------------------------------------------
TickCache::~TickCache()
{
if (mTickCacheHead)
{
setCacheSize(0);
freeHead(mTickCacheHead);
mTickCacheHead = NULL;
}
}
Move * TickCacheEntry::allocateMove()
{
return allocMove();
}
TickCacheEntry * TickCache::addCacheEntry()
{
// Add a new entry, creating head if needed
if (!mTickCacheHead)
{
mTickCacheHead = allocHead();
mTickCacheHead->newest = mTickCacheHead->oldest = mTickCacheHead->next = NULL;
mTickCacheHead->numEntry = 0;
}
if (!mTickCacheHead->newest)
{
mTickCacheHead->newest = mTickCacheHead->oldest = allocEntry();
}
else
{
mTickCacheHead->newest->next = allocEntry();
mTickCacheHead->newest = mTickCacheHead->newest->next;
}
mTickCacheHead->newest->next = NULL;
mTickCacheHead->newest->move = NULL;
mTickCacheHead->numEntry++;
return mTickCacheHead->newest;
}
void TickCache::setCacheSize(S32 len)
{
// grow cache to len size, adding to newest side of the list
while (!mTickCacheHead || mTickCacheHead->numEntry < len)
addCacheEntry();
// shrink tick cache down to given size, popping off oldest entries first
while (mTickCacheHead && mTickCacheHead->numEntry > len)
dropOldest();
}
void TickCache::dropOldest()
{
AssertFatal(mTickCacheHead->oldest,"Popping off too many tick cache entries");
TickCacheEntry * oldest = mTickCacheHead->oldest;
mTickCacheHead->oldest = oldest->next;
if (oldest->move)
freeMove(oldest->move);
freeEntry(oldest);
mTickCacheHead->numEntry--;
if (mTickCacheHead->numEntry < 2)
mTickCacheHead->newest = mTickCacheHead->oldest;
}
void TickCache::dropNextOldest()
{
AssertFatal(mTickCacheHead->oldest && mTickCacheHead->numEntry>1,"Popping off too many tick cache entries");
TickCacheEntry * oldest = mTickCacheHead->oldest;
TickCacheEntry * nextoldest = mTickCacheHead->oldest->next;
oldest->next = nextoldest->next;
if (nextoldest->move)
freeMove(nextoldest->move);
freeEntry(nextoldest);
mTickCacheHead->numEntry--;
if (mTickCacheHead->numEntry==1)
mTickCacheHead->newest = mTickCacheHead->oldest;
}
void TickCache::ageCache(S32 numToAge, S32 len)
{
AssertFatal(mTickCacheHead,"No tick cache head");
AssertFatal(mTickCacheHead->numEntry>=numToAge,"Too few entries!");
AssertFatal(mTickCacheHead->numEntry>numToAge,"Too few entries!");
while (numToAge--)
dropOldest();
while (mTickCacheHead->numEntry>len)
dropNextOldest();
while (mTickCacheHead->numEntry<len)
addCacheEntry();
}
void TickCache::beginCacheList()
{
// get ready iterate from oldest to newest entry
if (mTickCacheHead)
mTickCacheHead->next = mTickCacheHead->oldest;
// if no head, that's ok, we'll just add entries as we go
}
TickCacheEntry * TickCache::incCacheList(bool addIfNeeded)
{
// continue iterating through cache, returning current entry
// we'll add new entries if need be
TickCacheEntry * ret = NULL;
if (mTickCacheHead && mTickCacheHead->next)
{
ret = mTickCacheHead->next;
mTickCacheHead->next = mTickCacheHead->next->next;
}
else if (addIfNeeded)
{
addCacheEntry();
ret = mTickCacheHead->newest;
}
return ret;
}

View file

@ -0,0 +1,69 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TICKCACHE_H_
#define _TICKCACHE_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
struct Move;
struct TickCacheEntry
{
enum { MaxPacketSize=140 };
U8 packetData[MaxPacketSize];
TickCacheEntry * next;
Move * move;
// If you want to assign moves to tick cache for later playback, allocate them here
Move * allocateMove();
};
struct TickCacheHead;
class TickCache
{
public:
TickCache();
~TickCache();
TickCacheEntry * addCacheEntry();
void dropOldest();
void dropNextOldest();
void ageCache(S32 numToAge, S32 len);
void setCacheSize(S32 len);
void beginCacheList();
TickCacheEntry * incCacheList(bool addIfNeeded=true);
private:
TickCacheHead * mTickCacheHead;
};
inline TickCache::TickCache()
{
mTickCacheHead = NULL;
}
#endif // _TICKCACHE_H_

View file

@ -0,0 +1,470 @@
//-----------------------------------------------------------------------------
// 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/gameFunctions.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/camera.h"
#include "T3D/sfx/sfx3DWorld.h"
#include "console/consoleTypes.h"
#include "gui/3d/guiTSControl.h"
#include "core/util/journal/process.h"
#include "materials/materialManager.h"
#include "math/mEase.h"
#include "core/module.h"
#include "console/engineAPI.h"
static void RegisterGameFunctions();
static void Process3D();
MODULE_BEGIN( 3D )
MODULE_INIT_AFTER( Process )
MODULE_INIT_AFTER( Scene )
MODULE_SHUTDOWN_BEFORE( Process )
MODULE_SHUTDOWN_BEFORE( Sim )
MODULE_SHUTDOWN_AFTER( Scene )
MODULE_INIT
{
Process::notify(Process3D, PROCESS_TIME_ORDER);
GameConnection::smFovUpdate.notify(GameSetCameraFov);
RegisterGameFunctions();
}
MODULE_SHUTDOWN
{
GameConnection::smFovUpdate.remove(GameSetCameraFov);
Process::remove(Process3D);
}
MODULE_END;
static S32 gEaseInOut = Ease::InOut;
static S32 gEaseIn = Ease::In;
static S32 gEaseOut = Ease::Out;
static S32 gEaseLinear = Ease::Linear;
static S32 gEaseQuadratic= Ease::Quadratic;
static S32 gEaseCubic= Ease::Cubic;
static S32 gEaseQuartic = Ease::Quartic;
static S32 gEaseQuintic = Ease::Quintic;
static S32 gEaseSinusoidal= Ease::Sinusoidal;
static S32 gEaseExponential = Ease::Exponential;
static S32 gEaseCircular = Ease::Circular;
static S32 gEaseElastic = Ease::Elastic;
static S32 gEaseBack = Ease::Back;
static S32 gEaseBounce = Ease::Bounce;
extern void ShowInit();
//------------------------------------------------------------------------------
/// Camera and FOV info
namespace {
const U32 MaxZoomSpeed = 2000; ///< max number of ms to reach target FOV
static F32 sConsoleCameraFov = 90.f; ///< updated to camera FOV each frame
static F32 sDefaultFov = 90.f; ///< normal FOV
static F32 sCameraFov = 90.f; ///< current camera FOV
static F32 sTargetFov = 90.f; ///< the desired FOV
static F32 sLastCameraUpdateTime = 0; ///< last time camera was updated
static S32 sZoomSpeed = 500; ///< ms per 90deg fov change
/// A scale to apply to the normal visible distance
/// typically used for tuning performance.
static F32 sVisDistanceScale = 1.0f;
} // namespace {}
// query
static SimpleQueryList sgServerQueryList;
static U32 sgServerQueryIndex = 0;
//SERVER FUNCTIONS ONLY
ConsoleFunctionGroupBegin( Containers, "Spatial query functions. <b>Server side only!</b>");
ConsoleFunction(containerFindFirst, const char*, 6, 6, "(int mask, Point3F point, float x, float y, float z)"
"@brief Find objects matching the bitmask type within a box centered at point, with extents x, y, z.\n\n"
"@returns The first object found, or an empty string if nothing was found. Thereafter, you can get more "
"results using containerFindNext()."
"@see containerFindNext\n"
"@ingroup Game")
{
//find out what we're looking for
U32 typeMask = U32(dAtoi(argv[1]));
//find the center of the container volume
Point3F origin(0.0f, 0.0f, 0.0f);
dSscanf(argv[2], "%g %g %g", &origin.x, &origin.y, &origin.z);
//find the box dimensions
Point3F size(0.0f, 0.0f, 0.0f);
size.x = mFabs(dAtof(argv[3]));
size.y = mFabs(dAtof(argv[4]));
size.z = mFabs(dAtof(argv[5]));
//build the container volume
Box3F queryBox;
queryBox.minExtents = origin;
queryBox.maxExtents = origin;
queryBox.minExtents -= size;
queryBox.maxExtents += size;
//initialize the list, and do the query
sgServerQueryList.mList.clear();
gServerContainer.findObjects(queryBox, typeMask, SimpleQueryList::insertionCallback, &sgServerQueryList);
//return the first element
sgServerQueryIndex = 0;
char *buff = Con::getReturnBuffer(100);
if (sgServerQueryList.mList.size())
dSprintf(buff, 100, "%d", sgServerQueryList.mList[sgServerQueryIndex++]->getId());
else
buff[0] = '\0';
return buff;
}
ConsoleFunction( containerFindNext, const char*, 1, 1, "()"
"@brief Get more results from a previous call to containerFindFirst().\n\n"
"@note You must call containerFindFirst() to begin the search.\n"
"@returns The next object found, or an empty string if nothing else was found.\n"
"@see containerFindFirst()\n"
"@ingroup Game")
{
//return the next element
char *buff = Con::getReturnBuffer(100);
if (sgServerQueryIndex < sgServerQueryList.mList.size())
dSprintf(buff, 100, "%d", sgServerQueryList.mList[sgServerQueryIndex++]->getId());
else
buff[0] = '\0';
return buff;
}
ConsoleFunctionGroupEnd( Containers );
//------------------------------------------------------------------------------
bool GameGetCameraTransform(MatrixF *mat, Point3F *velocity)
{
// Return the position and velocity of the control object
GameConnection* connection = GameConnection::getConnectionToServer();
return connection && connection->getControlCameraTransform(0, mat) &&
connection->getControlCameraVelocity(velocity);
}
//------------------------------------------------------------------------------
DefineEngineFunction( setDefaultFov, void, ( F32 defaultFOV ),,
"@brief Set the default FOV for a camera.\n"
"@param defaultFOV The default field of view in degrees\n"
"@ingroup CameraSystem")
{
sDefaultFov = mClampF(defaultFOV, MinCameraFov, MaxCameraFov);
if(sCameraFov == sTargetFov)
sTargetFov = sDefaultFov;
}
DefineEngineFunction( setZoomSpeed, void, ( S32 speed ),,
"@brief Set the zoom speed of the camera.\n"
"This affects how quickly the camera changes from one field of view "
"to another.\n"
"@param speed The camera's zoom speed in ms per 90deg FOV change\n"
"@ingroup CameraSystem")
{
sZoomSpeed = mClamp(speed, 0, MaxZoomSpeed);
}
DefineEngineFunction( setFov, void, ( F32 FOV ),,
"@brief Set the FOV of the camera.\n"
"@param FOV The camera's new FOV in degrees\n"
"@ingroup CameraSystem")
{
sTargetFov = mClampF(FOV, MinCameraFov, MaxCameraFov);
}
F32 GameGetCameraFov()
{
return(sCameraFov);
}
void GameSetCameraFov(F32 fov)
{
sTargetFov = sCameraFov = fov;
}
void GameSetCameraTargetFov(F32 fov)
{
sTargetFov = fov;
}
void GameUpdateCameraFov()
{
F32 time = F32(Platform::getVirtualMilliseconds());
// need to update fov?
if(sTargetFov != sCameraFov)
{
F32 delta = time - sLastCameraUpdateTime;
// snap zoom?
if((sZoomSpeed == 0) || (delta <= 0.f))
sCameraFov = sTargetFov;
else
{
// gZoomSpeed is time in ms to zoom 90deg
F32 step = 90.f * (delta / F32(sZoomSpeed));
if(sCameraFov > sTargetFov)
{
sCameraFov -= step;
if(sCameraFov < sTargetFov)
sCameraFov = sTargetFov;
}
else
{
sCameraFov += step;
if(sCameraFov > sTargetFov)
sCameraFov = sTargetFov;
}
}
}
// the game connection controls the vertical and the horizontal
GameConnection * connection = GameConnection::getConnectionToServer();
if(connection)
{
// check if fov is valid on control object
if(connection->isValidControlCameraFov(sCameraFov))
connection->setControlCameraFov(sCameraFov);
else
{
// will set to the closest fov (fails only on invalid control object)
if(connection->setControlCameraFov(sCameraFov))
{
F32 setFov = sCameraFov;
connection->getControlCameraFov(&setFov);
sTargetFov = sCameraFov = setFov;
}
}
}
// update the console variable
sConsoleCameraFov = sCameraFov;
sLastCameraUpdateTime = time;
}
//--------------------------------------------------------------------------
#ifdef TORQUE_DEBUG
// ConsoleFunction(dumpTSShapes, void, 1, 1, "dumpTSShapes();")
// {
// argc, argv;
// FindMatch match("*.dts", 4096);
// gResourceManager->findMatches(&match);
// for (U32 i = 0; i < match.numMatches(); i++)
// {
// U32 j;
// Resource<TSShape> shape = ResourceManager::get().load(match.matchList[i]);
// if (bool(shape) == false)
// Con::errorf(" aaa Couldn't load: %s", match.matchList[i]);
// U32 numMeshes = 0, numSkins = 0;
// for (j = 0; j < shape->meshes.size(); j++)
// if (shape->meshes[j])
// numMeshes++;
// for (j = 0; j < shape->skins.size(); j++)
// if (shape->skins[j])
// numSkins++;
// Con::printf(" aaa Shape: %s (%d meshes, %d skins)", match.matchList[i], numMeshes, numSkins);
// Con::printf(" aaa Meshes");
// for (j = 0; j < shape->meshes.size(); j++)
// {
// if (shape->meshes[j])
// Con::printf(" aaa %d -> nf: %d, nmf: %d, nvpf: %d (%d, %d, %d, %d, %d)",
// shape->meshes[j]->meshType & TSMesh::TypeMask,
// shape->meshes[j]->numFrames,
// shape->meshes[j]->numMatFrames,
// shape->meshes[j]->vertsPerFrame,
// shape->meshes[j]->verts.size(),
// shape->meshes[j]->norms.size(),
// shape->meshes[j]->tverts.size(),
// shape->meshes[j]->primitives.size(),
// shape->meshes[j]->indices.size());
// }
// Con::printf(" aaa Skins");
// for (j = 0; j < shape->skins.size(); j++)
// {
// if (shape->skins[j])
// Con::printf(" aaa %d -> nf: %d, nmf: %d, nvpf: %d (%d, %d, %d, %d, %d)",
// shape->skins[j]->meshType & TSMesh::TypeMask,
// shape->skins[j]->numFrames,
// shape->skins[j]->numMatFrames,
// shape->skins[j]->vertsPerFrame,
// shape->skins[j]->verts.size(),
// shape->skins[j]->norms.size(),
// shape->skins[j]->tverts.size(),
// shape->skins[j]->primitives.size(),
// shape->skins[j]->indices.size());
// }
// }
// }
#endif
bool GameProcessCameraQuery(CameraQuery *query)
{
GameConnection* connection = GameConnection::getConnectionToServer();
if (connection && connection->getControlCameraTransform(0.032f, &query->cameraMatrix))
{
query->object = dynamic_cast<ShapeBase*>(connection->getControlObject());
query->nearPlane = gClientSceneGraph->getNearClip();
// Scale the normal visible distance by the performance
// tuning scale which we never let over 1.
sVisDistanceScale = mClampF( sVisDistanceScale, 0.01f, 1.0f );
query->farPlane = gClientSceneGraph->getVisibleDistance() * sVisDistanceScale;
F32 cameraFov;
if(!connection->getControlCameraFov(&cameraFov))
return false;
query->fov = mDegToRad(cameraFov);
return true;
}
return false;
}
void GameRenderWorld()
{
PROFILE_START(GameRenderWorld);
FrameAllocator::setWaterMark(0);
gClientSceneGraph->renderScene( SPT_Diffuse );
// renderScene leaves some states dirty, which causes problems if GameTSCtrl is the last Gui object rendered
GFX->updateStates();
AssertFatal(FrameAllocator::getWaterMark() == 0,
"Error, someone didn't reset the water mark on the frame allocator!");
FrameAllocator::setWaterMark(0);
PROFILE_END();
}
static void Process3D()
{
MATMGR->updateTime();
// Update the SFX world, if there is one.
if( gSFX3DWorld )
gSFX3DWorld->update();
}
static void RegisterGameFunctions()
{
Con::addVariable( "$pref::Camera::distanceScale", TypeF32, &sVisDistanceScale,
"A scale to apply to the normal visible distance, typically used for tuning performance.\n"
"@ingroup Game");
Con::addVariable( "$cameraFov", TypeF32, &sConsoleCameraFov,
"The camera's Field of View.\n\n"
"@ingroup Game" );
// Stuff game types into the console
Con::setIntVariable("$TypeMasks::StaticObjectType", StaticObjectType);
Con::setIntVariable("$TypeMasks::EnvironmentObjectType", EnvironmentObjectType);
Con::setIntVariable("$TypeMasks::TerrainObjectType", TerrainObjectType);
Con::setIntVariable("$TypeMasks::InteriorObjectType", InteriorObjectType);
Con::setIntVariable("$TypeMasks::WaterObjectType", WaterObjectType);
Con::setIntVariable("$TypeMasks::TriggerObjectType", TriggerObjectType);
Con::setIntVariable("$TypeMasks::MarkerObjectType", MarkerObjectType);
Con::setIntVariable("$TypeMasks::GameBaseObjectType", GameBaseObjectType);
Con::setIntVariable("$TypeMasks::ShapeBaseObjectType", ShapeBaseObjectType);
Con::setIntVariable("$TypeMasks::CameraObjectType", CameraObjectType);
Con::setIntVariable("$TypeMasks::StaticShapeObjectType", StaticShapeObjectType);
Con::setIntVariable("$TypeMasks::DynamicShapeObjectType", DynamicShapeObjectType);
Con::setIntVariable("$TypeMasks::PlayerObjectType", PlayerObjectType);
Con::setIntVariable("$TypeMasks::ItemObjectType", ItemObjectType);
Con::setIntVariable("$TypeMasks::VehicleObjectType", VehicleObjectType);
Con::setIntVariable("$TypeMasks::VehicleBlockerObjectType", VehicleBlockerObjectType);
Con::setIntVariable("$TypeMasks::ProjectileObjectType", ProjectileObjectType);
Con::setIntVariable("$TypeMasks::ExplosionObjectType", ExplosionObjectType);
Con::setIntVariable("$TypeMasks::CorpseObjectType", CorpseObjectType);
Con::setIntVariable("$TypeMasks::DebrisObjectType", DebrisObjectType);
Con::setIntVariable("$TypeMasks::PhysicalZoneObjectType", PhysicalZoneObjectType);
Con::setIntVariable("$TypeMasks::LightObjectType", LightObjectType);
Con::addVariable("Ease::InOut", TypeS32, &gEaseInOut,
"InOut ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::In", TypeS32, &gEaseIn,
"In ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Out", TypeS32, &gEaseOut,
"Out ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Linear", TypeS32, &gEaseLinear,
"Linear ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Quadratic", TypeS32, &gEaseQuadratic,
"Quadratic ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Cubic", TypeS32, &gEaseCubic,
"Cubic ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Quartic", TypeS32, &gEaseQuartic,
"Quartic ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Quintic", TypeS32, &gEaseQuintic,
"Quintic ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Sinusoidal", TypeS32, &gEaseSinusoidal,
"Sinusoidal ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Exponential", TypeS32, &gEaseExponential,
"Exponential ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Circular", TypeS32, &gEaseCircular,
"Circular ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Elastic", TypeS32, &gEaseElastic,
"Elastic ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Back", TypeS32, &gEaseBack,
"Backwards ease for curve movement.\n"
"@ingroup Game");
Con::addVariable("Ease::Bounce", TypeS32, &gEaseBounce,
"Bounce ease for curve movement.\n"
"@ingroup Game");
}

View file

@ -0,0 +1,64 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GAMEFUNCTIONS_H_
#define _GAMEFUNCTIONS_H_
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _MMATRIX_H_
#include "math/mMatrix.h"
#endif
struct CameraQuery;
/// Actually renders the world. This is the function that will render the
/// scene ONLY - new guis, no damage flashes.
void GameRenderWorld();
/// Renders overlays such as damage flashes, white outs, and water masks.
/// These are usually a color applied over the entire screen.
void GameRenderFilters(const CameraQuery& camq);
/// Does the same thing as GameGetCameraTransform, but fills in other data
/// including information about the far and near clipping planes.
bool GameProcessCameraQuery(CameraQuery *query);
/// Gets the position, rotation, and velocity of the camera.
bool GameGetCameraTransform(MatrixF *mat, Point3F *velocity);
/// Gets the camera field of view angle.
F32 GameGetCameraFov();
/// Sets the field of view angle of the camera.
void GameSetCameraFov(F32 fov);
/// Sets where the camera fov will be change to. This is for
/// non-instantaneous zooms/retractions.
void GameSetCameraTargetFov(F32 fov);
/// Update the camera fov to be closer to the target fov.
void GameUpdateCameraFov();
#endif // _GAMEFUNCTIONS_H_

View file

@ -0,0 +1,209 @@
//-----------------------------------------------------------------------------
// 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 "T3D/gameTSCtrl.h"
#include "console/consoleTypes.h"
#include "T3D/gameBase/gameBase.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/shapeBase.h"
#include "T3D/gameFunctions.h"
#include "console/engineAPI.h"
//---------------------------------------------------------------------------
// Debug stuff:
Point3F lineTestStart = Point3F(0, 0, 0);
Point3F lineTestEnd = Point3F(0, 1000, 0);
Point3F lineTestIntersect = Point3F(0, 0, 0);
bool gSnapLine = false;
//----------------------------------------------------------------------------
// Class: GameTSCtrl
//----------------------------------------------------------------------------
IMPLEMENT_CONOBJECT(GameTSCtrl);
// See Torque manual (.CHM) for more information
ConsoleDocClass( GameTSCtrl,
"@brief The main 3D viewport for a Torque 3D game.\n\n"
"@ingroup Gui3D\n");
GameTSCtrl::GameTSCtrl()
{
}
//---------------------------------------------------------------------------
bool GameTSCtrl::onAdd()
{
if ( !Parent::onAdd() )
return false;
#ifdef TORQUE_DEMO_WATERMARK
mWatermark.init();
#endif
return true;
}
//---------------------------------------------------------------------------
bool GameTSCtrl::processCameraQuery(CameraQuery *camq)
{
GameUpdateCameraFov();
return GameProcessCameraQuery(camq);
}
//---------------------------------------------------------------------------
void GameTSCtrl::renderWorld(const RectI &updateRect)
{
GameRenderWorld();
}
//---------------------------------------------------------------------------
void GameTSCtrl::makeScriptCall(const char *func, const GuiEvent &evt) const
{
// write screen position
char *sp = Con::getArgBuffer(32);
dSprintf(sp, 32, "%d %d", evt.mousePoint.x, evt.mousePoint.y);
// write world position
char *wp = Con::getArgBuffer(32);
Point3F camPos;
mLastCameraQuery.cameraMatrix.getColumn(3, &camPos);
dSprintf(wp, 32, "%g %g %g", camPos.x, camPos.y, camPos.z);
// write click vector
char *vec = Con::getArgBuffer(32);
Point3F fp(evt.mousePoint.x, evt.mousePoint.y, 1.0);
Point3F ray;
unproject(fp, &ray);
ray -= camPos;
ray.normalizeSafe();
dSprintf(vec, 32, "%g %g %g", ray.x, ray.y, ray.z);
Con::executef( (SimObject*)this, func, sp, wp, vec );
}
void GameTSCtrl::onMouseDown(const GuiEvent &evt)
{
Parent::onMouseDown(evt);
if( isMethod( "onMouseDown" ) )
makeScriptCall( "onMouseDown", evt );
}
void GameTSCtrl::onRightMouseDown(const GuiEvent &evt)
{
Parent::onRightMouseDown(evt);
if( isMethod( "onRightMouseDown" ) )
makeScriptCall( "onRightMouseDown", evt );
}
void GameTSCtrl::onMiddleMouseDown(const GuiEvent &evt)
{
Parent::onMiddleMouseDown(evt);
if( isMethod( "onMiddleMouseDown" ) )
makeScriptCall( "onMiddleMouseDown", evt );
}
void GameTSCtrl::onMouseUp(const GuiEvent &evt)
{
Parent::onMouseUp(evt);
if( isMethod( "onMouseUp" ) )
makeScriptCall( "onMouseUp", evt );
}
void GameTSCtrl::onRightMouseUp(const GuiEvent &evt)
{
Parent::onRightMouseUp(evt);
if( isMethod( "onRightMouseUp" ) )
makeScriptCall( "onRightMouseUp", evt );
}
void GameTSCtrl::onMiddleMouseUp(const GuiEvent &evt)
{
Parent::onMiddleMouseUp(evt);
if( isMethod( "onMiddleMouseUp" ) )
makeScriptCall( "onMiddleMouseUp", evt );
}
void GameTSCtrl::onMouseMove(const GuiEvent &evt)
{
if(gSnapLine)
return;
MatrixF mat;
Point3F vel;
if ( GameGetCameraTransform(&mat, &vel) )
{
Point3F pos;
mat.getColumn(3,&pos);
Point3F screenPoint((F32)evt.mousePoint.x, (F32)evt.mousePoint.y, -1.0f);
Point3F worldPoint;
if (unproject(screenPoint, &worldPoint)) {
Point3F vec = worldPoint - pos;
lineTestStart = pos;
vec.normalizeSafe();
lineTestEnd = pos + vec * 1000;
}
}
}
void GameTSCtrl::onRender(Point2I offset, const RectI &updateRect)
{
// check if should bother with a render
GameConnection * con = GameConnection::getConnectionToServer();
bool skipRender = !con || (con->getWhiteOut() >= 1.f) || (con->getDamageFlash() >= 1.f) || (con->getBlackOut() >= 1.f);
if(!skipRender || true)
Parent::onRender(offset, updateRect);
#ifdef TORQUE_DEMO_WATERMARK
mWatermark.render(getExtent());
#endif
}
//--------------------------------------------------------------------------
DefineEngineFunction(snapToggle, void, (),,
"@brief Prevents mouse movement from being processed\n\n"
"In the source, whenever a mouse move event occurs "
"GameTSCtrl::onMouseMove() is called. Whenever snapToggle() "
"is called, it will flag a variable that can prevent this "
"from happening: gSnapLine. This variable is not exposed to "
"script, so you need to call this function to trigger it.\n\n"
"@tsexample\n"
"// Snapping is off by default, so we will toggle\n"
"// it on first:\n"
"PlayGui.snapToggle();\n\n"
"// Mouse movement should be disabled\n"
"// Let's turn it back on\n"
"PlayGui.snapToggle();\n"
"@endtsexample\n\n"
"@ingroup GuiGame")
{
gSnapLine = !gSnapLine;
}
//
//ConsoleFunction( snapToggle, void, 1, 1, "()" )
//{
// gSnapLine = !gSnapLine;
//}

View file

@ -0,0 +1,78 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GAMETSCTRL_H_
#define _GAMETSCTRL_H_
#ifndef _GAME_H_
#include "app/game.h"
#endif
#ifndef _GUITSCONTROL_H_
#include "gui/3d/guiTSControl.h"
#endif
#ifdef TORQUE_DEMO_WATERMARK
#ifndef _WATERMARK_H_
#include "demo/watermark/watermark.h"
#endif
#endif
class ProjectileData;
class GameBase;
//----------------------------------------------------------------------------
class GameTSCtrl : public GuiTSCtrl
{
private:
typedef GuiTSCtrl Parent;
#ifdef TORQUE_DEMO_WATERMARK
Watermark mWatermark;
#endif
void makeScriptCall(const char *func, const GuiEvent &evt) const;
public:
GameTSCtrl();
DECLARE_CONOBJECT(GameTSCtrl);
DECLARE_DESCRIPTION( "A control that renders a 3D view from the current control object." );
bool processCameraQuery(CameraQuery *query);
void renderWorld(const RectI &updateRect);
// GuiControl
virtual void onMouseDown(const GuiEvent &evt);
virtual void onRightMouseDown(const GuiEvent &evt);
virtual void onMiddleMouseDown(const GuiEvent &evt);
virtual void onMouseUp(const GuiEvent &evt);
virtual void onRightMouseUp(const GuiEvent &evt);
virtual void onMiddleMouseUp(const GuiEvent &evt);
void onMouseMove(const GuiEvent &evt);
void onRender(Point2I offset, const RectI &updateRect);
virtual bool onAdd();
};
#endif

View file

@ -0,0 +1,556 @@
//-----------------------------------------------------------------------------
// 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/groundPlane.h"
#include "renderInstance/renderPassManager.h"
#include "scene/sceneRenderState.h"
#include "materials/sceneData.h"
#include "materials/materialDefinition.h"
#include "materials/materialManager.h"
#include "materials/baseMatInstance.h"
#include "math/util/frustum.h"
#include "math/mPlane.h"
#include "console/consoleTypes.h"
#include "console/engineAPI.h"
#include "core/stream/bitStream.h"
#include "collision/boxConvex.h"
#include "collision/abstractPolyList.h"
#include "T3D/physics/physicsPlugin.h"
#include "T3D/physics/physicsBody.h"
#include "T3D/physics/physicsCollision.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
/// limit, too). Dynamically clipping extents into range is a problem since the
/// location of the horizon depends on the camera orientation. Just shifting
/// squareSize as needed also doesn't work as that causes different geometry to
/// be generated depending on the viewpoint and orientation which affects the
/// texturing.
static const F32 sMIN_SQUARE_SIZE = 16;
IMPLEMENT_CO_NETOBJECT_V1( GroundPlane );
ConsoleDocClass( GroundPlane,
"@brief An infinite plane extending in all direction.\n\n"
"%GroundPlane is useful for setting up simple testing scenes, or it can be "
"placed under an existing scene to keep objects from falling into 'nothing'.\n\n"
"%GroundPlane may not be moved or rotated, it is always at the world origin.\n\n"
"@ingroup Terrain"
);
GroundPlane::GroundPlane()
: mSquareSize( 128.0f ),
mScaleU( 1.0f ),
mScaleV( 1.0f ),
mMaterial( NULL ),
mMin( 0.0f, 0.0f ),
mMax( 0.0f, 0.0f ),
mPhysicsRep( NULL )
{
mTypeMask |= StaticObjectType | StaticShapeObjectType;
mNetFlags.set( Ghostable | ScopeAlways );
mConvexList = new Convex;
}
GroundPlane::~GroundPlane()
{
if( mMaterial )
SAFE_DELETE( mMaterial );
mConvexList->nukeList();
SAFE_DELETE( mConvexList );
}
void GroundPlane::initPersistFields()
{
addGroup( "Plane" );
addField( "squareSize", TypeF32, Offset( mSquareSize, GroundPlane ), "Square size in meters to which %GroundPlane subdivides its geometry." );
addField( "scaleU", TypeF32, Offset( mScaleU, GroundPlane ), "Scale of texture repeat in the U direction." );
addField( "scaleV", TypeF32, Offset( mScaleV, GroundPlane ), "Scale of texture repeat in the V direction." );
addField( "material", TypeMaterialName, Offset( mMaterialName, GroundPlane ), "Name of Material used to render %GroundPlane's surface." );
endGroup( "Plane" );
Parent::initPersistFields();
removeField( "scale" );
removeField( "position" );
removeField( "rotation" );
}
bool GroundPlane::onAdd()
{
if( !Parent::onAdd() )
return false;
if( isClientObject() )
_updateMaterial();
if( mSquareSize < sMIN_SQUARE_SIZE )
{
Con::errorf( "GroundPlane - squareSize below threshold; re-setting to %.02f", sMIN_SQUARE_SIZE );
mSquareSize = sMIN_SQUARE_SIZE;
}
Parent::setScale( VectorF( 1.0f, 1.0f, 1.0f ) );
Parent::setTransform( MatrixF::Identity );
setGlobalBounds();
resetWorldBox();
addToScene();
if ( PHYSICSMGR )
{
PhysicsCollision *colShape = PHYSICSMGR->createCollision();
colShape->addPlane( PlaneF( Point3F::Zero, Point3F( 0, 0, 1 ) ) );
PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
mPhysicsRep = PHYSICSMGR->createBody();
mPhysicsRep->init( colShape, 0, 0, this, world );
}
return true;
}
void GroundPlane::onRemove()
{
SAFE_DELETE( mPhysicsRep );
removeFromScene();
Parent::onRemove();
}
void GroundPlane::inspectPostApply()
{
Parent::inspectPostApply();
setMaskBits( U32( -1 ) );
if( mSquareSize < sMIN_SQUARE_SIZE )
{
Con::errorf( "GroundPlane - squareSize below threshold; re-setting to %.02f", sMIN_SQUARE_SIZE );
mSquareSize = sMIN_SQUARE_SIZE;
}
setScale( VectorF( 1.0f, 1.0f, 1.0f ) );
}
void GroundPlane::setTransform( const MatrixF &mat )
{
// Ignore.
}
void GroundPlane::setScale( const Point3F& scale )
{
// Ignore.
}
U32 GroundPlane::packUpdate( NetConnection* connection, U32 mask, BitStream* stream )
{
U32 retMask = Parent::packUpdate( connection, mask, stream );
stream->write( mSquareSize );
stream->write( mScaleU );
stream->write( mScaleV );
stream->write( mMaterialName );
return retMask;
}
void GroundPlane::unpackUpdate( NetConnection* connection, BitStream* stream )
{
Parent::unpackUpdate( connection, stream );
stream->read( &mSquareSize );
stream->read( &mScaleU );
stream->read( &mScaleV );
stream->read( &mMaterialName );
// If we're added then something possibly changed in
// the editor... do an update of the material and the
// geometry.
if ( isProperlyAdded() )
{
_updateMaterial();
mVertexBuffer = NULL;
}
}
void GroundPlane::_updateMaterial()
{
if( mMaterialName.isEmpty() )
{
Con::warnf( "GroundPlane::_updateMaterial - no material set; defaulting to 'WarningMaterial'" );
mMaterialName = "WarningMaterial";
}
// If the material name matches then don't
// bother updating it.
if ( mMaterial &&
mMaterialName.compare( mMaterial->getMaterial()->getName() ) == 0 )
return;
SAFE_DELETE( mMaterial );
mMaterial = MATMGR->createMatInstance( mMaterialName, getGFXVertexFormat< VertexType >() );
if ( !mMaterial )
Con::errorf( "GroundPlane::_updateMaterial - no material called '%s'", mMaterialName.c_str() );
}
bool GroundPlane::castRay( const Point3F& start, const Point3F& end, RayInfo* info )
{
PlaneF plane( Point3F( 0.0f, 0.0f, 0.0f ), Point3F( 0.0f, 0.0f, 1.0f ) );
F32 t = plane.intersect( start, end );
if( t >= 0.0 && t <= 1.0 )
{
info->t = t;
info->setContactPoint( start, end );
info->normal.set( 0, 0, 1 );
info->material = mMaterial;
info->object = this;
info->distance = 0;
info->faceDot = 0;
info->texCoord.set( 0, 0 );
return true;
}
return false;
}
void GroundPlane::buildConvex( const Box3F& box, Convex* convex )
{
mConvexList->collectGarbage();
Box3F planeBox = getPlaneBox();
if ( !box.isOverlapped( planeBox ) )
return;
// See if we already have a convex in the working set.
BoxConvex *boxConvex = NULL;
CollisionWorkingList &wl = convex->getWorkingList();
CollisionWorkingList *itr = wl.wLink.mNext;
for ( ; itr != &wl; itr = itr->wLink.mNext )
{
if ( itr->mConvex->getType() == BoxConvexType &&
itr->mConvex->getObject() == this )
{
boxConvex = (BoxConvex*)itr->mConvex;
break;
}
}
if ( !boxConvex )
{
boxConvex = new BoxConvex;
mConvexList->registerObject( boxConvex );
boxConvex->init( this );
convex->addToWorkingList( boxConvex );
}
// Update our convex to best match the queried box
if ( boxConvex )
{
Point3F queryCenter = box.getCenter();
boxConvex->mCenter = Point3F( queryCenter.x, queryCenter.y, -GROUND_PLANE_BOX_HEIGHT_HALF );
boxConvex->mSize = Point3F( box.getExtents().x,
box.getExtents().y,
GROUND_PLANE_BOX_HEIGHT_HALF );
}
}
bool GroundPlane::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F&, const SphereF& )
{
polyList->setObject( this );
polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) );
Box3F planeBox = getPlaneBox();
polyList->addBox( planeBox, mMaterial );
return true;
}
void GroundPlane::prepRenderImage( SceneRenderState* state )
{
PROFILE_SCOPE( GroundPlane_prepRenderImage );
// TODO: Should we skip rendering the ground plane into
// the shadows? Its not like you can ever get under it.
if ( !mMaterial )
return;
// If we don't have a material instance after the override then
// we can skip rendering all together.
BaseMatInstance *matInst = state->getOverrideMaterial( mMaterial );
if ( !matInst )
return;
PROFILE_SCOPE( GroundPlane_prepRender );
// Update the geometry.
createGeometry( state->getFrustum() );
if( mVertexBuffer.isNull() )
return;
// Add a render instance.
RenderPassManager* pass = state->getRenderPass();
MeshRenderInst* ri = pass->allocInst< MeshRenderInst >();
ri->type = RenderPassManager::RIT_Mesh;
ri->vertBuff = &mVertexBuffer;
ri->primBuff = &mPrimitiveBuffer;
ri->prim = &mPrimitive;
ri->matInst = matInst;
ri->objectToWorld = pass->allocUniqueXform( MatrixF::Identity );
ri->worldToCamera = pass->allocSharedXform( RenderPassManager::View );
ri->projection = pass->allocSharedXform( RenderPassManager::Projection );
ri->visibility = 1.0f;
ri->translucentSort = matInst->getMaterial()->isTranslucent();
ri->defaultKey = matInst->getStateHint();
if( ri->translucentSort )
ri->type = RenderPassManager::RIT_Translucent;
// If we need lights then set them up.
if ( matInst->isForwardLit() )
{
LightQuery query;
query.init( getWorldSphere() );
query.getLights( ri->lights, 8 );
}
pass->addInst( ri );
}
/// Generate a subset of the ground plane matching the given frustum.
void GroundPlane::createGeometry( const Frustum& frustum )
{
PROFILE_SCOPE( GroundPlane_createGeometry );
enum { MAX_WIDTH = 256, MAX_HEIGHT = 256 };
// Project the frustum onto the XY grid.
Point2F min;
Point2F max;
projectFrustum( frustum, mSquareSize, min, max );
// Early out if the grid projection hasn't changed.
if( mVertexBuffer.isValid() &&
min == mMin &&
max == mMax )
return;
mMin = min;
mMax = max;
// Determine the grid extents and allocate the buffers.
// Adjust square size permanently if with the given frustum,
// we end up producing more than a certain limit of geometry.
// This is to prevent this code from causing trouble with
// long viewing distances.
// This only affects the client object, of course, and thus
// has no permanent effect.
U32 width = mCeil( ( max.x - min.x ) / mSquareSize );
if( width > MAX_WIDTH )
{
mSquareSize = mCeil( ( max.x - min.x ) / MAX_WIDTH );
width = MAX_WIDTH;
}
else if( !width )
width = 1;
U32 height = mCeil( ( max.y - min.y ) / mSquareSize );
if( height > MAX_HEIGHT )
{
mSquareSize = mCeil( ( max.y - min.y ) / MAX_HEIGHT );
height = MAX_HEIGHT;
}
else if( !height )
height = 1;
const U32 numVertices = ( width + 1 ) * ( height + 1 );
const U32 numTriangles = width * height * 2;
// Only reallocate if the buffers are too small.
if ( mVertexBuffer.isNull() || numVertices > mVertexBuffer->mNumVerts )
{
#if defined(TORQUE_OS_XENON)
mVertexBuffer.set( GFX, numVertices, GFXBufferTypeVolatile );
#else
mVertexBuffer.set( GFX, numVertices, GFXBufferTypeDynamic );
#endif
}
if ( mPrimitiveBuffer.isNull() || numTriangles > mPrimitiveBuffer->mPrimitiveCount )
{
#if defined(TORQUE_OS_XENON)
mPrimitiveBuffer.set( GFX, numTriangles*3, numTriangles, GFXBufferTypeVolatile );
#else
mPrimitiveBuffer.set( GFX, numTriangles*3, numTriangles, GFXBufferTypeDynamic );
#endif
}
// Generate the grid.
generateGrid( width, height, mSquareSize, min, max, mVertexBuffer, mPrimitiveBuffer );
// Set up GFX primitive.
mPrimitive.type = GFXTriangleList;
mPrimitive.numPrimitives = numTriangles;
mPrimitive.numVertices = numVertices;
}
/// Project the given frustum onto the ground plane and return the XY bounds in world space.
void GroundPlane::projectFrustum( const Frustum& frustum, F32 squareSize, Point2F& outMin, Point2F& outMax )
{
// Get the frustum's min and max XY coordinates.
const Box3F bounds = frustum.getBounds();
Point2F minPt( bounds.minExtents.x, bounds.minExtents.y );
Point2F maxPt( bounds.maxExtents.x, bounds.maxExtents.y );
// Round the min and max coordinates so they align on the grid.
minPt.x -= mFmod( minPt.x, squareSize );
minPt.y -= mFmod( minPt.y, squareSize );
F32 maxDeltaX = mFmod( maxPt.x, squareSize );
F32 maxDeltaY = mFmod( maxPt.y, squareSize );
if( maxDeltaX != 0.0f )
maxPt.x += ( squareSize - maxDeltaX );
if( maxDeltaY != 0.0f )
maxPt.y += ( squareSize - maxDeltaY );
// Add a safezone, so we don't touch the clipping planes.
minPt.x -= squareSize; minPt.y -= squareSize;
maxPt.x += squareSize; maxPt.y += squareSize;
outMin = minPt;
outMax = maxPt;
}
/// Generate a triangulated grid spanning the given bounds into the given buffers.
void GroundPlane::generateGrid( U32 width, U32 height, F32 squareSize,
const Point2F& min, const Point2F& max,
GFXVertexBufferHandle< VertexType >& outVertices,
GFXPrimitiveBufferHandle& outPrimitives )
{
// Generate the vertices.
VertexType* vertices = outVertices.lock();
for( F32 y = min.y; y <= max.y; y += squareSize )
for( F32 x = min.x; x <= max.x; x += squareSize )
{
vertices->point.x = x;
vertices->point.y = y;
vertices->point.z = 0.0;
vertices->texCoord.x = ( x / squareSize ) * mScaleU;
vertices->texCoord.y = ( y / squareSize ) * -mScaleV;
vertices->normal.x = 0.0f;
vertices->normal.y = 0.0f;
vertices->normal.z = 1.0f;
vertices->tangent.x = 1.0f;
vertices->tangent.y = 0.0f;
vertices->tangent.z = 0.0f;
vertices->binormal.x = 0.0f;
vertices->binormal.y = 1.0f;
vertices->binormal.z = 0.0f;
vertices++;
}
outVertices.unlock();
// Generate the indices.
U16* indices;
outPrimitives.lock( &indices );
U16 corner1 = 0;
U16 corner2 = 1;
U16 corner3 = width + 1;
U16 corner4 = width + 2;
for( U32 y = 0; y < height; ++ y )
{
for( U32 x = 0; x < width; ++ x )
{
indices[ 0 ] = corner3;
indices[ 1 ] = corner2;
indices[ 2 ] = corner1;
indices += 3;
indices[ 0 ] = corner3;
indices[ 1 ] = corner4;
indices[ 2 ] = corner2;
indices += 3;
corner1 ++;
corner2 ++;
corner3 ++;
corner4 ++;
}
corner1 ++;
corner2 ++;
corner3 ++;
corner4 ++;
}
outPrimitives.unlock();
}
DefineEngineMethod( GroundPlane, postApply, void, (),,
"Intended as a helper to developers and editor scripts.\n"
"Force trigger an inspectPostApply. This will transmit "
"material and other fields to client objects."
)
{
object->inspectPostApply();
}

View file

@ -0,0 +1,138 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _TORQUE_T3D_GROUNDPLANE_H_
#define _TORQUE_T3D_GROUNDPLANE_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _GFXVERTEXBUFFER_H_
#include "gfx/gfxVertexBuffer.h"
#endif
#ifndef _GFXPRIMITIVEBUFFER_H_
#include "gfx/gfxPrimitiveBuffer.h"
#endif
class PhysicsBody;
class BaseMatInstance;
/// A virtually infinite XY ground plane primitive.
///
/// For rendering, a subset of the plane spanning the view frustum is generated
/// and rendered. Tesselation is determined by the given squareSize property.
///
/// For collision detection, a finite bounding box is used to deal with finite
/// precision of floating-point operations (we can't use floating-point infinity
/// as infinity*0 is undefined.)
///
/// The ground plane can be textured like regular geometry by assigning a material
/// name to its 'material' property. UVs mirror grid coordinates so that when
/// using UV wrapping, textures will tile nicely.
class GroundPlane : public SceneObject
{
public:
typedef SceneObject Parent;
DECLARE_CONOBJECT( GroundPlane );
GroundPlane();
virtual ~GroundPlane();
virtual bool onAdd();
virtual void onRemove();
virtual U32 packUpdate( NetConnection* connection, U32 mask, BitStream* stream );
virtual void unpackUpdate( NetConnection* connection, BitStream* stream );
virtual void prepRenderImage( SceneRenderState* state );
virtual bool castRay( const Point3F& start, const Point3F& end, RayInfo* info );
virtual void buildConvex( const Box3F& box, Convex* convex );
virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere );
virtual void inspectPostApply();
virtual void setTransform( const MatrixF &mat );
virtual void setScale( const Point3F& scale );
static void initPersistFields();
protected:
typedef GFXVertexPNTBT VertexType;
void _updateMaterial();
void createGeometry( const Frustum& frustum );
void projectFrustum( const Frustum& frustum, F32 squareSize,
Point2F& outMin, Point2F& outMax );
void generateGrid( U32 width, U32 height, F32 squareSize,
const Point2F& min, const Point2F& max,
GFXVertexBufferHandle< VertexType >& outVertices,
GFXPrimitiveBufferHandle& outPrimitives );
Box3F getPlaneBox();
private:
typedef GFXVertexBufferHandle< VertexType > VertexBuffer;
typedef GFXPrimitiveBufferHandle PrimitiveBuffer;
F32 mSquareSize; ///< World units per grid cell edge.
F32 mScaleU; ///< Scale factor for U texture coordinates.
F32 mScaleV; ///< Scale factor for V texture coordinates.
String mMaterialName; ///< Object name of material to use.
BaseMatInstance* mMaterial; ///< Instantiated material based on given material name.
PhysicsBody *mPhysicsRep;
/// @name Rendering State
/// @{
Point2F mMin;
Point2F mMax;
VertexBuffer mVertexBuffer;
PrimitiveBuffer mPrimitiveBuffer;
GFXPrimitive mPrimitive;
/// @}
Convex* mConvexList; ///< List of collision convexes we have created; for cleanup.
};
static const F32 GROUND_PLANE_BOX_HEIGHT_HALF = 1.0f;
static const F32 GROUND_PLANE_BOX_EXTENT_HALF = 16000.0f;
inline Box3F GroundPlane::getPlaneBox()
{
Box3F planeBox;
planeBox.minExtents = Point3F( - GROUND_PLANE_BOX_EXTENT_HALF,
- GROUND_PLANE_BOX_EXTENT_HALF,
- 0.05f );
planeBox.maxExtents = Point3F( GROUND_PLANE_BOX_EXTENT_HALF,
GROUND_PLANE_BOX_EXTENT_HALF,
0.05f );
return planeBox;
}
#endif // _TORQUE_T3D_GROUNDPLANE_H_

View file

@ -0,0 +1,496 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// Original header:
// GuiMaterialPreview Control for Material Editor Written by Travis Vroman of Gaslight Studios
// Updated 2-14-09
// Portions based off Constructor viewport code.
#include "console/engineAPI.h"
#include "T3D/guiMaterialPreview.h"
#include "renderInstance/renderPassManager.h"
#include "lighting/lightManager.h"
#include "lighting/lightInfo.h"
#include "core/resourceManager.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
// GuiMaterialPreview
GuiMaterialPreview::GuiMaterialPreview()
: mMaxOrbitDist(5.0f),
mMinOrbitDist(0.0f),
mOrbitDist(5.0f),
mMouseState(None),
mModel(NULL),
mLastMousePoint(0, 0),
lastRenderTime(0),
runThread(0),
mFakeSun(NULL)
{
mActive = true;
mCameraMatrix.identity();
mCameraRot.set( mDegToRad(30.0f), 0, mDegToRad(-30.0f) );
mCameraPos.set(0.0f, 1.75f, 1.25f);
mCameraMatrix.setColumn(3, mCameraPos);
mOrbitPos.set(0.0f, 0.0f, 0.0f);
mTransStep = 0.01f;
mTranMult = 4.0;
mLightTransStep = 0.01f;
mLightTranMult = 4.0;
mOrbitRelPos = Point3F(0,0,0);
// By default don't do dynamic reflection
// updates for this viewport.
mReflectPriority = 0.0f;
}
GuiMaterialPreview::~GuiMaterialPreview()
{
SAFE_DELETE(mModel);
SAFE_DELETE(mFakeSun);
}
bool GuiMaterialPreview::onWake()
{
if( !Parent::onWake() )
return false;
if (!mFakeSun)
mFakeSun = LightManager::createLightInfo();
mFakeSun->setColor( ColorF( 1.0f, 1.0f, 1.0f ) );
mFakeSun->setAmbient( ColorF( 0.5f, 0.5f, 0.5f ) );
mFakeSun->setDirection( VectorF( 0.0f, 0.707f, -0.707f ) );
mFakeSun->setPosition( mFakeSun->getDirection() * -10000.0f );
mFakeSun->setRange( 2000000.0f );
return true;
}
// This function allows the viewport's ambient color to be changed. This is exposed to script below.
void GuiMaterialPreview::setAmbientLightColor( F32 r, F32 g, F32 b )
{
ColorF temp(r, g, b);
temp.clamp();
GuiMaterialPreview::mFakeSun->setAmbient( temp );
}
// This function allows the light's color to be changed. This is exposed to script below.
void GuiMaterialPreview::setLightColor( F32 r, F32 g, F32 b )
{
ColorF temp(r, g, b);
temp.clamp();
GuiMaterialPreview::mFakeSun->setColor( temp );
}
// This function is for moving the light in the scene. This needs to be adjusted to keep the light
// from getting all out of whack. For now, we'll just rely on the reset function if we need it
// fixed.
void GuiMaterialPreview::setLightTranslate(S32 modifier, F32 xstep, F32 ystep)
{
F32 _lighttransstep = (modifier & SI_SHIFT ? mLightTransStep : (mLightTransStep*mLightTranMult));
Point3F relativeLightDirection = GuiMaterialPreview::mFakeSun->getDirection();
// May be able to get rid of this. For now, it helps to fix the position of the light if i gets messed up.
if (modifier & SI_PRIMARY_CTRL)
{
relativeLightDirection.x += ( xstep * _lighttransstep * -1 );//need to invert this for some reason. Otherwise, it's backwards.
relativeLightDirection.y += ( ystep * _lighttransstep );
GuiMaterialPreview::mFakeSun->setDirection(relativeLightDirection);
}
// Default action taken by mouse wheel clicking.
else
{
relativeLightDirection.x += ( xstep * _lighttransstep * -1 ); //need to invert this for some reason. Otherwise, it's backwards.
relativeLightDirection.z += ( ystep * _lighttransstep );
GuiMaterialPreview::mFakeSun->setDirection(relativeLightDirection);
}
}
// This is for panning the viewport camera.
void GuiMaterialPreview::setTranslate(S32 modifier, F32 xstep, F32 ystep)
{
F32 transstep = (modifier & SI_SHIFT ? mTransStep : (mTransStep*mTranMult));
F32 nominalDistance = 20.0;
Point3F vec = mCameraPos;
vec -= mOrbitPos;
transstep *= vec.len() / nominalDistance;
if (modifier & SI_PRIMARY_CTRL)
{
mOrbitRelPos.x += ( xstep * transstep );
mOrbitRelPos.y += ( ystep * transstep );
}
else
{
mOrbitRelPos.x += ( xstep * transstep );
mOrbitRelPos.z += ( ystep * transstep );
}
}
// Left Click
void GuiMaterialPreview::onMouseDown(const GuiEvent &event)
{
mMouseState = MovingLight;
mLastMousePoint = event.mousePoint;
mouseLock();
}
// Left Click Release
void GuiMaterialPreview::onMouseUp(const GuiEvent &event)
{
mouseUnlock();
mMouseState = None;
}
// Left Click Drag
void GuiMaterialPreview::onMouseDragged(const GuiEvent &event)
{
if(mMouseState != MovingLight)
{
return;
}
// If we are MovingLight...
else
{
Point2I delta = event.mousePoint - mLastMousePoint;
mLastMousePoint = event.mousePoint;
setLightTranslate(event.modifier, delta.x, delta.y);
}
}
// Right Click
void GuiMaterialPreview::onRightMouseDown(const GuiEvent &event)
{
mMouseState = Rotating;
mLastMousePoint = event.mousePoint;
mouseLock();
}
// Right Click Release
void GuiMaterialPreview::onRightMouseUp(const GuiEvent &event)
{
mouseUnlock();
mMouseState = None;
}
// Right Click Drag
void GuiMaterialPreview::onRightMouseDragged(const GuiEvent &event)
{
if (mMouseState != Rotating)
{
return;
}
Point2I delta = event.mousePoint - mLastMousePoint;
mLastMousePoint = event.mousePoint;
mCameraRot.x += (delta.y * 0.01f);
mCameraRot.z += (delta.x * 0.01f);
}
// Mouse Wheel Scroll Up
bool GuiMaterialPreview::onMouseWheelUp(const GuiEvent &event)
{
mOrbitDist = (mOrbitDist - 0.10f);
return true;
}
// Mouse Wheel Scroll Down
bool GuiMaterialPreview::onMouseWheelDown(const GuiEvent &event)
{
mOrbitDist = (mOrbitDist + 0.10f);
return true;
}
// Mouse Wheel Click
void GuiMaterialPreview::onMiddleMouseDown(const GuiEvent &event)
{
if (!mActive || !mVisible || !mAwake)
{
return;
}
mMouseState = Panning;
mLastMousePoint = event.mousePoint;
mouseLock();
}
// Mouse Wheel Click Release
void GuiMaterialPreview::onMiddleMouseUp(const GuiEvent &event)
{
mouseUnlock();
mMouseState = None;
}
// Mouse Wheel Click Drag
void GuiMaterialPreview::onMiddleMouseDragged(const GuiEvent &event)
{
if (mMouseState != Panning)
{
return;
}
Point2I delta = event.mousePoint - mLastMousePoint;
mLastMousePoint = event.mousePoint;
setTranslate(event.modifier, delta.x, delta.y);
}
// This is used to set the model we want to view in the control object.
void GuiMaterialPreview::setObjectModel(const char* modelName)
{
deleteModel();
Resource<TSShape> model = ResourceManager::get().load(modelName);
if (! bool(model))
{
Con::warnf(avar("GuiMaterialPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName));
return;
}
mModel = new TSShapeInstance(model, true);
AssertFatal(mModel, avar("GuiMaterialPreview: Failed to load model %s. Please check your model name and load a valid model.", modelName));
// Initialize camera values:
mOrbitPos = mModel->getShape()->center;
mMinOrbitDist = mModel->getShape()->radius;
lastRenderTime = Platform::getVirtualMilliseconds();
}
void GuiMaterialPreview::deleteModel()
{
SAFE_DELETE(mModel);
runThread = 0;
}
// This is called whenever there is a change in the camera.
bool GuiMaterialPreview::processCameraQuery(CameraQuery* query)
{
MatrixF xRot, zRot;
Point3F vecf, vecu, vecr;;
xRot.set(EulerF(mCameraRot.x, 0.0f, 0.0f));
zRot.set(EulerF(0.0f, 0.0f, mCameraRot.z));
if(mMouseState != Panning)
{
// Adjust the camera so that we are still facing the model:
Point3F vec;
mCameraMatrix.mul(zRot, xRot);
mCameraMatrix.getColumn(1, &vec);
vec *= mOrbitDist;
mCameraPos = mOrbitPos - vec;
query->farPlane = 2100.0f;
query->nearPlane = query->farPlane / 5000.0f;
query->fov = 45.0f;
mCameraMatrix.setColumn(3, mCameraPos);
query->cameraMatrix = mCameraMatrix;
}
else
{
mCameraMatrix.mul( zRot, xRot );
mCameraMatrix.getColumn( 1, &vecf ); // Forward vector
mCameraMatrix.getColumn( 2, &vecu ); // Up vector
mCameraMatrix.getColumn( 0, &vecr ); // Right vector
Point3F flatVecf(vecf.x, vecf.y, 0.0f);
Point3F modvecf = flatVecf * mOrbitRelPos.y;
Point3F modvecu = vecu * mOrbitRelPos.z;
Point3F modvecr = vecr * mOrbitRelPos.x;
// Change the orbit position
mOrbitPos += modvecu - modvecr + modvecf;
F32 vecfmul = mOrbitDist;
Point3F virtualVecF = vecf * mOrbitDist;
vecf *= vecfmul;
mCameraPos = mOrbitPos - virtualVecF;
// Update the camera's position
mCameraMatrix.setColumn( 3, (mOrbitPos - vecf) );
query->farPlane = 2100.0f;
query->nearPlane = query->farPlane / 5000.0f;
query->fov = 45.0f;
query->cameraMatrix = mCameraMatrix;
// Reset the relative position
mOrbitRelPos = Point3F(0,0,0);
}
return true;
}
void GuiMaterialPreview::onMouseEnter(const GuiEvent & event)
{
Con::executef(this, "onMouseEnter");
}
void GuiMaterialPreview::onMouseLeave(const GuiEvent & event)
{
Con::executef(this, "onMouseLeave");
}
void GuiMaterialPreview::renderWorld(const RectI &updateRect)
{
// nothing to render, punt
if ( !mModel && !mMountedModel )
return;
S32 time = Platform::getVirtualMilliseconds();
//S32 dt = time - lastRenderTime;
lastRenderTime = time;
F32 left, right, top, bottom, nearPlane, farPlane;
bool isOrtho;
GFX->getFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho);
Frustum frust( isOrtho, left, right, bottom, top, nearPlane, farPlane, MatrixF::Identity );
FogData savedFogData = gClientSceneGraph->getFogData();
gClientSceneGraph->setFogData( FogData() ); // no fog in preview window
RenderPassManager* renderPass = gClientSceneGraph->getDefaultRenderPass();
SceneRenderState state
(
gClientSceneGraph,
SPT_Diffuse,
SceneCameraState( GFX->getViewport(), frust, GFX->getWorldMatrix(), GFX->getProjectionMatrix() ),
renderPass,
true
);
// Set up our TS render state here.
TSRenderState rdata;
rdata.setSceneState( &state );
// We might have some forward lit materials
// so pass down a query to gather lights.
LightQuery query;
query.init( SphereF( Point3F::Zero, 1.0f ) );
rdata.setLightQuery( &query );
// Set up pass transforms
renderPass->assignSharedXform(RenderPassManager::View, MatrixF::Identity);
renderPass->assignSharedXform(RenderPassManager::Projection, GFX->getProjectionMatrix());
LIGHTMGR->unregisterAllLights();
LIGHTMGR->setSpecialLight( LightManager::slSunLightType, mFakeSun );
if ( mModel )
mModel->render( rdata );
if ( mMountedModel )
{
// render a weapon
/*
MatrixF mat;
GFX->pushWorldMatrix();
GFX->multWorld( mat );
GFX->popWorldMatrix();
*/
}
renderPass->renderPass( &state );
gClientSceneGraph->setFogData( savedFogData ); // restore fog setting
// Make sure to remove our fake sun
LIGHTMGR->unregisterAllLights();
}
// Make sure the orbit distance is within the acceptable range.
void GuiMaterialPreview::setOrbitDistance(F32 distance)
{
mOrbitDist = mClampF(distance, mMinOrbitDist, mMaxOrbitDist);
}
// This function is meant to be used with a button to put everything back to default settings.
void GuiMaterialPreview::resetViewport()
{
// Reset the camera's orientation.
mCameraRot.set( mDegToRad(30.0f), 0, mDegToRad(-30.0f) );
mCameraPos.set(0.0f, 1.75f, 1.25f);
mOrbitDist = 5.0f;
mOrbitPos = mModel->getShape()->center;
// Reset the viewport's lighting.
GuiMaterialPreview::mFakeSun->setColor( ColorF( 1.0f, 1.0f, 1.0f ) );
GuiMaterialPreview::mFakeSun->setAmbient( ColorF( 0.5f, 0.5f, 0.5f ) );
GuiMaterialPreview::mFakeSun->setDirection( VectorF( 0.0f, 0.707f, -0.707f ) );
}
// Expose the class and functions to the console.
IMPLEMENT_CONOBJECT(GuiMaterialPreview);
ConsoleDocClass( GuiMaterialPreview,
"@brief Visual preview of a specified Material\n\n"
"Editor use only.\n\n"
"@internal"
);
// Set the model.
DefineEngineMethod(GuiMaterialPreview, setModel, void, ( const char* shapeName ),,
"Sets the model to be displayed in this control\n\n"
"@param shapeName Name of the model to display.\n")
{
object->setObjectModel(shapeName);
}
DefineEngineMethod(GuiMaterialPreview, deleteModel, void, (),,
"Deletes the preview model.\n")
{
object->deleteModel();
}
// Set orbit distance around the model.
DefineEngineMethod(GuiMaterialPreview, setOrbitDistance, void, ( F32 distance ),,
"Sets the distance at which the camera orbits the object. Clamped to the "
"acceptable range defined in the class by min and max orbit distances.\n\n"
"@param distance The distance to set the orbit to (will be clamped).")
{
object->setOrbitDistance(distance);
}
// Reset control to default values. Meant to be used with a button.
DefineEngineMethod(GuiMaterialPreview, reset, void, (),,
"Resets the viewport to default zoom, pan, rotate and lighting.")
{
object->resetViewport();
}
// This function allows the user to change the light's color.
DefineEngineMethod(GuiMaterialPreview, setLightColor, void, ( ColorF color ),,
"Sets the color of the light in the scene.\n")
{
object->setLightColor( color.red, color.green, color.blue );
}
// This function allows the user to change the viewports's ambient color.
DefineEngineMethod(GuiMaterialPreview, setAmbientLightColor, void, ( ColorF color ),,
"Sets the color of the ambient light in the scene.\n")
{
object->setAmbientLightColor( color.red, color.green, color.blue );
}

View file

@ -0,0 +1,129 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// Original header:
// GuiMaterialPreview Control for Material Editor Written by Travis Vroman of Gaslight Studios
// Updated 2-14-09
// Portions based off Constructor viewport code.
#ifndef _GUIMATERIALPREVIEW_H_
#define _GUIMATERIALPREVIEW_H_
#include "gui/3d/guiTSControl.h"
#include "ts/tsShapeInstance.h"
class LightInfo;
class GuiMaterialPreview : public GuiTSCtrl
{
private:
typedef GuiTSCtrl Parent;
protected:
enum MouseState
{
None,
Rotating,
Zooming,
Panning,
MovingLight
};
MouseState mMouseState;
TSShapeInstance* mModel;
TSShapeInstance* mMountedModel;
U32 mSkinTag;
// For Camera Panning.
F32 mTransStep; //*** Amount of translation with each mouse move
F32 mTranMult; //*** With a modifier, how much faster to translate
// For light translation.
F32 mLightTransStep;
F32 mLightTranMult;
Point3F mCameraPos;
MatrixF mCameraMatrix;
EulerF mCameraRot;
Point3F mOrbitPos;
Point3F mOrbitRelPos;
Point3F mCameraTransform;
TSThread * runThread;
S32 lastRenderTime;
Point2I mLastMousePoint;
LightInfo* mFakeSun;
public:
bool onWake();
void onMouseEnter(const GuiEvent &event);
void onMouseLeave(const GuiEvent &event);
void onMouseDown(const GuiEvent &event);
void onMouseUp(const GuiEvent &event);
void onMouseDragged(const GuiEvent &event);
void onRightMouseDown(const GuiEvent &event);
void onRightMouseUp(const GuiEvent &event);
void onRightMouseDragged(const GuiEvent &event);
bool onMouseWheelUp(const GuiEvent &event);
bool onMouseWheelDown(const GuiEvent &event);
void onMiddleMouseUp(const GuiEvent &event);
void onMiddleMouseDown(const GuiEvent &event);
void onMiddleMouseDragged(const GuiEvent &event);
// For Camera Panning.
void setTranslate(S32 modifier, F32 xstep, F32 ystep);
// For Light Translation.
void setLightTranslate(S32 modifier, F32 xstep, F32 ystep);
// For changing the light color.
void setLightColor( F32 r, F32 g, F32 b );
// For changing the ambient light color.
void setAmbientLightColor( F32 r, F32 g, F32 b );
void setObjectModel(const char * modelName);
void deleteModel();
void resetViewport();
void setOrbitDistance(F32 distance);
bool processCameraQuery(CameraQuery *query);
void renderWorld(const RectI &updateRect);
DECLARE_CONOBJECT(GuiMaterialPreview);
DECLARE_CATEGORY( "Gui Editor" );
GuiMaterialPreview();
~GuiMaterialPreview();
private:
F32 mMaxOrbitDist;
F32 mMinOrbitDist;
F32 mOrbitDist;
};
#endif

View file

@ -0,0 +1,41 @@
//-----------------------------------------------------------------------------
// 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 "gui/core/guiControl.h"
//------------------------------------------------------------------------------
class GuiNoMouseCtrl : public GuiControl
{
typedef GuiControl Parent;
public:
// GuiControl
bool pointInControl(const Point2I &) { return(false); }
DECLARE_CONOBJECT(GuiNoMouseCtrl);
DECLARE_CATEGORY( "Gui Other" );
};
IMPLEMENT_CONOBJECT(GuiNoMouseCtrl);
ConsoleDocClass( GuiNoMouseCtrl,
"@brief No known usage, possibly Legacy.\n\n"
"Not used at all, internal until deprecated\n\n"
"@internal");

View file

@ -0,0 +1,988 @@
//-----------------------------------------------------------------------------
// 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 "T3D/guiObjectView.h"
#include "renderInstance/renderPassManager.h"
#include "lighting/lightManager.h"
#include "lighting/lightInfo.h"
#include "core/resourceManager.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
#include "console/consoleTypes.h"
#include "math/mathTypes.h"
#include "gfx/gfxTransformSaver.h"
#include "console/engineAPI.h"
IMPLEMENT_CONOBJECT( GuiObjectView );
ConsoleDocClass( GuiObjectView,
"@brief GUI control which displays a 3D model.\n\n"
"Model displayed in the control can have other objects mounted onto it, and the light settings can be adjusted.\n\n"
"@tsexample\n"
" new GuiObjectView(ObjectPreview)\n"
" {\n"
" shapeFile = \"art/shapes/items/kit/healthkit.dts\";\n"
" mountedNode = \"mount0\";\n"
" lightColor = \"1 1 1 1\";\n"
" lightAmbient = \"0.5 0.5 0.5 1\";\n"
" lightDirection = \"0 0.707 -0.707\";\n"
" orbitDiststance = \"2\";\n"
" minOrbitDiststance = \"0.917688\";\n"
" maxOrbitDiststance = \"5\";\n"
" cameraSpeed = \"0.01\";\n"
" cameraZRot = \"0\";\n"
" forceFOV = \"0\";\n"
" reflectPriority = \"0\";\n"
" };\n"
"@endtsexample\n\n"
"@see GuiControl\n\n"
"@ingroup Gui3D\n"
);
IMPLEMENT_CALLBACK( GuiObjectView, onMouseEnter, void, (),(),
"@brief Called whenever the mouse enters the control.\n\n"
"@tsexample\n"
"// The mouse has entered the control, causing the callback to occur\n"
"GuiObjectView::onMouseEnter(%this)\n"
" {\n"
" // Code to run when the mouse enters this control\n"
" }\n"
"@endtsexample\n\n"
"@see GuiControl\n\n"
);
IMPLEMENT_CALLBACK( GuiObjectView, onMouseLeave, void, (),(),
"@brief Called whenever the mouse leaves the control.\n\n"
"@tsexample\n"
"// The mouse has left the control, causing the callback to occur\n"
"GuiObjectView::onMouseLeave(%this)\n"
" {\n"
" // Code to run when the mouse leaves this control\n"
" }\n"
"@endtsexample\n\n"
"@see GuiControl\n\n"
);
//------------------------------------------------------------------------------
GuiObjectView::GuiObjectView()
: mMaxOrbitDist( 5.0f ),
mMinOrbitDist( 0.0f ),
mOrbitDist( 5.0f ),
mMouseState( None ),
mModel( NULL ),
mMountedModel( NULL ),
mLastMousePoint( 0, 0 ),
mLastRenderTime( 0 ),
mRunThread( NULL ),
mLight( NULL ),
mAnimationSeq( -1 ),
mMountNodeName( "mount0" ),
mMountNode( -1 ),
mCameraSpeed( 0.01f ),
mLightColor( 1.0f, 1.0f, 1.0f ),
mLightAmbient( 0.5f, 0.5f, 0.5f ),
mLightDirection( 0.f, 0.707f, -0.707f )
{
mCameraMatrix.identity();
mCameraRot.set( 0.0f, 0.0f, 3.9f );
mCameraPos.set( 0.0f, 1.75f, 1.25f );
mCameraMatrix.setColumn( 3, mCameraPos );
mOrbitPos.set( 0.0f, 0.0f, 0.0f );
// By default don't do dynamic reflection
// updates for this viewport.
mReflectPriority = 0.0f;
}
//------------------------------------------------------------------------------
GuiObjectView::~GuiObjectView()
{
if( mModel )
SAFE_DELETE( mModel );
if( mMountedModel )
SAFE_DELETE( mMountedModel );
if( mLight )
SAFE_DELETE( mLight );
}
//------------------------------------------------------------------------------
void GuiObjectView::initPersistFields()
{
addGroup( "Model" );
addField( "shapeFile", TypeStringFilename, Offset( mModelName, GuiObjectView ),
"The object model shape file to show in the view." );
addField( "skin", TypeRealString, Offset( mSkinName, GuiObjectView ),
"The skin to use on the object model." );
endGroup( "Model" );
addGroup( "Animation" );
addField( "animSequence", TypeRealString, Offset( mAnimationSeqName, GuiObjectView ),
"The animation sequence to play on the model." );
endGroup( "Animation" );
addGroup( "Mounting" );
addField( "mountedShapeFile", TypeStringFilename, Offset( mMountedModelName, GuiObjectView ),
"Optional shape file to mount on the primary model (e.g. weapon)." );
addField( "mountedSkin", TypeRealString, Offset( mMountSkinName, GuiObjectView ),
"Skin name used on mounted shape file." );
addField( "mountedNode", TypeRealString, Offset( mMountNodeName, GuiObjectView ),
"Name of node on primary model to which to mount the secondary shape." );
endGroup( "Mounting" );
addGroup( "Lighting" );
addField( "lightColor", TypeColorF, Offset( mLightColor, GuiObjectView ),
"Diffuse color of the sunlight used to render the model." );
addField( "lightAmbient", TypeColorF, Offset( mLightAmbient, GuiObjectView ),
"Ambient color of the sunlight used to render the model." );
addField( "lightDirection", TypePoint3F, Offset( mLightDirection, GuiObjectView ),
"Direction from which the model is illuminated." );
endGroup( "Lighting" );
addGroup( "Camera" );
addField( "orbitDiststance", TypeF32, Offset( mOrbitDist, GuiObjectView ),
"Distance from which to render the model." );
addField( "minOrbitDiststance", TypeF32, Offset( mMinOrbitDist, GuiObjectView ),
"Maxiumum distance to which the camera can be zoomed out." );
addField( "maxOrbitDiststance", TypeF32, Offset( mMaxOrbitDist, GuiObjectView ),
"Minimum distance below which the camera will not zoom in further." );
addField( "cameraSpeed", TypeF32, Offset( mCameraSpeed, GuiObjectView ),
"Multiplier for mouse camera operations." );
endGroup( "Camera" );
Parent::initPersistFields();
}
//------------------------------------------------------------------------------
void GuiObjectView::onStaticModified( StringTableEntry slotName, const char* newValue )
{
Parent::onStaticModified( slotName, newValue );
static StringTableEntry sShapeFile = StringTable->insert( "shapeFile" );
static StringTableEntry sSkin = StringTable->insert( "skin" );
static StringTableEntry sMountedShapeFile = StringTable->insert( "mountedShapeFile" );
static StringTableEntry sMountedSkin = StringTable->insert( "mountedSkin" );
static StringTableEntry sMountedNode = StringTable->insert( "mountedNode" );
static StringTableEntry sLightColor = StringTable->insert( "lightColor" );
static StringTableEntry sLightAmbient = StringTable->insert( "lightAmbient" );
static StringTableEntry sLightDirection = StringTable->insert( "lightDirection" );
static StringTableEntry sOrbitDistance = StringTable->insert( "orbitDistance" );
static StringTableEntry sMinOrbitDistance = StringTable->insert( "minOrbitDistance" );
static StringTableEntry sMaxOrbitDistance = StringTable->insert( "maxOrbitDistance" );
static StringTableEntry sAnimSequence = StringTable->insert( "animSequence" );
if( slotName == sShapeFile )
setObjectModel( String( mModelName ) );
else if( slotName == sSkin )
setSkin( String( mSkinName ) );
else if( slotName == sMountedShapeFile )
setMountedObject( String( mMountedModelName ) );
else if( slotName == sMountedSkin )
setMountSkin( String( mMountSkinName ) );
else if( slotName == sMountedNode )
setMountNode( String( mMountNodeName ) );
else if( slotName == sLightColor )
setLightColor( mLightColor );
else if( slotName == sLightAmbient )
setLightAmbient( mLightAmbient );
else if( slotName == sLightDirection )
setLightDirection( mLightDirection );
else if( slotName == sOrbitDistance || slotName == sMinOrbitDistance || slotName == sMaxOrbitDistance )
setOrbitDistance( mOrbitDist );
else if( slotName == sAnimSequence )
setObjectAnimation( String( mAnimationSeqName ) );
}
//------------------------------------------------------------------------------
bool GuiObjectView::onWake()
{
if( !Parent::onWake() )
return false;
if( !mLight )
{
mLight = LIGHTMGR->createLightInfo();
mLight->setColor( mLightColor );
mLight->setAmbient( mLightAmbient );
mLight->setDirection( mLightDirection );
}
return true;
}
//------------------------------------------------------------------------------
void GuiObjectView::onMouseDown( const GuiEvent &event )
{
if( !mActive || !mVisible || !mAwake )
return;
mMouseState = Rotating;
mLastMousePoint = event.mousePoint;
mouseLock();
}
//------------------------------------------------------------------------------
void GuiObjectView::onMouseUp( const GuiEvent &event )
{
mouseUnlock();
mMouseState = None;
}
//------------------------------------------------------------------------------
void GuiObjectView::onMouseDragged( const GuiEvent &event )
{
if( mMouseState != Rotating )
return;
Point2I delta = event.mousePoint - mLastMousePoint;
mLastMousePoint = event.mousePoint;
mCameraRot.x += ( delta.y * mCameraSpeed );
mCameraRot.z += ( delta.x * mCameraSpeed );
}
//------------------------------------------------------------------------------
void GuiObjectView::onRightMouseDown( const GuiEvent &event )
{
mMouseState = Zooming;
mLastMousePoint = event.mousePoint;
mouseLock();
}
//------------------------------------------------------------------------------
void GuiObjectView::onRightMouseUp( const GuiEvent &event )
{
mouseUnlock();
mMouseState = None;
}
//------------------------------------------------------------------------------
void GuiObjectView::onRightMouseDragged( const GuiEvent &event )
{
if( mMouseState != Zooming )
return;
S32 delta = event.mousePoint.y - mLastMousePoint.y;
mLastMousePoint = event.mousePoint;
mOrbitDist += ( delta * mCameraSpeed );
}
//------------------------------------------------------------------------------
void GuiObjectView::setObjectAnimation( S32 index )
{
mAnimationSeq = index;
mAnimationSeqName = String();
if( mModel )
_initAnimation();
}
//------------------------------------------------------------------------------
void GuiObjectView::setObjectAnimation( const String& sequenceName )
{
mAnimationSeq = -1;
mAnimationSeqName = sequenceName;
if( mModel )
_initAnimation();
}
//------------------------------------------------------------------------------
void GuiObjectView::setObjectModel( const String& modelName )
{
SAFE_DELETE( mModel );
mRunThread = 0;
mModelName = String::EmptyString;
// Load the shape.
Resource< TSShape > model = ResourceManager::get().load( modelName );
if( !model )
{
Con::warnf( "GuiObjectView::setObjectModel - Failed to load model '%s'", modelName.c_str() );
return;
}
// Instantiate it.
mModel = new TSShapeInstance( model, true );
mModelName = modelName;
if( !mSkinName.isEmpty() )
mModel->reSkin( mSkinName );
// Initialize camera values.
mOrbitPos = mModel->getShape()->center;
mMinOrbitDist = mModel->getShape()->radius;
// Initialize animation.
_initAnimation();
_initMount();
}
//------------------------------------------------------------------------------
void GuiObjectView::setSkin( const String& name )
{
if( mModel )
mModel->reSkin( name, mSkinName );
mSkinName = name;
}
//------------------------------------------------------------------------------
void GuiObjectView::setMountSkin( const String& name )
{
if( mMountedModel )
mMountedModel->reSkin( name, mMountSkinName );
mMountSkinName = name;
}
//------------------------------------------------------------------------------
void GuiObjectView::setMountNode( S32 index )
{
setMountNode( String::ToString( "mount%i", index ) );
}
//------------------------------------------------------------------------------
void GuiObjectView::setMountNode( const String& name )
{
mMountNodeName = name;
if( mModel )
_initMount();
}
//------------------------------------------------------------------------------
void GuiObjectView::setMountedObject( const String& modelName )
{
SAFE_DELETE( mMountedModel );
mMountedModelName = String::EmptyString;
// Load the model.
Resource< TSShape > model = ResourceManager::get().load( modelName );
if( !model )
{
Con::warnf( "GuiObjectView::setMountedObject - Failed to load object model '%s'",
modelName.c_str() );
return;
}
mMountedModel = new TSShapeInstance( model, true );
mMountedModelName = modelName;
if( !mMountSkinName.isEmpty() )
mMountedModel->reSkin( mMountSkinName );
if( mModel )
_initMount();
}
//------------------------------------------------------------------------------
bool GuiObjectView::processCameraQuery( CameraQuery* query )
{
// Adjust the camera so that we are still facing the model.
Point3F vec;
MatrixF xRot, zRot;
xRot.set( EulerF( mCameraRot.x, 0.0f, 0.0f ) );
zRot.set( EulerF( 0.0f, 0.0f, mCameraRot.z ) );
mCameraMatrix.mul( zRot, xRot );
mCameraMatrix.getColumn( 1, &vec );
vec *= mOrbitDist;
mCameraPos = mOrbitPos - vec;
query->farPlane = 2100.0f;
query->nearPlane = query->farPlane / 5000.0f;
query->fov = 45.0f;
mCameraMatrix.setColumn( 3, mCameraPos );
query->cameraMatrix = mCameraMatrix;
return true;
}
//------------------------------------------------------------------------------
void GuiObjectView::onMouseEnter( const GuiEvent & event )
{
onMouseEnter_callback();
}
//------------------------------------------------------------------------------
void GuiObjectView::onMouseLeave( const GuiEvent & event )
{
onMouseLeave_callback();
}
//------------------------------------------------------------------------------
void GuiObjectView::renderWorld( const RectI& updateRect )
{
if( !mModel )
return;
GFXTransformSaver _saveTransforms;
// Determine the camera position, and store off render state.
MatrixF modelview;
MatrixF mv;
Point3F cp;
modelview = GFX->getWorldMatrix();
mv = modelview;
mv.inverse();
mv.getColumn( 3, &cp );
RenderPassManager* renderPass = gClientSceneGraph->getDefaultRenderPass();
S32 time = Platform::getVirtualMilliseconds();
S32 dt = time - mLastRenderTime;
mLastRenderTime = time;
LIGHTMGR->unregisterAllLights();
LIGHTMGR->setSpecialLight( LightManager::slSunLightType, mLight );
GFX->setStateBlock( mDefaultGuiSB );
F32 left, right, top, bottom, nearPlane, farPlane;
bool isOrtho;
GFX->getFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho );
Frustum frust( false, left, right, top, bottom, nearPlane, farPlane, MatrixF::Identity );
SceneRenderState state
(
gClientSceneGraph,
SPT_Diffuse,
SceneCameraState( GFX->getViewport(), frust, GFX->getWorldMatrix(), GFX->getProjectionMatrix() ),
renderPass,
false
);
// Set up our TS render state here.
TSRenderState rdata;
rdata.setSceneState( &state );
// We might have some forward lit materials
// so pass down a query to gather lights.
LightQuery query;
query.init( SphereF( Point3F::Zero, 1.0f ) );
rdata.setLightQuery( &query );
// Render primary model.
if( mModel )
{
if( mRunThread )
{
mModel->advanceTime( dt / 1000.f, mRunThread );
mModel->animate();
}
mModel->render( rdata );
}
// Render mounted model.
if( mMountedModel && mMountNode != -1 )
{
GFX->pushWorldMatrix();
GFX->multWorld( mModel->mNodeTransforms[ mMountNode ] );
GFX->multWorld( mMountTransform );
mMountedModel->render( rdata );
GFX->popWorldMatrix();
}
renderPass->renderPass( &state );
// Make sure to remove our fake sun.
LIGHTMGR->unregisterAllLights();
}
//------------------------------------------------------------------------------
void GuiObjectView::setOrbitDistance( F32 distance )
{
// Make sure the orbit distance is within the acceptable range
mOrbitDist = mClampF( distance, mMinOrbitDist, mMaxOrbitDist );
}
//------------------------------------------------------------------------------
void GuiObjectView::setCameraSpeed( F32 factor )
{
mCameraSpeed = factor;
}
//------------------------------------------------------------------------------
void GuiObjectView::setLightColor( const ColorF& color )
{
mLightColor = color;
if( mLight )
mLight->setColor( color );
}
//------------------------------------------------------------------------------
void GuiObjectView::setLightAmbient( const ColorF& color )
{
mLightAmbient = color;
if( mLight )
mLight->setAmbient( color );
}
//------------------------------------------------------------------------------
void GuiObjectView::setLightDirection( const Point3F& direction )
{
mLightDirection = direction;
if( mLight )
mLight->setDirection( direction );
}
//------------------------------------------------------------------------------
void GuiObjectView::_initAnimation()
{
AssertFatal( mModel, "GuiObjectView::_initAnimation - No model loaded!" );
if( mAnimationSeqName.isEmpty() && mAnimationSeq == -1 )
return;
// Look up sequence by name.
if( !mAnimationSeqName.isEmpty() )
{
mAnimationSeq = mModel->getShape()->findSequence( mAnimationSeqName );
if( mAnimationSeq == -1 )
{
Con::errorf( "GuiObjectView::_initAnimation - Cannot find animation sequence '%s' on '%s'",
mAnimationSeqName.c_str(),
mModelName.c_str()
);
return;
}
}
// Start sequence.
if( mAnimationSeq != -1 )
{
if( mAnimationSeq >= mModel->getShape()->sequences.size() )
{
Con::errorf( "GuiObjectView::_initAnimation - Sequence '%i' out of range for model '%s'",
mAnimationSeq,
mModelName.c_str()
);
mAnimationSeq = -1;
return;
}
if( !mRunThread )
mRunThread = mModel->addThread();
mModel->setSequence( mRunThread, mAnimationSeq, 0.f );
}
mLastRenderTime = Platform::getVirtualMilliseconds();
}
//------------------------------------------------------------------------------
void GuiObjectView::_initMount()
{
AssertFatal( mModel, "GuiObjectView::_initMount - No model loaded!" );
if( !mMountedModel )
return;
mMountTransform.identity();
// Look up the node to which to mount to.
if( !mMountNodeName.isEmpty() )
{
mMountNode = mModel->getShape()->findNode( mMountNodeName );
if( mMountNode == -1 )
{
Con::errorf( "GuiObjectView::_initMount - No node '%s' on '%s'",
mMountNodeName.c_str(),
mModelName.c_str()
);
return;
}
}
// Make sure mount node is valid.
if( mMountNode != -1 && mMountNode >= mModel->getShape()->nodes.size() )
{
Con::errorf( "GuiObjectView::_initMount - Mount node index '%i' out of range for '%s'",
mMountNode,
mModelName.c_str()
);
mMountNode = -1;
return;
}
// Look up node on the mounted model from
// which to mount to the primary model's node.
S32 mountPoint = mMountedModel->getShape()->findNode( "mountPoint" );
if( mountPoint != -1 )
{
mMountedModel->getShape()->getNodeWorldTransform( mountPoint, &mMountTransform ),
mMountTransform.inverse();
}
}
//=============================================================================
// Console Methods.
//=============================================================================
// MARK: ---- Console Methods ----
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, getModel, const char*, (),,
"@brief Return the model displayed in this view.\n\n"
"@tsexample\n"
"// Request the displayed model name from the GuiObjectView object.\n"
"%modelName = %thisGuiObjectView.getModel();\n"
"@endtsexample\n\n"
"@return Name of the displayed model.\n\n"
"@see GuiControl")
{
return Con::getReturnBuffer( object->getModelName() );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setModel, void, (const char* shapeName),,
"@brief Sets the model to be displayed in this control.\n\n"
"@param shapeName Name of the model to display.\n"
"@tsexample\n"
"// Define the model we want to display\n"
"%shapeName = \"gideon.dts\";\n\n"
"// Tell the GuiObjectView object to display the defined model\n"
"%thisGuiObjectView.setModel(%shapeName);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
object->setObjectModel( shapeName );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, getMountedModel, const char*, (),,
"@brief Return the name of the mounted model.\n\n"
"@tsexample\n"
"// Request the name of the mounted model from the GuiObjectView object\n"
"%mountedModelName = %thisGuiObjectView.getMountedModel();\n"
"@endtsexample\n\n"
"@return Name of the mounted model.\n\n"
"@see GuiControl")
{
return Con::getReturnBuffer( object->getMountedModelName() );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setMountedModel, void, (const char* shapeName),,
"@brief Sets the model to be mounted on the primary model.\n\n"
"@param shapeName Name of the model to mount.\n"
"@tsexample\n"
"// Define the model name to mount\n"
"%modelToMount = \"GideonGlasses.dts\";\n\n"
"// Inform the GuiObjectView object to mount the defined model to the existing model in the control\n"
"%thisGuiObjectView.setMountedModel(%modelToMount);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
object->setObjectModel(shapeName);
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, getSkin, const char*, (),,
"@brief Return the name of skin used on the primary model.\n\n"
"@tsexample\n"
"// Request the name of the skin used on the primary model in the control\n"
"%skinName = %thisGuiObjectView.getSkin();\n"
"@endtsexample\n\n"
"@return Name of the skin used on the primary model.\n\n"
"@see GuiControl")
{
return Con::getReturnBuffer( object->getSkin() );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setSkin, void, (const char* skinName),,
"@brief Sets the skin to use on the model being displayed.\n\n"
"@param skinName Name of the skin to use.\n"
"@tsexample\n"
"// Define the skin we want to apply to the main model in the control\n"
"%skinName = \"disco_gideon\";\n\n"
"// Inform the GuiObjectView control to update the skin the to defined skin\n"
"%thisGuiObjectView.setSkin(%skinName);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
object->setSkin( skinName );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, getMountSkin, const char*, ( S32 param1, S32 param2),,
"@brief Return the name of skin used on the mounted model.\n\n"
"@tsexample\n"
"// Request the skin name from the model mounted on to the main model in the control\n"
"%mountModelSkin = %thisGuiObjectView.getMountSkin();\n"
"@endtsexample\n\n"
"@return Name of the skin used on the mounted model.\n\n"
"@see GuiControl")
{
return Con::getReturnBuffer( object->getMountSkin() );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setMountSkin, void, (const char* skinName),,
"@brief Sets the skin to use on the mounted model.\n\n"
"@param skinName Name of the skin to set on the model mounted to the main model in the control\n"
"@tsexample\n"
"// Define the name of the skin\n"
"%skinName = \"BronzeGlasses\";\n\n"
"// Inform the GuiObjectView Control of the skin to use on the mounted model\n"
"%thisGuiObjectViewCtrl.setMountSkin(%skinName);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
object->setMountSkin(skinName);
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setSeq, void, (const char* indexOrName),,
"@brief Sets the animation to play for the viewed object.\n\n"
"@param indexOrName The index or name of the animation to play.\n"
"@tsexample\n"
"// Set the animation index value, or animation sequence name.\n"
"%indexVal = \"3\";\n"
"//OR:\n"
"%indexVal = \"idle\";\n\n"
"// Inform the GuiObjectView object to set the animation sequence of the object in the control.\n"
"%thisGuiObjectVew.setSeq(%indexVal);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
if( dIsdigit( indexOrName[0] ) )
object->setObjectAnimation( dAtoi( indexOrName ) );
else
object->setObjectAnimation( indexOrName );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setMount, void, ( const char* shapeName, const char* mountNodeIndexOrName),,
"@brief Mounts the given model to the specified mount point of the primary model displayed in this control.\n\n"
"Detailed description\n\n"
"@param shapeName Name of the model to mount.\n"
"@param mountNodeIndexOrName Index or name of the mount point to be mounted to. If index, corresponds to \"mountN\" in your shape where N is the number passed here.\n"
"@tsexample\n"
"// Set the shapeName to mount\n"
"%shapeName = \"GideonGlasses.dts\"\n\n"
"// Set the mount node of the primary model in the control to mount the new shape at\n"
"%mountNodeIndexOrName = \"3\";\n"
"//OR:\n"
"%mountNodeIndexOrName = \"Face\";\n\n"
"// Inform the GuiObjectView object to mount the shape at the specified node.\n"
"%thisGuiObjectView.setMount(%shapeName,%mountNodeIndexOrName);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
if( dIsdigit( mountNodeIndexOrName[0] ) )
object->setMountNode( dAtoi( mountNodeIndexOrName ) );
else
object->setMountNode( mountNodeIndexOrName );
object->setMountedObject( shapeName );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, getOrbitDistance, F32, (),,
"@brief Return the current distance at which the camera orbits the object.\n\n"
"@tsexample\n"
"// Request the current orbit distance\n"
"%orbitDistance = %thisGuiObjectView.getOrbitDistance();\n"
"@endtsexample\n\n"
"@return The distance at which the camera orbits the object.\n\n"
"@see GuiControl")
{
return object->getOrbitDistance();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setOrbitDistance, void, (F32 distance),,
"@brief Sets the distance at which the camera orbits the object. Clamped to the acceptable range defined in the class by min and max orbit distances.\n\n"
"Detailed description\n\n"
"@param distance The distance to set the orbit to (will be clamped).\n"
"@tsexample\n"
"// Define the orbit distance value\n"
"%orbitDistance = \"1.5\";\n\n"
"// Inform the GuiObjectView object to set the orbit distance to the defined value\n"
"%thisGuiObjectView.setOrbitDistance(%orbitDistance);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
object->setOrbitDistance( distance );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, getCameraSpeed, F32, (),,
"@brief Return the current multiplier for camera zooming and rotation.\n\n"
"@tsexample\n"
"// Request the current camera zooming and rotation multiplier value\n"
"%multiplier = %thisGuiObjectView.getCameraSpeed();\n"
"@endtsexample\n\n"
"@return Camera zooming / rotation multiplier value.\n\n"
"@see GuiControl")
{
return object->getCameraSpeed();
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setCameraSpeed, void, (F32 factor),,
"@brief Sets the multiplier for the camera rotation and zoom speed.\n\n"
"@param factor Multiplier for camera rotation and zoom speed.\n"
"@tsexample\n"
"// Set the factor value\n"
"%factor = \"0.75\";\n\n"
"// Inform the GuiObjectView object to set the camera speed.\n"
"%thisGuiObjectView.setCameraSpeed(%factor);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
object->setCameraSpeed( factor );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setLightColor, void, ( ColorF color),,
"@brief Set the light color on the sun object used to render the model.\n\n"
"@param color Color of sunlight.\n"
"@tsexample\n"
"// Set the color value for the sun\n"
"%color = \"1.0 0.4 0.5\";\n\n"
"// Inform the GuiObjectView object to change the sun color to the defined value\n"
"%thisGuiObjectView.setLightColor(%color);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
object->setLightColor( color );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setLightAmbient, void, (ColorF color),,
"@brief Set the light ambient color on the sun object used to render the model.\n\n"
"@param color Ambient color of sunlight.\n"
"@tsexample\n"
"// Define the sun ambient color value\n"
"%color = \"1.0 0.4 0.6\";\n\n"
"// Inform the GuiObjectView object to set the sun ambient color to the requested value\n"
"%thisGuiObjectView.setLightAmbient(%color);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
object->setLightAmbient( color );
}
//-----------------------------------------------------------------------------
DefineEngineMethod( GuiObjectView, setLightDirection, void, (Point3F direction),,
"@brief Set the light direction from which to light the model.\n\n"
"@param direction XYZ direction from which the light will shine on the model\n"
"@tsexample\n"
"// Set the light direction\n"
"%direction = \"1.0 0.2 0.4\"\n\n"
"// Inform the GuiObjectView object to change the light direction to the defined value\n"
"%thisGuiObjectView.setLightDirection(%direction);\n"
"@endtsexample\n\n"
"@see GuiControl")
{
object->setLightDirection( direction );
}

View file

@ -0,0 +1,285 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _GUIOBJECTVIEW_H_
#define _GUIOBJECTVIEW_H_
#ifndef _GUITSCONTROL_H_
#include "gui/3d/guiTSControl.h"
#endif
#ifndef _TSSHAPEINSTANCE_H_
#include "ts/tsShapeInstance.h"
#endif
class LightInfo;
/// A control that displays a TSShape in its view.
class GuiObjectView : public GuiTSCtrl
{
public:
typedef GuiTSCtrl Parent;
DECLARE_CALLBACK( void, onMouseEnter, ());
DECLARE_CALLBACK( void, onMouseLeave, ());
protected:
/// @name Mouse Control
/// @{
enum MouseState
{
None,
Rotating,
Zooming
};
/// Current mouse operation.
MouseState mMouseState;
/// Last mouse position during tracked mouse operations.
Point2I mLastMousePoint;
/// @}
/// @name Model
/// @{
/// Name of the model loaded for display.
String mModelName;
/// Model being displayed in the view.
TSShapeInstance* mModel;
/// Name of skin to use on model.
String mSkinName;
/// @}
/// @name Camera State
/// @{
Point3F mCameraPos;
MatrixF mCameraMatrix;
EulerF mCameraRot;
Point3F mOrbitPos;
F32 mMaxOrbitDist;
F32 mMinOrbitDist;
///
F32 mOrbitDist;
/// Multiplier for camera mouse operations (rotation and zooming).
F32 mCameraSpeed;
/// @}
/// @name Mounting
/// @{
/// Name of model to mount to the primary model.
String mMountedModelName;
///
String mMountSkinName;
/// Index of the node to mount the secondary model to. -1 (disabled) by default.
S32 mMountNode;
/// Name of node to mount the secondary model to. Unset by default.
String mMountNodeName;
/// Model mounted as an image to the primary model.
TSShapeInstance* mMountedModel;
///
MatrixF mMountTransform;
/// @}
/// @name Animation
/// @{
/// Index of the animation sequence to play on the model. -1 (disabled) by default.
S32 mAnimationSeq;
/// Name of the animation sequence to play on the model. Unset by default.
String mAnimationSeqName;
/// Animation thread on the model.
TSThread* mRunThread;
/// Last time we rendered the model. Used for animation.
S32 mLastRenderTime;
/// @}
/// @name Lighting
/// @{
/// Light object used as sun during rendering.
LightInfo* mLight;
///
ColorF mLightColor;
///
ColorF mLightAmbient;
///
Point3F mLightDirection;
/// @}
///
void _initAnimation();
///
void _initMount();
///
void onStaticModified( StringTableEntry slotName, const char* newValue );
public:
GuiObjectView();
~GuiObjectView();
/// @name Model
/// @{
///
const String& getModelName() const { return mModelName; }
/// Return the instance of the model being rendered in the view.
TSShapeInstance* getModel() const { return mModel; }
/// Return the name of the skin used on the primary model.
const String& getSkin() const { return mSkinName; }
/// Set the skin to use on the primary model.
void setSkin( const String& name );
/// Set the model to show in this view.
void setObjectModel( const String& modelName );
/// @}
/// @name Animation
/// @{
/// Set the animation sequence to play on the model.
/// @param seqIndex Index of sequence to play.
void setObjectAnimation( S32 seqIndex );
/// Set the animation sequence to play on the model.
/// @param seqIndex Name of sequence to play.
void setObjectAnimation( const String& sequenceName );
/// @}
/// @name Mounting
/// @{
/// Return the model mounted to the current primary model; NULL if none.
TSShapeInstance* getMountedModel() const { return mMountedModel; }
///
const String& getMountedModelName() const { return mMountedModelName; }
/// Return the name of the skin used on the mounted model.
const String& getMountSkin() const { return mMountSkinName; }
/// Set the skin to use on the mounted model.
void setMountSkin( const String& name );
///
void setMountNode( S32 index );
///
void setMountNode( const String& nodeName );
///
void setMountedObject( const String& modelName );
/// @}
/// @name Camera
/// @{
/// Return the current camera speed multiplier.
F32 getCameraSpeed() const { return mCameraSpeed; }
/// Set the multiplier to apply to camera rotation and zooming.
void setCameraSpeed( F32 factor );
///
F32 getOrbitDistance() const { return mOrbitDist; }
/// Sets the distance at which the camera orbits the object. Clamped to the
/// acceptable range defined in the class by min and max orbit distances.
///
/// @param distance The distance to set the orbit to (will be clamped).
void setOrbitDistance( F32 distance );
/// @}
/// @name Lighting
/// @{
///
void setLightColor( const ColorF& color );
///
void setLightAmbient( const ColorF& color );
///
void setLightDirection( const Point3F& direction );
/// @}
// GuiTsCtrl.
bool onWake();
void onMouseEnter( const GuiEvent& event );
void onMouseLeave( const GuiEvent& event );
void onMouseDown( const GuiEvent& event );
void onMouseUp( const GuiEvent& event );
void onMouseDragged( const GuiEvent& event );
void onRightMouseDown( const GuiEvent& event );
void onRightMouseUp( const GuiEvent& event );
void onRightMouseDragged( const GuiEvent& event );
bool processCameraQuery( CameraQuery* query );
void renderWorld( const RectI& updateRect );
static void initPersistFields();
DECLARE_CONOBJECT( GuiObjectView );
DECLARE_DESCRIPTION( "A control that shows a TSShape model." );
};
#endif // !_GUIOBJECTVIEW_H_

1370
Engine/source/T3D/item.cpp Normal file

File diff suppressed because it is too large Load diff

191
Engine/source/T3D/item.h Normal file
View file

@ -0,0 +1,191 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _ITEM_H_
#define _ITEM_H_
#ifndef _SHAPEBASE_H_
#include "T3D/shapeBase.h"
#endif
#ifndef _BOXCONVEX_H_
#include "collision/boxConvex.h"
#endif
#ifndef _DYNAMIC_CONSOLETYPES_H_
#include "console/dynamicTypes.h"
#endif
class PhysicsBody;
//----------------------------------------------------------------------------
struct ItemData: public ShapeBaseData {
typedef ShapeBaseData Parent;
F32 friction;
F32 elasticity;
bool sticky;
F32 gravityMod;
F32 maxVelocity;
bool lightOnlyStatic;
S32 lightType;
ColorF lightColor;
S32 lightTime;
F32 lightRadius;
bool simpleServerCollision;
ItemData();
DECLARE_CONOBJECT(ItemData);
static void initPersistFields();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
};
//----------------------------------------------------------------------------
class Item: public ShapeBase
{
protected:
typedef ShapeBase Parent;
enum MaskBits {
HiddenMask = Parent::NextFreeMask,
ThrowSrcMask = Parent::NextFreeMask << 1,
PositionMask = Parent::NextFreeMask << 2,
RotationMask = Parent::NextFreeMask << 3,
NextFreeMask = Parent::NextFreeMask << 4
};
// Client interpolation data
struct StateDelta {
Point3F pos;
VectorF posVec;
S32 warpTicks;
Point3F warpOffset;
F32 dt;
};
StateDelta delta;
// Static attributes
ItemData* mDataBlock;
static F32 mGravity;
bool mStatic;
bool mRotate;
//
VectorF mVelocity;
bool mAtRest;
S32 mAtRestCounter;
static const S32 csmAtRestTimer;
bool mInLiquid;
ShapeBase* mCollisionObject;
U32 mCollisionTimeout;
PhysicsBody *mPhysicsRep;
bool mSubclassItemHandlesScene; ///< A subclass of Item will handle all of the adding to the scene
protected:
DECLARE_CALLBACK( void, onStickyCollision, ( const char* objID ));
DECLARE_CALLBACK( void, onEnterLiquid, ( const char* objID, const char* waterCoverage, const char* liquidType ));
DECLARE_CALLBACK( void, onLeaveLiquid, ( const char* objID, const char* liquidType ));
public:
void registerLights(LightManager * lightManager, bool lightingScene);
enum LightType
{
NoLight = 0,
ConstantLight,
PulsingLight,
NumLightTypes,
};
private:
S32 mDropTime;
LightInfo* mLight;
public:
Point3F mStickyCollisionPos;
Point3F mStickyCollisionNormal;
//
private:
OrthoBoxConvex mConvex;
Box3F mWorkingQueryBox;
void updateVelocity(const F32 dt);
void updatePos(const U32 mask, const F32 dt);
void updateWorkingCollisionSet(const U32 mask, const F32 dt);
bool buildPolyList(PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere);
void buildConvex(const Box3F& box, Convex* convex);
void onDeleteNotify(SimObject*);
protected:
void _updatePhysics();
void prepRenderImage(SceneRenderState *state);
void advanceTime(F32 dt);
public:
DECLARE_CONOBJECT(Item);
Item();
~Item();
static void initPersistFields();
static void consoleInit();
bool onAdd();
void onRemove();
bool onNewDataBlock( GameBaseData *dptr, bool reload );
bool isStatic() { return mStatic; }
bool isAtRest() { return mAtRest; }
bool isRotating() { return mRotate; }
Point3F getVelocity() const;
void setVelocity(const VectorF& vel);
void applyImpulse(const Point3F& pos,const VectorF& vec);
void setCollisionTimeout(ShapeBase* obj);
ShapeBase* getCollisionObject() { return mCollisionObject; };
void processTick(const Move *move);
void interpolateTick(F32 delta);
virtual void setTransform(const MatrixF &mat);
U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream);
void unpackUpdate(NetConnection *conn, BitStream *stream);
};
typedef Item::LightType ItemLightType;
DefineEnumType( ItemLightType );
#endif

View file

@ -0,0 +1,339 @@
//-----------------------------------------------------------------------------
// 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/levelInfo.h"
#include "console/consoleTypes.h"
#include "core/stream/bitStream.h"
#include "scene/sceneManager.h"
#include "lighting/advanced/advancedLightManager.h"
#include "lighting/advanced/advancedLightBinManager.h"
#include "sfx/sfxAmbience.h"
#include "sfx/sfxSoundscape.h"
#include "sfx/sfxSystem.h"
#include "sfx/sfxTypes.h"
#include "console/engineAPI.h"
#include "math/mathIO.h"
IMPLEMENT_CO_NETOBJECT_V1(LevelInfo);
ConsoleDocClass( LevelInfo,
"@brief Stores and controls the rendering and status information for a game level.\n\n"
"@tsexample\n"
"new LevelInfo(theLevelInfo)\n"
"{\n"
" visibleDistance = \"1000\";\n"
" fogColor = \"0.6 0.6 0.7 1\";\n"
" fogDensity = \"0\";\n"
" fogDensityOffset = \"700\";\n"
" fogAtmosphereHeight = \"0\";\n"
" canvasClearColor = \"0 0 0 255\";\n"
" canSaveDynamicFields = \"1\";\n"
" levelName = \"Blank Room\";\n"
" desc0 = \"A blank room ready to be populated with Torque objects.\";\n"
" Enabled = \"1\";\n"
"};\n"
"@endtsexample\n"
"@ingroup enviroMisc\n"
);
/// The color used to clear the canvas.
/// @see GuiCanvas
extern ColorI gCanvasClearColor;
/// @see DecalManager
extern F32 gDecalBias;
/// Default SFXAmbience used to reset the global soundscape.
static SFXAmbience sDefaultAmbience;
//-----------------------------------------------------------------------------
LevelInfo::LevelInfo()
: mNearClip( 0.1f ),
mVisibleDistance( 1000.0f ),
mDecalBias( 0.0015f ),
mCanvasClearColor( 255, 0, 255, 255 ),
mSoundAmbience( NULL ),
mSoundscape( NULL ),
mSoundDistanceModel( SFXDistanceModelLinear ),
mWorldSize( 10000.0f ),
mAmbientLightBlendPhase( 1.f )
{
mFogData.density = 0.0f;
mFogData.densityOffset = 0.0f;
mFogData.atmosphereHeight = 0.0f;
mFogData.color.set( 0.5f, 0.5f, 0.5f, 1.0f ),
mNetFlags.set( ScopeAlways | Ghostable );
mAdvancedLightmapSupport = false;
// Register with the light manager activation signal, and we need to do it first
// so the advanced light bin manager can be instructed about MRT lightmaps
LightManager::smActivateSignal.notify(this, &LevelInfo::_onLMActivate, 0.01f);
}
//-----------------------------------------------------------------------------
LevelInfo::~LevelInfo()
{
LightManager::smActivateSignal.remove(this, &LevelInfo::_onLMActivate);
}
//-----------------------------------------------------------------------------
void LevelInfo::initPersistFields()
{
addGroup( "Visibility" );
addField( "nearClip", TypeF32, Offset( mNearClip, LevelInfo ), "Closest distance from the camera's position to render the world." );
addField( "visibleDistance", TypeF32, Offset( mVisibleDistance, LevelInfo ), "Furthest distance fromt he camera's position to render the world." );
addField( "decalBias", TypeF32, Offset( mDecalBias, LevelInfo ),
"NearPlane bias used when rendering Decal and DecalRoad. This should be tuned to the visibleDistance in your level." );
endGroup( "Visibility" );
addGroup( "Fog" );
addField( "fogColor", TypeColorF, Offset( mFogData.color, LevelInfo ),
"The default color for the scene fog." );
addField( "fogDensity", TypeF32, Offset( mFogData.density, LevelInfo ),
"The 0 to 1 density value for the exponential fog falloff." );
addField( "fogDensityOffset", TypeF32, Offset( mFogData.densityOffset, LevelInfo ),
"An offset from the camera in meters for moving the start of the fog effect." );
addField( "fogAtmosphereHeight", TypeF32, Offset( mFogData.atmosphereHeight, LevelInfo ),
"A height in meters for altitude fog falloff." );
endGroup( "Fog" );
addGroup( "LevelInfo" );
addField( "canvasClearColor", TypeColorI, Offset( mCanvasClearColor, LevelInfo ),
"The color used to clear the background before the scene or any GUIs are rendered." );
endGroup( "LevelInfo" );
addGroup( "Lighting" );
addField( "ambientLightBlendPhase", TypeF32, Offset( mAmbientLightBlendPhase, LevelInfo ),
"Number of seconds it takes to blend from one ambient light color to a different one." );
addField( "ambientLightBlendCurve", TypeEaseF, Offset( mAmbientLightBlendCurve, LevelInfo ),
"Interpolation curve to use for blending from one ambient light color to a different one." );
addField( "advancedLightmapSupport", TypeBool, Offset( mAdvancedLightmapSupport, LevelInfo ),
"Enable expanded support for mixing static and dynamic lighting (more costly)" );
endGroup( "Lighting" );
addGroup( "Sound" );
addField( "soundAmbience", TypeSFXAmbienceName, Offset( mSoundAmbience, LevelInfo ), "The global ambient sound environment." );
addField( "soundDistanceModel", TypeSFXDistanceModel, Offset( mSoundDistanceModel, LevelInfo ), "The distance attenuation model to use." );
endGroup( "Sound" );
Parent::initPersistFields();
}
//-----------------------------------------------------------------------------
void LevelInfo::inspectPostApply()
{
_updateSceneGraph();
setMaskBits( 0xFFFFFFFF );
Parent::inspectPostApply();
}
//-----------------------------------------------------------------------------
U32 LevelInfo::packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
{
U32 retMask = Parent::packUpdate(conn, mask, stream);
stream->write( mNearClip );
stream->write( mVisibleDistance );
stream->write( mDecalBias );
stream->write( mFogData.density );
stream->write( mFogData.densityOffset );
stream->write( mFogData.atmosphereHeight );
stream->write( mFogData.color );
stream->write( mCanvasClearColor );
stream->write( mWorldSize );
stream->writeFlag( mAdvancedLightmapSupport );
stream->write( mAmbientLightBlendPhase );
mathWrite( *stream, mAmbientLightBlendCurve );
sfxWrite( stream, mSoundAmbience );
stream->writeInt( mSoundDistanceModel, 1 );
return retMask;
}
//-----------------------------------------------------------------------------
void LevelInfo::unpackUpdate(NetConnection *conn, BitStream *stream)
{
Parent::unpackUpdate(conn, stream);
stream->read( &mNearClip );
stream->read( &mVisibleDistance );
stream->read( &mDecalBias );
stream->read( &mFogData.density );
stream->read( &mFogData.densityOffset );
stream->read( &mFogData.atmosphereHeight );
stream->read( &mFogData.color );
stream->read( &mCanvasClearColor );
stream->read( &mWorldSize );
mAdvancedLightmapSupport = stream->readFlag();
stream->read( &mAmbientLightBlendPhase );
mathRead( *stream, &mAmbientLightBlendCurve );
String errorStr;
if( !sfxReadAndResolve( stream, &mSoundAmbience, errorStr ) )
Con::errorf( "%s", errorStr.c_str() );
mSoundDistanceModel = ( SFXDistanceModel ) stream->readInt( 1 );
if( isProperlyAdded() )
{
_updateSceneGraph();
if( mSoundscape )
{
if( mSoundAmbience )
mSoundscape->setAmbience( mSoundAmbience );
else
mSoundscape->setAmbience( &sDefaultAmbience );
}
SFX->setDistanceModel( mSoundDistanceModel );
}
}
//-----------------------------------------------------------------------------
bool LevelInfo::onAdd()
{
if ( !Parent::onAdd() )
return false;
// If no sound ambience has been set, default to
// 'AudioAmbienceDefault'.
if( !mSoundAmbience )
Sim::findObject( "AudioAmbienceDefault", mSoundAmbience );
// Set up sound on client.
if( isClientObject() )
{
SFX->setDistanceModel( mSoundDistanceModel );
// Set up the global ambient soundscape.
mSoundscape = SFX->getSoundscapeManager()->getGlobalSoundscape();
if( mSoundAmbience )
mSoundscape->setAmbience( mSoundAmbience );
}
_updateSceneGraph();
return true;
}
//-----------------------------------------------------------------------------
void LevelInfo::onRemove()
{
if( mSoundscape )
mSoundscape->setAmbience( &sDefaultAmbience );
Parent::onRemove();
}
//-----------------------------------------------------------------------------
void LevelInfo::_updateSceneGraph()
{
// Clamp above zero before setting on the sceneGraph.
// If we don't we get serious crashes.
if ( mNearClip <= 0.0f )
mNearClip = 0.001f;
SceneManager* scene = isClientObject() ? gClientSceneGraph : gServerSceneGraph;
scene->setNearClip( mNearClip );
scene->setVisibleDistance( mVisibleDistance );
gDecalBias = mDecalBias;
// Set ambient lighting properties.
scene->setAmbientLightTransitionTime( mAmbientLightBlendPhase * 1000.f );
scene->setAmbientLightTransitionCurve( mAmbientLightBlendCurve );
// Copy our AirFogData into the sceneGraph.
scene->setFogData( mFogData );
// If the level info specifies that MRT pre-pass should be used in this scene
// enable it via the appropriate light manager
// (Basic lighting doesn't do anything different right now)
#ifndef TORQUE_DEDICATED
if(isClientObject())
_onLMActivate(LIGHTMGR->getId(), true);
#endif
// TODO: This probably needs to be moved.
gCanvasClearColor = mCanvasClearColor;
}
//-----------------------------------------------------------------------------
void LevelInfo::_onLMActivate(const char *lm, bool enable)
{
#ifndef TORQUE_DEDICATED
// Advanced light manager
if(enable && String(lm) == String("ADVLM"))
{
AssertFatal(dynamic_cast<AdvancedLightManager *>(LIGHTMGR), "Bad light manager type!");
AdvancedLightManager *lightMgr = static_cast<AdvancedLightManager *>(LIGHTMGR);
lightMgr->getLightBinManager()->MRTLightmapsDuringPrePass(mAdvancedLightmapSupport);
}
#endif
}

View file

@ -0,0 +1,138 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
#ifndef _LEVELINFO_H_
#define _LEVELINFO_H_
#ifndef _NETOBJECT_H_
#include "sim/netObject.h"
#endif
#ifndef _COLOR_H_
#include "core/color.h"
#endif
#ifndef _FOGSTRUCTS_H_
#include "scene/fogStructs.h"
#endif
#ifndef _SFXCOMMON_H_
#include "sfx/sfxCommon.h"
#endif
class SFXAmbience;
class SFXSoundscape;
class LevelInfo : public NetObject
{
typedef NetObject Parent;
private:
F32 mWorldSize;
FogData mFogData;
F32 mNearClip;
F32 mVisibleDistance;
F32 mDecalBias;
ColorI mCanvasClearColor;
/// @name Lighting Properties
/// @{
bool mAdvancedLightmapSupport;
/// Seconds it takes to go from one global ambient color
/// to a different one.
F32 mAmbientLightBlendPhase;
/// Interpolation for going from one global ambient color
/// to a different one.
EaseF mAmbientLightBlendCurve;
/// @}
/// @name Sound Properties
/// @{
/// Global ambient sound space properties.
SFXAmbience* mSoundAmbience;
/// Distance attenuation model to use.
SFXDistanceModel mSoundDistanceModel;
///
SFXSoundscape* mSoundscape;
/// @}
/// Responsible for passing on
/// the LevelInfo settings to the
/// client SceneGraph, from which
/// other systems can get at them.
void _updateSceneGraph();
void _onLMActivate(const char *lm, bool enable);
public:
LevelInfo();
virtual ~LevelInfo();
DECLARE_CONOBJECT(LevelInfo);
/// @name SceneObject Inheritance
/// @{
virtual SFXAmbience* getSoundAmbience() const { return mSoundAmbience; }
/// @}
/// @name SimObject Inheritance
/// @{
virtual bool onAdd();
virtual void onRemove();
virtual void inspectPostApply();
static void initPersistFields();
/// @}
/// @name NetObject Inheritance
/// @{
enum NetMaskBits
{
UpdateMask = BIT(0)
};
virtual U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream );
virtual void unpackUpdate( NetConnection *conn, BitStream *stream );
/// @}
};
#endif // _LEVELINFO_H_

View file

@ -0,0 +1,309 @@
//-----------------------------------------------------------------------------
// 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 "lightAnimData.h"
#include "console/consoleTypes.h"
#include "T3D/lightBase.h"
#include "math/mRandom.h"
#include "math/mathIO.h"
#include "T3D/gameBase/processList.h"
#include "core/stream/bitStream.h"
LightAnimData::LightAnimData()
{
}
LightAnimData::~LightAnimData()
{
}
IMPLEMENT_CO_DATABLOCK_V1( LightAnimData );
ConsoleDocClass( LightAnimData,
"@brief A datablock which defines and performs light animation, such as rotation, brightness fade, and colorization.\n\n"
"@tsexample\n"
"datablock LightAnimData( SubtlePulseLightAnim )\n"
"{\n"
" brightnessA = 0.5;\n"
" brightnessZ = 1;\n"
" brightnessPeriod = 1;\n"
" brightnessKeys = \"aza\";\n"
" brightnessSmooth = true;\n"
"};\n"
"@endtsexample\n\n"
"@see LightBase\n\n"
"@see LightDescription\n\n"
"@ingroup FX\n"
"@ingroup Lighting\n"
);
void LightAnimData::initPersistFields()
{
addGroup( "Offset",
"The XYZ translation animation state relative to the light position." );
addField( "offsetA", TypeF32, Offset( mOffset.value1, LightAnimData ), 3,
"The value of the A key in the keyframe sequence." );
addField( "offsetZ", TypeF32, Offset( mOffset.value2, LightAnimData ), 3,
"The value of the Z key in the keyframe sequence." );
addField( "offsetPeriod", TypeF32, Offset( mOffset.period, LightAnimData ), 3,
"The animation time for keyframe sequence." );
addField( "offsetKeys", TypeString, Offset( mOffset.keys, LightAnimData ), 3,
"The keyframe sequence encoded into a string where characters from A to Z define "
"a position between the two animation values." );
addField( "offsetSmooth", TypeBool, Offset( mOffset.smooth, LightAnimData ), 3,
"If true the transition between keyframes will be smooth." );
endGroup( "Offset" );
addGroup( "Rotation",
"The XYZ rotation animation state relative to the light orientation." );
addField( "rotA", TypeF32, Offset( mRot.value1, LightAnimData ), 3,
"The value of the A key in the keyframe sequence." );
addField( "rotZ", TypeF32, Offset( mRot.value2, LightAnimData ), 3,
"The value of the Z key in the keyframe sequence." );
addField( "rotPeriod", TypeF32, Offset( mRot.period, LightAnimData ), 3,
"The animation time for keyframe sequence." );
addField( "rotKeys", TypeString, Offset( mRot.keys, LightAnimData ), 3,
"The keyframe sequence encoded into a string where characters from A to Z define "
"a position between the two animation values." );
addField( "rotSmooth", TypeBool, Offset( mRot.smooth, LightAnimData ), 3,
"If true the transition between keyframes will be smooth." );
endGroup( "Rotation" );
addGroup( "Color",
"The RGB color animation state." );
addField( "colorA", TypeF32, Offset( mColor.value1, LightAnimData ), 3,
"The value of the A key in the keyframe sequence." );
addField( "colorZ", TypeF32, Offset( mColor.value2, LightAnimData ), 3,
"The value of the Z key in the keyframe sequence." );
addField( "colorPeriod", TypeF32, Offset( mColor.period, LightAnimData ), 3,
"The animation time for keyframe sequence." );
addField( "colorKeys", TypeString, Offset( mColor.keys, LightAnimData ), 3,
"The keyframe sequence encoded into a string where characters from A to Z define "
"a position between the two animation values." );
addField( "colorSmooth", TypeBool, Offset( mColor.smooth, LightAnimData ), 3,
"If true the transition between keyframes will be smooth." );
endGroup( "Color" );
addGroup( "Brightness",
"The brightness animation state." );
addField( "brightnessA", TypeF32, Offset( mBrightness.value1, LightAnimData ),
"The value of the A key in the keyframe sequence." );
addField( "brightnessZ", TypeF32, Offset( mBrightness.value2, LightAnimData ),
"The value of the Z key in the keyframe sequence." );
addField( "brightnessPeriod", TypeF32, Offset( mBrightness.period, LightAnimData ),
"The animation time for keyframe sequence." );
addField( "brightnessKeys", TypeString, Offset( mBrightness.keys, LightAnimData ),
"The keyframe sequence encoded into a string where characters from A to Z define "
"a position between the two animation values." );
addField( "brightnessSmooth", TypeBool, Offset( mBrightness.smooth, LightAnimData ),
"If true the transition between keyframes will be smooth." );
endGroup( "Brightness" );
Parent::initPersistFields();
}
bool LightAnimData::preload( bool server, String &errorStr )
{
if ( !Parent::preload( server, errorStr ) )
return false;
_updateKeys();
return true;
}
void LightAnimData::inspectPostApply()
{
Parent::inspectPostApply();
_updateKeys();
}
void LightAnimData::_updateKeys()
{
mOffset.updateKey();
mRot.updateKey();
mColor.updateKey();
mBrightness.updateKey();
}
template<U32 COUNT>
void LightAnimData::AnimValue<COUNT>::updateKey()
{
for ( U32 i=0; i < COUNT; i++ )
{
timeScale[i] = 0.0f;
keyLen[i] = 0.0f;
if ( keys[i] && keys[i][0] && period[i] > 0.0f )
{
keyLen[i] = dStrlen( keys[i] );
timeScale[i] = (F32)( keyLen[i] - 1 ) / period[i];
}
}
}
template<U32 COUNT>
bool LightAnimData::AnimValue<COUNT>::animate( F32 time, F32 *output )
{
F32 scaledTime, lerpFactor, valueRange, keyFrameLerp;
U32 posFrom, posTo;
S32 keyFrameFrom, keyFrameTo;
bool wasAnimated = false;
for ( U32 i=0; i < COUNT; i++ )
{
if ( mIsZero( timeScale[i] ) )
continue;
wasAnimated = true;
scaledTime = mFmod( time, period[i] ) * timeScale[i];
posFrom = mFloor( scaledTime );
posTo = mCeil( scaledTime );
keyFrameFrom = dToupper( keys[i][posFrom] ) - 65;
keyFrameTo = dToupper( keys[i][posTo] ) - 65;
valueRange = ( value2[i] - value1[i] ) / 25.0f;
if ( !smooth[i] )
output[i] = value1[i] + ( keyFrameFrom * valueRange );
else
{
lerpFactor = scaledTime - posFrom;
keyFrameLerp = ( keyFrameTo - keyFrameFrom ) * lerpFactor;
output[i] = value1[i] + ( ( keyFrameFrom + keyFrameLerp ) * valueRange );
}
}
return wasAnimated;
}
template<U32 COUNT>
void LightAnimData::AnimValue<COUNT>::write( BitStream *stream ) const
{
for ( U32 i=0; i < COUNT; i++ )
{
stream->write( value1[i] );
stream->write( value2[i] );
stream->write( period[i] );
stream->writeString( keys[i] );
}
}
template<U32 COUNT>
void LightAnimData::AnimValue<COUNT>::read( BitStream *stream )
{
for ( U32 i=0; i < COUNT; i++ )
{
stream->read( &value1[i] );
stream->read( &value2[i] );
stream->read( &period[i] );
keys[i] = stream->readSTString();
}
}
void LightAnimData::packData( BitStream *stream )
{
Parent::packData( stream );
mOffset.write( stream );
mRot.write( stream );
mColor.write( stream );
mBrightness.write( stream );
}
void LightAnimData::unpackData( BitStream *stream )
{
Parent::unpackData( stream );
mOffset.read( stream );
mRot.read( stream );
mColor.read( stream );
mBrightness.read( stream );
}
void LightAnimData::animate( LightInfo *lightInfo, LightAnimState *state )
{
PROFILE_SCOPE( LightAnimData_animate );
// Calculate the input time for animation.
F32 time = state->animationPhase +
( (F32)Sim::getCurrentTime() * 0.001f ) /
state->animationPeriod;
MatrixF transform( state->transform );
EulerF euler( Point3F::Zero );
if ( mRot.animate( time, euler ) )
{
euler.x = mDegToRad( euler.x );
euler.y = mDegToRad( euler.y );
euler.z = mDegToRad( euler.z );
MatrixF rot( euler );
transform.mul( rot );
}
Point3F offset( Point3F::Zero );
if ( mOffset.animate( time, offset ) )
transform.displace( offset );
lightInfo->setTransform( transform );
ColorF color = state->color;
mColor.animate( time, color );
lightInfo->setColor( color );
F32 brightness = state->brightness;
mBrightness.animate( time, &brightness );
lightInfo->setBrightness( brightness );
}

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