decalRoad.cpp

Engine/source/environment/decalRoad.cpp

More...

Classes:

Public Variables

A bias applied to the nearPlane for Decal and DecalRoad rendering.

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