HC/shapes.lua

444 lines
11 KiB
Lua
Raw Normal View History

2011-01-13 13:38:36 +00:00
--[[
Copyright (c) 2011 Matthias Richter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
Except as contained in this notice, the name(s) of the above copyright holders
shall not be used in advertising or otherwise to promote the sale, use or
other dealings in this Software without prior written authorization.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]--
local math_min, math_sqrt, math_huge = math.min, math.sqrt, math.huge
2011-01-13 13:38:36 +00:00
local _PACKAGE = (...):match("^(.+)%.[^%.]+")
if not (common and common.class and common.instance) then
class_commons = true
require(_PACKAGE .. '.class')
end
2012-04-12 14:07:50 +00:00
local vector = require(_PACKAGE .. '.vector-light')
local Polygon = require(_PACKAGE .. '.polygon')
local GJK = require(_PACKAGE .. '.gjk') -- actual collision detection
2011-02-06 16:40:06 +00:00
--
-- base class
2011-01-13 13:38:36 +00:00
--
local Shape = {}
function Shape:init(t)
2011-01-13 13:38:36 +00:00
self._type = t
self._rotation = 0
end
2011-01-13 13:38:36 +00:00
2011-02-01 22:41:28 +00:00
function Shape:moveTo(x,y)
2011-02-07 16:51:51 +00:00
local cx,cy = self:center()
self:move(x - cx, y - cy)
2011-02-01 22:41:28 +00:00
end
function Shape:rotation()
return self._rotation
end
function Shape:rotate(angle)
self._rotation = self._rotation + angle
end
function Shape:setRotation(angle, x,y)
return self:rotate(angle - self._rotation, x,y)
end
2011-02-06 16:40:06 +00:00
--
-- class definitions
2011-01-13 13:38:36 +00:00
--
local ConvexPolygonShape = {}
function ConvexPolygonShape:init(polygon)
2012-07-10 14:29:34 +00:00
Shape.init(self, 'polygon')
2011-01-13 13:38:36 +00:00
assert(polygon:isConvex(), "Polygon is not convex.")
self._polygon = polygon
end
2011-01-13 13:38:36 +00:00
local ConcavePolygonShape = {}
function ConcavePolygonShape:init(poly)
2012-07-10 14:29:34 +00:00
Shape.init(self, 'compound')
self._polygon = poly
self._shapes = poly:splitConvex()
for i,s in ipairs(self._shapes) do
self._shapes[i] = common.instance(ConvexPolygonShape, s)
end
end
local CircleShape = {}
function CircleShape:init(cx,cy, radius)
2012-07-10 14:29:34 +00:00
Shape.init(self, 'circle')
2012-04-12 14:07:50 +00:00
self._center = {x = cx, y = cy}
2011-02-06 16:40:06 +00:00
self._radius = radius
end
2011-02-06 16:40:06 +00:00
local PointShape = {}
function PointShape:init(x,y)
2012-07-10 14:29:34 +00:00
Shape.init(self, 'point')
2012-04-12 14:07:50 +00:00
self._pos = {x = x, y = y}
end
2011-02-26 16:27:04 +00:00
2011-02-06 16:40:06 +00:00
--
-- collision functions
--
function ConvexPolygonShape:support(dx,dy)
2012-04-12 14:07:50 +00:00
local v = self._polygon.vertices
local max, vmax = -math_huge
2012-04-12 14:07:50 +00:00
for i = 1,#v do
local d = vector.dot(v[i].x,v[i].y, dx,dy)
if d > max then
max, vmax = d, v[i]
end
2011-01-13 13:38:36 +00:00
end
return vmax.x, vmax.y
2011-01-13 13:38:36 +00:00
end
function CircleShape:support(dx,dy)
return vector.add(self._center.x, self._center.y,
vector.mul(self._radius, vector.normalize(dx,dy)))
2011-02-06 16:40:06 +00:00
end
-- collision dispatching:
-- let circle shape or compund shape handle the collision
function ConvexPolygonShape:collidesWith(other)
if self == other then return false end
2012-07-10 14:29:34 +00:00
if other._type ~= 'polygon' then
2012-04-12 14:07:50 +00:00
local collide, sx,sy = other:collidesWith(self)
return collide, sx and -sx, sy and -sy
2011-01-13 13:38:36 +00:00
end
2012-07-10 14:29:34 +00:00
-- else: type is POLYGON
return GJK(self, other)
2011-01-13 13:38:36 +00:00
end
function ConcavePolygonShape:collidesWith(other)
if self == other then return false end
2012-07-10 14:29:34 +00:00
if other._type == 'point' then
2011-02-26 16:27:04 +00:00
return other:collidesWith(self)
end
2012-04-12 14:07:50 +00:00
-- TODO: better way of doing this. report all the separations?
local collide,dx,dy = false,0,0
2011-01-13 13:38:36 +00:00
for _,s in ipairs(self._shapes) do
2012-04-12 14:07:50 +00:00
local status, sx,sy = s:collidesWith(other)
2011-01-13 13:38:36 +00:00
collide = collide or status
if status then
if math.abs(dx) < math.abs(sx) then
dx = sx
end
if math.abs(dy) < math.abs(sy) then
dy = sy
end
2011-01-13 13:38:36 +00:00
end
end
return collide, dx, dy
2011-01-13 13:38:36 +00:00
end
function CircleShape:collidesWith(other)
if self == other then return false end
2012-07-10 14:29:34 +00:00
if other._type == 'circle' then
2012-04-12 14:07:50 +00:00
local px,py = self._center.x-other._center.x, self._center.y-other._center.y
local d = vector.len2(px,py)
local radii = self._radius + other._radius
2012-04-12 14:07:50 +00:00
if d < radii*radii then
-- if circles overlap, push it out upwards
2012-04-12 14:07:50 +00:00
if d == 0 then return true, 0,radii end
-- otherwise push out in best direction
2012-04-12 14:07:50 +00:00
return true, vector.mul(radii - math_sqrt(d), vector.normalize(px,py))
2011-01-14 22:25:43 +00:00
end
return false
2012-07-10 14:29:34 +00:00
elseif other._type == 'polygon' then
return GJK(self, other)
2011-01-13 13:38:36 +00:00
end
2011-02-06 16:40:06 +00:00
2012-07-10 14:29:34 +00:00
-- else: let the other shape decide
local collide, sx,sy = other:collidesWith(self)
return collide, sx and -sx, sy and -sy
2011-01-13 13:38:36 +00:00
end
2011-02-26 16:27:04 +00:00
function PointShape:collidesWith(other)
if self == other then return false end
2012-07-10 14:29:34 +00:00
if other._type == 'point' then
2012-04-12 14:07:50 +00:00
return (self._pos == other._pos), 0,0
2011-02-26 16:27:04 +00:00
end
2012-04-12 14:07:50 +00:00
return other:contains(self._pos.x, self._pos.y), 0,0
2011-02-26 16:27:04 +00:00
end
2011-02-06 16:40:06 +00:00
--
-- 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)
2012-04-12 14:07:50 +00:00
return vector.len2(x-self._center.x, y-self._center.y) < self._radius * self._radius
2011-02-06 16:40:06 +00:00
end
2011-02-26 16:27:04 +00:00
function PointShape:contains(x,y)
return x == self._pos.x and y == self._pos.y
end
2011-02-06 23:08:02 +00:00
function ConcavePolygonShape:intersectsRay(x,y, dx,dy)
return self._polygon:intersectsRay(x,y, dx,dy)
end
function ConvexPolygonShape:intersectsRay(x,y, dx,dy)
return self._polygon:intersectsRay(x,y, dx,dy)
end
-- circle intersection if distance of ray/center is smaller
2012-02-20 12:13:49 +00:00
-- than radius.
-- with r(s) = p + d*s = (x,y) + (dx,dy) * s defining the ray and
-- (x - cx)^2 + (y - cy)^2 = r^2, this problem is eqivalent to
-- solving [with c = (cx,cy)]:
--
-- d*d s^2 + 2 d*(p-c) s + (p-c)*(p-c)-r^2 = 0
2011-02-06 23:08:02 +00:00
function CircleShape:intersectsRay(x,y, dx,dy)
2012-04-12 14:07:50 +00:00
local pcx,pcy = x-self._center.x, y-self._center.y
2012-04-12 14:07:50 +00:00
local a = vector.len2(dx,dy)
local b = 2 * vector.dot(dx,dy, pcx,pcy)
local c = vector.len2(pcx,pcy) - self._radius * self._radius
2012-02-20 12:13:49 +00:00
local discr = b*b - 4*a*c
if discr < 0 then return false end
discr = math_sqrt(discr)
local s1,s2 = discr-b, -discr-b
if s1 < 0 then -- first solution is off the ray
return s2 >= 0, s2/(2*a)
elseif s2 < 0 then -- second solution is off the ray
return s1 >= 0, s1/(2*a)
end
-- both solutions on the ray
return true, math_min(s1,s2)/(2*a)
2011-02-06 23:08:02 +00:00
end
2011-02-26 16:27:04 +00:00
-- point shape intersects ray if it lies on the ray
function PointShape:intersectsRay(x,y,dx,dy)
2012-04-12 14:07:50 +00:00
local px,py = self._pos.x-x, self._pos.y-y
local t = vector.dot(px,py, dx,dy) / vector.len2(dx,dy)
return t >= 0, t
2011-02-26 16:27:04 +00:00
end
2011-02-06 16:40:06 +00:00
--
-- auxiliary
--
function ConvexPolygonShape:center()
2012-04-12 14:07:50 +00:00
return self._polygon.centroid.x, self._polygon.centroid.y
2011-02-06 16:40:06 +00:00
end
function ConcavePolygonShape:center()
2012-04-12 14:07:50 +00:00
return self._polygon.centroid.x, self._polygon.centroid.y
2011-01-13 13:38:36 +00:00
end
function CircleShape:center()
2012-04-12 14:07:50 +00:00
return self._center.x, self._center.y
2011-01-13 13:38:36 +00:00
end
2011-02-26 16:27:04 +00:00
function PointShape:center()
2012-04-12 14:07:50 +00:00
return self._pos.x, self._pos.y
2011-02-26 16:27:04 +00:00
end
2011-02-06 16:40:06 +00:00
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
2011-02-26 16:27:04 +00:00
function PointShape:outcircle()
return self._pos.x, self._pos.y, 0
end
function ConvexPolygonShape:bbox()
return self._polygon:getBBox()
end
function ConcavePolygonShape:bbox()
return self._polygon:getBBox()
end
function CircleShape:bbox()
2012-04-12 14:07:50 +00:00
local cx,cy = self:center()
local r = self._radius
return cx-r,cy-r, cx+r,cy+r
end
function PointShape:bbox()
2012-04-12 14:07:50 +00:00
local x,y = self:center()
return x,y,x,y
end
2011-02-06 16:40:06 +00:00
function ConvexPolygonShape:move(x,y)
2011-02-07 16:42:48 +00:00
self._polygon:move(x,y)
2011-02-06 16:40:06 +00:00
end
function ConcavePolygonShape:move(x,y)
2011-02-07 16:42:48 +00:00
self._polygon:move(x,y)
2011-02-06 16:40:06 +00:00
for _,p in ipairs(self._shapes) do
2011-02-07 16:42:48 +00:00
p:move(x,y)
2011-02-06 16:40:06 +00:00
end
end
2011-01-13 13:38:36 +00:00
function CircleShape:move(x,y)
2012-04-12 14:07:50 +00:00
self._center.x = self._center.x + x
self._center.y = self._center.y + y
2011-01-13 13:38:36 +00:00
end
2011-02-26 16:27:04 +00:00
function PointShape:move(x,y)
self._pos.x = self._pos.x + x
self._pos.y = self._pos.y + y
end
2011-02-06 16:40:06 +00:00
function ConcavePolygonShape:rotate(angle,cx,cy)
Shape.rotate(self, angle)
2012-04-12 14:07:50 +00:00
if not (cx and cy) then
cx,cy = self:center()
end
self._polygon:rotate(angle,cx,cy)
2011-02-06 16:40:06 +00:00
for _,p in ipairs(self._shapes) do
2012-04-12 14:07:50 +00:00
p:rotate(angle, cx,cy)
2011-02-06 16:40:06 +00:00
end
end
function ConvexPolygonShape:rotate(angle, cx,cy)
Shape.rotate(self, angle)
2011-02-07 16:42:48 +00:00
self._polygon:rotate(angle, cx, cy)
2011-02-06 16:40:06 +00:00
end
2011-01-14 22:25:43 +00:00
function CircleShape:rotate(angle, cx,cy)
Shape.rotate(self, angle)
2012-04-12 14:07:50 +00:00
if not (cx and cy) then return end
self._center.x,self._center.y = vector.add(cx,cy, vector.rotate(angle, self._center.x-cx, self._center.y-cy))
2011-01-13 13:38:36 +00:00
end
2011-02-26 16:27:04 +00:00
function PointShape:rotate(angle, cx,cy)
Shape.rotate(self, angle)
2012-04-12 14:07:50 +00:00
if not (cx and cy) then return end
self._pos.x,self._pos.y = vector.add(cx,cy, vector.rotate(angle, self._pos.x-cx, self._pos.y-cy))
2011-02-26 16:27:04 +00:00
end
2011-02-06 16:40:06 +00:00
2012-07-05 15:30:13 +00:00
function ConcavePolygonShape:scale(s)
assert(type(s) == "number" and s > 0, "Invalid argument. Scale must be greater than 0")
local cx,cy = self:center()
self._polygon:scale(s, cx,cy)
for _, p in ipairs(self._shapes) do
local dx,dy = vector.sub(cx,cy, p:center())
p:scale(s)
p:moveTo(cx-dx*s, cy-dy*s)
end
end
function ConvexPolygonShape:scale(s)
assert(type(s) == "number" and s > 0, "Invalid argument. Scale must be greater than 0")
self._polygon:scale(s, self:center())
end
function CircleShape:scale(s)
assert(type(s) == "number" and s > 0, "Invalid argument. Scale must be greater than 0")
self._radius = self._radius * s
end
function PointShape:scale()
-- nothing
end
2011-02-06 16:40:06 +00:00
function ConvexPolygonShape:draw(mode)
local mode = mode or 'line'
love.graphics.polygon(mode, self._polygon:unpack())
2011-01-13 13:38:36 +00:00
end
2012-07-05 15:30:13 +00:00
function ConcavePolygonShape:draw(mode, wireframe)
2011-02-06 16:40:06 +00:00
local mode = mode or 'line'
if mode == 'line' then
love.graphics.polygon('line', self._polygon:unpack())
2012-07-05 15:30:13 +00:00
if not wireframe then return end
end
for _,p in ipairs(self._shapes) do
love.graphics.polygon(mode, p._polygon:unpack())
2011-02-06 16:40:06 +00:00
end
end
function CircleShape:draw(mode, segments)
2012-04-12 14:07:50 +00:00
love.graphics.circle(mode or 'line', self:outcircle())
2011-01-13 13:38:36 +00:00
end
2011-02-06 16:40:06 +00:00
2011-02-26 16:27:04 +00:00
function PointShape:draw()
2012-04-12 14:07:50 +00:00
love.graphics.point(self:center())
2011-02-26 16:27:04 +00:00
end
Shape = common.class('Shape', Shape)
ConvexPolygonShape = common.class('ConvexPolygonShape', ConvexPolygonShape, Shape)
ConcavePolygonShape = common.class('ConcavePolygonShape', ConcavePolygonShape, Shape)
CircleShape = common.class('CircleShape', CircleShape, Shape)
PointShape = common.class('PointShape', PointShape, Shape)
local function newPolygonShape(polygon, ...)
-- create from coordinates if needed
if type(polygon) == "number" then
polygon = common.instance(Polygon, polygon, ...)
else
polygon = polygon:clone()
end
if polygon:isConvex() then
return common.instance(ConvexPolygonShape, polygon)
end
return common.instance(ConcavePolygonShape, polygon)
end
local function newCircleShape(...)
return common.instance(CircleShape, ...)
end
local function newPointShape(...)
return common.instance(PointShape, ...)
end
return {
ConcavePolygonShape = ConcavePolygonShape,
ConvexPolygonShape = ConvexPolygonShape,
CircleShape = CircleShape,
PointShape = PointShape,
newPolygonShape = newPolygonShape,
newCircleShape = newCircleShape,
newPointShape = newPointShape,
}