khanat-opennel-code/code/nel/src/3d/mini_col.cpp

480 lines
12 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/mini_col.h"
#include "nel/misc/aabbox.h"
#include "nel/3d/quad_grid.h"
using namespace NLMISC;
using namespace std;
namespace NL3D
{
static const sint QuadDepth= 10;
// Element for grid lookup.
static const sint GridSize=512;
static const float GridEltSize=2;
// ***************************************************************************
CMiniCol::CMiniCol()
{
_RadMin= 100;
_RadMax= 125;
_Grid.create(GridSize, GridEltSize);
}
// ***************************************************************************
void CMiniCol::addFaces(const std::vector<CTriangle> &faces, uint16 zoneId, uint16 patchId)
{
for(sint i=0;i<(sint)faces.size();i++)
{
const CTriangle &f= faces[i];
CAABBox box;
box.setCenter(f.V0);
box.extend(f.V1);
box.extend(f.V2);
CFace node;
node.Face= f;
node.Plane.make(f.V0, f.V1, f.V2);
node.ZoneId= zoneId;
node.PatchId=patchId;
_Grid.insert(box.getMin(), box.getMax(), node);
}
}
// ***************************************************************************
void CMiniCol::addLandscapePart(uint16 zoneId, uint16 patchId)
{
vector<CTriangle> faces;
_Landscape->buildCollideFaces(zoneId, patchId, faces);
addFaces(faces, zoneId, patchId);
}
// ***************************************************************************
void CMiniCol::removeLandScapePart(uint16 zoneId, uint16 patchId, const CBSphere &sphere)
{
// Build the AAbox which englobe the bsphere of the patch.
CAABBox bb;
bb.setCenter(sphere.Center);
float l= sphere.Radius;
bb.setHalfSize(CVector(l,l,l));
// For optimisation, select only faces which are IN the bbox of the patch.
_Grid.select(bb.getMin(), bb.getMax());
CQuadGrid<CFace>::CIterator iFace;
for(iFace= _Grid.begin();iFace!=_Grid.end();)
{
if((*iFace).isFromPatch(zoneId, patchId))
iFace= _Grid.erase(iFace);
else
iFace++;
}
}
// ***************************************************************************
void CMiniCol::init(CLandscape *land, float radMin, float radDelta)
{
_Landscape= land;
_RadMin= radMin;
_RadMax= radMin+radDelta;
}
// ***************************************************************************
void CMiniCol::addZone(uint16 zoneId)
{
CZoneIdent newZone;
// landscape must have been inited.
nlassert(_Landscape);
const CZone *zone= _Landscape->getZone(zoneId);
// zone must be loaded into landscape.
nlassert(zone);
// Fill the newzone.
newZone.ZoneId= zoneId;
newZone.Sphere.Center= zone->getZoneBB().getCenter();
newZone.Sphere.Radius= zone->getZoneBB().getRadius();
newZone.Patchs.resize(zone->getNumPatchs());
for(sint i=0;i<zone->getNumPatchs();i++)
{
newZone.Patchs[i].Sphere= zone->getPatchBSphere(i);
}
// Add it to the set (if not already done...).
_Zones.insert(newZone);
}
// ***************************************************************************
void CMiniCol::removeZone(uint16 zoneId)
{
CZoneIdent delZone;
// First, delete all patch from the grid.
//=======================================
// Fill the key part only.
delZone.ZoneId= zoneId;
// Find the zone (or quit).
TZoneSet::iterator itZone;
itZone= _Zones.find(delZone);
if(itZone==_Zones.end())
return;
CZoneIdent &zone= const_cast<CZoneIdent&>(*itZone);
for(sint i=0;i<(sint)zone.Patchs.size();i++)
{
CPatchIdent &pa= zone.Patchs[i];
if(pa.Inserted)
{
// Reject the patch.
removeLandScapePart(uint16(zone.ZoneId), uint16(i), pa.Sphere);
pa.Inserted= false;
zone.NPatchInserted--;
}
}
// Then, delete it.
//=================
_Zones.erase(delZone);
}
// ***************************************************************************
void CMiniCol::setCenter(const CVector& center)
{
CBSphere BMin(center, _RadMin), BMax(center, _RadMax);
// For all zones, test if must insert patchs..
TZoneSet::iterator itZone;
for(itZone= _Zones.begin();itZone!=_Zones.end();itZone++)
{
CZoneIdent &zone= const_cast<CZoneIdent&>(*itZone);
// Tests must be done in 2D...
BMin.Center.z= zone.Sphere.Center.z;
BMax.Center.z= zone.Sphere.Center.z;
// Must test first if the zone is IN the area.
//=============================================
bool zoneIn= false;
if(zone.NPatchInserted==0)
{
if(BMin.intersect(zone.Sphere))
zoneIn= true;
}
else
zoneIn= true;
// Then for all patchs, must test if the patch must be inserted, or rejected.
//=============================================
if(zoneIn)
{
for(sint i=0;i<(sint)zone.Patchs.size();i++)
{
CPatchIdent &pa= zone.Patchs[i];
// Tests must be done in 2D...
BMin.Center.z= pa.Sphere.Center.z;
BMax.Center.z= pa.Sphere.Center.z;
if(pa.Inserted)
{
// Reject the patch, if entirely OUT the max radius.
if(!BMax.intersect(pa.Sphere))
{
removeLandScapePart(uint16(zone.ZoneId), uint16(i), pa.Sphere);
pa.Inserted= false;
zone.NPatchInserted--;
}
}
else
{
// Insert the pacth, if only partially IN the min radius.
if(BMin.intersect(pa.Sphere))
{
addLandscapePart(uint16(zone.ZoneId), uint16(i));
pa.Inserted= true;
zone.NPatchInserted++;
}
}
}
}
}
}
// ***************************************************************************
bool CMiniCol::snapToGround(CVector &pos, float hup, float hbot)
{
CVector b1,b2;
bool found=false;
float height=0.f;
// Select quad nodes which contains pos.
b1=b2=pos;
b1.z-= hbot;
b2.z+= hup;
// Select.
_Grid.select(b1,b2);
// For each face, test if it is under pos, then test if height is correct.
CQuadGrid<CFace>::CIterator iFace;
for(iFace= _Grid.begin();iFace!=_Grid.end();iFace++)
{
CTriangle &pFace= (*iFace).Face;
CPlane &pPlane= (*iFace).Plane;
// Order is important.
CVector &p0= pFace.V0;
CVector &p1= pFace.V1;
CVector &p2= pFace.V2;
// TOIMP: This is VERY SLOW!!! (hope that the quadtree will help, but it still very slow...).
// Yoyo Debug, test, if the point may be IN the bbox.
/*CAABBox bbFace;
bbFace.setCenter(p0);
bbFace.extend(p1);
bbFace.extend(p2);
CVector bext=p0;
bext.z= maxof(p0.z, p1.z, p2.z)+hbot;
bbFace.extend(bext);
bext.z= minof(p0.z, p1.z, p2.z)-hup;
bbFace.extend(bext);
if(!bbFace.include(pos))
continue;*/
// 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*pos.x + b*pos.y + c) < 0) continue;
// Line p1-p2.
a= -(p2.y-p1.y);
b= (p2.x-p1.x);
c= -(p1.x*a + p1.y*b);
if( (a*pos.x + b*pos.y + c) < 0) continue;
// Line p2-p0.
a= -(p0.y-p2.y);
b= (p0.x-p2.x);
c= -(p2.x*a + p2.y*b);
if( (a*pos.x + b*pos.y + c) < 0) continue;
// Compute the possible height.
CVector tmp;
// intersect the vertical line with the plane.
tmp= pPlane.intersect(pos, pos-CVector(0,0,100));
/*
// CTriangle intersect() method.
CVector tmp;
if(pFace.intersect(b1, b2, tmp, pPlane))
*/
{
float h= tmp.z;
// Test if it would fit in the wanted field.
if(h>pos.z+hup) continue;
if(h<pos.z-hbot) continue;
// OK!!
if(!found)
{
found=true;
height=h;
}
else
{
height= max(height,h);
}
}
}
if(found)
pos.z= height;
return found;
}
// ***************************************************************************
bool CMiniCol::getGroundNormal(const CVector &pos, CVector &normal, float hup, float hbot)
{
CVector b1,b2;
bool found=false;
float height=0.0;
// Select quad nodes which contains pos.
b1=b2=pos;
b1.z-= hbot;
b2.z+= hup;
// Select.
_Grid.select(b1,b2);
// For each face, test if it is under pos, then test if height is correct.
CQuadGrid<CFace>::CIterator iFace;
for(iFace= _Grid.begin();iFace!=_Grid.end();iFace++)
{
CTriangle &pFace= (*iFace).Face;
CPlane &pPlane= (*iFace).Plane;
// Order is important.
CVector &p0= pFace.V0;
CVector &p1= pFace.V1;
CVector &p2= pFace.V2;
// TOIMP: This is VERY SLOW!!! (hope that the quadtree will help, but it still very slow...).
// Yoyo Debug, test, if the point may be IN the bbox.
CAABBox bbFace;
bbFace.setCenter(p0);
bbFace.extend(p1);
bbFace.extend(p2);
CVector bext=p0;
bext.z= maxof(p0.z, p1.z, p2.z)+hbot;
bbFace.extend(bext);
bext.z= minof(p0.z, p1.z, p2.z)-hup;
bbFace.extend(bext);
if(!bbFace.include(pos))
continue;
// 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*pos.x + b*pos.y + c) < 0) continue;
// Line p1-p2.
a= -(p2.y-p1.y);
b= (p2.x-p1.x);
c= -(p1.x*a + p1.y*b);
if( (a*pos.x + b*pos.y + c) < 0) continue;
// Line p2-p0.
a= -(p0.y-p2.y);
b= (p0.x-p2.x);
c= -(p2.x*a + p2.y*b);
if( (a*pos.x + b*pos.y + c) < 0) continue;
// Compute the possible height.
CVector tmp;
// intersect the vertical line with the plane.
tmp= pPlane.intersect(pos, pos-CVector(0,0,100));
float h= tmp.z;
// Test if it would fit in the wanted field.
if(h>pos.z+hup) continue;
if(h<pos.z-hbot) continue;
// OK!!
if(!found)
{
found=true;
height=h;
normal= pPlane.getNormal();
}
else
{
if(h>height)
{
normal= pPlane.getNormal();
height= h;
}
}
}
return found;
}
// ***************************************************************************
bool CMiniCol::testMove(const CVector &prec, CVector &cur)
{
CVector dir= cur-prec;
dir.normalize();
// Angle max.
float anglemax= 65; // 65 degrees.
anglemax= (float)tan( anglemax*Pi/180);
// Must not go to near of a wall.
CVector test= cur+dir*0.5;
float norm= (test-prec).norm();
norm*=anglemax;
if(!snapToGround(test, norm, norm))
{
cur= prec;
return false;
}
else
{
// Must test and snap the current position.
norm= (cur-prec).norm();
norm*=anglemax;
if(!snapToGround(cur, norm, norm))
{
cur= prec;
return false;
}
}
return true;
}
// ***************************************************************************
void CMiniCol::getFaces(std::vector<CTriangle> &triresult, const CAABBox &bbox)
{
triresult.clear();
// Select.
_Grid.select(bbox.getMin(),bbox.getMax());
// For each face, test if it is under pos, then test if height is correct.
CQuadGrid<CFace>::CIterator iFace;
for(iFace= _Grid.begin();iFace!=_Grid.end();iFace++)
{
CTriangle &face= (*iFace).Face;
if(bbox.intersect(face.V0, face.V1, face.V2))
{
triresult.push_back(face);
}
}
}
} // NL3D