Change Encyclo button name and macros icon
[ryzomcore.git] / nel / src / pacs / edge_collide.cpp
blobb25792d62506712fec549c75efd16ca17f44cc66
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2016 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdpacs.h"
22 #include "nel/pacs/edge_collide.h"
24 using namespace NLMISC;
25 using namespace std;
29 namespace NLPACS
33 static const float EdgeCollideEpsilon= 1e-5f;
36 // ***************************************************************************
37 void CEdgeCollide::make(const CVector2f &p0, const CVector2f &p1)
39 P0= p0;
40 P1= p1;
41 // translation axis of the edge.
42 Dir= P1-P0;
43 Dir.normalize();
44 A0= P0*Dir;
45 A1= P1*Dir;
46 // line equation.
47 Norm.x= Dir.y;
48 Norm.y= -Dir.x;
49 C= - P0*Norm;
53 // ***************************************************************************
54 CRational64 CEdgeCollide::testPointMove(const CVector2f &start, const CVector2f &end, TPointMoveProblem &moveBug)
57 To have a correct test (with no float precision problem):
58 - test first if there is collision beetween the 2 edges:
59 - test if first edge collide the other line.
60 - test if second edge collide the other line.
61 - if both true, yes, there is a collision.
62 - compute time of collision.
66 // *this must be a correct edge.
67 if(P0==P1)
69 moveBug= EdgeNull;
70 return -1;
73 // if no movement, no collision.
74 if(start==end)
75 return 1;
77 // NB those edges are snapped (1/256 for edgeCollide, and 1/1024 for start/end), so no float problem here.
78 // precision is 20 bits.
79 CVector2f normEdge;
80 CVector2f normMove;
81 CVector2f deltaEdge;
82 CVector2f deltaMove;
84 // compute normal of the edge (not normalized, because no need, and for precision problem).
85 deltaEdge= P1-P0;
86 normEdge.x= -deltaEdge.y;
87 normEdge.y= deltaEdge.x;
89 // compute normal of the movement (not normalized, because no need, and for precision problem).
90 deltaMove= end-start;
91 normMove.x= -deltaMove.y;
92 normMove.y= deltaMove.x;
94 // distance from points of movment against edge line. Use double, because of multiplication.
95 // precision is now 43 bits.
96 double moveD0= (double)normEdge.x*(double)(start.x-P0.x) + (double)normEdge.y*(double)(start.y-P0.y);
97 double moveD1= (double)normEdge.x*(double)(end.x -P0.x) + (double)normEdge.y*(double)(end.y -P0.y);
99 // distance from points of edge against movement line. Use double, because of multiplication.
100 // precision is now 43 bits.
101 double edgeD0= (double)normMove.x*(double)(P0.x-start.x) + (double)normMove.y*(double)(P0.y-start.y);
102 double edgeD1= (double)normMove.x*(double)(P1.x-start.x) + (double)normMove.y*(double)(P1.y-start.y);
105 // If both edges intersect lines (including endpoints), there is a collision, else none.
106 sint sgnMove0, sgnMove1;
107 sgnMove0= fsgn(moveD0);
108 sgnMove1= fsgn(moveD1);
110 // special case if the 2 edges lies on the same line.
111 if(sgnMove0==0 && sgnMove1==0)
113 // must test if there is a collision. if yes, problem.
114 // project all the points on the line of the edge.
115 // Use double because of multiplication. precision is now 43 bits.
116 double moveA0= (double)deltaEdge.x*(double)(start.x-P0.x) + (double)deltaEdge.y*(double)(start.y-P0.y);
117 double moveA1= (double)deltaEdge.x*(double)(end.x -P0.x) + (double)deltaEdge.y*(double)(end.y -P0.y);
118 double edgeA0= 0;
119 double edgeA1= (double)deltaEdge.x*(double)deltaEdge.x + (double)deltaEdge.y*(double)deltaEdge.y;
121 // Test is there is intersection (endpoints included). if yes, return -1. else return 1 (no collision at all).
122 if(moveA1>=edgeA0 && edgeA1>=moveA0)
124 moveBug= ParallelEdges;
125 return -1;
127 else
128 return 1;
131 // if on same side of the line=> there is no collision.
132 if( sgnMove0==sgnMove1)
133 return 1;
135 // test edge against move line.
136 sint sgnEdge0, sgnEdge1;
137 sgnEdge0= fsgn(edgeD0);
138 sgnEdge1= fsgn(edgeD1);
140 // should not have this case, because tested before with (sgnMove==0 && sgnMove1==0).
141 nlassert(sgnEdge0!=0 || sgnEdge1!=0);
144 // if on same side of the line, no collision against this edge.
145 if( sgnEdge0==sgnEdge1 )
146 return 1;
148 // Here the edges intersect, but ensure that there is no limit problem.
149 if(sgnEdge0==0 || sgnEdge1==0)
151 moveBug= TraverseEndPoint;
152 return -1;
154 else if(sgnMove1==0)
156 moveBug= StopOnEdge;
157 return -1;
159 else if(sgnMove0==0)
161 // this should not arrive.
162 moveBug= StartOnEdge;
163 return -1;
167 // Here, there is a normal collision, just compute it.
168 // Because of Division, there is a precision lost in double. So compute a CRational64.
169 // First, compute numerator and denominator in the highest precision. this is 1024*1024 because of prec multiplication.
170 double numerator= (0-moveD0)*1024*1024;
171 double denominator= (moveD1-moveD0)*1024*1024;
172 sint64 numeratorInt= (sint64)numerator;
173 sint64 denominatorInt= (sint64)denominator;
175 nlassert(numerator == numeratorInt);
176 nlassert(denominator == denominatorInt);
179 if (numerator != numeratorInt)
180 nlwarning("numerator(%f) != numeratorInt(%" NL_I64 "d)", numerator, numeratorInt);
181 if (denominator != denominatorInt)
182 nlwarning("denominator(%f) != denominatorInt(%" NL_I64 "d)", denominator, denominatorInt);
184 return CRational64(numeratorInt, denominatorInt);
188 // ***************************************************************************
189 static inline float testCirclePoint(const CVector2f &start, const CVector2f &delta, float radius, const CVector2f &point)
191 // factors of the qaudratic: at^2 + bt + c=0
192 float a,b,c;
193 float dta;
194 float r0, r1, res;
195 CVector2f relC, relV;
197 // As long as delta is not NULL (ensured in testCircleMove() ), this code should not generate Divide by 0.
199 // compute quadratic..
200 relC= start-point;
201 relV= delta;
202 a= relV.x*relV.x + relV.y*relV.y;
203 // a should be >0. BUT BECAUSE OF PRECISION PROBLEM, it may be ==0, and then cause
204 // divide by zero (because b may be near 0, but not 0)
205 if(a==0)
207 // in this case the move is very small. return 0 if the point is in the circle and if we go toward the point
208 if(relC.norm()<radius && relC*delta<0)
209 return 0;
210 else
211 return 1;
213 else
215 b= 2* (relC.x*relV.x + relC.y*relV.y);
216 c= relC.x*relC.x + relC.y*relC.y - radius*radius;
217 // compute delta of the quadratic.
218 dta= b*b - 4*a*c; // b^2-4ac
219 if(dta>=0)
221 dta= (float)sqrt(dta);
222 r0= (-b -dta)/(2*a);
223 r1= (-b +dta)/(2*a);
224 // since a>0, r0<=r1.
225 if(r0>r1)
226 swap(r0,r1);
227 // if r1 is negative, then we are out and go away from this point. OK.
228 if(r1<=0)
230 res= 1;
232 // if r0 is positive, then we may collide this point.
233 else if(r0>=0)
235 res= min(1.f, r0);
237 else // r0<0 && r1>0. the point is already in the sphere!!
239 //nlinfo("COL: Point problem: %.2f, %.2f. b=%.2f", r0, r1, b);
240 // we allow the movement only if we go away from this point.
241 // this is true if the derivative at t=0 is >=0 (because a>0).
242 if(b>0)
243 res= 1; // go out.
244 else
245 res=0;
248 else
250 // never hit this point along this movement.
251 res= 1;
255 return res;
259 // ***************************************************************************
260 float CEdgeCollide::testCircleMove(const CVector2f &start, const CVector2f &delta, float radius, CVector2f &normal)
262 // If the movement is NULL, return 1 (no collision!)
263 if( delta.isNull() )
264 return 1;
266 // distance from point to line.
267 double dist= start*Norm + C;
268 // projection of speed on normal.
269 double speed= delta*Norm;
271 // test if the movement is against the line or not.
272 bool sensPos= dist>0;
273 bool sensSpeed= speed>0;
275 // Does the point intersect the line?
276 dist= fabs(dist) - radius;
277 speed= fabs(speed);
278 if( dist > speed )
279 return 1;
281 // if not already in collision with the line, test when it collides.
282 // ===============================
283 if(dist>=0)
285 // if signs are equals, same side of the line, so we allow the circle to leave the line.
286 if(sensPos==sensSpeed )
287 return 1;
289 // if speed is 0, it means that movement is parralel to the line => never collide.
290 if(speed==0)
291 return 1;
293 // collide the line, at what time.
294 double t= dist/speed;
297 // compute the collision position of the Circle on the edge.
298 // this gives the center of the sphere at the collision point.
299 CVector2d proj= CVector2d(start) + CVector2d(delta)*t;
300 // must add radius vector.
301 proj+= Norm * (sensSpeed?radius:-radius);
302 // compute projection on edge.
303 double aProj= proj*Dir;
305 // if on the interval of the edge.
306 if( aProj>=A0 && aProj<=A1)
308 // collision occurs on interior of the edge. the normal to return is +- Norm.
309 if(sensPos) // if algebric distance of start position was >0.
310 normal= Norm;
311 else
312 normal= -Norm;
314 // return time of collision.
315 return (float)t;
318 // else, must test if circle collide segment at t=0. if yes, return 0.
319 // ===============================
320 else
322 // There is just need to test if projection of circle's center onto the line hit the segment.
323 // This is not a good test to know if a circle intersect a segment, but other cases are
324 // managed with test of endPoints of the segment after.
325 float aProj= start*Dir;
327 // if on the interval of the edge.
328 if( aProj>=A0 && aProj<=A1)
330 // if signs are equals, same side of the line, so we allow the circle to leave the edge.
331 /* Special case: do not allow to leave the edge if we are too much in the edge.
332 It is important for CGlobalRetriever::testCollisionWithCollisionChains() because of the
333 "SURFACEMOVE NOT DETECTED" Problem.
334 Suppose we can walk on this chain SA/SB (separate Surface A/SurfaceB). Suppose we are near this edge,
335 and on Surface SA, and suppose there is another chain SB/SC the circle collide with. If we
336 return 1 (no collision), SB/SC won't be detected (because only SA/?? chains will be tested) and
337 so the cylinder will penetrate SB/SC...
338 This case arise at best if chains SA/SB and chain SB/SC do an angle of 45deg
340 if(sensPos==sensSpeed && (-dist)<0.5*radius)
342 return 1;
344 else
346 // hit the interior of the edge, and sensPos!=sensSpeed. So must stop now!!
347 // collision occurs on interior of the edge. the normal to return is +- Norm.
348 if(sensPos) // if algebric distance of start position was >0.
349 normal= Norm;
350 else
351 normal= -Norm;
353 return 0;
358 // In this case, the Circle do not hit the edge on the interior, but may hit on borders.
359 // ===============================
360 // Then, we must compute collision sphere-points.
361 float tmin, ttmp;
362 // first point.
363 tmin= testCirclePoint(start, delta, radius, P0);
364 // second point.
365 ttmp= testCirclePoint(start, delta, radius, P1);
366 tmin= min(tmin, ttmp);
368 // if collision occurs, compute normal of collision.
369 if(tmin<1)
371 // to which point we collide?
372 CVector2f colPoint= tmin==ttmp? P1 : P0;
373 // compute position of the entity at collision.
374 CVector2f colPos= start + delta*tmin;
376 // and so we have this normal (the perpendicular of the tangent at this point).
377 normal= colPos - colPoint;
378 normal.normalize();
381 return tmin;
386 // ***************************************************************************
387 bool CEdgeCollide::testEdgeMove(const CVector2f &q0, const CVector2f &q1, const CVector2f &delta, float &tMin, float &tMax, bool &normalOnBox)
389 double a,b,cv,cc, d,e,f;
390 CVector2d tmp;
392 // compute D1 line equation of q0q1. bx - ay + c(t)=0, where c is function of time [0,1].
393 // ===========================
394 tmp= q1 - q0; // NB: along time, the direction doesn't change.
395 // Divide by norm()^2, so that a projection on this edge is true if the proj is in interval [0,1].
396 tmp/= tmp.sqrnorm();
397 a= tmp.x;
398 b= tmp.y;
399 // c= - q0(t)*CVector2d(b,-a). but since q0(t) is a function of time t (q0+delta*t), compute cv, and cc.
400 // so c= cv*t + cc.
401 cv= - CVector2d(b,-a)*delta;
402 cc= - CVector2d(b,-a)*q0;
404 // compute D2 line equation of P0P1. ex - dy + f=0.
405 // ===========================
406 tmp= P1 - P0;
407 // Divide by norm()^2, so that a projection on this edge is true if the proj is in interval [0,1].
408 tmp/= tmp.sqrnorm();
409 d= tmp.x;
410 e= tmp.y;
411 f= - CVector2d(e,-d)*P0;
414 // Solve system.
415 // ===========================
417 Compute the intersection I of 2 lines across time.
418 We have the system:
419 bx - ay + c(t)=0
420 ex - dy + f=0
422 which solve for:
423 det= ae-bd (0 <=> // lines)
424 x(t)= (d*c(t) - fa) / det
425 y(t)= (e*c(t) - fb) / det
428 // determinant of matrix2x2.
429 double det= a*e - b*d;
430 // if to near of 0. (take delta for reference of test).
431 if(det==0 || fabs(det)<delta.norm()*EdgeCollideEpsilon)
432 return false;
434 // intersection I(t)= pInt + vInt*t.
435 CVector2d pInt, vInt;
436 pInt.x= ( d*cc - f*a ) / det;
437 pInt.y= ( e*cc - f*b ) / det;
438 vInt.x= ( d*cv ) / det;
439 vInt.y= ( e*cv ) / det;
442 // Project Intersection.
443 // ===========================
445 Now, we project x,y onto each line D1 and D2, which gives u(t) and v(t), each one giving the parameter of
446 the parametric line function. When it is in [0,1], we are on the edge.
448 u(t)= (I(t)-q0(t)) * CVector2d(a,b) = uc + uv*t
449 v(t)= (I(t)-P0) * CVector2d(d,e) = vc + vv*t
451 double uc, uv;
452 double vc, vv;
453 // NB: q0(t)= q0+delta*t
454 uc= (pInt-q0) * CVector2d(a,b);
455 uv= (vInt-delta) * CVector2d(a,b);
456 vc= (pInt-P0) * CVector2d(d,e);
457 vv= (vInt) * CVector2d(d,e);
460 // Compute intervals.
461 // ===========================
463 Now, for each edge, compute time interval where parameter is in [0,1]. If intervals overlap, there is a collision.
465 double tu0, tu1, tv0, tv1;
466 // infinite interval.
467 bool allU=false, allV=false;
469 // compute time interval for u(t).
470 if(uv==0 || fabs(uv)<EdgeCollideEpsilon)
472 // The intersection does not move along D1. Always projected on u(t)=uc. so if in [0,1], OK, else never collide.
473 if(uc<0 || uc>1)
474 return false;
475 // else suppose "always valid".
476 tu0 =tu1= 0;
477 allU= true;
479 else
481 tu0= (0-uc)/uv; // t for u(t)=0
482 tu1= (1-uc)/uv; // t for u(t)=1
485 // compute time interval for v(t).
486 if(vv==0 || fabs(vv)<EdgeCollideEpsilon)
488 // The intersection does not move along D2. Always projected on v(t)=vc. so if in [0,1], OK, else never collide.
489 if(vc<0 || vc>1)
490 return false;
491 // else suppose "always valid".
492 tv0 =tv1= 0;
493 allV= true;
495 else
497 tv0= (0-vc)/vv; // t for v(t)=0
498 tv1= (1-vc)/vv; // t for v(t)=1
502 // clip intervals.
503 // ===========================
504 // order time interval.
505 if(tu0>tu1)
506 swap(tu0, tu1); // now, [tu0, tu1] represent the time interval where line D2 hit the edge D1.
507 if(tv0>tv1)
508 swap(tv0, tv1); // now, [tv0, tv1] represent the time interval where line D1 hit the edge D2.
510 normalOnBox= false;
511 if(!allU && !allV)
513 // if intervals do not overlap, no collision.
514 if(tu0>tv1 || tv0>tu1)
515 return false;
516 else
518 // compute intersection of intervals.
519 tMin= (float)max(tu0, tv0);
520 tMax= (float)min(tu1, tv1);
521 // if collision of edgeCollide against the bbox.
522 if(tv0>tu0)
523 normalOnBox= true;
526 else if(allU)
528 // intersection of Infinite and V interval.
529 tMin= (float)tv0;
530 tMax= (float)tv1;
531 // if collision of edgeCollide against the bbox.
532 normalOnBox= true;
534 else if(allV)
536 // intersection of Infinite and U interval.
537 tMin= (float)tu0;
538 tMax= (float)tu1;
540 else
542 // if allU && allV, this means delta is near 0, and so there is always collision.
543 tMin= -1000;
544 tMax= 1000;
547 return true;
551 // ***************************************************************************
552 float CEdgeCollide::testBBoxMove(const CVector2f &start, const CVector2f &delta, const CVector2f bbox[4], CVector2f &normal)
554 // distance from center to line.
555 float dist= start*Norm + C;
557 // test if the movement is against the line or not.
558 bool sensPos= dist>0;
559 // if signs are equals, same side of the line, so we allow the circle to leave the line.
560 /*if(sensPos==sensSpeed)
561 return 1;*/
564 // Else, do 4 test edge/edge, and return Tmin.
565 float tMin = 0.f, tMax = 0.f;
566 bool edgeCollided= false;
567 bool normalOnBox= false;
568 CVector2f boxNormal(0.f, 0.f);
569 for(sint i=0;i<4;i++)
571 float t0, t1;
572 bool nob;
573 CVector2f a= bbox[i];
574 CVector2f b= bbox[(i+1)&3];
576 // test move against this edge.
577 if(testEdgeMove(a, b, delta, t0, t1, nob))
579 if(edgeCollided)
581 tMin= min(t0, tMin);
582 tMax= max(t1, tMax);
584 else
586 edgeCollided= true;
587 tMin= t0;
588 tMax= t1;
591 // get normal of box against we collide.
592 if(tMin==t0)
594 normalOnBox= nob;
595 if(nob)
597 CVector2f dab;
598 // bbox must be CCW.
599 dab= b-a;
600 // the normal is computed so that the vector goes In the bbox.
601 boxNormal.x= -dab.y;
602 boxNormal.y= dab.x;
608 // if collision occurs,and int the [0,1] interval...
609 if(edgeCollided && tMin<1 && tMax>0)
611 // compute normal of collision.
612 if(normalOnBox)
614 // assume collsion is an endpoint of the edge against the bbox.
615 normal= boxNormal;
617 else
619 // assume collision occurs on interior of the edge. the normal to return is +- Norm.
620 if(sensPos) // if algebric distance of start position was >0.
621 normal= Norm;
622 else
623 normal= -Norm;
626 // compute time of collison.
627 if(tMin>0)
628 // return time of collision.
629 return tMin;
630 else
632 // The bbox is inside the edge, at t==0. test if it goes out or not.
633 // accept only if we are much near the exit than the enter.
634 /* NB: 0.2 is an empirical value "which works well". Normally, 1 is the good value, but because of the
635 "SURFACEMOVE NOT DETECTED" Problem (see testCircleMove()), we must be more restrictive.
637 if( tMax<0.2*(-tMin) )
638 return 1;
639 else
640 return 0;
643 else
644 return 1;
649 // ***************************************************************************
650 bool CEdgeCollide::testBBoxCollide(const CVector2f bbox[4])
652 // clip the edge against the edge of the bbox.
653 CVector2f p0= P0, p1= P1;
655 for(sint i=0; i<4; i++)
657 CVector2f a= bbox[i];
658 CVector2f b= bbox[(i+1)&3];
659 CVector2f delta= b-a, norm;
660 // sign is important. bbox is CCW. normal goes OUT the bbox.
661 norm.x= delta.y;
662 norm.y= -delta.x;
664 float d0= (p0-a)*norm;
665 float d1= (p1-a)*norm;
667 // if boths points are out this plane, no collision.
668 if( d0>0 && d1>0)
669 return false;
670 // if difference, must clip.
671 if( d0>0 || d1>0)
673 CVector2f intersect= p0 + (p1-p0)* ((0-d0)/(d1-d0));
674 if(d1>0)
675 p1= intersect;
676 else
677 p0= intersect;
681 // if a segment is still in the bbox, collision occurs.
682 return true;
687 } // NLPACS