{- Haskell Dynamics Engine, Copyright (C) 2007 Ruben Henner Zilibowitz All rights reserved. Email: rubenz@cse.unsw.edu.au Web: www.cse.unsw.edu.au/~rubenz This is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License that is include with this package in the file LICENSE-GPL.TXT. -} module Collisions where import Matrix3x3 import Quaternions import Objects import Data.Maybe import Data.Array -- sweep takes a list of intervals sorted by start position -- and returns a list of overlapping pairs of intervals. sweep :: [Interval] -> [(Int,Int)] sweep [] = [] sweep ((i,(b,e)) : is) = [(i,j) | (j,_) <- (takeWhile ((<= e).fst.snd) is)] ++ (sweep is) -- does the collision detection -- uses sort and sweep optimisation findAllContacts :: World -> [Joint] findAllContacts (World_ bodies islands _ _ sortedIntervals _ _ _) = (concat.catMaybes) [tryCollide i j (bodies!i) (bodies!j) | (i,j) <- (sweep sortedIntervals), not (or [(i `elem` island) && (j `elem` island) | island <- islands]), not ((anchored (bodies!i)) && (anchored (bodies!j)))] {- -- do collision detection -- no optimisation - all pairs checked findAllContacts :: World -> [Contact] findAllContacts (World_ bodies islands _ _ _) = (concat.catMaybes) (map2' (\i j -> if (or [(i `elem` island) && (j `elem` island) | island <- islands]) then Nothing else (tryCollide i j (bodies!i) (bodies!j))) (indices bodies)) map2 :: (a -> b -> c) -> [a] -> [b] -> [c] map2 f as bs = [f a b | a <- as, b <- bs] map2' :: (a -> a -> c) -> [a] -> [c] map2' _ [] = [] map2' f (a:as) = (map (f a) as) ++ (map2' f as) -} --- --- tryCollide --- Try colliding two geometries together. If no result then do them the other way around. --- tryCollide :: Int -> Int -> Body -> Body -> Maybe [Joint] tryCollide i j b1 b2 | (anchored b1 && anchored b2) = Nothing | (not (bodyBoundingSpheresIntersect b1 b2)) = Nothing | (isJust test1) = test1 | otherwise = test2 where test1 = collide i j b1 b2 test2 = collide j i b2 b1 boundingSpheresIntersect :: BoundingSphere -> BoundingSphere -> Bool boundingSpheresIntersect (c1,r1) (c2,r2) = let d = c2-c1 in (dot d d) < (r1 + r2)^2 bodyBoundingSpheresIntersect :: Body -> Body -> Bool bodyBoundingSpheresIntersect b1 b2 = boundingSpheresIntersect (boundingSphere b1) (boundingSphere b2) --- --- collide --- In the resulting contacts the normal vector should always point outwards from the second body. --- collide :: Int -> Int -> Body -> Body -> Maybe [Joint] collide i j body1@(Body_ {geometry = CompoundGeometry g}) body2 = Just ((concat.catMaybes) [tryCollide i j (body1 {geometry = geom, orientation = (orientation body1)*rot, Objects.position = (Objects.position body1) + (orientation body1).*loc}) body2 | (geom,rot,loc) <- g]) -- sphere/sphere -- works collide i j body1@(Body_ {geometry = Sphere r}) body2@(Body_ {geometry = Sphere s}) = Just (map (makeContact i j) (maybeToList (sphereWithSphere (r,position body1) (s,position body2)))) -- plane/sphere -- works collide i j body1@(Body_ {geometry = Plane a b c}) body2@(Body_ {geometry = Sphere r}) = Just (map (makeContact i j) (maybeToList (planeWithSphere (a,b,c) (r,position body2)))) -- sphere/box -- nyi --collide (Sphere r,loc1,_) (Box x y z,loc2,rot2) = Just [] -- sphere/SphereCappedCylinder -- test this collide i j body1@(Body_ {geometry = Sphere r1}) body2@(Body_ {geometry = SphereCappedCylinder r2 h}) | (isJust shell) = Just [reverseContactNormal (makeContact j i (fromJust shell))] | otherwise = Just (map (makeContact i j) caps) where loc1 = position body1 c = (position body2) + ((orientation body2) .* (Vec 0 0 (0.5*h))) d = (position body2) + ((orientation body2) .* (Vec 0 0 (-0.5*h))) shell = cylinderShellWithSphere (r2,c,d) (r1,loc1) caps = catMaybes (map (sphereWithSphere (r1,loc1)) [(r2,c),(r2,d)]) -- SphereCappedCylinder/SphereCappedCylinder -- test this collide i j body1@(Body_ {geometry = SphereCappedCylinder r1 h1}) body2@(Body_ {geometry = SphereCappedCylinder r2 h2}) | (isJust shells) = Just (map (makeContact i j) (maybeToList shells)) | ((not.null) caps) = Just (map (makeContact i j) caps) | otherwise = Just (abCapsShell ++ cdCapsShell) where loc1 = position body1 loc2 = position body2 rot1 = orientation body1 rot2 = orientation body2 a = loc1 + (rot1 .* (Vec 0 0 (0.5*h1))) b = loc1 + (rot1 .* (Vec 0 0 (-0.5*h1))) c = loc2 + (rot2 .* (Vec 0 0 (0.5*h2))) d = loc2 + (rot2 .* (Vec 0 0 (-0.5*h2))) shells = cylinderShells (r1,a,b) (r2,c,d) abCapsShell = map reverseContactNormal (map (makeContact j i) (catMaybes (map (cylinderShellWithSphere (r2,c,d)) [(r1,a),(r1,b)]))) cdCapsShell = map (makeContact i j) (catMaybes (map (cylinderShellWithSphere (r1,a,b)) [(r2,c),(r2,d)])) caps = catMaybes ((map (sphereWithSphere (r1,a)) [(r2,c),(r2,d)]) ++ (map (sphereWithSphere (r1,b)) [(r2,c),(r2,d)])) -- Plane/SphereCappedCylinder -- tested - seems to work collide i j body1@(Body_ {geometry = Plane a b c}) body2@(Body_ {geometry = SphereCappedCylinder r h}) = Just (map (makeContact i j) (catMaybes [planeWithSphere (a,b,c) (r,p),planeWithSphere (a,b,c) (r,q)])) where loc = position body2 rot = orientation body2 p = loc + (rot .* (Vec 0 0 (-0.5*h))) q = loc + (rot .* (Vec 0 0 (0.5*h))) -- Box/Box -- nyi --collide b1@(Box _ _ _,_,_) b2@(Box _ _ _,_,_) = Just (boxbox b1 b2) -- Box/Plane -- test this {- collide (Box x y z,loc,rot) (Plane a b c,_,_) = Just (filter (\(Contact_ _ _ d) -> d >= 0) [Contact_ boxV n (-((dot n boxV) + p)) | boxV <- corners]) where n = Matrix3x3.normalise (cross (c-a) (b-a)) p = -(dot n a) corners = [(quatToVec (rot*(Q 0 x' y' z')/rot)) + loc | x'<-[-x,x],y'<-[-y,y],z'<-[-z,z]] quatToVec (Q _ x y z) = Vec x y z -} -- Plane/Plane (should be ignored) -- works collide _ _ (Body_ {geometry = Plane _ _ _}) (Body_ {geometry = Plane _ _ _}) = Just [] collide _ _ _ _ = Nothing reverseContactNormal :: Joint -> Joint reverseContactNormal (Joint_ i j (Contact p n d)) = Joint_ j i (Contact p (-n) d) ----------- makeContact i j (ContactData_ loc norm depth) = Joint_ i j (Contact loc norm depth) data ContactData = ContactData_ (Vector RealNum) (Vector RealNum) RealNum -- location normal depth -- collides a plane with a sphere planeWithSphere :: (Vector RealNum,Vector RealNum,Vector RealNum) -> (RealNum,Vector RealNum) -> Maybe ContactData planeWithSphere (a,b,c) (radius,centre) | (centreLen > radius) = Nothing | otherwise = Just (ContactData_ pos normal (centreLen - radius)) where n = cross (c-a) (b-a) m = toMatrixCols (b-a) (c-a) n m' = Matrix3x3.inverse m (Vec x y _) = matXvec m' (centre-a) q = (b-a).*.x + (c-a).*.y + a centreVec = q - centre centreLen = Matrix3x3.magnitude centreVec normal = centreVec./.centreLen p = centre + normal.*.radius pos = (q + p).*.0.5 -- collides two spheres with one another sphereWithSphere :: (RealNum,Vector RealNum) -> (RealNum,Vector RealNum) -> Maybe ContactData sphereWithSphere (r,c) (s,d) | denom > ((r+s)^2) = Nothing | denom <= 0 = Just (ContactData_ c (Vec 1 0 0) (negate (r+s))) | otherwise = Just (ContactData_ pos n (dist-r-s)) where p = c - d denom = dot p p dist = sqrt denom n = p ./. dist pos = (d + n.*.(s-r) + c).*.0.5 -- collides a cylindrical shell with a sphere -- nb: ignores case where sphere centre lies on the axis of the cylinder cylinderShellWithSphere :: (RealNum,Vector RealNum,Vector RealNum) -> (RealNum,Vector RealNum) -> Maybe ContactData cylinderShellWithSphere (r1,a@(Vec ax ay az),b@(Vec bx by bz)) (r2,c@(Vec cx cy cz)) | (centreDist <= 0) = Nothing | (0 <= alpha && alpha <= 1 && depth <= 0) = Just (ContactData_ pos normal depth) | otherwise = Nothing where p = a - b --denom = (bz^2-2*az*bz+by^2-2*ay*by+bx^2-2*ax*bx+az^2+ay^2+ax^2) denom = dot p p alpha = -((bz-az)*cz+(by-ay)*cy+(bx-ax)*cx-bz^2+az*bz-by^2+ay*by-bx^2+ax*bx)/denom axisPos = b + p.*.alpha centreVec = axisPos - c centreDist = Matrix3x3.magnitude centreVec normal = centreVec ./. centreDist pos1 = axisPos - normal.*.r1 pos2 = c + normal.*.r2 pos = (pos1 + pos2).*.0.5 depth = dot (pos1 - pos2) normal -- collides two cylindrical shells -- nb: ignores case where cylinders are parallel to one another cylinderShells :: (RealNum,Vector RealNum,Vector RealNum) -> (RealNum,Vector RealNum,Vector RealNum) -> Maybe ContactData cylinderShells (r1,a@(Vec ax ay az),b@(Vec bx by bz)) (r2,c@(Vec cx cy cz),d@(Vec dx dy dz)) | (denom > 0 && 0 <= alpha && alpha <= 1 && 0 <= beta && beta <= 1 && depth <= 0) = Just (ContactData_ pos normal depth) | otherwise = Nothing where p@(Vec axmbx aymby azmbz) = a - b q@(Vec cxmdx cymdy czmdz) = c - d (Vec dxmbx dymby dzmbz) = d - b denom=((aymby^2+axmbx^2)*czmdz^2+((-2)*aymby*azmbz*cymdy-2*axmbx*azmbz*cxmdx)*czmdz+(azmbz^2+axmbx^2)*cymdy^2-2*axmbx*aymby*cxmdx*cymdy+(azmbz^2+aymby^2)*cxmdx^2) alpha=(-(((aymby*cymdy+axmbx*cxmdx)*czmdz-azmbz*cymdy^2-azmbz*cxmdx^2)*dzmbz+((-aymby)*czmdz^2+azmbz*cymdy*czmdz+axmbx*cxmdx*cymdy-aymby*cxmdx^2)*dymby+((-axmbx)*czmdz^2+azmbz*cxmdx*czmdz-axmbx*cymdy^2+aymby*cxmdx*cymdy)*dxmbx))/denom beta=(-(((aymby^2+axmbx^2)*czmdz-aymby*azmbz*cymdy-axmbx*azmbz*cxmdx)*dzmbz+((-aymby)*azmbz*czmdz+(azmbz^2+axmbx^2)*cymdy-axmbx*aymby*cxmdx)*dymby+((-axmbx)*azmbz*czmdz-axmbx*aymby*cymdy+(azmbz^2+aymby^2)*cxmdx)*dxmbx))/denom nearestPos1 = b + p.*.alpha nearestPos2 = d + q.*.beta pos1 = nearestPos1 - normal.*.r1 pos2 = nearestPos2 + normal.*.r2 pos = (pos1 + pos2).*.0.5 depth = dot (pos1 - pos2) normal normal = Matrix3x3.normalise (nearestPos1 - nearestPos2) ----------------- ---------