Gosu Forums
Topic Gosu / Gosu Showcase / A little code about collisions
By bestguigui Date 2015-11-21 15:49
Hi,

I coded something today that may help others, so I share it. The basic idea was to create a Shape class, that has some children :
- Point : a position in 2D space
- Segment : finite lines from a point_a to a point_b
- Circle : a Circle class that has a center Point and a radius (thanks jlnr for your drawing code about this !)
- AABB : a class to represent rectangles

The idea was to provide a generic collides?(other) method, that will return true of false if the two shapes are colliding. It's not perfect, but at least, it works ! I suck at Maths a lot, so it's basically an adaptation of C++ code, without understanding the calculations so much... Except for the cases that are really simple.

require 'gosu'

class CircleGenerator
@columns = @rows = radius * 2
lower_half = (0...radius).map do |y|
x = Math.sqrt(radius**2 - y**2).round
right_half = "#{"\xff" * x}#{"\x00" * (radius - x)}"
"#{right_half.reverse}#{right_half}"
end.join
@blob = lower_half.reverse + lower_half
@blob.gsub!(/./) { |alpha| "\xff\xff\xff#{alpha}"}
end

def to_blob
@blob
end
end

class Shape
def collision_point_point(p1, p2)
return (p1.x == p2.x and p1.y == p2.y)
end

def collision_point_circle(p, c)
x, y = p.x, p.y
d2 = (x-c.x)*(x-c.x) + (y-c.y)*(y-c.y)
return (d2 < c.radius**2)
end

def collision_point_aabb(p, a)
x2, y2 = p.x, p.y
x, y, w, h = a.x, a.y, a.w, a.h
return (x2 >= x and x2 < x + w  and y2 >= y and y2 < y + h)
end

def collision_aabb_aabb(a, b)
return !(a.x >= b.x + b.w or a.x + a.w <= b.x or a.y >= b.y + b.h or a.y + a.h <= b.y)
end

def collision_circle_circle(c1, c2)
d2 = (c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y)
end

def projection_on_segment(cx, cy, ax, ay, bx, by)
acx = cx - ax;  acy = cy - ay
abx = bx - ax; aby = by - ay
bcx = cx - bx; bcy = cy - by
s1 = (acx * abx) + (acy * aby)
s2 = (bcx * abx) + (bcy * aby)
return (s1 * s2 > 0)
end

def collision_circle_aabb(c, a)
x = c.x - c.radius; y = c.y - c.radius; w = h = c.radius * 2
box_cercle = AABB.new(x, y, w, h)
return false if !collision_aabb_aabb(box_cercle, a)
return true if collision_point_circle(Point.new(a.x, a.y), c) or collision_point_circle(Point.new(a.x, a.y + a.h), c) or collision_point_circle(Point.new(a.x + a.w, a.y), c) or collision_point_circle(Point.new(a.x + a.w, a.y + a.h), c)
return true if collision_point_aabb(Point.new(c.x, c.y), a)
projection_v = projection_on_segment(c.x, c.y, a.x, a.y, a.x, a.y + a.h)
projection_h = projection_on_segment(c.x, c.y, a.x, a.y, a.x + a.w, a.y)
if (!projection_v or !projection_h)
return true
else
return false
end
end

def collision_droite_cercle(a, b, c)
u = Point.new(b.x - a.x, b.y - a.y)
ac = Point.new(c.x - a.x, c.y - a.y)
numerateur = u.x * ac.y - u.y * ac.x
numerateur = -numerateur if (numerateur < 0)
denominateur = Math.sqrt(u.x * u.x + u.y * u.y)
ci = numerateur / denominateur
if (ci < c.radius)
return true
else
return false
end
end

def collision_droite_segment(a, b, o, p)
ab = Point.new(b.x - a.x, b.y - a.y)
ap = Point.new(p.x - a.x, p.y - a.y)
ao = Point.new(o.x - a.x, o.y - a.y)
if ((ab.x * ap.y - ab.y * ap.x) * (ab.x * ao.y - ab.y * ao.x) < 0)
return true
else
return false
end
end

def collision_segment_segment(a, b, o, p)
return false if !collision_droite_segment(a, b, o, p)
return false if !collision_droite_segment(o, p, a, b)
return true
end

def collision_segment_aabb(a, b, aabb)
# top segment
return true if collision_segment_segment(a, b, Point.new(aabb.x, aabb.y), Point.new(aabb.x + aabb.w, aabb.y))
# bottom segment
return true if collision_segment_segment(a, b, Point.new(aabb.x, aabb.y + aabb.h), Point.new(aabb.x + aabb.w, aabb.y + aabb.h))
# left segment
return true if collision_segment_segment(a, b, Point.new(aabb.x, aabb.y), Point.new(aabb.x, aabb.y + aabb.h))
# right segment
return true if collision_segment_segment(a, b, Point.new(aabb.x + aabb.w, aabb.y), Point.new(aabb.x + aabb.w, aabb.y + aabb.h))
return false
end

def collision_segment_cercle(a, b, c)
return false if !collision_droite_cercle(a, b, c)
ab = Point.new(b.x - a.x, b.y - a.y)
ac = Point.new(c.x - a.x, c.y - a.y)
bc = Point.new(c.x - b.x, c.y - b.y)
pscal1 = ab.x * ac.x + ab.y * ac.y
pscal2 = (-ab.x) * bc.x + (-ab.y) * bc.y
return true if (pscal1 >= 0 and pscal2 >= 0)
return true if (collision_point_circle(a, c))
return true if (collision_point_circle(b, c))
return false
end

def collides?(other)
# Point vs Point
if (self.is_a?(Point) and other.is_a?(Point))
collision_point_point(self, other)
# Circle vs Point
elsif (self.is_a?(Circle) and other.is_a?(Point)) or (other.is_a?(Circle) and self.is_a?(Point))
x = other.is_a?(Point) ? other.x : self.x
y = other.is_a?(Point) ? other.y : self.y
c = other.is_a?(Point) ? self : other
collision_point_circle(Point.new(x, y), c)
# AABB vs Point
elsif (self.is_a?(Point) and other.is_a?(AABB)) or (self.is_a?(AABB) and other.is_a?(Point))
x2 = other.is_a?(Point) ? other.x : self.x
y2 = other.is_a?(Point) ? other.y : self.y
x = other.is_a?(Point) ? self.x : other.x
y = other.is_a?(Point) ? self.y : other.y
w = other.is_a?(Point) ? self.w : other.w
h = other.is_a?(Point) ? self.h : other.h
collision_point_aabb(Point.new(x2, y2), AABB.new(x, y, w, h))
# AABB vs AABB
elsif self.is_a?(AABB) and other.is_a?(AABB)
collision_aabb_aabb(self, other)
# Circle vs Circle
elsif self.is_a?(Circle) and other.is_a?(Circle)
collision_circle_circle(self, other)
# Circle vs AABB
elsif (self.is_a?(AABB) and other.is_a?(Circle)) or (self.is_a?(Circle) and other.is_a?(AABB))
c1 = self.is_a?(Circle) ? self : other
box1 = self.is_a?(AABB) ? self : other
collision_circle_aabb(c1, box1)
# Segment vs Circle
elsif (self.is_a?(Segment) and other.is_a?(Circle)) or (self.is_a?(Circle) and other.is_a?(Segment))
a = other.is_a?(Segment) ? other.point_a : self.point_a
b = other.is_a?(Segment) ? other.point_b : self.point_b
c = other.is_a?(Circle) ? other : self
collision_segment_cercle(a, b, c)
# Segment vs Segment
elsif self.is_a?(Segment) and other.is_a?(Segment)
a = self.point_a; b = self.point_b; o = other.point_a; p = other.point_b
collision_segment_segment(a, b, o, p)
# Segment vs AABB
elsif (self.is_a?(Segment) and other.is_a?(AABB)) or (self.is_a?(AABB) and other.is_a?(Segment))
a = other.is_a?(Segment) ? other.point_a : self.point_a
b = other.is_a?(Segment) ? other.point_b : self.point_b
aabb = other.is_a?(AABB) ? other : self
collision_segment_aabb(a, b, aabb)
else
raise("no collision handled between #{self.class} and #{other.class}")
end
end
end

class Point < Shape
attr_accessor :x, :y, :z
def initialize(x, y)
@x, @y, @z = x, y, 0
end
end

class Circle < Shape
attr_accessor :x, :y, :radius
def initialize(x, y, radius)
@x, @y, @z = x, y, 0
end

def draw(color = 0xff_ffffff)
@img.draw_rot(@x, @y, @z, 0, 0.5, 0.5, 1, 1, color)
end
end

class AABB < Shape
attr_accessor :x, :y, :w, :h
def initialize(x, y, w, h)
@x, @y, @w, @h = x, y, w, h
end

def draw(color = 0xff_ffffff)
Gosu::draw_rect(@x, @y, @w, @h, color)
end
end

class Segment < Shape
attr_accessor :point_a, :point_b
def initialize(point_a, point_b)
@point_a, @point_b = point_a, point_b
@z = 0
end

def draw(color = 0xff_ffffff)
Gosu::draw_line(@point_a.x, @point_a.y, color, @point_b.x, @point_b.y, color, @z)
end
end
By lol_o2 Date 2015-11-21 16:24
I did something similar: https://www.libgosu.org/cgi-bin/mwf/topic_show.pl?tid=989, but without line and point.
Ruby code for collisions is extremely slow (unless you did some optimizations), so I then ported it to Chimpmunk, which required adding only a collisions manager (tried to keep it as simple as possible). Chipmunk approach is 50 times faster.
By bestguigui Date 2015-11-21 18:01
Yes, I saw your code, but the Segment collision was the one I needed most !

I tried Chipmunk, but I found it a little obscure. I would love to understand it better.
Topic Gosu / Gosu Showcase / A little code about collisions