Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
A bigger bouncing ball demo - collision with vector reflection
#1
This is an expanded version of the bigballdemo posted in the filled circle thread (Here) that uses vector reflection to bounce balls off of each other in a realistic way.  This is something I am in the process of learning.

This v2 demo adds a 2nd big ball with an extra layer of bouncing balls.  So there balls inside a bigger ball, which is inside an even bigger ball, and there's a layer of bouncing balls between those two big balls.  On the outside is another layer of balls bouncing around.   I made 3 SUBs to handle the 3 kinds of collision, Ball vs Ball, Ball vs Inside of Larger ball edge (when the balls are on the inside), and Ball vs Outside of a larger ball (when balls are on the outside).

Move the middle big ball with the mouse, it will move all the others and they will react to each other.

- Dav

Code: (Select All)
'================
'BIGBALLDEMO2.BAS
'================
'Bigger Bouncing balls demo using vector reflection.
'By Dav, SEP 16th/2024, for QB64 Phoenix Edition.

'===============
'About this demo
'===============

'This demo shows balls bouncing inside a bigger ball, which is inside
'a bigger outer ball. Between the big balls are more bouncing balls.
'There are more balls bouncing on the outside, so there are 3 layers
'of bouncing balls.  Use the mouse to drag the big middle ball, which
'will move the outer ball.  You will see that all the balls collide
'and reflect off each other.  It uses the FC SUB to draw all the balls.

'============================
'More details about this demo
'============================

'This demo was a challenge and a great learning experience for me.
'Instead of just reversing velocity direction when a ball hits an object,
'this demo uses 'vector reflection' to make them bounce realistically.
'When two balls collide, their velocity vector changes direction based on
'angle of impact, and the normal vector at the contact point.  Their
'reflection velocities are computed based on their sizes, and their x/y
'positions are adjusted to prevent overlapping after collision.

'There are three collision SUB used in this program.  One to handle
'two balls colliding, one to bounce a ball off the inside of a larger
'ball, and one to bouce off the outside of a larger ball.

Randomize Timer

Screen _NewImage(1000, 700, 32)

'=== defaults for the bigball ===
bigballsize = 190
bigballx = _Width / 2
bigbally = _Height / 2

'=== defaults for the outer ball ===
outerballsize = 300 ' size of the outer ball
outerballx = _Width / 2
outerbally = _Height / 2

'=== arrays for inside balls ===
insidenum = 45 'num of inside balls
Dim insidex(insidenum) 'x positions of inside balls
Dim insidey(insidenum) 'ypositions of inside balls
Dim insidexv(insidenum) 'x velocities of inside balls
Dim insideyv(insidenum) 'y velocities of inside balls
Dim insidesize(insidenum) 'sizes of inside balls
Dim insideclr~&(insidenum) 'colors of inside balls

'=== arrays for middle balls ===
middlenum = 125 'num of inside balls
Dim middlex(middlenum) 'x positions of inside balls
Dim middley(middlenum) 'ypositions of inside balls
Dim middlexv(middlenum) 'x velocities of inside balls
Dim middleyv(middlenum) 'y velocities of inside balls
Dim middlesize(middlenum) 'sizes of inside balls
Dim middleclr~&(middlenum) 'colors of inside balls

'=== arrays for outside balls ===
outsidenum = 125 'num of outside balls
Dim outsidex(outsidenum) 'x positions of outside balls
Dim outsidey(outsidenum) 'y positions of outside balls
Dim outsidexv(outsidenum) 'x velocities of outside balls
Dim outsideyv(outsidenum) 'y velocities of outside balls
Dim outsidesizes(outsidenum) 'sizes of outside balls
Dim outsideclr~&(outsidenum) 'colors of outside balls

'=== initialize inside balls ===
For i = 0 To insidenum - 1
    insidesize(i) = 5 + (Rnd * 15) 'random size
    insideclr~&(i) = _RGBA(Rnd * 255, Rnd * 255, Rnd * 255, 200) 'color
    insidexv(i) = (Rnd * 2 + 1) * (2 * Rnd - 1) 'x velocity between -3 and 3
    insideyv(i) = (Rnd * 2 + 1) * (2 * Rnd - 1) 'y velocity between -3 and 3
Next

'=== initialize middle balls ===
For i = 0 To middlenum - 1
    middlex(i) = Rnd * (outerballsize - 50) + outerballx 'x position of middle ball
    middley(i) = Rnd * (outerballsize - 50) + outerbally 'y x position of middle ball
    middlexv(i) = (Rnd * 2 + 1) * (2 * Rnd - 1) 'x velocity between -3 and 3
    middleyv(i) = (Rnd * 2 + 1) * (2 * Rnd - 1) 'y velocity between -3 and 3
    middlesize(i) = 5 + (Rnd * 10) 'random size
    middleclr~&(i) = _RGBA(Rnd * 200, Rnd * 100, Rnd * 255, 170) 'color
Next

'=== initialize outside Balls ===
For j = 0 To outsidenum - 1
    outsidesizes(j) = Int(Rnd * 26) + 5 'random size
    outsideclr~&(j) = _RGBA(Rnd * 225, Rnd * 225, Rnd * 225, 125) 'color
    outsidex(j) = Int(Rnd * _Width) 'x position
    outsidey(j) = Int(Rnd * _Height) 'y position
    outsidexv(j) = (Rnd * 2 + 1) * (2 * Rnd - 1) 'x velocity between -3 and 3
    outsideyv(j) = (Rnd * 2 + 1) * (2 * Rnd - 1) 'y velocity between -3 and 3
Next

'=== draw a background image ===
For i = 1 To 1000
    fc Rnd * _Width, Rnd * _Height, 20, _RGBA(55 + (Rnd * 100), 55 + (Rnd * 150), 55 + (Rnd * 200), 30), 0
Next: back& = _CopyImage(_Display)

'=== put mouse in middle of screen ===
_MouseMove _Width / 2, _Height / 2

'=========
'MAIN LOOP
'=========

Do

    '=== put down background image ===
    Cls: _PutImage (0, 0), back&

    '=== get mouse input ===
    While _MouseInput: Wend

    '=== assign bigball x/y to mouse x/y ===
    bigballx = _MouseX: bigbally = _MouseY

    '=== handle inside balls ===
    For i = 0 To insidenum - 1

        '== move inside balls ==
        insidex(i) = insidex(i) + insidexv(i)
        insidey(i) = insidey(i) + insideyv(i)

        '=== check collision with the big ball ===
        Ball2BallInsideEdgeCollision insidex(i), insidey(i), insidesize(i), insidexv(i), insideyv(i), bigballx, bigbally, bigballsize

        '=== finally draw insideball ===
        fc insidex(i), insidey(i), insidesize(i), insideclr~&(i), 1

    Next

    '=== handle collisions of insideballs ===
    For i = 0 To insidenum - 1
        For j = i + 1 To insidenum - 1
            If i <> j Then
                Ball2BallCollision insidex(i), insidey(i), insidesize(i), insidexv(i), insideyv(i), insidex(j), insidey(j), insidesize(j), insidexv(j), insideyv(j)
            End If
        Next
    Next

    '=== handle middle balls ===
    For i = 0 To middlenum - 1
        '== move middle balls ==
        middlex(i) = middlex(i) + middlexv(i)
        middley(i) = middley(i) + middleyv(i)

        '=== Check for boundary with the outer ball ===
        Ball2BallInsideEdgeCollision middlex(i), middley(i), middlesize(i), middlexv(i), middleyv(i), outerballx, outerbally, outerballsize

        '=== Check middleball collision with the bigball ===
        Ball2BallOutsideEdgeCollision middlex(i), middley(i), middlesize(i), middlexv(i), middleyv(i), bigballx, bigbally, bigballsize

        '=== finally draw middle ball ===
        fc middlex(i), middley(i), middlesize(i), middleclr~&(i), 1
    Next

    '=== Handle middleball collisions with each other ===
    For i = 0 To middlenum - 1
        For j = i + 1 To middlenum - 1
            If i <> j Then
                Ball2BallCollision middlex(i), middley(i), middlesize(i), middlexv(i), middleyv(i), middlex(j), middley(j), middlesize(j), middlexv(j), middleyv(j)
            End If
        Next
    Next

    '=== handle Outside balls ===
    For j = 0 To outsidenum - 1

        'draw outside ball
        fc outsidex(j), outsidey(j), outsidesizes(j), outsideclr~&(j), 1

        outsidex(j) = outsidex(j) + outsidexv(j)
        outsidey(j) = outsidey(j) + outsideyv(j)

        '=== bounce outside balls off the screen edges
        If outsidex(j) < outsidesizes(j) Then
            outsidex(j) = outsidesizes(j): outsidexv(j) = -outsidexv(j)
        End If
        If outsidex(j) > _Width - outsidesizes(j) Then
            outsidex(j) = _Width - outsidesizes(j): outsidexv(j) = -outsidexv(j)
        End If
        If outsidey(j) < outsidesizes(j) Then
            outsidey(j) = outsidesizes(j): outsideyv(j) = -outsideyv(j)
        End If
        If outsidey(j) > _Height - outsidesizes(j) Then
            outsidey(j) = _Height - outsidesizes(j): outsideyv(j) = -outsideyv(j)
        End If

        '==== check for outside ball collision with outer ball ====
        Ball2BallOutsideEdgeCollision outsidex(j), outsidey(j), outsidesizes(j), outsidexv(j), outsideyv(j), outerballx, outerbally, outerballsize

    Next

    '=== handle collisions between outside balls ===
    For i = 0 To outsidenum - 1
        For j = i + 1 To outsidenum - 1
            If i <> j Then
                Ball2BallCollision outsidex(i), outsidey(i), outsidesizes(i), outsidexv(i), outsideyv(i), outsidex(j), outsidey(j), outsidesizes(j), outsidexv(j), outsideyv(j)
            End If
        Next
    Next

    '=== check for collision between the bigball and the outer ball      ===
    '==== this keeps the bigball and outerball at the edge of each other ===
    dis = Sqr((bigballx - outerballx) ^ 2 + (bigbally - outerbally) ^ 2)
    If dis > (bigballsize / 2) Then
        'calculate direction from bigball to outerball
        angle = _Atan2(outerbally - bigbally, outerballx - bigballx)
        'make distance from center of bigball to edge of outerball
        newdis = outerballsize + (bigballsize / 2)
        'move the outer ball to exactly touch the big ball's edge
        outerballx = bigballx + Cos(angle) * (newdis - outerballsize)
        outerbally = bigbally + Sin(angle) * (newdis - outerballsize)
    End If

    '=== draw the outer ball ===
    fc outerballx, outerbally, outerballsize, _RGBA(200, 100, 255, 50), 0
    'draw an edge around it
    Circle (outerballx, outerbally), outerballsize, _RGBA(255, 255, 255, 75)

    '=== draw the bigball ===
    fc bigballx, bigbally, bigballsize, _RGBA(100, 200, 255, 75), 0
    'draw an edge around it
    Circle (bigballx, bigbally), bigballsize, _RGBA(255, 255, 255, 75)

    _Display
    _Limit 60

Loop Until InKey$ <> ""

Sub fc (cx, cy, radius, clr~&, grad)
    'FC SUB by Dav
    'Draws filled circle at cx/cy with given radius and color.
    'If grad=1 it will create a gradient effect, otherwise it's a solid color.

    If radius < 1 Then Exit Sub 'a safety bail (thanks bplus!)

    If grad = 1 Then
        red = _Red32(clr~&)
        grn = _Green32(clr~&)
        blu = _Blue32(clr~&)
        alpha = _Alpha32(clr~&)
    End If
    r2 = radius * radius
    For y = -radius To radius
        x = Sqr(r2 - y * y)
        ' If doing gradient
        If grad = 1 Then
            For i = -x To x
                dis = Sqr(i * i + y * y) / radius
                red2 = red * (1 - dis) + (red / 2) * dis
                grn2 = grn * (1 - dis) + (grn / 2) * dis
                blu2 = blu * (1 - dis) + (blu / 2) * dis
                clr2~& = _RGBA(red2, grn2, blu2, alpha)
                Line (cx + i, cy + y)-(cx + i, cy + y), clr2~&, BF
            Next
        Else
            Line (cx - x, cy + y)-(cx + x, cy + y), clr~&, BF
        End If
    Next
End Sub

Sub Ball2BallCollision (Ball1x, Ball1y, Ball1s, Ball1xv, Ball1yv, Ball2x, Ball2y, Ball2s, Ball2xv, Ball2yv)

    'This SUB handles ball to ball collision

    'calculate distance between the two balls
    dx = Ball2x - Ball1x
    dy = Ball2y - Ball1y
    dis = Sqr(dx * dx + dy * dy)
    'check for collision, if so...
    If dis < (Ball1s + Ball2s) Then
        'calculate normal vector and overlapping distance
        x = dx / dis: y = dy / dis 'normal
        over = (Ball1s + Ball2s) - dis 'overlap distance
        'move balls apart based on overlap amount
        Ball1x = Ball1x - x * (over / 2)
        Ball1y = Ball1y - y * (over / 2)
        Ball2x = Ball2x + x * (over / 2)
        Ball2y = Ball2y + y * (over / 2)
        'reflect velocities based on collision
        vr = (Ball2xv - Ball1xv) * x + (Ball2yv - Ball1yv) * y
        'update ball velocities based on collision
        Ball1xv = Ball1xv + vr * x: Ball1yv = Ball1yv + vr * y
        Ball2xv = Ball2xv - vr * x: Ball2yv = Ball2yv - vr * y
    End If
End Sub

Sub Ball2BallInsideEdgeCollision (Ball1x, Ball1y, Ball1s, Ball1xv, Ball1yv, BallEdgex, BallEdgey, BallEdgeSize)

    'This sub handles balls colliding with the inside edge of a larger ball

    dis = Sqr((Ball1x - BallEdgex) ^ 2 + (Ball1y - BallEdgey) ^ 2)
    'check if iball collides with ball edge
    If dis + Ball1s > BallEdgeSize Then
        'calculate normal vector for reflection
        x = (Ball1x - BallEdgex) / dis
        y = (Ball1y - BallEdgey) / dis
        'calculate the reflection of velocity based impact angle
        vr = Ball1xv * x + Ball1yv * y
        'update velocity of inside ball based on the normal
        Ball1xv = Ball1xv - 2 * vr * x
        Ball1yv = Ball1yv - 2 * vr * y
        'push back to prevent overlapping
        over = (dis + Ball1s) - BallEdgeSize
        Ball1x = Ball1x - x * over
        Ball1y = Ball1y - y * over
    End If
End Sub

Sub Ball2BallOutsideEdgeCollision (Ball1x, Ball1y, Ball1s, Ball1xv, Ball1yv, BallEdgex, BallEdgey, BallEdgeSize)

    'This sub handles balls colliding with the outside edge of a larger ball

    dis = Sqr((Ball1x - BallEdgex) ^ 2 + (Ball1y - BallEdgey) ^ 2)
    If dis < BallEdgeSize + Ball1s Then
        'calculate normal vector for reflection
        x = (Ball1x - BallEdgex) / dis
        y = (Ball1y - BallEdgey) / dis
        'reflect velocity based on normal vector
        vr = Ball1xv * x + Ball1yv * y
        Ball1xv = Ball1xv - 2 * vr * x
        Ball1yv = Ball1yv - 2 * vr * y
        'move the middle ball out to prevent overlap
        over = (BallEdgeSize + Ball1s) - dis
        Ball1x = Ball1x + x * over
        Ball1y = Ball1y + y * over
    End If
End Sub

Find my programs here in Dav's QB64 Corner
Reply
#2
Excellent program... well done!

One issue:

Illegal function call in line 255, which I remedied by modifying as follows:

x = Sqr(abs(r2 - y * y))

Apparently r2 < y*y that threw the error in sqr()

Regardless, it's a beautiful program!
Reply
#3
great!

How is y*y getting > r2 ???

If radius = 0 Then Exit Sub 'a safety bail (thanks bplus!)

Change to

If radius < 1 then exit sub

because .7 * .7 = .49 r2 < r when r < 1
b = b + ...
Reply
#4
Thanks, @sbblank for the report, and @bplus for the fix!  Updated code.

- Dav

Find my programs here in Dav's QB64 Corner
Reply
#5
Thanks bplus... the abs() was just a quick fix guess that a negative number was involved in the sqr() function and I wanted to get the code working (no patience on my part).  Your analysis and fix is better!

Beautiful physics and beautiful program graphics, Dav!
Reply
#6
(09-17-2024, 05:51 AM)bplus Wrote: great!

How is y*y getting > r2 ???

If radius = 0 Then Exit Sub 'a safety bail (thanks bplus!)

Change to

If radius < 1 then exit sub

because .7 * .7 = .49 r2 < r when r < 1

I am using a MacBook Pro M1 Max and doing some further checking by printing values for r2 - y*y, I get a list of positive numbers... mostly.  Eventually a sneaky value very near to 0, but slightly negative pops up (i.e. -2.831865E-06 as one example), which generates an Illegal function call.  It may be that this is due to arithmetic operations in the arm cpu?  Not certain about that.  However, adding the abs() function inside sqr() in line 255 remedies the situation for me.

I would be curious if anyone else is using Apple Silicon and getting a similar result?
Reply
#7
Hey @sbblank. Yeh, things are a little different for us Mac users. I had the same issue with another Dav program here: 
https://qb64phoenix.com/forum/showthread...1#pid28011
Reply
#8
Good ole floating point math in QB64!

Well Abs() is one solution, checking if r2 - y * y > 0 before SQR is another and converting from default usu Singfle Floats to declaring all as Long should also work around the lousy math processing and might even be a nano faster. Integer math does not have to manage a decimal point.
b = b + ...
Reply
#9
Sorry mac users, I will try to remember to use Abs from here on in that sub

- Dav

Find my programs here in Dav's QB64 Corner
Reply
#10
(09-18-2024, 02:02 PM)Dav Wrote: Sorry mac users, I will try to remember to use Abs from here on in that sub

- Dav

No need to apologize... I need the mental workout to help delay the onset of dementia Smile
Reply




Users browsing this thread: 3 Guest(s)