653 lines
18 KiB
C++
653 lines
18 KiB
C++
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include "std3d.h"
|
|
|
|
#include "nel/3d/u_visual_collision_entity.h"
|
|
|
|
#include "nel/3d/visual_collision_entity.h"
|
|
#include "nel/3d/landscape.h"
|
|
#include "nel/3d/dru.h"
|
|
#include "nel/3d/driver.h"
|
|
#include "nel/3d/tile_bank.h"
|
|
#include "nel/misc/hierarchical_timer.h"
|
|
|
|
|
|
using namespace std;
|
|
using namespace NLMISC;
|
|
|
|
|
|
namespace NL3D
|
|
{
|
|
|
|
|
|
// ***************************************************************************
|
|
// should be at least 2 meters.
|
|
const float CVisualCollisionEntity::BBoxRadius= 10;
|
|
const float CVisualCollisionEntity::BBoxRadiusZ= 20;
|
|
const uint32 CVisualCollisionEntity::_StartPatchQuadBlockSize= 64; // 256 octets per entity minimum.
|
|
vector<CPatchBlockIdent> CVisualCollisionEntity::_TmpBlockIds;
|
|
vector<CPatchQuadBlock*> CVisualCollisionEntity::_TmpPatchQuadBlocks;
|
|
|
|
|
|
// ***************************************************************************
|
|
CVisualCollisionEntity::CVisualCollisionEntity(CVisualCollisionManager *owner) : _LandscapeQuadGrid(owner)
|
|
{
|
|
_Owner= owner;
|
|
_PatchQuadBlocks.reserve(_StartPatchQuadBlockSize);
|
|
|
|
_CurrentBBoxValidity.setHalfSize(CVector::Null);
|
|
|
|
_GroundMode= true;
|
|
_CeilMode= false;
|
|
_SnapToRenderedTesselation= true;
|
|
|
|
_LastGPTValid= false;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
CVisualCollisionEntity::~CVisualCollisionEntity()
|
|
{
|
|
// delete the _PatchQuadBlocks.
|
|
for(sint i=0; i<(sint)_PatchQuadBlocks.size(); i++)
|
|
{
|
|
_Owner->deletePatchQuadBlock(_PatchQuadBlocks[i]);
|
|
}
|
|
_PatchQuadBlocks.clear();
|
|
|
|
// delete the quadgrid.
|
|
_LandscapeQuadGrid.clear();
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CVisualCollisionEntity::snapToGround(CVector &pos)
|
|
{
|
|
CVector normal;
|
|
// Not optimized, but doesn't matter (one cross product is negligible).
|
|
return snapToGround(pos, normal);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
CTrianglePatch *CVisualCollisionEntity::getPatchTriangleUnderUs(const CVector &pos, CVector &res)
|
|
{
|
|
// verify if landscape (refptr) is here.
|
|
if(_Owner->_Landscape==NULL)
|
|
{
|
|
_LastGPTValid= false;
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// Test GPT cache.
|
|
//==================
|
|
// If last call was valid, and if same pos (input or output), return cached information
|
|
if(_LastGPTValid && (pos==_LastGPTPosInput || pos==_LastGPTPosOutput) )
|
|
{
|
|
// copy from cache.
|
|
res= _LastGPTPosOutput;
|
|
/* don't modify _LastGPTPosInput cache, for best cache behavior in all cases.
|
|
1/ this is not necessary (here, if pos!=_LastGPTPosInput, THEN pos==_LastGPTPosOutput, no other possibilities)
|
|
2/ it causes problems when getPatchTriangleUnderUs() is called randomly with the unsnapped or snapped position:
|
|
1st Time: zin:9.0 => zout:9.5 cache fail (ok, 1st time...)
|
|
2nd Time: zin:9.0 => zout:9.5 cache OK (_LastGPTPosInput.z==zin)
|
|
3rd Time: zin:9.5 => zout:9.5 cache OK (_LastGPTPosOutput.z==zin)
|
|
4th Time: zin:9.0 => zout:9.5 cache FAILS (_LastGPTPosInput.z= 9.5 => !=zin)
|
|
*/
|
|
// and return ptr on cache.
|
|
return &_LastGPTTrianglePatch;
|
|
}
|
|
|
|
|
|
// update the cache of tile info near this position.
|
|
// =================
|
|
testComputeLandscape(pos);
|
|
|
|
|
|
// find possible faces under the entity.
|
|
// =================
|
|
CVisualTileDescNode *ptr= _LandscapeQuadGrid.select(pos);
|
|
|
|
|
|
// find the better face under the entity.
|
|
// =================
|
|
float sqrBestDist= sqr(1000.f);
|
|
CVector hit;
|
|
// build the vertical ray.
|
|
CVector segP0= pos - CVector(0,0,100);
|
|
CVector segP1= pos + CVector(0,0,100);
|
|
|
|
|
|
// triangles builded from this list.
|
|
static vector<CTrianglePatch> testTriangles;
|
|
// NB: not so many reallocation here, because static.
|
|
testTriangles.clear();
|
|
sint bestTriangle= 0;
|
|
|
|
|
|
// For all the faces in this quadgrid node.
|
|
while(ptr)
|
|
{
|
|
// what is the quad block of this tile Id.
|
|
sint qbId= ptr->PatchQuadBlocId;
|
|
nlassert(qbId>=0 && qbId<(sint)_PatchQuadBlocks.size());
|
|
CPatchQuadBlock &qb= *_PatchQuadBlocks[qbId];
|
|
|
|
// Build the 2 triangles of this tile Id.
|
|
sint idStart= (sint)testTriangles.size();
|
|
testTriangles.resize(idStart+2);
|
|
qb.buildTileTriangles((uint8)ptr->QuadId, &testTriangles[idStart]);
|
|
|
|
// Test the 2 triangles.
|
|
for(sint i=0; i<2; i++)
|
|
{
|
|
CTrianglePatch &tri= testTriangles[idStart+i];
|
|
// test if the ray intersect.
|
|
// NB: triangleIntersect() is faster than CTriangle::intersect().
|
|
if(triangleIntersect(tri, segP0, segP1, hit))
|
|
{
|
|
// find the nearest triangle.
|
|
float sqrdist= (hit-pos).sqrnorm();
|
|
if(sqrdist<sqrBestDist)
|
|
{
|
|
bestTriangle= idStart+i;
|
|
res= hit;
|
|
sqrBestDist= sqrdist;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Next in the list.
|
|
ptr= ptr->Next;
|
|
}
|
|
|
|
|
|
// found ??
|
|
if(sqrBestDist<sqr(1000))
|
|
{
|
|
// copy into cache
|
|
_LastGPTValid= true;
|
|
_LastGPTTrianglePatch= testTriangles[bestTriangle];
|
|
_LastGPTPosInput= pos;
|
|
_LastGPTPosOutput= res;
|
|
// and return ptr on cache.
|
|
return &_LastGPTTrianglePatch;
|
|
}
|
|
else
|
|
{
|
|
_LastGPTValid= false;
|
|
return NULL;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CVisualCollisionEntity::snapToGround(CVector &pos, CVector &normal)
|
|
{
|
|
// Get Patch Triangle Under Us
|
|
CVector res;
|
|
CTrianglePatch *tri= getPatchTriangleUnderUs(pos, res);
|
|
|
|
// result. NB: if not found, dot not modify.
|
|
if( tri )
|
|
{
|
|
if(_SnapToRenderedTesselation)
|
|
{
|
|
// snap the position to the nearest tesselation.
|
|
pos= res;
|
|
|
|
// snap the position to the current rendered tesselation.
|
|
snapToLandscapeCurrentTesselation(pos, *tri);
|
|
}
|
|
else
|
|
{
|
|
// just snap to the accurate tile tesselation.
|
|
pos= res;
|
|
}
|
|
|
|
|
|
// compute the normal.
|
|
normal= (tri->V1-tri->V0)^(tri->V2-tri->V0);
|
|
normal.normalize();
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CVisualCollisionEntity::computeUvForPos(const CTrianglePatch &tri, const CVector &pos, CUV &uv)
|
|
{
|
|
// compute UV gradients.
|
|
CVector Gu;
|
|
CVector Gv;
|
|
tri.computeGradient(tri.Uv0.U, tri.Uv1.U, tri.Uv2.U, Gu);
|
|
tri.computeGradient(tri.Uv0.V, tri.Uv1.V, tri.Uv2.V, Gv);
|
|
// interpolate
|
|
uv.U= tri.Uv0.U + Gu*(pos-tri.V0);
|
|
uv.V= tri.Uv0.V + Gv*(pos-tri.V0);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CVisualCollisionEntity::snapToLandscapeCurrentTesselation(CVector &pos, const CTrianglePatch &tri)
|
|
{
|
|
// compute UV for position.
|
|
CUV uv;
|
|
computeUvForPos(tri, pos, uv);
|
|
|
|
// Ask pos to landscape.
|
|
CVector posLand;
|
|
posLand= _Owner->_Landscape->getTesselatedPos(tri.PatchId, uv);
|
|
|
|
// just keep Z.
|
|
pos.z= posLand.z;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
// TestYoyo. For Precision problem.
|
|
/*static bool testLine(CVector &p1, CVector &p0, const CVector &pos0)
|
|
{
|
|
float epsilon= 0.1f;
|
|
|
|
float a,b,c; // 2D cartesian coefficients of line in plane X/Y.
|
|
float norm;
|
|
// Line p0-p1.
|
|
a= -(p1.y-p0.y);
|
|
b= (p1.x-p0.x);
|
|
norm= sqrtf(sqr(a) + sqr(b));
|
|
a/= norm;
|
|
b/= norm;
|
|
c= -(p0.x*a + p0.y*b);
|
|
if( (a*pos0.x + b*pos0.y + c) < -epsilon)
|
|
return false;
|
|
else
|
|
return true;
|
|
}*/
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CVisualCollisionEntity::triangleIntersect2DGround(CTriangle &tri, const CVector &pos0)
|
|
{
|
|
CVector &p0= tri.V0;
|
|
CVector &p1= tri.V1;
|
|
CVector &p2= tri.V2;
|
|
|
|
// TestYoyo. Test for precision problems.
|
|
/*if( testLine(p1, p0, pos0) && testLine(p2, p1, pos0) && testLine(p0, p2, pos0) )
|
|
{
|
|
nlinfo("Found Tri For Pos: %.07f, %.07f\n P0: %.07f, %.07f\n P1: %.07f, %.07f\n P2: %.07f, %.07f",
|
|
pos0.x, pos0.y, p0.x, p0.y, p1.x, p1.y, p2.x, p2.y);
|
|
}*/
|
|
|
|
// Test if the face enclose the pos in X/Y plane.
|
|
// NB: compute and using a BBox to do a rapid test is not a very good idea, since it will
|
|
// add an overhead which is NOT negligeable compared to the following test.
|
|
float a,b,c; // 2D cartesian coefficients of line in plane X/Y.
|
|
// Line p0-p1.
|
|
a= -(p1.y-p0.y);
|
|
b= (p1.x-p0.x);
|
|
c= -(p0.x*a + p0.y*b);
|
|
if( (a*pos0.x + b*pos0.y + c) < 0) return false;
|
|
// Line p1-p2.
|
|
a= -(p2.y-p1.y);
|
|
b= (p2.x-p1.x);
|
|
c= -(p1.x*a + p1.y*b);
|
|
if( (a*pos0.x + b*pos0.y + c) < 0) return false;
|
|
// Line p2-p0.
|
|
a= -(p0.y-p2.y);
|
|
b= (p0.x-p2.x);
|
|
c= -(p2.x*a + p2.y*b);
|
|
if( (a*pos0.x + b*pos0.y + c) < 0) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
bool CVisualCollisionEntity::triangleIntersect2DCeil(CTriangle &tri, const CVector &pos0)
|
|
{
|
|
CVector &p0= tri.V0;
|
|
CVector &p1= tri.V1;
|
|
CVector &p2= tri.V2;
|
|
|
|
// Test if the face enclose the pos in X/Y plane.
|
|
// NB: compute and using a BBox to do a rapid test is not a very good idea, since it will
|
|
// add an overhead which is NOT negligeable compared to the following test.
|
|
float a,b,c; // 2D cartesian coefficients of line in plane X/Y.
|
|
// Line p0-p1.
|
|
a= -(p1.y-p0.y);
|
|
b= (p1.x-p0.x);
|
|
c= -(p0.x*a + p0.y*b);
|
|
if( (a*pos0.x + b*pos0.y + c) > 0) return false;
|
|
// Line p1-p2.
|
|
a= -(p2.y-p1.y);
|
|
b= (p2.x-p1.x);
|
|
c= -(p1.x*a + p1.y*b);
|
|
if( (a*pos0.x + b*pos0.y + c) > 0) return false;
|
|
// Line p2-p0.
|
|
a= -(p0.y-p2.y);
|
|
b= (p0.x-p2.x);
|
|
c= -(p2.x*a + p2.y*b);
|
|
if( (a*pos0.x + b*pos0.y + c) > 0) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
bool CVisualCollisionEntity::triangleIntersect(CTriangle &tri, const CVector &pos0, const CVector &pos1, CVector &hit)
|
|
{
|
|
CVector &p0= tri.V0;
|
|
CVector &p1= tri.V1;
|
|
CVector &p2= tri.V2;
|
|
|
|
bool ok= false;
|
|
if( _GroundMode && triangleIntersect2DGround(tri, pos0) )
|
|
ok= true;
|
|
if(!ok && _CeilMode && triangleIntersect2DCeil(tri, pos0) )
|
|
ok= true;
|
|
if(!ok)
|
|
return false;
|
|
|
|
|
|
// Compute the possible height.
|
|
CVector tmp;
|
|
// build the plane
|
|
CPlane plane;
|
|
plane.make (p0, p1, p2);
|
|
// intersect the vertical line with the plane.
|
|
tmp= plane.intersect(pos0, pos1);
|
|
|
|
float h= tmp.z;
|
|
// Test if it would fit in the wanted field.
|
|
if(h>pos1.z) return false;
|
|
if(h<pos0.z) return false;
|
|
|
|
// OK!!
|
|
// For cache completness, ensure that X and Y don't move, take same XY than pos0
|
|
hit.x= pos0.x;
|
|
hit.y= pos0.y;
|
|
hit.z= h;
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CVisualCollisionEntity::testComputeLandscape(const CVector &pos)
|
|
{
|
|
// if new position is out of the bbox surounding the entity.
|
|
if(_CurrentBBoxValidity.getHalfSize()==CVector::Null || !_CurrentBBoxValidity.include(pos))
|
|
{
|
|
// must recompute the data around the entity.
|
|
doComputeLandscape(pos);
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
void CVisualCollisionEntity::doComputeLandscape(const CVector &pos)
|
|
{
|
|
sint i;
|
|
|
|
// setup new bbox.
|
|
//==================
|
|
// compute the bbox which must includes the patchQuadBlocks
|
|
CAABBox bboxToIncludePatchs;
|
|
bboxToIncludePatchs.setCenter(pos);
|
|
bboxToIncludePatchs.setHalfSize(CVector(BBoxRadius, BBoxRadius, BBoxRadiusZ));
|
|
// setup the _CurrentBBoxValidity with same values, but BBoxRadiusZ/2
|
|
_CurrentBBoxValidity.setCenter(pos);
|
|
_CurrentBBoxValidity.setHalfSize(CVector(BBoxRadius, BBoxRadius, BBoxRadiusZ/2));
|
|
|
|
|
|
// Search landscape blocks which are in the bbox.
|
|
//==================
|
|
_Owner->_Landscape->buildPatchBlocksInBBox(bboxToIncludePatchs, _TmpBlockIds);
|
|
|
|
|
|
|
|
// Recompute PatchQuadBlockcs.
|
|
//==================
|
|
// This parts try to keeps old patch blocks so they are not recomputed if they already here.
|
|
|
|
// sort PatchBlockIdent.
|
|
sort(_TmpBlockIds.begin(), _TmpBlockIds.end());
|
|
|
|
// Copy old array of ptr (ptr copy only).
|
|
_TmpPatchQuadBlocks= _PatchQuadBlocks;
|
|
|
|
// allocate dest array.
|
|
_PatchQuadBlocks.resize(_TmpBlockIds.size());
|
|
|
|
// Traverse all current patchBlocks, deleting old ones no longer needed, and creating new ones.
|
|
// this algorithm suppose both array are sorted.
|
|
uint iOld=0;
|
|
// parse until dest is filled.
|
|
for(i=0; i<(sint)_PatchQuadBlocks.size();)
|
|
{
|
|
// get requested new BlockIdent.
|
|
CPatchBlockIdent newBi= _TmpBlockIds[i];
|
|
|
|
// get requested old BlockIdent in the array.
|
|
bool oldEnd= false;
|
|
CPatchBlockIdent oldBi;
|
|
if(iOld==_TmpPatchQuadBlocks.size())
|
|
oldEnd= true;
|
|
else
|
|
oldBi= _TmpPatchQuadBlocks[iOld]->PatchBlockId;
|
|
|
|
// if no more old blocks, or if new Block is < than current, we must create a new block, and insert it.
|
|
if(oldEnd || newBi < oldBi)
|
|
{
|
|
// allocate the patch block.
|
|
_PatchQuadBlocks[i]= _Owner->newPatchQuadBlock();
|
|
// fill the patch block.
|
|
_PatchQuadBlocks[i]->PatchBlockId= _TmpBlockIds[i];
|
|
_Owner->_Landscape->fillPatchQuadBlock(*_PatchQuadBlocks[i]);
|
|
|
|
// next new patch block.
|
|
i++;
|
|
}
|
|
// else if current new Block is same than old block, just copy ptr.
|
|
else if(newBi==oldBi)
|
|
{
|
|
// just copy ptr with the old one.
|
|
_PatchQuadBlocks[i]= _TmpPatchQuadBlocks[iOld];
|
|
|
|
// next new and old patch block.
|
|
i++;
|
|
iOld++;
|
|
}
|
|
// else, this old block is no longer used, delete it.
|
|
else
|
|
{
|
|
_Owner->deletePatchQuadBlock(_TmpPatchQuadBlocks[iOld]);
|
|
// next old patch block.
|
|
iOld++;
|
|
}
|
|
}
|
|
// Here, must delete old blocks not yet processed.
|
|
for(;iOld<_TmpPatchQuadBlocks.size(); iOld++)
|
|
{
|
|
_Owner->deletePatchQuadBlock(_TmpPatchQuadBlocks[iOld]);
|
|
}
|
|
_TmpPatchQuadBlocks.clear();
|
|
|
|
|
|
// Compute the quadGrid.
|
|
//==================
|
|
|
|
// Compute a delta so elt position for CLandscapeCollisionGrid are positive, and so fastFloor() used will work.
|
|
CVector delta;
|
|
// center the position on 0.
|
|
// floor may be important for precision when the delta is applied.
|
|
delta.x= (float)floor(-pos.x);
|
|
delta.y= (float)floor(-pos.y);
|
|
delta.z= 0;
|
|
// We must always have positive values for patchBlocks vertices.
|
|
float val= (float)ceil(BBoxRadius + 256);
|
|
// NB: 256 is a security. Because of size of patchs, a value of 32 at max should be sufficient (64 for bigger patch (gfx))
|
|
// we are large because doesn't matter, the CLandscapeCollisionGrid tiles.
|
|
delta.x+= val;
|
|
delta.y+= val;
|
|
|
|
// rebuild the quadGrid.
|
|
_LandscapeQuadGrid.build(_PatchQuadBlocks, delta);
|
|
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CVisualCollisionEntity::getStaticLightSetup(NLMISC::CRGBA sunAmbient, const CVector &pos,
|
|
std::vector<CPointLightInfluence> &pointLightList, uint8 &sunContribution, NLMISC::CRGBA &localAmbient)
|
|
{
|
|
// Get Patch Triangle Under Us
|
|
CVector res;
|
|
CTrianglePatch *tri= getPatchTriangleUnderUs(pos, res);
|
|
|
|
// For now, no special ambient support on landscape => take sunAmbient
|
|
localAmbient= sunAmbient;
|
|
|
|
// result. NB: if not found, dot not modify.
|
|
if( tri )
|
|
{
|
|
// compute UV for position.
|
|
CUV uv;
|
|
computeUvForPos(*tri, pos, uv);
|
|
|
|
// get the sunContribution
|
|
sunContribution= _Owner->_Landscape->getLumel(tri->PatchId, uv);
|
|
// see getStaticLightSetup.
|
|
sunContribution= _Owner->_SunContributionLUT[sunContribution];
|
|
|
|
// append any lights of interest.
|
|
_Owner->_Landscape->appendTileLightInfluences(tri->PatchId, uv, pointLightList);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// Suppose full Sun Contribution, and don't add any pointLight
|
|
sunContribution= 255;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CVisualCollisionEntity::getSurfaceInfo(const CVector &pos, CSurfaceInfo &surfaceInfo)
|
|
{
|
|
H_AUTO( NL3D_CVisualCollisionEntity_getSurfaceInfo )
|
|
// Get Patch Triangle Under Us
|
|
CVector res;
|
|
CTrianglePatch *tri= getPatchTriangleUnderUs(pos, res);
|
|
|
|
// result. NB: if not found, dot not modify.
|
|
if( tri )
|
|
{
|
|
// compute UV for position.
|
|
CUV uv;
|
|
computeUvForPos(*tri, pos, uv);
|
|
|
|
// get the tileelement
|
|
CTileElement *tileElm = _Owner->_Landscape->getTileElement(tri->PatchId, uv);
|
|
if (tileElm)
|
|
{
|
|
// Valid tile ?
|
|
uint16 tileId = tileElm->Tile[0];
|
|
if (tileId != NL_TILE_ELM_LAYER_EMPTY)
|
|
{
|
|
// The tilebank
|
|
CTileBank &tileBank = _Owner->_Landscape->TileBank;
|
|
|
|
// Get xref info for this tile
|
|
int tileSet;
|
|
int number;
|
|
CTileBank::TTileType type;
|
|
tileBank.getTileXRef ((int)tileId, tileSet, number, type);
|
|
|
|
// Get the tileset from layer 0
|
|
const CTileSet* tileSetPtr = tileBank.getTileSet (tileSet);
|
|
|
|
// Fill the surface
|
|
surfaceInfo.UserSurfaceData = tileSetPtr->SurfaceData;
|
|
|
|
// Ok
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CVisualCollisionEntity::displayDebugGrid(IDriver &drv) const
|
|
{
|
|
// static to not reallocate each frame
|
|
static CMaterial mat;
|
|
static bool inited= false;
|
|
if(!inited)
|
|
{
|
|
inited= true;
|
|
mat.initUnlit();
|
|
}
|
|
static vector<CLine> lineList;
|
|
lineList.clear();
|
|
|
|
// Build lines for all patch quad blocks.
|
|
for(uint i=0;i<_PatchQuadBlocks.size();i++)
|
|
{
|
|
CPatchQuadBlock &pqb= *_PatchQuadBlocks[i];
|
|
// Parse all quads of this patch block.
|
|
CPatchBlockIdent &pbid= pqb.PatchBlockId;
|
|
for(uint t= pbid.T0; t<pbid.T1; t++)
|
|
{
|
|
for(uint s= pbid.S0; s<pbid.S1; s++)
|
|
{
|
|
// compute quad coordinate in pqb.
|
|
uint sd0= (s-pbid.S0);
|
|
uint td0= (t-pbid.T0);
|
|
uint sd1= sd0+1;
|
|
uint td1= td0+1;
|
|
// get 4 vertex coord of quad
|
|
const CVector &p0= pqb.Vertices[sd0 + td0*NL_PATCH_BLOCK_MAX_VERTEX];
|
|
const CVector &p1= pqb.Vertices[sd0 + td1*NL_PATCH_BLOCK_MAX_VERTEX];
|
|
const CVector &p2= pqb.Vertices[sd1 + td1*NL_PATCH_BLOCK_MAX_VERTEX];
|
|
const CVector &p3= pqb.Vertices[sd1 + td0*NL_PATCH_BLOCK_MAX_VERTEX];
|
|
|
|
// build the 5 lines
|
|
lineList.push_back(CLine(p0, p1));
|
|
lineList.push_back(CLine(p1, p2));
|
|
lineList.push_back(CLine(p2, p3));
|
|
lineList.push_back(CLine(p3, p0));
|
|
lineList.push_back(CLine(p0, p2));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Display the lines.
|
|
CDRU::drawLinesUnlit(lineList, mat, drv);
|
|
}
|
|
|
|
|
|
|
|
} // NL3D
|