decalRoad.cpp
Engine/source/environment/decalRoad.cpp
Classes:
Public Variables
A bias applied to the nearPlane for Decal and DecalRoad rendering.
bool
Public Functions
ConsoleDocClass(DecalRoad , "@brief A strip shaped decal defined by spine nodes which clips against Terrain <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">objects.\n\n</a>" "<a href="/coding/class/classdecalroad/">DecalRoad</a> is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> representing <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> road or path ( or other inventive things ) across " "<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> TerrainBlock. It renders as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> decal and is therefore only <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> features that do " "not need geometric <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">depth.\n\n</a>" "The <a href="/coding/class/classmaterial/">Material</a> assigned <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/class/classdecalroad/">DecalRoad</a> should tile <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vertically.\n\n</a>" "@ingroup Terrain" )
ConsoleDocClass(DecalRoadNodeEvent , "@brief Sends messages <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the Decal Road <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editor\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
DefineEngineMethod(DecalRoad , postApply , void , () , "Intended as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> helper <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> developers and editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n</a>" "Force trigger an inspectPostApply. This will transmit " "the material and other fields ( not including nodes ) " "<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> client objects." )
DefineEngineMethod(DecalRoad , regenerate , void , () , "Intended as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> helper <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> developers and editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n</a>" "Force <a href="/coding/class/classdecalroad/">DecalRoad</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> update it's spline and reclip geometry." )
Detailed Description
Public Variables
F32 gDecalBias
A bias applied to the nearPlane for Decal and DecalRoad rendering.
Is set by by LevelInfo.
bool gEditingMission
Public Functions
ConsoleDocClass(DecalRoad , "@brief A strip shaped decal defined by spine nodes which clips against Terrain <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">objects.\n\n</a>" "<a href="/coding/class/classdecalroad/">DecalRoad</a> is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> representing <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> road or path ( or other inventive things ) across " "<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> TerrainBlock. It renders as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> decal and is therefore only <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> features that do " "not need geometric <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">depth.\n\n</a>" "The <a href="/coding/class/classmaterial/">Material</a> assigned <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/class/classdecalroad/">DecalRoad</a> should tile <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vertically.\n\n</a>" "@ingroup Terrain" )
ConsoleDocClass(DecalRoadNodeEvent , "@brief Sends messages <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the Decal Road <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editor\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
DefineEngineMethod(DecalRoad , postApply , void , () , "Intended as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> helper <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> developers and editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n</a>" "Force trigger an inspectPostApply. This will transmit " "the material and other fields ( not including nodes ) " "<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> client objects." )
DefineEngineMethod(DecalRoad , regenerate , void , () , "Intended as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> helper <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> developers and editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n</a>" "Force <a href="/coding/class/classdecalroad/">DecalRoad</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> update it's spline and reclip geometry." )
IMPLEMENT_CO_NETEVENT_V1(DecalRoadNodeEvent )
IMPLEMENT_CO_NETOBJECT_V1(DecalRoad )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "environment/decalRoad.h" 26 27#include "console/consoleTypes.h" 28#include "console/engineAPI.h" 29#include "util/catmullRom.h" 30#include "math/util/quadTransforms.h" 31#include "scene/sceneRenderState.h" 32#include "scene/sceneManager.h" 33#include "core/stream/bitStream.h" 34#include "gfx/gfxDrawUtil.h" 35#include "gfx/gfxTransformSaver.h" 36#include "math/mathIO.h" 37#include "math/mathUtils.h" 38#include "terrain/terrData.h" 39#include "materials/materialDefinition.h" 40#include "materials/materialManager.h" 41#include "materials/baseMatInstance.h" 42#include "environment/nodeListManager.h" 43#include "lighting/lightQuery.h" 44 45 46extern F32 gDecalBias; 47extern bool gEditingMission; 48 49 50//----------------------------------------------------------------------------- 51// DecalRoadNodeList Struct 52//----------------------------------------------------------------------------- 53 54struct DecalRoadNodeList : public NodeListManager::NodeList 55{ 56 Vector<Point3F> mPositions; 57 Vector<F32> mWidths; 58 59 DecalRoadNodeList() { } 60 virtual ~DecalRoadNodeList() { } 61}; 62 63//----------------------------------------------------------------------------- 64// DecalRoadNodeEvent Class 65//----------------------------------------------------------------------------- 66 67class DecalRoadNodeEvent : public NodeListEvent 68{ 69 typedef NodeListEvent Parent; 70 71public: 72 Vector<Point3F> mPositions; 73 Vector<F32> mWidths; 74 75public: 76 DecalRoadNodeEvent() { mNodeList = NULL; } 77 virtual ~DecalRoadNodeEvent() { } 78 79 virtual void pack(NetConnection*, BitStream*); 80 virtual void unpack(NetConnection*, BitStream*); 81 82 virtual void copyIntoList(NodeListManager::NodeList* copyInto); 83 virtual void padListToSize(); 84 85 DECLARE_CONOBJECT(DecalRoadNodeEvent); 86}; 87 88void DecalRoadNodeEvent::pack(NetConnection* conn, BitStream* stream) 89{ 90 Parent::pack( conn, stream ); 91 92 stream->writeInt( mPositions.size(), 16 ); 93 94 for (U32 i=0; i<mPositions.size(); ++i) 95 { 96 mathWrite( *stream, mPositions[i] ); 97 stream->write( mWidths[i] ); 98 } 99} 100 101void DecalRoadNodeEvent::unpack(NetConnection* conn, BitStream* stream) 102{ 103 mNodeList = new DecalRoadNodeList(); 104 105 Parent::unpack( conn, stream ); 106 107 U32 count = stream->readInt( 16 ); 108 109 Point3F pos; 110 F32 width; 111 112 DecalRoadNodeList* list = static_cast<DecalRoadNodeList*>(mNodeList); 113 114 for (U32 i=0; i<count; ++i) 115 { 116 mathRead( *stream, &pos ); 117 stream->read( &width ); 118 119 list->mPositions.push_back( pos ); 120 list->mWidths.push_back( width ); 121 } 122 123 list->mTotalValidNodes = count; 124 125 // Do we have a complete list? 126 if (list->mPositions.size() >= mTotalNodes) 127 list->mListComplete = true; 128} 129 130void DecalRoadNodeEvent::copyIntoList(NodeListManager::NodeList* copyInto) 131{ 132 DecalRoadNodeList* prevList = static_cast<DecalRoadNodeList*>(copyInto); 133 DecalRoadNodeList* list = static_cast<DecalRoadNodeList*>(mNodeList); 134 135 // Merge our list with the old list. 136 for (U32 i=<a href="/coding/class/classnodelistevent/#classnodelistevent_1a05350212961bce959f29916230c0209e">mLocalListStart</a>, index=0; i<mLocalListStart+list->mPositions.size(); ++i, ++index) 137 { 138 prevList->mPositions[i] = list->mPositions[index]; 139 prevList->mWidths[i] = list->mWidths[index]; 140 } 141} 142 143void DecalRoadNodeEvent::padListToSize() 144{ 145 DecalRoadNodeList* list = static_cast<DecalRoadNodeList*>(mNodeList); 146 147 U32 totalValidNodes = list->mTotalValidNodes; 148 149 // Pad our list front? 150 if (mLocalListStart) 151 { 152 DecalRoadNodeList* newlist = new DecalRoadNodeList(); 153 newlist->mPositions.increment(mLocalListStart); 154 newlist->mWidths.increment(mLocalListStart); 155 156 newlist->mPositions.merge(list->mPositions); 157 newlist->mWidths.merge(list->mWidths); 158 159 delete list; 160 mNodeList = list = newlist; 161 } 162 163 // Pad our list end? 164 if (list->mPositions.size() < mTotalNodes) 165 { 166 U32 delta = mTotalNodes - list->mPositions.size(); 167 list->mPositions.increment(delta); 168 list->mWidths.increment(delta); 169 } 170 171 list->mTotalValidNodes = totalValidNodes; 172} 173 174IMPLEMENT_CO_NETEVENT_V1(DecalRoadNodeEvent); 175 176ConsoleDocClass( DecalRoadNodeEvent, 177 "@brief Sends messages to the Decal Road Editor\n\n" 178 "Editor use only.\n\n" 179 "@internal" 180); 181//----------------------------------------------------------------------------- 182// DecalRoadNodeListNotify Class 183//----------------------------------------------------------------------------- 184 185class DecalRoadNodeListNotify : public NodeListNotify 186{ 187 typedef NodeListNotify Parent; 188 189protected: 190 SimObjectPtr<DecalRoad> mRoad; 191 192public: 193 DecalRoadNodeListNotify( DecalRoad* road, U32 listId ) { mRoad = road; mListId = listId; } 194 virtual ~DecalRoadNodeListNotify() { mRoad = NULL; } 195 196 virtual void sendNotification( NodeListManager::NodeList* list ); 197}; 198 199void DecalRoadNodeListNotify::sendNotification( NodeListManager::NodeList* list ) 200{ 201 if (mRoad.isValid()) 202 { 203 // Build the road's nodes 204 DecalRoadNodeList* roadList = dynamic_cast<DecalRoadNodeList*>( list ); 205 if (roadList) 206 mRoad->buildNodesFromList( roadList ); 207 } 208} 209 210//----------------------------------------------------------------------------- 211// DecalRoadUpdateEvent Class 212//----------------------------------------------------------------------------- 213 214void DecalRoadUpdateEvent::process( SimObject *object ) 215{ 216 DecalRoad *road = dynamic_cast<DecalRoad*>( object ); 217 AssertFatal( road, "DecalRoadRegenEvent::process - wasn't a DecalRoad" ); 218 219 // Inform clients to perform the update. 220 road->setMaskBits( mMask ); 221 222 if ( !road->isProperlyAdded() ) 223 return; 224 225 // Perform the server side update. 226 if ( mMask & DecalRoad::TerrainChangedMask ) 227 { 228 road->_generateEdges(); 229 } 230 if ( mMask & DecalRoad::GenEdgesMask ) 231 { 232 // Server has already done this. 233 //road->_generateEdges(); 234 } 235 if ( mMask & DecalRoad::ReClipMask ) 236 { 237 // Server does not need to capture verts. 238 road->_captureVerts(); 239 } 240} 241 242 243//------------------------------------------------------------------------------ 244// Class: DecalRoad 245//------------------------------------------------------------------------------ 246 247ConsoleDocClass( DecalRoad, 248 "@brief A strip shaped decal defined by spine nodes which clips against Terrain objects.\n\n" 249 250 "DecalRoad is for representing a road or path ( or other inventive things ) across " 251 "a TerrainBlock. It renders as a decal and is therefore only for features that do " 252 "not need geometric depth.\n\n" 253 254 "The Material assigned to DecalRoad should tile vertically.\n\n" 255 256 "@ingroup Terrain" 257); 258 259// Init Statics 260 261// Static ConsoleVars for toggling debug rendering 262bool DecalRoad::smEditorOpen = false; 263bool DecalRoad::smWireframe = true; 264bool DecalRoad::smShowBatches = false; 265bool DecalRoad::smDiscardAll = false; 266bool DecalRoad::smShowSpline = true; 267bool DecalRoad::smShowRoad = true; 268S32 DecalRoad::smUpdateDelay = 500; 269 270SimObjectPtr<SimSet> DecalRoad::smServerDecalRoadSet = NULL; 271 272 273// Constructors 274 275DecalRoad::DecalRoad() 276 : mBreakAngle( 3.0f ), 277 mSegmentsPerBatch( 10 ), 278 mTextureLength( 5.0f ), 279 mRenderPriority( 10 ), 280 mLoadRenderData( true ), 281 mMaterial( NULL ), 282 mMatInst( NULL ), 283 mTriangleCount(0), 284 mVertCount(0), 285 mUpdateEventId( -1 ), 286 mLastEvent(NULL), 287 mTerrainUpdateRect( Box3F::Invalid ) 288{ 289 // Setup NetObject. 290 mTypeMask |= StaticObjectType | StaticShapeObjectType; 291 mNetFlags.set(Ghostable); 292 293 initMaterialAsset(Material); 294} 295 296DecalRoad::~DecalRoad() 297{ 298} 299 300IMPLEMENT_CO_NETOBJECT_V1(DecalRoad); 301 302 303// ConsoleObject 304 305void DecalRoad::initPersistFields() 306{ 307 addGroup( "DecalRoad" ); 308 309 addProtectedField("materialAsset", TypeMaterialAssetId, Offset(mMaterialAssetId, DecalRoad), &DecalRoad::_setMaterialAsset, &defaultProtectedGetFn, "Material Asset used for rendering."); 310 addProtectedField( "material", TypeMaterialName, Offset( mMaterialName, DecalRoad ), &DecalRoad::_setMaterialName, &defaultProtectedGetFn, "Material used for rendering." ); 311 312 addProtectedField( "textureLength", TypeF32, Offset( mTextureLength, DecalRoad ), &DecalRoad::ptSetTextureLength, &defaultProtectedGetFn, 313 "The length in meters of textures mapped to the DecalRoad" ); 314 315 addProtectedField( "breakAngle", TypeF32, Offset( mBreakAngle, DecalRoad ), &DecalRoad::ptSetBreakAngle, &defaultProtectedGetFn, 316 "Angle in degrees - DecalRoad will subdivided the spline if its curve is greater than this threshold." ); 317 318 addField( "renderPriority", TypeS32, Offset( mRenderPriority, DecalRoad ), 319 "DecalRoad(s) are rendered in descending renderPriority order." ); 320 321 endGroup( "DecalRoad" ); 322 323 addGroup( "Internal" ); 324 325 addProtectedField( "node", TypeString, NULL, &addNodeFromField, &emptyStringProtectedGetFn, 326 "Do not modify, for internal use." ); 327 328 endGroup( "Internal" ); 329 330 Parent::initPersistFields(); 331} 332 333void DecalRoad::consoleInit() 334{ 335 Parent::consoleInit(); 336 337 // Vars for debug rendering while the RoadEditor is open, only used if smEditorOpen is true. 338 Con::addVariable( "$DecalRoad::EditorOpen", TypeBool, &DecalRoad::smEditorOpen, "For use by the Decal Editor.\n\n" 339 "@ingroup Editors\n" ); 340 Con::addVariable( "$DecalRoad::wireframe", TypeBool, &DecalRoad::smWireframe, "For use by the Decal Editor.\n\n" 341 "@ingroup Editors\n" ); 342 Con::addVariable( "$DecalRoad::showBatches", TypeBool, &DecalRoad::smShowBatches, "For use by the Decal Editor.\n\n" 343 "@ingroup Editors\n" ); 344 Con::addVariable( "$DecalRoad::discardAll", TypeBool, &DecalRoad::smDiscardAll, "For use by the Decal Editor.\n\n" 345 "@ingroup Editors\n"); 346 Con::addVariable( "$DecalRoad::showSpline", TypeBool, &DecalRoad::smShowSpline, "For use by the Decal Editor.\n\n" 347 "@ingroup Editors\n" ); 348 Con::addVariable( "$DecalRoad::showRoad", TypeBool, &DecalRoad::smShowRoad, "For use by the Decal Editor.\n\n" 349 "@ingroup Editors\n" ); 350 Con::addVariable( "$DecalRoad::updateDelay", TypeS32, &DecalRoad::smUpdateDelay, "For use by the Decal Editor.\n\n" 351 "@ingroup Editors\n" ); 352} 353 354 355// SimObject 356 357bool DecalRoad::onAdd() 358{ 359 if ( !Parent::onAdd() ) 360 return false; 361 362 // DecalRoad is at position zero when created, 363 // it sets its own position to the first node inside 364 // _generateEdges but until it has at least one node 365 // it will be at 0,0,0. 366 367 MatrixF mat(true); 368 Parent::setTransform( mat ); 369 370 // The client side calculates bounds based on clipped geometry. It would 371 // be wasteful for the server to do this so the server uses global bounds. 372 if ( isServerObject() ) 373 { 374 setGlobalBounds(); 375 resetWorldBox(); 376 } 377 378 // Set the Render Transform. 379 setRenderTransform(mObjToWorld); 380 381 // Add to Scene. 382 addToScene(); 383 384 if ( isServerObject() ) 385 getServerSet()->addObject( this ); 386 387 // 388 TerrainBlock::smUpdateSignal.notify( this, &DecalRoad::_onTerrainChanged ); 389 390 // 391 if ( isClientObject() ) 392 _initMaterial(); 393 394 _generateEdges(); 395 _captureVerts(); 396 397 return true; 398} 399 400void DecalRoad::onRemove() 401{ 402 SAFE_DELETE( mMatInst ); 403 404 TerrainBlock::smUpdateSignal.remove( this, &DecalRoad::_onTerrainChanged ); 405 406 removeFromScene(); 407 408 Parent::onRemove(); 409} 410 411void DecalRoad::inspectPostApply() 412{ 413 Parent::inspectPostApply(); 414 415 setMaskBits( DecalRoadMask ); 416} 417 418void DecalRoad::onStaticModified( const char* slotName, const char*newValue ) 419{ 420 Parent::onStaticModified( slotName, newValue ); 421 422 /* 423 if ( isProperlyAdded() && 424 dStricmp( slotName, "material" ) == 0 ) 425 { 426 setMaskBits( DecalRoadMask ); 427 } 428 */ 429 430 if ( dStricmp( slotName, "renderPriority" ) == 0 ) 431 { 432 mRenderPriority = getMax( dAtoi(newValue), (S32)1 ); 433 } 434} 435 436SimSet* DecalRoad::getServerSet() 437{ 438 if ( !smServerDecalRoadSet ) 439 { 440 smServerDecalRoadSet = new SimSet(); 441 smServerDecalRoadSet->registerObject( "ServerDecalRoadSet" ); 442 Sim::getRootGroup()->addObject( smServerDecalRoadSet ); 443 } 444 445 return smServerDecalRoadSet; 446} 447 448void DecalRoad::writeFields( Stream &stream, U32 tabStop ) 449{ 450 Parent::writeFields( stream, tabStop ); 451 452 // Now write all nodes 453 454 stream.write(2, "\r\n"); 455 456 for ( U32 i = 0; i < mNodes.size(); i++ ) 457 { 458 const RoadNode &node = mNodes[i]; 459 460 stream.writeTabs(tabStop); 461 462 char buffer[1024]; 463 dMemset( buffer, 0, 1024 ); 464 dSprintf( buffer, 1024, "Node = \"%f %f %f %f\";", node.point.x, node.point.y, node.point.z, node.width ); 465 stream.writeLine( (const U8*)buffer ); 466 } 467} 468 469bool DecalRoad::writeField( StringTableEntry fieldname, const char *value ) 470{ 471 if ( fieldname == StringTable->insert("node") ) 472 return false; 473 474 return Parent::writeField( fieldname, value ); 475} 476 477void DecalRoad::onEditorEnable() 478{ 479} 480 481void DecalRoad::onEditorDisable() 482{ 483} 484 485 486// NetObject 487 488U32 DecalRoad::packUpdate(NetConnection * con, U32 mask, BitStream * stream) 489{ 490 // Pack Parent. 491 U32 retMask = Parent::packUpdate(con, mask, stream); 492 493 if ( stream->writeFlag( mask & DecalRoadMask ) ) 494 { 495 // Write Texture Name. 496 packMaterialAsset(con, Material); 497 498 stream->write( mBreakAngle ); 499 500 stream->write( mSegmentsPerBatch ); 501 502 stream->write( mTextureLength ); 503 504 stream->write( mRenderPriority ); 505 } 506 507 if ( stream->writeFlag( mask & NodeMask ) ) 508 { 509 //stream->writeInt( mNodes.size(), 16 ); 510 511 //for ( U32 i = 0; i < mNodes.size(); i++ ) 512 //{ 513 // mathWrite( *stream, mNodes[i].point ); 514 // stream->write( mNodes[i].width ); 515 //} 516 517 const U32 nodeByteSize = 16; // Based on sending all of a node's parameters 518 519 // Test if we can fit all of our nodes within the current stream. 520 // We make sure we leave 100 bytes still free in the stream for whatever 521 // may follow us. 522 S32 allowedBytes = stream->getWriteByteSize() - 100; 523 if ( stream->writeFlag( (nodeByteSize * mNodes.size()) < allowedBytes ) ) 524 { 525 // All nodes should fit, so send them out now. 526 stream->writeInt( mNodes.size(), 16 ); 527 528 for ( U32 i = 0; i < mNodes.size(); i++ ) 529 { 530 mathWrite( *stream, mNodes[i].point ); 531 stream->write( mNodes[i].width ); 532 } 533 } 534 else 535 { 536 // There isn't enough space left in the stream for all of the 537 // nodes. Batch them up into NetEvents. 538 U32 id = gServerNodeListManager->nextListId(); 539 U32 count = 0; 540 U32 index = 0; 541 while (count < mNodes.size()) 542 { 543 count += NodeListManager::smMaximumNodesPerEvent; 544 if (count > mNodes.size()) 545 { 546 count = mNodes.size(); 547 } 548 549 DecalRoadNodeEvent* event = new DecalRoadNodeEvent(); 550 event->mId = id; 551 event->mTotalNodes = mNodes.size(); 552 event->mLocalListStart = index; 553 554 for (; index<count; ++index) 555 { 556 event->mPositions.push_back( mNodes[index].point ); 557 event->mWidths.push_back( mNodes[index].width ); 558 } 559 560 con->postNetEvent( event ); 561 } 562 563 stream->write( id ); 564 } 565 } 566 567 stream->writeFlag( mask & GenEdgesMask ); 568 569 stream->writeFlag( mask & ReClipMask ); 570 571 stream->writeFlag( mask & TerrainChangedMask ); 572 573 // Were done ... 574 return retMask; 575} 576 577void DecalRoad::unpackUpdate( NetConnection *con, BitStream *stream ) 578{ 579 // Unpack Parent. 580 Parent::unpackUpdate( con, stream ); 581 582 // DecalRoadMask 583 if ( stream->readFlag() ) 584 { 585 unpackMaterialAsset(con, Material); 586 587 if (isProperlyAdded()) 588 _initMaterial(); 589 590 stream->read( &mBreakAngle ); 591 592 stream->read( &mSegmentsPerBatch ); 593 594 stream->read( &mTextureLength ); 595 596 stream->read( &mRenderPriority ); 597 } 598 599 // NodeMask 600 if ( stream->readFlag() ) 601 { 602 //U32 count = stream->readInt( 16 ); 603 604 //mNodes.clear(); 605 606 //Point3F pos; 607 //F32 width; 608 //for ( U32 i = 0; i < count; i++ ) 609 //{ 610 // mathRead( *stream, &pos ); 611 // stream->read( &width ); 612 // _addNode( pos, width ); 613 //} 614 615 if (stream->readFlag()) 616 { 617 // Nodes have been passed in this update 618 U32 count = stream->readInt( 16 ); 619 620 mNodes.clear(); 621 622 Point3F pos; 623 F32 width; 624 for ( U32 i = 0; i < count; i++ ) 625 { 626 mathRead( *stream, &pos ); 627 stream->read( &width ); 628 _addNode( pos, width ); 629 } 630 } 631 else 632 { 633 // Nodes will arrive as events 634 U32 id; 635 stream->read( &id ); 636 637 // Check if the road's nodes made it here before we did. 638 NodeListManager::NodeList* list = NULL; 639 if ( gClientNodeListManager->findListById( id, &list, true) ) 640 { 641 // Work with the completed list 642 DecalRoadNodeList* roadList = dynamic_cast<DecalRoadNodeList*>( list ); 643 if (roadList) 644 buildNodesFromList( roadList ); 645 646 delete list; 647 } 648 else 649 { 650 // Nodes have not yet arrived, so register our interest in the list 651 DecalRoadNodeListNotify* notify = new DecalRoadNodeListNotify( this, id ); 652 gClientNodeListManager->registerNotification( notify ); 653 } 654 } 655 } 656 657 // GenEdgesMask 658 if ( stream->readFlag() && isProperlyAdded() ) 659 _generateEdges(); 660 661 // ReClipMask 662 if ( stream->readFlag() && isProperlyAdded() ) 663 _captureVerts(); 664 665 // TerrainChangedMask 666 if ( stream->readFlag() ) 667 { 668 if ( isProperlyAdded() ) 669 { 670 if ( mTerrainUpdateRect.isOverlapped( getWorldBox() ) ) 671 { 672 _generateEdges(); 673 _captureVerts(); 674 // Clear out the mTerrainUpdateRect since we have updated its 675 // region and we now need to store future terrain changes 676 // in it. 677 mTerrainUpdateRect = Box3F::Invalid; 678 } 679 } 680 } 681} 682 683void DecalRoad::prepRenderImage( SceneRenderState* state ) 684{ 685 PROFILE_SCOPE( DecalRoad_prepRenderImage ); 686 687 if ( mNodes.size() <= 1 || 688 mBatches.size() == 0 || 689 !mMatInst || 690 state->isShadowPass() ) 691 return; 692 693 // If we don't have a material instance after the override then 694 // we can skip rendering all together. 695 BaseMatInstance *matInst = state->getOverrideMaterial( mMatInst ); 696 if ( !matInst ) 697 return; 698 699 RenderPassManager *renderPass = state->getRenderPass(); 700 701 // Debug RenderInstance 702 // Only when editor is open. 703 if ( smEditorOpen ) 704 { 705 ObjectRenderInst *ri = renderPass->allocInst<ObjectRenderInst>(); 706 ri->type = RenderPassManager::RIT_Editor; 707 ri->renderDelegate.bind( this, &DecalRoad::_debugRender ); 708 state->getRenderPass()->addInst( ri ); 709 } 710 711 // Normal Road RenderInstance 712 // Always rendered when the editor is not open 713 // otherwise obey the smShowRoad flag 714 if ( !smShowRoad && smEditorOpen ) 715 return; 716 717 const Frustum &frustum = state->getCameraFrustum(); 718 719 MeshRenderInst coreRI; 720 coreRI.clear(); 721 coreRI.objectToWorld = &MatrixF::Identity; 722 coreRI.worldToCamera = renderPass->allocSharedXform(RenderPassManager::View); 723 724 MatrixF *tempMat = renderPass->allocUniqueXform( MatrixF( true ) ); 725 MathUtils::getZBiasProjectionMatrix( gDecalBias, frustum, tempMat ); 726 coreRI.projection = tempMat; 727 728 coreRI.type = RenderPassManager::RIT_DecalRoad; 729 coreRI.vertBuff = &mVB; 730 coreRI.primBuff = &mPB; 731 coreRI.matInst = matInst; 732 733 // Make it the sort distance the max distance so that 734 // it renders after all the other opaque geometry in 735 // the deferred bin. 736 coreRI.sortDistSq = F32_MAX; 737 738 // If we need lights then set them up. 739 if ( matInst->isForwardLit() ) 740 { 741 LightQuery query; 742 query.init( getWorldSphere() ); 743 query.getLights( coreRI.lights, 8 ); 744 } 745 746 U32 startBatchIdx = -1; 747 U32 endBatchIdx = 0; 748 749 for ( U32 i = 0; i < mBatches.size(); i++ ) 750 { 751 const RoadBatch &batch = mBatches[i]; 752 const bool isVisible = !frustum.isCulled( batch.bounds ); 753 if ( isVisible ) 754 { 755 // If this is the start of a set of batches. 756 if ( startBatchIdx == -1 ) 757 endBatchIdx = startBatchIdx = i; 758 759 // Else we're extending the end batch index. 760 else 761 ++endBatchIdx; 762 763 // If this isn't the last batch then continue. 764 if ( i < mBatches.size()-1 ) 765 continue; 766 } 767 768 // We we still don't have a start batch, so skip. 769 if ( startBatchIdx == -1 ) 770 continue; 771 772 // Render this set of batches. 773 const RoadBatch &startBatch = mBatches[startBatchIdx]; 774 const RoadBatch &endBatch = mBatches[endBatchIdx]; 775 776 U32 startVert = startBatch.startVert; 777 U32 startIdx = startBatch.startIndex; 778 U32 vertCount = endBatch.endVert - startVert; 779 U32 idxCount = ( endBatch.endIndex - startIdx ) + 1; 780 U32 triangleCount = idxCount / 3; 781 782 AssertFatal( startVert + vertCount <= mVertCount, "DecalRoad, bad draw call!" ); 783 AssertFatal( startIdx + triangleCount < mTriangleCount * 3, "DecalRoad, bad draw call!" ); 784 785 MeshRenderInst *ri = renderPass->allocInst<MeshRenderInst>(); 786 787 *ri = coreRI; 788 789 ri->prim = renderPass->allocPrim(); 790 ri->prim->type = GFXTriangleList; 791 ri->prim->minIndex = 0; 792 ri->prim->startIndex = startIdx; 793 ri->prim->numPrimitives = triangleCount; 794 ri->prim->startVertex = 0; 795 ri->prim->numVertices = endBatch.endVert + 1; 796 797 // For sorting we first sort by render priority 798 // and then by objectId. 799 // 800 // Since a road can submit more than one render instance, we want all 801 // draw calls for a single road to occur consecutively, since they 802 // could use the same vertex buffer. 803 ri->defaultKey = mRenderPriority << 0 | mId << 16; 804 ri->defaultKey2 = 0; 805 806 renderPass->addInst( ri ); 807 808 // Reset the batching. 809 startBatchIdx = -1; 810 } 811} 812 813void DecalRoad::setTransform( const MatrixF &mat ) 814{ 815 // We ignore transform requests from the editor 816 // right now. 817} 818 819void DecalRoad::setScale( const VectorF &scale ) 820{ 821 // We ignore scale requests from the editor 822 // right now. 823} 824 825 826// DecalRoad Public Methods 827 828bool DecalRoad::getClosestNode( const Point3F &pos, U32 &idx ) 829{ 830 F32 closestDist = F32_MAX; 831 832 for ( U32 i = 0; i < mNodes.size(); i++ ) 833 { 834 F32 dist = ( mNodes[i].point - pos ).len(); 835 if ( dist < closestDist ) 836 { 837 closestDist = dist; 838 idx = i; 839 } 840 } 841 842 return closestDist != F32_MAX; 843} 844 845bool DecalRoad::containsPoint( const Point3F &worldPos, U32 *nodeIdx ) const 846{ 847 // This is just for making selections in the editor, we use the 848 // client-side road because it has the proper edge's. 849 if ( isServerObject() && getClientObject() ) 850 return ((DecalRoad*)getClientObject())->containsPoint( worldPos, nodeIdx ); 851 852 // If point isn't in the world box, 853 // it's definitely not in the road. 854 //if ( !getWorldBox().isContained( worldPos ) ) 855 // return false; 856 857 if ( mEdges.size() < 2 ) 858 return false; 859 860 Point2F testPt( worldPos.x, 861 worldPos.y ); 862 Point2F poly[4]; 863 864 // Look through all edges, does the polygon 865 // formed from adjacent edge's contain the worldPos? 866 for ( U32 i = 0; i < mEdges.size() - 1; i++ ) 867 { 868 const RoadEdge &edge0 = mEdges[i]; 869 const RoadEdge &edge1 = mEdges[i+1]; 870 871 poly[0].set( edge0.p0.x, edge0.p0.y ); 872 poly[1].set( edge0.p2.x, edge0.p2.y ); 873 poly[2].set( edge1.p2.x, edge1.p2.y ); 874 poly[3].set( edge1.p0.x, edge1.p0.y ); 875 876 if ( MathUtils::pointInPolygon( poly, 4, testPt ) ) 877 { 878 if ( nodeIdx ) 879 *nodeIdx = edge0.parentNodeIdx; 880 881 return true; 882 } 883 884 } 885 886 return false; 887} 888 889bool DecalRoad::castray( const Point3F &start, const Point3F &end ) const 890{ 891 // We just cast against the object box for the editor. 892 return mWorldBox.collideLine( start, end ); 893} 894 895Point3F DecalRoad::getNodePosition( U32 idx ) 896{ 897 if ( mNodes.size() - 1 < idx ) 898 return Point3F(); 899 900 return mNodes[idx].point; 901} 902 903void DecalRoad::setNodePosition( U32 idx, const Point3F &pos ) 904{ 905 if ( mNodes.size() - 1 < idx ) 906 return; 907 908 mNodes[idx].point = pos; 909 910 _generateEdges(); 911 scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask ); 912} 913 914U32 DecalRoad::addNode( const Point3F &pos, F32 width ) 915{ 916 U32 idx = _addNode( pos, width ); 917 918 _generateEdges(); 919 scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask ); 920 921 return idx; 922} 923 924U32 DecalRoad::insertNode(const Point3F &pos, const F32 &width, const U32 &idx) 925{ 926 U32 ret = _insertNode( pos, width, idx ); 927 928 _generateEdges(); 929 scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask ); 930 931 return ret; 932} 933 934void DecalRoad::setNodeWidth( U32 idx, F32 width ) 935{ 936 if ( mNodes.size() - 1 < idx ) 937 return; 938 939 mNodes[idx].width = width; 940 941 _generateEdges(); 942 scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask ); 943} 944 945F32 DecalRoad::getNodeWidth( U32 idx ) 946{ 947 if ( mNodes.size() - 1 < idx ) 948 return -1.0f; 949 950 return mNodes[idx].width; 951} 952 953void DecalRoad::deleteNode( U32 idx ) 954{ 955 if ( mNodes.size() - 1 < idx ) 956 return; 957 958 mNodes.erase(idx); 959 960 _generateEdges(); 961 scheduleUpdate( GenEdgesMask | ReClipMask | NodeMask ); 962} 963 964void DecalRoad::buildNodesFromList( DecalRoadNodeList* list ) 965{ 966 mNodes.clear(); 967 968 for (U32 i=0; i<list->mPositions.size(); ++i) 969 { 970 _addNode( list->mPositions[i], list->mWidths[i] ); 971 } 972 973 _generateEdges(); 974 _captureVerts(); 975} 976 977void DecalRoad::setTextureLength( F32 meters ) 978{ 979 meters = getMax( meters, 0.1f ); 980 if ( mTextureLength == meters ) 981 return; 982 983 mTextureLength = meters; 984 985 _generateEdges(); 986 scheduleUpdate( DecalRoadMask | ReClipMask ); 987} 988 989void DecalRoad::setBreakAngle( F32 degrees ) 990{ 991 //meters = getMax( meters, MIN_METERS_PER_SEGMENT ); 992 //if ( mBreakAngle == meters ) 993 // return; 994 995 mBreakAngle = degrees; 996 997 _generateEdges(); 998 scheduleUpdate( DecalRoadMask | GenEdgesMask | ReClipMask ); 999} 1000 1001void DecalRoad::scheduleUpdate( U32 updateMask ) 1002{ 1003 scheduleUpdate( updateMask, smUpdateDelay, true ); 1004} 1005 1006void DecalRoad::scheduleUpdate( U32 updateMask, U32 delayMs, bool restartTimer ) 1007{ 1008 if ( Sim::isEventPending( mUpdateEventId ) ) 1009 { 1010 if ( !restartTimer ) 1011 { 1012 mLastEvent->mMask |= updateMask; 1013 return; 1014 } 1015 else 1016 { 1017 Sim::cancelEvent( mUpdateEventId ); 1018 } 1019 } 1020 1021 mLastEvent = new DecalRoadUpdateEvent( updateMask, delayMs ); 1022 mUpdateEventId = Sim::postEvent( this, mLastEvent, Sim::getCurrentTime() + delayMs ); 1023} 1024 1025void DecalRoad::regenerate() 1026{ 1027 _generateEdges(); 1028 _captureVerts(); 1029 setMaskBits( NodeMask | GenEdgesMask | ReClipMask ); 1030} 1031 1032bool DecalRoad::addNodeFromField( void *object, const char *index, const char *data ) 1033{ 1034 DecalRoad *pObj = static_cast<DecalRoad*>(object); 1035 1036 F32 x,y,z,width; 1037 U32 result = dSscanf( data, "%f %f %f %f", &x, &y, &z, &width ); 1038 if ( result == 4 ) 1039 pObj->_addNode( Point3F(x,y,z), width ); 1040 1041 return false; 1042} 1043 1044 1045// Internal Helper Methods 1046 1047void DecalRoad::_initMaterial() 1048{ 1049 if (mMaterialAsset.notNull()) 1050 { 1051 if (mMatInst && String(mMaterialAsset->getMaterialDefinitionName()).equal(mMatInst->getMaterial()->getName(), String::NoCase)) 1052 return; 1053 1054 SAFE_DELETE(mMatInst); 1055 1056 Material* tMat = nullptr; 1057 1058 if (!Sim::findObject(mMaterialAsset->getMaterialDefinitionName(), tMat)) 1059 Con::errorf("DecalRoad::_initMaterial - Material %s was not found.", mMaterialAsset->getMaterialDefinitionName()); 1060 1061 mMaterial = tMat; 1062 1063 if (mMaterial) 1064 mMatInst = mMaterial->createMatInstance(); 1065 else 1066 mMatInst = MATMGR->createMatInstance("WarningMaterial"); 1067 1068 if (!mMatInst) 1069 Con::errorf("DecalRoad::_initMaterial - no Material called '%s'", mMaterialAsset->getMaterialDefinitionName()); 1070 } 1071 1072 if (!mMatInst) 1073 return; 1074 1075 GFXStateBlockDesc desc; 1076 desc.setZReadWrite( true, false ); 1077 mMatInst->addStateBlockDesc( desc ); 1078 1079 mMatInst->init( MATMGR->getDefaultFeatures(), getGFXVertexFormat<GFXVertexPNTBT>() ); 1080} 1081 1082void DecalRoad::_debugRender( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* ) 1083{ 1084 //if ( mStateBlock.isNull() ) 1085 // return; 1086 1087 GFX->enterDebugEvent( ColorI( 255, 0, 0 ), "DecalRoad_debugRender" ); 1088 GFXTransformSaver saver; 1089 1090 //GFX->setStateBlock( mStateBlock ); 1091 1092 Point3F size(1,1,1); 1093 ColorI color( 255, 0, 0, 255 ); 1094 1095 GFXStateBlockDesc desc; 1096 desc.setZReadWrite( true, false ); 1097 desc.setBlend( true ); 1098 desc.fillMode = GFXFillWireframe; 1099 1100 if ( smShowBatches ) 1101 { 1102 for ( U32 i = 0; i < mBatches.size(); i++ ) 1103 { 1104 const Box3F &box = mBatches[i].bounds; 1105 GFX->getDrawUtil()->drawCube( desc, box, ColorI(255,100,100,255) ); 1106 } 1107 } 1108 1109 //GFX->leaveDebugEvent(); 1110} 1111 1112void DecalRoad::_generateEdges() 1113{ 1114 PROFILE_SCOPE( DecalRoad_generateEdges ); 1115 1116 //Con::warnf( "%s - generateEdges", isServerObject() ? "server" : "client" ); 1117 1118 if ( mNodes.size() > 0 ) 1119 { 1120 // Set our object position to the first node. 1121 const Point3F &nodePt = mNodes.first().point; 1122 MatrixF mat( true ); 1123 mat.setPosition( nodePt ); 1124 Parent::setTransform( mat ); 1125 1126 // The server object has global bounds, which Parent::setTransform 1127 // messes up so we must reset it. 1128 if ( isServerObject() ) 1129 { 1130 mObjBox.minExtents.set(-1e10, -1e10, -1e10); 1131 mObjBox.maxExtents.set( 1e10, 1e10, 1e10); 1132 } 1133 } 1134 1135 1136 if ( mNodes.size() < 2 ) 1137 return; 1138 1139 // Ensure nodes are above the terrain height at their xy position 1140 for ( U32 i = 0; i < mNodes.size(); i++ ) 1141 { 1142 _getTerrainHeight( mNodes[i].point ); 1143 } 1144 1145 // Now start generating edges... 1146 1147 U32 nodeCount = mNodes.size(); 1148 Point3F *positions = new Point3F[nodeCount]; 1149 1150 for ( U32 i = 0; i < nodeCount; i++ ) 1151 { 1152 const RoadNode &node = mNodes[i]; 1153 positions[i].set( node.point.x, node.point.y, node.width ); 1154 } 1155 1156 CatmullRom<Point3F> spline; 1157 spline.initialize( nodeCount, positions ); 1158 delete [] positions; 1159 1160 mEdges.clear(); 1161 1162 Point3F lastBreakVector(0,0,0); 1163 RoadEdge slice; 1164 Point3F lastBreakNode; 1165 lastBreakNode = spline.evaluate(0.0f); 1166 1167 for ( U32 i = 1; i < mNodes.size(); i++ ) 1168 { 1169 F32 t1 = spline.getTime(i); 1170 F32 t0 = spline.getTime(i-1); 1171 1172 F32 segLength = spline.arcLength( t0, t1 ); 1173 1174 U32 numSegments = mCeil( segLength / MIN_METERS_PER_SEGMENT ); 1175 numSegments = getMax( numSegments, (U32)1 ); 1176 F32 tstep = ( t1 - t0 ) / numSegments; 1177 1178 U32 startIdx = 0; 1179 U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments; 1180 1181 for ( U32 j = startIdx; j < endIdx; j++ ) 1182 { 1183 F32 t = t0 + tstep * j; 1184 Point3F splineNode = spline.evaluate(t); 1185 F32 width = splineNode.z; 1186 _getTerrainHeight( splineNode ); 1187 1188 Point3F toNodeVec = splineNode - lastBreakNode; 1189 toNodeVec.normalizeSafe(); 1190 1191 if ( lastBreakVector.isZero() ) 1192 lastBreakVector = toNodeVec; 1193 1194 F32 angle = mRadToDeg( mAcos( mDot( toNodeVec, lastBreakVector ) ) ); 1195 1196 if ( j == startIdx || 1197 ( j == endIdx - 1 && i == mNodes.size() - 1 ) || 1198 angle > mBreakAngle ) 1199 { 1200 // Push back a spline node 1201 //slice.p1.set( splineNode.x, splineNode.y, 0.0f ); 1202 //_getTerrainHeight( slice.p1 ); 1203 slice.p1 = splineNode; 1204 slice.uvec.set(0,0,1); 1205 slice.width = width; 1206 slice.parentNodeIdx = i-1; 1207 mEdges.push_back( slice ); 1208 1209 lastBreakVector = splineNode - lastBreakNode; 1210 lastBreakVector.normalizeSafe(); 1211 1212 lastBreakNode = splineNode; 1213 } 1214 } 1215 } 1216 1217 /* 1218 for ( U32 i = 1; i < nodeCount; i++ ) 1219 { 1220 F32 t0 = spline.getTime( i-1 ); 1221 F32 t1 = spline.getTime( i ); 1222 1223 F32 segLength = spline.arcLength( t0, t1 ); 1224 1225 U32 numSegments = mCeil( segLength / mBreakAngle ); 1226 numSegments = getMax( numSegments, (U32)1 ); 1227 F32 tstep = ( t1 - t0 ) / numSegments; 1228 1229 AssertFatal( numSegments > 0, "DecalRoad::_generateEdges, got zero segments!" ); 1230 1231 U32 startIdx = 0; 1232 U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments; 1233 1234 for ( U32 j = startIdx; j < endIdx; j++ ) 1235 { 1236 F32 t = t0 + tstep * j; 1237 Point3F val = spline.evaluate(t); 1238 1239 RoadEdge edge; 1240 edge.p1.set( val.x, val.y, 0.0f ); 1241 _getTerrainHeight( val.x, val.y, edge.p1.z ); 1242 edge.uvec.set(0,0,1); 1243 edge.width = val.z; 1244 edge.parentNodeIdx = i-1; 1245 mEdges.push_back( edge ); 1246 } 1247 } 1248 */ 1249 1250 // 1251 // Calculate fvec and rvec for all edges 1252 // 1253 RoadEdge *edge = NULL; 1254 RoadEdge *nextEdge = NULL; 1255 1256 for ( U32 i = 0; i < mEdges.size() - 1; i++ ) 1257 { 1258 edge = &mEdges[i]; 1259 nextEdge = &mEdges[i+1]; 1260 1261 edge->fvec = nextEdge->p1 - edge->p1; 1262 edge->fvec.normalize(); 1263 1264 edge->rvec = mCross( edge->fvec, edge->uvec ); 1265 edge->rvec.normalize(); 1266 } 1267 1268 // Must do the last edge outside the loop 1269 RoadEdge *lastEdge = &mEdges[mEdges.size()-1]; 1270 RoadEdge *prevEdge = &mEdges[mEdges.size()-2]; 1271 lastEdge->fvec = prevEdge->fvec; 1272 lastEdge->rvec = prevEdge->rvec; 1273 1274 1275 // 1276 // Calculate p0/p2 for all edges 1277 // 1278 for ( U32 i = 0; i < mEdges.size(); i++ ) 1279 { 1280 edge = &mEdges[i]; 1281 edge->p0 = edge->p1 - edge->rvec * edge->width * 0.5f; 1282 edge->p2 = edge->p1 + edge->rvec * edge->width * 0.5f; 1283 _getTerrainHeight( edge->p0 ); 1284 _getTerrainHeight( edge->p2 ); 1285 } 1286} 1287 1288void DecalRoad::_captureVerts() 1289{ 1290 PROFILE_SCOPE( DecalRoad_captureVerts ); 1291 1292 //Con::warnf( "%s - captureVerts", isServerObject() ? "server" : "client" ); 1293 1294 if ( isServerObject() ) 1295 { 1296 //Con::errorf( "DecalRoad::_captureVerts - called on the server side!" ); 1297 return; 1298 } 1299 1300 if ( mEdges.size() == 0 ) 1301 return; 1302 1303 // 1304 // Construct ClippedPolyList objects for each pair 1305 // of roadEdges. 1306 // Use them to capture Terrain verts. 1307 // 1308 SphereF sphere; 1309 RoadEdge *edge = NULL; 1310 RoadEdge *nextEdge = NULL; 1311 1312 mTriangleCount = 0; 1313 mVertCount = 0; 1314 1315 Vector<ClippedPolyList> clipperList; 1316 1317 for ( U32 i = 0; i < mEdges.size() - 1; i++ ) 1318 { 1319 Box3F box; 1320 edge = &mEdges[i]; 1321 nextEdge = &mEdges[i+1]; 1322 1323 box.minExtents = edge->p1; 1324 box.maxExtents = edge->p1; 1325 box.extend( edge->p0 ); 1326 box.extend( edge->p2 ); 1327 box.extend( nextEdge->p0 ); 1328 box.extend( nextEdge->p1 ); 1329 box.extend( nextEdge->p2 ); 1330 box.minExtents.z -= 5.0f; 1331 box.maxExtents.z += 5.0f; 1332 1333 sphere.center = ( nextEdge->p1 + edge->p1 ) * 0.5f; 1334 sphere.radius = 100.0f; // NOTE: no idea how to calculate this 1335 1336 ClippedPolyList clipper; 1337 clipper.mNormal.set(0.0f, 0.0f, 0.0f); 1338 VectorF n; 1339 PlaneF plane0, plane1; 1340 1341 // Construct Back Plane 1342 n = edge->p2 - edge->p0; 1343 n.normalize(); 1344 n = mCross( n, edge->uvec ); 1345 plane0.set( edge->p0, n ); 1346 clipper.mPlaneList.push_back( plane0 ); 1347 1348 // Construct Front Plane 1349 n = nextEdge->p2 - nextEdge->p0; 1350 n.normalize(); 1351 n = -mCross( edge->uvec, n ); 1352 plane1.set( nextEdge->p0, -n ); 1353 //clipper.mPlaneList.push_back( plane1 ); 1354 1355 // Test if / where the planes intersect. 1356 bool discardLeft = false; 1357 bool discardRight = false; 1358 Point3F iPos; 1359 VectorF iDir; 1360 1361 if ( plane0.intersect( plane1, iPos, iDir ) ) 1362 { 1363 Point2F iPos2F( iPos.x, iPos.y ); 1364 Point2F cPos2F( edge->p1.x, edge->p1.y ); 1365 Point2F rVec2F( edge->rvec.x, edge->rvec.y ); 1366 1367 Point2F iVec2F = iPos2F - cPos2F; 1368 F32 iLen = iVec2F.len(); 1369 iVec2F.normalize(); 1370 1371 if ( iLen < edge->width * 0.5f ) 1372 { 1373 F32 dot = mDot( rVec2F, iVec2F ); 1374 1375 // The clipping planes intersected on the right side, 1376 // discard the right side clipping plane. 1377 if ( dot > 0.0f ) 1378 discardRight = true; 1379 // The clipping planes intersected on the left side, 1380 // discard the left side clipping plane. 1381 else 1382 discardLeft = true; 1383 } 1384 } 1385 1386 // Left Plane 1387 if ( !discardLeft ) 1388 { 1389 n = ( nextEdge->p0 - edge->p0 ); 1390 n.normalize(); 1391 n = mCross( edge->uvec, n ); 1392 clipper.mPlaneList.push_back( PlaneF(edge->p0, n) ); 1393 } 1394 else 1395 { 1396 nextEdge->p0 = edge->p0; 1397 } 1398 1399 // Right Plane 1400 if ( !discardRight ) 1401 { 1402 n = ( nextEdge->p2 - edge->p2 ); 1403 n.normalize(); 1404 n = -mCross( n, edge->uvec ); 1405 clipper.mPlaneList.push_back( PlaneF(edge->p2, -n) ); 1406 } 1407 else 1408 { 1409 nextEdge->p2 = edge->p2; 1410 } 1411 1412 n = nextEdge->p2 - nextEdge->p0; 1413 n.normalize(); 1414 n = -mCross( edge->uvec, n ); 1415 plane1.set( nextEdge->p0, -n ); 1416 clipper.mPlaneList.push_back( plane1 ); 1417 1418 // We have constructed the clipping planes, 1419 // now grab/clip the terrain geometry 1420 getContainer()->buildPolyList( PLC_Decal, box, TerrainObjectType, &clipper ); 1421 clipper.cullUnusedVerts(); 1422 clipper.triangulate(); 1423 clipper.generateNormals(); 1424 1425 // If we got something, add it to the ClippedPolyList Vector 1426 if ( !clipper.isEmpty() && !( smDiscardAll && ( discardRight || discardLeft ) ) ) 1427 { 1428 clipperList.push_back( clipper ); 1429 1430 mVertCount += clipper.mVertexList.size(); 1431 mTriangleCount += clipper.mPolyList.size(); 1432 } 1433 } 1434 1435 // 1436 // Set the roadEdge height to be flush with terrain 1437 // This is not really necessary but makes the debug spline rendering better. 1438 // 1439 for ( U32 i = 0; i < mEdges.size() - 1; i++ ) 1440 { 1441 edge = &mEdges[i]; 1442 1443 _getTerrainHeight( edge->p0.x, edge->p0.y, edge->p0.z ); 1444 1445 _getTerrainHeight( edge->p2.x, edge->p2.y, edge->p2.z ); 1446 } 1447 1448 // 1449 // Allocate the RoadBatch(s) 1450 // 1451 1452 // If we captured no verts, then we can return here without 1453 // allocating any RoadBatches or the Vert/Index Buffers. 1454 // PreprenderImage will not allocate a render instance while 1455 // mBatches.size() is zero. 1456 U32 numClippers = clipperList.size(); 1457 if ( numClippers == 0 ) 1458 return; 1459 1460 mBatches.clear(); 1461 1462 // Allocate the VertexBuffer and PrimitiveBuffer 1463 mVB.set( GFX, mVertCount, GFXBufferTypeStatic ); 1464 mPB.set( GFX, mTriangleCount * 3, 0, GFXBufferTypeStatic ); 1465 1466 // Lock the VertexBuffer 1467 GFXVertexPNTBT *vertPtr = mVB.lock(); 1468 if(!vertPtr) return; 1469 U32 vertIdx = 0; 1470 1471 // 1472 // Fill the VertexBuffer and vertex data for the RoadBatches 1473 // Loop through the ClippedPolyList Vector 1474 // 1475 RoadBatch *batch = NULL; 1476 F32 texStart = 0.0f; 1477 F32 texEnd; 1478 1479 for ( U32 i = 0; i < clipperList.size(); i++ ) 1480 { 1481 ClippedPolyList *clipper = &clipperList[i]; 1482 edge = &mEdges[i]; 1483 nextEdge = &mEdges[i+1]; 1484 1485 VectorF segFvec = nextEdge->p1 - edge->p1; 1486 F32 segLen = segFvec.len(); 1487 segFvec.normalize(); 1488 1489 F32 texLen = segLen / mTextureLength; 1490 texEnd = texStart + texLen; 1491 1492 BiQuadToSqr quadToSquare( Point2F( edge->p0.x, edge->p0.y ), 1493 Point2F( edge->p2.x, edge->p2.y ), 1494 Point2F( nextEdge->p2.x, nextEdge->p2.y ), 1495 Point2F( nextEdge->p0.x, nextEdge->p0.y ) ); 1496 1497 // 1498 if ( i % mSegmentsPerBatch == 0 ) 1499 { 1500 mBatches.increment(); 1501 batch = &mBatches.last(); 1502 1503 batch->bounds.minExtents = clipper->mVertexList[0].point; 1504 batch->bounds.maxExtents = clipper->mVertexList[0].point; 1505 batch->startVert = vertIdx; 1506 } 1507 1508 // Loop through each ClippedPolyList 1509 for ( U32 j = 0; j < clipper->mVertexList.size(); j++ ) 1510 { 1511 // Add each vert to the VertexBuffer 1512 Point3F pos = clipper->mVertexList[j].point; 1513 vertPtr[vertIdx].point = pos; 1514 vertPtr[vertIdx].normal = clipper->mNormalList[j]; 1515 1516 Point2F uv = quadToSquare.transform( Point2F(pos.x,pos.y) ); 1517 vertPtr[vertIdx].texCoord.x = uv.x; 1518 vertPtr[vertIdx].texCoord.y = -(( texEnd - texStart ) * uv.y + texStart); 1519 1520 vertPtr[vertIdx].tangent = mCross( segFvec, clipper->mNormalList[j] ); 1521 vertPtr[vertIdx].binormal = segFvec; 1522 1523 vertIdx++; 1524 1525 // Expand the RoadBatch bounds to contain this vertex 1526 batch->bounds.extend( pos ); 1527 } 1528 1529 batch->endVert = vertIdx - 1; 1530 1531 texStart = texEnd; 1532 } 1533 1534 // Unlock the VertexBuffer, we are done filling it. 1535 mVB.unlock(); 1536 1537 // Lock the PrimitiveBuffer 1538 U16 *idxBuff; 1539 mPB.lock(&idxBuff); 1540 U32 curIdx = 0; 1541 U16 vertOffset = 0; 1542 batch = NULL; 1543 S32 batchIdx = -1; 1544 1545 // Fill the PrimitiveBuffer 1546 // Loop through each ClippedPolyList in the Vector 1547 for ( U32 i = 0; i < clipperList.size(); i++ ) 1548 { 1549 ClippedPolyList *clipper = &clipperList[i]; 1550 1551 if ( i % mSegmentsPerBatch == 0 ) 1552 { 1553 batchIdx++; 1554 batch = &mBatches[batchIdx]; 1555 batch->startIndex = curIdx; 1556 } 1557 1558 for ( U32 j = 0; j < clipper->mPolyList.size(); j++ ) 1559 { 1560 // Write indices for each Poly 1561 ClippedPolyList::Poly *poly = &clipper->mPolyList[j]; 1562 1563 AssertFatal( poly->vertexCount == 3, "Got non-triangle poly!" ); 1564 1565 idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart] + vertOffset; 1566 curIdx++; 1567 idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart + 1] + vertOffset; 1568 curIdx++; 1569 idxBuff[curIdx] = clipper->mIndexList[poly->vertexStart + 2] + vertOffset; 1570 curIdx++; 1571 } 1572 1573 batch->endIndex = curIdx - 1; 1574 1575 vertOffset += clipper->mVertexList.size(); 1576 } 1577 1578 // Unlock the PrimitiveBuffer, we are done filling it. 1579 mPB.unlock(); 1580 1581 // Generate the object/world bounds 1582 // Is the union of all batch bounding boxes. 1583 1584 Box3F box; 1585 for ( U32 i = 0; i < mBatches.size(); i++ ) 1586 { 1587 batch = &mBatches[i]; 1588 1589 if ( i == 0 ) 1590 box = batch->bounds; 1591 else 1592 box.intersect( batch->bounds ); 1593 } 1594 1595 mWorldBox = box; 1596 resetObjectBox(); 1597 1598 // Make sure we are in the correct bins given our world box. 1599 if( getSceneManager() != NULL ) 1600 getSceneManager()->notifyObjectDirty( this ); 1601} 1602 1603U32 DecalRoad::_addNode( const Point3F &pos, F32 width ) 1604{ 1605 mNodes.increment(); 1606 RoadNode &node = mNodes.last(); 1607 1608 node.point = pos; 1609 node.width = width; 1610 1611 return mNodes.size() - 1; 1612} 1613 1614U32 DecalRoad::_insertNode( const Point3F &pos, const F32 &width, const U32 &idx ) 1615{ 1616 U32 ret; 1617 RoadNode *node; 1618 1619 if ( idx == U32_MAX ) 1620 { 1621 mNodes.increment(); 1622 node = &mNodes.last(); 1623 ret = mNodes.size() - 1; 1624 } 1625 else 1626 { 1627 mNodes.insert( idx ); 1628 node = &mNodes[idx]; 1629 ret = idx; 1630 } 1631 1632 node->point = pos; 1633 //node->t = -1.0f; 1634 //node->rot.identity(); 1635 node->width = width; 1636 1637 return ret; 1638} 1639 1640bool DecalRoad::_getTerrainHeight( Point3F &pos ) 1641{ 1642 return _getTerrainHeight( pos.x, pos.y, pos.z ); 1643} 1644 1645bool DecalRoad::_getTerrainHeight( const Point2F &pos, F32 &height ) 1646{ 1647 return _getTerrainHeight( pos.x, pos.y, height ); 1648} 1649 1650bool DecalRoad::_getTerrainHeight( const F32 &x, const F32 &y, F32 &height ) 1651{ 1652 Point3F startPnt( x, y, 10000.0f ); 1653 Point3F endPnt( x, y, -10000.0f ); 1654 1655 RayInfo ri; 1656 bool hit; 1657 1658 hit = getContainer()->castRay(startPnt, endPnt, TerrainObjectType, &ri); 1659 1660 if ( hit ) 1661 height = ri.point.z; 1662 1663 return hit; 1664} 1665 1666void DecalRoad::_onTerrainChanged( U32 type, TerrainBlock* tblock, const Point2I &min, const Point2I &max ) 1667{ 1668 // The client side object just stores the area that has changed 1669 // and waits for the (delayed) update event from the server 1670 // to actually perform the update. 1671 if ( isClientObject() && tblock->isClientObject() ) 1672 { 1673 // Convert the min and max into world space. 1674 const F32 size = tblock->getSquareSize(); 1675 const Point3F pos = tblock->getPosition(); 1676 1677 // TODO: I don't think this works right with tiling! 1678 Box3F dirty( F32( min.x * size ) + pos.x, F32( min.y * size ) + pos.y, -F32_MAX, 1679 F32( max.x * size ) + pos.x, F32( max.y * size ) + pos.y, F32_MAX ); 1680 1681 if ( !mTerrainUpdateRect.isValidBox() ) 1682 mTerrainUpdateRect = dirty; 1683 else 1684 mTerrainUpdateRect.intersect( dirty ); 1685 } 1686 // The server object only updates edges (doesn't clip to geometry) 1687 // and schedules an update to be sent to the client. 1688 else if ( isServerObject() && tblock->isServerObject() ) 1689 { 1690 //_generateEdges(); 1691 scheduleUpdate( TerrainChangedMask ); 1692 } 1693} 1694 1695 1696// Static protected field set methods 1697 1698bool DecalRoad::ptSetBreakAngle( void *object, const char *index, const char *data ) 1699{ 1700 DecalRoad *road = static_cast<DecalRoad*>( object ); 1701 F32 val = dAtof( data ); 1702 1703 road->setBreakAngle( val ); 1704 1705 // we already set the field 1706 return false; 1707} 1708 1709bool DecalRoad::ptSetTextureLength( void *object, const char *index, const char *data ) 1710{ 1711 DecalRoad *road = static_cast<DecalRoad*>( object ); 1712 F32 val = dAtof( data ); 1713 1714 road->setTextureLength( val ); 1715 1716 // we already set the field 1717 return false; 1718} 1719 1720 1721// ConsoleMethods 1722 1723DefineEngineMethod( DecalRoad, regenerate, void, (),, 1724 "Intended as a helper to developers and editor scripts.\n" 1725 "Force DecalRoad to update it's spline and reclip geometry." 1726 ) 1727{ 1728 object->regenerate(); 1729} 1730 1731DefineEngineMethod( DecalRoad, postApply, void, (),, 1732 "Intended as a helper to developers and editor scripts.\n" 1733 "Force trigger an inspectPostApply. This will transmit " 1734 "the material and other fields ( not including nodes ) " 1735 "to client objects." 1736 ) 1737{ 1738 object->inspectPostApply(); 1739} 1740