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 href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> road or path ( or other inventive things ) across " "<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> TerrainBlock. It renders as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> decal and is therefore only <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> features that do " "not need geometric <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">depth.\n\n</a>" "The <a href="/coding/class/classmaterial/">Material</a> assigned <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/class/classdecalroad/">DecalRoad</a> should tile <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vertically.\n\n</a>" "@ingroup Terrain" )
ConsoleDocClass(DecalRoadNodeEvent , "@brief Sends messages <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the Decal Road <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editor\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )
DefineEngineMethod(DecalRoad , postApply , void , () , "Intended as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> helper <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> developers and editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n</a>" "Force trigger an inspectPostApply. This will transmit " "the material and other fields ( not including nodes ) " "<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> client objects." )
DefineEngineMethod(DecalRoad , regenerate , void , () , "Intended as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> helper <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> developers and editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n</a>" "Force <a href="/coding/class/classdecalroad/">DecalRoad</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> update it's spline and reclip geometry." )

Detailed Description

Public Variables

F32 gDecalBias 

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

Is set by by LevelInfo.

bool gEditingMission 

Public Functions

ConsoleDocClass(DecalRoad , "@brief A strip shaped decal defined by spine nodes which clips against Terrain <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">objects.\n\n</a>" "<a href="/coding/class/classdecalroad/">DecalRoad</a> is <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> representing <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> road or path ( or other inventive things ) across " "<a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> TerrainBlock. It renders as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> decal and is therefore only <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> features that do " "not need geometric <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">depth.\n\n</a>" "The <a href="/coding/class/classmaterial/">Material</a> assigned <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/class/classdecalroad/">DecalRoad</a> should tile <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">vertically.\n\n</a>" "@ingroup Terrain" )

ConsoleDocClass(DecalRoadNodeEvent , "@brief Sends messages <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the Decal Road <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">Editor\n\n</a>" "Editor use <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">only.\n\n</a>" "@internal" )

DefineEngineMethod(DecalRoad , postApply , void , () , "Intended as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> helper <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> developers and editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n</a>" "Force trigger an inspectPostApply. This will transmit " "the material and other fields ( not including nodes ) " "<a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> client objects." )

DefineEngineMethod(DecalRoad , regenerate , void , () , "Intended as <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</a> helper <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> developers and editor <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">scripts.\n</a>" "Force <a href="/coding/class/classdecalroad/">DecalRoad</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> update it's spline and reclip geometry." )

IMPLEMENT_CO_NETEVENT_V1(DecalRoadNodeEvent )

IMPLEMENT_CO_NETOBJECT_V1(DecalRoad )

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