mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-11 22:54:34 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
200
Engine/source/forest/editor/forestBrushElement.cpp
Normal file
200
Engine/source/forest/editor/forestBrushElement.cpp
Normal 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 );
|
||||
}
|
||||
125
Engine/source/forest/editor/forestBrushElement.h
Normal file
125
Engine/source/forest/editor/forestBrushElement.h
Normal 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_
|
||||
687
Engine/source/forest/editor/forestBrushTool.cpp
Normal file
687
Engine/source/forest/editor/forestBrushTool.cpp
Normal 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(¤tTree);
|
||||
|
||||
// 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();
|
||||
}
|
||||
152
Engine/source/forest/editor/forestBrushTool.h
Normal file
152
Engine/source/forest/editor/forestBrushTool.h
Normal 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_
|
||||
|
||||
|
||||
|
||||
402
Engine/source/forest/editor/forestEditorCtrl.cpp
Normal file
402
Engine/source/forest/editor/forestEditorCtrl.cpp
Normal 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();
|
||||
}
|
||||
107
Engine/source/forest/editor/forestEditorCtrl.h
Normal file
107
Engine/source/forest/editor/forestEditorCtrl.h
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _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_
|
||||
|
||||
|
||||
|
||||
590
Engine/source/forest/editor/forestSelectionTool.cpp
Normal file
590
Engine/source/forest/editor/forestSelectionTool.cpp
Normal 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();
|
||||
}
|
||||
|
||||
121
Engine/source/forest/editor/forestSelectionTool.h
Normal file
121
Engine/source/forest/editor/forestSelectionTool.h
Normal 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_
|
||||
|
||||
|
||||
|
||||
68
Engine/source/forest/editor/forestTool.cpp
Normal file
68
Engine/source/forest/editor/forestTool.cpp
Normal 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();
|
||||
}
|
||||
87
Engine/source/forest/editor/forestTool.h
Normal file
87
Engine/source/forest/editor/forestTool.h
Normal 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_
|
||||
|
||||
|
||||
|
||||
223
Engine/source/forest/editor/forestUndo.cpp
Normal file
223
Engine/source/forest/editor/forestUndo.cpp
Normal 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();
|
||||
}
|
||||
121
Engine/source/forest/editor/forestUndo.h
Normal file
121
Engine/source/forest/editor/forestUndo.h
Normal 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_
|
||||
|
||||
|
||||
|
||||
376
Engine/source/forest/forest.cpp
Normal file
376
Engine/source/forest/forest.cpp
Normal 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();
|
||||
}
|
||||
214
Engine/source/forest/forest.h
Normal file
214
Engine/source/forest/forest.h
Normal 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_
|
||||
505
Engine/source/forest/forestCell.cpp
Normal file
505
Engine/source/forest/forestCell.cpp
Normal 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 ] );
|
||||
}
|
||||
225
Engine/source/forest/forestCell.h
Normal file
225
Engine/source/forest/forestCell.h
Normal 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_
|
||||
71
Engine/source/forest/forestCellBatch.cpp
Normal file
71
Engine/source/forest/forestCellBatch.cpp
Normal 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 );
|
||||
}
|
||||
62
Engine/source/forest/forestCellBatch.h
Normal file
62
Engine/source/forest/forestCellBatch.h
Normal 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_
|
||||
477
Engine/source/forest/forestCollision.cpp
Normal file
477
Engine/source/forest/forestCollision.cpp
Normal 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;
|
||||
}
|
||||
88
Engine/source/forest/forestCollision.h
Normal file
88
Engine/source/forest/forestCollision.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#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_
|
||||
834
Engine/source/forest/forestDataFile.cpp
Normal file
834
Engine/source/forest/forestDataFile.cpp
Normal 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 );
|
||||
}
|
||||
}
|
||||
212
Engine/source/forest/forestDataFile.h
Normal file
212
Engine/source/forest/forestDataFile.h
Normal 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_
|
||||
249
Engine/source/forest/forestItem.cpp
Normal file
249
Engine/source/forest/forestItem.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
274
Engine/source/forest/forestItem.h
Normal file
274
Engine/source/forest/forestItem.h
Normal 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_
|
||||
319
Engine/source/forest/forestRender.cpp
Normal file
319
Engine/source/forest/forestRender.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
132
Engine/source/forest/forestWindAccumulator.cpp
Normal file
132
Engine/source/forest/forestWindAccumulator.cpp
Normal 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;
|
||||
}
|
||||
79
Engine/source/forest/forestWindAccumulator.h
Normal file
79
Engine/source/forest/forestWindAccumulator.h
Normal 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_
|
||||
578
Engine/source/forest/forestWindEmitter.cpp
Normal file
578
Engine/source/forest/forestWindEmitter.cpp
Normal 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 );
|
||||
}
|
||||
219
Engine/source/forest/forestWindEmitter.h
Normal file
219
Engine/source/forest/forestWindEmitter.h
Normal 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_
|
||||
316
Engine/source/forest/forestWindMgr.cpp
Normal file
316
Engine/source/forest/forestWindMgr.cpp
Normal 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 );
|
||||
}
|
||||
101
Engine/source/forest/forestWindMgr.h
Normal file
101
Engine/source/forest/forestWindMgr.h
Normal 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_
|
||||
210
Engine/source/forest/glsl/windDeformationGLSL.cpp
Normal file
210
Engine/source/forest/glsl/windDeformationGLSL.cpp
Normal 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;
|
||||
}
|
||||
66
Engine/source/forest/glsl/windDeformationGLSL.h
Normal file
66
Engine/source/forest/glsl/windDeformationGLSL.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _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_
|
||||
207
Engine/source/forest/hlsl/windDeformationHLSL.cpp
Normal file
207
Engine/source/forest/hlsl/windDeformationHLSL.cpp
Normal 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;
|
||||
}
|
||||
66
Engine/source/forest/hlsl/windDeformationHLSL.h
Normal file
66
Engine/source/forest/hlsl/windDeformationHLSL.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _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_
|
||||
149
Engine/source/forest/ts/tsForestCellBatch.cpp
Normal file
149
Engine/source/forest/ts/tsForestCellBatch.cpp
Normal 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 );
|
||||
}
|
||||
68
Engine/source/forest/ts/tsForestCellBatch.h
Normal file
68
Engine/source/forest/ts/tsForestCellBatch.h
Normal 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_
|
||||
252
Engine/source/forest/ts/tsForestItemData.cpp
Normal file
252
Engine/source/forest/ts/tsForestItemData.cpp
Normal 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;
|
||||
}
|
||||
98
Engine/source/forest/ts/tsForestItemData.h
Normal file
98
Engine/source/forest/ts/tsForestItemData.h
Normal 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_
|
||||
134
Engine/source/forest/windDeformation.cpp
Normal file
134
Engine/source/forest/windDeformation.cpp
Normal 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 ) );
|
||||
}
|
||||
52
Engine/source/forest/windDeformation.h
Normal file
52
Engine/source/forest/windDeformation.h
Normal 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_
|
||||
Loading…
Add table
Add a link
Reference in a new issue