Not logged inGosu Forums
Forum www.libgosu.org Help Search Register Login
Up 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
  attr_reader :columns, :rows 
  def initialize radius
    @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)
    return !(d2 > (c1.radius + c2.radius) * (c1.radius + c2.radius))
  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)
    @radius = radius
    @x, @y, @z = x, y, 0
    @img = Gosu::Image.new(CircleGenerator.new(@radius))
  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
Parent - - 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.
Parent - 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.
Up Topic Gosu / Gosu Showcase / A little code about collisions

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill