// NeL - MMORPG Framework // 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 . #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 &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 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::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;igetNumPatchs();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(*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(*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::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::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(hheight) { 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 &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::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