// 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/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 CVisualCollisionEntity::_TmpBlockIds; vector 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 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= 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(sqrdistNext; } // found ?? if(sqrBestDistV1-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_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 &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 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