mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-01-20 20:54:46 +00:00
3567 lines
99 KiB
C++
3567 lines
99 KiB
C++
//-----------------------------------------------------------------------------
|
|
// 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.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
|
|
// Copyright (C) 2015 Faust Logic, Inc.
|
|
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
|
|
|
|
#include "platform/platform.h"
|
|
#include "environment/meshRoad.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 "scene/sgUtil.h"
|
|
#include "renderInstance/renderPassManager.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 "materials/materialManager.h"
|
|
#include "math/mathIO.h"
|
|
#include "math/mathUtils.h"
|
|
#include "math/util/frustum.h"
|
|
#include "gui/3d/guiTSControl.h"
|
|
#include "materials/shaderData.h"
|
|
#include "gfx/sim/gfxStateBlockData.h"
|
|
#include "gfx/sim/debugDraw.h"
|
|
#include "collision/concretePolyList.h"
|
|
#include "T3D/physics/physicsPlugin.h"
|
|
#include "T3D/physics/physicsBody.h"
|
|
#include "T3D/physics/physicsCollision.h"
|
|
#include "environment/nodeListManager.h"
|
|
#ifdef TORQUE_AFX_ENABLED
|
|
#include "afx/ce/afxZodiacMgr.h"
|
|
#endif
|
|
|
|
#define MIN_METERS_PER_SEGMENT 1.0f
|
|
#define MIN_NODE_DEPTH 0.25f
|
|
#define MAX_NODE_DEPTH 50.0f
|
|
#define MIN_NODE_WIDTH 0.25f
|
|
#define MAX_NODE_WIDTH 50.0f
|
|
|
|
|
|
static U32 gIdxArray[6][2][3] = {
|
|
{ { 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
|
|
};
|
|
|
|
static S32 QSORT_CALLBACK compareHitSegments(const void* a,const void* b)
|
|
{
|
|
const MeshRoadHitSegment *fa = (MeshRoadHitSegment*)a;
|
|
const MeshRoadHitSegment *fb = (MeshRoadHitSegment*)b;
|
|
|
|
F32 diff = fb->t - fa->t;
|
|
|
|
return (diff > 0) ? 1 : (diff < 0) ? -1 : 0;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// MeshRoadNodeList Struct
|
|
//-----------------------------------------------------------------------------
|
|
|
|
struct MeshRoadNodeList : public NodeListManager::NodeList
|
|
{
|
|
Vector<Point3F> mPositions;
|
|
Vector<F32> mWidths;
|
|
Vector<F32> mDepths;
|
|
Vector<VectorF> mNormals;
|
|
|
|
MeshRoadNodeList() { }
|
|
virtual ~MeshRoadNodeList() { }
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// MeshRoadNodeEvent Class
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class MeshRoadNodeEvent : public NodeListEvent
|
|
{
|
|
typedef NodeListEvent Parent;
|
|
|
|
public:
|
|
Vector<Point3F> mPositions;
|
|
Vector<F32> mWidths;
|
|
Vector<F32> mDepths;
|
|
Vector<VectorF> mNormals;
|
|
|
|
public:
|
|
MeshRoadNodeEvent() { mNodeList = NULL; }
|
|
virtual ~MeshRoadNodeEvent() { }
|
|
|
|
virtual void pack(NetConnection*, BitStream*);
|
|
virtual void unpack(NetConnection*, BitStream*);
|
|
|
|
virtual void copyIntoList(NodeListManager::NodeList* copyInto);
|
|
virtual void padListToSize();
|
|
|
|
DECLARE_CONOBJECT(MeshRoadNodeEvent);
|
|
};
|
|
|
|
void MeshRoadNodeEvent::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 MeshRoadNodeEvent::unpack(NetConnection* conn, BitStream* stream)
|
|
{
|
|
mNodeList = new MeshRoadNodeList();
|
|
|
|
Parent::unpack( conn, stream );
|
|
|
|
U32 count = stream->readInt( 16 );
|
|
|
|
Point3F pos;
|
|
F32 width, depth;
|
|
VectorF normal;
|
|
|
|
MeshRoadNodeList* list = static_cast<MeshRoadNodeList*>(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 MeshRoadNodeEvent::copyIntoList(NodeListManager::NodeList* copyInto)
|
|
{
|
|
MeshRoadNodeList* prevList = dynamic_cast<MeshRoadNodeList*>(copyInto);
|
|
MeshRoadNodeList* list = static_cast<MeshRoadNodeList*>(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 MeshRoadNodeEvent::padListToSize()
|
|
{
|
|
MeshRoadNodeList* list = static_cast<MeshRoadNodeList*>(mNodeList);
|
|
|
|
U32 totalValidNodes = list->mTotalValidNodes;
|
|
|
|
// Pad our list front?
|
|
if (mLocalListStart)
|
|
{
|
|
MeshRoadNodeList* newlist = new MeshRoadNodeList();
|
|
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;
|
|
mNodeList = list = newlist;
|
|
}
|
|
|
|
// 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(MeshRoadNodeEvent);
|
|
|
|
ConsoleDocClass( MeshRoadNodeEvent,
|
|
"@brief Sends messages to the Mesh Road Editor\n\n"
|
|
"Editor use only.\n\n"
|
|
"@internal"
|
|
);
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// MeshRoadNodeListNotify Class
|
|
//-----------------------------------------------------------------------------
|
|
|
|
class MeshRoadNodeListNotify : public NodeListNotify
|
|
{
|
|
typedef NodeListNotify Parent;
|
|
|
|
protected:
|
|
SimObjectPtr<MeshRoad> mRoad;
|
|
|
|
public:
|
|
MeshRoadNodeListNotify( MeshRoad* road, U32 listId ) { mRoad = road; mListId = listId; }
|
|
virtual ~MeshRoadNodeListNotify() { mRoad = NULL; }
|
|
|
|
virtual void sendNotification( NodeListManager::NodeList* list );
|
|
};
|
|
|
|
void MeshRoadNodeListNotify::sendNotification( NodeListManager::NodeList* list )
|
|
{
|
|
if (mRoad.isValid())
|
|
{
|
|
// Build the road's nodes
|
|
MeshRoadNodeList* roadList = dynamic_cast<MeshRoadNodeList*>( list );
|
|
if (roadList)
|
|
mRoad->buildNodesFromList( roadList );
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// MeshRoadProfile Class
|
|
//-------------------------------------------------------------------------
|
|
|
|
MeshRoadProfile::MeshRoadProfile()
|
|
{
|
|
mRoad = NULL;
|
|
|
|
// Set transformation matrix to identity
|
|
mObjToSlice.identity();
|
|
mSliceToObj.identity();
|
|
}
|
|
|
|
S32 MeshRoadProfile::clickOnLine(Point3F &p)
|
|
{
|
|
Point3F newProfilePt;
|
|
Point3F ptOnSegment;
|
|
F32 dist = 0.0f;
|
|
F32 minDist = 99999.0f;
|
|
U32 idx = 0;
|
|
|
|
for(U32 i=0; i < mNodes.size()-1; i++)
|
|
{
|
|
ptOnSegment = MathUtils::mClosestPointOnSegment(mNodes[i].getPosition(), mNodes[i+1].getPosition(), p);
|
|
|
|
dist = (p - ptOnSegment).len();
|
|
|
|
if(dist < minDist)
|
|
{
|
|
minDist = dist;
|
|
newProfilePt = ptOnSegment;
|
|
idx = i+1;
|
|
}
|
|
}
|
|
|
|
if(minDist <= 0.1f)
|
|
{
|
|
p.set(newProfilePt.x, newProfilePt.y, newProfilePt.z);
|
|
|
|
return idx;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void MeshRoadProfile::addPoint(U32 nodeId, Point3F &p)
|
|
{
|
|
if(nodeId < mNodes.size() && nodeId != 0)
|
|
{
|
|
p.z = 0.0f;
|
|
mNodes.insert(nodeId, p);
|
|
mSegMtrls.insert(nodeId-1, mSegMtrls[nodeId-1]);
|
|
mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask);
|
|
generateNormals();
|
|
}
|
|
}
|
|
|
|
void MeshRoadProfile::removePoint(U32 nodeId)
|
|
{
|
|
if(nodeId > 0 && nodeId < mNodes.size()-1)
|
|
{
|
|
mNodes.erase(nodeId);
|
|
mSegMtrls.remove(nodeId-1);
|
|
mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask);
|
|
generateNormals();
|
|
}
|
|
}
|
|
|
|
void MeshRoadProfile::setNodePosition(U32 nodeId, Point3F pos)
|
|
{
|
|
if(nodeId < mNodes.size())
|
|
{
|
|
mNodes[nodeId].setPosition(pos.x, pos.y);
|
|
mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask);
|
|
generateNormals();
|
|
}
|
|
}
|
|
|
|
void MeshRoadProfile::toggleSmoothing(U32 nodeId)
|
|
{
|
|
if(nodeId > 0 && nodeId < mNodes.size()-1)
|
|
{
|
|
mNodes[nodeId].setSmoothing(!mNodes[nodeId].isSmooth());
|
|
mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask);
|
|
generateNormals();
|
|
}
|
|
}
|
|
|
|
void MeshRoadProfile::toggleSegMtrl(U32 seg)
|
|
{
|
|
if(seg < mSegMtrls.size())
|
|
{
|
|
switch(mSegMtrls[seg])
|
|
{
|
|
case MeshRoad::Side: mSegMtrls[seg] = MeshRoad::Top; break;
|
|
case MeshRoad::Top: mSegMtrls[seg] = MeshRoad::Bottom; break;
|
|
case MeshRoad::Bottom: mSegMtrls[seg] = MeshRoad::Side; break;
|
|
}
|
|
|
|
mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask);
|
|
}
|
|
}
|
|
|
|
void MeshRoadProfile::generateNormals()
|
|
{
|
|
VectorF t, b, n, t2, n2;
|
|
Point3F averagePt;
|
|
|
|
mNodeNormals.clear();
|
|
|
|
// Loop through all profile line segments
|
|
for(U32 i=0; i < mNodes.size()-1; i++)
|
|
{
|
|
// Calculate normal for each node in line segment
|
|
for(U32 j=0; j<2; j++)
|
|
{
|
|
// Smoothed Node: Average the node with nodes before and after.
|
|
// Direction between the node and the average is the smoothed normal.
|
|
if( mNodes[i+j].isSmooth() )
|
|
{
|
|
b = Point3F(0.0f, 0.0f, 1.0f);
|
|
t = mNodes[i+j-1].getPosition() - mNodes[i+j].getPosition();
|
|
n = mCross(t, b);
|
|
n.normalizeSafe();
|
|
|
|
t2 = mNodes[i+j].getPosition() - mNodes[i+j+1].getPosition();
|
|
n2 = mCross(t2, b);
|
|
n2.normalizeSafe();
|
|
|
|
n += n2;
|
|
}
|
|
// Non-smoothed Node: Normal is perpendicular to segment.
|
|
else
|
|
{
|
|
b = Point3F(0.0f, 0.0f, 1.0f);
|
|
t = mNodes[i].getPosition() - mNodes[i+1].getPosition();
|
|
n = mCross(t, b);
|
|
}
|
|
|
|
n.normalizeSafe();
|
|
mNodeNormals.push_back(n);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MeshRoadProfile::generateEndCap(F32 width)
|
|
{
|
|
Point3F pt;
|
|
|
|
mCap.newPoly();
|
|
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
{
|
|
pt = mNodes[i].getPosition();
|
|
mCap.addVert(pt);
|
|
}
|
|
|
|
for ( S32 i = mNodes.size()-1; i >= 0; i-- )
|
|
{
|
|
pt = mNodes[i].getPosition();
|
|
pt.x = -pt.x - width;
|
|
mCap.addVert(pt);
|
|
}
|
|
|
|
mCap.decompose();
|
|
}
|
|
|
|
void MeshRoadProfile::setProfileDepth(F32 depth)
|
|
{
|
|
Point3F curPos = mNodes[mNodes.size()-1].getPosition();
|
|
|
|
mNodes[mNodes.size()-1].setPosition(curPos.x, -depth);
|
|
}
|
|
|
|
void MeshRoadProfile::setTransform(const MatrixF &mat, const Point3F &p)
|
|
{
|
|
mObjToSlice.identity();
|
|
mSliceToObj.identity();
|
|
|
|
mObjToSlice *= mat;
|
|
mSliceToObj *= mObjToSlice.inverse();
|
|
mSliceToObj.transpose();
|
|
|
|
mStartPos = p;
|
|
}
|
|
|
|
void MeshRoadProfile::getNodeWorldPos(U32 nodeId, Point3F &p)
|
|
{
|
|
if(nodeId < mNodes.size())
|
|
{
|
|
p = mNodes[nodeId].getPosition();
|
|
mObjToSlice.mulP(p);
|
|
p += mStartPos;
|
|
}
|
|
}
|
|
|
|
void MeshRoadProfile::getNormToSlice(U32 normId, VectorF &n)
|
|
{
|
|
if(normId < mNodeNormals.size())
|
|
{
|
|
n = mNodeNormals[normId];
|
|
mObjToSlice.mulP(n);
|
|
}
|
|
}
|
|
|
|
void MeshRoadProfile::getNormWorldPos(U32 normId, Point3F &p)
|
|
{
|
|
if(normId < mNodeNormals.size())
|
|
{
|
|
U32 nodeId = normId/2 + (U32)(mFmod(normId,2.0f));
|
|
p = mNodes[nodeId].getPosition();
|
|
p += 0.5f * mNodeNormals[normId]; // Length = 0.5 units
|
|
mObjToSlice.mulP(p);
|
|
p += mStartPos;
|
|
}
|
|
}
|
|
|
|
void MeshRoadProfile::worldToObj(Point3F &p)
|
|
{
|
|
p -= mStartPos;
|
|
mSliceToObj.mulP(p);
|
|
p.z = 0.0f;
|
|
}
|
|
|
|
void MeshRoadProfile::objToWorld(Point3F &p)
|
|
{
|
|
mObjToSlice.mulP(p);
|
|
p += mStartPos;
|
|
}
|
|
|
|
F32 MeshRoadProfile::getProfileLen()
|
|
{
|
|
F32 sum = 0.0f;
|
|
Point3F segmentVec;
|
|
|
|
for(U32 i=0; i < mNodes.size()-1; i++)
|
|
{
|
|
segmentVec = mNodes[i+1].getPosition() - mNodes[i].getPosition();
|
|
sum += segmentVec.len();
|
|
}
|
|
|
|
return sum;
|
|
}
|
|
|
|
F32 MeshRoadProfile::getNodePosPercent(U32 nodeId)
|
|
{
|
|
nodeId = mFmod(nodeId, mNodes.size());
|
|
|
|
if(nodeId == 0)
|
|
return 0.0f;
|
|
else if(nodeId == mNodes.size()-1)
|
|
return 1.0f;
|
|
|
|
F32 totLen = getProfileLen();
|
|
F32 sum = 0.0f;
|
|
Point3F segmentVec;
|
|
|
|
for(U32 i=0; i < nodeId; i++)
|
|
{
|
|
segmentVec = mNodes[i+1].getPosition() - mNodes[i].getPosition();
|
|
sum += segmentVec.len();
|
|
}
|
|
|
|
return sum/totLen;
|
|
}
|
|
|
|
void MeshRoadProfile::resetProfile(F32 defaultDepth)
|
|
{
|
|
Point3F pos(0.0f, 0.0f, 0.0f);
|
|
|
|
mNodes.clear();
|
|
mNodes.push_back(pos);
|
|
|
|
pos.y = -defaultDepth;
|
|
mNodes.push_back(pos);
|
|
|
|
mSegMtrls.clear();
|
|
mSegMtrls.push_back(MeshRoad::Side);
|
|
|
|
mRoad->setMaskBits(MeshRoad::ProfileMask | MeshRoad::RegenMask);
|
|
generateNormals();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// MeshRoadConvex Class
|
|
//------------------------------------------------------------------------------
|
|
|
|
const MatrixF& MeshRoadConvex::getTransform() const
|
|
{
|
|
return MatrixF::Identity; //mObject->getTransform();
|
|
}
|
|
|
|
Box3F MeshRoadConvex::getBoundingBox() const
|
|
{
|
|
return box;
|
|
}
|
|
|
|
Box3F MeshRoadConvex::getBoundingBox(const MatrixF& mat, const Point3F& scale) const
|
|
{
|
|
Box3F newBox = box;
|
|
newBox.minExtents.convolve(scale);
|
|
newBox.maxExtents.convolve(scale);
|
|
mat.mul(newBox);
|
|
return newBox;
|
|
}
|
|
|
|
Point3F MeshRoadConvex::support(const VectorF& vec) const
|
|
{
|
|
F32 bestDot = mDot( verts[0], vec );
|
|
|
|
const Point3F *bestP = &verts[0];
|
|
for(S32 i=1; i<4; i++)
|
|
{
|
|
F32 newD = mDot(verts[i], vec);
|
|
if(newD > bestDot)
|
|
{
|
|
bestDot = newD;
|
|
bestP = &verts[i];
|
|
}
|
|
}
|
|
|
|
return *bestP;
|
|
}
|
|
|
|
void MeshRoadConvex::getFeatures(const MatrixF& mat, const VectorF& n, ConvexFeature* cf)
|
|
{
|
|
cf->material = 0;
|
|
cf->mObject = mObject;
|
|
|
|
// For a tetrahedron this is pretty easy... first
|
|
// convert everything into world space.
|
|
Point3F tverts[4];
|
|
mat.mulP(verts[0], &tverts[0]);
|
|
mat.mulP(verts[1], &tverts[1]);
|
|
mat.mulP(verts[2], &tverts[2]);
|
|
mat.mulP(verts[3], &tverts[3]);
|
|
|
|
// Points...
|
|
S32 firstVert = cf->mVertexList.size();
|
|
cf->mVertexList.increment(); cf->mVertexList.last() = tverts[0];
|
|
cf->mVertexList.increment(); cf->mVertexList.last() = tverts[1];
|
|
cf->mVertexList.increment(); cf->mVertexList.last() = tverts[2];
|
|
cf->mVertexList.increment(); cf->mVertexList.last() = tverts[3];
|
|
|
|
// Edges...
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert+0;
|
|
cf->mEdgeList.last().vertex[1] = firstVert+1;
|
|
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert+1;
|
|
cf->mEdgeList.last().vertex[1] = firstVert+2;
|
|
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert+2;
|
|
cf->mEdgeList.last().vertex[1] = firstVert+0;
|
|
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert+3;
|
|
cf->mEdgeList.last().vertex[1] = firstVert+0;
|
|
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert+3;
|
|
cf->mEdgeList.last().vertex[1] = firstVert+1;
|
|
|
|
cf->mEdgeList.increment();
|
|
cf->mEdgeList.last().vertex[0] = firstVert+3;
|
|
cf->mEdgeList.last().vertex[1] = firstVert+2;
|
|
|
|
// Triangles...
|
|
cf->mFaceList.increment();
|
|
cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[0]);
|
|
cf->mFaceList.last().vertex[0] = firstVert+2;
|
|
cf->mFaceList.last().vertex[1] = firstVert+1;
|
|
cf->mFaceList.last().vertex[2] = firstVert+0;
|
|
|
|
cf->mFaceList.increment();
|
|
cf->mFaceList.last().normal = PlaneF(tverts[1], tverts[0], tverts[3]);
|
|
cf->mFaceList.last().vertex[0] = firstVert+1;
|
|
cf->mFaceList.last().vertex[1] = firstVert+0;
|
|
cf->mFaceList.last().vertex[2] = firstVert+3;
|
|
|
|
cf->mFaceList.increment();
|
|
cf->mFaceList.last().normal = PlaneF(tverts[2], tverts[1], tverts[3]);
|
|
cf->mFaceList.last().vertex[0] = firstVert+2;
|
|
cf->mFaceList.last().vertex[1] = firstVert+1;
|
|
cf->mFaceList.last().vertex[2] = firstVert+3;
|
|
|
|
cf->mFaceList.increment();
|
|
cf->mFaceList.last().normal = PlaneF(tverts[0], tverts[2], tverts[3]);
|
|
cf->mFaceList.last().vertex[0] = firstVert+0;
|
|
cf->mFaceList.last().vertex[1] = firstVert+2;
|
|
cf->mFaceList.last().vertex[2] = firstVert+3;
|
|
}
|
|
|
|
|
|
void MeshRoadConvex::getPolyList( AbstractPolyList* list )
|
|
{
|
|
// Transform the list into object space and set the pointer to the object
|
|
//MatrixF i( mObject->getTransform() );
|
|
//Point3F iS( mObject->getScale() );
|
|
//list->setTransform(&i, iS);
|
|
|
|
list->setTransform( &MatrixF::Identity, Point3F::One );
|
|
list->setObject(mObject);
|
|
|
|
// Points...
|
|
S32 base = list->addPoint(verts[1]);
|
|
list->addPoint(verts[2]);
|
|
list->addPoint(verts[0]);
|
|
list->addPoint(verts[3]);
|
|
|
|
// Planes...
|
|
list->begin(0,0);
|
|
list->vertex(base + 2);
|
|
list->vertex(base + 1);
|
|
list->vertex(base + 0);
|
|
list->plane(base + 2, base + 1, base + 0);
|
|
list->end();
|
|
list->begin(0,0);
|
|
list->vertex(base + 2);
|
|
list->vertex(base + 1);
|
|
list->vertex(base + 3);
|
|
list->plane(base + 2, base + 1, base + 3);
|
|
list->end();
|
|
list->begin(0,0);
|
|
list->vertex(base + 3);
|
|
list->vertex(base + 1);
|
|
list->vertex(base + 0);
|
|
list->plane(base + 3, base + 1, base + 0);
|
|
list->end();
|
|
list->begin(0,0);
|
|
list->vertex(base + 2);
|
|
list->vertex(base + 3);
|
|
list->vertex(base + 0);
|
|
list->plane(base + 2, base + 3, base + 0);
|
|
list->end();
|
|
}
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
// MeshRoadSegment Class
|
|
//------------------------------------------------------------------------------
|
|
|
|
MeshRoadSegment::MeshRoadSegment()
|
|
{
|
|
mPlaneCount = 0;
|
|
columns = 0;
|
|
rows = 0;
|
|
numVerts = 0;
|
|
numTriangles = 0;
|
|
|
|
startVert = 0;
|
|
endVert = 0;
|
|
startIndex = 0;
|
|
endIndex = 0;
|
|
|
|
slice0 = NULL;
|
|
slice1 = NULL;
|
|
}
|
|
|
|
MeshRoadSegment::MeshRoadSegment( MeshRoadSlice *rs0, MeshRoadSlice *rs1, const MatrixF &roadMat )
|
|
{
|
|
columns = 0;
|
|
rows = 0;
|
|
numVerts = 0;
|
|
numTriangles = 0;
|
|
|
|
startVert = 0;
|
|
endVert = 0;
|
|
startIndex = 0;
|
|
endIndex = 0;
|
|
|
|
slice0 = rs0;
|
|
slice1 = rs1;
|
|
|
|
// Calculate the bounding box(s)
|
|
worldbounds.minExtents = worldbounds.maxExtents = rs0->p0;
|
|
|
|
for(U32 i=0; i < rs0->verts.size(); i++)
|
|
worldbounds.extend( rs0->verts[i] );
|
|
|
|
for(U32 i=0; i < rs1->verts.size(); i++)
|
|
worldbounds.extend( rs1->verts[i] );
|
|
|
|
objectbounds = worldbounds;
|
|
roadMat.mul( objectbounds );
|
|
|
|
// Calculate the planes for this segment
|
|
// Will be used for intersection/buoyancy tests
|
|
|
|
mPlaneCount = 6;
|
|
mPlanes[0].set( slice0->pb0, slice0->p0, slice1->p0 ); // left
|
|
mPlanes[1].set( slice1->pb2, slice1->p2, slice0->p2 ); // right
|
|
mPlanes[2].set( slice0->pb2, slice0->p2, slice0->p0 ); // near
|
|
mPlanes[3].set( slice1->p0, slice1->p2, slice1->pb2 ); // far
|
|
mPlanes[4].set( slice1->p2, slice1->p0, slice0->p0 ); // top
|
|
mPlanes[5].set( slice0->pb0, slice1->pb0, slice1->pb2 ); // bottom
|
|
}
|
|
|
|
void MeshRoadSegment::set( MeshRoadSlice *rs0, MeshRoadSlice *rs1 )
|
|
{
|
|
columns = 0;
|
|
rows = 0;
|
|
numVerts = 0;
|
|
numTriangles = 0;
|
|
|
|
startVert = 0;
|
|
endVert = 0;
|
|
startIndex = 0;
|
|
endIndex = 0;
|
|
|
|
slice0 = rs0;
|
|
slice1 = rs1;
|
|
}
|
|
|
|
bool MeshRoadSegment::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 MeshRoadSegment::containsPoint( const Point3F &pnt ) const
|
|
{
|
|
// 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.0f )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
F32 MeshRoadSegment::distanceToSurface(const Point3F &pnt) const
|
|
{
|
|
return mPlanes[4].distToPlane( pnt );
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// MeshRoad Class
|
|
//------------------------------------------------------------------------------
|
|
|
|
ConsoleDocClass( MeshRoad,
|
|
"@brief A strip of rectangular mesh segments defined by a 3D spline "
|
|
"for prototyping road-shaped objects in your scene.\n\n"
|
|
|
|
"User may control width and depth per node, overall spline shape in three "
|
|
"dimensions, and seperate Materials for rendering the top, bottom, and side surfaces.\n\n"
|
|
|
|
"MeshRoad is not capable of handling intersections, branches, curbs, or other "
|
|
"desirable features in a final 'road' asset and is therefore intended for "
|
|
"prototyping and experimentation.\n\n"
|
|
|
|
"Materials assigned to MeshRoad should tile vertically.\n\n"
|
|
|
|
"@ingroup Terrain"
|
|
);
|
|
|
|
bool MeshRoad::smEditorOpen = false;
|
|
bool MeshRoad::smShowBatches = false;
|
|
bool MeshRoad::smShowSpline = true;
|
|
bool MeshRoad::smShowRoad = true;
|
|
bool MeshRoad::smShowRoadProfile = false;
|
|
bool MeshRoad::smWireframe = true;
|
|
SimObjectPtr<SimSet> MeshRoad::smServerMeshRoadSet = NULL;
|
|
|
|
GFXStateBlockRef MeshRoad::smWireframeSB;
|
|
|
|
IMPLEMENT_CO_NETOBJECT_V1(MeshRoad);
|
|
|
|
MeshRoad::MeshRoad()
|
|
: mTextureLength( 5.0f ),
|
|
mBreakAngle( 3.0f ),
|
|
mWidthSubdivisions( 0 ),
|
|
mPhysicsRep( NULL )
|
|
{
|
|
mConvexList = new Convex;
|
|
|
|
// Setup NetObject.
|
|
mTypeMask |= StaticObjectType | StaticShapeObjectType;
|
|
mNetFlags.set(Ghostable);
|
|
|
|
mMatInst[Top] = NULL;
|
|
mMatInst[Bottom] = NULL;
|
|
mMatInst[Side] = NULL;
|
|
mTypeMask |= TerrainLikeObjectType;
|
|
for (U32 i = 0; i < SurfaceCount; i++)
|
|
{
|
|
mVertCount[i] = 0;
|
|
mTriangleCount[i] = 0;
|
|
}
|
|
|
|
INIT_ASSET(TopMaterial);
|
|
INIT_ASSET(BottomMaterial);
|
|
INIT_ASSET(SideMaterial);
|
|
|
|
mSideProfile.mRoad = this;
|
|
}
|
|
|
|
MeshRoad::~MeshRoad()
|
|
{
|
|
delete mConvexList;
|
|
mConvexList = NULL;
|
|
}
|
|
|
|
void MeshRoad::initPersistFields()
|
|
{
|
|
docsURL;
|
|
addGroup( "MeshRoad" );
|
|
|
|
INITPERSISTFIELD_MATERIALASSET(TopMaterial, MeshRoad, "Material for the upper surface of the road.");
|
|
INITPERSISTFIELD_MATERIALASSET(BottomMaterial, MeshRoad, "Material for the bottom surface of the road.");
|
|
INITPERSISTFIELD_MATERIALASSET(SideMaterial, MeshRoad, "Material for the side surface of the road.");
|
|
|
|
addField( "textureLength", TypeF32, Offset( mTextureLength, MeshRoad ),
|
|
"The length in meters of textures mapped to the MeshRoad." );
|
|
|
|
addField( "breakAngle", TypeF32, Offset( mBreakAngle, MeshRoad ),
|
|
"Angle in degrees - MeshRoad will subdivide the spline if its curve is greater than this threshold." );
|
|
|
|
addField( "widthSubdivisions", TypeS32, Offset( mWidthSubdivisions, MeshRoad ),
|
|
"Subdivide segments widthwise this many times when generating vertices." );
|
|
|
|
endGroup( "MeshRoad" );
|
|
|
|
addGroup( "Internal" );
|
|
|
|
addProtectedField( "Node", TypeString, 0, &addNodeFromField, &emptyStringProtectedGetFn,
|
|
"Do not modify, for internal use." );
|
|
|
|
addProtectedField( "ProfileNode", TypeString, 0, &addProfileNodeFromField, &emptyStringProtectedGetFn,
|
|
"Do not modify, for internal use." );
|
|
|
|
endGroup( "Internal" );
|
|
|
|
Parent::initPersistFields();
|
|
}
|
|
|
|
void MeshRoad::consoleInit()
|
|
{
|
|
Parent::consoleInit();
|
|
|
|
Con::addVariable( "$MeshRoad::EditorOpen", TypeBool, &MeshRoad::smEditorOpen, "True if the MeshRoad editor is open, otherwise false.\n"
|
|
"@ingroup Editors\n");
|
|
Con::addVariable( "$MeshRoad::wireframe", TypeBool, &MeshRoad::smWireframe, "If true, will render the wireframe of the road.\n"
|
|
"@ingroup Editors\n");
|
|
Con::addVariable( "$MeshRoad::showBatches", TypeBool, &MeshRoad::smShowBatches, "Determines if the debug rendering of the batches cubes is displayed or not.\n"
|
|
"@ingroup Editors\n");
|
|
Con::addVariable( "$MeshRoad::showSpline", TypeBool, &MeshRoad::smShowSpline, "If true, the spline on which the curvature of this road is based will be rendered.\n"
|
|
"@ingroup Editors\n");
|
|
Con::addVariable( "$MeshRoad::showRoad", TypeBool, &MeshRoad::smShowRoad, "If true, the road will be rendered. When in the editor, roads are always rendered regardless of this flag.\n"
|
|
"@ingroup Editors\n");
|
|
Con::addVariable( "$MeshRoad::showRoadProfile", TypeBool, &MeshRoad::smShowRoadProfile, "If true, the road profile will be shown in the editor.\n"
|
|
"@ingroup Editors\n");
|
|
}
|
|
|
|
bool MeshRoad::addNodeFromField( void *object, const char *index, const char *data )
|
|
{
|
|
MeshRoad *pObj = static_cast<MeshRoad*>(object);
|
|
|
|
//if ( !pObj->isProperlyAdded() )
|
|
//{
|
|
F32 width, depth;
|
|
Point3F pos, normal;
|
|
U32 result = dSscanf( data, "%g %g %g %g %g %g %g %g", &pos.x, &pos.y, &pos.z, &width, &depth, &normal.x, &normal.y, &normal.z );
|
|
if ( result == 8 )
|
|
pObj->_addNode( pos, width, depth, normal );
|
|
//}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MeshRoad::addProfileNodeFromField( void* obj, const char *index, const char* data )
|
|
{
|
|
MeshRoad *pObj = static_cast<MeshRoad*>(obj);
|
|
|
|
F32 x, y;
|
|
U32 smooth, mtrl;
|
|
|
|
U32 result = dSscanf( data, "%g %g %d %d", &x, &y, &smooth, &mtrl );
|
|
if ( result == 4 )
|
|
{
|
|
if(!pObj->mSideProfile.mNodes.empty())
|
|
pObj->mSideProfile.mSegMtrls.push_back(mtrl);
|
|
|
|
MeshRoadProfileNode node;
|
|
node.setPosition(x, y);
|
|
node.setSmoothing(smooth != 0);
|
|
pObj->mSideProfile.mNodes.push_back(node);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MeshRoad::onAdd()
|
|
{
|
|
if ( !Parent::onAdd() )
|
|
return false;
|
|
|
|
// Reset the World Box.
|
|
//setGlobalBounds();
|
|
resetWorldBox();
|
|
|
|
// Set the Render Transform.
|
|
setRenderTransform(mObjToWorld);
|
|
|
|
// Add to ServerMeshRoadSet
|
|
if ( isServerObject() )
|
|
{
|
|
getServerSet()->addObject( this );
|
|
}
|
|
|
|
if ( isClientObject() )
|
|
_initMaterial();
|
|
|
|
// If this road was not created from a file, give profile two default nodes
|
|
if(mSideProfile.mNodes.empty())
|
|
{
|
|
// Initialize with two nodes in vertical line with unit length
|
|
MeshRoadProfileNode node1(Point3F(0.0f, 0.0f, 0.0f));
|
|
MeshRoadProfileNode node2(Point3F(0.0f, -5.0f, 0.0f));
|
|
|
|
mSideProfile.mNodes.push_back(node1);
|
|
mSideProfile.mNodes.push_back(node2);
|
|
|
|
// Both node normals are straight to the right, perpendicular to the profile line
|
|
VectorF norm(1.0f, 0.0f, 0.0f);
|
|
|
|
mSideProfile.mNodeNormals.push_back(norm);
|
|
mSideProfile.mNodeNormals.push_back(norm);
|
|
|
|
mSideProfile.mSegMtrls.push_back(MeshRoad::Side);
|
|
}
|
|
else
|
|
mSideProfile.generateNormals();
|
|
|
|
// Generate the Vert/Index buffers and everything else.
|
|
_regenerate();
|
|
|
|
// Add to Scene.
|
|
addToScene();
|
|
|
|
return true;
|
|
}
|
|
|
|
void MeshRoad::onRemove()
|
|
{
|
|
SAFE_DELETE( mPhysicsRep );
|
|
|
|
mConvexList->nukeList();
|
|
|
|
for ( U32 i = 0; i < SurfaceCount; i++ )
|
|
{
|
|
SAFE_DELETE( mMatInst[i] );
|
|
}
|
|
|
|
removeFromScene();
|
|
Parent::onRemove();
|
|
}
|
|
|
|
void MeshRoad::inspectPostApply()
|
|
{
|
|
// Set Parent.
|
|
Parent::inspectPostApply();
|
|
|
|
//if ( mMetersPerSegment < MIN_METERS_PER_SEGMENT )
|
|
// mMetersPerSegment = MIN_METERS_PER_SEGMENT;
|
|
|
|
setMaskBits(MeshRoadMask);
|
|
}
|
|
|
|
void MeshRoad::onStaticModified( const char* slotName, const char*newValue )
|
|
{
|
|
Parent::onStaticModified( slotName, newValue );
|
|
|
|
if ( dStricmp( slotName, "breakAngle" ) == 0 )
|
|
{
|
|
setMaskBits( RegenMask );
|
|
}
|
|
}
|
|
|
|
void MeshRoad::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 MeshRoadNode &node = mNodes[i];
|
|
|
|
stream.writeTabs(tabStop);
|
|
|
|
char buffer[1024];
|
|
dMemset( buffer, 0, 1024 );
|
|
dSprintf( buffer, 1024, "Node = \"%g %g %g %g %g %g %g %g\";", 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 );
|
|
}
|
|
|
|
stream.write(2, "\r\n");
|
|
|
|
Point3F nodePos;
|
|
U8 smooth, mtrl;
|
|
|
|
for ( U32 i = 0; i < mSideProfile.mNodes.size(); i++ )
|
|
{
|
|
nodePos = mSideProfile.mNodes[i].getPosition();
|
|
|
|
if(i)
|
|
mtrl = mSideProfile.mSegMtrls[i-1];
|
|
else
|
|
mtrl = 0;
|
|
|
|
if(mSideProfile.mNodes[i].isSmooth())
|
|
smooth = 1;
|
|
else
|
|
smooth = 0;
|
|
|
|
stream.writeTabs(tabStop);
|
|
|
|
char buffer[1024];
|
|
dMemset( buffer, 0, 1024 );
|
|
dSprintf( buffer, 1024, "ProfileNode = \"%.6f %.6f %d %d\";", nodePos.x, nodePos.y, smooth, mtrl);
|
|
stream.writeLine( (const U8*)buffer );
|
|
}
|
|
}
|
|
|
|
bool MeshRoad::writeField( StringTableEntry fieldname, const char *value )
|
|
{
|
|
if ( fieldname == StringTable->insert("Node") )
|
|
return false;
|
|
|
|
if ( fieldname == StringTable->insert("ProfileNode") )
|
|
return false;
|
|
|
|
return Parent::writeField( fieldname, value );
|
|
}
|
|
|
|
void MeshRoad::onEditorEnable()
|
|
{
|
|
}
|
|
|
|
void MeshRoad::onEditorDisable()
|
|
{
|
|
}
|
|
|
|
SimSet* MeshRoad::getServerSet()
|
|
{
|
|
if ( !smServerMeshRoadSet )
|
|
{
|
|
smServerMeshRoadSet = new SimSet();
|
|
smServerMeshRoadSet->registerObject( "ServerMeshRoadSet" );
|
|
Sim::getRootGroup()->addObject( smServerMeshRoadSet );
|
|
}
|
|
|
|
return smServerMeshRoadSet;
|
|
}
|
|
|
|
void MeshRoad::prepRenderImage( SceneRenderState* state )
|
|
{
|
|
if ( mNodes.size() <= 1 )
|
|
return;
|
|
|
|
RenderPassManager *renderPass = state->getRenderPass();
|
|
|
|
// Normal Road RenderInstance
|
|
// Always rendered when the editor is not open
|
|
// otherwise obey the smShowRoad flag
|
|
if ( smShowRoad || !smEditorOpen )
|
|
{
|
|
#ifdef TORQUE_AFX_ENABLED
|
|
afxZodiacMgr::renderMeshRoadZodiacs(state, this);
|
|
#endif
|
|
MeshRenderInst coreRI;
|
|
coreRI.clear();
|
|
coreRI.objectToWorld = &MatrixF::Identity;
|
|
coreRI.worldToCamera = renderPass->allocSharedXform(RenderPassManager::View);
|
|
coreRI.projection = renderPass->allocSharedXform(RenderPassManager::Projection);
|
|
coreRI.type = RenderPassManager::RIT_Mesh;
|
|
|
|
BaseMatInstance *matInst;
|
|
for ( U32 i = 0; i < SurfaceCount; i++ )
|
|
{
|
|
matInst = state->getOverrideMaterial( mMatInst[i] );
|
|
if ( !matInst )
|
|
continue;
|
|
|
|
// Get the lights if we haven't already.
|
|
if ( matInst->isForwardLit() && !coreRI.lights[0] )
|
|
{
|
|
LightQuery query;
|
|
query.init( getWorldSphere() );
|
|
query.getLights( coreRI.lights, 8 );
|
|
}
|
|
|
|
MeshRenderInst *ri = renderPass->allocInst<MeshRenderInst>();
|
|
*ri = coreRI;
|
|
|
|
// Currently rendering whole road, fix to cull and batch
|
|
// per segment.
|
|
|
|
// Set the correct material for rendering.
|
|
ri->matInst = matInst;
|
|
ri->vertBuff = &mVB[i];
|
|
ri->primBuff = &mPB[i];
|
|
|
|
ri->prim = renderPass->allocPrim();
|
|
ri->prim->type = GFXTriangleList;
|
|
ri->prim->minIndex = 0;
|
|
ri->prim->startIndex = 0;
|
|
ri->prim->numPrimitives = mTriangleCount[i];
|
|
ri->prim->startVertex = 0;
|
|
ri->prim->numVertices = mVertCount[i];
|
|
|
|
// We sort by the material then vertex buffer.
|
|
ri->defaultKey = matInst->getStateHint();
|
|
ri->defaultKey2 = (uintptr_t)ri->vertBuff; // Not 64bit safe!
|
|
|
|
renderPass->addInst( ri );
|
|
}
|
|
}
|
|
|
|
// Debug RenderInstance
|
|
// Only when editor is open.
|
|
if ( smEditorOpen )
|
|
{
|
|
ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();
|
|
ri->renderDelegate.bind( this, &MeshRoad::_debugRender );
|
|
ri->type = RenderPassManager::RIT_Editor;
|
|
state->getRenderPass()->addInst( ri );
|
|
}
|
|
}
|
|
|
|
void MeshRoad::_initMaterial()
|
|
{
|
|
if (mTopMaterialAsset.notNull())
|
|
{
|
|
if (!mMatInst[Top] || !String(mTopMaterialAsset->getMaterialDefinitionName()).equal(mMatInst[Top]->getMaterial()->getName(), String::NoCase))
|
|
{
|
|
SAFE_DELETE(mMatInst[Top]);
|
|
|
|
Material* tMat = nullptr;
|
|
if (!Sim::findObject(mTopMaterialAsset->getMaterialDefinitionName(), tMat))
|
|
Con::errorf("MeshRoad::_initMaterial - Material %s was not found.", mTopMaterialAsset->getMaterialDefinitionName());
|
|
|
|
mMaterial[Top] = tMat;
|
|
|
|
if (mMaterial[Top])
|
|
mMatInst[Top] = mMaterial[Top]->createMatInstance();
|
|
else
|
|
mMatInst[Top] = MATMGR->createMatInstance("WarningMaterial");
|
|
|
|
mMatInst[Top]->init(MATMGR->getDefaultFeatures(), getGFXVertexFormat<GFXVertexPNTT>());
|
|
}
|
|
}
|
|
|
|
if (mBottomMaterialAsset.notNull())
|
|
{
|
|
if (!mMatInst[Bottom] || !String(mBottomMaterialAsset->getMaterialDefinitionName()).equal(mMatInst[Bottom]->getMaterial()->getName(), String::NoCase))
|
|
{
|
|
|
|
SAFE_DELETE(mMatInst[Bottom]);
|
|
|
|
Material* tMat = nullptr;
|
|
if (!Sim::findObject(mBottomMaterialAsset->getMaterialDefinitionName(), tMat))
|
|
Con::errorf("MeshRoad::_initMaterial - Material %s was not found.", mBottomMaterialAsset->getMaterialDefinitionName());
|
|
|
|
mMaterial[Bottom] = tMat;
|
|
|
|
if (mMaterial[Bottom])
|
|
mMatInst[Bottom] = mMaterial[Bottom]->createMatInstance();
|
|
else
|
|
mMatInst[Bottom] = MATMGR->createMatInstance("WarningMaterial");
|
|
|
|
mMatInst[Bottom]->init(MATMGR->getDefaultFeatures(), getGFXVertexFormat<GFXVertexPNTT>());
|
|
}
|
|
}
|
|
|
|
if (mSideMaterialAsset.notNull())
|
|
{
|
|
if (!mMatInst[Side] || !String(mSideMaterialAsset->getMaterialDefinitionName()).equal(mMatInst[Side]->getMaterial()->getName(), String::NoCase))
|
|
{
|
|
SAFE_DELETE(mMatInst[Side]);
|
|
|
|
Material* tMat = nullptr;
|
|
if (!Sim::findObject(mSideMaterialAsset->getMaterialDefinitionName(), tMat))
|
|
Con::errorf("MeshRoad::_initMaterial - Material %s was not found.", mSideMaterialAsset->getMaterialDefinitionName());
|
|
|
|
mMaterial[Side] = tMat;
|
|
|
|
if (mMaterial[Side])
|
|
mMatInst[Side] = mMaterial[Side]->createMatInstance();
|
|
else
|
|
mMatInst[Side] = MATMGR->createMatInstance("WarningMaterial");
|
|
|
|
mMatInst[Side]->init(MATMGR->getDefaultFeatures(), getGFXVertexFormat<GFXVertexPNTT>());
|
|
}
|
|
}
|
|
}
|
|
|
|
void MeshRoad::_debugRender( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance* )
|
|
{
|
|
//MeshRoadConvex convex;
|
|
//buildConvex( Box3F(true), convex );
|
|
//convex.render();
|
|
//GFXDrawUtil *drawer = GFX->getDrawUtil();
|
|
|
|
//GFX->setStateBlock( smStateBlock );
|
|
return;
|
|
/*
|
|
U32 convexCount = mDebugConvex.size();
|
|
|
|
PrimBuild::begin( GFXTriangleList, convexCount * 12 );
|
|
PrimBuild::color4i( 0, 0, 255, 155 );
|
|
|
|
for ( U32 i = 0; i < convexCount; i++ )
|
|
{
|
|
MeshRoadConvex *convex = mDebugConvex[i];
|
|
|
|
Point3F a = convex->verts[0];
|
|
Point3F b = convex->verts[1];
|
|
Point3F c = convex->verts[2];
|
|
Point3F p = convex->verts[3];
|
|
|
|
//mObjToWorld.mulP(a);
|
|
//mObjToWorld.mulP(b);
|
|
//mObjToWorld.mulP(c);
|
|
//mObjToWorld.mulP(p);
|
|
|
|
PrimBuild::vertex3fv( c );
|
|
PrimBuild::vertex3fv( b );
|
|
PrimBuild::vertex3fv( a );
|
|
|
|
PrimBuild::vertex3fv( b );
|
|
PrimBuild::vertex3fv( a );
|
|
PrimBuild::vertex3fv( p );
|
|
|
|
PrimBuild::vertex3fv( c );
|
|
PrimBuild::vertex3fv( b );
|
|
PrimBuild::vertex3fv( p );
|
|
|
|
PrimBuild::vertex3fv( a );
|
|
PrimBuild::vertex3fv( c );
|
|
PrimBuild::vertex3fv( p );
|
|
}
|
|
|
|
PrimBuild::end();
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
{
|
|
///GFX->getDrawUtil()->drawWireBox( mSegments[i].worldbounds, ColorI(255,0,0,255) );
|
|
}
|
|
|
|
GFX->enterDebugEvent( ColorI( 255, 0, 0 ), "DecalRoad_debugRender" );
|
|
GFXTransformSaver saver;
|
|
|
|
GFX->setStateBlock( smStateBlock );
|
|
|
|
Point3F size(1,1,1);
|
|
ColorI color( 255, 0, 0, 255 );
|
|
|
|
if ( smShowBatches )
|
|
{
|
|
for ( U32 i = 0; i < mBatches.size(); i++ )
|
|
{
|
|
const Box3F &box = mBatches[i].bounds;
|
|
Point3F center;
|
|
box.getCenter( ¢er );
|
|
|
|
GFX->getDrawUtil()->drawWireCube( ( box.maxExtents - box.minExtents ) * 0.5f, center, ColorI(255,100,100,255) );
|
|
}
|
|
}
|
|
|
|
GFX->leaveDebugEvent();
|
|
*/
|
|
}
|
|
|
|
U32 MeshRoad::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
|
|
{
|
|
U32 retMask = Parent::packUpdate(con, mask, stream);
|
|
|
|
if ( stream->writeFlag( mask & MeshRoadMask ) )
|
|
{
|
|
// Write Object Transform.
|
|
stream->writeAffineTransform( mObjToWorld );
|
|
|
|
// Write Materials
|
|
PACK_ASSET(con, TopMaterial);
|
|
PACK_ASSET(con, BottomMaterial);
|
|
PACK_ASSET(con, SideMaterial);
|
|
|
|
stream->write( mTextureLength );
|
|
stream->write( mBreakAngle );
|
|
stream->write( mWidthSubdivisions );
|
|
}
|
|
|
|
if ( stream->writeFlag( mask & ProfileMask ) )
|
|
{
|
|
stream->writeInt( mSideProfile.mNodes.size(), 16 );
|
|
|
|
for( U32 i = 0; i < mSideProfile.mNodes.size(); i++ )
|
|
{
|
|
mathWrite( *stream, mSideProfile.mNodes[i].getPosition() );
|
|
stream->writeFlag( mSideProfile.mNodes[i].isSmooth() );
|
|
|
|
if(i)
|
|
stream->writeInt(mSideProfile.mSegMtrls[i-1], 3);
|
|
else
|
|
stream->writeInt(0, 3);
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
MeshRoadNodeEvent* event = new MeshRoadNodeEvent();
|
|
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 );
|
|
}
|
|
}
|
|
|
|
stream->writeFlag( mask & RegenMask );
|
|
|
|
// Were done ...
|
|
return retMask;
|
|
}
|
|
|
|
void MeshRoad::unpackUpdate(NetConnection * con, BitStream * stream)
|
|
{
|
|
// Unpack Parent.
|
|
Parent::unpackUpdate(con, stream);
|
|
|
|
// MeshRoadMask
|
|
if(stream->readFlag())
|
|
{
|
|
MatrixF ObjectMatrix;
|
|
stream->readAffineTransform(&ObjectMatrix);
|
|
Parent::setTransform(ObjectMatrix);
|
|
|
|
UNPACK_ASSET(con, TopMaterial);
|
|
UNPACK_ASSET(con, BottomMaterial);
|
|
UNPACK_ASSET(con, SideMaterial);
|
|
|
|
if ( isProperlyAdded() )
|
|
_initMaterial();
|
|
|
|
stream->read( &mTextureLength );
|
|
|
|
stream->read( &mBreakAngle );
|
|
|
|
stream->read( &mWidthSubdivisions );
|
|
}
|
|
|
|
// ProfileMask
|
|
if(stream->readFlag())
|
|
{
|
|
Point3F pos;
|
|
|
|
mSideProfile.mNodes.clear();
|
|
mSideProfile.mSegMtrls.clear();
|
|
|
|
U32 count = stream->readInt( 16 );
|
|
|
|
for( U32 i = 0; i < count; i++)
|
|
{
|
|
mathRead( *stream, &pos );
|
|
MeshRoadProfileNode node(pos);
|
|
node.setSmoothing( stream->readFlag() );
|
|
mSideProfile.mNodes.push_back(node);
|
|
|
|
if(i)
|
|
mSideProfile.mSegMtrls.push_back(stream->readInt(3));
|
|
else
|
|
stream->readInt(3);
|
|
}
|
|
|
|
mSideProfile.generateNormals();
|
|
}
|
|
|
|
// NodeMask
|
|
if ( stream->readFlag() )
|
|
{
|
|
if (stream->readFlag())
|
|
{
|
|
// Nodes have been passed in this update
|
|
U32 count = stream->readInt( 16 );
|
|
|
|
mNodes.clear();
|
|
|
|
Point3F pos, 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
|
|
MeshRoadNodeList* roadList = dynamic_cast<MeshRoadNodeList*>( list );
|
|
if (roadList)
|
|
buildNodesFromList( roadList );
|
|
|
|
delete list;
|
|
}
|
|
else
|
|
{
|
|
// Nodes have not yet arrived, so register our interest in the list
|
|
MeshRoadNodeListNotify* notify = new MeshRoadNodeListNotify( this, id );
|
|
gClientNodeListManager->registerNotification( notify );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( stream->readFlag() && isProperlyAdded() )
|
|
_regenerate();
|
|
}
|
|
|
|
void MeshRoad::setTransform( const MatrixF &mat )
|
|
{
|
|
for ( U32 i = 0; i < mNodes.size(); i++ )
|
|
{
|
|
mWorldToObj.mulP( mNodes[i].point );
|
|
mat.mulP( mNodes[i].point );
|
|
}
|
|
|
|
Parent::setTransform( mat );
|
|
|
|
if ( mPhysicsRep )
|
|
mPhysicsRep->setTransform( mat );
|
|
|
|
// Regenerate and update the client
|
|
_regenerate();
|
|
setMaskBits( NodeMask | RegenMask );
|
|
}
|
|
|
|
void MeshRoad::setScale( const VectorF &scale )
|
|
{
|
|
// We ignore scale requests from the editor
|
|
// right now.
|
|
|
|
//Parent::setScale( scale );
|
|
}
|
|
|
|
void MeshRoad::buildConvex(const Box3F& box, Convex* convex)
|
|
{
|
|
if ( mSlices.size() < 2 )
|
|
return;
|
|
|
|
mConvexList->collectGarbage();
|
|
mDebugConvex.clear();
|
|
|
|
Box3F realBox = box;
|
|
mWorldToObj.mul(realBox);
|
|
realBox.minExtents.convolveInverse(mObjScale);
|
|
realBox.maxExtents.convolveInverse(mObjScale);
|
|
|
|
if (realBox.isOverlapped(getObjBox()) == false)
|
|
return;
|
|
|
|
U32 segmentCount = mSegments.size();
|
|
|
|
U32 numConvexes ;
|
|
U32 halfConvexes;
|
|
U32 nextSegOffset = 2*mSideProfile.mNodes.size();
|
|
U32 leftSideOffset = nextSegOffset/2;
|
|
U32 k2, capIdx1, capIdx2, capIdx3;
|
|
|
|
// Create convex(s) for each segment
|
|
for ( U32 i = 0; i < segmentCount; i++ )
|
|
{
|
|
const MeshRoadSegment &segment = mSegments[i];
|
|
|
|
// Is this segment overlapped?
|
|
if ( !segment.getWorldBounds().isOverlapped( box ) )
|
|
continue;
|
|
|
|
// Each segment has 6 faces
|
|
for ( U32 j = 0; j < 6; j++ )
|
|
{
|
|
// Only first segment has front face
|
|
if ( j == 4 && i != 0 )
|
|
continue;
|
|
// Only last segment has back face
|
|
if ( j == 5 && i != segmentCount-1 )
|
|
continue;
|
|
|
|
// The top and bottom sides have 2 convex(s)
|
|
// The left, right, front, and back sides depend on the user-defined profile
|
|
switch(j)
|
|
{
|
|
case 0: numConvexes = 2; break; // Top
|
|
case 1: // Left
|
|
case 2: numConvexes = 2* (mSideProfile.mNodes.size()-1); break; // Right
|
|
case 3: numConvexes = 2; break; // Bottom
|
|
case 4: // Front
|
|
case 5: numConvexes = mSideProfile.mCap.getNumTris(); break; // Back
|
|
default: numConvexes = 0;
|
|
}
|
|
|
|
halfConvexes = numConvexes/2;
|
|
|
|
for ( U32 k = 0; k < numConvexes; k++ )
|
|
{
|
|
// See if this convex exists in the working set already...
|
|
Convex* cc = 0;
|
|
CollisionWorkingList& wl = convex->getWorkingList();
|
|
for ( CollisionWorkingList* itr = wl.wLink.mNext; itr != &wl; itr = itr->wLink.mNext )
|
|
{
|
|
if ( itr->mConvex->getType() == MeshRoadConvexType )
|
|
{
|
|
MeshRoadConvex *pConvex = static_cast<MeshRoadConvex*>(itr->mConvex);
|
|
|
|
if ( pConvex->pRoad == this &&
|
|
pConvex->segmentId == i &&
|
|
pConvex->faceId == j &&
|
|
pConvex->triangleId == k )
|
|
{
|
|
cc = itr->mConvex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (cc)
|
|
continue;
|
|
|
|
Point3F a, b, c;
|
|
|
|
// Top or Bottom
|
|
if(j == 0 || j == 3)
|
|
{
|
|
// Get the triangle...
|
|
U32 idx0 = gIdxArray[j][k][0];
|
|
U32 idx1 = gIdxArray[j][k][1];
|
|
U32 idx2 = gIdxArray[j][k][2];
|
|
|
|
a = segment[idx0];
|
|
b = segment[idx1];
|
|
c = segment[idx2];
|
|
}
|
|
// Left Side
|
|
else if(j == 1)
|
|
{
|
|
if(k >= halfConvexes)
|
|
{
|
|
k2 = k + leftSideOffset - halfConvexes;
|
|
a = segment.slice1->verts[k2];
|
|
b = segment.slice0->verts[k2];
|
|
c = segment.slice1->verts[k2 + 1];
|
|
}
|
|
else
|
|
{
|
|
k2 = k + leftSideOffset;
|
|
a = segment.slice0->verts[k2];
|
|
b = segment.slice0->verts[k2 + 1];
|
|
c = segment.slice1->verts[k2 + 1];
|
|
}
|
|
}
|
|
// Right Side
|
|
else if(j == 2)
|
|
{
|
|
// a.set(2*k, 2*k, 0.0f);
|
|
// b.set(2*k, 2*k, 2.0f);
|
|
// c.set(2*(k+1), 2*(k+1), 0.0f);
|
|
|
|
if(k >= halfConvexes)
|
|
{
|
|
k2 = k - halfConvexes;
|
|
a = segment.slice1->verts[k2];
|
|
b = segment.slice1->verts[k2 + 1];
|
|
c = segment.slice0->verts[k2];
|
|
}
|
|
else
|
|
{
|
|
a = segment.slice0->verts[k];
|
|
b = segment.slice1->verts[k + 1];
|
|
c = segment.slice0->verts[k + 1];
|
|
}
|
|
}
|
|
// Front
|
|
else if(j == 4)
|
|
{
|
|
k2 = nextSegOffset + leftSideOffset - 1;
|
|
|
|
capIdx1 = mSideProfile.mCap.getTriIdx(k, 0);
|
|
capIdx2 = mSideProfile.mCap.getTriIdx(k, 1);
|
|
capIdx3 = mSideProfile.mCap.getTriIdx(k, 2);
|
|
|
|
if(capIdx1 >= leftSideOffset)
|
|
capIdx1 = k2 - capIdx1;
|
|
|
|
if(capIdx2 >= leftSideOffset)
|
|
capIdx2 = k2 - capIdx2;
|
|
|
|
if(capIdx3 >= leftSideOffset)
|
|
capIdx3 = k2 - capIdx3;
|
|
|
|
a = segment.slice0->verts[capIdx1];
|
|
b = segment.slice0->verts[capIdx2];
|
|
c = segment.slice0->verts[capIdx3];
|
|
}
|
|
// Back
|
|
else
|
|
{
|
|
k2 = nextSegOffset + leftSideOffset - 1;
|
|
|
|
capIdx1 = mSideProfile.mCap.getTriIdx(k, 0);
|
|
capIdx2 = mSideProfile.mCap.getTriIdx(k, 1);
|
|
capIdx3 = mSideProfile.mCap.getTriIdx(k, 2);
|
|
|
|
if(capIdx1 >= leftSideOffset)
|
|
capIdx1 = k2 - capIdx1;
|
|
|
|
if(capIdx2 >= leftSideOffset)
|
|
capIdx2 = k2 - capIdx2;
|
|
|
|
if(capIdx3 >= leftSideOffset)
|
|
capIdx3 = k2 - capIdx3;
|
|
|
|
a = segment.slice1->verts[capIdx3];
|
|
b = segment.slice1->verts[capIdx2];
|
|
c = segment.slice1->verts[capIdx1];
|
|
}
|
|
|
|
// Transform the result into object space!
|
|
//mWorldToObj.mulP( a );
|
|
//mWorldToObj.mulP( b );
|
|
//mWorldToObj.mulP( c );
|
|
|
|
PlaneF p( c, b, a );
|
|
Point3F peak = ((a + b + c) / 3.0f) + (p * 0.15f);
|
|
|
|
// Set up the convex...
|
|
MeshRoadConvex *cp = new MeshRoadConvex();
|
|
|
|
mConvexList->registerObject( cp );
|
|
convex->addToWorkingList( cp );
|
|
|
|
cp->mObject = this;
|
|
cp->pRoad = this;
|
|
cp->segmentId = i;
|
|
cp->faceId = j;
|
|
cp->triangleId = k;
|
|
|
|
cp->normal = p;
|
|
cp->verts[0] = c;
|
|
cp->verts[1] = b;
|
|
cp->verts[2] = a;
|
|
cp->verts[3] = peak;
|
|
|
|
// Update the bounding box.
|
|
Box3F &bounds = cp->box;
|
|
bounds.minExtents.set( F32_MAX, F32_MAX, F32_MAX );
|
|
bounds.maxExtents.set( -F32_MAX, -F32_MAX, -F32_MAX );
|
|
|
|
bounds.minExtents.setMin( a );
|
|
bounds.minExtents.setMin( b );
|
|
bounds.minExtents.setMin( c );
|
|
bounds.minExtents.setMin( peak );
|
|
|
|
bounds.maxExtents.setMax( a );
|
|
bounds.maxExtents.setMax( b );
|
|
bounds.maxExtents.setMax( c );
|
|
bounds.maxExtents.setMax( peak );
|
|
|
|
mDebugConvex.push_back(cp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool MeshRoad::buildPolyList( PolyListContext, AbstractPolyList* polyList, const Box3F &box, const SphereF & )
|
|
{
|
|
if ( mSlices.size() < 2 )
|
|
return false;
|
|
|
|
polyList->setTransform( &MatrixF::Identity, Point3F::One );
|
|
polyList->setObject(this);
|
|
|
|
// JCF: optimize this to not always add everything.
|
|
|
|
return buildSegmentPolyList( polyList, 0, mSegments.size() - 1, true, true );
|
|
}
|
|
|
|
bool MeshRoad::buildSegmentPolyList( AbstractPolyList* polyList, U32 startSegIdx, U32 endSegIdx, bool capFront, bool capEnd )
|
|
{
|
|
if ( mSlices.size() < 2 )
|
|
return false;
|
|
|
|
// Add verts
|
|
for ( U32 i = startSegIdx; i <= endSegIdx; i++ )
|
|
{
|
|
const MeshRoadSegment &seg = mSegments[i];
|
|
|
|
if ( i == startSegIdx )
|
|
{
|
|
for(U32 j = 0; j < seg.slice0->verts.size(); j++)
|
|
polyList->addPoint( seg.slice0->verts[j] );
|
|
}
|
|
|
|
for(U32 j = 0; j < seg.slice1->verts.size(); j++)
|
|
polyList->addPoint( seg.slice1->verts[j] );
|
|
}
|
|
|
|
// Temporaries to hold indices for the corner points of a quad.
|
|
S32 p00, p01, p11, p10;
|
|
S32 pb00, pb01, pb11, pb10;
|
|
U32 offset = 0;
|
|
S32 a, b, c;
|
|
U32 mirror;
|
|
|
|
DebugDrawer *ddraw = NULL;//DebugDrawer::get();
|
|
ClippedPolyList *cpolyList = dynamic_cast<ClippedPolyList*>(polyList);
|
|
MatrixF mat;
|
|
Point3F scale;
|
|
if ( cpolyList )
|
|
cpolyList->getTransform( &mat, &scale );
|
|
|
|
U32 nextSegOffset = 2*mSideProfile.mNodes.size();
|
|
U32 leftSideOffset = nextSegOffset/2;
|
|
|
|
for ( U32 i = startSegIdx; i <= endSegIdx; i++ )
|
|
{
|
|
p00 = offset + leftSideOffset;
|
|
p10 = offset;
|
|
pb00 = offset + nextSegOffset - 1;
|
|
pb10 = offset + leftSideOffset - 1;
|
|
p01 = offset + nextSegOffset + leftSideOffset;
|
|
p11 = offset + nextSegOffset;
|
|
pb01 = offset + 2*nextSegOffset - 1;
|
|
pb11 = offset + nextSegOffset + leftSideOffset - 1;
|
|
|
|
// Top Face
|
|
|
|
polyList->begin( 0,0 );
|
|
polyList->vertex( p00 );
|
|
polyList->vertex( p01 );
|
|
polyList->vertex( p11 );
|
|
polyList->plane( p00, p01, p11 );
|
|
polyList->end();
|
|
|
|
if ( ddraw && cpolyList )
|
|
{
|
|
Point3F v0 = cpolyList->mVertexList[p00].point;
|
|
mat.mulP( v0 );
|
|
Point3F v1 = cpolyList->mVertexList[p01].point;
|
|
mat.mulP( v1 );
|
|
Point3F v2 = cpolyList->mVertexList[p11].point;
|
|
mat.mulP( v2 );
|
|
ddraw->drawTri( v0, v1, v2 );
|
|
ddraw->setLastZTest( false );
|
|
ddraw->setLastTTL( 0 );
|
|
}
|
|
|
|
polyList->begin( 0,0 );
|
|
polyList->vertex( p00 );
|
|
polyList->vertex( p11 );
|
|
polyList->vertex( p10 );
|
|
polyList->plane( p00, p11, p10 );
|
|
polyList->end();
|
|
if ( ddraw && cpolyList )
|
|
{
|
|
ddraw->drawTri( cpolyList->mVertexList[p00].point, cpolyList->mVertexList[p11].point, cpolyList->mVertexList[p10].point );
|
|
ddraw->setLastTTL( 0 );
|
|
}
|
|
|
|
if (buildPolyList_TopSurfaceOnly)
|
|
{
|
|
offset += 4;
|
|
continue;
|
|
}
|
|
// Left Face
|
|
for(U32 j = leftSideOffset; j < nextSegOffset-1; j++)
|
|
{
|
|
a = offset + j;
|
|
b = a + nextSegOffset + 1;
|
|
c = b - 1;
|
|
|
|
polyList->begin( 0,0 );
|
|
polyList->vertex( a );
|
|
polyList->vertex( b );
|
|
polyList->vertex( c);
|
|
polyList->plane( a, b, c );
|
|
polyList->end();
|
|
|
|
a = offset + j;
|
|
b = a + 1;
|
|
c = a + nextSegOffset + 1;
|
|
|
|
polyList->begin( 0,0 );
|
|
polyList->vertex( a );
|
|
polyList->vertex( b );
|
|
polyList->vertex( c );
|
|
polyList->plane( a, b, c );
|
|
polyList->end();
|
|
}
|
|
|
|
// Right Face
|
|
for(U32 j = 0; j < leftSideOffset-1; j++)
|
|
{
|
|
a = offset + j;
|
|
b = a + nextSegOffset;
|
|
c = b + 1;
|
|
|
|
polyList->begin( 0,0 );
|
|
polyList->vertex( a );
|
|
polyList->vertex( b );
|
|
polyList->vertex( c);
|
|
polyList->plane( a, b, c );
|
|
polyList->end();
|
|
|
|
a = offset + j;
|
|
b = a + nextSegOffset + 1;
|
|
c = a + 1;
|
|
|
|
polyList->begin( 0,0 );
|
|
polyList->vertex( a );
|
|
polyList->vertex( b );
|
|
polyList->vertex( c );
|
|
polyList->plane( a, b, c );
|
|
polyList->end();
|
|
}
|
|
|
|
// Bottom Face
|
|
|
|
polyList->begin( 0,0 );
|
|
polyList->vertex( pb00 );
|
|
polyList->vertex( pb10 );
|
|
polyList->vertex( pb11 );
|
|
polyList->plane( pb00, pb10, pb11 );
|
|
polyList->end();
|
|
|
|
polyList->begin( 0,0 );
|
|
polyList->vertex( pb00 );
|
|
polyList->vertex( pb11 );
|
|
polyList->vertex( pb01 );
|
|
polyList->plane( pb00, pb11, pb01 );
|
|
polyList->end();
|
|
|
|
// Front Face
|
|
|
|
if ( i == startSegIdx && capFront )
|
|
{
|
|
mirror = nextSegOffset + leftSideOffset - 1;
|
|
|
|
for(U32 j = 0; j < mSideProfile.mCap.getNumTris(); j++)
|
|
{
|
|
a = mSideProfile.mCap.getTriIdx(j, 0);
|
|
b = mSideProfile.mCap.getTriIdx(j, 1);
|
|
c = mSideProfile.mCap.getTriIdx(j, 2);
|
|
|
|
if(a >= leftSideOffset)
|
|
a = mirror - a;
|
|
|
|
if(b >= leftSideOffset)
|
|
b = mirror - b;
|
|
|
|
if(c >= leftSideOffset)
|
|
c = mirror - c;
|
|
|
|
polyList->begin( 0,0 );
|
|
polyList->vertex( a );
|
|
polyList->vertex( b );
|
|
polyList->vertex( c );
|
|
polyList->plane( a, b, c );
|
|
polyList->end();
|
|
}
|
|
}
|
|
|
|
// Back Face
|
|
if ( i == endSegIdx && capEnd )
|
|
{
|
|
mirror = nextSegOffset + leftSideOffset - 1;
|
|
|
|
for(U32 j = 0; j < mSideProfile.mCap.getNumTris(); j++)
|
|
{
|
|
a = mSideProfile.mCap.getTriIdx(j, 0);
|
|
b = mSideProfile.mCap.getTriIdx(j, 1);
|
|
c = mSideProfile.mCap.getTriIdx(j, 2);
|
|
|
|
if(a >= leftSideOffset)
|
|
a = offset + nextSegOffset + mirror - a;
|
|
|
|
if(b >= leftSideOffset)
|
|
b = offset + nextSegOffset + mirror - b;
|
|
|
|
if(c >= leftSideOffset)
|
|
c = offset + nextSegOffset + mirror - c;
|
|
|
|
polyList->begin( 0,0 );
|
|
polyList->vertex( c );
|
|
polyList->vertex( b );
|
|
polyList->vertex( a );
|
|
polyList->plane( c, b, a );
|
|
polyList->end();
|
|
}
|
|
}
|
|
|
|
offset += nextSegOffset;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MeshRoad::castRay( const Point3F &s, const Point3F &e, RayInfo *info )
|
|
{
|
|
Point3F start = s;
|
|
Point3F end = e;
|
|
mObjToWorld.mulP(start);
|
|
mObjToWorld.mulP(end);
|
|
|
|
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
|
|
|
|
Vector<MeshRoadHitSegment> hitSegments;
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
{
|
|
const MeshRoadSegment &segment = mSegments[i];
|
|
|
|
F32 t;
|
|
VectorF n;
|
|
|
|
if ( segment.getWorldBounds().collideLine( start, end, &t, &n ) )
|
|
{
|
|
hitSegments.increment();
|
|
hitSegments.last().t = t;
|
|
hitSegments.last().idx = i;
|
|
}
|
|
}
|
|
|
|
dQsort( hitSegments.address(), hitSegments.size(), sizeof(MeshRoadHitSegment), compareHitSegments );
|
|
|
|
U32 idx0, idx1, idx2;
|
|
F32 t;
|
|
|
|
for ( U32 i = 0; i < hitSegments.size(); i++ )
|
|
{
|
|
U32 segIdx = hitSegments[i].idx;
|
|
const MeshRoadSegment &segment = mSegments[segIdx];
|
|
|
|
U32 numConvexes ;
|
|
U32 halfConvexes;
|
|
U32 nextSegOffset = 2*mSideProfile.mNodes.size();
|
|
U32 leftSideOffset = nextSegOffset/2;
|
|
U32 k2, capIdx1, capIdx2, capIdx3;
|
|
|
|
// Each segment has 6 faces
|
|
for ( U32 j = 0; j < 6; j++ )
|
|
{
|
|
if ( j == 4 && segIdx != 0 )
|
|
continue;
|
|
|
|
if ( j == 5 && segIdx != mSegments.size() - 1 )
|
|
continue;
|
|
|
|
// The top and bottom sides have 2 convex(s)
|
|
// The left, right, front, and back sides depend on the user-defined profile
|
|
switch(j)
|
|
{
|
|
case 0: numConvexes = 2; break; // Top
|
|
case 1: // Left
|
|
case 2: numConvexes = 2* (mSideProfile.mNodes.size()-1); break; // Right
|
|
case 3: numConvexes = 2; break; // Bottom
|
|
case 4: // Front
|
|
case 5: numConvexes = mSideProfile.mCap.getNumTris(); break; // Back
|
|
default: numConvexes = 0;
|
|
}
|
|
|
|
halfConvexes = numConvexes/2;
|
|
|
|
// Each face has 2 triangles
|
|
for ( U32 k = 0; k < numConvexes; k++ )
|
|
{
|
|
const Point3F *a = NULL;
|
|
const Point3F *b = NULL;
|
|
const Point3F *c = NULL;
|
|
|
|
// Top or Bottom
|
|
if(j == 0 || j == 3)
|
|
{
|
|
idx0 = gIdxArray[j][k][0];
|
|
idx1 = gIdxArray[j][k][1];
|
|
idx2 = gIdxArray[j][k][2];
|
|
|
|
a = &segment[idx0];
|
|
b = &segment[idx1];
|
|
c = &segment[idx2];
|
|
}
|
|
// Left Side
|
|
else if(j == 1)
|
|
{
|
|
if(k >= halfConvexes)
|
|
{
|
|
k2 = k + leftSideOffset - halfConvexes;
|
|
a = &segment.slice1->verts[k2];
|
|
b = &segment.slice0->verts[k2];
|
|
c = &segment.slice1->verts[k2 + 1];
|
|
}
|
|
else
|
|
{
|
|
k2 = k + leftSideOffset;
|
|
a = &segment.slice0->verts[k2];
|
|
b = &segment.slice0->verts[k2 + 1];
|
|
c = &segment.slice1->verts[k2 + 1];
|
|
}
|
|
}
|
|
// Right Side
|
|
else if(j == 2)
|
|
{
|
|
if(k >= halfConvexes)
|
|
{
|
|
k2 = k - halfConvexes;
|
|
a = &segment.slice1->verts[k2];
|
|
b = &segment.slice1->verts[k2 + 1];
|
|
c = &segment.slice0->verts[k2];
|
|
}
|
|
else
|
|
{
|
|
a = &segment.slice0->verts[k];
|
|
b = &segment.slice1->verts[k + 1];
|
|
c = &segment.slice0->verts[k + 1];
|
|
}
|
|
}
|
|
// Front
|
|
else if(j == 4)
|
|
{
|
|
k2 = nextSegOffset + leftSideOffset - 1;
|
|
|
|
capIdx1 = mSideProfile.mCap.getTriIdx(k, 0);
|
|
capIdx2 = mSideProfile.mCap.getTriIdx(k, 1);
|
|
capIdx3 = mSideProfile.mCap.getTriIdx(k, 2);
|
|
|
|
if(capIdx1 >= leftSideOffset)
|
|
capIdx1 = k2 - capIdx1;
|
|
|
|
if(capIdx2 >= leftSideOffset)
|
|
capIdx2 = k2 - capIdx2;
|
|
|
|
if(capIdx3 >= leftSideOffset)
|
|
capIdx3 = k2 - capIdx3;
|
|
|
|
a = &segment.slice0->verts[capIdx1];
|
|
b = &segment.slice0->verts[capIdx2];
|
|
c = &segment.slice0->verts[capIdx3];
|
|
}
|
|
// Back
|
|
else
|
|
{
|
|
k2 = nextSegOffset + leftSideOffset - 1;
|
|
|
|
capIdx1 = mSideProfile.mCap.getTriIdx(k, 0);
|
|
capIdx2 = mSideProfile.mCap.getTriIdx(k, 1);
|
|
capIdx3 = mSideProfile.mCap.getTriIdx(k, 2);
|
|
|
|
if(capIdx1 >= leftSideOffset)
|
|
capIdx1 = k2 - capIdx1;
|
|
|
|
if(capIdx2 >= leftSideOffset)
|
|
capIdx2 = k2 - capIdx2;
|
|
|
|
if(capIdx3 >= leftSideOffset)
|
|
capIdx3 = k2 - capIdx3;
|
|
|
|
a = &segment.slice1->verts[capIdx3];
|
|
b = &segment.slice1->verts[capIdx2];
|
|
c = &segment.slice1->verts[capIdx1];
|
|
}
|
|
|
|
if ( !MathUtils::mLineTriangleCollide( start, end,
|
|
*c, *b, *a,
|
|
NULL,
|
|
&t ) )
|
|
continue;
|
|
|
|
if ( t >= 0.0f && t < 1.0f && t < out )
|
|
{
|
|
out = t;
|
|
norm = PlaneF( *a, *b, *c );
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
info->material = this->mMatInst[0];
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool MeshRoad::collideBox(const Point3F &start, const Point3F &end, RayInfo* info)
|
|
{
|
|
Con::warnf( "MeshRoad::collideBox() - not yet implemented!" );
|
|
return Parent::collideBox( start, end, info );
|
|
}
|
|
|
|
void MeshRoad::_regenerate()
|
|
{
|
|
if ( mNodes.size() == 0 )
|
|
return;
|
|
|
|
if ( mSideProfile.mNodes.size() == 2 && mSideProfile.mNodes[1].getPosition().x == 0.0f)
|
|
mSideProfile.setProfileDepth(mNodes[0].depth);
|
|
|
|
const Point3F &nodePt = mNodes.first().point;
|
|
|
|
MatrixF mat( true );
|
|
mat.setPosition( nodePt );
|
|
Parent::setTransform( mat );
|
|
|
|
_generateSlices();
|
|
|
|
// Make sure we are in the correct bins given our world box.
|
|
if( getSceneManager() != NULL )
|
|
getSceneManager()->notifyObjectDirty( this );
|
|
}
|
|
|
|
void MeshRoad::_generateSlices()
|
|
{
|
|
if ( mNodes.size() < 2 )
|
|
return;
|
|
|
|
// Create the spline, initialized with the MeshRoadNode(s)
|
|
U32 nodeCount = mNodes.size();
|
|
MeshRoadSplineNode *splineNodes = new MeshRoadSplineNode[nodeCount];
|
|
|
|
for ( U32 i = 0; i < nodeCount; i++ )
|
|
{
|
|
MeshRoadSplineNode &splineNode = splineNodes[i];
|
|
const MeshRoadNode &node = mNodes[i];
|
|
|
|
splineNode.x = node.point.x;
|
|
splineNode.y = node.point.y;
|
|
splineNode.z = node.point.z;
|
|
splineNode.width = node.width;
|
|
splineNode.depth = node.depth;
|
|
splineNode.normal = node.normal;
|
|
}
|
|
|
|
CatmullRom<MeshRoadSplineNode> spline;
|
|
spline.initialize( nodeCount, splineNodes );
|
|
delete [] splineNodes;
|
|
|
|
mSlices.clear();
|
|
|
|
VectorF lastBreakVector(0,0,0);
|
|
MeshRoadSlice slice;
|
|
MeshRoadSplineNode lastBreakNode;
|
|
lastBreakNode = spline.evaluate(0.0f);
|
|
|
|
for ( U32 i = 1; i < mNodes.size(); i++ )
|
|
{
|
|
F32 t1 = spline.getTime(i);
|
|
F32 t0 = spline.getTime(i-1);
|
|
|
|
F32 segLength = spline.arcLength( t0, t1 );
|
|
|
|
U32 numSegments = mCeil( segLength / MIN_METERS_PER_SEGMENT );
|
|
numSegments = getMax( numSegments, (U32)1 );
|
|
F32 tstep = ( t1 - t0 ) / numSegments;
|
|
|
|
U32 startIdx = 0;
|
|
U32 endIdx = ( i == nodeCount - 1 ) ? numSegments + 1 : numSegments;
|
|
|
|
for ( U32 j = startIdx; j < endIdx; j++ )
|
|
{
|
|
F32 t = t0 + tstep * j;
|
|
MeshRoadSplineNode splineNode = spline.evaluate(t);
|
|
|
|
VectorF toNodeVec = splineNode.getPosition() - lastBreakNode.getPosition();
|
|
toNodeVec.normalizeSafe();
|
|
|
|
if ( lastBreakVector.isZero() )
|
|
lastBreakVector = toNodeVec;
|
|
|
|
F32 angle = mRadToDeg( mAcos( mDot( toNodeVec, lastBreakVector ) ) );
|
|
|
|
if ( j == startIdx ||
|
|
( j == endIdx - 1 && i == mNodes.size() - 1 ) ||
|
|
angle > mBreakAngle )
|
|
{
|
|
// Push back a spline node
|
|
slice.p1.set( splineNode.x, splineNode.y, splineNode.z );
|
|
slice.width = splineNode.width;
|
|
slice.depth = splineNode.depth;
|
|
slice.normal = splineNode.normal;
|
|
slice.normal.normalize();
|
|
slice.parentNodeIdx = i-1;
|
|
slice.t = t;
|
|
mSlices.push_back( slice );
|
|
|
|
lastBreakVector = splineNode.getPosition() - lastBreakNode.getPosition();
|
|
lastBreakVector.normalizeSafe();
|
|
|
|
lastBreakNode = splineNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
MatrixF mat(true);
|
|
Box3F box;
|
|
|
|
U32 lastProfileNode = mSideProfile.mNodes.size() - 1;
|
|
F32 depth = mSideProfile.mNodes[lastProfileNode].getPosition().y;
|
|
F32 bttmOffset = mSideProfile.mNodes[lastProfileNode].getPosition().x;
|
|
|
|
for ( U32 i = 0; i < mSlices.size(); i++ )
|
|
{
|
|
// Calculate uvec, fvec, and rvec for all slices
|
|
calcSliceTransform( i, mat );
|
|
MeshRoadSlice *slicePtr = &mSlices[i];
|
|
mat.getColumn( 0, &slicePtr->rvec );
|
|
mat.getColumn( 1, &slicePtr->fvec );
|
|
mat.getColumn( 2, &slicePtr->uvec );
|
|
|
|
// Calculate p0/p2/pb0/pb2 for all slices
|
|
slicePtr->p0 = slicePtr->p1 - slicePtr->rvec * slicePtr->width * 0.5f;
|
|
slicePtr->p2 = slicePtr->p1 + slicePtr->rvec * slicePtr->width * 0.5f;
|
|
slicePtr->pb0 = slicePtr->p0 + slicePtr->uvec * depth - slicePtr->rvec * bttmOffset;
|
|
slicePtr->pb2 = slicePtr->p2 + slicePtr->uvec * depth + slicePtr->rvec * bttmOffset;
|
|
|
|
// Generate or extend the object/world bounds
|
|
if ( i == 0 )
|
|
{
|
|
box.minExtents = slicePtr->p0;
|
|
box.maxExtents = slicePtr->p2;
|
|
box.extend(slicePtr->pb0 );
|
|
box.extend(slicePtr->pb2 );
|
|
}
|
|
else
|
|
{
|
|
box.extend(slicePtr->p0 );
|
|
box.extend(slicePtr->p2 );
|
|
box.extend(slicePtr->pb0 );
|
|
box.extend(slicePtr->pb2 );
|
|
}
|
|
|
|
// Right side
|
|
Point3F pos;
|
|
VectorF norm;
|
|
|
|
MatrixF profileMat1(true);
|
|
profileMat1.setRow(0, slicePtr->rvec);
|
|
profileMat1.setRow(1, slicePtr->uvec);
|
|
profileMat1.setRow(2, -slicePtr->fvec);
|
|
|
|
// Left side
|
|
MatrixF profileMat2(true);
|
|
profileMat2.setRow(0, -slicePtr->rvec);
|
|
profileMat2.setRow(1, slicePtr->uvec);
|
|
profileMat2.setRow(2, slicePtr->fvec);
|
|
|
|
for(U32 i = 0; i < 2; i++)
|
|
{
|
|
if(i)
|
|
mSideProfile.setTransform(profileMat2, slicePtr->p0);
|
|
else
|
|
mSideProfile.setTransform(profileMat1, slicePtr->p2);
|
|
|
|
// Retain original per-node depth functionality
|
|
if(mSideProfile.mNodes.size() == 2 && mSideProfile.mNodes[1].getPosition().y == -mSlices[0].depth)
|
|
{
|
|
mSideProfile.getNodeWorldPos(0, pos);
|
|
slicePtr->verts.push_back(pos);
|
|
box.extend( pos );
|
|
|
|
pos.z -= slicePtr->depth;
|
|
slicePtr->verts.push_back(pos);
|
|
box.extend( pos );
|
|
|
|
if(i)
|
|
slicePtr->pb0 = pos;
|
|
else
|
|
slicePtr->pb2 = pos;
|
|
|
|
mSideProfile.getNormToSlice(0, norm);
|
|
slicePtr->norms.push_back(norm);
|
|
|
|
mSideProfile.getNormToSlice(1, norm);
|
|
slicePtr->norms.push_back(norm);
|
|
}
|
|
// New profile functionality
|
|
else
|
|
{
|
|
for(U32 j = 0; j < mSideProfile.mNodes.size(); j++)
|
|
{
|
|
mSideProfile.getNodeWorldPos(j, pos);
|
|
slicePtr->verts.push_back(pos);
|
|
box.extend( pos );
|
|
}
|
|
|
|
for(U32 j = 0; j < mSideProfile.mNodeNormals.size(); j++)
|
|
{
|
|
mSideProfile.getNormToSlice(j, norm);
|
|
slicePtr->norms.push_back(norm);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mWorldBox = box;
|
|
resetObjectBox();
|
|
|
|
_generateSegments();
|
|
}
|
|
|
|
void MeshRoad::_generateSegments()
|
|
{
|
|
SAFE_DELETE( mPhysicsRep );
|
|
|
|
mSegments.clear();
|
|
|
|
for ( U32 i = 0; i < mSlices.size() - 1; i++ )
|
|
{
|
|
MeshRoadSegment seg( &mSlices[i], &mSlices[i+1], getWorldTransform() );
|
|
|
|
mSegments.push_back( seg );
|
|
}
|
|
|
|
//mSideProfile.generateEndCap(mSlices[0].width);
|
|
|
|
if ( isClientObject() )
|
|
_generateVerts();
|
|
|
|
if ( PHYSICSMGR )
|
|
{
|
|
ConcretePolyList polylist;
|
|
if ( buildPolyList( PLC_Collision, &polylist, getWorldBox(), getWorldSphere() ) )
|
|
{
|
|
polylist.triangulate();
|
|
|
|
PhysicsCollision *colShape = PHYSICSMGR->createCollision();
|
|
colShape->addTriangleMesh( polylist.mVertexList.address(),
|
|
polylist.mVertexList.size(),
|
|
polylist.mIndexList.address(),
|
|
polylist.mIndexList.size() / 3,
|
|
MatrixF::Identity );
|
|
|
|
PhysicsWorld *world = PHYSICSMGR->getWorld( isServerObject() ? "server" : "client" );
|
|
mPhysicsRep = PHYSICSMGR->createBody();
|
|
mPhysicsRep->init( colShape, 0, 0, this, world );
|
|
}
|
|
}
|
|
}
|
|
|
|
void MeshRoad::_generateVerts()
|
|
{
|
|
const U32 widthDivisions = getMax( 0, mWidthSubdivisions );
|
|
const F32 divisionStep = 1.0f / (F32)( widthDivisions + 1 );
|
|
const U32 sliceCount = mSlices.size();
|
|
const U32 segmentCount = mSegments.size();
|
|
|
|
U32 numProfSide, numProfTop, numProfBottom;
|
|
|
|
numProfSide = numProfTop = numProfBottom = 0;
|
|
|
|
// Find how many profile segments are set to side, top, and bottom materials
|
|
for ( U32 i = 0; i < mSideProfile.mSegMtrls.size(); i++)
|
|
{
|
|
switch(mSideProfile.mSegMtrls[i])
|
|
{
|
|
case Side: numProfSide++; break;
|
|
case Top: numProfTop++; break;
|
|
case Bottom: numProfBottom++; break;
|
|
}
|
|
}
|
|
|
|
F32 profLen = mSideProfile.getProfileLen();
|
|
|
|
mVertCount[Top] = ( 2 + widthDivisions ) * sliceCount;
|
|
mVertCount[Top] += sliceCount * numProfTop * 4;
|
|
mTriangleCount[Top] = segmentCount * 2 * ( widthDivisions + 1 );
|
|
mTriangleCount[Top] += segmentCount * numProfTop * 4;
|
|
|
|
mVertCount[Bottom] = sliceCount * 2;
|
|
mVertCount[Bottom] += sliceCount * numProfBottom * 4;
|
|
mTriangleCount[Bottom] = segmentCount * 2;
|
|
mTriangleCount[Bottom] += segmentCount * numProfBottom * 4;
|
|
|
|
mVertCount[Side] = sliceCount * numProfSide * 4; // side verts
|
|
mVertCount[Side] += mSideProfile.mNodes.size() * 4; // end cap verts
|
|
mTriangleCount[Side] = segmentCount * numProfSide * 4; // side tris
|
|
mTriangleCount[Side] += mSideProfile.mCap.getNumTris() * 2; // end cap tris
|
|
|
|
// Calculate TexCoords for Slices
|
|
|
|
F32 texCoordV = 0.0f;
|
|
mSlices[0].texCoordV = 0.0f;
|
|
|
|
for ( U32 i = 1; i < sliceCount; i++ )
|
|
{
|
|
MeshRoadSlice &slice = mSlices[i];
|
|
MeshRoadSlice &prevSlice = mSlices[i-1];
|
|
|
|
// Increment the textCoordV for the next slice.
|
|
F32 len = ( slice.p1 - prevSlice.p1 ).len();
|
|
texCoordV += len / mTextureLength;
|
|
|
|
slice.texCoordV = texCoordV;
|
|
}
|
|
|
|
// Make Vertex Buffers
|
|
GFXVertexPNTT *pVert = NULL;
|
|
U32 vertCounter = 0;
|
|
|
|
// Top Buffers...
|
|
|
|
mVB[Top].set( GFX, mVertCount[Top], GFXBufferTypeStatic );
|
|
pVert = mVB[Top].lock();
|
|
vertCounter = 0;
|
|
|
|
for ( U32 i = 0; i < sliceCount; i++ )
|
|
{
|
|
MeshRoadSlice &slice = mSlices[i];
|
|
|
|
pVert->point = slice.p0;
|
|
pVert->normal = slice.uvec;
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(1,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
|
|
for ( U32 j = 0; j < widthDivisions; j++ )
|
|
{
|
|
const F32 t = divisionStep * (F32)( j + 1 );
|
|
|
|
pVert->point.interpolate( slice.p0, slice.p2, t );
|
|
pVert->normal = slice.uvec;
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set( 1.0f - t, slice.texCoordV );
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
|
|
pVert->point = slice.p2;
|
|
pVert->normal = slice.uvec;
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set( 0, slice.texCoordV );
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
|
|
if(numProfTop)
|
|
{
|
|
for ( U32 i = 0; i < sliceCount; i++ )
|
|
{
|
|
MeshRoadSlice &slice = mSlices[i];
|
|
|
|
// Right Side
|
|
for ( U32 j = 0; j < mSideProfile.mNodes.size()-1; j++)
|
|
{
|
|
if(mSideProfile.mSegMtrls[j] == Top)
|
|
{
|
|
// Vertex 1
|
|
pVert->point = slice.verts[j];
|
|
pVert->normal = slice.norms[2*j];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
|
|
// Vertex 2
|
|
pVert->point = slice.verts[j+1];
|
|
pVert->normal = slice.norms[2*j+1];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
}
|
|
|
|
// Left Side
|
|
for( U32 j = mSideProfile.mNodes.size(); j < 2*mSideProfile.mNodes.size()-1; j++)
|
|
{
|
|
if(mSideProfile.mSegMtrls[j-mSideProfile.mNodes.size()] == Top)
|
|
{
|
|
// Vertex 1
|
|
pVert->point = slice.verts[j];
|
|
pVert->normal = slice.norms[2*j-2];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
|
|
// Vertex 2
|
|
pVert->point = slice.verts[j+1];
|
|
pVert->normal = slice.norms[2*j-1];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AssertFatal( vertCounter == mVertCount[Top], "MeshRoad, wrote incorrect number of verts in mVB[Top]!" );
|
|
|
|
mVB[Top].unlock();
|
|
|
|
// Bottom Buffer...
|
|
|
|
mVB[Bottom].set( GFX, mVertCount[Bottom], GFXBufferTypeStatic );
|
|
pVert = mVB[Bottom].lock();
|
|
vertCounter = 0;
|
|
|
|
for ( U32 i = 0; i < sliceCount; i++ )
|
|
{
|
|
MeshRoadSlice &slice = mSlices[i];
|
|
|
|
pVert->point = slice.pb2;
|
|
pVert->normal = -slice.uvec;
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(0,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
|
|
pVert->point = slice.pb0;
|
|
pVert->normal = -slice.uvec;
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(1,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
|
|
if(numProfBottom)
|
|
{
|
|
for ( U32 i = 0; i < sliceCount; i++ )
|
|
{
|
|
MeshRoadSlice &slice = mSlices[i];
|
|
|
|
// Right Side
|
|
for ( U32 j = 0; j < mSideProfile.mNodes.size()-1; j++)
|
|
{
|
|
if(mSideProfile.mSegMtrls[j] == Bottom)
|
|
{
|
|
// Vertex 1
|
|
pVert->point = slice.verts[j];
|
|
pVert->normal = slice.norms[2*j];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
|
|
// Vertex 2
|
|
pVert->point = slice.verts[j+1];
|
|
pVert->normal = slice.norms[2*j+1];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
}
|
|
|
|
// Left Side
|
|
for( U32 j = mSideProfile.mNodes.size(); j < 2*mSideProfile.mNodes.size()-1; j++)
|
|
{
|
|
if(mSideProfile.mSegMtrls[j-mSideProfile.mNodes.size()] == Bottom)
|
|
{
|
|
// Vertex 1
|
|
pVert->point = slice.verts[j];
|
|
pVert->normal = slice.norms[2*j-2];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
|
|
// Vertex 2
|
|
pVert->point = slice.verts[j+1];
|
|
pVert->normal = slice.norms[2*j-1];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
AssertFatal( vertCounter == mVertCount[Bottom], "MeshRoad, wrote incorrect number of verts in mVB[Bottom]!" );
|
|
|
|
mVB[Bottom].unlock();
|
|
|
|
// Side Buffers...
|
|
|
|
mVB[Side].set( GFX, mVertCount[Side], GFXBufferTypeStatic );
|
|
pVert = mVB[Side].lock();
|
|
vertCounter = 0;
|
|
|
|
if(numProfSide)
|
|
{
|
|
for ( U32 i = 0; i < sliceCount; i++ )
|
|
{
|
|
MeshRoadSlice &slice = mSlices[i];
|
|
|
|
// Right Side
|
|
for( U32 j = 0; j < mSideProfile.mNodes.size()-1; j++)
|
|
{
|
|
if(mSideProfile.mSegMtrls[j] == Side)
|
|
{
|
|
// Segment Vertex 1
|
|
pVert->point = slice.verts[j];
|
|
pVert->normal = slice.norms[2*j];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
|
|
// Segment Vertex 2
|
|
pVert->point = slice.verts[j+1];
|
|
pVert->normal = slice.norms[2*j+1];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
}
|
|
|
|
// Left Side
|
|
for( U32 j = mSideProfile.mNodes.size(); j < 2*mSideProfile.mNodes.size()-1; j++)
|
|
{
|
|
if(mSideProfile.mSegMtrls[j-mSideProfile.mNodes.size()] == Side)
|
|
{
|
|
// Segment Vertex 1
|
|
pVert->point = slice.verts[j];
|
|
pVert->normal = slice.norms[2*j-2];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
|
|
// Segment Vertex 2
|
|
pVert->point = slice.verts[j+1];
|
|
pVert->normal = slice.norms[2*j-1];
|
|
pVert->tangent = slice.fvec;
|
|
pVert->texCoord.set(mSideProfile.getNodePosPercent(j+1)*profLen/mTextureLength,slice.texCoordV);
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cap verts
|
|
Point3F pos;
|
|
VectorF norm;
|
|
VectorF tang;
|
|
|
|
for( U32 i = 0; i < mSlices.size(); i += mSlices.size()-1)
|
|
{
|
|
MeshRoadSlice &slice = mSlices[i];
|
|
|
|
// Back cap
|
|
if(i)
|
|
{
|
|
norm = slice.fvec;
|
|
tang = -slice.rvec;
|
|
}
|
|
// Front cap
|
|
else
|
|
{
|
|
norm = -slice.fvec;
|
|
tang = slice.rvec;
|
|
}
|
|
|
|
// Right side
|
|
for( U32 j = 0; j < mSideProfile.mNodes.size(); j++)
|
|
{
|
|
pVert->point = slice.verts[j];
|
|
pVert->normal = norm;
|
|
pVert->tangent = tang;
|
|
pos = mSideProfile.mNodes[j].getPosition();
|
|
pVert->texCoord.set(pos.x/mTextureLength, pos.y/mTextureLength);
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
|
|
// Left side
|
|
for( U32 j = 2*mSideProfile.mNodes.size()-1; j >= mSideProfile.mNodes.size(); j--)
|
|
{
|
|
pVert->point = slice.verts[j];
|
|
pVert->normal = norm;
|
|
pVert->tangent = tang;
|
|
pos = mSideProfile.mNodes[j-mSideProfile.mNodes.size()].getPosition();
|
|
pos.x = -pos.x - slice.width;
|
|
pVert->texCoord.set(pos.x/mTextureLength, pos.y/mTextureLength);
|
|
pVert++;
|
|
vertCounter++;
|
|
}
|
|
}
|
|
|
|
AssertFatal( vertCounter == mVertCount[Side], "MeshRoad, wrote incorrect number of verts in mVB[Side]!" );
|
|
|
|
mVB[Side].unlock();
|
|
|
|
// Make Primitive Buffers
|
|
U32 p00, p01, p11, p10;
|
|
U32 offset = 0;
|
|
U16 *pIdx = NULL;
|
|
U32 curIdx = 0;
|
|
|
|
// Top Primitive Buffer
|
|
|
|
mPB[Top].set( GFX, mTriangleCount[Top] * 3, mTriangleCount[Top], GFXBufferTypeStatic );
|
|
|
|
mPB[Top].lock(&pIdx);
|
|
curIdx = 0;
|
|
offset = 0;
|
|
|
|
const U32 rowStride = 2 + widthDivisions;
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
{
|
|
for ( U32 j = 0; j < widthDivisions + 1; j++ )
|
|
{
|
|
p00 = offset;
|
|
p10 = offset + 1;
|
|
p01 = offset + rowStride;
|
|
p11 = offset + rowStride + 1;
|
|
|
|
pIdx[curIdx] = p00;
|
|
curIdx++;
|
|
pIdx[curIdx] = p01;
|
|
curIdx++;
|
|
pIdx[curIdx] = p11;
|
|
curIdx++;
|
|
|
|
pIdx[curIdx] = p00;
|
|
curIdx++;
|
|
pIdx[curIdx] = p11;
|
|
curIdx++;
|
|
pIdx[curIdx] = p10;
|
|
curIdx++;
|
|
|
|
offset += 1;
|
|
}
|
|
|
|
offset += 1;
|
|
}
|
|
|
|
offset += 2;
|
|
|
|
if(numProfTop)
|
|
{
|
|
U32 nextSegOffset = 4 * numProfTop;
|
|
|
|
for ( U32 i = 0; i < segmentCount; i++ )
|
|
{
|
|
// Loop through profile segments on right side
|
|
for( U32 j = 0; j < numProfTop; j++)
|
|
{
|
|
// Profile Segment Face 1
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + 1 + offset;
|
|
curIdx++;
|
|
|
|
// Profile Segment Face 2
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset;
|
|
curIdx++;
|
|
}
|
|
|
|
// Loop through profile segments on left side
|
|
for( U32 j = numProfTop; j < 2*numProfTop; j++)
|
|
{
|
|
// Profile Segment Face 1
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + 1 + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset;
|
|
curIdx++;
|
|
|
|
// Profile Segment Face 2
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + offset;
|
|
curIdx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
AssertFatal( curIdx == mTriangleCount[Top] * 3, "MeshRoad, wrote incorrect number of indices in mPB[Top]!" );
|
|
|
|
mPB[Top].unlock();
|
|
|
|
// Bottom Primitive Buffer
|
|
|
|
mPB[Bottom].set( GFX, mTriangleCount[Bottom] * 3, mTriangleCount[Bottom], GFXBufferTypeStatic );
|
|
|
|
mPB[Bottom].lock(&pIdx);
|
|
curIdx = 0;
|
|
offset = 0;
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
{
|
|
p00 = offset;
|
|
p10 = offset + 1;
|
|
p01 = offset + 2;
|
|
p11 = offset + 3;
|
|
|
|
pIdx[curIdx] = p00;
|
|
curIdx++;
|
|
pIdx[curIdx] = p01;
|
|
curIdx++;
|
|
pIdx[curIdx] = p11;
|
|
curIdx++;
|
|
|
|
pIdx[curIdx] = p00;
|
|
curIdx++;
|
|
pIdx[curIdx] = p11;
|
|
curIdx++;
|
|
pIdx[curIdx] = p10;
|
|
curIdx++;
|
|
|
|
offset += 2;
|
|
}
|
|
|
|
offset += 2;
|
|
|
|
if(numProfBottom)
|
|
{
|
|
U32 nextSegOffset = 4 * numProfBottom;
|
|
|
|
for ( U32 i = 0; i < segmentCount; i++ )
|
|
{
|
|
// Loop through profile segments on right side
|
|
for( U32 j = 0; j < numProfBottom; j++)
|
|
{
|
|
// Profile Segment Face 1
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + 1 + offset;
|
|
curIdx++;
|
|
|
|
// Profile Segment Face 2
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset;
|
|
curIdx++;
|
|
}
|
|
|
|
// Loop through profile segments on left side
|
|
for( U32 j = numProfBottom; j < 2*numProfBottom; j++)
|
|
{
|
|
// Profile Segment Face 1
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + 1 + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset;
|
|
curIdx++;
|
|
|
|
// Profile Segment Face 2
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + 1 + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = nextSegOffset*i + 2*j + nextSegOffset + offset;
|
|
curIdx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
AssertFatal( curIdx == mTriangleCount[Bottom] * 3, "MeshRoad, wrote incorrect number of indices in mPB[Bottom]!" );
|
|
|
|
mPB[Bottom].unlock();
|
|
|
|
// Side Primitive Buffer
|
|
|
|
mPB[Side].set( GFX, mTriangleCount[Side] * 3, mTriangleCount[Side], GFXBufferTypeStatic );
|
|
|
|
mPB[Side].lock(&pIdx);
|
|
curIdx = 0;
|
|
offset = 4 * numProfSide;
|
|
|
|
if(numProfSide)
|
|
{
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
{
|
|
// Loop through profile segments on right side
|
|
for( U32 j = 0; j < numProfSide; j++)
|
|
{
|
|
// Profile Segment Face 1
|
|
pIdx[curIdx] = offset*i + 2*j;
|
|
curIdx++;
|
|
pIdx[curIdx] = offset*i + 2*j + offset + 1;
|
|
curIdx++;
|
|
pIdx[curIdx] = offset*i + 2*j + 1;
|
|
curIdx++;
|
|
|
|
// Profile Segment Face 2
|
|
pIdx[curIdx] = offset*i + 2*j;
|
|
curIdx++;
|
|
pIdx[curIdx] = offset*i + 2*j + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = offset*i + 2*j + offset + 1;
|
|
curIdx++;
|
|
}
|
|
|
|
// Loop through profile segments on left side
|
|
for( U32 j = numProfSide; j < 2*numProfSide; j++)
|
|
{
|
|
// Profile Segment Face 1
|
|
pIdx[curIdx] = offset*i + 2*j;
|
|
curIdx++;
|
|
pIdx[curIdx] = offset*i + 2*j + 1;
|
|
curIdx++;
|
|
pIdx[curIdx] = offset*i + 2*j + offset + 1;
|
|
curIdx++;
|
|
|
|
// Profile Segment Face 2
|
|
pIdx[curIdx] = offset*i + 2*j;
|
|
curIdx++;
|
|
pIdx[curIdx] = offset*i + 2*j + offset + 1;
|
|
curIdx++;
|
|
pIdx[curIdx] = offset*i + 2*j + offset;
|
|
curIdx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cap the front
|
|
offset = sliceCount * numProfSide * 4;
|
|
|
|
for ( U32 i = 0; i < mSideProfile.mCap.getNumTris(); i++ )
|
|
{
|
|
pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 0) + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 1) + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 2) + offset;
|
|
curIdx++;
|
|
}
|
|
|
|
// Cap the back
|
|
offset += mSideProfile.mNodes.size() * 2;
|
|
|
|
for ( U32 i = 0; i < mSideProfile.mCap.getNumTris(); i++ )
|
|
{
|
|
pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 2) + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 1) + offset;
|
|
curIdx++;
|
|
pIdx[curIdx] = mSideProfile.mCap.getTriIdx(i, 0) + offset;
|
|
curIdx++;
|
|
}
|
|
|
|
AssertFatal( curIdx == mTriangleCount[Side] * 3, "MeshRoad, wrote incorrect number of indices in mPB[Side]!" );
|
|
|
|
mPB[Side].unlock();
|
|
}
|
|
|
|
const MeshRoadNode& MeshRoad::getNode( U32 idx )
|
|
{
|
|
return mNodes[idx];
|
|
}
|
|
|
|
VectorF MeshRoad::getNodeNormal( U32 idx )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return VectorF::Zero;
|
|
|
|
return mNodes[idx].normal;
|
|
}
|
|
|
|
void MeshRoad::setNodeNormal( U32 idx, const VectorF &normal )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return;
|
|
|
|
mNodes[idx].normal = normal;
|
|
|
|
regenerate();
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
}
|
|
|
|
Point3F MeshRoad::getNodePosition( U32 idx )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return Point3F::Zero;
|
|
|
|
return mNodes[idx].point;
|
|
}
|
|
|
|
void MeshRoad::setNodePosition( U32 idx, const Point3F &pos )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return;
|
|
|
|
mNodes[idx].point = pos;
|
|
|
|
regenerate();
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
}
|
|
|
|
U32 MeshRoad::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;
|
|
}
|
|
|
|
void MeshRoad::buildNodesFromList( MeshRoadNodeList* 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();
|
|
}
|
|
|
|
U32 MeshRoad::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 MeshRoad::setNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return;
|
|
|
|
MeshRoadNode &node = mNodes[idx];
|
|
|
|
node.point = pos;
|
|
node.width = width;
|
|
node.depth = depth;
|
|
node.normal = normal;
|
|
|
|
regenerate();
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
}
|
|
|
|
void MeshRoad::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 );
|
|
}
|
|
|
|
F32 MeshRoad::getNodeWidth( U32 idx )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return -1.0f;
|
|
|
|
return mNodes[idx].width;
|
|
}
|
|
|
|
void MeshRoad::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( MeshRoadMask | RegenMask | NodeMask );
|
|
}
|
|
|
|
F32 MeshRoad::getNodeDepth( U32 idx )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return -1.0f;
|
|
|
|
return mNodes[idx].depth;
|
|
}
|
|
|
|
MatrixF MeshRoad::getNodeTransform( U32 idx )
|
|
{
|
|
MatrixF mat(true);
|
|
|
|
if ( mNodes.size() - 1 < idx )
|
|
return mat;
|
|
|
|
bool hasNext = idx + 1 < mNodes.size();
|
|
bool hasPrev = (S32)idx - 1 > 0;
|
|
|
|
const MeshRoadNode &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 MeshRoad::calcSliceTransform( U32 idx, MatrixF &mat )
|
|
{
|
|
if ( mSlices.size() - 1 < idx )
|
|
return;
|
|
|
|
bool hasNext = idx + 1 < mSlices.size();
|
|
bool hasPrev = (S32)idx - 1 >= 0;
|
|
|
|
const MeshRoadSlice &slice = mSlices[idx];
|
|
|
|
VectorF fvec( 0, 1, 0 );
|
|
|
|
if ( hasNext )
|
|
{
|
|
fvec = mSlices[idx+1].p1 - slice.p1;
|
|
fvec.normalizeSafe();
|
|
}
|
|
else if ( hasPrev )
|
|
{
|
|
fvec = slice.p1 - mSlices[idx-1].p1;
|
|
fvec.normalizeSafe();
|
|
}
|
|
else
|
|
fvec = mPerp( slice.normal );
|
|
|
|
if ( fvec.isZero() )
|
|
fvec = mPerp( slice.normal );
|
|
|
|
F32 dot = mDot( fvec, slice.normal );
|
|
if ( dot < -0.9f || dot > 0.9f )
|
|
fvec = mPerp( slice.normal );
|
|
|
|
VectorF rvec = mCross( fvec, slice.normal );
|
|
if ( rvec.isZero() )
|
|
rvec = mPerp( fvec );
|
|
rvec.normalize();
|
|
|
|
fvec = mCross( slice.normal, rvec );
|
|
fvec.normalize();
|
|
|
|
mat.setColumn( 0, rvec );
|
|
mat.setColumn( 1, fvec );
|
|
mat.setColumn( 2, slice.normal );
|
|
mat.setColumn( 3, slice.p1 );
|
|
|
|
AssertFatal( m_matF_determinant( mat ) != 0.0f, "no inverse!");
|
|
}
|
|
|
|
F32 MeshRoad::getRoadLength() const
|
|
{
|
|
F32 length = 0.0f;
|
|
|
|
for ( U32 i = 0; i < mSegments.size(); i++ )
|
|
{
|
|
length += mSegments[i].length();
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
void MeshRoad::deleteNode( U32 idx )
|
|
{
|
|
if ( mNodes.size() - 1 < idx )
|
|
return;
|
|
|
|
mNodes.erase(idx);
|
|
_regenerate();
|
|
|
|
setMaskBits( RegenMask | NodeMask );
|
|
}
|
|
|
|
U32 MeshRoad::_addNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal )
|
|
{
|
|
mNodes.increment();
|
|
MeshRoadNode &node = mNodes.last();
|
|
|
|
node.point = pos;
|
|
node.width = width;
|
|
node.depth = depth;
|
|
node.normal = normal;
|
|
|
|
setMaskBits( NodeMask | RegenMask );
|
|
|
|
return mNodes.size() - 1;
|
|
}
|
|
|
|
U32 MeshRoad::_insertNode( const Point3F &pos, const F32 &width, const F32 &depth, const VectorF &normal, const U32 &idx )
|
|
{
|
|
U32 ret;
|
|
MeshRoadNode *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;
|
|
}
|
|
|
|
bool MeshRoad::collideRay( const Point3F &origin, const Point3F &direction, U32 *nodeIdx, Point3F *collisionPnt )
|
|
{
|
|
Point3F p0 = origin;
|
|
Point3F p1 = origin + direction * 2000.0f;
|
|
|
|
// If the line segment does not collide with the MeshRoad'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 road segment (formed by a pair of slices) for collision
|
|
// with the line segment.
|
|
for ( U32 i = 0; i < mSlices.size() - 1; i++ )
|
|
{
|
|
const MeshRoadSlice &slice0 = mSlices[i];
|
|
const MeshRoadSlice &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;
|
|
|
|
if ( MathUtils::mRayQuadCollide( quad, ray, NULL, &t ) )
|
|
{
|
|
if ( nodeIdx )
|
|
*nodeIdx = slice0.parentNodeIdx;
|
|
if ( collisionPnt )
|
|
*collisionPnt = ray.origin + ray.direction * t;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void MeshRoad::regenerate()
|
|
{
|
|
_regenerate();
|
|
setMaskBits( RegenMask );
|
|
}
|
|
|
|
//-------------------------------------------------------------------------
|
|
// Console Methods
|
|
//-------------------------------------------------------------------------
|
|
|
|
DefineEngineMethod( MeshRoad, 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( MeshRoad, regenerate, void, (),,
|
|
"Intended as a helper to developers and editor scripts.\n"
|
|
"Force MeshRoad to recreate its geometry."
|
|
)
|
|
{
|
|
object->regenerate();
|
|
}
|
|
|
|
DefineEngineMethod( MeshRoad, postApply, void, (),,
|
|
"Intended as a helper to developers and editor scripts.\n"
|
|
"Force trigger an inspectPostApply. This will transmit "
|
|
"material and other fields ( not including nodes ) to client objects."
|
|
)
|
|
{
|
|
object->inspectPostApply();
|
|
}
|
|
bool MeshRoad::buildPolyList_TopSurfaceOnly = false;
|
|
|
|
bool MeshRoad::buildTopPolyList(PolyListContext plc, AbstractPolyList* polyList)
|
|
{
|
|
static Box3F box_prox; static SphereF ball_prox;
|
|
|
|
buildPolyList_TopSurfaceOnly = true;
|
|
bool result = buildPolyList(plc, polyList, box_prox, ball_prox);
|
|
buildPolyList_TopSurfaceOnly = false;
|
|
|
|
return result;
|
|
}
|
|
|