require 'ruby-processing' class Ball attr_accessor :x, :y, :r, :m def initialize(x = 0.0, y = 0.0, r = 0.0) @x = x @y = y @r = r @m = r*0.1 end end class Vect2D attr_accessor :vx, :vy def initialize(vx = 0.0, vy = 0.0) @vx = vx @vy = vy end end class Sketch < Processing::App include Math def setup smooth noStroke frameRate 30 @balls = [ Ball.new(100, 400, 10), Ball.new(700, 400, 40) ] @vels = [ Vect2D.new(2.15*2, -1.35*2), Vect2D.new(-1.65*3, 0.42*3) ] @frame_time = nil end def draw t = Time.now if @frame_time fps = 1.0 / (t - @frame_time) # printf "%0.1ffps\n", fps end @frame_time = t background 51 fill 240 @balls.length.times do |i| @balls[i].x += @vels[i].vx @balls[i].y += @vels[i].vy ellipse(@balls[i].x, @balls[i].y, @balls[i].r*2, @balls[i].r*2) checkBoundaryCollision(@balls[i], @vels[i]) end checkObjectCollision(@balls, @vels) end def checkObjectCollision(b, v) # get distances between the balls components bVect = Vect2D.new bVect.vx = b[1].x - b[0].x bVect.vy = b[1].y - b[0].y # calculate magnitude of the vector separating the balls bVectMag = sqrt(bVect.vx * bVect.vx + bVect.vy * bVect.vy) if bVectMag < b[0].r + b[1].r # get angle of bVect theta = atan2(bVect.vy, bVect.vx) # precalculate trig values sine = sin(theta) cosine = cos(theta) # bTemp will hold rotated ball positions. You just # need to worry about bTemp[1] position bTemp = [Ball.new, Ball.new] # b[1]'s position is relative to b[0]'s # so you can use the vector between them (bVect) as the # reference point in the rotation expressions. # bTemp[0].x and bTemp[0].y will initialize # automatically to 0.0, which is what you want # since b[1] will rotate around b[0] bTemp[1].x = cosine * bVect.vx + sine * bVect.vy bTemp[1].y = cosine * bVect.vy - sine * bVect.vx # rotate Temporary velocities vTemp = [Vect2D.new, Vect2D.new] vTemp[0].vx = cosine * v[0].vx + sine * v[0].vy vTemp[0].vy = cosine * v[0].vy - sine * v[0].vx vTemp[1].vx = cosine * v[1].vx + sine * v[1].vy vTemp[1].vy = cosine * v[1].vy - sine * v[1].vx # Now that velocities are rotated, you can use 1D # conservation of momentum equations to calculate # the final velocity along the x-axis. vFinal = [Vect2D.new, Vect2D.new] # final rotated velocity for b[0] vFinal[0].vx = ((b[0].m - b[1].m) * vTemp[0].vx + 2 * b[1].m * vTemp[1].vx) / (b[0].m + b[1].m) vFinal[0].vy = vTemp[0].vy # final rotated velocity for b[0] vFinal[1].vx = ((b[1].m - b[0].m) * vTemp[1].vx + 2 * b[0].m * vTemp[0].vx) / (b[0].m + b[1].m) vFinal[1].vy = vTemp[1].vy # hack to avoid clumping bTemp[0].x += vFinal[0].vx bTemp[1].x += vFinal[1].vx # Rotate ball positions and velocities back # Reverse signs in trig expressions to rotate # in the opposite direction # rotate balls bFinal = [Ball.new, Ball.new] bFinal[0].x = cosine * bTemp[0].x - sine * bTemp[0].y bFinal[0].y = cosine * bTemp[0].y + sine * bTemp[0].x bFinal[1].x = cosine * bTemp[1].x - sine * bTemp[1].y bFinal[1].y = cosine * bTemp[1].y + sine * bTemp[1].x # update balls to screen position b[1].x = b[0].x + bFinal[1].x b[1].y = b[0].y + bFinal[1].y b[0].x = b[0].x + bFinal[0].x b[0].y = b[0].y + bFinal[0].y # update velocities v[0].vx = cosine * vFinal[0].vx - sine * vFinal[0].vy v[0].vy = cosine * vFinal[0].vy + sine * vFinal[0].vx v[1].vx = cosine * vFinal[1].vx - sine * vFinal[1].vy v[1].vy = cosine * vFinal[1].vy + sine * vFinal[1].vx end end # checkBoundaryCollision() function: def checkBoundaryCollision(ball, vel) if ball.x > width-ball.r ball.x = width-ball.r vel.vx *= -1 elsif ball.x < ball.r ball.x = ball.r vel.vx *= -1 elsif ball.y > height-ball.r ball.y = height-ball.r vel.vy *= -1 elsif ball.y < ball.r ball.y = ball.r vel.vy *= -1 end end end Sketch.new(:width => 400, :height => 400, :title => "CircleCollision")