JNR
laCollider.cpp
1 /*
2 
3 Jump'n'Run Engine
4 http://www.atanaslaskov.com/jnr/
5 
6 BSD LICENSE
7 Copyright (c) 2007-2013, Atanas Laskov
8 All rights reserved.
9 
10 Redistribution and use in source and binary forms, with or without
11 modification, are permitted provided that the following conditions are met:
12 1. Redistributions of source code must retain the above copyright notice,
13 this list of conditions and the following disclaimer.
14 2. Redistributions in binary form must reproduce the above copyright notice,
15 this list of conditions and the following disclaimer in the documentation
16 and/or other materials provided with the distribution.
17 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 DISCLAIMED. IN NO EVENT SHALL ATANAS LASKOV BE LIABLE FOR ANY
21 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 
28 */
29 
30 //
31 // FILE: laCollider.cpp
32 // Collision detection
33 //
34 // Copyright (C) 2007-2013 Atanas Laskov, <latanas@gmail.com>
35 //
36 #include "stdafx.h"
37 #include "Core.h"
38 
39 // Find the distance to the intersection and the intersection point
40 // between a moving line and a static line
41 //
42 M_BOOL laCollider::intersection(const laLine2 &lnMovement, const laLine2 &lnCollision,
43  double *pDistance, laPoint2 *pIntersection)
44 {
45  PROFILE_COL(laCollider_intersection);
46  double k, k_ln;
47 
48  // Is there an intersection?
49  //
50  if( lnMovement.intersection(lnCollision, &k, &k_ln) )
51  {
52  //Get the point of intersection
53  *pIntersection = lnMovement.at(k);
54 
55  // Is the intersection within the bonds of the two line segments ?
56  //
57  if( (k>=0) && (k<=1) && (k_ln>=0) && (k_ln<=1) )
58  {
59  *pDistance = k * lnMovement.lenght_sq();
60  return M_TRUE;
61  }
62  }
63 
64  return M_FALSE;
65 }
66 
67 
68 // Project velocity on collision geometry
69 //
70 void laCollider::response(const laPoint2 &position, const laPoint2 &vector, const laLine2 &lnCollisionCauser,
71  laPoint2* pModifiedVector)
72 {
73  PROFILE_COL(laCollider_response);
74  static double k, k_ln;
75 
76  //Project the destination point on the collision line
77  //
78  laPoint2 ptDestination = position + vector;
79  laPoint2 normal = lnCollisionCauser.normal();
80  laLine2 lnNormalProjection(ptDestination, ptDestination + normal);
81 
82  lnCollisionCauser.intersection(lnNormalProjection, &k, &k_ln);
83  laPoint2 ptIntersection = lnCollisionCauser.at(k);
84 
85  //Modify the movement vector
86  *pModifiedVector = ptIntersection - position;
87  //*pModifiedVector += normal * M_NORMAL_RESPONSE;
88 }
89 
90 inline void safe_magin(const laLine2 &lnPositioned, laCollisionScenario *pScenario)
91 {
92  PROFILE_COL(laCollider_safe_magin);
93  laCollisionDomain *pDomain = pScenario->pFirstDomain;
94  unsigned nCurrentDomain = 0, i;
95 
96  laRect2 rectBoundingLine, rectBoundingDomain;
97  laLine2 *pDomainLine;
98 
99  static double d1, d2, d3, d4, d_min;
100  static laPoint2 pt;
101 
102  rectBoundingLine.add( lnPositioned );
103  rectBoundingLine.inflate( pScenario->ptModifiedVector );
104  rectBoundingLine.inflate( laPoint2(M_NORMAL_RESPONSE, M_NORMAL_RESPONSE) );
105  rectBoundingLine.inflate( laPoint2(-M_NORMAL_RESPONSE, -M_NORMAL_RESPONSE) );
106 
107  //Cylce through all collision domains
108  //
109  do{
110  if( rectBoundingLine.intersecting( pDomain->boundingRect() ) ) //Quick test for the domain
111  for(i=0; i<pDomain->countLines(); i++)
112  {
113  pDomainLine = pDomain->line(i);
114 
115  // Ignore backfacing
116  //
117  pt = pDomainLine->origin + *(pDomain->normal(i));
118 
119  if( (! pDomainLine->onSameSide(pt, lnPositioned.origin)) && (! pDomainLine->onSameSide(pt, lnPositioned.end())) )
120  continue;
121 
122  // Safe margin
123  //
124  laLine2 lnMagin(lnPositioned.origin + pScenario->ptModifiedVector, lnPositioned.end() + pScenario->ptModifiedVector);
125  laLine2 lnMagin2(pDomainLine->origin - pScenario->ptModifiedVector, pDomainLine->end() - pScenario->ptModifiedVector);
126  d1 = pDomainLine->distance_sq( lnMagin.origin );
127  d2 = pDomainLine->distance_sq( lnMagin.end() );
128  d3 = lnPositioned.distance_sq( lnMagin2.origin );
129  d4 = lnPositioned.distance_sq( lnMagin2.end() );
130  d_min = sqrt( M_MIN(d1, M_MIN(d2, M_MIN(d3, d4))) );
131 
132  if( d_min < M_NORMAL_RESPONSE )
133  pScenario->ptModifiedVector += *(pDomain->normal(i)) * ( M_NORMAL_RESPONSE - d_min );
134  }
135  } while( (pDomain = pDomain->nextDomain()) && (++nCurrentDomain < pScenario->nDomains) );
136 }
137 
138 
139 // Collision detection between a single line and a list of collision domains
140 //
141 void laCollider::collideLineWithDomain(const laLine2 &line, laCollisionScenario *pScenario,
142  int nIterations, M_BOOL bResponse)
143 {
144  PROFILE_COL(laCollider_collideLineWithDomain);
145  unsigned nCurrentDomain = 0, i, j;
146  laCollisionDomain *pDomain = pScenario->pFirstDomain;
147 
148  static laLine2 *pDomainLine, lnMovement[4];
149  static laPoint2 ptIntersection, pt;
150  laRect2 rectBoundingLine, rectBoundingDomain;
151 
152  // Build line; bounding box
153  //
154  laLine2 lnPositioned = line;
155  lnPositioned.origin += pScenario->ptPosition;
156 
157  rectBoundingLine.add( lnPositioned );
158  rectBoundingLine.inflate( pScenario->ptVector );
159  rectBoundingLine.inflate( laPoint2(M_NORMAL_RESPONSE, M_NORMAL_RESPONSE) );
160  rectBoundingLine.inflate( laPoint2(-M_NORMAL_RESPONSE, -M_NORMAL_RESPONSE) );
161 
162  /*::laSystemIntegrator::getRenderer()->styleLine( 1 );
163  ::laSystemIntegrator::getRenderer()->styleSet( laColor(0,255,0) );
164  rectBoundingLine.draw(::laSystemIntegrator::getRenderer());*/
165 
166  // Movement lines orignating form the endpoints of the line we're testing for
167  //
168  lnMovement[0].build_v( lnPositioned.origin, pScenario->ptVector );
169  lnMovement[1].build_v( lnPositioned.end(), pScenario->ptVector );
170 
171  // Initial state
172  //
173  pScenario->bCollision = M_FALSE;
174  pScenario->ptModifiedVector = pScenario->ptVector;
175  pScenario->pTrap = NULL;
176 
177  double d, dNearestDistance = pScenario->ptVector.lenght_sq() + M_UNIT;
178 
179  //Cylce through all collision domains
180  //
181  do{
182  /*::laSystemIntegrator::getRenderer()->styleSet( laColor(255,0,0) );
183  ::laSystemIntegrator::getRenderer()->styleLine( 1 );
184  pDomain->draw(::laSystemIntegrator::getRenderer(), pDomain->offset());*/
185 
186  if( rectBoundingLine.intersecting( pDomain->boundingRect() ) ) // Quick intersection test for the whole domain
187  for(i=0; i<pDomain->countLines(); i++)
188  {
189  pDomainLine = pDomain->line(i);
190 
191  // Ignore backfacing
192  //
193  pt = pDomainLine->origin + *(pDomain->normal(i));
194 
195  if( (! pDomainLine->onSameSide(pt, lnPositioned.origin)) && (! pDomainLine->onSameSide(pt, lnPositioned.end())) )
196  continue;
197 
198  /*::laSystemIntegrator::getRenderer()->styleSet( laColor(0,255,0) );
199  ::laSystemIntegrator::getRenderer()->styleLine( 1 );
200  pDomain->boundingRect().draw(::laSystemIntegrator::getRenderer());*/
201 
202  //Build inverted movement lines for the collision line's end-points
203  lnMovement[2].build_v( pDomainLine->origin, pScenario->ptVector * (-1) );
204  lnMovement[3].build_v( pDomainLine->end(), pScenario->ptVector * (-1) );
205 
206  // Check for intersections
207  //
208  for(j=0; j<4; j++){
209  /*::laSystemIntegrator::getRenderer()->styleLine( 1 );
210  ::laSystemIntegrator::getRenderer()->styleSet( laColor(0,0,255) );
211  lnMovement[j].draw(::laSystemIntegrator::getRenderer(), laPoint2());*/
212 
213  if( intersection(lnMovement[j], (j<2)? *pDomainLine : lnPositioned, &d, &ptIntersection) && (d<dNearestDistance) )
214  {
215  /*ptIntersection.draw( ::laSystemIntegrator::getRenderer());*/
216 
217  dNearestDistance = d;
218  pScenario->lnCollision = *pDomainLine;
219 
220  if( j<2 ) {
221  pScenario->ptCausing = lnMovement[j].origin;
222  pScenario->ptIntersection = ptIntersection;
223  }
224  else {
225  // Invert these, since we are projecting from the opposite side
226  pScenario->ptCausing = ptIntersection;
227  pScenario->ptIntersection = lnMovement[j].origin;
228  }
229 
230  pScenario->pTrap = pDomain->trap(i);
231  }
232  }
233 
234  }
235 
236  } while( (pDomain = pDomain->nextDomain()) && (++nCurrentDomain < pScenario->nDomains) );
237 
238  // Is there a collision with some of the lines?
239  //
240  if( dNearestDistance < pScenario->ptVector.lenght_sq() )
241  {
242  pScenario->bCollision = M_TRUE;
243 
244  // Project the movement vector on the collision line
245  //
246  if(bResponse)
247  response(pScenario->ptCausing, pScenario->ptVector, pScenario->lnCollision, &(pScenario->ptModifiedVector));
248  else
249  pScenario->ptModifiedVector = pScenario->ptIntersection - pScenario->ptCausing;
250 
251  //safe_magin(lnPositioned, pScenario);
252 
253  // Next step
254  //
255  if(nIterations)
256  {
257  laPoint2 tv = pScenario->ptVector;
258  pScenario->ptVector = pScenario->ptModifiedVector;
259 
260  collideLineWithDomain(line, pScenario, nIterations-1);
261  pScenario->ptVector = tv;
262  pScenario->bCollision = M_TRUE;
263  }
264  }
265  //else safe_magin(lnPositioned, pScenario);
266 }
267 
268 // Collision detection between multiple lines and a list of collision domains
269 //
270 void laCollider::collideLinesWithDomain(laLine2* arLines, unsigned nCount, laCollisionScenario *pScenario,
271  int nIterations, M_BOOL bResponse)
272 {
273  PROFILE_COL(laCollider_collideLinesWithDomain);
274  laCollisionScenario scenarioClosest = *pScenario;
275  double d, dNearestDistance = pScenario->ptVector.lenght_sq() + M_UNIT;
276  unsigned nLine = 0;
277 
278  // Cycle through all lines
279  //
280  for(unsigned i=0; i<nCount; i++)
281  {
282  collideLineWithDomain(arLines[i], pScenario, 0, M_FALSE);
283  d = pScenario->ptModifiedVector.lenght_sq();
284 
285  // Is this collision the closest so far?
286  //
287  if( d < dNearestDistance ) {
288  nLine = i;
289  dNearestDistance = d;
290  scenarioClosest = *pScenario;
291  }
292  }
293 
294  // Is there a collision?
295  //
296  if( dNearestDistance < pScenario->ptVector.lenght_sq() )
297  {
298  *pScenario = scenarioClosest;
299 
300  if(bResponse)
301  response(pScenario->ptCausing, pScenario->ptVector, pScenario->lnCollision, &(pScenario->ptModifiedVector) );
302  else
303  pScenario->ptModifiedVector = pScenario->ptIntersection - pScenario->ptCausing;
304 
305  laLine2 ln = laLine2(arLines[nLine].origin + pScenario->ptPosition, arLines[nLine].end() + pScenario->ptPosition);
306  safe_magin(ln, pScenario);
307 
308  // Next step
309  //
310  if(nIterations) {
311  laPoint2 tv = pScenario->ptVector;
312  pScenario->ptVector = pScenario->ptModifiedVector;
313 
314  collideLinesWithDomain(arLines, nCount, pScenario, nIterations-1);
315 
316  pScenario->ptVector = tv;
317  pScenario->bCollision = M_TRUE;
318  //if(pTrap) pScenario->pTrap = pTrap;
319  }
320  }
321  else for(unsigned j=0; j<nIterations; j++)
322  {
323  laLine2 ln = laLine2(arLines[nLine].origin + pScenario->ptPosition, arLines[nLine].end() + pScenario->ptPosition);
324  safe_magin(ln, pScenario);
325  }
326 }
327 
328 // Collision detection between a single point and a list of collision domains
329 //
330 void laCollider::collidePointWithDomain(laCollisionScenario *pScenario, int nIterations, M_BOOL bResponse)
331 {
332  PROFILE_COL(laCollider_collidePointWithDomain);
333  unsigned nCurrentDomain = 0;
334  laCollisionDomain *pDomain = pScenario->pFirstDomain;
335 
336  static laLine2 *pDomainLine, lnMovement;
337  laRect2 rect;
338 
339  static laPoint2 ptIntersection, pt;
340  static double d, dNearestDistance;
341 
342  rect.add( pScenario->ptPosition );
343  rect.add( pScenario->ptPosition + pScenario->ptVector );
344  rect.inflate( pScenario->ptVector );
347 
348  lnMovement.build_v( pScenario->ptPosition, pScenario->ptVector);
349  dNearestDistance = lnMovement.lenght_sq() + M_UNIT*2;
350 
351  //Cylce through all collision domains
352  //
353  do{
354  if( rect.intersecting(pDomain->boundingRect()) ) //Simple test
355  for(unsigned i=0; i<pDomain->countLines(); i++)
356  {
357  pDomainLine = pDomain->line(i);
358 
359  //Ignore backfacing
360  //
361  pt = pDomainLine->origin + *(pDomain->normal(i));
362  if( pDomainLine->direction(pt)!=pDomainLine->direction(pScenario->ptPosition) ) continue;
363 
364  //Is it intersecting with the movement vector?
365  //Mark the intersection point as our best candidate for collision
366  //
367  if( intersection(lnMovement, *pDomainLine, &d, &ptIntersection) && (d<dNearestDistance) )
368  {
369  dNearestDistance = d;
370  pScenario->lnCollision = *pDomainLine;
371  pScenario->ptIntersection = ptIntersection;
372 
373  pScenario->pTrap = pDomain->trap(i);
374  }
375  }
376  } while( (pDomain = pDomain->nextDomain()) && (++nCurrentDomain < pScenario->nDomains) );
377 
378  //Was there a collision with some of the lines?
379  //
380  if( dNearestDistance < lnMovement.lenght_sq() )
381  {
382  pScenario->bCollision = M_TRUE;
383 
384  //Project the movement vector on the collision line
385  if(bResponse)
386  response(pScenario->ptPosition, pScenario->ptVector, pScenario->lnCollision, &(pScenario->ptModifiedVector));
387  else
388  pScenario->ptModifiedVector = pScenario->ptIntersection - pScenario->ptPosition;
389 
390  //Unless this is the last step of the recursion, prepare for the next one
391  //
392  // This crashese sometimes; Also if nIter == 0, then -- overflows and it crashes
393  //
394  /*if( (nIterations>0) && (--nIterations) )
395  {
396  //Spoof the movement vector
397  laPoint2 OriginalVector = pScenario->ptVector;
398  pScenario->ptVector = ModifiedVector;
399 
400  //Recurse
401  collidePointWithDomain(pScenario, nIterations);
402 
403  //Restore the movement vector, in case the calling function uses it somehow
404  pScenario->ptVector = OriginalVector;
405  }*/
406  }
407  else
408  {
409  pScenario->bCollision = M_FALSE;
410  pScenario->ptModifiedVector = pScenario->ptVector;
411  pScenario->pTrap = NULL;
412  }
413 
414 }
415 
416 // Collision detection between multiple points and a list of collision domains
417 //
418 void laCollider::collidePointsWithDomain(laPoint2* arPoint, unsigned nCount, laCollisionScenario *pScenario, int nIterations)
419 {
420  ASSERT(M_FALSE, "Not Implelemted");
421  /*PROFILE_COL(laCollider_collidePointsWithDomain);
422  laCollisionScenario Scenario;
423 
424  laLine2 lnMovement, lnNearest;
425  laPoint2 ptNearest;
426 
427  //Initial state
428  //
429  pScenario->bCollision = M_FALSE;
430  pScenario->ptModifiedVector = pScenario->ptVector;
431 
432  double dNearestDistance = pScenario->ptVector.lenght() + 1;
433 
434  //Cycle through all points
435  for(unsigned i=0; i<nCount; i++)
436  {
437  //Copy the scanario and modify the position
438  Scenario = *pScenario;
439  Scenario.ptPosition += arPoint[i];
440 
441  //Cut the movement vector at the nearest collision (without collision response)
442  collidePointWithDomain(&Scenario, 1, M_FALSE);
443 
444  //If this intersection is closer than any other that w eknow of
445  if(Scenario.ptModifiedVector.lenght()<dNearestDistance)
446  {
447  //Then mark it as such
448  dNearestDistance = Scenario.ptModifiedVector.lenght();
449  lnNearest = Scenario.lnCollision;
450  ptNearest = Scenario.ptPosition;
451  }
452  }
453 
454  //Was there a collision with some of the lines?
455  if(dNearestDistance < pScenario->ptVector.lenght())
456  {
457  laPoint2 ModifiedVector;
458 
459  //Project the movement vector on the collision line
460  response(ptNearest, pScenario->ptVector, lnNearest, &ModifiedVector);
461 
462  //Indicate that there is a collision
463  pScenario->bCollision = M_TRUE;
464  pScenario->lnCollision = lnNearest;
465 
466  //Unless this is the last step of the recursion, prepare for the next one
467  if(nIterations)
468  {
469  //Spoof the movement vector
470  laPoint2 OriginalVector = pScenario->ptVector;
471  pScenario->ptVector = ModifiedVector;
472 
473  //Recurse
474  collidePointsWithDomain(arPoint, nCount, pScenario, nIterations-1);
475 
476  //Restore the movement vector, in case the calling function uses it somehow
477  pScenario->ptVector = OriginalVector;
478  }
479  else pScenario->ptModifiedVector = ModifiedVector;
480  }*/
481 }
#define M_NORMAL_RESPONSE
Distance an object "bounces back" form a collision surface.
M_BOOL onSameSide(const laPoint2 &a, const laPoint2 &b) const
Check if two points are on the same side of the line.
Definition: laLine2.h:141
double lenght_sq() const
Squared root of segment lenght.
Definition: laLine2.h:69
int direction(const laPoint2 &pt) const
Definition: laLine2.h:123
#define M_UNIT
Unit of 1 meter.
void build_v(const laPoint2 &a, const laPoint2 &v)
Build a line from an end-point and a vector.
Definition: laLine2.h:57
2D Point
Definition: laPoint_novec.h:41
M_BOOL intersection(const laLine2 &ln, double *k, double *k_ln) const
Definition: laLine2.h:149
void add(const laPoint2 &p)
Revert to "uninitialized" state.
Definition: laRect2.h:64
laPoint2 end() const
Get the end-point.
Definition: laLine2.h:78
void inflate(const laPoint2 &inf)
Add vector to the rectangle (e.g. velocity)
Definition: laRect2.h:92
void at(double k, laPoint2 *ppt) const
Get point at 1/k lenght (returns in user-specified pointer)
Definition: laLine2.h:81
2D line segment
Definition: laLine2.h:40
laPoint2 normal(int direction=1) const
Get a perpendicular vector of lenght 1 (normal of the line segment)
Definition: laLine2.h:113
2D rectangle (used by the GUI and also as a simple tool for proximity testing)
Definition: laRect2.h:39
Collision Domain.
laPoint2 origin
Point of origin.
Definition: laLine2.h:43
double distance_sq(laPoint2 &pt) const
Compute the square of the distance between a point and the line.
Definition: laLine2.h:178