2012-09-19 15:15:01 +00:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
// Copyright (c) 2012 GarageGames, LLC
|
|
|
|
|
//
|
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
// of this software and associated documentation files (the "Software"), to
|
|
|
|
|
// deal in the Software without restriction, including without limitation the
|
|
|
|
|
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
|
|
|
// sell copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
|
//
|
|
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
|
|
|
// all copies or substantial portions of the Software.
|
|
|
|
|
//
|
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
|
|
|
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
|
|
|
// IN THE SOFTWARE.
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
#include "platform/platform.h"
|
|
|
|
|
#include "environment/river.h"
|
|
|
|
|
|
|
|
|
|
#include "console/consoleTypes.h"
|
|
|
|
|
#include "console/engineAPI.h"
|
|
|
|
|
#include "util/catmullRom.h"
|
|
|
|
|
#include "math/util/quadTransforms.h"
|
|
|
|
|
#include "scene/simPath.h"
|
|
|
|
|
#include "scene/sceneRenderState.h"
|
|
|
|
|
#include "scene/sceneManager.h"
|
|
|
|
|
#include "materials/sceneData.h"
|
|
|
|
|
#include "materials/baseMatInstance.h"
|
|
|
|
|
#include "scene/sgUtil.h"
|
|
|
|
|
#include "T3D/gameBase/gameConnection.h"
|
|
|
|
|
#include "core/stream/bitStream.h"
|
|
|
|
|
#include "gfx/gfxDrawUtil.h"
|
|
|
|
|
#include "gfx/gfxTransformSaver.h"
|
|
|
|
|
#include "gfx/primBuilder.h"
|
|
|
|
|
#include "gfx/gfxDebugEvent.h"
|
|
|
|
|
#include "gfx/gfxOcclusionQuery.h"
|
|
|
|
|
#include "math/mathIO.h"
|
|
|
|
|
#include "math/mathUtils.h"
|
|
|
|
|
#include "math/util/frustum.h"
|
|
|
|
|
#include "math/util/quadTransforms.h"
|
|
|
|
|
#include "gui/3d/guiTSControl.h"
|
|
|
|
|
#include "gfx/sim/debugDraw.h"
|
|
|
|
|
#include "T3D/fx/particleEmitter.h"
|
|
|
|
|
#include "scene/reflectionManager.h"
|
|
|
|
|
#include "ts/tsShapeInstance.h"
|
|
|
|
|
#include "postFx/postEffect.h"
|
|
|
|
|
#include "math/util/matrixSet.h"
|
|
|
|
|
#include "environment/nodeListManager.h"
|
|
|
|
|
|
|
|
|
|
ConsoleDocClass( River,
|
|
|
|
|
"@brief A water volume defined by a 3D spline.\n\n"
|
|
|
|
|
|
|
|
|
|
"User may control width and depth per node and overall spline shape in three "
|
|
|
|
|
"dimensions.\n\n"
|
|
|
|
|
|
|
|
|
|
"%River supports dynamic planar reflections (fullReflect) like all WaterObject "
|
|
|
|
|
"classes, but keep in mind it is not necessarily a planar surface. For best "
|
|
|
|
|
"visual quality a %River should be less reflective the more it twists and "
|
|
|
|
|
"bends. This caution only applies to %Rivers with fullReflect on.\n\n"
|
|
|
|
|
|
|
|
|
|
"@see WaterObject for inherited functionality.\n\n"
|
|
|
|
|
|
|
|
|
|
"@ingroup Water"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
#define MIN_METERS_PER_SEGMENT 1.0f
|
|
|
|
|
#define MIN_NODE_DEPTH 0.25f
|
|
|
|
|
#define MAX_NODE_DEPTH 500.0f
|
|
|
|
|
#define MIN_NODE_WIDTH 0.25f
|
|
|
|
|
#define MAX_NODE_WIDTH 1000.0f
|
|
|
|
|
#define NODE_RADIUS 15.0f
|
|
|
|
|
|
|
|
|
|
static U32 gIdxArray[6][2][3] = {
|
2017-01-12 04:34:46 +00:00
|
|
|
{ { 0, 4, 5 }, { 0, 5, 1 }, }, // Top Face
|
|
|
|
|
{ { 2, 6, 4 }, { 2, 4, 0 }, }, // Left Face
|
|
|
|
|
{ { 1, 5, 7 }, { 1, 7, 3 }, }, // Right Face
|
|
|
|
|
{ { 2, 3, 7 }, { 2, 7, 6 }, }, // Bottom Face
|
|
|
|
|
{ { 0, 1, 3 }, { 0, 3, 2 }, }, // Front Face
|
|
|
|
|
{ { 4, 6, 7 }, { 4, 7, 5 }, }, // Back Face
|
2012-09-19 15:15:01 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct RiverHitSegment
|
|
|
|
|
{
|
|
|
|
|
U32 idx;
|
|
|
|
|
F32 t;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static S32 QSORT_CALLBACK compareHitSegments(const void* a,const void* b)
|
|
|
|
|
{
|
2017-01-12 04:34:46 +00:00
|
|
|
const RiverHitSegment *fa = (RiverHitSegment*)a;
|
|
|
|
|
const RiverHitSegment *fb = (RiverHitSegment*)b;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
return mSign(fb->t - fa->t);
|
2012-09-19 15:15:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Point3F sSegmentPointComparePoints[4];
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
// DecalRoadNodeList Struct
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
struct RiverNodeList : public NodeListManager::NodeList
|
|
|
|
|
{
|
|
|
|
|
Vector<Point3F> mPositions;
|
|
|
|
|
Vector<F32> mWidths;
|
|
|
|
|
Vector<F32> mDepths;
|
|
|
|
|
Vector<VectorF> mNormals;
|
|
|
|
|
|
|
|
|
|
RiverNodeList() { }
|
|
|
|
|
virtual ~RiverNodeList() { }
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
// RiverNodeEvent Class
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class RiverNodeEvent : public NodeListEvent
|
|
|
|
|
{
|
|
|
|
|
typedef NodeListEvent Parent;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
Vector<Point3F> mPositions;
|
|
|
|
|
Vector<F32> mWidths;
|
|
|
|
|
Vector<F32> mDepths;
|
|
|
|
|
Vector<VectorF> mNormals;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
RiverNodeEvent() { mNodeList = NULL; }
|
|
|
|
|
virtual ~RiverNodeEvent() { }
|
|
|
|
|
|
|
|
|
|
virtual void pack(NetConnection*, BitStream*);
|
|
|
|
|
virtual void unpack(NetConnection*, BitStream*);
|
|
|
|
|
|
|
|
|
|
virtual void copyIntoList(NodeListManager::NodeList* copyInto);
|
|
|
|
|
virtual void padListToSize();
|
|
|
|
|
|
|
|
|
|
DECLARE_CONOBJECT(RiverNodeEvent);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void RiverNodeEvent::pack(NetConnection* conn, BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
Parent::pack( conn, stream );
|
|
|
|
|
|
|
|
|
|
stream->writeInt( mPositions.size(), 16 );
|
|
|
|
|
|
|
|
|
|
for (U32 i=0; i<mPositions.size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
mathWrite( *stream, mPositions[i] );
|
|
|
|
|
stream->write( mWidths[i] );
|
|
|
|
|
stream->write( mDepths[i] );
|
|
|
|
|
mathWrite( *stream, mNormals[i] );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RiverNodeEvent::unpack(NetConnection* conn, BitStream* stream)
|
|
|
|
|
{
|
|
|
|
|
mNodeList = new RiverNodeList();
|
|
|
|
|
|
|
|
|
|
Parent::unpack( conn, stream );
|
|
|
|
|
|
|
|
|
|
U32 count = stream->readInt( 16 );
|
|
|
|
|
|
|
|
|
|
Point3F pos;
|
|
|
|
|
F32 width, depth;
|
|
|
|
|
VectorF normal;
|
|
|
|
|
|
|
|
|
|
RiverNodeList* list = static_cast<RiverNodeList*>(mNodeList);
|
|
|
|
|
|
|
|
|
|
for (U32 i=0; i<count; ++i)
|
|
|
|
|
{
|
|
|
|
|
mathRead( *stream, &pos );
|
|
|
|
|
stream->read( &width );
|
|
|
|
|
stream->read( &depth );
|
|
|
|
|
mathRead( *stream, &normal );
|
|
|
|
|
|
|
|
|
|
list->mPositions.push_back( pos );
|
|
|
|
|
list->mWidths.push_back( width );
|
|
|
|
|
list->mDepths.push_back( depth );
|
|
|
|
|
list->mNormals.push_back( normal );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list->mTotalValidNodes = count;
|
|
|
|
|
|
|
|
|
|
// Do we have a complete list?
|
|
|
|
|
if (list->mPositions.size() >= mTotalNodes)
|
|
|
|
|
list->mListComplete = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RiverNodeEvent::copyIntoList(NodeListManager::NodeList* copyInto)
|
|
|
|
|
{
|
|
|
|
|
RiverNodeList* prevList = dynamic_cast<RiverNodeList*>(copyInto);
|
|
|
|
|
RiverNodeList* list = static_cast<RiverNodeList*>(mNodeList);
|
|
|
|
|
|
|
|
|
|
// Merge our list with the old list.
|
|
|
|
|
for (U32 i=mLocalListStart, index=0; i<mLocalListStart+list->mPositions.size(); ++i, ++index)
|
|
|
|
|
{
|
|
|
|
|
prevList->mPositions[i] = list->mPositions[index];
|
|
|
|
|
prevList->mWidths[i] = list->mWidths[index];
|
|
|
|
|
prevList->mDepths[i] = list->mDepths[index];
|
|
|
|
|
prevList->mNormals[i] = list->mNormals[index];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RiverNodeEvent::padListToSize()
|
|
|
|
|
{
|
|
|
|
|
RiverNodeList* list = static_cast<RiverNodeList*>(mNodeList);
|
|
|
|
|
|
|
|
|
|
U32 totalValidNodes = list->mTotalValidNodes;
|
|
|
|
|
|
|
|
|
|
// Pad our list front?
|
|
|
|
|
if (mLocalListStart)
|
|
|
|
|
{
|
|
|
|
|
RiverNodeList* newlist = new RiverNodeList();
|
|
|
|
|
newlist->mPositions.increment(mLocalListStart);
|
|
|
|
|
newlist->mWidths.increment(mLocalListStart);
|
|
|
|
|
newlist->mDepths.increment(mLocalListStart);
|
|
|
|
|
newlist->mNormals.increment(mLocalListStart);
|
|
|
|
|
|
|
|
|
|
newlist->mPositions.merge(list->mPositions);
|
|
|
|
|
newlist->mWidths.merge(list->mWidths);
|
|
|
|
|
newlist->mDepths.merge(list->mDepths);
|
|
|
|
|
newlist->mNormals.merge(list->mNormals);
|
|
|
|
|
|
|
|
|
|
delete list;
|
2014-11-11 20:15:11 +00:00
|
|
|
mNodeList = list = newlist;
|
2012-09-19 15:15:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pad our list end?
|
|
|
|
|
if (list->mPositions.size() < mTotalNodes)
|
|
|
|
|
{
|
|
|
|
|
U32 delta = mTotalNodes - list->mPositions.size();
|
|
|
|
|
list->mPositions.increment(delta);
|
|
|
|
|
list->mWidths.increment(delta);
|
|
|
|
|
list->mDepths.increment(delta);
|
|
|
|
|
list->mNormals.increment(delta);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
list->mTotalValidNodes = totalValidNodes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IMPLEMENT_CO_NETEVENT_V1(RiverNodeEvent);
|
|
|
|
|
|
|
|
|
|
ConsoleDocClass( RiverNodeEvent,
|
|
|
|
|
"@brief Sends messages to the River Editor\n\n"
|
|
|
|
|
"Editor use only.\n\n"
|
|
|
|
|
"@internal"
|
|
|
|
|
);
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
// RiverNodeListNotify Class
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class RiverNodeListNotify : public NodeListNotify
|
|
|
|
|
{
|
|
|
|
|
typedef NodeListNotify Parent;
|
|
|
|
|
|
|
|
|
|
protected:
|
|
|
|
|
SimObjectPtr<River> mRiver;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
RiverNodeListNotify( River* river, U32 listId ) { mRiver = river; mListId = listId; }
|
|
|
|
|
virtual ~RiverNodeListNotify() { mRiver = NULL; }
|
|
|
|
|
|
|
|
|
|
virtual void sendNotification( NodeListManager::NodeList* list );
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void RiverNodeListNotify::sendNotification( NodeListManager::NodeList* list )
|
|
|
|
|
{
|
|
|
|
|
if (mRiver.isValid())
|
|
|
|
|
{
|
|
|
|
|
// Build the road's nodes
|
|
|
|
|
RiverNodeList* riverList = dynamic_cast<RiverNodeList*>( list );
|
|
|
|
|
if (riverList)
|
|
|
|
|
mRiver->buildNodesFromList( riverList );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// Class: RiverSegment
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
RiverSegment::RiverSegment()
|
|
|
|
|
{
|
|
|
|
|
mPlaneCount = 0;
|
|
|
|
|
columns = 0;
|
|
|
|
|
rows = 0;
|
|
|
|
|
numVerts = 0;
|
|
|
|
|
numTriangles = 0;
|
|
|
|
|
|
|
|
|
|
startVert = 0;
|
|
|
|
|
endVert = 0;
|
|
|
|
|
startIndex = 0;
|
|
|
|
|
endIndex = 0;
|
|
|
|
|
|
|
|
|
|
slice0 = NULL;
|
|
|
|
|
slice1 = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RiverSegment::RiverSegment( RiverSlice *rs0, RiverSlice *rs1 )
|
|
|
|
|
{
|
|
|
|
|
columns = 0;
|
|
|
|
|
rows = 0;
|
|
|
|
|
numVerts = 0;
|
|
|
|
|
numTriangles = 0;
|
|
|
|
|
|
|
|
|
|
startVert = 0;
|
|
|
|
|
endVert = 0;
|
|
|
|
|
startIndex = 0;
|
|
|
|
|
endIndex = 0;
|
|
|
|
|
|
|
|
|
|
slice0 = rs0;
|
|
|
|
|
slice1 = rs1;
|
|
|
|
|
|
|
|
|
|
// Calculate the planes for this segment
|
|
|
|
|
// Will be used for intersection/buoyancy tests
|
|
|
|
|
VectorF normal;
|
|
|
|
|
mPlaneCount = 6;
|
|
|
|
|
|
|
|
|
|
sSegmentPointCompareReference = getFaceCenter(6);
|
|
|
|
|
|
|
|
|
|
// left
|
|
|
|
|
mPlanes[0] = _getBestPlane( &slice1->p0, &slice1->pb0, &slice0->pb0, &slice0->p0 );
|
|
|
|
|
|
|
|
|
|
// right
|
|
|
|
|
mPlanes[1] = _getBestPlane( &slice0->pb2, &slice1->pb2, &slice1->p2, &slice0->p2 );
|
|
|
|
|
|
|
|
|
|
// near
|
|
|
|
|
mPlanes[2] = _getBestPlane( &slice0->pb0, &slice0->pb2, &slice0->p2, &slice0->p0 );
|
|
|
|
|
|
|
|
|
|
// far
|
|
|
|
|
mPlanes[3] = _getBestPlane( &slice1->pb2, &slice1->pb0, &slice1->p0, &slice1->p2 );
|
|
|
|
|
|
|
|
|
|
// top
|
|
|
|
|
mPlanes[4] = _getBestPlane( &slice0->p2, &slice1->p2, &slice1->p0, &slice0->p0 );
|
|
|
|
|
|
|
|
|
|
// bottom
|
|
|
|
|
mPlanes[5] = _getBestPlane( &slice0->pb2, &slice0->pb0, &slice1->pb0, &slice1->pb2 );
|
|
|
|
|
|
|
|
|
|
// Calculate the bounding box(s)
|
|
|
|
|
worldbounds.minExtents = worldbounds.maxExtents = rs0->p0;
|
|
|
|
|
worldbounds.extend( rs0->p2 );
|
|
|
|
|
worldbounds.extend( rs0->pb0 );
|
|
|
|
|
worldbounds.extend( rs0->pb2 );
|
|
|
|
|
worldbounds.extend( rs1->p0 );
|
|
|
|
|
worldbounds.extend( rs1->p2 );
|
|
|
|
|
worldbounds.extend( rs1->pb0 );
|
|
|
|
|
worldbounds.extend( rs1->pb2 );
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
// Calculate tetrahedrons (for collision and buoyancy testing)
|
|
|
|
|
// This is 0 in the diagram.
|
|
|
|
|
mCubePoints[0] = cornerPoint;
|
|
|
|
|
mCubePoints[1] = cornerPoint + (VectorF( 1.0f, 0.0f, 0.0f ) * size );
|
|
|
|
|
mCubePoints[2] = cornerPoint + (VectorF( 0.0f, 1.0f, 0.0f ) * size );
|
|
|
|
|
mCubePoints[3] = cornerPoint + (VectorF( 1.0f, 1.0f, 0.0f ) * size );
|
|
|
|
|
|
|
|
|
|
mCubePoints[4] = cornerPoint + (VectorF( 0.0f, 0.0f, 1.0f );
|
|
|
|
|
mCubePoints[5] = cornerPoint + (VectorF( 1.0f, 0.0f, 1.0f );
|
|
|
|
|
mCubePoints[6] = cornerPoint + (VectorF( 0.0f, 1.0f, 1.0f );
|
|
|
|
|
mCubePoints[7] = cornerPoint + (VectorF( 1.0f, 1.0f, 1.0f );
|
|
|
|
|
|
|
|
|
|
// Center tetra.
|
|
|
|
|
mTetras[0].p0 = &mCubePoints[1];
|
|
|
|
|
mTetras[0].p1 = &mCubePoints[2];
|
|
|
|
|
mTetras[0].p2 = &mCubePoints[4];
|
|
|
|
|
mTetras[0].p3 = &mCubePoints[7];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mTetras[1].p0 = &mCubePoints[0]; // this is the tip
|
|
|
|
|
mTetras[1].p1 = &mCubePoints[1];
|
|
|
|
|
mTetras[1].p2 = &mCubePoints[2];
|
|
|
|
|
mTetras[1].p3 = &mCubePoints[4];
|
|
|
|
|
|
|
|
|
|
mTetras[2].p0 = &mCubePoints[3]; // tip
|
|
|
|
|
mTetras[2].p1 = &mCubePoints[2];
|
|
|
|
|
mTetras[2].p2 = &mCubePoints[1];
|
|
|
|
|
mTetras[2].p3 = &mCubePoints[7];
|
|
|
|
|
|
|
|
|
|
mTetras[3].p0 = &mCubePoints[6]; // tip
|
|
|
|
|
mTetras[3].p1 = &mCubePoints[7];
|
|
|
|
|
mTetras[3].p2 = &mCubePoints[4];
|
|
|
|
|
mTetras[3].p3 = &mCubePoints[2];
|
|
|
|
|
|
|
|
|
|
mTetras[4].p0 = &mCubePoints[5]; // tip
|
|
|
|
|
mTetras[4].p1 = &mCubePoints[7];
|
|
|
|
|
mTetras[4].p2 = &mCubePoints[4];
|
|
|
|
|
mTetras[4].p3 = &mCubePoints[3];*/
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void RiverSegment::set( RiverSlice *rs0, RiverSlice *rs1 )
|
|
|
|
|
{
|
|
|
|
|
columns = 0;
|
|
|
|
|
rows = 0;
|
|
|
|
|
numVerts = 0;
|
|
|
|
|
numTriangles = 0;
|
|
|
|
|
|
|
|
|
|
startVert = 0;
|
|
|
|
|
endVert = 0;
|
|
|
|
|
startIndex = 0;
|
|
|
|
|
endIndex = 0;
|
|
|
|
|
|
|
|
|
|
slice0 = rs0;
|
|
|
|
|
slice1 = rs1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static S32 QSORT_CALLBACK SegmentPointCompare(const void *aptr, const void *bptr)
|
|
|
|
|
{
|
|
|
|
|
const U32 a = *(const U32*)aptr;
|
|
|
|
|
const U32 b = *(const U32*)bptr;
|
|
|
|
|
|
|
|
|
|
F32 lenA = ( sSegmentPointCompareReference - sSegmentPointComparePoints[a] ).lenSquared();
|
|
|
|
|
F32 lenB = ( sSegmentPointCompareReference - sSegmentPointComparePoints[b] ).lenSquared();
|
|
|
|
|
return ( lenB - lenA );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PlaneF RiverSegment::_getBestPlane( const Point3F *p0, const Point3F *p1, const Point3F *p2, const Point3F *p3 )
|
|
|
|
|
{
|
|
|
|
|
sSegmentPointComparePoints[0] = *p0;
|
|
|
|
|
sSegmentPointComparePoints[1] = *p1;
|
|
|
|
|
sSegmentPointComparePoints[2] = *p2;
|
|
|
|
|
sSegmentPointComparePoints[3] = *p3;
|
|
|
|
|
|
|
|
|
|
Point3F points[4] = {
|
|
|
|
|
*p0, *p1, *p2, *p3
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
U32 indices[4] = {
|
|
|
|
|
0,1,2,3
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
dQsort(indices, 4, sizeof(U32), SegmentPointCompare);
|
|
|
|
|
|
|
|
|
|
// Collect the best three points (in correct winding order)
|
|
|
|
|
// To generate the plane's normal
|
|
|
|
|
Vector<Point3F> normalPnts;
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < 4; i++ )
|
|
|
|
|
{
|
|
|
|
|
if ( i == indices[3] )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
normalPnts.push_back(points[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PlaneF plane( normalPnts[0], normalPnts[1], normalPnts[2] );
|
|
|
|
|
return plane;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Point3F RiverSegment::getFaceCenter( U32 faceIdx ) const
|
|
|
|
|
{
|
|
|
|
|
Point3F center(0,0,0);
|
|
|
|
|
|
|
|
|
|
switch ( faceIdx )
|
|
|
|
|
{
|
|
|
|
|
case 0: // left
|
|
|
|
|
center = slice1->p0 + slice0->p0 + slice0->pb0 + slice1->pb0;
|
|
|
|
|
center *= 0.25f;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1: // right
|
|
|
|
|
center = slice0->p2 + slice1->p2 + slice1->pb2 + slice0->pb2;
|
|
|
|
|
center *= 0.25f;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2: // near
|
|
|
|
|
center = slice0->p0 + slice0->p2 + slice0->pb2 + slice0->pb0;
|
|
|
|
|
center *= 0.25f;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 3: // far
|
|
|
|
|
center = slice1->pb0 + slice1->p0 + slice1->pb0 + slice1->pb2;
|
|
|
|
|
center *= 0.25f;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 4: // top
|
|
|
|
|
center = slice0->p0 + slice1->p0 + slice1->p2 + slice0->p2;
|
|
|
|
|
center *= 0.25f;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 5: // bottom
|
|
|
|
|
center = slice1->pb2 + slice1->pb0 + slice0->pb0 + slice0->pb2;
|
|
|
|
|
center *= 0.25f;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 6: // segment center
|
|
|
|
|
center = slice0->p0 + slice0->p2 + slice1->p0 + slice1->p2 + slice0->pb0 + slice0->pb2 + slice1->pb0 + slice1->pb2;
|
|
|
|
|
center /= 8;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool RiverSegment::intersectBox( const Box3F &bounds ) const
|
|
|
|
|
{
|
|
|
|
|
// This code copied from Frustum class.
|
|
|
|
|
|
|
|
|
|
Point3F maxPoint;
|
|
|
|
|
F32 maxDot;
|
|
|
|
|
|
|
|
|
|
// Note the planes are ordered left, right, near,
|
|
|
|
|
// far, top, bottom for getting early rejections
|
|
|
|
|
// from the typical horizontal scene.
|
|
|
|
|
for ( S32 i = 0; i < mPlaneCount; i++ )
|
|
|
|
|
{
|
|
|
|
|
// This is pretty much as optimal as you can
|
|
|
|
|
// get for a plane vs AABB test...
|
|
|
|
|
//
|
|
|
|
|
// 4 comparisons
|
|
|
|
|
// 3 multiplies
|
|
|
|
|
// 2 adds
|
|
|
|
|
// 1 negation
|
|
|
|
|
//
|
|
|
|
|
// It will early out as soon as it detects the
|
|
|
|
|
// bounds is outside one of the planes.
|
|
|
|
|
|
|
|
|
|
if ( mPlanes[i].x > 0 )
|
|
|
|
|
maxPoint.x = bounds.maxExtents.x;
|
|
|
|
|
else
|
|
|
|
|
maxPoint.x = bounds.minExtents.x;
|
|
|
|
|
|
|
|
|
|
if ( mPlanes[i].y > 0 )
|
|
|
|
|
maxPoint.y = bounds.maxExtents.y;
|
|
|
|
|
else
|
|
|
|
|
maxPoint.y = bounds.minExtents.y;
|
|
|
|
|
|
|
|
|
|
if ( mPlanes[i].z > 0 )
|
|
|
|
|
maxPoint.z = bounds.maxExtents.z;
|
|
|
|
|
else
|
|
|
|
|
maxPoint.z = bounds.minExtents.z;
|
|
|
|
|
|
|
|
|
|
maxDot = mDot( maxPoint, mPlanes[ i ] );
|
|
|
|
|
|
|
|
|
|
if ( maxDot <= -mPlanes[ i ].d )
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool RiverSegment::containsPoint( const Point3F &pnt ) const
|
|
|
|
|
{
|
|
|
|
|
// NOTE: this code from Frustum class.
|
|
|
|
|
|
|
|
|
|
F32 maxDot;
|
|
|
|
|
|
|
|
|
|
// Note the planes are ordered left, right, near,
|
|
|
|
|
// far, top, bottom for getting early rejections
|
|
|
|
|
// from the typical horizontal scene.
|
|
|
|
|
for ( S32 i = 0; i < mPlaneCount; i++ )
|
|
|
|
|
{
|
|
|
|
|
const PlaneF &plane = mPlanes[ i ];
|
|
|
|
|
|
|
|
|
|
// This is pretty much as optimal as you can
|
|
|
|
|
// get for a plane vs point test...
|
|
|
|
|
//
|
|
|
|
|
// 1 comparison
|
|
|
|
|
// 2 multiplies
|
|
|
|
|
// 1 adds
|
|
|
|
|
//
|
|
|
|
|
// It will early out as soon as it detects the
|
|
|
|
|
// point is outside one of the planes.
|
|
|
|
|
|
|
|
|
|
maxDot = mDot( pnt, plane ) + plane.d;
|
|
|
|
|
if ( maxDot < -0.1f )
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
F32 RiverSegment::distanceToSurface(const Point3F &pnt) const
|
|
|
|
|
{
|
|
|
|
|
return mPlanes[4].distToPlane( pnt );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool River::smEditorOpen = false;
|
|
|
|
|
bool River::smWireframe = false;
|
|
|
|
|
bool River::smShowWalls = false;
|
|
|
|
|
bool River::smShowNodes = false;
|
|
|
|
|
bool River::smShowSpline = true;
|
|
|
|
|
bool River::smShowRiver = true;
|
|
|
|
|
SimObjectPtr<SimSet> River::smServerRiverSet = NULL;
|
|
|
|
|
|
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(River);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
River::River()
|
2020-05-11 19:56:04 +00:00
|
|
|
: mLowVertCount(0),
|
|
|
|
|
mHighVertCount(0),
|
|
|
|
|
mLowTriangleCount(0),
|
|
|
|
|
mHighTriangleCount(0),
|
|
|
|
|
mSegmentsPerBatch(10),
|
2016-10-14 23:16:55 +00:00
|
|
|
mMetersPerSegment(10.0f),
|
2012-09-19 15:15:01 +00:00
|
|
|
mDepthScale(1.0f),
|
2016-10-14 23:16:55 +00:00
|
|
|
mFlowMagnitude(1.0f),
|
|
|
|
|
mLodDistance( 50.0f ),
|
2012-09-19 15:15:01 +00:00
|
|
|
mMaxDivisionSize(2.5f),
|
|
|
|
|
mMinDivisionSize(0.25f),
|
2016-10-14 23:16:55 +00:00
|
|
|
mColumnCount(5)
|
2012-09-19 15:15:01 +00:00
|
|
|
{
|
|
|
|
|
mNetFlags.set( Ghostable | ScopeAlways );
|
|
|
|
|
|
|
|
|
|
mObjScale.set( 1, 1, 1 );
|
|
|
|
|
|
|
|
|
|
mObjBox.minExtents.set( -0.5, -0.5, -0.5 );
|
|
|
|
|
mObjBox.maxExtents.set( 0.5, 0.5, 0.5 );
|
|
|
|
|
|
|
|
|
|
mReflectNormalUp = false;
|
|
|
|
|
|
|
|
|
|
// We use the shader const miscParams.w to signify
|
|
|
|
|
// that this object is a River.
|
|
|
|
|
mMiscParamW = 1.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
River::~River()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::initPersistFields()
|
|
|
|
|
{
|
|
|
|
|
addGroup( "River" );
|
|
|
|
|
|
|
|
|
|
addField( "SegmentLength", TypeF32, Offset( mMetersPerSegment, River ),
|
|
|
|
|
"Divide the River lengthwise into segments of this length in meters. "
|
|
|
|
|
"These geometric volumes are used for spacial queries like determining containment." );
|
|
|
|
|
|
|
|
|
|
addField( "SubdivideLength", TypeF32, Offset( mMaxDivisionSize, River ),
|
|
|
|
|
"For purposes of generating the renderable geometry River segments are further subdivided "
|
|
|
|
|
"such that no quad is of greater width or length than this distance in meters." );
|
|
|
|
|
|
|
|
|
|
addField( "FlowMagnitude", TypeF32, Offset( mFlowMagnitude, River ),
|
|
|
|
|
"Magnitude of the force vector applied to dynamic objects within the River." );
|
|
|
|
|
|
|
|
|
|
addField( "LowLODDistance", TypeF32, Offset( mLodDistance, River ),
|
|
|
|
|
"Segments of the river at this distance in meters or greater will "
|
|
|
|
|
"render as a single unsubdivided without undulation effects." );
|
|
|
|
|
|
|
|
|
|
endGroup( "River" );
|
|
|
|
|
|
|
|
|
|
addGroup( "Internal" );
|
|
|
|
|
|
2021-10-05 00:04:21 +00:00
|
|
|
addProtectedField( "Node", TypeString, 0, &addNodeFromField, &emptyStringProtectedGetFn, "For internal use, do not modify." );
|
2012-09-19 15:15:01 +00:00
|
|
|
|
|
|
|
|
endGroup( "Internal" );
|
|
|
|
|
|
|
|
|
|
Parent::initPersistFields();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::consoleInit()
|
|
|
|
|
{
|
|
|
|
|
Parent::consoleInit();
|
|
|
|
|
|
|
|
|
|
Con::addVariable( "$River::EditorOpen", TypeBool, &River::smEditorOpen, "For editor use.\n"
|
2017-01-12 04:34:46 +00:00
|
|
|
"@ingroup Editors\n" );
|
2012-09-19 15:15:01 +00:00
|
|
|
Con::addVariable( "$River::showWalls", TypeBool, &River::smShowWalls, "For editor use.\n"
|
2017-01-12 04:34:46 +00:00
|
|
|
"@ingroup Editors\n" );
|
2012-09-19 15:15:01 +00:00
|
|
|
Con::addVariable( "$River::showNodes", TypeBool, &River::smShowNodes, "For editor use.\n"
|
2017-01-12 04:34:46 +00:00
|
|
|
"@ingroup Editors\n");
|
2012-09-19 15:15:01 +00:00
|
|
|
Con::addVariable( "$River::showSpline", TypeBool, &River::smShowSpline, "For editor use.\n"
|
2017-01-12 04:34:46 +00:00
|
|
|
"@ingroup Editors\n" );
|
2012-09-19 15:15:01 +00:00
|
|
|
Con::addVariable( "$River::showRiver", TypeBool, &River::smShowRiver, "For editor use.\n"
|
2017-01-12 04:34:46 +00:00
|
|
|
"@ingroup Editors\n" );
|
|
|
|
|
Con::addVariable( "$River::showWireframe", TypeBool, &River::smWireframe, "For editor use.\n"
|
|
|
|
|
"@ingroup Editors\n");
|
2012-09-19 15:15:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool River::addNodeFromField( void *object, const char *index, const char *data )
|
|
|
|
|
{
|
|
|
|
|
River *pObj = static_cast<River*>(object);
|
|
|
|
|
|
|
|
|
|
//if ( !pObj->isProperlyAdded() )
|
|
|
|
|
//{
|
|
|
|
|
F32 x,y,z,width,depth;
|
|
|
|
|
VectorF normal;
|
|
|
|
|
U32 result = dSscanf( data, "%f %f %f %f %f %f %f %f", &x, &y, &z, &width, &depth, &normal.x, &normal.y, &normal.z );
|
|
|
|
|
if ( result == 8 )
|
|
|
|
|
pObj->_addNode( Point3F(x,y,z), width, depth, normal );
|
|
|
|
|
//}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool River::onAdd()
|
|
|
|
|
{
|
|
|
|
|
if ( !Parent::onAdd() )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Reset the World Box.
|
|
|
|
|
//setGlobalBounds();
|
|
|
|
|
resetWorldBox();
|
|
|
|
|
|
|
|
|
|
// Set the Render Transform.
|
|
|
|
|
setRenderTransform(mObjToWorld);
|
|
|
|
|
|
|
|
|
|
// Add to Scene.
|
|
|
|
|
addToScene();
|
|
|
|
|
|
|
|
|
|
if ( isServerObject() )
|
|
|
|
|
getServerSet()->addObject( this );
|
|
|
|
|
|
|
|
|
|
_regenerate();
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::onRemove()
|
|
|
|
|
{
|
|
|
|
|
removeFromScene();
|
|
|
|
|
|
|
|
|
|
Parent::onRemove();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::inspectPostApply()
|
|
|
|
|
{
|
|
|
|
|
// Set Parent.
|
|
|
|
|
Parent::inspectPostApply();
|
|
|
|
|
|
|
|
|
|
if ( mMetersPerSegment < MIN_METERS_PER_SEGMENT )
|
|
|
|
|
mMetersPerSegment = MIN_METERS_PER_SEGMENT;
|
|
|
|
|
|
|
|
|
|
mMaxDivisionSize = getMax( mMaxDivisionSize, mMinDivisionSize );
|
|
|
|
|
|
|
|
|
|
// Set fxPortal Mask.
|
|
|
|
|
setMaskBits(RiverMask|RegenMask);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::onStaticModified( const char* slotName, const char*newValue )
|
|
|
|
|
{
|
|
|
|
|
Parent::onStaticModified( slotName, newValue );
|
|
|
|
|
|
|
|
|
|
if ( dStricmp( slotName, "surfMaterial" ) == 0 )
|
|
|
|
|
setMaskBits( MaterialMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SimSet* River::getServerSet()
|
|
|
|
|
{
|
|
|
|
|
if ( !smServerRiverSet )
|
|
|
|
|
{
|
|
|
|
|
smServerRiverSet = new SimSet();
|
|
|
|
|
smServerRiverSet->registerObject( "ServerRiverSet" );
|
|
|
|
|
Sim::getRootGroup()->addObject( smServerRiverSet );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return smServerRiverSet;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::writeFields( Stream &stream, U32 tabStop )
|
|
|
|
|
{
|
|
|
|
|
Parent::writeFields( stream, tabStop );
|
|
|
|
|
|
|
|
|
|
// Now write all nodes
|
|
|
|
|
|
|
|
|
|
stream.write(2, "\r\n");
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverNode &node = mNodes[i];
|
|
|
|
|
|
|
|
|
|
stream.writeTabs(tabStop);
|
|
|
|
|
|
|
|
|
|
char buffer[1024];
|
|
|
|
|
dMemset( buffer, 0, 1024 );
|
|
|
|
|
dSprintf( buffer, 1024, "Node = \"%f %f %f %f %f %f %f %f\";", node.point.x, node.point.y, node.point.z,
|
|
|
|
|
node.width,
|
|
|
|
|
node.depth,
|
|
|
|
|
node.normal.x, node.normal.y, node.normal.z );
|
|
|
|
|
stream.writeLine( (const U8*)buffer );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool River::writeField( StringTableEntry fieldname, const char *value )
|
|
|
|
|
{
|
|
|
|
|
if ( fieldname == StringTable->insert("node") )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
return Parent::writeField( fieldname, value );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::innerRender( SceneRenderState *state )
|
|
|
|
|
{
|
|
|
|
|
GFXDEBUGEVENT_SCOPE( River_innerRender, ColorI( 255, 0, 0 ) );
|
|
|
|
|
|
|
|
|
|
PROFILE_SCOPE( River_innerRender );
|
|
|
|
|
|
|
|
|
|
// Setup SceneData
|
|
|
|
|
SceneData sgData;
|
|
|
|
|
sgData.init( state );
|
|
|
|
|
sgData.lights[0] = LIGHTMGR->getSpecialLight( LightManager::slSunLightType );
|
|
|
|
|
sgData.backBuffTex = REFLECTMGR->getRefractTex();
|
|
|
|
|
sgData.reflectTex = mPlaneReflector.reflectTex;
|
|
|
|
|
sgData.wireframe |= smWireframe;
|
|
|
|
|
|
|
|
|
|
const Point3F &camPosition = state->getCameraPosition();
|
|
|
|
|
|
|
|
|
|
// set the material
|
|
|
|
|
|
|
|
|
|
S32 matIdx = getMaterialIndex( camPosition );
|
|
|
|
|
|
|
|
|
|
if ( !initMaterial( matIdx ) )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
BaseMatInstance *mat = mMatInstances[matIdx];
|
|
|
|
|
WaterMatParams matParams = mMatParamHandles[matIdx];
|
|
|
|
|
|
|
|
|
|
if ( !mat )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// setup proj/world transform
|
|
|
|
|
GFXTransformSaver saver;
|
|
|
|
|
|
|
|
|
|
setShaderParams( state, mat, matParams );
|
|
|
|
|
|
|
|
|
|
_makeRenderBatches( camPosition );
|
|
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
if ( !River::smShowRiver )
|
2012-09-19 15:15:01 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// If no material... we're done.
|
|
|
|
|
if ( mLowLODBatches.empty() && mHighLODBatches.empty() )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if ( !mHighLODBatches.empty() )
|
|
|
|
|
_makeHighLODBuffers();
|
|
|
|
|
|
|
|
|
|
mMatrixSet->restoreSceneViewProjection();
|
|
|
|
|
mMatrixSet->setWorld( MatrixF::Identity );
|
|
|
|
|
|
|
|
|
|
while( mat->setupPass( state, sgData ) )
|
|
|
|
|
{
|
|
|
|
|
mat->setSceneInfo(state, sgData);
|
|
|
|
|
mat->setTransforms(*mMatrixSet, state);
|
|
|
|
|
|
|
|
|
|
setCustomTextures( matIdx, mat->getCurPass(), matParams );
|
|
|
|
|
|
|
|
|
|
GFX->setVertexBuffer( mVB_low );
|
|
|
|
|
GFX->setPrimitiveBuffer( mPB_low );
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mLowLODBatches.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverRenderBatch &batch = mLowLODBatches[i];
|
|
|
|
|
|
|
|
|
|
U32 startVert = batch.startSegmentIdx * 2;
|
|
|
|
|
U32 endVert = ( batch.endSegmentIdx + 1 ) * 2 + 1;
|
|
|
|
|
U32 startIdx = batch.startSegmentIdx * 6;
|
|
|
|
|
U32 endIdx = batch.endSegmentIdx * 6 + 5;
|
|
|
|
|
|
|
|
|
|
U32 vertCount = ( endVert - startVert ) + 1;
|
|
|
|
|
U32 idxCount = ( endIdx - startIdx ) + 1;
|
|
|
|
|
U32 triangleCount = idxCount / 3;
|
2017-01-12 04:34:46 +00:00
|
|
|
|
2012-09-19 15:15:01 +00:00
|
|
|
AssertFatal( startVert < mLowVertCount, "River, bad draw call!" );
|
|
|
|
|
AssertFatal( startVert + vertCount <= mLowVertCount, "River, bad draw call!" );
|
|
|
|
|
AssertFatal( triangleCount <= mLowTriangleCount, "River, bad draw call!" );
|
|
|
|
|
|
|
|
|
|
GFX->drawIndexedPrimitive( GFXTriangleList, 0, startVert, vertCount, startIdx, triangleCount );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Render all high detail batches.
|
|
|
|
|
//
|
|
|
|
|
// It is possible that the buffers could not be allocated because
|
|
|
|
|
// the max number of verts/indices was exceeded. We don't want to
|
|
|
|
|
// crash because that would be unhelpful for working in the editor.
|
|
|
|
|
if ( mVB_high.isValid() && mPB_high.isValid() )
|
|
|
|
|
{
|
|
|
|
|
GFX->setVertexBuffer( mVB_high );
|
|
|
|
|
GFX->setPrimitiveBuffer( mPB_high );
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mHighLODBatches.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverRenderBatch &batch = mHighLODBatches[i];
|
|
|
|
|
|
|
|
|
|
AssertFatal( batch.startVert < mHighVertCount, "River, bad draw call!" );
|
|
|
|
|
AssertFatal( batch.startVert + batch.vertCount <= mHighVertCount, "River, bad draw call!" );
|
|
|
|
|
AssertFatal( batch.triangleCount <= mHighTriangleCount, "River, bad draw call!" );
|
|
|
|
|
AssertFatal( batch.startIndex < mHighTriangleCount * 3, "River, bad draw call!" );
|
|
|
|
|
AssertFatal( batch.startIndex + batch.triangleCount * 3 <= mHighTriangleCount * 3, "River, bad draw call!" );
|
|
|
|
|
|
|
|
|
|
GFX->drawIndexedPrimitive( GFXTriangleList,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
batch.vertCount,
|
|
|
|
|
batch.startIndex,
|
|
|
|
|
batch.triangleCount );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // while( mat->setupPass( sgData ) )
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::updateUnderwaterEffect( SceneRenderState *state )
|
|
|
|
|
{
|
|
|
|
|
// Calculate mWaterPlane before calling updateUnderwaterEffect.
|
|
|
|
|
Point3F dummy;
|
|
|
|
|
_getWaterPlane( state->getCameraPosition(), mWaterFogData.plane, dummy );
|
|
|
|
|
|
|
|
|
|
Parent::updateUnderwaterEffect( state );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setShaderParams( SceneRenderState *state, BaseMatInstance* mat, const WaterMatParams& paramHandles )
|
|
|
|
|
{
|
|
|
|
|
// Set variables that will be assigned to shader consts within WaterCommon
|
|
|
|
|
// before calling Parent::setShaderParams
|
|
|
|
|
|
|
|
|
|
mUndulateMaxDist = mLodDistance;
|
|
|
|
|
|
|
|
|
|
Parent::setShaderParams( state, mat, paramHandles );
|
|
|
|
|
|
|
|
|
|
// Now set the rest of the shader consts that are either unique to this
|
|
|
|
|
// class or that WaterObject leaves to us to handle...
|
|
|
|
|
|
|
|
|
|
MaterialParameters* matParams = mat->getMaterialParameters();
|
|
|
|
|
|
|
|
|
|
// set vertex shader constants
|
|
|
|
|
//-----------------------------------
|
|
|
|
|
|
|
|
|
|
matParams->setSafe(paramHandles.mGridElementSizeSC, 1.0f);
|
|
|
|
|
if ( paramHandles.mModelMatSC->isValid() )
|
|
|
|
|
matParams->set(paramHandles.mModelMatSC, MatrixF::Identity, GFXSCT_Float4x4);
|
|
|
|
|
|
|
|
|
|
// set pixel shader constants
|
|
|
|
|
//-----------------------------------
|
|
|
|
|
|
2017-06-23 16:36:20 +00:00
|
|
|
LinearColorF c( mWaterFogData.color );
|
2012-09-19 15:15:01 +00:00
|
|
|
matParams->setSafe(paramHandles.mBaseColorSC, c);
|
|
|
|
|
|
|
|
|
|
// By default we need to show a true reflection is fullReflect is enabled and
|
|
|
|
|
// we are above water.
|
|
|
|
|
F32 reflect = mPlaneReflector.isEnabled() && !isUnderwater( state->getCameraPosition() );
|
|
|
|
|
|
|
|
|
|
// If we were occluded the last frame a query was fetched ( not necessarily last frame )
|
|
|
|
|
// and we weren't updated last frame... we don't have a valid texture to show
|
|
|
|
|
// so use the cubemap / fake reflection color this frame.
|
|
|
|
|
if ( mPlaneReflector.lastUpdateMs != REFLECTMGR->getLastUpdateMs() && mPlaneReflector.isOccluded() )
|
|
|
|
|
reflect = false;
|
|
|
|
|
|
|
|
|
|
Point4F reflectParams( mWaterPos.z, 0.0f, 1000.0f, !reflect );
|
|
|
|
|
matParams->setSafe(paramHandles.mReflectParamsSC, reflectParams );
|
|
|
|
|
|
|
|
|
|
matParams->setSafe(paramHandles.mReflectNormalSC, mPlaneReflector.refplane );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool River::isUnderwater( const Point3F &pnt ) const
|
|
|
|
|
{
|
|
|
|
|
return containsPoint( pnt, NULL );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
U32 River::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
|
|
|
|
|
{
|
|
|
|
|
// Pack Parent.
|
|
|
|
|
U32 retMask = Parent::packUpdate(con, mask, stream);
|
|
|
|
|
|
|
|
|
|
if ( stream->writeFlag( mask & RiverMask ) )
|
|
|
|
|
{
|
|
|
|
|
// Write Object Transform.
|
|
|
|
|
stream->writeAffineTransform(mObjToWorld);
|
|
|
|
|
|
|
|
|
|
stream->write( mMetersPerSegment );
|
|
|
|
|
stream->write( mSegmentsPerBatch );
|
|
|
|
|
stream->write( mDepthScale );
|
|
|
|
|
stream->write( mMaxDivisionSize );
|
2017-01-12 04:34:46 +00:00
|
|
|
stream->write( mColumnCount );
|
2012-09-19 15:15:01 +00:00
|
|
|
|
|
|
|
|
stream->write( mFlowMagnitude );
|
|
|
|
|
stream->write( mLodDistance );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( stream->writeFlag( mask & NodeMask ) )
|
|
|
|
|
{
|
|
|
|
|
const U32 nodeByteSize = 32; // Based on sending all of a node's parameters
|
|
|
|
|
|
|
|
|
|
// Test if we can fit all of our nodes within the current stream.
|
|
|
|
|
// We make sure we leave 100 bytes still free in the stream for whatever
|
|
|
|
|
// may follow us.
|
|
|
|
|
S32 allowedBytes = stream->getWriteByteSize() - 100;
|
|
|
|
|
if ( stream->writeFlag( (nodeByteSize * mNodes.size()) < allowedBytes ) )
|
|
|
|
|
{
|
|
|
|
|
// All nodes should fit, so send them out now.
|
|
|
|
|
stream->writeInt( mNodes.size(), 16 );
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
mathWrite( *stream, mNodes[i].point );
|
|
|
|
|
stream->write( mNodes[i].width );
|
|
|
|
|
stream->write( mNodes[i].depth );
|
|
|
|
|
mathWrite( *stream, mNodes[i].normal );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// There isn't enough space left in the stream for all of the
|
|
|
|
|
// nodes. Batch them up into NetEvents.
|
|
|
|
|
U32 id = gServerNodeListManager->nextListId();
|
|
|
|
|
U32 count = 0;
|
|
|
|
|
U32 index = 0;
|
|
|
|
|
while (count < mNodes.size())
|
|
|
|
|
{
|
|
|
|
|
count += NodeListManager::smMaximumNodesPerEvent;
|
|
|
|
|
if (count > mNodes.size())
|
|
|
|
|
{
|
|
|
|
|
count = mNodes.size();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RiverNodeEvent* event = new RiverNodeEvent();
|
|
|
|
|
event->mId = id;
|
|
|
|
|
event->mTotalNodes = mNodes.size();
|
|
|
|
|
event->mLocalListStart = index;
|
|
|
|
|
|
|
|
|
|
for (; index<count; ++index)
|
|
|
|
|
{
|
|
|
|
|
event->mPositions.push_back( mNodes[index].point );
|
|
|
|
|
event->mWidths.push_back( mNodes[index].width );
|
|
|
|
|
event->mDepths.push_back( mNodes[index].depth );
|
|
|
|
|
event->mNormals.push_back( mNodes[index].normal );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
con->postNetEvent( event );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream->write( id );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if( stream->writeFlag( mask & ( RiverMask | InitialUpdateMask ) ) )
|
|
|
|
|
{
|
|
|
|
|
// This is set to allow the user to modify the size of the water dynamically
|
|
|
|
|
// in the editor
|
|
|
|
|
mathWrite( *stream, mObjScale );
|
|
|
|
|
stream->writeAffineTransform( mObjToWorld );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream->writeFlag( mask & RegenMask );
|
|
|
|
|
|
|
|
|
|
return retMask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::unpackUpdate(NetConnection * con, BitStream * stream)
|
|
|
|
|
{
|
|
|
|
|
// Unpack Parent.
|
|
|
|
|
Parent::unpackUpdate(con, stream);
|
|
|
|
|
|
|
|
|
|
// RiverMask
|
|
|
|
|
if(stream->readFlag())
|
|
|
|
|
{
|
2017-01-12 04:34:46 +00:00
|
|
|
MatrixF ObjectMatrix;
|
2012-09-19 15:15:01 +00:00
|
|
|
stream->readAffineTransform(&ObjectMatrix);
|
|
|
|
|
Parent::setTransform(ObjectMatrix);
|
|
|
|
|
|
|
|
|
|
stream->read( &mMetersPerSegment );
|
|
|
|
|
stream->read( &mSegmentsPerBatch );
|
|
|
|
|
stream->read( &mDepthScale );
|
|
|
|
|
stream->read( &mMaxDivisionSize );
|
2017-01-12 04:34:46 +00:00
|
|
|
stream->read( &mColumnCount );
|
2012-09-19 15:15:01 +00:00
|
|
|
|
|
|
|
|
stream->read( &mFlowMagnitude );
|
|
|
|
|
stream->read( &mLodDistance );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NodeMask
|
|
|
|
|
if ( stream->readFlag() )
|
|
|
|
|
{
|
|
|
|
|
if (stream->readFlag())
|
|
|
|
|
{
|
|
|
|
|
// Nodes have been passed in this update
|
|
|
|
|
U32 count = stream->readInt( 16 );
|
|
|
|
|
|
|
|
|
|
mNodes.clear();
|
|
|
|
|
|
|
|
|
|
Point3F pos;
|
|
|
|
|
VectorF normal;
|
|
|
|
|
F32 width,depth;
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < count; i++ )
|
|
|
|
|
{
|
|
|
|
|
mathRead( *stream, &pos );
|
|
|
|
|
stream->read( &width );
|
|
|
|
|
stream->read( &depth );
|
|
|
|
|
mathRead( *stream, &normal );
|
|
|
|
|
_addNode( pos, width, depth, normal );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Nodes will arrive as events
|
|
|
|
|
U32 id;
|
|
|
|
|
stream->read( &id );
|
|
|
|
|
|
|
|
|
|
// Check if the road's nodes made it here before we did.
|
|
|
|
|
NodeListManager::NodeList* list = NULL;
|
|
|
|
|
if ( gClientNodeListManager->findListById( id, &list, true) )
|
|
|
|
|
{
|
|
|
|
|
// Work with the completed list
|
|
|
|
|
RiverNodeList* riverList = dynamic_cast<RiverNodeList*>( list );
|
|
|
|
|
if (riverList)
|
|
|
|
|
buildNodesFromList( riverList );
|
|
|
|
|
|
|
|
|
|
delete list;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Nodes have not yet arrived, so register our interest in the list
|
|
|
|
|
RiverNodeListNotify* notify = new RiverNodeListNotify( this, id );
|
|
|
|
|
gClientNodeListManager->registerNotification( notify );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RiverMask | InitialUpdateMask
|
|
|
|
|
if( stream->readFlag() )
|
|
|
|
|
{
|
|
|
|
|
mathRead( *stream, &mObjScale );
|
|
|
|
|
stream->readAffineTransform( &mObjToWorld );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// RegenMask
|
|
|
|
|
if ( stream->readFlag() && isProperlyAdded() )
|
|
|
|
|
regenerate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::_getWaterPlane( const Point3F &camPos, PlaneF &outPlane, Point3F &outPos )
|
|
|
|
|
{
|
|
|
|
|
// Find the RiverSegment closest to the camera.
|
|
|
|
|
F32 closestDist = F32_MAX;
|
|
|
|
|
S32 closestSegment = 0;
|
|
|
|
|
Point3F projPnt(0.0f, 0.0f, 0.0f);
|
|
|
|
|
|
|
|
|
|
VectorF normal(0,0,0);
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment &segment = mSegments[i];
|
|
|
|
|
|
|
|
|
|
const Point3F pos = MathUtils::mClosestPointOnSegment( segment.slice0->p1, segment.slice1->p1, camPos );
|
|
|
|
|
|
|
|
|
|
F32 dist = ( camPos - pos ).len();
|
|
|
|
|
|
|
|
|
|
if ( dist < closestDist )
|
|
|
|
|
{
|
|
|
|
|
closestDist = dist;
|
|
|
|
|
closestSegment = i;
|
|
|
|
|
projPnt = pos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
normal += segment.getSurfaceNormal();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( mReflectNormalUp )
|
|
|
|
|
normal.set(0,0,1);
|
|
|
|
|
else
|
|
|
|
|
normal.normalizeSafe();
|
|
|
|
|
|
|
|
|
|
outPos = projPnt;
|
|
|
|
|
outPlane.set( projPnt, normal );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setTransform( const MatrixF &mat )
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
mWorldToObj.mulP( mNodes[i].point );
|
|
|
|
|
mat.mulP( mNodes[i].point );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
// Get the amount of change in position.
|
|
|
|
|
MatrixF oldMat = getTransform();
|
|
|
|
|
Point3F oldPos = oldMat.getPosition();
|
|
|
|
|
Point3F newPos = mat.getPosition();
|
|
|
|
|
Point3F delta = newPos - oldPos;
|
|
|
|
|
|
|
|
|
|
// Offset all nodes by that amount
|
|
|
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
mNodes[i].point += delta;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Assign the new position ( we ignore rotation )
|
|
|
|
|
MatrixF newMat( oldMat );
|
|
|
|
|
newMat.setPosition( newPos );
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
Parent::setTransform( mat );
|
|
|
|
|
|
|
|
|
|
// Regenerate and update the client
|
|
|
|
|
_regenerate();
|
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setScale( const VectorF &scale )
|
|
|
|
|
{
|
|
|
|
|
// We ignore scale requests from the editor
|
|
|
|
|
// right now.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool River::castRay(const Point3F &s, const Point3F &e, RayInfo* info)
|
|
|
|
|
{
|
2017-01-12 04:34:46 +00:00
|
|
|
Point3F start = s;
|
|
|
|
|
Point3F end = e;
|
|
|
|
|
mObjToWorld.mulP(start);
|
|
|
|
|
mObjToWorld.mulP(end);
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
F32 out = 1.0f; // The output fraction/percentage along the line defined by s and e
|
|
|
|
|
VectorF norm(0.0f, 0.0f, 0.0f); // The normal of the face intersected
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
Vector<RiverHitSegment> hitSegments;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment &segment = mSegments[i];
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
F32 t;
|
|
|
|
|
VectorF n;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
if ( segment.worldbounds.collideLine( start, end, &t, &n ) )
|
|
|
|
|
{
|
|
|
|
|
hitSegments.increment();
|
|
|
|
|
hitSegments.last().t = t;
|
|
|
|
|
hitSegments.last().idx = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
dQsort( hitSegments.address(), hitSegments.size(), sizeof(RiverHitSegment), compareHitSegments );
|
2012-09-19 15:15:01 +00:00
|
|
|
|
|
|
|
|
U32 idx0, idx1, idx2;
|
|
|
|
|
F32 t;
|
|
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
for ( U32 i = 0; i < hitSegments.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
U32 segIdx = hitSegments[i].idx;
|
|
|
|
|
const RiverSegment &segment = mSegments[segIdx];
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
// Each segment has 6 faces
|
|
|
|
|
for ( U32 j = 0; j < 6; j++ )
|
|
|
|
|
{
|
|
|
|
|
if ( j == 4 && segIdx != 0 )
|
|
|
|
|
continue;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
if ( j == 5 && segIdx != mSegments.size() - 1 )
|
|
|
|
|
continue;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
// Each face has 2 triangles
|
|
|
|
|
for ( U32 k = 0; k < 2; k++ )
|
|
|
|
|
{
|
|
|
|
|
idx0 = gIdxArray[j][k][0];
|
|
|
|
|
idx1 = gIdxArray[j][k][1];
|
|
|
|
|
idx2 = gIdxArray[j][k][2];
|
2012-09-19 15:15:01 +00:00
|
|
|
|
|
|
|
|
const Point3F &v0 = segment[idx0];
|
|
|
|
|
const Point3F &v1 = segment[idx1];
|
|
|
|
|
const Point3F &v2 = segment[idx2];
|
|
|
|
|
|
|
|
|
|
if ( !MathUtils::mLineTriangleCollide( start, end,
|
|
|
|
|
v2, v1, v0,
|
|
|
|
|
NULL,
|
|
|
|
|
&t ) )
|
2017-01-12 04:34:46 +00:00
|
|
|
continue;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
if ( t >= 0.0f && t < 1.0f && t < out )
|
|
|
|
|
{
|
|
|
|
|
out = t;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
|
|
|
|
// optimize this, can be calculated easily within
|
|
|
|
|
// the collision test
|
|
|
|
|
norm = PlaneF( v0, v1, v2 );
|
2017-01-12 04:34:46 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (out >= 0.0f && out < 1.0f)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (out >= 0.0f && out < 1.0f)
|
|
|
|
|
{
|
|
|
|
|
info->t = out;
|
|
|
|
|
info->normal = norm;
|
|
|
|
|
info->point.interpolate(start, end, out);
|
|
|
|
|
info->face = -1;
|
|
|
|
|
info->object = this;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
2012-09-19 15:15:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool River::collideBox(const Point3F &start, const Point3F &end, RayInfo* info)
|
|
|
|
|
{
|
2017-01-12 04:34:46 +00:00
|
|
|
return false;
|
2012-09-19 15:15:01 +00:00
|
|
|
}
|
|
|
|
|
|
2014-11-28 08:42:10 +00:00
|
|
|
bool River::buildPolyList( PolyListContext context, AbstractPolyList* polyList, const Box3F& box, const SphereF& sphere )
|
|
|
|
|
{
|
|
|
|
|
Vector<const RiverSegment*> hitSegments;
|
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment &segment = mSegments[i];
|
|
|
|
|
if ( segment.worldbounds.isOverlapped( box ) )
|
|
|
|
|
{
|
|
|
|
|
hitSegments.push_back( &segment );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !hitSegments.size() )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
polyList->setObject( this );
|
|
|
|
|
polyList->setTransform( &MatrixF::Identity, Point3F( 1.0f, 1.0f, 1.0f ) );
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < hitSegments.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment* segment = hitSegments[i];
|
|
|
|
|
for ( U32 k = 0; k < 2; k++ )
|
|
|
|
|
{
|
|
|
|
|
// gIdxArray[0] gives us the top plane (see table definition).
|
|
|
|
|
U32 idx0 = gIdxArray[0][k][0];
|
|
|
|
|
U32 idx1 = gIdxArray[0][k][1];
|
|
|
|
|
U32 idx2 = gIdxArray[0][k][2];
|
|
|
|
|
|
|
|
|
|
const Point3F &v0 = (*segment)[idx0];
|
|
|
|
|
const Point3F &v1 = (*segment)[idx1];
|
|
|
|
|
const Point3F &v2 = (*segment)[idx2];
|
|
|
|
|
|
|
|
|
|
// Add vertices to poly list.
|
|
|
|
|
U32 i0 = polyList->addPoint(v0);
|
|
|
|
|
polyList->addPoint(v1);
|
|
|
|
|
polyList->addPoint(v2);
|
|
|
|
|
|
|
|
|
|
// Add plane between them.
|
|
|
|
|
polyList->begin(0, 0);
|
|
|
|
|
polyList->vertex(i0);
|
|
|
|
|
polyList->vertex(i0+1);
|
|
|
|
|
polyList->vertex(i0+2);
|
|
|
|
|
polyList->plane(i0, i0+1, i0+2);
|
|
|
|
|
polyList->end();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2012-09-19 15:15:01 +00:00
|
|
|
F32 River::getWaterCoverage( const Box3F &worldBox ) const
|
|
|
|
|
{
|
|
|
|
|
PROFILE_SCOPE( River_GetWaterCoverage );
|
|
|
|
|
|
|
|
|
|
if ( !mWorldBox.isOverlapped(worldBox) )
|
|
|
|
|
return 0.0f;
|
|
|
|
|
|
|
|
|
|
Point3F bottomPnt = worldBox.getCenter();
|
|
|
|
|
bottomPnt.z = worldBox.minExtents.z;
|
|
|
|
|
|
|
|
|
|
F32 farthest = 0.0f;
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment &segment = mSegments[i];
|
|
|
|
|
|
|
|
|
|
if ( !segment.worldbounds.isOverlapped(worldBox) )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if ( !segment.intersectBox( worldBox ) )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
F32 distance = segment.distanceToSurface( bottomPnt );
|
|
|
|
|
|
|
|
|
|
if ( distance > farthest )
|
|
|
|
|
farthest = distance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
F32 height = worldBox.maxExtents.z - worldBox.minExtents.z;
|
|
|
|
|
F32 distance = mClampF( farthest, 0.0f, height );
|
|
|
|
|
F32 coverage = distance / height;
|
|
|
|
|
|
|
|
|
|
return coverage;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
F32 River::getSurfaceHeight( const Point2F &pos ) const
|
|
|
|
|
{
|
|
|
|
|
PROFILE_SCOPE( River_GetSurfaceHeight );
|
|
|
|
|
|
|
|
|
|
Point3F origin( pos.x, pos.y, mWorldBox.maxExtents.z );
|
|
|
|
|
Point3F direction(0,0,-1);
|
|
|
|
|
U32 nodeIdx;
|
|
|
|
|
Point3F collisionPnt;
|
|
|
|
|
|
|
|
|
|
if ( !collideRay( origin, direction, &nodeIdx, &collisionPnt ) )
|
|
|
|
|
return -1.0f;
|
|
|
|
|
|
|
|
|
|
return collisionPnt.z;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VectorF River::getFlow( const Point3F &pos ) const
|
|
|
|
|
{
|
|
|
|
|
PROFILE_SCOPE( River_GetFlow );
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment &segment = mSegments[i];
|
|
|
|
|
|
|
|
|
|
if ( !segment.containsPoint(pos) )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
VectorF flow = segment.slice0->p1 - segment.slice1->p1;
|
|
|
|
|
flow.normalize();
|
|
|
|
|
flow *= mFlowMagnitude;
|
|
|
|
|
|
|
|
|
|
return flow;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return VectorF::Zero;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::onReflectionInfoChanged()
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
if ( isClientObject() && GFX->getPixelShaderVersion() >= 1.4 )
|
|
|
|
|
{
|
|
|
|
|
if ( mFullReflect )
|
|
|
|
|
REFLECTMGR->registerObject( this, ReflectDelegate( this, &River::updateReflection ), mReflectPriority, mReflectMaxRateMs, mReflectMaxDist );
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
REFLECTMGR->unregisterObject( this );
|
|
|
|
|
mReflectTex = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::_regenerate()
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() == 0 )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const Point3F &nodePt = mNodes.first().point;
|
|
|
|
|
|
|
|
|
|
MatrixF mat( true );
|
|
|
|
|
mat.setPosition( nodePt );
|
|
|
|
|
Parent::setTransform( mat );
|
|
|
|
|
|
|
|
|
|
_generateSlices();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::_generateSlices()
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() < 2 )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
U32 nodeCount = mNodes.size();
|
|
|
|
|
RiverSplineNode *splineNodes = new RiverSplineNode[nodeCount];
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < nodeCount; i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverNode &node = mNodes[i];
|
|
|
|
|
splineNodes[i].x = node.point.x;
|
|
|
|
|
splineNodes[i].y = node.point.y;
|
|
|
|
|
splineNodes[i].z = node.point.z;
|
|
|
|
|
splineNodes[i].width = node.width;
|
|
|
|
|
splineNodes[i].depth = node.depth;
|
|
|
|
|
splineNodes[i].normal = node.normal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CatmullRom<RiverSplineNode> spline;
|
|
|
|
|
spline.initialize( nodeCount, splineNodes );
|
|
|
|
|
delete [] splineNodes;
|
|
|
|
|
|
|
|
|
|
mSlices.clear();
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 1; i < nodeCount; i++ )
|
|
|
|
|
{
|
|
|
|
|
F32 t0 = spline.getTime( i-1 );
|
|
|
|
|
F32 t1 = spline.getTime( i );
|
|
|
|
|
|
|
|
|
|
F32 segLength = spline.arcLength( t0, t1 );
|
|
|
|
|
|
|
|
|
|
U32 numSegments = mCeil( segLength / mMetersPerSegment );
|
|
|
|
|
numSegments = getMax( numSegments, (U32)1 );
|
|
|
|
|
F32 tstep = ( t1 - t0 ) / numSegments;
|
|
|
|
|
|
|
|
|
|
//AssertFatal( numSegments > 0, "River::_generateSlices, got zero segments!" );
|
|
|
|
|
|
|
|
|
|
U32 startIdx = 0;
|
|
|
|
|
U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments;
|
|
|
|
|
|
|
|
|
|
for ( U32 j = startIdx; j < endIdx; j++ )
|
|
|
|
|
{
|
|
|
|
|
F32 t = t0 + tstep * j; //spline.findParameterByDistance( 0.0f, i * segLen );
|
|
|
|
|
RiverSplineNode val = spline.evaluate(t);
|
|
|
|
|
|
|
|
|
|
RiverSlice slice;
|
|
|
|
|
slice.p1.set( val.x, val.y, val.z );
|
|
|
|
|
slice.uvec.set( 0,0,1 );
|
|
|
|
|
slice.width = val.width;
|
|
|
|
|
slice.depth = val.depth;
|
|
|
|
|
slice.parentNodeIdx = i-1;
|
|
|
|
|
slice.normal = val.normal;
|
|
|
|
|
slice.normal.normalize();
|
|
|
|
|
mSlices.push_back( slice );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Calculate fvec and rvec for all slices
|
|
|
|
|
//
|
|
|
|
|
RiverSlice *pSlice = NULL;
|
|
|
|
|
RiverSlice *pNextSlice = NULL;
|
|
|
|
|
|
|
|
|
|
// Must do the first slice outside the loop
|
|
|
|
|
{
|
|
|
|
|
pSlice = &mSlices[0];
|
|
|
|
|
pNextSlice = &mSlices[1];
|
|
|
|
|
pSlice->fvec = pNextSlice->p1 - pSlice->p1;
|
|
|
|
|
pSlice->fvec.normalize();
|
|
|
|
|
pSlice->rvec = mCross( pSlice->fvec, pSlice->normal );
|
|
|
|
|
pSlice->rvec.normalize();
|
|
|
|
|
pSlice->uvec = mCross( pSlice->rvec, pSlice->fvec );
|
|
|
|
|
pSlice->uvec.normalize();
|
|
|
|
|
pSlice->rvec = mCross( pSlice->fvec, pSlice->uvec );
|
|
|
|
|
pSlice->rvec.normalize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 1; i < mSlices.size() - 1; i++ )
|
|
|
|
|
{
|
|
|
|
|
pSlice = &mSlices[i];
|
|
|
|
|
pNextSlice = &mSlices[i+1];
|
|
|
|
|
|
|
|
|
|
pSlice->fvec = pNextSlice->p1 - pSlice->p1;
|
|
|
|
|
pSlice->fvec.normalize();
|
|
|
|
|
|
|
|
|
|
pSlice->rvec = mCross( pSlice->fvec, pSlice->normal );
|
|
|
|
|
pSlice->rvec.normalize();
|
|
|
|
|
|
|
|
|
|
pSlice->uvec = mCross( pSlice->rvec, pSlice->fvec );
|
|
|
|
|
pSlice->uvec.normalize();
|
|
|
|
|
|
|
|
|
|
pSlice->rvec = mCross( pSlice->fvec, pSlice->uvec );
|
|
|
|
|
pSlice->rvec.normalize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Must do the last slice outside the loop
|
|
|
|
|
{
|
|
|
|
|
RiverSlice *lastSlice = &mSlices[mSlices.size()-1];
|
|
|
|
|
RiverSlice *prevSlice = &mSlices[mSlices.size()-2];
|
|
|
|
|
|
|
|
|
|
lastSlice->fvec = prevSlice->fvec;
|
|
|
|
|
|
|
|
|
|
lastSlice->rvec = mCross( lastSlice->fvec, lastSlice->normal );
|
|
|
|
|
lastSlice->rvec.normalize();
|
|
|
|
|
|
|
|
|
|
lastSlice->uvec = mCross( lastSlice->rvec, lastSlice->fvec );
|
|
|
|
|
lastSlice->uvec.normalize();
|
|
|
|
|
|
|
|
|
|
lastSlice->rvec = mCross( lastSlice->fvec, lastSlice->uvec );
|
|
|
|
|
lastSlice->rvec.normalize();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Calculate p0/p2/pb0/pb2 for all slices
|
|
|
|
|
//
|
|
|
|
|
for ( U32 i = 0; i < mSlices.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
RiverSlice *slice = &mSlices[i];
|
|
|
|
|
slice->p0 = slice->p1 - slice->rvec * slice->width * 0.5f;
|
|
|
|
|
slice->p2 = slice->p1 + slice->rvec * slice->width * 0.5f;
|
|
|
|
|
slice->pb0 = slice->p0 - slice->uvec * slice->depth;
|
|
|
|
|
slice->pb2 = slice->p2 - slice->uvec * slice->depth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate the object/world bounds
|
|
|
|
|
Box3F box;
|
|
|
|
|
for ( U32 i = 0; i < mSlices.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSlice &slice = mSlices[i];
|
|
|
|
|
|
|
|
|
|
if ( i == 0 )
|
|
|
|
|
{
|
|
|
|
|
box.minExtents = slice.p0;
|
|
|
|
|
box.maxExtents = slice.p2;
|
|
|
|
|
box.extend( slice.pb0 );
|
|
|
|
|
box.extend( slice.pb2 );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
box.extend( slice.p0 );
|
|
|
|
|
box.extend( slice.p2 );
|
|
|
|
|
box.extend( slice.pb0 );
|
|
|
|
|
box.extend( slice.pb2 );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mWorldBox = box;
|
|
|
|
|
//mObjBox.minExtents -= pos;
|
|
|
|
|
//mObjBox.maxExtents -= pos;
|
|
|
|
|
resetObjectBox();
|
|
|
|
|
|
|
|
|
|
// Make sure we are in the correct bins given our world box.
|
|
|
|
|
if( getSceneManager() != NULL )
|
|
|
|
|
getSceneManager()->notifyObjectDirty( this );
|
|
|
|
|
|
|
|
|
|
_generateSegments();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::_generateSegments()
|
|
|
|
|
{
|
|
|
|
|
mSegments.clear();
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mSlices.size() - 1; i++ )
|
|
|
|
|
{
|
|
|
|
|
RiverSegment seg( &mSlices[i], &mSlices[i+1] );
|
|
|
|
|
|
|
|
|
|
mSegments.push_back( seg );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
#ifdef TORQUE_DEBUG
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment &segment = mSegments[i];
|
|
|
|
|
PlaneF normal0 = MathUtils::mTriangleNormal( segment.slice0->p0, segment.slice1->p0, segment.slice1->p2 );
|
|
|
|
|
PlaneF normal1 = MathUtils::mTriangleNormal( segment.slice0->p0, segment.slice1->p2, segment.slice0->p2 );
|
|
|
|
|
AssertFatal( true || normal0 != normal1, "River::generateSegments, segment is not coplanar!" );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif // TORQUE_DEBUG
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// We have to go back and generate normals for each slice
|
|
|
|
|
// to be used in calculation of the reflect plane.
|
|
|
|
|
// The slice-normal we calculate are relative to the surface normal
|
|
|
|
|
// of the segments adjacent to the slice.
|
|
|
|
|
/*
|
|
|
|
|
if ( mSlices.size() >= 2 )
|
|
|
|
|
{
|
|
|
|
|
mSlices[0].normal = mSegments[0].getSurfaceNormal();
|
|
|
|
|
for ( U32 i = 1; i < mSlices.size() - 1; i++ )
|
|
|
|
|
{
|
|
|
|
|
mSlices[i].normal = ( mSegments[i-1].getSurfaceNormal() + mSegments[i].getSurfaceNormal() ) / 2;
|
|
|
|
|
}
|
|
|
|
|
mSlices.last().normal = mSegments.last().getSurfaceNormal();
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
_generateVerts();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::_generateVerts()
|
|
|
|
|
{
|
|
|
|
|
if ( isServerObject() )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// These will depend on the level of subdivision per segment
|
|
|
|
|
// calculated below.
|
|
|
|
|
mHighVertCount = 0;
|
2017-01-12 04:34:46 +00:00
|
|
|
mHighTriangleCount = 0;
|
|
|
|
|
|
2012-09-19 15:15:01 +00:00
|
|
|
// Calculate the number of row/column subdivisions per each
|
|
|
|
|
// RiverSegment.
|
|
|
|
|
|
|
|
|
|
F32 greatestWidth = 0.1f;
|
|
|
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
RiverNode &node = mNodes[i];
|
|
|
|
|
if ( node.width > greatestWidth )
|
|
|
|
|
greatestWidth = node.width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mColumnCount = mCeil( greatestWidth / mMaxDivisionSize );
|
|
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
|
|
|
{
|
2012-09-19 15:15:01 +00:00
|
|
|
RiverSegment &segment = mSegments[i];
|
|
|
|
|
const RiverSlice *slice = segment.slice0;
|
2017-01-12 04:34:46 +00:00
|
|
|
const RiverSlice *nextSlice = segment.slice1;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
// Calculate the size of divisions in the forward direction ( p00 -> p01 )
|
|
|
|
|
F32 segLength = (nextSlice->p1 - slice->p1).len();
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
// A division count of one is actually NO subdivision,
|
|
|
|
|
// the segment corners are the only verts in this segment.
|
|
|
|
|
U32 numRows = 1;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
|
|
|
|
if ( segLength > 0.0f )
|
|
|
|
|
numRows = mCeil( segLength / mMaxDivisionSize );
|
|
|
|
|
|
|
|
|
|
// The problem with calculating num columns per segment is
|
|
|
|
|
// two adjacent - high lod segments of different width can have
|
|
|
|
|
// verts that don't line up! So even though RiverSegment HAS a
|
|
|
|
|
// column data member we initialize all segments in the river to
|
|
|
|
|
// the same (River::mColumnCount)
|
|
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
// Calculate the size of divisions in the right direction ( p00 -> p10 )
|
|
|
|
|
// F32 segWidth = ( ( p11 - p01 ).len() + ( p10 - p00 ).len() ) * 0.5f;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
// U32 numColumns = 5;
|
|
|
|
|
//F32 columnSize = segWidth / numColumns;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
//while ( columnSize > mMaxDivisionSize )
|
|
|
|
|
//{
|
|
|
|
|
// numColumns++;
|
|
|
|
|
// columnSize = segWidth / numColumns;
|
|
|
|
|
//}
|
|
|
|
|
|
2012-09-19 15:15:01 +00:00
|
|
|
// Save the calculated numb of columns / rows for this segment.
|
|
|
|
|
segment.columns = mColumnCount;
|
|
|
|
|
segment.rows = numRows;
|
2017-01-12 04:34:46 +00:00
|
|
|
|
2012-09-19 15:15:01 +00:00
|
|
|
// Save the corresponding number of verts/prims
|
|
|
|
|
segment.numVerts = ( 1 + mColumnCount ) * ( 1 + numRows );
|
|
|
|
|
segment.numTriangles = mColumnCount * numRows * 2;
|
|
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
mHighVertCount += segment.numVerts;
|
|
|
|
|
mHighTriangleCount += segment.numTriangles;
|
|
|
|
|
}
|
2012-09-19 15:15:01 +00:00
|
|
|
|
|
|
|
|
// Number of low detail verts/prims.
|
2017-01-12 04:34:46 +00:00
|
|
|
mLowVertCount = mSlices.size() * 2;
|
|
|
|
|
mLowTriangleCount = mSegments.size() * 2;
|
2012-09-19 15:15:01 +00:00
|
|
|
|
|
|
|
|
// Allocate the low detail VertexBuffer,
|
|
|
|
|
// this will stay in memory and will never need to change.
|
|
|
|
|
mVB_low.set( GFX, mLowVertCount, GFXBufferTypeStatic );
|
|
|
|
|
|
|
|
|
|
GFXWaterVertex *lowVertPtr = mVB_low.lock();
|
|
|
|
|
U32 vertCounter = 0;
|
|
|
|
|
|
2017-01-12 04:34:46 +00:00
|
|
|
// The texCoord.y value start/end for a segment
|
|
|
|
|
// as we loop through them.
|
2012-09-19 15:15:01 +00:00
|
|
|
F32 textCoordV = 0;
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Fill the low-detail VertexBuffer
|
|
|
|
|
//
|
|
|
|
|
for ( U32 i = 0; i < mSlices.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
RiverSlice &slice = mSlices[i];
|
|
|
|
|
|
|
|
|
|
lowVertPtr->point = slice.p0;
|
|
|
|
|
lowVertPtr->normal = slice.normal;
|
|
|
|
|
lowVertPtr->undulateData.set( -slice.width*0.5f, textCoordV );
|
|
|
|
|
lowVertPtr->horizonFactor.set( 0, 0, 0, 0 );
|
|
|
|
|
lowVertPtr++;
|
|
|
|
|
vertCounter++;
|
|
|
|
|
|
|
|
|
|
lowVertPtr->point = slice.p2;
|
|
|
|
|
lowVertPtr->normal = slice.normal;
|
|
|
|
|
lowVertPtr->undulateData.set( slice.width*0.5f, textCoordV );
|
|
|
|
|
lowVertPtr->horizonFactor.set( 0, 0, 0, 0 );
|
|
|
|
|
lowVertPtr++;
|
|
|
|
|
vertCounter++;
|
|
|
|
|
|
|
|
|
|
// Save this so we can get it later.
|
|
|
|
|
slice.texCoordV = textCoordV;
|
|
|
|
|
|
|
|
|
|
if ( i < mSlices.size() - 1 )
|
|
|
|
|
{
|
|
|
|
|
// Increment the textCoordV for the next slice.
|
|
|
|
|
F32 segLen = ( mSlices[i+1].p1 - slice.p1 ).len();
|
2017-01-12 04:34:46 +00:00
|
|
|
textCoordV += segLen;
|
2012-09-19 15:15:01 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AssertFatal( vertCounter == mLowVertCount, "River, wrote incorrect number of verts in mBV_low!" );
|
|
|
|
|
|
|
|
|
|
// Unlock the low-detail VertexBuffer, we are done filling it.
|
|
|
|
|
mVB_low.unlock();
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Create the low-detail prim buffer(s)
|
2017-01-12 04:34:46 +00:00
|
|
|
//
|
|
|
|
|
mPB_low.set( GFX, mLowTriangleCount * 3, mLowTriangleCount, GFXBufferTypeStatic );
|
2012-09-19 15:15:01 +00:00
|
|
|
|
|
|
|
|
U16 *lowIdxBuff;
|
|
|
|
|
mPB_low.lock(&lowIdxBuff);
|
|
|
|
|
U32 curLowIdx = 0;
|
|
|
|
|
|
|
|
|
|
// Temporaries to hold indices for the corner points of a quad.
|
|
|
|
|
U32 p00, p01, p11, p10;
|
|
|
|
|
|
|
|
|
|
U32 offset = 0;
|
|
|
|
|
|
|
|
|
|
// Fill the low-detail PrimitiveBuffer
|
2017-01-12 04:34:46 +00:00
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
|
|
|
{
|
2012-09-19 15:15:01 +00:00
|
|
|
//const RiverSegment &segment = mSegments[i];
|
2017-01-12 04:34:46 +00:00
|
|
|
|
2012-09-19 15:15:01 +00:00
|
|
|
// Two triangles formed by the corner points of this segment
|
|
|
|
|
// into the the low detail primitive buffer.
|
2017-01-12 04:34:46 +00:00
|
|
|
p00 = offset;
|
2012-09-19 15:15:01 +00:00
|
|
|
p01 = p00 + 2;
|
|
|
|
|
p11 = p01 + 1;
|
|
|
|
|
p10 = p00 + 1;
|
|
|
|
|
|
|
|
|
|
// Upper-Left triangle
|
|
|
|
|
lowIdxBuff[curLowIdx] = p00;
|
|
|
|
|
curLowIdx++;
|
|
|
|
|
lowIdxBuff[curLowIdx] = p01;
|
|
|
|
|
curLowIdx++;
|
|
|
|
|
lowIdxBuff[curLowIdx] = p11;
|
|
|
|
|
curLowIdx++;
|
|
|
|
|
|
|
|
|
|
// Lower-Right Triangle
|
|
|
|
|
lowIdxBuff[curLowIdx] = p00;
|
|
|
|
|
curLowIdx++;
|
|
|
|
|
lowIdxBuff[curLowIdx] = p11;
|
|
|
|
|
curLowIdx++;
|
|
|
|
|
lowIdxBuff[curLowIdx] = p10;
|
|
|
|
|
curLowIdx++;
|
|
|
|
|
|
|
|
|
|
offset += 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AssertFatal( curLowIdx == mLowTriangleCount * 3, "River, wrote incorrect number of indices in mPB_low!" );
|
|
|
|
|
|
|
|
|
|
// Unlock the low-detail PrimitiveBuffer, we are done filling it.
|
|
|
|
|
mPB_low.unlock();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool River::getClosestNode( const Point3F &pos, U32 &idx ) const
|
|
|
|
|
{
|
|
|
|
|
F32 closestDist = F32_MAX;
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
F32 dist = ( mNodes[i].point - pos ).len();
|
|
|
|
|
if ( dist < closestDist )
|
|
|
|
|
{
|
|
|
|
|
closestDist = dist;
|
|
|
|
|
idx = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return closestDist != F32_MAX;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool River::containsPoint( const Point3F &worldPos, U32 *nodeIdx ) const
|
|
|
|
|
{
|
|
|
|
|
// If point isn't in the world box,
|
|
|
|
|
// it's definitely not in the River.
|
|
|
|
|
//if ( !getWorldBox().isContained( worldPos ) )
|
|
|
|
|
// return false;
|
|
|
|
|
|
|
|
|
|
// Look through all edges, does the polygon
|
|
|
|
|
// formed from adjacent edge's contain the worldPos?
|
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment &segment = mSegments[i];
|
|
|
|
|
|
|
|
|
|
if ( segment.containsPoint( worldPos ) )
|
|
|
|
|
{
|
|
|
|
|
if ( nodeIdx )
|
|
|
|
|
*nodeIdx = i;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
F32 River::distanceToSurface( const Point3F &pnt, U32 segmentIdx )
|
|
|
|
|
{
|
|
|
|
|
return mSegments[segmentIdx].distanceToSurface( pnt );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool River::collideRay( const Point3F &origin, const Point3F &direction, U32 *nodeIdx, Point3F *collisionPnt ) const
|
|
|
|
|
{
|
|
|
|
|
Point3F p0 = origin;
|
|
|
|
|
Point3F p1 = origin + direction * 2000.0f;
|
|
|
|
|
|
|
|
|
|
// If the line segment does not collide with the river's world box,
|
|
|
|
|
// it definitely does not collide with any part of the river.
|
|
|
|
|
if ( !getWorldBox().collideLine( p0, p1 ) )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if ( mSlices.size() < 2 )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
MathUtils::Quad quad;
|
|
|
|
|
MathUtils::Ray ray;
|
|
|
|
|
F32 t;
|
|
|
|
|
|
|
|
|
|
// Check each river segment (formed by a pair of slices) for collision
|
|
|
|
|
// with the line segment.
|
|
|
|
|
for ( U32 i = 0; i < mSlices.size() - 1; i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSlice &slice0 = mSlices[i];
|
|
|
|
|
const RiverSlice &slice1 = mSlices[i+1];
|
|
|
|
|
|
|
|
|
|
// For simplicities sake we will only test for collision between the
|
|
|
|
|
// line segment and the Top face of the river segment.
|
|
|
|
|
|
|
|
|
|
// Clockwise starting with the leftmost/closest point.
|
|
|
|
|
quad.p00 = slice0.p0;
|
|
|
|
|
quad.p01 = slice1.p0;
|
|
|
|
|
quad.p11 = slice1.p2;
|
|
|
|
|
quad.p10 = slice0.p2;
|
|
|
|
|
|
|
|
|
|
ray.origin = origin;
|
|
|
|
|
ray.direction = direction;
|
|
|
|
|
|
|
|
|
|
// NOTE:
|
|
|
|
|
// mRayQuadCollide is designed for a "real" quad in which all four points
|
|
|
|
|
// are coplanar which is actually not the case here. The more twist
|
|
|
|
|
// and turn in-between two neighboring river slices the more incorrect
|
|
|
|
|
// this calculation will be.
|
|
|
|
|
|
|
|
|
|
if ( MathUtils::mRayQuadCollide( quad, ray, NULL, &t ) )
|
|
|
|
|
{
|
|
|
|
|
if ( nodeIdx )
|
|
|
|
|
*nodeIdx = slice0.parentNodeIdx;
|
|
|
|
|
if ( collisionPnt )
|
|
|
|
|
*collisionPnt = ray.origin + ray.direction * t;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Point3F River::getNodePosition( U32 idx ) const
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return Point3F();
|
|
|
|
|
|
|
|
|
|
return mNodes[idx].point;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setNodePosition( U32 idx, const Point3F &pos )
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
mNodes[idx].point = pos;
|
|
|
|
|
|
|
|
|
|
regenerate();
|
|
|
|
|
|
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
U32 River::addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal )
|
|
|
|
|
{
|
|
|
|
|
U32 idx = _addNode( pos, width, depth, normal );
|
|
|
|
|
|
|
|
|
|
regenerate();
|
|
|
|
|
|
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
|
|
|
|
|
|
|
|
return idx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
U32 River::insertNode(const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx)
|
|
|
|
|
{
|
|
|
|
|
U32 ret = _insertNode( pos, width, depth, normal, idx );
|
|
|
|
|
|
|
|
|
|
regenerate();
|
|
|
|
|
|
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setNode(const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx)
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
RiverNode &node = mNodes[idx];
|
|
|
|
|
node.point = pos;
|
|
|
|
|
node.width = width;
|
|
|
|
|
node.depth = depth;
|
|
|
|
|
node.normal = normal;
|
|
|
|
|
|
|
|
|
|
regenerate();
|
|
|
|
|
|
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setNodeWidth( U32 idx, F32 meters )
|
|
|
|
|
{
|
|
|
|
|
meters = mClampF( meters, MIN_NODE_WIDTH, MAX_NODE_WIDTH );
|
|
|
|
|
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
mNodes[idx].width = meters;
|
|
|
|
|
_regenerate();
|
|
|
|
|
|
|
|
|
|
setMaskBits( RegenMask | NodeMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setNodeHeight( U32 idx, F32 height )
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
mNodes[idx].point.z = height;
|
|
|
|
|
_regenerate();
|
|
|
|
|
|
|
|
|
|
setMaskBits( RegenMask | NodeMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
F32 River::getNodeWidth( U32 idx ) const
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return -1.0f;
|
|
|
|
|
|
|
|
|
|
return mNodes[idx].width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setNodeDepth( U32 idx, F32 meters )
|
|
|
|
|
{
|
|
|
|
|
meters = mClampF( meters, MIN_NODE_DEPTH, MAX_NODE_DEPTH );
|
|
|
|
|
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
mNodes[idx].depth = meters;
|
|
|
|
|
_regenerate();
|
|
|
|
|
setMaskBits( RiverMask | RegenMask | NodeMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setNodeNormal( U32 idx, const VectorF &normal )
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
mNodes[idx].normal = normal;
|
|
|
|
|
|
|
|
|
|
regenerate();
|
|
|
|
|
|
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
F32 River::getNodeDepth( U32 idx ) const
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return -1.0f;
|
|
|
|
|
|
|
|
|
|
return mNodes[idx].depth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VectorF River::getNodeNormal( U32 idx ) const
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return VectorF::Zero;
|
|
|
|
|
|
|
|
|
|
return mNodes[idx].normal;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MatrixF River::getNodeTransform( U32 idx ) const
|
|
|
|
|
{
|
|
|
|
|
MatrixF mat(true);
|
|
|
|
|
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return mat;
|
|
|
|
|
|
|
|
|
|
bool hasNext = idx + 1 < mNodes.size();
|
|
|
|
|
bool hasPrev = (S32)idx - 1 >= 0;
|
|
|
|
|
|
|
|
|
|
const RiverNode &node = mNodes[idx];
|
|
|
|
|
|
|
|
|
|
VectorF fvec( 0, 1, 0 );
|
|
|
|
|
|
|
|
|
|
if ( hasNext )
|
|
|
|
|
{
|
|
|
|
|
fvec = mNodes[idx+1].point - node.point;
|
|
|
|
|
fvec.normalizeSafe();
|
|
|
|
|
}
|
|
|
|
|
else if ( hasPrev )
|
|
|
|
|
{
|
|
|
|
|
fvec = node.point - mNodes[idx-1].point;
|
|
|
|
|
fvec.normalizeSafe();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
fvec = mPerp( node.normal );
|
|
|
|
|
|
|
|
|
|
if ( fvec.isZero() )
|
|
|
|
|
fvec = mPerp( node.normal );
|
|
|
|
|
|
|
|
|
|
F32 dot = mDot( fvec, node.normal );
|
|
|
|
|
if ( dot < -0.9f || dot > 0.9f )
|
|
|
|
|
fvec = mPerp( node.normal );
|
|
|
|
|
|
|
|
|
|
VectorF rvec = mCross( fvec, node.normal );
|
|
|
|
|
if ( rvec.isZero() )
|
|
|
|
|
rvec = mPerp( fvec );
|
|
|
|
|
rvec.normalize();
|
|
|
|
|
|
|
|
|
|
fvec = mCross( node.normal, rvec );
|
|
|
|
|
fvec.normalize();
|
|
|
|
|
|
|
|
|
|
mat.setColumn( 0, rvec );
|
|
|
|
|
mat.setColumn( 1, fvec );
|
|
|
|
|
mat.setColumn( 2, node.normal );
|
|
|
|
|
mat.setColumn( 3, node.point );
|
|
|
|
|
|
|
|
|
|
AssertFatal( m_matF_determinant( mat ) != 0.0f, "no inverse!");
|
|
|
|
|
|
|
|
|
|
return mat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::deleteNode( U32 idx )
|
|
|
|
|
{
|
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
mNodes.erase(idx);
|
|
|
|
|
_regenerate();
|
|
|
|
|
|
|
|
|
|
setMaskBits( RegenMask | NodeMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::buildNodesFromList( RiverNodeList* list )
|
|
|
|
|
{
|
|
|
|
|
mNodes.clear();
|
|
|
|
|
|
|
|
|
|
for (U32 i=0; i<list->mPositions.size(); ++i)
|
|
|
|
|
{
|
|
|
|
|
_addNode( list->mPositions[i], list->mWidths[i], list->mDepths[i], list->mNormals[i] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_regenerate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::_makeRenderBatches( const Point3F &cameraPos )
|
|
|
|
|
{
|
|
|
|
|
// Loop through each segment to determine if it is either 1 [not visible], 2 [high LOD], 3 [low LOD]
|
|
|
|
|
|
|
|
|
|
mHighLODBatches.clear();
|
|
|
|
|
mLowLODBatches.clear();
|
|
|
|
|
|
|
|
|
|
// Keeps track of what we batch type we are currently collecting.
|
|
|
|
|
// -1 is uninitialized, 0 is low detail, 1 is high detail
|
|
|
|
|
S32 lastDetail = -1;
|
|
|
|
|
bool highDetail;
|
|
|
|
|
|
|
|
|
|
U32 startSegmentIdx = -1;
|
|
|
|
|
U32 endSegmentIdx = 0;
|
|
|
|
|
|
|
|
|
|
F32 lodDistSquared = mLodDistance * mLodDistance;
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment &segment = mSegments[i];
|
|
|
|
|
const RiverSlice *slice = segment.slice0;
|
|
|
|
|
const RiverSlice *nextSlice = segment.slice1;
|
|
|
|
|
|
|
|
|
|
// TODO: add bounds BoxF to RiverSegment
|
|
|
|
|
const bool isVisible = true; //frustum.intersects( segment.bounds );
|
|
|
|
|
if ( isVisible )
|
|
|
|
|
{
|
|
|
|
|
F32 dist0 = MathUtils::mTriangleDistance( slice->p0, nextSlice->p0, nextSlice->p2, cameraPos );
|
|
|
|
|
F32 dist1 = MathUtils::mTriangleDistance( slice->p0, nextSlice->p2, slice->p2, cameraPos );
|
|
|
|
|
|
|
|
|
|
F32 dist = getMin( dist0, dist1 );
|
|
|
|
|
highDetail = ( dist < lodDistSquared );
|
2016-10-14 22:26:13 +00:00
|
|
|
if ( (highDetail && lastDetail == 0) ||
|
|
|
|
|
(!highDetail && lastDetail == 1) )
|
2012-09-19 15:15:01 +00:00
|
|
|
{
|
|
|
|
|
// We hit a segment with a different lod than the previous.
|
|
|
|
|
// Save what we have so far...
|
|
|
|
|
|
|
|
|
|
RiverRenderBatch batch;
|
|
|
|
|
|
|
|
|
|
batch.startSegmentIdx = startSegmentIdx;
|
|
|
|
|
batch.endSegmentIdx = endSegmentIdx;
|
|
|
|
|
|
|
|
|
|
if ( lastDetail == 0 )
|
|
|
|
|
{
|
|
|
|
|
mLowLODBatches.push_back( batch );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
mHighLODBatches.push_back( batch );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reset the batching
|
|
|
|
|
startSegmentIdx = -1;
|
|
|
|
|
lastDetail = -1;
|
|
|
|
|
i--;
|
|
|
|
|
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If this is the start of a set of batches.
|
|
|
|
|
if ( startSegmentIdx == -1 )
|
|
|
|
|
{
|
|
|
|
|
endSegmentIdx = startSegmentIdx = i;
|
|
|
|
|
lastDetail = ( highDetail ) ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Else we're extending the end batch index.
|
|
|
|
|
else
|
|
|
|
|
++endSegmentIdx;
|
|
|
|
|
|
|
|
|
|
// If this isn't the last batch then continue.
|
|
|
|
|
if ( i < mSegments.size()-1 )
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If we still don't have a start batch skip.
|
|
|
|
|
if ( startSegmentIdx == -1 )
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Save what we have so far...
|
|
|
|
|
|
|
|
|
|
RiverRenderBatch batch;
|
|
|
|
|
|
|
|
|
|
batch.startSegmentIdx = startSegmentIdx;
|
|
|
|
|
batch.endSegmentIdx = endSegmentIdx;
|
|
|
|
|
|
|
|
|
|
if ( lastDetail == 0 )
|
|
|
|
|
{
|
|
|
|
|
mLowLODBatches.push_back( batch );
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
mHighLODBatches.push_back( batch );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reset the batching.
|
|
|
|
|
startSegmentIdx = -1;
|
|
|
|
|
lastDetail = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::_makeHighLODBuffers()
|
|
|
|
|
{
|
|
|
|
|
PROFILE_SCOPE( River_makeHighLODBuffers );
|
|
|
|
|
|
|
|
|
|
// This is the number of verts/triangles for ALL high lod batches combined.
|
|
|
|
|
// eg. the size for the buffers.
|
|
|
|
|
U32 numVerts = 0;
|
|
|
|
|
U32 numTriangles = 0;
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mHighLODBatches.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
RiverRenderBatch &batch = mHighLODBatches[i];
|
|
|
|
|
|
|
|
|
|
for ( U32 j = batch.startSegmentIdx; j <= batch.endSegmentIdx; j++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment &segment = mSegments[j];
|
|
|
|
|
|
|
|
|
|
numTriangles += segment.numTriangles;
|
|
|
|
|
numVerts += segment.numVerts;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-05 02:57:17 +00:00
|
|
|
if ( numVerts > GFX_MAX_DYNAMIC_VERTS || numTriangles * 3 > GFX_MAX_DYNAMIC_INDICES )
|
2012-09-19 15:15:01 +00:00
|
|
|
{
|
|
|
|
|
mVB_high = NULL;
|
|
|
|
|
mPB_high = NULL;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mHighTriangleCount = numTriangles;
|
|
|
|
|
mHighVertCount = numVerts;
|
|
|
|
|
|
|
|
|
|
mVB_high.set( GFX, numVerts, GFXBufferTypeVolatile );
|
|
|
|
|
GFXWaterVertex *vertPtr = mVB_high.lock();
|
|
|
|
|
U32 vertCounter = 0;
|
|
|
|
|
|
|
|
|
|
// NOTE: this will break if different segments have different number
|
|
|
|
|
// of columns, but that will also cause T-junction triangles so just don't
|
|
|
|
|
// do that.
|
|
|
|
|
|
|
|
|
|
// For each batch, loop through the segments contained by
|
|
|
|
|
// that batch, and add their verts to the buffer.
|
|
|
|
|
for ( U32 i = 0; i < mHighLODBatches.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
RiverRenderBatch &batch = mHighLODBatches[i];
|
|
|
|
|
|
|
|
|
|
batch.startVert = vertCounter;
|
|
|
|
|
batch.vertCount = 0;
|
|
|
|
|
|
|
|
|
|
VectorF lastNormal(0,0,1);
|
|
|
|
|
|
|
|
|
|
for ( U32 j = batch.startSegmentIdx; j <= batch.endSegmentIdx; j++ )
|
|
|
|
|
{
|
|
|
|
|
// Add the verts for this segment to the buffer.
|
|
|
|
|
RiverSegment &segment = mSegments[j];
|
|
|
|
|
|
|
|
|
|
BiSqrToQuad3D squareToQuad( segment.getP00(),
|
|
|
|
|
segment.getP10(),
|
|
|
|
|
segment.getP11(),
|
|
|
|
|
segment.getP01() );
|
|
|
|
|
|
|
|
|
|
// We are duplicating the last row of verts in a segment on
|
|
|
|
|
// the first row of the next segment. This could be optimized but
|
|
|
|
|
// shouldn't cause any problems.
|
|
|
|
|
|
|
|
|
|
VectorF normal = segment.getSurfaceNormal();
|
|
|
|
|
|
|
|
|
|
for ( U32 k = 0; k <= segment.rows; k++ )
|
|
|
|
|
{
|
|
|
|
|
VectorF vertNormal = ( k == 0 && j != batch.startSegmentIdx ) ? lastNormal : normal;
|
|
|
|
|
|
|
|
|
|
F32 rowLen = mLerp( segment.slice0->width, segment.slice1->width, (F32)k / (F32)segment.rows );
|
|
|
|
|
|
|
|
|
|
for ( U32 l = 0; l <= segment.columns; l++ )
|
|
|
|
|
{
|
|
|
|
|
// We are generating a "row" of verts along the forwardDivision
|
|
|
|
|
// Each l iteration is a step to the right along with row.
|
|
|
|
|
|
|
|
|
|
Point2F uv( (F32)l / (F32)segment.columns, (F32)k / (F32)segment.rows );
|
|
|
|
|
|
|
|
|
|
Point3F pnt = squareToQuad.transform( uv );
|
|
|
|
|
|
|
|
|
|
// Assign the Vert
|
|
|
|
|
vertPtr->point = pnt;
|
|
|
|
|
vertPtr->normal = vertNormal;
|
|
|
|
|
vertPtr->undulateData.x = ( uv.x - 0.5f ) * rowLen;
|
|
|
|
|
vertPtr->undulateData.y = ( segment.TexCoordEnd() - segment.TexCoordStart() ) * uv.y + segment.TexCoordStart();
|
|
|
|
|
vertPtr->horizonFactor.set( 0, 0, 0, 0 );
|
|
|
|
|
|
|
|
|
|
vertPtr++;
|
|
|
|
|
vertCounter++;
|
|
|
|
|
batch.vertCount++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastNormal = normal;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
AssertFatal( vertCounter == mHighVertCount, "River, wrote incorrect number of verts in mVB_high" );
|
|
|
|
|
|
|
|
|
|
mVB_high.unlock();
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Do the high lod primitive buffer.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
mPB_high.set( GFX, numTriangles * 3, numTriangles, GFXBufferTypeVolatile );
|
|
|
|
|
U16 *idxBuff;
|
|
|
|
|
mPB_high.lock(&idxBuff);
|
|
|
|
|
U32 curIdx = 0;
|
|
|
|
|
|
|
|
|
|
U32 batchOffset = 0;
|
|
|
|
|
|
|
|
|
|
// For each high lod batch, we must add indices to the buffer
|
|
|
|
|
// for each segment it contains ( and the count will depend on
|
|
|
|
|
// the division level columns/rows for each segment ).
|
|
|
|
|
|
|
|
|
|
// Temporaries for holding the indices of a quad
|
|
|
|
|
U32 p00, p01, p11, p10;
|
|
|
|
|
|
|
|
|
|
for ( U32 i = 0; i < mHighLODBatches.size(); i++ )
|
|
|
|
|
{
|
|
|
|
|
RiverRenderBatch &batch = mHighLODBatches[i];
|
|
|
|
|
|
|
|
|
|
batch.indexCount = 0;
|
|
|
|
|
batch.triangleCount = 0;
|
|
|
|
|
batch.startIndex = curIdx;
|
|
|
|
|
|
|
|
|
|
U32 temp = 0;
|
|
|
|
|
U32 segmentOffset = 0;
|
|
|
|
|
|
|
|
|
|
for ( U32 j = batch.startSegmentIdx; j <= batch.endSegmentIdx; j++ )
|
|
|
|
|
{
|
|
|
|
|
const RiverSegment &segment = mSegments[j];
|
|
|
|
|
|
|
|
|
|
// Loop through all divisions adding the indices to the
|
|
|
|
|
// high detail primitive buffer.
|
|
|
|
|
for ( U32 k = 0; k < segment.rows; k++ )
|
|
|
|
|
{
|
|
|
|
|
for ( U32 l = 0; l < segment.columns; l++ )
|
|
|
|
|
{
|
|
|
|
|
// The indices for this quad.
|
|
|
|
|
p00 = batchOffset + segmentOffset + l + k * ( segment.columns + 1 );
|
|
|
|
|
p01 = p00 + segment.columns + 1;
|
|
|
|
|
p11 = p01 + 1;
|
|
|
|
|
p10 = p00 + 1;
|
|
|
|
|
|
|
|
|
|
AssertFatal( p00 <= mHighTriangleCount * 3, "River, bad draw call!" );
|
|
|
|
|
AssertFatal( p01 <= mHighTriangleCount * 3, "River, bad draw call!" );
|
|
|
|
|
AssertFatal( p11 <= mHighTriangleCount * 3, "River, bad draw call!" );
|
|
|
|
|
AssertFatal( p10 <= mHighTriangleCount * 3, "River, bad draw call!" );
|
|
|
|
|
|
|
|
|
|
// Upper-Left triangle
|
|
|
|
|
idxBuff[curIdx] = p00;
|
|
|
|
|
curIdx++;
|
|
|
|
|
idxBuff[curIdx] = p01;
|
|
|
|
|
curIdx++;
|
|
|
|
|
idxBuff[curIdx] = p11;
|
|
|
|
|
curIdx++;
|
|
|
|
|
|
|
|
|
|
// Lower-Right Triangle
|
|
|
|
|
idxBuff[curIdx] = p00;
|
|
|
|
|
curIdx++;
|
|
|
|
|
idxBuff[curIdx] = p11;
|
|
|
|
|
curIdx++;
|
|
|
|
|
idxBuff[curIdx] = p10;
|
|
|
|
|
curIdx++;
|
|
|
|
|
|
|
|
|
|
batch.indexCount += 6;
|
|
|
|
|
batch.triangleCount += 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Increment the sliceOffset by the number of verts
|
|
|
|
|
// used by this segment. So the next segment will index
|
|
|
|
|
// into new verts.
|
|
|
|
|
segmentOffset += ( segment.columns + 1 ) * ( segment.rows + 1 );
|
|
|
|
|
temp += ( segment.columns + 1 ) * ( segment.rows + 1 );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
batchOffset += temp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unlock the PrimitiveBuffer, we are done filling it.
|
|
|
|
|
mPB_high.unlock();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
U32 River::_addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal )
|
|
|
|
|
{
|
|
|
|
|
mNodes.increment();
|
|
|
|
|
RiverNode &node = mNodes.last();
|
|
|
|
|
|
|
|
|
|
node.point = pos;
|
|
|
|
|
node.width = width;
|
|
|
|
|
node.depth = depth;
|
|
|
|
|
node.normal = normal;
|
|
|
|
|
|
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
|
|
|
|
|
|
|
|
return mNodes.size() - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
U32 River::_insertNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx )
|
|
|
|
|
{
|
|
|
|
|
U32 ret;
|
|
|
|
|
RiverNode *node;
|
|
|
|
|
|
|
|
|
|
if ( idx == U32_MAX )
|
|
|
|
|
{
|
|
|
|
|
mNodes.increment();
|
|
|
|
|
node = &mNodes.last();
|
|
|
|
|
ret = mNodes.size() - 1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
mNodes.insert( idx );
|
|
|
|
|
node = &mNodes[idx];
|
|
|
|
|
ret = idx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
node->point = pos;
|
|
|
|
|
node->depth = depth;
|
|
|
|
|
node->width = width;
|
|
|
|
|
node->normal = normal;
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setMetersPerSegment( F32 meters )
|
|
|
|
|
{
|
|
|
|
|
if ( meters < MIN_METERS_PER_SEGMENT )
|
|
|
|
|
{
|
|
|
|
|
Con::warnf( "River::setMetersPerSegment, specified meters (%g) is below the min meters (%g), NOT SET!", meters, MIN_METERS_PER_SEGMENT );
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mMetersPerSegment = meters;
|
|
|
|
|
_regenerate();
|
|
|
|
|
setMaskBits( RiverMask | RegenMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setBatchSize( U32 size )
|
|
|
|
|
{
|
|
|
|
|
// Not functional
|
|
|
|
|
//mSegmentsPerBatch = size;
|
|
|
|
|
//_regenerate();
|
|
|
|
|
//setMaskBits( RiverMask | RegenMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::regenerate()
|
|
|
|
|
{
|
|
|
|
|
_regenerate();
|
|
|
|
|
setMaskBits( RegenMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void River::setMaxDivisionSize( F32 meters )
|
|
|
|
|
{
|
|
|
|
|
if ( meters < mMinDivisionSize )
|
|
|
|
|
mMaxDivisionSize = mMinDivisionSize;
|
|
|
|
|
else
|
|
|
|
|
mMaxDivisionSize = meters;
|
|
|
|
|
|
|
|
|
|
_regenerate();
|
|
|
|
|
setMaskBits( RiverMask | RegenMask );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
// Console Methods
|
|
|
|
|
//-------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
DefineEngineMethod( River, regenerate, void, (),,
|
|
|
|
|
"Intended as a helper to developers and editor scripts.\n"
|
|
|
|
|
"Force River to recreate its geometry."
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
object->regenerate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DefineEngineMethod( River, setMetersPerSegment, void, ( F32 meters ),,
|
|
|
|
|
"Intended as a helper to developers and editor scripts.\n"
|
|
|
|
|
"@see SegmentLength field."
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
object->setMetersPerSegment( meters );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DefineEngineMethod( River, setBatchSize, void, ( F32 meters ),,
|
|
|
|
|
"Intended as a helper to developers and editor scripts.\n"
|
|
|
|
|
"BatchSize is not currently used."
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
object->setBatchSize( meters );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DefineEngineMethod( River, setNodeDepth, void, ( S32 idx, F32 meters ),,
|
|
|
|
|
"Intended as a helper to developers and editor scripts.\n"
|
|
|
|
|
"Sets the depth in meters of a particular node."
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
object->setNodeDepth( idx, meters );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DefineEngineMethod( River, setMaxDivisionSize, void, ( F32 meters ),,
|
|
|
|
|
"Intended as a helper to developers and editor scripts.\n"
|
|
|
|
|
"@see SubdivideLength field."
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
object->setMaxDivisionSize( meters );
|
|
|
|
|
}
|