Version 21 of Colliding balls

Updated 2003-04-03 23:03:26

David Easton: 17 Mar 2003 Bouncing Balls inspired me to post a little application I wrote to show balls that bounce off each other as well as the edges of the canvas.

Ed Suominen: 25 Mar 2003 This is great! There are some pretty advanced calculations going on here.

David Easton: 26 Mar 2003 Thanks! The calculations used by postColVels come from conservation of energy and momentum. colide resolves velocities parallel and perpendicular to the collision. Thus, this should be a fairly accurate simulation.

LH: 3 Apr 2003 - Consult An Improvement to Colliding Balls if interested in simpler calculations to find post-collision velocities. Also, two bugs are corrected there.


NEM - I have been inspired by this, and a conversation in the chatroom, to create the start of a small pool/snooker/billiards game, which I have called TkPool.


 #
 # Colliding Balls
 # Author: David Easton
 #

 package require Tk

 #
 # Return an entry from the list at random
 #
 proc randFromList { inputList } {

     set index [expr int(rand() * [llength $inputList])]
     return [lindex $inputList $index]
 }

 #
 # Given the initial velocities and masses
 # calculates velocities following a collision
 #
 proc postColVels { u1 u2 m1 m2 } {

     # No collision if u2 > u1
     if { $u2 > $u1 } {
         return [list $u1 $u2]
     }

     set u1 [expr 1.0 * $u1]
     set u2 [expr 1.0 * $u2]
     set m1 [expr 1.0 * $m1]
     set m2 [expr 1.0 * $m2]

     set M [expr $m1 / $m2]

     set b [expr ($M * $u1) + $u2]
     set c [expr ($M * $u1 * $u1) + ($u2 * $u2)]

     set q [expr 2 * $M * $b]
     set p [expr 4 * $M * $M * $b * $b]
     set r [expr 4 * ($M + ($M * $M)) * (($b * $b) - $c)]
     set s [expr 2 * ($M + ($M * $M))]

     if { $r > $p } {
         "No solution"
     } else {

         set root [expr sqrt($p -$r)]

 #        set v1(1) [expr ($q + $root) / $s]
         set v1(2) [expr ($q - $root) / $s]

 #        set v2(1) [expr $b - ($M * $v1(1))]
         set v2(2) [expr $b - ($M * $v1(2))]

         # v2 should always be greater than v1
         # which means the answer is always v1(2) and v2(2)

         return [list $v1(2) $v2(2)]
     }
 }

 proc createBall { tag } {

     global State

     set radius [expr int((30 * rand()) + 20)]
     set diam   [expr 2 * $radius]
     # Mass is proportional to area
     set mass   [expr $radius * $radius]

     set canvasHeight [winfo height $State(canvas)]
     set canvasWidth  [winfo width $State(canvas)]

     set xpos [expr $radius + int(($canvasWidth - $diam) * rand())]
     set ypos [expr $radius + int(($canvasHeight - $diam) * rand())]

     set x1 [expr $xpos - $radius]
     set x2 [expr $xpos + $radius]
     set y1 [expr $ypos - $radius]
     set y2 [expr $ypos + $radius]

     # Random colour
     set colList [list red yellow darkgreen green blue lightblue orange pink purple white]
     set colour [randFromList $colList]

     # Now create ball
     set id [$State(canvas) create oval $x1 $y1 $x2 $y2 -outline black -fill $colour -tags [list $tag ball]]

     set State(id2tag,$id) $tag

     set xvel [expr (rand() * 8.0) -2]
     set yvel [expr (rand() * 8.0) -2]

     set State(pos,$tag) [list $xpos $ypos]
     set State(vel,$tag) [list $xvel $yvel]
     set State(rad,$tag) $radius
     set State(mass,$tag) $mass
 }

 #
 # Check if we have collided with another ball
 #
 # Returns: 1 - If there was a collision
 #          0 - If no collision
 #
 proc checkForCollision { tag } {

     global State

     set didCollide 0

     set overlapList {}

     foreach {ourX ourY} $State(pos,$tag) {}

     set ourId [$State(canvas) find withtag $tag]

     set id [$State(canvas) find closest $ourX $ourY $State(rad,$tag) $ourId]

     while { $id != $ourId } {

         if { [lsearch -glob [$State(canvas) gettags $id] "ball*"] > -1 } {

             set didCollide 1
             lappend overlapList $id
         }
         set id [$State(canvas) find closest $ourX $ourY $State(rad,$tag) $id]
     }

     if { [llength $overlapList] > 0 } {

         foreach id $overlapList {

             collide $tag $State(id2tag,$id)
         }
     }

     return $didCollide
 }

 proc moveBalls { } {

     global State

     set canvasHeight [winfo height $State(canvas)]
     set canvasWidth  [winfo width $State(canvas)]

     foreach ball $State(ballList) {

         foreach {xpos ypos} $State(pos,$ball) {}
         foreach {xvel yvel} $State(vel,$ball) {}

         set xpos [expr $xpos + $xvel]
         set ypos [expr $ypos + $yvel]

         $State(canvas) move $ball $xvel $yvel

         # Bounce off the edges

         foreach {x1 y1 x2 y2} [$State(canvas) bbox $ball] {}

         # Left edge
         if { $x1 < 0 && $xvel < 0} {
             set xvel [expr -1.0 * $xvel]
         }
         if { $x2 > $canvasWidth && $xvel > 0} {
             set xvel [expr -1.0 * $xvel]
         }
         if { $y1 < 0 && $yvel < 0} {
             set yvel [expr -1.0 * $yvel]
         }
         if { $y2 > $canvasHeight && $yvel > 0} {
             set yvel [expr -1.0 * $yvel]
         }

         if {[checkForCollision $ball]} {

             # Collided
             set State(pos,$ball) [list $xpos $ypos]

         } else {

             # Update for new position and velocity

             set State(pos,$ball) [list $xpos $ypos]
             set State(vel,$ball) [list $xvel $yvel]
         }
     }

     after 50 moveBalls
 }

 proc collide { tag1 tag2 } {

     global State

     # Get position of each ball

     foreach {x1 y1} $State(pos,$tag1) {}
     foreach {x2 y2} $State(pos,$tag2) {}

     # Always call the ball on the right (2) and the one on the left (1)

     if { $x1 > $x2 } {

         set temp $tag2
         set tag2 $tag1
         set tag1 $temp

         # Get position of each ball

         foreach {x1 y1} $State(pos,$tag1) {}
         foreach {x2 y2} $State(pos,$tag2) {}
     }

     # Get velocity of each ball

     foreach {ux1 uy1} $State(vel,$tag1) {}
     foreach {ux2 uy2} $State(vel,$tag2) {}

     # Work out the angle along the axis of collision

     set diffX [expr 1.0 * ($x2 - $x1)]
     set diffY [expr 1.0 * ($y2 - $y1)]

     set phi [expr atan ($diffY / $diffX)]

     # Now work out the velocity parallel and perpendicular

     set uparr1 [expr ($ux1 * cos($phi)) + ($uy1 * sin($phi))]
     set uperp1 [expr ($ux1 * sin($phi)) - ($uy1 * cos($phi))]

     set uparr2 [expr ($ux2 * cos($phi)) + ($uy2 * sin($phi))]
     set uperp2 [expr ($ux2 * sin($phi)) - ($uy2 * cos($phi))]

     # If they are not going towards each other, then they will not collide
     if { $uparr2 > $uparr1 } {
         return
     }

     set mass1 $State(mass,$tag1)
     set mass2 $State(mass,$tag2)

     foreach {vparr1 vparr2} [postColVels $uparr1 $uparr2 $mass1 $mass2] {}

     # Perpendicular velocites are unchanged

     set vperp1 $uperp1
     set vperp2 $uperp2

     # Now convert back into x and y movements   
     set vx1 [expr ($vparr1 * cos($phi)) + ($vperp1 * sin($phi))]
     set vy1 [expr ($vparr1 * sin($phi)) - ($vperp1 * cos($phi))]

     set vx2 [expr ($vparr2 * cos($phi)) + ($vperp2 * sin($phi))]
     set vy2 [expr ($vparr2 * sin($phi)) - ($vperp2 * cos($phi))]

     # Update for new velocities

     set State(vel,$tag1) [list $vx1 $vy1]
     set State(vel,$tag2) [list $vx2 $vy2]
 }

 # Seed random number generator
 expr srand([clock clicks])

 # Window things
 wm title . "Bouncing balls"

 # Create canvas
 set State(canvas) [canvas .c -width 500 -height 400]
 pack $State(canvas) -fill both -expand true

 update

 # Create balls
 set State(ballList) [list ball1 ball2 ball3 ball4 ball5 ball6 ball7 ball8]

 foreach ball $State(ballList) {

     createBall $ball
 }

 moveBalls

[ Category Graphics

Category Animation ]