Line Segment/Circle - Collision Detection

27 Jul 2010

Here is another piece of code I didn't want to throw away, it uses vector projection to determine if a circle collides with a line segment. The idea was to figure out if one NPC can pass the ball to another in a way that no opposing team members can intercept it. We represent each opposing team player as a circle, the size of the circle will grow or shrink depending on the velocity of the player then we check each circle for collision with the line segment if non collides we assume it is a safe pass, following images shows a safe pass where non of the three opposing players can intercept the pass,

line segment collision

 (defn closest-point-on-line [a b c]
   (let [ac (subtract c a)
         ab (subtract b a)
         proj-mag (dot-product ac (normalize ab))]
     (cond (< proj-mag 0) a
           (> proj-mag (magnitude ab)) b
           :default (add (project ac ab) a))))

We begin by creating two new vectors, one from the start of the line to end of the line (AB) and one from start of the line to the center of the circle (AC), then we calculate the magnitude (length) of the projection of AC onto AB, if it is smaller than 0 then the closest point on this line to the circle is the point A (start of the line segment), if it is bigger than the magnitude of the AB vector then the closest point is B, else we return the projection of AC onto AB plus A (which converts it back into world coordinates) that gives us the closest point on the line to the circle.

 (defn path-clean [a b c r]
   (let [closest (closest-point-on-line a b c)
         distance (magnitude (subtract c closest))]
     (if (<= distance r)
       false true)))

Now that we know the location of the closest point on the line to the circle, collision detection is as simple as calculating the length between closest point and the circle, if it is smaller than the radius of the circle we have a collision else we have a clean path.

Physics with Clojure

21 Jul 2010

A brief demonstration of simulating mechanical things in Clojure using ODE4J,

 (defn world [g]
   (let [[x y z] g] 
     (doto (OdeHelper/createWorld)
       (.setGravity x y z))))

We begin by creating a world object which will hold all rigid bodies and joints and set the gravitational force that will act on our objects.

 (defn body [world [x y z]]
   (let [body (OdeHelper/createBody world)
         mass (doto (OdeHelper/createMass)
                (.setSphere 2500 0.05))] 
     (doto body
       (.setMass mass)
       (.setPosition x y z))))

For each object we want to simulate we create a body and set the parameters associated with it, i.e its shape and mass, for this simple example we create a body that behaves like a sphere with a density of 2500 and a radius of 0.05,

 (defn environment [world]
   (let [b1 (body world [1 2 0])
         b2 (body world [2 2 0])
         j1 (doto (OdeHelper/createHingeJoint world)
              (.attach b1 nil)
              (.setAnchor 0 2 0)
              (.setAxis 0 0 1)
              (.setParamVel 0)
              (.setParamFMax 30))
         j2 (doto (OdeHelper/createBallJoint world)
              (.attach b1 b2)
              (.setAnchor 1 2 0))] 
     [b1 b2 j2 j1]))

Next we create the simulation environment we will be using, we create two bodies and two joints forming two pendulums connected to each other, bottom joint is a ball joint which allows the body to move freely, upper joint is a hinge joint which is a motor driven joint that rotates around Z axis with an initial angular speed of 0 and 30 units of maximum force.

 (defn bang-bang [joint]
   (if (> (.getAngle joint) 2.5)
     (.setParamVel joint -0.1)
     (.setParamVel joint 2)))

In order to keep the pendulum at desired angle, we use a type of controller called a bang bang controller (on-off controller),

 (defn bang-bang [joint]
   (if (> (.getAngle joint) 2.5)
     (.setParamVel joint -0.1)
     (.setParamVel joint 2)))

All thats left to do is to paint the bodies to visualize the motion, on every tick we repaint the panel, calculate the velocity needed and let the engine step 0.05 seconds,

 (defn panel [world environment]
   (let [pos #(let [pos (.getPosition %)] [(.get0 pos) (.get1 pos)])
         coords #(vector (+ 100 (* 50 (first %))) (* 50 (second %)))
         circle #(let [rad 20 
                       offset (int (/ rad 2))
                       x (- %2 offset) 
                       y (- %3 offset)]
                   (.fill %1 (Ellipse2D$Double. x y rad rad)))]
     (proxy [JPanel ActionListener] [] 
       (paintComponent
        [g]
        (let [[b1 b2 _ j2] environment
              [x1 y1] (coords (pos b1))
              [x2 y2] (coords (pos b2))
              [lx1 ly1] (coords [0 2])]
          (.setColor g java.awt.Color/WHITE)
          (.fillRect g 0 0 (.getWidth this) (.getHeight this))
          (.setColor g java.awt.Color/BLACK)
          (.drawString g (apply str "Angle: " 
                                (take 5 (str (.getAngle j2)))) 20 20)
          (.drawLine g lx1 ly1 x1 y1)
          (circle g x1 y1)
          (.drawLine g x1 y1 x2 y2)
          (circle g x2 y2)))
       (actionPerformed [e] 
                        (.repaint this)
                        (bang-bang (last environment))
                        (.step world 0.05)))))

ode4j bang-bang pendulum

Files,