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