#encoding: utf-8
#author: shitake
#data: 16-9-26
class Range
# 判断两个范围是否相交。
def intersects(range)
!(self.last < range.first || self.first > range.last)
end
end
module Math
def self.a2r(angle)
angle * Math::PI / 180 if angle.kind_of? Numeric
end
end
# 表示二维坐标系上点的对象
class Point
attr_accessor :x
attr_accessor :y
def initialize(x = 0, y = x)
@x = x
@y = y
end
# 二维坐标下两点距离
def distance(point)
Math.hypot(@x - point.x, @y - point.y)
end
# 二维坐标下两点解析距离
def distance_axis(point)
[point.x - @x,point.y - @y]
end
def set(x = 0, y = x)
@x = x
@y = y
end
end
class Vector2
attr_accessor :x
attr_accessor :y
def initialize(x, y)
@x = x || 0
@y = y || 0
end
#点乘
def dot_product(vector2)
@x * vector2.x + @y * vector2.y
end
#模
def module
Math.hypot(@x, @y)
end
end
class Axes
attr_accessor :origin
attr_accessor :radian
attr_reader :x
attr_reader :y
#origin:原点,radian:偏转弧度
def initialize(origin, radian)
@origin = origin
@radian = radian
set_axes(@radian)
end
def radian=(value)
return if @radian == value
@radian = value
set_axes(value)
end
def set_axes(radian)
@x = Vector2.new(Math.cos(radian), Math.sin(radian))
@y = Vector2.new(-1 * Math.sin(radian), Math.cos(radian))
end
#返回逆坐标系
def a_axes
Axes.new(Point.new(- @origin.x, - @origin.y), -radian)
end
#返回向量在当前坐标系下的投影
def shadow(vector2)
x = vector2.dot_product(@x)
y = vector2.dot_product(@y)
Vector2.new(x, y)
end
#返回点在当前坐标系下的坐标
def point(point)
vector2 = shadow(Vector2.new(point.x - @origin.x, point.y - @origin.y))
Point.new(vector2.x, vector2.y)
end
end
class AABB
attr_reader :x
attr_reader :y
attr_reader :width
attr_reader :height
attr_reader :center
attr_reader :width_range
attr_reader :height_range
def initialize(x = 0, y = 0, width = 0, height = 0)
@x = x
@y = y
@width = width
@height = height
create
end
def create
@width_range = Range.new(@x, @x + @width)
@height_range = Range.new(@y, @y + @height)
@center = Point.new(@width_range / 2, @height_range / 2)
end
def points
[
Point.new(@x, @y), #左上
Point.new(@x + @width, @y), #右上
Point.new(@x, @y + @height), #左下
Point.new(@x + @width, @y + @height) #右下
]
end
def x=(value)
return if @x == value
@x = value
create
end
def y=(value)
return if @y == value
@y = value
create
end
def width=(value)
return if @width == value
@width = value
create
end
def height=(value)
return if @height == value
@height = value
create
end
def collision(obj)
case obj.class
when Point
point_collision(obj)
when AABB
aabb_collision(obj)
when OBB
obb_collision(obj)
when Sphere
sphere_collision(obj)
else
raise "TypeError:不正确类型#{obj.class}!"
end
end
# point 相交测试
def point_collision(point)
@width_range.eql?(point.x) && @height_range.eql?(point.y)
end
# aabb 相交测试
def aabb_collision(aabb)
@width_range.intersects(aabb.width_range) && @height_range.intersects(aabb.height_range)
end
# obb 相交测试
def obb_collision(obb)
return aabb_collision(obb.obb2aabb) if obb.angle % 90 == 0
obb.obb_collision(aabb2obb)
end
def sphere_collision(sphere)
sphere.aabb_collision(self)
end
def aabb2obb
OBB.new(@center, @width, @height, 0)
end
def aabb2sphere
radius = Math.sqrt((@width / 2) ** 2 + (@height / 2) ** 2).ceil
Sphere.new(@x, @y, radius)
end
end
class OBB
attr_reader :center
attr_reader :width
attr_reader :height
attr_reader :angle
attr_reader :width_range
attr_reader :height_range
attr_reader :center
attr_reader :axes
def initialize(center, width = 0, height = 0, angle)
@center = center
@width = width
@height = height
value = angle % 360
@angle = value > 180 ? value - 180 : value
create
end
def create
@axes = Axes.new(@center, Math.a2r(@angle))
@width_range = Range(-@width / 2, @width / 2)
@height_range = Range(-@height / 2, @height / 2)
end
def center=(value)
return if @center == value
@center = value
create
end
def width=(value)
return if @width == value
@width = value
create
end
def height=(value)
return if @height == value
@height = value
create
end
def radian=(value)
value = value % 360
return if @angle == (value > 180 ? value - 180 : value)
@angle = value
create
end
def points
[
Point.new(@center.x - @width / 2, @center.y - @height / 2),
Point.new(@center.x - @width / 2, @center.y + @height / 2),
Point.new(@center.x + @width / 2, @center.y - @height / 2),
Point.new(@center.x + @width / 2, @center.y + @height / 2),
]
end
def collision(obj)
case obj.class
when Point
point_collision(obj)
when AABB
aabb_collision(obj)
when OBB
obb_collision(obj)
when Sphere
sphere_collision(obj)
else
raise "TypeError:不正确类型#{obj.class}!"
end
end
# point 相交测试
def point_collision(point)
point = @axes.point(point)
width_range.eql?(point.x) && height_range.eql?(point.y)
end
# aabb 相交测试
def aabb_collision(aabb)
aabb.obb_collision(self)
end
# obb 相交测试,分离轴定理
def obb_collision(obb)
this_in_obb_center = obb.axes.point(@center)
obb_in_this_center = axes.point(obb.center)
ow_range = Range.new(obb_in_this_center.x - obb.width / 2, obb_in_this_center.x + obb.width / 2)
oh_range = Range.new(obb_in_this_center.y - obb.height / 2, obb_in_this_center.y + obb.height / 2)
w_o_range = Range.new(this_in_obb_center.x - @width / 2, this_in_obb_center.x + @width / 2)
h_o_range = Range.new(obb_in_this_center.y - @height / 2, obb_in_this_center.y + @height / 2)
width_range.intersects(ow_range) && height_range.intersects(oh_range) && obb.width_range.intersects(w_o_range) && obb.height_range.intersects(h_o_range)
end
def sphere_collision(sphere)
sphere.obb_collision(self)
end
def obb2aabb
return AABB.new(@center.x - @width / 2, @center.y, @width, @height) if @radian == 0
return AABB.new(@center.x - @height / 2, @center.y, @height, @width) if @angle == 90 && @angle == 180
AABB.new(points.min_by(&:x), points.min_by(&:y), points.max_by(&:x), points.max_by(&:y))
end
def obb2sphere
radius = Math.sqrt((@width / 2) ** 2 + (@height / 2) ** 2).ceil
Sphere.new(@center.x, @center.y, radius)
end
end
class Sphere
attr_reader :x
attr_reader :y
attr_reader :radius
attr_reader :center
attr_reader :width_range
attr_reader :height_range
def initialize(x, y, radius)
@x = x
@y = y
@radius = radius
create
end
def x=(value)
return if @x == value
@x = value
create
end
def y=(value)
return if @y == value
@y = value
create
end
def radius=(value)
return if @radius == value
@radius = value
create
end
def create
@center = Point.new(@x, @y)
@width_range = Range.new(@x - @radius, @x + @radius)
@height_range = Range.new(@y - @radius, @y + @radius)
end
def collision(obj)
case obj.class
when Point
point_collision(obj)
when AABB
aabb_collision(obj)
when OBB
obb_collision(obj)
when Sphere
sphere_collision(obj)
else
raise "TypeError:不正确类型#{obj.class}!"
end
end
def point_collision(point)
(point.x - @x) ** 2 + (point.y - @y) ** 2 <= @radius ** 2
end
def sphere_collision(sphere)
point_collision(sphere.point)
end
def aabb_collision(aabb)
# [url]http://www.zhihu.com/question/24251545/answer/27184960[/url]
vx, vy = (@x - aabb.center.x).abs,(@y - aabb.center.y).abs
hx, hy = (aabb.points[1].x - aabb.center.x).abs, (aabb.points[1].y - aabb.center.y).abs
ux = vx - hx > 0 ? vx - hx : 0
uy = vy - hy > 0 ? vy - hy : 0
ux ** 2 + uy ** 2 <= @radius ** 2
end
def obb_collision(obb)
point = obb.axes.point(@center)
vx, vy = point.x.abs, point.y.abs
hx, hy = obb.width / 2, obb.height / 2
ux = vx - hx > 0 ? vx - hx : 0
uy = vy - hy > 0 ? vy - hy : 0
ux ** 2 + uy ** 2 <= @radius ** 2
end
def sphere2aabb
AABB.new(@x - @radius, @y - @radius, @radius * 2, @radius * 2)
end
def sphere2obb
sphere2aabb.aabb2obb
end
end