Author Topic: 2D ball collisions without trigonometry.  (Read 18109 times)

0 Members and 1 Guest are viewing this topic.

Offline NOVARSEG

  • Forum Resident
  • Posts: 509
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #75 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. ?

« Last Edit: May 26, 2021, 07:16:24 pm by NOVARSEG »

Offline OldMoses

  • Seasoned Forum Regular
  • Posts: 469
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #76 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.  
« Last Edit: May 26, 2021, 07:31:39 pm by OldMoses »

Offline NOVARSEG

  • Forum Resident
  • Posts: 509
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #77 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







Offline OldMoses

  • Seasoned Forum Regular
  • Posts: 469
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #78 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.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: 2D ball collisions without trigonometry.
« Reply #79 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.
 
« Last Edit: May 26, 2021, 08:23:28 pm by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #80 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
You're not done when it works, you're done when it's right.

Offline NOVARSEG

  • Forum Resident
  • Posts: 509
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #81 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
« Last Edit: May 27, 2021, 07:02:00 pm by NOVARSEG »

Offline OldMoses

  • Seasoned Forum Regular
  • Posts: 469
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #82 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.  
« Last Edit: May 27, 2021, 09:08:19 pm by OldMoses »

Offline NOVARSEG

  • Forum Resident
  • Posts: 509
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #83 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


« Last Edit: May 28, 2021, 02:34:31 am by NOVARSEG »

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #84 on: May 27, 2021, 11:30:59 pm »
Looks good Moses!
You're not done when it works, you're done when it's right.

Offline NOVARSEG

  • Forum Resident
  • Posts: 509
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #85 on: May 28, 2021, 01:19:04 am »
Looking at OldMoses code now.  Great work.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #86 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.

Offline OldMoses

  • Seasoned Forum Regular
  • Posts: 469
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #87 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.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #88 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.
« Last Edit: May 29, 2021, 09:52:20 am by bplus »

Offline NOVARSEG

  • Forum Resident
  • Posts: 509
    • View Profile
Re: 2D ball collisions without trigonometry.
« Reply #89 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)?