// 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