Add point location and broad phase

This commit is contained in:
Matthias Richter 2011-02-06 17:40:06 +01:00
parent 4554742579
commit 56737e7aa0
2 changed files with 185 additions and 104 deletions

View File

@ -144,6 +144,12 @@ Polygon = Class{name = "Polygon", function(self, ...)
self.centroid.y = self.centroid.y + (p.y+q.y) * det
end
self.centroid = self.centroid / (6 * self.area)
-- get outcircle
self._radius = 0
for i = 1,#vertices do
self._radius = math.max(vertices[i]:dist(self.centroid), self._radius)
end
end}
-- return vertices as x1,y1,x2,y2, ..., xn,yn
@ -321,6 +327,32 @@ function Polygon:splitConvex()
return convex
end
function Polygon:contains(x,y)
-- test if an edge cuts the ray
local function cut_ray(p,q)
return ((p.y > y and q.y < y) or (p.y < y and q.y > y)) -- possible cut
and (x - p.x < (y - p.y) * (q.x - p.x) / (q.y - p.y)) -- x < cut.x
end
-- test if the ray crosses boundary from interior to exterior.
-- this is needed due to edge cases, when the ray passes through
-- polygon corners
local function cross_boundary(p,q)
return (p.y == y and p.x > x and q.y < y)
or (q.y == y and q.x > x and p.y < y)
end
local v = self.vertices
local in_polygon = false
for i = 1, #v - 1 do
if cut_ray(v[i], v[i+1]) or cross_boundary(v[i], v[i+1]) then
in_polygon = not in_polygon
end
end
return in_polygon
end
-- module() as shortcut to module.Polygon()
do
local m = getmetatable(_M)

View File

@ -54,8 +54,14 @@ local function SAT(shape_one, axes_one, shape_two, axes_two)
return collide, sep
end
---------------
-- Base class
local function outcircles_intersect(shape_one, shape_two)
local x1,y1,r1 = shape_one:outcircle()
local x2,y2,r2 = shape_two:outcircle()
return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) <= (r1+r2)*(r1+r2)
end
--
-- base class
--
local Shape = Class{name = 'Shape', function(self, t)
self._type = t
@ -72,8 +78,8 @@ Shape.POLYGON = setmetatable({}, {__tostring = function() return 'POLYGON' end
Shape.COMPOUND = setmetatable({}, {__tostring = function() return 'COMPOUND' end})
Shape.CIRCLE = setmetatable({}, {__tostring = function() return 'CIRCLE' end})
-------------------
-- Polygon Shapes
--
-- class definitions
--
local ConvexPolygonShape = Class{name = 'ConvexPolygonShape', function(self, polygon)
Shape.construct(self, Shape.POLYGON)
@ -92,7 +98,6 @@ local ConcavePolygonShape = Class{name = 'ConcavePolygonShape', function(self, p
end}
ConcavePolygonShape:inherit(Shape)
-- decides wether to use a convex or concave polygon
function PolygonShape(polygon, ...)
-- create from coordinates if needed
if type(polygon) == "number" then
@ -107,6 +112,16 @@ function PolygonShape(polygon, ...)
return ConcavePolygonShape(polygon)
end
CircleShape = Class{name = 'CircleShape', function(self, cx,cy, radius)
Shape.construct(self, Shape.CIRCLE)
self._center = vector(cx,cy)
self._radius = radius
end}
CircleShape:inherit(Shape)
--
-- collision functions
--
function ConvexPolygonShape:getAxes()
local axes = {}
local vert = self._polygon.vertices
@ -126,36 +141,29 @@ function ConvexPolygonShape:projectOn(axis)
return math.min(unpack(projection)), math.max(unpack(projection))
end
function CircleShape:projectOn(axis)
-- v:projectOn(a) * a = v * a (see ConvexPolygonShape)
-- therefore: (c +- a*r) * a = c*a +- |a|^2 * r
local center = self._center * axis
local shift = self._radius * axis:len2()
return center - shift, center + shift
end
-- collision dispatching:
-- let circle shape or compund shape handle the collision
function ConvexPolygonShape:collidesWith(other)
if other._type ~= Shape.POLYGON then
return other:collidesWith(self)
end
-- else: type is POLYGON, use the SAT
if not outcircles_intersect(self, other) then return false end
return SAT(self, self:getAxes(), other, other:getAxes())
end
function ConvexPolygonShape:draw(mode)
local mode = mode or 'line'
love.graphics.polygon(mode, self._polygon:unpack())
end
function ConvexPolygonShape:center()
return self._polygon.centroid:unpack()
end
function ConvexPolygonShape:move(x,y)
-- y not given => x is a vector
if y then x = vector(x,y) end
self._polygon:move(x)
end
function ConvexPolygonShape:rotate(angle, cx,cy)
if cx and cy then cx = vector(cx,cy) end
self._polygon:rotate(angle, cx)
end
function ConcavePolygonShape:collidesWith(other)
if not outcircles_intersect(self, other) then return false end
local sep, collide, collisions = vector(0,0), false, 0
for _,s in ipairs(self._shapes) do
local status, separating_vector = s:collidesWith(other)
@ -167,6 +175,126 @@ function ConcavePolygonShape:collidesWith(other)
return collide, sep / collisions
end
function CircleShape:collidesWith(other)
if other._type == Shape.CIRCLE then
local d = self._center:dist(other._center)
if d < self._radius + other._radius then
return true, d * (self._center - other.center)
end
return false
elseif other._type == Shape.COMPOUND then
return other:collidesWith(self)
end
-- else: other._type == POLYGON
if not outcircles_intersect(self, other) then return false end
-- retrieve closest edge to center
local points = other._polygon.vertices
local closest, dist = points[1], (self._center - points[1]):len2()
for i = 2,#points do
local d = (self._center - points[i]):len2()
if d < dist then
closest, dist = points[i], d
end
end
return SAT(self, {(closest-self._center):normalize_inplace()}, other, other:getAxes())
end
--
-- point location/ray intersection
--
function ConvexPolygonShape:contains(x,y)
return self._polygon:contains(x,y)
end
function ConcavePolygonShape:contains(x,y)
return self._polygon:contains(x,y)
end
function CircleShape:contains(x,y)
if y then x = vector(x,y) end
return (x - self._center):len2() < self._radius * self._radius
end
--
-- auxiliary
--
function ConvexPolygonShape:center()
return self._polygon.centroid:unpack()
end
function ConcavePolygonShape:center()
return self._polygon.centroid:unpack()
end
function CircleShape:center()
return self._center:unpack()
end
function ConvexPolygonShape:outcircle()
local cx,cy = self:center()
return cx,cy, self._polygon._radius
end
function ConcavePolygonShape:outcircle()
local cx,cy = self:center()
return cx,cy, self._polygon._radius
end
function CircleShape:outcircle()
local cx,cy = self:center()
return cx,cy, self._radius
end
function ConvexPolygonShape:move(x,y)
-- y not given => x is a vector
if y then x = vector(x,y) end
self._polygon:move(x)
end
function ConcavePolygonShape:move(x,y)
-- y not give => x is a vector
if y then x = vector(x,y) end
self._polygon:move(x)
for _,p in ipairs(self._shapes) do
p:move(x)
end
end
function CircleShape:move(x,y)
-- y not given => x is a vector
if y then x = vector(x,y) end
self._center = self._center + x
end
function ConcavePolygonShape:rotate(angle,cx,cy)
if cx and cy then cx = vector(cx,cy) end
self._polygon:rotate(angle,cx)
for _,p in ipairs(self._shapes) do
p:rotate(angle, cx or self._polygon.centroid)
end
end
function ConvexPolygonShape:rotate(angle, cx,cy)
if cx and cy then cx = vector(cx,cy) end
self._polygon:rotate(angle, cx)
end
function CircleShape:rotate(angle, cx,cy)
if not cx then return end
if cx and cy then cx = vector(cx,cy) end
self._center = (self._center - cx):rotate_inplace(angle) + cx
end
function ConvexPolygonShape:draw(mode)
local mode = mode or 'line'
love.graphics.polygon(mode, self._polygon:unpack())
end
function ConcavePolygonShape:draw(mode)
local mode = mode or 'line'
if mode == 'line' then
@ -178,89 +306,10 @@ function ConcavePolygonShape:draw(mode)
end
end
function ConcavePolygonShape:center()
return self._polygon.centroid:unpack()
end
function ConcavePolygonShape:move(x,y)
-- y not give => x is a vector
if y then x = vector(x,y) end
self._polygon:move(x)
for _,p in ipairs(self._shapes) do
p:move(x)
end
end
function ConcavePolygonShape:rotate(angle,cx,cy)
if cx and cy then cx = vector(cx,cy) end
self._polygon:rotate(angle,cx)
for _,p in ipairs(self._shapes) do
p:rotate(angle, cx or self._polygon.centroid)
end
end
-------------------
-- Perfect circle
--
CircleShape = Class{name = 'CircleShape', function(self, cx,cy, radius)
Shape.construct(self, Shape.CIRCLE)
self._center = vector(cx,cy)
self._radius = radius
end}
CircleShape:inherit(Shape)
function CircleShape:collidesWith(other)
if other._type == Shape.CIRCLE then
local d = self._center:dist(other._center)
if d < self._radius + other._radius then
return true, d * (self._center - other.center)
end
return false
elseif other._type == Shape.COMPOUND then
return other:collidesWith(self)
end
-- else: other._type == POLYGON
-- retrieve closest edge to center
local points = other._polygon.vertices
local closest, dist = points[1], (self._center - points[1]):len2()
for i = 2,#points do
local d = (self._center - points[i]):len2()
if d < dist then
closest, dist = points[i], d
end
end
return SAT(self, {(closest-self._center):normalize_inplace()}, other, other:getAxes())
end
function CircleShape:draw(mode, segments)
local segments = segments or math.max(3, math.floor(math.pi * math.log(self._radius)))
love.graphics.circle(mode, self._center.x, self._center.y, self._radius, segments)
end
function CircleShape:center()
return self._center:unpack()
end
function CircleShape:move(x,y)
-- y not given => x is a vector
if y then x = vector(x,y) end
self._center = self._center + x
end
function CircleShape:rotate(angle, cx,cy)
if not cx then return end
if cx and cy then cx = vector(cx,cy) end
self._center = (self._center - cx):rotate_inplace(angle) + cx
end
function CircleShape:projectOn(axis)
-- v:projectOn(a) * a = v * a (see ConvexPolygonShape)
-- therefore: (c +- a*r) * a = c*a +- |a|^2 * r
local center = self._center * axis
local shift = self._radius * axis:len2()
return center - shift, center + shift
end
function CircleShape:center()
return self._center:unpack()
end