// 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/camera_col.h" #include "nel/misc/matrix.h" #include "nel/misc/triangle.h" using namespace std; using namespace NLMISC; namespace NL3D { // *************************************************************************** const float NL3D_CameraSmoothRadiusFactor= 4; const float NL3D_CameraSmoothNumZSample= 20; const float NL3D_CameraSmoothNumAngleSample= 10; // *************************************************************************** CCameraCol::CCameraCol() { } // *************************************************************************** void CCameraCol::build(const CVector &start, const CVector &end, float radius, bool cone) { // copy _Start= start; _End= end; _Radius= radius; _Cone= cone; _SimpleRay= false; // For camera smoothing float maxRadiusFactor= NL3D_CameraSmoothRadiusFactor; // not a Cone? => no smoothing if(!_Cone) maxRadiusFactor= 1; // **** Compute Camera smooth infos _MaxRadius= radius * maxRadiusFactor; _MinRadiusProj= _Radius / (end-start).norm(); _MaxRadiusProj= _MaxRadius / (end-start).norm(); _RayNorm= (end-start).normed(); _RayLen= (end-start).norm(); _OODeltaRadiusProj= 0; if(_MaxRadiusProj>_MinRadiusProj) _OODeltaRadiusProj= 1.f / (_MaxRadiusProj-_MinRadiusProj); // **** build the pyramid, with MaxRadius // approximate with a box CMatrix mat; // Precision note: make the pyramid local to Start mat.setRot(CVector::I, (start-end).normed(), CVector::K); mat.normalize(CMatrix::YZX); // build the start 4 points CVector ps[4]; // cone or cylinder? if(cone) { _NPlanes= 5; // local to start! ps[0]= CVector::Null; ps[1]= CVector::Null; ps[2]= CVector::Null; ps[3]= CVector::Null; } else { _NPlanes= 6; // local to start! ps[0]= mat * CVector(_MaxRadius, 0, -_MaxRadius); ps[1]= mat * CVector(_MaxRadius, 0, _MaxRadius); ps[2]= mat * CVector(-_MaxRadius, 0, _MaxRadius); ps[3]= mat * CVector(-_MaxRadius, 0, -_MaxRadius); } // build the end 4 points CVector pe[4]; // local to start! mat.setPos(end-start); pe[0]= mat * CVector(_MaxRadius, 0, -_MaxRadius); pe[1]= mat * CVector(_MaxRadius, 0, _MaxRadius); pe[2]= mat * CVector(-_MaxRadius, 0, _MaxRadius); pe[3]= mat * CVector(-_MaxRadius, 0, -_MaxRadius); // try to roder for optimisation // left/right _Pyramid[0].make(ps[3], pe[3], pe[2]); _Pyramid[1].make(ps[1], pe[1], pe[0]); // back _Pyramid[2].make(pe[0], pe[1], pe[2]); // top-bottom _Pyramid[3].make(ps[2], pe[2], pe[1]); _Pyramid[4].make(ps[0], pe[0], pe[3]); // front if not cone if(!cone) _Pyramid[5].make(ps[0], ps[2], ps[1]); // **** build the bbox _BBox.setCenter(start); _BBox.extend(end); // enlarge a bit for radius _BBox.setHalfSize(_BBox.getHalfSize()+CVector(_MaxRadius, _MaxRadius, _MaxRadius)); } // *************************************************************************** void CCameraCol::buildRay(const CVector &start, const CVector &end) { // copy _Start= start; _End= end; _Radius= 0.f; _Cone= false; _SimpleRay= true; // compute infos _MaxRadius= 0.f; _MinRadiusProj= 0.f; _MaxRadiusProj= 0.f; _RayNorm= (end-start).normed(); _RayLen= (end-start).norm(); _OODeltaRadiusProj= 0; // Don't need to build the pyramids here // **** build the bbox _BBox.setCenter(start); _BBox.extend(end); // enlarge a bit for radius _BBox.setHalfSize(_BBox.getHalfSize()+CVector(0.01f, 0.01f, 0.01f)); } // *************************************************************************** void CCameraCol::setApplyMatrix(const CCameraCol &other, const NLMISC::CMatrix &matrix) { // get parameters modified by matrix CVector start= matrix * other._Start; CVector end= matrix * other._End; float radius= other._Radius; // scale the radius if(matrix.hasScalePart()) { // get the uniform scale if(matrix.hasScaleUniform()) radius*= matrix.getScaleUniform(); // Tricky code, deduce a uniform scale. Should not arise else { float meanScale= matrix.getI().norm() + matrix.getJ().norm() + matrix.getK().norm(); meanScale/= 3; radius*= meanScale; } } // rebuild build(start, end, radius, other._Cone); } // *************************************************************************** void CCameraCol::minimizeDistanceAgainstTri(const CVector &p0, const CVector &p1, const CVector &p2, float &sqrMinDist) { // If the camera collision is actually a ray test, use a simpler method if(_SimpleRay) { intersectRay(p0, p1, p2, sqrMinDist); return; } // Else compute the distance with a smoother way. CVector *pIn= _ArrayIn; CVector *pOut= _ArrayOut; // **** clip triangle against the pyramid // build the triangle, local to start for precision problems pIn[0]= p0 - _Start; pIn[1]= p1 - _Start; pIn[2]= p2 - _Start; sint nVert= 3; // clip for(uint i=0;i<_NPlanes;i++) { nVert= _Pyramid[i].clipPolygonBack(pIn, pOut, nVert); swap(pIn, pOut); if(!nVert) break; } // **** if clipped => collision if(nVert) { /* Polygon nearest distance to a point is: - the nearest distance of all vertices - or the nearest distance to the plane (if the project lies in the polygon) - or the nearest distance to each edge NB: testing only points works with low radius, but may fails in general case */ // compute first the poly min distance float sqrPolyMinDist= FLT_MAX; // **** get the nearest distance for all points (avoid precision problem if doing only edge ones) sint i; for(i=0;i0 && fStart0) { if(sign==-1) break; else sign=1; } else break; } // if succeed, minimize if(i==nVert) sqrPolyMinDist= sqrPlaneDist; } // **** Camera Smoothing: modulate according to angle of poly against cone // Camera smooth not enabled? if(_MaxRadiusProj<=_MinRadiusProj) { // then just take minum if(sqrPolyMinDist=0 if(z>0) pProj[i]= pIn[i] / z; else pProj[i]= CVector::Null; // make local pProj[i]-= _RayNorm; } // Compute perimeter of projected poly float perimeterProj= 0; for(i=0;i=0 if(z>0) proj= sample / z; else proj= CVector::Null; // make local proj-= _RayNorm; // **** compute the Cone Linear factor (like a spot light) float rayDist= proj.norm(); float angleFactor= (rayDist-_MinRadiusProj) * _OODeltaRadiusProj; clamp(angleFactor, 0.f, 1.f); // avoid C1 discontinuity when angleFactor==0 angleFactor= sqr(angleFactor); // **** modulate, but to a bigger value! (ie raylen) sqrDist= _RayLen * angleFactor + sqrtf(sqrDist) * (1-angleFactor); sqrDist= sqr(sqrDist); // if distance is lesser, take it if(sqrDist=0 if(z>0) pIn[i]= pIn[i] / z; else pIn[i]= CVector::Null; // make local pIn[i]-= _RayNorm; } // Compute now poly distance to the ray // If the ray intersect the poly this is 0!!! // else this is the min distance of each edge/vertex against CVector::Null // test poly inclusion sint sign= 0; for(i=0;i0) { if(sign==-1) break; else sign=1; } else break; } // if succeed, then the poly fully intersect the ray => just minimize if(i==nVert) sqrMinDist= sqrPolyMinDist; // else smooth distance! else { // must get min distance of each projected edge/vertex againt origin float sqrMinRayDist= FLT_MAX; // get min distance of each vertex against origin for(i=0;i0 && fStart=0 if(z>0) proj= sample / z; else proj= CVector::Null; // make local proj-= _RayNorm; // **** compute the Cone Linear factor (like a spot light) float rayDist= proj.norm(); float angleFactor= (rayDist-_MinRadiusProj) / (_MaxRadiusProj-_MinRadiusProj); clamp(angleFactor, 0.f, 1.f); // avoid C1 discontinuity when angleFactor==0 angleFactor= sqr(angleFactor); // **** modulate, but to a bigger value! (ie raylen) sqrDist= _RayLen * angleFactor + sqrtf(sqrDist) * (1-angleFactor); sqrDist= sqr(sqrDist); // if distance is lesser, take it if(sqrDist