Engine directory for ticket #1

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

View file

@ -0,0 +1,200 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/editor/forestBrushElement.h"
#include "console/consoleTypes.h"
#include "forest/forestItem.h"
//-------------------------------------------------------------------------
// ForestBrushElement
//-------------------------------------------------------------------------
IMPLEMENT_CONOBJECT( ForestBrushElement );
ConsoleDocClass( ForestBrushElement,
"@brief Represents a type of ForestItem and parameters for how it is placed"
" when painting with a ForestBrush that contains it.\n\n"
"@ingroup Forest"
);
ForestBrushElement::ForestBrushElement()
: mData( NULL ),
mProbability( 1 ),
mRotationRange( 360 ),
mScaleMin( 1 ),
mScaleMax( 1 ),
mScaleExponent( 1 ),
mSinkMin( 0.0f ),
mSinkMax( 0.0f ),
mSinkRadius( 1 ),
mSlopeMax( 90.0f ),
mSlopeMin( 0.0f ),
mElevationMin( -10000.0f ),
mElevationMax( 10000.0f )
{
}
void ForestBrushElement::initPersistFields()
{
Parent::initPersistFields();
addGroup( "ForestBrushElement" );
addField( "forestItemData", TYPEID< ForestItemData >(), Offset( mData, ForestBrushElement ),
"The type of ForestItem this element holds placement parameters for." );
addField( "probability", TypeF32, Offset( mProbability, ForestBrushElement ),
"The probability that this element will be created during an editor brush stroke "
"is the sum of all element probabilities in the brush divided by the probability "
"of this element." );
addField( "rotationRange", TypeF32, Offset( mRotationRange, ForestBrushElement ),
"The max rotation in degrees that items will be placed." );
addField( "scaleMin", TypeF32, Offset( mScaleMin, ForestBrushElement ),
"The minimum random size for each item." );
addField( "scaleMax", TypeF32, Offset( mScaleMax, ForestBrushElement ),
"The maximum random size of each item." );
addField( "scaleExponent", TypeF32, Offset( mScaleExponent, ForestBrushElement ),
"An exponent used to bias between the minimum and maximum random sizes." );
addField( "sinkMin", TypeF32, Offset( mSinkMin, ForestBrushElement ),
"Min variation in the sink radius." );
addField( "sinkMax", TypeF32, Offset( mSinkMax, ForestBrushElement ),
"Max variation in the sink radius." );
addField( "sinkRadius", TypeF32, Offset( mSinkRadius, ForestBrushElement ),
"This is the radius used to calculate how much to sink the trunk at "
"its base and is used to sink the tree into the ground when its on a slope." );
addField( "slopeMin", TypeF32, Offset( mSlopeMin, ForestBrushElement ),
"The min surface slope in degrees this item will be placed on." );
addField( "slopeMax", TypeF32, Offset( mSlopeMax, ForestBrushElement ),
"The max surface slope in degrees this item will be placed on." );
addField( "elevationMin", TypeF32, Offset( mElevationMin, ForestBrushElement ),
"The min world space elevation this item will be placed." );
addField( "elevationMax", TypeF32, Offset( mElevationMax, ForestBrushElement ),
"The max world space elevation this item will be placed." );
endGroup( "ForestBrushElement" );
}
//-------------------------------------------------------------------------
// ForestBrushElementSet
//-------------------------------------------------------------------------
SimObjectPtr<SimGroup> ForestBrush::smGroup = NULL;
IMPLEMENT_CONOBJECT( ForestBrush );
ConsoleDocClass( ForestBrush,
"@brief Container class for ForestBrushElements\n\n"
"Editor use only.\n\n"
"@internal"
);
ForestBrush::ForestBrush()
{
}
bool ForestBrush::onAdd()
{
if ( !Parent::onAdd() )
return false;
getGroup()->addObject( this );
return true;
}
void ForestBrush::addObject( SimObject *inObj )
{
ForestBrushElement *ele = dynamic_cast<ForestBrushElement*>( inObj );
if ( !ele )
return;
//if ( containsItemData( ele->mData ) )
// return;
Parent::addObject( inObj );
}
SimGroup* ForestBrush::getGroup()
{
if ( !smGroup )
{
SimGroup *dummy;
if ( Sim::findObject( "ForestBrushGroup", dummy ) )
{
smGroup = dummy;
return smGroup;
}
smGroup = new SimGroup;
smGroup->assignName( "ForestBrushGroup" );
smGroup->registerObject();
Sim::getRootGroup()->addObject( smGroup );
}
return smGroup;
}
bool ForestBrush::containsItemData( const ForestItemData *inData )
{
SimObjectList::iterator iter = objectList.begin();
for ( ; iter != objectList.end(); iter++ )
{
ForestBrushElement *pElement = dynamic_cast<ForestBrushElement*>(*iter);
if ( !pElement )
continue;
if ( pElement->mData == inData )
return true;
}
return false;
}
ConsoleMethod( ForestBrush, containsItemData, bool, 3, 3, "( ForestItemData obj )" )
{
ForestItemData *data = NULL;
if ( !Sim::findObject( argv[2], data ) )
{
Con::warnf( "ForestBrush::containsItemData - invalid object passed" );
return false;
}
return object->containsItemData( data );
}

View file

@ -0,0 +1,125 @@
//-----------------------------------------------------------------------------
// 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 _FOREST_EDITOR_BRUSHELEMENT_H_
#define _FOREST_EDITOR_BRUSHELEMENT_H_
//#ifndef _SIMOBJECT_H_
//#include "console/simObject.h"
//#endif
#ifndef _SIMSET_H_
#include "console/simSet.h"
#endif
//-------------------------------------------------------------------------
// ForestBrushElement
//-------------------------------------------------------------------------
class ForestItemData;
class ForestBrushElement : public SimObject
{
typedef SimObject Parent;
public:
ForestBrushElement();
DECLARE_CONOBJECT( ForestBrushElement );
static void initPersistFields();
public:
/// The type of ForestItem this element holds placement parameters for.
ForestItemData *mData;
/// The probability that this element will be
/// created during an editor brush stroke.
F32 mProbability;
/// The max rotation in degrees that items will be placed.
F32 mRotationRange;
/// The minimum random size for each item.
F32 mScaleMin;
/// The maximum random size of each item.
F32 mScaleMax;
/// An exponent used to bias between the minimum
/// and maximum random sizes.
F32 mScaleExponent;
/// Min variation in the sink radius.
F32 mSinkMin;
/// Max variation in the sink radius.
F32 mSinkMax;
/// This is the radius used to calculate how much to
/// sink the trunk at its base and is used to sink
/// the tree into the ground when its on a slope.
F32 mSinkRadius;
/// The min surface slope in degrees this item will be placed on.
F32 mSlopeMin;
/// The max surface slope in degrees this item will be placed on.
F32 mSlopeMax;
/// The min world space elevation this item will be placed.
F32 mElevationMin;
/// The max world space elevation this item will be placed.
F32 mElevationMax;
};
//-------------------------------------------------------------------------
// ForestBrush
//-------------------------------------------------------------------------
class ForestBrush : public SimGroup
{
typedef SimGroup Parent;
public:
ForestBrush();
DECLARE_CONOBJECT( ForestBrush );
virtual bool onAdd();
virtual void addObject(SimObject*);
static SimGroup* getGroup();
bool containsItemData( const ForestItemData *inData );
protected:
static SimObjectPtr<SimGroup> smGroup;
};
#endif // _FOREST_EDITOR_BRUSHELEMENT_H_

View file

@ -0,0 +1,687 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/editor/forestBrushTool.h"
#include "forest/forest.h"
#include "forest/editor/forestUndo.h"
#include "forest/editor/forestBrushElement.h"
#include "forest/editor/forestEditorCtrl.h"
#include "gui/worldEditor/editTSCtrl.h"
#include "console/consoleTypes.h"
#include "core/util/tVector.h"
#include "gfx/gfxDrawUtil.h"
#include "gui/core/guiCanvas.h"
#include "gfx/primBuilder.h"
#include "gui/controls/guiTreeViewCtrl.h"
#include "core/strings/stringUnit.h"
#include "math/mRandomDeck.h"
#include "math/mRandomSet.h"
bool ForestBrushTool::protectedSetSize( void *object, const char *index, const char *data )
{
ForestBrushTool *tool = static_cast<ForestBrushTool*>( object );
F32 val = dAtof(data);
tool->setSize( val );
return false;
}
bool ForestBrushTool::protectedSetPressure( void *object, const char *index, const char *data )
{
ForestBrushTool *tool = static_cast<ForestBrushTool*>( object );
F32 val = dAtof(data);
tool->setPressure( val );
return false;
}
bool ForestBrushTool::protectedSetHardness( void *object, const char *index, const char *data )
{
ForestBrushTool *tool = static_cast<ForestBrushTool*>( object );
F32 val = dAtof(data);
tool->setHardness( val );
return false;
}
ImplementEnumType( ForestBrushMode,
"Active brush mode type.\n"
"@internal\n\n")
{ ForestBrushTool::Paint, "Paint", "Creates Items based on the Elements you have selected.\n" },
{ ForestBrushTool::Erase, "Erase", "Erases Items of any Mesh type.\n" },
{ ForestBrushTool::EraseSelected, "EraseSelected", "Erases items of a specific type.\n" },
EndImplementEnumType;
ForestBrushTool::ForestBrushTool()
: mSize( 5.0f ),
mPressure( 0.1f ),
mHardness( 1.0f ),
mDrawBrush( false ),
mCurrAction( NULL ),
mStrokeEvent( 0 ),
mBrushDown( false ),
mColor( ColorI::WHITE ),
mMode( Paint ),
mRandom( Platform::getRealMilliseconds() + 1 )
{
}
ForestBrushTool::~ForestBrushTool()
{
}
IMPLEMENT_CONOBJECT( ForestBrushTool );
ConsoleDocClass( ForestBrushTool,
"@brief Defines the brush properties when painting trees in Forest Editor\n\n"
"Editor use only.\n\n"
"@internal"
);
void ForestBrushTool::initPersistFields()
{
addGroup( "ForestBrushTool" );
addField( "mode", TYPEID< BrushMode >(), Offset( mMode, ForestBrushTool) );
addProtectedField( "size", TypeF32, Offset( mSize, ForestBrushTool ),
&protectedSetSize, &defaultProtectedGetFn, "Brush Size" );
addProtectedField( "pressure", TypeF32, Offset( mPressure, ForestBrushTool ),
&protectedSetPressure, &defaultProtectedGetFn, "Brush Pressure" );
addProtectedField( "hardness", TypeF32, Offset( mHardness, ForestBrushTool ),
&protectedSetHardness, &defaultProtectedGetFn, "Brush Hardness" );
endGroup( "ForestBrushTool" );
Parent::initPersistFields();
}
bool ForestBrushTool::onAdd()
{
if ( !Parent::onAdd() )
return false;
return true;
}
void ForestBrushTool::onRemove()
{
Parent::onRemove();
}
void ForestBrushTool::on3DMouseDown( const Gui3DMouseEvent &evt )
{
Con::executef( this, "onMouseDown" );
if ( !_updateBrushPoint( evt ) || !mForest )
return;
mBrushDown = true;
mEditor->getRoot()->showCursor( false );
_collectElements();
_onStroke();
return;
}
void ForestBrushTool::on3DMouseUp( const Gui3DMouseEvent &evt )
{
_updateBrushPoint( evt );
Sim::cancelEvent( mStrokeEvent );
mBrushDown = false;
mEditor->getRoot()->showCursor( true );
if ( mCurrAction )
{
_submitUndo( mCurrAction );
mCurrAction = NULL;
}
//mElements.clear();
}
void ForestBrushTool::on3DMouseMove( const Gui3DMouseEvent &evt )
{
_updateBrushPoint( evt );
}
void ForestBrushTool::on3DMouseDragged( const Gui3DMouseEvent &evt )
{
_updateBrushPoint( evt );
if ( mBrushDown && !Sim::isEventPending( mStrokeEvent ) )
mStrokeEvent = Sim::postEvent( this, new ForestBrushToolEvent(), Sim::getCurrentTime() + 250 );
}
bool ForestBrushTool::onMouseWheel( const GuiEvent &evt )
{
if ( evt.modifier & SI_PRIMARY_CTRL )
setPressure( mPressure + evt.fval * ( 0.05f / 120.0f ) );
else if ( evt.modifier & SI_SHIFT )
setHardness( mHardness + evt.fval * ( 0.05f / 120.0f ) );
else
setSize( mSize + evt.fval * ( 1.0f / 120.0f ) );
return true;
}
void ForestBrushTool::onRender3D( )
{
}
void ForestBrushTool::onRender2D()
{
if ( !mDrawBrush )
return;
if ( !mEditor->getRoot()->isCursorON() && !mBrushDown )
return;
RayInfo ri;
Point3F start( 0, 0, mLastBrushPoint.z + mSize );
Point3F end( 0, 0, mLastBrushPoint.z - mSize );
Vector<Point3F> pointList;
const U32 steps = 32;
if ( mForest )
mForest->disableCollision();
for ( S32 i = 0; i < steps; i++ )
{
F32 radians = (F32)i / (F32)(steps-1) * M_2PI_F;
VectorF vec(0,1,0);
MathUtils::vectorRotateZAxis( vec, radians );
end.x = start.x = mLastBrushPoint.x + vec.x * mSize;
end.y = start.y = mLastBrushPoint.y + vec.y * mSize;
bool hit = gServerContainer.castRay( start, end, TerrainObjectType | StaticShapeObjectType, &ri );
if ( hit )
pointList.push_back( ri.point );
}
if ( mForest )
mForest->enableCollision();
if ( pointList.empty() )
return;
ColorI brushColor( ColorI::WHITE );
if ( mMode == Paint )
brushColor = ColorI::BLUE;
else if ( mMode == Erase )
brushColor = ColorI::RED;
else if ( mMode == EraseSelected )
brushColor.set( 150, 0, 0 );
if ( mMode == Paint || mMode == EraseSelected )
{
if ( mElements.empty() )
{
brushColor.set( 140, 140, 140 );
}
}
mEditor->drawLineList( pointList, brushColor, 1.5f );
}
void ForestBrushTool::onActivated( const Gui3DMouseEvent &lastEvent )
{
_updateBrushPoint( lastEvent );
Con::executef( this, "onActivated" );
}
void ForestBrushTool::onDeactivated()
{
Con::executef( this, "onDeactivated" );
}
bool ForestBrushTool::updateGuiInfo()
{
GuiTextCtrl *statusbar;
Sim::findObject( "EWorldEditorStatusBarInfo", statusbar );
GuiTextCtrl *selectionBar;
Sim::findObject( "EWorldEditorStatusBarSelection", selectionBar );
String text;
if ( mMode == Paint )
text = "Forest Editor ( Paint Tool ) - This brush creates Items based on the Elements you have selected.";
else if ( mMode == Erase )
text = "Forest Editor ( Erase Tool ) - This brush erases Items of any Mesh type.";
else if ( mMode == EraseSelected )
text = "Forest Editor ( Erase Selected ) - This brush erases Items based on the Elements you have selected.";
if ( statusbar )
statusbar->setText( text );
if ( mMode == Paint || mMode == EraseSelected )
text = String::ToString( "%i elements selected", mElements.size() );
else
text = "";
if ( selectionBar )
selectionBar->setText( text );
return true;
}
void ForestBrushTool::setSize( F32 val )
{
mSize = mClampF( val, 0.0f, 150.0f );
Con::executef( this, "syncBrushToolbar" );
}
void ForestBrushTool::setPressure( F32 val )
{
mPressure = mClampF( val, 0.0f, 1.0f );
Con::executef( this, "syncBrushToolbar" );
}
void ForestBrushTool::setHardness( F32 val )
{
mHardness = mClampF( val, 0.0f, 1.0f );
Con::executef( this, "syncBrushToolbar" );
}
void ForestBrushTool::_onStroke()
{
if ( !mForest || !mBrushDown )
return;
_action( mLastBrushPoint );
}
void ForestBrushTool::_action( const Point3F &point )
{
if ( mMode == Paint )
_paint( point );
else if ( mMode == Erase || mMode == EraseSelected )
_erase( point );
}
inline F32 mCircleArea( F32 radius )
{
return radius * radius * M_PI_F;
}
void ForestBrushTool::_paint( const Point3F &point )
{
AssertFatal( mForest, "ForestBrushTool::_paint() - Can't paint without a Forest!" );
if ( mElements.empty() )
return;
// Iterators, pointers, and temporaries.
ForestBrushElement *pElement;
ForestItemData *pData;
F32 radius, area;
// How much area do we have to fill with trees ( within the brush ).
F32 fillArea = mCircleArea( mSize );
// Scale that down by pressure, this is how much area we want to fill.
fillArea *= mPressure;
// Create an MRandomSet we can get items we are painting out of with
// the desired distribution.
// Also grab the smallest and largest radius elements while we are looping.
ForestBrushElement *smallestElement, *largestElement;
smallestElement = largestElement = mElements[0];
MRandomSet<ForestBrushElement*> randElementSet(&mRandom);
for ( S32 i = 0; i < mElements.size(); i++ )
{
pElement = mElements[i];
if ( pElement->mData->mRadius > largestElement->mData->mRadius )
largestElement = pElement;
if ( pElement->mData->mRadius < smallestElement->mData->mRadius )
smallestElement = pElement;
randElementSet.add( pElement, pElement->mProbability );
}
// Pull elements from the random set until we would theoretically fill
// the desired area.
F32 areaLeft = fillArea;
F32 scaleFactor, sink, randRot, worldCoordZ, slope;
Point2F worldCoords;
Point3F normalVector, right;
Point2F temp;
const S32 MaxTries = 5;
while ( areaLeft > 0.0f )
{
pElement = randElementSet.get();
pData = pElement->mData;
scaleFactor = mLerp( pElement->mScaleMin, pElement->mScaleMax, mClampF( mPow( mRandom.randF(), pElement->mScaleExponent ), 0.0f, 1.0f ) );
radius = getMax( pData->mRadius * scaleFactor, 0.1f );
area = mCircleArea( radius );
areaLeft -= area * 5.0f; // fudge value
// No room left we are done.
//if ( areaLeft < 0.0f )
// break;
// We have area left to fill...
const F32 rotRange = mDegToRad( pElement->mRotationRange );
// Get a random sink value.
sink = mRandom.randF( pElement->mSinkMin, pElement->mSinkMax ) * scaleFactor;
// Get a random rotation.
randRot = mRandom.randF( 0.0f, rotRange );
// Look for a place within the brush area to place this item.
// We may have to try several times or give and go onto the next.
S32 i = 0;
for ( ; i < MaxTries; i++ )
{
// Pick some randoms for placement.
worldCoords = MathUtils::randomPointInCircle( mSize ) + point.asPoint2F();
// Look for the ground at this position.
if ( !getGroundAt( Point3F( worldCoords.x, worldCoords.y , point.z ),
&worldCoordZ,
&normalVector ) )
{
continue;
}
// Does this pass our slope and elevation limits.
right.set( normalVector.x, normalVector.y, 0 );
right.normalizeSafe();
slope = mRadToDeg( mDot( right, normalVector ) );
if ( worldCoordZ < pElement->mElevationMin ||
worldCoordZ > pElement->mElevationMax ||
slope < pElement->mSlopeMin ||
slope > pElement->mSlopeMax )
{
continue;
}
// Are we up against another tree?
if ( mForest->getData()->getItems( worldCoords, radius, NULL ) > 0 )
continue;
// If the trunk radius is set then we need to sink
// the tree into the ground a bit to hide the bottom.
if ( pElement->mSinkRadius > 0.0f )
{
// Items that are not aligned to the ground surface
// get sunken down to hide the bottom of their trunks.
// sunk down a bit to hide their bottoms on slopes.
normalVector.z = 0;
normalVector.normalizeSafe();
normalVector *= sink;
temp = worldCoords + normalVector.asPoint2F();
getGroundAt( Point3F( temp.x, temp.y, point.z ),
&worldCoordZ,
NULL );
}
worldCoordZ -= sink;
// Create a new undo action if this is a new stroke.
ForestCreateUndoAction *action = dynamic_cast<ForestCreateUndoAction*>( mCurrAction );
if ( !action )
{
action = new ForestCreateUndoAction( mForest->getData(), mEditor );
mCurrAction = action;
}
//Con::printf( "worldCoords = %g, %g, %g", worldCoords.x, worldCoords.y, worldCoordZ );
// Let the action manage adding it to the forest.
action->addItem( pData,
Point3F( worldCoords.x, worldCoords.y, worldCoordZ ),
randRot,
scaleFactor );
break;
}
}
}
void ForestBrushTool::_erase( const Point3F &point )
{
AssertFatal( mForest, "ForestBrushTool::_erase() - Can't erase without a Forest!" );
// First grab all the forest items around the point.
ForestItemVector trees;
if ( mForest->getData()->getItems( point.asPoint2F(), mSize, &trees ) == 0 )
return;
if ( mMode == EraseSelected )
{
for ( U32 i = 0; i < trees.size(); i++ )
{
const ForestItem &tree = trees[i];
if ( !mDatablocks.contains( tree.getData() ) )
{
trees.erase_fast( i );
i--;
}
}
}
if ( trees.empty() )
return;
// Number of trees to erase depending on pressure.
S32 eraseCount = getMax( (S32)mCeil( (F32)trees.size() * mPressure ), 0 );
// Initialize an MRandomDeck with trees under the brush.
MRandomDeck<ForestItem> deck(&mRandom);
deck.addToPile( trees );
deck.shuffle();
ForestItem currentTree;
// Draw eraseCount number of trees from MRandomDeck, adding them to our erase action.
for ( U32 i = 0; i < eraseCount; i++ )
{
deck.draw(&currentTree);
// Create a new undo action if this is a new stroke.
ForestDeleteUndoAction *action = dynamic_cast<ForestDeleteUndoAction*>( mCurrAction );
if ( !action )
{
action = new ForestDeleteUndoAction( mForest->getData(), mEditor );
mCurrAction = action;
}
action->removeItem( currentTree );
}
}
bool ForestBrushTool::_updateBrushPoint( const Gui3DMouseEvent &event_ )
{
// Do a raycast for terrain... thats the placement center.
const U32 mask = TerrainObjectType | StaticShapeObjectType; // TODO: Make an option!
Point3F start( event_.pos );
Point3F end( event_.pos + ( event_.vec * 10000.0f ) );
if ( mForest )
mForest->disableCollision();
RayInfo rinfo;
mDrawBrush = gServerContainer.castRay( start, end, mask, &rinfo );
if ( mForest )
mForest->enableCollision();
if ( mDrawBrush )
{
mLastBrushPoint = rinfo.point;
mLastBrushNormal = rinfo.normal;
}
return mDrawBrush;
}
bool findSelectedElements( ForestBrushElement *obj )
{
if ( obj->isSelectedRecursive() )
return true;
return false;
}
void ForestBrushTool::_collectElements()
{
mElements.clear();
// Get the selected objects from the tree view.
// These can be a combination of ForestBrush(s) and ForestBrushElement(s).
GuiTreeViewCtrl *brushTree;
if ( !Sim::findObject( "ForestEditBrushTree", brushTree ) )
return;
const char* objectIdList = Con::executef( brushTree, "getSelectedObjectList" );
// Collect those objects in a vector and mark them as selected.
Vector<SimObject*> objectList;
SimObject *simobj;
S32 wordCount = StringUnit::getUnitCount( objectIdList, " " );
for ( S32 i = 0; i < wordCount; i++ )
{
const char* word = StringUnit::getUnit( objectIdList, i, " " );
if ( Sim::findObject( word, simobj ) )
{
objectList.push_back( simobj );
simobj->setSelected(true);
}
}
// Find all ForestBrushElements that are directly or indirectly selected.
SimGroup *brushGroup = ForestBrush::getGroup();
brushGroup->findObjectByCallback( findSelectedElements, mElements );
// We just needed to flag these objects as selected for the benefit of our
// findSelectedElements callback, we can now mark them un-selected again.
for ( S32 i = 0; i < objectList.size(); i++ )
objectList[i]->setSelected(false);
// If we are in Paint or EraseSelected mode we filter out elements with
// a non-positive probability.
if ( mMode == Paint || mMode == EraseSelected )
{
for ( S32 i = 0; i < mElements.size(); i++ )
{
if ( mElements[i]->mProbability <= 0.0f )
{
mElements.erase_fast(i);
i--;
}
}
}
// Filter out elements with NULL datablocks and collect all unique datablocks
// in a vector.
mDatablocks.clear();
for ( S32 i = 0; i < mElements.size(); i++ )
{
if ( mElements[i]->mData == NULL )
{
mElements.erase_fast(i);
i--;
continue;
}
mDatablocks.push_back_unique( mElements[i]->mData );
}
}
bool ForestBrushTool::getGroundAt( const Point3F &worldPt, float *zValueOut, VectorF *normalOut )
{
const U32 mask = TerrainObjectType | StaticShapeObjectType;
Point3F start( worldPt.x, worldPt.y, worldPt.z + mSize );
Point3F end( worldPt.x, worldPt.y, worldPt.z - mSize );
if ( mForest )
mForest->disableCollision();
// Do a cast ray at this point from the top to
// the bottom of our brush radius.
RayInfo rinfo;
bool hit = gServerContainer.castRay( start, end, mask, &rinfo );
if ( mForest )
mForest->enableCollision();
if ( !hit )
return false;
if (zValueOut)
*zValueOut = rinfo.point.z;
if (normalOut)
*normalOut = rinfo.normal;
return true;
}
ConsoleMethod( ForestBrushTool, collectElements, void, 2, 2, "" )
{
object->collectElements();
}

View file

@ -0,0 +1,152 @@
//-----------------------------------------------------------------------------
// 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 _FOREST_EDITOR_BRUSH_H_
#define _FOREST_EDITOR_BRUSH_H_
#ifndef _FOREST_EDITOR_TOOL_H_
#include "forest/editor/forestTool.h"
#endif
#ifndef _FORESTITEM_H_
#include "forest/forestItem.h"
#endif
#ifndef _FOREST_EDITOR_BRUSHELEMENT_H_
#include "forest/editor/forestBrushElement.h"
#endif
#ifndef _COLOR_H_
#include "core/color.h"
#endif
class Forest;
class ForestUndoAction;
class ForestBrushTool : public ForestTool
{
typedef ForestTool Parent;
friend class ForestBrushToolEvent;
public:
enum BrushMode
{
Paint = 0,
Erase,
EraseSelected
};
ForestBrushTool();
virtual ~ForestBrushTool();
// SimObject
DECLARE_CONOBJECT( ForestBrushTool );
static void initPersistFields();
virtual bool onAdd();
virtual void onRemove();
// ForestTool
virtual void on3DMouseDown( const Gui3DMouseEvent &evt );
virtual void on3DMouseUp( const Gui3DMouseEvent &evt );
virtual void on3DMouseMove( const Gui3DMouseEvent &evt );
virtual void on3DMouseDragged( const Gui3DMouseEvent &evt );
virtual bool onMouseWheel(const GuiEvent &evt );
virtual void onRender3D();
virtual void onRender2D();
virtual void onActivated( const Gui3DMouseEvent &lastEvent );
virtual void onDeactivated();
virtual bool updateGuiInfo();
// ForestBrushTool
void setSize( F32 val );
void setPressure( F32 val );
void setHardness( F32 val );
void collectElements() { _collectElements(); }
bool getGroundAt( const Point3F &worldPt, float *zValueOut, VectorF *normalOut );
protected:
void _onStroke();
virtual void _action( const Point3F &point );
virtual void _paint( const Point3F &point );
virtual void _erase( const Point3F &point );
bool _updateBrushPoint( const Gui3DMouseEvent &event_ );
virtual void _collectElements();
static bool protectedSetSize( void *object, const char *index, const char *data );
static bool protectedSetPressure( void *object, const char *index, const char *data );
static bool protectedSetHardness( void *object, const char *index, const char *data );
protected:
MRandom mRandom;
/// We treat this as a radius.
F32 mSize;
F32 mPressure;
F32 mHardness;
U32 mMode;
ColorI mColor;
Vector<ForestBrushElement*> mElements;
Vector<ForestItemData*> mDatablocks;
///
bool mBrushDown;
///
bool mDrawBrush;
///
U32 mStrokeEvent;
///
Point3F mLastBrushPoint;
Point3F mLastBrushNormal;
/// The creation action we're actively filling.
ForestUndoAction *mCurrAction;
};
typedef ForestBrushTool::BrushMode ForestBrushMode;
DefineEnumType( ForestBrushMode );
class ForestBrushToolEvent : public SimEvent
{
public:
void process( SimObject *object )
{
((ForestBrushTool*)object)->_onStroke();
}
};
#endif // _FOREST_EDITOR_BRUSH_H_

View file

@ -0,0 +1,402 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/editor/forestEditorCtrl.h"
#include "forest/editor/forestBrushTool.h"
#include "console/consoleTypes.h"
#include "gui/core/guiCanvas.h"
#include "windowManager/platformCursorController.h"
#include "forest/editor/forestUndo.h"
#include "gui/worldEditor/undoActions.h"
IMPLEMENT_CONOBJECT( ForestEditorCtrl );
ConsoleDocClass( ForestEditorCtrl,
"@brief The actual Forest Editor control\n\n"
"Editor use only, should not be modified.\n\n"
"@internal"
);
ForestEditorCtrl::ForestEditorCtrl()
{
dMemset( &mLastEvent, 0, sizeof(Gui3DMouseEvent) );
}
ForestEditorCtrl::~ForestEditorCtrl()
{
}
bool ForestEditorCtrl::onAdd()
{
if ( !Parent::onAdd() )
return false;
return true;
}
void ForestEditorCtrl::initPersistFields()
{
Parent::initPersistFields();
}
bool ForestEditorCtrl::onWake()
{
if ( !Parent::onWake() )
return false;
// Push our default cursor on here once.
GuiCanvas *root = getRoot();
if ( root )
{
S32 currCursor = PlatformCursorController::curArrow;
PlatformWindow *window = root->getPlatformWindow();
PlatformCursorController *controller = window->getCursorController();
controller->pushCursor( currCursor );
}
return true;
}
void ForestEditorCtrl::onSleep()
{
// Pop our default cursor off.
GuiCanvas *root = getRoot();
if ( root )
{
PlatformWindow *window = root->getPlatformWindow();
PlatformCursorController *controller = window->getCursorController();
controller->popCursor();
}
Parent::onSleep();
}
bool ForestEditorCtrl::updateActiveForest( bool createNew )
{
mForest = dynamic_cast<Forest*>( Sim::findObject( "theForest" ) );
Con::executef( this, "onActiveForestUpdated", mForest ? mForest->getIdString() : "", createNew ? "1" : "0" );
if ( mTool )
mTool->setActiveForest( mForest );
return mForest;
}
void ForestEditorCtrl::setActiveTool( ForestTool *tool )
{
if ( mTool )
{
mTool->onDeactivated();
}
mTool = tool;
if ( mTool )
{
mTool->setActiveForest( mForest );
mTool->setParentEditor( this );
mTool->onActivated( mLastEvent );
}
}
void ForestEditorCtrl::onMouseUp( const GuiEvent &event_ )
{
Parent::onMouseUp( event_ );
}
void ForestEditorCtrl::get3DCursor( GuiCursor *&cursor,
bool &visible,
const Gui3DMouseEvent &event_ )
{
cursor = NULL;
visible = false;
GuiCanvas *root = getRoot();
if ( !root )
return;
S32 currCursor = PlatformCursorController::curArrow;
if ( root->mCursorChanged == currCursor )
return;
PlatformWindow *window = root->getPlatformWindow();
PlatformCursorController *controller = window->getCursorController();
// We've already changed the cursor,
// so set it back before we change it again.
if( root->mCursorChanged != -1)
controller->popCursor();
// Now change the cursor shape
controller->pushCursor(currCursor);
root->mCursorChanged = currCursor;
}
void ForestEditorCtrl::on3DMouseDown( const Gui3DMouseEvent &evt )
{
if ( !mForest && !updateActiveForest( true ) )
return;
if ( mTool )
mTool->on3DMouseDown( evt );
mouseLock();
}
void ForestEditorCtrl::on3DMouseUp( const Gui3DMouseEvent &evt )
{
if ( !isMouseLocked() )
return;
if ( mTool )
mTool->on3DMouseUp( evt );
mouseUnlock();
}
void ForestEditorCtrl::on3DMouseMove( const Gui3DMouseEvent &evt )
{
if ( !mForest )
updateActiveForest( false );
if ( mTool )
mTool->on3DMouseMove( evt );
}
void ForestEditorCtrl::on3DMouseDragged( const Gui3DMouseEvent &evt )
{
if ( mTool )
mTool->on3DMouseDragged( evt );
}
void ForestEditorCtrl::on3DMouseEnter( const Gui3DMouseEvent &evt )
{
if ( mTool )
mTool->on3DMouseEnter( evt );
}
void ForestEditorCtrl::on3DMouseLeave( const Gui3DMouseEvent &evt )
{
if ( mTool )
mTool->on3DMouseLeave( evt );
}
void ForestEditorCtrl::on3DRightMouseDown( const Gui3DMouseEvent &evt )
{
}
void ForestEditorCtrl::on3DRightMouseUp( const Gui3DMouseEvent &evt )
{
}
bool ForestEditorCtrl::onMouseWheelUp(const GuiEvent &event_)
{
if ( mTool )
return mTool->onMouseWheel( event_ );
return Parent::onMouseWheelUp( event_ );
}
bool ForestEditorCtrl::onMouseWheelDown(const GuiEvent &event_)
{
if ( mTool )
return mTool->onMouseWheel( event_ );
return Parent::onMouseWheelDown( event_ );
}
void ForestEditorCtrl::updateGuiInfo()
{
// Note: This is intended to be used for updating
// GuiControls with info before they are rendered.
SimObject *statusbar;
Sim::findObject( "EditorGuiStatusBar", statusbar );
SimObject *selectionBar;
Sim::findObject( "EWorldEditorStatusBarSelection", selectionBar );
String text;
if ( !mForest )
{
if ( statusbar )
Con::executef( statusbar, "setInfo", "Forest Editor. You have no Forest in your level; click anywhere in the scene to create one." );
if ( selectionBar )
Con::executef( selectionBar, "setInfo", "" );
return;
}
if ( mTool )
{
if ( mTool->updateGuiInfo() )
return;
}
// Tool did not handle the update so we will.
if ( statusbar )
Con::executef( statusbar, "setInfo", "Forest Editor." );
if ( selectionBar )
Con::executef( selectionBar, "setInfo", "" );
}
void ForestEditorCtrl::renderScene( const RectI &updateRect )
{
if ( mTool )
mTool->onRender3D();
}
void ForestEditorCtrl::updateGizmo()
{
if ( mTool )
mTool->updateGizmo();
}
void ForestEditorCtrl::renderGui( Point2I offset, const RectI &updateRect )
{
if ( mTool )
mTool->onRender2D();
}
static ForestItemData* sKey = NULL;
bool findMeshReferences( SimObject *obj )
{
ForestBrushElement *element = dynamic_cast<ForestBrushElement*>(obj);
if ( element && element->mData == sKey )
return true;
return false;
}
void ForestEditorCtrl::onUndoAction()
{
if ( mTool )
mTool->onUndoAction();
updateCollision();
}
void ForestEditorCtrl::deleteMeshSafe( ForestItemData *mesh )
{
UndoManager *undoMan = NULL;
if ( !Sim::findObject( "EUndoManager", undoMan ) )
{
Con::errorf( "ForestEditorCtrl::deleteMeshSafe() - EUndoManager not found." );
return;
}
// CompoundUndoAction which will delete the ForestItemData, ForestItem(s), and ForestBrushElement(s).
CompoundUndoAction *compoundAction = new CompoundUndoAction( "Delete Forest Mesh" );
// Find ForestItem(s) referencing this datablock and add their deletion
// to the undo action.
if ( mForest )
{
Vector<ForestItem> foundItems;
mForest->getData()->getItems( mesh, &foundItems );
ForestDeleteUndoAction *itemAction = new ForestDeleteUndoAction( mForest->getData(), this );
itemAction->removeItem( foundItems );
compoundAction->addAction( itemAction );
}
// Find ForestBrushElement(s) referencing this datablock.
SimGroup *brushGroup = ForestBrush::getGroup();
sKey = mesh;
Vector<SimObject*> foundElements;
brushGroup->findObjectByCallback( &findMeshReferences, foundElements );
// Add UndoAction to delete the ForestBrushElement(s) and the ForestItemData.
MEDeleteUndoAction *elementAction = new MEDeleteUndoAction();
elementAction->deleteObject( foundElements );
elementAction->deleteObject( mesh );
// Add compound action to the UndoManager. Done.
undoMan->addAction( compoundAction );
updateCollision();
}
void ForestEditorCtrl::updateCollision()
{
if ( mForest )
{
mForest->updateCollision();
if ( mForest->getClientObject() )
((Forest*)(mForest->getClientObject()))->updateCollision();
}
}
void FindDirtyForests( SceneObject *obj, void *key )
{
Forest *forest = dynamic_cast<Forest*>(obj);
if ( forest && forest->getData()->isDirty() )
*((bool*)(key)) = true;
}
bool ForestEditorCtrl::isDirty()
{
bool foundDirty = false;
gServerContainer.findObjects( EnvironmentObjectType, FindDirtyForests, (void*)&foundDirty );
return foundDirty;
}
ConsoleMethod( ForestEditorCtrl, updateActiveForest, void, 2, 2, "()" )
{
object->updateActiveForest( true );
}
ConsoleMethod( ForestEditorCtrl, setActiveTool, void, 3, 3, "( ForestTool tool )" )
{
ForestTool *tool = dynamic_cast<ForestTool*>( Sim::findObject( argv[2] ) );
object->setActiveTool( tool );
}
ConsoleMethod( ForestEditorCtrl, getActiveTool, S32, 2, 2, "()" )
{
ForestTool *tool = object->getActiveTool();
return tool ? tool->getId() : 0;
}
ConsoleMethod( ForestEditorCtrl, deleteMeshSafe, void, 3, 3, "( ForestItemData obj )" )
{
ForestItemData *db;
if ( !Sim::findObject( argv[2], db ) )
return;
object->deleteMeshSafe( db );
}
ConsoleMethod( ForestEditorCtrl, isDirty, bool, 2, 2, "" )
{
return object->isDirty();
}

View 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 _FOREST_EDITOR_FORESTEDITORCTRL_H_
#define _FOREST_EDITOR_FORESTEDITORCTRL_H_
#ifndef _EDITTSCTRL_H_
#include "gui/worldEditor/editTSCtrl.h"
#endif
#ifndef _H_FOREST_
#include "forest/forest.h"
#endif
#ifndef _FOREST_EDITOR_TOOL_H_
#include "forest/editor/forestTool.h"
#endif
class ForestEditorCtrl : public EditTSCtrl
{
typedef EditTSCtrl Parent;
friend class ForestPaintEvent;
protected:
/// The server Forest we're editing.
SimObjectPtr<Forest> mForest;
/// The active tool in used by the editor.
SimObjectPtr<ForestTool> mTool;
public:
ForestEditorCtrl();
virtual ~ForestEditorCtrl();
DECLARE_CONOBJECT( ForestEditorCtrl );
// SimObject
bool onAdd();
static void initPersistFields();
// GuiControl
virtual bool onWake();
virtual void onSleep();
virtual void onMouseUp( const GuiEvent &event_ );
// EditTSCtrl
void get3DCursor( GuiCursor *&cursor, bool &visible, const Gui3DMouseEvent &event_ );
void on3DMouseDown( const Gui3DMouseEvent &event_ );
void on3DMouseUp( const Gui3DMouseEvent &event_ );
void on3DMouseMove( const Gui3DMouseEvent &event_ );
void on3DMouseDragged( const Gui3DMouseEvent &event_ );
void on3DMouseEnter( const Gui3DMouseEvent &event_ );
void on3DMouseLeave( const Gui3DMouseEvent &event_ );
void on3DRightMouseDown( const Gui3DMouseEvent &event_ );
void on3DRightMouseUp( const Gui3DMouseEvent &event_ );
bool onMouseWheelUp(const GuiEvent &event_);
bool onMouseWheelDown(const GuiEvent &event_);
void updateGuiInfo();
void updateGizmo();
void renderScene( const RectI &updateRect );
void renderGui( Point2I offset, const RectI &updateRect );
/// Causes the editor to reselect the active forest.
bool updateActiveForest( bool createNew );
/// Returns the active Forest.
Forest *getActiveForest() const { return mForest; }
void setActiveTool( ForestTool *tool );
ForestTool* getActiveTool() { return mTool; }
void onUndoAction();
void deleteMeshSafe( ForestItemData *itemData );
void updateCollision();
bool isDirty();
};
#endif // _FOREST_EDITOR_FORESTEDITORCTRL_H_

View file

@ -0,0 +1,590 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/editor/forestSelectionTool.h"
#include "forest/forest.h"
#include "forest/editor/forestUndo.h"
#include "forest/editor/forestEditorCtrl.h"
#include "gui/worldEditor/editTSCtrl.h"
#include "gui/worldEditor/gizmo.h"
#include "console/consoleTypes.h"
#include "core/util/tVector.h"
#include "core/util/safeDelete.h"
#include "gfx/gfxDrawUtil.h"
#include "gui/worldEditor/worldEditor.h"
#include "math/mMatrix.h"
template <>
MatrixF Selection<ForestItem>::getOrientation()
{
if ( size() == 1 )
return first().getTransform();
return MatrixF::Identity;
}
template <>
Point3F Selection<ForestItem>::getOrigin()
{
Point3F centroid( Point3F::Zero );
if ( empty() )
return centroid;
Selection<ForestItem>::iterator itr = begin();
for ( ; itr != end(); itr++ )
{
const MatrixF &mat = itr->getTransform();
Point3F wPos;
mat.getColumn( 3, &wPos );
centroid += wPos;
}
centroid /= (F32)size();
return centroid;
}
template <>
Point3F Selection<ForestItem>::getScale()
{
if ( size() == 1 )
return Point3F( first().getScale() );
return Point3F::One;
}
void ForestItemSelection::offsetObject( ForestItem &object, const Point3F &delta )
{
if ( !mData )
return;
MatrixF newMat( object.getTransform() );
newMat.displace( delta );
object = mData->updateItem( object.getKey(), object.getPosition(), object.getData(), newMat, object.getScale() );
}
void ForestItemSelection::rotateObject( ForestItem &object, const EulerF &delta, const Point3F &origin )
{
if ( !mData )
return;
MatrixF mat = object.getTransform();
Point3F pos;
mat.getColumn( 3, &pos );
MatrixF wMat( mat );
wMat.inverse();
// get offset in obj space
Point3F offset = pos - origin;
if ( size() == 1 )
{
wMat.mulV(offset);
MatrixF transform(EulerF::Zero, -offset);
transform.mul(MatrixF(delta));
transform.mul(MatrixF(EulerF::Zero, offset));
mat.mul(transform);
}
else
{
MatrixF transform( delta );
Point3F wOffset;
transform.mulV( offset, &wOffset );
wMat.mulV( offset );
transform.set( EulerF::Zero, -offset );
mat.setColumn( 3, Point3F::Zero );
wMat.setColumn( 3, Point3F::Zero );
transform.mul( wMat );
transform.mul( MatrixF(delta) );
transform.mul( mat );
mat.mul( transform );
mat.normalize();
mat.setColumn( 3, wOffset + origin );
}
object = mData->updateItem( object.getKey(), object.getPosition(), object.getData(), mat, object.getScale() );
}
void ForestItemSelection::scaleObject( ForestItem &object, const Point3F &delta )
{
if ( !mData )
return;
object = mData->updateItem( object.getKey(), object.getPosition(), object.getData(), object.getTransform(), delta.z );
}
IMPLEMENT_CONOBJECT( ForestSelectionTool );
ConsoleDocClass( ForestSelectionTool,
"@brief Specialized selection tool for picking individual trees in a forest.\n\n"
"Editor use only.\n\n"
"@internal"
);
ForestSelectionTool::ForestSelectionTool()
: Parent(),
mCurrAction( NULL ),
mGizmo( NULL ),
mGizmoProfile( NULL )
{
mBounds = Box3F::Invalid;
mDragRectColor.set(255,255,0);
mMouseDown = false;
mDragSelect = false;
}
ForestSelectionTool::~ForestSelectionTool()
{
SAFE_DELETE( mCurrAction );
}
void ForestSelectionTool::setParentEditor( ForestEditorCtrl *editor )
{
mEditor = editor;
mGizmo = editor->getGizmo();
mGizmoProfile = mGizmo->getProfile();
}
void ForestSelectionTool::_selectItem( const ForestItem &item )
{
// Make sure its not already selected.
for ( U32 i=0; i < mSelection.size(); i++ )
{
if ( mSelection[i].getKey() == item.getKey() )
return;
}
mSelection.push_back( item );
mBounds.intersect( item.getWorldBox() );
}
void ForestSelectionTool::deleteSelection()
{
ForestDeleteUndoAction *action = new ForestDeleteUndoAction( mForest->getData(), mEditor );
for ( U32 i=0; i < mSelection.size(); i++ )
{
const ForestItem &item = mSelection[i];
action->removeItem( item );
}
clearSelection();
_submitUndo( action );
}
void ForestSelectionTool::clearSelection()
{
mSelection.clear();
mBounds = Box3F::Invalid;
}
void ForestSelectionTool::cutSelection()
{
}
void ForestSelectionTool::copySelection()
{
}
void ForestSelectionTool::pasteSelection()
{
}
void ForestSelectionTool::setActiveForest( Forest *forest )
{
mForest = forest;
if ( forest )
mSelection.setForestData( forest->getData() );
else
mSelection.setForestData( NULL );
}
void ForestSelectionTool::on3DMouseDown( const Gui3DMouseEvent &evt )
{
mMouseDown = true;
mMouseDragged = false;
mUsingGizmo = !mSelection.empty() && mGizmo->getSelection() != Gizmo::None;
if ( mUsingGizmo )
{
mGizmo->on3DMouseDown( evt );
return;
}
mDragSelection.clear();
mDragRect.set( Point2I(evt.mousePoint), Point2I(0,0) );
mDragStart = evt.mousePoint;
const bool multiSel = evt.modifier & SI_CTRL;
if ( !multiSel )
clearSelection();
if ( mHoverItem.isValid() )
_selectItem( mHoverItem );
// This should never happen... it should have been
// submitted and nulled in on3DMouseUp()!
//
// Yeah, unless you had a breakpoint there and on3DMouseUp never fired,
// in which case this is really annoying.
//
//AssertFatal( !mCurrAction, "ForestSelectionTool::on3DMouseDown() - Dangling undo action!" );
}
void ForestSelectionTool::on3DMouseMove( const Gui3DMouseEvent &evt )
{
// Invalidate the hover item first... we're gonna find a new one.
mHoverItem.makeInvalid();
if ( !mForest )
return;
if ( !mSelection.empty() )
mGizmo->on3DMouseMove( evt );
RayInfo ri;
ri.userData = new ForestItem;
Point3F startPnt = evt.pos;
Point3F endPnt = evt.pos + evt.vec * 1000.0f;
if ( mForest->castRayRendered( startPnt, endPnt, &ri ) )
mHoverItem = (*(ForestItem*)ri.userData);
delete static_cast<ForestItem*>(ri.userData);
}
void ForestSelectionTool::on3DMouseDragged( const Gui3DMouseEvent &evt )
{
mHoverItem.makeInvalid();
if ( mUsingGizmo )
{
mGizmo->on3DMouseDragged( evt );
const Point3F &deltaRot = mGizmo->getDeltaRot();
const Point3F &deltaPos = mGizmo->getOffset();
const Point3F &deltaScale = mGizmo->getDeltaScale();
if ( deltaRot.isZero() && deltaPos.isZero() && deltaScale.isZero() )
return;
// Store the current item states!
if ( !mCurrAction )
{
mCurrAction = new ForestUpdateAction( mForest->getData(), mEditor );
for ( U32 i=0; i < mSelection.size(); i++ )
{
const ForestItem &item = mSelection[i];
mCurrAction->saveItem( item );
}
}
switch ( mGizmo->getMode() )
{
case MoveMode:
mSelection.offset( deltaPos ); break;
case RotateMode:
mSelection.rotate( deltaRot ); break;
case ScaleMode:
mSelection.scale( mGizmo->getScale() ); break;
default: ;
}
return;
}
mDragSelect = true;
mHoverItem.makeInvalid();
// Doing a drag selection.
if ( mDragSelect )
{
// build the drag selection on the renderScene method - make sure no neg extent!
mDragRect.point.x = (evt.mousePoint.x < mDragStart.x) ? evt.mousePoint.x : mDragStart.x;
mDragRect.extent.x = (evt.mousePoint.x > mDragStart.x) ? evt.mousePoint.x - mDragStart.x : mDragStart.x - evt.mousePoint.x;
mDragRect.point.y = (evt.mousePoint.y < mDragStart.y) ? evt.mousePoint.y : mDragStart.y;
mDragRect.extent.y = (evt.mousePoint.y > mDragStart.y) ? evt.mousePoint.y - mDragStart.y : mDragStart.y - evt.mousePoint.y;
return;
}
}
void ForestSelectionTool::on3DMouseUp( const Gui3DMouseEvent &evt )
{
mGizmo->on3DMouseUp( evt );
mMouseDown = false;
// If we have an undo action then we must have
// moved, rotated, or scaled something.
if ( mCurrAction )
{
_submitUndo( mCurrAction );
mCurrAction = NULL;
return;
}
// If we were performing a drag select, finalize it now.
if ( mDragSelect )
{
mDragSelect = false;
clearSelection();
for ( S32 i = 0; i < mDragSelection.size(); i++ )
_selectItem( mDragSelection[i] );
mDragSelection.clear();
return;
}
}
void ForestSelectionTool::onRender3D()
{
GFXDrawUtil *drawUtil = GFX->getDrawUtil();
ColorI color( 255, 255, 255, 255 );
MatrixF treeMat;
GFXStateBlockDesc desc;
desc.setBlend( true );
desc.setZReadWrite( true, false );
if ( mHoverItem.isValid() )
{
treeMat = mHoverItem.getTransform();
drawUtil->drawObjectBox( desc, mHoverItem.getSize(), mHoverItem.getWorldBox().getCenter(), treeMat, color );
}
if ( !mSelection.empty() )
{
for ( U32 i = 0; i < mSelection.size(); i++ )
{
const ForestItem &item = mSelection[i];
treeMat = item.getTransform();
drawUtil->drawObjectBox( desc, item.getSize(), item.getWorldBox().getCenter(), treeMat, color );
}
mGizmo->set( mSelection.getOrientation(), mSelection.getOrigin(), mSelection.getScale() );
mGizmo->renderGizmo( mEditor->getLastCameraQuery().cameraMatrix, mEditor->getLastCameraQuery().fov );
}
}
static Frustum gDragFrustum;
void ForestSelectionTool::onRender2D()
{
// Draw drag selection rect.
if ( mDragSelect && mDragRect.extent.x > 1 && mDragRect.extent.y > 1 )
GFX->getDrawUtil()->drawRect( mDragRect, mDragRectColor );
// update what is in the selection
if ( mDragSelect )
mDragSelection.clear();
// Determine selected objects based on the drag box touching
// a mesh if a drag operation has begun.
if ( mDragSelect && mDragRect.extent.x > 1 && mDragRect.extent.y > 1 )
{
// Build the drag frustum based on the rect
F32 wwidth;
F32 wheight;
F32 aspectRatio = F32(mEditor->getWidth()) / F32(mEditor->getHeight());
const CameraQuery &lastCameraQuery = mEditor->getLastCameraQuery();
if(!lastCameraQuery.ortho)
{
wheight = lastCameraQuery.nearPlane * mTan(lastCameraQuery.fov / 2);
wwidth = aspectRatio * wheight;
}
else
{
wheight = lastCameraQuery.fov;
wwidth = aspectRatio * wheight;
}
F32 hscale = wwidth * 2 / F32(mEditor->getWidth());
F32 vscale = wheight * 2 / F32(mEditor->getHeight());
F32 left = (mDragRect.point.x - mEditor->getPosition().x) * hscale - wwidth;
F32 right = (mDragRect.point.x - mEditor->getPosition().x + mDragRect.extent.x) * hscale - wwidth;
F32 top = wheight - vscale * (mDragRect.point.y - mEditor->getPosition().y);
F32 bottom = wheight - vscale * (mDragRect.point.y - mEditor->getPosition().y + mDragRect.extent.y);
gDragFrustum.set(lastCameraQuery.ortho, left, right, top, bottom, lastCameraQuery.nearPlane, lastCameraQuery.farPlane, lastCameraQuery.cameraMatrix );
mForest->getData()->getItems( gDragFrustum, &mDragSelection );
}
}
bool ForestSelectionTool::updateGuiInfo()
{
SimObject *statusbar;
if ( !Sim::findObject( "EditorGuiStatusBar", statusbar ) )
return false;
String text( "Forest Editor." );
GizmoMode mode = mGizmoProfile->mode;
if ( mMouseDown && mGizmo->getSelection() != Gizmo::None )
{
Point3F delta;
String qualifier;
if ( mode == RotateMode )
delta = mGizmo->getDeltaTotalRot();
else if ( mode == MoveMode )
delta = mGizmo->getTotalOffset();
else if ( mode == ScaleMode )
delta = mGizmo->getDeltaTotalScale();
if ( mGizmo->getAlignment() == Object && mode != ScaleMode )
{
mSelection.getOrientation().mulV( delta );
}
if ( mIsZero( delta.x, 0.0001f ) )
delta.x = 0.0f;
if ( mIsZero( delta.y, 0.0001f ) )
delta.y = 0.0f;
if ( mIsZero( delta.z, 0.0001f ) )
delta.z = 0.0f;
if ( mode == RotateMode )
{
delta.x = mRadToDeg( delta.x );
delta.y = mRadToDeg( delta.y );
delta.z = mRadToDeg( delta.z );
text = String::ToString( "Delta angle ( x: %4.2f, y: %4.2f, z: %4.2f ).", delta.x, delta.y, delta.z );
}
else if ( mode == MoveMode )
text = String::ToString( "Delta position ( x: %4.2f, y: %4.2f, z: %4.2f ).", delta.x, delta.y, delta.z );
else if ( mode == ScaleMode )
text = String::ToString( "Delta scale ( x: %4.2f, y: %4.2f, z: %4.2f ).", delta.x, delta.y, delta.z );
}
else
{
if ( mode == MoveMode )
text = "Move selection. SHIFT while dragging duplicates objects.";
else if ( mode == RotateMode )
text = "Rotate selection.";
else if ( mode == ScaleMode )
text = "Scale selection.";
}
Con::executef( statusbar, "setInfo", text.c_str() );
Con::executef( statusbar, "setSelectionObjectsByCount", Con::getIntArg( mSelection.size() ) );
return true;
}
void ForestSelectionTool::updateGizmo()
{
mGizmoProfile->restoreDefaultState();
const GizmoMode &mode = mGizmoProfile->mode;
if ( mode == ScaleMode )
{
mGizmoProfile->flags &= ~GizmoProfile::PlanarHandlesOn;
mGizmoProfile->allAxesScaleUniform = true;
}
}
void ForestSelectionTool::onUndoAction()
{
ForestData *data = mForest->getData();
// Remove items from our selection that no longer exist.
for ( S32 i = 0; i < mSelection.size(); i++ )
{
const ForestItem &item = data->findItem( mSelection[i].getKey(), mSelection[i].getPosition() );
if ( item == ForestItem::Invalid )
{
mSelection.erase_fast( i );
i--;
}
else
mSelection[i] = item;
}
// Recalculate our selection bounds.
mBounds = Box3F::Invalid;
for ( S32 i = 0; i < mSelection.size(); i++ )
mBounds.intersect( mSelection[i].getWorldBox() );
}
ConsoleMethod( ForestSelectionTool, getSelectionCount, S32, 2, 2, "" )
{
return object->getSelectionCount();
}
ConsoleMethod( ForestSelectionTool, deleteSelection, void, 2, 2, "" )
{
object->deleteSelection();
}
ConsoleMethod( ForestSelectionTool, clearSelection, void, 2, 2, "" )
{
object->clearSelection();
}
ConsoleMethod( ForestSelectionTool, cutSelection, void, 2, 2, "" )
{
object->cutSelection();
}
ConsoleMethod( ForestSelectionTool, copySelection, void, 2, 2, "" )
{
object->copySelection();
}
ConsoleMethod( ForestSelectionTool, pasteSelection, void, 2, 2, "" )
{
object->pasteSelection();
}

View file

@ -0,0 +1,121 @@
//-----------------------------------------------------------------------------
// 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 _FOREST_EDITOR_SELECTIONTOOL_H_
#define _FOREST_EDITOR_SELECTIONTOOL_H_
#ifndef _FOREST_EDITOR_TOOL_H_
#include "forest/editor/forestTool.h"
#endif
#ifndef _FORESTITEM_H_
#include "forest/forestItem.h"
#endif
#ifndef _TSELECTION_H_
#include "gui/worldEditor/tSelection.h"
#endif
class Forest;
class Gizmo;
class GizmoProfile;
class ForestUpdateAction;
class ForestItemSelection : public Selection<ForestItem>
{
public:
void setForestData( ForestData* data ) { mData = data; }
protected:
void offsetObject( ForestItem &object, const Point3F &delta );
void rotateObject( ForestItem &object, const EulerF &delta, const Point3F &origin );
void scaleObject( ForestItem &object, const Point3F &delta );
protected:
ForestData *mData;
};
class ForestSelectionTool : public ForestTool
{
typedef ForestTool Parent;
protected:
Gizmo *mGizmo;
GizmoProfile *mGizmoProfile;
ForestItem mHoverItem;
ForestItemSelection mSelection;
ForestItemSelection mDragSelection;
bool mDragSelect;
RectI mDragRect;
Point2I mDragStart;
bool mMouseDown;
bool mMouseDragged;
ColorI mDragRectColor;
bool mUsingGizmo;
Box3F mBounds;
ForestUpdateAction *mCurrAction;
void _selectItem( const ForestItem &item );
public:
ForestSelectionTool();
virtual ~ForestSelectionTool();
DECLARE_CONOBJECT( ForestSelectionTool );
// ForestTool
virtual void setParentEditor( ForestEditorCtrl *editor );
virtual void setActiveForest( Forest *forest );
virtual void on3DMouseDown( const Gui3DMouseEvent &evt );
virtual void on3DMouseUp( const Gui3DMouseEvent &evt );
virtual void on3DMouseMove( const Gui3DMouseEvent &evt );
virtual void on3DMouseDragged( const Gui3DMouseEvent &evt );
virtual void onRender3D();
virtual void onRender2D();
virtual bool updateGuiInfo();
virtual void updateGizmo();
virtual void onUndoAction();
S32 getSelectionCount() const { return mSelection.size(); }
void deleteSelection();
void clearSelection();
void cutSelection();
void copySelection();
void pasteSelection();
};
#endif // _FOREST_EDITOR_SELECTIONTOOL_H_

View file

@ -0,0 +1,68 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/editor/forestTool.h"
#include "forest/editor/forestEditorCtrl.h"
#include "forest/forestDataFile.h"
#include "forest/forestCell.h"
#include "util/undo.h"
#include "math/mMath.h"
#include "math/mathUtils.h"
IMPLEMENT_CONOBJECT( ForestTool );
ConsoleDocClass( ForestTool,
"@brief Base class for Forest Editor specific tools\n\n"
"Editor use only.\n\n"
"@internal"
);
ForestTool::ForestTool()
: mForest( NULL ),
mEditor( NULL )
{
}
ForestTool::~ForestTool()
{
}
void ForestTool::_submitUndo( UndoAction *action )
{
AssertFatal( action, "ForestTool::_submitUndo() - No undo action!" );
// Grab the mission editor undo manager.
UndoManager *undoMan = NULL;
if ( !Sim::findObject( "EUndoManager", undoMan ) )
{
Con::errorf( "ForestTool::_submitUndo() - EUndoManager not found!" );
return;
}
undoMan->addAction( action );
mEditor->updateCollision();
}

View file

@ -0,0 +1,87 @@
//-----------------------------------------------------------------------------
// 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 _FOREST_EDITOR_TOOL_H_
#define _FOREST_EDITOR_TOOL_H_
#ifndef _FORESTITEM_H_
#include "forest/forestItem.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _GUITYPES_H_
#include "gui/core/guiTypes.h"
#endif
class Forest;
class ForestEditorCtrl;
class ForestData;
class UndoAction;
class GuiTSCtrl;
class Point3F;
struct Gui3DMouseEvent;
class ForestTool : public SimObject
{
typedef SimObject Parent;
protected:
SimObjectPtr<Forest> mForest;
ForestEditorCtrl *mEditor;
void _submitUndo( UndoAction *action );
public:
ForestTool();
virtual ~ForestTool();
DECLARE_CONOBJECT( ForestTool );
virtual void setActiveForest( Forest *forest ) { mForest = forest; }
virtual void setParentEditor( ForestEditorCtrl *editor ) { mEditor = editor; }
virtual void onActivated( const Gui3DMouseEvent &lastEvent ) {}
virtual void onDeactivated() {}
virtual void on3DMouseDown( const Gui3DMouseEvent &evt ) {}
virtual void on3DMouseUp( const Gui3DMouseEvent &evt ) {}
virtual void on3DMouseMove( const Gui3DMouseEvent &evt ) {}
virtual void on3DMouseDragged( const Gui3DMouseEvent &evt ) {}
virtual void on3DMouseEnter( const Gui3DMouseEvent &evt ) {}
virtual void on3DMouseLeave( const Gui3DMouseEvent &evt ) {}
virtual bool onMouseWheel( const GuiEvent &evt ) { return false; }
virtual void onRender3D() {}
virtual void onRender2D() {}
virtual void updateGizmo() {}
virtual bool updateGuiInfo() { return false; }
virtual void onUndoAction() {}
};
#endif // _FOREST_EDITOR_TOOL_H_

View file

@ -0,0 +1,223 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/editor/forestUndo.h"
#include "forest/forestDataFile.h"
#include "forest/editor/forestEditorCtrl.h"
ForestUndoAction::ForestUndoAction( const Resource<ForestData> &data,
ForestEditorCtrl *editor,
const char *description )
: UndoAction( description ),
mData( data ),
mEditor( editor )
{
}
ForestCreateUndoAction::ForestCreateUndoAction( const Resource<ForestData> &data,
ForestEditorCtrl *editor )
: ForestUndoAction( data, editor, "Create Forest Items" )
{
}
void ForestCreateUndoAction::addItem( ForestItemData *data,
const Point3F &position,
F32 rotation,
F32 scale )
{
const ForestItem &item = mData->addItem( data, position, rotation, scale );
mItems.push_back( item );
// We store the datablock ID rather than the actual pointer
// since the pointer could go bad.
SimObjectId dataId = item.getData()->getId();
mItems.last().setData( (ForestItemData*)dataId );
}
void ForestCreateUndoAction::redo()
{
for ( U32 i = 0; i < mItems.size(); i++ )
{
const ForestItem &item = mItems[i];
// Not 64bit safe!
// We store the datablock ID rather than the actual pointer
// since the pointer could go bad.
ForestItemData *data;
if ( !Sim::findObject( (SimObjectId)(item.getData()), data ) )
{
Con::errorf( "ForestCreateUndoAction::redo() - ForestItemData for item to restore does not seem to exist. Undo stack may be hosed." );
continue;
}
mData->addItem( item.getKey(),
data,
item.getTransform(),
item.getScale() );
}
mEditor->onUndoAction();
}
void ForestCreateUndoAction::undo()
{
for ( S32 i = mItems.size()-1; i >= 0; i-- )
{
const ForestItem &item = mItems[i];
mData->removeItem( item.getKey(), item.getPosition() );
}
mEditor->onUndoAction();
}
ForestDeleteUndoAction::ForestDeleteUndoAction( const Resource<ForestData> &data,
ForestEditorCtrl *editor )
: ForestUndoAction( data, editor, "Delete Forest Items" )
{
}
void ForestDeleteUndoAction::removeItem( const ForestItem &item )
{
// Not 64bit safe!
// We store the datablock ID rather than the actual pointer
// since the pointer could go bad.
SimObjectId dataId = item.getData()->getId();
mItems.push_back( item );
mItems.last().setData( (ForestItemData*)dataId );
mData->removeItem( item.getKey(), item.getPosition() );
}
void ForestDeleteUndoAction::removeItem( const Vector<ForestItem> &itemList )
{
for ( S32 i = 0; i < itemList.size(); i++ )
removeItem( itemList[i] );
}
void ForestDeleteUndoAction::redo()
{
for ( U32 i = 0; i < mItems.size(); i++ )
{
const ForestItem &item = mItems[i];
mData->removeItem( item.getKey(), item.getPosition() );
}
mEditor->onUndoAction();
}
void ForestDeleteUndoAction::undo()
{
for ( S32 i = mItems.size()-1; i >= 0; i-- )
{
const ForestItem &item = mItems[i];
// We store the datablock ID rather than the actual pointer
// since the pointer could go bad.
ForestItemData *data;
if ( !Sim::findObject( (SimObjectId)(item.getData()), data ) )
{
Con::errorf( "ForestDeleteUndoAction::undo() - ForestItemData for item to restore does not seem to exist. Undo stack may be hosed." );
continue;
}
mData->addItem( item.getKey(),
data,
item.getTransform(),
item.getScale() );
}
mEditor->onUndoAction();
}
ForestUpdateAction::ForestUpdateAction( const Resource<ForestData> &data,
ForestEditorCtrl *editor )
: ForestUndoAction( data, editor, "Update Forest Items" )
{
}
void ForestUpdateAction::saveItem( const ForestItem &item )
{
// We just store the current state... we undo it later.
mItems.push_back( item );
// We store the datablock ID rather than the actual pointer
// since the pointer could go bad.
SimObjectId dataId = item.getData()->getId();
mItems.last().setData( (ForestItemData*)dataId );
}
void ForestUpdateAction::_swapState()
{
Vector<ForestItem> prevItems = mItems;
mItems.clear();
for ( U32 i=0; i < prevItems.size(); i++ )
{
const ForestItem &item = prevItems[i];
// Save the current state so we can reverse this swap.
//
// Note that we do 'not' want the const ref returned by findItem
// because when we call updateItem below we would lose our copy
// of the items state before the call.
//
ForestItem newItem = mData->findItem( item.getKey() );
if ( !newItem.isValid() )
{
Con::errorf( "ForestUpdateAction::_swapState() - saved item no longer exists. Undo stack may be hosed." );
continue;
}
// Not 64bit safe!
// We store the datablock ID rather than the actual pointer
// since the pointer could go bad.
ForestItemData *data;
if ( !Sim::findObject( (SimObjectId)(item.getData()), data ) )
{
Con::errorf( "ForestUpdateAction::_swapState() - ForestItemData for item to restore does not seem to exist. Undo stack may be hosed." );
continue;
}
// Now revert to the old state.
mData->updateItem( item.getKey(),
item.getPosition(),
data,
item.getTransform(),
item.getScale() );
// Save the state before this swap for the next swap.
newItem.setData( (ForestItemData*)data->getId() );
mItems.push_back( newItem );
}
mEditor->onUndoAction();
}

View file

@ -0,0 +1,121 @@
//-----------------------------------------------------------------------------
// 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 _FOREST_EDITOR_UNDO_H_
#define _FOREST_EDITOR_UNDO_H_
#ifndef _UNDO_H_
#include "util/undo.h"
#endif
#ifndef _FORESTITEM_H_
#include "forest/forestItem.h"
#endif
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
class ForestData;
class ForestEditorCtrl;
class ForestUndoAction : public UndoAction
{
typedef UndoAction Parent;
public:
ForestUndoAction( const Resource<ForestData> &data, ForestEditorCtrl *editor, const char *description );
// UndoAction
virtual void undo() {}
virtual void redo() {}
protected:
ForestEditorCtrl *mEditor;
Vector<ForestItem> mItems;
Resource<ForestData> mData;
};
class ForestCreateUndoAction : public ForestUndoAction
{
typedef ForestUndoAction Parent;
public:
ForestCreateUndoAction( const Resource<ForestData> &data,
ForestEditorCtrl *editor );
/// Adds the item to the Forest and stores
/// its info for undo later.
void addItem( ForestItemData *data,
const Point3F &position,
F32 rotation,
F32 scale );
// UndoAction
virtual void undo();
virtual void redo();
};
class ForestDeleteUndoAction : public ForestUndoAction
{
typedef ForestUndoAction Parent;
public:
ForestDeleteUndoAction( const Resource<ForestData> &data,
ForestEditorCtrl *editor );
///
void removeItem( const ForestItem &item );
void removeItem( const Vector<ForestItem> &itemList );
// UndoAction
virtual void undo();
virtual void redo();
};
class ForestUpdateAction : public ForestUndoAction
{
typedef ForestUndoAction Parent;
public:
ForestUpdateAction( const Resource<ForestData> &data,
ForestEditorCtrl *editor );
void saveItem( const ForestItem &item );
virtual void undo() { _swapState(); }
virtual void redo() { _swapState(); }
protected:
void _swapState();
};
#endif // _FOREST_EDITOR_UNDO_H_

View file

@ -0,0 +1,376 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/forest.h"
#include "forest/forestCell.h"
#include "forest/forestCollision.h"
#include "forest/forestDataFile.h"
#include "forest/forestWindMgr.h"
#include "forest/forestWindAccumulator.h"
#include "core/resourceManager.h"
#include "core/volume.h"
#include "T3D/gameBase/gameConnection.h"
#include "console/consoleInternal.h"
#include "core/stream/bitStream.h"
#include "math/mathIO.h"
#include "environment/sun.h"
#include "scene/sceneManager.h"
#include "math/mathUtils.h"
#include "T3D/physics/physicsBody.h"
#include "forest/editor/forestBrushElement.h"
/// For frame signal
#include "gui/core/guiCanvas.h"
extern bool gEditingMission;
ForestCreatedSignal Forest::smCreatedSignal;
ForestCreatedSignal Forest::smDestroyedSignal;
bool Forest::smForceImposters = false;
bool Forest::smDisableImposters = false;
bool Forest::smDrawCells = false;
bool Forest::smDrawBounds = false;
IMPLEMENT_CO_NETOBJECT_V1(Forest);
ConsoleDocClass( Forest,
"@brief %Forest is a global-bounds scene object provides collision and rendering for a "
"(.forest) data file.\n\n"
"%Forest is designed to efficiently render a large number of static meshes: trees, rocks "
"plants, etc. These cannot be moved at game-time or play animations but do support wind "
"effects using vertex shader transformations guided by vertex color in the asset and "
"user placed wind emitters ( or weapon explosions ).\n\n"
"Script level manipulation of forest data is not possible through %Forest, it is only "
"the rendering/collision. All editing is done through the world editor.\n\n"
"@see TSForestItemData Defines a tree type.\n"
"@see GuiForestEditorCtrl Used by the world editor to provide manipulation of forest data.\n"
"@ingroup Forest"
);
Forest::Forest()
: mDataFileName( NULL ),
mReflectionLodScalar( 2.0f ),
mConvexList( new Convex() ),
mZoningDirty( false )
{
mTypeMask |= EnvironmentObjectType | StaticShapeObjectType | StaticObjectType;
mNetFlags.set(Ghostable | ScopeAlways);
}
Forest::~Forest()
{
delete mConvexList;
mConvexList = NULL;
}
void Forest::initPersistFields()
{
Parent::initPersistFields();
addField( "dataFile", TypeFilename, Offset( mDataFileName, Forest ),
"The source forest data file." );
addGroup( "Lod" );
addField( "lodReflectScalar", TypeF32, Offset( mReflectionLodScalar, Forest ),
"Scalar applied to the farclip distance when Forest renders into a reflection." );
endGroup( "Lod" );
}
void Forest::consoleInit()
{
// Some stats exposed to the console.
Con::addVariable("$Forest::totalCells", TypeS32, &Forest::smTotalCells, "@internal" );
Con::addVariable("$Forest::cellsRendered", TypeS32, &Forest::smCellsRendered, "@internal" );
Con::addVariable("$Forest::cellItemsRendered", TypeS32, &Forest::smCellItemsRendered, "@internal" );
Con::addVariable("$Forest::cellsBatched", TypeS32, &Forest::smCellsBatched, "@internal" );
Con::addVariable("$Forest::cellItemsBatched", TypeS32, &Forest::smCellItemsBatched, "@internal" );
Con::addVariable("$Forest::averageCellItems", TypeF32, &Forest::smAverageItemsPerCell, "@internal" );
// Some debug flags.
Con::addVariable("$Forest::forceImposters", TypeBool, &Forest::smForceImposters,
"A debugging aid which will force all forest items to be rendered as imposters.\n"
"@ingroup Forest\n" );
Con::addVariable("$Forest::disableImposters", TypeBool, &Forest::smDisableImposters,
"A debugging aid which will disable rendering of all imposters in the forest.\n"
"@ingroup Forest\n" );
Con::addVariable("$Forest::drawCells", TypeBool, &Forest::smDrawCells,
"A debugging aid which renders the forest cell bounds.\n"
"@ingroup Forest\n" );
Con::addVariable("$Forest::drawBounds", TypeBool, &Forest::smDrawBounds,
"A debugging aid which renders the forest bounds.\n"
"@ingroup Forest\n" );
// The canvas signal lets us know to clear the rendering stats.
GuiCanvas::getGuiCanvasFrameSignal().notify( &Forest::_clearStats );
}
bool Forest::onAdd()
{
if (!Parent::onAdd())
return false;
const char *name = getName();
if(name && name[0] && getClassRep())
{
Namespace *parent = getClassRep()->getNameSpace();
Con::linkNamespaces(parent->mName, name);
mNameSpace = Con::lookupNamespace(name);
}
setGlobalBounds();
resetWorldBox();
// TODO: Make sure this calls the script "onAdd" which will
// populate the object with forest entries before creation.
addToScene();
// If we don't have a file name and the editor is
// enabled then create an empty forest data file.
if ( isServerObject() && ( !mDataFileName || !mDataFileName[0] ) )
createNewFile();
else
{
// Try to load the forest file.
mData = ResourceManager::get().load( mDataFileName );
if ( !mData )
{
if ( isClientObject() )
NetConnection::setLastError( "You are missing a file needed to play this mission: %s", mDataFileName );
return false;
}
}
updateCollision();
smCreatedSignal.trigger( this );
if ( isClientObject() )
{
mZoningDirty = true;
SceneZoneSpaceManager::getZoningChangedSignal().notify( this, &Forest::_onZoningChanged );
ForestWindMgr::getAdvanceSignal().notify( this, &Forest::getLocalWindTrees );
}
return true;
}
void Forest::onRemove()
{
Parent::onRemove();
smDestroyedSignal.trigger( this );
if ( mData )
mData->clearPhysicsRep( this );
mData = NULL;
if ( isClientObject() )
{
SceneZoneSpaceManager::getZoningChangedSignal().remove( this, &Forest::_onZoningChanged );
ForestWindMgr::getAdvanceSignal().remove( this, &Forest::getLocalWindTrees );
}
mConvexList->nukeList();
removeFromScene();
}
U32 Forest::packUpdate( NetConnection *connection, U32 mask, BitStream *stream )
{
U32 retMask = Parent::packUpdate( connection, mask, stream );
if ( stream->writeFlag( mask & MediaMask ) )
stream->writeString( mDataFileName );
if ( stream->writeFlag( mask & LodMask ) )
{
stream->write( mReflectionLodScalar );
}
return retMask;
}
void Forest::unpackUpdate(NetConnection *connection, BitStream *stream)
{
Parent::unpackUpdate(connection,stream);
if ( stream->readFlag() )
{
mDataFileName = stream->readSTString();
}
if ( stream->readFlag() ) // LodMask
{
stream->read( &mReflectionLodScalar );
}
}
void Forest::inspectPostApply()
{
Parent::inspectPostApply();
// Update the client... note that this
// doesn't cause a regen of the forest.
setMaskBits( LodMask );
}
void Forest::setTransform( const MatrixF &mat )
{
// Note: We do not use the position of the forest at all.
Parent::setTransform( mat );
}
void Forest::_onZoningChanged( SceneZoneSpaceManager *zoneManager )
{
if ( mData == NULL || zoneManager != getSceneManager()->getZoneManager() )
return;
mZoningDirty = true;
}
void Forest::getLocalWindTrees( const Point3F &camPos, F32 radius, Vector<TreePlacementInfo> *placementInfo )
{
PROFILE_SCOPE( Forest_getLocalWindTrees );
Vector<ForestItem> items;
items.reserve( placementInfo->capacity() );
mData->getItems( camPos, radius, &items );
TreePlacementInfo treeInfo;
dMemset( &treeInfo, 0, sizeof ( TreePlacementInfo ) );
// Reserve some space in the output.
placementInfo->reserve( items.size() );
// Build an info struct for each returned item.
Vector<ForestItem>::const_iterator iter = items.begin();
for ( ; iter != items.end(); iter++ )
{
// Skip over any zero wind elements here and
// just keep them out of the final list.
treeInfo.dataBlock = iter->getData();
if ( treeInfo.dataBlock->mWindScale < 0.001f )
continue;
treeInfo.pos = iter->getPosition();
treeInfo.scale = iter->getScale();
treeInfo.itemKey = iter->getKey();
placementInfo->push_back( treeInfo );
}
}
void Forest::applyRadialImpulse( const Point3F &origin, F32 radius, F32 magnitude )
{
if ( isServerObject() )
return;
// Find all the trees in the radius
// then get their accumulators and
// push our impulse into them.
VectorF impulse( 0, 0, 0 );
ForestWindAccumulator *accumulator = NULL;
Vector<TreePlacementInfo> trees;
getLocalWindTrees( origin, radius, &trees );
for ( U32 i = 0; i < trees.size(); i++ )
{
const TreePlacementInfo &treeInfo = trees[i];
accumulator = WINDMGR->getLocalWind( treeInfo.itemKey );
if ( !accumulator )
continue;
impulse = treeInfo.pos - origin;
impulse.normalize();
impulse *= magnitude;
accumulator->applyImpulse( impulse );
}
}
void Forest::createNewFile()
{
// Release the current file if we have one.
mData = NULL;
// We need to construct a default file name
String missionName( Con::getVariable( "$Client::MissionFile" ) );
missionName.replace( "tools/levels", "levels" );
missionName = Platform::makeRelativePathName(missionName, Platform::getMainDotCsDir());
Torque::Path basePath( missionName );
String fileName = Torque::FS::MakeUniquePath( basePath.getPath(), basePath.getFileName(), "forest" );
mDataFileName = StringTable->insert( fileName );
ForestData *file = new ForestData;
file->write( mDataFileName );
delete file;
mData = ResourceManager::get().load( mDataFileName );
mZoningDirty = true;
}
void Forest::saveDataFile( const char *path )
{
if ( path )
mDataFileName = StringTable->insert( path );
if ( mData )
mData->write( mDataFileName );
}
ConsoleMethod( Forest, saveDataFile, bool, 2, 3, "saveDataFile( [path] )" )
{
object->saveDataFile( argc == 3 ? argv[2] : NULL );
return true;
}
ConsoleMethod(Forest, isDirty, bool, 2, 2, "()")
{
return object->getData() && object->getData()->isDirty();
}
ConsoleMethod(Forest, regenCells, void, 2, 2, "()")
{
object->getData()->regenCells();
}
ConsoleMethod(Forest, clear, void, 2, 2, "()" )
{
object->clear();
}

View file

@ -0,0 +1,214 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _H_FOREST_
#define _H_FOREST_
#ifndef _FORESTITEM_H_
#include "forest/forestItem.h"
#endif
#ifndef _FORESTDATAFILE_H_
#include "forest/forestDataFile.h"
#endif
#ifndef _MATHUTIL_FRUSTUM_H_
#include "math/util/frustum.h"
#endif
#ifndef _GFXTEXTUREHANDLE_H_
#include "gfx/gfxTextureHandle.h"
#endif
#ifndef _COLLISION_H_
#include "collision/collision.h"
#endif
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _SIGNAL_H_
#include "core/util/tSignal.h"
#endif
#ifndef __RESOURCE_H__
#include "core/resource.h"
#endif
#ifndef _CONVEX_H_
#include "collision/convex.h"
#endif
class TSShapeInstance;
class Forest;
class ForestItemData;
class ForestData;
class GBitmap;
class IForestCollision;
class IForestMask;
class PhysicsBody;
struct ObjectRenderInst;
struct TreePlacementInfo;
class ForestRayInfo;
class SceneZoneSpaceManager;
struct TreeInfo
{
U32 treeId;
SimObjectId treeTypeId;
F32 distance;
SimObjectId forestId;
Point3F position;
};
typedef Signal<void( Forest *forest )> ForestCreatedSignal;
///
class Forest : public SceneObject
{
friend class CreateForestEvent;
friend class ForestConvex;
protected:
typedef SceneObject Parent;
/// Collision and Physics
/// @{
Convex* mConvexList;
/// @}
/// The name of the planting data file.
StringTableEntry mDataFileName;
/// The forest data file which defines planting.
Resource<ForestData> mData;
/// Used to scale the tree LODs when rendering into
/// reflections. It should be greater or equal to 1.
F32 mReflectionLodScalar;
/// Set when rezoning of forest cells is required.
bool mZoningDirty;
/// Debug helpers.
static bool smForceImposters;
static bool smDisableImposters;
static bool smDrawCells;
static bool smDrawBounds;
///
bool mRegen;
enum MaskBits
{
MediaMask = Parent::NextFreeMask << 1,
LodMask = Parent::NextFreeMask << 2,
NextFreeMask = Parent::NextFreeMask << 3
};
static U32 smTotalCells;
static U32 smCellsRendered;
static U32 smCellItemsRendered;
static U32 smCellsBatched;
static U32 smCellItemsBatched;
static F32 smAverageItemsPerCell;
static void _clearStats(bool);
void _renderCellBounds( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );
void _onZoningChanged( SceneZoneSpaceManager *zoneManager );
static ForestCreatedSignal smCreatedSignal;
static ForestCreatedSignal smDestroyedSignal;
public:
static ForestCreatedSignal& getCreatedSignal() { return smCreatedSignal; }
static ForestCreatedSignal& getDestroyedSignal() { return smDestroyedSignal; }
Forest();
virtual ~Forest();
DECLARE_CONOBJECT(Forest);
static void consoleInit();
static void initPersistFields();
// SimObject
bool onAdd();
void onRemove();
/// Overloaded from SceneObject to properly update
/// the client side forest when changes occur within
/// the mission editor.
void inspectPostApply();
/// Overloaded from SceneObject for updating the
/// client side position of the forest.
void setTransform( const MatrixF &mat );
void prepRenderImage( SceneRenderState *state );
bool isTreeInRange( const Point2F& point, F32 radius ) const;
// Network
U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream );
void unpackUpdate( NetConnection *conn, BitStream *stream );
//IForestCollision *getCollision() const { return mCollision; }
// SceneObject - Collision
virtual void buildConvex( const Box3F& box, Convex* convex );
virtual bool buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere );
virtual bool castRay( const Point3F &start, const Point3F &end, RayInfo *outInfo );
virtual bool castRayRendered( const Point3F &start, const Point3F &end, RayInfo *outInfo );
virtual bool collideBox( const Point3F &start, const Point3F &end, RayInfo *outInfo );
// SceneObject - Other
virtual void applyRadialImpulse( const Point3F &origin, F32 radius, F32 magnitude );
bool castRayBase( const Point3F &start, const Point3F &end, RayInfo *outInfo, bool rendered );
const Resource<ForestData>& getData() const { return mData; }
Resource<ForestData>& getData() { return mData; }
//bool buildPolyList(AbstractPolyList* polyList, const Box3F &box, const SphereF& sphere);
//void buildConvex(const Box3F& box, Convex* convex);
void getLocalWindTrees( const Point3F &camPos, F32 radius, Vector<TreePlacementInfo> *placementInfo );
/// Called to create a new empty planting data file and
/// assign it to this forest.
void createNewFile();
///
void saveDataFile( const char *path = NULL );
///
void clear() { mData->clear(); }
/// Called to rebuild the collision state.
void updateCollision();
};
#endif // _H_FOREST_

View file

@ -0,0 +1,505 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/forestCell.h"
#include "forest/forest.h"
#include "forest/forestCellBatch.h"
#include "forest/forestCollision.h"
#include "T3D/physics/physicsPlugin.h"
#include "T3D/physics/physicsBody.h"
#include "T3D/physics/physicsCollision.h"
#include "collision/concretePolyList.h"
#include "gfx/gfxDrawUtil.h"
#include "math/util/frustum.h"
ForestCell::ForestCell( const RectF &rect ) :
mRect( rect ),
mBounds( Box3F::Invalid ),
mLargestItem( ForestItem::Invalid ),
mIsDirty( false ),
mIsInteriorOnly( false )
{
dMemset( mSubCells, 0, sizeof( mSubCells ) );
dMemset( mPhysicsRep, 0, sizeof( mPhysicsRep ) );
}
ForestCell::~ForestCell()
{
mItems.clear();
for ( U32 i=0; i < 4; i++ )
SAFE_DELETE( mSubCells[i] );
freeBatches();
SAFE_DELETE( mPhysicsRep[0] );
SAFE_DELETE( mPhysicsRep[1] );
}
void ForestCell::freeBatches()
{
for ( U32 i=0; i < mBatches.size(); i++ )
SAFE_DELETE( mBatches[i] );
mBatches.clear();
}
void ForestCell::buildBatches()
{
// Gather items for batches.
Vector<ForestItem> items;
getItems( &items );
// Ask the item to batch itself.
Vector<ForestItem>::const_iterator item = items.begin();
bool batched = false;
for ( ; item != items.end(); item++ )
{
// Loop thru the batches till someone
// takes this guy off our hands.
batched = false;
for ( S32 i=0; i < mBatches.size(); i++ )
{
if ( mBatches[i]->add( *item ) )
{
batched = true;
break;
}
}
if ( batched )
continue;
// Gotta create a new batch.
ForestCellBatch *batch = item->getData()->allocateBatch();
if ( batch )
{
batch->add( *item );
mBatches.push_back( batch );
}
}
}
S32 ForestCell::renderBatches( SceneRenderState *state, Frustum *culler )
{
PROFILE_SCOPE( ForestCell_renderBatches );
if ( !hasBatches() )
return 0;
S32 renderedItems = 0;
for ( S32 i=0; i < mBatches.size(); i++ )
{
// Is this batch entirely culled?
if ( culler && culler->isCulled( mBatches[i]->getWorldBox() ) )
continue;
mBatches[i]->render( state );
renderedItems += mBatches[i]->getItemCount();
}
return renderedItems;
}
S32 ForestCell::render( TSRenderState *rdata, const Frustum *culler )
{
PROFILE_SCOPE( ForestCell_render );
AssertFatal( isLeaf(), "ForestCell::render() - This shouldn't be called on non-leaf cells!" );
U32 itemsRendered = 0;
// TODO: Items are generated in order of type,
// so we can maybe save some overhead by preparing
// the item for rendering once.
Vector<ForestItem>::iterator item = mItems.begin();
for ( ; item != mItems.end(); item++ )
{
// Do we need to cull individual items?
if ( culler && culler->isCulled( item->getWorldBox() ) )
continue;
if ( item->getData()->render( rdata, *item ) )
++itemsRendered;
}
return itemsRendered;
}
void ForestCell::_updateBounds()
{
mIsDirty = false;
mBounds = Box3F::Invalid;
mLargestItem = ForestItem::Invalid;
F32 radius;
if ( isBranch() )
{
for ( U32 i=0; i < 4; i++ )
{
mBounds.intersect( mSubCells[i]->getBounds() );
radius = mSubCells[i]->mLargestItem.getRadius();
if ( radius > mLargestItem.getRadius() )
mLargestItem = mSubCells[i]->mLargestItem;
}
return;
}
// Loop thru all the items in this cell.
Vector<ForestItem>::const_iterator item = mItems.begin();
for ( ; item != mItems.end(); item++ )
{
mBounds.intersect( (*item).getWorldBox() );
radius = (*item).getRadius();
if ( radius > mLargestItem.getRadius() )
mLargestItem = (*item);
}
}
void ForestCell::_updateZoning( const SceneZoneSpaceManager *zoneManager )
{
PROFILE_SCOPE( ForestCell_UpdateZoning );
mZoneOverlap.setSize( zoneManager->getNumZones() );
mZoneOverlap.clear();
mIsInteriorOnly = true;
if ( isLeaf() )
{
// Skip empty cells... they don't have valid bounds.
if ( mItems.empty() )
return;
Vector<U32> zones;
zoneManager->findZones( getBounds(), zones );
for ( U32 i=0; i < zones.size(); i++ )
{
// Set overlap bit for zone except it's the outdoor zone.
if( zones[ i ] != SceneZoneSpaceManager::RootZoneId )
mZoneOverlap.set( zones[i] );
else
mIsInteriorOnly = false;
}
return;
}
for ( U32 i = 0; i < 4; i++ )
{
ForestCell *cell = mSubCells[i];
cell->_updateZoning( zoneManager );
mZoneOverlap.combineOR( cell->getZoneOverlap() );
mIsInteriorOnly &= cell->mIsInteriorOnly;
}
}
bool ForestCell::findIndexByKey( ForestItemKey key, U32 *outIndex ) const
{
// Do a simple binary search.
U32 i = 0,
lo = 0,
hi = mItems.size();
const ForestItem *items = mItems.address();
while ( lo < hi )
{
i = (lo + hi) / 2;
if ( key < items[i].getKey() )
hi = i;
else if ( key > items[i].getKey() )
lo = i + 1;
else
{
*outIndex = i;
return true;
}
}
*outIndex = lo;
return false;
}
const ForestItem& ForestCell::insertItem( ForestItemKey key,
ForestItemData *data,
const MatrixF &xfm,
F32 scale )
{
AssertFatal( key != 0, "ForestCell::insertItem() - Got null key!" );
AssertFatal( data != NULL, "ForestCell::insertItem() - Got null datablock!" );
// Make sure we update the bounds later.
mIsDirty = true;
// PhysicsBody is now invalid and must be rebuilt later.
SAFE_DELETE( mPhysicsRep[0] );
SAFE_DELETE( mPhysicsRep[1] );
// Destroy batches so we recreate it on
// the next next render.
freeBatches();
// Ok... do we need to split this cell?
if ( isLeaf() && mItems.size() > MaxItems )
{
// Add the children.
for ( U32 i=0; i < 4; i++ )
mSubCells[i] = new ForestCell( _makeChildRect( i ) );
// Now push all our current children down.
Vector<ForestItem>::iterator item = mItems.begin();
for ( ; item != mItems.end(); item++ )
{
U32 index = _getSubCell( item->getPosition().x, item->getPosition().y );
mSubCells[index]->insertItem( item->getKey(),
item->getData(),
item->getTransform(),
item->getScale() );
}
// Clean up.
mItems.clear();
mItems.compact();
}
// Do we have children?
if ( isBranch() )
{
// Ok... kick this item down then.
U32 index = _getSubCell( xfm.getPosition().x, xfm.getPosition().y );
const ForestItem &result = mSubCells[index]->insertItem( key, data, xfm, scale );
AssertFatal( index == _getSubCell( result.getPosition().x, result.getPosition().y ), "ForestCell::insertItem() - binning is hosed." );
return result;
}
// Do the datablock preload here to insure it happens
// before an item is used in the scene.
data->preload();
// First see if we can find it. This is nifty so
// I'll explain it a bit more.
//
// The find function does a binary search thru the
// sorted item list.
//
// If found the index is the position of the item.
//
// If not found the index is the correct insertion
// position for adding the new item.
//
// So not only do we have a fast find which is worst
// case O(log n)... but we also have the proper insert
// position to maintain a sorted item list.
//
U32 index;
bool found = findIndexByKey( key, &index );
// If we didn't find one then insert it.
if ( !found )
mItems.insert( index );
// Update the item settings.
ForestItem &item = mItems[ index ];
item.setData( data );
item.setTransform( xfm, scale );
if ( !found )
item.setKey( key );
return item;
}
bool ForestCell::removeItem( ForestItemKey key, const Point3F &keyPos, bool deleteIfEmpty )
{
PROFILE_SCOPE( ForestCell_removeItem );
AssertFatal( key != 0, "ForestCell::removeItem() - Got null key!" );
// If this cell has no items then check the children.
if ( mItems.empty() )
{
// Let the child deal with it.
U32 index = _getSubCell( keyPos.x, keyPos.y );
if ( !mSubCells[index]->removeItem( key, keyPos, deleteIfEmpty ) )
{
// For debugging lets make sure we didn't pick the wrong subCell...
return false;
}
// If requested by the caller delete our empty subcells.
// Note that by deleting SubCell[0] we have become a leaf with no items
// and will return true to our parent's isEmpty test.
if ( deleteIfEmpty &&
mSubCells[0]->isEmpty() &&
mSubCells[1]->isEmpty() &&
mSubCells[2]->isEmpty() &&
mSubCells[3]->isEmpty() )
{
SAFE_DELETE( mSubCells[0] );
SAFE_DELETE( mSubCells[1] );
SAFE_DELETE( mSubCells[2] );
SAFE_DELETE( mSubCells[3] );
}
}
else
{
// First see if we can find it.
U32 index;
if ( !findIndexByKey( key, &index ) )
return false;
// Erase it.
mItems.erase( index );
}
// Do a full bounds update on the next request.
mIsDirty = true;
// PhysicsBody is now invalid and must be rebuilt later.
SAFE_DELETE( mPhysicsRep[0] );
SAFE_DELETE( mPhysicsRep[1] );
// Destroy batches so we recreate it on
// the next next render.
freeBatches();
return true;
}
void ForestCell::getItems( Vector<ForestItem> *outItems ) const
{
Vector<const ForestCell*> stack;
stack.push_back( this );
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
const ForestCell *cell = stack.last();
stack.pop_back();
// Recurse thru non-leaf cells.
if ( !cell->isLeaf() )
{
stack.merge( cell->mSubCells, 4 );
continue;
}
// Get the items.
outItems->merge( cell->getItems() );
}
}
void ForestCell::buildPhysicsRep( Forest *forest )
{
AssertFatal( isLeaf(), "ForestCell::buildPhysicsRep() - This shouldn't be called on non-leaf cells!" );
bool isServer = forest->isServerObject();
// Already has a PhysicsBody, if it needed to be rebuilt it would
// already be null.
if ( mPhysicsRep[ isServer ] )
return;
if ( !PHYSICSMGR )
return;
PhysicsCollision *colShape = NULL;
// If we can steal the collision shape from the server-side cell
// then do so as it saves us alot of cpu time and memory.
if ( mPhysicsRep[ 1 ] )
{
colShape = mPhysicsRep[ 1 ]->getColShape();
}
else
{
// We must pass a sphere to buildPolyList but it is not used.
const static SphereF dummySphere( Point3F::Zero, 0 );
// Step thru them and build collision data.
ForestItemVector::iterator itemItr = mItems.begin();
ConcretePolyList polyList;
for ( ; itemItr != mItems.end(); itemItr++ )
{
const ForestItem &item = *itemItr;
const ForestItemData *itemData = item.getData();
// If not collidable don't need to build anything.
if ( !itemData->mCollidable )
continue;
// TODO: When we add breakable tree support this is where
// we would need to store their collision data seperately.
item.buildPolyList( &polyList, item.getWorldBox(), dummySphere );
// TODO: Need to support multiple collision shapes
// for really big forests at some point in the future.
}
if ( !polyList.isEmpty() )
{
colShape = PHYSICSMGR->createCollision();
if ( !colShape->addTriangleMesh( polyList.mVertexList.address(),
polyList.mVertexList.size(),
polyList.mIndexList.address(),
polyList.mIndexList.size() / 3,
MatrixF::Identity ) )
{
SAFE_DELETE( colShape );
}
}
}
// We might not have any trees.
if ( !colShape )
return;
PhysicsWorld *world = PHYSICSMGR->getWorld( isServer ? "server" : "client" );
mPhysicsRep[ isServer ] = PHYSICSMGR->createBody();
mPhysicsRep[ isServer ]->init( colShape, 0, 0, forest, world );
}
void ForestCell::clearPhysicsRep( Forest *forest )
{
bool isServer = forest->isServerObject();
SAFE_DELETE( mPhysicsRep[ isServer ] );
}

View file

@ -0,0 +1,225 @@
//-----------------------------------------------------------------------------
// 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 _FORESTCELL_H_
#define _FORESTCELL_H_
#ifndef _FORESTITEM_H_
#include "forest/forestItem.h"
#endif
#ifndef _H_FOREST_
#include "forest/forest.h"
#endif
#ifndef _BITVECTOR_H_
#include "core/bitVector.h"
#endif
class ForestCellBatch;
class SceneRenderState;
class Frustum;
class IForestCellCollision;
class PhysicsBody;
//class ForestRayInfo;
///
class ForestCell
{
friend class Forest;
protected:
/// The area which this cell represents in the world.
RectF mRect;
/// If items have been added or removed the dirty flag
/// is set so that the bounds can be rebuilt on the next
/// request.
bool mIsDirty;
/// The combined bounding box of all the items
/// in or children of this cell.
Box3F mBounds;
/// All the items in this cell.
Vector<ForestItem> mItems;
/// A vector of the current batches
/// associated with this cell.
Vector<ForestCellBatch*> mBatches;
/// The largest item in this cell.
ForestItem mLargestItem;
/// PhysicsBody for client and server for all trees in this ForestCell,
/// if it is a leaf cell.
PhysicsBody *mPhysicsRep[2];
/// The quad tree cells below this one which
/// may contain sub elements.
ForestCell* mSubCells[4];
/// The zone overlap for this cell.
/// @note The bit for the outdoor zone is never set.
BitVector mZoneOverlap;
/// Whether this cell is fully contained inside interior zones.
bool mIsInteriorOnly;
///
inline U32 _getSubCell( F32 x, F32 y ) const
{
bool left = x < ( mRect.point.x + ( mRect.extent.x / 2.0f ) );
bool top = y < ( mRect.point.y + ( mRect.extent.y / 2.0f ) );
if ( left && top ) return 0;
if ( left && !top ) return 1;
if ( !left && top ) return 2;
return 3;
}
///
inline RectF _makeChildRect( U32 index ) const
{
RectF rect( mRect.point, mRect.extent / 2.0f );
if ( index == 1 || index == 3 )
rect.point.y += rect.extent.y;
if ( index > 1 )
rect.point.x += rect.extent.x;
return rect;
}
void _updateBounds();
///
void _updateZoning( const SceneZoneSpaceManager *zoneManager );
public:
/// The maximum amount of objects allowed in a
/// cell before we repartition it.
static const U32 MaxItems = 200;
ForestCell( const RectF &rect );
virtual ~ForestCell();
/// Returns the 2D rectangle of quad tree bounds for this
/// cell. It is fixed and does not change during runtime.
const RectF& getRect() const { return mRect; }
/// The bounds of the cell generated by combining the
/// bounds of all the contained items or child cells.
const Box3F& getBounds() const;
/// Set flag so this cells bounds will be recalculated the next call to getBounds.
void invalidateBounds() { mIsDirty = true; }
/// Returns true if this cell has no items and no child cells.
bool isEmpty() const { return isLeaf() && mItems.empty(); }
/// Returns true if this cell does not have child cells.
/// It should directly contain items.
bool isLeaf() const { return !mSubCells[0]; }
/// Returns true if this cell has child cells.
/// This is the same as !isLeaf() but exists for clarity.
bool isBranch() const { return mSubCells[0] != NULL; }
/// Returns a bit vector of what zones overlap this cell.
const BitVector& getZoneOverlap() const { return mZoneOverlap; }
///
bool castRay( const Point3F &start, const Point3F &end, RayInfo *outInfo, bool rendered ) const;
bool hasBatches() const { return !mBatches.empty(); }
void buildBatches();
void freeBatches();
S32 renderBatches( SceneRenderState *state, Frustum *culler );
S32 render( TSRenderState *rdata, const Frustum *culler );
/// The find function does a binary search thru the sorted
/// item list. If the key is found then the index is the
/// position of the item. If the key is not found the index
/// is the correct insertion position for adding the new item.
///
/// @param key The item key to search for.
///
/// @param outIndex The item index or insertion index if
/// the item was not found.
///
/// @return Returns true if the item index is found.
///
bool findIndexByKey( ForestItemKey key, U32 *outIndex ) const;
const ForestItem& getLargestItem() const { return mLargestItem; }
const ForestItem& insertItem( ForestItemKey key,
ForestItemData *data,
const MatrixF &xfm,
F32 scale );
bool removeItem( ForestItemKey key, const Point3F &keyPos, bool deleteIfEmpty = false );
/// Returns the child cell at the position. The position is
/// assumed to be within this cell.
ForestCell* getChildAt( const Point3F &pos ) const;
/// Returns the child cells.
void getChildren( Vector<ForestCell*> *outCells ) const { outCells->merge( mSubCells, 4 ); }
void getChildren( Vector<const ForestCell*> *outCells ) const { outCells->merge( mSubCells, 4 ); }
/// Returns the items from this one cell.
const Vector<ForestItem>& getItems() const { return mItems; }
/// Returns the items from this cell and all its sub-cells.
void getItems( Vector<ForestItem> *outItems ) const;
void clearPhysicsRep( Forest *forest );
void buildPhysicsRep( Forest *forest );
};
inline const Box3F& ForestCell::getBounds() const
{
if ( mIsDirty )
const_cast<ForestCell*>( this )->_updateBounds();
return mBounds;
}
inline ForestCell* ForestCell::getChildAt( const Point3F &pos ) const
{
U32 index = _getSubCell( pos.x, pos.y );
return mSubCells[index];
}
#endif // _FORESTCELL_H_

View file

@ -0,0 +1,71 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/forestCellBatch.h"
#include "forest/forestItem.h"
ForestCellBatch::ForestCellBatch()
: mDirty( false ),
mBounds( Box3F::Invalid )
{
}
ForestCellBatch::~ForestCellBatch()
{
}
bool ForestCellBatch::add( const ForestItem &item )
{
// A little hacky, but don't allow more than 65K / 6 items
// in a cell... this is generally the VB size limit on hardware.
const U32 maxItems = 10000;
if ( mItems.size() > maxItems )
return false;
// Do the pre batching tests... if it fails
// then we cannot batch this type!
if ( !_prepBatch( item ) )
return false;
// Add it to our list and we'll populate the VB at render time.
mItems.push_back( item );
mDirty = true;
// Expand out bounds.
const Box3F &box = item.getWorldBox();
mBounds.intersect( box );
return true;
}
void ForestCellBatch::render( SceneRenderState *state )
{
if ( mDirty )
{
_rebuildBatch();
mDirty = false;
}
_render( state );
}

View file

@ -0,0 +1,62 @@
//-----------------------------------------------------------------------------
// 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 _FORESTCELLBATCH_H_
#define _FORESTCELLBATCH_H_
#include "scene/sceneRenderState.h"
class ForestItem;
class ForestCellBatch
{
protected:
/// Used to detect when the batch rendering
/// objects need to be repacked.
bool mDirty;
/// The items in the batch.
Vector<ForestItem> mItems;
/// The world space bounding box of this batch.
Box3F mBounds;
virtual bool _prepBatch( const ForestItem &item ) = 0;
virtual void _rebuildBatch() = 0;
virtual void _render( const SceneRenderState *state ) = 0;
public:
ForestCellBatch();
virtual ~ForestCellBatch();
bool add( const ForestItem &item );
S32 getItemCount() const { return mItems.size(); }
void render( SceneRenderState *state );
const Box3F& getWorldBox() const { return mBounds; }
};
#endif // _FORESTCELLBATCH_H_

View file

@ -0,0 +1,477 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/forestCollision.h"
#include "forest/forest.h"
#include "forest/forestCell.h"
#include "forest/ts/tsForestItemData.h"
#include "ts/tsShapeInstance.h"
#include "T3D/physics/physicsPlugin.h"
#include "T3D/physics/physicsBody.h"
#include "T3D/physics/physicsCollision.h"
#include "collision/concretePolyList.h"
#include "platform/profiler.h"
/*
bool Forest::castRay( const Point3F &start, const Point3F &end, RayInfo* info )
{
//if ( !mData )
//return false;
//return mData->castRay( start, end, outInfo );
return false;
}
bool Forest::castRayI( const Point3F &start, const Point3F &end, ForestRayInfo *outInfo )
{
if ( !mData )
return false;
return mData->castRay( start, end, outInfo );
}
ScriptMethod( Forest, forestRayCast, const char*, 4, 4, "( Point3F start, Point3F end )"
"Cast a ray from start to end, checking for collision against trees in this forest.\n\n"
"@returns A string containing either null, if nothing was struck, or these fields:\n"
" - The ID of the tree type datablock.\n"
" - The tree ID (not a normal object id).\n"
" - t, The time during the raycast at which the collision occured." )
{
Point3F start, end;
dSscanf(argv[2], "%g %g %g", &start.x, &start.y, &start.z);
dSscanf(argv[3], "%g %g %g", &end.x, &end.y, &end.z);
char *returnBuffer = Con::getReturnBuffer(256);
returnBuffer[0] = '0';
returnBuffer[1] = '\0';
ForestRayInfo rinfo;
if ( object->castRayI( start, end, &rinfo ) )
dSprintf( returnBuffer, 256, "%d %d %g", rinfo.item->getData()->getId(), rinfo.key, rinfo.t );
return returnBuffer;
}
*/
void ForestConvex::calculateTransform( const MatrixF &worldXfrm )
{
mTransform = worldXfrm;
return;
}
Box3F ForestConvex::getBoundingBox() const
{
// This is probably a bad idea? -- BJG
return getBoundingBox( mTransform, Point3F(mScale,mScale,mScale) );
}
Box3F ForestConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const
{
Box3F newBox = box;
newBox.minExtents.convolve(scale);
newBox.maxExtents.convolve(scale);
mat.mul(newBox);
return newBox;
}
Point3F ForestConvex::support(const VectorF& v) const
{
TSShapeInstance *si = mData->getShapeInstance();
TSShape::ConvexHullAccelerator* pAccel =
si->getShape()->getAccelerator(mData->getCollisionDetails()[hullId]);
AssertFatal(pAccel != NULL, "Error, no accel!");
F32 currMaxDP = mDot(pAccel->vertexList[0], v);
U32 index = 0;
for (U32 i = 1; i < pAccel->numVerts; i++)
{
F32 dp = mDot(pAccel->vertexList[i], v);
if (dp > currMaxDP)
{
currMaxDP = dp;
index = i;
}
}
return pAccel->vertexList[index];
}
void ForestConvex::getFeatures( const MatrixF &mat, const VectorF &n, ConvexFeature *cf )
{
cf->material = 0;
cf->object = mObject;
TSShapeInstance *si = mData->getShapeInstance();
TSShape::ConvexHullAccelerator* pAccel =
si->getShape()->getAccelerator(mData->getCollisionDetails()[hullId]);
AssertFatal(pAccel != NULL, "Error, no accel!");
F32 currMaxDP = mDot(pAccel->vertexList[0], n);
U32 index = 0;
U32 i;
for (i = 1; i < pAccel->numVerts; i++)
{
F32 dp = mDot(pAccel->vertexList[i], n);
if (dp > currMaxDP)
{
currMaxDP = dp;
index = i;
}
}
const U8* emitString = pAccel->emitStrings[index];
U32 currPos = 0;
U32 numVerts = emitString[currPos++];
for (i = 0; i < numVerts; i++)
{
cf->mVertexList.increment();
U32 index = emitString[currPos++];
mat.mulP(pAccel->vertexList[index], &cf->mVertexList.last());
}
U32 numEdges = emitString[currPos++];
for (i = 0; i < numEdges; i++)
{
U32 ev0 = emitString[currPos++];
U32 ev1 = emitString[currPos++];
cf->mEdgeList.increment();
cf->mEdgeList.last().vertex[0] = ev0;
cf->mEdgeList.last().vertex[1] = ev1;
}
U32 numFaces = emitString[currPos++];
for (i = 0; i < numFaces; i++)
{
cf->mFaceList.increment();
U32 plane = emitString[currPos++];
mat.mulV(pAccel->normalList[plane], &cf->mFaceList.last().normal);
for (U32 j = 0; j < 3; j++)
cf->mFaceList.last().vertex[j] = emitString[currPos++];
}
}
void ForestConvex::getPolyList(AbstractPolyList* list)
{
list->setTransform( &mTransform, Point3F(mScale,mScale,mScale));
list->setObject(mObject);
TSShapeInstance *si = mData->getShapeInstance();
S32 detail = mData->getCollisionDetails()[hullId];
si->animate(detail);
si->buildPolyList(list, detail);
}
void Forest::buildConvex( const Box3F &box, Convex *convex )
{
mConvexList->collectGarbage();
// Get all ForestItem(s) within the box.
Vector<ForestItem> trees;
mData->getItems( box, &trees );
if ( trees.empty() )
return;
for ( U32 i = 0; i < trees.size(); i++ )
{
const ForestItem &forestItem = trees[i];
Box3F realBox = box;
mWorldToObj.mul( realBox );
realBox.minExtents.convolveInverse( mObjScale );
realBox.maxExtents.convolveInverse( mObjScale );
// JCF: is this really necessary if we already got this ForestItem
// as a result from getItems?
if ( realBox.isOverlapped( getObjBox() ) == false )
continue;
TSForestItemData *data = (TSForestItemData*)forestItem.getData();
// Find CollisionDetail(s) that are defined...
const Vector<S32> &details = data->getCollisionDetails();
for ( U32 j = 0; j < details.size(); j++ )
{
// JCFHACK: need to fix this if we want this to work with speedtree
// or other cases in which we don't have a TSForestItemData.
// Most likely via preventing this method and other torque collision
// specific stuff from ever getting called.
if ( details[j] == -1 )
continue;
// See if this convex exists in the working set already...
Convex* cc = 0;
CollisionWorkingList& wl = convex->getWorkingList();
for ( CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext )
{
if ( itr->mConvex->getType() == ForestConvexType )
{
ForestConvex *pConvex = static_cast<ForestConvex*>(itr->mConvex);
if ( pConvex->mObject == this &&
pConvex->mForestItemKey == forestItem.getKey() &&
pConvex->hullId == j )
{
cc = itr->mConvex;
break;
}
}
}
if (cc)
continue;
// Then we need to make one.
ForestConvex *cp = new ForestConvex;
mConvexList->registerObject(cp);
convex->addToWorkingList(cp);
cp->mObject = this;
cp->mForestItemKey = forestItem.getKey();
cp->mData = data;
cp->mScale = forestItem.getScale();
cp->hullId = j;
cp->box = forestItem.getObjBox();
cp->calculateTransform( forestItem.getTransform() );
}
}
}
bool Forest::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere )
{
if ( context == PLC_Decal )
return false;
// Get all ForestItem(s) within the box.
Vector<ForestItem> trees;
if ( mData->getItems( box, &trees ) == 0 )
return false;
polyList->setObject(this);
bool gotPoly = false;
for ( U32 i = 0; i < trees.size(); i++ )
gotPoly |= trees[i].buildPolyList( polyList, box, sphere );
return gotPoly;
}
bool ForestItem::buildPolyList( AbstractPolyList* polyList, const Box3F &box, const SphereF &sphere ) const
{
TSForestItemData *data = (TSForestItemData*)mDataBlock;
bool ret = false;
MatrixF xfm = getTransform();
polyList->setTransform( &xfm, Point3F(mScale,mScale,mScale) );
TSShapeInstance *si = data->getShapeInstance();
const Vector<S32> &details = data->getCollisionDetails();
S32 detail;
for (U32 i = 0; i < details.size(); i++ )
{
detail = details[i];
if (detail != -1)
ret |= si->buildPolyList( polyList, detail );
}
return ret;
}
bool Forest::collideBox( const Point3F &start, const Point3F &end, RayInfo *outInfo )
{
//Con::warnf( "Forest::collideBox() - not yet implemented!" );
return Parent::collideBox( start, end, outInfo );
}
bool Forest::castRay( const Point3F &start, const Point3F &end, RayInfo *outInfo )
{
return castRayBase( start, end, outInfo, false );
}
bool Forest::castRayRendered( const Point3F &start, const Point3F &end, RayInfo *outInfo )
{
return castRayBase( start, end, outInfo, true );
}
bool Forest::castRayBase( const Point3F &start, const Point3F &end, RayInfo *outInfo, bool rendered )
{
if ( !getObjBox().collideLine( start, end ) )
return false;
if ( mData->castRay( start, end, outInfo, rendered ) )
{
outInfo->object = this;
return true;
}
return false;
}
void Forest::updateCollision()
{
if ( !mData )
return;
mData->buildPhysicsRep( this );
// Make the assumption that if collision needs
// to be updated that the zoning probably changed too.
if ( isClientObject() )
mZoningDirty = true;
}
bool ForestData::castRay( const Point3F &start, const Point3F &end, RayInfo *outInfo, bool rendered ) const
{
RayInfo shortest;
shortest.userData = outInfo->userData;
shortest.t = F32_MAX;
BucketTable::ConstIterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
{
if ( iter->value->castRay( start, end, outInfo, rendered ) )
{
if ( outInfo->t < shortest.t )
shortest = *outInfo;
}
}
*outInfo = shortest;
return ( outInfo->t < F32_MAX );
}
bool ForestCell::castRay( const Point3F &start, const Point3F &end, RayInfo *outInfo, bool rendered ) const
{
// JCF: start/end are in object space of the Forest but mBounds
// is in world space... Luckily Forest is global bounds and always at the origin
// with no scale.
if ( !mBounds.collideLine( start, end ) )
return false;
RayInfo shortest;
shortest.userData = outInfo->userData;
shortest.t = F32_MAX;
if ( !isLeaf() )
{
for ( U32 i=0; i < 4; i++ )
{
if ( mSubCells[i]->castRay( start, end, outInfo, rendered ) )
{
if ( outInfo->t < shortest.t )
shortest = *outInfo;
}
}
}
else
{
for ( U32 i = 0; i < mItems.size(); i++ )
{
if ( mItems[i].castRay( start, end, outInfo, rendered ) )
{
if ( outInfo->t < shortest.t )
shortest = *outInfo;
}
}
}
*outInfo = shortest;
return ( outInfo->t < F32_MAX );
}
bool ForestItem::castRay( const Point3F &start, const Point3F &end, RayInfo *outInfo, bool rendered ) const
{
if ( !mWorldBox.collideLine( start, end ) )
return false;
Point3F s, e;
MatrixF mat = getTransform();
mat.scale( Point3F(mScale) );
mat.inverse();
mat.mulP( start, &s );
mat.mulP( end, &e );
TSForestItemData *data = (TSForestItemData*)mDataBlock;
TSShapeInstance *si = data->getShapeInstance();
if ( !si )
return false;
if ( rendered )
{
// Always raycast against detail level zero
// because it makes lots of things easier.
if ( !si->castRayRendered( s, e, outInfo, 0 ) )
return false;
if ( outInfo->userData != NULL )
*(ForestItem*)(outInfo->userData) = *this;
return true;
}
RayInfo shortest;
shortest.t = 1e8;
bool gotMatch = false;
S32 detail;
const Vector<S32> &details = data->getLOSDetails();
for (U32 i = 0; i < details.size(); i++)
{
detail = details[i];
if (detail != -1)
{
//si->animate(data->mLOSDetails[i]);
if ( si->castRayOpcode( detail, s, e, outInfo ) )
{
if (outInfo->t < shortest.t)
{
gotMatch = true;
shortest = *outInfo;
}
}
}
}
if ( !gotMatch )
return false;
// Copy out the shortest time...
*outInfo = shortest;
if ( outInfo->userData != NULL )
*(ForestItem*)(outInfo->userData) = *this;
return true;
}

View 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.
//-----------------------------------------------------------------------------
#ifndef _FORESTCOLLISION_H_
#define _FORESTCOLLISION_H_
#ifndef _CONVEX_H_
#include "collision/convex.h"
#endif
#ifndef _COLLISION_H_
#include "collision/collision.h"
#endif
class Forest;
class ForestItem;
class TSForestItemData;
class ForestConvex : public Convex
{
typedef Convex Parent;
friend class Forest;
public:
ForestConvex()
{
mType = ForestConvexType;
mTransform.identity();
}
ForestConvex( const ForestConvex &cv )
{
mType = ForestConvexType;
mObject = cv.mObject;
mForestItemKey = cv.mForestItemKey;
mTransform = cv.mTransform;
mData = cv.mData;
mScale = cv.mScale;
hullId = cv.hullId;
box = box;
}
void calculateTransform( const MatrixF &worldXfrm );
const MatrixF& getTransform() const { return mTransform; }
Box3F getBoundingBox() const;
Box3F getBoundingBox( const MatrixF &mat, const Point3F &scale) const;
Point3F support( const VectorF &v ) const;
void getFeatures( const MatrixF &mat, const VectorF &n, ConvexFeature *cf );
void getPolyList( AbstractPolyList *list);
public:
U32 hullId;
Box3F box;
protected:
// JCF: ForestItemKey is a U32, didn't want to include forest.h here
// fix me if ForestItemKey is changed to a class.
U32 mForestItemKey;
MatrixF mTransform;
TSForestItemData *mData;
F32 mScale;
};
#endif // _FORESTCOLLISION_H_

View file

@ -0,0 +1,834 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/forestDataFile.h"
#include "forest/forest.h"
#include "forest/forestCell.h"
#include "T3D/physics/physicsBody.h"
#include "core/stream/fileStream.h"
#include "core/resource.h"
#include "math/mathIO.h"
#include "math/mPoint2.h"
#include "platform/profiler.h"
template<> ResourceBase::Signature Resource<ForestData>::signature()
{
return MakeFourCC('f','k','d','f');
}
template<>
void* Resource<ForestData>::create( const Torque::Path &path )
{
FileStream stream;
stream.open( path.getFullPath(), Torque::FS::File::Read );
if ( stream.getStatus() != Stream::Ok )
return NULL;
ForestData *file = new ForestData();
if ( !file->read( stream ) )
{
delete file;
return NULL;
}
return file;
}
U32 ForestData::smNextItemId = 1;
ForestData::ForestData()
: mIsDirty( false )
{
ForestItemData::getReloadSignal().notify( this, &ForestData::_onItemReload );
}
ForestData::~ForestData()
{
ForestItemData::getReloadSignal().remove( this, &ForestData::_onItemReload );
clear();
}
void ForestData::clear()
{
// We only have to delete the top level cells and ForestCell will
// clean up its sub-cells in its destructor.
BucketTable::Iterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ ) delete iter->value;
mBuckets.clear();
mIsDirty = true;
}
bool ForestData::read( Stream &stream )
{
// Read our identifier... so we know we're
// not reading in pure garbage.
char id[4] = { 0 };
stream.read( 4, id );
if ( dMemcmp( id, "FKDF", 4 ) != 0 )
{
Con::errorf( "ForestDataFile::read() - This is not a Forest planting file!" );
return false;
}
// Empty ourselves before we really begin reading.
clear();
// Now the version number.
U8 version;
stream.read( &version );
if ( version > (U8)FILE_VERSION )
{
Con::errorf( "ForestDataFile::read() - This file was created with an newer version of Forest!" );
return false;
}
// Read in the names of the ForestItemData datablocks
// and recover the datablock.
Vector<ForestItemData*> allDatablocks;
U32 count;
stream.read( &count );
allDatablocks.setSize( count );
for ( U32 i=0; i < count; i++ )
{
StringTableEntry name = stream.readSTString();
ForestItemData* data = ForestItemData::find( name );
// TODO: Change this to instead create a dummy forest data
// for each so that the user can swap it with the right one.
if ( data == NULL )
{
Con::warnf( "ForestData::read - ForestItemData named %s was not found.", name );
Con::warnf( "Note this can occur if you have deleted or renamed datablocks prior to loading this forest and is not an 'error' in this scenario." );
}
allDatablocks[ i ] = data;
}
U8 dataIndex;
Point3F pos;
QuatF rot;
F32 scale;
ForestItemData* data;
MatrixF xfm;
U32 skippedItems = 0;
// Read in the items.
stream.read( &count );
for ( U32 i=0; i < count; i++ )
{
stream.read( &dataIndex );
mathRead( stream, &pos );
mathRead( stream, &rot );
stream.read( &scale );
data = allDatablocks[ dataIndex ];
if ( data )
{
rot.setMatrix( &xfm );
xfm.setPosition( pos );
addItem( smNextItemId++, data, xfm, scale );
}
else
{
skippedItems++;
}
}
if ( skippedItems > 0 )
Con::warnf( "ForestData::read - %i items were skipped because their datablocks were not found.", skippedItems );
// Clear the dirty flag.
mIsDirty = false;
return true;
}
bool ForestData::write( const char *path )
{
// Open the stream.
FileStream stream;
if ( !stream.open( path, Torque::FS::File::Write ) )
{
Con::errorf( "ForestDataFile::write() - Failed opening stream!" );
return false;
}
// Write our identifier... so we have a better
// idea if we're reading pure garbage.
stream.write( 4, "FKDF" );
// Now the version number.
stream.write( (U8)FILE_VERSION );
// First gather all the ForestItemData datablocks
// used by the items in the forest.
Vector<ForestItemData*> allDatablocks;
getDatablocks( &allDatablocks );
// Write out the datablock list.
U32 count = allDatablocks.size();
stream.write( count );
for ( U32 i=0; i < count; i++ )
{
StringTableEntry localName = allDatablocks[i]->getInternalName();
AssertFatal( localName != NULL && localName[0] != '\0', "ForestData::write - ForestItemData had no internal name set!" );
stream.writeString( allDatablocks[i]->getInternalName() );
}
// Get a copy of all the items.
Vector<ForestItem> items;
getItems( &items );
// Save the item count.
stream.write( (U32)items.size() );
// Save the items.
Vector<ForestItem>::const_iterator iter = items.begin();
for ( ; iter != items.end(); iter++ )
{
U8 dataIndex = find( allDatablocks.begin(), allDatablocks.end(), iter->getData() ) - allDatablocks.begin();
stream.write( dataIndex );
mathWrite( stream, iter->getPosition() );
QuatF quat;
quat.set( iter->getTransform() );
mathWrite( stream, quat );
stream.write( iter->getScale() );
}
// Clear the dirty flag.
mIsDirty = false;
return true;
}
void ForestData::regenCells()
{
Vector<ForestItem> items;
getItems( &items );
clear();
for ( U32 i=0; i < items.size(); i++ )
{
const ForestItem &item = items[i];
addItem( item.getKey(), item.getData(), item.getTransform(), item.getScale() );
}
mIsDirty = true;
}
ForestCell* ForestData::_findBucket( const Point2I &key ) const
{
BucketTable::ConstIterator iter = mBuckets.find( key );
if ( iter != mBuckets.end() )
return iter->value;
else
return NULL;
}
ForestCell* ForestData::_findOrCreateBucket( const Point3F &pos )
{
// Look it up.
const Point2I key = _getBucketKey( pos );
BucketTable::Iterator iter = mBuckets.find( key );
ForestCell *bucket = NULL;
if ( iter != mBuckets.end() )
bucket = iter->value;
else
{
bucket = new ForestCell( RectF( key.x, key.y, BUCKET_DIM, BUCKET_DIM ) );
mBuckets.insertUnique( key, bucket );
mIsDirty = true;
}
return bucket;
}
void ForestData::_onItemReload()
{
// Invalidate cell batches and bounds so they
// will be regenerated next render.
Vector<ForestCell*> stack;
getCells( &stack );
ForestCell *pCell;
while ( !stack.empty() )
{
pCell = stack.last();
stack.pop_back();
if ( !pCell )
continue;
pCell->freeBatches();
pCell->invalidateBounds();
pCell->getChildren( &stack );
}
}
const ForestItem& ForestData::addItem( ForestItemData *data,
const Point3F &position,
F32 rotation,
F32 scale )
{
MatrixF xfm;
xfm.set( EulerF( 0, 0, rotation ), position );
return addItem( smNextItemId++,
data,
xfm,
scale );
}
const ForestItem& ForestData::addItem( ForestItemKey key,
ForestItemData *data,
const MatrixF &xfm,
F32 scale )
{
ForestCell *bucket = _findOrCreateBucket( xfm.getPosition() );
mIsDirty = true;
return bucket->insertItem( key, data, xfm, scale );
}
const ForestItem& ForestData::updateItem( ForestItemKey key,
const Point3F &keyPosition,
ForestItemData *newData,
const MatrixF &newXfm,
F32 newScale )
{
Point2I bucketKey = _getBucketKey( keyPosition );
ForestCell *bucket = _findBucket( bucketKey );
if ( !bucket || !bucket->removeItem( key, keyPosition, true ) )
return ForestItem::Invalid;
if ( bucket->isEmpty() )
{
delete bucket;
mBuckets.erase( bucketKey );
}
return addItem( key, newData, newXfm, newScale );
}
const ForestItem& ForestData::updateItem( ForestItem &item )
{
return updateItem( item.getKey(),
item.getPosition(),
item.getData(),
item.getTransform(),
item.getScale() );
}
bool ForestData::removeItem( ForestItemKey key, const Point3F &keyPosition )
{
Point2I bucketkey = _getBucketKey( keyPosition );
ForestCell *bucket = _findBucket( keyPosition );
if ( !bucket || !bucket->removeItem( key, keyPosition, true ) )
return false;
if ( bucket->isEmpty() )
{
delete bucket;
mBuckets.erase( bucketkey );
}
mIsDirty = true;
return true;
}
const ForestItem& ForestData::findItem( ForestItemKey key, const Point3F &keyPos ) const
{
PROFILE_SCOPE( ForestData_findItem );
AssertFatal( key != 0, "ForestCell::findItem() - Got null key!" );
ForestCell *cell = _findBucket( keyPos );
while ( cell && !cell->isLeaf() )
cell = cell->getChildAt( keyPos );
U32 index;
if ( cell && cell->findIndexByKey( key, &index ) )
return cell->getItems()[ index ];
return ForestItem::Invalid;
}
const ForestItem& ForestData::findItem( ForestItemKey key ) const
{
PROFILE_SCOPE( ForestData_findItem_Slow );
AssertFatal( key != 0, "ForestData::findItem() - Got null key!" );
// Do an exhaustive search thru all the cells... this
// is really crappy... we shouldn't do this regularly.
Vector<const ForestCell*> stack;
BucketTable::ConstIterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
stack.push_back( iter->value );
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
const ForestCell *cell = stack.last();
stack.pop_back();
// Recurse thru non-leaf cells.
if ( !cell->isLeaf() )
{
cell->getChildren( &stack );
continue;
}
// Finally search for the item.
U32 index;
if ( cell->findIndexByKey( key, &index ) )
return cell->getItems()[ index ];
}
return ForestItem::Invalid;
}
U32 ForestData::getItems( Vector<ForestItem> *outItems ) const
{
AssertFatal( outItems, "ForestData::getItems() - The output vector was NULL!" );
PROFILE_SCOPE( ForestData_getItems );
Vector<const ForestCell*> stack;
U32 count = 0;
BucketTable::ConstIterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
stack.push_back( iter->value );
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
const ForestCell *cell = stack.last();
stack.pop_back();
// Recurse thru non-leaf cells.
if ( !cell->isLeaf() )
{
cell->getChildren( &stack );
continue;
}
// Get the items.
count += cell->getItems().size();
outItems->merge( cell->getItems() );
}
return count;
}
U32 ForestData::getItems( const Frustum &culler, Vector<ForestItem> *outItems ) const
{
AssertFatal( outItems, "ForestData::getItems() - The output vector was NULL!" );
PROFILE_SCOPE( ForestData_getItems_ByFrustum );
Vector<ForestCell*> stack;
getCells( &stack );
Vector<ForestItem>::const_iterator iter;
U32 count = 0;
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
const ForestCell *cell = stack.last();
stack.pop_back();
if ( culler.isCulled( cell->getBounds() ) )
continue;
// Recurse thru non-leaf cells.
if ( cell->isBranch() )
{
cell->getChildren( &stack );
continue;
}
// Get the items.
iter = cell->getItems().begin();
for ( ; iter != cell->getItems().end(); iter++ )
{
if ( !culler.isCulled( iter->getWorldBox() ) )
{
outItems->merge( cell->getItems() );
count++;
}
}
}
return count;
}
U32 ForestData::getItems( const Box3F &box, Vector<ForestItem> *outItems ) const
{
PROFILE_SCOPE( ForestData_getItems_ByBox );
Vector<const ForestCell*> stack;
U32 count = 0;
BucketTable::ConstIterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
stack.push_back( iter->value );
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
const ForestCell *cell = stack.last();
stack.pop_back();
// If the cell is empty or doesn't overlap the box... skip it.
if ( cell->isEmpty() ||
!cell->getBounds().isOverlapped( box ) )
continue;
// Recurse thru non-leaf cells.
if ( !cell->isLeaf() )
{
cell->getChildren( &stack );
continue;
}
// Finally look thru the items.
const Vector<ForestItem> &items = cell->getItems();
Vector<ForestItem>::const_iterator item = items.begin();
for ( ; item != items.end(); item++ )
{
if ( item->getWorldBox().isOverlapped( box ) )
{
// If we don't have an output vector then the user just
// wanted to know if any object existed... so early out.
if ( !outItems )
return 1;
++count;
outItems->push_back( *item );
}
}
}
return count;
}
U32 ForestData::getItems( const Point3F &point, F32 radius, Vector<ForestItem> *outItems ) const
{
PROFILE_SCOPE( ForestData_getItems_BySphere );
Vector<const ForestCell*> stack;
U32 count = 0;
BucketTable::ConstIterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
stack.push_back( iter->value );
const F32 radiusSq = radius * radius;
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
const ForestCell *cell = stack.last();
stack.pop_back();
// TODO: If we could know here that the cell is fully within
// the sphere... we could do a fast gather of all its elements
// without any further testing of it or its children.
// If the cell is empty or doesn't overlap the sphere... skip it.
if ( cell->isEmpty() ||
cell->getBounds().getSqDistanceToPoint( point ) > radiusSq )
continue;
// Recurse thru non-leaf cells.
if ( !cell->isLeaf() )
{
cell->getChildren( &stack );
continue;
}
// Finally look thru the items.
const Vector<ForestItem> &items = cell->getItems();
Vector<ForestItem>::const_iterator item = items.begin();
for ( ; item != items.end(); item++ )
{
if ( item->getWorldBox().getSqDistanceToPoint( point ) < radiusSq )
{
// If we don't have an output vector then the user just
// wanted to know if any object existed... so early out.
if ( !outItems )
return 1;
++count;
outItems->push_back( *item );
}
}
}
return count;
}
U32 ForestData::getItems( const Point2F &point, F32 radius, Vector<ForestItem> *outItems ) const
{
PROFILE_SCOPE( ForestData_getItems_ByCircle );
Vector<const ForestCell*> stack;
U32 count = 0;
BucketTable::ConstIterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
stack.push_back( iter->value );
const F32 radiusSq = radius * radius;
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
const ForestCell *cell = stack.last();
stack.pop_back();
// If the cell is empty or doesn't overlap the sphere... skip it.
if ( cell->isEmpty() ||
cell->getRect().getSqDistanceToPoint( point ) > radiusSq )
continue;
// Recurse thru non-leaf cells.
if ( !cell->isLeaf() )
{
cell->getChildren( &stack );
continue;
}
// Finally look thru the items.
const Vector<ForestItem> &items = cell->getItems();
Vector<ForestItem>::const_iterator item = items.begin();
F32 compareDist;
for ( ; item != items.end(); item++ )
{
compareDist = mSquared( radius + item->getData()->mRadius );
if ( item->getSqDistanceToPoint( point ) < compareDist )
{
// If we don't have an output vector then the user just
// wanted to know if any object existed... so early out.
if ( !outItems )
return 1;
++count;
outItems->push_back( *item );
}
}
}
return count;
}
U32 ForestData::getItems( const ForestItemData *data, Vector<ForestItem> *outItems ) const
{
AssertFatal( outItems, "ForestData::getItems() - The output vector was NULL!" );
PROFILE_SCOPE( ForestData_getItems_ByDatablock );
Vector<const ForestCell*> stack;
U32 count = 0;
BucketTable::ConstIterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
stack.push_back( iter->value );
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
const ForestCell *cell = stack.last();
stack.pop_back();
// Recurse thru non-leaf cells.
if ( !cell->isLeaf() )
{
cell->getChildren( &stack );
continue;
}
// Get the items.
const Vector<ForestItem> &items = cell->getItems();
Vector<ForestItem>::const_iterator item = items.begin();
for ( ; item != items.end(); item++ )
{
if ( item->getData() == data )
{
++count;
outItems->push_back( *item );
}
}
}
return count;
}
void ForestData::getCells( const Frustum &frustum, Vector<ForestCell*> *outCells ) const
{
PROFILE_SCOPE( ForestData_getCells_frustum );
BucketTable::ConstIterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
{
if ( !frustum.isCulled( iter->value->getBounds() ) )
outCells->push_back( iter->value );
}
}
void ForestData::getCells( Vector<ForestCell*> *outCells ) const
{
PROFILE_SCOPE( ForestData_getCells_nofrustum );
BucketTable::ConstIterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
outCells->push_back( iter->value );
}
U32 ForestData::getDatablocks( Vector<ForestItemData*> *outVector ) const
{
Vector<const ForestCell*> stack;
U32 count = 0;
BucketTable::ConstIterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
stack.push_back( iter->value );
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
const ForestCell *cell = stack.last();
stack.pop_back();
// Recurse thru non-leaf cells.
if ( !cell->isLeaf() )
{
cell->getChildren( &stack );
continue;
}
// Go thru the items.
const Vector<ForestItem> &items = cell->getItems();
Vector<ForestItem>::const_iterator item = items.begin();
for ( ; item != items.end(); item++ )
{
ForestItemData *data = item->getData();
if ( find( outVector->begin(), outVector->end(), data ) != outVector->end() )
continue;
count++;
outVector->push_back( data );
}
}
return count;
}
void ForestData::clearPhysicsRep( Forest *forest )
{
Vector<ForestCell*> stack;
BucketTable::Iterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
stack.push_back( iter->value );
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
ForestCell *cell = stack.last();
stack.pop_back();
// Recurse thru non-leaf cells.
if ( !cell->isLeaf() )
{
cell->getChildren( &stack );
continue;
}
cell->clearPhysicsRep( forest );
}
}
void ForestData::buildPhysicsRep( Forest *forest )
{
Vector<ForestCell*> stack;
BucketTable::Iterator iter = mBuckets.begin();
for ( ; iter != mBuckets.end(); iter++ )
stack.push_back( iter->value );
// Now loop till we run out of cells.
while ( !stack.empty() )
{
// Pop off the next cell.
ForestCell *cell = stack.last();
stack.pop_back();
// Recurse thru non-leaf cells.
if ( !cell->isLeaf() )
{
cell->getChildren( &stack );
continue;
}
cell->buildPhysicsRep( forest );
}
}

View file

@ -0,0 +1,212 @@
//-----------------------------------------------------------------------------
// 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 _FORESTDATAFILE_H_
#define _FORESTDATAFILE_H_
#ifndef _FORESTITEM_H_
#include "forest/forestItem.h"
#endif
#ifndef _TDICTIONARY_H_
#include "core/util/tDictionary.h"
#endif
class ForestCell;
class Forest;
class Frustum;
/// This is the data file for Forests.
class ForestData
{
protected:
enum { FILE_VERSION = 1 };
/// Set the bucket dimensions to 2km x 2km.
static const U32 BUCKET_DIM = 2000;
/// Set to true if the file is dirty and
/// needs to be saved before being destroyed.
bool mIsDirty;
///
typedef HashTable<Point2I,ForestCell*> BucketTable;
/// The top level cell buckets which allows us
/// to have virtually unbounded range.
BucketTable mBuckets;
/// The next free item id.
static U32 smNextItemId;
/// Converts a ForestItem's Point3F 'KeyPosition' to a Point2I
/// key we index into BucketTable with.
static Point2I _getBucketKey( const Point3F &pos );
/// Finds the bucket with the given Point2I key or returns NULL.
ForestCell* _findBucket( const Point2I &key ) const;
/// Finds the best top level bucket for the ForestItem 'key' position
/// or returns NULL.
ForestCell* _findBucket( const Point3F &pos ) const;
/// Find the best top level bucket for the given position
/// or returns a new one.
ForestCell* _findOrCreateBucket( const Point3F &pos );
void _onItemReload();
public:
ForestData();
virtual ~ForestData();
bool isDirty() const { return mIsDirty; }
/// Deletes all the data and resets the
/// file to an empty state.
void clear();
/// Helper for debugging cell generation.
void regenCells();
///
bool read( Stream &stream );
///
bool write( const char *path );
const ForestItem& addItem( ForestItemData *data,
const Point3F &position,
F32 rotation,
F32 scale );
const ForestItem& addItem( ForestItemKey key,
ForestItemData *data,
const MatrixF &xfm,
F32 scale );
const ForestItem& updateItem( ForestItemKey key,
const Point3F &keyPosition,
ForestItemData *newData,
const MatrixF &newXfm,
F32 newscale );
const ForestItem& updateItem( ForestItem &item );
bool removeItem( ForestItemKey key, const Point3F &keyPosition );
/// Performs a search using the position to limit tested cells.
const ForestItem& findItem( ForestItemKey key, const Point3F &keyPosition ) const;
/// Does an exhaustive search thru all cells looking for
/// the item. This method is slow and should be avoided.
const ForestItem& findItem( ForestItemKey key ) const;
/// Fills a vector with a copy of all the items in the data set.
///
/// @param outItems The output vector of items.
/// @return The count of items found.
///
U32 getItems( Vector<ForestItem> *outItems ) const;
/// Fills a vector with a copy of all items in the Frustum.
/// Note that this IS expensive and this is not how Forest internally
/// collects items for rendering. This is here for ForestSelectionTool.
///
/// @param The Frustum to cull with.
/// @param outItems The output vector of items.
/// @return The count of items found.
///
U32 getItems( const Frustum &culler, Vector<ForestItem> *outItems ) const;
/// Returns a copy of all the items that intersect the box. If
/// the output vector is NULL then it will early out on the first
/// found item returning 1.
///
/// @param box The search box.
/// @param outItems The output vector of items or NULL.
/// @return The count of items found.
///
U32 getItems( const Box3F &box, Vector<ForestItem> *outItems ) const;
/// Returns a copy of all the items that intersect the sphere. If
/// the output vector is NULL then it will early out on the first
/// found item returning 1.
///
/// @param point The center of the search sphere.
/// @param radius The radius of the search sphere.
/// @param outItems The output vector of items or NULL.
/// @return The count of items found.
///
U32 getItems( const Point3F &point, F32 radius, Vector<ForestItem> *outItems ) const;
/// Returns a copy of all the items that intersect the 2D circle ignoring
/// the z component. If the output vector is NULL then it will early out
/// on the first found item returning 1.
///
/// @param point The center point of the search circle.
/// @param radius The radius of the search circle.
/// @param outItems The output vector of items or NULL.
/// @return The count of items found.
///
U32 getItems( const Point2F &point, F32 radius, Vector<ForestItem> *outItems ) const;
/// Returns a copy of all the items that share the input item datablock.
///
/// @param data The datablock to search for.
/// @param outItems The output vector of items.
/// @return The count of items found.
///
U32 getItems( const ForestItemData *data, Vector<ForestItem> *outItems ) const;
/// Returns all the top level cells which intersect the frustum.
void getCells( const Frustum &frustum, Vector<ForestCell*> *outCells ) const;
/// Returns all top level cells.
void getCells( Vector<ForestCell*> *outCells ) const;
/// Gathers all the datablocks used and returns the count.
U32 getDatablocks( Vector<ForestItemData*> *outVector ) const;
///
bool castRay( const Point3F &start, const Point3F &end, RayInfo *outInfo, bool rendered ) const;
///
void clearPhysicsRep( Forest *forest );
void buildPhysicsRep( Forest *forest );
};
inline Point2I ForestData::_getBucketKey( const Point3F &pos )
{
return Point2I ( (S32)mFloor(pos.x / BUCKET_DIM) * BUCKET_DIM,
(S32)mFloor(pos.y / BUCKET_DIM) * BUCKET_DIM );
}
inline ForestCell* ForestData::_findBucket( const Point3F &pos ) const
{
return _findBucket( _getBucketKey( pos ) );
}
#endif // _FORESTDATAFILE_H_

View file

@ -0,0 +1,249 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/forestItem.h"
#include "forest/forestCollision.h"
#include "core/stream/bitStream.h"
#include "console/consoleTypes.h"
IMPLEMENT_CO_DATABLOCK_V1(ForestItemData);
ConsoleDocClass( ForestItemData,
"@brief Base class for defining a type of ForestItem. It does not implement "
"loading or rendering of the shapeFile.\n\n"
"@ingroup Forest"
);
SimSet* ForestItemData::smSet = NULL;
ForestItemData::ForestItemData()
: mNeedPreload( true ),
mShapeFile( NULL ),
mCollidable( true ),
mRadius( 1 ),
mWindScale( 0.0f ),
mTrunkBendScale( 0.0f ),
mWindBranchAmp( 0.0f ),
mWindDetailAmp( 0.0f ),
mWindDetailFreq( 0.0f ),
mMass( 5.0f ),
mRigidity( 10.0f ),
mTightnessCoefficient( 0.4f ),
mDampingCoefficient( 0.7f )
{
}
void ForestItemData::initPersistFields()
{
Parent::initPersistFields();
addGroup( "Media" );
addField( "shapeFile", TypeShapeFilename, Offset( mShapeFile, ForestItemData ),
"Shape file for this item type" );
addField( "collidable", TypeBool, Offset( mCollidable, ForestItemData ),
"Can other objects or spacial queries hit items of this type." );
addField( "radius", TypeF32, Offset( mRadius, ForestItemData ),
"Radius used during placement to ensure items are not crowded." );
endGroup( "Media" );
addGroup( "Wind" );
addField( "mass", TypeF32, Offset( mMass, ForestItemData ),
"Mass used in calculating spring forces on the trunk. Generally how "
"springy a plant is." );
addField( "rigidity", TypeF32, Offset( mRigidity, ForestItemData ),
"Rigidity used in calculating spring forces on the trunk. How much the plant resists the wind force" );
addField( "tightnessCoefficient", TypeF32, Offset( mTightnessCoefficient, ForestItemData ),
"Coefficient used in calculating spring forces on the trunk. "
"How much the plant resists bending." );
addField( "dampingCoefficient", TypeF32, Offset( mDampingCoefficient, ForestItemData ),
"Coefficient used in calculating spring forces on the trunk. "
"Causes oscillation and forces to decay faster over time." );
addField( "windScale", TypeF32, Offset( mWindScale, ForestItemData ),
"Overall scale to the effect of wind." );
addField( "trunkBendScale", TypeF32, Offset( mTrunkBendScale, ForestItemData ),
"Overall bend amount of the tree trunk by wind and impacts." );
addField( "branchAmp", TypeF32, Offset( mWindBranchAmp, ForestItemData ),
"Amplitude of the effect on larger branches." );
addField( "detailAmp", TypeF32, Offset( mWindDetailAmp, ForestItemData ),
"Amplitude of the winds effect on leafs/fronds." );
addField( "detailFreq", TypeF32, Offset( mWindDetailFreq, ForestItemData ),
"Frequency (speed) of the effect on leafs/fronds." );
endGroup( "Wind" );
}
void ForestItemData::consoleInit()
{
}
SimSet* ForestItemData::getSet()
{
if ( !smSet )
{
if ( Sim::findObject( "ForestItemDataSet", smSet ) )
return smSet;
smSet = new SimSet;
smSet->assignName( "ForestItemDataSet" );
smSet->registerObject();
Sim::getRootGroup()->addObject( smSet );
}
return smSet;
}
ForestItemData* ForestItemData::find( const char *name )
{
ForestItemData *result = dynamic_cast<ForestItemData*>( getSet()->findObjectByInternalName( name ) );
if ( !result )
Sim::findObject( name, result );
return result;
}
void ForestItemData::onNameChange( const char *name )
{
setInternalName( name );
}
bool ForestItemData::onAdd()
{
if ( !Parent::onAdd() )
return false;
getSet()->addObject( this );
return true;
}
void ForestItemData::packData(BitStream* stream)
{
Parent::packData(stream);
String localName = getInternalName();
if ( localName.isEmpty() )
localName = getName();
stream->write( localName );
stream->writeString(mShapeFile);
stream->writeFlag( mCollidable );
stream->write( mRadius );
stream->write( mMass );
stream->write( mRigidity );
stream->write( mTightnessCoefficient );
stream->write( mDampingCoefficient );
stream->write( mWindScale );
stream->write( mTrunkBendScale );
stream->write( mWindBranchAmp );
stream->write( mWindDetailAmp );
stream->write( mWindDetailFreq );
}
void ForestItemData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
String localName;
stream->read( &localName );
setInternalName( localName );
char readBuffer[1024];
stream->readString(readBuffer);
mShapeFile = StringTable->insert(readBuffer);
mCollidable = stream->readFlag();
stream->read( &mRadius );
stream->read( &mMass );
stream->read( &mRigidity );
stream->read( &mTightnessCoefficient );
stream->read( &mDampingCoefficient );
stream->read( &mWindScale );
stream->read( &mTrunkBendScale );
stream->read( &mWindBranchAmp );
stream->read( &mWindDetailAmp );
stream->read( &mWindDetailFreq );
}
const ForestItem ForestItem::Invalid;
ForestItem::ForestItem()
: mDataBlock( NULL ),
mTransform( true ),
mScale( 0.0f ),
mWorldBox( 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f ),
mRadius( 0.0f ),
mKey( 0 )
{
}
ForestItem::~ForestItem()
{
}
void ForestItem::setTransform( const MatrixF &xfm, F32 scale )
{
mTransform = xfm;
mScale = scale;
// Cache the world box to improve culling performance.
VectorF objScale( mScale, mScale, mScale );
mWorldBox = getObjBox();
mWorldBox.minExtents.convolve( objScale );
mWorldBox.maxExtents.convolve( objScale );
mTransform.mul( mWorldBox );
// Generate a radius that encompasses the entire box.
mRadius = ( mWorldBox.maxExtents - mWorldBox.minExtents ).len() / 2.0f;
}
void ForestItem::setData( ForestItemData *data )
{
mDataBlock = data;
}

View file

@ -0,0 +1,274 @@
//-----------------------------------------------------------------------------
// 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 _FORESTITEM_H_
#define _FORESTITEM_H_
#ifndef _MMATH_H_
#include "math/mMath.h"
#endif
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#ifndef _DYNAMIC_CONSOLETYPES_H_
#include "console/dynamicTypes.h"
#endif
class ForestItem;
class ForestCellBatch;
class ForestConvex;
class ForestWindAccumulator;
class Box3F;
class Point3F;
class TSRenderState;
class SceneRenderState;
struct RayInfo;
class AbstractPolyList;
class ForestItemData : public SimDataBlock
{
protected:
typedef SimDataBlock Parent;
static SimSet* smSet;
bool mNeedPreload;
virtual void _preload() {}
public:
/// Shape file for this item type.
StringTableEntry mShapeFile;
/// This is the radius used during placement to ensure
/// the element isn't crowded up against other trees.
F32 mRadius;
/// Overall scale to the effect of wind.
F32 mWindScale;
/// This is used to control the overall bend amount of the tree by wind and impacts.
F32 mTrunkBendScale;
/// Amplitude of the effect on larger branches.
F32 mWindBranchAmp;
/// Amplitude of the winds effect on leafs/fronds.
F32 mWindDetailAmp;
/// Frequency (speed) of the effect on leafs/fronds.
F32 mWindDetailFreq;
/// Mass used in calculating spring forces.
F32 mMass;
// The rigidity of the tree's trunk.
F32 mRigidity;
// The tightness coefficient of the spring for this tree type's ForestWindAccumulator.
F32 mTightnessCoefficient;
// The damping coefficient.
F32 mDampingCoefficient;
/// Can other objects or spacial queries hit items of this type.
bool mCollidable;
static SimSet* getSet();
static ForestItemData* find( const char *name );
ForestItemData();
virtual ~ForestItemData() {}
DECLARE_CONOBJECT( ForestItemData );
static void consoleInit();
static void initPersistFields();
virtual void onNameChange(const char *name);
virtual bool onAdd();
virtual void packData(BitStream* stream);
virtual void unpackData(BitStream* stream);
/// Called from Forest the first time a datablock is used
/// in order to lazy load content.
void preload()
{
if ( !mNeedPreload )
return;
_preload();
mNeedPreload = false;
}
virtual const Box3F& getObjBox() const { return Box3F::Invalid; }
virtual bool render( TSRenderState *rdata, const ForestItem &item ) const { return false; }
virtual bool canBillboard( const SceneRenderState *state, const ForestItem &item, F32 distToCamera ) const { return false; }
virtual ForestCellBatch* allocateBatch() const { return NULL; }
typedef Signal<void(void)> ReloadSignal;
static ReloadSignal& getReloadSignal()
{
static ReloadSignal theSignal;
return theSignal;
}
};
typedef Vector<ForestItemData*> ForestItemDataVector;
typedef U32 ForestItemKey;
class ForestItem
{
protected:
ForestItemData *mDataBlock;
// The unique identifier used when querying forest items.
ForestItemKey mKey;
MatrixF mTransform;
F32 mScale;
F32 mRadius;
Box3F mWorldBox;
// JCFHACK: change this to an abstract physics-rep.
//NxActor *mActor;
/// If we're currently being effected by one or
/// more wind emitters then we hold the results
/// in this class.
//ForestWindAccumulator *mWind;
public:
// Constructs an invalid item.
ForestItem();
// Note: We keep this non-virtual to save vtable space.
~ForestItem();
static const ForestItem Invalid;
// Comparison operators with other ForestItems.
// Note that this compares only the ForestItemKey, we are not validating
// that any other data like the position actually matches.
bool operator==(const ForestItem&) const;
bool operator!=(const ForestItem&) const;
/// Returns true if this is a valid item.
bool isValid() const { return mKey != 0; }
/// Invalidates the item.
void makeInvalid() { mKey = 0; }
const ForestItemKey& getKey() const { return mKey; };
void setKey( const ForestItemKey &key ) { mKey = key; }
Point3F getPosition() const { return mTransform.getPosition(); }
const MatrixF& getTransform() const { return mTransform; }
F32 getScale() const { return mScale; }
F32 getRadius() const { return mRadius; }
Point3F getCenterPoint() const { return mWorldBox.getCenter(); }
void setTransform( const MatrixF &xfm, F32 scale );
F32 getSqDistanceToPoint( const Point2F &point ) const;
void setData( ForestItemData *data );
const Box3F& getObjBox() const { return mDataBlock->getObjBox(); }
const Box3F& getWorldBox() const { return mWorldBox; }
Point3F getSize() const
{
if ( !mDataBlock )
return Point3F::One;
Box3F size = mDataBlock->getObjBox();
size.scale( mScale );
return size.getExtents();
}
ForestItemData* getData() const { return mDataBlock; };
inline bool canBillboard( const SceneRenderState *state, F32 distToCamera ) const
{
return mDataBlock && mDataBlock->canBillboard( state, *this, distToCamera );
}
/// Collision
/// @{
bool castRay( const Point3F &start, const Point3F &end, RayInfo *outInfo, bool rendered ) const;
bool buildPolyList( AbstractPolyList *polyList, const Box3F &box, const SphereF &sphere ) const;
//ForestConvex* buildConvex( const Box3F &box, Convex *convex ) const;
/// @}
};
typedef Vector<ForestItem> ForestItemVector;
inline F32 ForestItem::getSqDistanceToPoint( const Point2F &point ) const
{
return ( getPosition().asPoint2F() - point ).lenSquared();
}
inline bool ForestItem::operator==(const ForestItem& _test) const
{
return mKey == _test.mKey;
}
inline bool ForestItem::operator!=(const ForestItem& _test) const
{
return mKey != _test.mKey;
}
#endif // _FORESTITEM_H_

View file

@ -0,0 +1,319 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/forest.h"
#include "forest/forestCell.h"
#include "forest/forestDataFile.h"
#include "gfx/gfxTransformSaver.h"
#include "renderInstance/renderPassManager.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
#include "lighting/lightManager.h"
#include "ts/tsMesh.h"
#include "ts/tsRenderState.h"
#include "ts/tsShapeInstance.h"
#include "gfx/primBuilder.h"
#include "gfx/gfxDrawUtil.h"
#include "math/mathUtils.h"
U32 Forest::smTotalCells = 0;
U32 Forest::smCellsRendered = 0;
U32 Forest::smCellItemsRendered = 0;
U32 Forest::smCellsBatched = 0;
U32 Forest::smCellItemsBatched = 0;
F32 Forest::smAverageItemsPerCell = 0.0f;
void Forest::_clearStats(bool beginFrame)
{
// Reset the rendering stats!
if (beginFrame)
{
smTotalCells = 0;
smCellsRendered = 0;
smCellItemsRendered = 0;
smCellsBatched = 0;
smCellItemsBatched = 0;
smAverageItemsPerCell = 0.0f;
}
}
void Forest::prepRenderImage( SceneRenderState *state )
{
PROFILE_SCOPE(Forest_RenderCells);
// TODO: Fix stats.
/*
ForestCellVector &theCells = mData->getCells();
smTotalCells += theCells.size();
// Don't render if we don't have a grid!
if ( theCells.empty() )
return false;
*/
// Prepare to render.
GFXTransformSaver saver;
// Figure out the grid range in the viewing area.
const bool isReflectPass = state->isReflectPass();
const F32 cullScale = isReflectPass ? mReflectionLodScalar : 1.0f;
// If we need to update our cached
// zone state then do it now.
if ( mZoningDirty )
{
mZoningDirty = false;
Vector<ForestCell*> cells;
mData->getCells( &cells );
for ( U32 i=0; i < cells.size(); i++ )
cells[i]->_updateZoning( getSceneManager()->getZoneManager() );
}
// TODO: Move these into the TSForestItemData as something we
// setup once and don't do per-instance.
// Set up the TS render state.
TSRenderState rdata;
rdata.setSceneState( state );
// Use origin sort on all forest elements as
// its alot cheaper than the bounds sort.
rdata.setOriginSort( true );
// We may have some forward lit materials in
// the forest, so pass down a LightQuery for it.
LightQuery lightQuery;
rdata.setLightQuery( &lightQuery );
Frustum culler = state->getFrustum();
// Adjust the far distance if the cull scale has changed.
if ( !mIsEqual( cullScale, 1.0f ) )
{
const F32 visFarDist = culler.getFarDist() * cullScale;
culler.setFarDist( visFarDist );
}
Box3F worldBox;
// Used for debug drawing.
GFXDrawUtil* drawer = GFX->getDrawUtil();
drawer->clearBitmapModulation();
// Go thru the visible cells.
const Box3F &cullerBounds = culler.getBounds();
const Point3F &camPos = state->getDiffuseCameraPosition();
U32 clipMask;
smAverageItemsPerCell = 0.0f;
U32 cellsProcessed = 0;
ForestCell *cell;
// First get all the top level cells which
// intersect the frustum.
Vector<ForestCell*> cellStack;
mData->getCells( culler, &cellStack );
// Get the culling zone state.
const BitVector &zoneState = state->getCullingState().getZoneVisibilityFlags();
// Now loop till we run out of cells.
while ( !cellStack.empty() )
{
// Pop off the next cell.
cell = cellStack.last();
cellStack.pop_back();
const Box3F &cellBounds = cell->getBounds();
// If the cell is empty or its bounds is outside the frustum
// bounds then we have nothing nothing more to do.
if ( cell->isEmpty() || !cullerBounds.isOverlapped( cellBounds ) )
continue;
// Can we cull this cell entirely?
clipMask = culler.testPlanes( cellBounds, Frustum::PlaneMaskAll );
if ( clipMask == -1 )
continue;
// Test cell visibility for interior zones.
const bool visibleInside = !cell->getZoneOverlap().empty() ? zoneState.testAny( cell->getZoneOverlap() ) : false;
// Test cell visibility for outdoor zone, but only
// if we need to.
bool visibleOutside = false;
if( !cell->mIsInteriorOnly && !visibleInside )
{
U32 outdoorZone = SceneZoneSpaceManager::RootZoneId;
visibleOutside = !state->getCullingState().isCulled( cellBounds, &outdoorZone, 1 );
}
// Skip cell if neither visible indoors nor outdoors.
if( !visibleInside && !visibleOutside )
continue;
// Update the stats.
smAverageItemsPerCell += cell->getItems().size();
++cellsProcessed;
//if ( cell->isLeaf() )
//++leafCellsProcessed;
// Get the distance from the camera to the cell bounds.
F32 dist = cellBounds.getDistanceToPoint( camPos );
// If the largest item in the cell can be billboarded
// at the cell distance to the camera... then the whole
// cell can be billboarded.
//
if ( smForceImposters ||
( dist > 0.0f && cell->getLargestItem().canBillboard( state, dist ) ) )
{
// If imposters are disabled then skip out.
if ( smDisableImposters )
continue;
PROFILE_SCOPE(Forest_RenderBatches);
// Keep track of how many cells were batched.
++smCellsBatched;
// Ok... everything in this cell should be batched. First
// create the batches if we don't have any.
if ( !cell->hasBatches() )
cell->buildBatches();
//if ( drawCells )
//mCellRenderFlag[ cellIter - theCells.begin() ] = 1;
// TODO: Light queries for batches?
// Now render the batches... we pass the culler if the
// cell wasn't fully visible so that each batch can be culled.
smCellItemsBatched += cell->renderBatches( state, clipMask != 0 ? &culler : NULL );
continue;
}
// If this isn't a leaf then recurse.
if ( !cell->isLeaf() )
{
cell->getChildren( &cellStack );
continue;
}
// This cell has mixed billboards and mesh based items.
++smCellsRendered;
PROFILE_SCOPE(Forest_RenderItems);
//if ( drawCells )
//mCellRenderFlag[ cellIter - theCells.begin() ] = 2;
// Use the cell bounds as the light query volume.
//
// This means all forward lit items in this cell will
// get the same lights, but it performs much better.
lightQuery.init( cellBounds );
// This cell is visible... have it render its items.
smCellItemsRendered += cell->render( &rdata, clipMask != 0 ? &culler : NULL );
}
// Keep track of the average items per cell.
if ( cellsProcessed > 0 )
smAverageItemsPerCell /= (F32)cellsProcessed;
// Got debug drawing to do?
if ( smDrawCells && state->isDiffusePass() )
{
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
ri->renderDelegate.bind( this, &Forest::_renderCellBounds );
ri->type = RenderPassManager::RIT_Editor;
state->getRenderPass()->addInst( ri );
}
}
void Forest::_renderCellBounds( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
{
PROFILE_SCOPE( Forest_RenderCellBounds );
if ( overrideMat )
return;
GFXTransformSaver saver;
MatrixF projBias(true);
const Frustum frustum = GFX->getFrustum();
MathUtils::getZBiasProjectionMatrix( 0.001f, frustum, &projBias );
GFX->setProjectionMatrix( projBias );
VectorF extents;
Point3F pos;
// Get top level cells
Vector<ForestCell*> cellStack;
mData->getCells( &cellStack );
// Holds child cells we need to render as we encounter them.
Vector<ForestCell*> frontier;
GFXDrawUtil *drawer = GFX->getDrawUtil();
GFXStateBlockDesc desc;
desc.setZReadWrite( true, false );
desc.setBlend( true );
desc.setFillModeWireframe();
while ( !cellStack.empty() )
{
while ( !cellStack.empty() )
{
const ForestCell *cell = cellStack.last();
cellStack.pop_back();
Box3F box = cell->getBounds();
drawer->drawCube( desc, box, ColorI( 0, 255, 0 ) );
RectF rect = cell->getRect();
box.minExtents.set( rect.point.x, rect.point.y, box.minExtents.z );
box.maxExtents.set( rect.point.x + rect.extent.x, rect.point.y + rect.extent.y, box.minExtents.z );
drawer->drawCube( desc, box, ColorI::RED );
// If this cell has children, add them to the frontier.
if ( !cell->isLeaf() )
cell->getChildren( &frontier );
}
// Now the frontier becomes the cellStack and we empty the frontier.
cellStack = frontier;
frontier.clear();
}
}

View file

@ -0,0 +1,132 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/forestWindAccumulator.h"
#include "forest/forestWindMgr.h"
#include "forest/forestItem.h"
#include "platform/profiler.h"
ForestWindAccumulator::ForestWindAccumulator( const TreePlacementInfo &info )
: mCurrentStrength( 0.0f )
{
mCurrentDir.set( 0, 0 );
mPosition.set( info.pos );
mScale = info.scale;
mDataBlock = info.dataBlock;
dMemset( &mParticles[0], 0, sizeof( VerletParticle ) );
dMemset( &mParticles[1], 0, sizeof( VerletParticle ) );
}
ForestWindAccumulator::~ForestWindAccumulator()
{
}
void ForestWindAccumulator::presimulate( const VectorF &windVector, U32 ticks )
{
PROFILE_SCOPE( ForestWindAccumulator_Presimulate );
for ( U32 i = 0; i < ticks; i++ )
updateWind( windVector, TickSec );
}
void ForestWindAccumulator::updateWind( const VectorF &windForce, F32 timeDelta )
{
PROFILE_SCOPE( ForestWindAccumulator_UpdateWind );
// Update values from datablock... this way we can
// change settings live and see instant results.
const F32 tightnessCoefficient = mDataBlock->mTightnessCoefficient;
const F32 dampingCoefficient = mDataBlock->mDampingCoefficient;
const F32 mass = mDataBlock->mMass * mScale;
const F32 rigidity = mDataBlock->mRigidity * mScale;
// This will be the accumulated
// target strength for flutter.
//F32 targetStrength = windForce.len();
// This will be the accumulated
// target displacement vector.
Point2F target( windForce.x, windForce.y );
// This particle is the spring target.
// It has a mass of 0, which we count as
// an infinite mass.
mParticles[0].position = target;
Point2F relVel = target * timeDelta;
Point2F diff( 0, 0 );
Point2F springForce( 0, 0 );
// Spring length is the target
// particle's position minus the
// current displacement/direction vector.
diff = mParticles[0].position - mCurrentDir;
// F = diff * tightness - v * -damping
diff *= tightnessCoefficient;
springForce = diff - ( (mParticles[1].position - mParticles[1].lastPosition) * -dampingCoefficient );
Point2F accel( 0, 0 );
accel = springForce * (rigidity * 0.001f) / (mass * 0.001f);
_updateParticle( &mParticles[1], accel, timeDelta );
mCurrentDir *= 0.989f;
mCurrentDir += mParticles[1].position;
mCurrentStrength += windForce.len() * timeDelta;
mCurrentStrength *= 0.98f;
}
void ForestWindAccumulator::_updateParticle( VerletParticle *particle, const Point2F &accel, F32 timeDelta )
{
// Verlet integration:
// x' = 2x - x* + a * dt^2
// x' is the new position.
// x is the current position.
// x* is the last position.
// a is the acceleration for this frame.
// dt is the delta time.
particle->position = ((particle->position * 2.0f) - particle->lastPosition) + accel * (timeDelta * timeDelta);
particle->lastPosition = particle->position;
}
void ForestWindAccumulator::applyImpulse( const VectorF &impulse )
{
// First build the current force.
VectorF force( mCurrentDir.x, mCurrentDir.y, 0 );
// Add in our mass corrected force.
const F32 mass = mDataBlock->mMass * mScale;
force += impulse / mass;
// Set the new direction and force.
mCurrentDir.set( force.x, force.y );
mCurrentStrength += impulse.len() * TickSec;
}

View file

@ -0,0 +1,79 @@
//-----------------------------------------------------------------------------
// 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 _FORESTWINDACCUMULATOR_H_
#define _FORESTWINDACCUMULATOR_H_
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
struct TreePlacementInfo;
class ForestItemData;
/// This simple class holds the state of the accumulated
/// wind effect for a single tree.
class ForestWindAccumulator
{
protected:
struct VerletParticle
{
Point2F position;
Point2F lastPosition;
};
F32 mCurrentStrength;
Point2F mCurrentDir;
Point3F mPosition;
F32 mScale;
ForestItemData *mDataBlock;
VerletParticle mParticles[2];
void _updateParticle( VerletParticle *particle, const Point2F &force, F32 timeDelta );
public:
ForestWindAccumulator( const TreePlacementInfo &info );
~ForestWindAccumulator();
void presimulate( const VectorF &windVector, U32 ticks );
void updateWind( const VectorF &windVector, F32 timeDelta );
void setDirection( const VectorF &dir ) { mCurrentDir.set( dir.x, dir.y ); }
VectorF getDirection() const { return VectorF( mCurrentDir.x, mCurrentDir.y, 0 ); }
void setStrength( F32 strength ) { mCurrentStrength = strength; }
F32 getStrength() const { return mCurrentStrength; }
void setPosition( const Point3F &pos ) { mPosition = pos; }
Point3F getPosition() const { return mPosition; }
void applyImpulse( const VectorF &impulse );
};
#endif // _FORESTWINDACCUMULATOR_H_

View file

@ -0,0 +1,578 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/forestWindEmitter.h"
#include "forest/forestWindMgr.h"
#include "console/consoleInternal.h"
#include "core/stream/bitStream.h"
#include "core/util/safeDelete.h"
#include "platform/profiler.h"
#include "math/mathIO.h"
#include "math/mRandom.h"
#include "scene/sceneManager.h"
#include "scene/sceneRenderState.h"
#include "renderInstance/renderPassManager.h"
#include "gfx/gfxDrawUtil.h"
#include "gfx/gfxTransformSaver.h"
#include "sim/netConnection.h"
#include "T3D/gameBase/processList.h"
#include "console/engineAPI.h"
ConsoleDocClass( ForestWindEmitter,
"@brief Object responsible for simulating wind in a level.\n\n"
"When placed in the level, a ForestWindEmitter will cause tree branches to bend and sway, leaves "
"to flutter, and create vertical bending on the tree's trunk.\n\n"
"@tsexample\n"
"// The following is a full declaration of a wind emitter\n"
"new ForestWindEmitter()\n"
"{\n"
" position = \"497.739 765.821 102.395\";\n"
" windEnabled = \"1\";\n"
" radialEmitter = \"1\";\n"
" strength = \"1\";\n"
" radius = \"3\";\n"
" gustStrength = \"0.5\";\n"
" gustFrequency = \"1\";\n"
" gustYawAngle = \"10\";\n"
" gustYawFrequency = \"4\";\n"
" gustWobbleStrength = \"2\";\n"
" turbulenceStrength = \"1\";\n"
" turbulenceFrequency = \"2\";\n"
" hasMount = \"0\";\n"
" scale = \"3 3 3\";\n"
" canSave = \"1\";\n"
" canSaveDynamicFields = \"1\";\n"
" rotation = \"1 0 0 0\";\n"
"};\n"
"@endtsexample\n\n"
"@ingroup FX\n"
"@ingroup Forest\n"
"@ingroup Atmosphere\n"
);
// We need to know when the mission editor is enabled.
extern bool gEditingMission;
ForestWind::ForestWind( ForestWindEmitter *emitter )
: mStrength( 0.0f ),
mDirection( 1.0f, 0, 0 ),
mLastGustTime( 0 ),
mLastYawTime( 0 ),
mCurrentTarget( 0, 0 ),
mCurrentInterp( 0 ),
mTargetYawAngle( 0 ),
mParent( emitter ),
mIsDirty( false ),
mRandom( Platform::getRealMilliseconds() + 1 )
{
}
ForestWind::~ForestWind()
{
}
void ForestWind::processTick()
{
PROFILE_SCOPE( ForestWind_ProcessTick );
const F32 deltaTime = 0.032f;
const U32 simTime = Sim::getCurrentTime();
Point2F finalVec( 0, 0 );
Point2F windDir( mParent->mWindDirection.x, mParent->mWindDirection.y );
if ( mLastGustTime < simTime )
{
Point2F turbVec( 0, 0 );
if ( mLastGustTime < simTime + (mParent->mWindTurbulenceFrequency * 1000.0f) )
turbVec = (mRandom.randF() * mParent->mWindTurbulenceStrength) * windDir;
mLastGustTime = simTime + (mParent->mWindGustFrequency * 1000.0f);
Point2F gustVec = (mRandom.randF() * mParent->mWindGustStrength + mRandom.randF() * mParent->mWindGustWobbleStrength) * windDir;
finalVec += gustVec + turbVec;
//finalVec.normalizeSafe();
}
//bool rotationChange = false;
if ( mLastYawTime < simTime )
{
mLastYawTime = simTime + (mParent->mWindGustYawFrequency * 1000.0f);
F32 rotateAmt = mRandom.randF() * mParent->mWindGustYawAngle + mRandom.randF() * mParent->mWindGustWobbleStrength;
if ( mRandom.randF() <= 0.5f )
rotateAmt = -rotateAmt;
rotateAmt = mDegToRad( rotateAmt );
if ( rotateAmt > M_2PI_F )
rotateAmt -= M_2PI_F;
else if ( rotateAmt < -M_2PI_F )
rotateAmt += M_2PI_F;
mTargetYawAngle = rotateAmt;
//finalVec.rotate( rotateAmt );
mCurrentTarget.rotate( rotateAmt );
}
//mCurrentTarget.normalizeSafe();
if ( mCurrentTarget.isZero() || mCurrentInterp >= 1.0f )
{
mCurrentInterp = 0;
mCurrentTarget.set( 0, 0 );
Point2F windDir( mDirection.x, mDirection.y );
windDir.normalizeSafe();
mCurrentTarget = finalVec + windDir;
}
else
{
mCurrentInterp += deltaTime;
mCurrentInterp = mClampF( mCurrentInterp, 0.0f, 1.0f );
mDirection.interpolate( mDirection, Point3F( mCurrentTarget.x, mCurrentTarget.y, 0 ), mCurrentInterp );
//F32 rotateAmt = mLerp( 0, mTargetYawAngle, mCurrentInterp );
//mTargetYawAngle -= rotateAmt;
//Point2F dir( mDirection.x, mDirection.y );
//if ( mTargetYawAngle > 0.0f )
// dir.rotate( rotateAmt );
//mDirection.set( dir.x, dir.y, 0 );
}
}
void ForestWind::setStrengthAndDirection( F32 strength, const VectorF &direction )
{
if ( mStrength != strength ||
mDirection != direction )
{
mStrength = strength;
mDirection = direction;
mIsDirty = true;
}
}
void ForestWind::setStrength( F32 strength )
{
if ( mStrength != strength )
{
mStrength = strength;
mIsDirty = true;
}
}
void ForestWind::setDirection( const VectorF &direction )
{
if ( mDirection != direction )
{
mDirection = direction;
mIsDirty = true;
}
}
IMPLEMENT_CO_NETOBJECT_V1(ForestWindEmitter);
ForestWindEmitter::ForestWindEmitter( bool makeClientObject )
: mEnabled( true ),
mAddedToScene( false ),
mWind( NULL ),
mWindStrength( 1 ),
mWindDirection( 1, 0, 0 ),
mWindGustFrequency( 3.0f ),
mWindGustStrength( 0.25f ),
mWindGustYawAngle( 10.0f ),
mWindGustYawFrequency( 4.0f ),
mWindGustWobbleStrength( 2.0f ),
mWindTurbulenceFrequency( 2.0f ),
mWindTurbulenceStrength( 0.25f ),
mWindRadius( 0 ),
mRadialEmitter( false ),
mHasMount( false ),
mIsMounted( false ),
mMountObject( NULL )
{
mTypeMask |= StaticObjectType | EnvironmentObjectType;
if ( makeClientObject )
mNetFlags.set( IsGhost );
else
mNetFlags.set( Ghostable | ScopeAlways );
}
ForestWindEmitter::~ForestWindEmitter()
{
}
void ForestWindEmitter::initPersistFields()
{
// Initialise parents' persistent fields.
Parent::initPersistFields();
addGroup( "ForestWind" );
addField( "windEnabled", TypeBool, Offset( mEnabled, ForestWindEmitter ), "Determines if the emitter will be counted in wind calculations." );
addField( "radialEmitter", TypeBool, Offset( mRadialEmitter, ForestWindEmitter ), "Determines if the emitter is a global direction or local radial emitter." );
addField( "strength", TypeF32, Offset( mWindStrength, ForestWindEmitter ), "The strength of the wind force." );
addField( "radius", TypeF32, Offset( mWindRadius, ForestWindEmitter ), "The radius of the emitter for local radial emitters." );
addField( "gustStrength", TypeF32, Offset( mWindGustStrength, ForestWindEmitter ), "The maximum strength of a gust." );
addField( "gustFrequency", TypeF32, Offset( mWindGustFrequency, ForestWindEmitter ), "The frequency of gusting in seconds." );
addField( "gustYawAngle", TypeF32, Offset( mWindGustYawAngle, ForestWindEmitter ), "The amount of degrees the wind direction can drift (both positive and negative)." );
addField( "gustYawFrequency", TypeF32, Offset( mWindGustYawFrequency, ForestWindEmitter ), "The frequency of wind yaw drift, in seconds." );
addField( "gustWobbleStrength", TypeF32, Offset( mWindGustWobbleStrength, ForestWindEmitter ), "The amount of random wobble added to gust and turbulence vectors." );
addField( "turbulenceStrength", TypeF32, Offset( mWindTurbulenceStrength, ForestWindEmitter ), "The strength of gust turbulence." );
addField( "turbulenceFrequency", TypeF32, Offset( mWindTurbulenceFrequency, ForestWindEmitter ), "The frequency of gust turbulence, in seconds." );
addField( "hasMount", TypeBool, Offset( mHasMount, ForestWindEmitter ), "Determines if the emitter is mounted to another object." );
endGroup( "ForestWind" );
}
U32 ForestWindEmitter::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
{
U32 retMask = Parent::packUpdate( con, mask, stream );
mathWrite( *stream, mObjToWorld );
if ( stream->writeFlag( mask & EnabledMask ) )
stream->writeFlag( mEnabled );
if ( stream->writeFlag( mask & WindMask ) )
{
stream->write( mWindStrength );
stream->write( mWindRadius );
stream->writeFlag( mRadialEmitter );
stream->write( mWindGustStrength );
stream->write( mWindGustFrequency );
stream->write( mWindGustYawAngle );
stream->write( mWindGustYawFrequency );
stream->write( mWindGustWobbleStrength );
stream->write( mWindTurbulenceStrength );
stream->write( mWindTurbulenceFrequency );
// The wind direction should be normalized!
if ( mWindDirection.isZero() )
{
VectorF forwardVec( 0, 0, 0 );
mWorldToObj.getColumn( 1, &mWindDirection );
}
else
mWindDirection.normalize();
stream->writeNormalVector( mWindDirection, 8 );
stream->writeFlag( mHasMount );
}
return retMask;
}
void ForestWindEmitter::unpackUpdate(NetConnection * con, BitStream * stream)
{
// Unpack Parent.
Parent::unpackUpdate( con, stream );
MatrixF xfm;
mathRead( *stream, &xfm );
Parent::setTransform( xfm );
U32 windMask = 0;
if ( stream->readFlag() ) // EnabledMask
mEnabled = stream->readFlag();
if ( stream->readFlag() ) // WindMask
{
stream->read( &mWindStrength );
stream->read( &mWindRadius );
mRadialEmitter = stream->readFlag();
stream->read( &mWindGustStrength );
stream->read( &mWindGustFrequency );
stream->read( &mWindGustYawAngle );
stream->read( &mWindGustYawFrequency );
stream->read( &mWindGustWobbleStrength );
stream->read( &mWindTurbulenceStrength );
stream->read( &mWindTurbulenceFrequency );
stream->readNormalVector( &mWindDirection, 8 );
windMask |= WindMask;
mHasMount = stream->readFlag();
}
// This does nothing if the masks are not set!
if ( windMask != 0 && isProperlyAdded() )
{
Point3F boxRad( 0, 0, 0 );
if ( !isRadialEmitter() )
boxRad.set( 10000.0f, 10000.0f, 10000.0f );
else
boxRad.set( mWindRadius, mWindRadius, mWindRadius );
mObjBox.set( -boxRad, boxRad );
resetWorldBox();
_initWind( windMask );
}
}
bool ForestWindEmitter::onAdd()
{
if ( !Parent::onAdd() )
return false;
// Only the client side actually does wind.
if ( isClientObject() )
{
// TODO: wasn't this a big hack we already fixed better?
//Projectile::getGhostReceivedSignal().notify( this, &ForestWindEmitter::_onMountObjectGhostReceived );
_initWind();
WINDMGR->addEmitter( this );
}
Point3F boxRad( 0, 0, 0 );
if ( !isRadialEmitter() )
boxRad.set( 10000.0f, 10000.0f, 10000.0f );
else
boxRad.set( mWindRadius, mWindRadius, mWindRadius );
mObjBox.set( -boxRad, boxRad );
resetWorldBox();
enableCollision();
// If we are we editing the mission then
// be sure to add us to the scene.
if ( gEditingMission || mHasMount )
{
addToScene();
mAddedToScene = true;
}
return true;
}
void ForestWindEmitter::onRemove()
{
// Only the client side actually does wind.
if ( isClientObject() )
{
//Projectile::getGhostReceivedSignal().remove( this, &ForestWindEmitter::_onMountObjectGhostReceived );
WINDMGR->removeEmitter( this );
SAFE_DELETE( mWind );
}
// If we are editing the mission then remove
// us from the scene graph.
if ( gEditingMission || mHasMount )
{
removeFromScene();
mAddedToScene = false;
}
// Do Parent.
Parent::onRemove();
}
void ForestWindEmitter::_onMountObjectGhostReceived( SceneObject *object )
{
if ( !object )
return;
attachToObject( object );
}
void ForestWindEmitter::inspectPostApply()
{
// Force the client update!
setMaskBits(0xffffffff);
}
void ForestWindEmitter::onEditorEnable()
{
if ( !mAddedToScene )
{
addToScene();
mAddedToScene = true;
}
}
void ForestWindEmitter::onEditorDisable()
{
// Remove us from the scene.
if ( mAddedToScene )
{
removeFromScene();
mAddedToScene = false;
}
}
void ForestWindEmitter::_initWind( U32 mask )
{
AssertFatal( !isServerObject(), "SpeedWind is never updated on the server!" );
// If we don't have a wind
// object create one now.
if ( !mWind )
mWind = new ForestWind( this );
// Do we need to apply a new direction and strength?
if ( mask & WindMask )
{
mWorldToObj.getColumn( 1, &mWindDirection );
mWind->setStrengthAndDirection( mWindStrength, mWindDirection );
}
}
void ForestWindEmitter::setTransform( const MatrixF &mat )
{
Parent::setTransform( mat );
// Force the client update!
setMaskBits(0xffffffff);
if ( isClientObject() )
_initWind();
}
void ForestWindEmitter::prepRenderImage( SceneRenderState* state )
{
PROFILE_SCOPE( ForestWindEmitter_PrepRenderImage );
// Only render the radius and
// direction if we're in the editor.
// Don't render them if this is a reflect pass.
if ( !state->isDiffusePass() || !gEditingMission )
return;
// This should be sufficient for most objects that don't manage zones, and
// don't need to return a specialized RenderImage...
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
ri->renderDelegate.bind( this, &ForestWindEmitter::_renderEmitterInfo );
ri->type = RenderPassManager::RIT_Editor;
state->getRenderPass()->addInst( ri );
}
void ForestWindEmitter::_renderEmitterInfo( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat )
{
if ( overrideMat )
return;
GFXTransformSaver saver;
GFXDrawUtil *drawer = GFX->getDrawUtil();
AssertFatal( drawer, "Got NULL GFXDrawUtil!" );
const Point3F &pos = getPosition();
const VectorF &windVec = mWind->getDirection();
GFXStateBlockDesc desc;
desc.setBlend( true );
desc.setZReadWrite( true, false );
// Draw an arrow pointing
// in the wind direction.
drawer->drawArrow( desc, pos, pos + (windVec * mWindStrength), ColorI( 0, 0, 255, 255 ) );//Point3F( -235.214, 219.589, 34.0991 ), Point3F( -218.814, 244.731, 37.5587 ), ColorI( 255, 255, 0, 255 ) );//
drawer->drawArrow( desc, pos, pos + (mWind->getTarget() * mWindStrength ), ColorI( 255, 0, 0, 85 ) );
// Draw a 2D circle for the wind radius.
if ( isRadialEmitter() )
drawer->drawSphere( desc, mWindRadius, pos, ColorI( 255, 0, 0, 80 ) );
}
F32 ForestWindEmitter::getStrength() const
{
return mWind->getStrength();
}
void ForestWindEmitter::setStrength( F32 strength )
{
mWindStrength = strength;
mWind->setStrength( mWindStrength );
}
void ForestWindEmitter::attachToObject( SceneObject *obj )
{
if ( !obj )
return;
mMountObject = obj;
mIsMounted = true;
if ( isServerObject() )
deleteNotify( mMountObject );
}
void ForestWindEmitter::updateMountPosition()
{
AssertFatal( isClientObject(), "ForestWindEmitter::updateMountPosition - This should only happen on the client!" );
if ( !mHasMount || !mMountObject )
return;
MatrixF mat( true );
mat.setPosition( mMountObject->getPosition() );
Parent::setTransform( mat );
}
void ForestWindEmitter::onDeleteNotify(SimObject *object)
{
AssertFatal( isServerObject(), "ForestWindEmitter::onDeleteNotify - This should never happen on the client!" );
safeDeleteObject();
}
DefineEngineMethod( ForestWindEmitter, attachToObject, void, ( U32 objectID ),,
"@brief Mounts the wind emitter to another scene object\n\n"
"@param objectID Unique ID of the object wind emitter should attach to"
"@tsexample\n"
"// Wind emitter previously created and named %windEmitter\n"
"// Going to attach it to the player, making him a walking wind storm\n"
"%windEmitter.attachToObject(%player);\n"
"@endtsexample\n\n")
{
SceneObject *obj = dynamic_cast<SceneObject*>( Sim::findObject( objectID ) );
if ( !obj )
Con::warnf( "ForestWindEmitter::attachToObject - failed to find object with ID: %d", objectID );
object->attachToObject( obj );
}

View file

@ -0,0 +1,219 @@
//-----------------------------------------------------------------------------
// 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 _FORESTWINDEMITTER_H_
#define _FORESTWINDEMITTER_H_
#ifndef _SCENEOBJECT_H_
#include "scene/sceneObject.h"
#endif
#ifndef _MMATRIX_H_
#include "math/mMatrix.h"
#endif
#ifndef _MPOINT3_H_
#include "math/mPoint3.h"
#endif
#ifndef _MSPHERE_H_
#include "math/mSphere.h"
#endif
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
class ForestWindEmitter;
class ForestWindAccumulator;
class BaseMatInstance;
class ForestWind
{
protected:
F32 mStrength;
VectorF mDirection;
F32 mLastGustTime;
F32 mLastYawTime;
F32 mTargetYawAngle;
F32 mCurrentInterp;
Point2F mCurrentTarget;
ForestWindEmitter *mParent;
MRandom mRandom;
bool mIsDirty;
public:
ForestWind( ForestWindEmitter *emitter );
virtual ~ForestWind();
void processTick();
void setStrengthAndDirection( F32 strength, const VectorF &direction );
void setStrength( F32 strength );
void setDirection( const VectorF &direction );
void setIsDirty( bool isDirty ) { mIsDirty = isDirty; }
F32 getStrength() const { return mStrength; }
const VectorF& getDirection() const { return mDirection; }
VectorF getTarget() const { return VectorF( mCurrentTarget.x, mCurrentTarget.y, 0 ); }
};
/// A vector of WindEmitter pointers.
typedef Vector<ForestWindEmitter*> ForestWindEmitterList;
class ForestWindEmitter : public SceneObject
{
typedef SceneObject Parent;
friend class ForestWind;
friend class ForestWindAccumulator;
protected:
enum
{
EnabledMask = Parent::NextFreeMask << 0,
WindMask = Parent::NextFreeMask << 1,
NextFreeMask = Parent::NextFreeMask << 2
};
bool mAddedToScene;
bool mEnabled;
ForestWind *mWind;
F32 mWindStrength;
VectorF mWindDirection;
/// Controls how often the wind gust peaks per second.
F32 mWindGustFrequency;
/// The maximum distance in meters that the peak wind
/// gust will displace an element.
F32 mWindGustStrength;
/// The range that the wind direction
/// will yaw between (-val to +val).
F32 mWindGustYawAngle;
/// The frequency, in seconds, between
/// animations in the gust yaw angle.
F32 mWindGustYawFrequency;
/// The maximum amount of random wobble
/// that will be applied to the gusting
/// as well as the yaw rotation.
F32 mWindGustWobbleStrength;
/// Controls the overally rapidity of the wind turbulence.
F32 mWindTurbulenceFrequency;
/// The maximum distance in meters that the turbulence can
/// displace a ground cover element.
F32 mWindTurbulenceStrength;
/// If the radius is greater than zero then this is
/// a localized radial wind emitter.
F32 mWindRadius;
/// Explicitly denotes whether this is a
/// global directional wind emitter or a
/// localized radial wind emitter.
bool mRadialEmitter;
bool mHasMount;
bool mIsMounted;
/// An object that the emitter can
/// get position updates from.
SimObjectPtr<SceneObject> mMountObject;
static ForestWindEmitterList smEmitters;
void _initWind( U32 mask = 0xFFFFFFFF );
void _onMountObjectGhostReceived( SceneObject *object );
public:
ForestWindEmitter( bool makeClientObject = false );
virtual ~ForestWindEmitter();
bool isEnabled() const { return mEnabled; }
ForestWind* getWind() { return mWind; }
bool isLocalWind() const { return mWindRadius > 0.0f; }
bool isRadialEmitter() const { return mRadialEmitter; }
F32 getWindRadius() const { return mWindRadius; }
F32 getWindRadiusSquared() const { return mWindRadius * mWindRadius; }
void setWindRadius( F32 radius ) { mWindRadius = radius; }
F32 getStrength() const;
void setStrength( F32 strength );
void _renderEmitterInfo( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );
void attachToObject( SceneObject *obj );
void updateMountPosition();
// SceneObject
virtual void setTransform( const MatrixF &mat );
void prepRenderImage( SceneRenderState *state );
// SimObject
bool onAdd();
void onRemove();
void inspectPostApply();
void onEditorEnable();
void onEditorDisable();
void onDeleteNotify(SimObject *object);
// NetObject
U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream );
void unpackUpdate( NetConnection *conn, BitStream *stream );
// ConObject.
static void initPersistFields();
DECLARE_CONOBJECT(ForestWindEmitter);
};
#endif // _FORESTWINDEMITTER_H_

View file

@ -0,0 +1,316 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/forestWindMgr.h"
#include "platform/profiler.h"
#include "core/tAlgorithm.h"
#include "core/module.h"
#include "console/consoleTypes.h"
#include "math/mathUtils.h"
#include "T3D/gameBase/gameConnection.h"
#include "forest/forest.h"
#include "forest/forestWindAccumulator.h"
#include "T3D/fx/particleEmitter.h"
MODULE_BEGIN( ForestWindMgr )
MODULE_INIT
{
ManagedSingleton< ForestWindMgr >::createSingleton();
ForestWindMgr::initConsole();
}
MODULE_SHUTDOWN
{
ManagedSingleton< ForestWindMgr >::deleteSingleton();
}
MODULE_END;
ForestWindMgr::WindAdvanceSignal ForestWindMgr::smAdvanceSignal;
F32 ForestWindMgr::smWindEffectRadius = 25.0f;
ForestWindMgr::ForestWindMgr()
{
setProcessTicks( true );
mSources = new IdToWindMap();
mPrevSources = new IdToWindMap();
}
ForestWindMgr::~ForestWindMgr()
{
IdToWindMap::Iterator sourceIter = mSources->begin();
for( ; sourceIter != mSources->end(); sourceIter++ )
delete (*sourceIter).value;
delete mSources;
delete mPrevSources;
}
void ForestWindMgr::initConsole()
{
Con::addVariable( "$pref::windEffectRadius", TypeF32, &smWindEffectRadius, "Radius to affect the wind.\n"
"@ingroup Rendering\n");
}
void ForestWindMgr::addEmitter( ForestWindEmitter *emitter )
{
mEmitters.push_back( emitter );
}
void ForestWindMgr::removeEmitter( ForestWindEmitter *emitter )
{
ForestWindEmitterList::iterator iter = find( mEmitters.begin(),
mEmitters.end(),
emitter );
AssertFatal( iter != mEmitters.end(),
"SpeedTreeWindMgr::removeEmitter() - Bad emitter!" );
mEmitters.erase( iter );
}
void ForestWindMgr::processTick()
{
const F32 timeDelta = 0.032f;
if ( mEmitters.empty() )
return;
PROFILE_SCOPE(ForestWindMgr_AdvanceTime);
// Advance all ForestWinds.
{
PROFILE_SCOPE(ForestWindMgr_AdvanceTime_ForestWind_ProcessTick);
ForestWindEmitterList::iterator iter = mEmitters.begin();
for ( ; iter != mEmitters.end(); iter++ )
{
if ( (*iter)->getWind() &&
(*iter)->isEnabled() )
{
(*iter)->updateMountPosition();
ForestWind *wind = (*iter)->getWind();
if ( wind )
wind->processTick();
}
}
}
// Assign the new global wind value used by the particle system.
{
ForestWindEmitter *pWindEmitter = getGlobalWind();
if ( pWindEmitter == NULL )
ParticleEmitter::setWindVelocity( Point3F::Zero );
else
{
ForestWind *pWind = pWindEmitter->getWind();
ParticleEmitter::setWindVelocity( pWind->getDirection() * pWind->getStrength() );
}
}
// Get the game connection and camera object
// in order to retrieve the camera position.
GameConnection *conn = GameConnection::getConnectionToServer();
if ( !conn )
return;
GameBase *cam = conn->getCameraObject();
if ( !cam )
return;
const Point3F &camPos = cam->getPosition();
// Gather TreePlacementInfo for trees near the camera.
{
PROFILE_SCOPE( ForestWindMgr_AdvanceTime_GatherTreePlacementInfo );
smAdvanceSignal.trigger( camPos, smWindEffectRadius, &mPlacementInfo );
}
// Prepare to build a new local source map.
{
PROFILE_SCOPE( ForestWindMgr_AdvanceTime_SwapSources );
AssertFatal( mPrevSources->isEmpty(), "prev sources not empty!" );
swap( mSources, mPrevSources );
AssertFatal( mSources->isEmpty(), "swap failed!" );
}
// Update wind for each TreePlacementInfo
{
PROFILE_SCOPE( ForestWindMgr_AdvanceTime_UpdateWind );
for( S32 i = 0; i < mPlacementInfo.size(); i++ )
{
const TreePlacementInfo &info = mPlacementInfo[i];
updateWind( camPos, info, timeDelta );
}
mPlacementInfo.clear();
}
// Clean up any accumulators in the
// previous local source map.
{
PROFILE_SCOPE( ForestWindMgr_AdvanceTime_Cleanup );
IdToWindMap::Iterator sourceIter = mPrevSources->begin();
for( ; sourceIter != mPrevSources->end(); sourceIter++ )
{
ForestWindAccumulator *accum = (*sourceIter).value;
AssertFatal( accum, "Got null accumulator!" );
delete accum;
}
mPrevSources->clear();
}
}
ForestWindAccumulator* ForestWindMgr::getLocalWind( ForestItemKey key )
{
PROFILE_SCOPE( ForestWindMgr_getLocalWind );
ForestWindAccumulator *accumulator = NULL;
IdToWindMap::Iterator iter = mSources->find( key );
if ( iter != mSources->end() )
accumulator = iter->value;
if ( accumulator )
return accumulator;
else
{
/*
ForestWindEmitterList::iterator iter = mEmitters.begin();
for ( ; iter != mEmitters.end(); iter++ )
{
if ( (*iter)->getWind() &&
(*iter)->isEnabled() &&
!(*iter)->isLocalWind() )
{
return (*iter)->getWind();
}
}
*/
return NULL;
}
}
ForestWindEmitter* ForestWindMgr::getGlobalWind()
{
ForestWindEmitterList::iterator itr = mEmitters.begin();
for ( ; itr != mEmitters.end(); itr++ )
{
if ( !(*itr)->isRadialEmitter() )
return *itr;
}
return NULL;
}
void ForestWindMgr::updateWind( const Point3F &camPos,
const TreePlacementInfo &info,
F32 timeDelta )
{
PROFILE_SCOPE(ForestWindMgr_updateWind);
// See if we have the blended source available.
ForestWindAccumulator *blendDest = NULL;
{
IdToWindMap::Iterator iter = mPrevSources->find( info.itemKey );
if ( iter != mPrevSources->end() )
{
blendDest = iter->value;
mPrevSources->erase( iter );
}
}
// Get some stuff we'll need for finding the emitters.
F32 treeHeight = info.scale * info.dataBlock->getObjBox().len_z();
Point3F top = info.pos;
top.z += treeHeight;
if ( blendDest )
top += ( 1.0f / info.scale ) * blendDest->getDirection();
// Go thru the emitters to accumulate the total wind force.
VectorF windForce( 0, 0, 0 );
F32 time = Sim::getCurrentTime() / 1000.0f;
ForestWindEmitterList::iterator iter = mEmitters.begin();
for ( ; iter != mEmitters.end(); iter++ )
{
ForestWindEmitter *emitter = (*iter);
// If disabled or no wind object... skip it.
if ( !emitter->isEnabled() || !emitter->getWind() )
continue;
ForestWind *wind = emitter->getWind();
F32 strength = wind->getStrength();
if ( emitter->isRadialEmitter() )
{
Point3F closest = MathUtils::mClosestPointOnSegment( info.pos, top, emitter->getPosition() );
Point3F dir = closest - emitter->getPosition();
F32 lenSquared = dir.lenSquared();
if ( lenSquared > emitter->getWindRadiusSquared() )
continue;
dir *= 1.0f / mSqrt( lenSquared );
F32 att = lenSquared / emitter->getWindRadiusSquared();
strength *= 1.0f - att;
windForce += dir * strength;
}
else
{
F32 d = mDot( info.pos, Point3F::One ); //PlaneF( Point3F::Zero, wind->getDirection() ).distToPlane( Point3F( info.pos.x, info.pos.y, 0 ) );
//F32 d = PlaneF( Point3F::Zero, wind->getDirection() ).distToPlane( Point3F( info.pos.x, info.pos.y, 0 ) );
F32 scale = 1.0f + ( mSin( d + ( time / 10.0 ) ) * 0.5f );
windForce += wind->getDirection() * strength * scale;
}
}
// If we need a accumulator then we also need to presimulate.
if ( !blendDest )
{
blendDest = new ForestWindAccumulator( info );
blendDest->presimulate( windForce, 4.0f / TickSec );
}
else
blendDest->updateWind( windForce, timeDelta );
mSources->insertUnique( info.itemKey, blendDest );
}

View file

@ -0,0 +1,101 @@
//-----------------------------------------------------------------------------
// 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 _FORESTWINDMGR_H_
#define _FORESTWINDMGR_H_
#ifndef _FORESTWINDEMITTER_H_
#include "forest/forestWindEmitter.h"
#endif
#ifndef _TDICTIONARY_H_
#include "core/util/tDictionary.h"
#endif
#ifndef _ITICKABLE_H_
#include "core/iTickable.h"
#endif
#ifndef _FORESTITEM_H_
#include "forest/forestItem.h"
#endif
#ifndef _TSINGLETON_H_
#include "core/util/tSingleton.h"
#endif
struct TreePlacementInfo
{
F32 scale;
Point3F pos;
ForestItemKey itemKey;
ForestItemData *dataBlock;
};
class ForestWindMgr : public virtual ITickable
{
protected:
ForestWindEmitterList mEmitters;
typedef HashTable<U32,ForestWindAccumulator*> IdToWindMap;
typedef Signal<void( const Point3F &camPos, const F32 radius, Vector<TreePlacementInfo> *placementInfo )> WindAdvanceSignal;
IdToWindMap *mSources;
IdToWindMap *mPrevSources;
Vector<TreePlacementInfo> mPlacementInfo;
static WindAdvanceSignal smAdvanceSignal;
virtual void interpolateTick( F32 delta ) {};
virtual void processTick();
virtual void advanceTime( F32 timeDelta ) {};
public:
ForestWindMgr();
virtual ~ForestWindMgr();
void addEmitter( ForestWindEmitter *emitter );
void removeEmitter( ForestWindEmitter *emitter );
void updateWind( const Point3F &camPos,
const TreePlacementInfo &info,
F32 timeDelta );
ForestWindAccumulator* getLocalWind( ForestItemKey key );
// Returns the first non-radial emitter in the list.
ForestWindEmitter* getGlobalWind();
static WindAdvanceSignal& getAdvanceSignal() { return smAdvanceSignal; }
static F32 smWindEffectRadius;
static void initConsole();
// For ManagedSingleton.
static const char* getSingletonName() { return "ForestWindMgr"; }
};
/// Returns the ReflectionManager singleton.
#define WINDMGR ManagedSingleton<ForestWindMgr>::instance()
#endif // _FORESTWINDMGR_H_

View file

@ -0,0 +1,210 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/glsl/windDeformationGLSL.h"
#include "forest/windDeformation.h"
#include "forest/forestItem.h"
#include "forest/forestWindMgr.h"
#include "materials/sceneData.h"
#include "scene/sceneRenderState.h"
#include "shaderGen/shaderGen.h"
#include "shaderGen/featureMgr.h"
#include "shaderGen/langElement.h"
#include "shaderGen/shaderOp.h"
#include "shaderGen/shaderGenVars.h"
#include "gfx/gfxStructs.h"
#include "core/module.h"
static void _onRegisterFeatures( GFXAdapterType type )
{
FEATUREMGR->registerFeature( MFT_WindEffect, new WindDeformationGLSL );
}
MODULE_BEGIN( WindDeformationGLSL )
MODULE_INIT_AFTER( ShaderGen )
MODULE_INIT
{
SHADERGEN->getFeatureInitSignal().notify( _onRegisterFeatures );
}
MODULE_END;
WindDeformationGLSL::WindDeformationGLSL()
: mDep( "shaders/common/gl/wind.glsl" )
{
addDependency( &mDep );
}
void WindDeformationGLSL::determineFeature( Material *material,
const GFXVertexFormat *vertexFormat,
U32 stageNum,
const FeatureType &type,
const FeatureSet &features,
MaterialFeatureData *outFeatureData )
{
bool enabled = vertexFormat->hasColor() && features.hasFeature( MFT_WindEffect );
outFeatureData->features.setFeature( type, enabled );
}
void WindDeformationGLSL::processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
MultiLine *meta = new MultiLine;
output = meta;
// We combined all the tree parameters into one float4 to
// save constant space and reduce the memory copied to the
// card.
//
// This in particular helps when we're instancing.
//
// .x = bend scale
// .y = branch amplitude
// .z = detail amplitude
// .w = detail frequency
//
Var *windParams;
if ( fd.features[MFT_UseInstancing] )
{
ShaderConnector *vertStruct = dynamic_cast<ShaderConnector *>( componentList[C_VERT_STRUCT] );
windParams = vertStruct->getElement( RT_TEXCOORD );
windParams->setName( "inst_windParams" );
windParams->setType( "vec4" );
mInstancingFormat->addElement( "windParams", GFXDeclType_Float4, windParams->constNum );
}
else
{
windParams = new Var( "windParams", "vec4" );
windParams->uniform = true;
windParams->constSortPos = cspPotentialPrimitive;
}
// If we're instancing then we need to instance the wind direction
// and speed as its unique for each tree instance.
Var *windDirAndSpeed;
if ( fd.features[MFT_UseInstancing] )
{
ShaderConnector *vertStruct = dynamic_cast<ShaderConnector *>( componentList[C_VERT_STRUCT] );
windDirAndSpeed = vertStruct->getElement( RT_TEXCOORD );
windDirAndSpeed->setName( "inst_windDirAndSpeed" );
windDirAndSpeed->setType( "vec3" );
mInstancingFormat->addElement( "windDirAndSpeed", GFXDeclType_Float3, windDirAndSpeed->constNum );
}
else
{
windDirAndSpeed = new Var( "windDirAndSpeed", "vec3" );
windDirAndSpeed->uniform = true;
windDirAndSpeed->constSortPos = cspPrimitive;
}
Var *accumTime = (Var*)LangElement::find( "accumTime" );
if ( !accumTime )
{
accumTime = new Var( "accumTime", "float" );
accumTime->uniform = true;
accumTime->constSortPos = cspPass;
}
// Get the transform to world space.
Var *objTrans = getObjTrans( componentList, fd.features[MFT_UseInstancing], meta );
// First check for an input position from a previous feature
// then look for the default vertex position.
Var *inPosition = (Var*)LangElement::find( "inPosition" );
if ( !inPosition )
inPosition = (Var*)LangElement::find( "position" );
// Get the incoming color data
Var *inColor = (Var*)LangElement::find( "diffuse" );
// Do the branch and detail bending first so that
// it can work in pure object space of the tree.
LangElement *effect =
new GenOp( "windBranchBending( "
"@.xyz, " // vPos
"normalize( normal ), " // vNormal
"@, " // fTime
"@.z, " // fWindSpeed
"@.g, " // fBranchPhase
"@.y, " // fBranchAmp
"@.r, " // fBranchAtten
"dot( @[3], vec4( 1.0 ) ), " // fDetailPhase
"@.z, " // fDetailAmp
"@.w, " // fDetailFreq
"@.b )", // fEdgeAtten
inPosition, // vPos
// vNormal
accumTime, // fTime
windDirAndSpeed, // fWindSpeed
inColor, // fBranchPhase
windParams, // fBranchAmp
inColor, // fBranchAtten
objTrans, // fDetailPhase
windParams, // fDetailAmp
windParams, // fDetailFreq
inColor ); // fEdgeAtten
Var *outPosition = (Var*)LangElement::find( "inPosition" );
if ( outPosition )
meta->addStatement( new GenOp( " @.xyz = @;\r\n", outPosition, effect, inPosition ) );
else
{
outPosition = new Var;
outPosition->setType( "vec3" );
outPosition->setName( "inPosition" );
meta->addStatement( new GenOp(" vec3 inPosition = @;\r\n", effect, inPosition ) );
}
// Now do the trunk bending.
effect = new GenOp( "windTrunkBending( @, @.xy, @.z * @.x )",
outPosition, windDirAndSpeed, outPosition, windParams );
meta->addStatement( new GenOp(" @ = @;\r\n", outPosition, effect ) );
}
ShaderFeatureConstHandles* WindDeformationGLSL::createConstHandles( GFXShader *shader, SimObject *userObject )
{
WindDeformationConstHandles *handles = new WindDeformationConstHandles();
handles->init( shader );
return handles;
}

View 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 _FOREST_SHADERGEN_WINDDEFORMATIONGLSL_H_
#define _FOREST_SHADERGEN_WINDDEFORMATIONGLSL_H_
#ifndef _SHADERGEN_GLSL_SHADERFEATUREGLSL_H_
#include "shaderGen/GLSL/shaderFeatureGLSL.h"
#endif
#ifndef _FEATURETYPE_H_
#include "materials/materialFeatureTypes.h"
#endif
class GFXShaderConstHandle;
class WindDeformationGLSL : public ShaderFeatureGLSL
{
protected:
ShaderIncludeDependency mDep;
public:
WindDeformationGLSL();
virtual void processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual String getName()
{
return "Wind Effect";
}
virtual void determineFeature( Material *material,
const GFXVertexFormat *vertexFormat,
U32 stageNum,
const FeatureType &type,
const FeatureSet &features,
MaterialFeatureData *outFeatureData );
virtual ShaderFeatureConstHandles* createConstHandles( GFXShader *shader, SimObject *userObject );
};
DeclareFeatureType( MFT_WindEffect );
#endif // _FOREST_SHADERGEN_WINDDEFORMATIONGLSL_H_

View file

@ -0,0 +1,207 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/hlsl/windDeformationHLSL.h"
#include "forest/windDeformation.h"
#include "forest/forestItem.h"
#include "forest/forestWindMgr.h"
#include "materials/sceneData.h"
#include "scene/sceneRenderState.h"
#include "shaderGen/shaderGen.h"
#include "shaderGen/featureMgr.h"
#include "shaderGen/langElement.h"
#include "shaderGen/shaderOp.h"
#include "shaderGen/shaderGenVars.h"
#include "gfx/gfxStructs.h"
#include "core/module.h"
static void _onRegisterFeatures( GFXAdapterType type )
{
if ( type == OpenGL )
return;
FEATUREMGR->registerFeature( MFT_WindEffect, new WindDeformationHLSL );
}
MODULE_BEGIN( WindDeformationHLSL )
MODULE_INIT_AFTER( ShaderGen )
MODULE_INIT
{
SHADERGEN->getFeatureInitSignal().notify( _onRegisterFeatures );
}
MODULE_END;
WindDeformationHLSL::WindDeformationHLSL()
: mDep( "shaders/common/wind.hlsl" )
{
addDependency( &mDep );
}
void WindDeformationHLSL::determineFeature( Material *material,
const GFXVertexFormat *vertexFormat,
U32 stageNum,
const FeatureType &type,
const FeatureSet &features,
MaterialFeatureData *outFeatureData )
{
bool enabled = vertexFormat->hasColor() && features.hasFeature( MFT_WindEffect );
outFeatureData->features.setFeature( type, enabled );
}
void WindDeformationHLSL::processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd )
{
MultiLine *meta = new MultiLine;
output = meta;
// We combined all the tree parameters into one float4 to
// save constant space and reduce the memory copied to the
// card.
//
// .x = bend scale
// .y = branch amplitude
// .z = detail amplitude
// .w = detail frequency
//
Var *windParams = new Var( "windParams", "float4" );
windParams->uniform = true;
windParams->constSortPos = cspPotentialPrimitive;
// If we're instancing then we need to instance the wind direction
// and speed as its unique for each tree instance.
Var *windDirAndSpeed;
if ( fd.features[MFT_UseInstancing] )
{
ShaderConnector *vertStruct = dynamic_cast<ShaderConnector *>( componentList[C_VERT_STRUCT] );
windDirAndSpeed = vertStruct->getElement( RT_TEXCOORD );
windDirAndSpeed->setStructName( "IN" );
windDirAndSpeed->setName( "inst_windDirAndSpeed" );
windDirAndSpeed->setType( "float3" );
mInstancingFormat->addElement( "windDirAndSpeed", GFXDeclType_Float3, windDirAndSpeed->constNum );
}
else
{
windDirAndSpeed = new Var( "windDirAndSpeed", "float3" );
windDirAndSpeed->uniform = true;
windDirAndSpeed->constSortPos = cspPrimitive;
}
Var *accumTime = (Var*)LangElement::find( "accumTime" );
if ( !accumTime )
{
accumTime = new Var( "accumTime", "float" );
accumTime->uniform = true;
accumTime->constSortPos = cspPass;
}
// Get the transform to world space.
Var *objTrans = getObjTrans( componentList, fd.features[MFT_UseInstancing], meta );
// First check for an input position from a previous feature
// then look for the default vertex position.
Var *inPosition = (Var*)LangElement::find( "inPosition" );
if ( !inPosition )
inPosition = (Var*)LangElement::find( "position" );
// Copy the input position to the output first as
// the wind effects are conditional.
Var *outPosition = (Var*)LangElement::find( "inPosition" );
if ( !outPosition )
{
outPosition = new Var;
outPosition->setType( "float3" );
outPosition->setName( "inPosition" );
meta->addStatement( new GenOp(" @ = @.xyz;\r\n", new DecOp( outPosition ), inPosition ) );
}
// Get the incoming color data
Var *inColor = (Var*)LangElement::find( "diffuse" );
// Do a dynamic branch based on wind force.
if ( GFX->getPixelShaderVersion() >= 3.0f )
meta->addStatement( new GenOp(" [branch] if ( any( @ ) ) {\r\n", windDirAndSpeed ) );
// Do the branch and detail bending first so that
// it can work in pure object space of the tree.
LangElement *effect =
new GenOp( "windBranchBending( "
"@, " // vPos
"normalize( IN.normal ), " // vNormal
"@, " // fTime
"@.z, " // fWindSpeed
"@.g, " // fBranchPhase
"@.y, " // fBranchAmp
"@.r, " // fBranchAtten
"dot( @[3], 1 ), " // fDetailPhase
"@.z, " // fDetailAmp
"@.w, " // fDetailFreq
"@.b )", // fEdgeAtten
outPosition, // vPos
// vNormal
accumTime, // fTime
windDirAndSpeed, // fWindSpeed
inColor, // fBranchPhase
windParams, // fBranchAmp
inColor, // fBranchAtten
objTrans, // fDetailPhase
windParams, // fDetailAmp
windParams, // fDetailFreq
inColor ); // fEdgeAtten
meta->addStatement( new GenOp( " @ = @;\r\n", outPosition, effect ) );
// Now do the trunk bending.
meta->addStatement( new GenOp(" @ = windTrunkBending( @, @.xy, @.z * @.x );\r\n",
outPosition, outPosition, windDirAndSpeed, outPosition, windParams ) );
// End the dynamic branch.
if ( GFX->getPixelShaderVersion() >= 3.0f )
meta->addStatement( new GenOp(" } // [branch]\r\n" ) );
}
ShaderFeatureConstHandles* WindDeformationHLSL::createConstHandles( GFXShader *shader, SimObject *userObject )
{
WindDeformationConstHandles *handles = new WindDeformationConstHandles();
handles->init( shader );
return handles;
}

View 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 _FOREST_SHADERGEN_WINDDEFORMATIONHLSL_H_
#define _FOREST_SHADERGEN_WINDDEFORMATIONHLSL_H_
#ifndef _SHADERGEN_HLSL_SHADERFEATUREHLSL_H_
#include "shaderGen/HLSL/shaderFeatureHLSL.h"
#endif
#ifndef _FEATURETYPE_H_
#include "materials/materialFeatureTypes.h"
#endif
class GFXShaderConstHandle;
class WindDeformationHLSL : public ShaderFeatureHLSL
{
protected:
ShaderIncludeDependency mDep;
public:
WindDeformationHLSL();
virtual void processVert( Vector<ShaderComponent*> &componentList,
const MaterialFeatureData &fd );
virtual String getName()
{
return "Wind Effect";
}
virtual void determineFeature( Material *material,
const GFXVertexFormat *vertexFormat,
U32 stageNum,
const FeatureType &type,
const FeatureSet &features,
MaterialFeatureData *outFeatureData );
virtual ShaderFeatureConstHandles* createConstHandles( GFXShader *shader, SimObject *userObject );
};
DeclareFeatureType( MFT_WindEffect );
#endif // _FOREST_SHADERGEN_WINDDEFORMATION_H_

View file

@ -0,0 +1,149 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/ts/tsForestCellBatch.h"
#include "forest/ts/tsForestItemData.h"
#include "scene/sceneManager.h"
#include "ts/tsLastDetail.h"
TSForestCellBatch::TSForestCellBatch( TSLastDetail *detail )
: mDetail( detail )
{
}
TSForestCellBatch::~TSForestCellBatch()
{
}
bool TSForestCellBatch::_prepBatch( const ForestItem &item )
{
// Make sure it's our item type!
TSForestItemData* data = dynamic_cast<TSForestItemData*>( item.getData() );
if ( !data )
return false;
// TODO: Eventually we should atlas multiple details into
// a single combined texture map. Till then we have to
// do one batch per-detail type.
// If the detail type doesn't match then
// we need to start a new batch.
if ( data->getLastDetail() != mDetail )
return false;
return true;
}
void TSForestCellBatch::_rebuildBatch()
{
// Clean up first.
mVB = NULL;
if ( mItems.empty() )
return;
// How big do we need to make this?
U32 verts = mItems.size() * 6;
mVB.set( GFX, verts, GFXBufferTypeStatic );
if ( !mVB.isValid() )
{
// If we failed it is probably because we requested
// a size bigger than a VB can be. Warn the user.
AssertWarn( false, "TSForestCellBatch::_rebuildBatch: Batch too big... try reducing the forest cell size!" );
return;
}
// Fill this puppy!
ImposterState *vertPtr = mVB.lock();
Vector<ForestItem>::const_iterator item = mItems.begin();
const F32 radius = mDetail->getRadius();
ImposterState state;
for ( ; item != mItems.end(); item++ )
{
item->getWorldBox().getCenter( &state.center );
state.halfSize = radius * item->getScale();
state.alpha = 1.0f;
item->getTransform().getColumn( 2, &state.upVec );
item->getTransform().getColumn( 0, &state.rightVec );
*vertPtr = state;
vertPtr->corner = 0;
++vertPtr;
*vertPtr = state;
vertPtr->corner = 1;
++vertPtr;
*vertPtr = state;
vertPtr->corner = 2;
++vertPtr;
*vertPtr = state;
vertPtr->corner = 2;
++vertPtr;
*vertPtr = state;
vertPtr->corner = 3;
++vertPtr;
*vertPtr = state;
vertPtr->corner = 0;
++vertPtr;
}
mVB.unlock();
}
void TSForestCellBatch::_render( const SceneRenderState *state )
{
if ( !mVB.isValid() ||
( state->isShadowPass() && !TSLastDetail::smCanShadow ) )
return;
// Make sure we have a material to render with.
BaseMatInstance *mat = state->getOverrideMaterial( mDetail->getMatInstance() );
if ( mat == NULL )
return;
// We don't really render here... we submit it to
// the render manager which collects all the batches
// in the scene, sorts them by texture, sets up the
// shader, and then renders them all at once.
ImposterBatchRenderInst *inst = state->getRenderPass()->allocInst<ImposterBatchRenderInst>();
inst->mat = mat;
inst->vertBuff = &mVB;
// We sort by the imposter type first so that RIT_Imposter and
// RIT_ImposterBatches do not get mixed together.
//
// We then sort by material.
//
inst->defaultKey = 0;
inst->defaultKey2 = mat->getStateHint();
state->getRenderPass()->addInst( inst );
}

View file

@ -0,0 +1,68 @@
//-----------------------------------------------------------------------------
// 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 _TSFORESTCELLBATCH_H_
#define _TSFORESTCELLBATCH_H_
#ifndef _FORESTCELLBATCH_H_
#include "renderInstance/renderImposterMgr.h"
#endif
#ifndef _FORESTCELLBATCH_H_
#include "forest/forestCellBatch.h"
#endif
#ifndef _GFXTEXTUREHANDLE_H_
#include "gfx/gfxTextureHandle.h"
#endif
#ifndef _GFXVERTEXBUFFER_H_
#include "gfx/gfxVertexBuffer.h"
#endif
#ifndef _GFXSTRUCTS_H_
#include "gfx/gfxStructs.h"
#endif
class GFXShader;
class TSForestCellBatch : public ForestCellBatch
{
protected:
/// We use the same shader and vertex format as TSLastDetail.
GFXVertexBufferHandle<ImposterState> mVB;
TSLastDetail *mDetail;
// ForestCellBatch
virtual bool _prepBatch( const ForestItem &item );
virtual void _rebuildBatch();
virtual void _render( const SceneRenderState *state );
public:
TSForestCellBatch( TSLastDetail *detail );
virtual ~TSForestCellBatch();
};
#endif // _TSFORESTCELLBATCH_H_

View file

@ -0,0 +1,252 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/ts/tsForestItemData.h"
#include "forest/ts/tsForestCellBatch.h"
#include "core/resourceManager.h"
#include "ts/tsShapeInstance.h"
#include "ts/tsLastDetail.h"
#include "sim/netConnection.h"
#include "materials/materialManager.h"
#include "forest/windDeformation.h"
IMPLEMENT_CO_DATABLOCK_V1(TSForestItemData);
ConsoleDocClass( TSForestItemData,
"@brief Concrete implementation of ForestItemData which loads and renders "
"dts format shapeFiles.\n\n"
"@ingroup Forest"
);
TSForestItemData::TSForestItemData()
: mShapeInstance( NULL ),
mIsClientObject( false )
{
}
TSForestItemData::~TSForestItemData()
{
}
bool TSForestItemData::preload( bool server, String &errorBuffer )
{
mIsClientObject = !server;
if ( !SimDataBlock::preload( server, errorBuffer ) )
return false;
return true;
}
void TSForestItemData::_updateCollisionDetails()
{
mCollisionDetails.clear();
mLOSDetails.clear();
mShape->findColDetails( false, &mCollisionDetails, &mLOSDetails );
}
bool TSForestItemData::onAdd()
{
if ( !Parent::onAdd() )
return false;
// Register for the resource change signal.
ResourceManager::get().getChangedSignal().notify( this, &TSForestItemData::_onResourceChanged );
return true;
}
void TSForestItemData::onRemove()
{
// Remove the resource change signal.
ResourceManager::get().getChangedSignal().remove( this, &TSForestItemData::_onResourceChanged );
SAFE_DELETE( mShapeInstance );
Parent::onRemove();
}
void TSForestItemData::inspectPostApply()
{
Parent::inspectPostApply();
SAFE_DELETE( mShapeInstance );
_loadShape();
}
void TSForestItemData::_onResourceChanged( const Torque::Path &path )
{
if ( path != Path( mShapeFile ) )
return;
SAFE_DELETE( mShapeInstance );
_loadShape();
getReloadSignal().trigger();
}
void TSForestItemData::_loadShape()
{
mShape = ResourceManager::get().load(mShapeFile);
if ( !(bool)mShape )
return;
if ( mIsClientObject &&
!mShape->preloadMaterialList( mShapeFile ) )
return;
// Lets add an autobillboard detail if don't have one.
//_checkLastDetail();
_updateCollisionDetails();
}
TSShapeInstance* TSForestItemData::_getShapeInstance() const
{
// Create the shape instance if we haven't already.
if ( !mShapeInstance && mShape )
{
// Create the instance.
mShapeInstance = new TSShapeInstance( mShape, true );
// So we can make OpCode collision calls.
mShapeInstance->prepCollision();
// Get the material features adding the wind effect if
// we have a positive wind scale and have vertex color
// data which is used for the weighting.
FeatureSet features = MATMGR->getDefaultFeatures();
if ( mWindScale > 0.0f && mShape->getVertexFormat()->hasColor() )
{
// We create our own cloned material list to
// enable the wind effects.
features.addFeature( MFT_WindEffect );
mShapeInstance->cloneMaterialList( &features );
}
}
return mShapeInstance;
}
void TSForestItemData::_checkLastDetail()
{
const S32 dl = mShape->mSmallestVisibleDL;
const TSDetail *detail = &mShape->details[dl];
// TODO: Expose some real parameters to the datablock maybe?
if ( detail->subShapeNum != -1 )
{
mShape->addImposter( mShapeFile, 10, 4, 0, 0, 256, 0, 0 );
// HACK: If i don't do this it crashes!
while ( mShape->detailCollisionAccelerators.size() < mShape->details.size() )
mShape->detailCollisionAccelerators.push_back( NULL );
}
}
TSLastDetail* TSForestItemData::getLastDetail() const
{
// Gotta call this first of the last detail isn't created!
if (!_getShapeInstance())
return NULL;
const S32 dl = mShape->mSmallestVisibleDL;
const TSDetail* detail = &mShape->details[dl];
if ( detail->subShapeNum >= 0 ||
mShape->billboardDetails.size() <= dl )
return NULL;
return mShape->billboardDetails[dl];
}
ForestCellBatch* TSForestItemData::allocateBatch() const
{
TSLastDetail* lastDetail = getLastDetail();
if ( !lastDetail )
return NULL;
return new TSForestCellBatch( lastDetail );
}
bool TSForestItemData::canBillboard( const SceneRenderState *state, const ForestItem &item, F32 distToCamera ) const
{
PROFILE_SCOPE( TSForestItemData_canBillboard );
if ( !mShape )
return false;
// Use the shape instance to do the work it normally does.
TSShapeInstance *shapeInstance = _getShapeInstance();
const S32 dl = shapeInstance->setDetailFromDistance( state, distToCamera / item.getScale() );
// This item has a null LOD... lets consider
// that as being billboarded.
if ( dl < 0 )
return true;
const TSDetail *detail = &mShape->details[dl];
if ( detail->subShapeNum < 0 && dl < mShape->billboardDetails.size() )
return true;
return false;
}
bool TSForestItemData::render( TSRenderState *rdata, const ForestItem &item ) const
{
PROFILE_SCOPE( TSForestItemData_render );
// This shouldn't happen normally at runtime, but during
// development a file change notification on a bad file
// can cause us to get here without a shape.
TSShapeInstance *shapeInst = _getShapeInstance();
if ( !shapeInst )
return false;
const F32 scale = item.getScale();
// Figure out the distance of this item to the camera.
const SceneRenderState *state = rdata->getSceneState();
F32 dist = ( item.getPosition() - state->getDiffuseCameraPosition() ).len();
// TODO: Selecting the lod seems more expensive than
// it should be... we should look to optimize this.
if ( shapeInst->setDetailFromDistance( state, dist / scale ) < 0 )
return false;
// TSShapeInstance::render() uses the
// world matrix for the RenderInst.
MatrixF worldMat = item.getTransform();
worldMat.scale( scale );
GFX->setWorldMatrix( worldMat );
rdata->setMaterialHint( (void*)&item );
// This isn't documented well, but these calls
// don't really render... rather they batch the
// shape to be rendered by the render instance
// manager later on.
shapeInst->animate();
shapeInst->render( *rdata );
return true;
}

View file

@ -0,0 +1,98 @@
//-----------------------------------------------------------------------------
// 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 _TSFORESTITEMDATA_H_
#define _TSFORESTITEMDATA_H_
#ifndef _FORESTITEM_H_
#include "forest/forestItem.h"
#endif
#ifndef _TSSHAPE_H_
#include "ts/tsShape.h"
#endif
#ifndef _RESOURCEMANAGER_H_
#include "core/resourceManager.h"
#endif
class TSShapeInstance;
class TSLastDetail;
class TSForestItemData : public ForestItemData
{
protected:
typedef ForestItemData Parent;
bool mIsClientObject;
// This is setup during forest creation.
mutable TSShapeInstance *mShapeInstance;
Resource<TSShape> mShape;
Vector<S32> mCollisionDetails;
Vector<S32> mLOSDetails;
TSShapeInstance* _getShapeInstance() const;
void _loadShape();
void _onResourceChanged( const Torque::Path &path );
void _checkLastDetail();
void _updateCollisionDetails();
// ForestItemData
void _preload() { _loadShape(); }
public:
DECLARE_CONOBJECT(TSForestItemData);
TSForestItemData();
virtual ~TSForestItemData();
bool preload( bool server, String &errorBuffer );
void onRemove();
bool onAdd();
virtual void inspectPostApply();
TSLastDetail* getLastDetail() const;
// JCF: need access to this to build convex(s) in ForestConvex
TSShapeInstance* getShapeInstance() const { return _getShapeInstance(); }
const Vector<S32>& getCollisionDetails() const { return mCollisionDetails; }
const Vector<S32>& getLOSDetails() const { return mLOSDetails; }
// ForestItemData
const Box3F& getObjBox() const { return mShape ? mShape->bounds : Box3F::Invalid; }
bool render( TSRenderState *rdata, const ForestItem& item ) const;
ForestCellBatch* allocateBatch() const;
bool canBillboard( const SceneRenderState *state, const ForestItem &item, F32 distToCamera ) const;
bool buildPolyList( const ForestItem& item, AbstractPolyList *polyList, const Box3F *box ) const { return false; }
};
#endif // _TSFORESTITEMDATA_H_

View file

@ -0,0 +1,134 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "forest/windDeformation.h"
#include "forest/forestItem.h"
#include "forest/forestWindMgr.h"
#include "forest/forestWindAccumulator.h"
#include "materials/sceneData.h"
#include "scene/sceneRenderState.h"
#include "gfx/gfxShader.h"
#include "gfx/gfxStructs.h"
ImplementFeatureType( MFT_WindEffect, MFG_PreTransform, 0, false );
void WindDeformationConstHandles::init( GFXShader *shader )
{
mWindDirAndSpeed = shader->getShaderConstHandle( "$windDirAndSpeed" );
mWindParams = shader->getShaderConstHandle( "$windParams" );
}
void WindDeformationConstHandles::setConsts( SceneRenderState *state,
const SceneData &sgData,
GFXShaderConstBuffer *buffer )
{
PROFILE_SCOPE( WindDeformationConstHandles_setConsts );
Point3F windDir( 0, 0, 0 );
F32 windSpeed = 0;
F32 bendScale = 0;
F32 branchAmp = 0;
F32 detailAmp = 0;
F32 detailFreq = 0;
const ForestItem *item = (const ForestItem*)sgData.materialHint;
ForestWindAccumulator *wind = NULL;
if ( item )
{
// First setup the per-datablock shader constants.
//
// These cannot be skipped as they modifiy the rest
// state of a tree even without wind.
//
const ForestItemData *itemData = item->getData();
const F32 windScale = itemData->mWindScale;
bendScale = itemData->mTrunkBendScale * windScale;
branchAmp = itemData->mWindBranchAmp * windScale;
detailAmp = itemData->mWindDetailAmp * windScale;
detailFreq = itemData->mWindDetailFreq * windScale;
// Look for wind state.
wind = WINDMGR->getLocalWind( item->getKey() );
}
if ( wind )
{
// Calculate distance to camera fade.
F32 toCamLen = ( state->getDiffuseCameraPosition() - wind->getPosition()).len();
F32 toCamInterp = 1.0f - (toCamLen / ForestWindMgr::smWindEffectRadius);
toCamInterp = mClampF( toCamInterp, 0.0f, 1.0f );
windDir.set( wind->getDirection().x, wind->getDirection().y, 0.0f );
windSpeed = wind->getStrength();
// Since the effect is just a displacement
// of geometry i need to scale it up for smaller
// trees so it looks like its affecting it more.
//
// TODO: This is possibly a side effect of not
// properly doing the displacement physics in the
// first place.
//
const F32 strengthScale = 1.0f / item->getScale();
windDir *= strengthScale;
windSpeed *= strengthScale;
// Add in fade.
windDir *= toCamInterp;
windSpeed *= toCamInterp;
MatrixF invXfm( item->getTransform() );
invXfm.inverse();
invXfm.mulV( windDir );
}
else
{
/*
// HACK: This should go away... we need to work out a strategy
// for allocating accumulators that accounts for random objects
// and not just forest elements.
//
// Maybe add a generic 'id' value to SceneData that is set
// by the RenderInst that can be any unique identifier for that
// object that we can used down here to fetch an item id.
// TODO: This is horrible... we should avoid access to the script
// system while rendering as its really slow!
if ( Con::getBoolVariable( "$tsStaticWindHack", false ) )
{
VectorF windVec( mSin( Sim::getCurrentTime() / 1000.0f ) * 0.25f, 0.0f, 0.0f );
//sgData.objTrans.mulV( windVec );
windDir.x = windVec.x;
windDir.y = windVec.y;
windSpeed = 0.75f;
}
*/
}
buffer->setSafe( mWindDirAndSpeed, Point3F( windDir.x, windDir.y, windSpeed ) );
buffer->setSafe( mWindParams, Point4F( bendScale, branchAmp, detailAmp, detailFreq ) );
}

View file

@ -0,0 +1,52 @@
//-----------------------------------------------------------------------------
// 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 _FOREST_SHADERGEN_WINDDEFORMATION_H_
#define _FOREST_SHADERGEN_WINDDEFORMATION_H_
#ifndef _SHADERGEN_H_
#include "shaderGen/shaderGen.h"
#endif
#ifndef _FEATURETYPE_H_
#include "materials/materialFeatureTypes.h"
#endif
class GFXShaderConstHandle;
class WindDeformationConstHandles : public ShaderFeatureConstHandles
{
public:
GFXShaderConstHandle *mWindDirAndSpeed;
GFXShaderConstHandle *mWindParams;
// ShaderFeatureConstHandles
virtual void init( GFXShader *shader );
virtual void setConsts( SceneRenderState *state,
const SceneData &sgData,
GFXShaderConstBuffer *buffer );
};
DeclareFeatureType( MFT_WindEffect );
#endif // _FOREST_SHADERGEN_WINDDEFORMATION_H_