1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "nel/3d/camera_col.h"
19 #include "nel/misc/matrix.h"
20 #include "nel/misc/triangle.h"
23 using namespace NLMISC
;
32 // ***************************************************************************
33 const float NL3D_CameraSmoothRadiusFactor
= 4;
34 const float NL3D_CameraSmoothNumZSample
= 20;
35 const float NL3D_CameraSmoothNumAngleSample
= 10;
37 // ***************************************************************************
38 CCameraCol::CCameraCol()
42 // ***************************************************************************
43 void CCameraCol::build(const CVector
&start
, const CVector
&end
, float radius
, bool cone
)
52 // For camera smoothing
53 float maxRadiusFactor
= NL3D_CameraSmoothRadiusFactor
;
55 // not a Cone? => no smoothing
59 // **** Compute Camera smooth infos
60 _MaxRadius
= radius
* maxRadiusFactor
;
61 _MinRadiusProj
= _Radius
/ (end
-start
).norm();
62 _MaxRadiusProj
= _MaxRadius
/ (end
-start
).norm();
63 _RayNorm
= (end
-start
).normed();
64 _RayLen
= (end
-start
).norm();
65 _OODeltaRadiusProj
= 0;
66 if(_MaxRadiusProj
>_MinRadiusProj
)
67 _OODeltaRadiusProj
= 1.f
/ (_MaxRadiusProj
-_MinRadiusProj
);
69 // **** build the pyramid, with MaxRadius
70 // approximate with a box
72 // Precision note: make the pyramid local to Start
73 mat
.setRot(CVector::I
, (start
-end
).normed(), CVector::K
);
74 mat
.normalize(CMatrix::YZX
);
75 // build the start 4 points
91 ps
[0]= mat
* CVector(_MaxRadius
, 0, -_MaxRadius
);
92 ps
[1]= mat
* CVector(_MaxRadius
, 0, _MaxRadius
);
93 ps
[2]= mat
* CVector(-_MaxRadius
, 0, _MaxRadius
);
94 ps
[3]= mat
* CVector(-_MaxRadius
, 0, -_MaxRadius
);
96 // build the end 4 points
99 mat
.setPos(end
-start
);
100 pe
[0]= mat
* CVector(_MaxRadius
, 0, -_MaxRadius
);
101 pe
[1]= mat
* CVector(_MaxRadius
, 0, _MaxRadius
);
102 pe
[2]= mat
* CVector(-_MaxRadius
, 0, _MaxRadius
);
103 pe
[3]= mat
* CVector(-_MaxRadius
, 0, -_MaxRadius
);
104 // try to roder for optimisation
106 _Pyramid
[0].make(ps
[3], pe
[3], pe
[2]);
107 _Pyramid
[1].make(ps
[1], pe
[1], pe
[0]);
109 _Pyramid
[2].make(pe
[0], pe
[1], pe
[2]);
111 _Pyramid
[3].make(ps
[2], pe
[2], pe
[1]);
112 _Pyramid
[4].make(ps
[0], pe
[0], pe
[3]);
115 _Pyramid
[5].make(ps
[0], ps
[2], ps
[1]);
117 // **** build the bbox
118 _BBox
.setCenter(start
);
120 // enlarge a bit for radius
121 _BBox
.setHalfSize(_BBox
.getHalfSize()+CVector(_MaxRadius
, _MaxRadius
, _MaxRadius
));
126 // ***************************************************************************
127 void CCameraCol::buildRay(const CVector
&start
, const CVector
&end
)
140 _RayNorm
= (end
-start
).normed();
141 _RayLen
= (end
-start
).norm();
142 _OODeltaRadiusProj
= 0;
144 // Don't need to build the pyramids here
146 // **** build the bbox
147 _BBox
.setCenter(start
);
149 // enlarge a bit for radius
150 _BBox
.setHalfSize(_BBox
.getHalfSize()+CVector(0.01f
, 0.01f
, 0.01f
));
154 // ***************************************************************************
155 void CCameraCol::setApplyMatrix(const CCameraCol
&other
, const NLMISC::CMatrix
&matrix
)
157 // get parameters modified by matrix
158 CVector start
= matrix
* other
._Start
;
159 CVector end
= matrix
* other
._End
;
160 float radius
= other
._Radius
;
163 if(matrix
.hasScalePart())
165 // get the uniform scale
166 if(matrix
.hasScaleUniform())
167 radius
*= matrix
.getScaleUniform();
168 // Tricky code, deduce a uniform scale. Should not arise
171 float meanScale
= matrix
.getI().norm() + matrix
.getJ().norm() + matrix
.getK().norm();
178 build(start
, end
, radius
, other
._Cone
);
182 // ***************************************************************************
183 void CCameraCol::minimizeDistanceAgainstTri(const CVector
&p0
, const CVector
&p1
, const CVector
&p2
, float &sqrMinDist
)
185 // If the camera collision is actually a ray test, use a simpler method
188 intersectRay(p0
, p1
, p2
, sqrMinDist
);
192 // Else compute the distance with a smoother way.
193 CVector
*pIn
= _ArrayIn
;
194 CVector
*pOut
= _ArrayOut
;
196 // **** clip triangle against the pyramid
197 // build the triangle, local to start for precision problems
203 for(uint i
=0;i
<_NPlanes
;i
++)
205 nVert
= _Pyramid
[i
].clipPolygonBack(pIn
, pOut
, nVert
);
211 // **** if clipped => collision
215 Polygon nearest distance to a point is:
216 - the nearest distance of all vertices
217 - or the nearest distance to the plane (if the project lies in the polygon)
218 - or the nearest distance to each edge
220 NB: testing only points works with low radius, but may fails in general case
223 // compute first the poly min distance
224 float sqrPolyMinDist
= FLT_MAX
;
226 // **** get the nearest distance for all points (avoid precision problem if doing only edge ones)
230 // NB: pIn[i] is already local to start
231 float sqrDist
= pIn
[i
].sqrnorm();
232 if(sqrDist
<sqrPolyMinDist
)
233 sqrPolyMinDist
= sqrDist
;
236 // **** get the nearest distance of the Start against each edge
239 const CVector
&v0
= pIn
[i
];
240 const CVector
&v1
= pIn
[(i
+1)%nVert
];
243 float fLen
= vDir
.sqrnorm(); // same as vDir * (v1 - v0)
244 // NB: Project CVector::Null, since we are local to start here!
245 float fStart
= vDir
* (-v0
);
246 // if start projection in the edge
247 if(fStart
>0 && fStart
<fLen
)
249 // compute distance to line
250 CVector proj
= v0
+ (fStart
/ fLen
) * vDir
;
251 // proj is local to Start
252 float sqrDist
= proj
.sqrnorm();
253 if(sqrDist
<sqrPolyMinDist
)
254 sqrPolyMinDist
= sqrDist
;
258 // **** get the nearest distance of the Start against the plane
259 // get the plane local to start
261 plane
.make(p0
-_Start
, p1
-_Start
, p2
-_Start
);
262 // plane * StartLocalToStart == plane * CVector::Null == plane.d !
263 float planeDist
= plane
.d
;
264 // need to do the poly inclusion test only if the plane dist is better than the vertices
265 float sqrPlaneDist
= sqr(planeDist
);
266 if(sqrPlaneDist
< sqrPolyMinDist
)
268 CVector normal
= plane
.getNormal();
270 // the projection of Start on the plane: StartLocalToStart +
271 CVector proj
= planeDist
* normal
;
272 // test poly inclusion
276 const CVector
&v0
= pIn
[i
];
277 const CVector
&v1
= pIn
[(i
+1)%nVert
];
278 float d
= ((v1
-v0
)^normal
)*(proj
-v0
);
293 // if succeed, minimize
295 sqrPolyMinDist
= sqrPlaneDist
;
298 // **** Camera Smoothing: modulate according to angle of poly against cone
299 // Camera smooth not enabled?
300 if(_MaxRadiusProj
<=_MinRadiusProj
)
302 // then just take minum
303 if(sqrPolyMinDist
<sqrMinDist
)
304 sqrMinDist
= sqrPolyMinDist
;
306 // Camera Smooth mode. if the unmodulated distance is lower than the current minDist,
307 // then this poly may be interesting, else don't have a chance
308 else if(sqrPolyMinDist
<sqrMinDist
)
310 float sampleZSize
= _RayLen
/ NL3D_CameraSmoothNumZSample
;
311 float sampleProjSize
= 2*_MaxRadiusProj
/ NL3D_CameraSmoothNumAngleSample
;
313 // **** Compute num Subdivision required
314 // To compute the number of subdivision, let's take the max of 2 req:
315 // the subdivision in Z (for Distance part of the function)
316 // the subdivision in Projection (for angle part of the function)
318 // Project all vertices to the plane
319 static CVector pProj
[3+MaxNPlanes
];
324 float z
= pIn
[i
] * _RayNorm
;
327 // cause of pyramid cliping, z should be >=0
329 pProj
[i
]= pIn
[i
] / z
;
331 pProj
[i
]= CVector::Null
;
336 // Compute perimeter of projected poly
337 float perimeterProj
= 0;
340 perimeterProj
+= (pProj
[(i
+1)%nVert
]-pProj
[i
]).norm();
343 // compute the number of subdivision required on Z
344 uint numSubdivZ
= (uint
)((maxZ
-minZ
) / sampleZSize
);
345 // suppose a full projected quad perimeter will require max samples
346 uint numSubdivAngle
= (uint
)(perimeterProj
/ (4*sampleProjSize
));
347 // the number of subdivision
348 uint numSubdiv
= max(numSubdivZ
, numSubdivAngle
);
349 numSubdiv
= max(numSubdiv
, (uint
)1);
350 float ooNumSubdiv
= 1.f
/ (float)numSubdiv
;
352 // **** Sample the polygon, to compute the minimum of the function
353 // for each tri of the polygon
354 for(sint tri
=0;tri
<nVert
-2;tri
++)
357 // optim: prediv by numSubdiv
358 lp
[0]= pIn
[0] * ooNumSubdiv
;
359 lp
[1]= pIn
[tri
+1] * ooNumSubdiv
;
360 lp
[2]= pIn
[tri
+2] * ooNumSubdiv
;
362 // sample using barycentric coordinates
363 for(uint i
=0;i
<=numSubdiv
;i
++)
365 for(uint j
=0;j
<=numSubdiv
-i
;j
++)
367 uint k
= numSubdiv
- i
- j
;
368 CVector sample
= lp
[0] * (float)i
+ lp
[1] * (float)j
+ lp
[2] * (float)k
;
370 // NB: sample is already local to start
371 float sqrDist
= sample
.sqrnorm();
373 // **** get the point projection
374 float z
= sample
* _RayNorm
;
376 // cause of pyramid cliping, z should be >=0
384 // **** compute the Cone Linear factor (like a spot light)
385 float rayDist
= proj
.norm();
386 float angleFactor
= (rayDist
-_MinRadiusProj
) * _OODeltaRadiusProj
;
387 clamp(angleFactor
, 0.f
, 1.f
);
388 // avoid C1 discontinuity when angleFactor==0
389 angleFactor
= sqr(angleFactor
);
391 // **** modulate, but to a bigger value! (ie raylen)
392 sqrDist
= _RayLen
* angleFactor
+ sqrtf(sqrDist
) * (1-angleFactor
);
393 sqrDist
= sqr(sqrDist
);
395 // if distance is lesser, take it
396 if(sqrDist
<sqrMinDist
)
406 // ***************************************************************************
407 void CCameraCol::intersectRay(const CVector
&p0
, const CVector
&p1
, const CVector
&p2
, float &sqrMinDist
)
409 // build the triangle and the plane from p0,p1,p2
415 plane
.make(p0
,p1
,p2
);
417 // If doesn't intersect, quit
419 if(!tri
.intersect(_Start
, _End
, hit
, plane
))
422 // Else compute the intersection distance factor
423 float f
= (hit
-_Start
) * _RayNorm
;
424 clamp(f
, 0.f
, _RayLen
);
425 // set the result if less
433 // Perspective Project each vertices on the plane normalized to the ray, at Start + rayNormed * 1
434 // And then make local to the Point Start + rayNormed * 1 (ie _RayNorm since we are local to Start!).
437 float z= pIn[i] * _RayNorm;
438 // cause of pyramid cliping, z should be >=0
442 pIn[i]= CVector::Null;
448 // Compute now poly distance to the ray
449 // If the ray intersect the poly this is 0!!!
450 // else this is the min distance of each edge/vertex against CVector::Null
451 // test poly inclusion
455 const CVector &v0= pIn[i];
456 const CVector &v1= pIn[(i+1)%nVert];
457 // _RayNorm should be the plane 's normal of the poly
458 float d = ((v1-v0)^_RayNorm)*(-v0);
472 // if succeed, then the poly fully intersect the ray => just minimize
474 sqrMinDist= sqrPolyMinDist;
475 // else smooth distance!
478 // must get min distance of each projected edge/vertex againt origin
479 float sqrMinRayDist= FLT_MAX;
481 // get min distance of each vertex against origin
484 float sqrDist= pIn[i].sqrnorm();
485 if(sqrDist<sqrMinRayDist)
486 sqrMinRayDist= sqrDist;
489 // get distance of each edge against origin
492 const CVector &v0= pIn[i];
493 const CVector &v1= pIn[(i+1)%nVert];
496 float fLen= vDir.sqrnorm(); // same as vDir * (v1 - v0)
497 // NB: Project CVector::Null
498 float fStart= vDir * (-v0);
499 // if origin projection in the edge
500 if(fStart>0 && fStart<fLen)
502 // compute distance to line
503 CVector proj= v0 + (fStart / fLen) * vDir;
504 // proj is local to Start
505 float sqrDist= proj.sqrnorm();
506 if(sqrDist<sqrMinRayDist)
507 sqrMinRayDist= sqrDist;
511 float minRayDist= sqrtf(sqrMinRayDist);
513 // OK! now we have the minRayDist
514 // compute the Cone Linear factor (like a spot light)
515 float angleFactor= (minRayDist-_MinRadiusProj) / (_MaxRadiusProj-_MinRadiusProj);
516 clamp(angleFactor, 0.f, 1.f);
517 // avoid C1 discontinuity when angleFactor==0
518 angleFactor= sqr(angleFactor);
520 // modulate, but to a bigger value! (ie raylen)
521 sqrPolyMinDist= _RayLen * angleFactor + sqrtf(sqrPolyMinDist) * (1-angleFactor);
522 sqrPolyMinDist= sqr(sqrPolyMinDist);
524 // then if the modified distance is still lower than minDist, set
525 if(sqrPolyMinDist<sqrMinDist)
526 sqrMinDist= sqrPolyMinDist;
531 const float sampleSize= 0.1f;
533 // Sample the triangle, to compute the minimum of the function
539 // compute max and min edge length
540 len[0]= (lp[1]-lp[0]).norm();
541 len[1]= (lp[2]-lp[1]).norm();
542 len[2]= (lp[0]-lp[2]).norm();
543 float meanLen= (len[0] + len[1] + len[2])/3;
544 // the number of subdivision
545 uint numSubdiv= meanLen / sampleSize;
546 numSubdiv= max(numSubdiv, (uint)1);
552 // sample using barycentric coordinates
553 for(uint i=0;i<=numSubdiv;i++)
555 for(uint j=0;j<=numSubdiv-i;j++)
557 uint k= numSubdiv - i - j;
558 CVector sample= lp[0] * i + lp[1] * j + lp[2] * k;
560 // NB: sample is already local to start
561 float sqrDist= sample.sqrnorm();
563 // **** get the point projection
564 float z= sample * _RayNorm;
566 // cause of pyramid cliping, z should be >=0
574 // **** compute the Cone Linear factor (like a spot light)
575 float rayDist= proj.norm();
576 float angleFactor= (rayDist-_MinRadiusProj) / (_MaxRadiusProj-_MinRadiusProj);
577 clamp(angleFactor, 0.f, 1.f);
578 // avoid C1 discontinuity when angleFactor==0
579 angleFactor= sqr(angleFactor);
581 // **** modulate, but to a bigger value! (ie raylen)
582 sqrDist= _RayLen * angleFactor + sqrtf(sqrDist) * (1-angleFactor);
583 sqrDist= sqr(sqrDist);
585 // if distance is lesser, take it
586 if(sqrDist<sqrMinDist)