mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-04-20 03:45:26 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
174
Engine/source/util/catmullRom.cpp
Normal file
174
Engine/source/util/catmullRom.cpp
Normal 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 "util/catmullRom.h"
|
||||
|
||||
#include "math/mMathFn.h"
|
||||
|
||||
|
||||
const F32 CatmullRomBase::smX[] =
|
||||
{
|
||||
0.0000000000f, 0.5384693101f, -0.5384693101f, 0.9061798459f, -0.9061798459f
|
||||
};
|
||||
|
||||
const F32 CatmullRomBase::smC[] =
|
||||
{
|
||||
0.5688888889f, 0.4786286705f, 0.4786286705f, 0.2369268850f, 0.2369268850f
|
||||
};
|
||||
|
||||
|
||||
CatmullRomBase::CatmullRomBase()
|
||||
: mTimes( NULL ),
|
||||
mLengths( NULL ),
|
||||
mTotalLength( 0.0f ),
|
||||
mCount( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
void CatmullRomBase::_initialize( U32 count, const F32 *times )
|
||||
{
|
||||
//AssertFatal( times, "CatmullRomBase::_initialize() - Got null position!" )
|
||||
AssertFatal( count > 1, "CatmullRomBase::_initialize() - Must have more than 2 points!" )
|
||||
|
||||
// set up arrays
|
||||
mTimes = new F32[count];
|
||||
mCount = count;
|
||||
|
||||
// set up curve segment lengths
|
||||
mLengths = new F32[count-1];
|
||||
mTotalLength = 0.0f;
|
||||
for ( U32 i = 0; i < count-1; ++i )
|
||||
{
|
||||
mLengths[i] = segmentArcLength(i, 0.0f, 1.0f);
|
||||
mTotalLength += mLengths[i];
|
||||
}
|
||||
|
||||
// copy the times if we have them.
|
||||
F32 l = 0.0f;
|
||||
for ( U32 i = 0; i < count; ++i )
|
||||
{
|
||||
if ( times )
|
||||
mTimes[i] = times[i];
|
||||
else
|
||||
{
|
||||
if ( mIsZero( mTotalLength ) )
|
||||
mTimes[i] = 0.0f;
|
||||
else
|
||||
mTimes[i] = l / mTotalLength;
|
||||
if ( i < count-1 )
|
||||
l += mLengths[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CatmullRomBase::clear()
|
||||
{
|
||||
delete [] mTimes;
|
||||
mTimes = NULL;
|
||||
|
||||
delete [] mLengths;
|
||||
mLengths = NULL;
|
||||
|
||||
mTotalLength = 0.0f;
|
||||
mCount = 0;
|
||||
}
|
||||
|
||||
F32 CatmullRomBase::arcLength( F32 t1, F32 t2 )
|
||||
{
|
||||
if ( t2 <= t1 )
|
||||
return 0.0f;
|
||||
|
||||
if ( t1 < mTimes[0] )
|
||||
t1 = mTimes[0];
|
||||
|
||||
if ( t2 > mTimes[mCount-1] )
|
||||
t2 = mTimes[mCount-1];
|
||||
|
||||
// find segment and parameter
|
||||
U32 seg1;
|
||||
for ( seg1 = 0; seg1 < mCount-1; ++seg1 )
|
||||
{
|
||||
if ( t1 <= mTimes[seg1+1] )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
F32 u1 = (t1 - mTimes[seg1])/(mTimes[seg1+1] - mTimes[seg1]);
|
||||
|
||||
// find segment and parameter
|
||||
U32 seg2;
|
||||
for ( seg2 = 0; seg2 < mCount-1; ++seg2 )
|
||||
{
|
||||
if ( t2 <= mTimes[seg2+1] )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
F32 u2 = (t2 - mTimes[seg2])/(mTimes[seg2+1] - mTimes[seg2]);
|
||||
|
||||
F32 result;
|
||||
// both parameters lie in one segment
|
||||
if ( seg1 == seg2 )
|
||||
{
|
||||
result = segmentArcLength( seg1, u1, u2 );
|
||||
}
|
||||
// parameters cross segments
|
||||
else
|
||||
{
|
||||
result = segmentArcLength( seg1, u1, 1.0f );
|
||||
for ( U32 i = seg1+1; i < seg2; ++i )
|
||||
result += mLengths[i];
|
||||
result += segmentArcLength( seg2, 0.0f, u2 );
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
U32 CatmullRomBase::getPrevNode( F32 t )
|
||||
{
|
||||
AssertFatal( mCount >= 2, "CatmullRomBase::getPrevNode - Bad point count!" );
|
||||
|
||||
// handle boundary conditions
|
||||
if ( t <= mTimes[0] )
|
||||
return 0;
|
||||
else if ( t >= mTimes[mCount-1] )
|
||||
return mCount-1;
|
||||
|
||||
// find segment and parameter
|
||||
U32 i; // segment #
|
||||
for ( i = 0; i < mCount-1; ++i )
|
||||
{
|
||||
if ( t <= mTimes[i+1] )
|
||||
break;
|
||||
}
|
||||
|
||||
AssertFatal( i >= 0 && i < mCount, "CatmullRomBase::getPrevNode - Got bad output index!" );
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
F32 CatmullRomBase::getTime( U32 idx )
|
||||
{
|
||||
AssertFatal( idx >= 0 && idx < mCount, "CatmullRomBase::getTime - Got bad index!" );
|
||||
return mTimes[idx];
|
||||
}
|
||||
371
Engine/source/util/catmullRom.h
Normal file
371
Engine/source/util/catmullRom.h
Normal file
|
|
@ -0,0 +1,371 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _CATMULLROM_H_
|
||||
#define _CATMULLROM_H_
|
||||
|
||||
|
||||
/// The shared base class used by the catmull rom
|
||||
/// interpolation template class.
|
||||
/// @see CatmullRom
|
||||
class CatmullRomBase
|
||||
{
|
||||
protected:
|
||||
|
||||
CatmullRomBase();
|
||||
virtual ~CatmullRomBase() {}
|
||||
|
||||
public:
|
||||
|
||||
/// Clean out all the data.
|
||||
virtual void clear();
|
||||
|
||||
/// Find length of curve between parameters t1 and t2.
|
||||
F32 arcLength( F32 t1, F32 t2 );
|
||||
|
||||
/// Get the total length of the curve.
|
||||
inline F32 getLength() { return mTotalLength; }
|
||||
|
||||
/// Get the closest previous control point to time t.
|
||||
U32 getPrevNode( F32 t );
|
||||
|
||||
/// Returns the time at idx (rather than at a F32 time)
|
||||
F32 getTime( U32 idx );
|
||||
|
||||
/// Find length of curve segment between parameters u1 and u2.
|
||||
virtual F32 segmentArcLength( U32 i, F32 u1, F32 u2 ) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
static const F32 smX[];
|
||||
static const F32 smC[];
|
||||
|
||||
void _initialize( U32 count, const F32 *times = NULL );
|
||||
|
||||
/// The time to arrive at each point.
|
||||
F32 *mTimes;
|
||||
|
||||
/// the length of each curve segment.
|
||||
F32* mLengths;
|
||||
|
||||
/// The total length of curve.
|
||||
F32 mTotalLength;
|
||||
|
||||
/// The number of points and times.
|
||||
U32 mCount;
|
||||
};
|
||||
|
||||
|
||||
/// The catmull-rom template class for performing interpolation
|
||||
/// over an arbitraty type.
|
||||
template<typename TYPE>
|
||||
class CatmullRom : public CatmullRomBase
|
||||
{
|
||||
public:
|
||||
|
||||
CatmullRom();
|
||||
virtual ~CatmullRom();
|
||||
|
||||
/// Initialization.
|
||||
void initialize( U32 count, const TYPE *positions, const F32 *times = NULL );
|
||||
|
||||
// evaluate position
|
||||
TYPE evaluate( F32 t );
|
||||
|
||||
/// Evaluate derivative at parameter t.
|
||||
TYPE velocity( F32 t );
|
||||
|
||||
// Evaluate second derivative at parameter t.
|
||||
TYPE acceleration( F32 t );
|
||||
|
||||
// Returns the position at idx (rather than at a F32 time)
|
||||
TYPE getPosition( U32 idx );
|
||||
|
||||
// CatmullRomBase
|
||||
void clear();
|
||||
F32 segmentArcLength( U32 i, F32 u1, F32 u2 );
|
||||
|
||||
protected:
|
||||
|
||||
/// The sample points.
|
||||
TYPE* mPositions;
|
||||
|
||||
private:
|
||||
|
||||
/// The copy constructors are disabled.
|
||||
CatmullRom( const CatmullRom &other );
|
||||
CatmullRom& operator=( const CatmullRom &other );
|
||||
};
|
||||
|
||||
|
||||
template<typename TYPE>
|
||||
inline CatmullRom<TYPE>::CatmullRom()
|
||||
: CatmullRomBase(),
|
||||
mPositions( NULL )
|
||||
{
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
inline CatmullRom<TYPE>::~CatmullRom()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
inline void CatmullRom<TYPE>::clear()
|
||||
{
|
||||
delete [] mPositions;
|
||||
mPositions = NULL;
|
||||
|
||||
CatmullRomBase::clear();
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
inline void CatmullRom<TYPE>::initialize( U32 count, const TYPE *positions, const F32 *times )
|
||||
{
|
||||
AssertFatal( positions, "CatmullRom::initialize - Got null position!" )
|
||||
AssertFatal( count > 1, "CatmullRom::initialize - Must have more than 2 points!" )
|
||||
|
||||
// Clean up any previous state.
|
||||
clear();
|
||||
|
||||
// copy the points.
|
||||
mPositions = new TYPE[count];
|
||||
for ( U32 i = 0; i < count; ++i )
|
||||
mPositions[i] = positions[i];
|
||||
|
||||
_initialize( count, times );
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
inline TYPE CatmullRom<TYPE>::evaluate( F32 t )
|
||||
{
|
||||
AssertFatal( mCount >= 2, "CatmullRom::evaluate - Not initialized!" );
|
||||
|
||||
// handle boundary conditions
|
||||
if ( t <= mTimes[0] )
|
||||
return mPositions[0];
|
||||
else if ( t >= mTimes[mCount-1] )
|
||||
return mPositions[mCount-1];
|
||||
|
||||
// find segment and parameter
|
||||
U32 i; // segment #
|
||||
for ( i = 0; i < mCount-1; ++i )
|
||||
{
|
||||
if ( t <= mTimes[i+1] )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AssertFatal( i >= 0 && i < mCount, "CatmullRom::evaluate - Got bad index!" );
|
||||
|
||||
F32 t0 = mTimes[i];
|
||||
F32 t1 = mTimes[i+1];
|
||||
F32 u = (t - t0)/(t1 - t0);
|
||||
|
||||
S32 idx0, idx1, idx2, idx3;
|
||||
idx0 = i - 1;
|
||||
idx1 = i;
|
||||
idx2 = i + 1;
|
||||
idx3 = i + 2;
|
||||
|
||||
if ( idx0 < 0 )
|
||||
idx0 = 0;
|
||||
if ( idx3 >= mCount )
|
||||
idx3 = mCount - 1;
|
||||
|
||||
TYPE A = 3.0f*mPositions[idx1]
|
||||
- mPositions[idx0]
|
||||
- 3.0f*mPositions[idx2]
|
||||
+ mPositions[idx3];
|
||||
|
||||
TYPE B = 2.0f*mPositions[idx0]
|
||||
- 5.0f*mPositions[idx1]
|
||||
+ 4.0f*mPositions[idx2]
|
||||
- mPositions[idx3];
|
||||
|
||||
TYPE C = mPositions[idx2] - mPositions[idx0];
|
||||
|
||||
return mPositions[i] + (0.5f*u)*(C + u*(B + u*A));
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
inline TYPE CatmullRom<TYPE>::velocity( F32 t )
|
||||
{
|
||||
AssertFatal( mCount >= 2, "CatmullRom::velocity - Not initialized!" );
|
||||
|
||||
// handle boundary conditions
|
||||
if ( t <= mTimes[0] )
|
||||
t = 0.0f;
|
||||
else if ( t > mTimes[mCount-1] )
|
||||
t = mTimes[mCount-1];
|
||||
|
||||
// find segment and parameter
|
||||
U32 i;
|
||||
for ( i = 0; i < mCount-1; ++i )
|
||||
{
|
||||
if ( t <= mTimes[i+1] )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
F32 t0 = mTimes[i];
|
||||
F32 t1 = mTimes[i+1];
|
||||
F32 u = (t - t0)/(t1 - t0);
|
||||
|
||||
S32 idx0, idx1, idx2, idx3;
|
||||
idx0 = i - 1;
|
||||
idx1 = i;
|
||||
idx2 = i + 1;
|
||||
idx3 = i + 2;
|
||||
|
||||
if ( idx0 < 0 )
|
||||
idx0 = 0;
|
||||
if ( idx3 >= mCount )
|
||||
idx3 = mCount - 1;
|
||||
|
||||
TYPE A = 3.0f*mPositions[idx1]
|
||||
- mPositions[idx0]
|
||||
- 3.0f*mPositions[idx2]
|
||||
+ mPositions[idx3];
|
||||
|
||||
TYPE B = 2.0f*mPositions[idx0]
|
||||
- 5.0f*mPositions[idx1]
|
||||
+ 4.0f*mPositions[idx2]
|
||||
- mPositions[idx3];
|
||||
|
||||
TYPE C = mPositions[idx2] - mPositions[idx0];
|
||||
|
||||
return 0.5f*C + u*(B + 1.5f*u*A);
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
inline TYPE CatmullRom<TYPE>::acceleration( F32 t )
|
||||
{
|
||||
AssertFatal( mCount >= 2, "CatmullRom::acceleration - Not initialized!" );
|
||||
|
||||
// handle boundary conditions
|
||||
if ( t <= mTimes[0] )
|
||||
t = 0.0f;
|
||||
else if ( t > mTimes[mCount-1] )
|
||||
t = mTimes[mCount-1];
|
||||
|
||||
// find segment and parameter
|
||||
U32 i;
|
||||
for ( i = 0; i < mCount-1; ++i )
|
||||
{
|
||||
if ( t <= mTimes[i+1] )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
F32 t0 = mTimes[i];
|
||||
F32 t1 = mTimes[i+1];
|
||||
F32 u = (t - t0)/(t1 - t0);
|
||||
|
||||
S32 idx0, idx1, idx2, idx3;
|
||||
idx0 = i - 1;
|
||||
idx1 = i;
|
||||
idx2 = i + 1;
|
||||
idx3 = i + 2;
|
||||
|
||||
if ( idx0 < 0 )
|
||||
idx0 = 0;
|
||||
if ( idx3 >= mCount )
|
||||
idx3 = mCount - 1;
|
||||
|
||||
TYPE A = 3.0f*mPositions[idx1]
|
||||
- mPositions[idx0]
|
||||
- 3.0f*mPositions[idx2]
|
||||
+ mPositions[idx3];
|
||||
|
||||
TYPE B = 2.0f*mPositions[idx0]
|
||||
- 5.0f*mPositions[idx1]
|
||||
+ 4.0f*mPositions[idx2]
|
||||
- mPositions[idx3];
|
||||
|
||||
TYPE C = mPositions[idx2] - mPositions[idx0];
|
||||
|
||||
return B + (3.0f*u)*A;
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
inline TYPE CatmullRom<TYPE>::getPosition( U32 idx )
|
||||
{
|
||||
AssertFatal( idx >= 0 && idx < mCount-1, "CatmullRom<>::getPosition - Got bad index!" );
|
||||
return mPositions[idx];
|
||||
}
|
||||
|
||||
template<typename TYPE>
|
||||
inline F32 CatmullRom<TYPE>::segmentArcLength( U32 i, F32 u1, F32 u2 )
|
||||
{
|
||||
AssertFatal( i >= 0 && i < mCount-1, "CatmullRom<>::getPosition - Got bad index!" );
|
||||
|
||||
if ( u2 <= u1 )
|
||||
return 0.0f;
|
||||
|
||||
if ( u1 < 0.0f )
|
||||
u1 = 0.0f;
|
||||
|
||||
if ( u2 > 1.0f )
|
||||
u2 = 1.0f;
|
||||
|
||||
S32 idx0, idx1, idx2, idx3;
|
||||
idx0 = i - 1;
|
||||
idx1 = i;
|
||||
idx2 = i + 1;
|
||||
idx3 = i + 2;
|
||||
|
||||
if ( idx0 < 0 )
|
||||
idx0 = 0;
|
||||
if ( idx3 >= mCount )
|
||||
idx3 = mCount - 1;
|
||||
|
||||
TYPE A = 3.0f*mPositions[idx1]
|
||||
- mPositions[idx0]
|
||||
- 3.0f*mPositions[idx2]
|
||||
+ mPositions[idx3];
|
||||
TYPE B = 2.0f*mPositions[idx0]
|
||||
- 5.0f*mPositions[idx1]
|
||||
+ 4.0f*mPositions[idx2]
|
||||
- mPositions[idx3];
|
||||
TYPE C = mPositions[idx2] - mPositions[idx0];
|
||||
|
||||
F32 sum = 0.0f;
|
||||
|
||||
for ( U32 j = 0; j < 5; ++j )
|
||||
{
|
||||
F32 u = 0.5f*((u2 - u1)*smX[j] + u2 + u1);
|
||||
TYPE derivative;
|
||||
if ( i == 0 || i >= mCount-2)
|
||||
derivative = 0.5f*B + u*A;
|
||||
else
|
||||
derivative = 0.5f*C + u*(B + 1.5f*u*A);
|
||||
sum += smC[j]*derivative.len();
|
||||
}
|
||||
sum *= 0.5f*(u2-u1);
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
#endif // _CATMULLROM_H_
|
||||
95
Engine/source/util/fpsTracker.cpp
Normal file
95
Engine/source/util/fpsTracker.cpp
Normal 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "util/fpsTracker.h"
|
||||
#include "console/console.h"
|
||||
|
||||
FPSTracker gFPS;
|
||||
|
||||
FPSTracker::FPSTracker()
|
||||
{
|
||||
mUpdateInterval = 0.25f;
|
||||
reset();
|
||||
}
|
||||
|
||||
void FPSTracker::reset()
|
||||
{
|
||||
fpsNext = (F32)Platform::getRealMilliseconds()/1000.0f + mUpdateInterval;
|
||||
|
||||
fpsRealLast = 0.0f;
|
||||
fpsReal = 0.0f;
|
||||
fpsRealMin = 0.000001f; // Avoid division by zero.
|
||||
fpsRealMax = 1.0f;
|
||||
fpsVirtualLast = 0.0f;
|
||||
fpsVirtual = 0.0f;
|
||||
fpsFrames = 0;
|
||||
}
|
||||
|
||||
void FPSTracker::update()
|
||||
{
|
||||
const float alpha = 0.07f;
|
||||
F32 realSeconds = (F32)Platform::getRealMilliseconds()/1000.0f;
|
||||
F32 virtualSeconds = (F32)Platform::getVirtualMilliseconds()/1000.0f;
|
||||
|
||||
fpsFrames++;
|
||||
if (fpsFrames > 1)
|
||||
{
|
||||
fpsReal = fpsReal*(1.0-alpha) + (realSeconds-fpsRealLast)*alpha;
|
||||
fpsVirtual = fpsVirtual*(1.0-alpha) + (virtualSeconds-fpsVirtualLast)*alpha;
|
||||
|
||||
if( fpsFrames > 10 ) // Wait a few frames before updating these.
|
||||
{
|
||||
// Update min/max. This is a bit counter-intuitive, as the comparisons are
|
||||
// inversed because these are all one-over-x values.
|
||||
|
||||
if( fpsReal > fpsRealMin )
|
||||
fpsRealMin = fpsReal;
|
||||
if( fpsReal < fpsRealMax )
|
||||
fpsRealMax = fpsReal;
|
||||
}
|
||||
}
|
||||
|
||||
fpsRealLast = realSeconds;
|
||||
fpsVirtualLast = virtualSeconds;
|
||||
|
||||
// update variables every few frames
|
||||
F32 update = fpsRealLast - fpsNext;
|
||||
if (update > 0.5f)
|
||||
{
|
||||
Con::setVariable( "fps::real", avar( "%4.1f", 1.0f / fpsReal ) );
|
||||
Con::setVariable( "fps::realMin", avar( "%4.1f", 1.0f / fpsRealMin ) );
|
||||
Con::setVariable( "fps::realMax", avar( "%4.1f", 1.0f / fpsRealMax ) );
|
||||
Con::setVariable( "fps::virtual", avar( "%4.1f", 1.0f / fpsVirtual ) );
|
||||
|
||||
if (update > mUpdateInterval)
|
||||
fpsNext = fpsRealLast + mUpdateInterval;
|
||||
else
|
||||
fpsNext += mUpdateInterval;
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleFunction( resetFPSTracker, void, 1, 1, "()"
|
||||
"@brief Reset FPS stats (fps::)\n\n"
|
||||
"@ingroup Game")
|
||||
{
|
||||
gFPS.reset();
|
||||
}
|
||||
51
Engine/source/util/fpsTracker.h
Normal file
51
Engine/source/util/fpsTracker.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _UTIL_FPSTRACKER_H_
|
||||
#define _UTIL_FPSTRACKER_H_
|
||||
|
||||
#include "platform/platform.h"
|
||||
|
||||
struct FPSTracker
|
||||
{
|
||||
F32 fpsRealLast;
|
||||
F32 fpsReal;
|
||||
F32 fpsRealMin;
|
||||
F32 fpsRealMax;
|
||||
F32 fpsVirtualLast;
|
||||
F32 fpsVirtual;
|
||||
F32 fpsFrames;
|
||||
F32 fpsNext;
|
||||
F32 mUpdateInterval;
|
||||
|
||||
FPSTracker();
|
||||
|
||||
/// Resets the FPS variables
|
||||
void reset();
|
||||
|
||||
/// Updates the FPS variables
|
||||
void update();
|
||||
};
|
||||
|
||||
extern FPSTracker gFPS;
|
||||
|
||||
#endif
|
||||
522
Engine/source/util/imposterCapture.cpp
Normal file
522
Engine/source/util/imposterCapture.cpp
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "util/imposterCapture.h"
|
||||
|
||||
#include "gfx/bitmap/gBitmap.h"
|
||||
#include "core/color.h"
|
||||
#include "renderInstance/renderPassManager.h"
|
||||
#include "renderInstance/renderMeshMgr.h"
|
||||
#include "materials/materialManager.h"
|
||||
#include "materials/materialFeatureTypes.h"
|
||||
#include "materials/customMaterialDefinition.h"
|
||||
#include "ts/tsShapeInstance.h"
|
||||
#include "scene/sceneManager.h"
|
||||
#include "scene/sceneRenderState.h"
|
||||
#include "lighting/lightInfo.h"
|
||||
#include "gfx/gfxTransformSaver.h"
|
||||
#include "gfx/gfxDebugEvent.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
|
||||
|
||||
/// A material hook used to hold imposter generation
|
||||
/// rendering materials for an object.
|
||||
class ImposterCaptureMaterialHook : public MatInstanceHook
|
||||
{
|
||||
public:
|
||||
|
||||
ImposterCaptureMaterialHook();
|
||||
|
||||
// MatInstanceHook
|
||||
virtual ~ImposterCaptureMaterialHook();
|
||||
virtual const MatInstanceHookType& getType() const { return Type; }
|
||||
|
||||
/// The material hook type.
|
||||
static const MatInstanceHookType Type;
|
||||
|
||||
void init( BaseMatInstance *mat );
|
||||
|
||||
static BaseMatInstance* getDiffuseInst( BaseMatInstance *inMat )
|
||||
{ return _getOrCreateHook( inMat )->mDiffuseMatInst; }
|
||||
|
||||
static BaseMatInstance* getNormalsInst( BaseMatInstance *inMat )
|
||||
{ return _getOrCreateHook( inMat )->mNormalsMatInst; }
|
||||
|
||||
protected:
|
||||
|
||||
static void _overrideFeatures( ProcessedMaterial *mat,
|
||||
U32 stageNum,
|
||||
MaterialFeatureData &fd,
|
||||
const FeatureSet &features );
|
||||
|
||||
static ImposterCaptureMaterialHook* _getOrCreateHook( BaseMatInstance *inMat );
|
||||
|
||||
///
|
||||
BaseMatInstance *mDiffuseMatInst;
|
||||
|
||||
///
|
||||
BaseMatInstance *mNormalsMatInst;
|
||||
};
|
||||
|
||||
|
||||
const MatInstanceHookType ImposterCaptureMaterialHook::Type( "ImposterCapture" );
|
||||
|
||||
|
||||
ImposterCaptureMaterialHook::ImposterCaptureMaterialHook()
|
||||
: mDiffuseMatInst( NULL ),
|
||||
mNormalsMatInst( NULL )
|
||||
{
|
||||
}
|
||||
|
||||
ImposterCaptureMaterialHook::~ImposterCaptureMaterialHook()
|
||||
{
|
||||
SAFE_DELETE( mDiffuseMatInst );
|
||||
SAFE_DELETE( mNormalsMatInst );
|
||||
}
|
||||
|
||||
void ImposterCaptureMaterialHook::init( BaseMatInstance *inMat )
|
||||
{
|
||||
// We cannot capture impostors on custom materials
|
||||
// as we don't know how to get just diffuse and just
|
||||
// normals rendering.
|
||||
if ( dynamic_cast<CustomMaterial*>( inMat->getMaterial() ) )
|
||||
return;
|
||||
|
||||
// Tweak the feature data to include just what we need.
|
||||
FeatureSet features;
|
||||
features.addFeature( MFT_VertTransform );
|
||||
features.addFeature( MFT_DiffuseMap );
|
||||
features.addFeature( MFT_OverlayMap );
|
||||
features.addFeature( MFT_DetailMap );
|
||||
features.addFeature( MFT_DiffuseColor );
|
||||
features.addFeature( MFT_AlphaTest );
|
||||
features.addFeature( MFT_IsTranslucent );
|
||||
|
||||
const String &matName = inMat->getMaterial()->getName();
|
||||
|
||||
mDiffuseMatInst = MATMGR->createMatInstance( matName );
|
||||
mDiffuseMatInst->getFeaturesDelegate().bind( &ImposterCaptureMaterialHook::_overrideFeatures );
|
||||
mDiffuseMatInst->init( features, inMat->getVertexFormat() );
|
||||
|
||||
features.addFeature( MFT_IsDXTnm );
|
||||
features.addFeature( MFT_NormalMap );
|
||||
features.addFeature( MFT_NormalsOut );
|
||||
mNormalsMatInst = MATMGR->createMatInstance( matName );
|
||||
mNormalsMatInst->getFeaturesDelegate().bind( &ImposterCaptureMaterialHook::_overrideFeatures );
|
||||
mNormalsMatInst->init( features, inMat->getVertexFormat() );
|
||||
}
|
||||
|
||||
void ImposterCaptureMaterialHook::_overrideFeatures( ProcessedMaterial *mat,
|
||||
U32 stageNum,
|
||||
MaterialFeatureData &fd,
|
||||
const FeatureSet &features )
|
||||
{
|
||||
if ( features.hasFeature( MFT_NormalsOut) )
|
||||
fd.features.addFeature( MFT_NormalsOut );
|
||||
|
||||
fd.features.addFeature( MFT_ForwardShading );
|
||||
}
|
||||
|
||||
ImposterCaptureMaterialHook* ImposterCaptureMaterialHook::_getOrCreateHook( BaseMatInstance *inMat )
|
||||
{
|
||||
ImposterCaptureMaterialHook *hook = inMat->getHook<ImposterCaptureMaterialHook>();
|
||||
if ( !hook )
|
||||
{
|
||||
// Create a hook and initialize it using the incoming material.
|
||||
hook = new ImposterCaptureMaterialHook;
|
||||
hook->init( inMat );
|
||||
inMat->addHook( hook );
|
||||
}
|
||||
|
||||
return hook;
|
||||
}
|
||||
|
||||
|
||||
ImposterCapture::ImposterCapture()
|
||||
: mShapeInstance( NULL ),
|
||||
mDl( 0 ),
|
||||
mDim( 0 ),
|
||||
mRadius( 0.0f ),
|
||||
mCenter( Point3F( 0, 0, 0 ) ),
|
||||
mRenderPass( NULL ),
|
||||
mMeshRenderBin( NULL ),
|
||||
mBlackBmp( NULL ),
|
||||
mWhiteBmp( NULL ),
|
||||
mState( NULL ),
|
||||
mRenderTarget( NULL )
|
||||
{
|
||||
}
|
||||
|
||||
ImposterCapture::~ImposterCapture()
|
||||
{
|
||||
AssertFatal( !mShapeInstance, "ImposterCapture destructor - TSShapeInstance hasn't been cleared!" );
|
||||
}
|
||||
|
||||
void ImposterCapture::_colorAverageFilter( U32 dimensions, const U8 *inBmpBits, U8 *outBmpBits )
|
||||
{
|
||||
ColorF color;
|
||||
U32 count = 0;
|
||||
U32 index, index2;
|
||||
|
||||
for ( S32 y = 0; y < dimensions; y++ )
|
||||
{
|
||||
for( S32 x = 0; x < dimensions; x++ )
|
||||
{
|
||||
// We only blend on transparent pixels.
|
||||
index = ( ( y * dimensions ) + x ) * 4;
|
||||
if ( inBmpBits[index+3] > 84 )
|
||||
{
|
||||
outBmpBits[index+0] = inBmpBits[index+0];
|
||||
outBmpBits[index+1] = inBmpBits[index+1];
|
||||
outBmpBits[index+2] = inBmpBits[index+2];
|
||||
outBmpBits[index+3] = inBmpBits[index+3]; //hack
|
||||
continue;
|
||||
}
|
||||
|
||||
color.set(0,0,0);
|
||||
count = 0;
|
||||
|
||||
for ( S32 fy = y-6; fy <= y+6; fy++ )
|
||||
{
|
||||
for ( S32 fx = x-6; fx <= x+6; fx++ )
|
||||
{
|
||||
if ( fy >= 0 && fy < (dimensions-1) &&
|
||||
fx >= 0 && fx < (dimensions-1) )
|
||||
{
|
||||
index2 = ( ( fy * dimensions ) + fx ) * 4;
|
||||
if ( inBmpBits[index2+3] > 84 )
|
||||
{
|
||||
color.red += inBmpBits[index2+0];
|
||||
color.green += inBmpBits[index2+1];
|
||||
color.blue += inBmpBits[index2+2];
|
||||
++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outBmpBits[index+0] = (U8)( (F32)color.red / (F32)count );
|
||||
outBmpBits[index+1] = (U8)( (F32)color.green / (F32)count );
|
||||
outBmpBits[index+2] = (U8)( (F32)color.blue / (F32)count );
|
||||
outBmpBits[index+3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImposterCapture::_renderToTexture( GFXTexHandle texHandle, GBitmap *outBitmap, const ColorI &color )
|
||||
{
|
||||
GFXDEBUGEVENT_SCOPE( ImposterCapture_RenderToTexture, ColorI::RED );
|
||||
PROFILE_SCOPE( ImposterCapture_RenderToTexture );
|
||||
|
||||
mRenderTarget->attachTexture( GFXTextureTarget::Color0, texHandle );
|
||||
mRenderTarget->attachTexture( GFXTextureTarget::DepthStencil, GFXTextureTarget::sDefaultDepthStencil );
|
||||
GFX->setActiveRenderTarget( mRenderTarget );
|
||||
|
||||
GFX->clear( GFXClearZBuffer | GFXClearStencil | GFXClearTarget, color, 1.0f, 0 );
|
||||
|
||||
mShapeInstance->render( mRData, mDl, 1.0f );
|
||||
|
||||
mState->getRenderPass()->renderPass( mState );
|
||||
|
||||
mRenderTarget->resolve();
|
||||
|
||||
texHandle->copyToBmp( outBitmap );
|
||||
}
|
||||
|
||||
void ImposterCapture::_separateAlpha( GBitmap *imposterOut )
|
||||
{
|
||||
PROFILE_START(TSShapeInstance_snapshot_sb_separate);
|
||||
|
||||
// TODO: Remove all this when we get rid of the 'render on black/white'.
|
||||
|
||||
// now separate the color and alpha channels
|
||||
GBitmap *bmp = new GBitmap;
|
||||
bmp->allocateBitmap(mDim, mDim, false, GFXFormatR8G8B8A8);
|
||||
U8 * wbmp = (U8*)mWhiteBmp->getBits(0);
|
||||
U8 * bbmp = (U8*)mBlackBmp->getBits(0);
|
||||
U8 * dst = (U8*)bmp->getBits(0);
|
||||
|
||||
const U32 pixCount = mDim * mDim;
|
||||
|
||||
// simpler, probably faster...
|
||||
for ( U32 i=0; i < pixCount; i++ )
|
||||
{
|
||||
// Shape on black background is alpha * color, shape on white
|
||||
// background is alpha * color + (1-alpha) * 255 we want 255 *
|
||||
// alpha, or 255 - (white - black).
|
||||
//
|
||||
// JMQ: or more verbosely:
|
||||
// cB = alpha * color + (0 * (1 - alpha))
|
||||
// cB = alpha * color
|
||||
// cW = alpha * color + (255 * (1 - alpha))
|
||||
// cW = cB + (255 * (1 - alpha))
|
||||
// solving for alpha
|
||||
// cW - cB = 255 * (1 - alpha)
|
||||
// (cW - cB)/255 = (1 - alpha)
|
||||
// alpha = 1 - (cW - cB)/255
|
||||
// since we want alpha*255, multiply through by 255
|
||||
// alpha * 255 = 255 - cW - cB
|
||||
U32 alpha = 255 - (wbmp[i*3+0] - bbmp[i*3+0]);
|
||||
alpha += 255 - (wbmp[i*3+1] - bbmp[i*3+1]);
|
||||
alpha += 255 - (wbmp[i*3+2] - bbmp[i*3+2]);
|
||||
|
||||
if ( alpha != 0 )
|
||||
{
|
||||
F32 floatAlpha = ((F32)alpha)/(3.0f*255.0f);
|
||||
dst[i*4+0] = (U8)(bbmp[i*3+0] / floatAlpha);
|
||||
dst[i*4+1] = (U8)(bbmp[i*3+1] / floatAlpha);
|
||||
dst[i*4+2] = (U8)(bbmp[i*3+2] / floatAlpha);
|
||||
|
||||
// Before we assign the alpha we "fizzle" the value
|
||||
// if its greater than 84. This causes the imposter
|
||||
// to dissolve instead of popping into view.
|
||||
alpha /= 3;
|
||||
dst[i*4+3] = (U8)alpha;
|
||||
}
|
||||
else
|
||||
{
|
||||
dst[i*4+0] = dst[i*4+1] = dst[i*4+2] = dst[i*4+3] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
PROFILE_END(); // TSShapeInstance_snapshot_sb_separate
|
||||
|
||||
PROFILE_START(TSShapeInstance_snapshot_sb_filter);
|
||||
|
||||
// We now run a special kernel filter over the image that
|
||||
// averages color into the transparent areas. This should
|
||||
// in essence give us a border around the edges of the
|
||||
// imposter silhouette which fixes the artifacts around the
|
||||
// alpha test billboards.
|
||||
U8* dst2 = (U8*)imposterOut->getBits(0);
|
||||
|
||||
_colorAverageFilter( mDim, dst, dst2 );
|
||||
|
||||
if ( 0 )
|
||||
{
|
||||
FileStream fs;
|
||||
if ( fs.open( "./imposterout.png", Torque::FS::File::Write ) )
|
||||
imposterOut->writeBitmap( "png", fs );
|
||||
|
||||
fs.close();
|
||||
|
||||
if ( fs.open( "./temp.png", Torque::FS::File::Write ) )
|
||||
bmp->writeBitmap( "png", fs );
|
||||
|
||||
fs.close();
|
||||
}
|
||||
|
||||
|
||||
PROFILE_END(); // TSShapeInstance_snapshot_sb_filter
|
||||
|
||||
delete bmp;
|
||||
}
|
||||
|
||||
|
||||
void ImposterCapture::_convertDXT5nm( GBitmap *normalsOut )
|
||||
{
|
||||
PROFILE_SCOPE(ImposterCapture_ConvertDXT5nm);
|
||||
|
||||
U8 *bits = (U8*)normalsOut->getBits(0);
|
||||
const U32 pixCount = mDim * mDim;
|
||||
U8 x, y, z;
|
||||
|
||||
// Encoding in object space DXT5 which moves
|
||||
// one of the coords to the alpha channel for
|
||||
// improved precision.... in our case z.
|
||||
|
||||
for ( U32 i=0; i < pixCount; i++ )
|
||||
{
|
||||
x = bits[i*4+0];
|
||||
y = bits[i*4+1];
|
||||
z = bits[i*4+2];
|
||||
|
||||
bits[i*4+0] = x;
|
||||
bits[i*4+1] = y;
|
||||
bits[i*4+2] = 0;
|
||||
bits[i*4+3] = z;
|
||||
}
|
||||
}
|
||||
|
||||
void ImposterCapture::begin( TSShapeInstance *shapeInst,
|
||||
S32 dl,
|
||||
S32 dim,
|
||||
F32 radius,
|
||||
const Point3F ¢er )
|
||||
{
|
||||
mShapeInstance = shapeInst;
|
||||
mDl = dl;
|
||||
mDim = dim;
|
||||
mRadius = radius;
|
||||
mCenter = center;
|
||||
|
||||
mBlackTex.set( mDim, mDim, GFXFormatR8G8B8A8, &GFXDefaultRenderTargetProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) );
|
||||
mWhiteTex.set( mDim, mDim, GFXFormatR8G8B8A8, &GFXDefaultRenderTargetProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) );
|
||||
mNormalTex.set( mDim, mDim, GFXFormatR8G8B8A8, &GFXDefaultRenderTargetProfile, avar( "%s() - (line %d)", __FUNCTION__, __LINE__ ) );
|
||||
|
||||
// copy the black render target data into a bitmap
|
||||
mBlackBmp = new GBitmap;
|
||||
mBlackBmp->allocateBitmap(mDim, mDim, false, GFXFormatR8G8B8);
|
||||
|
||||
// copy the white target data into a bitmap
|
||||
mWhiteBmp = new GBitmap;
|
||||
mWhiteBmp->allocateBitmap(mDim, mDim, false, GFXFormatR8G8B8);
|
||||
|
||||
// Setup viewport and frustrum to do orthographic projection.
|
||||
RectI viewport( 0, 0, mDim, mDim );
|
||||
GFX->setViewport( viewport );
|
||||
GFX->setOrtho( -mRadius, mRadius, -mRadius, mRadius, 1, 20.0f * mRadius );
|
||||
|
||||
// Position camera looking out the X axis.
|
||||
MatrixF cameraMatrix( true );
|
||||
cameraMatrix.setColumn( 0, Point3F( 0, 0, 1 ) );
|
||||
cameraMatrix.setColumn( 1, Point3F( 1, 0, 0 ) );
|
||||
cameraMatrix.setColumn( 2, Point3F( 0, 1, 0 ) );
|
||||
|
||||
// setup scene state required for TS mesh render...this is messy and inefficient;
|
||||
// should have a mode where most of this is done just once (and then
|
||||
// only the camera matrix changes between snapshots).
|
||||
// note that we use getFrustum here, but we set up an ortho projection above.
|
||||
// it doesn't seem like the scene state object pays attention to whether the projection is
|
||||
// ortho or not. this could become a problem if some code downstream tries to
|
||||
// reconstruct the projection matrix using the dimensions and doesn't
|
||||
// realize it should be ortho. at the moment no code is doing that.
|
||||
F32 left, right, top, bottom, nearPlane, farPlane;
|
||||
bool isOrtho;
|
||||
GFX->getFrustum( &left, &right, &bottom, &top, &nearPlane, &farPlane, &isOrtho );
|
||||
Frustum frust( isOrtho, left, right, top, bottom, nearPlane, farPlane, cameraMatrix );
|
||||
|
||||
// Set up render pass.
|
||||
|
||||
mRenderPass = new RenderPassManager();
|
||||
mRenderPass->assignName( "DiffuseRenderPass" );
|
||||
mMeshRenderBin = new RenderMeshMgr();
|
||||
mRenderPass->addManager( mMeshRenderBin );
|
||||
|
||||
// Set up scene state.
|
||||
|
||||
mState = new SceneRenderState(
|
||||
gClientSceneGraph,
|
||||
SPT_Diffuse,
|
||||
SceneCameraState( viewport, frust, GFX->getWorldMatrix(),GFX->getProjectionMatrix() ),
|
||||
mRenderPass,
|
||||
false
|
||||
);
|
||||
|
||||
// Set up our TS render state.
|
||||
mRData.setSceneState( mState );
|
||||
mRData.setCubemap( NULL );
|
||||
mRData.setFadeOverride( 1.0f );
|
||||
|
||||
// set gfx up for render to texture
|
||||
GFX->pushActiveRenderTarget();
|
||||
mRenderTarget = GFX->allocRenderToTextureTarget();
|
||||
|
||||
}
|
||||
|
||||
void ImposterCapture::capture( const MatrixF &rotMatrix,
|
||||
GBitmap **imposterOut,
|
||||
GBitmap **normalMapOut )
|
||||
{
|
||||
GFXTransformSaver saver;
|
||||
|
||||
// this version of the snapshot function renders the shape to a black texture, then to white, then reads bitmaps
|
||||
// back for both renders and combines them, restoring the alpha and color values. this is based on the
|
||||
// TGE implementation. it is not fast due to the copy and software combination operations. the generated bitmaps
|
||||
// are upside-down (which is how TGE generated them...)
|
||||
|
||||
(*imposterOut) = new GBitmap( mDim, mDim, false, GFXFormatR8G8B8A8 );
|
||||
(*normalMapOut) = new GBitmap( mDim, mDim, false, GFXFormatR8G8B8A8 );
|
||||
|
||||
// The object to world transform.
|
||||
MatrixF centerMat( true );
|
||||
centerMat.setPosition( -mCenter );
|
||||
MatrixF objMatrix( rotMatrix );
|
||||
objMatrix.mul( centerMat );
|
||||
GFX->setWorldMatrix( objMatrix );
|
||||
|
||||
// The view transform.
|
||||
MatrixF view( EulerF( M_PI_F / 2.0f, 0, M_PI_F ), Point3F( 0, 0, -10.0f * mRadius ) );
|
||||
mRenderPass->assignSharedXform( RenderPassManager::View, view );
|
||||
|
||||
mRenderPass->assignSharedXform( RenderPassManager::Projection, GFX->getProjectionMatrix() );
|
||||
|
||||
// Render the diffuse pass.
|
||||
mRenderPass->clear();
|
||||
mMeshRenderBin->getMatOverrideDelegate().bind( ImposterCaptureMaterialHook::getDiffuseInst );
|
||||
_renderToTexture( mBlackTex, mBlackBmp, ColorI(0, 0, 0, 0) );
|
||||
_renderToTexture( mWhiteTex, mWhiteBmp, ColorI(255, 255, 255, 255) );
|
||||
|
||||
// Now render the normals.
|
||||
mRenderPass->clear();
|
||||
mMeshRenderBin->getMatOverrideDelegate().bind( ImposterCaptureMaterialHook::getNormalsInst );
|
||||
_renderToTexture( mNormalTex, *normalMapOut, ColorI(0, 0, 0, 0) );
|
||||
|
||||
|
||||
_separateAlpha( *imposterOut );
|
||||
_convertDXT5nm( *normalMapOut );
|
||||
|
||||
if ( 0 )
|
||||
{
|
||||
// Render out the bitmaps for debug purposes.
|
||||
FileStream fs;
|
||||
if ( fs.open( "./blackbmp.png", Torque::FS::File::Write ) )
|
||||
mBlackBmp->writeBitmap( "png", fs );
|
||||
|
||||
fs.close();
|
||||
|
||||
if ( fs.open( "./whitebmp.png", Torque::FS::File::Write ) )
|
||||
mWhiteBmp->writeBitmap( "png", fs );
|
||||
|
||||
fs.close();
|
||||
|
||||
if ( fs.open( "./normalbmp.png", Torque::FS::File::Write ) )
|
||||
(*normalMapOut)->writeBitmap( "png", fs );
|
||||
|
||||
fs.close();
|
||||
|
||||
if ( fs.open( "./finalimposter.png", Torque::FS::File::Write ) )
|
||||
(*imposterOut)->writeBitmap( "png", fs );
|
||||
|
||||
fs.close();
|
||||
}
|
||||
}
|
||||
|
||||
void ImposterCapture::end()
|
||||
{
|
||||
GFX->popActiveRenderTarget();
|
||||
|
||||
mBlackTex.free();
|
||||
mWhiteTex.free();
|
||||
mNormalTex.free();
|
||||
|
||||
mShapeInstance = NULL;
|
||||
|
||||
mRenderTarget = NULL;
|
||||
mMeshRenderBin = NULL; // Deleted by mRenderPass
|
||||
SAFE_DELETE( mState );
|
||||
SAFE_DELETE( mRenderPass );
|
||||
SAFE_DELETE( mBlackBmp );
|
||||
SAFE_DELETE( mWhiteBmp );
|
||||
}
|
||||
|
||||
110
Engine/source/util/imposterCapture.h
Normal file
110
Engine/source/util/imposterCapture.h
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _IMPOSTERCAPTURE_H_
|
||||
#define _IMPOSTERCAPTURE_H_
|
||||
|
||||
#ifndef _MATHTYPES_H_
|
||||
#include "math/mathTypes.h"
|
||||
#endif
|
||||
#ifndef _MPOINT3_H_
|
||||
#include "math/mPoint3.h"
|
||||
#endif
|
||||
#ifndef _MMATRIX_H_
|
||||
#include "math/mMatrix.h"
|
||||
#endif
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/util/tVector.h"
|
||||
#endif
|
||||
#ifndef _TSRENDERDATA_H_
|
||||
#include "ts/tsRenderState.h"
|
||||
#endif
|
||||
#ifndef _GFXTEXTUREHANDLE_H_
|
||||
#include "gfx/gfxTextureHandle.h"
|
||||
#endif
|
||||
|
||||
class GBitmap;
|
||||
class SceneRenderState;
|
||||
class TSShapeInstance;
|
||||
class GFXTextureTarget;
|
||||
class RenderPassManager;
|
||||
class RenderMeshMgr;
|
||||
|
||||
|
||||
class ImposterCapture
|
||||
{
|
||||
|
||||
protected:
|
||||
|
||||
S32 mDl;
|
||||
S32 mDim;
|
||||
|
||||
/// The bounding radius of the shape used to size the billboard.
|
||||
F32 mRadius;
|
||||
|
||||
///
|
||||
Point3F mCenter;
|
||||
|
||||
GBitmap *mBlackBmp;
|
||||
GBitmap *mWhiteBmp;
|
||||
|
||||
GFXTexHandle mBlackTex;
|
||||
GFXTexHandle mWhiteTex;
|
||||
GFXTexHandle mNormalTex;
|
||||
|
||||
SceneRenderState *mState;
|
||||
TSShapeInstance *mShapeInstance;
|
||||
TSRenderState mRData;
|
||||
|
||||
GFXTextureTarget *mRenderTarget;
|
||||
|
||||
RenderPassManager *mRenderPass;
|
||||
RenderMeshMgr *mMeshRenderBin;
|
||||
|
||||
void _colorAverageFilter( U32 dimensions, const U8 *inBmpBits, U8 *outBmpBits );
|
||||
void _renderToTexture( GFXTexHandle texHandle, GBitmap *outBitmap, const ColorI &color );
|
||||
|
||||
void _separateAlpha( GBitmap *imposterOut );
|
||||
|
||||
void _convertDXT5nm( GBitmap *imposterOut );
|
||||
|
||||
public:
|
||||
|
||||
ImposterCapture();
|
||||
|
||||
~ImposterCapture();
|
||||
|
||||
void begin( TSShapeInstance *shapeInst,
|
||||
S32 dl,
|
||||
S32 dim,
|
||||
F32 radius,
|
||||
const Point3F ¢er );
|
||||
|
||||
void capture( const MatrixF &rotMatrix,
|
||||
GBitmap **imposterOut,
|
||||
GBitmap **normalMapOut );
|
||||
|
||||
void end();
|
||||
|
||||
};
|
||||
|
||||
#endif // _IMPOSTERCAPTURE_H_
|
||||
191
Engine/source/util/interpolatedChangeProperty.h
Normal file
191
Engine/source/util/interpolatedChangeProperty.h
Normal 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 _INTERPOLATEDCHANGEPROPERTY_H_
|
||||
#define _INTERPOLATEDCHANGEPROPERTY_H_
|
||||
|
||||
#ifndef _SIM_H_
|
||||
#include "console/sim.h"
|
||||
#endif
|
||||
|
||||
#ifndef _MEASE_H_
|
||||
#include "math/mEase.h"
|
||||
#endif
|
||||
|
||||
#ifndef _TIMESOURCE_H_
|
||||
#include "core/util/timeSource.h"
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/// A property that smoothly transitions to new values instead of assuming
|
||||
/// them right away.
|
||||
///
|
||||
/// @param T Value type. Must have "interpolate( from, to, factor )" method.
|
||||
/// @param TimeSource Time source to which interpolation is synchronized.
|
||||
template< typename T, class TimeSource = GenericTimeSource< SimMSTimer > >
|
||||
class InterpolatedChangeProperty
|
||||
{
|
||||
public:
|
||||
|
||||
enum
|
||||
{
|
||||
/// Default time (in milliseconds) to go from one value
|
||||
/// to a new one.
|
||||
DEFAULT_TRANSITION_TIME = 2000
|
||||
};
|
||||
|
||||
typedef TimeSource TimeSourceType;
|
||||
typedef typename TimeSource::TickType TimeType;
|
||||
|
||||
protected:
|
||||
|
||||
/// The current value.
|
||||
mutable T mCurrentValue;
|
||||
|
||||
/// @name Transitioning
|
||||
///
|
||||
/// Transitioning allows to smoothly go from one value to
|
||||
/// a different one over a period of time.
|
||||
///
|
||||
/// @{
|
||||
|
||||
///
|
||||
TimeSourceType mTimeSource;
|
||||
|
||||
/// Number of milliseconds it takes to go from one value
|
||||
/// to a different one.
|
||||
TimeType mBlendPhaseTime;
|
||||
|
||||
/// Interpolation to use for going from source to target.
|
||||
EaseF mTransitionCurve;
|
||||
|
||||
/// The time the transition started. If 0, no transition is in progress.
|
||||
mutable TimeType mTransitionStartTime;
|
||||
|
||||
/// The value we are transitioning from.
|
||||
T mSourceValue;
|
||||
|
||||
/// The value we are transitioning to.
|
||||
T mTargetValue;
|
||||
|
||||
/// @}
|
||||
|
||||
/// Update #mCurrentValue.
|
||||
void _update() const;
|
||||
|
||||
public:
|
||||
|
||||
///
|
||||
InterpolatedChangeProperty( const T& initialValue = T() )
|
||||
: mCurrentValue( initialValue ),
|
||||
mTargetValue( initialValue ),
|
||||
mBlendPhaseTime( DEFAULT_TRANSITION_TIME ),
|
||||
mTransitionStartTime( 0 )
|
||||
{
|
||||
// By default, start time source right away.
|
||||
mTimeSource.start();
|
||||
}
|
||||
|
||||
/// Get the current value. If a transition is in progress, this will be
|
||||
/// an interpolation of the last value and the new one.
|
||||
const T& getCurrentValue() const
|
||||
{
|
||||
_update();
|
||||
return mCurrentValue;
|
||||
}
|
||||
|
||||
/// Set the interpolation to use for going from one ambient color to
|
||||
/// a different one.
|
||||
void setTransitionCurve( const EaseF& ease ) { mTransitionCurve = ease; }
|
||||
|
||||
/// Set the amount of time it takes to go from one ambient color to
|
||||
/// a different one.
|
||||
void setTransitionTime( TimeType time ) { mBlendPhaseTime = time; }
|
||||
|
||||
/// Set the desired value. If this differs from the current value,
|
||||
/// a smooth blend to the given color will be initiated.
|
||||
///
|
||||
/// @param value Desired value.
|
||||
void setTargetValue( const T& value );
|
||||
|
||||
/// Return the time source to which interpolation synchronizes.
|
||||
const TimeSourceType& geTimeSource() const { return mTimeSource; }
|
||||
TimeSourceType& getTimeSource() { return mTimeSource; }
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< typename T, typename TimeSource >
|
||||
void InterpolatedChangeProperty< T, TimeSource >::setTargetValue( const T& value )
|
||||
{
|
||||
if( mTargetValue == value )
|
||||
return;
|
||||
|
||||
if( mBlendPhaseTime == 0 )
|
||||
{
|
||||
mTargetValue = value;
|
||||
mCurrentValue = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the source value to the current value (which may be interpolated)
|
||||
// and then start a transition to the given target.
|
||||
|
||||
mSourceValue = getCurrentValue();
|
||||
mTargetValue = value;
|
||||
mTransitionStartTime = mTimeSource.getPosition();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< typename T, typename TimeSource >
|
||||
void InterpolatedChangeProperty< T, TimeSource >::_update() const
|
||||
{
|
||||
// Nothing to do if no transition in progress.
|
||||
|
||||
if( !mTransitionStartTime )
|
||||
return;
|
||||
|
||||
// See if we have finished the transition.
|
||||
|
||||
TimeType deltaTime = mTimeSource.getPosition() - mTransitionStartTime;
|
||||
if( deltaTime >= mBlendPhaseTime )
|
||||
{
|
||||
// We're done.
|
||||
mCurrentValue = mTargetValue;
|
||||
mTransitionStartTime = 0;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the interpolated value.
|
||||
|
||||
F32 blendFactor = F32( deltaTime ) / F32( mBlendPhaseTime );
|
||||
blendFactor = mTransitionCurve.getUnitValue( blendFactor );
|
||||
|
||||
mCurrentValue.interpolate( mSourceValue, mTargetValue, blendFactor );
|
||||
}
|
||||
|
||||
#endif // !_INTERPOLATEDCHANGEPROPERTY_H_
|
||||
421
Engine/source/util/messaging/dispatcher.cpp
Normal file
421
Engine/source/util/messaging/dispatcher.cpp
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "util/messaging/dispatcher.h"
|
||||
|
||||
#include "platform/threads/mutex.h"
|
||||
#include "core/tSimpleHashTable.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
|
||||
namespace Dispatcher
|
||||
{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// IMessageListener Methods
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
IMessageListener::~IMessageListener()
|
||||
{
|
||||
for(S32 i = 0;i < mQueues.size();i++)
|
||||
{
|
||||
unregisterMessageListener(mQueues[i], this);
|
||||
}
|
||||
}
|
||||
|
||||
void IMessageListener::onAddToQueue(StringTableEntry queue)
|
||||
{
|
||||
// [tom, 8/20/2006] The dispatcher won't let us get added twice, so no need
|
||||
// to worry about it here.
|
||||
|
||||
mQueues.push_back(queue);
|
||||
}
|
||||
|
||||
void IMessageListener::onRemoveFromQueue(StringTableEntry queue)
|
||||
{
|
||||
for(S32 i = 0;i < mQueues.size();i++)
|
||||
{
|
||||
if(mQueues[i] == queue)
|
||||
{
|
||||
mQueues.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Global State
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Internal class used by the dispatcher
|
||||
//-----------------------------------------------------------------------------
|
||||
typedef struct _DispatchData
|
||||
{
|
||||
void *mMutex;
|
||||
SimpleHashTable<MessageQueue> mQueues;
|
||||
U32 mLastAnonQueueID;
|
||||
|
||||
_DispatchData()
|
||||
{
|
||||
mMutex = Mutex::createMutex();
|
||||
mLastAnonQueueID = 0;
|
||||
}
|
||||
|
||||
~_DispatchData()
|
||||
{
|
||||
if(Mutex::lockMutex( mMutex ) )
|
||||
{
|
||||
mQueues.clearTables();
|
||||
|
||||
Mutex::unlockMutex( mMutex );
|
||||
}
|
||||
|
||||
Mutex::destroyMutex( mMutex );
|
||||
//SAFE_DELETE(mMutex);
|
||||
mMutex = NULL;
|
||||
}
|
||||
|
||||
const char *makeAnonQueueName()
|
||||
{
|
||||
char buf[512];
|
||||
dSprintf(buf, sizeof(buf), "AnonQueue.%lu", mLastAnonQueueID++);
|
||||
return StringTable->insert(buf);
|
||||
}
|
||||
} _DispatchData;
|
||||
|
||||
static _DispatchData& _dispatcherGetGDispatchData()
|
||||
{
|
||||
static _DispatchData dispatchData;
|
||||
return dispatchData;
|
||||
}
|
||||
|
||||
#define gDispatchData _dispatcherGetGDispatchData()
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Queue Registration
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool isQueueRegistered(const char *name)
|
||||
{
|
||||
MutexHandle mh;
|
||||
if(mh.lock(gDispatchData.mMutex, true))
|
||||
{
|
||||
return gDispatchData.mQueues.retreive(name) != NULL;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void registerMessageQueue(const char *name)
|
||||
{
|
||||
if(isQueueRegistered(name))
|
||||
return;
|
||||
|
||||
if(Mutex::lockMutex( gDispatchData.mMutex, true ))
|
||||
{
|
||||
MessageQueue *queue = new MessageQueue;
|
||||
queue->mQueueName = StringTable->insert(name);
|
||||
gDispatchData.mQueues.insert(queue, name);
|
||||
|
||||
Mutex::unlockMutex( gDispatchData.mMutex );
|
||||
}
|
||||
}
|
||||
|
||||
extern const char * registerAnonMessageQueue()
|
||||
{
|
||||
const char *name = NULL;
|
||||
if(Mutex::lockMutex( gDispatchData.mMutex, true ))
|
||||
{
|
||||
name = gDispatchData.makeAnonQueueName();
|
||||
Mutex::unlockMutex( gDispatchData.mMutex );
|
||||
}
|
||||
|
||||
if(name)
|
||||
registerMessageQueue(name);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
void unregisterMessageQueue(const char *name)
|
||||
{
|
||||
MutexHandle mh;
|
||||
if(mh.lock(gDispatchData.mMutex, true))
|
||||
{
|
||||
MessageQueue *queue = gDispatchData.mQueues.remove(name);
|
||||
if(queue == NULL)
|
||||
return;
|
||||
|
||||
// Tell the listeners about it
|
||||
for(S32 i = 0;i < queue->mListeners.size();i++)
|
||||
{
|
||||
queue->mListeners[i]->onRemoveFromQueue(name);
|
||||
}
|
||||
|
||||
delete queue;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Message Listener Registration
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool registerMessageListener(const char *queue, IMessageListener *listener)
|
||||
{
|
||||
if(! isQueueRegistered(queue))
|
||||
registerMessageQueue(queue);
|
||||
|
||||
MutexHandle mh;
|
||||
|
||||
if(! mh.lock(gDispatchData.mMutex, true))
|
||||
return false;
|
||||
|
||||
MessageQueue *q = gDispatchData.mQueues.retreive(queue);
|
||||
if(q == NULL)
|
||||
{
|
||||
Con::errorf("Dispatcher::registerMessageListener - Queue '%s' not found?! It should have been added automatically!", queue);
|
||||
return false;
|
||||
}
|
||||
|
||||
for(VectorPtr<IMessageListener *>::iterator i = q->mListeners.begin();i != q->mListeners.end();i++)
|
||||
{
|
||||
if(*i == listener)
|
||||
return false;
|
||||
}
|
||||
|
||||
q->mListeners.push_front(listener);
|
||||
listener->onAddToQueue(StringTable->insert(queue));
|
||||
return true;
|
||||
}
|
||||
|
||||
void unregisterMessageListener(const char *queue, IMessageListener *listener)
|
||||
{
|
||||
if(! isQueueRegistered(queue))
|
||||
return;
|
||||
|
||||
MutexHandle mh;
|
||||
|
||||
if(! mh.lock(gDispatchData.mMutex, true))
|
||||
return;
|
||||
|
||||
MessageQueue *q = gDispatchData.mQueues.retreive(queue);
|
||||
if(q == NULL)
|
||||
return;
|
||||
|
||||
for(VectorPtr<IMessageListener *>::iterator i = q->mListeners.begin();i != q->mListeners.end();i++)
|
||||
{
|
||||
if(*i == listener)
|
||||
{
|
||||
listener->onRemoveFromQueue(StringTable->insert(queue));
|
||||
q->mListeners.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Dispatcher
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool dispatchMessage( const char* queue, const char* msg, const char* data)
|
||||
{
|
||||
AssertFatal( queue != NULL, "Dispatcher::dispatchMessage - Got a NULL queue name" );
|
||||
AssertFatal( msg != NULL, "Dispatcher::dispatchMessage - Got a NULL message" );
|
||||
|
||||
MutexHandle mh;
|
||||
|
||||
if(! mh.lock(gDispatchData.mMutex, true))
|
||||
return true;
|
||||
|
||||
MessageQueue *q = gDispatchData.mQueues.retreive(queue);
|
||||
if(q == NULL)
|
||||
{
|
||||
Con::errorf("Dispatcher::dispatchMessage - Attempting to dispatch to unknown queue '%s'", queue);
|
||||
return true;
|
||||
}
|
||||
|
||||
return q->dispatchMessage(msg, data);
|
||||
}
|
||||
|
||||
|
||||
bool dispatchMessageObject(const char *queue, Message *msg)
|
||||
{
|
||||
MutexHandle mh;
|
||||
|
||||
if(msg == NULL)
|
||||
return true;
|
||||
|
||||
msg->addReference();
|
||||
|
||||
if(! mh.lock(gDispatchData.mMutex, true))
|
||||
{
|
||||
msg->freeReference();
|
||||
return true;
|
||||
}
|
||||
|
||||
MessageQueue *q = gDispatchData.mQueues.retreive(queue);
|
||||
if(q == NULL)
|
||||
{
|
||||
Con::errorf("Dispatcher::dispatchMessage - Attempting to dispatch to unknown queue '%s'", queue);
|
||||
msg->freeReference();
|
||||
return true;
|
||||
}
|
||||
|
||||
// [tom, 8/19/2006] Make sure that the message is registered with the sim, since
|
||||
// when it's ref count is zero it'll be deleted with deleteObject()
|
||||
if(! msg->isProperlyAdded())
|
||||
{
|
||||
SimObjectId id = Message::getNextMessageID();
|
||||
if(id != 0xffffffff)
|
||||
msg->registerObject(id);
|
||||
else
|
||||
{
|
||||
Con::errorf("dispatchMessageObject: Message was not registered and no more object IDs are available for messages");
|
||||
|
||||
msg->freeReference();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool bResult = q->dispatchMessageObject(msg);
|
||||
msg->freeReference();
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Internal Functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
MessageQueue * getMessageQueue(const char *name)
|
||||
{
|
||||
return gDispatchData.mQueues.retreive(name);
|
||||
}
|
||||
|
||||
extern bool lockDispatcherMutex()
|
||||
{
|
||||
return Mutex::lockMutex(gDispatchData.mMutex);
|
||||
}
|
||||
|
||||
extern void unlockDispatcherMutex()
|
||||
{
|
||||
Mutex::unlockMutex(gDispatchData.mMutex);
|
||||
}
|
||||
|
||||
} // end namespace Dispatcher
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Console Methods
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
using namespace Dispatcher;
|
||||
|
||||
ConsoleFunction(isQueueRegistered, bool, 2, 2, "(string queueName)"
|
||||
"@brief Determines if a dispatcher queue exists\n\n"
|
||||
"@param queueName String containing the name of queue\n"
|
||||
"@ingroup Messaging")
|
||||
{
|
||||
return isQueueRegistered(argv[1]);
|
||||
}
|
||||
|
||||
ConsoleFunction(registerMessageQueue, void, 2, 2, "(string queueName)"
|
||||
"@brief Registeres a dispatcher queue\n\n"
|
||||
"@param queueName String containing the name of queue\n"
|
||||
"@ingroup Messaging")
|
||||
{
|
||||
return registerMessageQueue(argv[1]);
|
||||
}
|
||||
|
||||
ConsoleFunction(unregisterMessageQueue, void, 2, 2, "(string queueName)"
|
||||
"@brief Unregisters a dispatcher queue\n\n"
|
||||
"@param queueName String containing the name of queue\n"
|
||||
"@ingroup Messaging")
|
||||
{
|
||||
return unregisterMessageQueue(argv[1]);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleFunction(registerMessageListener, bool, 3, 3, "(string queueName, string listener)"
|
||||
"@brief Registers an event message\n\n"
|
||||
"@param queueName String containing the name of queue to attach listener to\n"
|
||||
"@param listener Name of event messenger\n"
|
||||
"@ingroup Messaging")
|
||||
{
|
||||
IMessageListener *listener = dynamic_cast<IMessageListener *>(Sim::findObject(argv[2]));
|
||||
if(listener == NULL)
|
||||
{
|
||||
Con::errorf("registerMessageListener - Unable to find listener object, not an IMessageListener ?!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return registerMessageListener(argv[1], listener);
|
||||
}
|
||||
|
||||
ConsoleFunction(unregisterMessageListener, void, 3, 3, "(string queueName, string listener)"
|
||||
"@brief Unregisters an event message\n\n"
|
||||
"@param queueName String containing the name of queue\n"
|
||||
"@param listener Name of event messenger\n"
|
||||
"@ingroup Messaging")
|
||||
{
|
||||
IMessageListener *listener = dynamic_cast<IMessageListener *>(Sim::findObject(argv[2]));
|
||||
if(listener == NULL)
|
||||
{
|
||||
Con::errorf("unregisterMessageListener - Unable to find listener object, not an IMessageListener ?!");
|
||||
return;
|
||||
}
|
||||
|
||||
unregisterMessageListener(argv[1], listener);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleFunction(dispatchMessage, bool, 3, 4, "(string queueName, string message, string data)"
|
||||
"@brief Dispatch a message to a queue\n\n"
|
||||
"@param queueName Queue to dispatch the message to\n"
|
||||
"@param message Message to dispatch\n"
|
||||
"@param data Data for message\n"
|
||||
"@return True for success, false for failure\n"
|
||||
"@see dispatchMessageObject\n"
|
||||
"@ingroup Messaging")
|
||||
{
|
||||
return dispatchMessage(argv[1], argv[2], argc > 3 ? argv[3] : "" );
|
||||
}
|
||||
|
||||
ConsoleFunction(dispatchMessageObject, bool, 3, 3, "(string queueName, string message)"
|
||||
"@brief Dispatch a message object to a queue\n\n"
|
||||
"@param queueName Queue to dispatch the message to\n"
|
||||
"@param message Message to dispatch\n"
|
||||
"@return true for success, false for failure\n"
|
||||
"@see dispatchMessage\n"
|
||||
"@ingroup Messaging")
|
||||
{
|
||||
Message *msg = dynamic_cast<Message *>(Sim::findObject(argv[2]));
|
||||
if(msg == NULL)
|
||||
{
|
||||
Con::errorf("dispatchMessageObject - Unable to find message object");
|
||||
return false;
|
||||
}
|
||||
|
||||
return dispatchMessageObject(argv[1], msg);
|
||||
}
|
||||
276
Engine/source/util/messaging/dispatcher.h
Normal file
276
Engine/source/util/messaging/dispatcher.h
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _DISPATCHER_H_
|
||||
#define _DISPATCHER_H_
|
||||
|
||||
#ifndef _MESSAGE_H_
|
||||
#include "util/messaging/message.h"
|
||||
#endif
|
||||
|
||||
#ifndef _CONSOLE_H_
|
||||
#include "console/console.h"
|
||||
#endif
|
||||
|
||||
/// @addtogroup msgsys Message System
|
||||
// @{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Namespace for the message dispatcher functions
|
||||
//-----------------------------------------------------------------------------
|
||||
namespace Dispatcher
|
||||
{
|
||||
|
||||
// [tom, 2/19/2007] This semi colon prevents VS from auto indenting the comments
|
||||
// below, which is really annoying when you're trying to write docs.
|
||||
;
|
||||
|
||||
/// @addtogroup msgsys Message System
|
||||
// @{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Interface for objects that receive messages
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Listener interface for objects that receive messages
|
||||
///
|
||||
/// @see ScriptMsgListener
|
||||
//-----------------------------------------------------------------------------
|
||||
class IMessageListener
|
||||
{
|
||||
protected:
|
||||
/// List of queues this listener is registered with.
|
||||
Vector<StringTableEntry> mQueues;
|
||||
|
||||
public:
|
||||
virtual ~IMessageListener();
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Callback for when messages are received
|
||||
///
|
||||
/// @param queue The name of the queue the message was dispatched to
|
||||
/// @param msg The type of message
|
||||
/// @param data The data for the message
|
||||
/// @return false to prevent other listeners receiving this message, true otherwise
|
||||
/// @see onMessageObjectReceived()
|
||||
//-----------------------------------------------------------------------------
|
||||
virtual bool onMessageReceived(StringTableEntry queue, const char *msg, const char *data) = 0;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Callback for when message objects are received
|
||||
///
|
||||
/// @param queue The name of the queue the message was dispatched to
|
||||
/// @param msg The message object
|
||||
/// @return false to prevent other listeners receiving this message, true otherwise
|
||||
/// @see onMessageReceived()
|
||||
//-----------------------------------------------------------------------------
|
||||
virtual bool onMessageObjectReceived(StringTableEntry queue, Message *msg ) = 0;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Callback for when the listener is added to a queue
|
||||
///
|
||||
/// The default implementation of onAddToQueue() and onRemoveFromQueue()
|
||||
/// provide tracking of the queues this listener is added to through the
|
||||
/// #mQueues member. Overrides of onAddToQueue() or onRemoveFromQueue()
|
||||
/// should ensure they call the parent implementation in any overrides.
|
||||
///
|
||||
/// @param queue The name of the queue that the listener added to
|
||||
/// @see onRemoveFromQueue()
|
||||
//-----------------------------------------------------------------------------
|
||||
virtual void onAddToQueue(StringTableEntry queue);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Callback for when the listener is removed from a queue
|
||||
///
|
||||
/// The default implementation of onAddToQueue() and onRemoveFromQueue()
|
||||
/// provide tracking of the queues this listener is added to through the
|
||||
/// #mQueues member. Overrides of onAddToQueue() or onRemoveFromQueue()
|
||||
/// should ensure they call the parent implementation in any overrides.
|
||||
///
|
||||
/// @param queue The name of the queue the listener was removed from
|
||||
/// @see onAddToQueue()
|
||||
//-----------------------------------------------------------------------------
|
||||
virtual void onRemoveFromQueue(StringTableEntry queue);
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Internal class for tracking message queues
|
||||
//-----------------------------------------------------------------------------
|
||||
struct MessageQueue
|
||||
{
|
||||
StringTableEntry mQueueName;
|
||||
VectorPtr<IMessageListener *> mListeners;
|
||||
|
||||
MessageQueue() : mQueueName("")
|
||||
{
|
||||
}
|
||||
|
||||
bool isEmpty() { return mListeners.size() == 0; }
|
||||
|
||||
bool dispatchMessage(const char* event, const char* data)
|
||||
{
|
||||
for(VectorPtr<IMessageListener *>::iterator i = mListeners.begin();i != mListeners.end();i++)
|
||||
{
|
||||
if( !(*i)->onMessageReceived(mQueueName, event, data) )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dispatchMessageObject(Message *msg)
|
||||
{
|
||||
for(VectorPtr<IMessageListener *>::iterator i = mListeners.begin();i != mListeners.end();i++)
|
||||
{
|
||||
if( !(*i)->onMessageObjectReceived(mQueueName, msg) )
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Message Dispatcher Functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/// @name Message Queue Management
|
||||
// @{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Check if a message queue is registered
|
||||
///
|
||||
/// @param name The name of the message queue
|
||||
/// @return true if the queue is registered, false otherwise
|
||||
/// @see registerMessageQueue(), unregisterMessageQueue()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern bool isQueueRegistered(const char *name);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Register a message queue
|
||||
///
|
||||
/// @param name The name of the message queue to register
|
||||
/// @see isQueueRegistered(), unregisterMessageQueue()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern void registerMessageQueue(const char *name);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Register an anonymous message queue
|
||||
///
|
||||
/// @return name of anonymous message queue for passing to other functions
|
||||
/// @see isQueueRegistered(), unregisterMessageQueue()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern const char *registerAnonMessageQueue();
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Unregister a message queue
|
||||
///
|
||||
/// @param name The name of the message queue
|
||||
/// @see registerMessageQueue(), isQueueRegistered()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern void unregisterMessageQueue(const char *name);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Register a listener with a queue to receive messages
|
||||
///
|
||||
/// @param queue The name of the queue to register the listener with
|
||||
/// @param listener The listener interface that receives messages
|
||||
/// @return true for success, false otherwise
|
||||
/// @see unregisterMessageListener()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern bool registerMessageListener(const char *queue, IMessageListener *listener);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Unregister a listener with a queue
|
||||
///
|
||||
/// @param queue The name of the queue to unregister the listener
|
||||
/// @param listener The listener interface that was passed to registerMessageListener()
|
||||
/// @see registerMessageListener()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern void unregisterMessageListener(const char *queue, IMessageListener *listener);
|
||||
|
||||
// @}
|
||||
|
||||
/// @name Message Dispatcher
|
||||
// @{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Dispatch a message to a queue
|
||||
///
|
||||
/// @param queue Queue to dispatch the message to
|
||||
/// @param msg Message to dispatch
|
||||
/// @param data Data for message
|
||||
/// @return true for success, false for failure
|
||||
/// @see dispatchMessageObject()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern bool dispatchMessage(const char *queue, const char *msg, const char *data);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Dispatch a message object to a queue
|
||||
///
|
||||
/// @param queue Queue to dispatch the message to
|
||||
/// @param msg Message to dispatch
|
||||
/// @return true for success, false for failure
|
||||
/// @see dispatchMessage()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern bool dispatchMessageObject(const char *queue, Message *msg);
|
||||
|
||||
// @}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Internal Functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/// @name Internal Functions
|
||||
// @{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Internal function: Lock the dispatcher mutex.
|
||||
/// @return true for success, false for failure
|
||||
/// @see unlockDispatcherMutex()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern bool lockDispatcherMutex();
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Internal function: Unlock the dispatcher mutex.
|
||||
/// @see lockDispatcherMutex()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern void unlockDispatcherMutex();
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Internal function: obtain message queue. Dispatcher mutex must be locked.
|
||||
///
|
||||
/// @param name Name of the queue
|
||||
/// @return Message queue
|
||||
/// @see lockDispatcherMutex(), unlockDispatcherMutex()
|
||||
//-----------------------------------------------------------------------------
|
||||
extern MessageQueue *getMessageQueue(const char *name);
|
||||
|
||||
// @}
|
||||
|
||||
// @}
|
||||
|
||||
} // end namespace Dispatcher
|
||||
|
||||
// @}
|
||||
|
||||
#endif // _DISPATCHER_H_
|
||||
512
Engine/source/util/messaging/eventManager.cpp
Normal file
512
Engine/source/util/messaging/eventManager.cpp
Normal file
|
|
@ -0,0 +1,512 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "util/messaging/eventManager.h"
|
||||
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleInternal.h"
|
||||
|
||||
IMPLEMENT_CONOBJECT( EventManager );
|
||||
|
||||
ConsoleDocClass( EventManager,
|
||||
"@brief The EventManager class is a wrapper for the standard messaging system.\n\n"
|
||||
|
||||
"It provides functionality for management of event queues, events, and "
|
||||
"subscriptions. Creating an EventManager is as simple as calling new EventManager "
|
||||
"and specifying a queue name.\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create the EventManager.\n"
|
||||
"$MyEventManager = new EventManager() { queue = \"MyEventManager\"; };\n\n"
|
||||
"// Create an event.\n"
|
||||
"$MyEventManager.registerEvent( \"SomeCoolEvent\" );\n\n"
|
||||
"// Create a listener and subscribe.\n"
|
||||
"$MyListener = new ScriptMsgListener() { class = MyListener; };\n"
|
||||
"$MyEventManager.subscribe( $MyListener, \"SomeCoolEvent\" );\n\n"
|
||||
"function MyListener::onSomeCoolEvent( %this, %data )\n"
|
||||
"{\n"
|
||||
" echo( \"onSomeCoolEvent Triggered\" );\n"
|
||||
"}\n\n"
|
||||
"// Trigger the event.\n"
|
||||
"$MyEventManager.postEvent( \"SomeCoolEvent\", \"Data\" );\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see ScriptMsgListener\n\n"
|
||||
|
||||
"@ingroup Messaging\n"
|
||||
);
|
||||
|
||||
Vector<EventManager*> EventManager::smEventManagers;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Gets a list of all listeners of a specific event type and executes a
|
||||
/// callback on each one.
|
||||
///
|
||||
/// @param event The name of the event that was triggered.
|
||||
/// @param data The data associated with the event.
|
||||
/// @return true to allow other listeners to receive the event, false otherwise
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// CodeReview [tom, 2/20/2007] There seemed to be a little confusion on the return value here.
|
||||
// It is not a "successfully dispatched" value, it is used to prevent other
|
||||
// listeners from receiving the message. Using the event manager this probably
|
||||
// didn't matter since there was only one listener, however it would cause
|
||||
// problems if more then one listener is registered with the queue.
|
||||
bool EventManagerListener::onMessageReceived( StringTableEntry queue, const char* event, const char* data )
|
||||
{
|
||||
Vector<Subscriber>* subscribers = mSubscribers.retreive( event );
|
||||
if( subscribers == NULL )
|
||||
return true;
|
||||
|
||||
for( Vector<Subscriber>::iterator iter = subscribers->begin(); iter != subscribers->end(); iter++ )
|
||||
{
|
||||
if( iter->listener == NULL )
|
||||
{
|
||||
subscribers->erase_fast( iter -- );
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!iter->removeFlag)
|
||||
{
|
||||
iter->callDepth++;
|
||||
Con::executef( iter->listener, iter->callback, data );
|
||||
iter->callDepth--;
|
||||
if (iter->removeFlag && iter->callDepth==0)
|
||||
{
|
||||
subscribers->erase_fast(iter--);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
EventManager::EventManager() : mQueue( NULL )
|
||||
{
|
||||
addEventManager( this );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
EventManager::~EventManager()
|
||||
{
|
||||
setMessageQueue( "" );
|
||||
unregisterAllEvents();
|
||||
removeEventManager( this );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// initPersistFields
|
||||
//-----------------------------------------------------------------------------
|
||||
void EventManager::initPersistFields()
|
||||
{
|
||||
addProtectedField( "queue", TypeString, Offset( mQueue, EventManager ), &_setMessageQueue, &defaultProtectedGetFn, "List of events currently waiting" );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Registers the message queue and listener with the messaging system.
|
||||
///
|
||||
/// @param queue The name of the queue. Set to "" to destroy the queue.
|
||||
//-----------------------------------------------------------------------------
|
||||
void EventManager::setMessageQueue( const char* queue )
|
||||
{
|
||||
// If a queue is already registered, unregister it.
|
||||
if( mQueue && Dispatcher::isQueueRegistered( mQueue ) )
|
||||
{
|
||||
unregisterAllEvents();
|
||||
Dispatcher::unregisterMessageListener( mQueue, &mListener );
|
||||
Dispatcher::unregisterMessageQueue( mQueue );
|
||||
}
|
||||
|
||||
// Register the new queue.
|
||||
if( queue && *queue )
|
||||
{
|
||||
Dispatcher::registerMessageQueue( queue );
|
||||
Dispatcher::registerMessageListener( queue, &mListener );
|
||||
mQueue = StringTable->insert( queue );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Determines whether or not an event is registered with the EventManager.
|
||||
///
|
||||
/// @param event the event to check.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool EventManager::isRegisteredEvent( const char* event )
|
||||
{
|
||||
// Iterate over the event list.
|
||||
StringTableEntry eventName = StringTable->insert( event );
|
||||
for( Vector<StringTableEntry>::const_iterator iter = mEvents.begin(); iter != mEvents.end(); iter++ )
|
||||
{
|
||||
// Found.
|
||||
if( *iter == eventName )
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not found.
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Register an event with the EventManager.
|
||||
///
|
||||
/// @param event The event to register.
|
||||
/// @return Whether or not the event was successfully registered.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool EventManager::registerEvent( const char* event )
|
||||
{
|
||||
// Make sure the event has not been registered yet.
|
||||
if( isRegisteredEvent( event ) )
|
||||
{
|
||||
Con::warnf( "EventManager::registerEvent - event %s already registered", event );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add to the event list.
|
||||
mEvents.push_back( StringTable->insert( event ) );
|
||||
|
||||
// Create a list of subscribers for this event.
|
||||
mListener.mSubscribers.insert( new Vector<EventManagerListener::Subscriber>, event );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Removes all events from the EventManager.
|
||||
//-----------------------------------------------------------------------------
|
||||
void EventManager::unregisterAllEvents()
|
||||
{
|
||||
// Iterate over all events.
|
||||
for( Vector<StringTableEntry>::const_iterator iter = mEvents.begin(); iter != mEvents.end(); iter++ )
|
||||
{
|
||||
// Delete the subscriber list.
|
||||
Vector<EventManagerListener::Subscriber>* subscribers = mListener.mSubscribers.remove( *iter );
|
||||
if( subscribers )
|
||||
delete subscribers;
|
||||
}
|
||||
|
||||
// Clear the event list.
|
||||
mEvents.clear();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Removes an event from the EventManager.
|
||||
///
|
||||
/// @param event The event to remove.
|
||||
//-----------------------------------------------------------------------------
|
||||
void EventManager::unregisterEvent( const char* event )
|
||||
{
|
||||
// If the event doesn't exist, we have succeeded in removing it!
|
||||
if( !isRegisteredEvent( event ) )
|
||||
return;
|
||||
|
||||
// Iterate over all events.
|
||||
StringTableEntry eventName = StringTable->insert( event );
|
||||
for( Vector<StringTableEntry>::iterator iter = mEvents.begin(); iter != mEvents.end(); iter++ )
|
||||
{
|
||||
// Erase the event.
|
||||
if( *iter == eventName )
|
||||
{
|
||||
mEvents.erase_fast( iter );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the subscriber list.
|
||||
Vector<EventManagerListener::Subscriber>* subscribers = mListener.mSubscribers.remove( event );
|
||||
if( subscribers )
|
||||
delete subscribers;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Post an event to the EventManager's queue.
|
||||
///
|
||||
/// @param event The event to post.
|
||||
/// @param data Various data associated with the event.
|
||||
/// @return Whether or not the message was dispatched successfully.
|
||||
//-----------------------------------------------------------------------------
|
||||
bool EventManager::postEvent( const char* event, const char* data )
|
||||
{
|
||||
AssertFatal( mQueue != NULL, "EventManager::postEvent - Queue not initialized" );
|
||||
return Dispatcher::dispatchMessage( mQueue, event, data );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Subscribe a listener to an event.
|
||||
///
|
||||
/// @param listener The listener to subscribe.
|
||||
/// @param event The event to subscribe to.
|
||||
/// @param callback Optional callback name to be called when the event is
|
||||
/// triggered.
|
||||
/// @return Whether or not the subscription was successful.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// CodeReview [tom, 2/20/2007] The "listener" argument was an IMessageListener,
|
||||
// but it was actually used as a SimObject and never a listener. Thus, it is now a SimObject.
|
||||
bool EventManager::subscribe(SimObject *callbackObj, const char* event, const char* callback /*= NULL */)
|
||||
{
|
||||
// Make sure the event is valid.
|
||||
if( !isRegisteredEvent( event ) )
|
||||
{
|
||||
Con::warnf( "EventManager::subscribe - %s is not a registered event.", event );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Grab the callback name.
|
||||
char* cb = NULL;
|
||||
if( !callback || !*callback )
|
||||
{
|
||||
// Not specified, use default ( "onEvent" ).
|
||||
S32 length = dStrlen( event ) + 5;
|
||||
cb = new char[length];
|
||||
dSprintf( cb, length, "on%s", event );
|
||||
}
|
||||
else
|
||||
{
|
||||
cb = new char[dStrlen(callback) + 1];
|
||||
dStrcpy(cb, callback);
|
||||
}
|
||||
|
||||
// Create the subscriber object.
|
||||
EventManagerListener::Subscriber subscriber;
|
||||
subscriber.listener = callbackObj;
|
||||
subscriber.event = StringTable->insert( event );
|
||||
subscriber.callback = StringTable->insert( cb );
|
||||
subscriber.callDepth = 0;
|
||||
subscriber.removeFlag = false;
|
||||
|
||||
delete [] cb;
|
||||
|
||||
// Grab the subscriber list.
|
||||
Vector<EventManagerListener::Subscriber>* subscribers = mListener.mSubscribers.retreive( event );
|
||||
|
||||
// If the event exists, there should always be a valid subscriber list.
|
||||
AssertFatal( subscribers, "Invalid event subscriber list." );
|
||||
|
||||
// Add the subscriber.
|
||||
subscribers->push_back( subscriber );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// remove a listener from an event.
|
||||
///
|
||||
/// @param listener The listener to remove from an event callback list.
|
||||
/// @param event The event to remove the listener from.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// CodeReview [tom, 2/20/2007] The "listener" argument was an IMessageListener,
|
||||
// but it was actually used as a SimObject and never a listener. Thus, it is now a SimObject.
|
||||
void EventManager::remove(SimObject *cbObj, const char* event)
|
||||
{
|
||||
// If the event doesn't exist, we have succeeded in removing it!
|
||||
if( !isRegisteredEvent( event ) )
|
||||
return;
|
||||
|
||||
Vector<EventManagerListener::Subscriber>* subscribers = mListener.mSubscribers.retreive( event );
|
||||
if( !subscribers )
|
||||
return;
|
||||
|
||||
for( Vector<EventManagerListener::Subscriber>::iterator iter = subscribers->begin(); iter != subscribers->end(); iter++ )
|
||||
{
|
||||
// Erase the event.
|
||||
if( iter->listener == cbObj )
|
||||
{
|
||||
if (iter->callDepth > 0)
|
||||
iter->removeFlag = true;
|
||||
else
|
||||
subscribers->erase_fast( iter );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EventManager::removeAll(SimObject *cbObj)
|
||||
{
|
||||
// Iterate over all events.
|
||||
for( Vector<StringTableEntry>::const_iterator iter1 = mEvents.begin(); iter1 != mEvents.end(); iter1++ )
|
||||
{
|
||||
Vector<EventManagerListener::Subscriber>* subscribers = mListener.mSubscribers.retreive( *iter1 );
|
||||
if( !subscribers )
|
||||
continue;
|
||||
for( Vector<EventManagerListener::Subscriber>::iterator iter2 = subscribers->begin(); iter2 != subscribers->end(); iter2++ )
|
||||
{
|
||||
// Erase the event.
|
||||
if( iter2->listener == cbObj )
|
||||
{
|
||||
if (iter2->callDepth > 0)
|
||||
iter2->removeFlag = true;
|
||||
else
|
||||
subscribers->erase_fast( iter2 );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Print all registered events to the console.
|
||||
//-----------------------------------------------------------------------------
|
||||
void EventManager::dumpEvents()
|
||||
{
|
||||
Con::printf( "%s Events", mQueue );
|
||||
for( Vector<StringTableEntry>::const_iterator iter = mEvents.begin(); iter != mEvents.end(); iter++ )
|
||||
Con::printf( " %s", *iter );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Print the subscribers to an event.
|
||||
///
|
||||
/// @param event The event whose subscribers are to be printed.
|
||||
//-----------------------------------------------------------------------------
|
||||
void EventManager::dumpSubscribers( const char* event )
|
||||
{
|
||||
Vector<EventManagerListener::Subscriber>* subscribers = mListener.mSubscribers.retreive( event );
|
||||
if( !subscribers )
|
||||
{
|
||||
Con::warnf( "EventManager::dumpSubscriber - %s is not a valid event.", event );
|
||||
return;
|
||||
}
|
||||
|
||||
Con::printf( "%s Subscribers", event );
|
||||
for( Vector<EventManagerListener::Subscriber>::const_iterator iter = subscribers->begin(); iter != subscribers->end(); iter++ )
|
||||
if( iter->listener )
|
||||
{
|
||||
// Grab the best fit name. This should be the first found of name, class, superclass, or class type.
|
||||
Namespace* ns = iter->listener->getNamespace();
|
||||
const char* name = ns ? ns->mName : getClassName() ;
|
||||
Con::printf( " %s -> %s", name, iter->callback );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Print all registered events and their subscribers to the console.
|
||||
//-----------------------------------------------------------------------------
|
||||
void EventManager::dumpSubscribers()
|
||||
{
|
||||
Con::printf( "%s Events", mQueue );
|
||||
for( Vector<StringTableEntry>::const_iterator iter = mEvents.begin(); iter != mEvents.end(); iter++ )
|
||||
dumpSubscribers( *iter );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Console Methods
|
||||
//-----------------------------------------------------------------------------
|
||||
ConsoleMethod( EventManager, registerEvent, bool, 3, 3, "( String event )\n"
|
||||
"Register an event with the event manager.\n"
|
||||
"@param event The event to register.\n"
|
||||
"@return Whether or not the event was registered successfully." )
|
||||
{
|
||||
return object->registerEvent( argv[2] );
|
||||
}
|
||||
|
||||
ConsoleMethod( EventManager, unregisterEvent, void, 3, 3, "( String event )\n"
|
||||
"Remove an event from the EventManager.\n"
|
||||
"@param event The event to remove.\n" )
|
||||
{
|
||||
object->unregisterEvent( argv[2] );
|
||||
}
|
||||
|
||||
ConsoleMethod( EventManager, isRegisteredEvent, bool, 3, 3, "( String event )\n"
|
||||
"Check if an event is registered or not.\n"
|
||||
"@param event The event to check.\n"
|
||||
"@return Whether or not the event exists." )
|
||||
{
|
||||
return object->isRegisteredEvent( argv[2] );
|
||||
}
|
||||
|
||||
ConsoleMethod( EventManager, postEvent, bool, 3, 4, "( String event, String data )\n"
|
||||
"~Trigger an event.\n"
|
||||
"@param event The event to trigger.\n"
|
||||
"@param data The data associated with the event.\n"
|
||||
"@return Whether or not the event was dispatched successfully." )
|
||||
{
|
||||
if( !object->getMessageQueue() || !object->getMessageQueue()[ 0 ] )
|
||||
{
|
||||
Con::errorf( "EventManager::postEvent - No queue name set on EventManager" );
|
||||
return false;
|
||||
}
|
||||
|
||||
return object->postEvent( argv[2], argc > 3 ? argv[3] : "" );
|
||||
}
|
||||
|
||||
ConsoleMethod( EventManager, subscribe, bool, 4, 5, "( SimObject listener, String event, String callback )\n\n"
|
||||
"Subscribe a listener to an event.\n"
|
||||
"@param listener The listener to subscribe.\n"
|
||||
"@param event The event to subscribe to.\n"
|
||||
"@param callback Optional method name to receive the event notification. If this is not specified, \"on[event]\" will be used.\n"
|
||||
"@return Whether or not the subscription was successful." )
|
||||
{
|
||||
// Find the listener object.
|
||||
SimObject *cbObj = dynamic_cast<SimObject *>(Sim::findObject(argv[2]));
|
||||
if( cbObj == NULL )
|
||||
{
|
||||
Con::warnf( "EventManager::subscribe - Invalid listener." );
|
||||
return false;
|
||||
}
|
||||
|
||||
return object->subscribe( cbObj, argv[3], argc > 4 ? argv[4] : NULL );
|
||||
}
|
||||
|
||||
ConsoleMethod( EventManager, remove, void, 4, 4, "( SimObject listener, String event )\n\n"
|
||||
"Remove a listener from an event.\n"
|
||||
"@param listener The listener to remove.\n"
|
||||
"@param event The event to be removed from.\n")
|
||||
{
|
||||
// Find the listener object.
|
||||
SimObject * listener = dynamic_cast< SimObject * >( Sim::findObject( argv[2] ) );
|
||||
if( listener )
|
||||
object->remove( listener, argv[3] );
|
||||
}
|
||||
|
||||
ConsoleMethod( EventManager, removeAll, void, 3, 3, "( SimObject listener )\n\n"
|
||||
"Remove a listener from all events.\n"
|
||||
"@param listener The listener to remove.\n")
|
||||
{
|
||||
// Find the listener object.
|
||||
SimObject * listener = dynamic_cast< SimObject * >( Sim::findObject( argv[2] ) );
|
||||
if( listener )
|
||||
object->removeAll( listener );
|
||||
}
|
||||
|
||||
ConsoleMethod( EventManager, dumpEvents, void, 2, 2, "()\n\n"
|
||||
"Print all registered events to the console." )
|
||||
{
|
||||
object->dumpEvents();
|
||||
}
|
||||
|
||||
ConsoleMethod( EventManager, dumpSubscribers, void, 2, 3, "( String event )\n\n"
|
||||
"Print all subscribers to an event to the console.\n"
|
||||
"@param event The event whose subscribers are to be printed. If this parameter isn't specified, all events will be dumped." )
|
||||
{
|
||||
if( argc > 2 )
|
||||
object->dumpSubscribers( argv[2] );
|
||||
else
|
||||
object->dumpSubscribers();
|
||||
}
|
||||
217
Engine/source/util/messaging/eventManager.h
Normal file
217
Engine/source/util/messaging/eventManager.h
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _EVENTMANAGER_H_
|
||||
#define _EVENTMANAGER_H_
|
||||
|
||||
#ifndef _CONSOLE_H_
|
||||
#include "console/console.h"
|
||||
#endif
|
||||
|
||||
#ifndef _DISPATCHER_H_
|
||||
#include "util/messaging/dispatcher.h"
|
||||
#endif
|
||||
|
||||
#ifndef _TSIMPLEHASHTABLE_H
|
||||
#include "core/tSimpleHashTable.h"
|
||||
#endif
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Listener class used by the EventManager to dispatch messages to specific
|
||||
/// callbacks.
|
||||
//-----------------------------------------------------------------------------
|
||||
class EventManagerListener : public Dispatcher::IMessageListener
|
||||
{
|
||||
friend class EventManager;
|
||||
|
||||
/// Stores subscription information for a subscriber.
|
||||
struct Subscriber
|
||||
{
|
||||
SimObjectPtr< SimObject > listener; ///< The listener object.
|
||||
StringTableEntry callback; ///< The callback to execute when the event is triggered.
|
||||
StringTableEntry event; ///< The event being listened for.
|
||||
U32 callDepth;
|
||||
bool removeFlag;
|
||||
};
|
||||
|
||||
/// Subscriber table hashed by event name.
|
||||
SimpleHashTable< Vector<Subscriber> > mSubscribers;
|
||||
|
||||
public:
|
||||
/// Called by the EventManager queue when an event is triggered. Calls all listeners subscribed to the triggered event.
|
||||
virtual bool onMessageReceived( StringTableEntry queue, const char* event, const char* data );
|
||||
virtual bool onMessageObjectReceived( StringTableEntry queue, Message *msg ) { return true; };
|
||||
};
|
||||
|
||||
/// @addtogroup msgsys Message System
|
||||
// @{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// The EventManager class is a wrapper for the standard messaging system. It
|
||||
/// provides functionality for management of event queues, events, and
|
||||
/// subscriptions.
|
||||
///
|
||||
/// Creating an EventManager is as simple as calling <tt>new EventManager</tt>
|
||||
/// and specifying a queue name.
|
||||
///
|
||||
/// Example Usage:
|
||||
///
|
||||
/// @code
|
||||
/// // Create the EventManager.
|
||||
/// $MyEventManager = new EventManager() { queue = "MyEventManager"; };
|
||||
///
|
||||
/// // Create an event.
|
||||
/// $MyEventManager.registerEvent( "SomeCoolEvent" );
|
||||
///
|
||||
/// // Create a listener and subscribe.
|
||||
/// $MyListener = new ScriptMsgListener() { class = MyListener; };
|
||||
/// $MyEventManager.subscribe( $MyListener, "SomeCoolEvent" );
|
||||
///
|
||||
/// function MyListener::onSomeCoolEvent( %this, %data )
|
||||
/// {
|
||||
/// echo( "onSomeCoolEvent Triggered" );
|
||||
/// }
|
||||
///
|
||||
/// // Trigger the event.
|
||||
/// $MyEventManager.postEvent( "SomeCoolEvent", "Data" );
|
||||
/// @endcode
|
||||
//-----------------------------------------------------------------------------
|
||||
class EventManager : public SimObject
|
||||
{
|
||||
typedef SimObject Parent;
|
||||
|
||||
private:
|
||||
/// The name of the message queue.
|
||||
StringTableEntry mQueue;
|
||||
/// Registered events.
|
||||
Vector<StringTableEntry> mEvents;
|
||||
|
||||
/// The event listener. Listens for all events and dispatches them to the appropriate subscribers.
|
||||
EventManagerListener mListener;
|
||||
|
||||
/// List of all EventManagers.
|
||||
static Vector<EventManager*> smEventManagers;
|
||||
|
||||
/// Sets the message queue.
|
||||
static bool _setMessageQueue( void *obj, const char *index, const char *data )
|
||||
{
|
||||
static_cast<EventManager*>( obj )->setMessageQueue( data );
|
||||
return false;
|
||||
};
|
||||
|
||||
public:
|
||||
DECLARE_CONOBJECT( EventManager );
|
||||
|
||||
EventManager();
|
||||
virtual ~EventManager();
|
||||
|
||||
static void initPersistFields();
|
||||
|
||||
/// @name Properties
|
||||
/// @{
|
||||
|
||||
StringTableEntry getMessageQueue() const { return mQueue; }
|
||||
|
||||
void setMessageQueue( const char* queue );
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Event Management
|
||||
/// @{
|
||||
|
||||
/// Checks if an event is registered.
|
||||
bool isRegisteredEvent( const char* eventName );
|
||||
/// Registers an event.
|
||||
bool registerEvent( const char* eventName );
|
||||
/// Removes an event.
|
||||
void unregisterEvent( const char* eventName );
|
||||
/// Removes all events.
|
||||
void unregisterAllEvents();
|
||||
|
||||
/// Triggers an event.
|
||||
bool postEvent( const char* eventName, const char* data );
|
||||
/// Adds a subscription to an event.
|
||||
bool subscribe( SimObject *callbackObj, const char* event, const char* callback = NULL );
|
||||
/// Remove a subscriber from an event.
|
||||
void remove( SimObject *cbObj, const char* event );
|
||||
void removeAll( SimObject *cbObj );
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Debug Output
|
||||
/// @{
|
||||
|
||||
/// Prints all registered events to the console.
|
||||
void dumpEvents();
|
||||
/// Prints all subscribers to the console.
|
||||
void dumpSubscribers();
|
||||
/// Prints subscribers to a specific event to the console.
|
||||
void dumpSubscribers( const char* event );
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Event Manager Tracking
|
||||
/// @{
|
||||
|
||||
/// Adds an EventManager.
|
||||
static void addEventManager( EventManager* em ) { smEventManagers.push_back( em ); };
|
||||
|
||||
/// Removes an EventManager.
|
||||
static void removeEventManager( EventManager* em )
|
||||
{
|
||||
for( Vector<EventManager*>::iterator iter = smEventManagers.begin(); iter != smEventManagers.end(); iter++ )
|
||||
{
|
||||
if( *iter == em )
|
||||
{
|
||||
smEventManagers.erase_fast( iter );
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Retrieves an EventManager.
|
||||
static EventManager* getEventManager( const char* name )
|
||||
{
|
||||
StringTableEntry queue = StringTable->insert( name );
|
||||
for( Vector<EventManager*>::iterator iter = smEventManagers.begin(); iter != smEventManagers.end(); iter++ )
|
||||
{
|
||||
if( ( *iter )->mQueue == queue )
|
||||
return *iter;
|
||||
}
|
||||
return NULL;
|
||||
};
|
||||
|
||||
/// Prints all the EventManagers to the console.
|
||||
static void printEventManagers()
|
||||
{
|
||||
for( Vector<EventManager*>::iterator iter = smEventManagers.begin(); iter != smEventManagers.end(); iter++ )
|
||||
( *iter )->dumpSubscribers();
|
||||
}
|
||||
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
// @}
|
||||
|
||||
#endif // _EVENTMANAGER_H_
|
||||
173
Engine/source/util/messaging/message.cpp
Normal file
173
Engine/source/util/messaging/message.cpp
Normal 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "util/messaging/message.h"
|
||||
|
||||
#include "console/consoleTypes.h"
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
namespace Sim
|
||||
{
|
||||
extern SimIdDictionary *gIdDictionary;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Constructor/Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
Message::Message()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
IMPLEMENT_CONOBJECT(Message);
|
||||
|
||||
ConsoleDocClass( Message,
|
||||
"@brief Base class for messages\n\n"
|
||||
|
||||
"Message is the base class for C++ defined messages, and may also be used "
|
||||
"in script for script defined messages if no C++ subclass is appropriate.\n\n"
|
||||
|
||||
"Messages are reference counted and will be automatically deleted when "
|
||||
"their reference count reaches zero. When you dispatch a message, a "
|
||||
"reference will be added before the dispatch and freed after the dispatch. "
|
||||
"This allows for temporary messages with no additional code. If you want "
|
||||
"to keep the message around, for example to dispatch it to multiple "
|
||||
"queues, call addReference() before dispatching it and freeReference() "
|
||||
"when you are done with it. Never delete a Message object directly "
|
||||
"unless addReference() has not been called or the message has not been "
|
||||
"dispatched.\n\n"
|
||||
|
||||
"Message IDs are pooled similarly to datablocks, with the exception that "
|
||||
"IDs are reused. If you keep a message for longer than a single dispatch, "
|
||||
"then you should ensure that you clear any script variables that refer "
|
||||
"to it after the last freeReference(). If you don't, then it is probable "
|
||||
"that the object ID will become valid again in the future and could cause "
|
||||
"hard to track down bugs.\n\n"
|
||||
|
||||
"Messages have a unique type to simplify message handling code. For object "
|
||||
"messages, the type is defined as either the script defined class name "
|
||||
"or the C++ class name if no script class was defined. The message type "
|
||||
"may be obtained through the getType() method.\n\n"
|
||||
|
||||
"By convention, any data for the message is held in script accessible "
|
||||
"fields. Messages that need to be handled in C++ as well as script "
|
||||
"provide the relevant data through persistent fields in a subclass of "
|
||||
"Message to provide best performance on the C++ side. Script defined "
|
||||
"messages usually their through dynamic fields, and may be accessed in "
|
||||
"C++ using the SimObject::getDataField() method.\n\n"
|
||||
|
||||
"@ingroup Messaging\n"
|
||||
);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
IMPLEMENT_CALLBACK(Message, onAdd, void, (),(),
|
||||
"Script callback when a message is first created and registered.\n\n"
|
||||
"@tsexample\n"
|
||||
"function Message::onAdd(%this)\n"
|
||||
"{\n"
|
||||
" // Perform on add code here\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n"
|
||||
);
|
||||
|
||||
bool Message::onAdd()
|
||||
{
|
||||
if(! Parent::onAdd())
|
||||
return false;
|
||||
|
||||
linkNamespaces();
|
||||
onAdd_callback();
|
||||
//Con::executef(this, "onAdd");
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_CALLBACK(Message, onRemove, void, (),(),
|
||||
"Script callback when a message is deleted.\n\n"
|
||||
"@tsexample\n"
|
||||
"function Message::onRemove(%this)\n"
|
||||
"{\n"
|
||||
" // Perform on remove code here\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n"
|
||||
);
|
||||
|
||||
void Message::onRemove()
|
||||
{
|
||||
onRemove_callback();
|
||||
//Con::executef(this, "onRemove");
|
||||
unlinkNamespaces();
|
||||
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
SimObjectId Message::getNextMessageID()
|
||||
{
|
||||
for(S32 i = MessageObjectIdFirst;i < MessageObjectIdLast;i++)
|
||||
{
|
||||
if(Sim::gIdDictionary->find(i) == NULL)
|
||||
return i;
|
||||
}
|
||||
|
||||
// Oh no ...
|
||||
return 0xffffffff;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const char *Message::getType()
|
||||
{
|
||||
if(mClassName && mClassName[0] != 0)
|
||||
return mClassName;
|
||||
|
||||
return getClassName();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Console Methods
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleMethod(Message, getType, const char *, 2, 2, "() Get message type (script class name or C++ class name if no script defined class)")
|
||||
{
|
||||
return object->getType();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleMethod(Message, addReference, void, 2, 2, "() Increment the reference count for this message")
|
||||
{
|
||||
object->addReference();
|
||||
}
|
||||
|
||||
ConsoleMethod(Message, freeReference, void, 2, 2, "() Decrement the reference count for this message")
|
||||
{
|
||||
object->freeReference();
|
||||
}
|
||||
145
Engine/source/util/messaging/message.h
Normal file
145
Engine/source/util/messaging/message.h
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _MESSAGE_H_
|
||||
#define _MESSAGE_H_
|
||||
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
|
||||
#ifndef _NETCONNECTION_H_
|
||||
#include "sim/netConnection.h"
|
||||
#endif
|
||||
|
||||
// Forward Refs
|
||||
class MessageQueue;
|
||||
|
||||
/// @addtogroup msgsys Message System
|
||||
///
|
||||
/// Most of the message system docs are currently just stubs and will
|
||||
/// be fleshed out soon.
|
||||
///
|
||||
// @{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Message Base Class
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Base class for messages
|
||||
///
|
||||
/// Message is the base class for C++ defined messages, and may also be used
|
||||
/// in script for script defined messages if no C++ subclass is appropriate.
|
||||
///
|
||||
/// Messages are reference counted and will be automatically deleted when
|
||||
/// their reference count reaches zero. When you dispatch a message, a
|
||||
/// reference will be added before the dispatch and freed after the dispatch.
|
||||
/// This allows for temporary messages with no additional code. If you want
|
||||
/// to keep the message around, for example to dispatch it to multiple
|
||||
/// queues, call addReference() before dispatching it and freeReference()
|
||||
/// when you are done with it. Never delete a Message object directly
|
||||
/// unless addReference() has not been called or the message has not been
|
||||
/// dispatched.
|
||||
///
|
||||
/// Message IDs are pooled similarly to datablocks, with the exception that
|
||||
/// IDs are reused. If you keep a message for longer than a single dispatch,
|
||||
/// then you should ensure that you clear any script variables that refer
|
||||
/// to it after the last freeReference(). If you don't, then it is probable
|
||||
/// that the object ID will become valid again in the future and could cause
|
||||
/// hard to track down bugs.
|
||||
///
|
||||
/// Messages have a unique type to simplify message handling code. For object
|
||||
/// messages, the type is defined as either the script defined class name
|
||||
/// or the C++ class name if no script class was defined. The message type
|
||||
/// may be obtained through the getType() method.
|
||||
///
|
||||
/// By convention, any data for the message is held in script accessible
|
||||
/// fields. Messages that need to be handled in C++ as well as script
|
||||
/// provide the relevant data through persistent fields in a subclass of
|
||||
/// Message to provide best performance on the C++ side. Script defined
|
||||
/// messages usually their through dynamic fields, and may be accessed in
|
||||
/// C++ using the SimObject::getDataField() method.
|
||||
//-----------------------------------------------------------------------------
|
||||
class Message : public SimObject
|
||||
{
|
||||
typedef SimObject Parent;
|
||||
|
||||
public:
|
||||
Message();
|
||||
DECLARE_CONOBJECT(Message);
|
||||
DECLARE_CALLBACK( void, onAdd, () );
|
||||
DECLARE_CALLBACK( void, onRemove, () );
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Obtain next available #SimObjectId for messages
|
||||
///
|
||||
/// This is used in combination with the newmsg script operator to provide
|
||||
/// ID pooling for messages and works similarly to datablock IDs.
|
||||
///
|
||||
/// By default, the 64 IDs following the datablock IDs are used for messages.
|
||||
/// As message objects generally have a short life time this prevents them
|
||||
/// from eating object IDs as if they haven't eaten for a year.
|
||||
///
|
||||
/// Note that unlike SimObjects and datablocks, Messages IDs are re-used.
|
||||
/// If you store a message object in script and do not clear the variable
|
||||
/// containing the object ID after freeing the message, it is probable that
|
||||
/// the object ID will become valid again.
|
||||
///
|
||||
/// @return Next available SimObjectId
|
||||
//-----------------------------------------------------------------------------
|
||||
static SimObjectId getNextMessageID();
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Get the type of the message
|
||||
///
|
||||
/// The message type is either the script class name or the C++ class name
|
||||
/// if it has not been overridden in script. This allows easy identification
|
||||
/// of message types with minimum effort.
|
||||
///
|
||||
/// @return Type of message
|
||||
//-----------------------------------------------------------------------------
|
||||
const char *getType();
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Add a reference to the reference count of this message
|
||||
///
|
||||
/// Use freeReference() to free the reference when you are done with it.
|
||||
///
|
||||
/// @see freeReference()
|
||||
//-----------------------------------------------------------------------------
|
||||
void addReference() { incRefCount(); }
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Free a reference to this message
|
||||
///
|
||||
/// @see addReference()
|
||||
//-----------------------------------------------------------------------------
|
||||
void freeReference() { decRefCount(); }
|
||||
};
|
||||
|
||||
// @}
|
||||
|
||||
#endif // _MESSAGE_H_
|
||||
88
Engine/source/util/messaging/messageForwarder.cpp
Normal file
88
Engine/source/util/messaging/messageForwarder.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "util/messaging/messageForwarder.h"
|
||||
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Constructor/Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
MessageForwarder::MessageForwarder()
|
||||
{
|
||||
mToQueue = "";
|
||||
}
|
||||
|
||||
MessageForwarder::~MessageForwarder()
|
||||
{
|
||||
}
|
||||
|
||||
IMPLEMENT_CONOBJECT(MessageForwarder);
|
||||
|
||||
ConsoleDocClass( MessageForwarder,
|
||||
"@brief Forward messages from one queue to another\n\n"
|
||||
|
||||
"MessageForwarder is a script class that can be used to forward messages "
|
||||
"from one queue to another.\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"%fwd = new MessageForwarder()\n"
|
||||
"{\n"
|
||||
" toQueue = \"QueueToSendTo\";\n"
|
||||
"};\n\n"
|
||||
"registerMessageListener(\"FromQueue\", %fwd);\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"Where \"QueueToSendTo\" is the queue you want to forward to, and "
|
||||
"\"FromQueue\" is the queue you want to forward from.\n\n"
|
||||
|
||||
"@ingroup Messaging\n"
|
||||
);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void MessageForwarder::initPersistFields()
|
||||
{
|
||||
addField("toQueue", TypeCaseString, Offset(mToQueue, MessageForwarder), "Name of queue to forward to");
|
||||
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
bool MessageForwarder::onMessageReceived(StringTableEntry queue, const char *event, const char *data)
|
||||
{
|
||||
if(*mToQueue)
|
||||
Dispatcher::dispatchMessage(queue, event, data);
|
||||
return Parent::onMessageReceived(queue, event, data);
|
||||
}
|
||||
|
||||
bool MessageForwarder::onMessageObjectReceived(StringTableEntry queue, Message *msg)
|
||||
{
|
||||
if(*mToQueue)
|
||||
Dispatcher::dispatchMessageObject(mToQueue, msg);
|
||||
return Parent::onMessageObjectReceived(queue, msg);
|
||||
}
|
||||
74
Engine/source/util/messaging/messageForwarder.h
Normal file
74
Engine/source/util/messaging/messageForwarder.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "console/simBase.h"
|
||||
#include "util/messaging/dispatcher.h"
|
||||
#include "util/messaging/scriptMsgListener.h"
|
||||
|
||||
#ifndef _MESSAGEFORWARDER_H_
|
||||
#define _MESSAGEFORWARDER_H_
|
||||
|
||||
/// @addtogroup msgsys Message System
|
||||
// @{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Forward messages from one queue to another
|
||||
///
|
||||
/// MessageForwarder is a script class that can be used to forward messages
|
||||
/// from one queue to another.
|
||||
///
|
||||
/// <h2>Example</h2>
|
||||
///
|
||||
/// @code
|
||||
/// %fwd = new MessageForwarder()
|
||||
/// {
|
||||
/// toQueue = "QueueToSendTo";
|
||||
/// };
|
||||
///
|
||||
/// registerMessageListener("FromQueue", %fwd);
|
||||
/// @endcode
|
||||
///
|
||||
/// Where "QueueToSendTo" is the queue you want to forward to, and
|
||||
/// "FromQueue" is the queue you want to forward from.
|
||||
///
|
||||
//-----------------------------------------------------------------------------
|
||||
class MessageForwarder : public ScriptMsgListener
|
||||
{
|
||||
typedef ScriptMsgListener Parent;
|
||||
|
||||
protected:
|
||||
StringTableEntry mToQueue;
|
||||
|
||||
public:
|
||||
MessageForwarder();
|
||||
virtual ~MessageForwarder();
|
||||
DECLARE_CONOBJECT(MessageForwarder);
|
||||
|
||||
static void initPersistFields();
|
||||
|
||||
virtual bool onMessageReceived(StringTableEntry queue, const char *event, const char *data);
|
||||
virtual bool onMessageObjectReceived(StringTableEntry queue, Message *msg);
|
||||
};
|
||||
|
||||
// @}
|
||||
|
||||
#endif // _MESSAGEFORWARDER_H_
|
||||
184
Engine/source/util/messaging/scriptMsgListener.cpp
Normal file
184
Engine/source/util/messaging/scriptMsgListener.cpp
Normal 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 "util/messaging/scriptMsgListener.h"
|
||||
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ScriptMsgListener::ScriptMsgListener()
|
||||
{
|
||||
}
|
||||
|
||||
IMPLEMENT_CONOBJECT(ScriptMsgListener);
|
||||
|
||||
ConsoleDoc("@class ScriptMsgListener\n"
|
||||
"@brief Script accessible version of Dispatcher::IMessageListener. Often used in conjunction with EventManager\n\n"
|
||||
|
||||
"The main use of ScriptMsgListener is to allow script to listen for"
|
||||
"messages. You can subclass ScriptMsgListener in script to receive"
|
||||
"the Dispatcher::IMessageListener callbacks.\n\n"
|
||||
|
||||
"Alternatively, you can derive from it in C++ instead of SimObject to"
|
||||
"get an object that implements Dispatcher::IMessageListener with script"
|
||||
"callbacks. If you need to derive from something other then SimObject,"
|
||||
"then you will need to implement the Dispatcher::IMessageListener"
|
||||
"interface yourself.\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Create the EventManager.\n"
|
||||
"$MyEventManager = new EventManager() { queue = \"MyEventManager\"; };\n\n"
|
||||
"// Create an event.\n"
|
||||
"$MyEventManager.registerEvent( \"SomeCoolEvent\" );\n\n"
|
||||
"// Create a listener and subscribe.\n"
|
||||
"$MyListener = new ScriptMsgListener() { class = MyListener; };\n"
|
||||
"$MyEventManager.subscribe( $MyListener, \"SomeCoolEvent\" );\n\n"
|
||||
"function MyListener::onSomeCoolEvent( %this, %data )\n"
|
||||
"{\n"
|
||||
" echo( \"onSomeCoolEvent Triggered\" );\n"
|
||||
"}\n\n"
|
||||
"// Trigger the event.\n"
|
||||
"$MyEventManager.postEvent( \"SomeCoolEvent\", \"Data\" );\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@ingroup Messaging\n"
|
||||
);
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
IMPLEMENT_CALLBACK(ScriptMsgListener, onAdd, void, (),(),
|
||||
"Script callback when a listener is first created and registered.\n\n"
|
||||
"@tsexample\n"
|
||||
"function ScriptMsgListener::onAdd(%this)\n"
|
||||
"{\n"
|
||||
" // Perform on add code here\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n"
|
||||
);
|
||||
|
||||
bool ScriptMsgListener::onAdd()
|
||||
{
|
||||
if(! Parent::onAdd())
|
||||
return false;
|
||||
|
||||
linkNamespaces();
|
||||
onAdd_callback();
|
||||
//Con::executef(this, "onAdd");
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_CALLBACK(ScriptMsgListener, onRemove, void, (),(),
|
||||
"Script callback when a listener is deleted.\n\n"
|
||||
"@tsexample\n"
|
||||
"function ScriptMsgListener::onRemove(%this)\n"
|
||||
"{\n"
|
||||
" // Perform on remove code here\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n"
|
||||
);
|
||||
|
||||
void ScriptMsgListener::onRemove()
|
||||
{
|
||||
onRemove_callback();
|
||||
//Con::executef(this, "onRemove");
|
||||
unlinkNamespaces();
|
||||
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Public Methods
|
||||
//-----------------------------------------------------------------------------
|
||||
IMPLEMENT_CALLBACK( ScriptMsgListener, onMessageReceived, bool, ( const char* queue, const char* event, const char* data ), ( queue, event, data ),
|
||||
"Called when the listener has received a message.\n"
|
||||
"@param queue The name of the queue the message was dispatched to\n"
|
||||
"@param event The name of the event (function) that was triggered\n"
|
||||
"@param data The data (parameters) for the message\n\n"
|
||||
"@return false to prevent other listeners receiving this message, true otherwise\n" );
|
||||
|
||||
bool ScriptMsgListener::onMessageReceived(StringTableEntry queue, const char* event, const char* data)
|
||||
{
|
||||
return onMessageReceived_callback(queue, event, data);
|
||||
//return dAtob(Con::executef(this, "onMessageReceived", queue, event, data));
|
||||
}
|
||||
|
||||
IMPLEMENT_CALLBACK( ScriptMsgListener, onMessageObjectReceived, bool, ( const char* queue, Message *msg ), ( queue, msg ),
|
||||
"Called when a message object (not just the message data) is passed to a listener.\n"
|
||||
"@param queue The name of the queue the message was dispatched to\n"
|
||||
"@param msg The message object\n"
|
||||
"@return false to prevent other listeners receiving this message, true otherwise\n"
|
||||
"@see Message\n"
|
||||
"@see onMessageReceived");
|
||||
|
||||
bool ScriptMsgListener::onMessageObjectReceived(StringTableEntry queue, Message *msg)
|
||||
{
|
||||
return onMessageObjectReceived_callback(queue, msg);
|
||||
//return dAtob(Con::executef(this, "onMessageObjectReceived", queue, Con::getIntArg(msg->getId())));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
IMPLEMENT_CALLBACK( ScriptMsgListener, onAddToQueue, void, ( const char* queue), ( queue),
|
||||
"@brief Callback for when the listener is added to a queue\n\n"
|
||||
"The default implementation of onAddToQueue() and onRemoveFromQueue() "
|
||||
"provide tracking of the queues this listener is added to through the "
|
||||
"mQueues member. Overrides of onAddToQueue() or onRemoveFromQueue() "
|
||||
"should ensure they call the parent implementation in any overrides.\n"
|
||||
"@param queue The name of the queue that the listener added to\n"
|
||||
"@see onRemoveFromQueue()");
|
||||
|
||||
void ScriptMsgListener::onAddToQueue(StringTableEntry queue)
|
||||
{
|
||||
//Con::executef(this, "onAddToQueue", queue);
|
||||
onAddToQueue_callback(queue);
|
||||
IMLParent::onAddToQueue(queue);
|
||||
}
|
||||
|
||||
/// @brief Callback for when the listener is removed from a queue
|
||||
///
|
||||
/// The default implementation of onAddToQueue() and onRemoveFromQueue()
|
||||
/// provide tracking of the queues this listener is added to through the
|
||||
/// #mQueues member. Overrides of onAddToQueue() or onRemoveFromQueue()
|
||||
/// should ensure they call the parent implementation in any overrides.
|
||||
///
|
||||
/// @param queue The name of the queue the listener was removed from
|
||||
/// @see onAddToQueue()
|
||||
//-----------------------------------------------------------------------------
|
||||
IMPLEMENT_CALLBACK( ScriptMsgListener, onRemoveFromQueue, void, ( const char* queue), ( queue),
|
||||
"@brief Callback for when the listener is removed from a queue\n\n"
|
||||
"The default implementation of onAddToQueue() and onRemoveFromQueue() "
|
||||
"provide tracking of the queues this listener is added to through the "
|
||||
"mQueues member. Overrides of onAddToQueue() or onRemoveFromQueue() "
|
||||
"should ensure they call the parent implementation in any overrides.\n"
|
||||
"@param queue The name of the queue that the listener was removed from\n"
|
||||
"@see onAddToQueue()");
|
||||
|
||||
void ScriptMsgListener::onRemoveFromQueue(StringTableEntry queue)
|
||||
{
|
||||
//Con::executef(this, "onRemoveFromQueue", queue);
|
||||
onRemoveFromQueue_callback(queue);
|
||||
IMLParent::onRemoveFromQueue(queue);
|
||||
}
|
||||
83
Engine/source/util/messaging/scriptMsgListener.h
Normal file
83
Engine/source/util/messaging/scriptMsgListener.h
Normal 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "console/simBase.h"
|
||||
|
||||
#ifndef _SCRIPTMSGLISTENER_H_
|
||||
#define _SCRIPTMSGLISTENER_H_
|
||||
|
||||
#ifndef _DISPATCHER_H_
|
||||
#include "util/messaging/dispatcher.h"
|
||||
#endif
|
||||
|
||||
/// @addtogroup msgsys Message System
|
||||
// @{
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// @brief Script accessible version of Dispatcher::IMessageListener
|
||||
///
|
||||
/// The main use of ScriptMsgListener is to allow script to listen for
|
||||
/// messages. You can subclass ScriptMsgListener in script to receive
|
||||
/// the Dispatcher::IMessageListener callbacks.
|
||||
///
|
||||
/// Alternatively, you can derive from it in C++ instead of SimObject to
|
||||
/// get an object that implements Dispatcher::IMessageListener with script
|
||||
/// callbacks. If you need to derive from something other then SimObject,
|
||||
/// then you will need to implement the Dispatcher::IMessageListener
|
||||
/// interface yourself.
|
||||
//-----------------------------------------------------------------------------
|
||||
class ScriptMsgListener : public SimObject, public virtual Dispatcher::IMessageListener
|
||||
{
|
||||
typedef SimObject Parent;
|
||||
typedef Dispatcher::IMessageListener IMLParent;
|
||||
|
||||
public:
|
||||
ScriptMsgListener();
|
||||
|
||||
DECLARE_CONOBJECT(ScriptMsgListener);
|
||||
|
||||
DECLARE_CALLBACK( void, onAdd, () );
|
||||
DECLARE_CALLBACK( void, onRemove, () );
|
||||
|
||||
DECLARE_CALLBACK( bool, onMessageReceived, ( const char* queue, const char* event, const char* data ) );
|
||||
DECLARE_CALLBACK( bool, onMessageObjectReceived, ( const char* queue, Message *msg ) );
|
||||
|
||||
DECLARE_CALLBACK( void, onAddToQueue, ( const char* queue ) );
|
||||
DECLARE_CALLBACK( void, onRemoveFromQueue, ( const char* queue ) );
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
virtual bool onAdd();
|
||||
virtual void onRemove();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
|
||||
virtual bool onMessageReceived(StringTableEntry queue, const char* event, const char* data);
|
||||
virtual bool onMessageObjectReceived(StringTableEntry queue, Message *msg);
|
||||
|
||||
virtual void onAddToQueue(StringTableEntry queue);
|
||||
virtual void onRemoveFromQueue(StringTableEntry queue);
|
||||
};
|
||||
|
||||
// @}
|
||||
|
||||
#endif // _SCRIPTMSGLISTENER_H_
|
||||
518
Engine/source/util/noise2d.cpp
Normal file
518
Engine/source/util/noise2d.cpp
Normal file
|
|
@ -0,0 +1,518 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "util/noise2d.h"
|
||||
#include "core/util/tVector.h"
|
||||
|
||||
//--------------------------------------
|
||||
Noise2D::Noise2D()
|
||||
{
|
||||
mSeed = 0;
|
||||
}
|
||||
|
||||
Noise2D::~Noise2D()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------
|
||||
void Noise2D::normalize(F32 v[2])
|
||||
{
|
||||
F32 s;
|
||||
|
||||
s = mSqrt(v[0] * v[0] + v[1] * v[1]);
|
||||
v[0] = v[0] / s;
|
||||
v[1] = v[1] / s;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------
|
||||
void Noise2D::setSeed(U32 seed)
|
||||
{
|
||||
if (mSeed == seed)
|
||||
return;
|
||||
mSeed = seed;
|
||||
mRandom.setSeed(mSeed);
|
||||
|
||||
S32 i, j, k;
|
||||
|
||||
for (i = 0 ; i < SIZE ; i++) {
|
||||
mPermutation[i] = i;
|
||||
|
||||
for (j = 0 ; j < 2 ; j++)
|
||||
mGradient[i][j] = mRandom.randF( -1.0f, 1.0f );
|
||||
normalize(mGradient[i]);
|
||||
}
|
||||
|
||||
while (--i) {
|
||||
k = mPermutation[i];
|
||||
j = mRandom.randI(0, SIZE-1);
|
||||
mPermutation[i] = mPermutation[j];
|
||||
mPermutation[j] = k;
|
||||
}
|
||||
|
||||
// extend the size of the arrays x2 to get rid of a bunch of MODs
|
||||
// we'd have to do later in the code
|
||||
for (i = 0 ; i < SIZE + 2 ; i++) {
|
||||
mPermutation[SIZE + i] = mPermutation[i];
|
||||
for (j = 0 ; j < 2 ; j++)
|
||||
mGradient[SIZE + i][j] = mGradient[i][j];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------
|
||||
U32 Noise2D::getSeed()
|
||||
{
|
||||
return mSeed;
|
||||
}
|
||||
|
||||
|
||||
inline F32 Noise2D::lerp(F32 t, F32 a, F32 b)
|
||||
{
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
|
||||
inline F32 Noise2D::curve(F32 t)
|
||||
{
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
|
||||
|
||||
inline F32 clamp(F32 f, F32 m)
|
||||
{
|
||||
while (f > m)
|
||||
f -= m;
|
||||
while (f < 0.0f)
|
||||
f += m;
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------
|
||||
void Noise2D::fBm( Vector<F32> *dst, U32 size, U32 interval, F32 h, F32 octaves )
|
||||
{
|
||||
interval = getMin(U32(128), getMax(U32(1), interval));
|
||||
F32 H = getMin(1.0f, getMax(0.0f, h));
|
||||
octaves = getMin(5.0f, getMax(1.0f, octaves));
|
||||
F32 lacunarity = 2.0f;
|
||||
|
||||
F32 exponent_array[32];
|
||||
|
||||
U32 shift = getBinLog2( size );
|
||||
|
||||
// precompute and store spectral weights
|
||||
// seize required memory for exponent_array
|
||||
F32 frequency = 1.0;
|
||||
for (U32 i=0; i<=octaves; i++)
|
||||
{
|
||||
// compute weight for each frequency
|
||||
exponent_array[i] = mPow( frequency, -H );
|
||||
frequency *= lacunarity;
|
||||
}
|
||||
|
||||
// initialize dst
|
||||
for (S32 k=0; k < (size*size); k++)
|
||||
(*dst)[k] = 0.0f;
|
||||
|
||||
F32 scale = 1.0f / (F32)size * interval;
|
||||
for (S32 o=0; o<octaves; o++)
|
||||
{
|
||||
F32 exp = exponent_array[o];
|
||||
for (S32 y=0; y<size; y++)
|
||||
{
|
||||
F32 fy = (F32)y * scale;
|
||||
for (S32 x=0; x<size; x++)
|
||||
{
|
||||
F32 fx = (F32)x * scale;
|
||||
F32 noise = getValue(fx, fy, interval);
|
||||
(*dst)[x + (y << shift)] += noise * exp;
|
||||
}
|
||||
}
|
||||
scale *= lacunarity;
|
||||
interval = (U32)(interval * lacunarity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------
|
||||
void Noise2D::rigidMultiFractal(Vector<F32> *dst, Vector<F32> *sig, U32 size, U32 interval, F32 h, F32 octaves)
|
||||
{
|
||||
interval = getMin(U32(128), getMax(U32(1), interval));
|
||||
F32 H = getMin(1.0f, getMax(0.0f, h));
|
||||
octaves = getMin(5.0f, getMax(1.0f, octaves));
|
||||
F32 lacunarity = 2.0f;
|
||||
F32 offset = 1.0f;
|
||||
F32 gain = 2.0f;
|
||||
|
||||
U32 shift = getBinLog2( size );
|
||||
|
||||
F32 exponent_array[32];
|
||||
|
||||
// precompute and store spectral weights
|
||||
// seize required memory for exponent_array
|
||||
F32 frequency = 1.0;
|
||||
for (U32 i=0; i<=octaves; i++)
|
||||
{
|
||||
// compute weight for each frequency
|
||||
exponent_array[i] = mPow( frequency, -H );
|
||||
frequency *= lacunarity;
|
||||
}
|
||||
|
||||
F32 scale = 1.0f / (F32)size * interval;
|
||||
|
||||
//--------------------------------------
|
||||
// compute first octave
|
||||
for (S32 y=0; y<size; y++)
|
||||
{
|
||||
F32 fy = (F32)y * scale;
|
||||
for (S32 x=0; x<size; x++)
|
||||
{
|
||||
F32 fx = (F32)x * scale;
|
||||
|
||||
F32 signal = mFabs(getValue(fx,fy,interval)); // get absolute value of signal (this creates the ridges)
|
||||
//signal = mSqrt(signal);
|
||||
signal = offset - signal; // invert and translate (note that "offset" should be ~= 1.0)
|
||||
signal *= signal + 0.1; // square the signal, to increase "sharpness" of ridges
|
||||
|
||||
// assign initial values
|
||||
(*dst)[x + (y << shift)] = signal;
|
||||
(*sig)[x + (y << shift)] = signal;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// compute remaining octaves
|
||||
for (S32 o=1; o<octaves; o++)
|
||||
{
|
||||
// increase the frequency
|
||||
scale *= lacunarity;
|
||||
interval = (U32)(interval * lacunarity);
|
||||
F32 exp = exponent_array[o];
|
||||
|
||||
for (S32 y=0; y<size; y++)
|
||||
{
|
||||
F32 fy = (F32)y * scale;
|
||||
for (S32 x=0; x<size; x++)
|
||||
{
|
||||
F32 fx = (F32)x * scale;
|
||||
U32 index = x + (y << shift);
|
||||
F32 result = (*dst)[index];
|
||||
F32 signal = (*sig)[index];
|
||||
|
||||
// weight successive contributions by previous signal
|
||||
F32 weight = mClampF(signal * gain, 0.0f, 1.0f);
|
||||
|
||||
signal = mFabs(getValue( fx, fy, interval ));
|
||||
|
||||
signal = offset - signal;
|
||||
signal *= signal + 0.2;
|
||||
// weight the contribution
|
||||
signal *= weight;
|
||||
result += signal * exp;
|
||||
|
||||
(*dst)[index] = result;
|
||||
(*sig)[index] = signal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (S32 k=0; k < (size*size); k++)
|
||||
(*dst)[k] = ((*dst)[k]-1.0f)/2.0f;
|
||||
}
|
||||
|
||||
bool Noise2D::erodeHydraulic( Vector<F32> *src, Vector<F32> *dst, U32 iterations, U32 size )
|
||||
{
|
||||
// early out if there is nothing to do
|
||||
if (iterations == 0 )
|
||||
{
|
||||
*dst = *src;
|
||||
return true;
|
||||
}
|
||||
|
||||
F32 fmin, fmax;
|
||||
getMinMax( src, &fmin, &fmax, size);
|
||||
|
||||
U32 shift = getBinLog2( size );
|
||||
U32 mask = size - 1;
|
||||
|
||||
|
||||
// currently using SCRATCH_3 for debugging -- Rick
|
||||
Vector<F32> scratch = *src;
|
||||
U32 *o = (U32*)scratch.address();
|
||||
Vector<F32> a = *src;
|
||||
Vector<F32> b = *src;
|
||||
Vector<F32> c = *src;
|
||||
|
||||
for (S32 k=0; k < (size*size); k++)
|
||||
c[k] = 0.0f;
|
||||
|
||||
for (int i=0; i<iterations; i++)
|
||||
{
|
||||
b = a;
|
||||
|
||||
for (S32 y=0; y<size; y++)
|
||||
{
|
||||
for (S32 x=0; x<size; x++)
|
||||
{
|
||||
U32 srcOffset = (x + (y << shift));
|
||||
F32 height = a[srcOffset];
|
||||
o[srcOffset] = srcOffset;
|
||||
for (S32 y1=y-1; y1 <= y+1; y1++)
|
||||
{
|
||||
F32 maxDelta = 0.0f;
|
||||
S32 ywrap = (y1 & mask);
|
||||
for (S32 x1=x-1; x1 <= x+1; x1++)
|
||||
{
|
||||
if (x1 != x && y1 != y)
|
||||
{
|
||||
U32 adjOffset = ((x1 & mask) + (ywrap << shift));
|
||||
F32 &adjHeight = a[adjOffset];
|
||||
F32 delta = height - adjHeight;
|
||||
if (x1 != x || y1 != y)
|
||||
delta *= 1.414213562f; // compensate for diagonals
|
||||
if (delta > maxDelta)
|
||||
{
|
||||
maxDelta = delta;
|
||||
o[srcOffset] = adjOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (S32 j=0; j < (size*size); j++)
|
||||
{
|
||||
F32 &s = a[j];
|
||||
F32 &d = b[ o[j] ];
|
||||
F32 delta = s - d;
|
||||
if (delta > 0.0f)
|
||||
{
|
||||
F32 alt = (s-fmin) / (fmax-fmin);
|
||||
F32 amt = delta * (0.1f * (1.0f-alt));
|
||||
s -= amt;
|
||||
d += amt;
|
||||
}
|
||||
}
|
||||
// debug only
|
||||
for (S32 k=0; k < (size*size); k++)
|
||||
c[k] += b[k] - a[k];
|
||||
|
||||
Vector<F32> tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
*dst = b;
|
||||
//*dst = *c;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool Noise2D::erodeThermal(Vector<F32> *src, Vector<F32> *dst, F32 slope, F32 materialLoss, U32 iterations, U32 size, U32 squareSize, F32 maxHeight )
|
||||
{
|
||||
// early out if there is nothing to do
|
||||
if (iterations == 0 )
|
||||
{
|
||||
*dst = *src;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
F32 fmin, fmax;
|
||||
getMinMax(src, &fmin, &fmax, size);
|
||||
|
||||
Vector<F32> a = *src;
|
||||
// Heightfield *b = getScratch(1);
|
||||
Vector<F32> r;
|
||||
r.setSize( size * size );
|
||||
//dMemset( r.address(), 0, r.memSize() );
|
||||
|
||||
F32 conservation = 1.0f - mClampF(materialLoss, 0.0f, 100.0f)/100.0f;
|
||||
slope = mClampF(conservation, 0.0f, 89.0f); // clamp to 0-89 degrees
|
||||
|
||||
F32 talusConst = mTan(mDegToRad(slope)) * squareSize; // in world units
|
||||
talusConst = talusConst * (fmax-fmin) / maxHeight; // scale to current height units
|
||||
F32 p = 0.1f;
|
||||
|
||||
U32 mask = size - 1;
|
||||
U32 shift = getBinLog2( size );
|
||||
|
||||
for (U32 i=0; i<iterations; i++)
|
||||
{
|
||||
// clear out the rubble accumulation field
|
||||
dMemset( r.address(), 0, r.memSize() );
|
||||
|
||||
for (S32 y=0; y<size; y++)
|
||||
{
|
||||
for (S32 x=0; x<size; x++)
|
||||
{
|
||||
F32 *height = &a[ x + ( y << shift )];
|
||||
F32 *dstHeight = &r[ x + ( y << shift )];
|
||||
|
||||
// for each height look at the immediate surrounding heights
|
||||
// if any are higher than talusConst erode on me
|
||||
for (S32 y1=y-1; y1 <= y+1; y1++)
|
||||
{
|
||||
S32 ywrap = (y1 & mask);
|
||||
for (S32 x1=x-1; x1 <= x+1; x1++)
|
||||
{
|
||||
if (x1 != x && y1 != y)
|
||||
{
|
||||
S32 adjOffset = ((x1 & mask) + (ywrap << shift));
|
||||
F32 adjHeight = a[adjOffset];
|
||||
F32 delta = adjHeight - *height;
|
||||
if (delta > talusConst)
|
||||
{
|
||||
F32 rubble = p * (delta - talusConst);
|
||||
r[adjOffset] -= rubble;
|
||||
*dstHeight += rubble * conservation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (S32 k=0; k < (size*size); k++)
|
||||
a[k] += r[k];
|
||||
}
|
||||
*dst = a;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Noise2D::getMinMax( Vector<F32> *src, F32 *fmin, F32 *fmax, U32 size )
|
||||
{
|
||||
if (!src)
|
||||
return;
|
||||
|
||||
F32 *p = (*src).address();
|
||||
*fmin = *p;
|
||||
*fmax = *p;
|
||||
for (S32 i=0; i < (size*size); i++, p++)
|
||||
{
|
||||
if (*fmin > *p) *fmin = *p;
|
||||
if (*fmax < *p) *fmax = *p;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
F32 Noise2D::turbulence(F32 x, F32 y, F32 freq)
|
||||
{
|
||||
F32 t, x2, y2;
|
||||
|
||||
for ( t = 0.0f ; freq >= 3.0f ; freq /= 2.0f)
|
||||
{
|
||||
x2 = freq * x;
|
||||
y2 = freq * y;
|
||||
t += mFabs(getValue(x2, y2, (S32)freq)) / freq;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------
|
||||
inline void Noise2D::setup(F32 t, S32 &b0, S32 &b1, F32 &r0, F32 &r1)
|
||||
{
|
||||
// find the bounding integers of u
|
||||
b0 = S32(t) & SIZE_MASK;
|
||||
b1 = (b0+1) & SIZE_MASK;
|
||||
|
||||
// seperate the fractional components
|
||||
r0 = t - (S32)t;
|
||||
r1 = r0 - 1.0f;
|
||||
}
|
||||
|
||||
inline F32 Noise2D::dot(const F32 *q, F32 rx, F32 ry)
|
||||
{
|
||||
return (rx * q[0] + ry * q[1] );
|
||||
}
|
||||
|
||||
|
||||
|
||||
//--------------------------------------
|
||||
F32 Noise2D::getValue(F32 x, F32 y, S32 interval)
|
||||
{
|
||||
S32 bx0, bx1, by0, by1;
|
||||
F32 rx0, rx1, ry0, ry1;
|
||||
|
||||
// Imagine having a square of the type
|
||||
// p0---p1 Where p0 = (bx0, by0) +----> U
|
||||
// |(u,v)| p1 = (bx1, by0) |
|
||||
// | | p2 = (bx0, by1) | Coordinate System
|
||||
// p2---p3 p3 = (bx1, by1) V
|
||||
// The u, v point in 2D texture space is bounded by this rectangle.
|
||||
|
||||
// Goal, determine the scalar at the points p0, p1, p2, p3.
|
||||
// Then the scalar of the point (u, v) will be found by linear interpolation.
|
||||
|
||||
// First step: Get the 2D coordinates of the points p0, p1, p2, p3.
|
||||
// We also need vectors pointing from each point in the square above and
|
||||
// ending at the (u,v) coordinate located inside the square.
|
||||
// The vector (rx0, ry0) goes from P0 to the (u,v) coordinate.
|
||||
// The vector (rx1, ry0) goes from P1 to the (u,v) coordinate.
|
||||
// The vector (rx0, ry1) goes from P2 to the (u,v) coordinate.
|
||||
// The vector (rx1, ry1) goes from P3 to the (u,v) coordinate.
|
||||
|
||||
setup(x, bx0, bx1, rx0, rx1);
|
||||
setup(y, by0, by1, ry0, ry1);
|
||||
|
||||
// Make sure the box corners fall within the interval
|
||||
// so that the final output will wrap on itself
|
||||
bx0 = bx0 % interval;
|
||||
bx1 = bx1 % interval;
|
||||
by0 = by0 % interval;
|
||||
by1 = by1 % interval;
|
||||
|
||||
S32 i = mPermutation[ bx0 ];
|
||||
S32 j = mPermutation[ bx1 ];
|
||||
|
||||
S32 b00 = mPermutation[ i + by0 ];
|
||||
S32 b10 = mPermutation[ j + by0 ];
|
||||
S32 b01 = mPermutation[ i + by1 ];
|
||||
S32 b11 = mPermutation[ j + by1 ];
|
||||
|
||||
// Next, calculate the dropoff component about the point p0.
|
||||
F32 sx = curve(rx0);
|
||||
F32 sy = curve(ry0);
|
||||
|
||||
// Now, for each point in the square shown above, calculate the dot
|
||||
// product of the gradiant vector and the vector going from each square
|
||||
// corner point to the (u,v) point inside the square.
|
||||
F32 u = dot(mGradient[ b00 ], rx0,ry0);
|
||||
F32 v = dot(mGradient[ b10 ], rx1,ry0);
|
||||
|
||||
// Interpolation along the X axis.
|
||||
F32 a = lerp(sx, u, v);
|
||||
|
||||
u = dot(mGradient[ b01 ], rx0,ry1);
|
||||
v = dot(mGradient[ b11 ], rx1,ry1);
|
||||
|
||||
// Interpolation along the Y axis.
|
||||
F32 b = lerp(sx, u, v);
|
||||
|
||||
// Final Interpolation
|
||||
return lerp(sy, a, b);
|
||||
}
|
||||
|
||||
|
||||
95
Engine/source/util/noise2d.h
Normal file
95
Engine/source/util/noise2d.h
Normal 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 _NOISE2D_H_
|
||||
#define _NOISE2D_H_
|
||||
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _MMATH_H_
|
||||
#include "math/mMath.h"
|
||||
#endif
|
||||
#ifndef _MRANDOM_H_
|
||||
#include "math/mRandom.h"
|
||||
#endif
|
||||
|
||||
class Noise2D
|
||||
{
|
||||
private:
|
||||
enum Constants {
|
||||
SIZE = 0x100,
|
||||
SIZE_MASK = 0x0ff
|
||||
};
|
||||
S32 mPermutation[SIZE + SIZE + 2];
|
||||
F32 mGradient[SIZE + SIZE + 2][2];
|
||||
|
||||
U32 mSeed;
|
||||
|
||||
MRandom mRandom;
|
||||
|
||||
F32 lerp(F32 t, F32 a, F32 b);
|
||||
F32 curve(F32 t);
|
||||
void setup(F32 t, S32 &b0, S32 &b1, F32 &r0, F32 &r1);
|
||||
F32 dot(const F32 *q, F32 rx, F32 ry);
|
||||
void normalize(F32 v[2]);
|
||||
|
||||
public:
|
||||
Noise2D();
|
||||
~Noise2D();
|
||||
|
||||
void setSeed(U32 seed);
|
||||
U32 getSeed();
|
||||
|
||||
F32 getValue(F32 u, F32 v, S32 interval);
|
||||
|
||||
/// @name Noise
|
||||
/// These functions actually generate
|
||||
/// noise values into the passed in destination
|
||||
/// array.
|
||||
///
|
||||
/// Note that the output values of these functions
|
||||
/// are from -1.0 to 1.0.
|
||||
///
|
||||
/// fBm - Fractal Brownian Motion - A simple noise generation
|
||||
/// algorithm, it tends to generate either flowing rounded
|
||||
/// hills or rounded mountainous shapes.
|
||||
/// @{
|
||||
void fBm( Vector<F32> *dst, U32 size, U32 interval, F32 h, F32 octave=5.0f);
|
||||
|
||||
/// rigidMultiFractal
|
||||
/// Creates ridged mountains with a high frequency detail.
|
||||
void rigidMultiFractal( Vector<F32> *dst, Vector<F32> *signal, U32 size, U32 interval, F32 h, F32 octave=5.0f);
|
||||
/// @}
|
||||
|
||||
bool erodeHydraulic(Vector<F32> *src, Vector<F32> *dst, U32 iterations, U32 size );
|
||||
bool erodeThermal(Vector<F32> *src, Vector<F32> *dst, F32 slope, F32 materialLoss, U32 iterations, U32 size, U32 squareSize, F32 maxHeight );
|
||||
|
||||
F32 turbulence(F32 x, F32 y, F32 freq);
|
||||
|
||||
void getMinMax( Vector<F32> *src, F32 *maxNoise, F32 *minNoise, U32 size );
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // _NOISE2D_H_
|
||||
242
Engine/source/util/quadTreeTracer.cpp
Normal file
242
Engine/source/util/quadTreeTracer.cpp
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "util/quadTreeTracer.h"
|
||||
#include "platform/profiler.h"
|
||||
#include "core/frameAllocator.h"
|
||||
|
||||
static F32 calcInterceptV(F32 vStart, F32 invDeltaV, F32 intercept)
|
||||
{
|
||||
return (intercept - vStart) * invDeltaV;
|
||||
}
|
||||
|
||||
static F32 calcInterceptNone(F32, F32, F32)
|
||||
{
|
||||
return F32_MAX;
|
||||
}
|
||||
|
||||
static F32 (*calcInterceptX)(F32, F32, F32);
|
||||
static F32 (*calcInterceptY)(F32, F32, F32);
|
||||
|
||||
bool QuadTreeTracer::castRay(const Point3F &start, const Point3F &end, RayInfo *info)
|
||||
{
|
||||
PROFILE_START(QuadTreeTracer_castRay);
|
||||
|
||||
// Do some precalculations we'll use for the rest of this routine.
|
||||
// Set up our intercept calculation methods.
|
||||
F32 invDeltaX;
|
||||
if(end.x == start.x)
|
||||
{
|
||||
calcInterceptX = calcInterceptNone;
|
||||
invDeltaX = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
invDeltaX = 1.f / (end.x - start.x);
|
||||
calcInterceptX = calcInterceptV;
|
||||
}
|
||||
|
||||
F32 invDeltaY;
|
||||
if(end.y == start.y)
|
||||
{
|
||||
calcInterceptY = calcInterceptNone;
|
||||
invDeltaY = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
invDeltaY = 1.f / (end.y - start.y);
|
||||
calcInterceptY = calcInterceptV;
|
||||
}
|
||||
|
||||
// Subdivide our space based on the size of the lowest level of the tree...
|
||||
const F32 invSize = 1.f / F32(BIT(mTreeDepth-1));
|
||||
|
||||
// Grab this off the frame allocator, we don't want to do a proper alloc
|
||||
// on every ray!
|
||||
FrameAllocatorMarker stackAlloc;
|
||||
RayStackNode *stack = (RayStackNode*)stackAlloc.alloc(sizeof(RayStackNode) * (mTreeDepth * 3 + 1));
|
||||
|
||||
U32 stackSize = 1;
|
||||
|
||||
// Kick off the stack with the root node.
|
||||
stack[0].startT = 0;
|
||||
stack[0].endT = 1;
|
||||
stack[0].squarePos.set(0,0);
|
||||
stack[0].level = mTreeDepth - 1;
|
||||
|
||||
//Con::printf("QuadTreeTracer::castRay(%x)", this);
|
||||
|
||||
// Aright, now let's do some raycasting!
|
||||
while(stackSize--)
|
||||
{
|
||||
// Get the current node for easy access...
|
||||
RayStackNode *sn = stack + stackSize;
|
||||
|
||||
const U32 level = sn->level;
|
||||
const F32 startT = sn->startT;
|
||||
const F32 endT = sn->endT;
|
||||
const Point2I squarePos = sn->squarePos;
|
||||
|
||||
AssertFatal((startT >= 0.f) && (startT <= 1.f), "QuadTreeTracer::castRay - out of range startT on stack!");
|
||||
AssertFatal((endT >= 0.f) && (endT <= 1.f), "QuadTreeTracer::castRay - out of range endT on stack!");
|
||||
|
||||
//Con::printf(" -- node(%d, %d @ %d), sT=%f, eT=%f", squarePos.x, squarePos.y, level, startT, endT);
|
||||
|
||||
// Figure our start and end Z.
|
||||
const F32 startZ = startT * (end.z - start.z) + start.z;
|
||||
const F32 endZ = endT * (end.z - start.z) + start.z;
|
||||
|
||||
// Ok, now let's see if we hit the lower bound
|
||||
const F32 squareMin = getSquareMin(level, squarePos);
|
||||
|
||||
if(startZ < squareMin && endZ < squareMin)
|
||||
continue; //Nope, skip out.
|
||||
|
||||
// Hmm, let's check the upper bound.
|
||||
const F32 squareMax = getSquareMax(level, squarePos);
|
||||
|
||||
if(startZ > squareMax && endZ > squareMax)
|
||||
continue; //Nope, skip out.
|
||||
|
||||
// We might be intersecting something... If we've hit
|
||||
// the tree depth let's deal with the leaf intersection.
|
||||
if(level == 0)
|
||||
{
|
||||
//Con::printf(" ++ check node(%d, %d @ %d), sT=%f, eT=%f", squarePos.x, squarePos.y, level, startT, endT);
|
||||
|
||||
if(castLeafRay(squarePos, start, end, startT, endT, info))
|
||||
{
|
||||
PROFILE_END();
|
||||
return true; // We hit, tell 'em so!
|
||||
}
|
||||
continue; // Otherwise, keep looking.
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ok, we have to push our children as we're an inner node.
|
||||
|
||||
// First, figure out some widths...
|
||||
U32 subSqSize = BIT(level - 1);
|
||||
|
||||
// Now, calculate intercepts so we know how to deal with this
|
||||
// situation... (intercept = position, int = t value for that pos)
|
||||
|
||||
const F32 xIntercept = (squarePos.x + subSqSize) * invSize;
|
||||
F32 xInt = calcInterceptX(start.x, invDeltaX, xIntercept);
|
||||
|
||||
const F32 yIntercept = (squarePos.y + subSqSize) * invSize;
|
||||
F32 yInt = calcInterceptY(start.y, invDeltaY, yIntercept);
|
||||
|
||||
// Our starting position for this subray...
|
||||
const F32 startX = startT * (end.x - start.x) + start.x;
|
||||
const F32 startY = startT * (end.y - start.y) + start.y;
|
||||
|
||||
// Deal with squares that might be "behind" the ray.
|
||||
if(xInt < startT) xInt = F32_MAX;
|
||||
if(yInt < startT) yInt = F32_MAX;
|
||||
|
||||
// Do a little magic to calculate our next checks...
|
||||
const U32 x0 = (startX > xIntercept) * subSqSize;
|
||||
const U32 y0 = (startY > yIntercept) * subSqSize;
|
||||
|
||||
const U32 x1 = subSqSize - x0;
|
||||
const U32 y1 = subSqSize - y0;
|
||||
|
||||
const U32 nextLevel = level - 1;
|
||||
|
||||
// Ok, now let's figure out what nodes, in what order, need to go
|
||||
// on the stack. We push things on in reverse order of processing.
|
||||
if(xInt > endT && yInt > endT)
|
||||
{
|
||||
stack[stackSize].squarePos.set(squarePos.x + x0, squarePos.y + y0);
|
||||
stack[stackSize].level = nextLevel;
|
||||
stackSize++;
|
||||
}
|
||||
else if(xInt < yInt)
|
||||
{
|
||||
F32 nextIntersect = endT;
|
||||
|
||||
if(yInt <= endT)
|
||||
{
|
||||
stack[stackSize].squarePos.set(squarePos.x + x1, squarePos.y + y1);
|
||||
stack[stackSize].startT = yInt;
|
||||
stack[stackSize].endT = endT;
|
||||
stack[stackSize].level = nextLevel;
|
||||
nextIntersect = yInt;
|
||||
stackSize++;
|
||||
}
|
||||
|
||||
// Do middle two, order doesn't matter.
|
||||
stack[stackSize].squarePos.set(squarePos.x + x1, squarePos.y + y0);
|
||||
stack[stackSize].startT = xInt;
|
||||
stack[stackSize].endT = nextIntersect;
|
||||
stack[stackSize].level = nextLevel;
|
||||
|
||||
stack[stackSize+1].squarePos.set(squarePos.x + x0, squarePos.y + y0);
|
||||
stack[stackSize+1].startT = startT;
|
||||
stack[stackSize+1].endT = xInt;
|
||||
stack[stackSize+1].level = nextLevel;
|
||||
stackSize += 2;
|
||||
}
|
||||
else if(yInt < xInt)
|
||||
{
|
||||
F32 nextIntersect = endT;
|
||||
if(xInt <= endT)
|
||||
{
|
||||
stack[stackSize].squarePos.set(squarePos.x + x1, squarePos.y + y1);
|
||||
stack[stackSize].startT = xInt;
|
||||
stack[stackSize].endT = endT;
|
||||
stack[stackSize].level = nextLevel;
|
||||
nextIntersect = xInt;
|
||||
stackSize++;
|
||||
}
|
||||
stack[stackSize].squarePos.set(squarePos.x + x0, squarePos.y + y1);
|
||||
stack[stackSize].startT = yInt;
|
||||
stack[stackSize].endT = nextIntersect;
|
||||
stack[stackSize].level = nextLevel;
|
||||
|
||||
stack[stackSize+1].squarePos.set(squarePos.x + x0, squarePos.y + y0);
|
||||
stack[stackSize+1].startT = startT;
|
||||
stack[stackSize+1].endT = yInt;
|
||||
stack[stackSize+1].level = nextLevel;
|
||||
stackSize += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
stack[stackSize].squarePos.set(squarePos.x + x1, squarePos.y + y1);
|
||||
stack[stackSize].startT = xInt;
|
||||
stack[stackSize].endT = endT;
|
||||
stack[stackSize].level = nextLevel;
|
||||
|
||||
stack[stackSize+1].squarePos.set(squarePos.x + x0, squarePos.y + y0);
|
||||
stack[stackSize+1].startT = startT;
|
||||
stack[stackSize+1].endT = xInt;
|
||||
stack[stackSize+1].level = nextLevel;
|
||||
stackSize += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing found, so give up.
|
||||
PROFILE_END();
|
||||
return false;
|
||||
}
|
||||
104
Engine/source/util/quadTreeTracer.h
Normal file
104
Engine/source/util/quadTreeTracer.h
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _QUADTREETRACER_H_
|
||||
#define _QUADTREETRACER_H_
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "math/mPoint3.h"
|
||||
#include "scene/sceneObject.h"
|
||||
|
||||
/// Helper class to perform a fast, recursive ray cast against a set of
|
||||
/// hierarchical bounding boxes.
|
||||
///
|
||||
/// This class assumes that it is working on a unit quadtree (ie, one that
|
||||
/// extends from 0..1 in the XY dimensions. Z scale is unaffected).
|
||||
///
|
||||
/// Node indexing is done TGE Terrain style - 0 is the largest level of the
|
||||
/// quadtree, while coordinates are always in the full range of the quadtree
|
||||
/// (in a 6 deep tree, 0..63, for instance). This allows the quadtree descent
|
||||
/// to be very fast!
|
||||
class QuadTreeTracer
|
||||
{
|
||||
protected:
|
||||
|
||||
struct StackNode
|
||||
{
|
||||
Point2I squarePos;
|
||||
U32 level;
|
||||
};
|
||||
|
||||
struct RayStackNode : StackNode
|
||||
{
|
||||
F32 startT;
|
||||
F32 endT;
|
||||
};
|
||||
|
||||
U32 mTreeDepth;
|
||||
|
||||
QuadTreeTracer(U32 treeDepth)
|
||||
: mTreeDepth(treeDepth)
|
||||
{
|
||||
}
|
||||
|
||||
/// Children better implement these! They return min/max height bounds
|
||||
/// of the specified square.
|
||||
virtual const F32 getSquareMin(const U32 &level, const Point2I &pos) const = 0;
|
||||
virtual const F32 getSquareMax(const U32 &level, const Point2I &pos) const = 0;
|
||||
|
||||
/// And this does checks on leaf nodes.
|
||||
virtual bool castLeafRay(const Point2I pos, const Point3F &start, const Point3F &end, const F32 &startT, const F32 &endT, RayInfo *info) = 0;
|
||||
|
||||
/// Helper function to calculate intercepts.
|
||||
inline const F32 calcIntercept(const F32 vStart, const F32 invDeltaV, const F32 intercept) const
|
||||
{
|
||||
return (intercept - vStart) * invDeltaV;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/// Size of a quadtree of depth.
|
||||
static inline const U32 getNodeCount(const U32 depth)
|
||||
{
|
||||
return 0x55555555 & ((1 << depth*2) - 1);
|
||||
}
|
||||
|
||||
/// Index of a node at given position in a quadtree.
|
||||
static inline const U32 getNodeIndex(const U32 level, const Point2I pos)
|
||||
{
|
||||
//AssertFatal(level < mTreeDepth, "QuadTreeTracer::getNodeIndex - out of range level!)
|
||||
AssertFatal(pos.x < BIT(level) && pos.x >= 0 , "QuadTreeTracer::getNodeIndex - out of range x for level!");
|
||||
AssertFatal(pos.y < BIT(level) && pos.y >= 0 , "QuadTreeTracer::getNodeIndex - out of range y for level!");
|
||||
|
||||
const U32 base = getNodeCount(level);
|
||||
return base + (pos.x << level) + pos.y;
|
||||
}
|
||||
|
||||
/// Cast a ray against a quadtree of hierarchical bounding boxes.
|
||||
///
|
||||
/// This method assumes the quadtree extends from (0..1) along the
|
||||
/// X and Y axes. Z is unscaled. You may need to adjust the points
|
||||
/// you pass into this method to get the proper results.
|
||||
bool castRay(const Point3F &start, const Point3F &end, RayInfo *info);
|
||||
};
|
||||
|
||||
#endif
|
||||
167
Engine/source/util/rectClipper.cpp
Normal file
167
Engine/source/util/rectClipper.cpp
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "util/rectClipper.h"
|
||||
|
||||
namespace {
|
||||
|
||||
inline void
|
||||
swap(F32& in_one, F32& in_two)
|
||||
{
|
||||
F32 temp = in_one;
|
||||
in_one = in_two;
|
||||
in_two = temp;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
RectClipper::clipLine(const Point2I& in_rStart,
|
||||
const Point2I& in_rEnd,
|
||||
Point2I& out_rStart,
|
||||
Point2I& out_rEnd) const
|
||||
{
|
||||
// Check for trivial rejection
|
||||
if ((in_rStart.x < m_clipRect.point.x && in_rEnd.x < m_clipRect.point.x) ||
|
||||
(in_rStart.x >= m_clipRect.point.x + m_clipRect.extent.x &&
|
||||
in_rEnd.x >= m_clipRect.point.x + m_clipRect.extent.x))
|
||||
return false;
|
||||
if ((in_rStart.y < m_clipRect.point.y && in_rEnd.y < m_clipRect.point.y) ||
|
||||
(in_rStart.y >= m_clipRect.point.y + m_clipRect.extent.y &&
|
||||
in_rEnd.y >= m_clipRect.point.y + m_clipRect.extent.y))
|
||||
return false;
|
||||
|
||||
F32 x1 = F32(in_rStart.x);
|
||||
F32 y1 = F32(in_rStart.y);
|
||||
F32 x2 = F32(in_rEnd.x);
|
||||
F32 y2 = F32(in_rEnd.y);
|
||||
|
||||
// I'm using essentially what's in the Phoenix libs, Liang-Biarsky based, but
|
||||
// converted to FP math for greater precision on the back end...
|
||||
//
|
||||
bool flipped = false;
|
||||
if (x1 > x2)
|
||||
{
|
||||
swap(x1, x2);
|
||||
swap(y1, y2);
|
||||
flipped = !flipped;
|
||||
}
|
||||
|
||||
F32 dx = x2 - x1;
|
||||
F32 dy = y2 - y1;
|
||||
|
||||
// Clip x coord
|
||||
F32 t;
|
||||
if (x1 < F32(m_clipRect.point.x))
|
||||
{
|
||||
t = (F32(m_clipRect.point.x) - x1) / F32(dx);
|
||||
x1 = F32(m_clipRect.point.x);
|
||||
y1 += t * dy;
|
||||
dx = x2 - x1;
|
||||
dy = y2 - y1;
|
||||
}
|
||||
if (x2 >= F32(m_clipRect.point.x + m_clipRect.extent.x))
|
||||
{
|
||||
t = (F32(m_clipRect.point.x + m_clipRect.extent.x - 1) - x1) / F32(dx);
|
||||
x2 = F32(m_clipRect.point.x + m_clipRect.extent.x - 1);
|
||||
y2 = y1 + (t * dy);
|
||||
dx = x2 - x1;
|
||||
dy = y2 - y1;
|
||||
}
|
||||
|
||||
// Recheck trivial rejection condition...
|
||||
if((y1 > F32(m_clipRect.point.y + m_clipRect.extent.y - 1) &&
|
||||
y2 > F32(m_clipRect.point.y + m_clipRect.extent.y - 1)) ||
|
||||
(y1 < F32(m_clipRect.point.y) && y2 < F32(m_clipRect.point.y)))
|
||||
return false;
|
||||
|
||||
if (y1 > y2)
|
||||
{
|
||||
swap(x1, x2);
|
||||
swap(y1, y2);
|
||||
flipped = !flipped;
|
||||
}
|
||||
|
||||
if (y1 < F32(m_clipRect.point.y))
|
||||
{
|
||||
t = (F32(m_clipRect.point.y) - y1) / F32(dy);
|
||||
y1 = F32(m_clipRect.point.y);
|
||||
x1 += t * dx;
|
||||
dx = x2 - x1;
|
||||
dy = y2 - y1;
|
||||
}
|
||||
if (y2 > F32(m_clipRect.point.y + m_clipRect.extent.y - 1))
|
||||
{
|
||||
t = (F32(m_clipRect.point.y + m_clipRect.extent.y - 1) - y1) / F32(dy);
|
||||
y2 = F32(m_clipRect.point.y + m_clipRect.extent.y - 1);
|
||||
x2 = x1 + (t * dx);
|
||||
}
|
||||
|
||||
if (flipped == true)
|
||||
{
|
||||
out_rEnd.x = S32(x1 + 0.5f);
|
||||
out_rEnd.y = S32(y1 + 0.5f);
|
||||
out_rStart.x = S32(x2 + 0.5f);
|
||||
out_rStart.y = S32(y2 + 0.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
out_rStart.x = S32(x1 + 0.5f);
|
||||
out_rStart.y = S32(y1 + 0.5f);
|
||||
out_rEnd.x = S32(x2 + 0.5f);
|
||||
out_rEnd.y = S32(y2 + 0.5f);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
RectClipper::clipRect(const RectI& in_rRect,
|
||||
RectI& out_rRect) const
|
||||
{
|
||||
AssertFatal(in_rRect.isValidRect(), "Inappropriate min/max coords for rectangle");
|
||||
|
||||
if (in_rRect.point.x + in_rRect.extent.x - 1 < m_clipRect.point.x ||
|
||||
in_rRect.point.x > m_clipRect.point.x + m_clipRect.extent.x - 1)
|
||||
return false;
|
||||
if (in_rRect.point.y + in_rRect.extent.y - 1 < m_clipRect.point.y ||
|
||||
in_rRect.point.y > m_clipRect.point.y + m_clipRect.extent.y - 1)
|
||||
return false;
|
||||
|
||||
if (in_rRect.point.x < m_clipRect.point.x) out_rRect.point.x = m_clipRect.point.x;
|
||||
else out_rRect.point.x = in_rRect.point.x;
|
||||
|
||||
if (in_rRect.point.y < m_clipRect.point.y) out_rRect.point.y = m_clipRect.point.y;
|
||||
else out_rRect.point.y = in_rRect.point.y;
|
||||
|
||||
Point2I bottomR;
|
||||
bottomR.x = getMin(in_rRect.point.x + in_rRect.extent.x - 1,
|
||||
m_clipRect.point.x + m_clipRect.extent.x - 1);
|
||||
bottomR.y = getMin(in_rRect.point.y + in_rRect.extent.y - 1,
|
||||
m_clipRect.point.y + m_clipRect.extent.y - 1);
|
||||
|
||||
out_rRect.extent.x = bottomR.x - out_rRect.point.x + 1;
|
||||
out_rRect.extent.x = bottomR.y - out_rRect.point.y + 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
72
Engine/source/util/rectClipper.h
Normal file
72
Engine/source/util/rectClipper.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _RECTCLIPPER_H_
|
||||
#define _RECTCLIPPER_H_
|
||||
|
||||
//Includes
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _MRECT_H_
|
||||
#include "math/mRect.h"
|
||||
#endif
|
||||
|
||||
|
||||
class RectClipper
|
||||
{
|
||||
RectI m_clipRect;
|
||||
|
||||
public:
|
||||
RectClipper(const RectI& in_rRect);
|
||||
|
||||
bool clipPoint(const Point2I& in_rPoint) const;
|
||||
bool clipLine(const Point2I& in_rStart,
|
||||
const Point2I& in_rEnd,
|
||||
Point2I& out_rStart,
|
||||
Point2I& out_rEnd) const;
|
||||
bool clipRect(const RectI& in_rRect,
|
||||
RectI& out_rRect) const;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
//-------------------------------------- INLINES
|
||||
//
|
||||
inline
|
||||
RectClipper::RectClipper(const RectI& in_rRect)
|
||||
: m_clipRect(in_rRect)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
inline bool
|
||||
RectClipper::clipPoint(const Point2I& in_rPoint) const
|
||||
{
|
||||
if ((in_rPoint.x < m_clipRect.point.x) ||
|
||||
(in_rPoint.y < m_clipRect.point.y) ||
|
||||
(in_rPoint.x >= m_clipRect.point.x + m_clipRect.extent.x) ||
|
||||
(in_rPoint.y >= m_clipRect.point.y + m_clipRect.extent.y))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif //_RECTCLIPPER_H_
|
||||
66
Engine/source/util/returnType.h
Normal file
66
Engine/source/util/returnType.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _UTIL_RETURNTYPE_H_
|
||||
#define _UTIL_RETURNTYPE_H_
|
||||
|
||||
/// @file
|
||||
///
|
||||
/// Helper templates to determine the return type of functions.
|
||||
|
||||
template <class R> struct ReturnType { typedef void ValueType; };
|
||||
|
||||
template <class R,class A,class B,class C,class D,class E,class F,class G>
|
||||
struct ReturnType<R (*)(A,B,C,D,E,F,G)> { typedef R ValueType; };
|
||||
template <class R,class A,class B,class C,class D,class E,class F>
|
||||
struct ReturnType<R (*)(A,B,C,D,E,F)> { typedef R ValueType; };
|
||||
template <class R,class A,class B,class C,class D,class E>
|
||||
struct ReturnType<R (*)(A,B,C,D,E)> { typedef R ValueType; };
|
||||
template <class R,class A,class B,class C,class D>
|
||||
struct ReturnType<R (*)(A,B,C,D)> { typedef R ValueType; };
|
||||
template <class R,class A,class B,class C>
|
||||
struct ReturnType<R (*)(A,B,C)> { typedef R ValueType; };
|
||||
template <class R,class A,class B>
|
||||
struct ReturnType<R (*)(A,B)> { typedef R ValueType; };
|
||||
template <class R,class A>
|
||||
struct ReturnType<R (*)(A)> { typedef R ValueType; };
|
||||
template <class R>
|
||||
struct ReturnType<R (*)()> { typedef R ValueType; };
|
||||
|
||||
template <class R,class O,class A,class B,class C,class D,class E,class F,class G>
|
||||
struct ReturnType<R (O::*)(A,B,C,D,E,F,G)> { typedef R ValueType; };
|
||||
template <class R,class O,class A,class B,class C,class D,class E,class F>
|
||||
struct ReturnType<R (O::*)(A,B,C,D,E,F)> { typedef R ValueType; };
|
||||
template <class R,class O,class A,class B,class C,class D,class E>
|
||||
struct ReturnType<R (O::*)(A,B,C,D,E)> { typedef R ValueType; };
|
||||
template <class R,class O,class A,class B,class C,class D>
|
||||
struct ReturnType<R (O::*)(A,B,C,D)> { typedef R ValueType; };
|
||||
template <class R,class O,class A,class B,class C>
|
||||
struct ReturnType<R (O::*)(A,B,C)> { typedef R ValueType; };
|
||||
template <class R,class O,class A,class B>
|
||||
struct ReturnType<R (O::*)(A,B)> { typedef R ValueType; };
|
||||
template <class R,class O,class A>
|
||||
struct ReturnType<R (O::*)(A)> { typedef R ValueType; };
|
||||
template <class R,class O>
|
||||
struct ReturnType<R (O::*)()> { typedef R ValueType; };
|
||||
|
||||
#endif
|
||||
432
Engine/source/util/sampler.cpp
Normal file
432
Engine/source/util/sampler.cpp
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "util/sampler.h"
|
||||
|
||||
#include "core/util/safeDelete.h"
|
||||
#include "core/util/tVector.h"
|
||||
#include "core/stream/fileStream.h"
|
||||
#include "console/console.h"
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
/// Bookkeeping structure for registered sampling keys.
|
||||
|
||||
struct SampleKey
|
||||
{
|
||||
bool mEnabled;
|
||||
const char* mName;
|
||||
|
||||
bool matchesPattern( const char* pattern )
|
||||
{
|
||||
U32 indexInName = 0;
|
||||
U32 indexInPattern = 0;
|
||||
|
||||
while( mName[ indexInName ] != '\0' )
|
||||
{
|
||||
if( pattern[ indexInPattern ] == '\0' )
|
||||
break;
|
||||
else if( dToupper( mName[ indexInName ] ) == dToupper( pattern[ indexInPattern ] ) )
|
||||
{
|
||||
indexInName ++;
|
||||
indexInPattern ++;
|
||||
}
|
||||
else if( pattern[ indexInPattern ] == '*' )
|
||||
{
|
||||
// Handle senseless concatenation of wildcards.
|
||||
while( pattern[ indexInPattern ] == '*' )
|
||||
indexInPattern ++;
|
||||
|
||||
// Skip to next slash in name.
|
||||
while( mName[ indexInName ] && mName[ indexInName ] != '/' )
|
||||
indexInName ++;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return ( pattern[ indexInPattern ] == '\0'
|
||||
|| ( indexInPattern > 0 && pattern[ indexInPattern ] == '*' ) );
|
||||
}
|
||||
};
|
||||
|
||||
/// A sampler backend is responsible for storing the actual sampling data.
|
||||
|
||||
struct ISamplerBackend
|
||||
{
|
||||
virtual ~ISamplerBackend() {}
|
||||
|
||||
virtual bool init( const char* location ) = 0;
|
||||
virtual void beginFrame() = 0;
|
||||
virtual void endFrame() = 0;
|
||||
|
||||
virtual void sample( U32 key, bool value ) = 0;
|
||||
virtual void sample( U32 key, S32 value ) = 0;
|
||||
virtual void sample( U32 key, F32 value ) = 0;
|
||||
virtual void sample( U32 key, const char* value ) = 0;
|
||||
};
|
||||
|
||||
static bool gSamplerRunning;
|
||||
static S32 gSamplingFrequency = 1; ///< Frequency = samples taken every nth frame.
|
||||
static U32 gCurrentFrameDelta;
|
||||
static Vector< SampleKey > gSampleKeys( __FILE__, __LINE__ );
|
||||
static ISamplerBackend* gSamplerBackend;
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// CSV Backend.
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
/// A sampler backend that outputs samples to a CSV file.
|
||||
|
||||
class CSVSamplerBackend : public ISamplerBackend
|
||||
{
|
||||
/// Value holder for an individual sample. Unfortunately, since the
|
||||
/// order in which samples arrive at the sampler may vary from frame to
|
||||
/// frame, we cannot emit data immediately but rather have to buffer
|
||||
/// it in these sample records and then flush them to disk once we receive
|
||||
/// the endFrame call.
|
||||
struct SampleRecord
|
||||
{
|
||||
U32 mKey;
|
||||
U32 mType; //< Console type code.
|
||||
bool mSet;
|
||||
union
|
||||
{
|
||||
bool mBool;
|
||||
S32 mS32;
|
||||
F32 mF32;
|
||||
const char* mString;
|
||||
} mValue;
|
||||
|
||||
SampleRecord() {}
|
||||
SampleRecord( U32 key )
|
||||
: mKey( key ), mSet( false ) {}
|
||||
|
||||
void set( bool value )
|
||||
{
|
||||
mType = TypeBool;
|
||||
mValue.mBool = value;
|
||||
mSet = true;
|
||||
}
|
||||
void set( S32 value )
|
||||
{
|
||||
mType = TypeS32;
|
||||
mValue.mS32 = value;
|
||||
mSet = true;
|
||||
}
|
||||
void set( F32 value )
|
||||
{
|
||||
mType = TypeF32;
|
||||
mValue.mF32 = value;
|
||||
mSet = true;
|
||||
}
|
||||
void set( const char* str )
|
||||
{
|
||||
mType = TypeString;
|
||||
mValue.mString = dStrdup( str );
|
||||
mSet = true;
|
||||
}
|
||||
|
||||
void clean()
|
||||
{
|
||||
if( mType == TypeString )
|
||||
dFree( ( void* ) mValue.mString );
|
||||
mSet = false;
|
||||
}
|
||||
};
|
||||
|
||||
FileStream mStream;
|
||||
Vector< SampleRecord > mRecords;
|
||||
|
||||
~CSVSamplerBackend()
|
||||
{
|
||||
mStream.close();
|
||||
}
|
||||
|
||||
/// Open the file and emit a row with the names of all enabled keys.
|
||||
virtual bool init( const char* fileName )
|
||||
{
|
||||
if( !mStream.open( fileName, Torque::FS::File::Write ) )
|
||||
{
|
||||
Con::errorf( "CSVSamplerBackend::init -- could not open '%s' for writing", fileName );
|
||||
return false;
|
||||
}
|
||||
|
||||
Con::printf( "CSVSamplerBackend::init -- writing samples to '%s'", fileName );
|
||||
|
||||
bool first = true;
|
||||
for( U32 i = 0; i < gSampleKeys.size(); ++ i )
|
||||
{
|
||||
SampleKey& key = gSampleKeys[ i ];
|
||||
if( key.mEnabled )
|
||||
{
|
||||
if( !first )
|
||||
mStream.write( 1, "," );
|
||||
|
||||
mRecords.push_back( SampleRecord( i + 1 ) );
|
||||
mStream.write( dStrlen( key.mName ), key.mName );
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
newline();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void beginFrame()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void endFrame()
|
||||
{
|
||||
char buffer[ 256 ];
|
||||
|
||||
for( U32 i = 0; i < mRecords.size(); ++ i )
|
||||
{
|
||||
if( i != 0 )
|
||||
mStream.write( 1, "," );
|
||||
|
||||
SampleRecord& record = mRecords[ i ];
|
||||
if( record.mSet )
|
||||
{
|
||||
if( record.mType == TypeBool )
|
||||
{
|
||||
if( record.mValue.mBool )
|
||||
mStream.write( 4, "true" );
|
||||
else
|
||||
mStream.write( 5, "false" );
|
||||
}
|
||||
else if( record.mType == TypeS32 )
|
||||
{
|
||||
dSprintf( buffer, sizeof( buffer ), "%d", record.mValue.mS32 );
|
||||
mStream.write( dStrlen( buffer ), buffer );
|
||||
}
|
||||
else if( record.mType == TypeF32 )
|
||||
{
|
||||
dSprintf( buffer, sizeof( buffer ), "%f", record.mValue.mF32 );
|
||||
mStream.write( dStrlen( buffer ), buffer );
|
||||
}
|
||||
else if( record.mType == TypeString )
|
||||
{
|
||||
//FIXME: does not do doubling of double quotes in the string at the moment
|
||||
mStream.write( 1, "\"" );
|
||||
mStream.write( dStrlen( record.mValue.mString ), record.mValue.mString );
|
||||
mStream.write( 1, "\"" );
|
||||
}
|
||||
else
|
||||
AssertWarn( false, "CSVSamplerBackend::endFrame - bug: invalid sample type" );
|
||||
}
|
||||
|
||||
record.clean();
|
||||
}
|
||||
|
||||
newline();
|
||||
}
|
||||
|
||||
void newline()
|
||||
{
|
||||
mStream.write( 1, "\n" );
|
||||
}
|
||||
|
||||
SampleRecord* lookup( U32 key )
|
||||
{
|
||||
//TODO: do this properly with a binary search (the mRecords array is already sorted by key)
|
||||
|
||||
for( U32 i = 0; i < mRecords.size(); ++ i )
|
||||
if( mRecords[ i ].mKey == key )
|
||||
return &mRecords[ i ];
|
||||
|
||||
AssertFatal( false, "CSVSamplerBackend::lookup - internal error: sample key not found" );
|
||||
return NULL; // silence compiler
|
||||
}
|
||||
|
||||
virtual void sample( U32 key, bool value )
|
||||
{
|
||||
lookup( key )->set( value );
|
||||
}
|
||||
virtual void sample( U32 key, S32 value )
|
||||
{
|
||||
lookup( key )->set( value );
|
||||
}
|
||||
virtual void sample( U32 key, F32 value )
|
||||
{
|
||||
lookup( key )->set( value );
|
||||
}
|
||||
virtual void sample( U32 key, const char* value )
|
||||
{
|
||||
lookup( key )->set( value );
|
||||
}
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Internal Functions.
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
static void stopSampling()
|
||||
{
|
||||
if( gSamplerRunning )
|
||||
{
|
||||
SAFE_DELETE( gSamplerBackend );
|
||||
gSamplerRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void beginSampling( const char* location, const char* backend )
|
||||
{
|
||||
if( gSamplerRunning )
|
||||
stopSampling();
|
||||
|
||||
if( dStricmp( backend, "CSV" ) == 0 )
|
||||
gSamplerBackend = new CSVSamplerBackend;
|
||||
else
|
||||
{
|
||||
Con::errorf( "beginSampling -- No backend called '%s'", backend );
|
||||
return;
|
||||
}
|
||||
|
||||
if( !gSamplerBackend->init( location ) )
|
||||
{
|
||||
SAFE_DELETE( gSamplerBackend );
|
||||
}
|
||||
else
|
||||
{
|
||||
gSamplerRunning = true;
|
||||
gCurrentFrameDelta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Sampler Functions.
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
void Sampler::init()
|
||||
{
|
||||
Con::addVariable( "Sampler::frequency", TypeS32, &gSamplingFrequency, "Samples taken every nth frame.\n"
|
||||
"@ingroup Rendering");
|
||||
}
|
||||
|
||||
void Sampler::beginFrame()
|
||||
{
|
||||
gCurrentFrameDelta ++;
|
||||
if( gSamplerBackend && gCurrentFrameDelta == gSamplingFrequency )
|
||||
gSamplerBackend->beginFrame();
|
||||
}
|
||||
|
||||
void Sampler::endFrame()
|
||||
{
|
||||
if( gSamplerBackend && gCurrentFrameDelta == gSamplingFrequency )
|
||||
{
|
||||
gSamplerBackend->endFrame();
|
||||
gCurrentFrameDelta = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Sampler::destroy()
|
||||
{
|
||||
if( gSamplerBackend )
|
||||
SAFE_DELETE( gSamplerBackend );
|
||||
}
|
||||
|
||||
U32 Sampler::registerKey( const char* name )
|
||||
{
|
||||
gSampleKeys.push_back( SampleKey() );
|
||||
U32 index = gSampleKeys.size();
|
||||
SampleKey& key = gSampleKeys.last();
|
||||
|
||||
key.mName = name;
|
||||
key.mEnabled = false;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void Sampler::enableKeys( const char* pattern, bool state )
|
||||
{
|
||||
if( gSamplerRunning )
|
||||
{
|
||||
Con::errorf( "Sampler::enableKeys -- cannot change key states while sampling" );
|
||||
return;
|
||||
}
|
||||
|
||||
for( U32 i = 0; i < gSampleKeys.size(); ++ i )
|
||||
if( gSampleKeys[ i ].matchesPattern( pattern ) )
|
||||
{
|
||||
gSampleKeys[ i ].mEnabled = state;
|
||||
Con::printf( "Sampler::enableKeys -- %s %s", state ? "enabling" : "disabling",
|
||||
gSampleKeys[ i ].mName );
|
||||
}
|
||||
}
|
||||
|
||||
#define SAMPLE_FUNC( type ) \
|
||||
void Sampler::sample( U32 key, type value ) \
|
||||
{ \
|
||||
if( gSamplerRunning \
|
||||
&& gCurrentFrameDelta == gSamplingFrequency \
|
||||
&& gSampleKeys[ key - 1 ].mEnabled ) \
|
||||
gSamplerBackend->sample( key, value ); \
|
||||
}
|
||||
|
||||
SAMPLE_FUNC( bool );
|
||||
SAMPLE_FUNC( S32 );
|
||||
SAMPLE_FUNC( F32 );
|
||||
SAMPLE_FUNC( const char* );
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
// Console Functions.
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
ConsoleFunction( beginSampling, void, 2, 3, "(location, [backend]) -"
|
||||
"@brief Takes a string informing the backend where to store "
|
||||
"sample data and optionally a name of the specific logging "
|
||||
"backend to use. The default is the CSV backend. In most "
|
||||
"cases, the logging store will be a file name."
|
||||
"@tsexample\n"
|
||||
"beginSampling( \"mysamples.csv\" );\n"
|
||||
"@endtsexample\n\n"
|
||||
"@ingroup Rendering")
|
||||
{
|
||||
const char* location = argv[ 1 ];
|
||||
const char* backend = "CSV";
|
||||
if( argc > 2 )
|
||||
backend = argv[ 2 ];
|
||||
|
||||
beginSampling( location, backend );
|
||||
}
|
||||
|
||||
ConsoleFunction( stopSampling, void, 1, 1, "()"
|
||||
"@brief Stops the rendering sampler\n\n"
|
||||
"@ingroup Rendering\n")
|
||||
{
|
||||
stopSampling();
|
||||
}
|
||||
|
||||
ConsoleFunction( enableSamples, void, 2, 3, "(pattern, [state]) -"
|
||||
"@brief Enable sampling for all keys that match the given name "
|
||||
"pattern. Slashes are treated as separators.\n\n"
|
||||
"@ingroup Rendering")
|
||||
{
|
||||
const char* pattern = argv[ 1 ];
|
||||
bool state = true;
|
||||
if( argc > 2 )
|
||||
state = dAtob( argv[ 2 ] );
|
||||
|
||||
Sampler::enableKeys( pattern, state );
|
||||
}
|
||||
146
Engine/source/util/sampler.h
Normal file
146
Engine/source/util/sampler.h
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _SAMPLER_H_
|
||||
#define _SAMPLER_H_
|
||||
|
||||
#include "platform/types.h"
|
||||
|
||||
/// The sampling framework.
|
||||
///
|
||||
/// Sampling allows per-frame snaphots of specific values to be logged. For
|
||||
/// each value that you want to have sampled, you define a sampling key and
|
||||
/// then simply call the sample function at an appropriate place. If you
|
||||
/// want to sample the same value multiple times within a single frame, simply
|
||||
/// register several keys for it.
|
||||
///
|
||||
/// The easiest way to use this facility is with the SAMPLE macro.
|
||||
///
|
||||
/// @code
|
||||
/// SAMPLE( "my/sample/value", my.sample->val );
|
||||
/// @endcode
|
||||
///
|
||||
/// @section SamplerUsage Using the Sampler
|
||||
///
|
||||
/// Before you use the sampler it is important that you let your game run for
|
||||
/// some frames and make sure that all relevant code paths have been touched (i.e.
|
||||
/// if you want to sample Atlas data, have an Atlas instance on screen). This
|
||||
/// will ensure that sampling keys are registered with the sampler.
|
||||
///
|
||||
/// Then use the console to first enable the keys you are interested in. For
|
||||
/// example, to enable sampling for all Atlas keys:
|
||||
///
|
||||
/// @code
|
||||
/// enableSamples( "atlas/*" );
|
||||
/// @endcode
|
||||
///
|
||||
/// Finally, you have to start the actual sampling. This is achieved with the
|
||||
/// beginSampling console function that takes a string informing the backend
|
||||
/// where to store sample data and optionally a name of the specific logging backend
|
||||
/// to use. The default is the CSV backend. In most cases, the logging store
|
||||
/// will be a file name.
|
||||
///
|
||||
/// @code
|
||||
/// beginSampling( "mysamples.csv" );
|
||||
/// @endcode
|
||||
///
|
||||
/// To stop sampling, use:
|
||||
///
|
||||
/// @code
|
||||
/// stopSampling();
|
||||
/// @endcode
|
||||
///
|
||||
/// @section Sample Keys
|
||||
///
|
||||
/// Sample key name should generally follow the pattern "path/to/group/samplename".
|
||||
/// This allows to very easily enable or disable specific sets of keys using
|
||||
/// wildcards.
|
||||
///
|
||||
/// Note that sampling keys are case-insensitive.
|
||||
|
||||
namespace Sampler
|
||||
{
|
||||
void init();
|
||||
void destroy();
|
||||
|
||||
void beginFrame();
|
||||
void endFrame();
|
||||
|
||||
void sample( U32 key, bool value );
|
||||
void sample( U32 key, S32 value );
|
||||
void sample( U32 key, F32 value );
|
||||
void sample( U32 key, const char* value );
|
||||
|
||||
inline void sample( U32 key, U32 value )
|
||||
{
|
||||
sample( key, S32( value ) );
|
||||
}
|
||||
|
||||
/// Register a new sample key.
|
||||
///
|
||||
/// @note Note that all keys are disabled by default.
|
||||
U32 registerKey( const char* name );
|
||||
|
||||
/// Enable sampling for all keys that match the given name
|
||||
/// pattern. Slashes are treated as separators.
|
||||
void enableKeys( const char* pattern, bool state = true );
|
||||
};
|
||||
|
||||
#ifdef TORQUE_ENABLE_SAMPLING
|
||||
# define SAMPLE( name, value ) \
|
||||
{ \
|
||||
static U32 key; \
|
||||
if( !key ) \
|
||||
key = Sampler::registerKey( name ); \
|
||||
Sampler::sample( key, value ); \
|
||||
}
|
||||
#else
|
||||
# define SAMPLE( name, value )
|
||||
#endif
|
||||
|
||||
#define SAMPLE_VECTOR( name, value ) \
|
||||
{ \
|
||||
SAMPLE( name "/x", value.x ); \
|
||||
SAMPLE( name "/y", value.y ); \
|
||||
SAMPLE( name "/z", value.z ); \
|
||||
}
|
||||
|
||||
#define SAMPLE_MATRIX( name, value ) \
|
||||
{ \
|
||||
SAMPLE( name "/a1", value[ value.idx( 0, 0 ) ] ); \
|
||||
SAMPLE( name "/a2", value[ value.idx( 1, 0 ) ] ); \
|
||||
SAMPLE( name "/a3", value[ value.idx( 2, 0 ) ] ); \
|
||||
SAMPLE( name "/a4", value[ value.idx( 3, 0 ) ] ); \
|
||||
SAMPLE( name "/b1", value[ value.idx( 0, 1 ) ] ); \
|
||||
SAMPLE( name "/b2", value[ value.idx( 1, 1 ) ] ); \
|
||||
SAMPLE( name "/b3", value[ value.idx( 2, 1 ) ] ); \
|
||||
SAMPLE( name "/b4", value[ value.idx( 3, 1 ) ] ); \
|
||||
SAMPLE( name "/c1", value[ value.idx( 0, 2 ) ] ); \
|
||||
SAMPLE( name "/c2", value[ value.idx( 1, 2 ) ] ); \
|
||||
SAMPLE( name "/c3", value[ value.idx( 2, 2 ) ] ); \
|
||||
SAMPLE( name "/c4", value[ value.idx( 3, 2 ) ] ); \
|
||||
SAMPLE( name "/d1", value[ value.idx( 0, 3 ) ] ); \
|
||||
SAMPLE( name "/d2", value[ value.idx( 1, 3 ) ] ); \
|
||||
SAMPLE( name "/d3", value[ value.idx( 2, 3 ) ] ); \
|
||||
SAMPLE( name "/d4", value[ value.idx( 3, 3 ) ] ); \
|
||||
}
|
||||
|
||||
#endif // _SAMPLER_H_
|
||||
873
Engine/source/util/scopeTracker.h
Normal file
873
Engine/source/util/scopeTracker.h
Normal file
|
|
@ -0,0 +1,873 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _SCOPETRACKER_H_
|
||||
#define _SCOPETRACKER_H_
|
||||
|
||||
#ifndef _TYPETRAITS_H_
|
||||
#include "platform/typetraits.h"
|
||||
#endif
|
||||
#ifndef _BITSET_H_
|
||||
#include "core/bitSet.h"
|
||||
#endif
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/util/tVector.h"
|
||||
#endif
|
||||
#ifndef _CONSOLE_H_
|
||||
#include "console/console.h"
|
||||
#endif
|
||||
#ifndef _PROFILER_H_
|
||||
#include "platform/profiler.h"
|
||||
#endif
|
||||
|
||||
//#define DEBUG_SPEW
|
||||
|
||||
|
||||
/// @file
|
||||
/// A mechanism for continuous tracking of point/box intersections.
|
||||
|
||||
|
||||
/// Base class for objects registered with a ScopeTracker.
|
||||
template< int NUM_DIMENSIONS >
|
||||
class ScopeTrackerObject
|
||||
{
|
||||
public:
|
||||
|
||||
typedef void Parent;
|
||||
|
||||
/// TrackingNodes are used to track object bounds along individual world axes.
|
||||
class TrackingNode
|
||||
{
|
||||
public:
|
||||
|
||||
typedef void Parent;
|
||||
|
||||
enum EFlags
|
||||
{
|
||||
FLAG_Min = BIT( 0 ),
|
||||
FLAG_Max = BIT( 1 ),
|
||||
FLAG_Reference = BIT( 2 ),
|
||||
};
|
||||
|
||||
///
|
||||
BitSet32 mFlags;
|
||||
|
||||
///
|
||||
TrackingNode* mOpposite;
|
||||
|
||||
/// Distance along axis.
|
||||
F32 mPosition;
|
||||
|
||||
/// The object being tracked by this node or NULL.
|
||||
ScopeTrackerObject* mObject;
|
||||
|
||||
/// Next node on axis tracking chain.
|
||||
TrackingNode* mNext;
|
||||
|
||||
/// Previous node on axis tracking chain.
|
||||
TrackingNode* mPrev;
|
||||
|
||||
///
|
||||
TrackingNode()
|
||||
: mPosition( 0.0f ), mObject( NULL ), mNext( NULL ), mPrev( NULL ), mOpposite( NULL ) {}
|
||||
|
||||
/// Return the object to which this tracking node belongs.
|
||||
ScopeTrackerObject* getObject() const { return mObject; }
|
||||
|
||||
///
|
||||
TrackingNode* getOpposite() const { return mOpposite; }
|
||||
|
||||
///
|
||||
F32 getPosition() const { return mPosition; }
|
||||
|
||||
///
|
||||
void setPosition( F32 value ) { mPosition = value; }
|
||||
|
||||
///
|
||||
TrackingNode* getNext() const { return mNext; }
|
||||
|
||||
///
|
||||
void setNext( TrackingNode* node ) { mNext = node; }
|
||||
|
||||
///
|
||||
TrackingNode* getPrev() const { return mPrev; }
|
||||
|
||||
///
|
||||
void setPrev( TrackingNode* node ) { mPrev = node; }
|
||||
|
||||
/// Return true if this is left/lower bound node of an object.
|
||||
bool isMin() const { return mFlags.test( FLAG_Min ); }
|
||||
|
||||
/// Return true if this is the right/upper bound node of an object.
|
||||
bool isMax() const { return mFlags.test( FLAG_Max ); }
|
||||
|
||||
/// Return true if this is the reference center tracking node. There will only
|
||||
/// ever be one such node on each tracking list.
|
||||
bool isReference() const { return mFlags.test( FLAG_Reference ); }
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
AllInScope = ( 0x01010101 >> ( ( 4 - NUM_DIMENSIONS ) * 8 ) )
|
||||
};
|
||||
|
||||
protected:
|
||||
|
||||
///
|
||||
union
|
||||
{
|
||||
U8 mBytes[ 4 ];
|
||||
U32 mDWord;
|
||||
} mScopeMask;
|
||||
|
||||
///
|
||||
TrackingNode mTrackingNodes[ NUM_DIMENSIONS ][ 2 ];
|
||||
|
||||
public:
|
||||
|
||||
///
|
||||
ScopeTrackerObject( U32 flags = 0 )
|
||||
{
|
||||
clearScopeMask();
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
{
|
||||
TrackingNode* minNode = getMinTrackingNode( n );
|
||||
TrackingNode* maxNode = getMaxTrackingNode( n );
|
||||
|
||||
minNode->mFlags = flags;
|
||||
maxNode->mFlags = flags;
|
||||
|
||||
minNode->mObject = this;
|
||||
maxNode->mObject = this;
|
||||
|
||||
minNode->mOpposite = maxNode;
|
||||
maxNode->mOpposite = minNode;
|
||||
|
||||
minNode->mFlags.set( TrackingNode::FLAG_Min );
|
||||
maxNode->mFlags.set( TrackingNode::FLAG_Max );
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if the object is currently being tracked.
|
||||
bool isRegistered() const
|
||||
{
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
if( getMinTrackingNode( n )->getNext() != NULL )
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Return true if the reference center lies within the object bound's on all axes.
|
||||
bool isInScope() const { return ( mScopeMask.mDWord == AllInScope ); }
|
||||
|
||||
///
|
||||
bool isInScope( U32 dimension ) const { return mScopeMask.mBytes[ dimension ]; }
|
||||
|
||||
///
|
||||
void setInScope( U32 dimension, bool state ) { mScopeMask.mBytes[ dimension ] = ( state ? 1 : 0 ); }
|
||||
|
||||
///
|
||||
void clearScopeMask() { mScopeMask.mDWord = 0; }
|
||||
|
||||
///
|
||||
TrackingNode* getMinTrackingNode( U32 dimension ) { return &mTrackingNodes[ dimension ][ 0 ]; }
|
||||
const TrackingNode* getMinTrackingNode( U32 dimension ) const { return &mTrackingNodes[ dimension ][ 0 ]; }
|
||||
|
||||
///
|
||||
TrackingNode* getMaxTrackingNode( U32 dimension ) { return &mTrackingNodes[ dimension ][ 1 ]; }
|
||||
const TrackingNode* getMaxTrackingNode( U32 dimension ) const { return &mTrackingNodes[ dimension ][ 1 ]; }
|
||||
|
||||
/// @name Implementor Interface
|
||||
///
|
||||
/// The following methods must be implemented by the client. They are defined here
|
||||
/// just for reference. If you don't override them, you'll get link errors.
|
||||
///
|
||||
/// @{
|
||||
|
||||
/// Return the position of the object in world-space.
|
||||
void getPosition( F32 pos[ NUM_DIMENSIONS ] ) const;
|
||||
|
||||
/// If this object is the reference object, this method should return the world-space pivot
|
||||
/// point in the object that will be the world reference center.
|
||||
void getReferenceCenter( F32 pos[ NUM_DIMENSIONS ] ) const;
|
||||
|
||||
/// Return the object's bounding box in world-space.
|
||||
void getBounds( F32 minBounds[ NUM_DIMENSIONS ], F32 maxBounds[ NUM_DIMENSIONS ] ) const;
|
||||
|
||||
///
|
||||
String describeSelf() const;
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
|
||||
/// Helper class to track the position of a point in N-dimensional space relative to a collection
|
||||
/// of N-dimensional volumes.
|
||||
///
|
||||
/// This class works by breaking the N-dimensional case down into N one-dimensional cases. By tracking
|
||||
/// objects independently along each of their axes, intersection testing becomes a trivial matter of
|
||||
/// doing point-on-line tests. The line segments can be conveniently represented as ordered linked
|
||||
/// lists along which the reference point is being moved.
|
||||
///
|
||||
/// To determine N-dimensional containment of the reference point, the result of each of the one-dimensional
|
||||
/// point-on-line tests simply has to be combined. If the point lies on each of N 1D segments of a
|
||||
/// given volume, then the point is fully contained in the volume.
|
||||
///
|
||||
/// This class may be used in places where otherwise a spatial subdivision scheme would be necessary in
|
||||
/// order to limit the number of containment tests triggered by each movement of the reference point.
|
||||
/// Once the tracker has been set up, each position change of an object or the reference center will result
|
||||
/// in a usually small number of incremental list updates.
|
||||
///
|
||||
/// Another advantage is that this class makes it easy to reduce 3D tracking to 2D tracking if tracking on
|
||||
/// the height axis isn't important.
|
||||
///
|
||||
/// The following interface must be implemented by the given "Object" type:
|
||||
///
|
||||
/// @code
|
||||
/// struct Object : public ScopeTrackerObject< NUM_DIMENSIONS >
|
||||
/// {
|
||||
/// /// Return the position of the object in world-space.
|
||||
/// void getPosition( F32 pos[ NUM_DIMENSIONS ] ) const;
|
||||
///
|
||||
/// /// If this object is the reference object, this method should return the world-space pivot
|
||||
/// /// point in the object that will be the world reference center.
|
||||
/// void getReferenceCenter( F32 pos[ NUM_DIMENSIONS ] ) const;
|
||||
///
|
||||
/// /// Return the object's bounding box in world-space.
|
||||
/// void getBounds( F32 minBounds[ NUM_DIMENSIONS ], F32 maxBounds[ NUM_DIMENSIONS ] ) const;
|
||||
/// };
|
||||
/// @endcode
|
||||
///
|
||||
/// Terminology:
|
||||
///
|
||||
/// - "In Scope": A volume is in scope if it fully contains the reference center.
|
||||
/// - "Reference Object": Object that is the designated center of the world.
|
||||
///
|
||||
/// @param NUM_DIMENSIONS Number of dimensions to track; must be <=4.
|
||||
/// @param Object Value type for objects tracked by the ScopeTracker. Must have pointer behavior.
|
||||
template< int NUM_DIMENSIONS, typename Object >
|
||||
class ScopeTracker
|
||||
{
|
||||
public:
|
||||
|
||||
typedef void Parent;
|
||||
typedef typename TypeTraits< Object >::BaseType ObjectType;
|
||||
typedef typename ObjectType::TrackingNode NodeType;
|
||||
|
||||
protected:
|
||||
|
||||
enum
|
||||
{
|
||||
MIN = 0,
|
||||
MAX = 1
|
||||
};
|
||||
|
||||
/// The reference object. This is the center relative to which all
|
||||
/// tracking occurs. Any other object is in scope when it contains the
|
||||
/// reference object.
|
||||
Object mReferenceObject;
|
||||
|
||||
///
|
||||
NodeType* mTrackingList[ NUM_DIMENSIONS ][ 2 ];
|
||||
|
||||
///
|
||||
NodeType mBoundaryNodes[ NUM_DIMENSIONS ][ 2 ];
|
||||
|
||||
///
|
||||
Vector< Object > mPotentialScopeInObjects;
|
||||
|
||||
/// @name Scoping
|
||||
/// @{
|
||||
|
||||
virtual void _onScopeIn( Object object ) {}
|
||||
|
||||
virtual void _onScopeOut( Object object ) {}
|
||||
|
||||
/// Set the scoping state of the given object.
|
||||
void _setScope( Object object );
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Tracking
|
||||
/// @{
|
||||
|
||||
///
|
||||
void _insertTrackingNode( U32 dimension, NodeType* node );
|
||||
|
||||
///
|
||||
void _removeTrackingNode( U32 dimension, NodeType* node );
|
||||
|
||||
///
|
||||
void _moveTrackingNode( U32 dimension, NodeType* node, F32 newPos );
|
||||
|
||||
///
|
||||
void _initTracking();
|
||||
|
||||
///
|
||||
void _uninitTracking();
|
||||
|
||||
/// @}
|
||||
|
||||
public:
|
||||
|
||||
///
|
||||
ScopeTracker();
|
||||
|
||||
/// Add a volume object to the world.
|
||||
void registerObject( Object object );
|
||||
|
||||
/// Remove a volume object from the world.
|
||||
void unregisterObject( Object object );
|
||||
|
||||
/// Update the position of the object in the world.
|
||||
void updateObject( Object object );
|
||||
|
||||
///
|
||||
Object getReferenceObject() const { return mReferenceObject; }
|
||||
|
||||
///
|
||||
///
|
||||
/// @note Switching reference centers is potentially costly.
|
||||
void setReferenceObject( Object object );
|
||||
|
||||
///
|
||||
void debugDump();
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
ScopeTracker< NUM_DIMENSIONS, Object >::ScopeTracker()
|
||||
: mReferenceObject( NULL )
|
||||
{
|
||||
VECTOR_SET_ASSOCIATION( mPotentialScopeInObjects );
|
||||
|
||||
// Initialize the tracking lists. Put the boundary
|
||||
// nodes in place that will always be the heads and tails
|
||||
// of each list.
|
||||
|
||||
dMemset( mTrackingList, 0, sizeof( mTrackingList ) );
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
{
|
||||
mBoundaryNodes[ n ][ MIN ].setPosition( TypeTraits< F32 >::MIN );
|
||||
mBoundaryNodes[ n ][ MAX ].setPosition( TypeTraits< F32 >::MAX );
|
||||
|
||||
mBoundaryNodes[ n ][ MIN ].setNext( &mBoundaryNodes[ n ][ MAX ] );
|
||||
mBoundaryNodes[ n ][ MAX ].setPrev( &mBoundaryNodes[ n ][ MIN ] );
|
||||
|
||||
mBoundaryNodes[ n ][ MIN ].mOpposite = &mBoundaryNodes[ n ][ MAX ];
|
||||
mBoundaryNodes[ n ][ MAX ].mOpposite = &mBoundaryNodes[ n ][ MIN ];
|
||||
|
||||
mTrackingList[ n ][ MIN ] = &mBoundaryNodes[ n ][ MIN ];
|
||||
mTrackingList[ n ][ MAX ] = &mBoundaryNodes[ n ][ MAX ];
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::setReferenceObject( Object object )
|
||||
{
|
||||
AssertFatal( !object || !Deref( object ).isRegistered(),
|
||||
"ScopeTracker::setReferenceObject - reference object must not be volume object" );
|
||||
|
||||
if( mReferenceObject == object )
|
||||
return;
|
||||
|
||||
// If object is invalid, remove the reference center
|
||||
// tracking.
|
||||
|
||||
if( !object )
|
||||
{
|
||||
// Transition all objects to out-of-scope and
|
||||
// deactivate tracking.
|
||||
|
||||
_uninitTracking();
|
||||
mReferenceObject = object;
|
||||
return;
|
||||
}
|
||||
|
||||
if( mReferenceObject )
|
||||
{
|
||||
//RDFIXME: this is very disruptive
|
||||
|
||||
// We have an existing reference object so we need to update
|
||||
// the scoping to match it. Brute-force this for now.
|
||||
|
||||
_uninitTracking();
|
||||
mReferenceObject = object;
|
||||
_initTracking();
|
||||
}
|
||||
else
|
||||
{
|
||||
// No reference object yet.
|
||||
|
||||
mReferenceObject = object;
|
||||
_initTracking();
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SPEW
|
||||
Platform::outputDebugString( "[ScopeTracker] Reference object is now 0x%x", object );
|
||||
#endif
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::registerObject( Object object )
|
||||
{
|
||||
PROFILE_SCOPE( ScopeTracker_registerObject );
|
||||
|
||||
// Get the object bounds.
|
||||
|
||||
F32 minBounds[ NUM_DIMENSIONS ];
|
||||
F32 maxBounds[ NUM_DIMENSIONS ];
|
||||
|
||||
Deref( object ).getBounds( minBounds, maxBounds );
|
||||
|
||||
// Insert the object's tracking nodes.
|
||||
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
{
|
||||
NodeType* minNode = Deref( object ).getMinTrackingNode( n );
|
||||
NodeType* maxNode = Deref( object ).getMaxTrackingNode( n );
|
||||
|
||||
minNode->setPosition( minBounds[ n ] );
|
||||
maxNode->setPosition( maxBounds[ n ] );
|
||||
|
||||
// Insert max before min so that max always comes out
|
||||
// to the right of min.
|
||||
|
||||
_insertTrackingNode( n, maxNode );
|
||||
_insertTrackingNode( n, minNode );
|
||||
}
|
||||
|
||||
// Set the scoping state of the object.
|
||||
|
||||
_setScope( object );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::unregisterObject( Object object )
|
||||
{
|
||||
PROFILE_SCOPE( ScopeTracker_unregisterObject );
|
||||
|
||||
if( !Deref( object ).isRegistered() )
|
||||
return;
|
||||
|
||||
// Clear its scoping state.
|
||||
|
||||
if( Deref( object ).isInScope() )
|
||||
_onScopeOut( object );
|
||||
Deref( object ).clearScopeMask();
|
||||
|
||||
// Remove the tracking state.
|
||||
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
{
|
||||
_removeTrackingNode( n, Deref( object ).getMinTrackingNode( n ) );
|
||||
_removeTrackingNode( n, Deref( object ).getMaxTrackingNode( n ) );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::updateObject( Object object )
|
||||
{
|
||||
PROFILE_SCOPE( ScopeTracker_updateObject );
|
||||
|
||||
if( object == mReferenceObject )
|
||||
{
|
||||
// Get the reference center position.
|
||||
|
||||
F32 position[ NUM_DIMENSIONS ];
|
||||
Deref( mReferenceObject ).getReferenceCenter( position );
|
||||
|
||||
// Move the reference tracking node.
|
||||
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
_moveTrackingNode( n, Deref( mReferenceObject ).getMinTrackingNode( n ), position[ n ] );
|
||||
|
||||
// Flush the potential-scope-in list.
|
||||
|
||||
while( !mPotentialScopeInObjects.empty() )
|
||||
{
|
||||
Object object = mPotentialScopeInObjects.last();
|
||||
mPotentialScopeInObjects.decrement();
|
||||
|
||||
if( Deref( object ).isInScope() )
|
||||
_onScopeIn( object );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Get the object bounds.
|
||||
|
||||
F32 minBounds[ NUM_DIMENSIONS ];
|
||||
F32 maxBounds[ NUM_DIMENSIONS ];
|
||||
|
||||
Deref( object ).getBounds( minBounds, maxBounds );
|
||||
|
||||
// Move the object's tracking nodes.
|
||||
|
||||
bool wasInScope = Deref( object ).isInScope();
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
{
|
||||
NodeType* minNode = Deref( object ).getMinTrackingNode( n );
|
||||
NodeType* maxNode = Deref( object ).getMaxTrackingNode( n );
|
||||
|
||||
_moveTrackingNode( n, minNode, minBounds[ n ] );
|
||||
_moveTrackingNode( n, maxNode, maxBounds[ n ] );
|
||||
}
|
||||
|
||||
// Rescope the object, if necessary.
|
||||
|
||||
if( wasInScope && !Deref( object ).isInScope() )
|
||||
_onScopeOut( object );
|
||||
else if( !wasInScope && Deref( object ).isInScope() )
|
||||
_onScopeIn( object );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::_insertTrackingNode( U32 dimension, NodeType* node )
|
||||
{
|
||||
//RDTODO: substitute brute-force search with some smarter insertion algorithm
|
||||
// (at least dynamically decide on direction for search)
|
||||
|
||||
F32 pos = node->getPosition();
|
||||
NodeType* current = mTrackingList[ dimension ][ MIN ]->getNext();
|
||||
NodeType* prev = mTrackingList[ dimension ][ MIN ];
|
||||
|
||||
while( current->getPosition() < pos )
|
||||
{
|
||||
prev = current;
|
||||
current = current->getNext();
|
||||
}
|
||||
|
||||
prev->setNext( node );
|
||||
current->setPrev( node );
|
||||
|
||||
node->setPrev( prev );
|
||||
node->setNext( current );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::_removeTrackingNode( U32 dimension, NodeType* node )
|
||||
{
|
||||
NodeType* next = node->getNext();
|
||||
NodeType* prev = node->getPrev();
|
||||
|
||||
AssertFatal( next != NULL, "ScopeTracker::_insertTrackingNode - invalid list state (no next node)!" );
|
||||
AssertFatal( prev != NULL, "ScopeTracker::_insertTrackingNode - invalid list state (no prev node)!" );
|
||||
|
||||
next->setPrev( prev );
|
||||
prev->setNext( next );
|
||||
|
||||
node->setNext( NULL );
|
||||
node->setPrev( NULL );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::_moveTrackingNode( U32 dimension, NodeType* node, F32 newPosition )
|
||||
{
|
||||
PROFILE_SCOPE( ScopeTracker_moveTrackingNode );
|
||||
|
||||
AssertFatal( TypeTraits< F32 >::MIN <= newPosition && newPosition <= TypeTraits< F32 >::MAX, "Invalid float in object coordinate!" );
|
||||
|
||||
enum EDirection
|
||||
{
|
||||
DIRECTION_Up,
|
||||
DIRECTION_Down
|
||||
};
|
||||
|
||||
// Determine in which direction we are sliding the node.
|
||||
|
||||
EDirection direction;
|
||||
if( newPosition < node->getPosition() )
|
||||
{
|
||||
direction = DIRECTION_Down;
|
||||
if( node->getPrev()->getPosition() <= newPosition )
|
||||
{
|
||||
node->setPosition( newPosition );
|
||||
return; // Nothing to do.
|
||||
}
|
||||
}
|
||||
else if( newPosition > node->getPosition() )
|
||||
{
|
||||
direction = DIRECTION_Up;
|
||||
if( node->getNext()->getPosition() >= newPosition )
|
||||
{
|
||||
node->setPosition( newPosition );
|
||||
return; // Nothing to do.
|
||||
}
|
||||
}
|
||||
else
|
||||
return; // Nothing to to.
|
||||
|
||||
const bool isReferenceNode = node->isReference();
|
||||
|
||||
// Unlink the node.
|
||||
|
||||
NodeType* next = node->getNext();
|
||||
NodeType* prev = node->getPrev();
|
||||
|
||||
next->setPrev( prev );
|
||||
prev->setNext( next );
|
||||
|
||||
// Iterate through to the node's new position.
|
||||
|
||||
while( ( direction == DIRECTION_Up && next->getPosition() < newPosition )
|
||||
|| ( direction == DIRECTION_Down && prev->getPosition() > newPosition ) )
|
||||
{
|
||||
NodeType* current = 0;
|
||||
switch( direction )
|
||||
{
|
||||
case DIRECTION_Up: current = next; break;
|
||||
case DIRECTION_Down: current = prev; break;
|
||||
}
|
||||
|
||||
if( isReferenceNode )
|
||||
{
|
||||
Object object = ( Object ) current->getObject();
|
||||
if( ( direction == DIRECTION_Up && current->isMin() )
|
||||
|| ( direction == DIRECTION_Down && current->isMax() ) )
|
||||
{
|
||||
Deref( object ).setInScope( dimension, true );
|
||||
mPotentialScopeInObjects.push_back( object );
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool wasInScope = Deref( object ).isInScope();
|
||||
Deref( object ).setInScope( dimension, false );
|
||||
if( wasInScope )
|
||||
_onScopeOut( object );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( current->isReference() )
|
||||
{
|
||||
Object object = ( Object ) node->getObject();
|
||||
if( ( direction == DIRECTION_Up && node->isMin() )
|
||||
|| ( direction == DIRECTION_Down && node->isMax() ) )
|
||||
Deref( object ).setInScope( dimension, false );
|
||||
else
|
||||
Deref( object ).setInScope( dimension, true );
|
||||
}
|
||||
}
|
||||
|
||||
switch( direction )
|
||||
{
|
||||
case DIRECTION_Down:
|
||||
next = current;
|
||||
prev = current->getPrev();
|
||||
break;
|
||||
|
||||
case DIRECTION_Up:
|
||||
prev = current;
|
||||
next = current->getNext();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Relink the node.
|
||||
|
||||
prev->setNext( node );
|
||||
next->setPrev( node );
|
||||
|
||||
node->setPrev( prev );
|
||||
node->setNext( next );
|
||||
|
||||
node->setPosition( newPosition );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::_setScope( Object object )
|
||||
{
|
||||
// If there's no reference object, all objects are out of scope.
|
||||
|
||||
if( !mReferenceObject || object == mReferenceObject )
|
||||
{
|
||||
Deref( object ).clearScopeMask();
|
||||
return;
|
||||
}
|
||||
|
||||
const bool wasInScope = Deref( object ).isInScope();
|
||||
|
||||
// Set the scoping state on each axis.
|
||||
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
{
|
||||
const F32 referencePos = Deref( mReferenceObject ).getMinTrackingNode( n )->getPosition();
|
||||
const F32 objectMin = Deref( object ).getMinTrackingNode( n )->getPosition();
|
||||
const F32 objectMax = Deref( object ).getMaxTrackingNode( n )->getPosition();
|
||||
|
||||
bool isInScope = referencePos >= objectMin
|
||||
&& referencePos <= objectMax;
|
||||
|
||||
Deref( object ).setInScope( n, isInScope );
|
||||
}
|
||||
|
||||
// Scope in/out if the scoping state has changed.
|
||||
|
||||
if( Deref( object ).isInScope() )
|
||||
{
|
||||
if( !wasInScope )
|
||||
_onScopeIn( object );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( wasInScope )
|
||||
_onScopeOut( object );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::_initTracking()
|
||||
{
|
||||
PROFILE_SCOPE( ScopeTracker_initTracking );
|
||||
|
||||
AssertFatal( bool( getReferenceObject() ),
|
||||
"ScopeTracker::_initTracking - can only be called with a valid reference object" );
|
||||
|
||||
// Put a single tracking node onto each of the lists for
|
||||
// the reference object center.
|
||||
|
||||
F32 position[ NUM_DIMENSIONS ];
|
||||
Deref( mReferenceObject ).getReferenceCenter( position );
|
||||
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
{
|
||||
AssertFatal( TypeTraits< F32 >::MIN <= position[ n ] && position[ n ] <= TypeTraits< F32 >::MAX, "Invalid float in object coordinate!" );
|
||||
|
||||
NodeType* node = Deref( mReferenceObject ).getMinTrackingNode( n );
|
||||
node->mFlags.set( NodeType::FLAG_Reference );
|
||||
|
||||
node->setPosition( position[ n ] );
|
||||
|
||||
_insertTrackingNode( n, node );
|
||||
}
|
||||
|
||||
// Update the surroundings of the reference object
|
||||
// in the tracking lists for each dimension.
|
||||
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
{
|
||||
//TODO: this could be optimized by dynamically determining whether to walk upwards
|
||||
// or downwards depending on which span has fewer nodes; finding that out is not immediately
|
||||
// obvious, though
|
||||
|
||||
// Walk from the left bound node upwards until we reach the
|
||||
// reference object's marker. Everything that has its max node
|
||||
// past the reference object is in scope.
|
||||
|
||||
F32 referencePos = Deref( mReferenceObject ).getMinTrackingNode( n )->getPosition();
|
||||
for( NodeType* node = mTrackingList[ n ][ 0 ]->getNext(); node->getPosition() < referencePos; node = node->getNext() )
|
||||
if( !node->isMax() && node->getOpposite()->getPosition() > referencePos )
|
||||
{
|
||||
node->getObject()->setInScope( n, true );
|
||||
|
||||
// If this is the last dimension we're working on and
|
||||
// the current object is in-scope on all dimension,
|
||||
// promote to in-scope status.
|
||||
|
||||
if( n == ( NUM_DIMENSIONS - 1 ) && node->getObject()->isInScope() )
|
||||
_onScopeIn( ( Object ) node->getObject() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::_uninitTracking()
|
||||
{
|
||||
PROFILE_SCOPE( ScopeTracker_uninitTracking );
|
||||
|
||||
AssertFatal( bool( getReferenceObject() ),
|
||||
"ScopeTracker::_uninitTracking - can only be called with a valid reference object" );
|
||||
|
||||
// Put all objects currently in scope, out of scope.
|
||||
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
{
|
||||
U32 referencePos = Deref( mReferenceObject ).getMinTrackingNode( n )->getPosition();
|
||||
for( NodeType* node = mTrackingList[ n ][ 0 ]->getNext(); node->getPosition() < referencePos; node = node->getNext() )
|
||||
{
|
||||
if( node->getObject()->isInScope() )
|
||||
_onScopeOut( ( Object ) node->getObject() );
|
||||
node->getObject()->clearScopeMask();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the reference object's tracking nodes.
|
||||
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
_removeTrackingNode( n, Deref( mReferenceObject ).getMinTrackingNode( n ) );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
template< int NUM_DIMENSIONS, class Object >
|
||||
void ScopeTracker< NUM_DIMENSIONS, Object >::debugDump()
|
||||
{
|
||||
for( U32 n = 0; n < NUM_DIMENSIONS; ++ n )
|
||||
{
|
||||
Con::printf( "Dimension %i", n );
|
||||
Con::printf( "----------------" );
|
||||
|
||||
for( NodeType* node = mTrackingList[ n ][ 0 ]; node != NULL; node = node->getNext() )
|
||||
{
|
||||
String desc;
|
||||
if( node->getObject() )
|
||||
{
|
||||
Object object = ( Object ) node->getObject();
|
||||
desc = Deref( object ).describeSelf();
|
||||
}
|
||||
|
||||
Con::printf( "pos=%f, type=%s, scope=%s, object=%s",
|
||||
node->getPosition(),
|
||||
node->isReference() ? "reference" : node->isMin() ? "min" : "max",
|
||||
node->getObject() ? node->getObject()->isInScope( n ) ? "1" : "0" : "0",
|
||||
desc.c_str() );
|
||||
}
|
||||
|
||||
Con::printf( "" );
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !_SCOPETRACKER_H_
|
||||
734
Engine/source/util/settings.cpp
Normal file
734
Engine/source/util/settings.cpp
Normal file
|
|
@ -0,0 +1,734 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "util/settings.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/SimXMLDocument.h"
|
||||
|
||||
IMPLEMENT_CONOBJECT(Settings);
|
||||
|
||||
ConsoleDocClass( Settings,
|
||||
"@brief Class used for writing out preferences and settings for editors\n\n"
|
||||
"Not intended for game development, for editors or internal use only.\n\n "
|
||||
"@internal");
|
||||
|
||||
Settings::Settings()
|
||||
{
|
||||
mFile = "";
|
||||
mSearchPos = 0;
|
||||
}
|
||||
|
||||
Settings::~Settings()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Settings::initPersistFields()
|
||||
{
|
||||
addField("file", TypeStringFilename, Offset(mFile, Settings), "The file path and name to be saved to and loaded from.");
|
||||
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
void Settings::setDefaultValue(const UTF8 *settingName, const UTF8 *settingValue, const UTF8 *settingType)
|
||||
{
|
||||
String baseName;
|
||||
buildGroupString(baseName, settingName);
|
||||
String name = baseName + "_default";
|
||||
StringTableEntry nameEntry = StringTable->insert(name.c_str());
|
||||
String type = baseName + "_type";
|
||||
StringTableEntry typeEntry = StringTable->insert(type.c_str());
|
||||
|
||||
setModStaticFields(false);
|
||||
setDataField(nameEntry, NULL, settingValue);
|
||||
setDataField(typeEntry, NULL, settingType);
|
||||
setModStaticFields(true);
|
||||
}
|
||||
|
||||
void Settings::setValue(const UTF8 *settingName, const UTF8 *settingValue)
|
||||
{
|
||||
String name;
|
||||
buildGroupString(name, settingName);
|
||||
StringTableEntry nameEntry = StringTable->insert(name.c_str());
|
||||
|
||||
setModStaticFields(false);
|
||||
setDataField(nameEntry, NULL, settingValue);
|
||||
setModStaticFields(true);
|
||||
}
|
||||
|
||||
const UTF8 *Settings::value(const UTF8 *settingName, const UTF8 *defaultValue)
|
||||
{
|
||||
String name;
|
||||
buildGroupString(name, settingName);
|
||||
|
||||
StringTableEntry nameEntry = StringTable->insert(name.c_str());
|
||||
name += "_default";
|
||||
StringTableEntry defaultNameEntry = StringTable->insert(name.c_str());
|
||||
|
||||
// we do this setModStaticFields call to make sure our get/set calls
|
||||
// don't grab a regular field, don't want to stomp anything
|
||||
setModStaticFields(false);
|
||||
const UTF8 *value = getDataField(nameEntry, NULL);
|
||||
const UTF8 *storedDefaultValue = getDataField(defaultNameEntry, NULL);
|
||||
setModStaticFields(true);
|
||||
|
||||
if(dStrcmp(value, "") != 0)
|
||||
return value;
|
||||
else if(dStrcmp(storedDefaultValue, "") != 0)
|
||||
return storedDefaultValue;
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
void Settings::remove(const UTF8 *settingName, bool includeDefaults)
|
||||
{
|
||||
// Fetch Dynamic-Field Dictionary.
|
||||
SimFieldDictionary* pFieldDictionary = getFieldDictionary();
|
||||
|
||||
// Any Field Dictionary?
|
||||
if ( pFieldDictionary == NULL )
|
||||
{
|
||||
// No, so we're done.
|
||||
return;
|
||||
}
|
||||
|
||||
String name;
|
||||
buildGroupString(name, settingName);
|
||||
StringTableEntry nameEntry = StringTable->insert(name.c_str());
|
||||
StringTableEntry nameEntryDefault = StringTable->insert( String::ToString("%s%s",name.c_str(), "_default") );
|
||||
|
||||
// Iterate fields.
|
||||
for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
|
||||
{
|
||||
// Fetch Field Entry.
|
||||
SimFieldDictionary::Entry* fieldEntry = *itr;
|
||||
|
||||
// is this a field of our current group
|
||||
if ( (dStrcmp(nameEntry, "") == 0) ||
|
||||
dStrcmp( nameEntry, fieldEntry->slotName ) == 0 ||
|
||||
(includeDefaults && dStrcmp( nameEntryDefault, fieldEntry->slotName ) == 0) )
|
||||
{
|
||||
// Yes, so remove it.
|
||||
pFieldDictionary->setFieldValue( fieldEntry->slotName, "" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::buildGroupString(String &name, const UTF8 *settingName)
|
||||
{
|
||||
// here we want to loop through the stack and build a "/" seperated string
|
||||
// representing the entire current group stack that gets pre-pended to the
|
||||
// setting name passed in
|
||||
if(mGroupStack.size() > 0)
|
||||
{
|
||||
for(S32 i=0; i < mGroupStack.size(); i++)
|
||||
{
|
||||
S32 pos = 0;
|
||||
if(name.size() > 0)
|
||||
pos = name.size()-1;
|
||||
|
||||
// tack on the "/" in front if this isn't the first
|
||||
if(i == 0)
|
||||
{
|
||||
name.insert(pos, mGroupStack[i]);
|
||||
} else
|
||||
{
|
||||
name.insert(pos, "/");
|
||||
name.insert(pos+1, mGroupStack[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// tack on a final "/"
|
||||
name.insert(name.size()-1, "/");
|
||||
if(dStrlen(settingName) > 0)
|
||||
name.insert(name.size()-1, settingName);
|
||||
} else
|
||||
name = settingName;
|
||||
}
|
||||
|
||||
void Settings::clearAllFields()
|
||||
{
|
||||
// Fetch Dynamic-Field Dictionary.
|
||||
SimFieldDictionary* pFieldDictionary = getFieldDictionary();
|
||||
|
||||
// Any Field Dictionary?
|
||||
if ( pFieldDictionary == NULL )
|
||||
{
|
||||
// No, so we're done.
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate fields.
|
||||
for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
|
||||
{
|
||||
// Fetch Field Entry.
|
||||
SimFieldDictionary::Entry* fieldEntry = *itr;
|
||||
|
||||
// don't remove default field values
|
||||
if (dStrEndsWith(fieldEntry->slotName, "_default"))
|
||||
continue;
|
||||
|
||||
// remove it.
|
||||
pFieldDictionary->setFieldValue( fieldEntry->slotName, "" );
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::write()
|
||||
{
|
||||
// Fetch Dynamic-Field Dictionary.
|
||||
SimFieldDictionary* pFieldDictionary = getFieldDictionary();
|
||||
|
||||
// Any Field Dictionary?
|
||||
if ( pFieldDictionary == NULL )
|
||||
{
|
||||
// No, so we're done.
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
// Iterate fields.
|
||||
for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
|
||||
{
|
||||
// Fetch Field Entry.
|
||||
SimFieldDictionary::Entry* fieldEntry = *itr;
|
||||
|
||||
String check(fieldEntry->slotName);
|
||||
String::SizeType pos = check.find("_default");
|
||||
if(pos != String::NPos)
|
||||
continue;
|
||||
|
||||
// let's build our XML doc
|
||||
document->pushNewElement("dynamicField");
|
||||
document->setAttribute("name", fieldEntry->slotName);
|
||||
document->addText(fieldEntry->value);
|
||||
document->popElement();
|
||||
}
|
||||
*/
|
||||
SimXMLDocument *document = new SimXMLDocument();
|
||||
document->registerObject();
|
||||
document->addHeader();
|
||||
|
||||
document->pushNewElement(getName());
|
||||
|
||||
SettingSaveNode *node = new SettingSaveNode();
|
||||
// Iterate fields.
|
||||
for ( SimFieldDictionaryIterator itr(pFieldDictionary); *itr; ++itr )
|
||||
{
|
||||
// Fetch Field Entry.
|
||||
SimFieldDictionary::Entry* fieldEntry = *itr;
|
||||
|
||||
String check(fieldEntry->slotName);
|
||||
if(check.find("_default") != String::NPos || check.find("_type") != String::NPos)
|
||||
continue;
|
||||
|
||||
node->addValue(fieldEntry->slotName, fieldEntry->value);
|
||||
}
|
||||
|
||||
node->buildDocument(document, true);
|
||||
node->clear();
|
||||
delete node;
|
||||
|
||||
bool saved = document->saveFile(mFile.c_str());
|
||||
document->deleteObject();
|
||||
|
||||
if(saved)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Settings::read()
|
||||
{
|
||||
SimXMLDocument *document = new SimXMLDocument();
|
||||
document->registerObject();
|
||||
|
||||
bool success = true;
|
||||
if(document->loadFile(mFile.c_str()))
|
||||
{
|
||||
clearAllFields();
|
||||
|
||||
// set our base element
|
||||
if(document->pushFirstChildElement(getName()))
|
||||
{
|
||||
setModStaticFields(false);
|
||||
readLayer(document);
|
||||
setModStaticFields(true);
|
||||
}
|
||||
else
|
||||
success = false;
|
||||
}
|
||||
else
|
||||
success = false;
|
||||
|
||||
document->deleteObject();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void Settings::readLayer(SimXMLDocument *document, String groupStack)
|
||||
{
|
||||
for(S32 i=0; document->pushChildElement(i); i++)
|
||||
{
|
||||
bool groupCount = 0;
|
||||
const UTF8 *type = document->elementValue();
|
||||
const UTF8 *name = document->attribute("name");
|
||||
const UTF8 *value = document->getText();
|
||||
|
||||
if(dStrcmp(type, "Group") == 0)
|
||||
{
|
||||
String newStack = groupStack;
|
||||
|
||||
if(!groupStack.isEmpty())
|
||||
newStack += "/";
|
||||
|
||||
newStack += name;
|
||||
readLayer(document, newStack);
|
||||
groupCount++;
|
||||
} else if(dStrcmp(type, "Setting") == 0)
|
||||
{
|
||||
String nameString = groupStack;
|
||||
|
||||
if(!groupStack.isEmpty())
|
||||
nameString += "/";
|
||||
|
||||
nameString += name;
|
||||
setDataField(StringTable->insert(nameString.c_str()), NULL, value);
|
||||
}
|
||||
|
||||
document->popElement();
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::beginGroup(const UTF8 *groupName, bool fromStart)
|
||||
{
|
||||
// check if we want to clear the stack
|
||||
if(fromStart)
|
||||
clearGroups();
|
||||
|
||||
mGroupStack.push_back(String(groupName));
|
||||
}
|
||||
|
||||
void Settings::endGroup()
|
||||
{
|
||||
if(mGroupStack.size() > 0)
|
||||
mGroupStack.pop_back();
|
||||
}
|
||||
|
||||
void Settings::clearGroups()
|
||||
{
|
||||
mGroupStack.clear();
|
||||
}
|
||||
|
||||
const UTF8 *Settings::getCurrentGroups()
|
||||
{
|
||||
// we want to return a string with our group setup
|
||||
String returnString;
|
||||
for(S32 i=0; i<mGroupStack.size(); i++)
|
||||
{
|
||||
S32 pos = returnString.size() - 1;
|
||||
if(pos < 0)
|
||||
pos = 0;
|
||||
|
||||
if(i == 0)
|
||||
{
|
||||
returnString.insert(pos, mGroupStack[i]);
|
||||
} else
|
||||
{
|
||||
returnString.insert(pos, "/");
|
||||
returnString.insert(pos+1, mGroupStack[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return StringTable->insert(returnString.c_str());
|
||||
}
|
||||
/*
|
||||
S32 Settings::buildSearchList( const char* pattern, bool deepSearch, bool includeDefaults )
|
||||
{
|
||||
mSearchResults.clear();
|
||||
|
||||
SimFieldDictionary* fieldDictionary = getFieldDictionary();
|
||||
// Get the dynamic field count
|
||||
if ( !fieldDictionary )
|
||||
return -1;
|
||||
|
||||
for (SimFieldDictionaryIterator itr(fieldDictionary); *itr; ++itr)
|
||||
{
|
||||
// Fetch Field Entry.
|
||||
SimFieldDictionary::Entry* fieldEntry = *itr;
|
||||
|
||||
// Compare strings, store proper results in vector
|
||||
String extendedPath = String::ToString(fieldEntry->slotName);
|
||||
String::SizeType start(0);
|
||||
String::SizeType slashPos = extendedPath.find('/', 0, String::Right);
|
||||
String shortPath = extendedPath.substr( start, slashPos );
|
||||
|
||||
if( deepSearch )
|
||||
{
|
||||
if( shortPath.find( pattern ) != -1 )
|
||||
{
|
||||
if( !includeDefaults && extendedPath.find("_default") != -1 )
|
||||
continue;
|
||||
|
||||
String listMember = String::ToString(fieldEntry->value);
|
||||
listMember.insert(start, " " );
|
||||
listMember.insert(start, String::ToString(fieldEntry->slotName) );
|
||||
|
||||
mSearchResults.push_back( listMember );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( shortPath.compare( pattern ) == 0 )
|
||||
{
|
||||
if( !includeDefaults && extendedPath.find("_default") != -1 )
|
||||
continue;
|
||||
|
||||
String listMember = String::ToString(fieldEntry->value);
|
||||
listMember.insert(start, " " );
|
||||
listMember.insert(start, String::ToString(fieldEntry->slotName) );
|
||||
|
||||
mSearchResults.push_back( listMember );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mSearchResults.size();
|
||||
}
|
||||
*/
|
||||
const char* Settings::findFirstValue( const char* pattern, bool deepSearch, bool includeDefaults )
|
||||
{
|
||||
mSearchResults.clear();
|
||||
|
||||
SimFieldDictionary* fieldDictionary = getFieldDictionary();
|
||||
// Get the dynamic field count
|
||||
if ( !fieldDictionary )
|
||||
return "";
|
||||
|
||||
for (SimFieldDictionaryIterator itr(fieldDictionary); *itr; ++itr)
|
||||
{
|
||||
// Fetch Field Entry.
|
||||
SimFieldDictionary::Entry* fieldEntry = *itr;
|
||||
|
||||
// Compare strings, store proper results in vector
|
||||
String extendedPath = String::ToString(fieldEntry->slotName);
|
||||
String::SizeType start(0);
|
||||
String::SizeType slashPos = extendedPath.find('/', 0, String::Right);
|
||||
String shortPath = extendedPath.substr( start, slashPos );
|
||||
|
||||
if( deepSearch )
|
||||
{
|
||||
if( shortPath.find( pattern ) != -1 )
|
||||
{
|
||||
if( !includeDefaults && extendedPath.find("_default") != -1 )
|
||||
continue;
|
||||
|
||||
String listMember = String::ToString(fieldEntry->slotName);
|
||||
//listMember.insert(start, " " );
|
||||
//listMember.insert(start, String::ToString(fieldEntry->slotName) );
|
||||
|
||||
mSearchResults.push_back( listMember );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( shortPath.compare( pattern ) == 0 )
|
||||
{
|
||||
if( !includeDefaults && extendedPath.find("_default") != -1 )
|
||||
continue;
|
||||
|
||||
String listMember = String::ToString(fieldEntry->slotName);
|
||||
//listMember.insert(start, " " );
|
||||
//listMember.insert(start, String::ToString(fieldEntry->slotName) );
|
||||
|
||||
mSearchResults.push_back( listMember );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( mSearchResults.size() < 1 )
|
||||
{
|
||||
Con::errorf("findFirstValue() : Pattern not found");
|
||||
return "";
|
||||
}
|
||||
|
||||
mSearchPos = 0;
|
||||
return mSearchResults[mSearchPos];
|
||||
}
|
||||
|
||||
const char* Settings::findNextValue()
|
||||
{
|
||||
if ( mSearchPos + 1 >= mSearchResults.size() )
|
||||
return "";
|
||||
mSearchPos++;
|
||||
return mSearchResults[mSearchPos];
|
||||
}
|
||||
|
||||
// make sure to replace the strings
|
||||
ConsoleMethod(Settings, findFirstValue, const char*, 2, 5, "settingObj.findFirstValue();")
|
||||
{
|
||||
if( argc == 3 )
|
||||
return object->findFirstValue( argv[2] );
|
||||
else if( argc == 4 )
|
||||
return object->findFirstValue( argv[2], argv[3] );
|
||||
else if( argc == 5 )
|
||||
return object->findFirstValue( argv[2], argv[3], argv[4] );
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, findNextValue, const char*, 2, 2, "settingObj.findNextValue();")
|
||||
{
|
||||
return object->findNextValue();
|
||||
}
|
||||
/*
|
||||
ConsoleMethod(Settings, buildSearchList, void, 2, 2, "settingObj.buildSearchList();")
|
||||
{
|
||||
object->buildSearchList( "foobar" );
|
||||
}
|
||||
*/
|
||||
void SettingSaveNode::addValue(const UTF8 *name, const UTF8 *value)
|
||||
{
|
||||
String nameString(name);
|
||||
S32 groupCount = getGroupCount(nameString);
|
||||
SettingSaveNode *parentNode = this;
|
||||
|
||||
// let's check to make sure all these groups exist already
|
||||
for(S32 i=0; i<groupCount; i++)
|
||||
{
|
||||
String groupName = getGroup(nameString, i);
|
||||
if(!groupName.isEmpty())
|
||||
{
|
||||
bool found = false;
|
||||
// loop through all of our nodes to find if this one exists,
|
||||
// if it does we want to use it
|
||||
for(S32 j=0; j<parentNode->mGroupNodes.size(); j++)
|
||||
{
|
||||
SettingSaveNode *node = parentNode->mGroupNodes[j];
|
||||
|
||||
if(!node->mIsGroup)
|
||||
continue;
|
||||
|
||||
if(node->mName.compare(groupName) == 0)
|
||||
{
|
||||
parentNode = node;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// not found, so we create it
|
||||
if(!found)
|
||||
{
|
||||
SettingSaveNode *node = new SettingSaveNode(groupName, true);
|
||||
parentNode->mGroupNodes.push_back(node);
|
||||
parentNode = node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now we can properly set our actual value
|
||||
String settingNameString = getSettingName(name);
|
||||
String valueString(value);
|
||||
SettingSaveNode *node = new SettingSaveNode(settingNameString, valueString);
|
||||
parentNode->mSettingNodes.push_back(node);
|
||||
}
|
||||
|
||||
S32 SettingSaveNode::getGroupCount(const String &name)
|
||||
{
|
||||
String::SizeType pos = 0;
|
||||
S32 count = 0;
|
||||
|
||||
// loop through and count our exiting groups
|
||||
while(pos != String::NPos)
|
||||
{
|
||||
pos = name.find("/", pos + 1);
|
||||
if(pos != String::NPos)
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
String SettingSaveNode::getGroup(const String &name, S32 num)
|
||||
{
|
||||
String::SizeType pos = 0;
|
||||
String::SizeType lastPos = 0;
|
||||
S32 count = 0;
|
||||
|
||||
while(pos != String::NPos)
|
||||
{
|
||||
lastPos = pos;
|
||||
pos = name.find("/", pos + 1);
|
||||
|
||||
if(count == num)
|
||||
{
|
||||
String::SizeType startPos = lastPos;
|
||||
|
||||
if(count > 0)
|
||||
startPos++;
|
||||
|
||||
if(pos == String::NPos)
|
||||
return name.substr(startPos, name.length() - (startPos));
|
||||
else
|
||||
return name.substr(startPos, pos - startPos);
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
|
||||
return String("");
|
||||
}
|
||||
|
||||
String SettingSaveNode::getSettingName(const String &name)
|
||||
{
|
||||
String::SizeType pos = name.find("/", 0, String::Right);
|
||||
|
||||
if(pos == String::NPos)
|
||||
return String(name);
|
||||
else
|
||||
return name.substr(pos+1, name.length() - (pos+1));
|
||||
}
|
||||
|
||||
void SettingSaveNode::clear()
|
||||
{
|
||||
for( U32 i = 0, num = mGroupNodes.size(); i < num; ++ i )
|
||||
delete mGroupNodes[ i ];
|
||||
for( U32 i = 0, num = mSettingNodes.size(); i < num; ++ i )
|
||||
delete mSettingNodes[ i ];
|
||||
|
||||
mGroupNodes.clear();
|
||||
mSettingNodes.clear();
|
||||
}
|
||||
|
||||
void SettingSaveNode::buildDocument(SimXMLDocument *document, bool skipWrite)
|
||||
{
|
||||
// let's build our XML doc
|
||||
if(mIsGroup && !skipWrite)
|
||||
{
|
||||
document->pushNewElement("Group");
|
||||
document->setAttribute("name", mName);
|
||||
}
|
||||
|
||||
if(!mIsGroup && !skipWrite)
|
||||
{
|
||||
document->pushNewElement("Setting");
|
||||
document->setAttribute("name", mName);
|
||||
document->addText(mValue);
|
||||
} else
|
||||
{
|
||||
for(int i=0; i<mSettingNodes.size(); i++)
|
||||
{
|
||||
SettingSaveNode *node = mSettingNodes[i];
|
||||
node->buildDocument(document);
|
||||
}
|
||||
|
||||
for(int i=0; i<mGroupNodes.size(); i++)
|
||||
{
|
||||
SettingSaveNode *node = mGroupNodes[i];
|
||||
node->buildDocument(document);
|
||||
}
|
||||
}
|
||||
|
||||
if(!skipWrite)
|
||||
document->popElement();
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, setValue, void, 3, 4, "settingObj.setValue(settingName, value);")
|
||||
{
|
||||
const char *fieldName = StringTable->insert( argv[2] );
|
||||
|
||||
if(argc == 3)
|
||||
object->setValue( fieldName);
|
||||
else if(argc == 4)
|
||||
object->setValue( fieldName, argv[3] );
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, setDefaultValue, void, 4, 4, "settingObj.setDefaultValue(settingName, value);")
|
||||
{
|
||||
const char *fieldName = StringTable->insert( argv[2] );
|
||||
object->setDefaultValue( fieldName, argv[3] );
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, value, const char*, 3, 4, "settingObj.value(settingName, defaultValue);")
|
||||
{
|
||||
const char *fieldName = StringTable->insert( argv[2] );
|
||||
|
||||
if(argc == 3)
|
||||
return object->value( fieldName );
|
||||
if(argc == 4)
|
||||
return object->value( fieldName, argv[3] );
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, remove, void, 3, 4, "settingObj.remove(settingName, includeDefaults = false);")
|
||||
{
|
||||
// there's a problem with some fields not being removed properly, but works if you run it twice,
|
||||
// a temporary solution for now is simply to call the remove twice
|
||||
if(argc == 3)
|
||||
{
|
||||
object->remove( argv[2] );
|
||||
object->remove( argv[2] );
|
||||
}
|
||||
else if(argc == 4)
|
||||
{
|
||||
object->remove( argv[2], argv[3] );
|
||||
object->remove( argv[2], argv[3] );
|
||||
}
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, write, bool, 2, 2, "%success = settingObj.write();")
|
||||
{
|
||||
TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
|
||||
return object->write();
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, read, bool, 2, 2, "%success = settingObj.read();")
|
||||
{
|
||||
TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
|
||||
return object->read();
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, beginGroup, void, 3, 4, "settingObj.beginGroup(groupName, fromStart = false);")
|
||||
{
|
||||
if(argc == 3)
|
||||
object->beginGroup( argv[2] );
|
||||
if(argc == 4)
|
||||
object->beginGroup( argv[2], dAtob(argv[3]) );
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, endGroup, void, 2, 2, "settingObj.endGroup();")
|
||||
{
|
||||
TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
|
||||
object->endGroup();
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, clearGroups, void, 2, 2, "settingObj.clearGroups();")
|
||||
{
|
||||
TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
|
||||
object->clearGroups();
|
||||
}
|
||||
|
||||
ConsoleMethod(Settings, getCurrentGroups, const char*, 2, 2, "settingObj.getCurrentGroups();")
|
||||
{
|
||||
return object->getCurrentGroups();
|
||||
}
|
||||
107
Engine/source/util/settings.h
Normal file
107
Engine/source/util/settings.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _SETTINGS_H_
|
||||
#define _SETTINGS_H_
|
||||
|
||||
#include "console/simBase.h"
|
||||
#include "core/util/tVector.h"
|
||||
|
||||
class SimXMLDocument;
|
||||
|
||||
///
|
||||
class Settings : public SimObject
|
||||
{
|
||||
private:
|
||||
FileName mFile;
|
||||
Vector<String> mGroupStack;
|
||||
S32 mSearchPos;
|
||||
Vector<String> mSearchResults;
|
||||
|
||||
public:
|
||||
Settings();
|
||||
virtual ~Settings();
|
||||
|
||||
// Required in all ConsoleObject subclasses.
|
||||
typedef SimObject Parent;
|
||||
DECLARE_CONOBJECT(Settings);
|
||||
static void initPersistFields();
|
||||
|
||||
/// These will set and get the values, with an option default value passed in to the get
|
||||
void setDefaultValue(const UTF8 *settingName, const UTF8 *settingValue, const UTF8 *settingType="");
|
||||
void setValue(const UTF8 *settingName, const UTF8 *settingValue = "");
|
||||
const UTF8 *value(const UTF8 *settingName, const UTF8 *defaultValue = "");
|
||||
void remove(const UTF8 *settingName, bool includeDefaults = false);
|
||||
void clearAllFields();
|
||||
bool write();
|
||||
bool read();
|
||||
void readLayer(SimXMLDocument *document, String groupStack = String(""));
|
||||
|
||||
void beginGroup(const UTF8 *groupName, bool fromStart = false);
|
||||
void endGroup();
|
||||
void clearGroups();
|
||||
|
||||
void buildGroupString(String &name, const UTF8 *settingName);
|
||||
const UTF8 *getCurrentGroups();
|
||||
|
||||
//S32 buildSearchList(const char* pattern, bool deepSearch = false, bool defaultsSearch = false);
|
||||
const char* findFirstValue(const char* pattern, bool deepSearch = false, bool includeDefaults = false);
|
||||
const char* findNextValue();
|
||||
};
|
||||
|
||||
class SettingSaveNode
|
||||
{
|
||||
public:
|
||||
Vector<SettingSaveNode*> mGroupNodes;
|
||||
Vector<SettingSaveNode*> mSettingNodes;
|
||||
|
||||
String mName;
|
||||
String mValue;
|
||||
bool mIsGroup;
|
||||
|
||||
SettingSaveNode(){};
|
||||
SettingSaveNode(const String &name, bool isGroup = false)
|
||||
{
|
||||
mName = name;
|
||||
mIsGroup = isGroup;
|
||||
}
|
||||
SettingSaveNode(const String &name, const String &value)
|
||||
{
|
||||
mName = name;
|
||||
mValue = value;
|
||||
mIsGroup = false;
|
||||
}
|
||||
~SettingSaveNode()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void addValue(const UTF8 *name, const UTF8 *value);
|
||||
S32 getGroupCount(const String &name);
|
||||
String getGroup(const String &name, S32 num);
|
||||
String getSettingName(const String &name);
|
||||
void buildDocument(SimXMLDocument *document, bool skipWrite = false);
|
||||
|
||||
void clear();
|
||||
};
|
||||
|
||||
#endif
|
||||
59
Engine/source/util/tempAlloc.h
Normal file
59
Engine/source/util/tempAlloc.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _TEMPALLOC_H_
|
||||
#define _TEMPALLOC_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
# include "platform/platform.h"
|
||||
#endif
|
||||
|
||||
|
||||
template< typename T >
|
||||
struct TempAlloc
|
||||
{
|
||||
T* ptr;
|
||||
U32 size;
|
||||
|
||||
TempAlloc()
|
||||
: size( 0 ), ptr( 0 ) {}
|
||||
TempAlloc( U32 size )
|
||||
: size( size )
|
||||
{
|
||||
ptr = ( T* ) dMalloc( size * sizeof( T ) );
|
||||
}
|
||||
~TempAlloc()
|
||||
{
|
||||
if( ptr )
|
||||
dFree( ptr );
|
||||
}
|
||||
operator T*()
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
|
||||
private:
|
||||
// Not safe.
|
||||
TempAlloc( const TempAlloc& ) {}
|
||||
};
|
||||
|
||||
#endif // _TEMPALLOC_H_
|
||||
192
Engine/source/util/triBoxCheck.cpp
Normal file
192
Engine/source/util/triBoxCheck.cpp
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// AABB-triangle overlap test code originally by Tomas Akenine-Möller
|
||||
// Assisted by Pierre Terdiman and David Hunt
|
||||
// http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/
|
||||
// Ported to TSE by BJG, 2005-4-14
|
||||
// Modified to avoid a lot of copying by ASM, 2007-9-28
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "util/triBoxCheck.h"
|
||||
|
||||
#define FINDMINMAX(x0,x1,x2,theMin,theMax) \
|
||||
theMin = theMax = x0; \
|
||||
if(x1<theMin) theMin=x1;\
|
||||
if(x1>theMax) theMax=x1;\
|
||||
if(x2<theMin) theMin=x2;\
|
||||
if(x2>theMax) theMax=x2;
|
||||
|
||||
static bool planeBoxOverlap(const Point3F &normal, const Point3F &vert, const Point3F &maxbox)
|
||||
{
|
||||
S32 q;
|
||||
F32 v;
|
||||
Point3F vmin, vmax;
|
||||
|
||||
for(q=0;q<=2;q++)
|
||||
{
|
||||
v=vert[q];
|
||||
if(normal[q]>0.0f)
|
||||
{
|
||||
vmin[q]=-maxbox[q] - v;
|
||||
vmax[q]= maxbox[q] - v;
|
||||
}
|
||||
else
|
||||
{
|
||||
vmin[q]= maxbox[q] - v;
|
||||
vmax[q]=-maxbox[q] - v;
|
||||
}
|
||||
}
|
||||
|
||||
if(mDot(normal, vmin) > 0.f)
|
||||
return false;
|
||||
|
||||
if(mDot(normal, vmax) >= 0.f)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*======================== X-tests ========================*/
|
||||
#define AXISTEST_X01(a, b, fa, fb) \
|
||||
p0 = a*v0.y - b*v0.z; \
|
||||
p2 = a*v2.y - b*v2.z; \
|
||||
if(p0<p2) {min=p0; max=p2;} else {min=p2; max=p0;} \
|
||||
rad = fa * boxhalfsize.y + fb * boxhalfsize.z; \
|
||||
if(min>rad || max<-rad) return false;
|
||||
|
||||
#define AXISTEST_X2(a, b, fa, fb) \
|
||||
p0 = a*v0.y - b*v0.z; \
|
||||
p1 = a*v1.y - b*v1.z; \
|
||||
if(p0<p1) {min=p0; max=p1;} else {min=p1; max=p0;} \
|
||||
rad = fa * boxhalfsize.y + fb * boxhalfsize.z; \
|
||||
if(min>rad || max<-rad) return false;
|
||||
|
||||
/*======================== Y-tests ========================*/
|
||||
#define AXISTEST_Y02(a, b, fa, fb) \
|
||||
p0 = -a*v0.x + b*v0.z; \
|
||||
p2 = -a*v2.x + b*v2.z; \
|
||||
if(p0<p2) {min=p0; max=p2;} else {min=p2; max=p0;} \
|
||||
rad = fa * boxhalfsize.x + fb * boxhalfsize.z; \
|
||||
if(min>rad || max<-rad) return false;
|
||||
|
||||
#define AXISTEST_Y1(a, b, fa, fb) \
|
||||
p0 = -a*v0.x + b*v0.z; \
|
||||
p1 = -a*v1.x + b*v1.z; \
|
||||
if(p0<p1) {min=p0; max=p1;} else {min=p1; max=p0;} \
|
||||
rad = fa * boxhalfsize.x + fb * boxhalfsize.z; \
|
||||
if(min>rad || max<-rad) return false;
|
||||
|
||||
/*======================== Z-tests ========================*/
|
||||
|
||||
#define AXISTEST_Z12(a, b, fa, fb) \
|
||||
p1 = a*v1.x - b*v1.y; \
|
||||
p2 = a*v2.x - b*v2.y; \
|
||||
if(p2<p1) {min=p2; max=p1;} else {min=p1; max=p2;} \
|
||||
rad = fa * boxhalfsize.x + fb * boxhalfsize.y; \
|
||||
if(min>rad || max<-rad) return false;
|
||||
|
||||
#define AXISTEST_Z0(a, b, fa, fb) \
|
||||
p0 = a*v0.x - b*v0.y; \
|
||||
p1 = a*v1.x - b*v1.y; \
|
||||
if(p0<p1) {min=p0; max=p1;} else {min=p1; max=p0;} \
|
||||
rad = fa * boxhalfsize.x + fb * boxhalfsize.y; \
|
||||
if(min>rad || max<-rad) return false;
|
||||
|
||||
bool triBoxOverlap(const Point3F &boxcenter, const Point3F &boxhalfsize, const Point3F triverts[3])
|
||||
{
|
||||
/* use separating axis theorem to test overlap between triangle and box */
|
||||
/* need to test for overlap in these directions: */
|
||||
/* 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle */
|
||||
/* we do not even need to test these) */
|
||||
/* 2) normal of the triangle */
|
||||
/* 3) crossproduct(edge from tri, {x,y,z}-directin) */
|
||||
/* this gives 3x3=9 more tests */
|
||||
|
||||
Point3F v0,v1,v2;
|
||||
|
||||
F32 min,max,p0,p1,p2,rad,fex,fey,fez; // -NJMP- "d" local variable removed
|
||||
Point3F normal,e0,e1,e2;
|
||||
|
||||
/* This is the fastest branch on Sun */
|
||||
/* move everything so that the boxcenter is in (0,0,0) */
|
||||
v0 = triverts[0] - boxcenter;
|
||||
v1 = triverts[1] - boxcenter;
|
||||
v2 = triverts[2] - boxcenter;
|
||||
|
||||
/* compute triangle edges */
|
||||
e0 = v1 - v0; /* tri edge 0 */
|
||||
e1 = v2 - v1; /* tri edge 1 */
|
||||
e2 = v0 - v2; /* tri edge 2 */
|
||||
|
||||
/* Bullet 3: */
|
||||
/* test the 9 tests first (this was faster) */
|
||||
fex = mFabs(e0.x);
|
||||
fey = mFabs(e0.y);
|
||||
fez = mFabs(e0.z);
|
||||
AXISTEST_X01(e0.z, e0.y, fez, fey);
|
||||
AXISTEST_Y02(e0.z, e0.x, fez, fex);
|
||||
AXISTEST_Z12(e0.y, e0.x, fey, fex);
|
||||
|
||||
fex = mFabs(e1.x);
|
||||
fey = mFabs(e1.y);
|
||||
fez = mFabs(e1.z);
|
||||
AXISTEST_X01(e1.z, e1.y, fez, fey);
|
||||
AXISTEST_Y02(e1.z, e1.x, fez, fex);
|
||||
AXISTEST_Z0(e1.y, e1.x, fey, fex);
|
||||
|
||||
fex = mFabs(e2.x);
|
||||
fey = mFabs(e2.y);
|
||||
fez = mFabs(e2.z);
|
||||
AXISTEST_X2(e2.z, e2.y, fez, fey);
|
||||
AXISTEST_Y1(e2.z, e2.x, fez, fex);
|
||||
AXISTEST_Z12(e2.y, e2.x, fey, fex);
|
||||
|
||||
/* Bullet 1: */
|
||||
/* first test overlap in the {x,y,z}-directions */
|
||||
/* find min, max of the triangle each direction, and test for overlap in */
|
||||
/* that direction -- this is equivalent to testing a minimal AABB around */
|
||||
/* the triangle against the AABB */
|
||||
|
||||
/* test in X-direction */
|
||||
FINDMINMAX(v0.x,v1.x,v2.x,min,max);
|
||||
if(min>boxhalfsize.x || max<-boxhalfsize.x) return false;
|
||||
|
||||
/* test in Y-direction */
|
||||
FINDMINMAX(v0.y,v1.y,v2.y,min,max);
|
||||
if(min>boxhalfsize.y || max<-boxhalfsize.y) return false;
|
||||
|
||||
/* test in Z-direction */
|
||||
FINDMINMAX(v0.z,v1.z,v2.z,min,max);
|
||||
if(min>boxhalfsize.z || max<-boxhalfsize.z) return false;
|
||||
|
||||
/* Bullet 2: */
|
||||
/* test if the box intersects the plane of the triangle */
|
||||
/* compute plane equation of triangle: normal*x+d=0 */
|
||||
normal = mCross(e0, e1);
|
||||
|
||||
if(!planeBoxOverlap(normal,v0,boxhalfsize)) return false;
|
||||
|
||||
return true; /* box and triangle overlaps */
|
||||
}
|
||||
53
Engine/source/util/triBoxCheck.h
Normal file
53
Engine/source/util/triBoxCheck.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// AABB-triangle overlap test code originally by Tomas Akenine-Möller
|
||||
// Assisted by Pierre Terdiman and David Hunt
|
||||
// http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/
|
||||
// Ported to TSE by BJG, 2005-4-14
|
||||
// Modified to avoid a lot of copying by ASM, 2007-9-28
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _TRIBOXCHECK_H_
|
||||
#define _TRIBOXCHECK_H_
|
||||
|
||||
#include "math/mPoint3.h"
|
||||
#include "math/mBox.h"
|
||||
|
||||
bool triBoxOverlap(const Point3F &boxcenter, const Point3F &boxhalfsize, const Point3F triverts[3]);
|
||||
|
||||
/// Massage stuff into right format for triBoxOverlap test. This is really
|
||||
/// just a helper function - use the other version if you want to be fast!
|
||||
inline bool triBoxOverlap(Box3F box, Point3F a, Point3F b, Point3F c)
|
||||
{
|
||||
Point3F halfSize(box.len_x() / 2.f, box.len_y() / 2.f, box.len_z() / 2.f);
|
||||
|
||||
Point3F center;
|
||||
box.getCenter(¢er);
|
||||
|
||||
Point3F verts[3] = {a,b,c};
|
||||
|
||||
return triBoxOverlap(center, halfSize, verts);
|
||||
}
|
||||
|
||||
#endif
|
||||
265
Engine/source/util/triRayCheck.cpp
Normal file
265
Engine/source/util/triRayCheck.cpp
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Ray to triangle intersection test code originally by Tomas Akenine-Möller
|
||||
// and Ben Trumbore.
|
||||
// http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/
|
||||
// Ported to TGE by DAW, 2005-7-15
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "util/triRayCheck.h"
|
||||
#include "math/mPlane.h"
|
||||
|
||||
#define EPSILON 0.000001
|
||||
#define CROSS(dest,v1,v2) \
|
||||
dest[0]=v1[1]*v2[2]-v1[2]*v2[1]; \
|
||||
dest[1]=v1[2]*v2[0]-v1[0]*v2[2]; \
|
||||
dest[2]=v1[0]*v2[1]-v1[1]*v2[0];
|
||||
#define DOT(v1,v2) (v1[0]*v2[0]+v1[1]*v2[1]+v1[2]*v2[2])
|
||||
#define SUB(dest,v1,v2) \
|
||||
dest[0]=v1[0]-v2[0]; \
|
||||
dest[1]=v1[1]-v2[1]; \
|
||||
dest[2]=v1[2]-v2[2];
|
||||
|
||||
bool intersect_triangle(Point3F orig, Point3F dir,
|
||||
Point3F vert0, Point3F vert1, Point3F vert2,
|
||||
F32& t, F32& u, F32& v)
|
||||
{
|
||||
Point3F edge1, edge2, tvec, pvec, qvec;
|
||||
F32 det,inv_det;
|
||||
|
||||
/* find vectors for two edges sharing vert0 */
|
||||
edge1.x = vert1.x - vert0.x;
|
||||
edge1.y = vert1.y - vert0.y;
|
||||
edge1.z = vert1.z - vert0.z;
|
||||
edge2.x = vert2.x - vert0.x;
|
||||
edge2.y = vert2.y - vert0.y;
|
||||
edge2.z = vert2.z - vert0.z;
|
||||
|
||||
/* begin calculating determinant - also used to calculate U parameter */
|
||||
//CROSS(pvec, dir, edge2);
|
||||
mCross(dir, edge2, &pvec);
|
||||
|
||||
/* if determinant is near zero, ray lies in plane of triangle */
|
||||
//det = DOT(edge1, pvec);
|
||||
det = mDot(edge1, pvec);
|
||||
|
||||
#ifdef TEST_CULL /* define TEST_CULL if culling is desired */
|
||||
if (det < EPSILON)
|
||||
return 0;
|
||||
|
||||
/* calculate distance from vert0 to ray origin */
|
||||
SUB(tvec, orig, vert0);
|
||||
|
||||
/* calculate U parameter and test bounds */
|
||||
*u = DOT(tvec, pvec);
|
||||
if (*u < 0.0 || *u > det)
|
||||
return 0;
|
||||
|
||||
/* prepare to test V parameter */
|
||||
CROSS(qvec, tvec, edge1);
|
||||
|
||||
/* calculate V parameter and test bounds */
|
||||
*v = DOT(dir, qvec);
|
||||
if (*v < 0.0 || *u + *v > det)
|
||||
return 0;
|
||||
|
||||
/* calculate t, scale parameters, ray intersects triangle */
|
||||
*t = DOT(edge2, qvec);
|
||||
inv_det = 1.0 / det;
|
||||
*t *= inv_det;
|
||||
*u *= inv_det;
|
||||
*v *= inv_det;
|
||||
#else /* the non-culling branch */
|
||||
if (det > -EPSILON && det < EPSILON)
|
||||
return false;
|
||||
inv_det = 1.0 / det;
|
||||
|
||||
/* calculate distance from vert0 to ray origin */
|
||||
//SUB(tvec, orig, vert0);
|
||||
tvec.x = orig.x - vert0.x;
|
||||
tvec.y = orig.y - vert0.y;
|
||||
tvec.z = orig.z - vert0.z;
|
||||
|
||||
/* calculate U parameter and test bounds */
|
||||
// *u = DOT(tvec, pvec) * inv_det;
|
||||
u = mDot(tvec, pvec) * inv_det;
|
||||
if (u < 0.0 || u > 1.0)
|
||||
return false;
|
||||
|
||||
/* prepare to test V parameter */
|
||||
//CROSS(qvec, tvec, edge1);
|
||||
mCross(tvec, edge1, &qvec);
|
||||
|
||||
/* calculate V parameter and test bounds */
|
||||
// *v = DOT(dir, qvec) * inv_det;
|
||||
v = mDot(dir, qvec) * inv_det;
|
||||
if (v < 0.0 || u + v > 1.0)
|
||||
return false;
|
||||
|
||||
/* calculate t, ray intersects triangle */
|
||||
// *t = DOT(edge2, qvec) * inv_det;
|
||||
t = mDot(edge2, qvec) * inv_det;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
//*** Taken from TSE, and based on the above
|
||||
bool castRayTriangle(Point3F orig, Point3F dir,
|
||||
Point3F vert0, Point3F vert1, Point3F vert2,
|
||||
F32 &t, Point2F &bary)
|
||||
{
|
||||
Point3F tvec, qvec;
|
||||
|
||||
// Find vectors for two edges sharing vert0
|
||||
const Point3F edge1 = vert1 - vert0;
|
||||
const Point3F edge2 = vert2 - vert0;
|
||||
|
||||
// Begin calculating determinant - also used to calculate U parameter.
|
||||
const Point3F pvec = mCross(dir, edge2);
|
||||
|
||||
// If determinant is near zero, ray lies in plane of triangle.
|
||||
const F32 det = mDot(edge1, pvec);
|
||||
|
||||
if (det > 0.00001)
|
||||
{
|
||||
// calculate distance from vert0 to ray origin
|
||||
tvec = orig - vert0;
|
||||
|
||||
// calculate U parameter and test bounds
|
||||
bary.x = mDot(tvec, pvec); // bary.x is really bary.u...
|
||||
if (bary.x < 0.0 || bary.x > det)
|
||||
return false;
|
||||
|
||||
// prepare to test V parameter
|
||||
qvec = mCross(tvec, edge1);
|
||||
|
||||
// calculate V parameter and test bounds
|
||||
bary.y = mDot(dir, qvec); // bary.y is really bary.v
|
||||
if (bary.y < 0.0 || (bary.x + bary.y) > det)
|
||||
return false;
|
||||
|
||||
}
|
||||
else if(det < -0.00001)
|
||||
{
|
||||
// calculate distance from vert0 to ray origin
|
||||
tvec = orig - vert0;
|
||||
|
||||
// calculate U parameter and test bounds
|
||||
bary.x = mDot(tvec, pvec);
|
||||
if (bary.x > 0.0 || bary.x < det)
|
||||
return false;
|
||||
|
||||
// prepare to test V parameter
|
||||
qvec = mCross(tvec, edge1);
|
||||
|
||||
// calculate V parameter and test bounds
|
||||
bary.y = mDot(dir, qvec);
|
||||
if (bary.y > 0.0 || (bary.x + bary.y) < det)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false; // ray is parallel to the plane of the triangle.
|
||||
|
||||
const F32 inv_det = 1.0 / det;
|
||||
|
||||
// calculate t, ray intersects triangle
|
||||
t = mDot(edge2, qvec) * inv_det;
|
||||
bary *= inv_det;
|
||||
|
||||
//AssertFatal((t >= 0.f && t <=1.f), "AtlasGeomTracer::castRayTriangle - invalid t!");
|
||||
|
||||
// Hack, check the math here!
|
||||
return (t >= 0.f && t <=1.f);
|
||||
}
|
||||
|
||||
bool castRayTriangle(const Point3D &orig, const Point3D &dir,
|
||||
const Point3D &vert0, const Point3D &vert1, const Point3D &vert2)
|
||||
{
|
||||
F64 t;
|
||||
Point2D bary;
|
||||
Point3D tvec, qvec;
|
||||
|
||||
// Find vectors for two edges sharing vert0
|
||||
const Point3D edge1 = vert1 - vert0;
|
||||
const Point3D edge2 = vert2 - vert0;
|
||||
|
||||
// Begin calculating determinant - also used to calculate U parameter.
|
||||
Point3D pvec;
|
||||
mCross(dir, edge2, &pvec);
|
||||
|
||||
// If determinant is near zero, ray lies in plane of triangle.
|
||||
const F64 det = mDot(edge1, pvec);
|
||||
|
||||
if (det > 0.00001)
|
||||
{
|
||||
// calculate distance from vert0 to ray origin
|
||||
tvec = orig - vert0;
|
||||
|
||||
// calculate U parameter and test bounds
|
||||
bary.x = mDot(tvec, pvec); // bary.x is really bary.u...
|
||||
if (bary.x < 0.0 || bary.x > det)
|
||||
return false;
|
||||
|
||||
// prepare to test V parameter
|
||||
mCross(tvec, edge1, &qvec);
|
||||
|
||||
// calculate V parameter and test bounds
|
||||
bary.y = mDot(dir, qvec); // bary.y is really bary.v
|
||||
if (bary.y < 0.0 || (bary.x + bary.y) > det)
|
||||
return false;
|
||||
|
||||
}
|
||||
else if(det < -0.00001)
|
||||
{
|
||||
// calculate distance from vert0 to ray origin
|
||||
tvec = orig - vert0;
|
||||
|
||||
// calculate U parameter and test bounds
|
||||
bary.x = mDot(tvec, pvec);
|
||||
if (bary.x > 0.0 || bary.x < det)
|
||||
return false;
|
||||
|
||||
// prepare to test V parameter
|
||||
mCross(tvec, edge1, &qvec);
|
||||
|
||||
// calculate V parameter and test bounds
|
||||
bary.y = mDot(dir, qvec);
|
||||
if (bary.y > 0.0 || (bary.x + bary.y) < det)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false; // ray is parallel to the plane of the triangle.
|
||||
|
||||
const F32 inv_det = 1.0 / det;
|
||||
|
||||
// calculate t, ray intersects triangle
|
||||
t = mDot(edge2, qvec) * inv_det;
|
||||
bary *= inv_det;
|
||||
|
||||
//AssertFatal((t >= 0.f && t <=1.f), "AtlasGeomTracer::castRayTriangle - invalid t!");
|
||||
|
||||
// Hack, check the math here!
|
||||
return (t >= 0.f && t <=1.f);
|
||||
}
|
||||
|
||||
44
Engine/source/util/triRayCheck.h
Normal file
44
Engine/source/util/triRayCheck.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Ray to triangle intersection test code originally by Tomas Akenine-Möller
|
||||
// and Ben Trumbore.
|
||||
// http://www.cs.lth.se/home/Tomas_Akenine_Moller/code/
|
||||
// Ported to TGE by DAW, 2005-7-15
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _TRIRAYCHECK_H_
|
||||
#define _TRIRAYCHECK_H_
|
||||
|
||||
#include "math/mPoint2.h"
|
||||
#include "math/mPoint3.h"
|
||||
|
||||
bool intersect_triangle(Point3F orig, Point3F dir,
|
||||
Point3F vert0, Point3F vert1, Point3F vert2,
|
||||
F32& t, F32& u, F32& v);
|
||||
|
||||
//*** Taken from TSE, but based on the above
|
||||
bool castRayTriangle(Point3F orig, Point3F dir, Point3F vert0, Point3F vert1, Point3F vert2, F32 &t, Point2F &bary);
|
||||
bool castRayTriangle(const Point3D &orig, const Point3D &dir, const Point3D &vert0, const Point3D &vert1, const Point3D &vert2);
|
||||
|
||||
#endif // _TRIRAYCHECK_H_
|
||||
596
Engine/source/util/undo.cpp
Normal file
596
Engine/source/util/undo.cpp
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "util/undo.h"
|
||||
|
||||
#include "console/console.h"
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// UndoAction
|
||||
//-----------------------------------------------------------------------------
|
||||
IMPLEMENT_CONOBJECT(UndoAction);
|
||||
IMPLEMENT_CONOBJECT(UndoScriptAction);
|
||||
|
||||
ConsoleDocClass( UndoAction,
|
||||
"@brief An event which signals the editors to undo the last action\n\n"
|
||||
"Not intended for game development, for editors or internal use only.\n\n "
|
||||
"@internal");
|
||||
|
||||
ConsoleDocClass( UndoScriptAction,
|
||||
"@brief Undo actions which can be created as script objects.\n\n"
|
||||
"Not intended for game development, for editors or internal use only.\n\n "
|
||||
"@internal");
|
||||
|
||||
UndoAction::UndoAction(const UTF8 *actionName)
|
||||
{
|
||||
mActionName = actionName;
|
||||
mUndoManager = NULL;
|
||||
}
|
||||
|
||||
UndoAction::~UndoAction()
|
||||
{
|
||||
// If we are registered to an undo manager, make sure
|
||||
// we get off its lists.
|
||||
if( mUndoManager )
|
||||
mUndoManager->removeAction( this, true );
|
||||
|
||||
clearAllNotifications();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void UndoAction::initPersistFields()
|
||||
{
|
||||
addField("actionName", TypeRealString, Offset(mActionName, UndoAction),
|
||||
"A brief description of the action, for UI representation of this undo/redo action.");
|
||||
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void UndoAction::addToManager(UndoManager* theMan)
|
||||
{
|
||||
if(theMan)
|
||||
{
|
||||
mUndoManager = theMan;
|
||||
(*theMan).addAction(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
mUndoManager = &UndoManager::getDefaultManager();
|
||||
mUndoManager->addAction(this);
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// CompoundUndoAction
|
||||
//-----------------------------------------------------------------------------
|
||||
IMPLEMENT_CONOBJECT( CompoundUndoAction );
|
||||
|
||||
ConsoleDocClass( CompoundUndoAction,
|
||||
"@brief An undo action that is comprised of other undo actions.\n\n"
|
||||
"Not intended for game development, for editors or internal use only.\n\n "
|
||||
"@internal");
|
||||
|
||||
CompoundUndoAction::CompoundUndoAction( const UTF8 *actionName )
|
||||
: Parent( actionName )
|
||||
{
|
||||
}
|
||||
|
||||
CompoundUndoAction::~CompoundUndoAction()
|
||||
{
|
||||
while( !mChildren.empty() )
|
||||
{
|
||||
UndoAction* action = mChildren.last();
|
||||
if( action->isProperlyAdded() )
|
||||
action->deleteObject();
|
||||
else
|
||||
{
|
||||
clearNotify( action ); // need to clear the delete notification manually in this case
|
||||
delete action;
|
||||
}
|
||||
|
||||
mChildren.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void CompoundUndoAction::addAction( UndoAction *action )
|
||||
{
|
||||
//AssertFatal( action->mUndoManager == NULL, "CompoundUndoAction::addAction, action already had an UndoManager." );
|
||||
mChildren.push_back( action );
|
||||
deleteNotify( action );
|
||||
}
|
||||
|
||||
void CompoundUndoAction::undo()
|
||||
{
|
||||
Vector<UndoAction*>::iterator itr = mChildren.end() - 1;
|
||||
for ( ; itr != mChildren.begin() - 1; itr-- )
|
||||
(*itr)->undo();
|
||||
}
|
||||
|
||||
void CompoundUndoAction::redo()
|
||||
{
|
||||
Vector<UndoAction*>::iterator itr = mChildren.begin();
|
||||
for ( ; itr != mChildren.end(); itr++ )
|
||||
(*itr)->redo();
|
||||
}
|
||||
|
||||
void CompoundUndoAction::onDeleteNotify( SimObject* object )
|
||||
{
|
||||
for( U32 i = 0; i < mChildren.size(); ++ i )
|
||||
if( mChildren[ i ] == object )
|
||||
mChildren.erase( i );
|
||||
|
||||
Parent::onDeleteNotify( object );
|
||||
}
|
||||
|
||||
ConsoleMethod( CompoundUndoAction, addAction, void, 3, 3, "addAction( UndoAction )" )
|
||||
{
|
||||
UndoAction *action;
|
||||
if ( Sim::findObject( argv[2], action ) )
|
||||
object->addAction( action );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// UndoManager
|
||||
//-----------------------------------------------------------------------------
|
||||
IMPLEMENT_CONOBJECT(UndoManager);
|
||||
|
||||
ConsoleDocClass( UndoManager,
|
||||
"@brief SimObject which adds, tracks, and deletes UndoAction objects.\n\n"
|
||||
"Not intended for game development, for editors or internal use only.\n\n "
|
||||
"@internal")
|
||||
|
||||
UndoManager::UndoManager(U32 levels)
|
||||
{
|
||||
VECTOR_SET_ASSOCIATION( mUndoStack );
|
||||
VECTOR_SET_ASSOCIATION( mRedoStack );
|
||||
VECTOR_SET_ASSOCIATION( mCompoundStack );
|
||||
|
||||
mNumLevels = levels;
|
||||
// levels can be arbitrarily high, so we don't really want to reserve(levels).
|
||||
mUndoStack.reserve(10);
|
||||
mRedoStack.reserve(10);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
UndoManager::~UndoManager()
|
||||
{
|
||||
clearStack(mUndoStack);
|
||||
clearStack(mRedoStack);
|
||||
clearStack( *( ( Vector< UndoAction* >* ) &mCompoundStack ) );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void UndoManager::initPersistFields()
|
||||
{
|
||||
addField("numLevels", TypeS32, Offset(mNumLevels, UndoManager), "Number of undo & redo levels.");
|
||||
// arrange for the default undo manager to exist.
|
||||
// UndoManager &def = getDefaultManager();
|
||||
// Con::printf("def = %s undo manager created", def.getName());
|
||||
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
UndoManager& UndoManager::getDefaultManager()
|
||||
{
|
||||
// the default manager is created the first time it is asked for.
|
||||
static UndoManager *defaultMan = NULL;
|
||||
if(!defaultMan)
|
||||
{
|
||||
defaultMan = new UndoManager();
|
||||
defaultMan->assignName("DefaultUndoManager");
|
||||
defaultMan->registerObject();
|
||||
}
|
||||
return *defaultMan;
|
||||
}
|
||||
|
||||
ConsoleMethod(UndoManager, clearAll, void, 2, 2, "Clears the undo manager.")
|
||||
{
|
||||
object->clearAll();
|
||||
}
|
||||
|
||||
void UndoManager::clearAll()
|
||||
{
|
||||
clearStack(mUndoStack);
|
||||
clearStack(mRedoStack);
|
||||
|
||||
Con::executef(this, "onClear");
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void UndoManager::clearStack(Vector<UndoAction*> &stack)
|
||||
{
|
||||
Vector<UndoAction*>::iterator itr = stack.begin();
|
||||
while (itr != stack.end())
|
||||
{
|
||||
UndoAction* undo = stack.first();
|
||||
stack.pop_front();
|
||||
|
||||
// Call deleteObject() if the action was registered.
|
||||
if ( undo->isProperlyAdded() )
|
||||
undo->deleteObject();
|
||||
else
|
||||
delete undo;
|
||||
}
|
||||
stack.clear();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void UndoManager::clampStack(Vector<UndoAction*> &stack)
|
||||
{
|
||||
while(stack.size() > mNumLevels)
|
||||
{
|
||||
UndoAction *act = stack.front();
|
||||
stack.pop_front();
|
||||
|
||||
// Call deleteObject() if the action was registered.
|
||||
if ( act->isProperlyAdded() )
|
||||
act->deleteObject();
|
||||
else
|
||||
delete act;
|
||||
}
|
||||
}
|
||||
|
||||
void UndoManager::removeAction(UndoAction *action, bool noDelete)
|
||||
{
|
||||
Vector<UndoAction*>::iterator itr = mUndoStack.begin();
|
||||
while (itr != mUndoStack.end())
|
||||
{
|
||||
if ((*itr) == action)
|
||||
{
|
||||
UndoAction* deleteAction = *itr;
|
||||
mUndoStack.erase(itr);
|
||||
doRemove( deleteAction, noDelete );
|
||||
return;
|
||||
}
|
||||
itr++;
|
||||
}
|
||||
|
||||
itr = mRedoStack.begin();
|
||||
while (itr != mRedoStack.end())
|
||||
{
|
||||
if ((*itr) == action)
|
||||
{
|
||||
UndoAction* deleteAction = *itr;
|
||||
mRedoStack.erase(itr);
|
||||
doRemove( deleteAction, noDelete );
|
||||
return;
|
||||
}
|
||||
itr++;
|
||||
}
|
||||
}
|
||||
|
||||
void UndoManager::doRemove( UndoAction* action, bool noDelete )
|
||||
{
|
||||
if( action->mUndoManager == this )
|
||||
action->mUndoManager = NULL;
|
||||
|
||||
if( !noDelete )
|
||||
{
|
||||
// Call deleteObject() if the action was registered.
|
||||
if ( action->isProperlyAdded() )
|
||||
action->deleteObject();
|
||||
else
|
||||
delete action;
|
||||
}
|
||||
|
||||
if( isProperlyAdded() )
|
||||
Con::executef(this, "onRemoveUndo");
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void UndoManager::undo()
|
||||
{
|
||||
// make sure we have an action available
|
||||
if(mUndoStack.size() < 1)
|
||||
return;
|
||||
|
||||
// pop the action off the undo stack
|
||||
UndoAction *act = mUndoStack.last();
|
||||
mUndoStack.pop_back();
|
||||
|
||||
// add it to the redo stack
|
||||
mRedoStack.push_back(act);
|
||||
if(mRedoStack.size() > mNumLevels)
|
||||
mRedoStack.pop_front();
|
||||
|
||||
Con::executef(this, "onUndo");
|
||||
|
||||
// perform the undo, whatever it may be.
|
||||
(*act).undo();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void UndoManager::redo()
|
||||
{
|
||||
// make sure we have an action available
|
||||
if(mRedoStack.size() < 1)
|
||||
return;
|
||||
|
||||
// pop the action off the redo stack
|
||||
UndoAction *react = mRedoStack.last();
|
||||
mRedoStack.pop_back();
|
||||
|
||||
// add it to the undo stack
|
||||
mUndoStack.push_back(react);
|
||||
if(mUndoStack.size() > mNumLevels)
|
||||
mUndoStack.pop_front();
|
||||
|
||||
Con::executef(this, "onRedo");
|
||||
|
||||
// perform the redo, whatever it may be.
|
||||
(*react).redo();
|
||||
}
|
||||
|
||||
ConsoleMethod(UndoManager, getUndoCount, S32, 2, 2, "")
|
||||
{
|
||||
return object->getUndoCount();
|
||||
}
|
||||
|
||||
S32 UndoManager::getUndoCount()
|
||||
{
|
||||
return mUndoStack.size();
|
||||
}
|
||||
|
||||
ConsoleMethod(UndoManager, getUndoName, const char*, 3, 3, "(index)")
|
||||
{
|
||||
return object->getUndoName(dAtoi(argv[2]));
|
||||
}
|
||||
|
||||
const char* UndoManager::getUndoName(S32 index)
|
||||
{
|
||||
if ((index < getUndoCount()) && (index >= 0))
|
||||
return mUndoStack[index]->mActionName;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ConsoleMethod(UndoManager, getUndoAction, S32, 3, 3, "(index)")
|
||||
{
|
||||
UndoAction * action = object->getUndoAction(dAtoi(argv[2]));
|
||||
if ( !action )
|
||||
return -1;
|
||||
|
||||
if ( !action->isProperlyAdded() )
|
||||
action->registerObject();
|
||||
|
||||
return action->getId();
|
||||
}
|
||||
|
||||
UndoAction* UndoManager::getUndoAction(S32 index)
|
||||
{
|
||||
if ((index < getUndoCount()) && (index >= 0))
|
||||
return mUndoStack[index];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ConsoleMethod(UndoManager, getRedoCount, S32, 2, 2, "")
|
||||
{
|
||||
return object->getRedoCount();
|
||||
}
|
||||
|
||||
S32 UndoManager::getRedoCount()
|
||||
{
|
||||
return mRedoStack.size();
|
||||
}
|
||||
|
||||
ConsoleMethod(UndoManager, getRedoName, const char*, 3, 3, "(index)")
|
||||
{
|
||||
return object->getRedoName(dAtoi(argv[2]));
|
||||
}
|
||||
|
||||
const char* UndoManager::getRedoName(S32 index)
|
||||
{
|
||||
if ((index < getRedoCount()) && (index >= 0))
|
||||
return mRedoStack[getRedoCount() - index - 1]->mActionName;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ConsoleMethod(UndoManager, getRedoAction, S32, 3, 3, "(index)")
|
||||
{
|
||||
UndoAction * action = object->getRedoAction(dAtoi(argv[2]));
|
||||
|
||||
if ( !action )
|
||||
return -1;
|
||||
|
||||
if ( !action->isProperlyAdded() )
|
||||
action->registerObject();
|
||||
|
||||
return action->getId();
|
||||
}
|
||||
|
||||
UndoAction* UndoManager::getRedoAction(S32 index)
|
||||
{
|
||||
if ((index < getRedoCount()) && (index >= 0))
|
||||
return mRedoStack[index];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
const char* UndoManager::getNextUndoName()
|
||||
{
|
||||
if(mUndoStack.size() < 1)
|
||||
return NULL;
|
||||
|
||||
UndoAction *act = mUndoStack.last();
|
||||
return (*act).mActionName;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
const char* UndoManager::getNextRedoName()
|
||||
{
|
||||
if(mRedoStack.size() < 1)
|
||||
return NULL;
|
||||
|
||||
UndoAction *act = mRedoStack.last();
|
||||
return (*act).mActionName;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void UndoManager::addAction(UndoAction* action)
|
||||
{
|
||||
// If we are assembling a compound, redirect the action to it
|
||||
// and don't modify our current undo/redo state.
|
||||
|
||||
if( mCompoundStack.size() )
|
||||
{
|
||||
mCompoundStack.last()->addAction( action );
|
||||
return;
|
||||
}
|
||||
|
||||
// clear the redo stack
|
||||
clearStack(mRedoStack);
|
||||
|
||||
// push the incoming action onto the stack, move old data off the end if necessary.
|
||||
mUndoStack.push_back(action);
|
||||
if(mUndoStack.size() > mNumLevels)
|
||||
mUndoStack.pop_front();
|
||||
|
||||
Con::executef(this, "onAddUndo");
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
CompoundUndoAction* UndoManager::pushCompound( const String& name )
|
||||
{
|
||||
mCompoundStack.push_back( new CompoundUndoAction( name ) );
|
||||
return mCompoundStack.last();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void UndoManager::popCompound( bool discard )
|
||||
{
|
||||
AssertFatal( getCompoundStackDepth() > 0, "UndoManager::popCompound - no compound on stack!" );
|
||||
|
||||
CompoundUndoAction* undo = mCompoundStack.last();
|
||||
mCompoundStack.pop_back();
|
||||
|
||||
if( discard || undo->getNumChildren() == 0 )
|
||||
{
|
||||
if( undo->isProperlyAdded() )
|
||||
undo->deleteObject();
|
||||
else
|
||||
delete undo;
|
||||
}
|
||||
else
|
||||
addAction( undo );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
ConsoleMethod(UndoAction, addToManager, void, 2, 3, "action.addToManager([undoManager])")
|
||||
{
|
||||
UndoManager *theMan = NULL;
|
||||
if(argc == 3)
|
||||
{
|
||||
SimObject *obj = Sim::findObject(argv[2]);
|
||||
if(obj)
|
||||
theMan = dynamic_cast<UndoManager*> (obj);
|
||||
}
|
||||
object->addToManager(theMan);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleMethod( UndoAction, undo, void, 2, 2, "() - Undo action contained in undo." )
|
||||
{
|
||||
object->undo();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleMethod( UndoAction, redo, void, 2, 2, "() - Reo action contained in undo." )
|
||||
{
|
||||
object->redo();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
ConsoleMethod(UndoManager, undo, void, 2, 2, "UndoManager.undo();")
|
||||
{
|
||||
object->undo();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
ConsoleMethod(UndoManager, redo, void, 2, 2, "UndoManager.redo();")
|
||||
{
|
||||
object->redo();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
ConsoleMethod(UndoManager, getNextUndoName, const char *, 2, 2, "UndoManager.getNextUndoName();")
|
||||
{
|
||||
const char *name = object->getNextUndoName();
|
||||
if(!name)
|
||||
return NULL;
|
||||
char *ret = Con::getReturnBuffer(dStrlen(name) + 1);
|
||||
dStrcpy(ret, name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
ConsoleMethod(UndoManager, getNextRedoName, const char *, 2, 2, "UndoManager.getNextRedoName();")
|
||||
{
|
||||
const char *name = object->getNextRedoName();
|
||||
if(!name)
|
||||
return NULL;
|
||||
char *ret = Con::getReturnBuffer(dStrlen(name) + 1);
|
||||
dStrcpy(ret, name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleMethod( UndoManager, pushCompound, const char*, 2, 3, "( string name=\"\" ) - Push a CompoundUndoAction onto the compound stack for assembly." )
|
||||
{
|
||||
String name;
|
||||
if( argc > 2 )
|
||||
name = argv[ 2 ];
|
||||
|
||||
CompoundUndoAction* action = object->pushCompound( name );
|
||||
if( !action )
|
||||
return "";
|
||||
|
||||
if( !action->isProperlyAdded() )
|
||||
action->registerObject();
|
||||
|
||||
return action->getIdString();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
ConsoleMethod( UndoManager, popCompound, void, 2, 3, "( bool discard=false ) - Pop the current CompoundUndoAction off the stack." )
|
||||
{
|
||||
if( !object->getCompoundStackDepth() )
|
||||
{
|
||||
Con::errorf( "%s::popCompound - no compound on stack", argv[ 0 ] );
|
||||
return;
|
||||
}
|
||||
|
||||
bool discard = false;
|
||||
if( argc > 2 )
|
||||
discard = dAtob( argv[ 2 ] );
|
||||
|
||||
object->popCompound( discard );
|
||||
}
|
||||
245
Engine/source/util/undo.h
Normal file
245
Engine/source/util/undo.h
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _UNDO_H_
|
||||
#define _UNDO_H_
|
||||
|
||||
#ifndef _SIMOBJECT_H_
|
||||
#include "console/simObject.h"
|
||||
#endif
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/util/tVector.h"
|
||||
#endif
|
||||
|
||||
class UndoManager;
|
||||
|
||||
///
|
||||
class UndoAction : public SimObject
|
||||
{
|
||||
friend class UndoManager;
|
||||
|
||||
protected:
|
||||
// The manager this was added to.
|
||||
UndoManager* mUndoManager;
|
||||
|
||||
public:
|
||||
|
||||
/// A brief description of the action, for display in menus and the like.
|
||||
// not private because we're exposing it to the console.
|
||||
String mActionName;
|
||||
|
||||
// Required in all ConsoleObject subclasses.
|
||||
typedef SimObject Parent;
|
||||
DECLARE_CONOBJECT(UndoAction);
|
||||
static void initPersistFields();
|
||||
|
||||
/// Create a new action, assigning it a name for display in menus et cetera.
|
||||
UndoAction(const UTF8 *actionName = " ");
|
||||
virtual ~UndoAction();
|
||||
|
||||
/// Implement these methods to perform your specific undo & redo tasks.
|
||||
virtual void undo() { };
|
||||
virtual void redo() { };
|
||||
|
||||
/// Adds the action to the undo stack of the default UndoManager, or the provided manager.
|
||||
void addToManager(UndoManager* theMan = NULL);
|
||||
};
|
||||
|
||||
/// An undo action that is comprised of other undo actions.
|
||||
class CompoundUndoAction : public UndoAction
|
||||
{
|
||||
friend class UndoManager;
|
||||
|
||||
protected:
|
||||
|
||||
Vector< UndoAction* > mChildren;
|
||||
|
||||
public:
|
||||
|
||||
typedef UndoAction Parent;
|
||||
|
||||
CompoundUndoAction( const UTF8 *actionName = " " );
|
||||
virtual ~CompoundUndoAction();
|
||||
|
||||
DECLARE_CONOBJECT(CompoundUndoAction);
|
||||
|
||||
virtual void addAction( UndoAction *action );
|
||||
virtual void undo();
|
||||
virtual void redo();
|
||||
|
||||
virtual void onDeleteNotify( SimObject* object );
|
||||
|
||||
U32 getNumChildren() const { return mChildren.size(); }
|
||||
};
|
||||
|
||||
///
|
||||
class UndoManager : public SimObject
|
||||
{
|
||||
private:
|
||||
/// Default number of undo & redo levels.
|
||||
const static U32 kDefaultNumLevels = 100;
|
||||
|
||||
/// The stacks of undo & redo actions. They will be capped at size mNumLevels.
|
||||
Vector<UndoAction*> mUndoStack;
|
||||
Vector<UndoAction*> mRedoStack;
|
||||
|
||||
/// Stack for assembling compound actions.
|
||||
Vector< CompoundUndoAction* > mCompoundStack;
|
||||
|
||||
/// Deletes all the UndoActions in a stack, then clears it.
|
||||
void clearStack(Vector<UndoAction*> &stack);
|
||||
/// Clamps a Vector to mNumLevels entries.
|
||||
void clampStack(Vector<UndoAction*> &stack);
|
||||
|
||||
/// Run the removal logic on the action.
|
||||
void doRemove( UndoAction* action, bool noDelete );
|
||||
|
||||
public:
|
||||
/// Number of undo & redo levels.
|
||||
// not private because we're exposing it to the console.
|
||||
U32 mNumLevels;
|
||||
|
||||
// Required in all ConsoleObject subclasses.
|
||||
typedef SimObject Parent;
|
||||
DECLARE_CONOBJECT(UndoManager);
|
||||
static void initPersistFields();
|
||||
|
||||
/// Constructor. If levels = 0, we use the default number of undo levels.
|
||||
UndoManager(U32 levels = kDefaultNumLevels);
|
||||
/// Destructor. deletes and clears the undo & redo stacks.
|
||||
~UndoManager();
|
||||
/// Accessor to the default undo manager singleton. Creates one if needed.
|
||||
static UndoManager& getDefaultManager();
|
||||
|
||||
/// Undo last action, and put it on the redo stack.
|
||||
void undo();
|
||||
/// Redo the last action, and put it on the undo stack.
|
||||
void redo();
|
||||
|
||||
/// Clears the undo and redo stacks.
|
||||
void clearAll();
|
||||
|
||||
/// Returns the printable name of the top actions on the undo & redo stacks.
|
||||
const char* getNextUndoName();
|
||||
const char* getNextRedoName();
|
||||
|
||||
S32 getUndoCount();
|
||||
S32 getRedoCount();
|
||||
|
||||
const char* getUndoName(S32 index);
|
||||
const char* getRedoName(S32 index);
|
||||
|
||||
UndoAction* getUndoAction(S32 index);
|
||||
UndoAction* getRedoAction(S32 index);
|
||||
|
||||
/// Add an action to the top of the undo stack, and clear the redo stack.
|
||||
void addAction(UndoAction* action);
|
||||
void removeAction(UndoAction* action, bool noDelete = false);
|
||||
|
||||
/// @name Compound Actions
|
||||
///
|
||||
/// The compound action stack allows to redirect undos to a CompoundUndoAction
|
||||
/// and thus assemble multi-operation undos directly through the UndoManager.
|
||||
/// When the bottom-most CompoundUndoAction is popped off the stack, the compound
|
||||
/// will be moved onto the undo stack.
|
||||
///
|
||||
/// @{
|
||||
|
||||
/// Push a compound action called "name" onto the compound stack. While the
|
||||
/// compound stack is not empty, all undos that are queued on the undo manager will
|
||||
/// go to the topmost compound instead of the undo stack.
|
||||
CompoundUndoAction* pushCompound( const String& name );
|
||||
|
||||
/// Pop the topmost compound off the compound stack and add it to the undo manager.
|
||||
/// If the compound stack is still not empty, the compound will be added to the next
|
||||
/// lower compound on the stack. Otherwise it will be recorded as a regular undo.
|
||||
void popCompound( bool discard = false );
|
||||
|
||||
/// Return the current nesting depth of the compound stack.
|
||||
U32 getCompoundStackDepth() const { return mCompoundStack.size(); }
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
|
||||
/// Script Undo Action Creation
|
||||
///
|
||||
/// Undo actions can be created in script like this:
|
||||
///
|
||||
/// ...
|
||||
/// %undo = new UndoScriptAction() { class = SampleUndo; actionName = "Sample Undo"; };
|
||||
/// %undo.addToManager(UndoManager);
|
||||
/// ...
|
||||
///
|
||||
/// function SampleUndo::undo()
|
||||
/// {
|
||||
/// ...
|
||||
/// }
|
||||
///
|
||||
/// function SampleUndo::redo()
|
||||
/// {
|
||||
/// ...
|
||||
/// }
|
||||
///
|
||||
class UndoScriptAction : public UndoAction
|
||||
{
|
||||
public:
|
||||
typedef UndoAction Parent;
|
||||
|
||||
UndoScriptAction() : UndoAction()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void undo() { Con::executef(this, "undo"); };
|
||||
virtual void redo() { Con::executef(this, "redo"); }
|
||||
|
||||
virtual bool onAdd()
|
||||
{
|
||||
// Let Parent Do Work.
|
||||
if(!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
|
||||
// Notify Script.
|
||||
if(isMethod("onAdd"))
|
||||
Con::executef(this, "onAdd");
|
||||
|
||||
// Return Success.
|
||||
return true;
|
||||
};
|
||||
|
||||
virtual void onRemove()
|
||||
{
|
||||
if (mUndoManager)
|
||||
mUndoManager->removeAction((UndoAction*)this, true);
|
||||
|
||||
// notify script
|
||||
if(isMethod("onRemove"))
|
||||
Con::executef(this, "onRemove");
|
||||
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
DECLARE_CONOBJECT(UndoScriptAction);
|
||||
};
|
||||
|
||||
#endif // _UNDO_H_
|
||||
Loading…
Add table
Add a link
Reference in a new issue