Path: utzoo!utgpu!jarvis.csri.toronto.edu!mailrus!tut.cis.ohio-state.edu!bloom-beacon!SESAME.STANFORD.EDU!mkatz From: mkatz@SESAME.STANFORD.EDU (Morris Katz) Newsgroups: comp.lang.scheme Subject: Billiard Ball Simulator Message-ID: <8903211707.AA12277@sesame.Stanford.EDU> Date: 21 Mar 89 17:07:17 GMT Sender: daemon@bloom-beacon.MIT.EDU Organization: The Internet Lines: 1345 I have been innundated with request for my Billiard Ball Simulator so despite its long length I have decided to post the code. Please note its limitations. This program is NOT a usefull simulator for Billiards, but only a medium sized test program. Morry Katz katz@polya.stanford.edu The following code is a quick hack I wrote for testing my parallelization system on a medium sized program (larger than a toy). It is sort of a billiard ball simulator (see comments at header of program). CAVEAT EMPTOR: 1) I make no promises that this code is bug free. It seems to work on simple test cases, but I have not tested it exhaustively. 2) It uses the MIT Cscheme macro package. A conversion to extend-syntax should be simple. Also, the code could probably be converted to just use functions, with some performance loss. (If you do either of the above, please let me know since it seems silly for multiple people to do the same work. ;;; ;;; BILLIARD.SCM: This file contains code for a very simple billiard ball ;;; simulator. The simulation takes place in two dimensions. ;;; The balls are really disks in that their height is not taken ;;; into account. All interactions are assumed to be ;;; frictionless so spin in irrelevant and not accounted for. ;;; (See section on limitations.) ;;; ;;; NOTES: A simulation is initiated by creating a number of balls and bumpers ;;; and and specifying a duration for the simulation. For each ball, ;;; its mass, radius, initial position, and initial velocity must be ;;; specified. For each bumper, the location of its two ends must be ;;; specified. (Bumpers are assumed to have zero width.) ;;; ;;; A sample run might be started as follows: ;;; (simulate ;;; (list (make-ball 2 1 9 5 -1 -1) ;;; (make-ball 4 2 2 5 1 -1)) ;;; (list (make-bumper 0 0 0 10) ;;; (make-bumper 0 0 10 0) ;;; (make-bumper 0 10 10 10) ;;; (make-bumper 10 0 10 10)) ;;; 30) ;;; ;;; It would create one billiard ball of mass 2 and radius 1 at position ;;; (9, 5) with initial velocity (-1, -1) and a second ball of mass 4 ;;; and radius 2 at position (2, 5) with initial velocity (1, -1). The ;;; table would be a 10X10 square. (See diagram below) ;;; ;;; +---------------------------+ ;;; | | ;;; | | ;;; | XXXX | ;;; | XXXXXXXX XX | ;;; |XXXXXX4XXXXX XXX2XX| ;;; | XXXXXXXX /XX | ;;; | XXXX \ | ;;; | | ;;; | | ;;; +---------------------------+ ;;; ;;; LIMITATIONS: This simulator does not handle 3 body problems correctly. If ;;; 3 objects interact at one time, only the interactions of 2 of ;;; the bodies will be accounted for. This can lead to strange ;;; effects like balls tunneling through walls and other balls. ;;; It is also possible to get balls bouncing inside of each ;;; other in this way. ;;; ;;MAKE-QUEUE-RECORD returns a queue record with the given next, previous, and ;;value values ;;NEXT = The next record pointer ;;PREV = The previous record pointer ;;REST = A list of values for any optional fields (this can be used for ;; creating structure inheritance) (define-macro (make-queue-record next prev . rest) `(vector ,next ,prev ,@rest)) ;;QUEUE-RECORD-NEXT returns the next field of the given queue record ;;QUEUE-RECORD = The queue record whose next field is to be returned (define-macro (queue-record-next queue-record) `(vector-ref ,queue-record 0)) ;;SET-QUEUE-RECORD-NEXT! sets the next field of the given queue record ;;QUEUE-RECORD = The queue record whose next field is to be set ;;VALUE = The value to which the next field is to be set (define-macro (set-queue-record-next! queue-record value) `(vector-set! ,queue-record 0 ,value)) ;;QUEUE-RECORD-PREV returns the prev field of the given queue record ;;QUEUE-RECORD = The queue record whose prev field is to be returned (define-macro (queue-record-prev queue-record) `(vector-ref ,queue-record 1)) ;;SET-QUEUE-RECORD-PREV! sets the prev field of the given queue record ;;QUEUE-RECORD = The queue record whose prev field is to be set ;;VALUE = The value to which the prev field is to be set (define-macro (set-queue-record-prev! queue-record value) `(vector-set! ,queue-record 1 ,value)) ;;QUEUE-RECORD-LEN returns the length of a queue record which has no optional ;;fields (define-macro (queue-record-len) 2) ;;QUEUE-HEAD returns a dummy record at the end of the queue with the record ;;with the smallest key. ;;QUEUE = the queue whose head record is to be returned (define-macro (queue-head queue) `(vector-ref ,queue 0)) ;;QUEUE-TAIL returns a dummy record at the end of the queue with the record ;;with the largest key. ;;QUEUE = the queue whose tail record is to be returned (define-macro (queue-tail queue) `(vector-ref ,queue 1)) ;;QUEUE- dot-product bumper-length-squared)) '() ;Return infinity (+ delta-t ;Else, return the contact time (ball-collision-time ball)))))))))))) ;;BALL-COLLISION-PROCEDURE calculates the new velocities of the given balls ;;based on their collision at the given time. Also, tells all other balls ;;about the new trajectories of these balls so they can update their event ;;queues ;;BALL1 = The first ball ;;BALL2 = The second ball ;;COLLISION-TIME = The collision time ;;GLOBAL-EVENT-QUEUE = The global queue of earliest events for each ball (define (ball-collision-procedure ball1 ball2 collision-time global-event-queue) (queue-remove ;Remove the earliest event associated (ball-global-event-queue-record ;with each ball from the global event ball1)) ;queue (queue-remove (ball-global-event-queue-record ball2)) (let ((ball1-collision-x-position ;Calculate the positions of both balls (+ (ball-collision-x-position ;when they collide ball1) (* (ball-x-velocity ball1) (- collision-time (ball-collision-time ball1))))) (ball1-collision-y-position (+ (ball-collision-y-position ball1) (* (ball-y-velocity ball1) (- collision-time (ball-collision-time ball1))))) (ball2-collision-x-position (+ (ball-collision-x-position ball2) (* (ball-x-velocity ball2) (- collision-time (ball-collision-time ball2))))) (ball2-collision-y-position (+ (ball-collision-y-position ball2) (* (ball-y-velocity ball2) (- collision-time (ball-collision-time ball2)))))) (let ((delta-x ;Calculate the displacements of the (- ball2-collision-x-position ;centers of the two balls ball1-collision-x-position)) (delta-y (- ball2-collision-y-position ball1-collision-y-position))) (let* ((denominator ;Calculate the angle of the line (sqrt (+ (square ;joining the centers at the collision delta-x) ;time with the x-axis (this line is (square ;the normal to the balls at the delta-y)))) ;collision point) (cos-theta (/ delta-x denominator)) (sin-theta (/ delta-y denominator))) (let ((ball1-old-normal-velocity ;Convert the velocities of the balls (+ (* (ball-x-velocity ;into the coordinate system defined by ball1) ;the normal and tangential lines at cos-theta) ;the collision point (* (ball-y-velocity ball1) sin-theta))) (ball1-tang-velocity (- (* (ball-y-velocity ball1) cos-theta) (* (ball-x-velocity ball1) sin-theta))) (ball2-old-normal-velocity (+ (* (ball-x-velocity ball2) cos-theta) (* (ball-y-velocity ball2) sin-theta))) (ball2-tang-velocity (- (* (ball-y-velocity ball2) cos-theta) (* (ball-x-velocity ball2) sin-theta))) (mass1 (ball-mass ball1)) (mass2 (ball-mass ball2))) (let ((ball1-new-normal-velocity ;Calculate the new velocities (/ ;following the collision (the (+ ;tangential velocities are unchanged (* ;because the balls are assumed to be (* 2 ;frictionless) mass2) ball2-old-normal-velocity) (* (- mass1 mass2) ball1-old-normal-velocity)) (+ mass1 mass2))) (ball2-new-normal-velocity (/ (+ (* (* 2 mass1) ball1-old-normal-velocity) (* (- mass2 mass1) ball2-old-normal-velocity)) (+ mass1 mass2)))) (set-ball-x-velocity! ;Store data about the collision in the ball1 ;structure for each ball after (- (* ball1-new-normal-velocity ;converting the information back cos-theta) ;to the x,y frame (* ball1-tang-velocity sin-theta))) (set-ball-y-velocity! ball1 (+ (* ball1-new-normal-velocity sin-theta) (* ball1-tang-velocity cos-theta))) (set-ball-x-velocity! ball2 (- (* ball2-new-normal-velocity cos-theta) (* ball2-tang-velocity sin-theta))) (set-ball-y-velocity! ball2 (+ (* ball2-new-normal-velocity sin-theta) (* ball2-tang-velocity cos-theta))) (set-ball-collision-time! ball1 collision-time) (set-ball-collision-time! ball2 collision-time) (set-ball-collision-x-position! ball1 ball1-collision-x-position) (set-ball-collision-y-position! ball1 ball1-collision-y-position) (set-ball-collision-x-position! ball2 ball2-collision-x-position) (set-ball-collision-y-position! ball2 ball2-collision-y-position)))))) (newline) (display "Ball ") (display (ball-number ball1)) (display " collides with ball ") (display (ball-number ball2)) (display " at time ") (display (ball-collision-time ball1)) (newline) (display " Ball ") (display (ball-number ball1)) (display " has a new velocity of ") (display (ball-x-velocity ball1)) (display ",") (display (ball-y-velocity ball1)) (display " starting at ") (display (ball-collision-x-position ball1)) (display ",") (display (ball-collision-y-position ball1)) (newline) (display " Ball ") (display (ball-number ball2)) (display " has a new velocity of ") (display (ball-x-velocity ball2)) (display ",") (display (ball-y-velocity ball2)) (display " starting at ") (display (ball-collision-x-position ball2)) (display ",") (display (ball-collision-y-position ball2)) (recalculate-collisions ball1 global-event-queue) (recalculate-collisions ball2 global-event-queue)) ;;BUMPER-COLLISION-PROCEDURE calculates the new velocity of the given ball ;;following its collision with the given bumper at the given time. Also, tells ;;other balls about the new trajectory of the given ball so they can update ;;their event queues. ;;BALL = The ball ;;BUMPER = The bumper ;;COLLISION-TIME = The collision time ;;GLOBAL-EVENT-QUEUE = The global queue of earliest events for each ball (define (bumper-collision-procedure ball bumper collision-time global-event-queue) (queue-remove ;Remove the earliest event associated (ball-global-event-queue-record ;with the ball from the global event ball)) ;queue (let ((delta-x-bumper ;Compute the bumper's delta-x (- (bumper-x2 bumper) (bumper-x1 bumper))) (delta-y-bumper ;delta-y (- (bumper-y2 bumper) (bumper-y1 bumper)))) (let ((bumper-length ;length (sqrt (+ (square delta-x-bumper) (square delta-y-bumper))))) (let ((cos-theta ;and cosine and sine of its angle with (/ delta-x-bumper ;respect to the positive x-axis bumper-length)) (sin-theta (/ delta-y-bumper bumper-length)) (x-velocity ;Cache the ball's velocity in the x,y (ball-x-velocity ball)) ;frame (y-velocity (ball-y-velocity ball))) (let ((tang-velocity ;Calculate the ball's velocity in the (+ (* x-velocity ;bumper frame cos-theta) (* y-velocity sin-theta))) (normal-velocity (- (* y-velocity cos-theta) (* x-velocity sin-theta)))) (set-ball-collision-x-position! ;Store the collision position ball (+ (ball-collision-x-position ball) (* (- collision-time (ball-collision-time ball)) (ball-x-velocity ball)))) (set-ball-collision-y-position! ball (+ (ball-collision-y-position ball) (* (- collision-time (ball-collision-time ball)) (ball-y-velocity ball)))) (set-ball-x-velocity! ;Calculate the new velocity in the ball ;x,y frame based on the fact that (+ (* tang-velocity ;tangential velocity is unchanged and cos-theta) ;the normal velocity is inverted when (* normal-velocity ;the ball collides with the bumper sin-theta))) (set-ball-y-velocity! ball (- (* tang-velocity sin-theta) (* normal-velocity cos-theta))) (set-ball-collision-time! ball collision-time))))) (newline) (display "Ball ") (display (ball-number ball)) (display " collides with bumper ") (display (bumper-number bumper)) (display " at time ") (display (ball-collision-time ball)) (newline) (display " Ball ") (display (ball-number ball)) (display " has a new velocity of ") (display (ball-x-velocity ball)) (display ",") (display (ball-y-velocity ball)) (display " starting at ") (display (ball-collision-x-position ball)) (display ",") (display (ball-collision-y-position ball)) (recalculate-collisions ball global-event-queue)) ;;RECALCULATE-COLLISIONS removes all old collisions for the given ball from ;;all other balls' event queues and calcultes new collisions for these balls ;;and places them on the event queues. Also, updates the global event queue if ;;the recalculation of the collision effects the earliest collision for any ;;other balls. ;;BALL = The ball whose collisions are being recalculated ;;GLOBAL-EVENT-QUEUE = The global queue of earliest events for each ball (define (recalculate-collisions ball global-event-queue) (clear-queue (ball-event-queue ;Clear the queue of events for this ball)) ;ball as they have all changed (let ((event-queue ;Calculate all ball collision events (ball-event-queue ball))) ;with balls of lower number (let ((ball-vector (ball-ball-vector ball))) (do ((i (-1+ (ball-number ball)) (-1+ i))) ((negative? i)) (let ((ball2-queue-record (vector-ref ball-vector i))) (set-event-queue-record-collision-time! ball2-queue-record (ball-ball-collision-time ball (event-queue-record-object ball2-queue-record))) (queue-insert event-queue ball2-queue-record)))) (let ((bumper-vector ;Calculate all bumper collision events (ball-bumper-vector ball))) (do ((i (-1+ (vector-length bumper-vector)) (-1+ i))) ((negative? i)) (let ((bumper-queue-record (vector-ref bumper-vector i))) (set-event-queue-record-collision-time! bumper-queue-record (ball-bumper-collision-time ball (event-queue-record-object bumper-queue-record))) (queue-insert event-queue bumper-queue-record)))) (let ((global-queue-record ;Get the global event queue record (ball-global-event-queue-record ;for this ball ball))) (set-event-queue-record-collision-time! ;Set the new earliest event time global-queue-record ;for this ball (if (empty-queue? event-queue) '() (event-queue-record-collision-time (queue-smallest event-queue)))) (queue-insert ;Enqueue on the global event queue global-event-queue ;the earliest event between this ball global-queue-record))) ;and any ball of lower number or any ;bumper (for-each ;For each ball on the ball list: (lambda (ball2) (let ((ball2-event-queue (ball-event-queue ball2))) (let ((alter-global-event-queue? ;Set flag to update global event queue (and ;if the earliest event for ball2 was (not (empty-queue? ;with the deflected ball ball2-event-queue)) (eq? ball (event-queue-record-object (queue-smallest ball2-event-queue))))) (ball-event-queue-record ;Get the queue record for the deflected (vector-ref ;ball for this ball (ball-ball-vector ball2) (ball-number ball)))) (queue-remove ;Remove the queue record for the ball-event-queue-record) ;deflected ball (set-event-queue-record-collision-time! ;Recalculate the collision ball-event-queue-record ;time for this ball and the deflected (ball-ball-collision-time ;ball ball ball2)) (queue-insert ;Enqueue the new collision event ball2-event-queue ball-event-queue-record) (if (or alter-global-event-queue? ;If the earliest collision event for (eq? ball ;this ball has changed: (event-queue-record-object (queue-smallest ball2-event-queue)))) (let ((queue-record ;Remove the old event from the global (ball-global-event-queue-record ;event queue and replace it ball2))) ;with the new event (set-event-queue-record-collision-time! queue-record (event-queue-record-collision-time (queue-smallest ball2-event-queue))) (queue-remove queue-record) (queue-insert global-event-queue queue-record)))))) (ball-ball-list ball))) ;;SIMULATE performs the billiard ball simulation for the given ball list and ;;bumper list until the specified time. ;;BALL-LIST = A list of balls ;;BUMPER-LIST = A list of bumpers ;;END-TIME = The time at which the simulation is to terminate (define (simulate ball-list bumper-list end-time) (let ((num-of-balls ;Cache the number of balls and bumpers (length ball-list)) (num-of-bumpers (length bumper-list)) (global-event-queue ;Build the global event queue (make-sorted-queue collision-time-