2012-09-19 15:15:01 +00:00
//-----------------------------------------------------------------------------
// 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 ;
2014-11-11 20:15:11 +00:00
mNodeList = list = newlist ;
2012-09-19 15:15:01 +00:00
}
// 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 ( )
2016-10-14 23:16:55 +00:00
: mBreakAngle ( 3.0f ) ,
2012-09-19 15:15:01 +00:00
mSegmentsPerBatch ( 10 ) ,
mTextureLength ( 5.0f ) ,
mRenderPriority ( 10 ) ,
2016-10-14 23:16:55 +00:00
mLoadRenderData ( true ) ,
2012-09-19 15:15:01 +00:00
mMaterial ( NULL ) ,
mMatInst ( NULL ) ,
2020-05-11 19:56:04 +00:00
mTriangleCount ( 0 ) ,
mVertCount ( 0 ) ,
2012-09-19 15:15:01 +00:00
mUpdateEventId ( - 1 ) ,
2020-05-11 19:56:04 +00:00
mLastEvent ( NULL ) ,
2012-09-19 15:15:01 +00:00
mTerrainUpdateRect ( Box3F : : Invalid )
{
// Setup NetObject.
mTypeMask | = StaticObjectType | StaticShapeObjectType ;
2021-01-03 14:58:53 +00:00
mNetFlags . set ( Ghostable ) ;
initMaterialAsset ( Material ) ;
2012-09-19 15:15:01 +00:00
}
DecalRoad : : ~ DecalRoad ( )
{
}
IMPLEMENT_CO_NETOBJECT_V1 ( DecalRoad ) ;
// ConsoleObject
void DecalRoad : : initPersistFields ( )
{
addGroup ( " DecalRoad " ) ;
2021-01-03 14:58:53 +00:00
addProtectedField ( " materialAsset " , TypeMaterialAssetId , Offset ( mMaterialAssetId , DecalRoad ) , & DecalRoad : : _setMaterialAsset , & defaultProtectedGetFn , " Material Asset used for rendering. " ) ;
addProtectedField ( " material " , TypeMaterialName , Offset ( mMaterialName , DecalRoad ) , & DecalRoad : : _setMaterialName , & defaultProtectedGetFn , " Material used for rendering. " ) ;
2012-09-19 15:15:01 +00:00
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.
2021-01-03 14:58:53 +00:00
packMaterialAsset ( con , Material ) ;
2012-09-19 15:15:01 +00:00
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 ( ) )
{
2021-01-03 14:58:53 +00:00
unpackMaterialAsset ( con , Material ) ;
if ( isProperlyAdded ( ) )
_initMaterial ( ) ;
2012-09-19 15:15:01 +00:00
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 ;
2013-11-07 20:07:16 +00:00
const Frustum & frustum = state - > getCameraFrustum ( ) ;
2012-09-19 15:15:01 +00:00
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 ;
The final step (barring any overlooked missing bits, requested refactors, and of course, rolling in dependencies already submitted as PRs) consists of:
renderPrePassMgr.cpp related:
A) shifting .addFeature( MFT_XYZ); calls from ProcessedShaderMaterial::_determineFeatures to ProcessedPrePassMaterial::_determineFeatures
B) mimicking the "// set the XXX if different" entries from RenderMeshMgr::render in RenderPrePassMgr::render
C) fleshing out ProcessedPrePassMaterial::getNumStages() so that it shares a 1:1 correlation with ProcessedShaderMaterial::getNumStages()
D) causing inline void Swizzle<T, mapLength>::ToBuffer( void *destination, const void *source, const dsize_t size ) to silently fail rather than fatally assert if a source or destination buffer is not yet ready to be filled. (support for #customTarget scripted render targets)
Reflections:
A) removing reflectRenderState.disableAdvancedLightingBins(true); entries. this would otherwise early out from prepass and provide no color data whatsoever.
B) removing the fd.features.addFeature( MFT_ForwardShading ); entry forcing all materials to be forward lit when reflected.
C) 2 things best described bluntly as working hacks:
C1) when reflected, a scattersky is rotated PI along it's z then x axis in order to draw properly.
C2) along similar lines, in terraincellmaterial, we shut off culling if it's a prepass material.
Skies: scattersky is given a pair of rotations for reflection purposes, all sky objects are given a z value for depth testing.
2016-02-16 08:50:49 +00:00
coreRI . type = RenderPassManager : : RIT_DecalRoad ;
2012-09-19 15:15:01 +00:00
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
2017-04-11 05:23:14 +00:00
// the deferred bin.
2012-09-19 15:15:01 +00:00
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 ( )
{
2021-01-03 14:58:53 +00:00
if ( mMaterialAsset . notNull ( ) )
{
if ( mMatInst & & String ( mMaterialAsset - > getMaterialDefinitionName ( ) ) . equal ( mMatInst - > getMaterial ( ) - > getName ( ) , String : : NoCase ) )
return ;
SAFE_DELETE ( mMatInst ) ;
Material * tMat = nullptr ;
if ( ! Sim : : findObject ( mMaterialAsset - > getMaterialDefinitionName ( ) , tMat ) )
Con : : errorf ( " DecalRoad::_initMaterial - Material %s was not found. " , mMaterialAsset - > getMaterialDefinitionName ( ) ) ;
mMaterial = tMat ;
2012-09-19 15:15:01 +00:00
2021-01-03 14:58:53 +00:00
if ( mMaterial )
mMatInst = mMaterial - > createMatInstance ( ) ;
else
mMatInst = MATMGR - > createMatInstance ( " WarningMaterial " ) ;
if ( ! mMatInst )
Con : : errorf ( " DecalRoad::_initMaterial - no Material called '%s' " , mMaterialAsset - > getMaterialDefinitionName ( ) ) ;
}
if ( ! mMatInst )
return ;
2012-09-19 15:15:01 +00:00
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 + + )
{
2018-03-14 22:43:03 +00:00
edge = & mEdges [ i ] ;
2012-09-19 15:15:01 +00:00
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
2013-06-30 15:51:38 +00:00
GFXVertexPNTBT * vertPtr = mVB . lock ( ) ;
if ( ! vertPtr ) return ;
2012-09-19 15:15:01 +00:00
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 ] ;
2018-03-14 22:43:03 +00:00
edge = & mEdges [ i ] ;
nextEdge = & mEdges [ i + 1 ] ;
2012-09-19 15:15:01 +00:00
2018-03-14 22:43:03 +00:00
VectorF segFvec = nextEdge - > p1 - edge - > p1 ;
2012-09-19 15:15:01 +00:00
F32 segLen = segFvec . len ( ) ;
segFvec . normalize ( ) ;
F32 texLen = segLen / mTextureLength ;
texEnd = texStart + texLen ;
2018-03-14 22:43:03 +00:00
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 ) ) ;
2012-09-19 15:15:01 +00:00
//
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 + + )
{
2018-03-14 22:43:03 +00:00
batch = & mBatches [ i ] ;
2012-09-19 15:15:01 +00:00
if ( i = = 0 )
2018-03-14 22:43:03 +00:00
box = batch - > bounds ;
2012-09-19 15:15:01 +00:00
else
2018-03-14 22:43:03 +00:00
box . intersect ( batch - > bounds ) ;
2012-09-19 15:15:01 +00:00
}
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 ( ) ;
2014-11-11 20:15:11 +00:00
}