mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-19 20:24:49 +00:00
1728 lines
46 KiB
C++
1728 lines
46 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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 "environment/decalRoad.h"
|
|
|
|
#include "console/consoleTypes.h"
|
|
#include "console/engineAPI.h"
|
|
#include "util/catmullRom.h"
|
|
#include "math/util/quadTransforms.h"
|
|
#include "scene/sceneRenderState.h"
|
|
#include "scene/sceneManager.h"
|
|
#include "core/stream/bitStream.h"
|
|
#include "gfx/gfxDrawUtil.h"
|
|
#include "gfx/gfxTransformSaver.h"
|
|
#include "math/mathIO.h"
|
|
#include "math/mathUtils.h"
|
|
#include "terrain/terrData.h"
|
|
#include "materials/materialDefinition.h"
|
|
#include "materials/materialManager.h"
|
|
#include "materials/baseMatInstance.h"
|
|
#include "environment/nodeListManager.h"
|
|
#include "lighting/lightQuery.h"
|
|
|
|
|
|
extern F32 gDecalBias;
|
|
extern bool gEditingMission;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// DecalRoadNodeList Struct
|
|
//-----------------------------------------------------------------------------
|
|
|
|
struct DecalRoadNodeList : public NodeListManager::NodeList
|
|
{
|
|
Vector<Point3F> mPositions;
|
|
Vector<F32> mWidths;
|
|
|
|
DecalRoadNodeList() { }
|
|
virtual ~DecalRoadNodeList() { }
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// DecalRoadNodeEvent Class
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class DecalRoadNodeEvent : public NodeListEvent
|
|
{
|
|
typedef NodeListEvent Parent;
|
|
|
|
public:
|
|
Vector<Point3F> mPositions;
|
|
Vector<F32> mWidths;
|
|
|
|
public:
|
|
DecalRoadNodeEvent() { mNodeList = NULL; }
|
|
virtual ~DecalRoadNodeEvent() { }
|
|
|
|
virtual void pack(NetConnection*, BitStream*);
|
|
virtual void unpack(NetConnection*, BitStream*);
|
|
|
|
virtual void copyIntoList(NodeListManager::NodeList* copyInto);
|
|
virtual void padListToSize();
|
|
|
|
DECLARE_CONOBJECT(DecalRoadNodeEvent);
|
|
};
|
|
|
|
void DecalRoadNodeEvent::pack(NetConnection* conn, BitStream* stream)
|
|
{
|
|
Parent::pack( conn, stream );
|
|
|
|
stream->writeInt( mPositions.size(), 16 );
|
|
|
|
for (U32 i=0; i<mPositions.size(); ++i)
|
|
{
|
|
mathWrite( *stream, mPositions[i] );
|
|
stream->write( mWidths[i] );
|
|
}
|
|
}
|
|
|
|
void DecalRoadNodeEvent::unpack(NetConnection* conn, BitStream* stream)
|
|
{
|
|
mNodeList = new DecalRoadNodeList();
|
|
|
|
Parent::unpack( conn, stream );
|
|
|
|
U32 count = stream->readInt( 16 );
|
|
|
|
Point3F pos;
|
|
F32 width;
|
|
|
|
DecalRoadNodeList* list = static_cast<DecalRoadNodeList*>(mNodeList);
|
|
|
|
for (U32 i=0; i<count; ++i)
|
|
{
|
|
mathRead( *stream, &pos );
|
|
stream->read( &width );
|
|
|
|
list->mPositions.push_back( pos );
|
|
list->mWidths.push_back( width );
|
|
}
|
|
|
|
list->mTotalValidNodes = count;
|
|
|
|
// Do we have a complete list?
|
|
if (list->mPositions.size() >= mTotalNodes)
|
|
list->mListComplete = true;
|
|
}
|
|
|
|
void DecalRoadNodeEvent::copyIntoList(NodeListManager::NodeList* copyInto)
|
|
{
|
|
DecalRoadNodeList* prevList = static_cast<DecalRoadNodeList*>(copyInto);
|
|
DecalRoadNodeList* list = static_cast<DecalRoadNodeList*>(mNodeList);
|
|
|
|
// Merge our list with the old list.
|
|
for (U32 i=mLocalListStart, index=0; i<mLocalListStart+list->mPositions.size(); ++i, ++index)
|
|
{
|
|
prevList->mPositions[i] = list->mPositions[index];
|
|
prevList->mWidths[i] = list->mWidths[index];
|
|
}
|
|
}
|
|
|
|
void DecalRoadNodeEvent::padListToSize()
|
|
{
|
|
DecalRoadNodeList* list = static_cast<DecalRoadNodeList*>(mNodeList);
|
|
|
|
U32 totalValidNodes = list->mTotalValidNodes;
|
|
|
|
// Pad our list front?
|
|
if (mLocalListStart)
|
|
{
|
|
DecalRoadNodeList* newlist = new DecalRoadNodeList();
|
|
newlist->mPositions.increment(mLocalListStart);
|
|
newlist->mWidths.increment(mLocalListStart);
|
|
|
|
newlist->mPositions.merge(list->mPositions);
|
|
newlist->mWidths.merge(list->mWidths);
|
|
|
|
delete list;
|
|
mNodeList = list = newlist;
|
|
}
|
|
|
|
// Pad our list end?
|
|
if (list->mPositions.size() < mTotalNodes)
|
|
{
|
|
U32 delta = mTotalNodes - list->mPositions.size();
|
|
list->mPositions.increment(delta);
|
|
list->mWidths.increment(delta);
|
|
}
|
|
|
|
list->mTotalValidNodes = totalValidNodes;
|
|
}
|
|
|
|
IMPLEMENT_CO_NETEVENT_V1(DecalRoadNodeEvent);
|
|
|
|
ConsoleDocClass( DecalRoadNodeEvent,
|
|
"@brief Sends messages to the Decal Road Editor\n\n"
|
|
"Editor use only.\n\n"
|
|
"@internal"
|
|
);
|
|
//-----------------------------------------------------------------------------
|
|
// DecalRoadNodeListNotify Class
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class DecalRoadNodeListNotify : public NodeListNotify
|
|
{
|
|
typedef NodeListNotify Parent;
|
|
|
|
protected:
|
|
SimObjectPtr<DecalRoad> mRoad;
|
|
|
|
public:
|
|
DecalRoadNodeListNotify( DecalRoad* road, U32 listId ) { mRoad = road; mListId = listId; }
|
|
virtual ~DecalRoadNodeListNotify() { mRoad = NULL; }
|
|
|
|
virtual void sendNotification( NodeListManager::NodeList* list );
|
|
};
|
|
|
|
void DecalRoadNodeListNotify::sendNotification( NodeListManager::NodeList* list )
|
|
{
|
|
if (mRoad.isValid())
|
|
{
|
|
// Build the road's nodes
|
|
DecalRoadNodeList* roadList = dynamic_cast<DecalRoadNodeList*>( list );
|
|
if (roadList)
|
|
mRoad->buildNodesFromList( roadList );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// DecalRoadUpdateEvent Class
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void DecalRoadUpdateEvent::process( SimObject *object )
|
|
{
|
|
DecalRoad *road = dynamic_cast<DecalRoad*>( object );
|
|
AssertFatal( road, "DecalRoadRegenEvent::process - wasn't a DecalRoad" );
|
|
|
|
// Inform clients to perform the update.
|
|
road->setMaskBits( mMask );
|
|
|
|
if ( !road->isProperlyAdded() )
|
|
return;
|
|
|
|
// Perform the server side update.
|
|
if ( mMask & DecalRoad::TerrainChangedMask )
|
|
{
|
|
road->_generateEdges();
|
|
}
|
|
if ( mMask & DecalRoad::GenEdgesMask )
|
|
{
|
|
// Server has already done this.
|
|
//road->_generateEdges();
|
|
}
|
|
if ( mMask & DecalRoad::ReClipMask )
|
|
{
|
|
// Server does not need to capture verts.
|
|
road->_captureVerts();
|
|
}
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Class: DecalRoad
|
|
//------------------------------------------------------------------------------
|
|
|
|
ConsoleDocClass( DecalRoad,
|
|
"@brief A strip shaped decal defined by spine nodes which clips against Terrain objects.\n\n"
|
|
|
|
"DecalRoad is for representing a road or path ( or other inventive things ) across "
|
|
"a TerrainBlock. It renders as a decal and is therefore only for features that do "
|
|
"not need geometric depth.\n\n"
|
|
|
|
"The Material assigned to DecalRoad should tile vertically.\n\n"
|
|
|
|
"@ingroup Terrain"
|
|
);
|
|
|
|
// Init Statics
|
|
|
|
// Static ConsoleVars for toggling debug rendering
|
|
bool DecalRoad::smEditorOpen = false;
|
|
bool DecalRoad::smWireframe = true;
|
|
bool DecalRoad::smShowBatches = false;
|
|
bool DecalRoad::smDiscardAll = false;
|
|
bool DecalRoad::smShowSpline = true;
|
|
bool DecalRoad::smShowRoad = true;
|
|
S32 DecalRoad::smUpdateDelay = 500;
|
|
|
|
SimObjectPtr<SimSet> DecalRoad::smServerDecalRoadSet = NULL;
|
|
|
|
|
|
// Constructors
|
|
|
|
DecalRoad::DecalRoad()
|
|
: mBreakAngle( 3.0f ),
|
|
mSegmentsPerBatch( 10 ),
|
|
mTextureLength( 5.0f ),
|
|
mRenderPriority( 10 ),
|
|
mLoadRenderData( true ),
|
|
mMaterial( NULL ),
|
|
mMatInst( NULL ),
|
|
mUpdateEventId( -1 ),
|
|
mTerrainUpdateRect( Box3F::Invalid )
|
|
{
|
|
// Setup NetObject.
|
|
mTypeMask |= StaticObjectType | StaticShapeObjectType;
|
|
mNetFlags.set(Ghostable);
|
|
}
|
|
|
|
DecalRoad::~DecalRoad()
|
|
{
|
|
}
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(DecalRoad);
|
|
|
|
|
|
// ConsoleObject
|
|
|
|
void DecalRoad::initPersistFields()
|
|
{
|
|
addGroup( "DecalRoad" );
|
|
|
|
addField( "material", TypeMaterialName, Offset( mMaterialName, DecalRoad ), "Material used for rendering." );
|
|
|
|
addProtectedField( "textureLength", TypeF32, Offset( mTextureLength, DecalRoad ), &DecalRoad::ptSetTextureLength, &defaultProtectedGetFn,
|
|
"The length in meters of textures mapped to the DecalRoad" );
|
|
|
|
addProtectedField( "breakAngle", TypeF32, Offset( mBreakAngle, DecalRoad ), &DecalRoad::ptSetBreakAngle, &defaultProtectedGetFn,
|
|
"Angle in degrees - DecalRoad will subdivided the spline if its curve is greater than this threshold." );
|
|
|
|
addField( "renderPriority", TypeS32, Offset( mRenderPriority, DecalRoad ),
|
|
"DecalRoad(s) are rendered in descending renderPriority order." );
|
|
|
|
endGroup( "DecalRoad" );
|
|
|
|
addGroup( "Internal" );
|
|
|
|
addProtectedField( "node", TypeString, NULL, &addNodeFromField, &emptyStringProtectedGetFn,
|
|
"Do not modify, for internal use." );
|
|
|
|
endGroup( "Internal" );
|
|
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
void DecalRoad::consoleInit()
|
|
{
|
|
Parent::consoleInit();
|
|
|
|
// Vars for debug rendering while the RoadEditor is open, only used if smEditorOpen is true.
|
|
Con::addVariable( "$DecalRoad::EditorOpen", TypeBool, &DecalRoad::smEditorOpen, "For use by the Decal Editor.\n\n"
|
|
"@ingroup Editors\n" );
|
|
Con::addVariable( "$DecalRoad::wireframe", TypeBool, &DecalRoad::smWireframe, "For use by the Decal Editor.\n\n"
|
|
"@ingroup Editors\n" );
|
|
Con::addVariable( "$DecalRoad::showBatches", TypeBool, &DecalRoad::smShowBatches, "For use by the Decal Editor.\n\n"
|
|
"@ingroup Editors\n" );
|
|
Con::addVariable( "$DecalRoad::discardAll", TypeBool, &DecalRoad::smDiscardAll, "For use by the Decal Editor.\n\n"
|
|
"@ingroup Editors\n");
|
|
Con::addVariable( "$DecalRoad::showSpline", TypeBool, &DecalRoad::smShowSpline, "For use by the Decal Editor.\n\n"
|
|
"@ingroup Editors\n" );
|
|
Con::addVariable( "$DecalRoad::showRoad", TypeBool, &DecalRoad::smShowRoad, "For use by the Decal Editor.\n\n"
|
|
"@ingroup Editors\n" );
|
|
Con::addVariable( "$DecalRoad::updateDelay", TypeS32, &DecalRoad::smUpdateDelay, "For use by the Decal Editor.\n\n"
|
|
"@ingroup Editors\n" );
|
|
}
|
|
|
|
|
|
// SimObject
|
|
|
|
bool DecalRoad::onAdd()
|
|
{
|
|
if ( !Parent::onAdd() )
|
|
return false;
|
|
|
|
// DecalRoad is at position zero when created,
|
|
// it sets its own position to the first node inside
|
|
// _generateEdges but until it has at least one node
|
|
// it will be at 0,0,0.
|
|
|
|
MatrixF mat(true);
|
|
Parent::setTransform( mat );
|
|
|
|
// The client side calculates bounds based on clipped geometry. It would
|
|
// be wasteful for the server to do this so the server uses global bounds.
|
|
if ( isServerObject() )
|
|
{
|
|
setGlobalBounds();
|
|
resetWorldBox();
|
|
}
|
|
|
|
// Set the Render Transform.
|
|
setRenderTransform(mObjToWorld);
|
|
|
|
// Add to Scene.
|
|
addToScene();
|
|
|
|
if ( isServerObject() )
|
|
getServerSet()->addObject( this );
|
|
|
|
//
|
|
TerrainBlock::smUpdateSignal.notify( this, &DecalRoad::_onTerrainChanged );
|
|
|
|
//
|
|
if ( isClientObject() )
|
|
_initMaterial();
|
|
|
|
_generateEdges();
|
|
_captureVerts();
|
|
|
|
return true;
|
|
}
|
|
|
|
void DecalRoad::onRemove()
|
|
{
|
|
SAFE_DELETE( mMatInst );
|
|
|
|
TerrainBlock::smUpdateSignal.remove( this, &DecalRoad::_onTerrainChanged );
|
|
|
|
removeFromScene();
|
|
|
|
Parent::onRemove();
|
|
}
|
|
|
|
void DecalRoad::inspectPostApply()
|
|
{
|
|
Parent::inspectPostApply();
|
|
|
|
setMaskBits( DecalRoadMask );
|
|
}
|
|
|
|
void DecalRoad::onStaticModified( const char* slotName, const char*newValue )
|
|
{
|
|
Parent::onStaticModified( slotName, newValue );
|
|
|
|
/*
|
|
if ( isProperlyAdded() &&
|
|
dStricmp( slotName, "material" ) == 0 )
|
|
{
|
|
setMaskBits( DecalRoadMask );
|
|
}
|
|
*/
|
|
|
|
if ( dStricmp( slotName, "renderPriority" ) == 0 )
|
|
{
|
|
mRenderPriority = getMax( dAtoi(newValue), (S32)1 );
|
|
}
|
|
}
|
|
|
|
SimSet* DecalRoad::getServerSet()
|
|
{
|
|
if ( !smServerDecalRoadSet )
|
|
{
|
|
smServerDecalRoadSet = new SimSet();
|
|
smServerDecalRoadSet->registerObject( "ServerDecalRoadSet" );
|
|
Sim::getRootGroup()->addObject( smServerDecalRoadSet );
|
|
}
|
|
|
|
return smServerDecalRoadSet;
|
|
}
|
|
|
|
void DecalRoad::writeFields( Stream &stream, U32 tabStop )
|
|
{
|
|
Parent::writeFields( stream, tabStop );
|
|
|
|
// Now write all nodes
|
|
|
|
stream.write(2, "\r\n");
|
|
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
{
|
|
const RoadNode &node = mNodes[i];
|
|
|
|
stream.writeTabs(tabStop);
|
|
|
|
char buffer[1024];
|
|
dMemset( buffer, 0, 1024 );
|
|
dSprintf( buffer, 1024, "Node = \"%f %f %f %f\";", node.point.x, node.point.y, node.point.z, node.width );
|
|
stream.writeLine( (const U8*)buffer );
|
|
}
|
|
}
|
|
|
|
bool DecalRoad::writeField( StringTableEntry fieldname, const char *value )
|
|
{
|
|
if ( fieldname == StringTable->insert("node") )
|
|
return false;
|
|
|
|
return Parent::writeField( fieldname, value );
|
|
}
|
|
|
|
void DecalRoad::onEditorEnable()
|
|
{
|
|
}
|
|
|
|
void DecalRoad::onEditorDisable()
|
|
{
|
|
}
|
|
|
|
|
|
// NetObject
|
|
|
|
U32 DecalRoad::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
|
|
{
|
|
// Pack Parent.
|
|
U32 retMask = Parent::packUpdate(con, mask, stream);
|
|
|
|
if ( stream->writeFlag( mask & DecalRoadMask ) )
|
|
{
|
|
// Write Texture Name.
|
|
stream->write( mMaterialName );
|
|
|
|
stream->write( mBreakAngle );
|
|
|
|
stream->write( mSegmentsPerBatch );
|
|
|
|
stream->write( mTextureLength );
|
|
|
|
stream->write( mRenderPriority );
|
|
}
|
|
|
|
if ( stream->writeFlag( mask & NodeMask ) )
|
|
{
|
|
//stream->writeInt( mNodes.size(), 16 );
|
|
|
|
//for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
//{
|
|
// mathWrite( *stream, mNodes[i].point );
|
|
// stream->write( mNodes[i].width );
|
|
//}
|
|
|
|
const U32 nodeByteSize = 16; // Based on sending all of a node's parameters
|
|
|
|
// Test if we can fit all of our nodes within the current stream.
|
|
// We make sure we leave 100 bytes still free in the stream for whatever
|
|
// may follow us.
|
|
S32 allowedBytes = stream->getWriteByteSize() - 100;
|
|
if ( stream->writeFlag( (nodeByteSize * mNodes.size()) < allowedBytes ) )
|
|
{
|
|
// All nodes should fit, so send them out now.
|
|
stream->writeInt( mNodes.size(), 16 );
|
|
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
{
|
|
mathWrite( *stream, mNodes[i].point );
|
|
stream->write( mNodes[i].width );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// There isn't enough space left in the stream for all of the
|
|
// nodes. Batch them up into NetEvents.
|
|
U32 id = gServerNodeListManager->nextListId();
|
|
U32 count = 0;
|
|
U32 index = 0;
|
|
while (count < mNodes.size())
|
|
{
|
|
count += NodeListManager::smMaximumNodesPerEvent;
|
|
if (count > mNodes.size())
|
|
{
|
|
count = mNodes.size();
|
|
}
|
|
|
|
DecalRoadNodeEvent* event = new DecalRoadNodeEvent();
|
|
event->mId = id;
|
|
event->mTotalNodes = mNodes.size();
|
|
event->mLocalListStart = index;
|
|
|
|
for (; index<count; ++index)
|
|
{
|
|
event->mPositions.push_back( mNodes[index].point );
|
|
event->mWidths.push_back( mNodes[index].width );
|
|
}
|
|
|
|
con->postNetEvent( event );
|
|
}
|
|
|
|
stream->write( id );
|
|
}
|
|
}
|
|
|
|
stream->writeFlag( mask & GenEdgesMask );
|
|
|
|
stream->writeFlag( mask & ReClipMask );
|
|
|
|
stream->writeFlag( mask & TerrainChangedMask );
|
|
|
|
// Were done ...
|
|
return retMask;
|
|
}
|
|
|
|
void DecalRoad::unpackUpdate( NetConnection *con, BitStream *stream )
|
|
{
|
|
// Unpack Parent.
|
|
Parent::unpackUpdate( con, stream );
|
|
|
|
// DecalRoadMask
|
|
if ( stream->readFlag() )
|
|
{
|
|
String matName;
|
|
stream->read( &matName );
|
|
|
|
if ( matName != mMaterialName )
|
|
{
|
|
mMaterialName = matName;
|
|
Material *pMat = NULL;
|
|
if ( !Sim::findObject( mMaterialName, pMat ) )
|
|
{
|
|
Con::printf( "DecalRoad::unpackUpdate, failed to find Material of name %s!", mMaterialName.c_str() );
|
|
}
|
|
else
|
|
{
|
|
mMaterial = pMat;
|
|
if ( isProperlyAdded() )
|
|
_initMaterial();
|
|
}
|
|
}
|
|
|
|
stream->read( &mBreakAngle );
|
|
|
|
stream->read( &mSegmentsPerBatch );
|
|
|
|
stream->read( &mTextureLength );
|
|
|
|
stream->read( &mRenderPriority );
|
|
}
|
|
|
|
// NodeMask
|
|
if ( stream->readFlag() )
|
|
{
|
|
//U32 count = stream->readInt( 16 );
|
|
|
|
//mNodes.clear();
|
|
|
|
//Point3F pos;
|
|
//F32 width;
|
|
//for ( U32 i = 0; i < count; i++ )
|
|
//{
|
|
// mathRead( *stream, &pos );
|
|
// stream->read( &width );
|
|
// _addNode( pos, width );
|
|
//}
|
|
|
|
if (stream->readFlag())
|
|
{
|
|
// Nodes have been passed in this update
|
|
U32 count = stream->readInt( 16 );
|
|
|
|
mNodes.clear();
|
|
|
|
Point3F pos;
|
|
F32 width;
|
|
for ( U32 i = 0; i < count; i++ )
|
|
{
|
|
mathRead( *stream, &pos );
|
|
stream->read( &width );
|
|
_addNode( pos, width );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Nodes will arrive as events
|
|
U32 id;
|
|
stream->read( &id );
|
|
|
|
// Check if the road's nodes made it here before we did.
|
|
NodeListManager::NodeList* list = NULL;
|
|
if ( gClientNodeListManager->findListById( id, &list, true) )
|
|
{
|
|
// Work with the completed list
|
|
DecalRoadNodeList* roadList = dynamic_cast<DecalRoadNodeList*>( list );
|
|
if (roadList)
|
|
buildNodesFromList( roadList );
|
|
|
|
delete list;
|
|
}
|
|
else
|
|
{
|
|
// Nodes have not yet arrived, so register our interest in the list
|
|
DecalRoadNodeListNotify* notify = new DecalRoadNodeListNotify( this, id );
|
|
gClientNodeListManager->registerNotification( notify );
|
|
}
|
|
}
|
|
}
|
|
|
|
// GenEdgesMask
|
|
if ( stream->readFlag() && isProperlyAdded() )
|
|
_generateEdges();
|
|
|
|
// ReClipMask
|
|
if ( stream->readFlag() && isProperlyAdded() )
|
|
_captureVerts();
|
|
|
|
// TerrainChangedMask
|
|
if ( stream->readFlag() )
|
|
{
|
|
if ( isProperlyAdded() )
|
|
{
|
|
if ( mTerrainUpdateRect.isOverlapped( getWorldBox() ) )
|
|
{
|
|
_generateEdges();
|
|
_captureVerts();
|
|
// Clear out the mTerrainUpdateRect since we have updated its
|
|
// region and we now need to store future terrain changes
|
|
// in it.
|
|
mTerrainUpdateRect = Box3F::Invalid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DecalRoad::prepRenderImage( SceneRenderState* state )
|
|
{
|
|
PROFILE_SCOPE( DecalRoad_prepRenderImage );
|
|
|
|
if ( mNodes.size() <= 1 ||
|
|
mBatches.size() == 0 ||
|
|
!mMatInst ||
|
|
state->isShadowPass() )
|
|
return;
|
|
|
|
// If we don't have a material instance after the override then
|
|
// we can skip rendering all together.
|
|
BaseMatInstance *matInst = state->getOverrideMaterial( mMatInst );
|
|
if ( !matInst )
|
|
return;
|
|
|
|
RenderPassManager *renderPass = state->getRenderPass();
|
|
|
|
// Debug RenderInstance
|
|
// Only when editor is open.
|
|
if ( smEditorOpen )
|
|
{
|
|
ObjectRenderInst *ri = renderPass->allocInst<ObjectRenderInst>();
|
|
ri->type = RenderPassManager::RIT_Editor;
|
|
ri->renderDelegate.bind( this, &DecalRoad::_debugRender );
|
|
state->getRenderPass()->addInst( ri );
|
|
}
|
|
|
|
// Normal Road RenderInstance
|
|
// Always rendered when the editor is not open
|
|
// otherwise obey the smShowRoad flag
|
|
if ( !smShowRoad && smEditorOpen )
|
|
return;
|
|
|
|
const Frustum &frustum = state->getCameraFrustum();
|
|
|
|
MeshRenderInst coreRI;
|
|
coreRI.clear();
|
|
coreRI.objectToWorld = &MatrixF::Identity;
|
|
coreRI.worldToCamera = renderPass->allocSharedXform(RenderPassManager::View);
|
|
|
|
MatrixF *tempMat = renderPass->allocUniqueXform( MatrixF( true ) );
|
|
MathUtils::getZBiasProjectionMatrix( gDecalBias, frustum, tempMat );
|
|
coreRI.projection = tempMat;
|
|
|
|
coreRI.type = RenderPassManager::RIT_DecalRoad;
|
|
coreRI.vertBuff = &mVB;
|
|
coreRI.primBuff = &mPB;
|
|
coreRI.matInst = matInst;
|
|
|
|
// Make it the sort distance the max distance so that
|
|
// it renders after all the other opaque geometry in
|
|
// the deferred bin.
|
|
coreRI.sortDistSq = F32_MAX;
|
|
|
|
// If we need lights then set them up.
|
|
if ( matInst->isForwardLit() )
|
|
{
|
|
LightQuery query;
|
|
query.init( getWorldSphere() );
|
|
query.getLights( coreRI.lights, 8 );
|
|
}
|
|
|
|
U32 startBatchIdx = -1;
|
|
U32 endBatchIdx = 0;
|
|
|
|
for ( U32 i = 0; i < mBatches.size(); i++ )
|
|
{
|
|
const RoadBatch &batch = mBatches[i];
|
|
const bool isVisible = !frustum.isCulled( batch.bounds );
|
|
if ( isVisible )
|
|
{
|
|
// If this is the start of a set of batches.
|
|
if ( startBatchIdx == -1 )
|
|
endBatchIdx = startBatchIdx = i;
|
|
|
|
// Else we're extending the end batch index.
|
|
else
|
|
++endBatchIdx;
|
|
|
|
// If this isn't the last batch then continue.
|
|
if ( i < mBatches.size()-1 )
|
|
continue;
|
|
}
|
|
|
|
// We we still don't have a start batch, so skip.
|
|
if ( startBatchIdx == -1 )
|
|
continue;
|
|
|
|
// Render this set of batches.
|
|
const RoadBatch &startBatch = mBatches[startBatchIdx];
|
|
const RoadBatch &endBatch = mBatches[endBatchIdx];
|
|
|
|
U32 startVert = startBatch.startVert;
|
|
U32 startIdx = startBatch.startIndex;
|
|
U32 vertCount = endBatch.endVert - startVert;
|
|
U32 idxCount = ( endBatch.endIndex - startIdx ) + 1;
|
|
U32 triangleCount = idxCount / 3;
|
|
|
|
AssertFatal( startVert + vertCount <= mVertCount, "DecalRoad, bad draw call!" );
|
|
AssertFatal( startIdx + triangleCount < mTriangleCount * 3, "DecalRoad, bad draw call!" );
|
|
|
|
MeshRenderInst *ri = renderPass->allocInst<MeshRenderInst>();
|
|
|
|
*ri = coreRI;
|
|
|
|
ri->prim = renderPass->allocPrim();
|
|
ri->prim->type = GFXTriangleList;
|
|
ri->prim->minIndex = 0;
|
|
ri->prim->startIndex = startIdx;
|
|
ri->prim->numPrimitives = triangleCount;
|
|
ri->prim->startVertex = 0;
|
|
ri->prim->numVertices = endBatch.endVert + 1;
|
|
|
|
// For sorting we first sort by render priority
|
|
// and then by objectId.
|
|
//
|
|
// Since a road can submit more than one render instance, we want all
|
|
// draw calls for a single road to occur consecutively, since they
|
|
// could use the same vertex buffer.
|
|
ri->defaultKey = mRenderPriority << 0 | mId << 16;
|
|
ri->defaultKey2 = 0;
|
|
|
|
renderPass->addInst( ri );
|
|
|
|
// Reset the batching.
|
|
startBatchIdx = -1;
|
|
}
|
|
}
|
|
|
|
void DecalRoad::setTransform( const MatrixF &mat )
|
|
{
|
|
// We ignore transform requests from the editor
|
|
// right now.
|
|
}
|
|
|
|
void DecalRoad::setScale( const VectorF &scale )
|
|
{
|
|
// We ignore scale requests from the editor
|
|
// right now.
|
|
}
|
|
|
|
|
|
// DecalRoad Public Methods
|
|
|
|
bool DecalRoad::getClosestNode( const Point3F &pos, U32 &idx )
|
|
{
|
|
F32 closestDist = F32_MAX;
|
|
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
{
|
|
F32 dist = ( mNodes[i].point - pos ).len();
|
|
if ( dist < closestDist )
|
|
{
|
|
closestDist = dist;
|
|
idx = i;
|
|
}
|
|
}
|
|
|
|
return closestDist != F32_MAX;
|
|
}
|
|
|
|
bool DecalRoad::containsPoint( const Point3F &worldPos, U32 *nodeIdx ) const
|
|
{
|
|
// This is just for making selections in the editor, we use the
|
|
// client-side road because it has the proper edge's.
|
|
if ( isServerObject() && getClientObject() )
|
|
return ((DecalRoad*)getClientObject())->containsPoint( worldPos, nodeIdx );
|
|
|
|
// If point isn't in the world box,
|
|
// it's definitely not in the road.
|
|
//if ( !getWorldBox().isContained( worldPos ) )
|
|
// return false;
|
|
|
|
if ( mEdges.size() < 2 )
|
|
return false;
|
|
|
|
Point2F testPt( worldPos.x,
|
|
worldPos.y );
|
|
Point2F poly[4];
|
|
|
|
// Look through all edges, does the polygon
|
|
// formed from adjacent edge's contain the worldPos?
|
|
for ( U32 i = 0; i < mEdges.size() - 1; i++ )
|
|
{
|
|
const RoadEdge &edge0 = mEdges[i];
|
|
const RoadEdge &edge1 = mEdges[i+1];
|
|
|
|
poly[0].set( edge0.p0.x, edge0.p0.y );
|
|
poly[1].set( edge0.p2.x, edge0.p2.y );
|
|
poly[2].set( edge1.p2.x, edge1.p2.y );
|
|
poly[3].set( edge1.p0.x, edge1.p0.y );
|
|
|
|
if ( MathUtils::pointInPolygon( poly, 4, testPt ) )
|
|
{
|
|
if ( nodeIdx )
|
|
*nodeIdx = edge0.parentNodeIdx;
|
|
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool DecalRoad::castray( const Point3F &start, const Point3F &end ) const
|
|
{
|
|
// We just cast against the object box for the editor.
|
|
return mWorldBox.collideLine( start, end );
|
|
}
|
|
|
|
Point3F DecalRoad::getNodePosition( U32 idx )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return Point3F();
|
|
|
|
return mNodes[idx].point;
|
|
}
|
|
|
|
void DecalRoad::setNodePosition( U32 idx, const Point3F &pos )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return;
|
|
|
|
mNodes[idx].point = pos;
|
|
|
|
_generateEdges();
|
|
scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask );
|
|
}
|
|
|
|
U32 DecalRoad::addNode( const Point3F &pos, F32 width )
|
|
{
|
|
U32 idx = _addNode( pos, width );
|
|
|
|
_generateEdges();
|
|
scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask );
|
|
|
|
return idx;
|
|
}
|
|
|
|
U32 DecalRoad::insertNode(const Point3F &pos, const F32 &width, const U32 &idx)
|
|
{
|
|
U32 ret = _insertNode( pos, width, idx );
|
|
|
|
_generateEdges();
|
|
scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask );
|
|
|
|
return ret;
|
|
}
|
|
|
|
void DecalRoad::setNodeWidth( U32 idx, F32 width )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return;
|
|
|
|
mNodes[idx].width = width;
|
|
|
|
_generateEdges();
|
|
scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask );
|
|
}
|
|
|
|
F32 DecalRoad::getNodeWidth( U32 idx )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return -1.0f;
|
|
|
|
return mNodes[idx].width;
|
|
}
|
|
|
|
void DecalRoad::deleteNode( U32 idx )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return;
|
|
|
|
mNodes.erase(idx);
|
|
|
|
_generateEdges();
|
|
scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask );
|
|
}
|
|
|
|
void DecalRoad::buildNodesFromList( DecalRoadNodeList* list )
|
|
{
|
|
mNodes.clear();
|
|
|
|
for (U32 i=0; i<list->mPositions.size(); ++i)
|
|
{
|
|
_addNode( list->mPositions[i], list->mWidths[i] );
|
|
}
|
|
|
|
_generateEdges();
|
|
_captureVerts();
|
|
}
|
|
|
|
void DecalRoad::setTextureLength( F32 meters )
|
|
{
|
|
meters = getMax( meters, 0.1f );
|
|
if ( mTextureLength == meters )
|
|
return;
|
|
|
|
mTextureLength = meters;
|
|
|
|
_generateEdges();
|
|
scheduleUpdate( DecalRoadMask | ReClipMask );
|
|
}
|
|
|
|
void DecalRoad::setBreakAngle( F32 degrees )
|
|
{
|
|
//meters = getMax( meters, MIN_METERS_PER_SEGMENT );
|
|
//if ( mBreakAngle == meters )
|
|
// return;
|
|
|
|
mBreakAngle = degrees;
|
|
|
|
_generateEdges();
|
|
scheduleUpdate( DecalRoadMask | GenEdgesMask | ReClipMask );
|
|
}
|
|
|
|
void DecalRoad::scheduleUpdate( U32 updateMask )
|
|
{
|
|
scheduleUpdate( updateMask, smUpdateDelay, true );
|
|
}
|
|
|
|
void DecalRoad::scheduleUpdate( U32 updateMask, U32 delayMs, bool restartTimer )
|
|
{
|
|
if ( Sim::isEventPending( mUpdateEventId ) )
|
|
{
|
|
if ( !restartTimer )
|
|
{
|
|
mLastEvent->mMask |= updateMask;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Sim::cancelEvent( mUpdateEventId );
|
|
}
|
|
}
|
|
|
|
mLastEvent = new DecalRoadUpdateEvent( updateMask, delayMs );
|
|
mUpdateEventId = Sim::postEvent( this, mLastEvent, Sim::getCurrentTime() + delayMs );
|
|
}
|
|
|
|
void DecalRoad::regenerate()
|
|
{
|
|
_generateEdges();
|
|
_captureVerts();
|
|
setMaskBits( NodeMask | GenEdgesMask | ReClipMask );
|
|
}
|
|
|
|
bool DecalRoad::addNodeFromField( void *object, const char *index, const char *data )
|
|
{
|
|
DecalRoad *pObj = static_cast<DecalRoad*>(object);
|
|
|
|
F32 x,y,z,width;
|
|
U32 result = dSscanf( data, "%f %f %f %f", &x, &y, &z, &width );
|
|
if ( result == 4 )
|
|
pObj->_addNode( Point3F(x,y,z), width );
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// Internal Helper Methods
|
|
|
|
void DecalRoad::_initMaterial()
|
|
{
|
|
SAFE_DELETE( mMatInst );
|
|
|
|
if ( mMaterial )
|
|
mMatInst = mMaterial->createMatInstance();
|
|
else
|
|
mMatInst = MATMGR->createMatInstance( "WarningMaterial" );
|
|
|
|
GFXStateBlockDesc desc;
|
|
desc.setZReadWrite( true, false );
|
|
mMatInst->addStateBlockDesc( desc );
|
|
|
|
mMatInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat<GFXVertexPNTBT>() );
|
|
}
|
|
|
|
void DecalRoad::_debugRender( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* )
|
|
{
|
|
//if ( mStateBlock.isNull() )
|
|
// return;
|
|
|
|
GFX->enterDebugEvent( ColorI( 255, 0, 0 ), "DecalRoad_debugRender" );
|
|
GFXTransformSaver saver;
|
|
|
|
//GFX->setStateBlock( mStateBlock );
|
|
|
|
Point3F size(1,1,1);
|
|
ColorI color( 255, 0, 0, 255 );
|
|
|
|
GFXStateBlockDesc desc;
|
|
desc.setZReadWrite( true, false );
|
|
desc.setBlend( true );
|
|
desc.fillMode = GFXFillWireframe;
|
|
|
|
if ( smShowBatches )
|
|
{
|
|
for ( U32 i = 0; i < mBatches.size(); i++ )
|
|
{
|
|
const Box3F &box = mBatches[i].bounds;
|
|
GFX->getDrawUtil()->drawCube( desc, box, ColorI(255,100,100,255) );
|
|
}
|
|
}
|
|
|
|
//GFX->leaveDebugEvent();
|
|
}
|
|
|
|
void DecalRoad::_generateEdges()
|
|
{
|
|
PROFILE_SCOPE( DecalRoad_generateEdges );
|
|
|
|
//Con::warnf( "%s - generateEdges", isServerObject() ? "server" : "client" );
|
|
|
|
if ( mNodes.size() > 0 )
|
|
{
|
|
// Set our object position to the first node.
|
|
const Point3F &nodePt = mNodes.first().point;
|
|
MatrixF mat( true );
|
|
mat.setPosition( nodePt );
|
|
Parent::setTransform( mat );
|
|
|
|
// The server object has global bounds, which Parent::setTransform
|
|
// messes up so we must reset it.
|
|
if ( isServerObject() )
|
|
{
|
|
mObjBox.minExtents.set(-1e10, -1e10, -1e10);
|
|
mObjBox.maxExtents.set( 1e10, 1e10, 1e10);
|
|
}
|
|
}
|
|
|
|
|
|
if ( mNodes.size() < 2 )
|
|
return;
|
|
|
|
// Ensure nodes are above the terrain height at their xy position
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
{
|
|
_getTerrainHeight( mNodes[i].point );
|
|
}
|
|
|
|
// Now start generating edges...
|
|
|
|
U32 nodeCount = mNodes.size();
|
|
Point3F *positions = new Point3F[nodeCount];
|
|
|
|
for ( U32 i = 0; i < nodeCount; i++ )
|
|
{
|
|
const RoadNode &node = mNodes[i];
|
|
positions[i].set( node.point.x, node.point.y, node.width );
|
|
}
|
|
|
|
CatmullRom<Point3F> spline;
|
|
spline.initialize( nodeCount, positions );
|
|
delete [] positions;
|
|
|
|
mEdges.clear();
|
|
|
|
Point3F lastBreakVector(0,0,0);
|
|
RoadEdge slice;
|
|
Point3F lastBreakNode;
|
|
lastBreakNode = spline.evaluate(0.0f);
|
|
|
|
for ( U32 i = 1; i < mNodes.size(); i++ )
|
|
{
|
|
F32 t1 = spline.getTime(i);
|
|
F32 t0 = spline.getTime(i-1);
|
|
|
|
F32 segLength = spline.arcLength( t0, t1 );
|
|
|
|
U32 numSegments = mCeil( segLength / MIN_METERS_PER_SEGMENT );
|
|
numSegments = getMax( numSegments, (U32)1 );
|
|
F32 tstep = ( t1 - t0 ) / numSegments;
|
|
|
|
U32 startIdx = 0;
|
|
U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments;
|
|
|
|
for ( U32 j = startIdx; j < endIdx; j++ )
|
|
{
|
|
F32 t = t0 + tstep * j;
|
|
Point3F splineNode = spline.evaluate(t);
|
|
F32 width = splineNode.z;
|
|
_getTerrainHeight( splineNode );
|
|
|
|
Point3F toNodeVec = splineNode - lastBreakNode;
|
|
toNodeVec.normalizeSafe();
|
|
|
|
if ( lastBreakVector.isZero() )
|
|
lastBreakVector = toNodeVec;
|
|
|
|
F32 angle = mRadToDeg( mAcos( mDot( toNodeVec, lastBreakVector ) ) );
|
|
|
|
if ( j == startIdx ||
|
|
( j == endIdx - 1 && i == mNodes.size() - 1 ) ||
|
|
angle > mBreakAngle )
|
|
{
|
|
// Push back a spline node
|
|
//slice.p1.set( splineNode.x, splineNode.y, 0.0f );
|
|
//_getTerrainHeight( slice.p1 );
|
|
slice.p1 = splineNode;
|
|
slice.uvec.set(0,0,1);
|
|
slice.width = width;
|
|
slice.parentNodeIdx = i-1;
|
|
mEdges.push_back( slice );
|
|
|
|
lastBreakVector = splineNode - lastBreakNode;
|
|
lastBreakVector.normalizeSafe();
|
|
|
|
lastBreakNode = splineNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
for ( U32 i = 1; i < nodeCount; i++ )
|
|
{
|
|
F32 t0 = spline.getTime( i-1 );
|
|
F32 t1 = spline.getTime( i );
|
|
|
|
F32 segLength = spline.arcLength( t0, t1 );
|
|
|
|
U32 numSegments = mCeil( segLength / mBreakAngle );
|
|
numSegments = getMax( numSegments, (U32)1 );
|
|
F32 tstep = ( t1 - t0 ) / numSegments;
|
|
|
|
AssertFatal( numSegments > 0, "DecalRoad::_generateEdges, got zero segments!" );
|
|
|
|
U32 startIdx = 0;
|
|
U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments;
|
|
|
|
for ( U32 j = startIdx; j < endIdx; j++ )
|
|
{
|
|
F32 t = t0 + tstep * j;
|
|
Point3F val = spline.evaluate(t);
|
|
|
|
RoadEdge edge;
|
|
edge.p1.set( val.x, val.y, 0.0f );
|
|
_getTerrainHeight( val.x, val.y, edge.p1.z );
|
|
edge.uvec.set(0,0,1);
|
|
edge.width = val.z;
|
|
edge.parentNodeIdx = i-1;
|
|
mEdges.push_back( edge );
|
|
}
|
|
}
|
|
*/
|
|
|
|
//
|
|
// Calculate fvec and rvec for all edges
|
|
//
|
|
RoadEdge *edge = NULL;
|
|
RoadEdge *nextEdge = NULL;
|
|
|
|
for ( U32 i = 0; i < mEdges.size() - 1; i++ )
|
|
{
|
|
edge = &mEdges[i];
|
|
nextEdge = &mEdges[i+1];
|
|
|
|
edge->fvec = nextEdge->p1 - edge->p1;
|
|
edge->fvec.normalize();
|
|
|
|
edge->rvec = mCross( edge->fvec, edge->uvec );
|
|
edge->rvec.normalize();
|
|
}
|
|
|
|
// Must do the last edge outside the loop
|
|
RoadEdge *lastEdge = &mEdges[mEdges.size()-1];
|
|
RoadEdge *prevEdge = &mEdges[mEdges.size()-2];
|
|
lastEdge->fvec = prevEdge->fvec;
|
|
lastEdge->rvec = prevEdge->rvec;
|
|
|
|
|
|
//
|
|
// Calculate p0/p2 for all edges
|
|
//
|
|
for ( U32 i = 0; i < mEdges.size(); i++ )
|
|
{
|
|
edge = &mEdges[i];
|
|
edge->p0 = edge->p1 - edge->rvec * edge->width * 0.5f;
|
|
edge->p2 = edge->p1 + edge->rvec * edge->width * 0.5f;
|
|
_getTerrainHeight( edge->p0 );
|
|
_getTerrainHeight( edge->p2 );
|
|
}
|
|
}
|
|
|
|
void DecalRoad::_captureVerts()
|
|
{
|
|
PROFILE_SCOPE( DecalRoad_captureVerts );
|
|
|
|
//Con::warnf( "%s - captureVerts", isServerObject() ? "server" : "client" );
|
|
|
|
if ( isServerObject() )
|
|
{
|
|
//Con::errorf( "DecalRoad::_captureVerts - called on the server side!" );
|
|
return;
|
|
}
|
|
|
|
if ( mEdges.size() == 0 )
|
|
return;
|
|
|
|
//
|
|
// Construct ClippedPolyList objects for each pair
|
|
// of roadEdges.
|
|
// Use them to capture Terrain verts.
|
|
//
|
|
SphereF sphere;
|
|
RoadEdge *edge = NULL;
|
|
RoadEdge *nextEdge = NULL;
|
|
|
|
mTriangleCount = 0;
|
|
mVertCount = 0;
|
|
|
|
Vector<ClippedPolyList> clipperList;
|
|
|
|
for ( U32 i = 0; i < mEdges.size() - 1; i++ )
|
|
{
|
|
Box3F box;
|
|
edge = &mEdges[i];
|
|
nextEdge = &mEdges[i+1];
|
|
|
|
box.minExtents = edge->p1;
|
|
box.maxExtents = edge->p1;
|
|
box.extend( edge->p0 );
|
|
box.extend( edge->p2 );
|
|
box.extend( nextEdge->p0 );
|
|
box.extend( nextEdge->p1 );
|
|
box.extend( nextEdge->p2 );
|
|
box.minExtents.z -= 5.0f;
|
|
box.maxExtents.z += 5.0f;
|
|
|
|
sphere.center = ( nextEdge->p1 + edge->p1 ) * 0.5f;
|
|
sphere.radius = 100.0f; // NOTE: no idea how to calculate this
|
|
|
|
ClippedPolyList clipper;
|
|
clipper.mNormal.set(0.0f, 0.0f, 0.0f);
|
|
VectorF n;
|
|
PlaneF plane0, plane1;
|
|
|
|
// Construct Back Plane
|
|
n = edge->p2 - edge->p0;
|
|
n.normalize();
|
|
n = mCross( n, edge->uvec );
|
|
plane0.set( edge->p0, n );
|
|
clipper.mPlaneList.push_back( plane0 );
|
|
|
|
// Construct Front Plane
|
|
n = nextEdge->p2 - nextEdge->p0;
|
|
n.normalize();
|
|
n = -mCross( edge->uvec, n );
|
|
plane1.set( nextEdge->p0, -n );
|
|
//clipper.mPlaneList.push_back( plane1 );
|
|
|
|
// Test if / where the planes intersect.
|
|
bool discardLeft = false;
|
|
bool discardRight = false;
|
|
Point3F iPos;
|
|
VectorF iDir;
|
|
|
|
if ( plane0.intersect( plane1, iPos, iDir ) )
|
|
{
|
|
Point2F iPos2F( iPos.x, iPos.y );
|
|
Point2F cPos2F( edge->p1.x, edge->p1.y );
|
|
Point2F rVec2F( edge->rvec.x, edge->rvec.y );
|
|
|
|
Point2F iVec2F = iPos2F - cPos2F;
|
|
F32 iLen = iVec2F.len();
|
|
iVec2F.normalize();
|
|
|
|
if ( iLen < edge->width * 0.5f )
|
|
{
|
|
F32 dot = mDot( rVec2F, iVec2F );
|
|
|
|
// The clipping planes intersected on the right side,
|
|
// discard the right side clipping plane.
|
|
if ( dot > 0.0f )
|
|
discardRight = true;
|
|
// The clipping planes intersected on the left side,
|
|
// discard the left side clipping plane.
|
|
else
|
|
discardLeft = true;
|
|
}
|
|
}
|
|
|
|
// Left Plane
|
|
if ( !discardLeft )
|
|
{
|
|
n = ( nextEdge->p0 - edge->p0 );
|
|
n.normalize();
|
|
n = mCross( edge->uvec, n );
|
|
clipper.mPlaneList.push_back( PlaneF(edge->p0, n) );
|
|
}
|
|
else
|
|
{
|
|
nextEdge->p0 = edge->p0;
|
|
}
|
|
|
|
// Right Plane
|
|
if ( !discardRight )
|
|
{
|
|
n = ( nextEdge->p2 - edge->p2 );
|
|
n.normalize();
|
|
n = -mCross( n, edge->uvec );
|
|
clipper.mPlaneList.push_back( PlaneF(edge->p2, -n) );
|
|
}
|
|
else
|
|
{
|
|
nextEdge->p2 = edge->p2;
|
|
}
|
|
|
|
n = nextEdge->p2 - nextEdge->p0;
|
|
n.normalize();
|
|
n = -mCross( edge->uvec, n );
|
|
plane1.set( nextEdge->p0, -n );
|
|
clipper.mPlaneList.push_back( plane1 );
|
|
|
|
// We have constructed the clipping planes,
|
|
// now grab/clip the terrain geometry
|
|
getContainer()->buildPolyList( PLC_Decal, box, TerrainObjectType, &clipper );
|
|
clipper.cullUnusedVerts();
|
|
clipper.triangulate();
|
|
clipper.generateNormals();
|
|
|
|
// If we got something, add it to the ClippedPolyList Vector
|
|
if ( !clipper.isEmpty() && !( smDiscardAll && ( discardRight || discardLeft ) ) )
|
|
{
|
|
clipperList.push_back( clipper );
|
|
|
|
mVertCount += clipper.mVertexList.size();
|
|
mTriangleCount += clipper.mPolyList.size();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set the roadEdge height to be flush with terrain
|
|
// This is not really necessary but makes the debug spline rendering better.
|
|
//
|
|
for ( U32 i = 0; i < mEdges.size() - 1; i++ )
|
|
{
|
|
edge = &mEdges[i];
|
|
|
|
_getTerrainHeight( edge->p0.x, edge->p0.y, edge->p0.z );
|
|
|
|
_getTerrainHeight( edge->p2.x, edge->p2.y, edge->p2.z );
|
|
}
|
|
|
|
//
|
|
// Allocate the RoadBatch(s)
|
|
//
|
|
|
|
// If we captured no verts, then we can return here without
|
|
// allocating any RoadBatches or the Vert/Index Buffers.
|
|
// PreprenderImage will not allocate a render instance while
|
|
// mBatches.size() is zero.
|
|
U32 numClippers = clipperList.size();
|
|
if ( numClippers == 0 )
|
|
return;
|
|
|
|
mBatches.clear();
|
|
|
|
// Allocate the VertexBuffer and PrimitiveBuffer
|
|
mVB.set( GFX, mVertCount, GFXBufferTypeStatic );
|
|
mPB.set( GFX, mTriangleCount * 3, 0, GFXBufferTypeStatic );
|
|
|
|
// Lock the VertexBuffer
|
|
GFXVertexPNTBT *vertPtr = mVB.lock();
|
|
if(!vertPtr) return;
|
|
U32 vertIdx = 0;
|
|
|
|
//
|
|
// Fill the VertexBuffer and vertex data for the RoadBatches
|
|
// Loop through the ClippedPolyList Vector
|
|
//
|
|
RoadBatch *batch = NULL;
|
|
F32 texStart = 0.0f;
|
|
F32 texEnd;
|
|
|
|
for ( U32 i = 0; i < clipperList.size(); i++ )
|
|
{
|
|
ClippedPolyList *clipper = &clipperList[i];
|
|
edge = &mEdges[i];
|
|
nextEdge = &mEdges[i+1];
|
|
|
|
VectorF segFvec = nextEdge->p1 - edge->p1;
|
|
F32 segLen = segFvec.len();
|
|
segFvec.normalize();
|
|
|
|
F32 texLen = segLen / mTextureLength;
|
|
texEnd = texStart + texLen;
|
|
|
|
BiQuadToSqr quadToSquare( Point2F( edge->p0.x, edge->p0.y ),
|
|
Point2F( edge->p2.x, edge->p2.y ),
|
|
Point2F( nextEdge->p2.x, nextEdge->p2.y ),
|
|
Point2F( nextEdge->p0.x, nextEdge->p0.y ) );
|
|
|
|
//
|
|
if ( i % mSegmentsPerBatch == 0 )
|
|
{
|
|
mBatches.increment();
|
|
batch = &mBatches.last();
|
|
|
|
batch->bounds.minExtents = clipper->mVertexList[0].point;
|
|
batch->bounds.maxExtents = clipper->mVertexList[0].point;
|
|
batch->startVert = vertIdx;
|
|
}
|
|
|
|
// Loop through each ClippedPolyList
|
|
for ( U32 j = 0; j < clipper->mVertexList.size(); j++ )
|
|
{
|
|
// Add each vert to the VertexBuffer
|
|
Point3F pos = clipper->mVertexList[j].point;
|
|
vertPtr[vertIdx].point = pos;
|
|
vertPtr[vertIdx].normal = clipper->mNormalList[j];
|
|
|
|
Point2F uv = quadToSquare.transform( Point2F(pos.x,pos.y) );
|
|
vertPtr[vertIdx].texCoord.x = uv.x;
|
|
vertPtr[vertIdx].texCoord.y = -(( texEnd - texStart ) * uv.y + texStart);
|
|
|
|
vertPtr[vertIdx].tangent = mCross( segFvec, clipper->mNormalList[j] );
|
|
vertPtr[vertIdx].binormal = segFvec;
|
|
|
|
vertIdx++;
|
|
|
|
// Expand the RoadBatch bounds to contain this vertex
|
|
batch->bounds.extend( pos );
|
|
}
|
|
|
|
batch->endVert = vertIdx - 1;
|
|
|
|
texStart = texEnd;
|
|
}
|
|
|
|
// Unlock the VertexBuffer, we are done filling it.
|
|
mVB.unlock();
|
|
|
|
// Lock the PrimitiveBuffer
|
|
U16 *idxBuff;
|
|
mPB.lock(&idxBuff);
|
|
U32 curIdx = 0;
|
|
U16 vertOffset = 0;
|
|
batch = NULL;
|
|
S32 batchIdx = -1;
|
|
|
|
// Fill the PrimitiveBuffer
|
|
// Loop through each ClippedPolyList in the Vector
|
|
for ( U32 i = 0; i < clipperList.size(); i++ )
|
|
{
|
|
ClippedPolyList *clipper = &clipperList[i];
|
|
|
|
if ( i % mSegmentsPerBatch == 0 )
|
|
{
|
|
batchIdx++;
|
|
batch = &mBatches[batchIdx];
|
|
batch->startIndex = curIdx;
|
|
}
|
|
|
|
for ( U32 j = 0; j < clipper->mPolyList.size(); j++ )
|
|
{
|
|
// Write indices for each Poly
|
|
ClippedPolyList::Poly *poly = &clipper->mPolyList[j];
|
|
|
|
AssertFatal( poly->vertexCount == 3, "Got non-triangle poly!" );
|
|
|
|
idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart] + vertOffset;
|
|
curIdx++;
|
|
idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart + 1] + vertOffset;
|
|
curIdx++;
|
|
idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart + 2] + vertOffset;
|
|
curIdx++;
|
|
}
|
|
|
|
batch->endIndex = curIdx - 1;
|
|
|
|
vertOffset += clipper->mVertexList.size();
|
|
}
|
|
|
|
// Unlock the PrimitiveBuffer, we are done filling it.
|
|
mPB.unlock();
|
|
|
|
// Generate the object/world bounds
|
|
// Is the union of all batch bounding boxes.
|
|
|
|
Box3F box;
|
|
for ( U32 i = 0; i < mBatches.size(); i++ )
|
|
{
|
|
batch = &mBatches[i];
|
|
|
|
if ( i == 0 )
|
|
box = batch->bounds;
|
|
else
|
|
box.intersect( batch->bounds );
|
|
}
|
|
|
|
mWorldBox = box;
|
|
resetObjectBox();
|
|
|
|
// Make sure we are in the correct bins given our world box.
|
|
if( getSceneManager() != NULL )
|
|
getSceneManager()->notifyObjectDirty( this );
|
|
}
|
|
|
|
U32 DecalRoad::_addNode( const Point3F &pos, F32 width )
|
|
{
|
|
mNodes.increment();
|
|
RoadNode &node = mNodes.last();
|
|
|
|
node.point = pos;
|
|
node.width = width;
|
|
|
|
return mNodes.size() - 1;
|
|
}
|
|
|
|
U32 DecalRoad::_insertNode( const Point3F &pos, const F32 &width, const U32 &idx )
|
|
{
|
|
U32 ret;
|
|
RoadNode *node;
|
|
|
|
if ( idx == U32_MAX )
|
|
{
|
|
mNodes.increment();
|
|
node = &mNodes.last();
|
|
ret = mNodes.size() - 1;
|
|
}
|
|
else
|
|
{
|
|
mNodes.insert( idx );
|
|
node = &mNodes[idx];
|
|
ret = idx;
|
|
}
|
|
|
|
node->point = pos;
|
|
//node->t = -1.0f;
|
|
//node->rot.identity();
|
|
node->width = width;
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool DecalRoad::_getTerrainHeight( Point3F &pos )
|
|
{
|
|
return _getTerrainHeight( pos.x, pos.y, pos.z );
|
|
}
|
|
|
|
bool DecalRoad::_getTerrainHeight( const Point2F &pos, F32 &height )
|
|
{
|
|
return _getTerrainHeight( pos.x, pos.y, height );
|
|
}
|
|
|
|
bool DecalRoad::_getTerrainHeight( const F32 &x, const F32 &y, F32 &height )
|
|
{
|
|
Point3F startPnt( x, y, 10000.0f );
|
|
Point3F endPnt( x, y, -10000.0f );
|
|
|
|
RayInfo ri;
|
|
bool hit;
|
|
|
|
hit = getContainer()->castRay(startPnt, endPnt, TerrainObjectType, &ri);
|
|
|
|
if ( hit )
|
|
height = ri.point.z;
|
|
|
|
return hit;
|
|
}
|
|
|
|
void DecalRoad::_onTerrainChanged( U32 type, TerrainBlock* tblock, const Point2I &min, const Point2I &max )
|
|
{
|
|
// The client side object just stores the area that has changed
|
|
// and waits for the (delayed) update event from the server
|
|
// to actually perform the update.
|
|
if ( isClientObject() && tblock->isClientObject() )
|
|
{
|
|
// Convert the min and max into world space.
|
|
const F32 size = tblock->getSquareSize();
|
|
const Point3F pos = tblock->getPosition();
|
|
|
|
// TODO: I don't think this works right with tiling!
|
|
Box3F dirty( F32( min.x * size ) + pos.x, F32( min.y * size ) + pos.y, -F32_MAX,
|
|
F32( max.x * size ) + pos.x, F32( max.y * size ) + pos.y, F32_MAX );
|
|
|
|
if ( !mTerrainUpdateRect.isValidBox() )
|
|
mTerrainUpdateRect = dirty;
|
|
else
|
|
mTerrainUpdateRect.intersect( dirty );
|
|
}
|
|
// The server object only updates edges (doesn't clip to geometry)
|
|
// and schedules an update to be sent to the client.
|
|
else if ( isServerObject() && tblock->isServerObject() )
|
|
{
|
|
//_generateEdges();
|
|
scheduleUpdate( TerrainChangedMask );
|
|
}
|
|
}
|
|
|
|
|
|
// Static protected field set methods
|
|
|
|
bool DecalRoad::ptSetBreakAngle( void *object, const char *index, const char *data )
|
|
{
|
|
DecalRoad *road = static_cast<DecalRoad*>( object );
|
|
F32 val = dAtof( data );
|
|
|
|
road->setBreakAngle( val );
|
|
|
|
// we already set the field
|
|
return false;
|
|
}
|
|
|
|
bool DecalRoad::ptSetTextureLength( void *object, const char *index, const char *data )
|
|
{
|
|
DecalRoad *road = static_cast<DecalRoad*>( object );
|
|
F32 val = dAtof( data );
|
|
|
|
road->setTextureLength( val );
|
|
|
|
// we already set the field
|
|
return false;
|
|
}
|
|
|
|
|
|
// ConsoleMethods
|
|
|
|
DefineEngineMethod( DecalRoad, regenerate, void, (),,
|
|
"Intended as a helper to developers and editor scripts.\n"
|
|
"Force DecalRoad to update it's spline and reclip geometry."
|
|
)
|
|
{
|
|
object->regenerate();
|
|
}
|
|
|
|
DefineEngineMethod( DecalRoad, postApply, void, (),,
|
|
"Intended as a helper to developers and editor scripts.\n"
|
|
"Force trigger an inspectPostApply. This will transmit "
|
|
"the material and other fields ( not including nodes ) "
|
|
"to client objects."
|
|
)
|
|
{
|
|
object->inspectPostApply();
|
|
}
|