#lang scheme/base
(require scheme/function
scheme/list
scheme/local
scheme/contract
scheme/unsafe/ops)
(define (unsafe-flsqr a)
(unsafe-fl* a a))
(define (posn-distance p1 p2)
(sqrt (unsafe-fl+ (unsafe-flsqr (unsafe-fl- (unsafe-vector-ref p1 0) (unsafe-vector-ref p2 0)))
(unsafe-flsqr (unsafe-fl- (unsafe-vector-ref p1 1) (unsafe-vector-ref p2 1))))))
(define-struct body (layer posn radius vel)
#:prefab)
(define (body-posn-adjust! b dp)
(define v1 (unsafe-struct-ref b 1))
(define v2 dp)
(unsafe-vector-set! v1 0 (unsafe-fl+ (unsafe-vector-ref v1 0) (unsafe-vector-ref v2 0)))
(unsafe-vector-set! v1 1 (unsafe-fl+ (unsafe-vector-ref v1 1) (unsafe-vector-ref v2 1))))
(define (body-step! dx b)
(define v1 (unsafe-struct-ref b 1))
(define v2 (unsafe-struct-ref b 3))
(unsafe-vector-set! v1 0 (unsafe-fl+ (unsafe-vector-ref v1 0) (unsafe-fl* dx (unsafe-vector-ref v2 0))))
(unsafe-vector-set! v1 1 (unsafe-fl+ (unsafe-vector-ref v1 1) (unsafe-fl* dx (unsafe-vector-ref v2 1)))))
(define how-many-collision-tests 0)
(define (bodies-overlap? b1 b2)
(define p1 (unsafe-struct-ref b1 1))
(define p2 (unsafe-struct-ref b2 1))
(define d (posn-distance p1 p2))
(set! how-many-collision-tests (add1 how-many-collision-tests))
(unsafe-fl< d (unsafe-fl+ (unsafe-struct-ref b1 2) (unsafe-struct-ref b2 2))))
(define-struct simulation ([body-count #:mutable] layer-hash))
(define (create-simulation)
(make-simulation 0 (make-hasheq)))
(define (simulation-add-body! sim b)
(define layer-hash (simulation-layer-hash sim))
(set-simulation-body-count! sim (add1 (simulation-body-count sim)))
(hash-update! layer-hash (body-layer b)
(curry list* b)
empty))
(define-syntax-rule (body-for-each-corner! the-hash b y-ht expr ...)
(local [(define the-b b)
(define pos (unsafe-struct-ref the-b 1))
(define rad (unsafe-struct-ref the-b 2))
(define min-x (unsafe-fl- (unsafe-vector-ref pos 0) rad))
(define max-x (unsafe-fl+ (unsafe-vector-ref pos 0) rad))
(define min-y (unsafe-fl- (unsafe-vector-ref pos 1) rad))
(define max-y (unsafe-fl+ (unsafe-vector-ref pos 1) rad))
(define min-x-c (comp->cell min-x))
(define max-x-c (comp->cell max-x))
(define min-y-c (comp->cell min-y))
(define max-y-c (comp->cell max-y))
(define minx-ht (hash-ref! the-hash min-x-c make-hasheq))
(define maxx-ht (hash-ref! the-hash max-x-c make-hasheq))
(define minx-miny-ht (hash-ref! minx-ht min-y-c make-hasheq))
(define maxx-miny-ht (hash-ref! maxx-ht min-y-c make-hasheq))
(define minx-maxy-ht (hash-ref! minx-ht max-y-c make-hasheq))
(define maxx-maxy-ht (hash-ref! maxx-ht max-y-c make-hasheq))]
(local [(define y-ht minx-miny-ht)]
expr ...)
(local [(define y-ht minx-maxy-ht)]
expr ...)
(local [(define y-ht maxx-miny-ht)]
expr ...)
(local [(define y-ht maxx-maxy-ht)]
expr ...)))
(define cell-size 2.0)
(define (comp->cell x)
(inexact->exact (floor (unsafe-fl/ x cell-size))))
(define (spatial-hash-collisions collide! sim)
(define the-hash (make-hasheq))
(define (add-body-to-hash! b)
(define b1-layer (body-layer b))
(body-for-each-corner!
the-hash b y-ht
(define old (hash-ref! y-ht b1-layer empty))
(hash-set! y-ht b1-layer (list* b old))))
(define seen?-ht (make-hasheq))
(simulation-for-each-body! sim add-body-to-hash!)
(for ([(b1-layer fbodies) (in-hash (simulation-layer-hash sim))])
(hash-set! seen?-ht b1-layer #t)
(for ([b1 (in-list fbodies)])
(define b1-seen?-ht (make-hasheq))
(body-for-each-corner!
the-hash b1 y-ht
(for* ([(b2-layer lbodies) (in-hash y-ht)]
#:when (not (hash-has-key? seen?-ht b2-layer))
[b2 (in-list lbodies)]
#:when (not (hash-has-key? b1-seen?-ht b2)))
(hash-set! b1-seen?-ht b2 #t)
(when (bodies-overlap? b1 b2)
(collide! b1 b2)))))))
(define (simulation-for-each-body! sim f)
(for* ([bodies (in-hash-values (simulation-layer-hash sim))]
[b (in-list bodies)])
(f b)))
(define (simulation-bodies sim)
(for*/list ([bodies (in-hash-values (simulation-layer-hash sim))]
[b (in-list bodies)])
b))
(define (simulate! collide! sim dx)
(simulation-for-each-body! sim (curry body-step! dx))
(set! how-many-collision-tests 0)
(spatial-hash-collisions collide! sim)
(printf "~a collisons tests in ~a bodies~n" how-many-collision-tests (simulation-body-count sim)))
(provide/contract
[struct body ([layer symbol?]
[posn (vector/c inexact-real? inexact-real?)]
[radius inexact-real?]
[vel (vector/c inexact-real? inexact-real?)])]
[body-posn-adjust! (body? (vector/c inexact-real? inexact-real?) . -> . void)]
[create-simulation (-> simulation?)]
[simulation-add-body! (simulation? body? . -> . void)]
[simulation-bodies (simulation? . -> . (listof body?))]
[simulate! ((body? body? . -> . void)
simulation?
inexact-real?
. -> .
void)])