river.cpp

Engine/source/environment/river.cpp

More...

Classes:

Public Defines

define
MAX_NODE_DEPTH() 500.0f
define
MAX_NODE_WIDTH() 1000.0f
define
define
define
NODE_RADIUS() 15.0f

Public Variables

Public Functions

ConsoleDocClass(River , "@brief A water volume defined by a 3D <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">spline.\n\n</a>" "User may <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> width and depth per node and overall spline shape in three " "<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">dimensions.\n\n</a>" "%<a href="/coding/class/classriver/">River</a> supports dynamic planar reflections (fullReflect) like all <a href="/coding/class/classwaterobject/">WaterObject</a> " " classes, but keep in mind it is not necessarily a planar surface. For best " "visual quality a %<a href="/coding/class/classriver/">River</a> should be less reflective the more it twists and " "bends. This caution only applies <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> %Rivers with fullReflect <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">on.\n\n</a>" " @see <a href="/coding/class/classwaterobject/">WaterObject</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> inherited <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">functionality.\n\n</a>" " @ingroup Water" )
ConsoleDocClass(RiverNodeEvent , "@brief Sends messages <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/class/classriver/">River</a> <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(River , 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/classriver/">River</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> recreate its geometry." )
DefineEngineMethod(River , setBatchSize , void , (F32 meters) , "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>" "BatchSize is not currently used." )
DefineEngineMethod(River , setMaxDivisionSize , void , (F32 meters) , "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>" "@see SubdivideLength field." )
DefineEngineMethod(River , setMetersPerSegment , void , (F32 meters) , "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>" "@see SegmentLength field." )
DefineEngineMethod(River , setNodeDepth , void , (S32 idx, F32 meters) , "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>" "Sets the depth in meters of a particular node." )

Detailed Description

Public Defines

MAX_NODE_DEPTH() 500.0f
MAX_NODE_WIDTH() 1000.0f
MIN_METERS_PER_SEGMENT() 1.0f
MIN_NODE_DEPTH() 0.25f
MIN_NODE_WIDTH() 0.25f
NODE_RADIUS() 15.0f

Public Variables

U32 gIdxArray [6][2][3]
Point3F sSegmentPointComparePoints [4]

Public Functions

compareHitSegments(const void * a, const void * b)

ConsoleDocClass(River , "@brief A water volume defined by a 3D <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">spline.\n\n</a>" "User may <a href="/coding/file/guieditctrl_8cpp/#guieditctrl_8cpp_1abb04e3738c4c5a96b3ade6fa47013a6c">control</a> width and depth per node and overall spline shape in three " "<a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">dimensions.\n\n</a>" "%<a href="/coding/class/classriver/">River</a> supports dynamic planar reflections (fullReflect) like all <a href="/coding/class/classwaterobject/">WaterObject</a> " " classes, but keep in mind it is not necessarily a planar surface. For best " "visual quality a %<a href="/coding/class/classriver/">River</a> should be less reflective the more it twists and " "bends. This caution only applies <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> %Rivers with fullReflect <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">on.\n\n</a>" " @see <a href="/coding/class/classwaterobject/">WaterObject</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> inherited <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">functionality.\n\n</a>" " @ingroup Water" )

ConsoleDocClass(RiverNodeEvent , "@brief Sends messages <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the <a href="/coding/class/classriver/">River</a> <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(River , 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/classriver/">River</a> <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> recreate its geometry." )

DefineEngineMethod(River , setBatchSize , void , (F32 meters) , "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>" "BatchSize is not currently used." )

DefineEngineMethod(River , setMaxDivisionSize , void , (F32 meters) , "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>" "@see SubdivideLength field." )

DefineEngineMethod(River , setMetersPerSegment , void , (F32 meters) , "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>" "@see SegmentLength field." )

DefineEngineMethod(River , setNodeDepth , void , (S32 idx, F32 meters) , "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>" "Sets the depth in meters of a particular node." )

IMPLEMENT_CO_NETEVENT_V1(RiverNodeEvent )

IMPLEMENT_CO_NETOBJECT_V1(River )

SegmentPointCompare(const void * aptr, const void * bptr)

   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/river.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/simPath.h"
  32#include "scene/sceneRenderState.h"
  33#include "scene/sceneManager.h"
  34#include "materials/sceneData.h"
  35#include "materials/baseMatInstance.h"
  36#include "scene/sgUtil.h"
  37#include "T3D/gameBase/gameConnection.h"
  38#include "core/stream/bitStream.h"
  39#include "gfx/gfxDrawUtil.h"
  40#include "gfx/gfxTransformSaver.h"
  41#include "gfx/primBuilder.h"
  42#include "gfx/gfxDebugEvent.h"
  43#include "gfx/gfxOcclusionQuery.h"
  44#include "math/mathIO.h"
  45#include "math/mathUtils.h"
  46#include "math/util/frustum.h"
  47#include "math/util/quadTransforms.h"
  48#include "gui/3d/guiTSControl.h"
  49#include "gfx/sim/debugDraw.h"
  50#include "T3D/fx/particleEmitter.h"
  51#include "scene/reflectionManager.h"
  52#include "ts/tsShapeInstance.h"
  53#include "postFx/postEffect.h"
  54#include "math/util/matrixSet.h"
  55#include "environment/nodeListManager.h"
  56
  57ConsoleDocClass( River,
  58   "@brief A water volume defined by a 3D spline.\n\n"
  59   
  60   "User may control width and depth per node and overall spline shape in three "
  61   "dimensions.\n\n"   
  62      
  63   "%River supports dynamic planar reflections (fullReflect) like all WaterObject "
  64   "classes, but keep in mind it is not necessarily a planar surface. For best "
  65   "visual quality a %River should be less reflective the more it twists and "
  66   "bends. This caution only applies to %Rivers with fullReflect on.\n\n"
  67
  68   "@see WaterObject for inherited functionality.\n\n"
  69
  70   "@ingroup Water"
  71);
  72
  73#define MIN_METERS_PER_SEGMENT 1.0f
  74#define MIN_NODE_DEPTH 0.25f
  75#define MAX_NODE_DEPTH 500.0f
  76#define MIN_NODE_WIDTH 0.25f
  77#define MAX_NODE_WIDTH 1000.0f
  78#define NODE_RADIUS 15.0f
  79
  80static U32 gIdxArray[6][2][3] = {
  81   { { 0, 4, 5 }, { 0, 5, 1 }, },   // Top Face
  82   { { 2, 6, 4 }, { 2, 4, 0 }, },   // Left Face
  83   { { 1, 5, 7 }, { 1, 7, 3 }, },   // Right Face
  84   { { 2, 3, 7 }, { 2, 7, 6 }, },   // Bottom Face
  85   { { 0, 1, 3 }, { 0, 3, 2 }, },   // Front Face
  86   { { 4, 6, 7 }, { 4, 7, 5 }, },   // Back Face
  87};
  88
  89struct RiverHitSegment
  90{
  91   U32 idx;
  92   F32 t;
  93};
  94
  95static S32 QSORT_CALLBACK compareHitSegments(const void* a,const void* b)
  96{
  97   const RiverHitSegment *fa = (RiverHitSegment*)a;
  98   const RiverHitSegment *fb = (RiverHitSegment*)b;
  99
 100   return mSign(fb->t - fa->t);
 101}
 102
 103static Point3F sSegmentPointComparePoints[4];
 104
 105//-----------------------------------------------------------------------------
 106// DecalRoadNodeList Struct
 107//-----------------------------------------------------------------------------
 108
 109struct RiverNodeList : public NodeListManager::NodeList
 110{
 111   Vector<Point3F>   mPositions;
 112   Vector<F32>       mWidths;
 113   Vector<F32>       mDepths;
 114   Vector<VectorF>   mNormals;
 115
 116   RiverNodeList() { }
 117   virtual ~RiverNodeList() { }
 118};
 119
 120//-----------------------------------------------------------------------------
 121// RiverNodeEvent Class
 122//-----------------------------------------------------------------------------
 123
 124class RiverNodeEvent : public NodeListEvent
 125{
 126   typedef NodeListEvent Parent;
 127
 128public:
 129   Vector<Point3F>   mPositions;
 130   Vector<F32>       mWidths;
 131   Vector<F32>       mDepths;
 132   Vector<VectorF>   mNormals;
 133
 134public:
 135   RiverNodeEvent() { mNodeList = NULL; }
 136   virtual ~RiverNodeEvent() { }
 137
 138   virtual void pack(NetConnection*, BitStream*);
 139   virtual void unpack(NetConnection*, BitStream*);
 140
 141   virtual void copyIntoList(NodeListManager::NodeList* copyInto);
 142   virtual void padListToSize();
 143
 144   DECLARE_CONOBJECT(RiverNodeEvent);
 145};
 146
 147void RiverNodeEvent::pack(NetConnection* conn, BitStream* stream)
 148{
 149   Parent::pack( conn, stream );
 150
 151   stream->writeInt( mPositions.size(), 16 );
 152
 153   for (U32 i=0; i<mPositions.size(); ++i)
 154   {
 155      mathWrite( *stream, mPositions[i] );
 156      stream->write( mWidths[i] );
 157      stream->write( mDepths[i] );
 158      mathWrite( *stream, mNormals[i] );         
 159   }
 160}
 161
 162void RiverNodeEvent::unpack(NetConnection* conn, BitStream* stream)
 163{
 164   mNodeList = new RiverNodeList();
 165
 166   Parent::unpack( conn, stream );
 167
 168   U32 count = stream->readInt( 16 );
 169
 170   Point3F pos;
 171   F32 width, depth;
 172   VectorF normal;
 173
 174   RiverNodeList* list = static_cast<RiverNodeList*>(mNodeList);
 175
 176   for (U32 i=0; i<count; ++i)
 177   {
 178      mathRead( *stream, &pos );
 179      stream->read( &width );   
 180      stream->read( &depth );
 181      mathRead( *stream, &normal );
 182
 183      list->mPositions.push_back( pos );
 184      list->mWidths.push_back( width );
 185      list->mDepths.push_back( depth );
 186      list->mNormals.push_back( normal );
 187   }
 188
 189   list->mTotalValidNodes = count;
 190
 191   // Do we have a complete list?
 192   if (list->mPositions.size() >= mTotalNodes)
 193      list->mListComplete = true;
 194}
 195
 196void RiverNodeEvent::copyIntoList(NodeListManager::NodeList* copyInto)
 197{
 198   RiverNodeList* prevList = dynamic_cast<RiverNodeList*>(copyInto);
 199   RiverNodeList* list = static_cast<RiverNodeList*>(mNodeList);
 200
 201   // Merge our list with the old list.
 202   for (U32 i=<a href="/coding/class/classnodelistevent/#classnodelistevent_1a05350212961bce959f29916230c0209e">mLocalListStart</a>, index=0; i<mLocalListStart+list->mPositions.size(); ++i, ++index)
 203   {
 204      prevList->mPositions[i] = list->mPositions[index];
 205      prevList->mWidths[i] = list->mWidths[index];
 206      prevList->mDepths[i] = list->mDepths[index];
 207      prevList->mNormals[i] = list->mNormals[index];
 208   }
 209}
 210
 211void RiverNodeEvent::padListToSize()
 212{
 213   RiverNodeList* list = static_cast<RiverNodeList*>(mNodeList);
 214
 215   U32 totalValidNodes = list->mTotalValidNodes;
 216
 217   // Pad our list front?
 218   if (mLocalListStart)
 219   {
 220      RiverNodeList* newlist = new RiverNodeList();
 221      newlist->mPositions.increment(mLocalListStart);
 222      newlist->mWidths.increment(mLocalListStart);
 223      newlist->mDepths.increment(mLocalListStart);
 224      newlist->mNormals.increment(mLocalListStart);
 225
 226      newlist->mPositions.merge(list->mPositions);
 227      newlist->mWidths.merge(list->mWidths);
 228      newlist->mDepths.merge(list->mDepths);
 229      newlist->mNormals.merge(list->mNormals);
 230
 231      delete list;
 232      mNodeList = list = newlist;
 233   }
 234
 235   // Pad our list end?
 236   if (list->mPositions.size() < mTotalNodes)
 237   {
 238      U32 delta = mTotalNodes - list->mPositions.size();
 239      list->mPositions.increment(delta);
 240      list->mWidths.increment(delta);
 241      list->mDepths.increment(delta);
 242      list->mNormals.increment(delta);
 243   }
 244
 245   list->mTotalValidNodes = totalValidNodes;
 246}
 247
 248IMPLEMENT_CO_NETEVENT_V1(RiverNodeEvent);
 249
 250ConsoleDocClass( RiverNodeEvent,
 251   "@brief Sends messages to the River Editor\n\n"
 252   "Editor use only.\n\n"
 253   "@internal"
 254);
 255//-----------------------------------------------------------------------------
 256// RiverNodeListNotify Class
 257//-----------------------------------------------------------------------------
 258
 259class RiverNodeListNotify : public NodeListNotify
 260{
 261   typedef NodeListNotify Parent;
 262
 263protected:
 264   SimObjectPtr<River> mRiver;
 265
 266public:
 267   RiverNodeListNotify( River* river, U32 listId ) { mRiver = river; mListId = listId; }
 268   virtual ~RiverNodeListNotify() { mRiver = NULL; }
 269
 270   virtual void sendNotification( NodeListManager::NodeList* list );
 271};
 272
 273void RiverNodeListNotify::sendNotification( NodeListManager::NodeList* list )
 274{
 275   if (mRiver.isValid())
 276   {
 277      // Build the road's nodes
 278      RiverNodeList* riverList = dynamic_cast<RiverNodeList*>( list );
 279      if (riverList)
 280         mRiver->buildNodesFromList( riverList );
 281   }
 282}
 283
 284//------------------------------------------------------------------------------
 285// Class: RiverSegment
 286//------------------------------------------------------------------------------
 287
 288RiverSegment::RiverSegment()
 289{
 290   mPlaneCount = 0;
 291   columns = 0;
 292   rows = 0;
 293   numVerts = 0;
 294   numTriangles = 0;
 295
 296   startVert = 0;
 297   endVert = 0;
 298   startIndex = 0;
 299   endIndex = 0;
 300
 301   slice0 = NULL;
 302   slice1 = NULL;
 303}
 304
 305RiverSegment::RiverSegment( RiverSlice *rs0, RiverSlice *rs1 )
 306{
 307   columns = 0;
 308   rows = 0;
 309   numVerts = 0;
 310   numTriangles = 0;
 311
 312   startVert = 0;
 313   endVert = 0;
 314   startIndex = 0;
 315   endIndex = 0;
 316
 317   slice0 = rs0;
 318   slice1 = rs1;
 319
 320   // Calculate the planes for this segment
 321   // Will be used for intersection/buoyancy tests
 322    VectorF normal;
 323    mPlaneCount = 6;
 324 
 325    sSegmentPointCompareReference = getFaceCenter(6);
 326
 327    // left
 328    mPlanes[0] = _getBestPlane( &slice1->p0, &slice1->pb0, &slice0->pb0, &slice0->p0 );
 329    
 330    // right
 331    mPlanes[1] = _getBestPlane( &slice0->pb2, &slice1->pb2, &slice1->p2, &slice0->p2 );    
 332 
 333    // near    
 334    mPlanes[2] = _getBestPlane( &slice0->pb0, &slice0->pb2, &slice0->p2, &slice0->p0 );
 335 
 336    // far    
 337    mPlanes[3] = _getBestPlane( &slice1->pb2, &slice1->pb0, &slice1->p0, &slice1->p2 );
 338 
 339    // top
 340    mPlanes[4] = _getBestPlane( &slice0->p2, &slice1->p2, &slice1->p0, &slice0->p0 );
 341 
 342    // bottom
 343    mPlanes[5] = _getBestPlane( &slice0->pb2, &slice0->pb0, &slice1->pb0, &slice1->pb2 );
 344
 345   // Calculate the bounding box(s)
 346   worldbounds.minExtents = worldbounds.maxExtents = rs0->p0;
 347   worldbounds.extend( rs0->p2 );
 348   worldbounds.extend( rs0->pb0 );
 349   worldbounds.extend( rs0->pb2 );
 350   worldbounds.extend( rs1->p0 );
 351   worldbounds.extend( rs1->p2 );
 352   worldbounds.extend( rs1->pb0 );
 353   worldbounds.extend( rs1->pb2 );
 354
 355   /*
 356   // Calculate tetrahedrons (for collision and buoyancy testing)
 357      // This is 0 in the diagram.
 358      mCubePoints[0] = cornerPoint;
 359      mCubePoints[1] = cornerPoint + (VectorF( 1.0f, 0.0f, 0.0f ) * size );
 360      mCubePoints[2] = cornerPoint + (VectorF( 0.0f, 1.0f, 0.0f ) * size );
 361      mCubePoints[3] = cornerPoint + (VectorF( 1.0f, 1.0f, 0.0f ) * size );
 362   
 363      mCubePoints[4] = cornerPoint + (VectorF( 0.0f, 0.0f, 1.0f );
 364      mCubePoints[5] = cornerPoint + (VectorF( 1.0f, 0.0f, 1.0f );
 365      mCubePoints[6] = cornerPoint + (VectorF( 0.0f, 1.0f, 1.0f );
 366      mCubePoints[7] = cornerPoint + (VectorF( 1.0f, 1.0f, 1.0f );
 367   
 368      // Center tetra.
 369      mTetras[0].p0 = &mCubePoints[1];
 370      mTetras[0].p1 = &mCubePoints[2];
 371      mTetras[0].p2 = &mCubePoints[4];
 372      mTetras[0].p3 = &mCubePoints[7];
 373   
 374   
 375   
 376      mTetras[1].p0 = &mCubePoints[0]; // this is the tip
 377      mTetras[1].p1 = &mCubePoints[1];
 378      mTetras[1].p2 = &mCubePoints[2];
 379      mTetras[1].p3 = &mCubePoints[4];
 380   
 381      mTetras[2].p0 = &mCubePoints[3]; // tip
 382      mTetras[2].p1 = &mCubePoints[2];
 383      mTetras[2].p2 = &mCubePoints[1];
 384      mTetras[2].p3 = &mCubePoints[7];
 385   
 386      mTetras[3].p0 = &mCubePoints[6]; // tip
 387      mTetras[3].p1 = &mCubePoints[7];
 388      mTetras[3].p2 = &mCubePoints[4];
 389      mTetras[3].p3 = &mCubePoints[2];
 390   
 391      mTetras[4].p0 = &mCubePoints[5]; // tip
 392      mTetras[4].p1 = &mCubePoints[7];
 393      mTetras[4].p2 = &mCubePoints[4];
 394      mTetras[4].p3 = &mCubePoints[3];*/
 395   
 396}
 397
 398void RiverSegment::set( RiverSlice *rs0, RiverSlice *rs1 )
 399{
 400   columns = 0;
 401   rows = 0;
 402   numVerts = 0;
 403   numTriangles = 0;
 404
 405   startVert = 0;
 406   endVert = 0;
 407   startIndex = 0;
 408   endIndex = 0;
 409
 410   slice0 = rs0;
 411   slice1 = rs1;
 412}
 413
 414static S32 QSORT_CALLBACK SegmentPointCompare(const void *aptr, const void *bptr)
 415{
 416   const U32 a = *(const U32*)aptr;
 417   const U32 b = *(const U32*)bptr;
 418   
 419   F32 lenA = ( sSegmentPointCompareReference - sSegmentPointComparePoints[a] ).lenSquared();
 420   F32 lenB = ( sSegmentPointCompareReference - sSegmentPointComparePoints[b] ).lenSquared();
 421   return ( lenB - lenA );   
 422}
 423
 424PlaneF RiverSegment::_getBestPlane( const Point3F *p0, const Point3F *p1, const Point3F *p2, const Point3F *p3 )
 425{   
 426   sSegmentPointComparePoints[0] = *p0;
 427   sSegmentPointComparePoints[1] = *p1;
 428   sSegmentPointComparePoints[2] = *p2;
 429   sSegmentPointComparePoints[3] = *p3;
 430
 431   Point3F points[4] = {
 432      *p0, *p1, *p2, *p3
 433   };
 434
 435   U32 indices[4] = {
 436      0,1,2,3
 437   };
 438
 439   dQsort(indices, 4, sizeof(U32), SegmentPointCompare);
 440
 441   // Collect the best three points (in correct winding order)
 442   // To generate the plane's normal
 443   Vector<Point3F> normalPnts;   
 444
 445   for ( U32 i = 0; i < 4; i++ )
 446   {
 447      if ( i == indices[3] )
 448         continue;
 449
 450      normalPnts.push_back(points[i]);
 451   }
 452
 453   PlaneF plane( normalPnts[0], normalPnts[1], normalPnts[2] );
 454   return plane;
 455}
 456
 457Point3F RiverSegment::getFaceCenter( U32 faceIdx ) const
 458{
 459   Point3F center(0,0,0);
 460
 461   switch ( faceIdx )
 462   {
 463   case 0: // left
 464      center = slice1->p0 + slice0->p0 + slice0->pb0 + slice1->pb0;
 465      center *= 0.25f;
 466      break;
 467
 468   case 1: // right
 469      center = slice0->p2 + slice1->p2 + slice1->pb2 + slice0->pb2;
 470      center *= 0.25f;
 471      break;   
 472
 473   case 2: // near    
 474      center = slice0->p0 + slice0->p2 + slice0->pb2 + slice0->pb0;
 475      center *= 0.25f;
 476      break;   
 477
 478   case 3: // far    
 479      center = slice1->pb0 + slice1->p0 + slice1->pb0 + slice1->pb2;
 480      center *= 0.25f;
 481      break;   
 482
 483   case 4: // top
 484      center = slice0->p0 + slice1->p0 + slice1->p2 + slice0->p2;
 485      center *= 0.25f;
 486      break;   
 487
 488   case 5: // bottom
 489      center = slice1->pb2 + slice1->pb0 + slice0->pb0 + slice0->pb2;
 490      center *= 0.25f;
 491      break;
 492
 493   case 6: // segment center
 494      center = slice0->p0 + slice0->p2 + slice1->p0 + slice1->p2 + slice0->pb0 + slice0->pb2 + slice1->pb0 + slice1->pb2;
 495      center /= 8;
 496      break;
 497   }
 498
 499   return center;
 500}
 501
 502bool RiverSegment::intersectBox( const Box3F &bounds ) const
 503{
 504   // This code copied from Frustum class.
 505
 506   Point3F maxPoint;
 507   F32 maxDot;
 508
 509   // Note the planes are ordered left, right, near, 
 510   // far, top, bottom for getting early rejections
 511   // from the typical horizontal scene.
 512   for ( S32 i = 0; i < mPlaneCount; i++ )
 513   {
 514      // This is pretty much as optimal as you can
 515      // get for a plane vs AABB test...
 516      // 
 517      // 4 comparisons
 518      // 3 multiplies
 519      // 2 adds
 520      // 1 negation
 521      //
 522      // It will early out as soon as it detects the
 523      // bounds is outside one of the planes.
 524
 525      if ( mPlanes[i].x > 0 )
 526         maxPoint.x = bounds.maxExtents.x;
 527      else
 528         maxPoint.x = bounds.minExtents.x;
 529
 530      if ( mPlanes[i].y > 0 )
 531         maxPoint.y = bounds.maxExtents.y;
 532      else
 533         maxPoint.y = bounds.minExtents.y;
 534
 535      if ( mPlanes[i].z > 0 )
 536         maxPoint.z = bounds.maxExtents.z;
 537      else
 538         maxPoint.z = bounds.minExtents.z;
 539
 540      maxDot = mDot( maxPoint, mPlanes[ i ] );
 541
 542      if ( maxDot <= -mPlanes[ i ].d )
 543         return false;
 544   }
 545
 546   return true;
 547}
 548
 549bool RiverSegment::containsPoint( const Point3F &pnt ) const
 550{
 551   // NOTE: this code from Frustum class.
 552
 553   F32 maxDot;
 554
 555   // Note the planes are ordered left, right, near, 
 556   // far, top, bottom for getting early rejections
 557   // from the typical horizontal scene.
 558   for ( S32 i = 0; i < mPlaneCount; i++ )
 559   {
 560      const PlaneF &plane = mPlanes[ i ];
 561
 562      // This is pretty much as optimal as you can
 563      // get for a plane vs point test...
 564      // 
 565      // 1 comparison
 566      // 2 multiplies
 567      // 1 adds
 568      //
 569      // It will early out as soon as it detects the
 570      // point is outside one of the planes.
 571
 572      maxDot = mDot( pnt, plane ) + plane.d;
 573      if ( maxDot < -0.1f )
 574         return false;
 575   }
 576
 577   return true;
 578}
 579
 580F32 RiverSegment::distanceToSurface(const Point3F &pnt) const
 581{
 582   return mPlanes[4].distToPlane( pnt );
 583}
 584
 585bool River::smEditorOpen = false;
 586bool River::smWireframe = false;
 587bool River::smShowWalls = false;
 588bool River::smShowNodes = false;
 589bool River::smShowSpline = true;
 590bool River::smShowRiver = true;
 591SimObjectPtr<SimSet> River::smServerRiverSet = NULL;
 592
 593IMPLEMENT_CO_NETOBJECT_V1(River);
 594
 595
 596River::River()
 597 : mSegmentsPerBatch(10),
 598   mMetersPerSegment(10.0f),
 599   mDepthScale(1.0f),
 600   mFlowMagnitude(1.0f),
 601   mLodDistance( 50.0f ),
 602   mMaxDivisionSize(2.5f),
 603   mMinDivisionSize(0.25f),
 604   mColumnCount(5)   
 605{   
 606   mNetFlags.set( Ghostable | ScopeAlways );
 607
 608   mObjScale.set( 1, 1, 1 );
 609
 610   mObjBox.minExtents.set( -0.5, -0.5, -0.5 );
 611   mObjBox.maxExtents.set(  0.5,  0.5,  0.5 );
 612
 613   mReflectNormalUp = false;
 614
 615   // We use the shader const miscParams.w to signify
 616   // that this object is a River.
 617   mMiscParamW = 1.0f;
 618}
 619
 620River::~River()
 621{      
 622}
 623
 624void River::initPersistFields()
 625{
 626   addGroup( "River" );
 627
 628      addField( "SegmentLength",       TypeF32,    Offset( mMetersPerSegment, River ),
 629         "Divide the River lengthwise into segments of this length in meters. "
 630         "These geometric volumes are used for spacial queries like determining containment." );      
 631
 632      addField( "SubdivideLength",     TypeF32,    Offset( mMaxDivisionSize, River ),
 633         "For purposes of generating the renderable geometry River segments are further subdivided "
 634         "such that no quad is of greater width or length than this distance in meters." );
 635
 636      addField( "FlowMagnitude",       TypeF32,    Offset( mFlowMagnitude, River ),
 637         "Magnitude of the force vector applied to dynamic objects within the River." );
 638
 639      addField( "LowLODDistance",      TypeF32,    Offset( mLodDistance, River ),
 640         "Segments of the river at this distance in meters or greater will "
 641         "render as a single unsubdivided without undulation effects." );      
 642
 643   endGroup( "River" );   
 644
 645   addGroup( "Internal" );
 646
 647      addProtectedField( "Node", TypeString, NULL, &addNodeFromField, &emptyStringProtectedGetFn, "For internal use, do not modify." );
 648
 649   endGroup( "Internal" );
 650
 651   Parent::initPersistFields();
 652}
 653
 654void River::consoleInit()
 655{
 656   Parent::consoleInit();
 657
 658   Con::addVariable( "$River::EditorOpen", TypeBool, &River::smEditorOpen, "For editor use.\n"
 659      "@ingroup Editors\n" );
 660   Con::addVariable( "$River::showWalls", TypeBool, &River::smShowWalls, "For editor use.\n"
 661      "@ingroup Editors\n" );
 662   Con::addVariable( "$River::showNodes", TypeBool, &River::smShowNodes, "For editor use.\n"
 663      "@ingroup Editors\n");
 664   Con::addVariable( "$River::showSpline", TypeBool, &River::smShowSpline, "For editor use.\n"
 665      "@ingroup Editors\n" );
 666   Con::addVariable( "$River::showRiver", TypeBool, &River::smShowRiver, "For editor use.\n"
 667      "@ingroup Editors\n" );
 668   Con::addVariable( "$River::showWireframe", TypeBool, &River::smWireframe, "For editor use.\n"
 669      "@ingroup Editors\n");
 670}
 671
 672bool River::addNodeFromField( void *object, const char *index, const char *data )
 673{
 674   River *pObj = static_cast<River*>(object);
 675
 676   //if ( !pObj->isProperlyAdded() )
 677   //{      
 678   F32 x,y,z,width,depth;      
 679   VectorF normal;
 680   U32 result = dSscanf( data, "%f %f %f %f %f %f %f %f", &x, &y, &z, &width, &depth, &normal.x, &normal.y, &normal.z );      
 681   if ( result == 8 )   
 682      pObj->_addNode( Point3F(x,y,z), width, depth, normal );      
 683   //}
 684
 685   return false;
 686}
 687
 688bool River::onAdd()
 689{
 690   if ( !Parent::onAdd() ) 
 691      return false;
 692
 693   // Reset the World Box.
 694   //setGlobalBounds();
 695   resetWorldBox();
 696
 697   // Set the Render Transform.
 698   setRenderTransform(mObjToWorld);
 699
 700   // Add to Scene.
 701   addToScene();
 702   
 703   if ( isServerObject() )
 704      getServerSet()->addObject( this );   
 705
 706   _regenerate();
 707
 708   return true;
 709}
 710
 711void River::onRemove()
 712{
 713   removeFromScene();
 714
 715   Parent::onRemove();
 716}
 717
 718void River::inspectPostApply()
 719{
 720   // Set Parent.
 721   Parent::inspectPostApply();
 722
 723   if ( mMetersPerSegment < MIN_METERS_PER_SEGMENT )
 724      mMetersPerSegment = MIN_METERS_PER_SEGMENT;
 725
 726   mMaxDivisionSize = getMax( mMaxDivisionSize, mMinDivisionSize );      
 727
 728   // Set fxPortal Mask.
 729   setMaskBits(RiverMask</a>|<a href="/coding/class/classriver/#classriver_1a2f297b8dff480e02e5f772191cf18190a083a3101bc65d3239b7295221c0c5a39">RegenMask);
 730}
 731
 732void River::onStaticModified( const char* slotName, const char*newValue )
 733{
 734   Parent::onStaticModified( slotName, newValue );
 735
 736   if ( dStricmp( slotName, "surfMaterial" ) == 0 )
 737      setMaskBits( MaterialMask );
 738}
 739
 740SimSet* River::getServerSet()
 741{
 742   if ( !smServerRiverSet )
 743   {
 744      smServerRiverSet = new SimSet();
 745      smServerRiverSet->registerObject( "ServerRiverSet" );
 746      Sim::getRootGroup()->addObject( smServerRiverSet );
 747   }
 748
 749   return smServerRiverSet;
 750}
 751
 752void River::writeFields( Stream &stream, U32 tabStop )
 753{
 754   Parent::writeFields( stream, tabStop );
 755
 756   // Now write all nodes
 757
 758   stream.write(2, "\r\n");   
 759
 760   for ( U32 i = 0; i < mNodes.size(); i++ )
 761   {
 762      const RiverNode &node = mNodes[i];
 763
 764      stream.writeTabs(tabStop);
 765
 766      char buffer[1024];
 767      dMemset( buffer, 0, 1024 );
 768      dSprintf( buffer, 1024, "Node = \"%f %f %f %f %f %f %f %f\";", node.point.x, node.point.y, node.point.z, 
 769                                                                     node.width, 
 770                                                                     node.depth, 
 771                                                                     node.normal.x, node.normal.y, node.normal.z );      
 772      stream.writeLine( (const U8*)buffer );
 773   }
 774}
 775
 776bool River::writeField( StringTableEntry fieldname, const char *value )
 777{   
 778   if ( fieldname == StringTable->insert("node") )
 779      return false;
 780
 781   return Parent::writeField( fieldname, value );
 782}
 783
 784void River::innerRender( SceneRenderState *state )
 785{   
 786   GFXDEBUGEVENT_SCOPE( River_innerRender, ColorI( 255, 0, 0 ) );
 787
 788   PROFILE_SCOPE( River_innerRender );
 789
 790   // Setup SceneData
 791   SceneData sgData;
 792   sgData.init( state );
 793   sgData.lights[0] = LIGHTMGR->getSpecialLight( LightManager::slSunLightType );
 794   sgData.backBuffTex = REFLECTMGR->getRefractTex();
 795   sgData.reflectTex = mPlaneReflector.reflectTex; 
 796   sgData.wireframe |= smWireframe;
 797
 798   const Point3F &camPosition = state->getCameraPosition();
 799
 800   // set the material
 801
 802   S32 matIdx = getMaterialIndex( camPosition );
 803
 804   if ( !initMaterial( matIdx ) )
 805      return;
 806
 807   BaseMatInstance *mat = mMatInstances[matIdx];
 808   WaterMatParams matParams = mMatParamHandles[matIdx];
 809
 810   if ( !mat )      
 811      return;
 812
 813   // setup proj/world transform
 814   GFXTransformSaver saver;
 815
 816   setShaderParams( state, mat, matParams );
 817
 818   _makeRenderBatches( camPosition );
 819
 820   if ( !River::smShowRiver )      
 821      return;
 822
 823   // If no material... we're done.
 824   if ( mLowLODBatches.empty() && mHighLODBatches.empty() )      
 825      return;
 826
 827   if ( !mHighLODBatches.empty() )
 828      _makeHighLODBuffers();
 829
 830   mMatrixSet->restoreSceneViewProjection();
 831   mMatrixSet->setWorld( MatrixF::Identity );
 832
 833   while( mat->setupPass( state, sgData ) )
 834   {
 835      mat->setSceneInfo(state, sgData);
 836      mat->setTransforms(*mMatrixSet, state);
 837
 838      setCustomTextures( matIdx, mat->getCurPass(), matParams );      
 839
 840      GFX->setVertexBuffer( mVB_low );
 841      GFX->setPrimitiveBuffer( mPB_low );
 842
 843      for ( U32 i = 0; i < mLowLODBatches.size(); i++ )
 844      {
 845         const RiverRenderBatch &batch = mLowLODBatches[i];
 846
 847         U32 startVert = batch.startSegmentIdx * 2;
 848         U32 endVert = ( batch.endSegmentIdx + 1 ) * 2 + 1;
 849         U32 startIdx = batch.startSegmentIdx * 6;
 850         U32 endIdx = batch.endSegmentIdx * 6 + 5;
 851          
 852         U32 vertCount = ( endVert - startVert ) + 1;
 853         U32 idxCount = ( endIdx - startIdx ) + 1;
 854         U32 triangleCount = idxCount / 3;
 855                     
 856         AssertFatal( startVert < mLowVertCount, "River, bad draw call!" );
 857         AssertFatal( startVert + vertCount <= mLowVertCount, "River, bad draw call!" );
 858         AssertFatal( triangleCount <= mLowTriangleCount, "River, bad draw call!" );
 859
 860         GFX->drawIndexedPrimitive( GFXTriangleList, 0, startVert, vertCount, startIdx, triangleCount );
 861      }
 862      
 863      // Render all high detail batches.
 864      //
 865      // It is possible that the buffers could not be allocated because
 866      // the max number of verts/indices was exceeded.  We don't want to 
 867      // crash because that would be unhelpful for working in the editor.
 868      if ( mVB_high.isValid() && mPB_high.isValid() )
 869      {
 870         GFX->setVertexBuffer( mVB_high );
 871         GFX->setPrimitiveBuffer( mPB_high );
 872
 873         for ( U32 i = 0; i < mHighLODBatches.size(); i++ )
 874         {
 875            const RiverRenderBatch &batch = mHighLODBatches[i];
 876
 877            AssertFatal( batch.startVert < mHighVertCount, "River, bad draw call!" );
 878            AssertFatal( batch.startVert + batch.vertCount <= mHighVertCount, "River, bad draw call!" );            
 879            AssertFatal( batch.triangleCount <= mHighTriangleCount, "River, bad draw call!" );
 880            AssertFatal( batch.startIndex < mHighTriangleCount * 3, "River, bad draw call!" );
 881            AssertFatal( batch.startIndex + batch.triangleCount * 3 <= mHighTriangleCount * 3, "River, bad draw call!" );
 882
 883            GFX->drawIndexedPrimitive( GFXTriangleList, 
 884                                       0, 
 885                                       0, 
 886                                       batch.vertCount, 
 887                                       batch.startIndex, 
 888                                       batch.triangleCount );
 889         }
 890      }
 891
 892   } // while( mat->setupPass( sgData ) )      
 893}
 894
 895void River::updateUnderwaterEffect( SceneRenderState *state )
 896{
 897   // Calculate mWaterPlane before calling updateUnderwaterEffect.
 898   Point3F dummy;
 899   _getWaterPlane( state->getCameraPosition(), mWaterFogData.plane, dummy );
 900
 901   Parent::updateUnderwaterEffect( state );
 902}
 903
 904void River::setShaderParams( SceneRenderState *state, BaseMatInstance* mat, const WaterMatParams& paramHandles )
 905{
 906   // Set variables that will be assigned to shader consts within WaterCommon
 907   // before calling Parent::setShaderParams
 908
 909   mUndulateMaxDist = mLodDistance;
 910
 911   Parent::setShaderParams( state, mat, paramHandles );   
 912
 913   // Now set the rest of the shader consts that are either unique to this
 914   // class or that WaterObject leaves to us to handle...
 915
 916   MaterialParameters* matParams = mat->getMaterialParameters();  
 917
 918   // set vertex shader constants
 919   //-----------------------------------           
 920
 921   matParams->setSafe(paramHandles.mGridElementSizeSC, 1.0f);
 922   if ( paramHandles.mModelMatSC->isValid() )
 923      matParams->set(paramHandles.mModelMatSC, MatrixF::Identity, GFXSCT_Float4x4);
 924
 925   // set pixel shader constants
 926   //-----------------------------------
 927
 928   ColorF c( mWaterFogData.color );
 929   matParams->setSafe(paramHandles.mBaseColorSC, c);
 930
 931   // By default we need to show a true reflection is fullReflect is enabled and
 932   // we are above water.
 933   F32 reflect = mPlaneReflector.isEnabled() && !isUnderwater( state->getCameraPosition() );
 934   
 935   // If we were occluded the last frame a query was fetched ( not necessarily last frame )
 936   // and we weren't updated last frame... we don't have a valid texture to show
 937   // so use the cubemap / fake reflection color this frame.
 938   if ( mPlaneReflector.lastUpdateMs != REFLECTMGR->getLastUpdateMs() && mPlaneReflector.isOccluded() )
 939      reflect = false;
 940
 941   Point4F reflectParams( mWaterPos.z, 0.0f, 1000.0f, !reflect );
 942   matParams->setSafe(paramHandles.mReflectParamsSC, reflectParams );
 943
 944   matParams->setSafe(paramHandles.mReflectNormalSC, mPlaneReflector.refplane );   
 945}
 946
 947bool River::isUnderwater( const Point3F &pnt ) const
 948{
 949   return containsPoint( pnt, NULL );
 950}
 951
 952U32 River::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
 953{  
 954   // Pack Parent.
 955   U32 retMask = Parent::packUpdate(con, mask, stream);
 956
 957   if ( stream->writeFlag( mask & RiverMask ) )
 958   {
 959      // Write Object Transform.
 960      stream->writeAffineTransform(mObjToWorld);
 961
 962      stream->write( mMetersPerSegment );      
 963      stream->write( mSegmentsPerBatch );
 964      stream->write( mDepthScale );
 965      stream->write( mMaxDivisionSize );
 966      stream->write( mColumnCount );
 967
 968      stream->write( mFlowMagnitude );
 969      stream->write( mLodDistance );
 970   }   
 971
 972   if ( stream->writeFlag( mask & NodeMask ) )
 973   {
 974      const U32 nodeByteSize = 32; // Based on sending all of a node's parameters
 975
 976      // Test if we can fit all of our nodes within the current stream.
 977      // We make sure we leave 100 bytes still free in the stream for whatever
 978      // may follow us.
 979      S32 allowedBytes = stream->getWriteByteSize() - 100;
 980      if ( stream->writeFlag( (nodeByteSize * mNodes.size()) < allowedBytes ) )
 981      {
 982         // All nodes should fit, so send them out now.
 983         stream->writeInt( mNodes.size(), 16 );
 984
 985         for ( U32 i = 0; i < mNodes.size(); i++ )
 986         {
 987            mathWrite( *stream, mNodes[i].point );
 988            stream->write( mNodes[i].width );
 989            stream->write( mNodes[i].depth );
 990            mathWrite( *stream, mNodes[i].normal );
 991         }
 992      }
 993      else
 994      {
 995         // There isn't enough space left in the stream for all of the
 996         // nodes.  Batch them up into NetEvents.
 997         U32 id = gServerNodeListManager->nextListId();
 998         U32 count = 0;
 999         U32 index = 0;
1000         while (count < mNodes.size())
1001         {
1002            count += NodeListManager::smMaximumNodesPerEvent;
1003            if (count > mNodes.size())
1004            {
1005               count = mNodes.size();
1006            }
1007
1008            RiverNodeEvent* event = new RiverNodeEvent();
1009            event->mId = id;
1010            event->mTotalNodes = mNodes.size();
1011            event->mLocalListStart = index;
1012
1013            for (; index<count; ++index)
1014            {
1015               event->mPositions.push_back( mNodes[index].point );
1016               event->mWidths.push_back( mNodes[index].width );
1017               event->mDepths.push_back( mNodes[index].depth );
1018               event->mNormals.push_back( mNodes[index].normal );
1019            }
1020
1021            con->postNetEvent( event );
1022         }
1023
1024         stream->write( id );
1025      }
1026   }
1027   
1028   if( stream->writeFlag( mask & ( RiverMask | InitialUpdateMask ) ) )
1029   {
1030      // This is set to allow the user to modify the size of the water dynamically
1031      // in the editor
1032      mathWrite( *stream, mObjScale );
1033      stream->writeAffineTransform( mObjToWorld );
1034   }
1035
1036   stream->writeFlag( mask & RegenMask );
1037
1038   return retMask;
1039}
1040
1041void River::unpackUpdate(NetConnection * con, BitStream * stream)
1042{
1043   // Unpack Parent.
1044   Parent::unpackUpdate(con, stream);
1045
1046   // RiverMask
1047   if(stream->readFlag())
1048   {
1049      MatrixF     ObjectMatrix;
1050      stream->readAffineTransform(&ObjectMatrix);
1051      Parent::setTransform(ObjectMatrix);
1052     
1053      stream->read( &mMetersPerSegment );    
1054      stream->read( &mSegmentsPerBatch );
1055      stream->read( &mDepthScale );
1056      stream->read( &mMaxDivisionSize );
1057      stream->read( &mColumnCount );
1058
1059      stream->read( &mFlowMagnitude );
1060      stream->read( &mLodDistance );
1061   }
1062
1063   // NodeMask
1064   if ( stream->readFlag() )
1065   {
1066      if (stream->readFlag())
1067      {
1068         // Nodes have been passed in this update
1069         U32 count = stream->readInt( 16 );
1070
1071         mNodes.clear();
1072
1073         Point3F pos;
1074         VectorF normal;
1075         F32 width,depth;
1076
1077         for ( U32 i = 0; i < count; i++ )
1078         {
1079            mathRead( *stream, &pos );
1080            stream->read( &width );         
1081            stream->read( &depth );
1082            mathRead( *stream, &normal );
1083            _addNode( pos, width, depth, normal );         
1084         }
1085      }
1086      else
1087      {
1088         // Nodes will arrive as events
1089         U32 id;
1090         stream->read( &id );
1091
1092         // Check if the road's nodes made it here before we did.
1093         NodeListManager::NodeList* list = NULL;
1094         if ( gClientNodeListManager->findListById( id, &list, true) )
1095         {
1096            // Work with the completed list
1097            RiverNodeList* riverList = dynamic_cast<RiverNodeList*>( list );
1098            if (riverList)
1099               buildNodesFromList( riverList );
1100
1101            delete list;
1102         }
1103         else
1104         {
1105            // Nodes have not yet arrived, so register our interest in the list
1106            RiverNodeListNotify* notify = new RiverNodeListNotify( this, id );
1107            gClientNodeListManager->registerNotification( notify );
1108         }
1109      }
1110   }
1111
1112   // RiverMask | InitialUpdateMask
1113   if( stream->readFlag() )
1114   {
1115      mathRead( *stream, &mObjScale );
1116      stream->readAffineTransform( &mObjToWorld );
1117   }
1118
1119   // RegenMask
1120   if ( stream->readFlag() && isProperlyAdded() )  
1121      regenerate();
1122}
1123
1124void River::_getWaterPlane( const Point3F &camPos, PlaneF &outPlane, Point3F &outPos )
1125{
1126   // Find the RiverSegment closest to the camera.   
1127   F32 closestDist = F32_MAX;
1128   S32 closestSegment = 0;
1129   Point3F projPnt(0.0f, 0.0f, 0.0f);
1130
1131   VectorF normal(0,0,0);
1132
1133   for ( U32 i = 0; i < mSegments.size(); i++ )
1134   {
1135      const RiverSegment &segment = mSegments[i];
1136
1137      const Point3F pos = MathUtils::mClosestPointOnSegment( segment.slice0->p1, segment.slice1->p1, camPos );
1138
1139      F32 dist = ( camPos - pos ).len();
1140
1141      if ( dist < closestDist )
1142      {
1143         closestDist = dist;
1144         closestSegment = i;
1145         projPnt = pos;
1146      }
1147
1148      normal += segment.getSurfaceNormal();
1149   }
1150
1151   if ( mReflectNormalUp )
1152      normal.set(0,0,1);
1153   else
1154      normal.normalizeSafe();
1155
1156   outPos = projPnt;
1157   outPlane.set( projPnt, normal );
1158}
1159
1160void River::setTransform( const MatrixF &mat )
1161{
1162   
1163   for ( U32 i = 0; i < mNodes.size(); i++ )
1164   {
1165      mWorldToObj.mulP( mNodes[i].point );
1166      mat.mulP( mNodes[i].point );
1167   }
1168
1169   /*
1170   // Get the amount of change in position.
1171   MatrixF oldMat = getTransform();
1172   Point3F oldPos = oldMat.getPosition();
1173   Point3F newPos = mat.getPosition();
1174   Point3F delta = newPos - oldPos;
1175
1176   // Offset all nodes by that amount
1177   for ( U32 i = 0; i < mNodes.size(); i++ )
1178   {
1179      mNodes[i].point += delta;
1180   }
1181
1182   // Assign the new position ( we ignore rotation )   
1183   MatrixF newMat( oldMat );
1184   newMat.setPosition( newPos );
1185   */
1186   
1187   Parent::setTransform( mat );
1188
1189   // Regenerate and update the client
1190   _regenerate();
1191   setMaskBits( NodeMask | RegenMask );
1192}
1193
1194void River::setScale( const VectorF &scale )
1195{
1196   // We ignore scale requests from the editor
1197   // right now.
1198}
1199
1200bool River::castRay(const Point3F &s, const Point3F &e, RayInfo* info)
1201{
1202   Point3F start = s;
1203   Point3F end = e;
1204   mObjToWorld.mulP(start);
1205   mObjToWorld.mulP(end);
1206
1207   F32 out = 1.0f;   // The output fraction/percentage along the line defined by s and e
1208   VectorF norm(0.0f, 0.0f, 0.0f);     // The normal of the face intersected
1209
1210   Vector<RiverHitSegment> hitSegments;
1211
1212   for ( U32 i = 0; i < mSegments.size(); i++ )
1213   {
1214      const RiverSegment &segment = mSegments[i];
1215
1216      F32 t;
1217      VectorF n;
1218
1219      if ( segment.worldbounds.collideLine( start, end, &t, &n ) )
1220      {
1221         hitSegments.increment();
1222         hitSegments.last().t = t;
1223         hitSegments.last().idx = i;         
1224      }
1225   }
1226
1227   dQsort( hitSegments.address(), hitSegments.size(), sizeof(RiverHitSegment), compareHitSegments );
1228
1229   U32 idx0, idx1, idx2;
1230   F32 t;
1231
1232   for ( U32 i = 0; i < hitSegments.size(); i++ )
1233   {
1234      U32 segIdx = hitSegments[i].idx;
1235      const RiverSegment &segment = mSegments[segIdx];
1236
1237      // Each segment has 6 faces
1238      for ( U32 j = 0; j < 6; j++ )
1239      {
1240         if ( j == 4 && segIdx != 0 )
1241            continue;
1242
1243         if ( j == 5 && segIdx != mSegments.size() - 1 )
1244            continue;
1245
1246         // Each face has 2 triangles
1247         for ( U32 k = 0; k < 2; k++ )
1248         {
1249            idx0 = gIdxArray[j][k][0];
1250            idx1 = gIdxArray[j][k][1];
1251            idx2 = gIdxArray[j][k][2];
1252
1253            const Point3F &v0 = segment[idx0];
1254            const Point3F &v1 = segment[idx1];
1255            const Point3F &v2 = segment[idx2];
1256
1257            if ( !MathUtils::mLineTriangleCollide( start, end, 
1258                                                   v2, v1, v0,
1259                                                   NULL,
1260                                                   &t ) )
1261               continue;
1262
1263            if ( t >= 0.0f && t < 1.0f && t < out )
1264            {
1265               out = t;
1266
1267               // optimize this, can be calculated easily within 
1268               // the collision test
1269               norm = PlaneF( v0, v1, v2 );
1270            }
1271         }
1272      }
1273
1274      if (out >= 0.0f && out < 1.0f)
1275         break;
1276   }
1277
1278   if (out >= 0.0f && out < 1.0f)
1279   {
1280      info->t = out;
1281      info->normal = norm;
1282      info->point.interpolate(start, end, out);
1283      info->face = -1;
1284      info->object = this;
1285
1286      return true;
1287   }
1288
1289   return false;
1290}
1291
1292bool River::collideBox(const Point3F &start, const Point3F &end, RayInfo* info)
1293{
1294   return false;
1295}
1296
1297bool River::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere )
1298{
1299   Vector<const RiverSegment*> hitSegments;
1300   for ( U32 i = 0; i < mSegments.size(); i++ )
1301   {
1302      const RiverSegment &segment = mSegments[i];
1303      if ( segment.worldbounds.isOverlapped( box ) )
1304      {
1305         hitSegments.push_back( &segment );
1306      }
1307   }
1308
1309   if ( !hitSegments.size() )
1310      return false;
1311   
1312   polyList->setObject( this );
1313   polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) );
1314
1315   for ( U32 i = 0; i < hitSegments.size(); i++ )
1316   {
1317      const RiverSegment* segment = hitSegments[i];
1318      for ( U32 k = 0; k < 2; k++ )
1319      {
1320         // gIdxArray[0] gives us the top plane (see table definition).
1321         U32 idx0 = gIdxArray[0][k][0];
1322         U32 idx1 = gIdxArray[0][k][1];
1323         U32 idx2 = gIdxArray[0][k][2];
1324
1325         const Point3F &v0 = (*segment)[idx0];
1326         const Point3F &v1 = (*segment)[idx1];
1327         const Point3F &v2 = (*segment)[idx2];
1328      
1329         // Add vertices to poly list.
1330         U32 i0 = polyList->addPoint(v0);
1331         polyList->addPoint(v1);
1332         polyList->addPoint(v2);
1333
1334         // Add plane between them.
1335         polyList->begin(0, 0);
1336         polyList->vertex(i0);
1337         polyList->vertex(i0+1);
1338         polyList->vertex(i0+2);
1339         polyList->plane(i0, i0+1, i0+2);
1340         polyList->end();
1341      }
1342   }
1343
1344   return true;
1345}
1346
1347F32 River::getWaterCoverage( const Box3F &worldBox ) const
1348{
1349   PROFILE_SCOPE( River_GetWaterCoverage );
1350
1351   if ( !mWorldBox.isOverlapped(worldBox) )
1352      return 0.0f;
1353
1354   Point3F bottomPnt = worldBox.getCenter();
1355   bottomPnt.z = worldBox.minExtents.z;
1356
1357   F32 farthest = 0.0f;
1358
1359   for ( U32 i = 0; i < mSegments.size(); i++ )
1360   {
1361      const RiverSegment &segment = mSegments[i];
1362
1363      if ( !segment.worldbounds.isOverlapped(worldBox) )
1364         continue;
1365
1366      if ( !segment.intersectBox( worldBox ) )
1367         continue;
1368
1369      F32 distance = segment.distanceToSurface( bottomPnt );
1370     
1371      if ( distance > farthest )
1372         farthest = distance;
1373   }
1374
1375   F32 height = worldBox.maxExtents.z - worldBox.minExtents.z;
1376   F32 distance = mClampF( farthest, 0.0f, height );
1377   F32 coverage = distance / height;
1378
1379   return coverage;   
1380}
1381
1382F32 River::getSurfaceHeight( const Point2F &pos ) const
1383{
1384   PROFILE_SCOPE( River_GetSurfaceHeight );
1385
1386   Point3F origin( pos.x, pos.y, mWorldBox.maxExtents.z );
1387   Point3F direction(0,0,-1);
1388   U32 nodeIdx;
1389   Point3F collisionPnt;
1390
1391   if ( !collideRay( origin, direction, &nodeIdx, &collisionPnt ) )
1392      return -1.0f;
1393
1394   return collisionPnt.z;
1395}
1396
1397VectorF River::getFlow( const Point3F &pos ) const
1398{
1399   PROFILE_SCOPE( River_GetFlow );
1400
1401   for ( U32 i = 0; i < mSegments.size(); i++ )
1402   {
1403      const RiverSegment &segment = mSegments[i];
1404
1405      if ( !segment.containsPoint(pos) )
1406         continue;
1407
1408      VectorF flow = segment.slice0->p1 - segment.slice1->p1;
1409      flow.normalize();
1410      flow *= mFlowMagnitude;
1411
1412      return flow;
1413   }
1414
1415   return VectorF::Zero;
1416}
1417
1418void River::onReflectionInfoChanged()
1419{
1420   /*
1421   if ( isClientObject() && GFX->getPixelShaderVersion() >= 1.4 )
1422   {
1423      if ( mFullReflect )
1424         REFLECTMGR->registerObject( this, ReflectDelegate( this, &River::updateReflection ), mReflectPriority, mReflectMaxRateMs, mReflectMaxDist );
1425      else
1426      {
1427         REFLECTMGR->unregisterObject( this );
1428         mReflectTex = NULL;
1429      }
1430   }
1431   */
1432}
1433
1434void River::_regenerate()
1435{               
1436   if ( mNodes.size() == 0 )
1437      return;
1438
1439   const Point3F &nodePt = mNodes.first().point;
1440
1441   MatrixF mat( true );
1442   mat.setPosition( nodePt );
1443   Parent::setTransform( mat );
1444
1445   _generateSlices();
1446}
1447
1448void River::_generateSlices()
1449{      
1450   if ( mNodes.size() < 2 )
1451      return;
1452
1453   U32 nodeCount = mNodes.size();
1454   RiverSplineNode *splineNodes = new RiverSplineNode[nodeCount];
1455
1456   for ( U32 i = 0; i < nodeCount; i++ )
1457   {
1458      const RiverNode &node = mNodes[i];
1459      splineNodes[i].x = node.point.x;
1460      splineNodes[i].y = node.point.y;
1461      splineNodes[i].z = node.point.z;
1462      splineNodes[i].width = node.width;
1463      splineNodes[i].depth = node.depth;
1464      splineNodes[i].normal = node.normal;
1465   }
1466
1467   CatmullRom<RiverSplineNode> spline;
1468   spline.initialize( nodeCount, splineNodes );
1469   delete [] splineNodes;
1470
1471   mSlices.clear();
1472
1473   for ( U32 i = 1; i < nodeCount; i++ )
1474   {
1475      F32 t0 = spline.getTime( i-1 );
1476      F32 t1 = spline.getTime( i );
1477
1478      F32 segLength = spline.arcLength( t0, t1 );
1479    
1480      U32 numSegments = mCeil( segLength / mMetersPerSegment );
1481      numSegments = getMax( numSegments, (U32)1 );
1482      F32 tstep = ( t1 - t0 ) / numSegments;
1483
1484      //AssertFatal( numSegments > 0, "River::_generateSlices, got zero segments!" );   
1485
1486      U32 startIdx = 0;
1487      U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments;
1488
1489      for ( U32 j = startIdx; j < endIdx; j++ )
1490      {
1491         F32 t = t0 + tstep * j; //spline.findParameterByDistance( 0.0f, i * segLen );
1492         RiverSplineNode val = spline.evaluate(t);
1493
1494         RiverSlice slice;
1495         slice.p1.set( val.x, val.y, val.z );
1496         slice.uvec.set( 0,0,1 );
1497         slice.width = val.width;
1498         slice.depth = val.depth;
1499         slice.parentNodeIdx = i-1;
1500         slice.normal = val.normal;
1501         slice.normal.normalize();
1502         mSlices.push_back( slice );
1503      }   
1504   }
1505   
1506   //
1507   // Calculate fvec and rvec for all slices
1508   //
1509   RiverSlice *pSlice = NULL;
1510   RiverSlice *pNextSlice = NULL;
1511
1512   // Must do the first slice outside the loop
1513   {
1514      pSlice = &mSlices[0];
1515      pNextSlice = &mSlices[1];
1516      pSlice->fvec = pNextSlice->p1 - pSlice->p1;
1517      pSlice->fvec.normalize();
1518      pSlice->rvec = mCross( pSlice->fvec, pSlice->normal );
1519      pSlice->rvec.normalize();
1520      pSlice->uvec = mCross( pSlice->rvec, pSlice->fvec );            
1521      pSlice->uvec.normalize();
1522      pSlice->rvec = mCross( pSlice->fvec, pSlice->uvec );      
1523      pSlice->rvec.normalize();
1524   }
1525
1526   for ( U32 i = 1; i < mSlices.size() - 1; i++ )
1527   {
1528      pSlice = &mSlices[i];
1529      pNextSlice = &mSlices[i+1];
1530
1531      pSlice->fvec = pNextSlice->p1 - pSlice->p1;
1532      pSlice->fvec.normalize();
1533
1534      pSlice->rvec = mCross( pSlice->fvec, pSlice->normal );
1535      pSlice->rvec.normalize();
1536
1537      pSlice->uvec = mCross( pSlice->rvec, pSlice->fvec );      
1538      pSlice->uvec.normalize();
1539
1540      pSlice->rvec = mCross( pSlice->fvec, pSlice->uvec );
1541      pSlice->rvec.normalize();
1542   }
1543
1544   // Must do the last slice outside the loop
1545   {
1546      RiverSlice *lastSlice = &mSlices[mSlices.size()-1];
1547      RiverSlice *prevSlice = &mSlices[mSlices.size()-2];
1548
1549      lastSlice->fvec = prevSlice->fvec;
1550
1551      lastSlice->rvec = mCross( lastSlice->fvec, lastSlice->normal );
1552      lastSlice->rvec.normalize();
1553
1554      lastSlice->uvec = mCross( lastSlice->rvec, lastSlice->fvec );      
1555      lastSlice->uvec.normalize();
1556
1557      lastSlice->rvec = mCross( lastSlice->fvec, lastSlice->uvec );
1558      lastSlice->rvec.normalize();
1559   }
1560
1561
1562   //
1563   // Calculate p0/p2/pb0/pb2 for all slices
1564   //      
1565   for ( U32 i = 0; i < mSlices.size(); i++ )
1566   {
1567      RiverSlice *slice = &mSlices[i];
1568      slice->p0 = slice->p1 - slice->rvec * slice->width * 0.5f;
1569      slice->p2 = slice->p1 + slice->rvec * slice->width * 0.5f;
1570      slice->pb0 = slice->p0 - slice->uvec * slice->depth;
1571      slice->pb2 = slice->p2 - slice->uvec * slice->depth;
1572   }
1573   
1574   // Generate the object/world bounds
1575   Box3F box;
1576   for ( U32 i = 0; i < mSlices.size(); i++ )
1577   {
1578      const RiverSlice &slice = mSlices[i];
1579
1580      if ( i == 0 )
1581      {
1582         box.minExtents = slice.p0;
1583         box.maxExtents = slice.p2;
1584         box.extend( slice.pb0 );
1585         box.extend( slice.pb2 );
1586      }
1587      else
1588      {
1589         box.extend( slice.p0 ); 
1590         box.extend( slice.p2 );
1591         box.extend( slice.pb0 );
1592         box.extend( slice.pb2 );
1593      }
1594   }
1595
1596   mWorldBox = box;
1597   //mObjBox.minExtents -= pos;
1598   //mObjBox.maxExtents -= pos;
1599   resetObjectBox();
1600
1601   // Make sure we are in the correct bins given our world box.
1602   if( getSceneManager() != NULL )
1603      getSceneManager()->notifyObjectDirty( this );
1604
1605   _generateSegments();   
1606}
1607
1608void River::_generateSegments()
1609{
1610   mSegments.clear();
1611
1612   for ( U32 i = 0; i < mSlices.size() - 1; i++ )
1613   {
1614      RiverSegment seg( &mSlices[i], &mSlices[i+1] );
1615      
1616      mSegments.push_back( seg );
1617   }
1618
1619   /*
1620   #ifdef TORQUE_DEBUG
1621
1622      for ( U32 i = 0; i < mSegments.size(); i++ )
1623      {
1624         const RiverSegment &segment = mSegments[i];         
1625         PlaneF normal0 = MathUtils::mTriangleNormal( segment.slice0->p0, segment.slice1->p0, segment.slice1->p2 );
1626         PlaneF normal1 = MathUtils::mTriangleNormal( segment.slice0->p0, segment.slice1->p2, segment.slice0->p2 );
1627         AssertFatal( true || normal0 != normal1, "River::generateSegments, segment is not coplanar!" );
1628      }
1629
1630   #endif // TORQUE_DEBUG
1631   */
1632
1633   // We have to go back and generate normals for each slice
1634   // to be used in calculation of the reflect plane.  
1635   // The slice-normal we calculate are relative to the surface normal 
1636   // of the segments adjacent to the slice.
1637   /*
1638   if ( mSlices.size() >= 2 )
1639   {
1640      mSlices[0].normal = mSegments[0].getSurfaceNormal();
1641      for ( U32 i = 1; i < mSlices.size() - 1; i++ )
1642      {
1643         mSlices[i].normal = ( mSegments[i-1].getSurfaceNormal() + mSegments[i].getSurfaceNormal() ) / 2;
1644      }
1645      mSlices.last().normal = mSegments.last().getSurfaceNormal();
1646   }
1647   */
1648   
1649   _generateVerts();
1650}
1651
1652void River::_generateVerts()
1653{           
1654   if ( isServerObject() )
1655      return;
1656
1657   // These will depend on the level of subdivision per segment
1658   // calculated below.
1659   mHighVertCount = 0;
1660   mHighTriangleCount = 0;
1661   
1662   // Calculate the number of row/column subdivisions per each
1663   // RiverSegment.
1664
1665   F32 greatestWidth = 0.1f;
1666   for ( U32 i = 0; i < mNodes.size(); i++ )
1667   {
1668      RiverNode &node = mNodes[i];
1669      if ( node.width > greatestWidth )
1670         greatestWidth = node.width;
1671   }
1672
1673   mColumnCount = mCeil( greatestWidth / mMaxDivisionSize );
1674
1675   for ( U32 i = 0; i < mSegments.size(); i++ )
1676   {
1677      RiverSegment &segment = mSegments[i];
1678      const RiverSlice *slice = segment.slice0;
1679      const RiverSlice *nextSlice = segment.slice1;
1680
1681      // Calculate the size of divisions in the forward direction ( p00 -> p01 )          
1682      F32 segLength = (nextSlice->p1 - slice->p1).len();    
1683
1684      // A division count of one is actually NO subdivision,
1685      // the segment corners are the only verts in this segment.
1686      U32 numRows = 1;
1687
1688      if ( segLength > 0.0f )
1689         numRows = mCeil( segLength / mMaxDivisionSize );
1690
1691      // The problem with calculating num columns per segment is 
1692      // two adjacent - high lod segments of different width can have
1693      // verts that don't line up!  So even though RiverSegment HAS a 
1694      // column data member we initialize all segments in the river to
1695      // the same (River::mColumnCount)
1696
1697      // Calculate the size of divisions in the right direction ( p00 -> p10 ) 
1698      // F32 segWidth = ( ( p11 - p01 ).len() + ( p10 - p00 ).len() ) * 0.5f;
1699
1700      // U32 numColumns = 5;
1701      //F32 columnSize = segWidth / numColumns;
1702
1703      //while ( columnSize > mMaxDivisionSize )
1704      //{
1705      // numColumns++;
1706      // columnSize = segWidth / numColumns;
1707      //}
1708      
1709      // Save the calculated numb of columns / rows for this segment.
1710      segment.columns = mColumnCount;
1711      segment.rows = numRows;
1712      
1713      // Save the corresponding number of verts/prims
1714      segment.numVerts = ( 1 + mColumnCount ) * ( 1 + numRows );
1715      segment.numTriangles = mColumnCount * numRows * 2;
1716
1717      mHighVertCount += segment.numVerts;
1718      mHighTriangleCount += segment.numTriangles;
1719   }
1720
1721   // Number of low detail verts/prims.
1722   mLowVertCount = mSlices.size() * 2; 
1723   mLowTriangleCount = mSegments.size() * 2;   
1724
1725   // Allocate the low detail VertexBuffer, 
1726   // this will stay in memory and will never need to change.
1727   mVB_low.set( GFX, mLowVertCount, GFXBufferTypeStatic );   
1728   
1729   GFXWaterVertex *lowVertPtr = mVB_low.lock(); 
1730   U32 vertCounter = 0;
1731
1732   // The texCoord.y value start/end for a segment
1733   // as we loop through them.   
1734   F32 textCoordV = 0;
1735
1736   //
1737   // Fill the low-detail VertexBuffer
1738   //
1739   for ( U32 i = 0; i < mSlices.size(); i++ )
1740   {
1741      RiverSlice &slice = mSlices[i];      
1742
1743      lowVertPtr->point = slice.p0;    
1744      lowVertPtr->normal = slice.normal;      
1745      lowVertPtr->undulateData.set( -slice.width*0.5f, textCoordV );   
1746      lowVertPtr->horizonFactor.set( 0, 0, 0, 0 );
1747      lowVertPtr++;
1748      vertCounter++;
1749
1750      lowVertPtr->point = slice.p2;
1751      lowVertPtr->normal = slice.normal;
1752      lowVertPtr->undulateData.set( slice.width*0.5f, textCoordV );     
1753      lowVertPtr->horizonFactor.set( 0, 0, 0, 0 );
1754      lowVertPtr++;
1755      vertCounter++;
1756
1757      // Save this so we can get it later.
1758      slice.texCoordV = textCoordV;
1759
1760      if ( i < mSlices.size() - 1 )
1761      {         
1762         // Increment the textCoordV for the next slice.
1763         F32 segLen = ( mSlices[i+1].p1 - slice.p1 ).len();
1764         textCoordV += segLen;         
1765      }
1766   }
1767
1768   AssertFatal( vertCounter == mLowVertCount, "River, wrote incorrect number of verts in mBV_low!" );
1769   
1770   // Unlock the low-detail VertexBuffer, we are done filling it.
1771   mVB_low.unlock();
1772
1773   //
1774   // Create the low-detail prim buffer(s)
1775   // 
1776   mPB_low.set( GFX, mLowTriangleCount * 3, mLowTriangleCount, GFXBufferTypeStatic );
1777
1778   U16 *lowIdxBuff;
1779   mPB_low.lock(&lowIdxBuff);     
1780   U32 curLowIdx = 0; 
1781
1782   // Temporaries to hold indices for the corner points of a quad.
1783   U32 p00, p01, p11, p10;
1784
1785   U32 offset = 0;
1786
1787   // Fill the low-detail PrimitiveBuffer   
1788   for ( U32 i = 0; i < mSegments.size(); i++ )
1789   {     
1790      //const RiverSegment &segment = mSegments[i];
1791      
1792      // Two triangles formed by the corner points of this segment
1793      // into the the low detail primitive buffer.
1794      p00 = offset;
1795      p01 = p00 + 2;
1796      p11 = p01 + 1;
1797      p10 = p00 + 1;
1798
1799      // Upper-Left triangle
1800      lowIdxBuff[curLowIdx] = p00;
1801      curLowIdx++;
1802      lowIdxBuff[curLowIdx] = p01;
1803      curLowIdx++;
1804      lowIdxBuff[curLowIdx] = p11;
1805      curLowIdx++;
1806
1807      // Lower-Right Triangle
1808      lowIdxBuff[curLowIdx] = p00;
1809      curLowIdx++;
1810      lowIdxBuff[curLowIdx] = p11;
1811      curLowIdx++;
1812      lowIdxBuff[curLowIdx] = p10;
1813      curLowIdx++;      
1814
1815      offset += 2;
1816   }
1817
1818   AssertFatal( curLowIdx == mLowTriangleCount * 3, "River, wrote incorrect number of indices in mPB_low!" );
1819
1820   // Unlock the low-detail PrimitiveBuffer, we are done filling it.
1821   mPB_low.unlock();
1822}
1823
1824bool River::getClosestNode( const Point3F &pos, U32 &idx ) const
1825{
1826   F32 closestDist = F32_MAX;
1827
1828   for ( U32 i = 0; i < mNodes.size(); i++ )
1829   {
1830      F32 dist = ( mNodes[i].point - pos ).len();
1831      if ( dist < closestDist )
1832      {
1833         closestDist = dist;
1834         idx = i;
1835      }      
1836   }
1837
1838   return closestDist != F32_MAX;
1839}
1840
1841bool River::containsPoint( const Point3F &worldPos, U32 *nodeIdx ) const
1842{
1843   // If point isn't in the world box, 
1844   // it's definitely not in the River.
1845   //if ( !getWorldBox().isContained( worldPos ) )
1846   //   return false;   
1847
1848   // Look through all edges, does the polygon
1849   // formed from adjacent edge's contain the worldPos?
1850   for ( U32 i = 0; i < mSegments.size(); i++ )
1851   {
1852      const RiverSegment &segment = mSegments[i];
1853
1854      if ( segment.containsPoint( worldPos ) )
1855      {
1856         if ( nodeIdx )
1857            *nodeIdx = i;
1858         return true;
1859      }      
1860   }
1861
1862   return false;
1863}
1864
1865F32 River::distanceToSurface( const Point3F &pnt, U32 segmentIdx )
1866{
1867   return mSegments[segmentIdx].distanceToSurface( pnt );
1868}
1869
1870bool River::collideRay( const Point3F &origin, const Point3F &direction, U32 *nodeIdx, Point3F *collisionPnt ) const
1871{
1872   Point3F p0 = origin;
1873   Point3F p1 = origin + direction * 2000.0f;
1874
1875   // If the line segment does not collide with the river's world box, 
1876   // it definitely does not collide with any part of the river.
1877   if ( !getWorldBox().collideLine( p0, p1 ) )
1878      return false;
1879   
1880   if ( mSlices.size() < 2 )
1881      return false;
1882
1883   MathUtils::Quad quad;
1884   MathUtils::Ray ray;
1885   F32 t;
1886
1887   // Check each river segment (formed by a pair of slices) for collision
1888   // with the line segment.
1889   for ( U32 i = 0; i < mSlices.size() - 1; i++ )
1890   {
1891      const RiverSlice &slice0 = mSlices[i];
1892      const RiverSlice &slice1 = mSlices[i+1];
1893
1894      // For simplicities sake we will only test for collision between the
1895      // line segment and the Top face of the river segment.
1896
1897      // Clockwise starting with the leftmost/closest point.
1898      quad.p00 = slice0.p0;
1899      quad.p01 = slice1.p0;
1900      quad.p11 = slice1.p2;
1901      quad.p10 = slice0.p2;
1902      
1903      ray.origin = origin;
1904      ray.direction = direction;
1905
1906      // NOTE: 
1907      // mRayQuadCollide is designed for a "real" quad in which all four points
1908      // are coplanar which is actually not the case here. The more twist 
1909      // and turn in-between two neighboring river slices the more incorrect 
1910      // this calculation will be.
1911
1912      if ( MathUtils::mRayQuadCollide( quad, ray, NULL, &t ) )
1913      {
1914         if ( nodeIdx )
1915            *nodeIdx = slice0.parentNodeIdx;         
1916         if ( collisionPnt )
1917            *collisionPnt = ray.origin + ray.direction * t;
1918         return true;
1919      }
1920   }
1921
1922   return false;
1923}
1924
1925Point3F River::getNodePosition( U32 idx ) const
1926{
1927   if ( mNodes.size() - 1 < idx )
1928      return Point3F();
1929
1930   return mNodes[idx].point;
1931}
1932
1933void River::setNodePosition( U32 idx, const Point3F &pos )
1934{
1935   if ( mNodes.size() - 1 < idx )
1936      return;
1937
1938   mNodes[idx].point = pos;
1939
1940   regenerate();
1941
1942   setMaskBits( NodeMask | RegenMask );
1943}
1944
1945U32 River::addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal )
1946{
1947   U32 idx = _addNode( pos, width, depth, normal );   
1948
1949   regenerate();
1950
1951   setMaskBits( NodeMask | RegenMask );
1952
1953   return idx;
1954}
1955
1956U32 River::insertNode(const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx)
1957{
1958   U32 ret = _insertNode( pos, width, depth, normal, idx );
1959
1960   regenerate();
1961
1962   setMaskBits( NodeMask | RegenMask );
1963
1964   return ret;
1965}
1966
1967void River::setNode(const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx)
1968{
1969   if ( mNodes.size() - 1 < idx )
1970      return;
1971
1972   RiverNode &node = mNodes[idx];
1973   node.point = pos;   
1974   node.width = width;
1975   node.depth = depth;
1976   node.normal = normal;
1977
1978   regenerate();
1979
1980   setMaskBits( NodeMask | RegenMask );
1981}
1982
1983void River::setNodeWidth( U32 idx, F32 meters )
1984{
1985   meters = mClampF( meters, MIN_NODE_WIDTH, MAX_NODE_WIDTH );
1986
1987   if ( mNodes.size() - 1 < idx )
1988      return;
1989
1990   mNodes[idx].width = meters;
1991   _regenerate();
1992
1993   setMaskBits( RegenMask | NodeMask );
1994}
1995
1996void River::setNodeHeight( U32 idx, F32 height )
1997{
1998   if ( mNodes.size() - 1 < idx )
1999      return;
2000
2001   mNodes[idx].point.z = height;
2002   _regenerate();
2003
2004   setMaskBits( RegenMask | NodeMask );
2005}
2006
2007F32 River::getNodeWidth( U32 idx ) const
2008{
2009   if ( mNodes.size() - 1 < idx )
2010      return -1.0f;
2011
2012   return mNodes[idx].width;
2013}
2014
2015void River::setNodeDepth( U32 idx, F32 meters )
2016{
2017   meters = mClampF( meters, MIN_NODE_DEPTH, MAX_NODE_DEPTH );
2018   
2019   if ( mNodes.size() - 1 < idx )
2020      return;
2021
2022   mNodes[idx].depth = meters;
2023   _regenerate();
2024   setMaskBits( RiverMask | RegenMask | NodeMask );
2025}
2026
2027void River::setNodeNormal( U32 idx, const VectorF &normal )
2028{
2029   if ( mNodes.size() - 1 < idx )
2030      return;
2031
2032   mNodes[idx].normal = normal;
2033
2034   regenerate();
2035
2036   setMaskBits( NodeMask | RegenMask );
2037}
2038
2039F32 River::getNodeDepth( U32 idx ) const
2040{
2041   if ( mNodes.size() - 1 < idx )
2042      return -1.0f;
2043
2044   return mNodes[idx].depth;
2045}
2046
2047VectorF River::getNodeNormal( U32 idx ) const
2048{
2049   if ( mNodes.size() - 1 < idx )
2050      return VectorF::Zero;
2051
2052   return mNodes[idx].normal;
2053}
2054
2055MatrixF River::getNodeTransform( U32 idx ) const
2056{   
2057   MatrixF mat(true);   
2058
2059   if ( mNodes.size() - 1 < idx )
2060      return mat;
2061
2062   bool hasNext = idx + 1 < mNodes.size();
2063   bool hasPrev = (S32)idx - 1 >= 0;
2064
2065   const RiverNode &node = mNodes[idx];   
2066
2067   VectorF fvec( 0, 1, 0 );
2068
2069   if ( hasNext )
2070   {
2071      fvec = mNodes[idx+1].point - node.point;      
2072      fvec.normalizeSafe();
2073   }
2074   else if ( hasPrev )
2075   {
2076      fvec = node.point - mNodes[idx-1].point;
2077      fvec.normalizeSafe();
2078   }
2079   else
2080      fvec = mPerp( node.normal );
2081   
2082   if ( fvec.isZero() )
2083      fvec = mPerp( node.normal );
2084
2085   F32 dot = mDot( fvec, node.normal );
2086   if ( dot < -0.9f || dot > 0.9f )
2087      fvec = mPerp( node.normal );
2088
2089   VectorF rvec = mCross( fvec, node.normal );
2090   if ( rvec.isZero() )
2091      rvec = mPerp( fvec );
2092   rvec.normalize();
2093
2094   fvec = mCross( node.normal, rvec );
2095   fvec.normalize();
2096
2097   mat.setColumn( 0, rvec );
2098   mat.setColumn( 1, fvec );
2099   mat.setColumn( 2, node.normal );
2100   mat.setColumn( 3, node.point );
2101
2102   AssertFatal( m_matF_determinant( mat ) != 0.0f, "no inverse!");
2103
2104   return mat; 
2105}
2106
2107void River::deleteNode( U32 idx )
2108{
2109   if ( mNodes.size() - 1 < idx )
2110      return;
2111
2112   mNodes.erase(idx);   
2113   _regenerate();
2114
2115   setMaskBits( RegenMask | NodeMask );
2116}
2117
2118void River::buildNodesFromList( RiverNodeList* list )
2119{
2120   mNodes.clear();
2121
2122   for (U32 i=0; i<list->mPositions.size(); ++i)
2123   {
2124      _addNode( list->mPositions[i], list->mWidths[i], list->mDepths[i], list->mNormals[i] );
2125   }
2126
2127   _regenerate();
2128}
2129
2130void River::_makeRenderBatches( const Point3F &cameraPos )
2131{
2132   // Loop through each segment to determine if it is either 1 [not visible], 2 [high LOD], 3 [low LOD]
2133
2134   mHighLODBatches.clear();
2135   mLowLODBatches.clear();      
2136
2137   // Keeps track of what we batch type we are currently collecting.
2138   // -1 is uninitialized, 0 is low detail, 1 is high detail
2139   S32 lastDetail = -1;
2140   bool highDetail;
2141
2142   U32 startSegmentIdx = -1;
2143   U32 endSegmentIdx = 0;
2144
2145   F32 lodDistSquared = mLodDistance * mLodDistance;
2146
2147   for ( U32 i = 0; i < mSegments.size(); i++ )   
2148   {
2149      const RiverSegment &segment = mSegments[i];
2150      const RiverSlice *slice = segment.slice0;
2151      const RiverSlice *nextSlice = segment.slice1;
2152
2153      // TODO: add bounds BoxF to RiverSegment
2154      const bool isVisible = true; //frustum.intersects( segment.bounds );
2155      if ( isVisible )
2156      {
2157         F32 dist0 = MathUtils::mTriangleDistance( slice->p0, nextSlice->p0, nextSlice->p2, cameraPos );
2158         F32 dist1 = MathUtils::mTriangleDistance( slice->p0, nextSlice->p2, slice->p2, cameraPos );
2159
2160         F32 dist = getMin( dist0, dist1 );
2161         highDetail = ( dist < lodDistSquared );
2162         if ( highDetail && lastDetail == 0 ||
2163            !highDetail && lastDetail == 1 )
2164         {
2165            // We hit a segment with a different lod than the previous.
2166            // Save what we have so far...
2167
2168            RiverRenderBatch batch;
2169
2170            batch.startSegmentIdx = startSegmentIdx;
2171            batch.endSegmentIdx = endSegmentIdx;               
2172
2173            if ( lastDetail == 0 )
2174            {
2175               mLowLODBatches.push_back( batch );
2176            }
2177            else
2178            {
2179               mHighLODBatches.push_back( batch );
2180            }
2181
2182            // Reset the batching
2183            startSegmentIdx = -1;
2184            lastDetail = -1;
2185            i--;
2186
2187            continue;
2188         }
2189
2190         // If this is the start of a set of batches.
2191         if ( startSegmentIdx == -1 )
2192         {
2193            endSegmentIdx = startSegmentIdx = i;
2194            lastDetail = ( highDetail ) ? 1 : 0;
2195         }
2196
2197         // Else we're extending the end batch index.
2198         else
2199            ++endSegmentIdx; 
2200
2201         // If this isn't the last batch then continue.
2202         if ( i < mSegments.size()-1 )
2203            continue;
2204      }
2205
2206      // If we still don't have a start batch skip.
2207      if ( startSegmentIdx == -1 )
2208         continue;
2209
2210      // Save what we have so far...
2211
2212      RiverRenderBatch batch;
2213
2214      batch.startSegmentIdx = startSegmentIdx;
2215      batch.endSegmentIdx = endSegmentIdx;               
2216
2217      if ( lastDetail == 0 )
2218      {
2219         mLowLODBatches.push_back( batch );
2220      }
2221      else
2222      {
2223         mHighLODBatches.push_back( batch );
2224      }
2225
2226      // Reset the batching.
2227      startSegmentIdx = -1;
2228      lastDetail = -1;
2229   }   
2230}
2231
2232void River::_makeHighLODBuffers()
2233{
2234   PROFILE_SCOPE( River_makeHighLODBuffers );
2235
2236   // This is the number of verts/triangles for ALL high lod batches combined.
2237   // eg. the size for the buffers.
2238   U32 numVerts = 0;
2239   U32 numTriangles = 0;
2240
2241   for ( U32 i = 0; i < mHighLODBatches.size(); i++ )
2242   {
2243      RiverRenderBatch &batch = mHighLODBatches[i];
2244
2245      for ( U32 j = batch.startSegmentIdx; j <= batch.endSegmentIdx; j++ )
2246      {
2247         const RiverSegment &segment = mSegments[j];
2248
2249         numTriangles += segment.numTriangles;
2250         numVerts += segment.numVerts;
2251      }
2252   }
2253
2254   if ( numVerts > MAX_DYNAMIC_VERTS || numTriangles * 3 > MAX_DYNAMIC_INDICES )
2255   {
2256      mVB_high = NULL;
2257      mPB_high = NULL;
2258      return;
2259   }
2260
2261   mHighTriangleCount = numTriangles;
2262   mHighVertCount = numVerts;
2263
2264   mVB_high.set( GFX, numVerts, GFXBufferTypeVolatile );
2265   GFXWaterVertex *vertPtr = mVB_high.lock();   
2266   U32 vertCounter = 0;
2267
2268   // NOTE: this will break if different segments have different number
2269   // of columns, but that will also cause T-junction triangles so just don't
2270   // do that.
2271
2272   // For each batch, loop through the segments contained by
2273   // that batch, and add their verts to the buffer.
2274   for ( U32 i = 0; i < mHighLODBatches.size(); i++ )
2275   {
2276      RiverRenderBatch &batch = mHighLODBatches[i];                        
2277
2278      batch.startVert = vertCounter;
2279      batch.vertCount = 0;
2280
2281      VectorF lastNormal(0,0,1);
2282
2283      for ( U32 j = batch.startSegmentIdx; j <= batch.endSegmentIdx; j++ )
2284      {
2285         // Add the verts for this segment to the buffer.
2286         RiverSegment &segment = mSegments[j];
2287
2288         BiSqrToQuad3D squareToQuad( segment.getP00(), 
2289                                     segment.getP10(), 
2290                                     segment.getP11(), 
2291                                     segment.getP01() );
2292
2293         // We are duplicating the last row of verts in a segment on
2294         // the first row of the next segment.  This could be optimized but 
2295         // shouldn't cause any problems.
2296
2297         VectorF normal = segment.getSurfaceNormal();
2298
2299         for ( U32 k = 0; k <= segment.rows; k++ )
2300         {
2301            VectorF vertNormal = ( k == 0 && j != batch.startSegmentIdx ) ? lastNormal : normal;    
2302
2303            F32 rowLen = mLerp( segment.slice0->width, segment.slice1->width, (F32)k / (F32)segment.rows );
2304
2305            for ( U32 l = 0; l <= segment.columns; l++ )
2306            {                                    
2307               // We are generating a "row" of verts along the forwardDivision
2308               // Each l iteration is a step to the right along with row.
2309
2310               Point2F uv( (F32)l / (F32)segment.columns, (F32)k / (F32)segment.rows );
2311
2312               Point3F pnt = squareToQuad.transform( uv );
2313
2314               // Assign the Vert
2315               vertPtr->point = pnt;            
2316               vertPtr->normal = vertNormal;
2317               vertPtr->undulateData.x = ( uv.x - 0.5f ) * rowLen;
2318               vertPtr->undulateData.y = ( segment.TexCoordEnd() - segment.TexCoordStart() ) * uv.y + segment.TexCoordStart();
2319               vertPtr->horizonFactor.set( 0, 0, 0, 0 );
2320
2321               vertPtr++;
2322               vertCounter++;
2323               batch.vertCount++;                     
2324            }
2325         }
2326
2327         lastNormal = normal;
2328      }
2329   }
2330
2331   AssertFatal( vertCounter == mHighVertCount, "River, wrote incorrect number of verts in mVB_high" );
2332
2333   mVB_high.unlock();
2334
2335   //
2336   // Do the high lod primitive buffer.         
2337   //
2338
2339   mPB_high.set( GFX, numTriangles * 3, numTriangles, GFXBufferTypeVolatile );         
2340   U16 *idxBuff;
2341   mPB_high.lock(&idxBuff);     
2342   U32 curIdx = 0; 
2343
2344   U32 batchOffset = 0;         
2345
2346   // For each high lod batch, we must add indices to the buffer
2347   // for each segment it contains ( and the count will depend on
2348   // the division level columns/rows for each segment ).
2349
2350   // Temporaries for holding the indices of a quad
2351   U32 p00, p01, p11, p10;
2352
2353   for ( U32 i = 0; i < mHighLODBatches.size(); i++ )
2354   {
2355      RiverRenderBatch &batch = mHighLODBatches[i];            
2356
2357      batch.indexCount = 0;
2358      batch.triangleCount = 0;
2359      batch.startIndex = curIdx;
2360
2361      U32 temp = 0;
2362      U32 segmentOffset = 0;            
2363
2364      for ( U32 j = batch.startSegmentIdx; j <= batch.endSegmentIdx; j++ )
2365      {               
2366         const RiverSegment &segment = mSegments[j];               
2367
2368         // Loop through all divisions adding the indices to the 
2369         // high detail primitive buffer.
2370         for ( U32 k = 0; k < segment.rows; k++ )
2371         {         
2372            for ( U32 l = 0; l < segment.columns; l++ )
2373            {                     
2374               // The indices for this quad.
2375               p00 = batchOffset + segmentOffset + l + k * ( segment.columns + 1 );
2376               p01 = p00 + segment.columns + 1;
2377               p11 = p01 + 1;
2378               p10 = p00 + 1;
2379
2380               AssertFatal( p00 <= mHighTriangleCount * 3, "River, bad draw call!" );
2381               AssertFatal( p01 <= mHighTriangleCount * 3, "River, bad draw call!" );
2382               AssertFatal( p11 <= mHighTriangleCount * 3, "River, bad draw call!" );
2383               AssertFatal( p10 <= mHighTriangleCount * 3, "River, bad draw call!" );
2384
2385               // Upper-Left triangle
2386               idxBuff[curIdx] = p00;
2387               curIdx++;
2388               idxBuff[curIdx] = p01;
2389               curIdx++;
2390               idxBuff[curIdx] = p11;
2391               curIdx++;
2392
2393               // Lower-Right Triangle
2394               idxBuff[curIdx] = p00;
2395               curIdx++;
2396               idxBuff[curIdx] = p11;
2397               curIdx++;
2398               idxBuff[curIdx] = p10;
2399               curIdx++;                     
2400
2401               batch.indexCount += 6;
2402               batch.triangleCount += 2;
2403            }
2404         }
2405
2406         // Increment the sliceOffset by the number of verts 
2407         // used by this segment.  So the next segment will index
2408         // into new verts.
2409         segmentOffset += ( segment.columns + 1 ) * ( segment.rows + 1 );                              
2410         temp += ( segment.columns + 1 ) * ( segment.rows + 1 );
2411      }            
2412
2413      batchOffset += temp;
2414   }
2415
2416   // Unlock the PrimitiveBuffer, we are done filling it.
2417   mPB_high.unlock();
2418}
2419
2420U32 River::_addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal )
2421{
2422   mNodes.increment();
2423   RiverNode &node = mNodes.last();
2424
2425   node.point = pos;   
2426   node.width = width;
2427   node.depth = depth;
2428   node.normal = normal;
2429
2430   setMaskBits( NodeMask | RegenMask );
2431
2432   return mNodes.size() - 1;
2433}
2434
2435U32 River::_insertNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx )
2436{
2437   U32 ret;
2438   RiverNode *node;
2439
2440   if ( idx == U32_MAX )
2441   {
2442      mNodes.increment();
2443      node = &mNodes.last();
2444      ret = mNodes.size() - 1;
2445   }
2446   else
2447   {
2448      mNodes.insert( idx );
2449      node = &mNodes[idx];
2450      ret = idx;
2451   }
2452
2453   node->point = pos;
2454   node->depth = depth;
2455   node->width = width;     
2456   node->normal = normal;
2457
2458   return ret;
2459}
2460
2461void River::setMetersPerSegment( F32 meters )
2462{
2463   if ( meters < MIN_METERS_PER_SEGMENT )
2464   {
2465      Con::warnf( "River::setMetersPerSegment, specified meters (%g) is below the min meters (%g), NOT SET!", meters, MIN_METERS_PER_SEGMENT );
2466      return;
2467   }
2468
2469   mMetersPerSegment = meters;
2470   _regenerate();
2471   setMaskBits( RiverMask | RegenMask );
2472}
2473
2474void River::setBatchSize( U32 size )
2475{
2476   // Not functional
2477   //mSegmentsPerBatch = size;
2478   //_regenerate();
2479   //setMaskBits( RiverMask | RegenMask );
2480}
2481
2482void River::regenerate()
2483{
2484   _regenerate();
2485   setMaskBits( RegenMask );
2486}
2487
2488void River::setMaxDivisionSize( F32 meters )
2489{
2490   if ( meters < mMinDivisionSize )
2491      mMaxDivisionSize = mMinDivisionSize;
2492   else
2493      mMaxDivisionSize = meters;      
2494
2495   _regenerate();
2496   setMaskBits( RiverMask | RegenMask );
2497}
2498
2499//-------------------------------------------------------------------------
2500// Console Methods
2501//-------------------------------------------------------------------------
2502
2503DefineEngineMethod( River, regenerate, void, (),,
2504                   "Intended as a helper to developers and editor scripts.\n"
2505                   "Force River to recreate its geometry."
2506                   )
2507{
2508   object->regenerate();
2509}
2510
2511DefineEngineMethod( River, setMetersPerSegment, void, ( F32 meters ),,
2512                   "Intended as a helper to developers and editor scripts.\n"
2513                   "@see SegmentLength field."
2514                   )
2515{
2516   object->setMetersPerSegment( meters );
2517}
2518
2519DefineEngineMethod( River, setBatchSize, void, ( F32 meters ),,
2520                   "Intended as a helper to developers and editor scripts.\n"
2521                   "BatchSize is not currently used."
2522                   )
2523{
2524   object->setBatchSize( meters );
2525}
2526
2527DefineEngineMethod( River, setNodeDepth, void, ( S32 idx, F32 meters ),,
2528                   "Intended as a helper to developers and editor scripts.\n"
2529                   "Sets the depth in meters of a particular node."
2530                   )
2531{
2532   object->setNodeDepth( idx, meters );
2533}
2534
2535DefineEngineMethod( River, setMaxDivisionSize, void, ( F32 meters ),,
2536                   "Intended as a helper to developers and editor scripts.\n"
2537                   "@see SubdivideLength field."
2538                   )
2539{
2540   object->setMaxDivisionSize( meters );
2541}
2542