1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2016 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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/>.
22 #include "nel/pacs/edge_collide.h"
24 using namespace NLMISC
;
33 static const float EdgeCollideEpsilon
= 1e-5f
;
36 // ***************************************************************************
37 void CEdgeCollide::make(const CVector2f
&p0
, const CVector2f
&p1
)
41 // translation axis of the edge.
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.
73 // if no movement, no collision.
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.
84 // compute normal of the edge (not normalized, because no need, and for precision problem).
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).
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
);
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
;
131 // if on same side of the line=> there is no collision.
132 if( sgnMove0
==sgnMove1
)
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
)
148 // Here the edges intersect, but ensure that there is no limit problem.
149 if(sgnEdge0
==0 || sgnEdge1
==0)
151 moveBug
= TraverseEndPoint
;
161 // this should not arrive.
162 moveBug
= StartOnEdge
;
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
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..
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)
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)
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
221 dta
= (float)sqrt(dta
);
224 // since a>0, r0<=r1.
227 // if r1 is negative, then we are out and go away from this point. OK.
232 // if r0 is positive, then we may collide this point.
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).
250 // never hit this point along this movement.
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!)
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
;
281 // if not already in collision with the line, test when it collides.
282 // ===============================
285 // if signs are equals, same side of the line, so we allow the circle to leave the line.
286 if(sensPos
==sensSpeed
)
289 // if speed is 0, it means that movement is parralel to the line => never collide.
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.
314 // return time of collision.
318 // else, must test if circle collide segment at t=0. if yes, return 0.
319 // ===============================
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
)
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.
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.
363 tmin
= testCirclePoint(start
, delta
, radius
, P0
);
365 ttmp
= testCirclePoint(start
, delta
, radius
, P1
);
366 tmin
= min(tmin
, ttmp
);
368 // if collision occurs, compute normal of collision.
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
;
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
;
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].
399 // c= - q0(t)*CVector2d(b,-a). but since q0(t) is a function of time t (q0+delta*t), compute cv, and 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 // ===========================
407 // Divide by norm()^2, so that a projection on this edge is true if the proj is in interval [0,1].
411 f
= - CVector2d(e
,-d
)*P0
;
415 // ===========================
417 Compute the intersection I of 2 lines across time.
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
)
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
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.
475 // else suppose "always valid".
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.
491 // else suppose "always valid".
497 tv0
= (0-vc
)/vv
; // t for v(t)=0
498 tv1
= (1-vc
)/vv
; // t for v(t)=1
503 // ===========================
504 // order time interval.
506 swap(tu0
, tu1
); // now, [tu0, tu1] represent the time interval where line D2 hit the edge D1.
508 swap(tv0
, tv1
); // now, [tv0, tv1] represent the time interval where line D1 hit the edge D2.
513 // if intervals do not overlap, no collision.
514 if(tu0
>tv1
|| tv0
>tu1
)
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.
528 // intersection of Infinite and V interval.
531 // if collision of edgeCollide against the bbox.
536 // intersection of Infinite and U interval.
542 // if allU && allV, this means delta is near 0, and so there is always collision.
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)
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
++)
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
))
591 // get normal of box against we collide.
600 // the normal is computed so that the vector goes In the bbox.
608 // if collision occurs,and int the [0,1] interval...
609 if(edgeCollided
&& tMin
<1 && tMax
>0)
611 // compute normal of collision.
614 // assume collsion is an endpoint of the edge against the bbox.
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.
626 // compute time of collison.
628 // return time of collision.
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
) )
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.
664 float d0
= (p0
-a
)*norm
;
665 float d1
= (p1
-a
)*norm
;
667 // if boths points are out this plane, no collision.
670 // if difference, must clip.
673 CVector2f intersect
= p0
+ (p1
-p0
)* ((0-d0
)/(d1
-d0
));
681 // if a segment is still in the bbox, collision occurs.