lib/gamebox/arbiter.rb in gamebox-0.1.1 vs lib/gamebox/arbiter.rb in gamebox-0.2.1
- old
+ new
@@ -1,7 +1,8 @@
# this module gets mixed into a stage to allow it to handle collision detection
module Arbiter
+ attr_reader :checks, :collisions
def register_collidable(actor)
@spatial_hash = stagehand(:spatial)
@collidable_actors ||= []
unless @collidable_actors.include? actor
@@ -25,46 +26,66 @@
@collision_handlers ||= {}
first_objs.each do |fobj|
second_objs.each do |sobj|
-# puts "registering #{fobj} and #{sobj}"
- @collision_handlers[fobj] ||= {}
- @collision_handlers[sobj] ||= {}
- @collision_handlers[fobj][sobj] = block
- @collision_handlers[sobj][fobj] = block
+ if fobj.to_i < sobj.to_i
+ @collision_handlers[fobj] ||= {}
+ @collision_handlers[fobj][sobj] = [false,block]
+ else
+ @collision_handlers[sobj] ||= {}
+ @collision_handlers[sobj][fobj] = [true,block]
+ end
end
end
end
def run_callbacks(collisions)
+ @collision_handlers ||= {}
collisions.each do |collision|
first = collision.first
second = collision.last
+ unless first.actor_type.to_i < second.actor_type.to_i
+ tmp = first
+ first = second
+ second = tmp
+ end
colliders = @collision_handlers[first.actor_type]
- callback = colliders[second.actor_type] unless colliders.nil?
- callback.call first, second unless callback.nil?
+ swapped, callback = colliders[second.actor_type] unless colliders.nil?
+ unless callback.nil?
+ if swapped
+ callback.call second, first
+ else
+ callback.call first, second
+ end
+ end
end
end
def find_collisions
@collidable_actors ||= []
+ @checks = 0
+ @collisions = 0
tmp_collidable_actors = @collidable_actors.dup
collisions = {}
@collidable_actors.each do |first|
x = first.x - @spatial_hash.cell_size
y = first.y - @spatial_hash.cell_size
+ # TODO base this on size of object
w = @spatial_hash.cell_size * 3
h = w
- tmp_collidable_actors = @spatial_hash.items_in(x,y,w,h)
+ tmp_collidable_actors = @spatial_hash.neighbors_of(first)
+
tmp_collidable_actors.each do |second|
+ @checks += 1
if first != second && collide?(first, second)
collisions[second] ||= []
if !collisions[second].include?(first)
+ @collisions += 1
collisions[first] ||= []
collisions[first] << second
end
end
end
@@ -77,23 +98,95 @@
end
run_callbacks unique_collisions
end
def collide?(object, other)
- self.send "collide_#{object.shape}_#{object.shape}?", object, other
+ # TODO perf analysis of this
+ self.send "collide_#{object.collidable_shape}_#{other.collidable_shape}?", object, other
end
def collide_circle_circle?(object, other)
-# puts "comparing #{object.actor_type}[#{object.object_id}] to #{other.actor_type}[#{other.object_id}]"
- x = object.x + object.radius
- y = object.y + object.radius
- x_prime = other.x + other.radius
- y_prime = other.y + other.radius
+ x = object.center_x
+ y = object.center_y
+ x_prime = other.center_x
+ y_prime = other.center_y
x_dist = (x_prime - x) * (x_prime - x)
y_dist = (y_prime - y) * (y_prime - y)
total_radius = object.radius + other.radius
- x_dist + y_dist < (total_radius) * (total_radius)
+ x_dist + y_dist <= (total_radius * total_radius)
end
+ # Idea from:
+ # http://gpwiki.org/index.php/Polygon_Collision
+ # and http://www.gamedev.net/community/forums/topic.asp?topic_id=540755&whichpage=1�
+ def collide_polygon_polygon?(object, other)
+ if collide_circle_circle? object, other
+ # collect vector's perp
+ potential_sep_axis =
+ (object.cw_world_edge_normals | other.cw_world_edge_normals).uniq
+ potential_sep_axis.each do |axis|
+ return false unless project_and_detect(axis, object, other)
+ end
+ else
+ return false
+ end
+ true
+ end
+ alias collide_aabb_aabb? collide_polygon_polygon?
+
+ # returns true if the projections overlap
+ def project_and_detect(axis, a, b)
+ a_min, a_max = send("#{a.collidable_shape}_interval", axis, a)
+ b_min, b_max = send("#{b.collidable_shape}_interval", axis, b)
+
+ a_min <= b_max && b_min <= a_max
+ end
+
+ def polygon_interval(axis, object)
+ min = max = nil
+ object.cw_world_points.each do |edge|
+ # vector dot product
+ d = edge[0] * axis[0] + edge[1] * axis[1]
+ min ||= d
+ max ||= d
+ min = d if d < min
+ max = d if d > max
+ end
+ [min,max]
+ end
+ alias aabb_interval polygon_interval
+
+ def circle_interval(axis, object)
+ axis_x = axis[0]
+ axis_y = axis[1]
+
+ obj_x = object.center_x
+ obj_y = object.center_y
+
+ length = Math.sqrt(axis_x * axis_x + axis_y * axis_y)
+ cn = axis_x*obj_x + axis_y*obj_y
+ rlength = object.radius*length
+ min = cn - rlength
+ max = cn + rlength
+ [min,max]
+ end
+
+ def collide_polygon_circle?(object, other)
+ collide_circle_polygon?(other, object)
+ end
+ alias collide_aabb_circle? collide_polygon_circle?
+
+ def collide_circle_polygon?(object, other)
+ if collide_circle_circle? object, other
+ potential_sep_axis = other.cw_world_edge_normals
+ potential_sep_axis.each do |axis|
+ return false unless project_and_detect(axis, object, other)
+ end
+ true
+ else
+ false
+ end
+ end
+ alias collide_circle_aabb? collide_circle_polygon?
end