river.cpp
Engine/source/environment/river.cpp
Classes:
Public Defines
define
MAX_NODE_DEPTH() 500.0f
define
MAX_NODE_WIDTH() 1000.0f
define
MIN_METERS_PER_SEGMENT() 1.0f
define
MIN_NODE_DEPTH() 0.25f
define
MIN_NODE_WIDTH() 0.25f
define
NODE_RADIUS() 15.0f
Public Variables
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." )
SegmentPointCompare(const void * aptr, const void * bptr)
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
