QB64.org Forum

Active Forums => QB64 Discussion => Topic started by: NOVARSEG on May 03, 2021, 11:36:08 pm

Title: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 03, 2021, 11:36:08 pm
Looking at vector addition videos example https://www.khanacademy.org/math/precalculus/x9e81a4f98389efdf:vectors/x9e81a4f98389efdf:vec-add-mag-dir/v/mag-dir-vec-sums

Say 2 objects, with no dimensions, collide at origin 0,0  (heading towards 0, 0)    This is the simple case for 2D collisions.  It is simple because the objects have no dimension and the collision is at 0, 0 .  The vector sum of these 2 objects, gives a resultant vector  (call it the mvector ) that is heading away from 0, 0.   mvector  exists momentarily  since energy is stored if the collision is elastic. For a moment, both objects head in the direction of mVector only for a small distance.

As the objects rebound from the stored energy, mVector determines the resultant direction and velocity of both objects.

The hard part is to calculate the mvector with objects like balls because the angle of collision wont always be parallel to the x axis of the screen

That requires some kind of vector rotation. I'm still working on that.

Any ideas how to do that?



Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 04, 2021, 11:54:52 pm
I tried my hand at an abortive attempt at a billiards program without resorting to trig. It had some "issues", and the code is a hot mess, but I imagine a good non-trig way is to obtain a normalized; aka unit, vector of the ball being processed, then use dot product between the balls direction and the strike angle to determine how much of a balls magnitude / speed/ force to apply to another ball. Of course, such a system is not dimensionless, colliding at 0,0

https://www.qb64.org/forum/index.php?topic=2382.msg116030#msg116030

  [ This attachment cannot be displayed inline in 'Print Page' view ]  
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 05, 2021, 12:36:14 am
That looks useful.

I've got a program that shows two balls colliding where the strike angle is in the same direction as the cyan colored ball.  I might remove the idea of "dimensionless"  for now because it is an abstract idea.  There is a idea  of "moments" involved because the mvector exits only for a moment.


The resultant direction of the cyan ball should maintain the same direction. This should give a clue on where the mvector is and how to calculate it as a function of strike angle and direction /velocity  of both balls.

run the program and in the top left corner you will see 56 56 that is the  x, y of the strike angle in pixels.  The magnitude is 80 pixels since the ball radius is 40 pixels

If anyone has a pool table at home what happens to the cyan ball does it stop maybe?

Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(1024, 768, 32)
  2.  
  3. DIM originX AS INTEGER
  4. originX = _WIDTH / 2
  5.  
  6. DIM originY AS INTEGER
  7. originY = _HEIGHT / 2
  8. DIM X(1) AS INTEGER 'can't have a fraction of a pixel
  9. DIM Y(1) AS INTEGER 'can't have a fraction of a pixel
  10. DIM DirY(1) AS INTEGER
  11. DIM DirX(1) AS INTEGER
  12.  
  13.  
  14. Y(0) = 672
  15. X(0) = originX - (700 - originY)
  16.  
  17. X(1) = _WIDTH - 40 'originX + (366 - originY)
  18. Y(1) = _HEIGHT - 40 '350
  19.  
  20. DirX(0) = 1
  21. DirY(0) = -1
  22.  
  23. DirX(1) = -1
  24. DirY(1) = -1
  25.  
  26.  
  27.     _DELAY .003
  28.     obj = 0
  29.  
  30.     IF (((X(1) - X(0)) ^ 2) + (Y(0) - Y(1)) ^ 2) ^ .5 <= 80 AND tog = 0 THEN
  31.         tog = 1
  32.  
  33.         PRINT X(0) - X(1); " "; Y(1) - Y(0)
  34.         ' PRINT ABS(3200 - (30 ^ 2)) ^ .5
  35.         'PRINT DirX(0) * ABS(3200 + 3200) ^ .5
  36.         'PRINT DirY(0) * ABS(3200 - 3200) ^ .5
  37.  
  38.         PRINT DirX(0) * ABS(3136 + (X(1) - X(0)) ^ 2) ^ .5
  39.         PRINT DirY(0) * ABS(3136 - (Y(1) - Y(0)) ^ 2) ^ .5
  40.  
  41.  
  42.  
  43.  
  44.  
  45.         DO
  46.             IF INKEY$ <> "" THEN EXIT DO
  47.         LOOP
  48.  
  49.         '_DELAY .6
  50.     END IF
  51.  
  52.     IF (((X(1) - X(0)) ^ 2) + (Y(0) - Y(1)) ^ 2) ^ .5 > 80 AND tog = 1 THEN tog = 0
  53.  
  54.     r = 0: g = 0: B = 0
  55.     FOR S = 1 TO 40
  56.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  57.     NEXT
  58.  
  59.     IF X(obj) + 40 = _WIDTH THEN DirX(obj) = -1
  60.     IF X(obj) - 40 = 0 THEN DirX(obj) = 1
  61.     X(obj) = X(obj) + DirX(obj)
  62.  
  63.     IF Y(obj) + 40 = _HEIGHT THEN DirY(obj) = -1
  64.     IF Y(obj) - 40 = 0 THEN DirY(obj) = 1
  65.     Y(obj) = Y(obj) + DirY(obj)
  66.  
  67.  
  68.     r = 255: g = 0: B = 0
  69.     FOR S = 1 TO 40
  70.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  71.     NEXT
  72.  
  73.     obj = 1
  74.  
  75.     r = 0: g = 0: B = 0
  76.     FOR S = 1 TO 40
  77.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  78.     NEXT
  79.  
  80.     IF X(obj) + 40 = _WIDTH THEN DirX(obj) = -1
  81.     IF X(obj) - 40 = 0 THEN DirX(obj) = 1
  82.     X(obj) = X(obj) + DirX(obj)
  83.  
  84.     IF Y(obj) + 40 = _HEIGHT THEN DirY(obj) = -1
  85.     IF Y(obj) - 40 = 0 THEN DirY(obj) = 1
  86.     Y(obj) = Y(obj) + DirY(obj)
  87.  
  88.     r = 0: g = 255: B = 255
  89.     FOR S = 1 TO 40
  90.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  91.     NEXT
  92.  
  93.     ' _DISPLAY
  94.  
  95.  
  96.  
  97.  
  98.  




Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 05, 2021, 02:58:08 am
Different ball collision  angles. How is mvector related to collision angle?

Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(1024, 768, 32)
  2.  
  3. DIM originX AS INTEGER
  4. originX = _WIDTH / 2
  5.  
  6. DIM originY AS INTEGER
  7. originY = _HEIGHT / 2
  8. DIM X(1) AS INTEGER 'can't have a fraction of a pixel
  9. DIM Y(1) AS INTEGER 'can't have a fraction of a pixel
  10. DIM DirY(1) AS INTEGER
  11. DIM DirX(1) AS INTEGER
  12.  
  13. LL1:
  14.  
  15. IF f1 = 2 THEN f1 = 0
  16.  
  17. IF f1 = 1 THEN
  18.     f1 = 2
  19.     Y(0) = 672
  20.     X(0) = originX - (700 - originY)
  21.  
  22. IF f1 = 0 THEN
  23.     f1 = 1
  24.     Y(0) = 648
  25.     X(0) = originX - (700 - originY)
  26.  
  27. X(1) = _WIDTH - 40
  28. Y(1) = _HEIGHT - 40
  29.  
  30. DirX(0) = 1
  31. DirY(0) = -1
  32.  
  33. DirX(1) = -1
  34. DirY(1) = -1
  35.  
  36.     _DELAY .003
  37.     obj = 0
  38.  
  39.     IF (((X(1) - X(0)) ^ 2) + (Y(0) - Y(1)) ^ 2) ^ .5 <= 80 AND tog = 0 THEN
  40.         tog = 1
  41.  
  42.         LOCATE 2, 1: PRINT X(0) - X(1); " X"
  43.  
  44.         LOCATE 3, 1: PRINT Y(1) - Y(0); " Y"
  45.         ' PRINT ABS(3200 - (30 ^ 2)) ^ .5
  46.         'PRINT DirX(0) * ABS(3200 + 3200) ^ .5
  47.         'PRINT DirY(0) * ABS(3200 - 3200) ^ .5
  48.  
  49.         '' LOCATE 4, 1: PRINT DirX(0) * ABS(3136 + (X(1) - X(0)) ^ 2) ^ .5
  50.         ' LOCATE 5, 1: PRINT DirY(0) * ABS(3136 - (Y(1) - Y(0)) ^ 2) ^ .5
  51.  
  52.         _DELAY 2
  53.         obj = 0
  54.         r = 0: g = 0: B = 0
  55.         FOR S = 1 TO 40
  56.             CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  57.         NEXT
  58.  
  59.         obj = 1
  60.         r = 0: g = 0: B = 0
  61.         FOR S = 1 TO 40
  62.             CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  63.         NEXT
  64.  
  65.         GOTO LL1
  66.  
  67.     END IF
  68.  
  69.     IF (((X(1) - X(0)) ^ 2) + (Y(0) - Y(1)) ^ 2) ^ .5 > 80 AND tog = 1 THEN tog = 0
  70.  
  71.     r = 0: g = 0: B = 0
  72.     FOR S = 1 TO 40
  73.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  74.     NEXT
  75.  
  76.     IF X(obj) + 40 = _WIDTH THEN DirX(obj) = -1
  77.     IF X(obj) - 40 = 0 THEN DirX(obj) = 1
  78.     X(obj) = X(obj) + DirX(obj)
  79.  
  80.     IF Y(obj) + 40 = _HEIGHT THEN DirY(obj) = -1
  81.     IF Y(obj) - 40 = 0 THEN DirY(obj) = 1
  82.     Y(obj) = Y(obj) + DirY(obj)
  83.  
  84.  
  85.     r = 255: g = 0: B = 0
  86.     FOR S = 1 TO 40
  87.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  88.     NEXT
  89.  
  90.     obj = 1
  91.  
  92.     r = 0: g = 0: B = 0
  93.     FOR S = 1 TO 40
  94.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  95.     NEXT
  96.  
  97.     IF X(obj) + 40 = _WIDTH THEN DirX(obj) = -1
  98.     IF X(obj) - 40 = 0 THEN DirX(obj) = 1
  99.     X(obj) = X(obj) + DirX(obj)
  100.  
  101.     IF Y(obj) + 40 = _HEIGHT THEN DirY(obj) = -1
  102.     IF Y(obj) - 40 = 0 THEN DirY(obj) = 1
  103.     Y(obj) = Y(obj) + DirY(obj)
  104.  
  105.     r = 0: g = 255: B = 255
  106.     FOR S = 1 TO 40
  107.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  108.     NEXT
  109.  
  110.  


Another angle added

Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(1024, 768, 32)
  2.  
  3. DIM originX AS INTEGER
  4. originX = _WIDTH / 2
  5.  
  6. DIM originY AS INTEGER
  7. originY = _HEIGHT / 2
  8. DIM X(1) AS INTEGER 'can't have a fraction of a pixel
  9. DIM Y(1) AS INTEGER 'can't have a fraction of a pixel
  10. DIM DirY(1) AS INTEGER
  11. DIM DirX(1) AS INTEGER
  12.  
  13. LL1:
  14.  
  15. IF f1 = 3 THEN f1 = 0
  16.  
  17. IF f1 = 2 THEN
  18.     f1 = 3
  19.     Y(0) = 728
  20.     X(0) = originX - (700 - originY)
  21.  
  22. 'GOTO LL2
  23.  
  24.  
  25. IF f1 = 1 THEN
  26.     f1 = 2
  27.     Y(0) = 672
  28.     X(0) = originX - (700 - originY)
  29.  
  30. IF f1 = 0 THEN
  31.     f1 = 1
  32.     Y(0) = 648
  33.     X(0) = originX - (700 - originY)
  34. LL2:
  35.  
  36. X(1) = _WIDTH - 40
  37. Y(1) = _HEIGHT - 40
  38.  
  39. DirX(0) = 1
  40. DirY(0) = -1
  41.  
  42. DirX(1) = -1
  43. DirY(1) = -1
  44.  
  45.     _DELAY .003
  46.     obj = 0
  47.  
  48.     IF (((X(1) - X(0)) ^ 2) + (Y(0) - Y(1)) ^ 2) ^ .5 <= 80 AND tog = 0 THEN
  49.         tog = 1
  50.  
  51.         LOCATE 2, 1: PRINT X(0) - X(1); " X"
  52.  
  53.         LOCATE 3, 1: PRINT Y(1) - Y(0); " Y"
  54.         ' PRINT ABS(3200 - (30 ^ 2)) ^ .5
  55.         'PRINT DirX(0) * ABS(3200 + 3200) ^ .5
  56.         'PRINT DirY(0) * ABS(3200 - 3200) ^ .5
  57.  
  58.         '' LOCATE 4, 1: PRINT DirX(0) * ABS(3136 + (X(1) - X(0)) ^ 2) ^ .5
  59.         ' LOCATE 5, 1: PRINT DirY(0) * ABS(3136 - (Y(1) - Y(0)) ^ 2) ^ .5
  60.  
  61.         _DELAY 2
  62.         obj = 0
  63.         r = 0: g = 0: B = 0
  64.         FOR S = 1 TO 40
  65.             CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  66.         NEXT
  67.  
  68.         obj = 1
  69.         r = 0: g = 0: B = 0
  70.         FOR S = 1 TO 40
  71.             CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  72.         NEXT
  73.  
  74.         GOTO LL1
  75.  
  76.     END IF
  77.  
  78.     IF (((X(1) - X(0)) ^ 2) + (Y(0) - Y(1)) ^ 2) ^ .5 > 80 AND tog = 1 THEN tog = 0
  79.  
  80.     r = 0: g = 0: B = 0
  81.     FOR S = 1 TO 40
  82.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  83.     NEXT
  84.  
  85.     IF X(obj) + 40 = _WIDTH THEN DirX(obj) = -1
  86.     IF X(obj) - 40 = 0 THEN DirX(obj) = 1
  87.     X(obj) = X(obj) + DirX(obj)
  88.  
  89.     IF Y(obj) + 40 = _HEIGHT THEN DirY(obj) = -1
  90.     IF Y(obj) - 40 = 0 THEN DirY(obj) = 1
  91.     Y(obj) = Y(obj) + DirY(obj)
  92.  
  93.  
  94.     r = 255: g = 0: B = 0
  95.     FOR S = 1 TO 40
  96.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  97.     NEXT
  98.  
  99.     obj = 1
  100.  
  101.     r = 0: g = 0: B = 0
  102.     FOR S = 1 TO 40
  103.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  104.     NEXT
  105.  
  106.     IF X(obj) + 40 = _WIDTH THEN DirX(obj) = -1
  107.     IF X(obj) - 40 = 0 THEN DirX(obj) = 1
  108.     X(obj) = X(obj) + DirX(obj)
  109.  
  110.     IF Y(obj) + 40 = _HEIGHT THEN DirY(obj) = -1
  111.     IF Y(obj) - 40 = 0 THEN DirY(obj) = 1
  112.     Y(obj) = Y(obj) + DirY(obj)
  113.  
  114.     r = 0: g = 255: B = 255
  115.     FOR S = 1 TO 40
  116.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  117.     NEXT
  118.  
  119.  



Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 05, 2021, 06:59:02 am
referring to the picture I attached, it is showing an exit angle for the striking ball that is orthogonal to the "strike axis" (i.e. the line between the centers of the two balls. The stationary ball moves straight away along that strike axis, mainly because it is not in motion initially. It gets a lot more complex when both balls are moving. At that point I presume that it becomes a function of adding the new (orthogonal) movement vector to the old pre-strike movement vector and modifying the magnitudes of the results according to the dot product result between strike axis and incoming vector. Thereby each ball imparts some of its energy to the other. I was not entirely successful in implementation in my example, as the balls would rather bizarrely swap places with each other or get stuck together on the same spot.

I opted for a Dot Product of unit vector function as it is a relatively straightforward computation and handled all angles reasonably, including head on strikes that would stop a cue ball in its tracks and send the struck ball on its way. Otherwise, I assume that the real world physics are quite beyond my pay grade.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 08, 2021, 03:21:43 am
Vector addition seems to work for simple cases.  I'm running tests on 2 balls with equal mass and velocity using vector addition  and vector rotation.

There are problems with unequal velocities such as when one ball is stationary and the other moving.  Does the stationary ball have a vector?.   So it looks like vector addition is only part of the solution.

'First case: starting vectors
red ball is traveling sqrt(8)x, 0y    towards 0, 0
cyan ball is traveling 2x , -2y        towards 0, 0

let strike angle = 0 (as respects x axis)

At what strike angle do these balls mathematically collide? The vector addition of the ball vectors implies that the bisection angle between the ball vectors is the position of the resultant vector. (mvector)

vector addition gives    sqrt(8) - 2 X  , 2 Y  (mvector)


0 X  , 2.164784 Y   (mvector)

OK  final directions and velocities of both balls.

red ball
sqrt(8)x, 0y   +   0 X  , 2.164784 Y  =  sqrt(8) x ,  2.164784 Y

cyan ball 2x , -2y   +  0 X  , 2.164784 Y =  2x ,  .167484y

Check the math.

According to the literature, the total momentum is preserved. Since the masses are the same then total velocities will do.   The total velocities are red + cyan ball velocity = (8^.5 ) *2

PRINT (4 + .16748 ^ 2) ^ .5 + (8 + 2.16474 ^ 2) ^ .5

= (8^.5 ) *2

Ok the math works out but more tests needed.
**************************
 the above math had some problems
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 09, 2021, 01:29:34 am
2 ball collision.    Both balls equal mass and velocity.

The red ball moves along X axis and the cyan ball moves at a 45 degrees angle.

press any key to run program again.  There are lines that show final direction and velocity.  Not sure if the code is correct.  Does this look OK?

Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(1024, 768, 32)
  2.  
  3. DIM DirY(1) AS INTEGER
  4. DIM DirX(1) AS INTEGER
  5.  
  6. LL1: CLS
  7. Y(0) = _HEIGHT / 2
  8. X(0) = 60
  9.  
  10. X(1) = _WIDTH - 196
  11. Y(1) = _HEIGHT - 40
  12.  
  13. DirX(0) = 1
  14. DirY(0) = -1
  15.  
  16. DirX(1) = -1
  17. DirY(1) = -1
  18.  
  19.     _DELAY .003
  20.     obj = 0
  21.  
  22.     IF (((X(1) - X(0)) ^ 2) + (Y(0) - Y(1)) ^ 2) ^ .5 <= 80 AND tog = 0 THEN
  23.         tog = 1
  24.  
  25.         LOCATE 2, 1: PRINT X(0) - X(1); " X"
  26.  
  27.         LOCATE 3, 1: PRINT Y(1) - Y(0); " Y"
  28.         LOCATE 4, 1: PRINT X(0); Y(0)
  29.  
  30.         rx = -80
  31.         ry = 0
  32.  
  33.         ' rx = 0
  34.         ' ry = -80
  35.  
  36.         ' rx = -(3200 ^ .5) ' * 2
  37.         ' ry = (3200 ^ .5) ' / 2
  38.  
  39.         cx = 3200 ^ .5 '* 2
  40.         cy = -(3200 ^ .5) ' / 2
  41.  
  42.         mvectorx = (0 - rx) - cx
  43.         mvectory = (0 - ry) + (0 - cy)
  44.         PRINT "mvectorx"; mvectorx
  45.         PRINT "mvectory"; mvectory
  46.  
  47.         'rotates mvector so it is at right angles to X axis
  48.         'mvectory = (mvectorx ^ 2 + mvectory ^ 2) ^ .5
  49.         ' mvectorx = 0
  50.  
  51.         rxf = rx + mvectorx
  52.         ryf = ry + mvectory
  53.         cxf = cx + mvectorx
  54.         cyf = cy + mvectory
  55.  
  56.         PRINT "rxf"; rxf
  57.         PRINT "ryf"; ryf
  58.         PRINT "cxf"; cxf
  59.         PRINT "cyf"; cyf
  60.  
  61.         rxf = rxf * 3
  62.         ryf = ryf * 3
  63.         cxf = cxf * 3
  64.         cyf = cyf * 3
  65.  
  66.         LINE (X(0), Y(0))-(X(0) + rxf, Y(0) - ryf)
  67.         LINE (X(1), Y(1))-(X(1) + cxf, Y(1) - cyf)
  68.  
  69.         DO
  70.             IF INKEY$ <> "" THEN EXIT DO
  71.         LOOP
  72.  
  73.         obj = 0
  74.         r = 0: g = 0: B = 0
  75.         FOR S = 1 TO 40
  76.             CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  77.         NEXT
  78.  
  79.         obj = 1
  80.         r = 0: g = 0: B = 0
  81.         FOR S = 1 TO 40
  82.             CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  83.         NEXT
  84.  
  85.         GOTO LL1
  86.  
  87.     END IF
  88.  
  89.     IF (((X(1) - X(0)) ^ 2) + (Y(0) - Y(1)) ^ 2) ^ .5 > 80 AND tog = 1 THEN tog = 0
  90.  
  91.     r = 0: g = 0: B = 0
  92.     FOR S = 1 TO 40
  93.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  94.     NEXT
  95.  
  96.     IF X(obj) + 40 = _WIDTH THEN DirX(obj) = -1
  97.     IF X(obj) - 40 = 0 THEN DirX(obj) = 1
  98.     X(obj) = X(obj) + DirX(obj)
  99.  
  100.     ' IF Y(obj) + 40 = _HEIGHT THEN DirY(obj) = -1
  101.     ' IF Y(obj) - 40 = 0 THEN DirY(obj) = 1
  102.     ' Y(obj) = Y(obj) + DirY(obj)
  103.  
  104.  
  105.     r = 255: g = 0: B = 0
  106.     FOR S = 1 TO 40
  107.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  108.     NEXT
  109.  
  110.     obj = 1
  111.  
  112.     r = 0: g = 0: B = 0
  113.     FOR S = 1 TO 40
  114.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  115.     NEXT
  116.  
  117.     IF X(obj) + 40 = _WIDTH THEN DirX(obj) = -1
  118.     IF X(obj) - 40 = 0 THEN DirX(obj) = 1
  119.     X(obj) = X(obj) + DirX(obj)
  120.  
  121.     IF Y(obj) + 40 = _HEIGHT THEN DirY(obj) = -1
  122.     IF Y(obj) - 40 = 0 THEN DirY(obj) = 1
  123.     Y(obj) = Y(obj) + DirY(obj)
  124.  
  125.     r = 0: g = 255: B = 255
  126.     FOR S = 1 TO 40
  127.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  128.     NEXT
  129.  
  130.  
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 09, 2021, 11:48:41 pm
Ball collision code -very simple.  Vector addition only.

Vector rotation not needed in this case.  Have not verified if the collisions are correct but it looks fairly reasonable.

WAIT BUG DETECTED !!!!!!!!!!!!!!!!!!!!!!!!!

Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 10, 2021, 03:41:10 am
Nasty bug.  Ball collisions using vector addition only.  Equal velocity and mass.

It looks like the final velocities are unchanged if the original velocities of both balls are the same.

I find it interesting that 99.99 % of internet info about ball collisions is about a moving ball colliding with a stationary ball or wall.  Almost no mention about balls colliding with equal velocity.

I still do not know if the vector addition can describe ball collisions.  Can anyone back this up?


Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(1024, 768, 32)
  2.  
  3. DIM DirY(1) AS INTEGER
  4. DIM DirX(1) AS INTEGER
  5.  
  6. L = 2
  7.  
  8.  
  9. Y(0) = _HEIGHT / 2
  10. X(0) = 60
  11.  
  12. X(1) = _WIDTH - 196
  13. Y(1) = _HEIGHT - 40
  14.  
  15. DirX(0) = 1
  16. DirY(0) = 0 '-1
  17.  
  18. DirX(1) = -1
  19. DirY(1) = -1
  20. rx = -80
  21. ry = 0
  22. cx = 3200 ^ .5
  23. cy = -(3200 ^ .5)
  24.  
  25.     _DELAY .0015
  26.  
  27.  
  28.     IF (((X(1) - X(0)) ^ 2) + (Y(0) - Y(1)) ^ 2) ^ .5 <= 80 THEN
  29.  
  30.  
  31.         LOCATE 10 + L, 1: PRINT X(0) - X(1); " X"
  32.  
  33.         LOCATE 11 + L, 1: PRINT Y(1) - Y(0); " Y"
  34.  
  35.         mvectorx = (0 - rx) + (0 - cx)
  36.         mvectory = (0 - ry) + (0 - cy)
  37.         LOCATE 12 + L, 1: PRINT "mvectorx"; mvectorx
  38.         LOCATE 13 + L, 1: PRINT "mvectory"; mvectory
  39.  
  40.         rxf = rx + mvectorx
  41.         ryf = ry + mvectory
  42.         cxf = cx + mvectorx
  43.         cyf = cy + mvectory
  44.         rx = rxf
  45.         ry = -ryf
  46.         cx = cxf
  47.         cy = -cyf
  48.  
  49.         LOCATE 14 + L, 1: PRINT "rxf"; rxf; "         "
  50.         LOCATE 15 + L, 1: PRINT "ryf"; ryf; "         "
  51.         LOCATE 16 + L, 1: PRINT "cxf"; cxf; "         "
  52.         LOCATE 17 + L, 1: PRINT "cyf"; cyf; "         "
  53.  
  54.  
  55.         DirX(0) = rxf / 80
  56.         DirY(0) = ryf / -80
  57.         DirX(1) = cxf / 80
  58.         DirY(1) = cyf / -80
  59.  
  60.         LINE (X(0), Y(0))-(X(0) + rxf * 3, Y(0) - ryf * 3)
  61.         LINE (X(1), Y(1))-(X(1) + cxf * 3, Y(1) - cyf * 3)
  62.  
  63.         _DELAY .5
  64.  
  65.         obj = 0
  66.         r = 0: g = 0: B = 0
  67.         FOR S = 1 TO 40
  68.             CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  69.         NEXT
  70.  
  71.         obj = 1
  72.         r = 0: g = 0: B = 0
  73.         FOR S = 1 TO 40
  74.             CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  75.         NEXT
  76.  
  77.     END IF
  78.  
  79.     obj = 0
  80.  
  81.     r = 0: g = 0: B = 0
  82.     FOR S = 1 TO 40
  83.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  84.     NEXT
  85.  
  86.     IF X(obj) + 40 = _WIDTH THEN DirX(obj) = -1
  87.     IF X(obj) - 40 = 0 THEN DirX(obj) = 1
  88.  
  89.     X(obj) = X(obj) + DirX(obj)
  90.  
  91.     IF Y(obj) + 40 = _HEIGHT THEN DirY(obj) = -1
  92.     IF Y(obj) - 40 = 0 THEN DirY(obj) = 1
  93.  
  94.     Y(obj) = Y(obj) + DirY(obj)
  95.  
  96.  
  97.     r = 255: g = 0: B = 0
  98.     FOR S = 1 TO 40
  99.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  100.     NEXT
  101.  
  102.     obj = 1
  103.  
  104.     r = 0: g = 0: B = 0
  105.     FOR S = 1 TO 40
  106.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  107.     NEXT
  108.  
  109.     IF X(obj) + 40 >= _WIDTH THEN DirX(obj) = -1
  110.     IF X(obj) - 40 = 0 THEN DirX(obj) = 1
  111.     X(obj) = X(obj) + DirX(obj)
  112.  
  113.     IF Y(obj) + 40 = _HEIGHT THEN DirY(obj) = -1
  114.     IF Y(obj) - 40 = 0 THEN DirY(obj) = 1
  115.     Y(obj) = Y(obj) + DirY(obj)
  116.  
  117.     r = 0: g = 255: B = 255
  118.     FOR S = 1 TO 40
  119.         CIRCLE (X(obj), Y(obj)), S, _RGB(r, g, B)
  120.     NEXT
  121.  
  122.  

Ok I've been searching the net and there is no examples of 2 ball collisions at the same velocity.  I have no way to verify my results.  Plenty (thousands) of examples of cue balls hitting stationary balls though -sigh
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 10, 2021, 08:41:16 pm
https://www.vobarian.com/collisions/2dcollisions2.pdf
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 11, 2021, 05:11:33 pm
Oh 2 balls same mass and velocity collide dead onto each other is supposed to knock all energy out of each other come to dead stop, right? (Been considering sum of Forces acting on object.)

I think I've been just trading momentums in such circumstance, having them bounce off each other like a off a wall.
Title: Re: 2D ball collisions without trigonometry.
Post by: johnno56 on May 11, 2021, 08:48:50 pm
Trying to get my head around this one.... Same mass. Same Velocity. Assuming they are colliding from directly opposite vectors (head on). The collision would be like hitting a fixed wall. Assuming that both objects can survive the re-percussive force of the collision then both objects should rebound in the opposite direction to their initial vectors. Some energy should be lost and as such reduce the velocities of each object. Of course there are other external influences which have been disregarded... There are a few ways that both objects could come to a dead stop-ish... If both object were not totally solid like steel. Say... plaster. Both objects should shatter. All energy either dissipated or transferred to the fragments. If one object was made of solid copper and the other a mono-pole magnet then both objects would not collide. The magnetic field would be induced into the copper object creating another magnetic field which would repel the magnet. More so as both objects near collision. Both fields opposing the velocities of both objects to the point of reducing momentum to a point of equilibrium.

Or something like that....

Therefore, excluding other external forces, the two spheres should just bounce of each other....
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 11, 2021, 09:22:06 pm
For simplicity sake no other forces, no gravity, no magnetism, no friction, no desire for coffee...

From the paper of OldMoses link https://www.vobarian.com/collisions/2dcollisions2.pdf
v1' = (v1(m1-m2) + 2m2v2)/(m1 + m2)
v2' = (v2(m2-m1) + 2m1v1)/(m1 + m2)

assume for simplicity m1 = m2 = 1 then

v1' = (v1(1-1) + 2v2)/2 = v2 
v2' = (v2(0) + 2v1)/(2) = v1

Ha! they do trade momentums therefore no dead stop which sum of vector forces suggests.
Title: Re: 2D ball collisions without trigonometry.
Post by: johnno56 on May 11, 2021, 09:28:17 pm
WHAT!?!?  No coffee?  Heresy!!!
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 11, 2021, 09:41:45 pm
WHAT!?!?  No coffee?  Heresy!!!

I am pretty sure I said no desire for coffee, coffee still exists at least until it gets cyber attacked.
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 11, 2021, 09:58:19 pm
For a head on, elastic collision between two moving balls of equal mass and material, I think that trading vector magnitudes in rebound would be appropriate. In lieu of a more complete knowledge of the subject, on my part.

That said, it would be rather rare for two independent balls to be moving on direct, inverted vectors. They're typically going to be striking at different vectors, or be on inverted vectors that are slightly (or grossly) offset. The angle between their travel vector and the line between the ball centers at the point of collision (I'll call it the strike vector) would govern how each shears away from the other.

Most treatises I've seen on the subject give a shear angle that is perpendicular (orthogonal) to the strike vector, although most of those are assuming a stationary target ball, as per the graphic I posted earlier. There are two orthogonals to the strike vector, and one has to control which is used in order for a ball to not deflect to the "inside", which looks rather weird. I reasoned that one would chose the shear orthogonal that would be closest to a projected point of the striking balls original incoming vector. There may be a simpler method that I'm missing.

After that it gets interesting. I normalized the strike vector and the striking balls incoming vector and apply dot product to those to figure out how much force is transferred to the struck ball, while 1 - dot product result would be retained by the striking ball on its new course. That seemed to yield a satisfying look to my eye, but my application had issues with balls blowing through each other and trading places or getting stuck on one another when in tight groups or high relative speeds. I assume they were moving beyond the collision test before it even had a chance to implement. Oh well, back to the drawing board...
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 11, 2021, 10:17:29 pm
Speaking of Dot Product, I call foul! the paper said no Trig but look at how Dot Product is calc'd with COS!
https://www.mathsisfun.com/algebra/vectors-dot-product.html


Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 11, 2021, 10:42:28 pm
The easy method of dot product calculation is:

a · b = ax × bx + ay × by

I typically assign a function to it.
Code: QB64: [Select]
  1. FUNCTION VecDot (var AS V2, var2 AS V2)
  2.     VecDot = var.x * var2.x + var.y * var2.y
  3. END FUNCTION 'VecDot
  4.  

I have to assume it's much faster than COS in the CPU
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 11, 2021, 10:48:25 pm
Trying to understand the 7 steps and Dot Product is hanging me up.

From Stx notes on vector math here is Dot Product without need of COS(angle between vectors)
Code: QB64: [Select]
  1. Function v2DotProduct (A As xyType, B As xyType) 'shadow or projection  if A Dot B = 0 then A , B are perpendicular
  2.     v2DotProduct = A.x * B.x + A.y * B.y
  3.  

ha I was just going to post this.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 12, 2021, 01:47:45 am
 Working with vector addition using the parallelogram method (what I call the mvector) There are times when this works OK and other times when it don't.  Sometimes the mvector is 0, 0  which is valid for 180 degree collisions with a strike angle of zero but any attempt to rotate 0,  0 to a required vector results in loss of final direction of one of the balls.

Mvector is strange. Sometimes it has no direction or magnitude (0, 0 ) at other times it acts like a real vector.  At other times it points in both directions (or should).

From a code example :  say mvector is  0X,   113.137 Y.  That works fine for the red ball but the cyan ball expects
 0X,   -113.137 Y..     It is possible that mvector can point in opposite directions. I'm not sure if that fits the definitions of a vector.


Title: Re: 2D ball collisions without trigonometry.
Post by: johnno56 on May 12, 2021, 02:39:25 am
I am pretty sure I said no desire for coffee, coffee still exists at least until it gets cyber attacked.

Almost as bad... No 'desire' for coffee... You should have your mouth washed out with soap... Blasphemy! LOL
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 12, 2021, 08:06:04 am
Working with vector addition using the parallelogram method (what I call the mvector) There are times when this works OK and other times when it don't.  Sometimes the mvector is 0, 0  which is valid for 180 degree collisions with a strike angle of zero but any attempt to rotate 0,  0 to a required vector results in loss of final direction of one of the balls.

Mvector is strange. Sometimes it has no direction or magnitude (0, 0 ) at other times it acts like a real vector.  At other times it points in both directions (or should).

From a code example :  say mvector is  0X,   113.137 Y.  That works fine for the red ball but the cyan ball expects
 0X,   -113.137 Y..     It is possible that mvector can point in opposite directions. I'm not sure if that fits the definitions of a vector.

It sounds like your "Mvector" is just like the strike orthogonals that I mention above, which indeed would go in two different directions. One ball would take one "Mvector" and the other ball would take the other. The important part is controlling which takes which. After determining the magnitude of each Mvector, you would then do the vector addition of the balls respective incoming vector and their separate Mvectors. That would conserve some of their original momentum. If the balls were striking head on, then the Dot product calculation would reflect that fact by resulting in an Mvector magnitude of 0, and should result in a swapping of velocities for the balls as Bplus proved earlier. The result would work whether one ball, or both were moving. Isn't math fun....
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 12, 2021, 12:54:45 pm
Quote
Isn't math fun....

It is a total b... until you learn the jargon. I am pretty comfortable with Trig but find physics vectors difficult because I haven't practiced with 100's of exercises in it's terminology. The challenge with Trig and Physics is that Basic uses an upside down Quadrant 1 only graphics system which is not how you learned it in math at school. That, I learned is an artifact of printing with print lines always positive and increasing as you go down the screen a rows and cols system for chars. QB64's Locate row, col is a good example.

I have to say it is fun when it starts producing results for you in computer applications, it's like magic. They say knowledge is power and when math works it is a power alright!

Coffee helps with the practicing ;-)) Maybe something stronger when you've had enough abstract jargon for the day.

2 Ball Collision is really good challenge for any style math.
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 12, 2021, 01:06:16 pm
Yeah, the quadrant issue is such a bear that I generally resort to WINDOW to redefine my coordinate system. Then when I do use trig, I have to remain cognizant of the reversals of various SIN/COS/TAN functions with respect to my redefined system.

The only reason that I've been able to fool with vector math is Stx's tutorials, and many hours watching Professor Leonard's calculus videos on Youtube.

I'm toying with doing a little program to demo a Cross product function in action.
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 13, 2021, 09:18:40 am
https://www.vobarian.com/collisions/2dcollisions2.pdf

Quote
but my application had issues with balls blowing through each other and trading places or getting stuck on one another when in tight groups or high relative speeds.

Last night I coded the whole paper step by step. The code passes the head on test but I am getting balls sticking together also. I will recheck code today and show. Meanwhile I did get a JB by tsh73 port using vectors working fine and much simpler than paper. I will show that one later too. I want to set up 2 more special cases other than Head on ones where 2 balls travel nearly parallel but converging paths and see their bump and reflection then with 2 balls at 90 degree collision and se that reflection.
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 13, 2021, 04:29:04 pm
OK here is first vector code I got working for 2 or more ball collisions by barrowing from tsh73 at JB forum. He was working study for an app for pin ball and one ball was always moving and the other was stationary circle, so I adapted to 2 moving balls and may have oversimplified. Anyway it can handle 20 balls no sticking:

All the special case experiments work fine with expected results.
Code: QB64: [Select]
  1. _Title "tsh73 from JB: 2 Ball Collision with Vectors,  r will restart a test" 'B+ 2021-05-13
  2. ' This is tsh73 code for collision though he was doing stationary and moving, I made mod for both moving.
  3. ' The vector functions are modifications of tsh73 JB code converted to QB64 by me.
  4. ' earlier code 2 Ball Collision with Vectors by Arrow Addition ' B+ 2021-05-10
  5. ' 2021-05-10  fixed arrow so points from base x,y To angle
  6. ' from Collision Study #4   press spacebar to toggle tracer" ' b+ 2021-05-09 by bplus
  7.  
  8. ' !!!!!!!!!!!!!!!! Adjust Ball according to experiment (just after restart: line label) 2 is always good!
  9.  
  10. 'Const Balls = 2 '<<<<<<<<< change this to 2 to test special case setups: head on, parallel and perpendicular
  11. Const Balls = 6 '<<<<<<<<<  no more than 20! because have to start all balls not overlapping any other
  12.  
  13.  
  14. Const Xmax = 600 ' screen width
  15. Const Ymax = 600 ' screen height
  16. Const R = 50 '     balls radii
  17. Const R22 = R * R * 4 ' save some time with (2 * R) * (2 * R)
  18.  
  19. Const MV_Speed = 45 ' magnitude of Force Arrow say it's constant for simplicity sake
  20. Type Ball
  21.     As Long x, y
  22.     As Double dx, dy, a ' dx, dy = change x, y axis
  23.     As _Unsigned Long colr
  24.  
  25. Screen _NewImage(Xmax, Ymax, 32)
  26. _Delay .25
  27. ' these can be static as no balls added or subtracted in closed system
  28. Dim As Ball b(1 To Balls), nf(1 To Balls) ' b() is current frame balls data , nf( ) is for next frame balls data
  29. Dim As Long clrMode, i, j, rdm
  30. b(1).colr = &HFFFF0000 'red ball 1 is red
  31. b(2).colr = &HFF0000FF 'blue ball 2 is blue
  32. clrMode = 1
  33. Dim v1$, v2$, dv1$, dv2$, dv1u$, dv2u$, norm$ 'vectors
  34.  
  35. restart:
  36. 'horizontal collisions  Head On
  37. 'b(1).dx = 5
  38. 'b(1).dy = 0 ' b1 is just moving on x axis to the right at 5 pixels per frame
  39. 'b(2).dx = -5
  40. 'b(2).dy = 0 ' b2 is just moving on x axis to the left at 5 pixels per frame
  41. 'b(1).x = 300 - 5 * b(1).dx - R ' in 5 frames b1 will meet b2  kiss at center of screen
  42. 'b(1).y = 300
  43. 'b(2).x = 300 - 5 * b(2).dx + R ' in 5 frames b1 will meet b2  kiss at center of screen
  44. 'b(2).y = 300
  45.  
  46. 'vertical collisions  Head On
  47. 'b(1).dy = 5
  48. 'b(1).dx = 0 ' b1 is just moving on x axis to the right at 5 pixels per frame
  49. 'b(2).dy = -5
  50. 'b(2).dx = 0 ' b2 is just moving on x axis to the left at 5 pixels per frame
  51. 'b(1).y = 300 - 5 * b(1).dy - R ' in 5 frames b1 will meet b2  kiss at center of screen
  52. 'b(1).x = 300
  53. 'b(2).y = 300 - 5 * b(2).dy + R ' in 5 frames b1 will meet b2  kiss at center of screen
  54. 'b(2).x = 300
  55.  
  56. ' 45 & 135 collsions   Head On
  57. 'b(1).dy = 5
  58. 'b(1).dx = 5 ' b1 is just moving on x axis to the right at 5 pixels per frame
  59. 'b(2).dy = -5
  60. 'b(2).dx = -5 ' b2 is just moving on x axis to the left at 5 pixels per frame
  61. 'b(1).y = 300 - 5 * b(1).dy - R ' in 5 frames b1 will meet b2  kiss at center of screen
  62. 'b(1).x = 300 - 5 * b(1).dx - R
  63. 'b(2).y = 300 - 5 * b(2).dy + R ' in 5 frames b1 will meet b2  kiss at center of screen
  64. 'b(2).x = 300 - 5 * b(2).dx + R
  65.  
  66. ' Parallel and converging paths
  67. 'b(1).dy = -6
  68. 'b(1).dx = 1 ' b1 is just moving on x axis to the right at 5 pixels per frame
  69. 'b(2).dy = -6
  70. 'b(2).dx = -1 ' b2 is just moving on x axis to the left at 5 pixels per frame
  71. 'b(1).y = Ymax - R ' in 5 frames b1 will meet b2  kiss at center of screen
  72. 'b(1).x = 300 - 20 * b(1).dx - R
  73. 'b(2).y = Ymax - R ' in 5 frames b1 will meet b2  kiss at center of screen
  74. 'b(2).x = 300 - 20 * b(2).dx + R
  75.  
  76. ' Perpendicular and 90 degree collision   should be just before mid screen
  77. 'b(1).dy = 0
  78. 'b(1).dx = 5
  79. 'b(2).dy = -5
  80. 'b(2).dx = 0
  81. 'b(1).y = 300
  82. 'b(1).x = R
  83. 'b(2).y = Ymax - R
  84. 'b(2).x = 300
  85.  
  86. ' Random assignments    (comment this block for special case 2 ball experiments  and set balls to 2 !)  ==============
  87. For i = 1 To Balls
  88.     tryAgain:
  89.     Print i
  90.     b(i).x = rand&(R, Xmax - R)
  91.     b(i).y = rand&(R, Ymax - R)
  92.     For j = 1 To i - 1
  93.         If _Hypot(b(i).x - b(j).x, b(i).y - b(j).y) < R + R Then GoTo tryAgain
  94.     Next
  95.     b(i).dx = (Rnd * 4 + 1) * rdir
  96.     b(i).dy = (Rnd * 4 + 1) * rdir
  97.     rdm = rand&(1, 7)
  98.     If rdm < 1 Or rdm > 7 Then Beep
  99.     Select Case rdm
  100.         Case 1: b(i).colr = _RGB32(Rnd * 200 + 55, 0, 0)
  101.         Case 2: b(i).colr = _RGB32(0, Rnd * 100 + 55, 0)
  102.         Case 3: b(i).colr = _RGB32(0, 0, Rnd * 200 + 55)
  103.         Case 4: b(i).colr = &HFFFF6600
  104.         Case 5: b(i).colr = &HFFFF0088
  105.         Case 6: b(i).colr = &HFF00FF88
  106.         Case 7: b(i).colr = _RGB32(Rnd * 200 + 55, Rnd * 200 + 55, Rnd * 200 + 55)
  107.     End Select
  108. '====================================================================================================================
  109.  
  110. While _KeyDown(27) = 0
  111.     Dim k$
  112.     k$ = InKey$
  113.     If Len(k$) Then
  114.         If Asc(k$) = 32 Then clrMode = -1 * clrMode
  115.         If Asc(k$) = 27 And Len(k$) = 1 Then End
  116.         If k$ = "r" Then GoTo restart
  117.     End If
  118.     If clrMode > 0 Then Cls
  119.     Circle (300, 300), 2
  120.     For i = 1 To Balls ' draw balls then  update for next frame
  121.  
  122.         ' this just draw the balls with arrows pointing to their headings
  123.         Circle (b(i).x, b(i).y), R, b(i).colr
  124.         b(i).a = _Atan2(b(i).dy, b(i).dx)
  125.         ArrowTo b(i).x, b(i).y, b(i).a, MV_Speed, &HFFFFFF00
  126.         ' debug
  127.         '_PrintString (b(i).x - 8, b(i).y - 8), _Trim$(Right$("00" + Str$(i), 2)) 'all the balls weren't getting colored
  128.  
  129.         ' check for collision
  130.         Dim cd, saveJ
  131.         cd = 100000: saveJ = 0
  132.         For j = 1 To Balls 'find deepest collision in case more than one we want earliest = deepest penetration
  133.             If i <> j Then
  134.                 Dim dx, dy
  135.                 dx = b(i).x - b(j).x: dy = b(i).y - b(j).y
  136.                 If dx * dx + dy * dy <= R22 Then ' collision but is it first or deepest collision
  137.                     If R22 - dx * dx + dy * dy < cd Then cd = R22 - dx * dx + dy * dy: saveJ = j
  138.                 End If
  139.             End If
  140.         Next
  141.         If cd <> 100000 Then ' found collision change ball i dx, dy   calc new course for ball i
  142.  
  143.             ''reflection  from circle  using Vectors  from JB, thanks tsh73
  144.             v1$ = vect$(b(i).x, b(i).y) ' circle i
  145.             v2$ = vect$(b(saveJ).x, b(saveJ).y) ' the other circle j
  146.             dv1$ = vect$(b(i).dx, b(i).dy) ' change in velocity vector
  147.             dv2$ = vect$(b(saveJ).dx, b(saveJ).dy)
  148.             dv1u$ = vectUnit$(dv1$) '1 pixel
  149.             dv2u$ = vectUnit$(dv2$)
  150.             'Print dv$, cv$, dv0$   ' check on things
  151.             '_Display
  152.             'Sleep
  153.             Do ' this should back up the balls to kiss point thanks tsh73
  154.                 v1$ = vectSub$(v1$, dv1u$)
  155.                 v2$ = vectSub(v2$, dv2u$)
  156.             Loop While vectLen(vectSub$(v1$, v2$)) < R + R 'back up our circle i to point on kiss
  157.             ''now, get reflection
  158.             ''radius to radius, norm is
  159.             norm$ = vectSub$(v1$, v2$)
  160.             dv1$ = vectSub$(vectNorm$(dv1$, norm$), vectTangent$(dv1$, norm$)) 'to this
  161.  
  162.             ' store in next frame array
  163.             nf(i).dx = vectX(dv1$)
  164.             nf(i).dy = vectY(dv1$)
  165.  
  166.         Else ' no collision
  167.             nf(i).dx = b(i).dx
  168.             nf(i).dy = b(i).dy
  169.         End If
  170.         'update location of ball next frame
  171.         nf(i).x = b(i).x + nf(i).dx
  172.         nf(i).y = b(i).y + nf(i).dy
  173.  
  174.         ' check in bounds next frame
  175.         If nf(i).x < R Then nf(i).dx = -nf(i).dx: nf(i).x = R
  176.         If nf(i).x > Xmax - R Then nf(i).dx = -nf(i).dx: nf(i).x = Xmax - R
  177.         If nf(i).y < R Then nf(i).dy = -nf(i).dy: nf(i).y = R
  178.         If nf(i).y > Ymax - R Then nf(i).dy = -nf(i).dy: nf(i).y = Ymax - R
  179.     Next
  180.  
  181.     ''now that we've gone through all old locations update b() with nf() data
  182.     For i = 1 To Balls
  183.         b(i).x = nf(i).x: b(i).y = nf(i).y
  184.         b(i).dx = nf(i).dx: b(i).dy = nf(i).dy
  185.     Next
  186.     ' next frame ready to draw
  187.     _Display
  188.     _Limit 30
  189. 'Cls   ' debug why aren't all the balls getting colored
  190. 'For i = 1 To Balls
  191. '    Print i, b(i).colr
  192. 'Next
  193.  
  194. Function rand& (lo As Long, hi As Long) ' fixed?
  195.     rand& = Int(Rnd * (hi - lo) + 1) + lo
  196.  
  197. Function rdir ()
  198.     If Rnd < .5 Then rdir = -1 Else rdir = 1
  199.  
  200. Sub ArrowTo (BaseX As Long, BaseY As Long, rAngle As Double, lngth As Long, colr As _Unsigned Long)
  201.     Dim As Long x1, y1, x2, y2, x3, y3
  202.     x1 = BaseX + lngth * Cos(rAngle)
  203.     y1 = BaseY + lngth * Sin(rAngle)
  204.     x2 = BaseX + .8 * lngth * Cos(rAngle - _Pi(.05))
  205.     y2 = BaseY + .8 * lngth * Sin(rAngle - _Pi(.05))
  206.     x3 = BaseX + .8 * lngth * Cos(rAngle + _Pi(.05))
  207.     y3 = BaseY + .8 * lngth * Sin(rAngle + _Pi(.05))
  208.     Line (BaseX, BaseY)-(x1, y1), colr
  209.     Line (x1, y1)-(x2, y2), colr
  210.     Line (x1, y1)-(x3, y3), colr
  211.  
  212. ' convert some vector functions from JB, turns out much easier to use string functions to pass vectors
  213. ' than using SUBs to do vector calcs
  214.  
  215. Function vect$ (x, y) ' convert x, y to string for passing vectors with Functions
  216.     vect$ = _Trim$(Str$(x)) + "," + _Trim$(Str$(y))
  217.  
  218. Function vectX (v$)
  219.     vectX = Val(LeftOf$(v$, ","))
  220.  
  221. Function vectY (v$)
  222.     vectY = Val(RightOf$(v$, ","))
  223.  
  224. Function vectLen (v$)
  225.     Dim x, y
  226.     x = Val(LeftOf$(v$, ","))
  227.     y = Val(RightOf$(v$, ","))
  228.     vectLen = Sqr(x * x + y * y)
  229.  
  230. Function vectUnit$ (v$)
  231.     Dim x, y, vl
  232.     x = Val(LeftOf$(v$, ","))
  233.     y = Val(RightOf$(v$, ","))
  234.     vl = Sqr(x * x + y * y)
  235.     vectUnit$ = vect$(x / vl, y / vl)
  236.  
  237. Function vectAdd$ (v1$, v2$)
  238.     Dim x1, y1, x2, y2
  239.     x1 = Val(LeftOf$(v1$, ","))
  240.     y1 = Val(RightOf$(v1$, ","))
  241.     x2 = Val(LeftOf$(v2$, ","))
  242.     y2 = Val(RightOf$(v2$, ","))
  243.     vectAdd$ = vect$(x1 + x2, y1 + y2)
  244.  
  245. Function vectSub$ (v1$, v2$)
  246.     Dim x1, y1, x2, y2
  247.     x1 = Val(LeftOf$(v1$, ","))
  248.     y1 = Val(RightOf$(v1$, ","))
  249.     x2 = Val(LeftOf$(v2$, ","))
  250.     y2 = Val(RightOf$(v2$, ","))
  251.     vectSub$ = vect$(x1 - x2, y1 - y2)
  252.  
  253. Function vectDotProduct (v1$, v2$)
  254.     Dim x1, y1, x2, y2
  255.     x1 = Val(LeftOf$(v1$, ","))
  256.     y1 = Val(RightOf$(v1$, ","))
  257.     x2 = Val(LeftOf$(v2$, ","))
  258.     y2 = Val(RightOf$(v2$, ","))
  259.     vectDotProduct = x1 * x2 + y1 * y2
  260.  
  261. Function vectScale$ (a, v$) 'a * vector v$
  262.     Dim x, y
  263.     x = Val(LeftOf$(v$, ","))
  264.     y = Val(RightOf$(v$, ","))
  265.     vectScale$ = vect$(a * x, a * y)
  266.  
  267. Function vectTangent$ (v$, base$)
  268.     Dim n$
  269.     n$ = vectUnit$(base$)
  270.     vectTangent$ = vectScale$(vectDotProduct(n$, v$), n$)
  271.  
  272. Function vectNorm$ (v$, base$)
  273.     vectNorm$ = vectSub$(v$, vectTangent$(v$, base$))
  274.  
  275. ' update these 2 in case of$ is not found! 2021-02-13
  276. Function LeftOf$ (source$, of$)
  277.     If InStr(source$, of$) > 0 Then LeftOf$ = Mid$(source$, 1, InStr(source$, of$) - 1) Else LeftOf$ = source$
  278.  
  279. ' update these 2 in case of$ is not found! 2021-02-13
  280. Function RightOf$ (source$, of$)
  281.     If InStr(source$, of$) > 0 Then RightOf$ = Mid$(source$, InStr(source$, of$) + Len(of$)) Else RightOf$ = ""
  282.  
  283.  
  284.  

Title: Re: 2D ball collisions without trigonometry.
Post by: justsomeguy on May 13, 2021, 04:42:37 pm
Nice. Seems to work pretty good. Occasionally two balls would get stuck on each other, but awesome progress. Physics is hard! 
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 13, 2021, 04:59:29 pm
Oh man you are getting sticky balls with that code! Well this next is much more fragile keep number of balls low and not too bad, it does really interesting things with ball speeds!

This is my attempt to follow the paper OldMoses gave link to, see ref in code:
Code: QB64: [Select]
  1. _Title "From Paper 2 Ball Collision with Vectors,  press r to restart" 'B+ 2021-05-13
  2. ' Collision tests based on Paper       https://www.vobarian.com/collisions/2dcollisions2.pdf
  3. ' The vector functions are modifications of tsh73 JB code converted to QB64 by me.
  4. ' earlier code 2 Ball Collision with Vectors by Arrow Addition ' B+ 2021-05-10
  5. ' 2021-05-10  fixed arrow so points from base x,y To angle
  6. ' from Collision Study #4   press spacebar to toggle tracer" ' b+ 2021-05-09
  7.  
  8. ' !!!!!!!!!!!!!!!! Adjust Ball according to experiment 2 is always good
  9.  
  10. 'Const Balls = 2 '<<<<<<<<< change this to 2 to test special case setups: head on, parallel and perpendicular
  11. Const Balls = 5 '<<<<<<<<< the more the balls the more problems pop up for Random Numbers experiments
  12. '                            no more than 20! because have to start all balls not overlapping any other
  13.  
  14.  
  15. Const Xmax = 600 ' screen width
  16. Const Ymax = 600 ' screen height
  17. Const R = 50 '     balls radii
  18. Const R22 = R * R * 4 ' save some time with (2 * R) * (2 * R)
  19.  
  20. Const MV_Speed = 45 ' magnitude of Force Arrow say it's constant for simplicity sake
  21. Type Ball
  22.     As Long x, y
  23.     As Double dx, dy, a ' dx, dy = change x, y axis
  24.     As _Unsigned Long colr
  25.  
  26. Screen _NewImage(Xmax, Ymax, 32)
  27. _Delay .25
  28. ' these can be static as no balls added or subtracted in closed system
  29. Dim As Ball b(1 To Balls), nf(1 To Balls) ' b() is current frame balls data , nf( ) is for next frame balls data
  30. Dim As Long clrMode, i, j, rdm
  31. b(1).colr = &HFFFF0000 'red ball 1 is red
  32. b(2).colr = &HFF0000FF 'blue ball 2 is blue
  33. clrMode = 1
  34. Dim v1$, v2$, dv1$, dv2$, dv1u$, dv2u$, norm$, unitNorm$, unitTan$ 'vectors
  35. Dim vp1n$, vp1t$, vp2n$, vp2t$ ' post collision vectors
  36. Dim As Double v1n, v1t, v2n, v2t ' dot products
  37. Dim As Double vp1n, vp1t, vp2n, vp2t ' post collision dot products
  38. restart:
  39. 'horizontal collisions  Head On
  40. 'b(1).dx = 5
  41. 'b(1).dy = 0 ' b1 is just moving on x axis to the right at 5 pixels per frame
  42. 'b(2).dx = -5
  43. 'b(2).dy = 0 ' b2 is just moving on x axis to the left at 5 pixels per frame
  44. 'b(1).x = 300 - 5 * b(1).dx - R ' in 5 frames b1 will meet b2  kiss at center of screen
  45. 'b(1).y = 300
  46. 'b(2).x = 300 - 5 * b(2).dx + R ' in 5 frames b1 will meet b2  kiss at center of screen
  47. 'b(2).y = 300
  48.  
  49. 'vertical collisions  Head On
  50. 'b(1).dy = 5
  51. 'b(1).dx = 0 ' b1 is just moving on x axis to the right at 5 pixels per frame
  52. 'b(2).dy = -5
  53. 'b(2).dx = 0 ' b2 is just moving on x axis to the left at 5 pixels per frame
  54. 'b(1).y = 300 - 5 * b(1).dy - R ' in 5 frames b1 will meet b2  kiss at center of screen
  55. 'b(1).x = 300
  56. 'b(2).y = 300 - 5 * b(2).dy + R ' in 5 frames b1 will meet b2  kiss at center of screen
  57. 'b(2).x = 300
  58.  
  59. ' 45 & 135 collsions   Head On
  60. 'b(1).dy = 5
  61. 'b(1).dx = 5 ' b1 is just moving on x axis to the right at 5 pixels per frame
  62. 'b(2).dy = -5
  63. 'b(2).dx = -5 ' b2 is just moving on x axis to the left at 5 pixels per frame
  64. 'b(1).y = 300 - 5 * b(1).dy - R ' in 5 frames b1 will meet b2  kiss at center of screen
  65. 'b(1).x = 300 - 5 * b(1).dx - R
  66. 'b(2).y = 300 - 5 * b(2).dy + R ' in 5 frames b1 will meet b2  kiss at center of screen
  67. 'b(2).x = 300 - 5 * b(2).dx + R
  68.  
  69. ' Parallel and converging paths
  70. 'b(1).dy = -6
  71. 'b(1).dx = 1 ' b1 is just moving on x axis to the right at 5 pixels per frame
  72. 'b(2).dy = -6
  73. 'b(2).dx = -1 ' b2 is just moving on x axis to the left at 5 pixels per frame
  74. 'b(1).y = Ymax - R ' in 5 frames b1 will meet b2  kiss at center of screen
  75. 'b(1).x = 300 - 20 * b(1).dx - R
  76. 'b(2).y = Ymax - R ' in 5 frames b1 will meet b2  kiss at center of screen
  77. 'b(2).x = 300 - 20 * b(2).dx + R
  78.  
  79. ' Perpendicular and 90 degree collision   should be just before mid screen
  80. 'b(1).dy = 0
  81. 'b(1).dx = 5
  82. 'b(2).dy = -5
  83. 'b(2).dx = 0
  84. 'b(1).y = 300
  85. 'b(1).x = R
  86. 'b(2).y = Ymax - R
  87. 'b(2).x = 300
  88.  
  89. ' random assignments
  90. For i = 1 To Balls
  91.     tryAgain:
  92.     Print i
  93.     b(i).x = rand&(R, Xmax - R)
  94.     b(i).y = rand&(R, Ymax - R)
  95.     For j = 1 To i - 1
  96.         If _Hypot(b(i).x - b(j).x, b(i).y - b(j).y) < R + R Then GoTo tryAgain
  97.     Next
  98.     b(i).dx = (Rnd * 4 + 1) * rdir
  99.     b(i).dy = (Rnd * 4 + 1) * rdir
  100.     rdm = rand&(1, 7)
  101.     If rdm < 1 Or rdm > 7 Then Beep
  102.     Select Case rdm
  103.         Case 1: b(i).colr = _RGB32(Rnd * 200 + 55, 0, 0)
  104.         Case 2: b(i).colr = _RGB32(0, Rnd * 100 + 55, 0)
  105.         Case 3: b(i).colr = _RGB32(0, 0, Rnd * 200 + 55)
  106.         Case 4: b(i).colr = &HFFFF6600
  107.         Case 5: b(i).colr = &HFFFF0088
  108.         Case 6: b(i).colr = &HFF00FF88
  109.         Case 7: b(i).colr = _RGB32(Rnd * 200 + 55, Rnd * 200 + 55, Rnd * 200 + 55)
  110.     End Select
  111.  
  112. While _KeyDown(27) = 0
  113.     Dim k$
  114.     k$ = InKey$
  115.     If Len(k$) Then
  116.         If Asc(k$) = 32 Then clrMode = -1 * clrMode
  117.         If Asc(k$) = 27 And Len(k$) = 1 Then End
  118.         If k$ = "r" Then GoTo restart
  119.     End If
  120.     If clrMode > 0 Then Cls
  121.     Circle (300, 300), 2
  122.     For i = 1 To Balls ' draw balls then  update for next frame
  123.  
  124.         ' this just draw the balls with arrows pointing to their headings
  125.         Circle (b(i).x, b(i).y), R, b(i).colr
  126.         b(i).a = _Atan2(b(i).dy, b(i).dx)
  127.         ArrowTo b(i).x, b(i).y, b(i).a, MV_Speed, &HFFFFFF00
  128.         ' debug
  129.         '_PrintString (b(i).x - 8, b(i).y - 8), _Trim$(Right$("00" + Str$(i), 2)) 'all the balls weren't getting colored
  130.  
  131.         ' check for collision
  132.         Dim cd, saveJ
  133.         cd = 100000: saveJ = 0
  134.         For j = 1 To Balls 'find deepest collision in case more than one we want earliest = deepest penetration
  135.             If i <> j Then
  136.                 Dim dx, dy
  137.                 dx = b(i).x - b(j).x: dy = b(i).y - b(j).y
  138.                 If dx * dx + dy * dy <= R22 Then ' collision but is it first or deepest collision
  139.                     If R22 - dx * dx + dy * dy < cd Then cd = R22 - dx * dx + dy * dy: saveJ = j
  140.                 End If
  141.             End If
  142.         Next
  143.         If cd <> 100000 Then ' found collision change ball i dx, dy   calc new course for ball i
  144.  
  145.             ''reflection  from circle  using Vectors  from JB, thanks tsh73
  146.             v1$ = vect$(b(i).x, b(i).y) ' circle i
  147.             v2$ = vect$(b(saveJ).x, b(saveJ).y) ' the other circle j
  148.             dv1$ = vect$(b(i).dx, b(i).dy) ' change in velocity vector
  149.             dv2$ = vect$(b(saveJ).dx, b(saveJ).dy)
  150.             dv1u$ = vectUnit$(dv1$) '1 pixel
  151.             dv2u$ = vectUnit$(dv2$)
  152.             'Print dv$, cv$, dv0$   ' check on things
  153.             '_Display
  154.             'Sleep
  155.             Do ' this should back up the balls to kiss point thanks tsh73
  156.                 v1$ = vectSub$(v1$, dv1u$)
  157.                 v2$ = vectSub(v2$, dv2u$)
  158.             Loop While vectLen(vectSub$(v1$, v2$)) < R + R 'back up our circle i to point on kiss
  159.             ''now, get reflection speed
  160.             ''radius to radius, norm is
  161.             norm$ = vectSub$(v1$, v2$) ' this to this worked without all between from that collision paper
  162.  
  163.             '  step 1 unit norm and tangent
  164.             unitNorm$ = vectUnit$(norm$)
  165.             unitTan$ = vect$(-vectY(unitNorm$), vectX(unitNorm$))
  166.             ' step 2 v$ and cv$ are 2 ball vectors (locations)  done already
  167.             ' step 3 dot products before collision projecting onto normal and tangent vectors
  168.             v1n = vectDotProduct(dv1$, unitNorm$)
  169.             v1t = vectDotProduct(dv1$, unitTan$)
  170.             v2n = vectDotProduct(dv2$, unitNorm$)
  171.             v2t = vectDotProduct(dv2$, unitTan$)
  172.             ' step 4 simplest  post collision dot products
  173.             vp1t = v1t
  174.             vp2t = v2t
  175.             ' step 5  simplified by m = 1 for both balls just swap the numbers
  176.             vp1n = v2n
  177.             vp2n = v1n
  178.             ' step 6  vp vectors mult the n, t numbers by unit vectors
  179.             vp1n$ = vectScale$(vp1n, unitNorm$)
  180.             vp1t$ = vectScale$(vp1t, unitTan$)
  181.             vp2n$ = vectScale$(vp2n, unitNorm$)
  182.             vp2t$ = vectScale$(vp2t, unitTan$)
  183.             'step  7  add the 2 vectors n and t
  184.             dv1$ = vectAdd$(vp1n$, vp1t$)
  185.  
  186.             ' to this  now just switch tangent and norm
  187.             'dv1$ = vectSub$(vectNorm$(dv1$, norm$), vectTangent$(dv1$, norm$)) 'to this
  188.  
  189.             ' store in next frame array
  190.             nf(i).dx = vectX(dv1$)
  191.             nf(i).dy = vectY(dv1$)
  192.  
  193.         Else ' no collision
  194.             nf(i).dx = b(i).dx
  195.             nf(i).dy = b(i).dy
  196.         End If
  197.         'update location of ball next frame
  198.         nf(i).x = b(i).x + nf(i).dx
  199.         nf(i).y = b(i).y + nf(i).dy
  200.  
  201.         ' check in bounds next frame
  202.         If nf(i).x < R Then nf(i).dx = -nf(i).dx: nf(i).x = R
  203.         If nf(i).x > Xmax - R Then nf(i).dx = -nf(i).dx: nf(i).x = Xmax - R
  204.         If nf(i).y < R Then nf(i).dy = -nf(i).dy: nf(i).y = R
  205.         If nf(i).y > Ymax - R Then nf(i).dy = -nf(i).dy: nf(i).y = Ymax - R
  206.     Next
  207.  
  208.     ''now that we've gone through all old locations update b() with nf() data
  209.     For i = 1 To Balls
  210.         b(i).x = nf(i).x: b(i).y = nf(i).y
  211.         b(i).dx = nf(i).dx: b(i).dy = nf(i).dy
  212.     Next
  213.     ' next frame ready to draw
  214.     _Display
  215.     _Limit 30
  216. 'Cls   ' debug why aren't all the balls getting colored
  217. 'For i = 1 To Balls
  218. '    Print i, b(i).colr
  219. 'Next
  220.  
  221.  
  222. Function rand& (lo As Long, hi As Long) ' fixed?
  223.     rand& = Int(Rnd * (hi - lo) + 1) + lo
  224.  
  225. Function rdir ()
  226.     If Rnd < .5 Then rdir = -1 Else rdir = 1
  227.  
  228.  
  229.  
  230. Sub ArrowTo (BaseX As Long, BaseY As Long, rAngle As Double, lngth As Long, colr As _Unsigned Long)
  231.     Dim As Long x1, y1, x2, y2, x3, y3
  232.     x1 = BaseX + lngth * Cos(rAngle)
  233.     y1 = BaseY + lngth * Sin(rAngle)
  234.     x2 = BaseX + .8 * lngth * Cos(rAngle - _Pi(.05))
  235.     y2 = BaseY + .8 * lngth * Sin(rAngle - _Pi(.05))
  236.     x3 = BaseX + .8 * lngth * Cos(rAngle + _Pi(.05))
  237.     y3 = BaseY + .8 * lngth * Sin(rAngle + _Pi(.05))
  238.     Line (BaseX, BaseY)-(x1, y1), colr
  239.     Line (x1, y1)-(x2, y2), colr
  240.     Line (x1, y1)-(x3, y3), colr
  241.  
  242. ' convert some vector functions from JB, turns out much easier to use string functions to pass vectors
  243. ' than using SUBs to do vector calcs
  244.  
  245. Function vect$ (x, y) ' convert x, y to string for passing vectors with Functions
  246.     vect$ = _Trim$(Str$(x)) + "," + _Trim$(Str$(y))
  247.  
  248. Function vectX (v$)
  249.     vectX = Val(LeftOf$(v$, ","))
  250.  
  251. Function vectY (v$)
  252.     vectY = Val(RightOf$(v$, ","))
  253.  
  254. Function vectLen (v$)
  255.     Dim x, y
  256.     x = Val(LeftOf$(v$, ","))
  257.     y = Val(RightOf$(v$, ","))
  258.     vectLen = Sqr(x * x + y * y)
  259.  
  260. Function vectUnit$ (v$)
  261.     Dim x, y, vl
  262.     x = Val(LeftOf$(v$, ","))
  263.     y = Val(RightOf$(v$, ","))
  264.     vl = Sqr(x * x + y * y)
  265.     vectUnit$ = vect$(x / vl, y / vl)
  266.  
  267. Function vectAdd$ (v1$, v2$)
  268.     Dim x1, y1, x2, y2
  269.     x1 = Val(LeftOf$(v1$, ","))
  270.     y1 = Val(RightOf$(v1$, ","))
  271.     x2 = Val(LeftOf$(v2$, ","))
  272.     y2 = Val(RightOf$(v2$, ","))
  273.     vectAdd$ = vect$(x1 + x2, y1 + y2)
  274.  
  275. Function vectSub$ (v1$, v2$)
  276.     Dim x1, y1, x2, y2
  277.     x1 = Val(LeftOf$(v1$, ","))
  278.     y1 = Val(RightOf$(v1$, ","))
  279.     x2 = Val(LeftOf$(v2$, ","))
  280.     y2 = Val(RightOf$(v2$, ","))
  281.     vectSub$ = vect$(x1 - x2, y1 - y2)
  282.  
  283. Function vectDotProduct (v1$, v2$)
  284.     Dim x1, y1, x2, y2
  285.     x1 = Val(LeftOf$(v1$, ","))
  286.     y1 = Val(RightOf$(v1$, ","))
  287.     x2 = Val(LeftOf$(v2$, ","))
  288.     y2 = Val(RightOf$(v2$, ","))
  289.     vectDotProduct = x1 * x2 + y1 * y2
  290.  
  291. Function vectScale$ (a, v$) 'a * vector v$
  292.     Dim x, y
  293.     x = Val(LeftOf$(v$, ","))
  294.     y = Val(RightOf$(v$, ","))
  295.     vectScale$ = vect$(a * x, a * y)
  296.  
  297. Function vectTangent$ (v$, base$)
  298.     Dim n$
  299.     n$ = vectUnit$(base$)
  300.     vectTangent$ = vectScale$(vectDotProduct(n$, v$), n$)
  301.  
  302. Function vectNorm$ (v$, base$)
  303.     vectNorm$ = vectSub$(v$, vectTangent$(v$, base$))
  304.  
  305. ' update these 2 in case of$ is not found! 2021-02-13
  306. Function LeftOf$ (source$, of$)
  307.     If InStr(source$, of$) > 0 Then LeftOf$ = Mid$(source$, 1, InStr(source$, of$) - 1) Else LeftOf$ = source$
  308.  
  309. ' update these 2 in case of$ is not found! 2021-02-13
  310. Function RightOf$ (source$, of$)
  311.     If InStr(source$, of$) > 0 Then RightOf$ = Mid$(source$, InStr(source$, of$) + Len(of$)) Else RightOf$ = ""
  312.  
  313.  
  314.  

PS It also passed all the special case collision tests: head on (3 angles), parallel converging and perpendicular.
The more balls the more sticky ball syndrome and sometime code will hang because not calculating fast enough for _limit to keep it smooth? But as I said really interesting, realistic?, changes in speed from collisions.

PPS make sure you have Const Balls set to 2 for all the Special Case Tests and comment last experiment and uncomment your next experiment.
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 13, 2021, 07:13:04 pm
That looks pretty good. I like how it handles balls getting into tight bunches, which was the downfall of my billiards program.
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 14, 2021, 04:07:08 pm
That looks pretty good. I like how it handles balls getting into tight bunches, which was the downfall of my billiards program.

Yes, I was very pleasantly surprised the ball action could stun balls into stillness or nearly so and then be made active again with just another little bump. The tsh73 version (which may never have been intended for 2 moving balls) and my own ball collision with trig only always have balls moving. My trig version you could start with overlapping balls and they will sort themselves and be clear of each other in a few frames, never a sticky ball but I never got balls stunned to stillness. It's a nice effect hinting I should do some more work trying to speed up the system.

How could I loose the $ Functions and use numbers and still have functions for vectors?
Yeah go ahead and say FB LOL!
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 17, 2021, 09:16:35 pm
Some vector addition exercises.

vector addition  for 2 ball collisions with equal mass and velocity
********************************************

say, Red ball heading -1x, 0y towards 0, 0  (origin)

say, Cyan ball heading  1x, -1y towards 0, 0  (origin)

add the ball vectors

first reverse signs

red ball =  1x, 0y
cyan ball = -1x, 1y

add the reversed ball vectors
1x+-1x,  0y+ 1y = 0x, 1y (mvector)

mvector is wrong (in this case)  because cyan ball velocity is greater than red ball velocity

 The directions of the red ball -1x, 0y and cyan ball 1x, 1y are correct but their magnitudes are not equal.
the solution is to match the red ball velocity with the cyan ball velocity

 Red ball is now  -2^.5x, 0y

cyan ball the same as before  1x, -1y

try again - first reverse signs

red ball = 2^.5x, 0y
cyan ball = -1x, 1y

add the reversed ball vectors
2^.5x-1x,  0y+ 1y = 0.4142135x, 1y (mvector)

 what angle is that?

 arc tan 0.4142135 = 22.5 degrees

The new ball directions are:

 Red ball vector after collision
 -2^.5x, + 0.4142135x,  0y +  1y    =  -1x,   1 y    (balls have the same final velocity)

 Cyan ball vector after collision
 1x, + 0.4142135x,  -1y +  1y    =  1.4142135x,   0y  (balls have the same final velocity)

 which should be correct if the strike angle is 22.5 degrees. In other words,
 if a line through both ball centers at collision is at a right angle to mvector

*****
With 2 balls of unequal velocity and everything else the same, the direction of mvector will change.  It looks like the difference in velocity is somehow related to a rotation of mvector.  The case where the strike angle agrees with mvector (a line through both ball centers at collision is at a right angle to mvector) was for normalized ball velocities.   

If the ball directions are maintained but the velocities are different then there is a rotation of mvector as respects strike angle agreement.

It is not known if this rotation of mvector represents the correct final ball vectors due to a difference in velocity only.



Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 17, 2021, 11:10:56 pm
I think the missing element is the balls center positions at contact. You reference "origin", but they can't both occupy (0, 0). There would be no vector from which to obtain a perpendicular movement vector. Not to mention that they would occupy the same point in space...

This is a condensed version of the algorithm I've been working on, using the terms you posted. It seems to work with my billiard ball impacts and yields a unit vector for each ball to exit the collision. Changing the impact positions (redpos & cyanpos) will give the appropriate direction for each ball. That direction can then be scaled up by whatever speeds are determined by further processing. Using the dot product calculation, there is no need to match velocities. Dot product can also handle the force transference.
Code: QB64: [Select]
  1. TYPE V2
  2.     x AS SINGLE
  3.     y AS SINGLE
  4.  
  5. DIM AS V2 redmov, cyanmov, mvec, redpos, cyanpos, strike
  6.  
  7. 'These are the approach vectors you give
  8. redmov.x = -1: redmov.y = 0
  9. cyanmov.x = 1: cyanmov.y = -1
  10.  
  11. 'The important part is, where are the balls when they make contact. This determines the strike vector between them
  12. 'choosing arbitrary points of contact... change these around to see the effect to verify its accuracy
  13. redpos.x = 2: redpos.y = 2
  14. cyanpos.x = 1: cyanpos.y = 1
  15.  
  16. strike.x = cyanpos.x - redpos.x
  17. strike.y = cyanpos.y - redpos.y
  18. ShearOrthoUnit mvec, redmov, strike
  19. PRINT "red exits at <"; mvec.x; ", "; mvec.y; ">"
  20.  
  21. strike.x = redpos.x - cyanpos.x
  22. strike.y = redpos.y - cyanpos.y
  23. ShearOrthoUnit mvec, cyanmov, strike
  24. PRINT "cyan exits at <"; mvec.x; ", "; mvec.y; ">"
  25.  
  26. SUB ShearOrthoUnit (nv AS V2, in AS V2, stk AS V2)
  27.  
  28.     DIM AS V2 or1, or2
  29.     VecNorm stk '                                               normalize the strike vector
  30.     or1.x = stk.y: or1.y = -stk.x '                             obtain two potential orthogonal vectors {or1 & or2} of strike
  31.     or2.x = -stk.y: or2.y = stk.x
  32.     dot = VecDot(in, or1) '                                     dot product one of them with the incoming vector
  33.     nv.x = -or2.x * (dot < 0) - stk.x * (dot = 0) - or1.x * (dot > 0) 'exit vector x component
  34.     nv.y = -or2.y * (dot < 0) - stk.y * (dot = 0) - or1.y * (dot > 0) 'exit vector y component
  35.  
  36. END SUB 'ShearOrthoUnit
  37.  
  38.  
  39. SUB VecNorm (var AS V2)
  40.  
  41.     m = SQR(var.x * var.x + var.y * var.y)
  42.     IF m = 0 THEN
  43.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  44.     ELSE
  45.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  46.     END IF
  47.  
  48. END SUB 'VecNorm
  49.  
  50.  
  51. FUNCTION VecDot (var AS V2, var2 AS V2)
  52.  
  53.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  54.  
  55. END FUNCTION 'VecDot
  56.  
  57.  
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 18, 2021, 12:01:54 am
Quote
You reference "origin", but they can't both occupy (0, 0). There would be no vector from which to obtain a perpendicular movement vector. Not to mention that they would occupy the same point in space...

that is correct. I tried  to get around that with "point masses" but that was too abstract.  The ball direction are on a line that crosses 0, 0. The size of the balls (i like 40 pixel radius) determines the magnitude in this case the normal velocity is 80 pixels (per hour?)

The strike vector,  can be referenced to another vector (like mvector) because the strike vector passes through 0x, 0y.  It is somewhat an abstract idea but I don't have any other way of relating mvector to strike angle / vector. I should mention that mvector passes through 0x , 0y as well.


I ran your code  using

red ball   -2^.5x, 0y heading   towards  0, 0

cyan ball  -1x,  1y   heading towards 0, 0

and got different results

Forgot to set the strike vectors
To make this easier Im  gonna use pixels

red ball   -80 , 0y heading   towards  0, 0

cyan ball  (3200^.5) x, -(3200^.5) y   heading towards 0, 0

strike.x = 73.9103 pixels
strike.y = 30.6146 pixels

arc tan 30.6146 / 73.9103 = 22.5 degrees

in this case the strike vector is at a right angle to mvector.  I don't think there is any vector rotation to correct for in this case.

Red ball vector after collision
   =  -(3200^.5) x,  3200^.5 y   ?

 Cyan ball vector after collision
  =  80x,   0y   ?

your code gives different results
 






Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 18, 2021, 08:14:38 am
Ugg, it would appear that the code I posted either has a bug, or my math is screwy. I plugged these numbers in: red (-80, 0)   cyan (3200 ^ .5, -(3200 ^ .5))

red ball   -80 , 0y heading   towards  0, 0

cyan ball  (3200^.5) x, -(3200^.5) y   heading towards 0, 0

and got identical exit vectors <-.3826834, -.9238796> for both balls, which should not have happened. The result should have been swapped components and signs from one ball to the other. Embarrassing to do that, I'd much rather be helpful than a distraction. Oh well, back to the drawing board...



On a point of terminology, forgive me if I'm a bit confused by your usage in the above.
I'm used to conceiving vectors as either position references (x, y) relative to origin (0, 0), or to other positions, or as magnitude/speed/force types <x, y> which may exist and be manipulated without reference to positional data.

A red ball at position (-80, 0) moving toward (0, 0) would have a magnitude/speed/force vector <0 - (-80)x, 0 - 0y> or <80, 0>. This would 'seem' to be how you're using the terms by saying "moving towards".

If, on the other hand the red ball is moving along <-80, 0> a reference to origin shouldn't be necessary for the vector math, except as updating a position after all force vectors are calculated, scaled and added to a previous position.

Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 18, 2021, 06:05:33 pm
Quote
A red ball at position (-80, 0) moving toward (0, 0) would have a magnitude/speed/force vector <0 - (-80)x, 0 - 0y> or <80, 0>. This would 'seem' to be how you're using the terms by saying "moving towards".

I'm using balls with 40 pixel radius. That gives a normal velocity of 80

red ball heading  -80x, 0y towards 0x , 0y

the magnitude or velocity of the red ball is

(80^2 + 0^2)^.5 = 80

It works out nicely with your strike.x and strike.y

(strike.x ^2 + strike.y ^2).5 = 80 if both balls head towards 0, 0.  The magnitude of strike vector is 80 because there are 80 pixels from ball center to ball center at collision.
The strike vector crosses 0, 0 and so does mvector.

The reference at 0, 0 seems to be necessary because mvector is actually vector addition using the  parallelogram method
where all vectors can start at 0, 0 if you want.
Title: Re: 2D ball collisions without trigonometry.
Post by: _vince on May 18, 2021, 06:49:40 pm
I haven't been following the thread but am just wondering, what is the benefit of no/without trigorometry?  Seems you can always convert between the trig and vector/sqrt/etc equivalent, perhaps not always easily.  FYI, Bill is the original QB-no-Trig:  https://www.tapatalk.com/groups/qbasic/rotation-t29617.html#p172877 (https://www.tapatalk.com/groups/qbasic/rotation-t29617.html#p172877)
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 18, 2021, 07:22:34 pm
probably no benefit, maybe a bit faster ? 

I have resorted to trig just to get this all sorted out.  I still have no proof the mvector method works or not. How can it be proven?

Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 19, 2021, 12:02:06 pm
Thanks to @OldMoses  link to paper I finally have decent ball action for Pool Game! As collision demo'd here in this thread:
https://www.qb64.org/forum/index.php?topic=3866.msg132377#msg132377

Can check out Pool code at Steve's forum here (no images all hand drawn)
https://qb64.freeforums.net/thread/150/pool
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 19, 2021, 10:32:27 pm
bplus

say red ball heads -80x, 0y towards 0x, 0y and blue ball heads  0x, -80y  towards 0x, 0y. In this case there is 90 degrees between ball headings and both balls have equal velocity

For the simplest case, vector addition using the parallelogram method simply bisects the angle between red ball and blue ball.  mvector is located on this bisection line.   However for this bisection to occur requires the strike vector to be at right angle to the bisection line

calculate mvector

first reverse signs of balls
red ball  80x, 0y
blue ball   0x, 80y

vector addition

80x +0x,, 0y + 80y = 80x,  80y

so the mvector, in this case, is at a 45 degree angle in the upper right quadrant which is the angle of the bisection line.
now calculate final ball vectors (after collision)
(add original ball vectors to mvector)

red ball
-80x + 80x ,  0y + 80y =   0x, 80y

blue ball
0x + 80x ,  -80y + 80y =   80x, 0y

Are the final vectors correct?


 
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 20, 2021, 12:22:59 am
A red ball heading east towards point 0,0 and blue heading north to 0,0 both equal distance away and equal speed will meet and kiss at tangent angle 45 /225 degree before 0,0 due to their radii and red will deflect North and blue deflect east. I demo'd this or something similar in special case collisions, this thread.

Here is demo for this from tiny mod of previous code in this thread:
Code: QB64: [Select]
  1. _Title "From Paper 2 Ball Collision with Vectors,  press r to restart" 'B+ 2021-05-13
  2. ' Collision tests based on Paper       https://www.vobarian.com/collisions/2dcollisions2.pdf
  3. ' The vector functions are modifications of tsh73 JB code converted to QB64 by me.
  4. ' earlier code 2 Ball Collision with Vectors by Arrow Addition ' B+ 2021-05-10
  5. ' 2021-05-10  fixed arrow so points from base x,y To angle
  6. ' from Collision Study #4   press spacebar to toggle tracer" ' b+ 2021-05-09
  7.  
  8. ' !!!!!!!!!!!!!!!! Adjust Ball according to experiment 2 is always good
  9.  
  10. Const Balls = 2 '<<<<<<<<< change this to 2 to test special case setups: head on, parallel and perpendicular
  11. 'Const Balls = 5 '<<<<<<<<< the more the balls the more problems pop up for Random Numbers experiments
  12. '                            no more than 20! because have to start all balls not overlapping any other
  13.  
  14.  
  15. Const Xmax = 600 ' screen width
  16. Const Ymax = 600 ' screen height
  17. Const R = 50 '     balls radii
  18. Const R22 = R * R * 4 ' save some time with (2 * R) * (2 * R)
  19.  
  20. Const MV_Speed = 45 ' magnitude of Force Arrow say it's constant for simplicity sake
  21. Type Ball
  22.     As Long x, y
  23.     As Double dx, dy, a ' dx, dy = change x, y axis
  24.     As _Unsigned Long colr
  25.  
  26. Screen _NewImage(Xmax, Ymax, 32)
  27. _Delay .25
  28. ' these can be static as no balls added or subtracted in closed system
  29. Dim As Ball b(1 To Balls), nf(1 To Balls) ' b() is current frame balls data , nf( ) is for next frame balls data
  30. Dim As Long clrMode, i, j, rdm
  31. b(1).colr = &HFFFF0000 'red ball 1 is red
  32. b(2).colr = &HFF0000FF 'blue ball 2 is blue
  33. clrMode = 1
  34. Dim v1$, v2$, dv1$, dv2$, dv1u$, dv2u$, norm$, unitNorm$, unitTan$ 'vectors
  35. Dim vp1n$, vp1t$, vp2n$, vp2t$ ' post collision vectors
  36. Dim As Double v1n, v1t, v2n, v2t ' dot products
  37. Dim As Double vp1n, vp1t, vp2n, vp2t ' post collision dot products
  38. restart:
  39. 'horizontal collisions  Head On
  40. 'b(1).dx = 5
  41. 'b(1).dy = 0 ' b1 is just moving on x axis to the right at 5 pixels per frame
  42. 'b(2).dx = -5
  43. 'b(2).dy = 0 ' b2 is just moving on x axis to the left at 5 pixels per frame
  44. 'b(1).x = 300 - 5 * b(1).dx - R ' in 5 frames b1 will meet b2  kiss at center of screen
  45. 'b(1).y = 300
  46. 'b(2).x = 300 - 5 * b(2).dx + R ' in 5 frames b1 will meet b2  kiss at center of screen
  47. 'b(2).y = 300
  48.  
  49. 'vertical collisions  Head On
  50. 'b(1).dy = 5
  51. 'b(1).dx = 0 ' b1 is just moving on x axis to the right at 5 pixels per frame
  52. 'b(2).dy = -5
  53. 'b(2).dx = 0 ' b2 is just moving on x axis to the left at 5 pixels per frame
  54. 'b(1).y = 300 - 5 * b(1).dy - R ' in 5 frames b1 will meet b2  kiss at center of screen
  55. 'b(1).x = 300
  56. 'b(2).y = 300 - 5 * b(2).dy + R ' in 5 frames b1 will meet b2  kiss at center of screen
  57. 'b(2).x = 300
  58.  
  59. ' 45 & 135 collsions   Head On
  60. 'b(1).dy = 5
  61. 'b(1).dx = 5 ' b1 is just moving on x axis to the right at 5 pixels per frame
  62. 'b(2).dy = -5
  63. 'b(2).dx = -5 ' b2 is just moving on x axis to the left at 5 pixels per frame
  64. 'b(1).y = 300 - 5 * b(1).dy - R ' in 5 frames b1 will meet b2  kiss at center of screen
  65. 'b(1).x = 300 - 5 * b(1).dx - R
  66. 'b(2).y = 300 - 5 * b(2).dy + R ' in 5 frames b1 will meet b2  kiss at center of screen
  67. 'b(2).x = 300 - 5 * b(2).dx + R
  68.  
  69. ' Parallel and converging paths
  70. 'b(1).dy = -6
  71. 'b(1).dx = 1 ' b1 is just moving on x axis to the right at 5 pixels per frame
  72. 'b(2).dy = -6
  73. 'b(2).dx = -1 ' b2 is just moving on x axis to the left at 5 pixels per frame
  74. 'b(1).y = Ymax - R ' in 5 frames b1 will meet b2  kiss at center of screen
  75. 'b(1).x = 300 - 20 * b(1).dx - R
  76. 'b(2).y = Ymax - R ' in 5 frames b1 will meet b2  kiss at center of screen
  77. 'b(2).x = 300 - 20 * b(2).dx + R
  78.  
  79. ' Perpendicular and 90 degree collision   should be just before mid screen
  80. b(1).dy = 0
  81. b(1).dx = -5
  82. b(2).dy = -5
  83. b(2).dx = 0
  84. b(1).y = 300
  85. b(1).x = Xmax - R
  86. b(2).y = Ymax - R ' north
  87. b(2).x = 300
  88.  
  89. ' random assignments
  90. 'For i = 1 To Balls
  91. '    tryAgain:
  92. '    Print i
  93. '    b(i).x = rand&(R, Xmax - R)
  94. '    b(i).y = rand&(R, Ymax - R)
  95. '    For j = 1 To i - 1
  96. '        If _Hypot(b(i).x - b(j).x, b(i).y - b(j).y) < R + R Then GoTo tryAgain
  97. '    Next
  98. '    b(i).dx = (Rnd * 4 + 1) * rdir
  99. '    b(i).dy = (Rnd * 4 + 1) * rdir
  100. '    rdm = rand&(1, 7)
  101. '    If rdm < 1 Or rdm > 7 Then Beep
  102. '    Select Case rdm
  103. '        Case 1: b(i).colr = _RGB32(Rnd * 200 + 55, 0, 0)
  104. '        Case 2: b(i).colr = _RGB32(0, Rnd * 100 + 55, 0)
  105. '        Case 3: b(i).colr = _RGB32(0, 0, Rnd * 200 + 55)
  106. '        Case 4: b(i).colr = &HFFFF6600
  107. '        Case 5: b(i).colr = &HFFFF0088
  108. '        Case 6: b(i).colr = &HFF00FF88
  109. '        Case 7: b(i).colr = _RGB32(Rnd * 200 + 55, Rnd * 200 + 55, Rnd * 200 + 55)
  110. '    End Select
  111. 'Next
  112.  
  113. While _KeyDown(27) = 0
  114.     Dim k$
  115.     k$ = InKey$
  116.     If Len(k$) Then
  117.         If Asc(k$) = 32 Then clrMode = -1 * clrMode
  118.         If Asc(k$) = 27 And Len(k$) = 1 Then End
  119.         If k$ = "r" Then GoTo restart
  120.     End If
  121.     If clrMode > 0 Then Cls
  122.     Circle (300, 300), 2
  123.     For i = 1 To Balls ' draw balls then  update for next frame
  124.  
  125.         ' this just draw the balls with arrows pointing to their headings
  126.         Circle (b(i).x, b(i).y), R, b(i).colr
  127.         b(i).a = _Atan2(b(i).dy, b(i).dx)
  128.         ArrowTo b(i).x, b(i).y, b(i).a, MV_Speed, &HFFFFFF00
  129.         ' debug
  130.         '_PrintString (b(i).x - 8, b(i).y - 8), _Trim$(Right$("00" + Str$(i), 2)) 'all the balls weren't getting colored
  131.  
  132.         ' check for collision
  133.         Dim cd, saveJ
  134.         cd = 100000: saveJ = 0
  135.         For j = 1 To Balls 'find deepest collision in case more than one we want earliest = deepest penetration
  136.             If i <> j Then
  137.                 Dim dx, dy
  138.                 dx = b(i).x - b(j).x: dy = b(i).y - b(j).y
  139.                 If dx * dx + dy * dy <= R22 Then ' collision but is it first or deepest collision
  140.                     If R22 - dx * dx + dy * dy < cd Then cd = R22 - dx * dx + dy * dy: saveJ = j
  141.                 End If
  142.             End If
  143.         Next
  144.         If cd <> 100000 Then ' found collision change ball i dx, dy   calc new course for ball i
  145.  
  146.             ''reflection  from circle  using Vectors  from JB, thanks tsh73
  147.             v1$ = vect$(b(i).x, b(i).y) ' circle i
  148.             v2$ = vect$(b(saveJ).x, b(saveJ).y) ' the other circle j
  149.             dv1$ = vect$(b(i).dx, b(i).dy) ' change in velocity vector
  150.             dv2$ = vect$(b(saveJ).dx, b(saveJ).dy)
  151.             dv1u$ = vectUnit$(dv1$) '1 pixel
  152.             dv2u$ = vectUnit$(dv2$)
  153.             'Print dv$, cv$, dv0$   ' check on things
  154.             '_Display
  155.             'Sleep
  156.             Do ' this should back up the balls to kiss point thanks tsh73
  157.                 v1$ = vectSub$(v1$, dv1u$)
  158.                 v2$ = vectSub(v2$, dv2u$)
  159.             Loop While vectLen(vectSub$(v1$, v2$)) < R + R 'back up our circle i to point on kiss
  160.             ''now, get reflection speed
  161.             ''radius to radius, norm is
  162.             norm$ = vectSub$(v1$, v2$) ' this to this worked without all between from that collision paper
  163.  
  164.             '  step 1 unit norm and tangent
  165.             unitNorm$ = vectUnit$(norm$)
  166.             unitTan$ = vect$(-vectY(unitNorm$), vectX(unitNorm$))
  167.             ' step 2 v$ and cv$ are 2 ball vectors (locations)  done already
  168.             ' step 3 dot products before collision projecting onto normal and tangent vectors
  169.             v1n = vectDotProduct(dv1$, unitNorm$)
  170.             v1t = vectDotProduct(dv1$, unitTan$)
  171.             v2n = vectDotProduct(dv2$, unitNorm$)
  172.             v2t = vectDotProduct(dv2$, unitTan$)
  173.             ' step 4 simplest  post collision dot products
  174.             vp1t = v1t
  175.             vp2t = v2t
  176.             ' step 5  simplified by m = 1 for both balls just swap the numbers
  177.             vp1n = v2n
  178.             vp2n = v1n
  179.             ' step 6  vp vectors mult the n, t numbers by unit vectors
  180.             vp1n$ = vectScale$(vp1n, unitNorm$)
  181.             vp1t$ = vectScale$(vp1t, unitTan$)
  182.             vp2n$ = vectScale$(vp2n, unitNorm$)
  183.             vp2t$ = vectScale$(vp2t, unitTan$)
  184.             'step  7  add the 2 vectors n and t
  185.             dv1$ = vectAdd$(vp1n$, vp1t$)
  186.  
  187.             ' to this  now just switch tangent and norm
  188.             'dv1$ = vectSub$(vectNorm$(dv1$, norm$), vectTangent$(dv1$, norm$)) 'to this
  189.  
  190.             ' store in next frame array
  191.             nf(i).dx = vectX(dv1$)
  192.             nf(i).dy = vectY(dv1$)
  193.  
  194.         Else ' no collision
  195.             nf(i).dx = b(i).dx
  196.             nf(i).dy = b(i).dy
  197.         End If
  198.         'update location of ball next frame
  199.         nf(i).x = b(i).x + nf(i).dx
  200.         nf(i).y = b(i).y + nf(i).dy
  201.  
  202.         ' check in bounds next frame
  203.         If nf(i).x < R Then nf(i).dx = -nf(i).dx: nf(i).x = R
  204.         If nf(i).x > Xmax - R Then nf(i).dx = -nf(i).dx: nf(i).x = Xmax - R
  205.         If nf(i).y < R Then nf(i).dy = -nf(i).dy: nf(i).y = R
  206.         If nf(i).y > Ymax - R Then nf(i).dy = -nf(i).dy: nf(i).y = Ymax - R
  207.     Next
  208.  
  209.     ''now that we've gone through all old locations update b() with nf() data
  210.     For i = 1 To Balls
  211.         b(i).x = nf(i).x: b(i).y = nf(i).y
  212.         b(i).dx = nf(i).dx: b(i).dy = nf(i).dy
  213.     Next
  214.     ' next frame ready to draw
  215.     _Display
  216.     _Limit 30
  217. 'Cls   ' debug why aren't all the balls getting colored
  218. 'For i = 1 To Balls
  219. '    Print i, b(i).colr
  220. 'Next
  221.  
  222.  
  223. Function rand& (lo As Long, hi As Long) ' fixed?
  224.     rand& = Int(Rnd * (hi - lo) + 1) + lo
  225.  
  226. Function rdir ()
  227.     If Rnd < .5 Then rdir = -1 Else rdir = 1
  228.  
  229.  
  230.  
  231. Sub ArrowTo (BaseX As Long, BaseY As Long, rAngle As Double, lngth As Long, colr As _Unsigned Long)
  232.     Dim As Long x1, y1, x2, y2, x3, y3
  233.     x1 = BaseX + lngth * Cos(rAngle)
  234.     y1 = BaseY + lngth * Sin(rAngle)
  235.     x2 = BaseX + .8 * lngth * Cos(rAngle - _Pi(.05))
  236.     y2 = BaseY + .8 * lngth * Sin(rAngle - _Pi(.05))
  237.     x3 = BaseX + .8 * lngth * Cos(rAngle + _Pi(.05))
  238.     y3 = BaseY + .8 * lngth * Sin(rAngle + _Pi(.05))
  239.     Line (BaseX, BaseY)-(x1, y1), colr
  240.     Line (x1, y1)-(x2, y2), colr
  241.     Line (x1, y1)-(x3, y3), colr
  242.  
  243. ' convert some vector functions from JB, turns out much easier to use string functions to pass vectors
  244. ' than using SUBs to do vector calcs
  245.  
  246. Function vect$ (x, y) ' convert x, y to string for passing vectors with Functions
  247.     vect$ = _Trim$(Str$(x)) + "," + _Trim$(Str$(y))
  248.  
  249. Function vectX (v$)
  250.     vectX = Val(LeftOf$(v$, ","))
  251.  
  252. Function vectY (v$)
  253.     vectY = Val(RightOf$(v$, ","))
  254.  
  255. Function vectLen (v$)
  256.     Dim x, y
  257.     x = Val(LeftOf$(v$, ","))
  258.     y = Val(RightOf$(v$, ","))
  259.     vectLen = Sqr(x * x + y * y)
  260.  
  261. Function vectUnit$ (v$)
  262.     Dim x, y, vl
  263.     x = Val(LeftOf$(v$, ","))
  264.     y = Val(RightOf$(v$, ","))
  265.     vl = Sqr(x * x + y * y)
  266.     vectUnit$ = vect$(x / vl, y / vl)
  267.  
  268. Function vectAdd$ (v1$, v2$)
  269.     Dim x1, y1, x2, y2
  270.     x1 = Val(LeftOf$(v1$, ","))
  271.     y1 = Val(RightOf$(v1$, ","))
  272.     x2 = Val(LeftOf$(v2$, ","))
  273.     y2 = Val(RightOf$(v2$, ","))
  274.     vectAdd$ = vect$(x1 + x2, y1 + y2)
  275.  
  276. Function vectSub$ (v1$, v2$)
  277.     Dim x1, y1, x2, y2
  278.     x1 = Val(LeftOf$(v1$, ","))
  279.     y1 = Val(RightOf$(v1$, ","))
  280.     x2 = Val(LeftOf$(v2$, ","))
  281.     y2 = Val(RightOf$(v2$, ","))
  282.     vectSub$ = vect$(x1 - x2, y1 - y2)
  283.  
  284. Function vectDotProduct (v1$, v2$)
  285.     Dim x1, y1, x2, y2
  286.     x1 = Val(LeftOf$(v1$, ","))
  287.     y1 = Val(RightOf$(v1$, ","))
  288.     x2 = Val(LeftOf$(v2$, ","))
  289.     y2 = Val(RightOf$(v2$, ","))
  290.     vectDotProduct = x1 * x2 + y1 * y2
  291.  
  292. Function vectScale$ (a, v$) 'a * vector v$
  293.     Dim x, y
  294.     x = Val(LeftOf$(v$, ","))
  295.     y = Val(RightOf$(v$, ","))
  296.     vectScale$ = vect$(a * x, a * y)
  297.  
  298. Function vectTangent$ (v$, base$)
  299.     Dim n$
  300.     n$ = vectUnit$(base$)
  301.     vectTangent$ = vectScale$(vectDotProduct(n$, v$), n$)
  302.  
  303. Function vectNorm$ (v$, base$)
  304.     vectNorm$ = vectSub$(v$, vectTangent$(v$, base$))
  305.  
  306. ' update these 2 in case of$ is not found! 2021-02-13
  307. Function LeftOf$ (source$, of$)
  308.     If InStr(source$, of$) > 0 Then LeftOf$ = Mid$(source$, 1, InStr(source$, of$) - 1) Else LeftOf$ = source$
  309.  
  310. ' update these 2 in case of$ is not found! 2021-02-13
  311. Function RightOf$ (source$, of$)
  312.     If InStr(source$, of$) > 0 Then RightOf$ = Mid$(source$, InStr(source$, of$) + Len(of$)) Else RightOf$ = ""
  313.  
  314.  

You need to know not only their new headings but the points they are at when they kiss and change direction.

Quote
red ball
-80x + 80x ,  0y + 80y =   0x, 80y

blue ball
0x + 80x ,  -80y + 80y =   80x, 0y

Are the final vectors correct?

I would say no, though your vector system unrecognizable to me but blue is definitely traveling in -x direction and red in  -y direction as shown in demo after they collide.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 20, 2021, 12:35:07 am
Quote
red will deflect North and blue deflect east.

good we got the same answer!

Quote
You need to know not only their new headings but the points they are at when they kiss and change direction.

yep OldMoses calls it the strike vector.  And yes, red will deflect North and blue deflect east only if strike vector is at right angle to bisection line.

With the same headings, if the strike vector changes, then the mvector is no longer on the bisection line.
I'm working on some math to prove where mvector ends up when that happens.

I'm hoping that if mvector can be located /calculated properly then all the math of 2d ball collisions can be derived from mvector only
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 20, 2021, 12:51:06 am
Quote
I would say no, though your vector system unrecognizable to me but blue is definitely traveling in -x direction and red in  -y direction as shown in demo after they collide.

if the red heads in a -y direction (after collision) that is south ?  how can that be if the blue was heading north before collision?

north = top of screen
west  = left of screen
east = right of screen
south = bottom of screen

my vector system is vector addition.  No dot  products - not yet anyway
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 20, 2021, 02:45:40 am
Strike vector can be calculated from mvector (on bisection line). The magnitude of strike vector is 80 pixels because the ball radius is 40 pixels.  When the balls collide there is a distance of 80 pixels between ball centers which is the hypotenuse of the x, y  strike components

Code: QB64: [Select]
  1. DIM strikeX AS SINGLE
  2. DIM strikeY AS SINGLE
  3. DIM mvecX AS SINGLE
  4. DIM mvecY AS SINGLE
  5. DIM ballDIA AS INTEGER
  6. ballDIA = 80 'pixels
  7.  
  8. mvecX = 80 'x component of mvector (mvector on bisection line)
  9. mvecY = 80 'y component of mvector (mvector on bisection line)
  10.  
  11. strikeX = (ballDIA ^ 2 / (mvecX / mvecY + 1)) ^ .5
  12. PRINT "strikeX"; strikeX
  13.  
  14. strikeY = (ballDIA ^ 2 / (mvecY / mvecX + 1)) ^ .5
  15.  
  16. PRINT "strikeY"; strikeY
  17.  

 Working on math to calculate mvector from strike vector if mvector magnitude on bisection line is known.

Expecting the final ball velocities to vary with a change in strike vector

Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 20, 2021, 04:18:25 am
More code.  Ok not verified yet.  Have to run a simulation to see if there are any bugs. Introduced bvector which is mvector on bisection line.  bvecX and bvecY are set to 80.

Code: QB64: [Select]
  1. DIM strikeX AS SINGLE
  2. DIM strikeY AS SINGLE
  3. DIM bvecX AS SINGLE 'bvector is mvector on bisection line
  4. DIM bvecY AS SINGLE 'bvector is mvector on bisection line
  5. DIM mvecX AS SINGLE
  6. DIM mvecY AS SINGLE
  7. DIM bvecMAG AS SINGLE
  8. DIM strikeMAG AS INTEGER
  9. DIM ballRAD AS INTEGER 'ball radius in pixels
  10. ballRAD = 40 'pixels
  11. strikeMAG = 2 * ballRAD
  12.  
  13.  
  14. bvecX = 80 'mvector on bisection line
  15. bvecY = 80 'mvector on bisection line
  16. bvecMAG = (bvecX ^ 2 + bvecY ^ 2) ^ .5
  17.  
  18. strikeX = (strikeMAG ^ 2 / (bvecX ^ 2 / bvecY ^ 2 + 1)) ^ .5
  19. PRINT "strikeX"; strikeX
  20.  
  21. strikeY = (strikeMAG ^ 2 / (bvecY ^ 2 / bvecX ^ 2 + 1)) ^ .5
  22.  
  23. PRINT "strikeY"; strikeY
  24.  
  25. '*************  calculate mvector from strike vector and bvector
  26.  
  27. mvecX = (bvecMAG ^ 2 / (strikeX ^ 2 / strikeY ^ 2 + 1)) ^ .5
  28. mvecY = (bvecMAG ^ 2 / (strikeY ^ 2 / strikeX ^ 2 + 1)) ^ .5
  29.  
  30. PRINT "mvecX"; mvecX
  31. PRINT "mvecY"; mvecY
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 20, 2021, 01:08:31 pm
if the red heads in a -y direction (after collision) that is south ?  how can that be if the blue was heading north before collision?

north = top of screen
west  = left of screen
east = right of screen
south = bottom of screen

my vector system is vector addition.  No dot  products - not yet anyway

Oh man! I still don't know my left and right or I should say my West from my East! Left Is heading West and moving to the right IS East.

I meant to say the Blue ball moving North will be deflected West, to the left.
and the Red ball moving West will be deflected North, up.

The demo is correct even if my description is wrong, sorry for making a difficult subject more confusing.

Vector addition should work, it was drilled into my head in Physics 101 College Course (for non Physics Majors that didn't do Calculus side by side with Physics like they do for Physics Majors which is so much better in my opinion).
But not drilled into my head was vectors that went at any angle even in degrees let alone radians!

To find which way a body will move you add up all the vector forces applied to the center of the body mass which for a ball is easiest to describe and find, its the origin of the circle.

And we drew arrows and placed the tail of one arrow at the head of the other for the "resultant" force an arrow from body center to head of the first arrow. That was as far as it got we did not have computers then just graduating from slide rules to hand calculators.

So these arrows have x, y components, add x components and y components yeah NOVARSEG you seem to be on right track so far.

But at moment of collision how do you calculate the force of the other ball acting on the first ball?

Don't you have to look at the angle the balls are at to each other, because the tangent line they will reflect from (just like light off a surface) is perpendicular to the angle the balls are at when they kiss. Angle in = Angle out off that tangent line at kiss point. Easy to describe a real b... to get mathematically!

Reflecting off a box boundary is piece of cake just reverse the dx or dy direction with a negative sign.
Bouncing off an angled surface not so fun because both dx, dy components are effected and most of the time unequally.

And in Basic Graphics  decreasing y is moving North and  increasing y is moving South another bug-a-boo obstacle to easy understanding and communication between math and science students and computer apps in Basic.

North is up in direction and y decreases to get there.
South is down direction and y increases to get there, it is Basic Fact of computer life face it or get out your Window command and fix your graphics to work as you learn it math or science class.

When you use Window command you have to know which graphics command can follow: line, circle (though you better check out arcs) pset these work with Window coordinate system. Mouse x, y doesn't work but does have a PMAP fix that comes with Basic read your Wiki carefully about WINDOW command.

Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 20, 2021, 06:45:50 pm
Quote
Don't you have to look at the angle the balls are at to each other, because the tangent line they will reflect from (just like light off a surface) is perpendicular to the angle the balls are at when they kiss. Angle in = Angle out off that tangent line at kiss point. Easy to describe a real b... to get mathematically!

For a ball striking a wall, ya, it's angle in, angle out.

Trying to code an interactive tool that will show two circles at the point of contact.   The strike vector will be rotatable by visual means of two circles rotating about 0x,0y.  (80 pixels from center to center)The bisection line is shown. As strike vector is rotated the mvector will move. Also shown are the final ball vectors.   I thought this tool would be a better way to show ball collisions without any moving balls.

As strike vector is rotated, mvector will line up with the bisection line which is also bvector
 Well I have not even started coding this yet.





Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 20, 2021, 07:35:09 pm
Speaking of interactive tools. I've been trying to wrap my poor tender head around this algorithm for quite some time and thought that something like that would be helpful to watch the effects of various changes to the equations. Maybe someone can make use of some or all of the following code, and/or substitute their own math functions, as mine are as useful as a turd in a public pool at the moment... ;)  But of course that's why I need this. It's cobbled together from the bones of what I posted earlier.

Anyway, the idea is that you can left click on the ends of the solid line vectors (i.e. incoming vector) and move them around and see what applied algorithms effects are on the exit paths. It acts a bit squirrely and you might have to do a pre-click on a vertex to sort of wake it up that its not a previous move continued. I'm not terribly familiar with Steve's MBS function for drag and drop ops, but figured that there's no better time than the present to play with it.

Moving the ball centers changes their radii, maintaining equivalent size, as I never conceived this as anything beyond identical billiard ball type stuff. It also redefines the coordinate system with WINDOW as Bplus mentioned earlier.

EDIT: Ah! I've discovered the secret to using my own code. Get near the desired vertex, click and hold until the line jumps to the mouse cursor. I guess I wasn't giving time for the delay parameter in Steve's MBS function.

Code: QB64: [Select]
  1. TYPE V2
  2.     x AS SINGLE
  3.     y AS SINGLE
  4.  
  5. TYPE ball
  6.     cn AS STRING * 4 '                                          ball name by color
  7.     c AS _UNSIGNED LONG '                                       color
  8.     p AS V2 '                                                   position
  9.     m AS V2 '                                                   movement (pre-contact) incoming
  10.     u AS V2 '                                                   unit vector of ball.m
  11.     x AS V2 '                                                   vector of post contact movement
  12.     s AS INTEGER '                                              magnitude of movement
  13.     sd AS SINGLE '                                              strike dot
  14.  
  15. DIM AS V2 mvec, strike
  16. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  17. DIM mouse AS V2
  18. DIM b(1) AS ball
  19. DIM SHARED AS V2 origin
  20. origin.x = 0: origin.y = 0
  21. b(0).c = Red: b(0).cn = "red"
  22. b(1).c = Cyan: b(1).cn = "cyan"
  23.  
  24. SCREEN _NEWIMAGE(600, 600, 32)
  25. WINDOW (-300, 300)-(300, -300)
  26. 'These are the approach vectors you give
  27. b(0).m.x = 0: b(0).m.y = -100 '                                 reds approach vector
  28. b(0).s = PyT(origin, b(0).m) '                                  reds magnitude (speed or force)
  29. b(1).m.x = 100: b(1).m.y = -100 '                               cyans approach vector
  30. b(1).s = PyT(origin, b(1).m) '                                  cyans magnitude (speed or force)
  31.  
  32. 'The important part is, where are the balls when they make contact. This determines the strike vector between them
  33. 'choosing arbitrary points of contact... change these around to see the effect to verify its accuracy
  34. b(0).p.x = 30: b(0).p.y = 10
  35. b(1).p.x = -17: b(1).p.y = 0
  36.  
  37.     ms = MBS '                                                  process mouse actions dragging endpoints
  38.     IF ms AND 64 THEN
  39.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  40.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  41.         FOR x = 0 TO 1
  42.             FOR y = 0 TO 1
  43.                 ds! = PyT(vertex(x, y), mouse)
  44.                 IF ds! < ballradius * .5 THEN i = x: j = y
  45.             NEXT y
  46.         NEXT x
  47.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  48.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  49.                 b(i).p = mouse
  50.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  51.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  52.         END SELECT
  53.     ELSE
  54.     END IF
  55.  
  56.  
  57.     'START OF COLLISION MATHEMATICS SECTION
  58.     ballradius = PyT(b(0).p, b(1).p) / 2
  59.     CLS
  60.  
  61.     FOR bn = 0 TO 1
  62.         other = ABS(NOT -bn) '                                  if we're on 0 the other is 1 and vice versa
  63.         b(bn).u = b(bn).m: VecNorm b(bn).u '                    normalize the movement vector
  64.         strike.x = b(other).p.x - b(bn).p.x '                   obtain a strike vector
  65.         strike.y = b(other).p.y - b(bn).p.y
  66.         ShearOrthoUnit mvec, b(bn).m, strike '                  strike is converted to a unit vector and mvec is orthogonal unit of strike
  67.         b(bn).x = mvec '                                        keep the result of mvec in the exit vector, we'll grow it in the next loop
  68.  
  69.         'now this is what we're really shooting for
  70.         b(bn).sd = VecDot(b(bn).u, strike)
  71.  
  72.         'establish the mouse handles for scenario manipulations
  73.         vertex(bn, 0) = b(bn).p
  74.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1
  75.     NEXT bn
  76.     'we finish the above FOR/NEXT loop so that we have all respective mvec's stored in b(#).x
  77.     'then we rerun the loop, converting b(#).x to a full exit vector
  78.     FOR bn = 0 TO 1
  79.         other = ABS(NOT -bn)
  80.         b(bn).x.x = b(bn).x.x * (b(bn).s * b(other).sd)
  81.         b(bn).x.y = b(bn).x.y * (b(bn).s * b(other).sd)
  82.         'PRINT b(bn).cn; " exits at <"; b(bn).x.x; ", "; b(bn).x.y; ">"
  83.     NEXT bn
  84.     'END OF COLLISION MATHEMATICS SECTION
  85.  
  86.     'graphic representation
  87.     FOR grid = -300 TO 300 STEP 20
  88.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  89.         LINE (grid, 300)-(grid, -300), c& 'Gray
  90.         LINE (-300, grid)-(300, grid), c& ' Gray
  91.     NEXT grid
  92.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  93.     FOR dr = 0 TO 1
  94.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c
  95.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x - b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  96.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  97.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  98.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  99.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">"
  100.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  101.     NEXT dr
  102.  
  103.     _LIMIT 50
  104.     _DISPLAY
  105.  
  106.  
  107. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  108.     STATIC StartTimer AS _FLOAT
  109.     STATIC ButtonDown AS INTEGER
  110.     STATIC ClickCount AS INTEGER
  111.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  112.     '                          Down longer counts as a HOLD event.
  113.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  114.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  115.         SELECT CASE SGN(_MOUSEWHEEL)
  116.             CASE 1: MBS = MBS OR 512
  117.             CASE -1: MBS = MBS OR 1024
  118.         END SELECT
  119.     WEND
  120.  
  121.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  122.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  123.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  124.  
  125.     IF StartTimer = 0 THEN
  126.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  127.             ButtonDown = 1: StartTimer = TIMER(0.01)
  128.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  129.         ELSEIF _MOUSEBUTTON(2) THEN
  130.             ButtonDown = 2: StartTimer = TIMER(0.01)
  131.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  132.         ELSEIF _MOUSEBUTTON(3) THEN
  133.             ButtonDown = 3: StartTimer = TIMER(0.01)
  134.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  135.         END IF
  136.     ELSE
  137.         BD = ButtonDown MOD 3
  138.         IF BD = 0 THEN BD = 3
  139.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  140.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  141.         ELSE
  142.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  143.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  144.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  145.             ELSE 'We've now started the hold event
  146.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  147.             END IF
  148.         END IF
  149.     END IF
  150.  
  151.  
  152. FUNCTION PyT (var1 AS V2, var2 AS V2)
  153.  
  154.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  155.  
  156.  
  157.  
  158. SUB ShearOrthoUnit (nv AS V2, in AS V2, stk AS V2)
  159.  
  160.     DIM AS V2 or1, or2
  161.     VecNorm stk '                                               normalize the strike vector
  162.     or1.x = stk.y: or1.y = -stk.x '                             obtain two potential orthogonal vectors {or1 & or2} of strike
  163.     or2.x = -stk.y: or2.y = stk.x
  164.     dot = VecDot(in, or1) '                                     dot product one of them with the incoming vector
  165.     nv.x = -or2.x * (dot < 0) - stk.x * (dot = 0) - or1.x * (dot > 0) 'exit vector x component
  166.     nv.y = -or2.y * (dot < 0) - stk.y * (dot = 0) - or1.y * (dot > 0) 'exit vector y component
  167.  
  168. END SUB 'ShearOrthoUnit
  169.  
  170.  
  171. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  172.  
  173.     var.x = var.x + (var2.x * var3)
  174.     var.y = var.y + (var2.y * var3)
  175.  
  176. END SUB 'Add_Vector
  177.  
  178.  
  179. SUB VecNorm (var AS V2)
  180.  
  181.     m = PyT(origin, var)
  182.     IF m = 0 THEN
  183.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  184.     ELSE
  185.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  186.     END IF
  187.  
  188. END SUB 'VecNorm
  189.  
  190.  
  191. FUNCTION VecDot (var AS V2, var2 AS V2)
  192.  
  193.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  194.  
  195. END FUNCTION 'VecDot
  196.  
  197.  
  198. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  199.  
  200.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  201.  
  202.  
  203.  
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 20, 2021, 09:11:16 pm
OldMoses hey  that  is what I was thinking of.  Great code example.


Are the dashed lines the final ball vectors or are they ball vectors before collision.

Bplus we are both wrong, according to OldMoses simulator the red and blue balls head off in a PARALLEL direction. Both balls have a final heading of north  east. Adjust the strike vector so it is at right angles to a line bisecting the angle between red and blue before collision.

(LATER (never mined what I just said, I misinterpreted what the dashed lines meant)
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 20, 2021, 09:24:16 pm
OldMoses hey  that  is what I was thinking of.  Great code example.


Are the dashed lines the final ball vectors or are they ball vectors before collision.

Bplus we are both wrong, according to OldMoses simulator the red and blue balls head off in a PARALLEL direction. Both balls have a final heading of north  east. Adjust the strike vector so it is at right angles to a line bisecting the angle between red and blue before collision.

Thanks, yes the dashed lines are are "supposed" to be the mvectors, but I have my doubts about the math as one can set up some wierd scenarios. I think my math needs a good deal more work on force transference between the balls, but I'm fairly confident that the graphics part is working properly.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 20, 2021, 09:47:04 pm
Ok scrap whaat I said before.  I kinda see what you've done. Each ball has it's own mvector.  My view of mvector is that it originates at the contact point between balls.  SO maybe 2 mvectors are needed but I was hoping that a single mvector would be enough.

There is an very interesting collision.

say red ball heads -80x, 0y towards 0, 0  and blue ball heads  0x, -80y towards 0, 0 with strike vector along y axis

according to the simulator the red ball does not change direction but the blue ball  reverses?? direction
****
Im not sure if the blue ball reverses direction or comes to a stop. Also does the red ball speed up?
****

If the red ball had been stationary it would head off  in the direction of the blue ball  and the blue ball would come to a stop

****

Another thing it looks like the strike vector is ALWAYS at a right angle to the mvectors which looks to be correct
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 21, 2021, 12:31:59 am

There is an very interesting collision.

say red ball heads -80x, 0y towards 0, 0  and blue ball heads  0x, -80y towards 0, 0 with strike vector along y axis

according to the simulator the red ball does not change direction but the blue ball  reverses direction.

If the red ball had been stationary it would head off  in the direction of the blue ball  and the blue ball would come to a stop

That's where my algorithm isn't working as I would expect, another thing that jumped out at me was head on collisions, which should rebound like that, but the program shows them rebounding at right angles.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 21, 2021, 01:07:19 am
Here is one where it is almost correct.

red @ (-100, 0)    along (100, 0)    exit along (50, 50)   should be (70, 70)   ?         

cyan @ (0, -100)  along (0, 100)    exit along ((70, 70) correct


70 approx 5000^.5

I looked at the head on collisions and that is basically correct. If you fix the above one then that'll look good too.
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 21, 2021, 08:13:20 am
I've gone back to the Berchek paper instead of my own crazy experiments and came up with this variation. While I think the math is getting better as the magnitudes seem to be right, there seems to be an inversion in the code somewhere. The exits consistantly converge instead of rebounding.

Interesting results though...

EDIT:
Even better, adding IF...THEN starting at line 78 seems to have corrected the inversion. Berchek was correct, you only need one strike vector. The second one just confused the issue.

Code: QB64: [Select]
  1. TYPE V2
  2.     x AS SINGLE
  3.     y AS SINGLE
  4.  
  5. TYPE ball
  6.     cn AS STRING * 4 '                                          ball name by color
  7.     c AS _UNSIGNED LONG '                                       color
  8.     p AS V2 '                                                   position
  9.     m AS V2 '                                                   movement (pre-contact) incoming
  10.     um AS V2 '                                                  unit vector of ball.m
  11.     x AS V2 '                                                   vector of post contact movement
  12.     un AS V2 '                                                  unit normal; obtained from strike
  13.     ut AS V2 '                                                  unit tangent; reciprocated from un
  14.     nci AS SINGLE '                                             normal component of input velocity
  15.     tci AS SINGLE '                                             tangential component of input velocity
  16.     ncx AS SINGLE '                                             normal component of exit velocity
  17.     tcx AS SINGLE '                                             tangential component of exit velocity
  18.     s AS INTEGER '                                              magnitude of movement
  19.     sd AS SINGLE '                                              strike dot
  20.  
  21. DIM AS V2 strike, norcom, tancom
  22. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  23. DIM mouse AS V2
  24. DIM b(1) AS ball
  25. DIM SHARED AS V2 origin
  26. origin.x = 0: origin.y = 0
  27. b(0).c = Red: b(0).cn = "red"
  28. b(1).c = Cyan: b(1).cn = "cyan"
  29.  
  30. SCREEN _NEWIMAGE(600, 600, 32)
  31. WINDOW (-300, 300)-(300, -300)
  32. 'These are the approach vectors you give
  33. b(0).m.x = 0: b(0).m.y = -100 '                                 reds approach vector
  34. b(0).s = PyT(origin, b(0).m) '                                  reds magnitude (speed or force)
  35. b(1).m.x = 100: b(1).m.y = -100 '                               cyans approach vector
  36. b(1).s = PyT(origin, b(1).m) '                                  cyans magnitude (speed or force)
  37.  
  38. b(0).p.x = 30: b(0).p.y = 10
  39. b(1).p.x = -17: b(1).p.y = 0
  40.  
  41.     ms = MBS '                                                  process mouse actions dragging endpoints
  42.     IF ms AND 64 THEN
  43.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  44.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  45.         FOR x = 0 TO 1
  46.             FOR y = 0 TO 1
  47.                 ds! = PyT(vertex(x, y), mouse)
  48.                 IF ds! < ballradius * .5 THEN i = x: j = y
  49.             NEXT y
  50.         NEXT x
  51.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  52.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  53.                 b(i).p = mouse
  54.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  55.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  56.         END SELECT
  57.     ELSE
  58.     END IF
  59.  
  60.     'START OF COLLISION MATHEMATICS SECTION
  61.     ballradius = PyT(b(0).p, b(1).p) / 2
  62.     CLS
  63.  
  64.     FOR bn = 0 TO 1
  65.         other = ABS(NOT -bn) '                                  if we're on 0 the other is 1 and vice versa
  66.  
  67.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  68.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  69.  
  70.         'Now for collision processing
  71.         b(bn).um = b(bn).m: VecNorm b(bn).um '                  normalize the movement vector
  72.         IF bn = 0 THEN '                                        we only need one strike vector from the first ball
  73.             strike = b(other).p: VecAdd strike, b(bn).p, -1 '   obtain a strike vector (Berchek step 1)
  74.         END IF
  75.         b(bn).un = strike: VecNorm b(bn).un '                   compute unit normal (Berchek step 1)
  76.         b(bn).ut.x = -b(bn).un.y: b(bn).ut.y = b(bn).un.x '     compute unit tangent (Berchek step 1)
  77.         b(bn).nci = VecDot(b(bn).un, b(bn).m) '                 compute normal component of velocity (Berchek step 3)
  78.         b(bn).tci = VecDot(b(bn).ut, b(bn).m) '                 compute tangential component of velocity (Berchek step 3)
  79.     NEXT bn
  80.     'we finish the above FOR/NEXT loop so that we have all respective data stored in the two ball variables
  81.     'then we rerun the loop, converting b(#).x to a full exit vector
  82.     FOR bn = 0 TO 1
  83.         other = ABS(NOT -bn)
  84.         'Now we tackle Berchek's step 4 tangential and normal velocities
  85.         'It is assumed that both balls are of unit mass, therefore:
  86.         'From Berchek's one dimensional collision formula
  87.         ' b(bn).ncx = (b(bn).nci * (1 - 1) + 2 * 1 * b(other).nci) / (1 + 1), therefore:
  88.         ' b(bn).ncx = (b(bn).nci * 0 + 2 * b(other).nci) / 2, therefore:
  89.         ' b(bn).ncx = 2 * b(other).nci / 2, therefore:
  90.         b(bn).ncx = b(other).nci '                              compute normal component of exit velocity (Berchek step 5)
  91.         'and this concurs with Bplus' conclusion, that under equal mass the balls transfer velocity
  92.         'https://www.qb64.org/forum/index.php?topic=3866.msg132332#msg132332
  93.  
  94.         b(bn).tcx = b(bn).tci '                                 compute tangent component of exit velocity (Berchek step 4)
  95.         'Since these are equivalent, according to Berchek, it is unnecessary to assign a tangent component of exit velocity variable
  96.         'it's only done for ordering the thought process
  97.  
  98.         'and now for step 6, we're almost there...
  99.         norcom = b(bn).un: VecMult norcom, b(bn).ncx '          unit normal exit vector x normal component of exit vector
  100.         tancom = b(bn).ut: VecMult tancom, b(bn).tcx '          unit tangent exit vector x tangent component of exit vector
  101.  
  102.         'Finally, at step 7, add norcom and tancom to get b(bn).x
  103.         b(bn).x = norcom: VecAdd b(bn).x, tancom, 1 '           add normal and tangent exit vectors
  104.         'after that we can update any other ball parameters for subsequent moves
  105.     NEXT bn
  106.     'END OF COLLISION MATHEMATICS SECTION
  107.  
  108.     'graphic representation
  109.     FOR grid = -300 TO 300 STEP 20
  110.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  111.         LINE (grid, 300)-(grid, -300), c& 'Gray
  112.         LINE (-300, grid)-(300, grid), c& ' Gray
  113.     NEXT grid
  114.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  115.     FOR dr = 0 TO 1
  116.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c
  117.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x - b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  118.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  119.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  120.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  121.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">"
  122.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  123.     NEXT dr
  124.  
  125.     _LIMIT 50
  126.     _DISPLAY
  127.  
  128.  
  129. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  130.     STATIC StartTimer AS _FLOAT
  131.     STATIC ButtonDown AS INTEGER
  132.     STATIC ClickCount AS INTEGER
  133.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  134.     '                          Down longer counts as a HOLD event.
  135.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  136.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  137.         SELECT CASE SGN(_MOUSEWHEEL)
  138.             CASE 1: MBS = MBS OR 512
  139.             CASE -1: MBS = MBS OR 1024
  140.         END SELECT
  141.     WEND
  142.  
  143.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  144.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  145.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  146.  
  147.     IF StartTimer = 0 THEN
  148.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  149.             ButtonDown = 1: StartTimer = TIMER(0.01)
  150.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  151.         ELSEIF _MOUSEBUTTON(2) THEN
  152.             ButtonDown = 2: StartTimer = TIMER(0.01)
  153.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  154.         ELSEIF _MOUSEBUTTON(3) THEN
  155.             ButtonDown = 3: StartTimer = TIMER(0.01)
  156.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  157.         END IF
  158.     ELSE
  159.         BD = ButtonDown MOD 3
  160.         IF BD = 0 THEN BD = 3
  161.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  162.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  163.         ELSE
  164.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  165.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  166.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  167.             ELSE 'We've now started the hold event
  168.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  169.             END IF
  170.         END IF
  171.     END IF
  172.  
  173.  
  174. FUNCTION PyT (var1 AS V2, var2 AS V2)
  175.  
  176.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  177.  
  178.  
  179.  
  180. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  181.  
  182.     var.x = var.x + (var2.x * var3)
  183.     var.y = var.y + (var2.y * var3)
  184.  
  185. END SUB 'Add_Vector
  186.  
  187.  
  188. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  189.  
  190.     'multiply vector by scalar value
  191.     vec.x = vec.x * multiplier
  192.     vec.y = vec.y * multiplier
  193.  
  194. END SUB 'Vec_Mult
  195.  
  196.  
  197. SUB VecNorm (var AS V2)
  198.  
  199.     m = PyT(origin, var)
  200.     IF m = 0 THEN
  201.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  202.     ELSE
  203.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  204.     END IF
  205.  
  206. END SUB 'VecNorm
  207.  
  208.  
  209. FUNCTION VecDot (var AS V2, var2 AS V2)
  210.  
  211.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  212.  
  213. END FUNCTION 'VecDot
  214.  
  215.  
  216. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  217.  
  218.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  219.  
  220.  

Here's the same thing reduced and optimized to a single SUB call, it seems to still work the same.
Code: QB64: [Select]
  1. TYPE V2
  2.     x AS SINGLE
  3.     y AS SINGLE
  4.  
  5. TYPE ball
  6.     cn AS STRING * 4 '                                          ball name by color
  7.     c AS _UNSIGNED LONG '                                       color
  8.     p AS V2 '                                                   position
  9.     m AS V2 '                                                   movement (pre-contact) incoming
  10.     x AS V2 '                                                   vector of post contact movement
  11.     s AS INTEGER '                                              magnitude of movement
  12.  
  13. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  14. DIM mouse AS V2
  15. DIM b(1) AS ball
  16. DIM SHARED AS V2 origin
  17. origin.x = 0: origin.y = 0
  18. b(0).c = Red: b(0).cn = "red"
  19. b(1).c = Cyan: b(1).cn = "cyan"
  20.  
  21. SCREEN _NEWIMAGE(600, 600, 32)
  22. WINDOW (-300, 300)-(300, -300)
  23.  
  24. 'starting state
  25. b(0).m.x = 0: b(0).m.y = -100 '                                 reds approach vector
  26. b(0).s = PyT(origin, b(0).m) '                                  reds magnitude (speed or force)
  27. b(1).m.x = 100: b(1).m.y = -100 '                               cyans approach vector
  28. b(1).s = PyT(origin, b(1).m) '                                  cyans magnitude (speed or force)
  29.  
  30. b(0).p.x = 30: b(0).p.y = 10
  31. b(1).p.x = -17: b(1).p.y = 0
  32.  
  33.     ms = MBS '                                                  process mouse actions dragging endpoints
  34.     IF ms AND 64 THEN
  35.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  36.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  37.         FOR x = 0 TO 1
  38.             FOR y = 0 TO 1
  39.                 ds! = PyT(vertex(x, y), mouse)
  40.                 IF ds! < ballradius * .5 THEN i = x: j = y
  41.             NEXT y
  42.         NEXT x
  43.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  44.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  45.                 b(i).p = mouse
  46.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  47.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  48.         END SELECT
  49.     ELSE
  50.     END IF
  51.  
  52.     'START OF COLLISION MATHEMATICS SECTION
  53.     ballradius = PyT(b(0).p, b(1).p) / 2
  54.     CLS
  55.  
  56.     FOR bn = 0 TO 1
  57.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  58.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  59.     NEXT bn
  60.  
  61.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  62.     B2BCollision b(0), b(1)
  63.     'END OF COLLISION MATHEMATICS SECTION
  64.  
  65.     'graphic representation
  66.     FOR grid = -300 TO 300 STEP 20
  67.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  68.         LINE (grid, 300)-(grid, -300), c& 'Gray
  69.         LINE (-300, grid)-(300, grid), c& ' Gray
  70.     NEXT grid
  71.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  72.     FOR dr = 0 TO 1
  73.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c
  74.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x - b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  75.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  76.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  77.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  78.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">"
  79.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  80.     NEXT dr
  81.  
  82.     _LIMIT 50
  83.     _DISPLAY
  84.  
  85.  
  86. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  87.  
  88.     DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  89.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  90.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  91.     bnci1 = VecDot(un, ball1.m) '
  92.     bnci2 = VecDot(un, ball2.m) '
  93.     btci1 = VecDot(ut, ball1.m) '
  94.     btci2 = VecDot(ut, ball2.m) '
  95.  
  96.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  97.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  98.  
  99.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  100.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  101.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  102.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  103.  
  104.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  105.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  106.  
  107. END SUB 'B2BCollision
  108.  
  109.  
  110. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  111.  
  112.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  113.  
  114.  
  115.  
  116. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  117.     STATIC StartTimer AS _FLOAT
  118.     STATIC ButtonDown AS INTEGER
  119.     STATIC ClickCount AS INTEGER
  120.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  121.     '                          Down longer counts as a HOLD event.
  122.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  123.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  124.         SELECT CASE SGN(_MOUSEWHEEL)
  125.             CASE 1: MBS = MBS OR 512
  126.             CASE -1: MBS = MBS OR 1024
  127.         END SELECT
  128.     WEND
  129.  
  130.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  131.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  132.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  133.  
  134.     IF StartTimer = 0 THEN
  135.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  136.             ButtonDown = 1: StartTimer = TIMER(0.01)
  137.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  138.         ELSEIF _MOUSEBUTTON(2) THEN
  139.             ButtonDown = 2: StartTimer = TIMER(0.01)
  140.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  141.         ELSEIF _MOUSEBUTTON(3) THEN
  142.             ButtonDown = 3: StartTimer = TIMER(0.01)
  143.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  144.         END IF
  145.     ELSE
  146.         BD = ButtonDown MOD 3
  147.         IF BD = 0 THEN BD = 3
  148.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  149.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  150.         ELSE
  151.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  152.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  153.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  154.             ELSE 'We've now started the hold event
  155.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  156.             END IF
  157.         END IF
  158.     END IF
  159.  
  160.  
  161. FUNCTION PyT (var1 AS V2, var2 AS V2)
  162.  
  163.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  164.  
  165.  
  166.  
  167. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  168.  
  169.     var.x = var.x + (var2.x * var3) '                           add vector (or a scalar multiple of) var2 to var
  170.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  171.  
  172. END SUB 'Add_Vector
  173.  
  174.  
  175. FUNCTION VecDot (var AS V2, var2 AS V2)
  176.  
  177.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  178.  
  179. END FUNCTION 'VecDot
  180.  
  181.  
  182. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  183.  
  184.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  185.     vec.y = vec.y * multiplier
  186.  
  187. END SUB 'Vec_Mult
  188.  
  189.  
  190. SUB VecNorm (var AS V2)
  191.  
  192.     m = PyT(origin, var)
  193.     IF m = 0 THEN
  194.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  195.     ELSE
  196.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  197.     END IF
  198.  
  199. END SUB 'VecNorm
  200.  
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 21, 2021, 12:19:10 pm
@OldMoses was that Berchek paper the same you gave link to that I used?

Oh yeah, I see at end 2009 Chad Berchek. It worked for me except for the problem of hanging when balls overlap. I am still trying to figure out how to get rid of that specially on breaks in Pool.
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 21, 2021, 12:41:09 pm
It's the same issue I had with mine, the breaks, tight groups and/or high speeds made weird things happen. At least I seem to have a good handle, provisionally speaking, on how the balls are supposed to move now.


EDIT:
I'm thinking that if one were to obtain the magnitude of the fastest ball and use that as the upper end of a loop counter, you could advance the fast ball one unit vector at a time, while using FUNCTION map! to advance the slower ball by the proper amount, you could determine more precise ball (x, y) positions at impact. Use those points as temporary positions to get the exit vectors and then finish out the movement along those new vectors. I used a similar system for planet/ship collisions in my starship program where both entities were in motion.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 22, 2021, 12:26:43 am
The dashed lines are the exit vectors.   Dotted line is the strike vector. solid lines are the ball headings. 

example
red  @ (0, 0)  along <0, 100>  exits along <100, -1>
cyan @ (-100, 100)  along <100, 0>  exits along <0, 100>

Exit vectors are correct.   The -1 looks to be a rounding error.

Now move cyan ball to

red  @ (0, 0)  along <0, 100>  exits along <100, 100>
cyan @ (-100, 0)  along <100, 0>  exits along <0, 0>

And a very interesting result. The cyan ball comes to a complete stop and the red ball speeds up. Intuitive guess is the red ball should be travelling twice as fast (200) but the red ball exit velocity is

(100^ + 100^2 ).5 = 141.421356

Solving for kinetic energy
                                     red             cyan
141.421356 ^ 2 / 2 =   100^2 /2 + 100^2 /2   =  10000
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 22, 2021, 07:27:39 am
example
red  @ (0, 0)  along <0, 100>  exits along <100, -1>
cyan @ (-100, 100)  along <100, 0>  exits along <0, 100>

Exit vectors are correct.   The -1 looks to be a rounding error.

Yeah, for the actual vector components I'm using SINGLE, but the data bar is using INT on the numbers. I imagine it has oodles of rounding issues.


Now move cyan ball to

red  @ (0, 0)  along <0, 100>  exits along <100, 100>
cyan @ (-100, 0)  along <100, 0>  exits along <0, 0>

And a very interesting result. The cyan ball comes to a complete stop and the red ball speeds up. Intuitive guess is the red ball should be travelling twice as fast (200) but the red ball exit velocity is


Now that seems appropriate, given that the cyan is striking red square while red is getting T-boned. Red retains its unit tangent component (along Y) and receives all the energy from cyan along the unit normal component (along X). Red is imparting zero force along its unit tangent in X to Cyan. Ergo, cyan loses all momentum.

In trying to set up the scenarios you tested, I've had some challenge getting things to set on the exact numbers. Mouse control is a bit coarse. So I added some _KEYDOWNs to allow finer control.

"r" to choose red ball
"c" to choose cyan ball
, or the last ball chosen by the mouse will be active
arrow keys will move the chosen ball in x/y
"aswd" keys will move the input vector tail in x/y

Code: QB64: [Select]
  1. TYPE V2
  2.     x AS SINGLE
  3.     y AS SINGLE
  4.  
  5. TYPE ball
  6.     cn AS STRING * 4 '                                          ball name by color
  7.     c AS _UNSIGNED LONG '                                       color
  8.     p AS V2 '                                                   position
  9.     m AS V2 '                                                   movement (pre-contact) incoming
  10.     x AS V2 '                                                   vector of post contact movement
  11.     s AS INTEGER '                                              magnitude of movement
  12.  
  13. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  14. DIM mouse AS V2
  15. DIM b(1) AS ball
  16. DIM SHARED AS V2 origin
  17. origin.x = 0: origin.y = 0
  18. b(0).c = Red: b(0).cn = "red"
  19. b(1).c = Cyan: b(1).cn = "cyan"
  20.  
  21. SCREEN _NEWIMAGE(600, 600, 32)
  22. WINDOW (-300, 300)-(300, -300)
  23.  
  24. 'starting state
  25. b(0).m.x = 0: b(0).m.y = -100 '                                 reds approach vector
  26. b(0).s = PyT(origin, b(0).m) '                                  reds magnitude (speed or force)
  27. b(1).m.x = 100: b(1).m.y = -100 '                               cyans approach vector
  28. b(1).s = PyT(origin, b(1).m) '                                  cyans magnitude (speed or force)
  29.  
  30. b(0).p.x = 30: b(0).p.y = 10
  31. b(1).p.x = -17: b(1).p.y = 0
  32.  
  33.     ms = MBS '                                                  process mouse actions dragging endpoints
  34.     IF ms AND 64 THEN
  35.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  36.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  37.         FOR x = 0 TO 1
  38.             FOR y = 0 TO 1
  39.                 ds! = PyT(vertex(x, y), mouse)
  40.                 IF ds! < ballradius * .5 THEN i = x: j = y
  41.             NEXT y
  42.         NEXT x
  43.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  44.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  45.                 b(i).p = mouse
  46.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  47.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  48.         END SELECT
  49.     ELSE
  50.         IF _KEYDOWN(114) THEN i = 0
  51.         IF _KEYDOWN(99) THEN i = 1
  52.         IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  53.         IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  54.         IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  55.         IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  56.         IF _KEYDOWN(119) THEN b(i).m.y = b(i).m.y - 1
  57.         IF _KEYDOWN(115) THEN b(i).m.y = b(i).m.y + 1
  58.         IF _KEYDOWN(97) THEN b(i).m.x = b(i).m.x + 1
  59.         IF _KEYDOWN(100) THEN b(i).m.x = b(i).m.x - 1
  60.         _DELAY .1
  61.     END IF
  62.  
  63.     'START OF COLLISION MATHEMATICS SECTION
  64.     ballradius = PyT(b(0).p, b(1).p) / 2
  65.     CLS
  66.  
  67.     FOR bn = 0 TO 1
  68.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  69.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  70.     NEXT bn
  71.  
  72.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  73.     B2BCollision b(0), b(1)
  74.     'END OF COLLISION MATHEMATICS SECTION
  75.  
  76.     'graphic representation
  77.     FOR grid = -300 TO 300 STEP 20
  78.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  79.         LINE (grid, 300)-(grid, -300), c& 'Gray
  80.         LINE (-300, grid)-(300, grid), c& ' Gray
  81.     NEXT grid
  82.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  83.     FOR dr = 0 TO 1
  84.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c
  85.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x - b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  86.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  87.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  88.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  89.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">"
  90.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  91.     NEXT dr
  92.  
  93.     _LIMIT 50
  94.     _DISPLAY
  95.  
  96.  
  97. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  98.  
  99.     DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  100.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  101.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  102.     bnci1 = VecDot(un, ball1.m) '
  103.     bnci2 = VecDot(un, ball2.m) '
  104.     btci1 = VecDot(ut, ball1.m) '
  105.     btci2 = VecDot(ut, ball2.m) '
  106.  
  107.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  108.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  109.  
  110.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  111.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  112.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  113.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  114.  
  115.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  116.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  117.  
  118. END SUB 'B2BCollision
  119.  
  120.  
  121. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  122.  
  123.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  124.  
  125.  
  126.  
  127. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  128.     STATIC StartTimer AS _FLOAT
  129.     STATIC ButtonDown AS INTEGER
  130.     STATIC ClickCount AS INTEGER
  131.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  132.     '                          Down longer counts as a HOLD event.
  133.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  134.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  135.         SELECT CASE SGN(_MOUSEWHEEL)
  136.             CASE 1: MBS = MBS OR 512
  137.             CASE -1: MBS = MBS OR 1024
  138.         END SELECT
  139.     WEND
  140.  
  141.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  142.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  143.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  144.  
  145.     IF StartTimer = 0 THEN
  146.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  147.             ButtonDown = 1: StartTimer = TIMER(0.01)
  148.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  149.         ELSEIF _MOUSEBUTTON(2) THEN
  150.             ButtonDown = 2: StartTimer = TIMER(0.01)
  151.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  152.         ELSEIF _MOUSEBUTTON(3) THEN
  153.             ButtonDown = 3: StartTimer = TIMER(0.01)
  154.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  155.         END IF
  156.     ELSE
  157.         BD = ButtonDown MOD 3
  158.         IF BD = 0 THEN BD = 3
  159.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  160.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  161.         ELSE
  162.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  163.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  164.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  165.             ELSE 'We've now started the hold event
  166.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  167.             END IF
  168.         END IF
  169.     END IF
  170.  
  171.  
  172. FUNCTION PyT (var1 AS V2, var2 AS V2)
  173.  
  174.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  175.  
  176.  
  177.  
  178. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  179.  
  180.     var.x = var.x + (var2.x * var3) '                           add vector (or a scalar multiple of) var2 to var
  181.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  182.  
  183. END SUB 'Add_Vector
  184.  
  185.  
  186. FUNCTION VecDot (var AS V2, var2 AS V2)
  187.  
  188.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  189.  
  190. END FUNCTION 'VecDot
  191.  
  192.  
  193. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  194.  
  195.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  196.     vec.y = vec.y * multiplier
  197.  
  198. END SUB 'Vec_Mult
  199.  
  200.  
  201. SUB VecNorm (var AS V2)
  202.  
  203.     m = PyT(origin, var)
  204.     IF m = 0 THEN
  205.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  206.     ELSE
  207.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  208.     END IF
  209.  
  210. END SUB 'VecNorm
  211.  
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 22, 2021, 07:20:55 pm
Well some folks might have big balls, but I've had teleporting balls and now I have magnetic balls. :D

Anyway, this is the result of plugging SUB B2BCollision into my billiards project. The basic issue has to be impact point control, but I have to say, I found the bug much more amusing this time.

left click to shoot the cue ball, right click to re-rack em, esc to quit.

Code: QB64: [Select]
  1.  
  2. 'ball colors 1 yellow 2 blue 3 red 4 purple 5 orange 6 green 7 maroon 8 black
  3. '9 yellow/s 10 blue/s 11 red/s 12 purple/s 13 orange/s 14 green/s 15 maroon/s
  4.  
  5. TYPE V2
  6.     x AS SINGLE
  7.     y AS SINGLE
  8.  
  9. TYPE ball
  10.     sunk AS _BYTE '                                             has ball been sunk true/false
  11.     im AS _BYTE '                                               impact register
  12.     c AS _UNSIGNED LONG '                                       ball color
  13.     p AS V2 '                                                 position vector
  14.     d AS V2 '                                                 direction vector
  15.     n AS V2 '                                                 normalized direction vector
  16.     s AS SINGLE '                                               speed
  17.     r AS _BYTE '                                                rack position
  18.  
  19. DIM SHARED bsiz AS INTEGER '                                    radius of ball
  20. DIM SHARED bsiz2 AS INTEGER '                                   ball diameter or sphere of contact
  21. DIM SHARED bl(15) AS ball '                                     ball data
  22. DIM SHARED bnum(15) AS LONG
  23. DIM SHARED smack(15, 16) AS _BYTE
  24. DIM SHARED origin AS V2
  25. origin.x = 0: origin.y = 0
  26.  
  27. 'Set the table size
  28.     xtable = _DESKTOPWIDTH - 100: ytable = xtable / 2
  29.     ytable = _DESKTOPHEIGHT - 80: xtable = ytable * 2
  30.  
  31. bsiz = INT(((xtable / 118.1102) * 2.375) / 2) '                 size balls to table
  32. bsiz2 = bsiz * 2
  33.  
  34. FOR x = 0 TO 15
  35.     READ bl(x).c
  36.  
  37. MakeBalls
  38. RackEmUp
  39. bl(0).p.y = INT(ytable * .5) '                                  position the cue
  40. bl(0).p.x = INT(xtable * .75)
  41.  
  42.  
  43. a& = _NEWIMAGE(xtable, ytable, 32)
  44. shot& = _NEWIMAGE(xtable, ytable, 32)
  45. _DEST a&: SCREEN a&
  46. COLOR , &HFF3AAF61
  47. 'set up the table
  48. FOR x = 0 TO 2
  49.     LINE (x, x)-(xtable - x, ytable - x), Black, B
  50.     FCirc xtable * .75, ytable * .5, 5, Gray, Gray
  51.     FCirc xtable * .75, ytable * .5, 2, White, White
  52.     CLS
  53.     FOR x = 0 TO 15
  54.         VecAdd bl(x).p, bl(x).d, 1
  55.         VecMult bl(x).d, .99
  56.         ColCheck x
  57.         _PUTIMAGE (INT(bl(x).p.x) - CINT(_WIDTH(bnum(x)) / 2), INT(bl(x).p.y) - CINT(_HEIGHT(bnum(x)) / 2)), bnum(x), a&
  58.     NEXT x
  59.  
  60.     ms = MBS%
  61.     IF ms AND 1 THEN
  62.         bl(0).d.x = (bl(0).p.x - _MOUSEX) * 0.05
  63.         bl(0).d.y = (bl(0).p.y - _MOUSEY) * 0.05
  64.     END IF
  65.     IF ms AND 2 THEN
  66.         BallStop
  67.         bl(0).p.y = INT(ytable * .5)
  68.         bl(0).p.x = INT(xtable * .75)
  69.         RackEmUp
  70.     END IF
  71.     LINE (_MOUSEX, _MOUSEY)-(CINT(bl(0).p.x), CINT(bl(0).p.y))
  72.     'slope of target line
  73.     pathx = CINT(bl(0).p.x) - _MOUSEX: pathy = CINT(bl(0).p.y) - _MOUSEY
  74.     LINE (bl(0).p.x, bl(0).p.y)-(pathx * 1000, pathy * 1000), Blue
  75.     _DISPLAY
  76.     _LIMIT 100 '100 seems good
  77.  
  78.  
  79. '                                                               DATA SECTION
  80. hue:
  81. DATA 4294967295,4294967040,4278190335,4294901760,4286578816,4294944000,4278222848,4286578688
  82. DATA 4278190080,4294967040,4278190335,4294901760,4286578816,4294944000,4278222848,4286578688
  83.  
  84. start:
  85. DATA 1,2,15,14,8,3,4,6,11,13,12,7,9,10,5,0
  86.  
  87. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  88.  
  89.     DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  90.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  91.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  92.     bnci1 = VecDot(un, ball1.d) '                               ball 1 normal component of input velocity
  93.     bnci2 = VecDot(un, ball2.d) '                               ball 2 normal component of input velocity
  94.     btci1 = VecDot(ut, ball1.d) '                               ball 1 tangent component of input velocity
  95.     btci2 = VecDot(ut, ball2.d) '                               ball 2 tangent component of input velocity
  96.  
  97.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  98.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  99.  
  100.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  101.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  102.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  103.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  104.  
  105.     ball1.d = ncomp1: VecAdd ball1.d, tcomp1, 1 '               add normal and tangent exit vectors
  106.     ball2.d = ncomp2: VecAdd ball2.d, tcomp2, 1 '               add normal and tangent exit vectors
  107.  
  108. END SUB 'B2BCollision
  109.  
  110.  
  111. SUB BallStop
  112.  
  113.     FOR x = 0 TO 15
  114.         bl(x).d = origin
  115.     NEXT x
  116.  
  117. END SUB 'BallStop
  118.  
  119.  
  120. SUB ColCheck (var AS INTEGER)
  121.  
  122.     'check for ball in displacement radius
  123.     disp = SQR(bl(var).d.x * bl(var).d.x + bl(var).d.y * bl(var).d.y) 'vector magnitude for this iteration
  124.     FOR x = 0 TO 15
  125.         IF x <> var THEN '                                      won't collide with self so skip self check
  126.             IF NOT bl(var).sunk THEN '                          if ball not already out of play
  127.                 dist = PyT(bl(var).p, bl(x).p) '                calculate distance between var and x
  128.                 IF dist - bsiz2 < disp THEN '                   if ball x is within reach of magnitude
  129.                     'check to see if there is an impact point-  Ray trace routine
  130.                     DIM neari AS V2
  131.                     'DIM strike AS V2
  132.                     'DIM strikeorth AS V2
  133.                     'DIM striker AS V2
  134.                     'DIM strikee AS V2
  135.                     'DIM orthox AS V2
  136.                     'DIM orthoy AS V2
  137.  
  138.                     'Using a variation of the collision routine from CTVector.bas
  139.                     'but the damn thing isn't working as envisioned... well it didn't work there either.
  140.                     dx = bl(var).p.x - bl(x).p.x
  141.                     dy = bl(var).p.y - bl(x).p.y
  142.                     A## = (bl(var).d.x * bl(var).d.x) + (bl(var).d.y * bl(var).d.y) 'displacement range
  143.                     B## = 2 * bl(var).d.x * dx + 2 * bl(var).d.y * dy
  144.                         C## = (bl(x).p.x * bl(x).p.x) + (bl(x).p.y * bl(x).p.y) + (bl(var).p.x * bl(var).p.x)_
  145.                              + (bl(var).p.y * bl(var).p.y) + -2 * (bl(x).p.x * bl(var).p.x + bl(x).p.y * bl(var).p.y) - (bsiz2 * bsiz2)
  146.                     disabc## = (B## * B##) - 4 * A## * C##
  147.                     IF disabc## <= 0 THEN 'let's make this <=, since a tangent brush by would ideally not move the ball
  148.                     ELSE
  149.                         t## = (-B## - ((B## * B##) - 4 * A## * C##) ^ .5) / (2 * A##) 'near intersect quadratic gives percentage of displacement to contact
  150.                         neari.x = bl(var).p.x + t## * bl(var).d.x: neari.y = bl(var).p.y + t## * bl(var).d.y 'contact point
  151.                         'now that we have a contact point, we can proceed to deflect the displacements of var and x
  152.  
  153.                         'Not sure yet how to get impact points but we can replace the following with SUB B2BCollision
  154.                         'bl(var).p = neari
  155.                         ''// get strike angle unit vector
  156.                         'strike.x = bl(x).p.x - neari.x: strike.y = bl(x).p.y - neari.y
  157.                         ''// get the two orthogonal vectors to strike
  158.                         'orthox.x = -strike.y: orthox.y = strike.x
  159.                         'orthoy.x = strike.y: orthoy.y = -strike.x
  160.                         ''// add orthogonals to impact point
  161.                         'VecAdd orthox, neari, 1: VecAdd orthoy, neari, 1
  162.                         ''// add present var displacement to ortho's
  163.                         'VecAdd orthox, bl(var).d, 1: VecAdd orthoy, bl(var).d, 1
  164.                         ''// check distances and compare, using farthest one for striker's new vector
  165.                         'vox = PyT(orthox, bl(x).p): voy = PyT(orthoy, bl(x).p)
  166.                         'IF vox > voy THEN
  167.                         '    strikeorth.x = -strike.y: strikeorth.y = strike.x
  168.                         'ELSEIF vox = voy THEN
  169.                         '    strikeorth = strike
  170.                         'ELSE
  171.                         '    strikeorth.x = strike.y: strikeorth.y = -strike.x
  172.                         'END IF
  173.                         ''// normalize strike vectors
  174.                         'VecNorm strike: VecNorm strikeorth
  175.  
  176.  
  177.                         'striker = bl(var).d: VecNorm striker '                  get striker unit vector
  178.                         'strikee = bl(x).d: VecNorm strikee '                    get strikee unit vector
  179.                         'dot = striker.x * strike.x + striker.y * strike.y '     apply to struck balls displacement magnitude along strike
  180.                         'dotback = 1 - dot '                                     apply to striking balls displacement along orthogonal of strike
  181.  
  182.                         ''get proportion of energy transfer and add it to existing vector of unit x
  183.                         'VecMult strike, 0.99 * dot * PyT(origin, bl(var).d)
  184.                         'VecAdd bl(x).d, strike, 1
  185.                         ''do the same with var using balance of energy
  186.                         'VecMult strikeorth, 0.99 * dotback * PyT(origin, bl(var).d)
  187.                         ''VecAdd bl(var).d, strikeorth, 1
  188.                         'bl(var).d = strikeorth
  189.                         ''bl(var).d.x = -bl(var).d.x: bl(var).d.y = -bl(var).d.y
  190.                         B2BCollision bl(var), bl(x)
  191.                     END IF 'disabc <= 0
  192.                 END IF 'dist < disp
  193.             END IF 'NOT bl(var).sunk
  194.         END IF 'x <> var
  195.     NEXT x
  196.  
  197.     'wall bounces
  198.     IF bl(var).p.x < bsiz OR bl(var).p.x > xtable - bsiz THEN
  199.         bl(var).d.x = -bl(var).d.x
  200.         IF bl(var).p.x < bsiz THEN '                            if beyond left edge
  201.             bl(var).p.y = bl(var).p.y - (bl(var).d.y * ((bl(var).p.x - bsiz) / bl(var).d.x))
  202.             bl(var).p.x = bsiz
  203.         END IF
  204.         IF bl(var).p.x > xtable - bsiz THEN '                   if beyond right edge
  205.             bl(var).p.y = bl(var).p.y - (bl(var).d.y * ((bl(var).p.x - xtable + bsiz) / bl(var).d.x))
  206.             bl(var).p.x = xtable - bsiz
  207.         END IF
  208.     END IF
  209.     IF bl(var).p.y < bsiz OR bl(var).p.y > ytable - bsiz THEN
  210.         bl(var).d.y = -bl(var).d.y
  211.         IF bl(var).p.y < bsiz THEN '                            if beyond top edge
  212.             bl(var).p.x = bl(var).p.x - (bl(var).d.x * ((bl(var).p.y - bsiz) / bl(var).d.y))
  213.             bl(var).p.y = bsiz
  214.         END IF
  215.         IF bl(var).p.y > ytable - bsiz THEN '                   if beyond bottom edge
  216.             bl(var).p.x = bl(var).p.x - (bl(var).d.x * ((bl(var).p.y - ytable + bsiz) / bl(var).d.y))
  217.             bl(var).p.y = ytable - bsiz
  218.         END IF
  219.     END IF
  220.  
  221. END SUB 'ColCheck
  222.  
  223.  
  224.     DIM R AS INTEGER, RError AS INTEGER '                       SMcNeill's circle fill
  225.     DIM X AS INTEGER, Y AS INTEGER
  226.  
  227.     R = ABS(RR)
  228.     RError = -R
  229.     X = R
  230.     Y = 0
  231.     IF R = 0 THEN PSET (CX, CY), C: EXIT SUB
  232.     LINE (CX - X, CY)-(CX + X, CY), C, BF
  233.     WHILE X > Y
  234.         RError = RError + Y * 2 + 1
  235.         IF RError >= 0 THEN
  236.             IF X <> Y + 1 THEN
  237.                 LINE (CX - Y, CY - X)-(CX + Y, CY - X), C2, BF 'these two need white here for 9-15 balls
  238.                 LINE (CX - Y, CY + X)-(CX + Y, CY + X), C2, BF
  239.             END IF
  240.             X = X - 1
  241.             RError = RError - X * 2
  242.         END IF
  243.         Y = Y + 1
  244.         LINE (CX - X, CY - Y)-(CX + X, CY - Y), C, BF
  245.         LINE (CX - X, CY + Y)-(CX + X, CY + Y), C, BF
  246.     WEND
  247. END SUB 'FCirc
  248.  
  249.  
  250. SUB LoadColArray
  251.  
  252.     FOR a = 0 TO 15
  253.         FOR b = 0 TO 16
  254.  
  255.     NEXT b, a
  256.  
  257. END SUB 'LoadColArray
  258.  
  259.  
  260. SUB MakeBalls
  261.  
  262.     FOR x = 0 TO 15
  263.         'make ball images here
  264.         bnum(x) = _NEWIMAGE(bsiz * 2 + 4, bsiz * 2 + 4, 32)
  265.         _DEST bnum(x)
  266.         IF x = 0 THEN '                                         Cue ball
  267.             FCirc INT(_WIDTH(bnum(x)) / 2), INT(_HEIGHT(bnum(x)) / 2), bsiz, bl(x).c, bl(x).c
  268.             CIRCLE (_WIDTH(bnum(x)) / 2, _HEIGHT(bnum(x)) / 2), bsiz + 1, Black
  269.         ELSE
  270.             'Solids or stripes
  271.             IF x <= 8 THEN
  272.                 FCirc INT(_WIDTH(bnum(x)) / 2), INT(_HEIGHT(bnum(x)) / 2), bsiz, bl(x).c, bl(x).c ' solid
  273.             ELSE
  274.                 FCirc INT(_WIDTH(bnum(x)) / 2), INT(_HEIGHT(bnum(x)) / 2), bsiz, bl(x).c, White '   stripe
  275.             END IF
  276.             FCirc INT(_WIDTH(bnum(x)) / 2), INT(_HEIGHT(bnum(x)) / 2), bsiz - 5, White, White 'number circle
  277.             CIRCLE (_WIDTH(bnum(x)) / 2, _HEIGHT(bnum(x)) / 2), bsiz + 1, Black
  278.             n$ = _TRIM$(STR$(x))
  279.             t& = _NEWIMAGE(16, 16, 32)
  280.             _DEST t&
  281.             COLOR Black
  282.             _PRINTMODE _KEEPBACKGROUND
  283.             IF LEN(n$) > 1 THEN a = 0 ELSE a = 4
  284.             _PRINTSTRING (a, 0), n$, t&
  285.             _DEST bnum(x)
  286.             _PUTIMAGE (8, 8)-(_WIDTH(bnum(x)) - 8, _HEIGHT(bnum(x)) - 8), t&, bnum(x)
  287.             _FREEIMAGE t&
  288.         END IF
  289.     NEXT x
  290.  
  291. END SUB 'MakeBalls
  292.  
  293.  
  294. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  295.     STATIC StartTimer AS _FLOAT
  296.     STATIC ButtonDown AS INTEGER
  297.     STATIC ClickCount AS INTEGER
  298.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  299.     '                          Down longer counts as a HOLD event.
  300.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  301.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  302.         SELECT CASE SGN(_MOUSEWHEEL)
  303.             CASE 1: MBS = MBS OR 512
  304.             CASE -1: MBS = MBS OR 1024
  305.         END SELECT
  306.     WEND
  307.  
  308.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  309.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  310.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  311.  
  312.     IF StartTimer = 0 THEN
  313.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  314.             ButtonDown = 1: StartTimer = TIMER(0.01)
  315.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  316.         ELSEIF _MOUSEBUTTON(2) THEN
  317.             ButtonDown = 2: StartTimer = TIMER(0.01)
  318.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  319.         ELSEIF _MOUSEBUTTON(3) THEN
  320.             ButtonDown = 3: StartTimer = TIMER(0.01)
  321.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  322.         END IF
  323.     ELSE
  324.         BD = ButtonDown MOD 3
  325.         IF BD = 0 THEN BD = 3
  326.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  327.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  328.         ELSE
  329.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  330.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  331.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  332.             ELSE 'We've now started the hold event
  333.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  334.             END IF
  335.         END IF
  336.     END IF
  337.  
  338.  
  339. FUNCTION PyT (var1 AS V2, var2 AS V2)
  340.  
  341.     PyT = _HYPOT(ABS(var1.x - var2.x), ABS(var1.y - var2.y)) '  distances and magnitudes
  342.  
  343.  
  344.  
  345. SUB RackEmUp
  346.  
  347.     yoff = bsiz2 + 4
  348.     xoff = SQR((yoff / 2) * (yoff / 2) + yoff * yoff)
  349.  
  350.     RESTORE start
  351.     FOR rank = 1 TO 5
  352.         FOR b = 1 TO rank
  353.             READ k
  354.             bl(k).p.x = (.25 * xtable) - (xoff * (rank - 1))
  355.             bl(k).p.y = (.5 * ytable) - ((rank - 1) * (.5 * yoff)) + ((b - 1) * yoff)
  356.         NEXT b
  357.     NEXT rank
  358.  
  359. END SUB 'RackEmUp
  360.  
  361.  
  362. SUB VecAdd (var AS V2, var2 AS V2, var3 AS INTEGER)
  363.  
  364.     var.x = var.x + (var2.x * var3) '                           add (or subtract) two vectors defined by unitpoint
  365.     var.y = var.y + (var2.y * var3) '                           var= base vector, var2= vector to add
  366.  
  367. END SUB 'VecAdd
  368.  
  369.  
  370. FUNCTION VecDot (var AS V2, var2 AS V2)
  371.  
  372.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  373.  
  374. END FUNCTION 'VecDot
  375.  
  376.  
  377. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  378.  
  379.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  380.     vec.y = vec.y * multiplier
  381.  
  382. END SUB 'VecMult
  383.  
  384. SUB VecNorm (var AS V2)
  385.  
  386.     m = SQR(var.x * var.x + var.y * var.y) '                    convert var to unit vector
  387.     var.x = var.x / m
  388.     var.y = var.y / m
  389.  
  390. END SUB 'VecNorm
  391.  
  392.  
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 22, 2021, 09:14:54 pm
@OldMoses

Well if this is wrong I don't want to be right, I like the look of your balls, I'm still trying to figure how you get the stripe but going with images is good. I could do it with lines and Paint I guess.

For sticky, balls I've found if you back the collided balls back to collision kiss point then calculate the vectors they don't stick.

The ball angles look pretty good too!

Update: Oh you changed the Fcirc sub to make stripes.
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 22, 2021, 09:54:49 pm
Once upon a time, I wanted to figure out how Steve's circle fill worked, and so I put a couple sleep commands in and watched it progress. When I started the billiards thing, it seemed the natural solution. It might even be good with rotozoom.

I'm trying to come up with a handler that will do a break, but I'm getting a nagging suspicion that I might have to do something recursive to deal with potential multiple strikes in a single displacement loop. Never tried anything like that before...beyond here there be dragons...
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 23, 2021, 03:44:35 am
Ha! I stopped overlapping balls and now cue ball goes through rack like Moses parting the Red Sea ;-)) crazy!

That wasn't my hanging problem because it still does.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 23, 2021, 03:51:30 am
Oldmoses

Quote
b(0).s = PyT(origin, b(0).m)

what does  variable "origin" refer to ?

Quote
TYPE V2
    x AS SINGLE
    y AS SINGLE
END TYPE

DIM SHARED origin AS V2

Quote
origin.x = 0: origin.y = 0


Ok I think Ive answered my own question


Quote
FUNCTION PyT (var1 AS V2, var2 AS V2)
 
    PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
 
END FUNCTION 'PyT

the .x or .y are supplied in the function.   I really don't get the dot stuff   The only advantage is a smaller DIM list.    I find the dot stuff harder to read






Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 23, 2021, 07:40:59 am
PyT is my method of using pythagorean theorem for obtaining speeds/magnitudes of vectors AND distances between points. I usually define an "origin" so that I can do the former, and simply plug in the two points if I'm after the later.

In this application, PyT only handles the V2 type, but in another of my projects it does both R2 and R3 vectors by accepting a third parameter telling it which to work on.

Code: QB64: [Select]
  1. FUNCTION PyT (var AS _BYTE, var1 AS V3, var2 AS V3)
  2.  
  3.     SELECT CASE var   'find distance/magnitude between 2D or 3D points
  4.         CASE IS = 2
  5.             PyT = _HYPOT(var1.pX - var2.pX, var1.pY - var2.pY)
  6.         CASE IS = 3
  7.             PyT = _HYPOT(_HYPOT(var1.pX - var2.pX, var1.pY - var2.pY), var1.pZ - var2.pZ)
  8.     END SELECT
  9.  
  10.  

In some speed testing I've done, I've found the _HYPOT command to be as fast or faster than more traditional methods. I'm thinking of trying to optimize that some more, by ditching the SELECT CASE part.

On the Dot Product topic, I only became aware of it a few months ago and was shocked to discover that it is an indispensable tool for game design. Getting at this sort of programming problem without it is like changing a water pump without wrenches. You might be able to use other tools, but the job will be exponentially harder.

Dot Product is a way of multiplying two vectors together which results in a non-vector number (scalar). The result gives one the ability to determine how much of one vector is "projected" onto another. In our case it is the ability to obtain the portion of red ball's force that is applied directly at cyan ball and what is directed toward red balls exit motion, and of course the same applied from cyan to red and cyan's exit.

  [ This attachment cannot be displayed inline in 'Print Page' view ]  

You needn't worry about the trig formula in the picture, that's mainly for obtaining a dot product when theta is a known quantity, which we're not concerned with in this topic. It might also be apparent, by the picture, that dot product is also closely related to the pythagorean theorem.

In ball collision we are primarily concerned with how much of the unit normal and unit tangent vectors are applied by a collision, which dot product supplies quite well.

Dot Product simply takes the components of two vectors, multiplies them together and sums the results. Example, two vectors A and B, dotted together are:

 A dot B = A.x * B.x + A.y * B.y    'and that doesn't get much simpler for what can be done with it...

and if you're dealing in 3D:

A dot B = A.x * B.x + A.y * B.y + A.z * B.z

Which is exactly what SUB VecDot does in my code, for our 2D representation. The order doesn't matter, but it is good to convert one of the vectors you're projecting into a unit vector, if vector projection is your main purpose. That's what my SUB VecNorm does to the "strike" (aka unit normal), and it's orthogonal (aka unit tangent). BTW, you were correct, I didn't need different strikes & orthogonals for each ball. One was sufficient.

The number that dot product returns will also tell you how the vectors are oriented to each other. A negative dot product indicates vectors angled away from each other (obtuse), a dot product of 0 indicates vectors that are perpendicular, and a positive dot product are vectors that subtend an acute angle. The closest thing to a mathematical multi-tool that I've seen so far. Once you learn it, you'll be looking for places to use it.

This guy does a pretty good job of presenting Dot Product:
&t=31s
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 23, 2021, 01:57:50 pm
Going back to the interactive analysis tool, I've added fine dashed lines to represent the normal and tangent vectors that influence how each ball's position and incoming vector affect the final exit vector. Each is colored by where its influence derives from.

Code: QB64: [Select]
  1. TYPE V2
  2.     x AS SINGLE
  3.     y AS SINGLE
  4.  
  5. TYPE ball
  6.     cn AS STRING * 4 '                                          ball name by color
  7.     c AS _UNSIGNED LONG '                                       color
  8.     p AS V2 '                                                   position
  9.     m AS V2 '                                                   movement (pre-contact) incoming
  10.     x AS V2 '                                                   vector of post contact movement
  11.     s AS INTEGER '                                              magnitude of movement
  12.  
  13. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  14. DIM mouse AS V2
  15. DIM b(1) AS ball
  16. DIM SHARED AS V2 origin
  17. origin.x = 0: origin.y = 0
  18. b(0).c = Red: b(0).cn = "red"
  19. b(1).c = Cyan: b(1).cn = "cyan"
  20.  
  21. SCREEN _NEWIMAGE(600, 600, 32)
  22. WINDOW (-300, 300)-(300, -300)
  23.  
  24. 'starting state
  25. b(0).m.x = 0: b(0).m.y = -100 '                                 reds approach vector
  26. b(0).s = PyT(origin, b(0).m) '                                  reds magnitude (speed or force)
  27. b(1).m.x = 100: b(1).m.y = -100 '                               cyans approach vector
  28. b(1).s = PyT(origin, b(1).m) '                                  cyans magnitude (speed or force)
  29.  
  30. b(0).p.x = 30: b(0).p.y = 10
  31. b(1).p.x = -17: b(1).p.y = 0
  32.  
  33.     ms = MBS '                                                  process mouse actions dragging endpoints
  34.     IF ms AND 64 THEN
  35.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  36.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  37.         FOR x = 0 TO 1
  38.             FOR y = 0 TO 1
  39.                 ds! = PyT(vertex(x, y), mouse)
  40.                 IF ds! < ballradius * .5 THEN i = x: j = y
  41.             NEXT y
  42.         NEXT x
  43.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  44.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  45.                 b(i).p = mouse
  46.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  47.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  48.         END SELECT
  49.     ELSE
  50.         IF _KEYDOWN(114) THEN i = 0
  51.         IF _KEYDOWN(99) THEN i = 1
  52.         IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  53.         IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  54.         IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  55.         IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  56.         IF _KEYDOWN(119) THEN b(i).m.y = b(i).m.y - 1
  57.         IF _KEYDOWN(115) THEN b(i).m.y = b(i).m.y + 1
  58.         IF _KEYDOWN(97) THEN b(i).m.x = b(i).m.x + 1
  59.         IF _KEYDOWN(100) THEN b(i).m.x = b(i).m.x - 1
  60.         _DELAY .1
  61.     END IF
  62.  
  63.     'START OF COLLISION MATHEMATICS SECTION
  64.     ballradius = PyT(b(0).p, b(1).p) / 2
  65.     CLS
  66.  
  67.     FOR bn = 0 TO 1
  68.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  69.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  70.     NEXT bn
  71.  
  72.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  73.     B2BCollision b(0), b(1)
  74.     'END OF COLLISION MATHEMATICS SECTION
  75.  
  76.     'graphic representation
  77.     FOR grid = -300 TO 300 STEP 20
  78.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  79.         LINE (grid, 300)-(grid, -300), c& 'Gray
  80.         LINE (-300, grid)-(300, grid), c& ' Gray
  81.     NEXT grid
  82.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  83.     FOR dr = 0 TO 1
  84.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c
  85.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x - b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  86.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111110011111100 'exit vector
  87.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  88.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  89.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">"
  90.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  91.     NEXT dr
  92.  
  93.     _LIMIT 50
  94.     _DISPLAY
  95.  
  96.  
  97. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  98.  
  99.     DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  100.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  101.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  102.     bnci1 = VecDot(un, ball1.m) '                               ball 1 normal component of input velocity
  103.     bnci2 = VecDot(un, ball2.m) '                               ball 2 normal component of input velocity
  104.     btci1 = VecDot(ut, ball1.m) '                               ball 1 tangent component of input velocity
  105.     btci2 = VecDot(ut, ball2.m) '                               ball 2 tangent component of input velocity
  106.  
  107.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  108.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  109.  
  110.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  111.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  112.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  113.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  114.  
  115.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  116.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  117.  
  118.     'The following is for graphic representation only and can be removed without affecting the SUB's purpose
  119.     LINE (ball1.p.x, ball1.p.y)-(ball1.p.x + ncomp1.x, ball1.p.y + ncomp1.y), &H9F00FFFF, , &B0011001100110011
  120.     LINE (ball1.p.x, ball1.p.y)-(ball1.p.x + tcomp1.x, ball1.p.y + tcomp1.y), &H9FFF7F7F, , &B0011001100110011
  121.     LINE (ball2.p.x, ball2.p.y)-(ball2.p.x + ncomp2.x, ball2.p.y + ncomp2.y), &H9FFF7F7F, , &B0011001100110011
  122.     LINE (ball2.p.x, ball2.p.y)-(ball2.p.x + tcomp2.x, ball2.p.y + tcomp2.y), &H9F00FFFF, , &B0011001100110011
  123.  
  124. END SUB 'B2BCollision
  125.  
  126.  
  127. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  128.  
  129.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  130.  
  131.  
  132.  
  133. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  134.     STATIC StartTimer AS _FLOAT
  135.     STATIC ButtonDown AS INTEGER
  136.     STATIC ClickCount AS INTEGER
  137.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  138.     '                          Down longer counts as a HOLD event.
  139.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  140.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  141.         SELECT CASE SGN(_MOUSEWHEEL)
  142.             CASE 1: MBS = MBS OR 512
  143.             CASE -1: MBS = MBS OR 1024
  144.         END SELECT
  145.     WEND
  146.  
  147.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  148.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  149.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  150.  
  151.     IF StartTimer = 0 THEN
  152.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  153.             ButtonDown = 1: StartTimer = TIMER(0.01)
  154.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  155.         ELSEIF _MOUSEBUTTON(2) THEN
  156.             ButtonDown = 2: StartTimer = TIMER(0.01)
  157.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  158.         ELSEIF _MOUSEBUTTON(3) THEN
  159.             ButtonDown = 3: StartTimer = TIMER(0.01)
  160.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  161.         END IF
  162.     ELSE
  163.         BD = ButtonDown MOD 3
  164.         IF BD = 0 THEN BD = 3
  165.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  166.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  167.         ELSE
  168.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  169.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  170.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  171.             ELSE 'We've now started the hold event
  172.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  173.             END IF
  174.         END IF
  175.     END IF
  176.  
  177.  
  178. FUNCTION PyT (var1 AS V2, var2 AS V2)
  179.  
  180.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  181.  
  182.  
  183.  
  184. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  185.  
  186.     var.x = var.x + (var2.x * var3) '                           add vector (or a scalar multiple of) var2 to var
  187.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  188.  
  189. END SUB 'Add_Vector
  190.  
  191.  
  192. FUNCTION VecDot (var AS V2, var2 AS V2)
  193.  
  194.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  195.  
  196. END FUNCTION 'VecDot
  197.  
  198.  
  199. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  200.  
  201.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  202.     vec.y = vec.y * multiplier
  203.  
  204. END SUB 'Vec_Mult
  205.  
  206.  
  207. SUB VecNorm (var AS V2)
  208.  
  209.     m = PyT(origin, var)
  210.     IF m = 0 THEN
  211.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  212.     ELSE
  213.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  214.     END IF
  215.  
  216. END SUB 'VecNorm
  217.  
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 23, 2021, 06:26:58 pm
Found source of hanging problem, it wasn't overlapping balls, it was infinite loop of subtracting vectors of no length.

This code does cleanup overlapping balls before next shot, Pool 2 at Steve's forum:
https://qb64.freeforums.net/thread/150/pool?page=1&scrollTo=615

So OldMoses link to paper still works.
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 23, 2021, 07:12:34 pm
That was outstanding, I ran the whole table with no issues at all.
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 23, 2021, 09:57:57 pm
That was outstanding, I ran the whole table with no issues at all.

Holy balls the whole table!? Man I am just now converted to ball images like you and am patting myself on back at how much easier to see shots because I made all outside edges the same color.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 23, 2021, 10:18:44 pm
OldMoses

The position fine tuning works great.

red  @ (0, 0)  along <0, 100>  exits along <0, 100>
cyan @ (-100, 100)  along <-100, 0>  exits along <-100, 0>

"along <-100, 0>" is correct but the actual cyan vector on the screen shows
<100, 0>
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 24, 2021, 03:19:11 am
"along <-100, 0>" is correct but the actual cyan vector on the screen shows
<100, 0>

I tried setting that up, but kept getting a slight rounding error on the cyan's exit vector. It would add a 1 in the Y axis

Is this similar to what you're getting?

  [ This attachment cannot be displayed inline in 'Print Page' view ]  

If so, I'd say it's working OK.

I've seen the math do some funny things with it, but that mostly happens when it's representing scenarios that are highly unlikely to happen in a real world ball collision. Such as when a slow ball gets around the backside of a fast one. In fact, I'm wondering if that might be the source of my magnetic ball bug.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 25, 2021, 09:50:41 pm
Code added to OldMoses program.
(some trig used)

Keys to rotate vector are z or x
keys to adjust vector magnitude (ball velocity) are c or v
Key to toggle red or cyan ball is b

As in the original code, the arrow keys move the ball positions.

rotate vector has an auto fine / coarse control.   hold down key longer, for coarse rotation and momentary for fine rotation

Don't know how this will interact with the mouse.
There might be a few bugs?

Will run on the older version of QB64 as well

Oh, If you hit the f key the ball parameters are saved in a file called
BALL STUFF.TXT and you can copy and paste from notepad etc.

Code: QB64: [Select]
  1. 'Original code by OldMoses
  2. 'Additional code by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   position
  12.     m AS V2 '                                                   movement (pre-contact) incoming
  13.     x AS V2 '                                                   vector of post contact movement
  14.     s AS INTEGER '                                              magnitude of movement
  15. DIM TEXT(1) AS STRING
  16. RAD = 0 ' * _PI
  17.  
  18. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  19. DIM mouse AS V2
  20. DIM b(1) AS ball
  21. 'DIM SHARED AS V2 origin
  22. DIM SHARED origin AS V2
  23. origin.x = 0: origin.y = 0
  24. b(0).c = Red: b(0).cn = "red"
  25. b(1).c = Cyan: b(1).cn = "cyan"
  26.  
  27. SCREEN _NEWIMAGE(600, 600, 32)
  28. WINDOW (-300, 300)-(300, -300)
  29.  
  30. 'starting state
  31. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  32. b(0).s = PyT(origin, b(0).m)
  33. 'b(0).s = _HYPOT(origin.x - b(0).m.x, origin.y - b(0).m.y) ' reds magnitude (speed or force)
  34.  
  35. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  36. b(1).s = PyT(origin, b(1).m)
  37. 'b(1).s = _HYPOT(origin.x - b(1).m.x, origin.y - b(1).m.y) ' cyans magnitude (speed or force)
  38.  
  39. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  40. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  41. f3 = 0
  42.     CLS
  43.     ms = MBS '                                                  process mouse actions dragging endpoints
  44.     IF ms AND 64 THEN
  45.         LOCATE 10, 10: PRINT "mousex"; mouse.x
  46.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  47.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  48.         ' LOCATE 10, 10: PRINT "mousex"; mouse.x
  49.         FOR x = 0 TO 1
  50.             FOR y = 0 TO 1
  51.                 ds! = PyT(vertex(x, y), mouse)
  52.                 IF ds! < ballradius * .5 THEN i = x: j = y
  53.             NEXT y
  54.         NEXT x
  55.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  56.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  57.                 b(i).p = mouse
  58.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  59.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  60.         END SELECT
  61.     ELSE
  62.         'IF _KEYDOWN(114) THEN i = 0
  63.         'IF _KEYDOWN(99) THEN i = 1
  64.         IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  65.         IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  66.         IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  67.         IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  68.  
  69.         'IF _KEYDOWN(119) THEN b(i).m.y = b(i).m.y - 1
  70.         'IF _KEYDOWN(115) THEN b(i).m.y = b(i).m.y + 1
  71.         'IF _KEYDOWN(97) THEN b(i).m.x = b(i).m.x + 1
  72.         'IF _KEYDOWN(100) THEN b(i).m.x = b(i).m.x - 1
  73.  
  74.         '**************Code added by Novarseg
  75.         'Vector rotation and vector magnitude adjustment using keyboard input
  76.  
  77.         I$ = INKEY$
  78.         IF f3 = 0 THEN I$ = "b": f3 = 1
  79.         IF I$ = "b" AND f2 = 0 THEN i = 1: f2 = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  80.         IF I$ = "b" AND f2 = 1 THEN i = 0: f2 = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  81.         LL1:
  82.  
  83.         IF I$ = "c" THEN 'increase vector magnitude
  84.             mult = 1.01
  85.             VecMult b(i).m, mult
  86.         END IF
  87.  
  88.         IF I$ = "v" THEN 'decrease vector magnitude
  89.             mult = 1.01 'should be div to avoid confusion, still works though
  90.             VecDIV b(i).m, mult 'added a new sub
  91.         END IF
  92.  
  93.  
  94.         IF I$ = "z" THEN 'rotate vector counter clockwise
  95.             IF RAD > _PI * 2 THEN RAD = 0
  96.             'IF RAD < 0 THEN RAD = _PI * 2
  97.             IF INKEY$ <> "z" THEN f1 = 0
  98.             IF f1 = 0 THEN t1 = TIMER
  99.             f1 = 1
  100.             RAD = RAD + .005
  101.             IF TIMER - t1 > 1.5 THEN
  102.                 RAD = RAD + .05
  103.             END IF
  104.             signC = COS(RAD) * 1 / ABS(COS(RAD))
  105.             signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  106.             b(i).s = PyT(origin, b(i).m)
  107.             b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  108.             b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  109.  
  110.         END IF
  111.  
  112.         IF I$ = "x" THEN 'rotate vector clockwise
  113.  
  114.             'IF RAD > _PI * 2 THEN RAD = 0
  115.             IF RAD < 0 THEN RAD = _PI * 2
  116.  
  117.             IF INKEY$ <> "x" THEN f1 = 0
  118.             IF f1 = 0 THEN t1 = TIMER
  119.             f1 = 1
  120.             RAD = RAD - .005
  121.             IF TIMER - t1 > 1.5 THEN
  122.                 RAD = RAD - .05
  123.             END IF
  124.             signC = COS(RAD) * 1 / ABS(COS(RAD))
  125.             signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  126.             b(i).s = PyT(origin, b(i).m)
  127.             b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  128.             b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  129.  
  130.             '**************END Code added Novarseg
  131.         END IF
  132.  
  133.         _DELAY .1
  134.     END IF
  135.     mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  136.  
  137.  
  138.     'START OF COLLISION MATHEMATICS SECTION
  139.     ballradius = PyT(b(0).p, b(1).p) / 2
  140.  
  141.     FOR bn = 0 TO 1
  142.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  143.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  144.     NEXT bn
  145.  
  146.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  147.     B2BCollision b(0), b(1)
  148.     'END OF COLLISION MATHEMATICS SECTION
  149.  
  150.     'graphic representation
  151.     FOR grid = -300 TO 300 STEP 20
  152.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  153.         LINE (grid, 300)-(grid, -300), c& 'Gray
  154.         LINE (-300, grid)-(300, grid), c& ' Gray
  155.     NEXT grid
  156.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  157.  
  158.     FOR dr = 0 TO 1
  159.  
  160.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c
  161.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  162.  
  163.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  164.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  165.  
  166.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  167.         _PRINTSTRING (0, 567 + (16 * dr)), SPACE$(80)
  168.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">" + c(dr)
  169.  
  170.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  171.  
  172.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  173.     NEXT dr
  174.  
  175.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  176.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  177.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  178.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  179.         CLOSE 'NOVARSEG added this line
  180.     END IF 'NOVARSEG added this line
  181.  
  182.     _LIMIT 50
  183.     _DISPLAY
  184.  
  185.  
  186. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  187.  
  188.     ' DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  189.     DIM un AS V2
  190.     DIM ut AS V2
  191.     DIM ncomp1 AS V2
  192.     DIM ncomp2 AS V2
  193.     DIM tcomp1 AS V2
  194.     DIM tcomp2 AS V2
  195.  
  196.  
  197.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  198.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  199.     bnci1 = VecDot(un, ball1.m) '
  200.     bnci2 = VecDot(un, ball2.m) '
  201.     btci1 = VecDot(ut, ball1.m) '
  202.     btci2 = VecDot(ut, ball2.m) '
  203.  
  204.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  205.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  206.  
  207.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  208.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  209.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  210.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  211.  
  212.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  213.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  214.  
  215. END SUB 'B2BCollision
  216.  
  217.  
  218. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  219.  
  220.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  221.  
  222.  
  223.  
  224. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  225.     STATIC StartTimer AS _FLOAT
  226.     STATIC ButtonDown AS INTEGER
  227.     STATIC ClickCount AS INTEGER
  228.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  229.     '                          Down longer counts as a HOLD event.
  230.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  231.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  232.         SELECT CASE SGN(_MOUSEWHEEL)
  233.             CASE 1: MBS = MBS OR 512
  234.             CASE -1: MBS = MBS OR 1024
  235.         END SELECT
  236.     WEND
  237.  
  238.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  239.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  240.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  241.  
  242.     IF StartTimer = 0 THEN
  243.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  244.             ButtonDown = 1: StartTimer = TIMER(0.01)
  245.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  246.         ELSEIF _MOUSEBUTTON(2) THEN
  247.             ButtonDown = 2: StartTimer = TIMER(0.01)
  248.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  249.         ELSEIF _MOUSEBUTTON(3) THEN
  250.             ButtonDown = 3: StartTimer = TIMER(0.01)
  251.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  252.         END IF
  253.     ELSE
  254.         BD = ButtonDown MOD 3
  255.         IF BD = 0 THEN BD = 3
  256.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  257.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  258.         ELSE
  259.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  260.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  261.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  262.             ELSE 'We've now started the hold event
  263.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  264.             END IF
  265.         END IF
  266.     END IF
  267.  
  268.  
  269. FUNCTION PyT (var1 AS V2, var2 AS V2)
  270.  
  271.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  272.  
  273.  
  274.  
  275. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  276.  
  277.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  278.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  279.  
  280. END SUB 'Add_Vector
  281.  
  282.  
  283. FUNCTION VecDot (var AS V2, var2 AS V2)
  284.  
  285.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  286.  
  287. END FUNCTION 'VecDot
  288.  
  289.  
  290. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  291.  
  292.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  293.     vec.y = vec.y * multiplier
  294.  
  295. END SUB 'Vec_Mult
  296.  
  297. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  298.  
  299.     vec.x = vec.x / divisor
  300.     vec.y = vec.y / divisor
  301.  
  302. END SUB 'VecDIV
  303.  
  304.  
  305. SUB VecNorm (var AS V2)
  306.  
  307.     m = PyT(origin, var)
  308.     IF m = 0 THEN
  309.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  310.     ELSE
  311.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  312.     END IF
  313.  
  314. END SUB 'VecNorm

example:
red  @ (0, 0)  along <0, 100>  exits along <200, 100>           
cyan @ (-100, 0)  along <-201, 0>  exits along <0, 0>  SELECTED

the cyan ball will come to a complete stop no matter how fast it's going


Added FULLSCREEN use q or w keys to adjust aspect ratio

Balls don't contact in FULLSCREEN mode, working on code to fix that

Code: QB64: [Select]
  1. 'Original code by OldMoses
  2. 'Additional code by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   position
  12.     m AS V2 '                                                   movement (pre-contact) incoming
  13.     x AS V2 '                                                   vector of post contact movement
  14.     s AS INTEGER '                                              magnitude of movement
  15. DIM TEXT(1) AS STRING
  16. RAD = 0 ' * _PI
  17.  
  18. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  19. DIM mouse AS V2
  20. DIM b(1) AS ball
  21. 'DIM SHARED AS V2 origin
  22. DIM SHARED origin AS V2
  23. origin.x = 0: origin.y = 0
  24. b(0).c = Red: b(0).cn = "red"
  25. b(1).c = Cyan: b(1).cn = "cyan"
  26.  
  27. SCREEN _NEWIMAGE(600, 600, 32)
  28. WINDOW (-300, 300)-(300, -300)
  29.  
  30. 'starting state
  31. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  32. b(0).s = PyT(origin, b(0).m)
  33. 'b(0).s = _HYPOT(origin.x - b(0).m.x, origin.y - b(0).m.y) ' reds magnitude (speed or force)
  34.  
  35. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  36. b(1).s = PyT(origin, b(1).m)
  37. 'b(1).s = _HYPOT(origin.x - b(1).m.x, origin.y - b(1).m.y) ' cyans magnitude (speed or force)
  38.  
  39. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  40. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  41. f3 = 0
  42. ar = 1.5
  43.     CLS
  44.     ms = MBS '                                                  process mouse actions dragging endpoints
  45.     IF ms AND 64 THEN
  46.         LOCATE 10, 10: PRINT "mousex"; mouse.x
  47.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  48.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  49.         ' LOCATE 10, 10: PRINT "mousex"; mouse.x
  50.         FOR x = 0 TO 1
  51.             FOR y = 0 TO 1
  52.                 ds! = PyT(vertex(x, y), mouse)
  53.                 IF ds! < ballradius * .5 THEN i = x: j = y
  54.             NEXT y
  55.         NEXT x
  56.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  57.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  58.                 b(i).p = mouse
  59.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  60.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  61.         END SELECT
  62.     ELSE
  63.         'IF _KEYDOWN(114) THEN i = 0
  64.         'IF _KEYDOWN(99) THEN i = 1
  65.         IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  66.         IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  67.         IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  68.         IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  69.  
  70.         'IF _KEYDOWN(119) THEN b(i).m.y = b(i).m.y - 1
  71.         'IF _KEYDOWN(115) THEN b(i).m.y = b(i).m.y + 1
  72.         'IF _KEYDOWN(97) THEN b(i).m.x = b(i).m.x + 1
  73.         'IF _KEYDOWN(100) THEN b(i).m.x = b(i).m.x - 1
  74.  
  75.         '**************Code added by Novarseg
  76.         'Vector rotation and vector magnitude adjustment using keyboard input
  77.  
  78.         I$ = INKEY$
  79.         IF f3 = 0 THEN I$ = "b": f3 = 1
  80.         IF I$ = "b" AND f2 = 0 THEN i = 1: f2 = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  81.         IF I$ = "b" AND f2 = 1 THEN i = 0: f2 = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  82.         LL1:
  83.  
  84.         IF I$ = "c" THEN 'increase vector magnitude
  85.             mult = 1.01
  86.             VecMult b(i).m, mult
  87.         END IF
  88.  
  89.         IF I$ = "v" THEN 'decrease vector magnitude
  90.             mult = 1.01 'should be div to avoid confusion, still works though
  91.             VecDIV b(i).m, mult 'added a new sub
  92.         END IF
  93.  
  94.  
  95.         IF I$ = "z" THEN 'rotate vector counter clockwise
  96.             IF RAD > _PI * 2 THEN RAD = 0
  97.             'IF RAD < 0 THEN RAD = _PI * 2
  98.             IF INKEY$ <> "z" THEN f1 = 0
  99.             IF f1 = 0 THEN t1 = TIMER
  100.             f1 = 1
  101.             RAD = RAD + .005
  102.             IF TIMER - t1 > 1.5 THEN
  103.                 RAD = RAD + .05
  104.             END IF
  105.             signC = COS(RAD) * 1 / ABS(COS(RAD))
  106.             signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  107.             b(i).s = PyT(origin, b(i).m)
  108.             b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  109.             b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  110.  
  111.         END IF
  112.  
  113.         IF I$ = "x" THEN 'rotate vector clockwise
  114.  
  115.             'IF RAD > _PI * 2 THEN RAD = 0
  116.             IF RAD < 0 THEN RAD = _PI * 2
  117.  
  118.             IF INKEY$ <> "x" THEN f1 = 0
  119.             IF f1 = 0 THEN t1 = TIMER
  120.             f1 = 1
  121.             RAD = RAD - .005
  122.             IF TIMER - t1 > 1.5 THEN
  123.                 RAD = RAD - .05
  124.             END IF
  125.             signC = COS(RAD) * 1 / ABS(COS(RAD))
  126.             signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  127.             b(i).s = PyT(origin, b(i).m)
  128.             b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  129.             b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  130.  
  131.             '**************END Code added Novarseg
  132.         END IF
  133.  
  134.         IF I$ = "q" THEN
  135.             ar = ar + .01
  136.         END IF
  137.  
  138.         IF I$ = "w" THEN
  139.             ar = ar - .01
  140.         END IF
  141.  
  142.  
  143.         _DELAY .1
  144.     END IF
  145.     mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  146.  
  147.  
  148.     'START OF COLLISION MATHEMATICS SECTION
  149.     ballradius = PyT(b(0).p, b(1).p) / 2
  150.  
  151.     FOR bn = 0 TO 1
  152.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  153.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  154.     NEXT bn
  155.  
  156.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  157.     B2BCollision b(0), b(1)
  158.     'END OF COLLISION MATHEMATICS SECTION
  159.  
  160.     'graphic representation
  161.     FOR grid = -300 TO 300 STEP 20
  162.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  163.         LINE (grid, 300)-(grid, -300), c& 'Gray
  164.         LINE (-300, grid)-(300, grid), c& ' Gray
  165.     NEXT grid
  166.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  167.  
  168.     FOR dr = 0 TO 1
  169.  
  170.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , ar
  171.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  172.  
  173.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  174.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  175.  
  176.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  177.         _PRINTSTRING (0, 567 + (16 * dr)), SPACE$(80)
  178.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">" + c(dr)
  179.  
  180.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  181.  
  182.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  183.     NEXT dr
  184.  
  185.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  186.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  187.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  188.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  189.         CLOSE 'NOVARSEG added this line
  190.     END IF 'NOVARSEG added this line
  191.  
  192.     _LIMIT 50
  193.     _DISPLAY
  194.  
  195.  
  196. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  197.  
  198.     ' DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  199.     DIM un AS V2
  200.     DIM ut AS V2
  201.     DIM ncomp1 AS V2
  202.     DIM ncomp2 AS V2
  203.     DIM tcomp1 AS V2
  204.     DIM tcomp2 AS V2
  205.  
  206.  
  207.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  208.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  209.     bnci1 = VecDot(un, ball1.m) '
  210.     bnci2 = VecDot(un, ball2.m) '
  211.     btci1 = VecDot(ut, ball1.m) '
  212.     btci2 = VecDot(ut, ball2.m) '
  213.  
  214.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  215.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  216.  
  217.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  218.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  219.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  220.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  221.  
  222.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  223.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  224.  
  225. END SUB 'B2BCollision
  226.  
  227.  
  228. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  229.  
  230.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  231.  
  232.  
  233.  
  234. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  235.     STATIC StartTimer AS _FLOAT
  236.     STATIC ButtonDown AS INTEGER
  237.     STATIC ClickCount AS INTEGER
  238.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  239.     '                          Down longer counts as a HOLD event.
  240.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  241.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  242.         SELECT CASE SGN(_MOUSEWHEEL)
  243.             CASE 1: MBS = MBS OR 512
  244.             CASE -1: MBS = MBS OR 1024
  245.         END SELECT
  246.     WEND
  247.  
  248.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  249.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  250.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  251.  
  252.     IF StartTimer = 0 THEN
  253.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  254.             ButtonDown = 1: StartTimer = TIMER(0.01)
  255.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  256.         ELSEIF _MOUSEBUTTON(2) THEN
  257.             ButtonDown = 2: StartTimer = TIMER(0.01)
  258.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  259.         ELSEIF _MOUSEBUTTON(3) THEN
  260.             ButtonDown = 3: StartTimer = TIMER(0.01)
  261.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  262.         END IF
  263.     ELSE
  264.         BD = ButtonDown MOD 3
  265.         IF BD = 0 THEN BD = 3
  266.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  267.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  268.         ELSE
  269.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  270.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  271.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  272.             ELSE 'We've now started the hold event
  273.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  274.             END IF
  275.         END IF
  276.     END IF
  277.  
  278.  
  279. FUNCTION PyT (var1 AS V2, var2 AS V2)
  280.  
  281.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  282.  
  283.  
  284.  
  285. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  286.  
  287.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  288.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  289.  
  290. END SUB 'Add_Vector
  291.  
  292.  
  293. FUNCTION VecDot (var AS V2, var2 AS V2)
  294.  
  295.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  296.  
  297. END FUNCTION 'VecDot
  298.  
  299.  
  300. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  301.  
  302.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  303.     vec.y = vec.y * multiplier
  304.  
  305. END SUB 'Vec_Mult
  306.  
  307. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  308.  
  309.     vec.x = vec.x / divisor
  310.     vec.y = vec.y / divisor
  311.  
  312. END SUB 'VecDIV
  313.  
  314.  
  315. SUB VecNorm (var AS V2)
  316.  
  317.     m = PyT(origin, var)
  318.     IF m = 0 THEN
  319.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  320.     ELSE
  321.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  322.     END IF
  323.  
  324. END SUB 'VecNorm

FULLSCREEN distorts the x  y axis so the vector magnitude will appear to be changing when rotating. That is from the x y distortion - not the code.

 Trying to correct distortion when in FULLSCREEN mode.



Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 26, 2021, 06:22:16 am
It did mess with the mouse some, but it's more intuitive to use on the keyboard.

I suspect the issue with full screen distortion is that WINDOW needs to be altered to reflect the full screen aspect ratio. Grid drawing loop will need changing too, I believe.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 26, 2021, 04:21:31 pm
How does FULLSCREEN calculate the aspect ratio?

Quote
WINDOW needs to be altered to reflect the full screen aspect ratio

I will try that







Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 26, 2021, 05:22:05 pm
_FULLSCREEN just stretches the QB64 screen over your computer screen.

For Square Window SqrXSqr, use a square QB64 screen, that should eliminate distortions.
ie Screen _NewImage(mySqr, mySqr, 32)

Oh you want _DeskTopWidth and _DeskTopHeight for your screen dimensions. Ah! then use those for _FullScreen without distortion.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 26, 2021, 05:56:00 pm
Interesting
Lets say FULLSCREEN uses _DeskTopWidth and _DeskTopHeight  so taking your square screen idea like newimage(600,600,32).

  x1 = ( 600 / (  (_DeskTopWidth  /_DeskTopHeight ) + 1 )  )
y1 = ( 600 / (  (_DeskTopHeight  /_DeskTopWidth  ) + 1 )  )


WINDOW (-x1,y1)-(x1,-y1)
er maybe it's
WINDOW (-x1,300)-(x1,-300)?

will it work?  The result should be an expanded square screen that will fit into DeskTopHeight dimensions. The negative signs make the center of the screen 0x, 0y

Gotta use _FULLSCREEN as well to make this work

Example, say DeskTopWidth  /_DeskTopHeight   = 1   then 1 + 1 = 2

600 / 2 = 300

WINDOW (-300, 300)-(300, -300)  the original code

but as we know, most if not all computer screens have more pixels wide than high so it looks that is why _FULLSCREEN distorts in certain cases.  Nothing wrong with FULLSCREEN though.

example say DeskTopWidth  /_DeskTopHeight   = 1.5   then 1.5 + 1 = 2.5

600 / 2.5 = 240

WINDOW (-240, 300)-(240, -300)

will that correct FULLSCREEN distortion?.  As OldMoses pointed out, the code that draws the grid has to work with these numbers as well.
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 26, 2021, 06:45:02 pm
Don't "gotta use full screen to make this work"

gotta use full screen to make this complicated as hell ;-))

Whatever your screen ratio height to width has to be matched in QB64 screen and Window screen they all need same proportions for _FULLSCREEN not to be distorted.

Say your Desktopwidth to height is 4 to 3 then make qb64 screen 400 X 300 or 800x600 or 1200x900
and window -200 to 200, 150 to -150 or -400 to 400, 300, -300.

And note your screen is not same as every one else screen. I would skip FULLSCREEN.

If you don't use FULLSCREEN then you can keep things simple with square ranges of x and y.


Wait is -200 to 200 = 400 pixels or 401 pixels probably 401 pixels.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 26, 2021, 07:01:19 pm
@bplus

Ok I will try coding that later. For now I'm just playing with the numbers.  The idea behind using FULLSCREEN is that it has the ability to fill the entire screen.   Doing an 800,600,32 with newimage will be close to the solution. 

The main thing is to have a nice undistorted grid with fullscreen.  I want to use the entire computer display.  Fullscreen also makes graphics  appear larger. (at times)

example
newimage (_DeskTopWidth  ,_DeskTopHeight ,32)

so I think that a FULLSCREEN after this won't have any effect because it can not stretch the image. ?

Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 26, 2021, 07:21:09 pm
I tweaked this a little bit earlier today for a large display. I still haven't gotten back mouse function on the input vector grab, IDK what is wrong there. Funny how sometimes a modeling tool will distract one from an original topic.

It's like, "Oh look, a chicken.":D


EDIT:
I almost forgot to fix the grid...that should do it

Code: QB64: [Select]
  1. 'Original code & some full display adjustments by OldMoses
  2. 'Additional code by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   position
  12.     m AS V2 '                                                   movement (pre-contact) incoming
  13.     x AS V2 '                                                   vector of post contact movement
  14.     s AS INTEGER '                                              magnitude of movement
  15. DIM TEXT(1) AS STRING
  16. RAD = 0 ' * _PI
  17.  
  18. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  19. DIM mouse AS V2
  20. DIM b(1) AS ball
  21. 'DIM SHARED AS V2 origin
  22. DIM SHARED origin AS V2
  23. origin.x = 0: origin.y = 0
  24. b(0).c = Red: b(0).cn = "red"
  25. b(1).c = Cyan: b(1).cn = "cyan"
  26.  
  27. '_FULLSCREEN
  28.  
  29. 'starting state
  30. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  31. b(0).s = PyT(origin, b(0).m)
  32.  
  33. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  34. b(1).s = PyT(origin, b(1).m)
  35.  
  36. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  37. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  38. f3 = 0
  39. ar = 1
  40.     CLS
  41.     ms = MBS '                                                  process mouse actions dragging endpoints
  42.     IF ms AND 64 THEN
  43.         _PRINTSTRING (0, 0), "mouse (" + STR$(mouse.x) + ", " + STR$(mouse.y) + ")"
  44.         mouse.x = map!(_MOUSEX, 0, _DESKTOPWIDTH - 1, -_DESKTOPWIDTH / 2, _DESKTOPWIDTH / 2)
  45.         mouse.y = map!(_MOUSEY, 0, _DESKTOPHEIGHT - 1, _DESKTOPHEIGHT / 2, -_DESKTOPHEIGHT / 2)
  46.         FOR x = 0 TO 1
  47.             FOR y = 0 TO 1
  48.                 ds! = PyT(vertex(x, y), mouse)
  49.                 IF ds! < ballradius * .25 THEN i = x: j = y
  50.             NEXT y
  51.         NEXT x
  52.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  53.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  54.                 b(i).p = mouse
  55.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  56.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  57.         END SELECT
  58.     ELSE
  59.         IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  60.         IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  61.         IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  62.         IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  63.  
  64.         '**************Code added by Novarseg
  65.         'Vector rotation and vector magnitude adjustment using keyboard input
  66.  
  67.         I$ = INKEY$
  68.         IF f3 = 0 THEN I$ = "b": f3 = 1
  69.         IF I$ = "b" AND i = 0 THEN i = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  70.         IF I$ = "b" AND i = 1 THEN i = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  71.         LL1:
  72.  
  73.         IF I$ = "c" THEN 'increase vector magnitude
  74.             mult = 1.01
  75.             VecMult b(i).m, mult
  76.         END IF
  77.  
  78.         IF I$ = "v" THEN 'decrease vector magnitude
  79.             mult = 1.01 'should be div to avoid confusion, still works though
  80.             VecDIV b(i).m, mult 'added a new sub
  81.         END IF
  82.  
  83.  
  84.         IF I$ = "z" THEN 'rotate vector counter clockwise
  85.             IF RAD > _PI * 2 THEN RAD = 0
  86.             'IF RAD < 0 THEN RAD = _PI * 2
  87.             IF INKEY$ <> "z" THEN f1 = 0
  88.             IF f1 = 0 THEN t1 = TIMER
  89.             f1 = 1
  90.             RAD = RAD + .005
  91.             IF TIMER - t1 > 1.5 THEN
  92.                 RAD = RAD + .05
  93.             END IF
  94.             signC = COS(RAD) * 1 / ABS(COS(RAD))
  95.             signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  96.             b(i).s = PyT(origin, b(i).m)
  97.             b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  98.             b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  99.  
  100.         END IF
  101.  
  102.         IF I$ = "x" THEN 'rotate vector clockwise
  103.  
  104.             'IF RAD > _PI * 2 THEN RAD = 0
  105.             IF RAD < 0 THEN RAD = _PI * 2
  106.  
  107.             IF INKEY$ <> "x" THEN f1 = 0
  108.             IF f1 = 0 THEN t1 = TIMER
  109.             f1 = 1
  110.             RAD = RAD - .005
  111.             IF TIMER - t1 > 1.5 THEN
  112.                 RAD = RAD - .05
  113.             END IF
  114.             signC = COS(RAD) * 1 / ABS(COS(RAD))
  115.             signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  116.             b(i).s = PyT(origin, b(i).m)
  117.             b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  118.             b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  119.  
  120.             '**************END Code added Novarseg
  121.         END IF
  122.  
  123.         IF I$ = "q" THEN
  124.             ar = ar + .01
  125.         END IF
  126.  
  127.         IF I$ = "w" THEN
  128.             ar = ar - .01
  129.         END IF
  130.  
  131.  
  132.         _DELAY .1
  133.     END IF
  134.     'mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  135.  
  136.  
  137.     'START OF COLLISION MATHEMATICS SECTION
  138.     ballradius = PyT(b(0).p, b(1).p) / 2
  139.  
  140.     FOR bn = 0 TO 1
  141.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  142.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  143.     NEXT bn
  144.  
  145.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  146.     B2BCollision b(0), b(1)
  147.     'END OF COLLISION MATHEMATICS SECTION
  148.  
  149.     'graphic representation
  150.     FOR grid = -_DESKTOPWIDTH / 2 TO _DESKTOPWIDTH / 2 'STEP 20
  151.         IF grid MOD 20 <> 0 THEN _CONTINUE
  152.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  153.         LINE (grid, _DESKTOPHEIGHT / 2)-(grid, -_DESKTOPHEIGHT / 2), c& 'Gray
  154.         LINE (-_DESKTOPWIDTH / 2, grid)-(_DESKTOPWIDTH / 2, grid), c& ' Gray
  155.     NEXT grid
  156.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  157.  
  158.     FOR dr = 0 TO 1
  159.  
  160.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , ar
  161.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  162.  
  163.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  164.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  165.  
  166.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  167.         _PRINTSTRING (0, _DESKTOPHEIGHT - 80 + (16 * dr)), SPACE$(80)
  168.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">" + c(dr)
  169.  
  170.         _PRINTSTRING (0, _DESKTOPHEIGHT - 80 + (16 * dr)), b$
  171.  
  172.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  173.     NEXT dr
  174.  
  175.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  176.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  177.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  178.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  179.         CLOSE 'NOVARSEG added this line
  180.     END IF 'NOVARSEG added this line
  181.  
  182.     _LIMIT 50
  183.     _DISPLAY
  184.  
  185.  
  186. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  187.  
  188.     ' DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  189.     DIM un AS V2
  190.     DIM ut AS V2
  191.     DIM ncomp1 AS V2
  192.     DIM ncomp2 AS V2
  193.     DIM tcomp1 AS V2
  194.     DIM tcomp2 AS V2
  195.  
  196.  
  197.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  198.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  199.     bnci1 = VecDot(un, ball1.m) '
  200.     bnci2 = VecDot(un, ball2.m) '
  201.     btci1 = VecDot(ut, ball1.m) '
  202.     btci2 = VecDot(ut, ball2.m) '
  203.  
  204.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  205.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  206.  
  207.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  208.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  209.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  210.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  211.  
  212.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  213.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  214.  
  215. END SUB 'B2BCollision
  216.  
  217.  
  218. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  219.  
  220.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  221.  
  222.  
  223.  
  224. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  225.     STATIC StartTimer AS _FLOAT
  226.     STATIC ButtonDown AS INTEGER
  227.     STATIC ClickCount AS INTEGER
  228.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  229.     '                          Down longer counts as a HOLD event.
  230.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  231.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  232.         SELECT CASE SGN(_MOUSEWHEEL)
  233.             CASE 1: MBS = MBS OR 512
  234.             CASE -1: MBS = MBS OR 1024
  235.         END SELECT
  236.     WEND
  237.  
  238.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  239.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  240.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  241.  
  242.     IF StartTimer = 0 THEN
  243.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  244.             ButtonDown = 1: StartTimer = TIMER(0.01)
  245.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  246.         ELSEIF _MOUSEBUTTON(2) THEN
  247.             ButtonDown = 2: StartTimer = TIMER(0.01)
  248.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  249.         ELSEIF _MOUSEBUTTON(3) THEN
  250.             ButtonDown = 3: StartTimer = TIMER(0.01)
  251.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  252.         END IF
  253.     ELSE
  254.         BD = ButtonDown MOD 3
  255.         IF BD = 0 THEN BD = 3
  256.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  257.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  258.         ELSE
  259.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  260.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  261.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  262.             ELSE 'We've now started the hold event
  263.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  264.             END IF
  265.         END IF
  266.     END IF
  267.  
  268.  
  269. FUNCTION PyT (var1 AS V2, var2 AS V2)
  270.  
  271.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  272.  
  273.  
  274.  
  275. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  276.  
  277.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  278.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  279.  
  280. END SUB 'Add_Vector
  281.  
  282.  
  283. FUNCTION VecDot (var AS V2, var2 AS V2)
  284.  
  285.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  286.  
  287. END FUNCTION 'VecDot
  288.  
  289.  
  290. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  291.  
  292.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  293.     vec.y = vec.y * multiplier
  294.  
  295. END SUB 'Vec_Mult
  296.  
  297. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  298.  
  299.     vec.x = vec.x / divisor
  300.     vec.y = vec.y / divisor
  301.  
  302. END SUB 'VecDIV
  303.  
  304.  
  305. SUB VecNorm (var AS V2)
  306.  
  307.     m = PyT(origin, var)
  308.     IF m = 0 THEN
  309.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  310.     ELSE
  311.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  312.     END IF
  313.  
  314. END SUB 'VecNorm
  315.  
  316.  
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 26, 2021, 07:42:16 pm
@OldMoses
Nice display.

 FULLSCREEN enabled

Can't see parameter text at bottom though

Code: QB64: [Select]
  1. 'Original code & some full display adjustments by OldMoses
  2. 'Additional code by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   position
  12.     m AS V2 '                                                   movement (pre-contact) incoming
  13.     x AS V2 '                                                   vector of post contact movement
  14.     s AS INTEGER '                                              magnitude of movement
  15. DIM TEXT(1) AS STRING
  16. RAD = 0 ' * _PI
  17.  
  18. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  19. DIM mouse AS V2
  20. DIM b(1) AS ball
  21. 'DIM SHARED AS V2 origin
  22. DIM SHARED origin AS V2
  23. origin.x = 0: origin.y = 0
  24. b(0).c = Red: b(0).cn = "red"
  25. b(1).c = Cyan: b(1).cn = "cyan"
  26.  
  27.  
  28. 'starting state
  29. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  30. b(0).s = PyT(origin, b(0).m)
  31.  
  32. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  33. b(1).s = PyT(origin, b(1).m)
  34.  
  35. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  36. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  37. f3 = 0
  38. ar = 1
  39.     CLS
  40.     ms = MBS '                                                  process mouse actions dragging endpoints
  41.     IF ms AND 64 THEN
  42.         _PRINTSTRING (0, 0), "mouse (" + STR$(mouse.x) + ", " + STR$(mouse.y) + ")"
  43.         mouse.x = map!(_MOUSEX, 0, _DESKTOPWIDTH - 1, -_DESKTOPWIDTH / 2, _DESKTOPWIDTH / 2)
  44.         mouse.y = map!(_MOUSEY, 0, _DESKTOPHEIGHT - 1, _DESKTOPHEIGHT / 2, -_DESKTOPHEIGHT / 2)
  45.         FOR x = 0 TO 1
  46.             FOR y = 0 TO 1
  47.                 ds! = PyT(vertex(x, y), mouse)
  48.                 IF ds! < ballradius * .25 THEN i = x: j = y
  49.             NEXT y
  50.         NEXT x
  51.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  52.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  53.                 b(i).p = mouse
  54.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  55.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  56.         END SELECT
  57.     ELSE
  58.         IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  59.         IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  60.         IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  61.         IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  62.  
  63.         '**************Code added by Novarseg
  64.         'Vector rotation and vector magnitude adjustment using keyboard input
  65.  
  66.         I$ = INKEY$
  67.         IF f3 = 0 THEN I$ = "b": f3 = 1
  68.         IF I$ = "b" AND i = 0 THEN i = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  69.         IF I$ = "b" AND i = 1 THEN i = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  70.         LL1:
  71.  
  72.         IF I$ = "c" THEN 'increase vector magnitude
  73.             mult = 1.01
  74.             VecMult b(i).m, mult
  75.         END IF
  76.  
  77.         IF I$ = "v" THEN 'decrease vector magnitude
  78.             mult = 1.01 'should be div to avoid confusion, still works though
  79.             VecDIV b(i).m, mult 'added a new sub
  80.         END IF
  81.  
  82.  
  83.         IF I$ = "z" THEN 'rotate vector counter clockwise
  84.             IF RAD > _PI * 2 THEN RAD = 0
  85.             'IF RAD < 0 THEN RAD = _PI * 2
  86.             IF INKEY$ <> "z" THEN f1 = 0
  87.             IF f1 = 0 THEN t1 = TIMER
  88.             f1 = 1
  89.             RAD = RAD + .005
  90.             IF TIMER - t1 > 1.5 THEN
  91.                 RAD = RAD + .05
  92.             END IF
  93.             signC = COS(RAD) * 1 / ABS(COS(RAD))
  94.             signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  95.             b(i).s = PyT(origin, b(i).m)
  96.             b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  97.             b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  98.  
  99.         END IF
  100.  
  101.         IF I$ = "x" THEN 'rotate vector clockwise
  102.  
  103.             'IF RAD > _PI * 2 THEN RAD = 0
  104.             IF RAD < 0 THEN RAD = _PI * 2
  105.  
  106.             IF INKEY$ <> "x" THEN f1 = 0
  107.             IF f1 = 0 THEN t1 = TIMER
  108.             f1 = 1
  109.             RAD = RAD - .005
  110.             IF TIMER - t1 > 1.5 THEN
  111.                 RAD = RAD - .05
  112.             END IF
  113.             signC = COS(RAD) * 1 / ABS(COS(RAD))
  114.             signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  115.             b(i).s = PyT(origin, b(i).m)
  116.             b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  117.             b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  118.  
  119.             '**************END Code added Novarseg
  120.         END IF
  121.  
  122.         IF I$ = "q" THEN
  123.             ar = ar + .01
  124.         END IF
  125.  
  126.         IF I$ = "w" THEN
  127.             ar = ar - .01
  128.         END IF
  129.  
  130.  
  131.         _DELAY .1
  132.     END IF
  133.     'mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  134.  
  135.  
  136.     'START OF COLLISION MATHEMATICS SECTION
  137.     ballradius = PyT(b(0).p, b(1).p) / 2
  138.  
  139.     FOR bn = 0 TO 1
  140.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  141.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  142.     NEXT bn
  143.  
  144.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  145.     B2BCollision b(0), b(1)
  146.     'END OF COLLISION MATHEMATICS SECTION
  147.  
  148.     'graphic representation
  149.     FOR grid = -_DESKTOPWIDTH / 2 TO _DESKTOPWIDTH / 2 STEP 20
  150.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  151.         LINE (grid, _DESKTOPHEIGHT / 2)-(grid, -_DESKTOPHEIGHT / 2), c& 'Gray
  152.         LINE (-_DESKTOPWIDTH / 2, grid)-(_DESKTOPWIDTH / 2, grid), c& ' Gray
  153.     NEXT grid
  154.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  155.  
  156.     FOR dr = 0 TO 1
  157.  
  158.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , ar
  159.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  160.  
  161.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  162.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  163.  
  164.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  165.         _PRINTSTRING (0, _DESKTOPHEIGHT - 80 + (16 * dr)), SPACE$(80)
  166.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">" + c(dr)
  167.  
  168.         _PRINTSTRING (0, _DESKTOPHEIGHT - 80 + (16 * dr)), b$
  169.  
  170.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  171.     NEXT dr
  172.  
  173.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  174.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  175.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  176.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  177.         CLOSE 'NOVARSEG added this line
  178.     END IF 'NOVARSEG added this line
  179.  
  180.     _LIMIT 50
  181.     _DISPLAY
  182.  
  183.  
  184. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  185.  
  186.     ' DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  187.     DIM un AS V2
  188.     DIM ut AS V2
  189.     DIM ncomp1 AS V2
  190.     DIM ncomp2 AS V2
  191.     DIM tcomp1 AS V2
  192.     DIM tcomp2 AS V2
  193.  
  194.  
  195.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  196.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  197.     bnci1 = VecDot(un, ball1.m) '
  198.     bnci2 = VecDot(un, ball2.m) '
  199.     btci1 = VecDot(ut, ball1.m) '
  200.     btci2 = VecDot(ut, ball2.m) '
  201.  
  202.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  203.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  204.  
  205.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  206.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  207.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  208.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  209.  
  210.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  211.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  212.  
  213. END SUB 'B2BCollision
  214.  
  215.  
  216. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  217.  
  218.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  219.  
  220.  
  221.  
  222. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  223.     STATIC StartTimer AS _FLOAT
  224.     STATIC ButtonDown AS INTEGER
  225.     STATIC ClickCount AS INTEGER
  226.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  227.     '                          Down longer counts as a HOLD event.
  228.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  229.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  230.         SELECT CASE SGN(_MOUSEWHEEL)
  231.             CASE 1: MBS = MBS OR 512
  232.             CASE -1: MBS = MBS OR 1024
  233.         END SELECT
  234.     WEND
  235.  
  236.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  237.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  238.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  239.  
  240.     IF StartTimer = 0 THEN
  241.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  242.             ButtonDown = 1: StartTimer = TIMER(0.01)
  243.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  244.         ELSEIF _MOUSEBUTTON(2) THEN
  245.             ButtonDown = 2: StartTimer = TIMER(0.01)
  246.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  247.         ELSEIF _MOUSEBUTTON(3) THEN
  248.             ButtonDown = 3: StartTimer = TIMER(0.01)
  249.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  250.         END IF
  251.     ELSE
  252.         BD = ButtonDown MOD 3
  253.         IF BD = 0 THEN BD = 3
  254.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  255.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  256.         ELSE
  257.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  258.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  259.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  260.             ELSE 'We've now started the hold event
  261.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  262.             END IF
  263.         END IF
  264.     END IF
  265.  
  266.  
  267. FUNCTION PyT (var1 AS V2, var2 AS V2)
  268.  
  269.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  270.  
  271.  
  272.  
  273. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  274.  
  275.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  276.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  277.  
  278. END SUB 'Add_Vector
  279.  
  280.  
  281. FUNCTION VecDot (var AS V2, var2 AS V2)
  282.  
  283.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  284.  
  285. END FUNCTION 'VecDot
  286.  
  287.  
  288. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  289.  
  290.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  291.     vec.y = vec.y * multiplier
  292.  
  293. END SUB 'Vec_Mult
  294.  
  295. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  296.  
  297.     vec.x = vec.x / divisor
  298.     vec.y = vec.y / divisor
  299.  
  300. END SUB 'VecDIV
  301.  
  302.  
  303. SUB VecNorm (var AS V2)
  304.  
  305.     m = PyT(origin, var)
  306.     IF m = 0 THEN
  307.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  308.     ELSE
  309.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  310.     END IF
  311.  
  312. END SUB 'VecNorm






Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 26, 2021, 08:02:12 pm
FULLSCREEN probably stretched it past the bottom of the display. Even without FULLSCREEN, I had to choke up on it a bit to keep it in my version of the display. You might have to pull it up some more on lines #176 & 179 by subtracting more from _DESKTOPHEIGHT, assuming that FULLSCREEN doesn't mess with _PRINTSTRING in some way I'm unaware of.

I've never been satisfied with FULLSCREEN whenever I've used it. It seems to render things a lot grainier than a 32 bit screen will.
Title: Re: 2D ball collisions without trigonometry.
Post by: SMcNeill on May 26, 2021, 08:21:32 pm
Instead of using full screen, why not just calculate the largest possible screen you can use and maintain the original screen ratio, and then _putimage your workscreen onto it, make it the display screen, and keep it centered on the desktop?   You'd always be working in your original screen, wouldn't need to change coordinate values or scale anything...  All you'd do is _PUTIMAGE your workscreen onto the displayscreen before your _DISPLAY statement.

Or, even easier:

Forget about _FULLSCREEN.   Just use $RESIZE:(smooth or stretch) and let the user stretch to whatever size they want for their desktop/readability.

Code: QB64: [Select]
  1. SX = 400: SY = 400
  2. SCREEN _NEWIMAGE(SX, SY, 32)
  3. CLS , &HFF0000AA
  4. FOR i = 1 TO 10
  5.     PRINT i
  6.     _LIMIT 30
  7.     _DISPLAY
  8.  

The above should show how simple resize is to add, and how to scales our images while maintaining aspect ratio, simply enough.  And, best of all -- all your code which you've wrote already will continue to work just like always.  mouse coordinates are automatically scaled.  screen coordinates are automatically scaled.  Your program is the same old 400 x 400 which you wrote it in, in all ways, except the user is simply resizing the final display to whatever size they want it to be on their desktop.

Honestly, I think $RESIZE:(smooth or stretch) should be included in almost all programs.  There's really very few cases where I see it as something you wouldn't want to allow.
 
Title: Re: 2D ball collisions without trigonometry.
Post by: STxAxTIC on May 26, 2021, 08:38:42 pm
When y'all get spheres figured out, you can play with this code, which works for any shape of any mass about any axis (all in 2d).

What it doesn't handle well is sustained contact between objects. (I would have to stare at this code a while to keep working on it.) Nobody tell me you were able to make an object pass through a boundary, I know! :-) This is because it's still in general mode and not optimized for any particular game.

Anyway - it completely nails the one-off collision. Have a ball... or an amoeba. PRESS SPACE DURING PLAY TO PAUSE EVERYTHING AND DRAW YOUR OWN THING. (Not sure if the instructions say this.)

Code: QB64: [Select]
  1. ' Display
  2. Screen _NewImage(800, 600, 32)
  3. _ScreenMove (_DesktopWidth \ 2 - _Width \ 2) - 3, (_DesktopHeight \ 2 - _Height \ 2) - 29
  4. _Title "Collisions - Version 9"
  5.  
  6. ' Meta
  7. start:
  8.  
  9. ' Data structures
  10. Type Vector
  11.     x As Double
  12.     y As Double
  13.  
  14. Dim Shared vtemp As Vector
  15.  
  16. ' Object type
  17. Type Object
  18.     Centroid As Vector
  19.     Collisions As Long
  20.     CollisionSwitch As Integer
  21.     DeltaO As Double
  22.     DeltaV As Vector
  23.     Diameter As Double
  24.     Elements As Integer
  25.     Fixed As Integer
  26.     Mass As Double
  27.     MOI As Double
  28.     PartialNormal As Vector
  29.     Omega As Double
  30.     Shade As _Unsigned Long
  31.     Velocity As Vector
  32.  
  33. ' Object storage
  34. Dim Shared Shape(300) As Object
  35. Dim Shared PointChain(300, 500) As Vector
  36. Dim Shared TempChain(300, 500) As Vector
  37. Dim Shared ShapeCount As Integer
  38. Dim Shared SelectedShape As Integer
  39.  
  40. ' Dynamics
  41. Dim Shared CollisionCount As Integer
  42. Dim Shared ProximalPairs(300 / 2, 1 To 2) As Integer
  43. Dim Shared ProximalPairsCount As Integer
  44. Dim Shared ContactPoints As Integer
  45. Dim Shared CPC, FPC, RST, VD, SV As Double
  46.  
  47. ' Environment
  48. Dim Shared ForceField As Vector ' Ex: gravity
  49.  
  50. ' Initialize
  51. ShapeCount = 0
  52. CollisionCount = 0
  53.  
  54. ' Prompt
  55. Call cprintstring(16 * 17, "WELCOME!                    ")
  56. Call cprintstring(16 * 16, "Press 1 for Pool prototype  ")
  57. Call cprintstring(16 * 15, "Press 2 for Wacky game      ")
  58. Call cprintstring(16 * 14, "Press 3 for Concentric rings")
  59. Call cprintstring(16 * 13, "Press 4 for Walls only      ")
  60. Call cprintstring(16 * 12, "Press 5 for Angle pong game ")
  61.  
  62.     kk = _KeyHit
  63.     Select Case kk
  64.         Case Asc("1")
  65.             Call SetupPoolGame
  66.             Exit Do
  67.         Case Asc("2")
  68.             Call SetupWackyGame
  69.             Exit Do
  70.         Case Asc("3")
  71.             Call SetupRings
  72.             Exit Do
  73.         Case Asc("4")
  74.             Call SetupWallsOnly
  75.             Exit Do
  76.         Case Asc("5")
  77.             Call SetupAnglePong
  78.             Exit Do
  79.         Case Else
  80.             _KeyClear
  81.     End Select
  82.     _Limit 60
  83.  
  84. Call Graphics
  85. Call cprintstring(-16 * 4, "During Play:")
  86. Call cprintstring(-16 * 6, "Move mouse to select closest object (by centroid).")
  87. Call cprintstring(-16 * 7, "Boost velocity with arrow keys or W/S/A/D.        ")
  88. Call cprintstring(-16 * 8, "Boost angluar velocity with Q/E.                  ")
  89. Call cprintstring(-16 * 9, "Drag and fling object with Mouse 1.               ")
  90. Call cprintstring(-16 * 10, "Rotate selected object with Mousewheel.           ")
  91. Call cprintstring(-16 * 11, "Halt all motion with ESC.                         ")
  92. Call cprintstring(-16 * 12, "Create new ball with Mouse 2.                     ")
  93. Call cprintstring(-16 * 13, "Initiate creative mode with SPACE.                ")
  94. Call cprintstring(-16 * 14, "Restart by pressing R during motion.              ")
  95. Call cprintstring(-16 * 16, "PRESS ANY KEY TO BEGIN.")
  96.  
  97. ' Main loop
  98.     If (UserInput = -1) Then GoTo start
  99.     Call PairDynamics(CPC, FPC, RST)
  100.     Call FleetDynamics(VD, SV)
  101.     Call Graphics
  102.     _Limit 120
  103.  
  104.  
  105. Function UserInput
  106.     TheReturn = 0
  107.     ' Keyboard input
  108.     kk = _KeyHit
  109.     Select Case kk
  110.         Case 32
  111.             Do: Loop Until _KeyHit
  112.             While _MouseInput: Wend
  113.             _KeyClear
  114.             Call cprintstring(16 * 17, "Drag Mouse 1 counter-clockwise to draw a new shape.")
  115.             Call cprintstring(16 * 16, "Make sure centroid is inside body.                 ")
  116.             Call NewMouseShape(7.5, 150, 15)
  117.             Cls
  118.         Case 18432, Asc("w"), Asc("W") ' Up arrow
  119.             Shape(SelectedShape).Velocity.y = Shape(SelectedShape).Velocity.y * 1.05 + 1.5
  120.         Case 20480, Asc("s"), Asc("S") ' Down arrow
  121.             Shape(SelectedShape).Velocity.y = Shape(SelectedShape).Velocity.y * 0.95 - 1.5
  122.         Case 19200, Asc("a"), Asc("A") ' Left arrow
  123.             Shape(SelectedShape).Velocity.x = Shape(SelectedShape).Velocity.x * 0.95 - 1.5
  124.         Case 19712, Asc("d"), Asc("D") ' Right arrow
  125.             Shape(SelectedShape).Velocity.x = Shape(SelectedShape).Velocity.x * 1.05 + 1.5
  126.         Case Asc("e"), Asc("E")
  127.             Shape(SelectedShape).Omega = Omega * 0.5 - .02
  128.         Case Asc("q"), Asc("Q")
  129.             Shape(SelectedShape).Omega = Omega * 1.5 + .02
  130.         Case Asc("r"), Asc("R")
  131.             TheReturn = -1
  132.         Case 27
  133.             For k = 1 To ShapeCount
  134.                 Shape(k).Velocity.x = .000001 * (Rnd - .5)
  135.                 Shape(k).Velocity.y = .000001 * (Rnd - .5)
  136.                 Shape(k).Omega = .000001 * (Rnd - .5)
  137.             Next
  138.     End Select
  139.     If (kk) Then
  140.         _KeyClear
  141.     End If
  142.  
  143.     ' Mouse input
  144.     mb = 0
  145.     mxold = 999999999
  146.     myold = 999999999
  147.         x = _MouseX
  148.         y = _MouseY
  149.         If (x > 0) And (x < _Width) And (y > 0) And (y < _Height) Then
  150.             x = x - (_Width / 2)
  151.             y = -y + (_Height / 2)
  152.             rmin = 999999999
  153.             For k = 1 To ShapeCount
  154.                 dx = x - Shape(k).Centroid.x
  155.                 dy = y - Shape(k).Centroid.y
  156.                 r2 = dx * dx + dy * dy
  157.                 If (r2 < rmin) Then
  158.                     rmin = r2
  159.                     SelectedShape = k
  160.                 End If
  161.             Next
  162.             If (_MouseButton(1)) Then
  163.                 If (mb = 0) Then
  164.                     mb = 1
  165.                     vtemp.x = x - Shape(SelectedShape).Centroid.x
  166.                     vtemp.y = y - Shape(SelectedShape).Centroid.y
  167.                     Call TranslateShape(SelectedShape, vtemp)
  168.                     Shape(SelectedShape).Velocity.x = 0
  169.                     Shape(SelectedShape).Velocity.y = 0
  170.                     Shape(SelectedShape).Omega = 0
  171.                     mxold = x
  172.                     myold = y
  173.                 End If
  174.             End If
  175.             If (_MouseButton(2)) Then
  176.                 If (mb = 0) Then
  177.                     mb = 1
  178.                     Call NewAutoBall(x, y, 15, 0, 1, 1, 0)
  179.                     _Delay .1
  180.                 End If
  181.             End If
  182.             If (_MouseWheel > 0) Then
  183.                 Call RotShape(SelectedShape, Shape(SelectedShape).Centroid, -.02 * 8 * Atn(1))
  184.             End If
  185.             If (_MouseWheel < 0) Then
  186.                 Call RotShape(SelectedShape, Shape(SelectedShape).Centroid, .02 * 8 * Atn(1))
  187.             End If
  188.         End If
  189.     Loop
  190.     If ((mxold <> 999999999) And (myold <> 999999999)) Then
  191.         Shape(SelectedShape).Velocity.x = x - mxold
  192.         Shape(SelectedShape).Velocity.y = y - myold
  193.     End If
  194.     UserInput = TheReturn
  195.  
  196. Sub PairDynamics (CoarseProximityConstant As Double, FineProximityConstant As Double, Restitution As Double)
  197.  
  198.     Dim GrossJ(300) As Integer
  199.     Dim GrossK(300) As Integer
  200.     Dim NumJK As Integer
  201.  
  202.     ' Proximity detection
  203.     ProximalPairsCount = 0
  204.     Shape1 = 0
  205.     Shape2 = 0
  206.     For j = 1 To ShapeCount
  207.         Shape(j).CollisionSwitch = 0
  208.         Shape(j).DeltaO = 0
  209.         Shape(j).DeltaV.x = 0
  210.         Shape(j).DeltaV.y = 0
  211.         Shape(j).PartialNormal.x = 0
  212.         Shape(j).PartialNormal.y = 0
  213.         For k = j + 1 To ShapeCount
  214.             dx = Shape(j).Centroid.x - Shape(k).Centroid.x
  215.             dy = Shape(j).Centroid.y - Shape(k).Centroid.y
  216.             dr = Sqr(dx * dx + dy * dy)
  217.             If (dr < (CoarseProximityConstant) * (Shape(j).Diameter + Shape(k).Diameter)) Then
  218.                 ProximalPairsCount = ProximalPairsCount + 1
  219.                 ProximalPairs(ProximalPairsCount, 1) = j
  220.                 ProximalPairs(ProximalPairsCount, 2) = k
  221.                 'Shape1 = j
  222.                 'Shape2 = k
  223.             End If
  224.         Next
  225.     Next
  226.  
  227.     ContactPoints = 0
  228.  
  229.     If (ProximalPairsCount > 0) Then
  230.         For n = 1 To ProximalPairsCount
  231.             Shape1 = ProximalPairs(n, 1)
  232.             Shape2 = ProximalPairs(n, 2)
  233.  
  234.             ' Collision detection
  235.             rmin = 999999999
  236.             ClosestIndex1 = 0
  237.             ClosestIndex2 = 0
  238.             NumJK = 0
  239.             For j = 1 To Shape(Shape1).Elements
  240.                 For k = 1 To Shape(Shape2).Elements
  241.                     dx = PointChain(Shape1, j).x - PointChain(Shape2, k).x
  242.                     dy = PointChain(Shape1, j).y - PointChain(Shape2, k).y
  243.                     r2 = dx * dx + dy * dy
  244.  
  245.                     If (r2 <= FineProximityConstant) Then
  246.  
  247.                         ContactPoints = ContactPoints + 1
  248.  
  249.                         ' Partial normal vector 1
  250.                         nx1 = CalculateNormalY(Shape1, j)
  251.                         ny1 = -CalculateNormalX(Shape1, j)
  252.                         nn = Sqr(nx1 * nx1 + ny1 * ny1)
  253.                         nx1 = nx1 / nn
  254.                         ny1 = ny1 / nn
  255.                         Shape(Shape1).PartialNormal.x = Shape(Shape1).PartialNormal.x + nx1
  256.                         Shape(Shape1).PartialNormal.y = Shape(Shape1).PartialNormal.y + ny1
  257.  
  258.                         ' Partial normal vector 2
  259.                         nx2 = CalculateNormalY(Shape2, k)
  260.                         ny2 = -CalculateNormalX(Shape2, k)
  261.                         nn = Sqr(nx2 * nx2 + ny2 * ny2)
  262.                         nx2 = nx2 / nn
  263.                         ny2 = ny2 / nn
  264.                         Shape(Shape2).PartialNormal.x = Shape(Shape2).PartialNormal.x + nx2
  265.                         Shape(Shape2).PartialNormal.y = Shape(Shape2).PartialNormal.y + ny2
  266.  
  267.                         NumJK = NumJK + 1
  268.                         GrossJ(NumJK) = j
  269.                         GrossK(NumJK) = k
  270.  
  271.                     End If
  272.                     If (r2 < rmin) Then
  273.                         rmin = r2
  274.                         ClosestIndex1 = j
  275.                         ClosestIndex2 = k
  276.                     End If
  277.                 Next
  278.             Next
  279.  
  280.             If (NumJK > 1) Then
  281.                 If ((GrossJ(1) - GrossJ(NumJK)) * (GrossJ(1) - GrossJ(NumJK)) > 50) Then
  282.                     'ClosestIndex1 = 1
  283.                 Else
  284.                     ClosestIndex1 = Int(IntegrateArray(GrossJ(), NumJK) / NumJK)
  285.                 End If
  286.                 If ((GrossK(1) - GrossK(NumJK)) * (GrossK(1) - GrossK(NumJK)) > 50) Then
  287.                     'ClosestIndex2 = 1
  288.                 Else
  289.                     ClosestIndex2 = Int(IntegrateArray(GrossK(), NumJK) / NumJK)
  290.                 End If
  291.             End If
  292.  
  293.             If (rmin <= FineProximityConstant) Then
  294.  
  295.                 CollisionCount = CollisionCount + 1
  296.                 Shape(Shape1).CollisionSwitch = 1
  297.                 Shape(Shape2).CollisionSwitch = 1
  298.  
  299.                 ' Undo previous motion
  300.                 If (Shape(Shape1).Collisions = 0) Then
  301.                     Call RotShape(Shape1, Shape(Shape1).Centroid, -1 * Shape(Shape1).Omega)
  302.                     vtemp.x = -1 * (Shape(Shape1).Velocity.x)
  303.                     vtemp.y = -1 * (Shape(Shape1).Velocity.y)
  304.                     Call TranslateShape(Shape1, vtemp)
  305.                 End If
  306.                 If (Shape(Shape2).Collisions = 0) Then
  307.                     Call RotShape(Shape2, Shape(Shape2).Centroid, -1 * Shape(Shape2).Omega)
  308.                     vtemp.x = -1 * (Shape(Shape2).Velocity.x)
  309.                     vtemp.y = -1 * (Shape(Shape2).Velocity.y)
  310.                     Call TranslateShape(Shape2, vtemp)
  311.                 End If
  312.  
  313.                 ' Momentum absorption
  314.                 If (Shape(Shape1).Collisions = 0) Then
  315.                     Shape(Shape1).Velocity.x = Shape(Shape1).Velocity.x * Restitution
  316.                     Shape(Shape1).Velocity.y = Shape(Shape1).Velocity.y * Restitution
  317.                 End If
  318.                 If (Shape(Shape2).Collisions = 0) Then
  319.                     Shape(Shape2).Velocity.x = Shape(Shape2).Velocity.x * Restitution
  320.                     Shape(Shape2).Velocity.y = Shape(Shape2).Velocity.y * Restitution
  321.                 End If
  322.  
  323.                 ' Centroid of object 1 (cx1, cy1)
  324.                 cx1 = Shape(Shape1).Centroid.x
  325.                 cy1 = Shape(Shape1).Centroid.y
  326.  
  327.                 ' Centroid of object 2 (cx2, cy2)
  328.                 cx2 = Shape(Shape2).Centroid.x
  329.                 cy2 = Shape(Shape2).Centroid.y
  330.  
  331.                 ' Contact point on object 1 (px1, py1)
  332.                 px1 = PointChain(Shape1, ClosestIndex1).x
  333.                 py1 = PointChain(Shape1, ClosestIndex1).y
  334.  
  335.                 ' Contact point on object 2 (px2, py2)
  336.                 px2 = PointChain(Shape2, ClosestIndex2).x
  337.                 py2 = PointChain(Shape2, ClosestIndex2).y
  338.  
  339.                 ' Contact-centroid differentials 1 (dx1, dy1)
  340.                 dx1 = px1 - cx1
  341.                 dy1 = py1 - cy1
  342.  
  343.                 ' Contact-centroid differentials 2 (dx2, dy2)
  344.                 dx2 = px2 - cx2
  345.                 dy2 = py2 - cy2
  346.  
  347.                 ' Normal vector 1 (nx1, ny1)
  348.                 nn = Sqr(Shape(Shape1).PartialNormal.x * Shape(Shape1).PartialNormal.x + Shape(Shape1).PartialNormal.y * Shape(Shape1).PartialNormal.y)
  349.                 nx1 = Shape(Shape1).PartialNormal.x / nn
  350.                 ny1 = Shape(Shape1).PartialNormal.y / nn
  351.  
  352.                 ' Normal vector 2 (nx2, ny2)
  353.                 nn = Sqr(Shape(Shape2).PartialNormal.x * Shape(Shape2).PartialNormal.x + Shape(Shape2).PartialNormal.y * Shape(Shape2).PartialNormal.y)
  354.                 nx2 = Shape(Shape2).PartialNormal.x / nn
  355.                 ny2 = Shape(Shape2).PartialNormal.y / nn
  356.  
  357.                 '''
  358.                 'nx1 = CalculateNormalY(Shape1, ClosestIndex1)
  359.                 'ny1 = -CalculateNormalX(Shape1, ClosestIndex1)
  360.                 'nn = SQR(nx1 * nx1 + ny1 * ny1)
  361.                 'nx1 = nx1 / nn
  362.                 'ny1 = ny1 / nn
  363.  
  364.                 'nx2 = CalculateNormalY(Shape2, ClosestIndex2)
  365.                 'ny2 = -CalculateNormalX(Shape2, ClosestIndex2)
  366.                 'nn = SQR(nx2 * nx2 + ny2 * ny2)
  367.                 'nx2 = nx2 / nn
  368.                 'ny2 = ny2 / nn
  369.                 '''
  370.  
  371.                 ' Perpendicular vector 1 (prx1, pry1)
  372.                 prx1 = -1 * dy1
  373.                 pry1 = dx1
  374.                 pp = Sqr(prx1 * prx1 + pry1 * pry1)
  375.                 prx1 = prx1 / pp
  376.                 pry1 = pry1 / pp
  377.  
  378.                 ' Perpendicular vector 2 (prx2, pry2)
  379.                 prx2 = -1 * dy2
  380.                 pry2 = dx2
  381.                 pp = Sqr(prx2 * prx2 + pry2 * pry2)
  382.                 prx2 = prx2 / pp
  383.                 pry2 = pry2 / pp
  384.  
  385.                 ' Angular velocity vector 1 (w1, r1, vx1, vy1)
  386.                 w1 = Shape(Shape1).Omega
  387.                 r1 = Sqr(dx1 * dx1 + dy1 * dy1)
  388.                 vx1 = w1 * r1 * prx1
  389.                 vy1 = w1 * r1 * pry1
  390.  
  391.                 ' Angular velocity vector 2 (w2, r2, vx2, vy2)
  392.                 w2 = Shape(Shape2).Omega
  393.                 r2 = Sqr(dx2 * dx2 + dy2 * dy2)
  394.                 vx2 = w2 * r2 * prx2
  395.                 vy2 = w2 * r2 * pry2
  396.  
  397.                 ' Mass terms (m1, m2, mu)
  398.                 m1 = Shape(Shape1).Mass
  399.                 m2 = Shape(Shape2).Mass
  400.                 mu = 1 / (1 / m1 + 1 / m2)
  401.  
  402.                 ' Re-Calculate moment of inertia (i1, i2)
  403.                 vtemp.x = px1
  404.                 vtemp.y = py1
  405.                 Call CalculateMOI(Shape1, vtemp)
  406.                 vtemp.x = px2
  407.                 vtemp.y = py2
  408.                 Call CalculateMOI(Shape2, vtemp)
  409.                 i1 = Shape(Shape1).MOI
  410.                 i2 = Shape(Shape2).MOI
  411.  
  412.                 ' Velocity differentials (v1, v2, dvtx, dvty)
  413.                 vcx1 = Shape(Shape1).Velocity.x
  414.                 vcy1 = Shape(Shape1).Velocity.y
  415.                 vcx2 = Shape(Shape2).Velocity.x
  416.                 vcy2 = Shape(Shape2).Velocity.y
  417.                 vtx1 = vcx1 + vx1
  418.                 vty1 = vcy1 + vy1
  419.                 vtx2 = vcx2 + vx2
  420.                 vty2 = vcy2 + vy2
  421.                 v1 = Sqr(vtx1 * vtx1 + vty1 * vty1)
  422.                 v2 = Sqr(vtx2 * vtx2 + vty2 * vty2)
  423.                 dvtx = vtx2 - vtx1
  424.                 dvty = vty2 - vty1
  425.  
  426.                 ' Geometry (n1dotdvt, n2dotdvt)
  427.                 n1dotdvt = nx1 * dvtx + ny1 * dvty
  428.                 n2dotdvt = nx2 * dvtx + ny2 * dvty
  429.  
  430.                 ' Momentum exchange (qx1, qy1, qx2, qy2)
  431.                 qx1 = nx1 * 2 * mu * n1dotdvt
  432.                 qy1 = ny1 * 2 * mu * n1dotdvt
  433.                 qx2 = nx2 * 2 * mu * n2dotdvt
  434.                 qy2 = ny2 * 2 * mu * n2dotdvt
  435.  
  436.                 ' Momentum exchange unit vector (qhat)
  437.                 qq = Sqr(qx1 * qx1 + qy1 * qy1)
  438.                 If (qx1 * qx1 > 0.01) Then
  439.                     qhatx1 = qx1 / qq
  440.                 Else
  441.                     qx1 = 0
  442.                     qhatx1 = 0
  443.                 End If
  444.                 If (qy1 * qy1 > 0.01) Then
  445.                     qhaty1 = qy1 / qq
  446.                 Else
  447.                     qy1 = 0
  448.                     qhaty1 = 0
  449.                 End If
  450.                 qq = Sqr(qx2 * qx2 + qy2 * qy2)
  451.                 If (qx2 * qx2 > 0.01) Then
  452.                     qhatx2 = qx2 / qq
  453.                 Else
  454.                     qx2 = 0
  455.                     qhatx2 = 0
  456.                 End If
  457.                 If (qy2 * qy2 > 0.01) Then
  458.                     qhaty2 = qy2 / qq
  459.                 Else
  460.                     qy2 = 0
  461.                     qhaty2 = 0
  462.                 End If
  463.  
  464.                 ' Angular impulse (qdotp)
  465.                 q1dotp1 = qx1 * prx1 + qy1 * pry1
  466.                 q2dotp2 = qx2 * prx2 + qy2 * pry2
  467.  
  468.                 ' Translational impulse (qdotn, ndotrhat, f)
  469.                 q1dotn1 = qhatx1 * nx1 + qhaty1 * ny1
  470.                 q2dotn2 = qhatx2 * nx2 + qhaty2 * ny2
  471.                 n1dotr1hat = (nx1 * dx1 + ny1 * dy1) / r1
  472.                 n2dotr2hat = (nx2 * dx2 + ny2 * dy2) / r2
  473.                 f1 = -q1dotn1 * n1dotr1hat
  474.                 f2 = -q2dotn2 * n2dotr2hat
  475.  
  476.                 ' Special case for shape within shape.
  477.                 np = nx1 * nx2 + ny1 * ny2
  478.                 If (np > 0) Then
  479.                     dcx = cx1 - cx2
  480.                     dcy = cy1 - cy2
  481.                     dc = Sqr(dcx * dcx + dcy * dcy)
  482.                     If (dc < (r1 + r2)) Then
  483.                         If (m1 > m2) Then ' This criteria may be bullshit in general but works now.
  484.                             q1dotp1 = -q1dotp1
  485.                             f1 = -f1
  486.                         Else
  487.                             q2dotp2 = -q2dotp2
  488.                             f2 = -f2
  489.                         End If
  490.                     End If
  491.                 End If
  492.  
  493.                 ' Angular impulse update (edits omega)
  494.                 Shape(Shape1).DeltaO = Shape(Shape1).DeltaO + r1 * q1dotp1 / i1
  495.                 Shape(Shape2).DeltaO = Shape(Shape2).DeltaO - r2 * q2dotp2 / i2
  496.  
  497.                 ' Linear impulse update (edits velocity)
  498.                 dvx1 = f1 * qx1 / m1
  499.                 dvy1 = f1 * qy1 / m1
  500.                 dvx2 = f2 * qx2 / m2
  501.                 dvy2 = f2 * qy2 / m2
  502.                 dvx1s = dvx1 * dvx1
  503.                 dvy1s = dvy1 * dvy1
  504.                 dvx2s = dvx2 * dvx2
  505.                 dvy2s = dvy2 * dvy2
  506.                 If ((dvx1s > .001) And (dvx1s < 50)) Then
  507.                     Shape(Shape1).DeltaV.x = Shape(Shape1).DeltaV.x + dvx1
  508.                 End If
  509.                 If ((dvy1s > .001) And (dvy1s < 50)) Then
  510.                     Shape(Shape1).DeltaV.y = Shape(Shape1).DeltaV.y + dvy1
  511.                 End If
  512.                 If ((dvx2s > .001) And (dvx2s < 50)) Then
  513.                     Shape(Shape2).DeltaV.x = Shape(Shape2).DeltaV.x + dvx2
  514.                 End If
  515.                 If ((dvy2s > .001) And (dvy2s < 50)) Then
  516.                     Shape(Shape2).DeltaV.y = Shape(Shape2).DeltaV.y + dvy2
  517.                 End If
  518.  
  519.                 ' External torque (edits omega)
  520.                 torque1 = m1 * (dx1 * ForceField.y - dy1 * ForceField.x)
  521.                 torque2 = m2 * (dx2 * ForceField.y - dy2 * ForceField.x)
  522.                 Shape(Shape1).DeltaO = Shape(Shape1).DeltaO - torque1 / i1
  523.                 Shape(Shape2).DeltaO = Shape(Shape2).DeltaO - torque2 / i2
  524.  
  525.                 ' Separate along normal (edits position)
  526.                 If (Shape(Shape1).Collisions < 2) Then ' changed from = 0
  527.                     vtemp.x = -nx1 * (.5 / m1) * (1 * v1 ^ 2 + 1 * w1 ^ 2)
  528.                     vtemp.y = -ny1 * (.5 / m1) * (1 * v1 ^ 2 + 1 * w1 ^ 2)
  529.                     Call TranslateShape(Shape1, vtemp)
  530.                 End If
  531.                 If (Shape(Shape2).Collisions < 2) Then
  532.                     vtemp.x = -nx2 * (.5 / m2) * (1 * v2 ^ 2 + 1 * w2 ^ 2)
  533.                     vtemp.y = -ny2 * (.5 / m2) * (1 * v2 ^ 2 + 1 * w2 ^ 2)
  534.                     Call TranslateShape(Shape2, vtemp)
  535.                 End If
  536.  
  537.                 ' Dent along normal
  538.                 'PointChain(Shape1, ClosestIndex1).x = PointChain(Shape1, ClosestIndex1).x - v1 * nx1 / 2
  539.                 'PointChain(Shape1, ClosestIndex1).y = PointChain(Shape1, ClosestIndex1).y - v1 * ny1 / 2
  540.                 'PointChain(Shape2, ClosestIndex2).x = PointChain(Shape2, ClosestIndex2).x - v2 * nx2 / 2
  541.                 'PointChain(Shape2, ClosestIndex2).y = PointChain(Shape2, ClosestIndex2).y - v2 * ny2 / 2
  542.  
  543.                 ' Feedback
  544.                 If ((Shape(Shape1).Collisions = 0) And (Shape(Shape2).Collisions = 0)) Then
  545.                     Call snd(100 * (v1 + v2) / 2, .5)
  546.                 End If
  547.  
  548.             End If
  549.         Next
  550.     End If
  551.  
  552. Sub FleetDynamics (MotionDamping As Double, LowLimitVelocity As Double)
  553.  
  554.     For ShapeIndex = 1 To ShapeCount
  555.  
  556.         ' Contact update
  557.         If (Shape(ShapeIndex).CollisionSwitch = 1) Then
  558.             Shape(ShapeIndex).Collisions = Shape(ShapeIndex).Collisions + 1
  559.         Else
  560.             Shape(ShapeIndex).Collisions = 0
  561.         End If
  562.  
  563.         If (Shape(ShapeIndex).Fixed = 0) Then
  564.  
  565.             ' Angular velocity update
  566.             Shape(ShapeIndex).Omega = Shape(ShapeIndex).Omega + Shape(ShapeIndex).DeltaO
  567.  
  568.             ' Linear velocity update
  569.             Shape(ShapeIndex).Velocity.x = Shape(ShapeIndex).Velocity.x + Shape(ShapeIndex).DeltaV.x
  570.             Shape(ShapeIndex).Velocity.y = Shape(ShapeIndex).Velocity.y + Shape(ShapeIndex).DeltaV.y
  571.  
  572.             If (Shape(ShapeIndex).Collisions = 0) Then
  573.                 ' Freefall (if airborne)
  574.                 Shape(ShapeIndex).Velocity.x = Shape(ShapeIndex).Velocity.x + ForceField.x
  575.                 Shape(ShapeIndex).Velocity.y = Shape(ShapeIndex).Velocity.y + ForceField.y
  576.             End If
  577.  
  578.             If (Shape(ShapeIndex).Collisions > 2) Then
  579.                 ' Static friction
  580.                 If ((Shape(ShapeIndex).Velocity.x * Shape(ShapeIndex).Velocity.x) < LowLimitVelocity) Then
  581.                     Shape(ShapeIndex).Velocity.x = Shape(ShapeIndex).Velocity.x * .05
  582.                 End If
  583.                 If ((Shape(ShapeIndex).Velocity.y * Shape(ShapeIndex).Velocity.y) < LowLimitVelocity) Then
  584.                     Shape(ShapeIndex).Velocity.y = Shape(ShapeIndex).Velocity.y * .05
  585.                 End If
  586.                 If ((Shape(ShapeIndex).Omega * Shape(ShapeIndex).Omega) < .000015 * LowLimitVelocity) Then
  587.                     Shape(ShapeIndex).Omega = 0
  588.                 End If
  589.             End If
  590.  
  591.             ' Rotation update
  592.             Call RotShape(ShapeIndex, Shape(ShapeIndex).Centroid, Shape(ShapeIndex).Omega)
  593.  
  594.             ' Position update
  595.             Call TranslateShape(ShapeIndex, Shape(ShapeIndex).Velocity)
  596.  
  597.             ' Motion Damping
  598.             Shape(ShapeIndex).Velocity.x = Shape(ShapeIndex).Velocity.x * MotionDamping
  599.             Shape(ShapeIndex).Velocity.y = Shape(ShapeIndex).Velocity.y * MotionDamping
  600.             Shape(ShapeIndex).Omega = Shape(ShapeIndex).Omega * MotionDamping
  601.  
  602.         Else
  603.  
  604.             ' Lock all motion
  605.             Shape(ShapeIndex).Velocity.x = 0
  606.             Shape(ShapeIndex).Velocity.y = 0
  607.             Shape(ShapeIndex).Omega = 0
  608.  
  609.         End If
  610.     Next
  611.  
  612.  
  613. Sub Graphics
  614.     Line (0, 0)-(_Width, _Height), _RGBA(0, 0, 0, 200), BF
  615.     Locate 1, 1: Print ProximalPairsCount, CollisionCount, ContactPoints
  616.     For ShapeIndex = 1 To ShapeCount
  617.         For i = 1 To Shape(ShapeIndex).Elements - 1
  618.             Call cpset(PointChain(ShapeIndex, i).x, PointChain(ShapeIndex, i).y, Shape(ShapeIndex).Shade)
  619.             Call cline(PointChain(ShapeIndex, i).x, PointChain(ShapeIndex, i).y, PointChain(ShapeIndex, i + 1).x, PointChain(ShapeIndex, i + 1).y, Shape(ShapeIndex).Shade)
  620.             If (ShapeIndex = SelectedShape) Then
  621.                 Call ccircle(PointChain(ShapeIndex, i).x, PointChain(ShapeIndex, i).y, 1, Shape(ShapeIndex).Shade)
  622.             End If
  623.         Next
  624.         Call cpset(PointChain(ShapeIndex, Shape(ShapeIndex).Elements).x, PointChain(ShapeIndex, Shape(ShapeIndex).Elements).y, Shape(ShapeIndex).Shade)
  625.         Call cline(PointChain(ShapeIndex, 1).x, PointChain(ShapeIndex, 1).y, PointChain(ShapeIndex, Shape(ShapeIndex).Elements).x, PointChain(ShapeIndex, Shape(ShapeIndex).Elements).y, Shape(ShapeIndex).Shade)
  626.         Call cline(Shape(ShapeIndex).Centroid.x, Shape(ShapeIndex).Centroid.y, PointChain(ShapeIndex, 1).x, PointChain(ShapeIndex, 1).y, Shape(ShapeIndex).Shade)
  627.         If (ShapeIndex = SelectedShape) Then
  628.             Call ccircle(Shape(ShapeIndex).Centroid.x, Shape(ShapeIndex).Centroid.y, 3, Shape(ShapeIndex).Shade)
  629.             Call cpaint(Shape(ShapeIndex).Centroid.x, Shape(ShapeIndex).Centroid.y, Shape(ShapeIndex).Shade, Shape(ShapeIndex).Shade)
  630.         End If
  631.     Next
  632.     _Display
  633.  
  634. Function IntegrateArray (arr() As Integer, lim As Integer)
  635.     t = 0
  636.     For j = 1 To lim
  637.         t = t + arr(j)
  638.     Next
  639.     IntegrateArray = t
  640.  
  641. Function CalculateNormalX (k As Integer, i As Integer)
  642.     Dim l As Vector
  643.     Dim r As Vector
  644.     li = i - 1
  645.     ri = i + 1
  646.     If (i = 1) Then li = Shape(k).Elements
  647.     If (i = Shape(k).Elements) Then ri = 1
  648.     l.x = PointChain(k, li).x
  649.     r.x = PointChain(k, ri).x
  650.     dx = r.x - l.x
  651.     CalculateNormalX = dx
  652.  
  653. Function CalculateNormalY (k As Integer, i As Integer)
  654.     Dim l As Vector
  655.     Dim r As Vector
  656.     li = i - 1
  657.     ri = i + 1
  658.     If (i = 1) Then li = Shape(k).Elements
  659.     If (i = Shape(k).Elements) Then ri = 1
  660.     l.y = PointChain(k, li).y
  661.     r.y = PointChain(k, ri).y
  662.     dy = r.y - l.y
  663.     CalculateNormalY = dy
  664.  
  665. Sub CalculateCentroid (k As Integer)
  666.     xx = 0
  667.     yy = 0
  668.     For i = 1 To Shape(k).Elements
  669.         xx = xx + PointChain(k, i).x
  670.         yy = yy + PointChain(k, i).y
  671.     Next
  672.     Shape(k).Centroid.x = xx / Shape(k).Elements
  673.     Shape(k).Centroid.y = yy / Shape(k).Elements
  674.  
  675. Sub CalculateDiameter (k As Integer)
  676.     r2max = -1
  677.     For i = 1 To Shape(k).Elements
  678.         xx = Shape(k).Centroid.x - PointChain(k, i).x
  679.         yy = Shape(k).Centroid.y - PointChain(k, i).y
  680.         r2 = xx * xx + yy * yy
  681.         If (r2 > r2max) Then
  682.             r2max = r2
  683.         End If
  684.     Next
  685.     Shape(k).Diameter = Sqr(r2max)
  686.  
  687. Sub CalculateMass (k As Integer, factor As Double)
  688.     aa = 0
  689.     For i = 2 To Shape(k).Elements
  690.         x = PointChain(k, i).x - Shape(k).Centroid.x
  691.         y = PointChain(k, i).y - Shape(k).Centroid.y
  692.         dx = (PointChain(k, i).x - PointChain(k, i - 1).x)
  693.         dy = (PointChain(k, i).y - PointChain(k, i - 1).y)
  694.         da = .5 * (x * dy - y * dx)
  695.         aa = aa + da
  696.     Next
  697.     Shape(k).Mass = factor * Sqr(aa * aa)
  698.  
  699. Sub CalculateMOI (k As Integer, ctrvec As Vector)
  700.     xx = 0
  701.     yy = 0
  702.     For i = 1 To Shape(k).Elements
  703.         a = ctrvec.x - PointChain(k, i).x
  704.         b = ctrvec.y - PointChain(k, i).y
  705.         xx = xx + a * a
  706.         yy = yy + b * b
  707.     Next
  708.     Shape(k).MOI = Sqr((xx + yy) * (xx + yy)) * (Shape(k).Mass / Shape(k).Elements)
  709.  
  710. Sub TranslateShape (k As Integer, c As Vector)
  711.     For i = 1 To Shape(k).Elements
  712.         PointChain(k, i).x = PointChain(k, i).x + c.x
  713.         PointChain(k, i).y = PointChain(k, i).y + c.y
  714.     Next
  715.     Shape(k).Centroid.x = Shape(k).Centroid.x + c.x
  716.     Shape(k).Centroid.y = Shape(k).Centroid.y + c.y
  717.  
  718. Sub RotShape (k As Integer, c As Vector, da As Double)
  719.     For i = 1 To Shape(k).Elements
  720.         xx = PointChain(k, i).x - c.x
  721.         yy = PointChain(k, i).y - c.y
  722.         PointChain(k, i).x = c.x + xx * Cos(da) - yy * Sin(da)
  723.         PointChain(k, i).y = c.y + yy * Cos(da) + xx * Sin(da)
  724.     Next
  725.  
  726. Sub NewAutoBall (x1 As Double, y1 As Double, r1 As Double, r2 As Double, pa As Double, pb As Double, fx As Integer)
  727.     ShapeCount = ShapeCount + 1
  728.     Shape(ShapeCount).Fixed = fx
  729.     Shape(ShapeCount).Collisions = 0
  730.     i = 0
  731.     For j = 0 To (8 * Atn(1)) Step .02 * 8 * Atn(1)
  732.         i = i + 1
  733.         r = r1 + r2 * Cos(pa * j) ^ pb
  734.         PointChain(ShapeCount, i).x = x1 + r * Cos(j)
  735.         PointChain(ShapeCount, i).y = y1 + r * Sin(j)
  736.     Next
  737.     Shape(ShapeCount).Elements = i
  738.     Call CalculateCentroid(ShapeCount)
  739.     If (fx = 0) Then
  740.         Call CalculateMass(ShapeCount, 1)
  741.     Else
  742.         Call CalculateMass(ShapeCount, 999999)
  743.     End If
  744.     Call CalculateMOI(ShapeCount, Shape(ShapeCount).Centroid)
  745.     Call CalculateDiameter(ShapeCount)
  746.     Shape(ShapeCount).Velocity.x = 0
  747.     Shape(ShapeCount).Velocity.y = 0
  748.     Shape(ShapeCount).Omega = 0
  749.     If (fx = 0) Then
  750.         Shape(ShapeCount).Shade = _RGB(100 + Int(Rnd * 155), 100 + Int(Rnd * 155), 100 + Int(Rnd * 155))
  751.     Else
  752.         Shape(ShapeCount).Shade = _RGB(100, 100, 100)
  753.     End If
  754.     SelectedShape = ShapeCount
  755.  
  756. Sub NewAutoBrick (x1 As Double, y1 As Double, wx As Double, wy As Double, ang As Double)
  757.     ShapeCount = ShapeCount + 1
  758.     Shape(ShapeCount).Fixed = 1
  759.     Shape(ShapeCount).Collisions = 0
  760.     i = 0
  761.     For j = -wy / 2 To wy / 2 Step 5
  762.         i = i + 1
  763.         PointChain(ShapeCount, i).x = x1 + wx / 2
  764.         PointChain(ShapeCount, i).y = y1 + j
  765.     Next
  766.     For j = wx / 2 To -wx / 2 Step -5
  767.         i = i + 1
  768.         PointChain(ShapeCount, i).x = x1 + j
  769.         PointChain(ShapeCount, i).y = y1 + wy / 2
  770.     Next
  771.     For j = wy / 2 To -wy / 2 Step -5
  772.         i = i + 1
  773.         PointChain(ShapeCount, i).x = x1 - wx / 2
  774.         PointChain(ShapeCount, i).y = y1 + j
  775.     Next
  776.     For j = -wx / 2 To wx / 2 Step 5
  777.         i = i + 1
  778.         PointChain(ShapeCount, i).x = x1 + j
  779.         PointChain(ShapeCount, i).y = y1 - wy / 2
  780.     Next
  781.     Shape(ShapeCount).Elements = i
  782.     Call CalculateCentroid(ShapeCount)
  783.     Call CalculateMass(ShapeCount, 99999)
  784.     Call CalculateMOI(ShapeCount, Shape(ShapeCount).Centroid)
  785.     Call CalculateDiameter(ShapeCount)
  786.     Shape(ShapeCount).Velocity.x = 0
  787.     Shape(ShapeCount).Velocity.y = 0
  788.     Shape(ShapeCount).Omega = 0
  789.     Shape(ShapeCount).Shade = _RGB(100, 100, 100)
  790.     SelectedShape = ShapeCount
  791.     Call RotShape(ShapeCount, Shape(ShapeCount).Centroid, ang)
  792.  
  793. Sub NewBrickLine (xi As Double, yi As Double, xf As Double, yf As Double, wx As Double, wy As Double)
  794.     d1 = Sqr((xf - xi) ^ 2 + (yf - yi) ^ 2)
  795.     d2 = Sqr(wx ^ 2 + wy ^ 2)
  796.     ang = Atn((yf - yi) / (xf - xi))
  797.     f = 1.2 * d2 / d1
  798.     For t = 0 To 1 + f Step f
  799.         Call NewAutoBrick(xi * (1 - t) + xf * t, yi * (1 - t) + yf * t, wx, wy, ang)
  800.     Next
  801.  
  802. Sub NewMouseShape (rawresolution As Double, targetpoints As Integer, smoothiterations As Integer)
  803.     ShapeCount = ShapeCount + 1
  804.     Shape(ShapeCount).Fixed = 0
  805.     Shape(ShapeCount).Collisions = 0
  806.     numpoints = 0
  807.     xold = 999 ^ 999
  808.     yold = 999 ^ 999
  809.     Do
  810.         Do While _MouseInput
  811.             x = _MouseX
  812.             y = _MouseY
  813.             If (x > 0) And (x < _Width) And (y > 0) And (y < _Height) Then
  814.                 If _MouseButton(1) Then
  815.                     x = x - (_Width / 2)
  816.                     y = -y + (_Height / 2)
  817.                     delta = Sqr((x - xold) ^ 2 + (y - yold) ^ 2)
  818.                     If (delta > rawresolution) And (numpoints < targetpoints - 1) Then
  819.                         numpoints = numpoints + 1
  820.                         PointChain(ShapeCount, numpoints).x = x
  821.                         PointChain(ShapeCount, numpoints).y = y
  822.                         Call cpset(x, y, _RGB(0, 255, 255))
  823.                         xold = x
  824.                         yold = y
  825.                     End If
  826.                 End If
  827.             End If
  828.         Loop
  829.         _Display
  830.     Loop Until Not _MouseButton(1) And (numpoints > 1)
  831.  
  832.     Do While (numpoints < targetpoints)
  833.         rad2max = -1
  834.         kmax = -1
  835.         For k = 1 To numpoints - 1
  836.             xfac = PointChain(ShapeCount, k).x - PointChain(ShapeCount, k + 1).x
  837.             yfac = PointChain(ShapeCount, k).y - PointChain(ShapeCount, k + 1).y
  838.             rad2 = xfac ^ 2 + yfac ^ 2
  839.             If rad2 > rad2max Then
  840.                 kmax = k
  841.                 rad2max = rad2
  842.             End If
  843.         Next
  844.         edgecase = 0
  845.         xfac = PointChain(ShapeCount, numpoints).x - PointChain(ShapeCount, 1).x
  846.         yfac = PointChain(ShapeCount, numpoints).y - PointChain(ShapeCount, 1).y
  847.         rad2 = xfac ^ 2 + yfac ^ 2
  848.         If (rad2 > rad2max) Then
  849.             kmax = numpoints
  850.             rad2max = rad2
  851.             edgecase = 1
  852.         End If
  853.         numpoints = numpoints + 1
  854.         If (edgecase = 0) Then
  855.             For j = numpoints To kmax + 1 Step -1
  856.                 PointChain(ShapeCount, j + 1).x = PointChain(ShapeCount, j).x
  857.                 PointChain(ShapeCount, j + 1).y = PointChain(ShapeCount, j).y
  858.             Next
  859.             PointChain(ShapeCount, kmax + 1).x = (1 / 2) * (PointChain(ShapeCount, kmax).x + PointChain(ShapeCount, kmax + 2).x)
  860.             PointChain(ShapeCount, kmax + 1).y = (1 / 2) * (PointChain(ShapeCount, kmax).y + PointChain(ShapeCount, kmax + 2).y)
  861.         Else
  862.             PointChain(ShapeCount, numpoints).x = (1 / 2) * (PointChain(ShapeCount, 1).x + PointChain(ShapeCount, numpoints - 1).x)
  863.             PointChain(ShapeCount, numpoints).y = (1 / 2) * (PointChain(ShapeCount, 1).y + PointChain(ShapeCount, numpoints - 1).y)
  864.         End If
  865.     Loop
  866.  
  867.     For j = 1 To smoothiterations
  868.         For k = 2 To numpoints - 1
  869.             TempChain(ShapeCount, k).x = (1 / 2) * (PointChain(ShapeCount, k - 1).x + PointChain(ShapeCount, k + 1).x)
  870.             TempChain(ShapeCount, k).y = (1 / 2) * (PointChain(ShapeCount, k - 1).y + PointChain(ShapeCount, k + 1).y)
  871.         Next
  872.         For k = 2 To numpoints - 1
  873.             PointChain(ShapeCount, k).x = TempChain(ShapeCount, k).x
  874.             PointChain(ShapeCount, k).y = TempChain(ShapeCount, k).y
  875.         Next
  876.         TempChain(ShapeCount, 1).x = (1 / 2) * (PointChain(ShapeCount, numpoints).x + PointChain(ShapeCount, 2).x)
  877.         TempChain(ShapeCount, 1).y = (1 / 2) * (PointChain(ShapeCount, numpoints).y + PointChain(ShapeCount, 2).y)
  878.         PointChain(ShapeCount, 1).x = TempChain(ShapeCount, 1).x
  879.         PointChain(ShapeCount, 1).y = TempChain(ShapeCount, 1).y
  880.         TempChain(ShapeCount, numpoints).x = (1 / 2) * (PointChain(ShapeCount, 1).x + PointChain(ShapeCount, numpoints - 1).x)
  881.         TempChain(ShapeCount, numpoints).y = (1 / 2) * (PointChain(ShapeCount, 1).y + PointChain(ShapeCount, numpoints - 1).y)
  882.         PointChain(ShapeCount, numpoints).x = TempChain(ShapeCount, numpoints).x
  883.         PointChain(ShapeCount, numpoints).y = TempChain(ShapeCount, numpoints).y
  884.     Next
  885.  
  886.     Shape(ShapeCount).Elements = numpoints
  887.     Call CalculateCentroid(ShapeCount)
  888.     Call CalculateMass(ShapeCount, 1)
  889.     Call CalculateMOI(ShapeCount, Shape(ShapeCount).Centroid)
  890.     Call CalculateDiameter(ShapeCount)
  891.     Shape(ShapeCount).Velocity.x = 0
  892.     Shape(ShapeCount).Velocity.y = 0
  893.     Shape(ShapeCount).Omega = 0
  894.     Shape(ShapeCount).Shade = _RGB(100 + Int(Rnd * 155), 100 + Int(Rnd * 155), 100 + Int(Rnd * 155))
  895.     SelectedShape = ShapeCount
  896.  
  897. Sub cline (x1 As Double, y1 As Double, x2 As Double, y2 As Double, col As _Unsigned Long)
  898.     Line (_Width / 2 + x1, -y1 + _Height / 2)-(_Width / 2 + x2, -y2 + _Height / 2), col
  899.  
  900. Sub ccircle (x1 As Double, y1 As Double, rad As Double, col As _Unsigned Long)
  901.     Circle (_Width / 2 + x1, -y1 + _Height / 2), rad, col
  902.  
  903. Sub cpset (x1 As Double, y1 As Double, col As _Unsigned Long)
  904.     PSet (_Width / 2 + x1, -y1 + _Height / 2), col
  905.  
  906. Sub cpaint (x1 As Double, y1 As Double, col1 As _Unsigned Long, col2 As _Unsigned Long)
  907.     Paint (_Width / 2 + x1, -y1 + _Height / 2), col1, col2
  908.  
  909. Sub cprintstring (y As Double, a As String)
  910.     _PrintString (_Width / 2 - (Len(a) * 8) / 2, -y + _Height / 2), a
  911.  
  912. Sub snd (frq As Double, dur As Double)
  913.     If ((frq >= 37) And (frq <= 2000)) Then
  914.         Sound frq, dur
  915.     End If
  916.  
  917. Sub SetupPoolGame
  918.     ' Set external field
  919.     ForceField.x = 0
  920.     ForceField.y = 0
  921.  
  922.     ' Rectangular border
  923.     wx = 42
  924.     wy = 10
  925.     Call NewBrickLine(-_Width / 2 + wx, _Height / 2 - wy, _Width / 2 - wx, _Height / 2 - wy, wx, wy)
  926.     Call NewBrickLine(-_Width / 2 + wx, -_Height / 2 + wy, _Width / 2 - wx, -_Height / 2 + wy, wx, wy)
  927.     wx = 40
  928.     wy = 10
  929.     Call NewBrickLine(-_Width / 2 + wy, -_Height / 2 + 2 * wx, -_Width / 2 + wy, _Height / 2 - 2 * wx, wx, wy)
  930.     Call NewBrickLine(_Width / 2 - wy, -_Height / 2 + 2 * wx, _Width / 2 - wy, _Height / 2 - 2 * wx, wx, wy)
  931.  
  932.     ' Balls (billiard setup)
  933.     x0 = 160
  934.     y0 = 0
  935.     r = 15
  936.     gg = 2 * r + 4
  937.     gx = gg * Cos(30 * 3.14159 / 180)
  938.     gy = gg * Sin(30 * 3.14159 / 180)
  939.     Call NewAutoBall(x0 + 0 * gx, y0 + 0 * gy, r, 0, 1, 1, 0)
  940.     Call NewAutoBall(x0 + 1 * gx, y0 + 1 * gy, r, 0, 1, 1, 0)
  941.     Call NewAutoBall(x0 + 1 * gx, y0 - 1 * gy, r, 0, 1, 1, 0)
  942.     Call NewAutoBall(x0 + 2 * gx, y0 + 2 * gy, r, 0, 1, 1, 0)
  943.     Call NewAutoBall(x0 + 2 * gx, y0 + 0 * gy, r, 0, 1, 1, 0)
  944.     Call NewAutoBall(x0 + 2 * gx, y0 - 2 * gy, r, 0, 1, 1, 0)
  945.     Call NewAutoBall(x0 + 3 * gx, y0 + 3 * gy, r, 0, 1, 1, 0)
  946.     Call NewAutoBall(x0 + 3 * gx, y0 + 1 * gy, r, 0, 1, 1, 0)
  947.     Call NewAutoBall(x0 + 3 * gx, y0 - 1 * gy, r, 0, 1, 1, 0)
  948.     Call NewAutoBall(x0 + 3 * gx, y0 - 3 * gy, r, 0, 1, 1, 0)
  949.     Call NewAutoBall(x0 + 4 * gx, y0 + 4 * gy, r, 0, 1, 1, 0)
  950.     Call NewAutoBall(x0 + 4 * gx, y0 + 2 * gy, r, 0, 1, 1, 0)
  951.     Call NewAutoBall(x0 + 4 * gx, y0 - 0 * gy, r, 0, 1, 1, 0)
  952.     Call NewAutoBall(x0 + 4 * gx, y0 - 2 * gy, r, 0, 1, 1, 0)
  953.     Call NewAutoBall(x0 + 4 * gx, y0 - 4 * gy, r, 0, 1, 1, 0)
  954.  
  955.     ' Cue ball
  956.     Call NewAutoBall(-220, 0, r, 0, 1, 1, 0)
  957.     Shape(ShapeCount).Velocity.x = 10 + 2 * Rnd
  958.     Shape(ShapeCount).Velocity.y = 1 * (Rnd - .5)
  959.     Shape(ShapeCount).Shade = _RGB(255, 255, 255)
  960.  
  961.     ' Parameters
  962.     CPC = 1.15
  963.     FPC = 8
  964.     RST = 0.75
  965.     VD = 0.995
  966.     SV = 0
  967.  
  968. Sub SetupWackyGame
  969.     ' Set external field
  970.     ForceField.x = 0
  971.     ForceField.y = -.08
  972.  
  973.     ' Rectangular border
  974.     wx = 42
  975.     wy = 10
  976.     Call NewBrickLine(-_Width / 2 + wx, _Height / 2 - wy, _Width / 2 - wx, _Height / 2 - wy, wx, wy)
  977.     Call NewBrickLine(-_Width / 2 + wx, -_Height / 2 + wy, _Width / 2 - wx, -_Height / 2 + wy, wx, wy)
  978.     wx = 40
  979.     wy = 10
  980.     Call NewBrickLine(-_Width / 2 + wy, -_Height / 2 + 2 * wx, -_Width / 2 + wy, _Height / 2 - 2 * wx, wx, wy)
  981.     Call NewBrickLine(_Width / 2 - wy, -_Height / 2 + 2 * wx, _Width / 2 - wy, _Height / 2 - 2 * wx, wx, wy)
  982.  
  983.     ' Wacky balls
  984.     x0 = -70
  985.     y0 = 120
  986.     r1 = 15
  987.     r2 = 2.5
  988.     gg = 2.5 * (r1 + r2) + 3.5
  989.     gx = gg * Cos(30 * 3.14159 / 180)
  990.     gy = gg * Sin(30 * 3.14159 / 180)
  991.     Call NewAutoBall(x0 + 0 * gx, y0 + 0 * gy, r1, r2, Int(Rnd * 3) + 1, Int(Rnd * 1) + 2, 0)
  992.     Call NewAutoBall(x0 + 1 * gx, y0 + 1 * gy, r1, r2, Int(Rnd * 3) + 1, Int(Rnd * 1) + 2, 0)
  993.     Call NewAutoBall(x0 + 1 * gx, y0 - 1 * gy, r1, r2, Int(Rnd * 3) + 1, Int(Rnd * 1) + 2, 0)
  994.     Call NewAutoBall(x0 + 2 * gx, y0 + 2 * gy, r1, r2, Int(Rnd * 3) + 1, Int(Rnd * 1) + 2, 0)
  995.     Call NewAutoBall(x0 + 2 * gx, y0 + 0 * gy, r1, r2, Int(Rnd * 3) + 1, Int(Rnd * 1) + 2, 0)
  996.     Call NewAutoBall(x0 + 2 * gx, y0 - 2 * gy, r1, r2, Int(Rnd * 3) + 1, Int(Rnd * 1) + 2, 0)
  997.     Call NewAutoBall(x0 + 3 * gx, y0 + 3 * gy, r1, r2, Int(Rnd * 3) + 1, Int(Rnd * 1) + 2, 0)
  998.     Call NewAutoBall(x0 + 3 * gx, y0 + 1 * gy, r1, r2, Int(Rnd * 3) + 1, Int(Rnd * 1) + 2, 0)
  999.     Call NewAutoBall(x0 + 3 * gx, y0 - 1 * gy, r1, r2, Int(Rnd * 3) + 1, Int(Rnd * 1) + 2, 0)
  1000.     Call NewAutoBall(x0 + 3 * gx, y0 - 3 * gy, r1, r2, Int(Rnd * 3) + 1, Int(Rnd * 1) + 2, 0)
  1001.  
  1002.     ' Slanted bricks
  1003.     wx = 60
  1004.     wy = 10
  1005.     ww = Sqr(wx * wx + wy * wy) * .85
  1006.     Call NewBrickLine(ww, 0, 100 + ww, 100, wx, wy)
  1007.     Call NewBrickLine(-ww, 0, -100 - ww, 100, wx, wy)
  1008.  
  1009.     ' Fidget spinner
  1010.     Call NewAutoBall(-220, 0, 20, 15, 1.5, 2, 0)
  1011.     Shape(ShapeCount).Shade = _RGB(255, 255, 255)
  1012.  
  1013.     ' Parameters
  1014.     CPC = 1.15
  1015.     FPC = 8
  1016.     RST = 0.70
  1017.     VD = 0.995
  1018.     SV = 0.025
  1019.  
  1020. Sub SetupRings
  1021.     ' Set external field
  1022.     ForceField.x = 0
  1023.     ForceField.y = 0
  1024.  
  1025.     ' Rectangular border
  1026.     wx = 42
  1027.     wy = 10
  1028.     Call NewBrickLine(-_Width / 2 + wx, _Height / 2 - wy, _Width / 2 - wx, _Height / 2 - wy, wx, wy)
  1029.     Call NewBrickLine(-_Width / 2 + wx, -_Height / 2 + wy, _Width / 2 - wx, -_Height / 2 + wy, wx, wy)
  1030.     wx = 40
  1031.     wy = 10
  1032.     Call NewBrickLine(-_Width / 2 + wy, -_Height / 2 + 2 * wx, -_Width / 2 + wy, _Height / 2 - 2 * wx, wx, wy)
  1033.     Call NewBrickLine(_Width / 2 - wy, -_Height / 2 + 2 * wx, _Width / 2 - wy, _Height / 2 - 2 * wx, wx, wy)
  1034.  
  1035.     For r = 25 To 175 Step 25
  1036.         Call NewAutoBall(0, 0, r, 0, 1, 1, 0)
  1037.     Next
  1038.  
  1039.     ' Parameters
  1040.     CPC = 1.15
  1041.     FPC = 8
  1042.     RST = 0.75
  1043.     VD = 0.995
  1044.     SV = 0.025
  1045.  
  1046. Sub SetupWallsOnly
  1047.     ' Set external field
  1048.     ForceField.x = 0
  1049.     ForceField.y = 0 - .08
  1050.  
  1051.     ' Fidget spinner
  1052.     Call NewAutoBall(-220, 0, 20, 15, 1.5, 2, 0)
  1053.     Shape(ShapeCount).Shade = _RGB(255, 255, 255)
  1054.  
  1055.     ' Rectangular border
  1056.     wx = 42
  1057.     wy = 10
  1058.     Call NewBrickLine(-_Width / 2 + wx, _Height / 2 - wy, _Width / 2 - wx, _Height / 2 - wy, wx, wy)
  1059.     Call NewBrickLine(-_Width / 2 + wx, -_Height / 2 + wy, _Width / 2 - wx, -_Height / 2 + wy, wx, wy)
  1060.     wx = 40
  1061.     wy = 10
  1062.     Call NewBrickLine(-_Width / 2 + wy, -_Height / 2 + 2 * wx, -_Width / 2 + wy, _Height / 2 - 2 * wx, wx, wy)
  1063.     Call NewBrickLine(_Width / 2 - wy, -_Height / 2 + 2 * wx, _Width / 2 - wy, _Height / 2 - 2 * wx, wx, wy)
  1064.  
  1065.     ' Parameters
  1066.     CPC = 1.15
  1067.     FPC = 8
  1068.     RST = 0.75
  1069.     VD = 0.995
  1070.     SV = 0.025
  1071.  
  1072. Sub SetupAnglePong
  1073.     ' Set external field
  1074.     ForceField.x = 0
  1075.     ForceField.y = 0
  1076.  
  1077.     ' Rectangular border
  1078.     wx = 42
  1079.     wy = 10
  1080.     Call NewBrickLine(-_Width / 2 + wx, _Height / 2 - wy, _Width / 2 - wx, _Height / 2 - wy, wx, wy)
  1081.     Call NewBrickLine(-_Width / 2 + wx, -_Height / 2 + wy, _Width / 2 - wx, -_Height / 2 + wy, wx, wy)
  1082.     wx = 40
  1083.     wy = 10
  1084.     Call NewBrickLine(-_Width / 2 + wy, -_Height / 2 + 2 * wx, -_Width / 2 + wy, _Height / 2 - 2 * wx, wx, wy)
  1085.     Call NewBrickLine(_Width / 2 - wy, -_Height / 2 + 2 * wx, _Width / 2 - wy, _Height / 2 - 2 * wx, wx, wy)
  1086.  
  1087.     ' Pong ball
  1088.     Call NewAutoBall(0, 200, 20, 0, 1, 1, 0)
  1089.     Shape(ShapeCount).Velocity.x = -1
  1090.     Shape(ShapeCount).Velocity.y = -3
  1091.     Shape(ShapeCount).Shade = _RGB(255, 255, 255)
  1092.  
  1093.     ' Pong Paddle
  1094.     Call NewAutoBrick(-100, 10, 100, -10, -.02 * 8 * Atn(1))
  1095.     vtemp.x = 0
  1096.     vtemp.y = -200
  1097.     Call TranslateShape(ShapeCount, vtemp)
  1098.     Shape(ShapeCount).Shade = _RGB(200, 200, 200)
  1099.  
  1100.     ' Parameters
  1101.     CPC = 1.15
  1102.     FPC = 8
  1103.     RST = 1 '0.75
  1104.     VD = 1 '0.995
  1105.     SV = 0.025
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 27, 2021, 02:28:17 am
several coffees later

was having problems with ball contact and I thought FULLSCREEN was to blame. Actually the FULLSCREEN is very precise.   Run the code and try the arrow keys. See how the balls maintain perfect contact at all positions.

Keys to rotate vector are z or x
keys to adjust vector magnitude (ball velocity) are c or v
Key to toggle red or cyan ball is b

As in the original code, the arrow keys move the ball positions.

rotate vector has an auto fine / coarse control.   hold down key longer, for coarse rotation and momentary for fine rotation.

Code shows how WINDOW, FULLSCREEN and NEWIMAGE can work together to make graphics larger on the computer display. This method fills the entire screen automatically.  This should work on any size of computer display.

Everyone puts down FULLSCREEN  but it has it's uses.  There are probably even better ways to do this.

Code: QB64: [Select]
  1. 'Original code by OldMoses
  2. 'Additional code by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   position
  12.     m AS V2 '                                                   movement (pre-contact) incoming
  13.     x AS V2 '                                                   vector of post contact movement
  14.     s AS INTEGER '                                              magnitude of movement
  15.  
  16. DIM TEXT(1) AS STRING
  17. RAD = 0
  18. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  19. DIM mouse AS V2
  20. DIM b(1) AS ball
  21. 'DIM SHARED AS V2 origin
  22. DIM SHARED origin AS V2
  23. origin.x = 0: origin.y = 0
  24. b(0).c = Red: b(0).cn = "red"
  25. b(1).c = Cyan: b(1).cn = "cyan"
  26.  
  27. SCREEN _NEWIMAGE(600, 600, 32) ' this will (should) allow the magnify effect of fullscreen
  28.  
  29. AR = _DESKTOPWIDTH / _DESKTOPHEIGHT 'Aspect ratio
  30.  
  31. y1 = (600 / ((_DESKTOPWIDTH / _DESKTOPHEIGHT) + 1))
  32.  
  33. x1 = (600 / ((_DESKTOPHEIGHT / _DESKTOPWIDTH) + 1))
  34.  
  35. WINDOW (-x1, y1)-(x1, -y1) 'correct fullscreen x , y  distortion etc
  36.  
  37.  
  38. 'starting state
  39. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  40. b(0).s = PyT(origin, b(0).m)
  41. 'b(0).s = _HYPOT(origin.x - b(0).m.x, origin.y - b(0).m.y) ' reds magnitude (speed or force)
  42.  
  43. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  44. b(1).s = PyT(origin, b(1).m)
  45. 'b(1).s = _HYPOT(origin.x - b(1).m.x, origin.y - b(1).m.y) ' cyans magnitude (speed or force)
  46.  
  47. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  48. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  49.  
  50.     CLS
  51.     ms = MBS '                                                  process mouse actions dragging endpoints
  52.     IF ms AND 64 THEN
  53.  
  54.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  55.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  56.  
  57.         FOR x = 0 TO 1
  58.             FOR y = 0 TO 1
  59.                 ds! = PyT(vertex(x, y), mouse)
  60.                 IF ds! < ballradius * .5 THEN i = x: j = y
  61.             NEXT y
  62.         NEXT x
  63.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  64.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  65.                 b(i).p = mouse
  66.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  67.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  68.         END SELECT
  69.     END IF
  70.  
  71.     'IF _KEYDOWN(114) THEN i = 0
  72.     'IF _KEYDOWN(99) THEN i = 1
  73.     IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  74.     IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  75.     IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  76.     IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  77.  
  78.     'IF _KEYDOWN(119) THEN b(i).m.y = b(i).m.y - 1
  79.     'IF _KEYDOWN(115) THEN b(i).m.y = b(i).m.y + 1
  80.     'IF _KEYDOWN(97) THEN b(i).m.x = b(i).m.x + 1
  81.     'IF _KEYDOWN(100) THEN b(i).m.x = b(i).m.x - 1
  82.  
  83.     '**************Code added by Novarseg
  84.     'Vector rotation and vector magnitude adjustment using keyboard input
  85.  
  86.     I$ = INKEY$
  87.  
  88.     _DELAY .04 'allows enough time for keyboard buffer to accumulate characters
  89.     '           so the vector rotation (fast / slow) operates properly
  90.     '           there is probably a better way to do this
  91.     IF I$ = "" THEN f1 = 0
  92.  
  93.     IF f3 = 0 THEN I$ = "b": f3 = 1
  94.     IF I$ = "b" AND f2 = 0 THEN i = 1: f2 = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  95.     IF I$ = "b" AND f2 = 1 THEN i = 0: f2 = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  96.     LL1:
  97.  
  98.     IF I$ = "c" THEN 'increase vector magnitude
  99.         mult = 1.01
  100.         VecMult b(i).m, mult
  101.     END IF
  102.  
  103.     IF I$ = "v" THEN 'decrease vector magnitude
  104.         div = 1.01
  105.         VecDIV b(i).m, div 'added a new sub
  106.     END IF
  107.  
  108.     IF I$ = "z" THEN 'rotate vector counter clockwise
  109.         IF RAD > _PI * 2 THEN RAD = 0
  110.  
  111.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  112.         IF TIMER - t1 > 1.5 THEN RAD = RAD + .05
  113.         IF TIMER - t1 <= 1.5 THEN RAD = RAD + .005
  114.  
  115.         signC = COS(RAD) * 1 / ABS(COS(RAD))
  116.         signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  117.         b(i).s = PyT(origin, b(i).m)
  118.         b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  119.         b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  120.  
  121.     END IF
  122.  
  123.     IF I$ = "x" THEN 'rotate vector clockwise
  124.         IF RAD < 0 THEN RAD = _PI * 2
  125.  
  126.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  127.         IF TIMER - t1 > 1.5 THEN RAD = RAD - .05
  128.         IF TIMER - t1 <= 1.5 THEN RAD = RAD - .005
  129.  
  130.         signC = COS(RAD) * 1 / ABS(COS(RAD))
  131.         signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  132.         b(i).s = PyT(origin, b(i).m)
  133.         b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  134.         b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  135.  
  136.  
  137.     END IF
  138.     '**************END Code added Novarseg
  139.  
  140.  
  141.     'START OF COLLISION MATHEMATICS SECTION
  142.     ballradius = bPyT(b(0).p, b(1).p, AR) / 2 'the only time this function is used
  143.  
  144.     FOR bn = 0 TO 1
  145.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  146.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  147.     NEXT bn
  148.  
  149.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  150.     B2BCollision b(0), b(1)
  151.     'END OF COLLISION MATHEMATICS SECTION
  152.  
  153.     'graphic representation
  154.     FOR grid = -300 TO 300 STEP 20
  155.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  156.         LINE (grid, 300)-(grid, -300), c& 'Gray  'vertical lines
  157.         LINE (-300, grid)-(300, grid), c& ' Gray  'horizontal lines
  158.     NEXT grid
  159.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  160.  
  161.     FOR dr = 0 TO 1
  162.  
  163.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , AR
  164.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  165.  
  166.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  167.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  168.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  169.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">" + c(dr)
  170.  
  171.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  172.  
  173.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  174.     NEXT dr
  175.  
  176.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  177.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  178.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  179.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  180.         CLOSE 'NOVARSEG added this line
  181.     END IF 'NOVARSEG added this line
  182.  
  183.     ' _LIMIT 500
  184.     _DISPLAY
  185.  
  186.  
  187. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  188.  
  189.     ' DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  190.     DIM un AS V2
  191.     DIM ut AS V2
  192.     DIM ncomp1 AS V2
  193.     DIM ncomp2 AS V2
  194.     DIM tcomp1 AS V2
  195.     DIM tcomp2 AS V2
  196.  
  197.  
  198.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  199.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  200.     bnci1 = VecDot(un, ball1.m) '
  201.     bnci2 = VecDot(un, ball2.m) '
  202.     btci1 = VecDot(ut, ball1.m) '
  203.     btci2 = VecDot(ut, ball2.m) '
  204.  
  205.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  206.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  207.  
  208.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  209.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  210.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  211.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  212.  
  213.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  214.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  215.  
  216. END SUB 'B2BCollision
  217.  
  218.  
  219. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  220.  
  221.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  222.  
  223.  
  224.  
  225. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  226.     STATIC StartTimer AS _FLOAT
  227.     STATIC ButtonDown AS INTEGER
  228.     STATIC ClickCount AS INTEGER
  229.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  230.     '                          Down longer counts as a HOLD event.
  231.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  232.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  233.         SELECT CASE SGN(_MOUSEWHEEL)
  234.             CASE 1: MBS = MBS OR 512
  235.             CASE -1: MBS = MBS OR 1024
  236.         END SELECT
  237.     WEND
  238.  
  239.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  240.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  241.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  242.  
  243.     IF StartTimer = 0 THEN
  244.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  245.             ButtonDown = 1: StartTimer = TIMER(0.01)
  246.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  247.         ELSEIF _MOUSEBUTTON(2) THEN
  248.             ButtonDown = 2: StartTimer = TIMER(0.01)
  249.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  250.         ELSEIF _MOUSEBUTTON(3) THEN
  251.             ButtonDown = 3: StartTimer = TIMER(0.01)
  252.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  253.         END IF
  254.     ELSE
  255.         BD = ButtonDown MOD 3
  256.         IF BD = 0 THEN BD = 3
  257.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  258.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  259.         ELSE
  260.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  261.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  262.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  263.             ELSE 'We've now started the hold event
  264.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  265.             END IF
  266.         END IF
  267.     END IF
  268.  
  269.  
  270. FUNCTION PyT (var1 AS V2, var2 AS V2)
  271.  
  272.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  273.  
  274.  
  275. FUNCTION bPyT (var1 AS V2, var2 AS V2, var3) 'to calulate ball radius only
  276.     'var1.x = var1.x * 1.6384
  277.     bPyT = _HYPOT((var1.x - var2.x) * var3, (var1.y - var2.y) * var3)
  278.  
  279.  
  280.  
  281.  
  282. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  283.  
  284.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  285.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  286.  
  287. END SUB 'Add_Vector
  288.  
  289.  
  290. FUNCTION VecDot (var AS V2, var2 AS V2)
  291.  
  292.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  293.  
  294. END FUNCTION 'VecDot
  295.  
  296.  
  297. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  298.  
  299.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  300.     vec.y = vec.y * multiplier
  301.  
  302. END SUB 'Vec_Mult
  303.  
  304. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  305.  
  306.     vec.x = vec.x / divisor
  307.     vec.y = vec.y / divisor
  308.  
  309. END SUB 'VecDIV
  310.  
  311.  
  312. SUB VecNorm (var AS V2)
  313.  
  314.     m = PyT(origin, var)
  315.     IF m = 0 THEN
  316.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  317.     ELSE
  318.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  319.     END IF
  320.  
  321. END SUB 'VecNorm


@STxAxTIC
realistic effects
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 27, 2021, 09:06:33 pm
I think I may have solved my magnetic ball issue. This latest version even seems to break well. At least I haven't been able to "break" it yet... and I'm amazed at how much the code distilled down. I'm starting to feel like I can now invest time and stress in the table details.

Also fixed the cue strike so that it strikes clean and doesn't "thrust" the cueball if you're slow releasing the button.

A couple control pointers:

It uses all mouse buttons and wheel.
roll the wheel back like you're pulling back a pinball spring. A force number will show in the cueball up to a max of 35
roll the wheel the other way and the force will "reduce" to a max of -35. Click the wheel button sets force at max positive.

Why negative force? you might ask...
positive force accelerates the ball away from the  mouse cursor
negative force accelerates the ball toward the mouse cursor
very useful for aiming when you're scrunched up against the table edge.

left click to shoot
click the right mouse button to reset the rack for a new break.

Code: QB64: [Select]
  1. 'ball colors 1 yellow 2 blue 3 red 4 purple 5 orange 6 green 7 maroon 8 black
  2. '9 yellow/s 10 blue/s 11 red/s 12 purple/s 13 orange/s 14 green/s 15 maroon/s
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     sunk AS _BYTE '                                             has ball been sunk true/false
  10.     c AS _UNSIGNED LONG '                                       ball color
  11.     p AS V2 '                                                   position vector
  12.     d AS V2 '                                                   direction vector
  13.     n AS V2 '                                                   normalized direction vector
  14.     s AS SINGLE '                                               speed
  15.     r AS _BYTE '                                                rack position
  16.  
  17. DIM SHARED bsiz AS INTEGER '                                    radius of ball
  18. DIM SHARED bsiz2 AS INTEGER '                                   ball diameter or sphere of contact
  19. DIM SHARED bl(15) AS ball '                                     ball data
  20. DIM SHARED bnum(15) AS LONG
  21. DIM SHARED origin AS V2
  22. origin.x = 0: origin.y = 0
  23.  
  24. 'Set the table size
  25.     xtable = _DESKTOPWIDTH - 100: ytable = xtable / 2
  26.     ytable = _DESKTOPHEIGHT - 80: xtable = ytable * 2
  27.  
  28.  
  29. bsiz = INT(((xtable / 118.1102) * 2.375) / 2) '                 size balls to table
  30. bsiz2 = bsiz * 2
  31. xt5 = xtable * .05
  32.  
  33. FOR x = 0 TO 15
  34.     READ bl(x).c
  35.  
  36. MakeTable
  37. MakeBalls
  38. RackEmUp
  39. bl(0).p.y = INT(ytable * .5) '                                  position the cue
  40. bl(0).p.x = INT(xtable * .75)
  41.  
  42. a& = _NEWIMAGE(xtable, ytable, 32)
  43. _DEST a&: SCREEN a&
  44.  
  45. COLOR , &HFF007632
  46.  
  47.     CLS
  48.     _PUTIMAGE , tbl, a&
  49.  
  50.     FOR x = 0 TO 15
  51.         IF bl(x).sunk THEN
  52.             IF x = 0 THEN
  53.                 'scratch
  54.                 bl(0).p.y = INT(ytable * .5) '                  position the cue
  55.                 bl(0).p.x = INT(xtable * .75)
  56.             ELSE
  57.                 _CONTINUE
  58.             END IF
  59.         END IF
  60.         VecAdd bl(x).p, bl(x).d, 1
  61.         VecMult bl(x).d, .99
  62.         IF PyT(origin, bl(x).d) < .05 THEN bl(x).d = origin
  63.         ColCheck x
  64.         _PUTIMAGE (INT(bl(x).p.x) - CINT(_WIDTH(bnum(x)) / 2), INT(bl(x).p.y) - CINT(_HEIGHT(bnum(x)) / 2)), bnum(x), a&
  65.     NEXT x
  66.  
  67.     ms = MBS%
  68.     IF ms AND 1 THEN
  69.         IF (origin.x = bl(0).d.x) AND (origin.y = bl(0).d.y) THEN
  70.             bl(0).d.x = bl(0).p.x - _MOUSEX '                   get the cue strike vector
  71.             bl(0).d.y = bl(0).p.y - _MOUSEY
  72.             VecNorm bl(0).d '                                   shrink it
  73.             VecMult bl(0).d, su '                               grow it
  74.             DO UNTIL NOT _MOUSEBUTTON(1) '                      prevents cue thrusting,
  75.                 WHILE _MOUSEINPUT: WEND '                       i.e. constant acceleration across table
  76.             LOOP '                                              while holding down mouse button
  77.             su = 0 '                                            reset strike units
  78.         END IF
  79.     END IF
  80.     IF ms AND 2 THEN '                                          if mouse right button reset the rack
  81.         BallStop '                                              all displacements to = origin
  82.         bl(0).p.y = INT(ytable * .5)
  83.         bl(0).p.x = INT(xtable * .75)
  84.         RackEmUp
  85.     END IF
  86.     IF ms AND 4 THEN '                                          if mouse center button, set full strike
  87.         IF su = 35 THEN su = 0
  88.         IF su = 0 THEN su = 35
  89.     END IF
  90.     IF ms AND 512 THEN '                                        roll mousewheel back, accelerate away from mouse cursor
  91.         su = Limit%(35, su + 1) '                               like pulling back a pinball spring
  92.     END IF
  93.     IF ms AND 1024 THEN '                                       roll mousewheel frw'd, accelerate towards mouse cursor
  94.         su = su + 1 * (su > -35) '                              helpful in aiming from table edge
  95.     END IF
  96.  
  97.     LINE (_MOUSEX, _MOUSEY)-(CINT(bl(0).p.x), CINT(bl(0).p.y))
  98.     'slope of target line
  99.     pathx = CINT(bl(0).p.x) - _MOUSEX: pathy = CINT(bl(0).p.y) - _MOUSEY
  100.     LINE (bl(0).p.x, bl(0).p.y)-(pathx * 1000, pathy * 1000), Blue
  101.     IF (bl(0).d.x = 0) AND (bl(0).d.y = 0) THEN
  102.         _PRINTSTRING (bl(0).p.x - 8, bl(0).p.y - 8), STR$(su)
  103.     END IF
  104.     _DISPLAY
  105.     _LIMIT 100
  106.  
  107.  
  108. '                                                               DATA SECTION
  109. hue:
  110. DATA 4294967295,4294967040,4278190335,4294901760,4286578816,4294944000,4278222848,4286578688
  111. DATA 4278190080,4294967040,4278190335,4294901760,4286578816,4294944000,4278222848,4286578688
  112.  
  113. start:
  114. DATA 1,2,15,14,8,3,4,6,11,13,12,7,9,10,5,0
  115.  
  116. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  117.  
  118.     DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  119.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  120.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  121.     bnci1 = VecDot(un, ball1.d) '                               ball 1 normal component of input velocity
  122.     bnci2 = VecDot(un, ball2.d) '                               ball 2 normal component of input velocity
  123.     btci1 = VecDot(ut, ball1.d) '                               ball 1 tangent component of input velocity
  124.     btci2 = VecDot(ut, ball2.d) '                               ball 2 tangent component of input velocity
  125.  
  126.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  127.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  128.  
  129.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  130.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  131.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  132.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  133.  
  134.     ball1.d = ncomp1: VecAdd ball1.d, tcomp1, 1 '               add normal and tangent exit vectors
  135.     ball2.d = ncomp2: VecAdd ball2.d, tcomp2, 1 '               add normal and tangent exit vectors
  136.  
  137.     VecMult ball1.d, .95 '                                      lets take 5% of energy in entropic factors
  138.     VecMult ball2.d, .95
  139.  
  140. END SUB 'B2BCollision
  141.  
  142.  
  143. SUB BallStop
  144.  
  145.     FOR x = 0 TO 15
  146.         bl(x).d = origin
  147.     NEXT x
  148.  
  149. END SUB 'BallStop
  150.  
  151.  
  152. SUB ColCheck (var AS INTEGER)
  153.  
  154.     'check for ball in displacement radius
  155.     disp = SQR(bl(var).d.x * bl(var).d.x + bl(var).d.y * bl(var).d.y) 'vector magnitude for this iteration
  156.     FOR x = 0 TO 15 '
  157.         IF x = var THEN _CONTINUE
  158.         dist = PyT(bl(var).p, bl(x).p) '                calculate distance between var and x
  159.         IF dist < bsiz2 THEN '                          are they closer than two radii, i.e. stuck together
  160.             DIM AS V2 un
  161.             un = bl(var).p: VecAdd un, bl(x).p, -1 '    get a normal vector between them
  162.             VecNorm un '                                shrink it to a unit vector
  163.             VecMult un, (bsiz2 - dist) '                grow it by the amount they intersect
  164.             VecAdd bl(var).p, un, 1 '                   add it to the position
  165.         END IF
  166.         IF dist - bsiz2 < disp THEN '                   if ball x is within reach of magnitude
  167.             dx = bl(var).p.x - bl(x).p.x
  168.             dy = bl(var).p.y - bl(x).p.y
  169.             A## = (bl(var).d.x * bl(var).d.x) + (bl(var).d.y * bl(var).d.y) 'displacement range
  170.             B## = 2 * bl(var).d.x * dx + 2 * bl(var).d.y * dy
  171.                 C## = (bl(x).p.x * bl(x).p.x) + (bl(x).p.y * bl(x).p.y) + (bl(var).p.x * bl(var).p.x)_
  172.                      + (bl(var).p.y * bl(var).p.y) + -2 * (bl(x).p.x * bl(var).p.x + bl(x).p.y * bl(var).p.y) - (bsiz2 * bsiz2)
  173.             disabc## = (B## * B##) - 4 * A## * C##
  174.             IF disabc## > 0 THEN '                          ray intersects ball x position
  175.                 B2BCollision bl(var), bl(x)
  176.             END IF '                                        end: disabc <= 0  aka ball missed
  177.         END IF '                                            end: dist < disp test
  178.     NEXT x
  179.  
  180.     'wall bounces - now we need to work in pocket corners which we will tentatively treat like immobile balls flanking the holes
  181.     IF bl(var).p.x < bsiz + xt5 OR bl(var).p.x > xtable - bsiz - xt5 THEN
  182.         bl(var).d.x = -bl(var).d.x
  183.         IF bl(var).p.x < bsiz + xt5 THEN '                            if beyond left edge
  184.             bl(var).p.x = bl(var).p.x + (2 * (bsiz + xt5 - bl(var).p.x))
  185.         END IF
  186.         IF bl(var).p.x > xtable - bsiz - xt5 THEN '                   if beyond right edge
  187.             bl(var).p.x = bl(var).p.x - (2 * (bl(var).p.x - (xtable - bsiz - xt5)))
  188.         END IF
  189.     END IF
  190.     IF bl(var).p.y < bsiz + xt5 OR bl(var).p.y > ytable - bsiz - xt5 THEN
  191.         bl(var).d.y = -bl(var).d.y
  192.         IF bl(var).p.y < bsiz + xt5 THEN '                            if beyond top edge
  193.             bl(var).p.y = bl(var).p.y + (2 * (bsiz + xt5 - bl(var).p.y))
  194.         END IF
  195.         IF bl(var).p.y > ytable - bsiz - xt5 THEN '                   if beyond bottom edge
  196.             bl(var).p.y = bl(var).p.y - (2 * (bl(var).p.y - (ytable - bsiz - xt5)))
  197.         END IF
  198.     END IF
  199.  
  200. END SUB 'ColCheck
  201.  
  202.  
  203.     DIM R AS INTEGER, RError AS INTEGER '                       SMcNeill's circle fill
  204.     DIM X AS INTEGER, Y AS INTEGER
  205.  
  206.     R = ABS(RR)
  207.     RError = -R
  208.     X = R
  209.     Y = 0
  210.     IF R = 0 THEN PSET (CX, CY), C: EXIT SUB
  211.     LINE (CX - X, CY)-(CX + X, CY), C, BF
  212.     WHILE X > Y
  213.         RError = RError + Y * 2 + 1
  214.         IF RError >= 0 THEN
  215.             IF X <> Y + 1 THEN
  216.                 LINE (CX - Y, CY - X)-(CX + Y, CY - X), C2, BF 'these two need white here for 9-15 balls
  217.                 LINE (CX - Y, CY + X)-(CX + Y, CY + X), C2, BF
  218.             END IF
  219.             X = X - 1
  220.             RError = RError - X * 2
  221.         END IF
  222.         Y = Y + 1
  223.         LINE (CX - X, CY - Y)-(CX + X, CY - Y), C, BF
  224.         LINE (CX - X, CY + Y)-(CX + X, CY + Y), C, BF
  225.     WEND
  226. END SUB 'FCirc
  227.  
  228.  
  229. FUNCTION Limit% (lim AS INTEGER, var AS INTEGER)
  230.  
  231.     Limit% = lim - ((var - lim) * (var < lim + 1))
  232.  
  233. END FUNCTION 'Limit%
  234.  
  235.  
  236. SUB MakeBalls
  237.  
  238.     FOR x = 0 TO 15
  239.         'make ball images here
  240.         bnum(x) = _NEWIMAGE(bsiz * 2 + 4, bsiz * 2 + 4, 32)
  241.         _DEST bnum(x)
  242.         IF x = 0 THEN '                                         Cue ball
  243.             FCirc INT(_WIDTH(bnum(x)) / 2), INT(_HEIGHT(bnum(x)) / 2), bsiz, bl(x).c, bl(x).c
  244.             CIRCLE (_WIDTH(bnum(x)) / 2, _HEIGHT(bnum(x)) / 2), bsiz + 1, Black
  245.         ELSE
  246.             'Solids or stripes
  247.             IF x <= 8 THEN
  248.                 FCirc INT(_WIDTH(bnum(x)) / 2), INT(_HEIGHT(bnum(x)) / 2), bsiz, bl(x).c, bl(x).c ' solid
  249.             ELSE
  250.                 FCirc INT(_WIDTH(bnum(x)) / 2), INT(_HEIGHT(bnum(x)) / 2), bsiz, bl(x).c, White '   stripe
  251.             END IF
  252.             FCirc INT(_WIDTH(bnum(x)) / 2), INT(_HEIGHT(bnum(x)) / 2), bsiz - 5, White, White 'number circle
  253.             CIRCLE (_WIDTH(bnum(x)) / 2, _HEIGHT(bnum(x)) / 2), bsiz + 1, Black
  254.             n$ = _TRIM$(STR$(x))
  255.             t& = _NEWIMAGE(16, 16, 32)
  256.             _DEST t&
  257.             COLOR Black
  258.             _PRINTMODE _KEEPBACKGROUND
  259.             IF LEN(n$) > 1 THEN a = 0 ELSE a = 4
  260.             _PRINTSTRING (a, 0), n$, t&
  261.             _DEST bnum(x)
  262.             _PUTIMAGE (8, 8)-(_WIDTH(bnum(x)) - 8, _HEIGHT(bnum(x)) - 8), t&, bnum(x)
  263.             _FREEIMAGE t&
  264.         END IF
  265.     NEXT x
  266.  
  267. END SUB 'MakeBalls
  268.  
  269.  
  270. SUB MakeTable
  271.  
  272.     tbl = _NEWIMAGE(xtable, ytable, 32)
  273.     _DEST tbl
  274.     COLOR , &HFF007632
  275.     CLS
  276.     FOR x = 0 TO 2
  277.         LINE (x, x)-(xtable - x, ytable - x), Black, B
  278.     NEXT x
  279.     FCirc xtable * .75, ytable * .5, 5, Gray, Gray
  280.     FCirc xtable * .75, ytable * .5, 2, White, White
  281.     LINE (xt5, xt5)-(xtable - xt5, ytable - xt5), &HFFFF0000, B , &HF0F0
  282.  
  283. END SUB 'MakeTable
  284.  
  285.  
  286. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  287.  
  288.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  289.  
  290.  
  291.  
  292. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  293.     STATIC StartTimer AS _FLOAT
  294.     STATIC ButtonDown AS INTEGER
  295.     STATIC ClickCount AS INTEGER
  296.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  297.     '                          Down longer counts as a HOLD event.
  298.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  299.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  300.         SELECT CASE SGN(_MOUSEWHEEL)
  301.             CASE 1: MBS = MBS OR 512
  302.             CASE -1: MBS = MBS OR 1024
  303.         END SELECT
  304.     WEND
  305.  
  306.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  307.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  308.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  309.  
  310.     IF StartTimer = 0 THEN
  311.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  312.             ButtonDown = 1: StartTimer = TIMER(0.01)
  313.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  314.         ELSEIF _MOUSEBUTTON(2) THEN
  315.             ButtonDown = 2: StartTimer = TIMER(0.01)
  316.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  317.         ELSEIF _MOUSEBUTTON(3) THEN
  318.             ButtonDown = 3: StartTimer = TIMER(0.01)
  319.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  320.         END IF
  321.     ELSE
  322.         BD = ButtonDown MOD 3
  323.         IF BD = 0 THEN BD = 3
  324.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  325.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  326.         ELSE
  327.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  328.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  329.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  330.             ELSE 'We've now started the hold event
  331.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  332.             END IF
  333.         END IF
  334.     END IF
  335.  
  336.  
  337. FUNCTION PyT (var1 AS V2, var2 AS V2)
  338.  
  339.     PyT = _HYPOT(ABS(var1.x - var2.x), ABS(var1.y - var2.y)) '  distances and magnitudes
  340.  
  341.  
  342.  
  343. SUB RackEmUp
  344.  
  345.     yoff = bsiz2 + 4
  346.     xoff = SQR((yoff / 2) * (yoff / 2) + yoff * yoff) - 4
  347.  
  348.     RESTORE start
  349.     FOR rank = 1 TO 5
  350.         FOR b = 1 TO rank
  351.             READ k
  352.             bl(k).p.x = (.25 * xtable) - (xoff * (rank - 1))
  353.             bl(k).p.y = (.5 * ytable) - ((rank - 1) * (.5 * yoff)) + ((b - 1) * yoff)
  354.     NEXT b, rank
  355.  
  356. END SUB 'RackEmUp
  357.  
  358.  
  359. SUB VecAdd (var AS V2, var2 AS V2, var3 AS INTEGER)
  360.  
  361.     var.x = var.x + (var2.x * var3) '                           add (or subtract) two vectors defined by unitpoint
  362.     var.y = var.y + (var2.y * var3) '                           var= base vector, var2= vector to add
  363.  
  364. END SUB 'VecAdd
  365.  
  366.  
  367. FUNCTION VecDot (var AS V2, var2 AS V2)
  368.  
  369.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  370.  
  371. END FUNCTION 'VecDot
  372.  
  373.  
  374. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  375.  
  376.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  377.     vec.y = vec.y * multiplier
  378.  
  379. END SUB 'VecMult
  380.  
  381. SUB VecNorm (var AS V2)
  382.  
  383.     m = SQR(var.x * var.x + var.y * var.y) '                    convert var to unit vector
  384.     var.x = var.x / m
  385.     var.y = var.y / m
  386.  
  387. END SUB 'VecNorm
  388.  
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 27, 2021, 09:22:26 pm
Graphics look better. Still uses FULLSCREEN .

Code: QB64: [Select]
  1. 'Original code by OldMoses
  2. 'Additional code by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   position
  12.     m AS V2 '                                                   movement (pre-contact) incoming
  13.     x AS V2 '                                                   vector of post contact movement
  14.     s AS INTEGER '                                              magnitude of movement
  15.  
  16.  
  17. DIM TEXT(1) AS STRING
  18. RAD = 0
  19. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  20. DIM mouse AS V2
  21. DIM b(1) AS ball
  22.  
  23. 'DIM SHARED AS V2 origin
  24. DIM SHARED origin AS V2
  25. origin.x = 0: origin.y = 0
  26. b(0).c = Red: b(0).cn = "red"
  27. b(1).c = Cyan: b(1).cn = "cyan"
  28.  
  29. SCREEN _NEWIMAGE(_DESKTOPWIDTH, _DESKTOPHEIGHT, 32) 'prevents FULLSCREEN distortion
  30.  
  31. ' _SCREENMOVE 10, 10
  32.  
  33. MAG = _DESKTOPHEIGHT / 300 'this 300 is from the grid drawing code.
  34.  
  35.  
  36. _FULLSCREEN 'gives a full screen - not a window!
  37.  
  38. 'starting state
  39. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  40. b(0).s = PyT(origin, b(0).m)
  41. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  42. b(1).s = PyT(origin, b(1).m)
  43. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  44. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  45.  
  46.     CLS
  47.     ms = MBS '                                                  process mouse actions dragging endpoints
  48.     IF ms AND 64 THEN
  49.  
  50.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  51.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  52.  
  53.         FOR x = 0 TO 1
  54.             FOR y = 0 TO 1
  55.                 ds! = PyT(vertex(x, y), mouse)
  56.                 IF ds! < ballradius * .5 THEN i = x: j = y
  57.             NEXT y
  58.         NEXT x
  59.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  60.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  61.                 b(i).p = mouse
  62.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  63.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  64.         END SELECT
  65.     END IF
  66.  
  67.     'IF _KEYDOWN(114) THEN i = 0
  68.     'IF _KEYDOWN(99) THEN i = 1
  69.     IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  70.     IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  71.     IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  72.     IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  73.  
  74.     'IF _KEYDOWN(119) THEN b(i).m.y = b(i).m.y - 1
  75.     'IF _KEYDOWN(115) THEN b(i).m.y = b(i).m.y + 1
  76.     'IF _KEYDOWN(97) THEN b(i).m.x = b(i).m.x + 1
  77.     'IF _KEYDOWN(100) THEN b(i).m.x = b(i).m.x - 1
  78.  
  79.     '**************Code added by Novarseg
  80.     'Vector rotation and vector magnitude adjustment using keyboard input
  81.  
  82.     I$ = INKEY$
  83.  
  84.     _DELAY .04 'allows enough time for keyboard buffer to accumulate characters
  85.     '           so the vector rotation (fast / slow) operates properly
  86.     '           there is probably a better way to do this
  87.     IF I$ = "" THEN f1 = 0
  88.  
  89.     IF f3 = 0 THEN I$ = "b": f3 = 1
  90.     IF I$ = "b" AND f2 = 0 THEN i = 1: f2 = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  91.     IF I$ = "b" AND f2 = 1 THEN i = 0: f2 = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  92.     LL1:
  93.  
  94.     IF I$ = "c" THEN 'increase vector magnitude
  95.         mult = 1.01
  96.         VecMult b(i).m, mult
  97.     END IF
  98.  
  99.     IF I$ = "v" THEN 'decrease vector magnitude
  100.         div = 1.01
  101.         VecDIV b(i).m, div 'added a new sub
  102.     END IF
  103.  
  104.     IF I$ = "z" THEN 'rotate vector counter clockwise
  105.         IF RAD > _PI * 2 THEN RAD = 0
  106.  
  107.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  108.         IF TIMER - t1 > 1.5 THEN RAD = RAD + .05
  109.         IF TIMER - t1 <= 1.5 THEN RAD = RAD + .005
  110.  
  111.         signC = COS(RAD) * 1 / ABS(COS(RAD))
  112.         signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  113.         b(i).s = PyT(origin, b(i).m)
  114.         b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  115.         b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  116.  
  117.     END IF
  118.  
  119.     IF I$ = "x" THEN 'rotate vector clockwise
  120.         IF RAD < 0 THEN RAD = _PI * 2
  121.  
  122.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  123.         IF TIMER - t1 > 1.5 THEN RAD = RAD - .05
  124.         IF TIMER - t1 <= 1.5 THEN RAD = RAD - .005
  125.  
  126.         signC = COS(RAD) * 1 / ABS(COS(RAD))
  127.         signS = SIN(RAD) * 1 / ABS(SIN(RAD))
  128.         b(i).s = PyT(origin, b(i).m)
  129.         b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5) * signS
  130.         b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5) * signC
  131.  
  132.  
  133.     END IF
  134.     '**************END Code added Novarseg
  135.  
  136.  
  137.     'START OF COLLISION MATHEMATICS SECTION
  138.  
  139.     ballradius = PyT(b(0).p, b(1).p) / 2
  140.  
  141.     FOR bn = 0 TO 1
  142.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  143.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  144.     NEXT bn
  145.  
  146.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  147.     B2BCollision b(0), b(1)
  148.     'END OF COLLISION MATHEMATICS SECTION
  149.  
  150.     'graphic representation
  151.     FOR grid = -300 TO 300 STEP 20
  152.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  153.         LINE (grid, 300)-(grid, -300), c& 'Gray  'vertical lines
  154.         LINE (-300, grid)-(300, grid), c& ' Gray  'horizontal lines
  155.     NEXT grid
  156.  
  157.  
  158.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  159.  
  160.     FOR dr = 0 TO 1
  161.  
  162.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , 1 'AR
  163.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  164.  
  165.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  166.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  167.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  168.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">" + c(dr)
  169.  
  170.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  171.  
  172.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  173.     NEXT dr
  174.  
  175.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  176.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  177.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  178.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  179.         CLOSE 'NOVARSEG added this line
  180.     END IF 'NOVARSEG added this line
  181.  
  182.     _LIMIT 500
  183.     _DISPLAY
  184.  
  185.  
  186. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  187.  
  188.     ' DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  189.     DIM un AS V2
  190.     DIM ut AS V2
  191.     DIM ncomp1 AS V2
  192.     DIM ncomp2 AS V2
  193.     DIM tcomp1 AS V2
  194.     DIM tcomp2 AS V2
  195.  
  196.  
  197.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  198.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  199.     bnci1 = VecDot(un, ball1.m) '
  200.     bnci2 = VecDot(un, ball2.m) '
  201.     btci1 = VecDot(ut, ball1.m) '
  202.     btci2 = VecDot(ut, ball2.m) '
  203.  
  204.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  205.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  206.  
  207.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  208.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  209.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  210.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  211.  
  212.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  213.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  214.  
  215. END SUB 'B2BCollision
  216.  
  217.  
  218. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  219.  
  220.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  221.  
  222.  
  223.  
  224. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  225.     STATIC StartTimer AS _FLOAT
  226.     STATIC ButtonDown AS INTEGER
  227.     STATIC ClickCount AS INTEGER
  228.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  229.     '                          Down longer counts as a HOLD event.
  230.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  231.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  232.         SELECT CASE SGN(_MOUSEWHEEL)
  233.             CASE 1: MBS = MBS OR 512
  234.             CASE -1: MBS = MBS OR 1024
  235.         END SELECT
  236.     WEND
  237.  
  238.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  239.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  240.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  241.  
  242.     IF StartTimer = 0 THEN
  243.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  244.             ButtonDown = 1: StartTimer = TIMER(0.01)
  245.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  246.         ELSEIF _MOUSEBUTTON(2) THEN
  247.             ButtonDown = 2: StartTimer = TIMER(0.01)
  248.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  249.         ELSEIF _MOUSEBUTTON(3) THEN
  250.             ButtonDown = 3: StartTimer = TIMER(0.01)
  251.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  252.         END IF
  253.     ELSE
  254.         BD = ButtonDown MOD 3
  255.         IF BD = 0 THEN BD = 3
  256.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  257.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  258.         ELSE
  259.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  260.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  261.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  262.             ELSE 'We've now started the hold event
  263.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  264.             END IF
  265.         END IF
  266.     END IF
  267.  
  268.  
  269. FUNCTION PyT (var1 AS V2, var2 AS V2)
  270.  
  271.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  272.  
  273.  
  274. FUNCTION bPyT (var1 AS V2, var2 AS V2, var3) 'to calulate ball radius only
  275.     'var1.x = var1.x * 1.6384
  276.     bPyT = _HYPOT((var1.x - var2.x) * var3, (var1.y - var2.y) * var3)
  277.  
  278.  
  279.  
  280.  
  281. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  282.  
  283.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  284.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  285.  
  286. END SUB 'Add_Vector
  287.  
  288.  
  289. FUNCTION VecDot (var AS V2, var2 AS V2)
  290.  
  291.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  292.  
  293. END FUNCTION 'VecDot
  294.  
  295.  
  296. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  297.  
  298.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  299.     vec.y = vec.y * multiplier
  300.  
  301. END SUB 'Vec_Mult
  302.  
  303. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  304.  
  305.     vec.x = vec.x / divisor
  306.     vec.y = vec.y / divisor
  307.  
  308. END SUB 'VecDIV
  309.  
  310.  
  311. SUB VecNorm (var AS V2)
  312.  
  313.     m = PyT(origin, var)
  314.     IF m = 0 THEN
  315.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  316.     ELSE
  317.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  318.     END IF
  319.  
  320. END SUB 'VecNorm

simplified code

changed

 
Quote
  signC = COS(RAD) * 1 / ABS(COS(RAD))
    signS = SIN(RAD) * 1 / ABS(SIN(RAD))
   b(i).s = PyT(origin, b(i).m)
b(i).m.y = ((b(i).s ^ 2 / (((COS(RAD)) ^ 2 / (SIN(RAD)) ^ 2) + 1)) ^ .5)* signS
b(i).m.x = -((b(i).s ^ 2 / (((SIN(RAD)) ^ 2 / (COS(RAD)) ^ 2) + 1)) ^ .5)* signC

to
     
Quote
   b(i).s = PyT(origin, b(i).m)
        b(i).m.y = b(i).s * COS(RAD(i))
        b(i).m.x = b(i).s * SIN(RAD(i))

Code: QB64: [Select]
  1. 'Original code by OldMoses
  2. 'Additional code by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   position
  12.     m AS V2 '                                                   movement (pre-contact) incoming
  13.     x AS V2 '                                                   vector of post contact movement
  14.     s AS INTEGER '                                              magnitude of movement
  15.  
  16.  
  17. DIM TEXT(1) AS STRING
  18. DIM RAD(1) AS SINGLE
  19. RAD = 0
  20. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  21. DIM mouse AS V2
  22. DIM b(1) AS ball
  23.  
  24. 'DIM SHARED AS V2 origin
  25. DIM SHARED origin AS V2
  26. origin.x = 0: origin.y = 0
  27. b(0).c = Red: b(0).cn = "red"
  28. b(1).c = Cyan: b(1).cn = "cyan"
  29.  
  30. SCREEN _NEWIMAGE(_DESKTOPWIDTH, _DESKTOPHEIGHT, 32) 'prevents FULLSCREEN distortion
  31.  
  32. ' _SCREENMOVE 10, 10
  33.  
  34. MAG = _DESKTOPHEIGHT / 300 'this 300 is from the grid drawing code.
  35.  
  36.  
  37. _FULLSCREEN 'gives a full screen - not a window!
  38.  
  39. 'starting state
  40. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  41. b(0).s = PyT(origin, b(0).m)
  42. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  43. RAD(1) = 3 * _PI / 2
  44. b(1).s = PyT(origin, b(1).m)
  45. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  46. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  47.  
  48.     CLS
  49.     ms = MBS '                                                  process mouse actions dragging endpoints
  50.     IF ms AND 64 THEN
  51.  
  52.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  53.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  54.  
  55.         FOR x = 0 TO 1
  56.             FOR y = 0 TO 1
  57.                 ds! = PyT(vertex(x, y), mouse)
  58.                 IF ds! < ballradius * .5 THEN i = x: j = y
  59.             NEXT y
  60.         NEXT x
  61.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  62.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  63.                 b(i).p = mouse
  64.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  65.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  66.         END SELECT
  67.     END IF
  68.  
  69.     'IF _KEYDOWN(114) THEN i = 0
  70.     'IF _KEYDOWN(99) THEN i = 1
  71.     IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  72.     IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  73.     IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  74.     IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  75.  
  76.     'IF _KEYDOWN(119) THEN b(i).m.y = b(i).m.y - 1
  77.     'IF _KEYDOWN(115) THEN b(i).m.y = b(i).m.y + 1
  78.     'IF _KEYDOWN(97) THEN b(i).m.x = b(i).m.x + 1
  79.     'IF _KEYDOWN(100) THEN b(i).m.x = b(i).m.x - 1
  80.  
  81.     '**************Code added by Novarseg
  82.     'Vector rotation and vector magnitude adjustment using keyboard input
  83.  
  84.     I$ = INKEY$
  85.  
  86.     _DELAY .04 'allows enough time for keyboard buffer to accumulate characters
  87.     '           so the vector rotation (fast / slow) operates properly
  88.     '           there is probably a better way to do this
  89.     IF I$ = "" THEN f1 = 0
  90.  
  91.     IF f3 = 0 THEN I$ = "b": f3 = 1
  92.     IF I$ = "b" AND f2 = 0 THEN i = 1: f2 = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  93.     IF I$ = "b" AND f2 = 1 THEN i = 0: f2 = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  94.     LL1:
  95.  
  96.     IF I$ = "c" THEN 'increase vector magnitude
  97.         mult = 1.01
  98.         VecMult b(i).m, mult
  99.     END IF
  100.  
  101.     IF I$ = "v" THEN 'decrease vector magnitude
  102.         div = 1.01
  103.         VecDIV b(i).m, div 'added a new sub
  104.     END IF
  105.  
  106.     IF I$ = "z" THEN 'rotate vector counter clockwise
  107.         IF RAD(i) > _PI * 2 THEN RAD(i) = 0
  108.  
  109.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  110.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) + .05
  111.         IF TIMER - t1 <= 1.5 THEN RAD(i) = RAD(i) + .005
  112.  
  113.  
  114.         b(i).s = PyT(origin, b(i).m)
  115.         b(i).m.y = b(i).s * COS(RAD(i))
  116.         b(i).m.x = b(i).s * SIN(RAD(i))
  117.  
  118.     END IF
  119.  
  120.     IF I$ = "x" THEN 'rotate vector clockwise
  121.         IF RAD(i) < 0 THEN RAD(i) = _PI * 2
  122.  
  123.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  124.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) - .05
  125.         IF TIMER - t1 <= 1.5 THEN RAD(i) = RAD(i) - .005
  126.  
  127.         b(i).s = PyT(origin, b(i).m)
  128.         b(i).m.y = b(i).s * COS(RAD(i))
  129.         b(i).m.x = b(i).s * SIN(RAD(i))
  130.     END IF
  131.     '**************END Code added Novarseg
  132.  
  133.  
  134.     'START OF COLLISION MATHEMATICS SECTION
  135.  
  136.     ballradius = PyT(b(0).p, b(1).p) / 2
  137.  
  138.     FOR bn = 0 TO 1
  139.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  140.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  141.     NEXT bn
  142.  
  143.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  144.     B2BCollision b(0), b(1)
  145.     'END OF COLLISION MATHEMATICS SECTION
  146.  
  147.     'graphic representation
  148.     FOR grid = -300 TO 300 STEP 20
  149.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  150.         LINE (grid, 300)-(grid, -300), c& 'Gray  'vertical lines
  151.         LINE (-300, grid)-(300, grid), c& ' Gray  'horizontal lines
  152.     NEXT grid
  153.  
  154.  
  155.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  156.  
  157.     FOR dr = 0 TO 1
  158.  
  159.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , 1 'AR
  160.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  161.  
  162.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  163.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  164.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  165.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">" + c(dr)
  166.  
  167.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  168.  
  169.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  170.     NEXT dr
  171.  
  172.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  173.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  174.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  175.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  176.         CLOSE 'NOVARSEG added this line
  177.     END IF 'NOVARSEG added this line
  178.  
  179.     _LIMIT 500
  180.     _DISPLAY
  181.  
  182.  
  183. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  184.  
  185.     ' DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  186.     DIM un AS V2
  187.     DIM ut AS V2
  188.     DIM ncomp1 AS V2
  189.     DIM ncomp2 AS V2
  190.     DIM tcomp1 AS V2
  191.     DIM tcomp2 AS V2
  192.  
  193.  
  194.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  195.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  196.     bnci1 = VecDot(un, ball1.m) '
  197.     bnci2 = VecDot(un, ball2.m) '
  198.     btci1 = VecDot(ut, ball1.m) '
  199.     btci2 = VecDot(ut, ball2.m) '
  200.  
  201.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  202.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  203.  
  204.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  205.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  206.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  207.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  208.  
  209.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  210.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  211.  
  212. END SUB 'B2BCollision
  213.  
  214.  
  215. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  216.  
  217.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  218.  
  219.  
  220.  
  221. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  222.     STATIC StartTimer AS _FLOAT
  223.     STATIC ButtonDown AS INTEGER
  224.     STATIC ClickCount AS INTEGER
  225.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  226.     '                          Down longer counts as a HOLD event.
  227.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  228.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  229.         SELECT CASE SGN(_MOUSEWHEEL)
  230.             CASE 1: MBS = MBS OR 512
  231.             CASE -1: MBS = MBS OR 1024
  232.         END SELECT
  233.     WEND
  234.  
  235.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  236.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  237.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  238.  
  239.     IF StartTimer = 0 THEN
  240.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  241.             ButtonDown = 1: StartTimer = TIMER(0.01)
  242.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  243.         ELSEIF _MOUSEBUTTON(2) THEN
  244.             ButtonDown = 2: StartTimer = TIMER(0.01)
  245.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  246.         ELSEIF _MOUSEBUTTON(3) THEN
  247.             ButtonDown = 3: StartTimer = TIMER(0.01)
  248.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  249.         END IF
  250.     ELSE
  251.         BD = ButtonDown MOD 3
  252.         IF BD = 0 THEN BD = 3
  253.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  254.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  255.         ELSE
  256.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  257.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  258.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  259.             ELSE 'We've now started the hold event
  260.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  261.             END IF
  262.         END IF
  263.     END IF
  264.  
  265.  
  266. FUNCTION PyT (var1 AS V2, var2 AS V2)
  267.  
  268.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  269.  
  270.  
  271. FUNCTION bPyT (var1 AS V2, var2 AS V2, var3) 'to calulate ball radius only
  272.     'var1.x = var1.x * 1.6384
  273.     bPyT = _HYPOT((var1.x - var2.x) * var3, (var1.y - var2.y) * var3)
  274.  
  275.  
  276.  
  277.  
  278. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  279.  
  280.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  281.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  282.  
  283. END SUB 'Add_Vector
  284.  
  285.  
  286. FUNCTION VecDot (var AS V2, var2 AS V2)
  287.  
  288.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  289.  
  290. END FUNCTION 'VecDot
  291.  
  292.  
  293. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  294.  
  295.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  296.     vec.y = vec.y * multiplier
  297.  
  298. END SUB 'Vec_Mult
  299.  
  300. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  301.  
  302.     vec.x = vec.x / divisor
  303.     vec.y = vec.y / divisor
  304.  
  305. END SUB 'VecDIV
  306.  
  307.  
  308. SUB VecNorm (var AS V2)
  309.  
  310.     m = PyT(origin, var)
  311.     IF m = 0 THEN
  312.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  313.     ELSE
  314.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  315.     END IF
  316.  
  317. END SUB 'VecNorm


Title: Re: 2D ball collisions without trigonometry.
Post by: STxAxTIC on May 27, 2021, 11:30:59 pm
Looks good Moses!
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 28, 2021, 01:19:04 am
Looking at OldMoses code now.  Great work.
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 28, 2021, 10:30:54 am
@OldMoses Yes! I am a little confused by cue stick (not the wheel, that's good!) but really nice ball action.
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 28, 2021, 07:00:39 pm
Probably with the addition of a cue stick image I could make it less confusing, it would then provide an orientation aid. I just can't figure out a natural way of cue handling that doesn't come with various aiming and/or force issues, particularly when shooting from a wall. Aim gets really coarse when the mouse cursor is close to the ball, for mathematically obvious reasons.

And speaking of mathematics; I actually realized that for all its nice appearance, it is not mathematically correct. It doesn't compute ball vectors at the point of impact, but rather from the point at which it's ray tracing equation detects a future collision in the present main loop iteration. It's close, but no cigar, as they say... This is more pronounced at faster speeds. By slowing down the _LIMIT 100 to a _LIMIT 10, the effect can then be easily seen, with faster balls shearing away from each other before actually touching. Updating the striking ball's position reintroduced the magnetic effect. So, back to the drawing board yet again...

Still, I'll consider this a usable baseline from which to tweak things.
Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 28, 2021, 08:49:03 pm
Quote
And speaking of mathematics; I actually realized that for all its nice appearance, it is not mathematically correct. It doesn't compute ball vectors at the point of impact, but rather from the point at which it's ray tracing equation detects a future collision in the present main loop iteration.

@OldMoses About backing up balls to collision kiss point see last code block below

This (from my Pool 3.1, Steve's forum) backs up 2 balls just detected as having deepest collision for ball i, saveJ was the other ball index.
Code: QB64: [Select]
  1.         For i = 0 To topBall
  2.             minDist = 100000: saveJ = -1
  3.             For j = 0 To topBall 'find deepest collision in case more than one we want earliest = deepest penetration
  4.                 If i <> j And b(i).x <> -1000 Then
  5.                     dist = Sqr((b(i).x - b(j).x) * (b(i).x - b(j).x) + (b(i).y - b(j).y) * (b(i).y - b(j).y))
  6.                     If dist < BDia Then ' collision but is it first or deepest collision
  7.                         If dist < minDist Then minDist = dist: saveJ = j
  8.                     End If
  9.                 End If
  10.             Next
  11.             If saveJ <> -1 Then ' found collision change ball i dx, dy   calc new course for ball i
  12.                 ''reflection  from circle  using Vectors  from JB, thanks tsh73
  13.                 v1$ = vect$(b(i).x, b(i).y) ' circle i
  14.                 v2$ = vect$(b(saveJ).x, b(saveJ).y) ' the other circle j
  15.                 dv1$ = vect$(b(i).dx, b(i).dy) ' change in velocity vector
  16.                 dv2$ = vect$(b(saveJ).dx, b(saveJ).dy)
  17.                 dv1u$ = vectUnit$(dv1$) '1 pixel
  18.                 dv2u$ = vectUnit$(dv2$)
  19.  
  20.                 ' Here is the place where code hangs, make sure at least 1 vector has a decent length to change
  21.                 If vectLen(dv1u$) > .00001 Or vectLen(dv2u$) > .00001 Then
  22.                     Do ' this should back up the balls to kiss point thanks tsh73
  23.                         v1$ = vectSub$(v1$, dv1u$)
  24.                         v2$ = vectSub(v2$, dv2u$)
  25.                     Loop While vectLen(vectSub$(v1$, v2$)) < BDia 'back up our circle i to point on kiss
  26.                 End If
  27.  
  28.  

The rest of the code is from that 2D Collision paper you gave us a link to.

I made 2 arrays to hold ball data one for current frame, b(i), and one for next frame, nf(i) that way all ball data gets changed at once for next frame without changing ball data of current frame, so all balls of current frame work on same set of data.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 28, 2021, 09:37:38 pm
@bplus
Quote
I made 2 arrays to hold ball data one for current frame, b(i), and one for next frame, nf(i) that way all ball data gets changed at once for next frame without changing ball data of current frame, so all balls of current frame work on same set of data.

So for frame b(i) check every ball for collisions and if there are any calculate new data and save it in frame nf(i). At what point is the switch made to frame nf(i)?

Title: Re: 2D ball collisions without trigonometry.
Post by: bplus on May 28, 2021, 10:16:44 pm
@bplus
So for frame b(i) check every ball for collisions and if there are any calculate new data and save it in frame nf(i). At what point is the switch made to frame nf(i)?

Just before the loop back. In this program, I broke my normal routine of drawing everything last to drawing everything first, then recalculating new locations.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 29, 2021, 01:10:01 am
I still don't know how 2d ball collisions work.  That's OK, about 99.999999% of the world's population don't know either.

I will try to do a step by step calculation of the following ball data to arrive at the exit vectors shown.

red  @ (0, 0)  along <0, 100>  exits along <79, 60>           
cyan @ (-100, 50)  along <-100, 100>  exits along <20, 140>  SELECTED

So there is the unit tangent and unit normal vector.  The unit normal vector is also known as the strike vector.  The unit tangent vector is at a right angle to the unit normal vector.

un = unit normal vector
ut = unit tangent vector

lets calculate un

un =  er wait .. what the hek is that again ........
cyan ball position -100x  50y
red ball position        0x     0y

normal vector (strike vector) is -100x   50y

unit normal vector =
(-100^2+ 50^2 )^.5 = 111.8033

-100x / 111.8033 = -.8944x
50y / 111.8033 = .4472y

un =   -.8944x    .4472y   
ut is easy to calculate because it is at right angle to un
multiply y by -1
swap x with y and y with x

ut  = -.4472x   -.8944y 


The literature says that UT is tangent to both balls but with the red ball centered at 0x  0y,  the above math indicates that ut can be in any position and still be the same vector

Ok from this point there are probably more than one way to solve for exit vectors of both balls.

STxAxTIC supplied an interesting equation:

q = 2( un. cyan ball vector  - un . red ball vector )

lets sidetrack and look at a straight on collision

red  @ (0, 0)  along <120, 60>  exits along <139, -70>           
cyan @ (-100, 50)  along <-140, -70>  exits along <-121, 60>  SELECTED

the unit normal vector is the same as before

un =   -.894427x    .447213y   


The cyan ball heading before collision -140x   -70y

actually the display for y should be 70y will fix  that later

divide cyan vector x ,y by cyan vector magnitude to obtain cyan unit vector

unit cyan vector = -.8944x,   .4472y

the dot product of unit normal (un)  with unit cyan  (uc)
 un . uc = -.8944  *  -.8944 +  .4472 *.4472  = .8   +  .2  = 1

The dot product for red ball =1.

un . uc - un . ur = 0

both balls come to a complete stop and rebound at the same velocity then arrived at. 
What happens if the cyan ball heads in a direction that is right angle to un?

red  @ (0, 0)  along <120, 60>  exits along <0, -1>           
cyan @ (-100, 50)  along <56, -115>  exits along <-178, -55>  SELECTED

unit cyan vector (uc)
(56^2 = 115^2 )^.5 = 127.9101

56 / 127.9101 =.4378
115 / 127.9101 = .89906

un . uc = -.8944  *.4378 + 44721 *.89906  = -.3915 + .4 = approx zero

not exactly zero due to small errors in graph output. 

RULE:  The dot product of 2 unit vectors ranges from 0 to 1









Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 29, 2021, 07:55:46 am
I still don't know how 2d ball collisions work.  That's OK, about 99.999999% of the world's population don't know either.

But you want to know, which is the point of all this.

Quote
I will try to do a step by step calculation of the following ball data to arrive at the exit vectors shown.

red  @ (0, 0)  along <0, 100>  exits along <79, 60>           
cyan @ (-100, 50)  along <-100, 100>  exits along <20, 140>  SELECTED

I set this up (hopefully I did this right) using the version of the test program that I posted at reply #63
https://www.qb64.org/forum/index.php?topic=3866.msg132698#msg132698
(play with this one some and see how unit normal component and unit tangent component vectors mirror around the incoming vectors, they're colored according to where they get their influence)

  [ This attachment cannot be displayed inline in 'Print Page' view ]  

The first thing that jumps out at me is not so much that it arrives at different exit vectors, but that the scenario, as presented, would not happen in a properly designed application. The balls, if I interpret your scenario correctly, would be starting from an overlapping state, and a slower ball would be colliding from behind with a faster ball. We can't expect plausible outputs from implausible inputs. I believe that such things were the reason my billiard balls were acting magnet like, because they had overlapped into a situation where the calculations could no longer output physically tenable data. It was a garbage in/garbage out situation.

The test bed assumes that balls are in an impacting state after having traveled in on a given vector, it doesn't make judgements as to whether the travel vectors and starting positions can happen in a proper physics model. It only forces an impact point by adjusting ball radii, then gives exit vectors.

Quote
So there is the unit tangent and unit normal vector.  The unit normal vector is also known as the strike vector.  The unit tangent vector is at a right angle to the unit normal vector.

un = unit normal vector
ut = unit tangent vector

lets calculate un

un =  er wait .. what the hek is that again ........
cyan ball position -100x  50y
red ball position        0x     0y

normal vector (strike vector) is -100x   50y

unit normal vector =
(-100^2+ 50^2 )^.5 = 111.8033

-100x / 111.8033 = -.8944x
50y / 111.8033 = .4472y

un =   -.8944x    .4472y   
ut is easy to calculate because it is at right angle to un
multiply y by -1
swap x with y and y with x

ut  = -.4472x   -.8944y 

All looks good to me.

Quote

The literature says that UT is tangent to both balls but with the red ball centered at 0x  0y,  the above math indicates that ut can be in any position and still be the same vector

That's how vectors work. They can be anywhere in the universe, but it doesn't change their direction and magnitude, and the math will still work the same. It doesn't matter where red ball is except for obtaining the UN vector. After that you've got UT vector as you did above. It's at this point that the Dot Product calculations step in and determine where the balls will deflect based upon which way they came in. The unit normal (UN) is how the opposite ball influences the collision and the unit tangent (UT) is how a ball's incoming affects itself upon exit. Those two are added together to get the final exit vector.
Title: Re: 2D ball collisions without trigonometry.
Post by: STxAxTIC on May 29, 2021, 11:33:36 am
Whenever I need to think about balls touching, I use these notes. Same thing Moses is saying:

  [ This attachment cannot be displayed inline in 'Print Page' view ]  
  [ This attachment cannot be displayed inline in 'Print Page' view ]  
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 29, 2021, 06:17:12 pm
@OldMoses
Im using this version.

The image you showed is not the one I'm working on.

 If you type the "f" key the ball data is saved to a file called BALL STUFF.txt.  That lets you copy and paste data.

If you want to minimize the program simply comment out _FULLSCREEN,   it'll still work but you won't get that nice DOS look.

Code: QB64: [Select]
  1. 'Original code by OldMoses
  2. 'Additional code by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   position
  12.     m AS V2 '                                                   movement (pre-contact) incoming
  13.     x AS V2 '                                                   vector of post contact movement
  14.     s AS INTEGER '                                              magnitude of movement
  15.  
  16.  
  17. DIM TEXT(1) AS STRING
  18. DIM RAD(1) AS SINGLE
  19. RAD = 0
  20. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  21. DIM mouse AS V2
  22. DIM b(1) AS ball
  23.  
  24. 'DIM SHARED AS V2 origin
  25. DIM SHARED origin AS V2
  26. origin.x = 0: origin.y = 0
  27. b(0).c = Red: b(0).cn = "red"
  28. b(1).c = Cyan: b(1).cn = "cyan"
  29.  
  30. SCREEN _NEWIMAGE(_DESKTOPWIDTH, _DESKTOPHEIGHT, 32) 'prevents FULLSCREEN distortion
  31.  
  32. ' _SCREENMOVE 10, 10
  33.  
  34. MAG = _DESKTOPHEIGHT / 300 'this 300 is from the grid drawing code.
  35.  
  36.  
  37. _FULLSCREEN 'gives a full screen - not a window!
  38.  
  39. 'starting state
  40. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  41. b(0).s = PyT(origin, b(0).m)
  42. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  43. RAD(1) = 3 * _PI / 2
  44. b(1).s = PyT(origin, b(1).m)
  45. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  46. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  47.  
  48.     CLS
  49.     ms = MBS '                                                  process mouse actions dragging endpoints
  50.     IF ms AND 64 THEN
  51.  
  52.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  53.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  54.  
  55.         FOR x = 0 TO 1
  56.             FOR y = 0 TO 1
  57.                 ds! = PyT(vertex(x, y), mouse)
  58.                 IF ds! < ballradius * .5 THEN i = x: j = y
  59.             NEXT y
  60.         NEXT x
  61.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  62.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  63.                 b(i).p = mouse
  64.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  65.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  66.         END SELECT
  67.     END IF
  68.  
  69.     'IF _KEYDOWN(114) THEN i = 0
  70.     'IF _KEYDOWN(99) THEN i = 1
  71.     IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  72.     IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  73.     IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  74.     IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  75.  
  76.     'IF _KEYDOWN(119) THEN b(i).m.y = b(i).m.y - 1
  77.     'IF _KEYDOWN(115) THEN b(i).m.y = b(i).m.y + 1
  78.     'IF _KEYDOWN(97) THEN b(i).m.x = b(i).m.x + 1
  79.     'IF _KEYDOWN(100) THEN b(i).m.x = b(i).m.x - 1
  80.  
  81.     '**************Code added by Novarseg
  82.     'Vector rotation and vector magnitude adjustment using keyboard input
  83.  
  84.     I$ = INKEY$
  85.  
  86.     _DELAY .04 'allows enough time for keyboard buffer to accumulate characters
  87.     '           so the vector rotation (fast / slow) operates properly
  88.     '           there is probably a better way to do this
  89.     IF I$ = "" THEN f1 = 0
  90.  
  91.     IF f3 = 0 THEN I$ = "b": f3 = 1
  92.     IF I$ = "b" AND f2 = 0 THEN i = 1: f2 = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  93.     IF I$ = "b" AND f2 = 1 THEN i = 0: f2 = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  94.     LL1:
  95.  
  96.     IF I$ = "c" THEN 'increase vector magnitude
  97.         mult = 1.01
  98.         VecMult b(i).m, mult
  99.     END IF
  100.  
  101.     IF I$ = "v" THEN 'decrease vector magnitude
  102.         div = 1.01
  103.         VecDIV b(i).m, div 'added a new sub
  104.     END IF
  105.  
  106.     IF I$ = "z" THEN 'rotate vector counter clockwise
  107.         IF RAD(i) > _PI * 2 THEN RAD(i) = 0
  108.  
  109.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  110.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) + .05
  111.         IF TIMER - t1 <= 1.5 THEN RAD(i) = RAD(i) + .005
  112.  
  113.  
  114.         b(i).s = PyT(origin, b(i).m)
  115.         b(i).m.y = b(i).s * COS(RAD(i))
  116.         b(i).m.x = b(i).s * SIN(RAD(i))
  117.  
  118.     END IF
  119.  
  120.     IF I$ = "x" THEN 'rotate vector clockwise
  121.         IF RAD(i) < 0 THEN RAD(i) = _PI * 2
  122.  
  123.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  124.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) - .05
  125.         IF TIMER - t1 <= 1.5 THEN RAD(i) = RAD(i) - .005
  126.  
  127.         b(i).s = PyT(origin, b(i).m)
  128.         b(i).m.y = b(i).s * COS(RAD(i))
  129.         b(i).m.x = b(i).s * SIN(RAD(i))
  130.     END IF
  131.     '**************END Code added Novarseg
  132.  
  133.  
  134.     'START OF COLLISION MATHEMATICS SECTION
  135.  
  136.     ballradius = PyT(b(0).p, b(1).p) / 2
  137.  
  138.     FOR bn = 0 TO 1
  139.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  140.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  141.     NEXT bn
  142.  
  143.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  144.     B2BCollision b(0), b(1)
  145.     'END OF COLLISION MATHEMATICS SECTION
  146.  
  147.     'graphic representation
  148.     FOR grid = -300 TO 300 STEP 20
  149.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  150.         LINE (grid, 300)-(grid, -300), c& 'Gray  'vertical lines
  151.         LINE (-300, grid)-(300, grid), c& ' Gray  'horizontal lines
  152.     NEXT grid
  153.  
  154.  
  155.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  156.  
  157.     FOR dr = 0 TO 1
  158.  
  159.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , 1 'AR
  160.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y - b(dr).m.y), b(dr).c 'incoming
  161.  
  162.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  163.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  164.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  165.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">" + c(dr)
  166.  
  167.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  168.  
  169.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  170.     NEXT dr
  171.  
  172.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  173.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  174.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  175.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  176.         CLOSE 'NOVARSEG added this line
  177.     END IF 'NOVARSEG added this line
  178.  
  179.     _LIMIT 500
  180.     _DISPLAY
  181.  
  182.  
  183. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  184.  
  185.     ' DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  186.     DIM un AS V2
  187.     DIM ut AS V2
  188.     DIM ncomp1 AS V2
  189.     DIM ncomp2 AS V2
  190.     DIM tcomp1 AS V2
  191.     DIM tcomp2 AS V2
  192.  
  193.  
  194.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  195.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  196.     bnci1 = VecDot(un, ball1.m) '
  197.     bnci2 = VecDot(un, ball2.m) '
  198.     btci1 = VecDot(ut, ball1.m) '
  199.     btci2 = VecDot(ut, ball2.m) '
  200.  
  201.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  202.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  203.  
  204.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  205.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  206.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  207.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  208.  
  209.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  210.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  211.  
  212. END SUB 'B2BCollision
  213.  
  214.  
  215. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  216.  
  217.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  218.  
  219.  
  220.  
  221. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  222.     STATIC StartTimer AS _FLOAT
  223.     STATIC ButtonDown AS INTEGER
  224.     STATIC ClickCount AS INTEGER
  225.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  226.     '                          Down longer counts as a HOLD event.
  227.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  228.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  229.         SELECT CASE SGN(_MOUSEWHEEL)
  230.             CASE 1: MBS = MBS OR 512
  231.             CASE -1: MBS = MBS OR 1024
  232.         END SELECT
  233.     WEND
  234.  
  235.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  236.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  237.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  238.  
  239.     IF StartTimer = 0 THEN
  240.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  241.             ButtonDown = 1: StartTimer = TIMER(0.01)
  242.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  243.         ELSEIF _MOUSEBUTTON(2) THEN
  244.             ButtonDown = 2: StartTimer = TIMER(0.01)
  245.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  246.         ELSEIF _MOUSEBUTTON(3) THEN
  247.             ButtonDown = 3: StartTimer = TIMER(0.01)
  248.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  249.         END IF
  250.     ELSE
  251.         BD = ButtonDown MOD 3
  252.         IF BD = 0 THEN BD = 3
  253.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  254.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  255.         ELSE
  256.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  257.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  258.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  259.             ELSE 'We've now started the hold event
  260.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  261.             END IF
  262.         END IF
  263.     END IF
  264.  
  265.  
  266. FUNCTION PyT (var1 AS V2, var2 AS V2)
  267.  
  268.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  269.  
  270.  
  271. FUNCTION bPyT (var1 AS V2, var2 AS V2, var3) 'to calulate ball radius only
  272.     'var1.x = var1.x * 1.6384
  273.     bPyT = _HYPOT((var1.x - var2.x) * var3, (var1.y - var2.y) * var3)
  274.  
  275.  
  276.  
  277.  
  278. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  279.  
  280.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  281.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  282.  
  283. END SUB 'Add_Vector
  284.  
  285.  
  286. FUNCTION VecDot (var AS V2, var2 AS V2)
  287.  
  288.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  289.  
  290. END FUNCTION 'VecDot
  291.  
  292.  
  293. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  294.  
  295.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  296.     vec.y = vec.y * multiplier
  297.  
  298. END SUB 'Vec_Mult
  299.  
  300. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  301.  
  302.     vec.x = vec.x / divisor
  303.     vec.y = vec.y / divisor
  304.  
  305. END SUB 'VecDIV
  306.  
  307.  
  308. SUB VecNorm (var AS V2)
  309.  
  310.     m = PyT(origin, var)
  311.     IF m = 0 THEN
  312.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  313.     ELSE
  314.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  315.     END IF
  316.  
  317. END SUB 'VecNorm

@STxAxTIC

your notes solve with unit normal vector.  Looks like unit tangent vector is not necessary?
Title: Re: 2D ball collisions without trigonometry.
Post by: OldMoses on May 29, 2021, 06:37:16 pm
If you add the four LINE statements to the end of SUB B2BCollision, as shown below, that will show how the vector components react to changes in ball position and velocity. It should work with your version and you can comment them out if you don't want or need them any more. It may help with visualizing the dot product interaction when you can see what component of the exit velocity comes from a ball's own velocity and what comes from the other ball.

Code: QB64: [Select]
  1. SUB B2BCollision (ball1 AS ball, ball2 AS ball)
  2.  
  3.     DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  4.     un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  5.     ut.x = -un.y: ut.y = un.x '                                 establish unit tangent
  6.     bnci1 = VecDot(un, ball1.m) '                               ball 1 normal component of input velocity
  7.     bnci2 = VecDot(un, ball2.m) '                               ball 2 normal component of input velocity
  8.     btci1 = VecDot(ut, ball1.m) '                               ball 1 tangent component of input velocity
  9.     btci2 = VecDot(ut, ball2.m) '                               ball 2 tangent component of input velocity
  10.  
  11.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  12.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  13.  
  14.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  15.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  16.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  17.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  18.  
  19.     ball1.x = ncomp1: VecAdd ball1.x, tcomp1, 1 '               add normal and tangent exit vectors
  20.     ball2.x = ncomp2: VecAdd ball2.x, tcomp2, 1 '               add normal and tangent exit vectors
  21.  
  22.     'The following is for graphic representation only and can be removed without affecting the SUB's purpose
  23.     LINE (ball1.p.x, ball1.p.y)-(ball1.p.x + ncomp1.x, ball1.p.y + ncomp1.y), &H9F00FFFF, , &B0011001100110011
  24.     LINE (ball1.p.x, ball1.p.y)-(ball1.p.x + tcomp1.x, ball1.p.y + tcomp1.y), &H9FFF7F7F, , &B0011001100110011
  25.     LINE (ball2.p.x, ball2.p.y)-(ball2.p.x + ncomp2.x, ball2.p.y + ncomp2.y), &H9FFF7F7F, , &B0011001100110011
  26.     LINE (ball2.p.x, ball2.p.y)-(ball2.p.x + tcomp2.x, ball2.p.y + tcomp2.y), &H9F00FFFF, , &B0011001100110011
  27.  
  28. END SUB 'B2BCollision
  29.  
Title: Re: 2D ball collisions without trigonometry.
Post by: STxAxTIC on May 29, 2021, 10:01:44 pm
@STxAxTIC
your notes solve with unit normal vector.  Looks like unit tangent vector is not necessary?

Correct. Tangent vector is superfluous.
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 29, 2021, 11:43:26 pm
OldMoses
I added the 4 lines - looking at it now

STxAxTIC
That is interesting!.   
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on May 30, 2021, 12:38:57 am
STxAxTIC

looking at your notes and there is the key observation

 vector q = magnitude of vector q * unit normal vector (un)

magnitude of vector q is found by

2 (un . vector v1   - un . vector v2)

where vec v1 and vec v2 are not unit vectors.

If that is the correct interpretation then how is vector q used?
Title: Re: 2D ball collisions without trigonometry.
Post by: STxAxTIC on May 30, 2021, 11:18:13 am
@NOVARSEG

Hey - yeah I've been meaning to type those notes for a few years but I'm not "on that chapter" yet (long story). Anyway... I did a fairly-ok job of distinguishing between full vectors (with the arrow) and unit vectors (with the hat). Throughout the whole thing, the vectors v1 and v2 are always full vectors, never unit vectors.

Keep in mind too I use the reduced mass term (greek mu) to calculate q

\mu = (m1 + m2) / (m1 * m2)
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on June 02, 2021, 12:10:05 am
Getting close to a solution. Thanks to everyone on this thread. So much information to distill.

Program shows the normal and tangent vectors as dashed lines.  The code is mess but at least I got some results.  The code still does not show the exit vectors.

Well, there are some ball angles that don't make sense at all.

example
red  @ (0, 0)  along <100, 0>  exits along <0, 0>
cyan @ (-100, 100)  along <-100, 0>  exits along <0, 0>

Does not seem correct.

****************
Looking at STxAxTIC's notes about momentum exchange

****************
Looking at this paper again  https://www.vobarian.com/collisions/2dcollisions2.pdf

I looked at that paper before, but it was of no use to me at the time, because I did not understand the math.


Code: QB64: [Select]
  1. 'Original code by OldMoses
  2. 'Additional code by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   ball position
  12.     m AS V2 '                                                   heading vector
  13.     x AS V2 '                                                   exit vector
  14.     s AS INTEGER '                                              magnitude of movement
  15.  
  16. DIM Nvec(1) AS V2
  17. DIM Tvec(1) AS V2
  18. DIM TEXT(1) AS STRING
  19. DIM RAD(1) AS SINGLE
  20. RAD = 0
  21. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  22. DIM mouse AS V2
  23. DIM b(1) AS ball
  24.  
  25. 'DIM SHARED AS V2 origin
  26. DIM SHARED origin AS V2
  27. origin.x = 0: origin.y = 0
  28. b(0).c = Red: b(0).cn = "red"
  29. b(1).c = Cyan: b(1).cn = "cyan"
  30.  
  31. SCREEN _NEWIMAGE(_DESKTOPWIDTH, _DESKTOPHEIGHT, 32) 'prevents FULLSCREEN distortion
  32.  
  33. ' _SCREENMOVE 10, 10
  34.  
  35. MAG = _DESKTOPHEIGHT / 300 'this 300 is from the grid drawing code.
  36. 'MAG = 4
  37.  
  38. _FULLSCREEN 'gives a full screen - not a window!
  39.  
  40. 'starting state
  41. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  42. RAD(0) = 2 * _PI / 2
  43. b(0).s = PyT(origin, b(0).m)
  44.  
  45. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  46. RAD(1) = 3 * _PI / 2
  47. b(1).s = PyT(origin, b(1).m)
  48.  
  49. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  50. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  51.  
  52.     CLS
  53.     ms = MBS '                                                  process mouse actions dragging endpoints
  54.     IF ms AND 64 THEN
  55.  
  56.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  57.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  58.  
  59.         FOR x = 0 TO 1
  60.             FOR y = 0 TO 1
  61.                 ds! = PyT(vertex(x, y), mouse)
  62.                 IF ds! < ballradius * .5 THEN i = x: j = y
  63.             NEXT y
  64.         NEXT x
  65.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  66.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  67.                 b(i).p = mouse
  68.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  69.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  70.         END SELECT
  71.     END IF
  72.  
  73.     'IF _KEYDOWN(114) THEN i = 0
  74.     'IF _KEYDOWN(99) THEN i = 1
  75.     IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  76.     IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  77.     IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  78.     IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  79.  
  80.     'IF _KEYDOWN(119) THEN b(i).m.y = b(i).m.y - 1
  81.     'IF _KEYDOWN(115) THEN b(i).m.y = b(i).m.y + 1
  82.     'IF _KEYDOWN(97) THEN b(i).m.x = b(i).m.x + 1
  83.     'IF _KEYDOWN(100) THEN b(i).m.x = b(i).m.x - 1
  84.  
  85.     '**************Code added by Novarseg
  86.     'Vector rotation and vector magnitude adjustment using keyboard input
  87.  
  88.     IF f4 = 0 THEN
  89.         f4 = 1
  90.         FOR i = 0 TO 1
  91.  
  92.             b(i).s = PyT(origin, b(i).m)
  93.             b(i).m.y = b(i).s * COS(RAD(i))
  94.             b(i).m.x = b(i).s * SIN(RAD(i))
  95.         NEXT i
  96.         i = 0
  97.     END IF
  98.  
  99.     I$ = INKEY$
  100.  
  101.     _DELAY .04 'allows enough time for keyboard buffer to accumulate characters
  102.     '           so the vector rotation (fast / slow) operates properly
  103.     '           there is probably a better way to do this
  104.     IF I$ = "" THEN f1 = 0
  105.  
  106.     'IF f3 = 0 THEN I$ = "b": f3 = 1
  107.     IF I$ = "b" AND f2 = 0 THEN i = 1: f2 = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  108.     IF I$ = "b" AND f2 = 1 THEN i = 0: f2 = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  109.     LL1:
  110.  
  111.     IF I$ = "c" THEN 'increase vector magnitude
  112.         mult = 1.01
  113.         VecMult b(i).m, mult
  114.     END IF
  115.  
  116.     IF I$ = "v" THEN 'decrease vector magnitude
  117.         div = 1.01
  118.         VecDIV b(i).m, div 'added a new sub
  119.     END IF
  120.  
  121.     IF I$ = "z" THEN 'rotate vector counter clockwise
  122.         IF RAD(i) > _PI * 2 THEN RAD(i) = 0
  123.  
  124.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  125.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) + .05
  126.         IF TIMER - t1 <= 1.5 THEN RAD(i) = RAD(i) + .005
  127.  
  128.  
  129.         b(i).s = PyT(origin, b(i).m)
  130.         b(i).m.y = b(i).s * COS(RAD(i))
  131.         b(i).m.x = b(i).s * SIN(RAD(i))
  132.  
  133.     END IF
  134.  
  135.     IF I$ = "x" THEN 'rotate vector clockwise
  136.         IF RAD(i) < 0 THEN RAD(i) = _PI * 2
  137.  
  138.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  139.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) - .05
  140.         IF TIMER - t1 <= 1.5 THEN RAD(i) = RAD(i) - .005
  141.  
  142.         b(i).s = PyT(origin, b(i).m)
  143.         b(i).m.y = b(i).s * COS(RAD(i))
  144.         b(i).m.x = b(i).s * SIN(RAD(i))
  145.     END IF
  146.     '**************END Code added Novarseg
  147.  
  148.  
  149.     'START OF COLLISION MATHEMATICS SECTION
  150.  
  151.     ballradius = PyT(b(0).p, b(1).p) / 2
  152.  
  153.     FOR bn = 0 TO 1
  154.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  155.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  156.     NEXT bn
  157.  
  158.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  159.     B2BCollision b(0), b(1), Nvec(), Tvec()
  160.     'END OF COLLISION MATHEMATICS SECTION
  161.  
  162.     'graphic representation
  163.     FOR grid = -300 TO 300 STEP 20
  164.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  165.         LINE (grid, 300)-(grid, -300), c& 'Gray  'vertical lines
  166.         LINE (-300, grid)-(300, grid), c& ' Gray  'horizontal lines
  167.     NEXT grid
  168.  
  169.  
  170.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  171.  
  172.     FOR dr = 0 TO 1
  173.  
  174.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , 1 'AR
  175.  
  176.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y + b(dr).m.y), b(dr).c 'incoming
  177.  
  178.         'LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  179.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Nvec(dr).x, b(dr).p.y + Nvec(dr).y), b(dr).c, , &B1111000011110000 'exit vector
  180.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Tvec(dr).x, b(dr).p.y + Tvec(dr).y), b(dr).c, , &B1111000011110000 'exit vector
  181.  
  182.  
  183.  
  184.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  185.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  186.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">" + c(dr)
  187.  
  188.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  189.  
  190.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  191.     NEXT dr
  192.  
  193.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  194.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  195.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  196.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  197.         CLOSE 'NOVARSEG added this line
  198.     END IF 'NOVARSEG added this line
  199.  
  200.     _LIMIT 500
  201.     _DISPLAY
  202.  
  203.  
  204. SUB B2BCollision (ball0 AS ball, ball1 AS ball, Nvec() AS V2, Tvec() AS V2)
  205.  
  206.     ' DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  207.     DIM un AS V2
  208.     DIM ut AS V2
  209.     DIM ncomp1 AS V2
  210.     DIM ncomp2 AS V2
  211.     DIM tcomp1 AS V2
  212.     DIM tcomp2 AS V2
  213.     DIM MAG AS SINGLE
  214.  
  215.     DIM unDOT0 AS SINGLE 'unit normal vector "dot" pre collision heading ball0
  216.     DIM unDOT1 AS SINGLE 'unit normal vector "dot" pre collision heading ball1
  217.     DIM utDOT0 AS SINGLE 'unit tangent vector "dot" pre collision heading ball0
  218.     DIM utDOT1 AS SINGLE ' unit tangent vector "dot" pre collision heading ball1
  219.  
  220.     DIM unVec0 AS V2
  221.     DIM utVec0 AS V2
  222.     DIM unVec1 AS V2
  223.     DIM utVec1 AS V2
  224.     ' DIM nvec(1) AS V2
  225.     ' DIM Tvec(1) AS V2
  226.  
  227.  
  228.     '  GOSUB getUnitNormal uses  ball1.p ball2.p
  229.  
  230.     'calculate unit normql vector from ball positions
  231.     'ball0 = red, ball1 =cyan
  232.     un.x = ball1.p.x - ball0.p.x
  233.     un.y = ball1.p.y - ball0.p.y
  234.     MAG = (un.x ^ 2 + un.y ^ 2) ^ .5
  235.     un.x = un.x / MAG
  236.     un.y = un.y / MAG
  237.  
  238.     'un = ball0.p: VecAdd un, ball1.p, -1: VecNorm un '
  239.     'un = ball1.p
  240.     'VecAdd un, ball1.p, -1
  241.     ' VecNorm un '          establish unit normal
  242.  
  243.  
  244.     ut.x = -un.y: ut.y = un.x '     establish unit tangent
  245.  
  246.  
  247.     unDOT0 = un.x * ball0.m.x + un.y * ball0.m.y 'unit normal vector DOT ball vector
  248.     unDOT1 = un.x * ball1.m.x + un.y * ball1.m.y 'unit normal vector DOT ball vector
  249.     utDOT0 = ut.x * ball0.m.x + ut.y * ball0.m.y 'unit tangent vector DOT ball vector
  250.     utDOT1 = ut.x * ball1.m.x + ut.y * ball1.m.y 'unit tangent vector DOT ball vector
  251.  
  252.  
  253.     'bnci1 = VecDot(un, ball0.m) '
  254.     'bnci2 = VecDot(un, ball1.m) '
  255.     'btci1 = VecDot(ut, ball0.m) '
  256.     'btci2 = VecDot(ut, ball1.m) '
  257.  
  258.     ' bncx1 = bnci2 '                                   compute normal component of ball 1 exit velocity
  259.     ' bncx2 = bnci1 '                                   compute normal component of ball 2 exit velocity
  260.  
  261.  
  262.  
  263.  
  264.     ' ncomp1 = un:
  265.  
  266.     VecMult2 un, unDOT0, unVec0 '    first two parameters are inputs, the 3rd is output vector
  267.     VecMult2 ut, utDOT0, utVec0 '
  268.     VecMult2 un, unDOT1, unVec1 '
  269.     VecMult2 ut, utDOT1, utVec1 '
  270.  
  271.     LOCATE 30, 10: PRINT "unvec0.x"; unVec0.x
  272.     LOCATE 31, 10: PRINT "unvec0.y"; unVec0.y
  273.  
  274.     LOCATE 32, 10: PRINT "utvec0.x"; utVec0.x
  275.     LOCATE 33, 10: PRINT "utvec0.y"; utVec0.y
  276.  
  277.  
  278.     'ncomp1 = un: VecMult ncomp1, bncx2 '
  279.     'tcomp1 = ut: VecMult tcomp1, btci1 '               unit tangent exit vector x tangent component of exit vector
  280.     'ncomp2 = un: VecMult ncomp2, bncx2 '               same for ball2, unit normal...
  281.     'tcomp2 = ut: VecMult tcomp2, btci2 '               same for ball2, unit tangent...
  282.  
  283.     'ball0.x = ncomp1: VecAdd ball0.x, tcomp1, 1 '      add normal and tangent exit vectors
  284.     'ball1.x = ncomp2: VecAdd ball1.x, tcomp2, 1 '      add normal and tangent exit vectors
  285.  
  286.     'VecAdd2 unVec1, utVec1, Nvec(1) '
  287.     'VecAdd2 unVec0, utVec0, Nvec(0) '
  288.     Nvec(0).x = unVec0.x
  289.     Nvec(0).y = unVec0.y
  290.  
  291.     Nvec(1).x = unVec1.x
  292.     Nvec(1).y = unVec1.y
  293.  
  294.     Tvec(1).x = -utVec0.x
  295.     Tvec(1).y = -utVec0.y
  296.  
  297.     Tvec(0).x = -utVec1.x
  298.     Tvec(0).y = -utVec1.y
  299.  
  300.  
  301. END SUB 'B2BCollision
  302.  
  303.  
  304. SUB B2B2Collision (ball0 AS ball, ball1 AS ball)
  305.  
  306.     ' DIM AS V2 un, ut, ncomp1, ncomp2, tcomp1, tcomp2
  307.     DIM un AS V2
  308.     DIM ut AS V2
  309.     DIM ncomp1 AS V2
  310.     DIM ncomp2 AS V2
  311.     DIM tcomp1 AS V2
  312.     DIM tcomp2 AS V2
  313.  
  314.     DIM MAG AS SINGLE
  315.  
  316.     DIM unDOT0 AS SINGLE 'unit normal vector "dot" pre collision heading ball0
  317.     DIM unDOT1 AS SINGLE 'unit normal vector "dot" pre collision heading ball1
  318.     DIM utDOT0 AS SINGLE 'unit tangent vector "dot" pre collision heading ball0
  319.     DIM utDOT1 AS SINGLE ' unit tangent vector "dot" pre collision heading ball1
  320.  
  321.     DIM unVec0 AS V2
  322.     DIM utVec0 AS V2
  323.     DIM unVec1 AS V2
  324.     DIM utVec1 AS V2
  325.  
  326.  
  327.     '  GOSUB getUnitNormal uses  ball1.p ball2.p
  328.  
  329.     'calculate unit normql vector from ball positions
  330.     'ball0 = red, ball1 =cyan
  331.     un.x = ball1.p.x - ball0.p.x
  332.     un.y = ball1.p.y - ball0.p.y
  333.     MAG = (un.x ^ 2 + un.y ^ 2) ^ .5
  334.     un.x = un.x / MAG
  335.     un.y = un.y / MAG
  336.  
  337.  
  338.     ' un = ball2.p: VecAdd un, ball1.p, -1: VecNorm un '          establish unit normal
  339.     ut.x = un.y: ut.y = un.x '                                 establish unit tangent
  340.     bnci1 = VecDot(un, ball0.m) '
  341.     bnci2 = VecDot(un, ball1.m) '
  342.     btci1 = VecDot(ut, ball0.m) '
  343.     btci2 = VecDot(ut, ball1.m) '
  344.  
  345.     bncx1 = bnci2 '                                             compute normal component of ball 1 exit velocity
  346.     bncx2 = bnci1 '                                             compute normal component of ball 2 exit velocity
  347.  
  348.     ncomp1 = un: VecMult ncomp1, bncx1 '                        unit normal exit vector x normal component of exit vector ball1
  349.     tcomp1 = ut: VecMult tcomp1, btci1 '                        unit tangent exit vector x tangent component of exit vector
  350.     ncomp2 = un: VecMult ncomp2, bncx2 '                        same for ball2, unit normal...
  351.     tcomp2 = ut: VecMult tcomp2, btci2 '                        same for ball2, unit tangent...
  352.  
  353.     ball0.x = ncomp1: VecAdd ball0.x, tcomp1, 1 '               add normal and tangent exit vectors
  354.     ball1.x = ncomp2: VecAdd ball1.x, tcomp2, 1 '               add normal and tangent exit vectors
  355.  
  356. END SUB 'B2BCollision
  357.  
  358.  
  359.  
  360. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  361.  
  362.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  363.  
  364.  
  365.  
  366. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  367.     STATIC StartTimer AS _FLOAT
  368.     STATIC ButtonDown AS INTEGER
  369.     STATIC ClickCount AS INTEGER
  370.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  371.     '                          Down longer counts as a HOLD event.
  372.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  373.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  374.         SELECT CASE SGN(_MOUSEWHEEL)
  375.             CASE 1: MBS = MBS OR 512
  376.             CASE -1: MBS = MBS OR 1024
  377.         END SELECT
  378.     WEND
  379.  
  380.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  381.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  382.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  383.  
  384.     IF StartTimer = 0 THEN
  385.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  386.             ButtonDown = 1: StartTimer = TIMER(0.01)
  387.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  388.         ELSEIF _MOUSEBUTTON(2) THEN
  389.             ButtonDown = 2: StartTimer = TIMER(0.01)
  390.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  391.         ELSEIF _MOUSEBUTTON(3) THEN
  392.             ButtonDown = 3: StartTimer = TIMER(0.01)
  393.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  394.         END IF
  395.     ELSE
  396.         BD = ButtonDown MOD 3
  397.         IF BD = 0 THEN BD = 3
  398.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  399.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  400.         ELSE
  401.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  402.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  403.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  404.             ELSE 'We've now started the hold event
  405.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  406.             END IF
  407.         END IF
  408.     END IF
  409.  
  410.  
  411. FUNCTION PyT (var1 AS V2, var2 AS V2)
  412.  
  413.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  414.  
  415.  
  416. FUNCTION bPyT (var1 AS V2, var2 AS V2, var3) 'to calulate ball radius only
  417.     'var1.x = var1.x * 1.6384
  418.     bPyT = _HYPOT((var1.x - var2.x) * var3, (var1.y - var2.y) * var3)
  419.  
  420.  
  421.  
  422.  
  423. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  424.  
  425.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  426.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  427.  
  428. END SUB 'Add_Vector
  429.  
  430. SUB VecAdd2 (var1 AS V2, var2 AS V2, var3 AS V2)
  431.  
  432.     'var3.x = var1.x + var2.x '                           add vector (or a scalar multiple of) var2 to var)
  433.     'var3.y = var1.y + var2.y '                           use var3 = -1 to subtract var2 from var
  434.  
  435.     var3.x = var1.x '+ var2.x '                           add vector (or a scalar multiple of) var2 to var)
  436.     var3.y = var1.y '+ var2.y '                           use var3 = -1 to subtract var2 from var
  437.  
  438. END SUB 'Add_Vector
  439.  
  440.  
  441. FUNCTION VecDot (var AS V2, var2 AS V2)
  442.  
  443.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  444.  
  445. END FUNCTION 'VecDot
  446.  
  447.  
  448. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  449.  
  450.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  451.     vec.y = vec.y * multiplier
  452.  
  453. END SUB 'Vec_Mult
  454.  
  455. SUB VecMult2 (var1 AS V2, var2 AS SINGLE, out1 AS V2)
  456.  
  457.     out1.x = var1.x * var2
  458.     out1.y = var1.y * var2
  459.  
  460. END SUB 'Vec_Mult
  461.  
  462.  
  463.  
  464.  
  465.  
  466.  
  467.  
  468. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  469.  
  470.     vec.x = vec.x / divisor
  471.     vec.y = vec.y / divisor
  472.  
  473. END SUB 'VecDIV
  474.  
  475.  
  476. SUB VecNorm (var AS V2)
  477.  
  478.     m = PyT(origin, var)
  479.     IF m = 0 THEN
  480.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  481.     ELSE
  482.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  483.     END IF
  484.  
  485. END SUB 'VecNorm
  486.  
  487.  
  488.  
Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on June 02, 2021, 11:14:25 pm
The code seems to work now. Have a look.

Math from https://www.vobarian.com/collisions/2dcollisions2.pdf

Again much thanks to everyone on this thread.

Code: QB64: [Select]
  1.  'Original code by OldMoses
  2. 'Additional code by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   ball position
  12.     m AS V2 '                                                   heading vector
  13.     x AS V2 '                                                   exit vector
  14.     s AS INTEGER '                                              magnitude of movement
  15.  
  16. DIM Nvec(1) AS V2 'normal velocity vectors
  17. DIM Tvec(1) AS V2 'tangential velocity vectors
  18. DIM Fvec(1) AS V2 'Final or exit vectors
  19.  
  20. DIM TEXT(1) AS STRING
  21. DIM RAD(1) AS SINGLE
  22. RAD = 0
  23. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  24. DIM mouse AS V2
  25. DIM b(1) AS ball
  26.  
  27. 'DIM SHARED AS V2 origin
  28. DIM SHARED origin AS V2
  29. origin.x = 0: origin.y = 0
  30. b(0).c = Red: b(0).cn = "red"
  31. b(1).c = Cyan: b(1).cn = "cyan"
  32.  
  33. SCREEN _NEWIMAGE(_DESKTOPWIDTH, _DESKTOPHEIGHT, 32) 'prevents FULLSCREEN distortion
  34.  
  35. ' _SCREENMOVE 10, 10
  36.  
  37. MAG = _DESKTOPHEIGHT / 300 'this 300 is from the grid drawing code.
  38. 'MAG = 4
  39.  
  40. _FULLSCREEN 'gives a full screen - not a window!
  41.  
  42. 'starting state
  43. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  44. RAD(0) = 2 * _PI / 2
  45. b(0).s = PyT(origin, b(0).m)
  46.  
  47. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  48. RAD(1) = 3 * _PI / 2
  49. b(1).s = PyT(origin, b(1).m)
  50.  
  51. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  52. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  53.  
  54.     CLS
  55.     ms = MBS '                                                  process mouse actions dragging endpoints
  56.     IF ms AND 64 THEN
  57.  
  58.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  59.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  60.  
  61.         FOR x = 0 TO 1
  62.             FOR y = 0 TO 1
  63.                 ds! = PyT(vertex(x, y), mouse)
  64.                 IF ds! < ballradius * .5 THEN i = x: j = y
  65.             NEXT y
  66.         NEXT x
  67.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  68.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  69.                 b(i).p = mouse
  70.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  71.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  72.         END SELECT
  73.     END IF
  74.  
  75.     'IF _KEYDOWN(114) THEN i = 0
  76.     'IF _KEYDOWN(99) THEN i = 1
  77.     IF _KEYDOWN(18432) THEN b(i).p.y = b(i).p.y + 1
  78.     IF _KEYDOWN(20480) THEN b(i).p.y = b(i).p.y - 1
  79.     IF _KEYDOWN(19200) THEN b(i).p.x = b(i).p.x - 1
  80.     IF _KEYDOWN(19712) THEN b(i).p.x = b(i).p.x + 1
  81.  
  82.     'IF _KEYDOWN(119) THEN b(i).m.y = b(i).m.y - 1
  83.     'IF _KEYDOWN(115) THEN b(i).m.y = b(i).m.y + 1
  84.     'IF _KEYDOWN(97) THEN b(i).m.x = b(i).m.x + 1
  85.     'IF _KEYDOWN(100) THEN b(i).m.x = b(i).m.x - 1
  86.  
  87.     '**************Code added by Novarseg
  88.     'Vector rotation and vector magnitude adjustment using keyboard input
  89.  
  90.     IF f4 = 0 THEN
  91.         f4 = 1
  92.         FOR i = 0 TO 1
  93.  
  94.             b(i).s = PyT(origin, b(i).m)
  95.             b(i).m.y = b(i).s * COS(RAD(i))
  96.             b(i).m.x = b(i).s * SIN(RAD(i))
  97.         NEXT i
  98.         i = 0
  99.     END IF
  100.  
  101.     I$ = INKEY$
  102.  
  103.     _DELAY .04 'allows enough time for keyboard buffer to accumulate characters
  104.     '           so the vector rotation (fast / slow) operates properly
  105.     '           there is probably a better way to do this
  106.     IF I$ = "" THEN f1 = 0
  107.  
  108.     'IF f3 = 0 THEN I$ = "b": f3 = 1
  109.     IF I$ = "b" AND f2 = 0 THEN i = 1: f2 = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  110.     IF I$ = "b" AND f2 = 1 THEN i = 0: f2 = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  111.     LL1:
  112.  
  113.     IF I$ = "c" THEN 'increase vector magnitude
  114.         mult = 1.01
  115.         VecMult b(i).m, mult
  116.     END IF
  117.  
  118.     IF I$ = "v" THEN 'decrease vector magnitude
  119.         div = 1.01
  120.         VecDIV b(i).m, div 'added a new sub
  121.     END IF
  122.  
  123.     IF I$ = "z" THEN 'rotate vector counter clockwise
  124.         IF RAD(i) > _PI * 2 THEN RAD(i) = 0
  125.  
  126.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  127.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) + .05
  128.         IF TIMER - t1 <= 1.5 THEN RAD(i) = RAD(i) + .005
  129.  
  130.  
  131.         b(i).s = PyT(origin, b(i).m)
  132.         b(i).m.y = b(i).s * COS(RAD(i))
  133.         b(i).m.x = b(i).s * SIN(RAD(i))
  134.  
  135.     END IF
  136.  
  137.     IF I$ = "x" THEN 'rotate vector clockwise
  138.         IF RAD(i) < 0 THEN RAD(i) = _PI * 2
  139.  
  140.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  141.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) - .05
  142.         IF TIMER - t1 <= 1.5 THEN RAD(i) = RAD(i) - .005
  143.  
  144.         b(i).s = PyT(origin, b(i).m)
  145.         b(i).m.y = b(i).s * COS(RAD(i))
  146.         b(i).m.x = b(i).s * SIN(RAD(i))
  147.     END IF
  148.     '**************END Code added Novarseg
  149.  
  150.  
  151.     'START OF COLLISION MATHEMATICS SECTION
  152.  
  153.     ballradius = PyT(b(0).p, b(1).p) / 2
  154.  
  155.     FOR bn = 0 TO 1
  156.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  157.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  158.     NEXT bn
  159.  
  160.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  161.     B2BCollision b(0), b(1), Nvec(), Tvec(), Fvec()
  162.     'END OF COLLISION MATHEMATICS SECTION
  163.  
  164.     'graphic representation
  165.     FOR grid = -300 TO 300 STEP 20
  166.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  167.         LINE (grid, 300)-(grid, -300), c& 'Gray  'vertical lines
  168.         LINE (-300, grid)-(300, grid), c& ' Gray  'horizontal lines
  169.     NEXT grid
  170.  
  171.  
  172.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  173.  
  174.     FOR dr = 0 TO 1
  175.  
  176.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , 1 'AR
  177.  
  178.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y + b(dr).m.y), b(dr).c 'incoming
  179.  
  180.         'LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).x.x, b(dr).p.y + b(dr).x.y), b(dr).c, , &B1111000011110000 'exit vector
  181.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Fvec(dr).x, b(dr).p.y + Fvec(dr).y), b(dr).c, , &B1111000011110000 'exit vector
  182.  
  183.         ' LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Nvec(dr).x, b(dr).p.y + Nvec(dr).y), b(dr).c, , &B1111000011110000 'exit vector
  184.         ' LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Tvec(dr).x, b(dr).p.y + Tvec(dr).y), b(dr).c, , &B1111000011110000 'exit vector
  185.  
  186.  
  187.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  188.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  189.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(b(dr).x.x))) + ", " + _TRIM$(STR$(INT(b(dr).x.y))) + ">" + c(dr)
  190.  
  191.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  192.  
  193.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  194.     NEXT dr
  195.  
  196.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  197.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  198.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  199.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  200.         CLOSE 'NOVARSEG added this line
  201.     END IF 'NOVARSEG added this line
  202.  
  203.     _LIMIT 500
  204.     _DISPLAY
  205.  
  206.  
  207. SUB B2BCollision (ball0 AS ball, ball1 AS ball, Nvec() AS V2, Tvec() AS V2, Fvec() AS V2)
  208.  
  209.     DIM un AS V2
  210.     DIM ut AS V2
  211.     DIM MAG AS SINGLE
  212.  
  213.     DIM unDOT0 AS SINGLE 'unit normal vector "dot" pre collision heading ball0
  214.     DIM unDOT1 AS SINGLE 'unit normal vector "dot" pre collision heading ball1
  215.     DIM utDOT0 AS SINGLE 'unit tangent vector "dot" pre collision heading ball0
  216.     DIM utDOT1 AS SINGLE 'unit tangent vector "dot" pre collision heading ball1
  217.  
  218.     DIM unVec0 AS V2
  219.     DIM utVec0 AS V2
  220.     DIM unVec1 AS V2
  221.     DIM utVec1 AS V2
  222.  
  223.     'calculate unit normql vector from ball positions
  224.     'ball0 = red, ball1 =cyan
  225.     un.x = ball1.p.x - ball0.p.x
  226.     un.y = ball1.p.y - ball0.p.y
  227.     MAG = (un.x ^ 2 + un.y ^ 2) ^ .5
  228.     un.x = un.x / MAG
  229.     un.y = un.y / MAG
  230.  
  231.     ut.x = -un.y: ut.y = un.x '     establish unit tangent
  232.  
  233.     unDOT0 = un.x * ball0.m.x + un.y * ball0.m.y 'unit normal vector DOT ball vector
  234.     unDOT1 = un.x * ball1.m.x + un.y * ball1.m.y 'unit normal vector DOT ball vector
  235.     utDOT0 = ut.x * ball0.m.x + ut.y * ball0.m.y 'unit tangent vector DOT ball vector
  236.     utDOT1 = ut.x * ball1.m.x + ut.y * ball1.m.y 'unit tangent vector DOT ball vector
  237.  
  238.     'swap "unit normal" scaled ball vectors (pre collision)   according to https://www.vobarian.com/collisions/2dcollisions2.pdf
  239.     A = unDOT0
  240.     B = unDOT1
  241.     unDOT0 = B: unDOT1 = A
  242.  
  243.     'Convert the scalar normal and tangential velocities into vectors
  244.     'according to https://www.vobarian.com/collisions/2dcollisions2.pdf
  245.     VecMult2 un, unDOT0, unVec0 '  third parameter is output
  246.     VecMult2 ut, utDOT0, utVec0 '  third parameter is output
  247.     VecMult2 un, unDOT1, unVec1 '  third parameter is output
  248.     VecMult2 ut, utDOT1, utVec1 '  third parameter is output
  249.  
  250.     VecAdd2 unVec1, utVec1, Fvec(1) 'vector addition to calculate final ball vectors
  251.     VecAdd2 unVec0, utVec0, Fvec(0) 'vector addition to calculate final ball vectors
  252.  
  253.     Nvec(0).x = -unVec0.x 'not necessary code - for display only
  254.     Nvec(0).y = -unVec0.y 'not necessary code - for display only
  255.  
  256.     Nvec(1).x = -unVec1.x 'not necessary code - for display only
  257.     Nvec(1).y = -unVec1.y 'not necessary code - for display only
  258.  
  259.     Tvec(0).x = -utVec0.x 'not necessary code - for display only
  260.     Tvec(0).y = -utVec0.y 'not necessary code - for display only
  261.  
  262.     Tvec(1).x = -utVec1.x 'not necessary code - for display only
  263.     Tvec(1).y = -utVec1.y 'not necessary code - for display only
  264.  
  265. END SUB 'B2BCollision
  266.  
  267.  
  268. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  269.  
  270.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  271.  
  272.  
  273.  
  274. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  275.     STATIC StartTimer AS _FLOAT
  276.     STATIC ButtonDown AS INTEGER
  277.     STATIC ClickCount AS INTEGER
  278.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  279.     '                          Down longer counts as a HOLD event.
  280.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  281.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  282.         SELECT CASE SGN(_MOUSEWHEEL)
  283.             CASE 1: MBS = MBS OR 512
  284.             CASE -1: MBS = MBS OR 1024
  285.         END SELECT
  286.     WEND
  287.  
  288.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  289.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  290.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  291.  
  292.     IF StartTimer = 0 THEN
  293.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  294.             ButtonDown = 1: StartTimer = TIMER(0.01)
  295.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  296.         ELSEIF _MOUSEBUTTON(2) THEN
  297.             ButtonDown = 2: StartTimer = TIMER(0.01)
  298.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  299.         ELSEIF _MOUSEBUTTON(3) THEN
  300.             ButtonDown = 3: StartTimer = TIMER(0.01)
  301.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  302.         END IF
  303.     ELSE
  304.         BD = ButtonDown MOD 3
  305.         IF BD = 0 THEN BD = 3
  306.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  307.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  308.         ELSE
  309.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  310.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  311.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  312.             ELSE 'We've now started the hold event
  313.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  314.             END IF
  315.         END IF
  316.     END IF
  317.  
  318.  
  319. FUNCTION PyT (var1 AS V2, var2 AS V2)
  320.  
  321.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  322.  
  323.  
  324. FUNCTION bPyT (var1 AS V2, var2 AS V2, var3) 'to calulate ball radius only
  325.  
  326.     bPyT = _HYPOT((var1.x - var2.x) * var3, (var1.y - var2.y) * var3)
  327.  
  328.  
  329.  
  330.  
  331. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  332.  
  333.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  334.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  335.  
  336. END SUB 'Add_Vector
  337.  
  338. SUB VecAdd2 (var1 AS V2, var2 AS V2, var3 AS V2)
  339.  
  340.     var3.x = -var1.x + -var2.x '
  341.     var3.y = -var1.y + -var2.y '
  342.  
  343. END SUB 'Add_Vector
  344.  
  345.  
  346. FUNCTION VecDot (var AS V2, var2 AS V2)
  347.  
  348.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  349.  
  350. END FUNCTION 'VecDot
  351.  
  352.  
  353. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  354.  
  355.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  356.     vec.y = vec.y * multiplier
  357.  
  358. END SUB 'Vec_Mult
  359.  
  360. SUB VecMult2 (var1 AS V2, var2 AS SINGLE, out1 AS V2)
  361.  
  362.     out1.x = var1.x * var2
  363.     out1.y = var1.y * var2
  364.  
  365. END SUB 'Vec_Mult
  366.  
  367. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  368.  
  369.     vec.x = vec.x / divisor
  370.     vec.y = vec.y / divisor
  371.  
  372. END SUB 'VecDIV
  373.  
  374.  
  375. SUB VecNorm (var AS V2)
  376.  
  377.     m = PyT(origin, var)
  378.     IF m = 0 THEN
  379.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  380.     ELSE
  381.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  382.     END IF
  383.  
  384. END SUB 'VecNorm
  385.  

The previous code was  annoying when rotating the vectors. The vector kept rotating when the key was released.  fixed with 3 speed rotation now.

Keys to rotate vector are z or x
keys to adjust vector magnitude (ball velocity) are c or v
Key to toggle red or cyan ball is b

As in the original code, the arrow keys move the ball positions.

rotate vector has an auto  - fine / medium / coarse control.   hold down key longer to obtain effect.

Code: QB64: [Select]
  1. 'Original code and concept by OldMoses
  2. 'additional code and mods by Novarseg
  3.  
  4. TYPE V2
  5.     x AS SINGLE
  6.     y AS SINGLE
  7.  
  8. TYPE ball
  9.     cn AS STRING * 4 '                                          ball name by color
  10.     c AS _UNSIGNED LONG '                                       color
  11.     p AS V2 '                                                   ball position
  12.     m AS V2 '                                                   heading vector
  13.     x AS V2 '                                                   exit vector
  14.     s AS INTEGER '                                              magnitude of movement
  15.  
  16. DIM Nvec(1) AS V2 'normal velocity vectors
  17. DIM Tvec(1) AS V2 'tangential velocity vectors
  18. DIM Fvec(1) AS V2 'Final or exit vectors
  19.  
  20. DIM TEXT(1) AS STRING
  21. DIM RAD(1) AS SINGLE
  22.  
  23.  
  24. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  25. DIM mouse AS V2
  26. DIM b(1) AS ball
  27.  
  28. 'DIM SHARED AS V2 origin
  29. DIM SHARED origin AS V2
  30. origin.x = 0: origin.y = 0
  31. b(0).c = Red: b(0).cn = "red"
  32. b(1).c = Cyan: b(1).cn = "cyan"
  33.  
  34. SCREEN _NEWIMAGE(_DESKTOPWIDTH, _DESKTOPHEIGHT, 32) 'prevents FULLSCREEN distortion
  35.  
  36. MAG = _DESKTOPHEIGHT / 300 'this 300 is from the grid drawing code.
  37.  
  38.  
  39. _FULLSCREEN 'gives a full screen - not a window!
  40. 'advantage:  only one position - the screen!
  41. 'disadvantage: can't be minimized - or can it?
  42.  
  43. 'starting state
  44. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  45. RAD(0) = 2 * _PI / 2
  46. b(0).s = PyT(origin, b(0).m)
  47.  
  48. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  49. RAD(1) = 3 * _PI / 2
  50. b(1).s = PyT(origin, b(1).m)
  51.  
  52. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  53. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  54.  
  55. f4 = 0 'initial ball select flag
  56. f5 = 0 'inital ball display flag
  57.  
  58.  
  59.     CLS
  60.     ms = MBS '                                                  process mouse actions dragging endpoints
  61.     IF ms AND 64 THEN
  62.  
  63.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  64.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  65.  
  66.         FOR x = 0 TO 1
  67.             FOR y = 0 TO 1
  68.                 ds! = PyT(vertex(x, y), mouse)
  69.                 IF ds! < ballradius * .5 THEN i = x: j = y
  70.             NEXT y
  71.         NEXT x
  72.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  73.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  74.                 b(i).p = mouse
  75.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  76.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  77.         END SELECT
  78.     END IF
  79.  
  80.     IF kh = 18432 THEN b(i).p.y = b(i).p.y + 1
  81.     IF kh = 20480 THEN b(i).p.y = b(i).p.y - 1
  82.     IF kh = 19200 THEN b(i).p.x = b(i).p.x - 1
  83.     IF kh = 19712 THEN b(i).p.x = b(i).p.x + 1
  84.  
  85.  
  86.     'Vector rotation and vector magnitude adjustment using keyboard input
  87.  
  88.     IF f5 = 0 THEN
  89.         f5 = 1
  90.         FOR i = 0 TO 1
  91.             b(i).s = PyT(origin, b(i).m)
  92.             b(i).m.y = b(i).s * COS(RAD(i))
  93.             b(i).m.x = b(i).s * SIN(RAD(i))
  94.         NEXT i
  95.         i = 1
  96.     END IF
  97.  
  98.     IF kh = 122 OR kh = 120 THEN f3 = 1
  99.  
  100.     IF (kh = -122 OR kh = -120) AND f3 = 1 THEN f1 = 0: f3 = 0
  101.  
  102.     IF f4 = 0 THEN kh = 98: f4 = 1
  103.     IF kh = 98 AND f2 = 0 THEN i = 1: f2 = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  104.     IF kh = 98 AND f2 = 1 THEN i = 0: f2 = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  105.     LL1:
  106.  
  107.     IF kh = 99 THEN 'increase vector magnitude  press c
  108.         mult = 1.01
  109.         VecMult b(i).m, mult
  110.     END IF
  111.  
  112.     IF kh = 118 THEN 'decrease vector magnitude     press v
  113.         div = 1.01
  114.         VecDIV b(i).m, div 'added a new sub
  115.     END IF
  116.  
  117.     IF kh = 122 THEN 'rotate vector counter clockwise  'press z
  118.         IF RAD(i) > _PI * 2 THEN RAD(i) = 0
  119.  
  120.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  121.  
  122.         IF TIMER - t1 <= 1 THEN RAD(i) = RAD(i) + .005
  123.         IF TIMER - t1 > 1 THEN RAD(i) = RAD(i) + .020
  124.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) + .05
  125.  
  126.         b(i).s = PyT(origin, b(i).m)
  127.         b(i).m.y = b(i).s * COS(RAD(i))
  128.         b(i).m.x = b(i).s * SIN(RAD(i))
  129.  
  130.     END IF
  131.  
  132.     IF kh = 120 THEN 'rotate vector clockwise   'press x
  133.         IF RAD(i) < 0 THEN RAD(i) = _PI * 2
  134.  
  135.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  136.  
  137.         IF TIMER - t1 <= 1 THEN RAD(i) = RAD(i) - .005
  138.         IF TIMER - t1 > 1 THEN RAD(i) = RAD(i) - .020
  139.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) - .05
  140.  
  141.         b(i).s = PyT(origin, b(i).m)
  142.         b(i).m.y = b(i).s * COS(RAD(i))
  143.         b(i).m.x = b(i).s * SIN(RAD(i))
  144.     END IF
  145.  
  146.     'START OF COLLISION MATHEMATICS SECTION
  147.  
  148.     ballradius = PyT(b(0).p, b(1).p) / 2
  149.  
  150.     FOR bn = 0 TO 1
  151.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  152.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  153.     NEXT bn
  154.  
  155.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  156.     B2BCollision b(0), b(1), Nvec(), Tvec(), Fvec()
  157.     'END OF COLLISION MATHEMATICS SECTION
  158.  
  159.     'graphic representation
  160.     FOR grid = -300 TO 300 STEP 20
  161.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  162.         LINE (grid, 300)-(grid, -300), c& 'Gray  'vertical lines
  163.         LINE (-300, grid)-(300, grid), c& ' Gray  'horizontal lines
  164.     NEXT grid
  165.  
  166.  
  167.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  168.  
  169.     FOR dr = 0 TO 1
  170.  
  171.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , 1 'AR
  172.  
  173.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y + b(dr).m.y), b(dr).c 'incoming
  174.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Fvec(dr).x, b(dr).p.y + Fvec(dr).y), b(dr).c, , &B1111000011110000 'exit vector
  175.  
  176.         ' LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Nvec(dr).x, b(dr).p.y + Nvec(dr).y), b(dr).c, , &B1111000011110000 ' for testing
  177.         ' LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Tvec(dr).x, b(dr).p.y + Tvec(dr).y), b(dr).c, , &B1111000011110000 ' for testing
  178.  
  179.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  180.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  181.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(Fvec(dr).x))) + ", " + _TRIM$(STR$(INT(Fvec(dr).y))) + ">" + c(dr)
  182.  
  183.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  184.  
  185.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  186.     NEXT dr
  187.  
  188.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  189.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  190.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  191.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  192.         CLOSE 'NOVARSEG added this line
  193.     END IF 'NOVARSEG added this line
  194.  
  195.     _DISPLAY
  196.  
  197.     DO
  198.         _LIMIT 500
  199.         kh = _KEYHIT
  200.         IF kh > 0 OR kh < 0 THEN EXIT DO
  201.     LOOP
  202.  
  203.  
  204.  
  205.  
  206. SUB B2BCollision (ball0 AS ball, ball1 AS ball, Nvec() AS V2, Tvec() AS V2, Fvec() AS V2)
  207.  
  208.     DIM un AS V2
  209.     DIM ut AS V2
  210.     DIM MAG AS SINGLE
  211.  
  212.     DIM unDOT0 AS SINGLE 'unit normal vector "dot" pre collision heading ball0
  213.     DIM unDOT1 AS SINGLE 'unit normal vector "dot" pre collision heading ball1
  214.     DIM utDOT0 AS SINGLE 'unit tangent vector "dot" pre collision heading ball0
  215.     DIM utDOT1 AS SINGLE 'unit tangent vector "dot" pre collision heading ball1
  216.  
  217.     DIM unVec0 AS V2
  218.     DIM utVec0 AS V2
  219.     DIM unVec1 AS V2
  220.     DIM utVec1 AS V2
  221.  
  222.     'calculate unit normql vector from ball positions
  223.     'ball0 = red, ball1 =cyan
  224.     un.x = ball1.p.x - ball0.p.x
  225.     un.y = ball1.p.y - ball0.p.y
  226.     MAG = (un.x ^ 2 + un.y ^ 2) ^ .5
  227.     un.x = un.x / MAG
  228.     un.y = un.y / MAG
  229.  
  230.     ut.x = -un.y: ut.y = un.x '     establish unit tangent
  231.  
  232.     unDOT0 = un.x * ball0.m.x + un.y * ball0.m.y 'unit normal vector DOT ball vector
  233.     unDOT1 = un.x * ball1.m.x + un.y * ball1.m.y 'unit normal vector DOT ball vector
  234.     utDOT0 = ut.x * ball0.m.x + ut.y * ball0.m.y 'unit tangent vector DOT ball vector
  235.     utDOT1 = ut.x * ball1.m.x + ut.y * ball1.m.y 'unit tangent vector DOT ball vector
  236.  
  237.     'swap "unit normal" scaled ball vectors (pre collision)   according to https://www.vobarian.com/collisions/2dcollisions2.pdf
  238.     A = unDOT0
  239.     B = unDOT1
  240.     unDOT0 = B: unDOT1 = A
  241.  
  242.     'Convert the scalar normal and tangential velocities into vectors
  243.     'according to https://www.vobarian.com/collisions/2dcollisions2.pdf
  244.     VecMult2 un, unDOT0, unVec0 '  third parameter is output
  245.     VecMult2 ut, utDOT0, utVec0 '  third parameter is output
  246.     VecMult2 un, unDOT1, unVec1 '  third parameter is output
  247.     VecMult2 ut, utDOT1, utVec1 '  third parameter is output
  248.  
  249.     VecAdd2 unVec1, utVec1, Fvec(1) 'vector addition to calculate final ball vectors
  250.     VecAdd2 unVec0, utVec0, Fvec(0) 'vector addition to calculate final ball vectors
  251.  
  252.     Nvec(0).x = -unVec0.x 'not necessary code - for display only
  253.     Nvec(0).y = -unVec0.y 'not necessary code - for display only
  254.  
  255.     Nvec(1).x = -unVec1.x 'not necessary code - for display only
  256.     Nvec(1).y = -unVec1.y 'not necessary code - for display only
  257.  
  258.     Tvec(0).x = -utVec0.x 'not necessary code - for display only
  259.     Tvec(0).y = -utVec0.y 'not necessary code - for display only
  260.  
  261.     Tvec(1).x = -utVec1.x 'not necessary code - for display only
  262.     Tvec(1).y = -utVec1.y 'not necessary code - for display only
  263.  
  264. END SUB 'B2BCollision
  265.  
  266.  
  267. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  268.  
  269.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  270.  
  271.  
  272.  
  273. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  274.     STATIC StartTimer AS _FLOAT
  275.     STATIC ButtonDown AS INTEGER
  276.     STATIC ClickCount AS INTEGER
  277.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  278.     '                          Down longer counts as a HOLD event.
  279.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  280.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  281.         SELECT CASE SGN(_MOUSEWHEEL)
  282.             CASE 1: MBS = MBS OR 512
  283.             CASE -1: MBS = MBS OR 1024
  284.         END SELECT
  285.     WEND
  286.  
  287.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  288.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  289.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  290.  
  291.     IF StartTimer = 0 THEN
  292.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  293.             ButtonDown = 1: StartTimer = TIMER(0.01)
  294.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  295.         ELSEIF _MOUSEBUTTON(2) THEN
  296.             ButtonDown = 2: StartTimer = TIMER(0.01)
  297.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  298.         ELSEIF _MOUSEBUTTON(3) THEN
  299.             ButtonDown = 3: StartTimer = TIMER(0.01)
  300.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  301.         END IF
  302.     ELSE
  303.         BD = ButtonDown MOD 3
  304.         IF BD = 0 THEN BD = 3
  305.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  306.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  307.         ELSE
  308.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  309.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  310.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  311.             ELSE 'We've now started the hold event
  312.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  313.             END IF
  314.         END IF
  315.     END IF
  316.  
  317.  
  318. FUNCTION PyT (var1 AS V2, var2 AS V2)
  319.  
  320.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  321.  
  322.  
  323. FUNCTION bPyT (var1 AS V2, var2 AS V2, var3) 'to calulate ball radius only
  324.  
  325.     bPyT = _HYPOT((var1.x - var2.x) * var3, (var1.y - var2.y) * var3)
  326.  
  327.  
  328.  
  329.  
  330. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  331.  
  332.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  333.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  334.  
  335. END SUB 'Add_Vector
  336.  
  337. SUB VecAdd2 (var1 AS V2, var2 AS V2, var3 AS V2)
  338.  
  339.     var3.x = -var1.x + -var2.x '
  340.     var3.y = -var1.y + -var2.y '
  341.  
  342. END SUB 'Add_Vector
  343.  
  344.  
  345. FUNCTION VecDot (var AS V2, var2 AS V2)
  346.  
  347.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  348.  
  349. END FUNCTION 'VecDot
  350.  
  351.  
  352. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  353.  
  354.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  355.     vec.y = vec.y * multiplier
  356.  
  357. END SUB 'Vec_Mult
  358.  
  359. SUB VecMult2 (var1 AS V2, var2 AS SINGLE, out1 AS V2)
  360.  
  361.     out1.x = var1.x * var2
  362.     out1.y = var1.y * var2
  363.  
  364. END SUB 'Vec_Mult
  365.  
  366. SUB VecDIV (vec AS V2, divisor AS SINGLE) 'added by Novarseg
  367.  
  368.     vec.x = vec.x / divisor
  369.     vec.y = vec.y / divisor
  370.  
  371. END SUB 'VecDIV
  372.  
  373.  
  374. SUB VecNorm (var AS V2)
  375.  
  376.     m = PyT(origin, var)
  377.     IF m = 0 THEN
  378.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  379.     ELSE
  380.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  381.     END IF
  382.  
  383. END SUB 'VecNorm
  384.  


Title: Re: 2D ball collisions without trigonometry.
Post by: NOVARSEG on June 06, 2021, 10:50:02 pm
Got the math from STxAxTIC's notes working

In the following code there are 2 subs

B2B_Collision   which is based on math from STxAxTIC's notes

B2BCollision   which is based on math from  https://www.vobarian.com/collisions/2dcollisions2.pdf

Both subs produce the same final (exit) vectors

Code: QB64: [Select]
  1. TYPE V2
  2.     x AS SINGLE
  3.     y AS SINGLE
  4.  
  5. TYPE ball
  6.     cn AS STRING * 4 '                                          ball name by color
  7.     c AS _UNSIGNED LONG '                                       color
  8.     p AS V2 '                                                   ball position
  9.     m AS V2 '                                                   heading vector
  10.     x AS V2 '                                                   exit vector
  11.     s AS INTEGER '                                              magnitude of movement
  12.  
  13. DIM Nvec(1) AS V2 'normal velocity vectors
  14. DIM Tvec(1) AS V2 'tangential velocity vectors
  15. DIM Fvec(1) AS V2 'Final or exit vectors
  16.  
  17. DIM TEXT(1) AS STRING
  18. DIM RAD(1) AS SINGLE
  19.  
  20.  
  21. DIM vertex(1, 1) AS V2 '                                        mouse grabbing handles
  22. DIM mouse AS V2
  23. DIM b(1) AS ball
  24.  
  25. 'DIM SHARED AS V2 origin
  26. DIM SHARED origin AS V2
  27. origin.x = 0: origin.y = 0
  28. b(0).c = Red: b(0).cn = "red"
  29. b(1).c = Cyan: b(1).cn = "cyan"
  30.  
  31. SCREEN _NEWIMAGE(_DESKTOPWIDTH, _DESKTOPHEIGHT, 32) 'prevents FULLSCREEN distortion
  32.  
  33. MAG = _DESKTOPHEIGHT / 300 'this 300 is from the grid drawing code.
  34.  
  35.  
  36. _FULLSCREEN 'gives a full screen - not a window!
  37. 'advantage:  only one position - the screen!
  38. 'disadvantage: can't be minimized - or can it?
  39.  
  40. 'starting state
  41. b(0).m.x = 0: b(0).m.y = 100 '    reds approach vector
  42. RAD(0) = 2 * _PI / 2
  43. b(0).s = PyT(origin, b(0).m)
  44.  
  45. b(1).m.x = -100: b(1).m.y = 0 '   cyans approach vector
  46. RAD(1) = 3 * _PI / 2
  47. b(1).s = PyT(origin, b(1).m)
  48.  
  49. b(0).p.x = 0: b(0).p.y = 0 '     ball position x, y
  50. b(1).p.x = -100: b(1).p.y = 100 'ball position x, y
  51.  
  52. f4 = 0 'initial ball select flag
  53. f5 = 0 'inital ball display flag
  54.  
  55.  
  56.     CLS
  57.     ms = MBS '                                                  process mouse actions dragging endpoints
  58.     IF ms AND 64 THEN
  59.  
  60.         mouse.x = map!(_MOUSEX, 0, 599, -300, 300)
  61.         mouse.y = map!(_MOUSEY, 0, 599, 300, -300)
  62.  
  63.         FOR x = 0 TO 1
  64.             FOR y = 0 TO 1
  65.                 ds! = PyT(vertex(x, y), mouse)
  66.                 IF ds! < ballradius * .5 THEN i = x: j = y
  67.             NEXT y
  68.         NEXT x
  69.         SELECT CASE j '                                         grabbing impact position or start of incoming vector
  70.             CASE IS = 0 '                                       impact position- here we use mouse as the new b(#).p
  71.                 b(i).p = mouse
  72.             CASE IS = 1 '                                       starting point- here we obtain the b(#).m mathematically
  73.                 b(i).m = b(i).p: VecAdd b(i).m, mouse, -1
  74.         END SELECT
  75.     END IF
  76.  
  77.     IF kh = 18432 THEN b(i).p.y = b(i).p.y + 1
  78.     IF kh = 20480 THEN b(i).p.y = b(i).p.y - 1
  79.     IF kh = 19200 THEN b(i).p.x = b(i).p.x - 1
  80.     IF kh = 19712 THEN b(i).p.x = b(i).p.x + 1
  81.  
  82.  
  83.     'Vector rotation and vector magnitude adjustment using keyboard input
  84.  
  85.     IF f5 = 0 THEN
  86.         f5 = 1
  87.         FOR i = 0 TO 1
  88.             b(i).s = PyT(origin, b(i).m)
  89.             b(i).m.y = b(i).s * COS(RAD(i))
  90.             b(i).m.x = b(i).s * SIN(RAD(i))
  91.         NEXT i
  92.         i = 1
  93.     END IF
  94.  
  95.     IF kh = 122 OR kh = 120 THEN f3 = 1
  96.  
  97.     IF (kh = -122 OR kh = -120) AND f3 = 1 THEN f1 = 0: f3 = 0
  98.  
  99.     IF f4 = 0 THEN kh = 98: f4 = 1
  100.     IF kh = 98 AND f2 = 0 THEN i = 1: f2 = 1: c(1) = "  SELECTED": c(0) = "           ": GOTO LL1 'cyan
  101.     IF kh = 98 AND f2 = 1 THEN i = 0: f2 = 0: c(0) = "  SELECTED": c(1) = "           " 'red
  102.     LL1:
  103.  
  104.     IF kh = 99 THEN 'increase vector magnitude  press c
  105.         mult = 1.01
  106.         VecMult b(i).m, mult
  107.     END IF
  108.  
  109.     IF kh = 118 THEN 'decrease vector magnitude     press v
  110.         div = 1.01
  111.         VecDIV b(i).m, div 'added a new sub
  112.     END IF
  113.  
  114.     IF kh = 122 THEN 'rotate vector counter clockwise  'press z
  115.         IF RAD(i) > _PI * 2 THEN RAD(i) = 0
  116.  
  117.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  118.  
  119.         IF TIMER - t1 <= 1 THEN RAD(i) = RAD(i) + .005
  120.         IF TIMER - t1 > 1 THEN RAD(i) = RAD(i) + .020
  121.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) + .05
  122.  
  123.         b(i).s = PyT(origin, b(i).m)
  124.         b(i).m.y = b(i).s * COS(RAD(i))
  125.         b(i).m.x = b(i).s * SIN(RAD(i))
  126.  
  127.     END IF
  128.  
  129.     IF kh = 120 THEN 'rotate vector clockwise   'press x
  130.         IF RAD(i) < 0 THEN RAD(i) = _PI * 2
  131.  
  132.         IF f1 = 0 THEN t1 = TIMER: f1 = 1
  133.  
  134.         IF TIMER - t1 <= 1 THEN RAD(i) = RAD(i) - .005
  135.         IF TIMER - t1 > 1 THEN RAD(i) = RAD(i) - .020
  136.         IF TIMER - t1 > 1.5 THEN RAD(i) = RAD(i) - .05
  137.  
  138.         b(i).s = PyT(origin, b(i).m)
  139.         b(i).m.y = b(i).s * COS(RAD(i))
  140.         b(i).m.x = b(i).s * SIN(RAD(i))
  141.     END IF
  142.  
  143.     'START OF COLLISION MATHEMATICS SECTION
  144.  
  145.     ballradius = PyT(b(0).p, b(1).p) / 2
  146.  
  147.     FOR bn = 0 TO 1
  148.         vertex(bn, 0) = b(bn).p '                               first we establish the mouse handles for ball position
  149.         vertex(bn, 1) = b(bn).p: VecAdd vertex(bn, 1), b(bn).m, -1 ' and incoming vector starting point
  150.     NEXT bn
  151.  
  152.     'Now all the previous garbage is distilled into a single SUB call once a collision is determined
  153.     B2B_Collision b(0), b(1), Nvec(), Tvec(), Fvec()
  154.     'END OF COLLISION MATHEMATICS SECTION
  155.  
  156.     'graphic representation
  157.     FOR grid = -300 TO 300 STEP 20
  158.         IF grid MOD 100 = 0 THEN c& = &HFF7F7F7F ELSE c& = &H5F7F7F7F
  159.         LINE (grid, 300)-(grid, -300), c& 'Gray  'vertical lines
  160.         LINE (-300, grid)-(300, grid), c& ' Gray  'horizontal lines
  161.     NEXT grid
  162.  
  163.  
  164.     LINE (b(1).p.x, b(1).p.y)-(b(0).p.x, b(0).p.y), White, , &B0010001000100010 'strike vector
  165.  
  166.     FOR dr = 0 TO 1
  167.  
  168.         CIRCLE (b(dr).p.x, b(dr).p.y), ballradius, b(dr).c, , , 1 'AR
  169.  
  170.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + b(dr).m.x, b(dr).p.y + b(dr).m.y), b(dr).c 'incoming
  171.         LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Fvec(dr).x, b(dr).p.y + Fvec(dr).y), b(dr).c, , &B1111000011110000 'exit vector
  172.  
  173.         ' LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Nvec(dr).x, b(dr).p.y + Nvec(dr).y), b(dr).c, , &B1111000011110000 ' for testing
  174.         ' LINE (b(dr).p.x, b(dr).p.y)-(b(dr).p.x + Tvec(dr).x, b(dr).p.y + Tvec(dr).y), b(dr).c, , &B1111000011110000 ' for testing
  175.  
  176.         b$ = b(dr).cn + " @ (" + _TRIM$(STR$(INT(b(dr).p.x))) + ", " + _TRIM$(STR$(INT(b(dr).p.y))) + ")"
  177.         b$ = b$ + "  along <" + _TRIM$(STR$(INT(b(dr).m.x))) + ", " + _TRIM$(STR$(INT(b(dr).m.y))) + ">"
  178.         b$ = b$ + "  exits along <" + _TRIM$(STR$(INT(Fvec(dr).x))) + ", " + _TRIM$(STR$(INT(Fvec(dr).y))) + ">" + c(dr)
  179.  
  180.         _PRINTSTRING (0, 567 + (16 * dr)), b$
  181.  
  182.         TEXT(dr) = b$ + CHR$(13) + CHR$(10) 'NOVARSEG added this line
  183.     NEXT dr
  184.  
  185.     IF _KEYHIT = ASC("f") THEN 'NOVARSEG added this line
  186.         OPEN "BALL STUFF.TXT" FOR BINARY AS #1 'NOVARSEG added this line
  187.         PUT #1, , TEXT(0) 'NOVARSEG added this line
  188.         PUT #1, , TEXT(1) 'NOVARSEG added this line
  189.         CLOSE 'NOVARSEG added this line
  190.     END IF 'NOVARSEG added this line
  191.  
  192.     _DISPLAY
  193.  
  194.     DO
  195.         _LIMIT 500
  196.         kh = _KEYHIT
  197.         IF kh > 0 OR kh < 0 THEN EXIT DO
  198.     LOOP
  199.  
  200.  
  201.  
  202.  
  203. SUB B2BCollision (ball0 AS ball, ball1 AS ball, Nvec() AS V2, Tvec() AS V2, Fvec() AS V2)
  204.  
  205.     DIM un AS V2
  206.     DIM ut AS V2
  207.     DIM MAG AS SINGLE
  208.  
  209.     DIM unDOT0 AS SINGLE 'unit normal vector "dot" pre collision heading ball0
  210.     DIM unDOT1 AS SINGLE 'unit normal vector "dot" pre collision heading ball1
  211.     DIM utDOT0 AS SINGLE 'unit tangent vector "dot" pre collision heading ball0
  212.     DIM utDOT1 AS SINGLE 'unit tangent vector "dot" pre collision heading ball1
  213.  
  214.     DIM unVec0 AS V2
  215.     DIM utVec0 AS V2
  216.     DIM unVec1 AS V2
  217.     DIM utVec1 AS V2
  218.  
  219.     'calculate unit normql vector from ball positions
  220.     'ball0 = red, ball1 =cyan
  221.     un.x = ball1.p.x - ball0.p.x
  222.     un.y = ball1.p.y - ball0.p.y
  223.     MAG = (un.x ^ 2 + un.y ^ 2) ^ .5
  224.     un.x = un.x / MAG
  225.     un.y = un.y / MAG
  226.  
  227.     ut.x = -un.y: ut.y = un.x '     establish unit tangent
  228.  
  229.     unDOT0 = un.x * ball0.m.x + un.y * ball0.m.y 'unit normal vector DOT ball vector
  230.     unDOT1 = un.x * ball1.m.x + un.y * ball1.m.y 'unit normal vector DOT ball vector
  231.     utDOT0 = ut.x * ball0.m.x + ut.y * ball0.m.y 'unit tangent vector DOT ball vector
  232.     utDOT1 = ut.x * ball1.m.x + ut.y * ball1.m.y 'unit tangent vector DOT ball vector
  233.  
  234.     'swap "unit normal" scaled ball vectors (pre collision)   according to https://www.vobarian.com/collisions/2dcollisions2.pdf
  235.     A = unDOT0
  236.     B = unDOT1
  237.     unDOT0 = B: unDOT1 = A
  238.  
  239.     'Convert the scalar normal and tangential velocities into vectors
  240.     'according to https://www.vobarian.com/collisions/2dcollisions2.pdf
  241.     VecMult2 un, unDOT0, unVec0 '  third parameter is output
  242.     VecMult2 ut, utDOT0, utVec0 '  third parameter is output
  243.     VecMult2 un, unDOT1, unVec1 '  third parameter is output
  244.     VecMult2 ut, utDOT1, utVec1 '  third parameter is output
  245.  
  246.     VecAdd2 unVec1, utVec1, Fvec(1) 'vector addition to calculate final ball vectors
  247.     VecAdd2 unVec0, utVec0, Fvec(0) 'vector addition to calculate final ball vectors
  248.  
  249.     'Nvec(0).x = -unVec0.x 'not necessary code - for testing only
  250.     'Nvec(0).y = -unVec0.y 'not necessary code - for testing only
  251.  
  252.     'Nvec(1).x = -unVec1.x 'not necessary code - for testing only
  253.     'Nvec(1).y = -unVec1.y 'not necessary code - for testing only
  254.  
  255.     'Tvec(0).x = -utVec0.x 'not necessary code - for testing only
  256.     'Tvec(0).y = -utVec0.y 'not necessary code - for testing only
  257.  
  258.     'Tvec(1).x = -utVec1.x 'not necessary code - for testing only
  259.     'Tvec(1).y = -utVec1.y 'not necessary code - for testing only
  260.  
  261. END SUB 'B2BCollision
  262. '************
  263. SUB B2B_Collision (ball0 AS ball, ball1 AS ball, Nvec() AS V2, Tvec() AS V2, Fvec() AS V2)
  264.  
  265.     DIM un AS V2
  266.     DIM ut AS V2
  267.     DIM MAG AS SINGLE
  268.  
  269.     DIM unDOT0 AS SINGLE 'unit normal vector "dot" pre collision heading ball0
  270.     DIM unDOT1 AS SINGLE 'unit normal vector "dot" pre collision heading ball1
  271.     DIM utDOT0 AS SINGLE 'unit tangent vector "dot" pre collision heading ball0
  272.     DIM utDOT1 AS SINGLE 'unit tangent vector "dot" pre collision heading ball1
  273.  
  274.     DIM unVec0 AS V2
  275.     DIM utVec0 AS V2
  276.     DIM unVec1 AS V2
  277.     DIM utVec1 AS V2
  278.  
  279.     'calculate unit normql vector from ball positions
  280.     'ball0 = red, ball1 =cyan
  281.     un.x = ball1.p.x - ball0.p.x
  282.     un.y = ball1.p.y - ball0.p.y
  283.     MAG = (un.x ^ 2 + un.y ^ 2) ^ .5
  284.     un.x = un.x / MAG
  285.     un.y = un.y / MAG
  286.  
  287.     'ut.x = -un.y: ut.y = un.x '     establish unit tangent
  288.  
  289.     unDOT0 = un.x * ball0.m.x + un.y * ball0.m.y 'unit normal vector DOT ball vector
  290.     unDOT1 = un.x * ball1.m.x + un.y * ball1.m.y 'unit normal vector DOT ball vector
  291.     ' utDOT0 = ut.x * ball0.m.x + ut.y * ball0.m.y 'unit tangent vector DOT ball vector
  292.     ' utDOT1 = ut.x * ball1.m.x + ut.y * ball1.m.y 'unit tangent vector DOT ball vector
  293.  
  294.     ' 'swap "unit normal" scaled ball vectors (pre collision)   according to https://www.vobarian.com/collisions/2dcollisions2.pdf
  295.     ' A = unDOT0
  296.     ' B = unDOT1
  297.     ' unDOT0 = B: unDOT1 = A
  298.  
  299.     'Convert the scalar normal and tangential velocities into vectors
  300.     'according to https://www.vobarian.com/collisions/2dcollisions2.pdf
  301.  
  302.     VecMult2 un, unDOT1 - unDOT0, unVec0 '  third parameter is output
  303.  
  304.     'VecMult2 ut, utDOT0, utVec0 '  third parameter is output
  305.  
  306.     VecMult2 un, unDOT0 - unDOT1, unVec1 '  third parameter is output
  307.  
  308.     'VecMult2 ut, utDOT1, utVec1 '  third parameter is output
  309.  
  310.     VecAdd2 unVec1, ball1.m, Fvec(1) 'vector addition to calculate final ball vectors
  311.     VecAdd2 unVec0, ball0.m, Fvec(0) 'vector addition to calculate final ball vectors
  312.  
  313. END SUB 'B2B_Collision
  314.  
  315.  
  316.  
  317. FUNCTION map! (value!, minRange!, maxRange!, newMinRange!, newMaxRange!)
  318.  
  319.     map! = ((value! - minRange!) / (maxRange! - minRange!)) * (newMaxRange! - newMinRange!) + newMinRange!
  320.  
  321.  
  322.  
  323. FUNCTION MBS% 'Mouse Button Status  Author: Steve McNeill
  324.     STATIC StartTimer AS _FLOAT
  325.     STATIC ButtonDown AS INTEGER
  326.     STATIC ClickCount AS INTEGER
  327.     CONST ClickLimit## = 0.2 'Less than 1/4th of a second to down, up a key to count as a CLICK.
  328.     '                          Down longer counts as a HOLD event.
  329.     SHARED Mouse_StartX, Mouse_StartY, Mouse_EndX, Mouse_EndY
  330.     WHILE _MOUSEINPUT 'Remark out this block, if mouse main input/clear is going to be handled manually in main program.
  331.         SELECT CASE SGN(_MOUSEWHEEL)
  332.             CASE 1: MBS = MBS OR 512
  333.             CASE -1: MBS = MBS OR 1024
  334.         END SELECT
  335.     WEND
  336.  
  337.     IF _MOUSEBUTTON(1) THEN MBS = MBS OR 1
  338.     IF _MOUSEBUTTON(2) THEN MBS = MBS OR 2
  339.     IF _MOUSEBUTTON(3) THEN MBS = MBS OR 4
  340.  
  341.     IF StartTimer = 0 THEN
  342.         IF _MOUSEBUTTON(1) THEN 'If a button is pressed, start the timer to see what it does (click or hold)
  343.             ButtonDown = 1: StartTimer = TIMER(0.01)
  344.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  345.         ELSEIF _MOUSEBUTTON(2) THEN
  346.             ButtonDown = 2: StartTimer = TIMER(0.01)
  347.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  348.         ELSEIF _MOUSEBUTTON(3) THEN
  349.             ButtonDown = 3: StartTimer = TIMER(0.01)
  350.             Mouse_StartX = _MOUSEX: Mouse_StartY = _MOUSEY
  351.         END IF
  352.     ELSE
  353.         BD = ButtonDown MOD 3
  354.         IF BD = 0 THEN BD = 3
  355.         IF TIMER(0.01) - StartTimer <= ClickLimit THEN 'Button was down, then up, within time limit.  It's a click
  356.             IF _MOUSEBUTTON(BD) = 0 THEN MBS = 4 * 2 ^ ButtonDown: ButtonDown = 0: StartTimer = 0
  357.         ELSE
  358.             IF _MOUSEBUTTON(BD) = 0 THEN 'hold event has now ended
  359.                 MBS = 0: ButtonDown = 0: StartTimer = 0
  360.                 Mouse_EndX = _MOUSEX: Mouse_EndY = _MOUSEY
  361.             ELSE 'We've now started the hold event
  362.                 MBS = MBS OR 32 * 2 ^ ButtonDown
  363.             END IF
  364.         END IF
  365.     END IF
  366.  
  367.  
  368. FUNCTION PyT (var1 AS V2, var2 AS V2)
  369.  
  370.     PyT = _HYPOT(var1.x - var2.x, var1.y - var2.y)
  371.  
  372.  
  373.  
  374. SUB VecAdd (var AS V2, var2 AS V2, var3 AS SINGLE)
  375.  
  376.     var.x = -(var.x + (var2.x * var3)) '                           add vector (or a scalar multiple of) var2 to var)
  377.     var.y = var.y + (var2.y * var3) '                           use var3 = -1 to subtract var2 from var
  378.  
  379. END SUB 'Add_Vector
  380.  
  381. SUB VecAdd2 (var1 AS V2, var2 AS V2, var3 AS V2)
  382.  
  383.     var3.x = -var1.x + -var2.x '
  384.     var3.y = -var1.y + -var2.y '
  385.  
  386. END SUB 'Add_Vector
  387.  
  388.  
  389. FUNCTION VecDot (var AS V2, var2 AS V2)
  390.  
  391.     VecDot = var.x * var2.x + var.y * var2.y '                  get dot product of var & var2
  392.  
  393. END FUNCTION 'VecDot
  394.  
  395.  
  396. SUB VecMult (vec AS V2, multiplier AS SINGLE)
  397.  
  398.     vec.x = vec.x * multiplier '                                multiply vector by scalar value
  399.     vec.y = vec.y * multiplier
  400.  
  401. END SUB 'Vec_Mult
  402.  
  403. SUB VecMult2 (var1 AS V2, var2 AS SINGLE, out1 AS V2)
  404.  
  405.     out1.x = var1.x * var2
  406.     out1.y = var1.y * var2
  407.  
  408. END SUB 'Vec_Mult
  409.  
  410. SUB VecDIV (vec AS V2, divisor AS SINGLE)
  411.  
  412.     vec.x = vec.x / divisor
  413.     vec.y = vec.y / divisor
  414.  
  415. END SUB 'VecDIV
  416.  
  417.  
  418. SUB VecNorm (var AS V2)
  419.  
  420.     m = PyT(origin, var)
  421.     IF m = 0 THEN
  422.         var.x = 0: var.y = 0 '                                  vector with magnitude 0 is a zero vector
  423.     ELSE
  424.         var.x = var.x / m: var.y = var.y / m '                  convert var to unit vector
  425.     END IF
  426.  
  427. END SUB 'VecNorm
  428.