Switch to light vector module

This commit is contained in:
Matthias Richter 2012-04-12 16:07:50 +02:00
parent 8a182a902f
commit 5b12e72aae
7 changed files with 311 additions and 303 deletions

View File

@ -66,22 +66,6 @@ local function new(args)
class.__is_a = {[class] = true} class.__is_a = {[class] = true}
class.is_a = function(self, other) return not not self.__is_a[other] end class.is_a = function(self, other) return not not self.__is_a[other] end
-- intercept assignment in global environment to infer the class name
if not (type(args) == "table" and args.name) then
local env, env_meta, interceptor = getfenv(0), getmetatable(getfenv(0)), {}
function interceptor:__newindex(key, value)
if value == class then
local name = tostring(key)
getmetatable(class).__tostring = function() return name end
end
-- restore old metatable and insert value
setmetatable(env, env_meta)
if env.global then env.global(key) end -- handle 'strict' module
env[key] = value
end
setmetatable(env, interceptor)
end
-- inherit superclasses (see above) -- inherit superclasses (see above)
inherit(class, unpack(super)) inherit(class, unpack(super))

View File

@ -31,7 +31,6 @@ if not (common and common.class and common.instance) then
end end
local Shapes = require(_NAME .. '.shapes') local Shapes = require(_NAME .. '.shapes')
local Spatialhash = require(_NAME .. '.spatialhash') local Spatialhash = require(_NAME .. '.spatialhash')
local vector = require(_NAME .. '.vector')
local newPolygonShape = Shapes.newPolygonShape local newPolygonShape = Shapes.newPolygonShape
local newCircleShape = Shapes.newCircleShape local newCircleShape = Shapes.newCircleShape
@ -173,9 +172,9 @@ function HC:update(dt)
local id = collision_id(self, shape,other) local id = collision_id(self, shape,other)
if not tested[id] then if not tested[id] then
if not (self._ghost_shapes[other] or self:share_group(shape, other)) then if not (self._ghost_shapes[other] or self:share_group(shape, other)) then
local collide, sep = shape:collidesWith(other) local collide, sx,sy = shape:collidesWith(other)
if collide then if collide then
colliding[id] = {shape, other, sep.x, sep.y} colliding[id] = {shape, other, sx, sy}
end end
tested[id] = true tested[id] = true
end end

View File

@ -29,22 +29,22 @@ if not (common and common.class and common.instance) then
class_commons = true class_commons = true
require(_PACKAGE .. '.class') require(_PACKAGE .. '.class')
end end
local vector = require(_PACKAGE .. '.vector') local vector = require(_PACKAGE .. '.vector-light')
---------------------------- ----------------------------
-- Private helper functions -- Private helper functions
-- --
-- create vertex list of coordinate pairs -- create vertex list of coordinate pairs
local function toVertexList(vertices, x,y, ...) local function toVertexList(vertices, x,y, ...)
if not x or not y then return vertices end -- no more arguments if not (x and y) then return vertices end -- no more arguments
vertices[#vertices + 1] = vector(x, y) -- set vertex vertices[#vertices + 1] = {x = x, y = y} -- set vertex
return toVertexList(vertices, ...) -- recurse return toVertexList(vertices, ...) -- recurse
end end
-- returns true if three points lie on a line -- returns true if three vertices lie on a line
local function areCollinear(p,q,r) local function areCollinear(p, q, r, eps)
return (q - p):cross(r - p) == 0 return math.abs(vector.det(q.x-p.x, q.y-p.y, r.x-p.x,r.y-p.y)) <= (eps or 1e-20)
end end
-- remove vertices that lie on a line -- remove vertices that lie on a line
local function removeCollinear(vertices) local function removeCollinear(vertices)
@ -72,24 +72,20 @@ end
-- returns true if three points make a counter clockwise turn -- returns true if three points make a counter clockwise turn
local function ccw(p, q, r) local function ccw(p, q, r)
return (q - p):cross(r - p) >= 0 return vector.det(q.x-p.x, q.y-p.y, r.x-p.x, r.y-p.y) >= 0
end
-- unpack vertex coordinates, i.e. {x=p, y=q}, ... -> p,q, ...
local function unpackHelper(v, ...)
if not v then return end
return v.x,v.y,unpackHelper(...)
end end
-- test if a point lies inside of a triangle using cramers rule -- test if a point lies inside of a triangle using cramers rule
local function pointInTriangle(q, p1,p2,p3) local function pointInTriangle(q, p1,p2,p3)
local v1,v2 = p2 - p1, p3 - p1 local v1x,v1y = p2.x-p1.x, p2.y-p1.y
local qp = q - p1 local v2x,v2y = p3.x-p1.x, p3.y-p1.y
local dv = v1:cross(v2) local qpx,qpy = q.x-p1.x, q.y-p1.y
local l = qp:cross(v2) local l = vector.det(qpx,qpy, v2x,v2y)
if l <= 0 then return false end if l <= 0 then return false end
local m = v1:cross(qp)
local m = vector.det(v1x,v1y, qpx,qpy)
if m <= 0 then return false end if m <= 0 then return false end
local dv = vector.det(v1x,v2y, v2x,v2y)
return (l+m)/dv < 1 return (l+m)/dv < 1
end end
@ -130,31 +126,42 @@ function Polygon:init(...)
setmetatable(self.vertices, {__newindex = function() error("Thou shall not change a polygon's vertices!") end}) setmetatable(self.vertices, {__newindex = function() error("Thou shall not change a polygon's vertices!") end})
-- compute polygon area and centroid -- compute polygon area and centroid
self.area = vertices[#vertices]:cross(vertices[1]) local p,q = vertices[#vertices], vertices[1]
for i = 1,#vertices-1 do local det = vector.det(p.x,p.y, q.x,q.y) -- also used below
self.area = self.area + vertices[i]:cross(vertices[i+1]) self.area = det
for i = 2,#vertices do
p,q = q,vertices[i]
self.area = self.area + vector.det(p.x,p.y, q.x,q.y)
end end
self.area = self.area / 2 self.area = self.area / 2
local p,q = vertices[#vertices], vertices[1] p,q = vertices[#vertices], vertices[1]
local det = p:cross(q) self.centroid = {x = (p.x+q.x)*det, y = (p.y+q.y)*det}
self.centroid = vector((p.x+q.x) * det, (p.y+q.y) * det) for i = 2,#vertices do
for i = 1,#vertices-1 do p,q = q,vertices[i]
p,q = vertices[i], vertices[i+1] det = vector.det(p.x,p.y, q.x,q.y)
det = p:cross(q)
self.centroid.x = self.centroid.x + (p.x+q.x) * det self.centroid.x = self.centroid.x + (p.x+q.x) * det
self.centroid.y = self.centroid.y + (p.y+q.y) * det self.centroid.y = self.centroid.y + (p.y+q.y) * det
end end
self.centroid = self.centroid / (6 * self.area) self.centroid.x = self.centroid.x / (6 * self.area)
self.centroid.y = self.centroid.y / (6 * self.area)
-- get outcircle -- get outcircle
self._radius = 0 self._radius = 0
for i = 1,#vertices do for i = 1,#vertices do
self._radius = math.max(vertices[i]:dist(self.centroid), self._radius) self._radius = math.max(self._radius,
vector.dist(vertices[i].x,vertices[i].y, self.centroid.x,self.centroid.y))
end end
end end
local newPolygon local newPolygon
-- unpack vertex coordinates, i.e. {x=p, y=q}, ... -> p,q, ...
local function unpackHelper(v, ...)
if not v then return end
return v.x,v.y,unpackHelper(...)
end
-- return vertices as x1,y1,x2,y2, ..., xn,yn -- return vertices as x1,y1,x2,y2, ..., xn,yn
function Polygon:unpack() function Polygon:unpack()
return unpackHelper( unpack(self.vertices) ) return unpackHelper( unpack(self.vertices) )
@ -167,18 +174,18 @@ end
-- get bounding box -- get bounding box
function Polygon:getBBox() function Polygon:getBBox()
local ul = self.vertices[1]:clone() local ulx,uly = self.vertices[1].x, self.vertices[1].y
local lr = ul:clone() local lrx,lry = ulx,uly
for i=2,#self.vertices do for i=2,#self.vertices do
local p = self.vertices[i] local p = self.vertices[i]
if ul.x > p.x then ul.x = p.x end if ulx > p.x then ulx = p.x end
if ul.y > p.y then ul.y = p.y end if uly > p.y then uly = p.y end
if lr.x < p.x then lr.x = p.x end if lrx < p.x then lrx = p.x end
if lr.y < p.y then lr.y = p.y end if lry < p.y then lry = p.y end
end end
return ul.x,ul.y, lr.x,lr.y return ulx,uly, lrx,lry
end end
-- a polygon is convex if all edges are oriented ccw -- a polygon is convex if all edges are oriented ccw
@ -219,13 +226,16 @@ function Polygon:move(dx, dy)
self.centroid.y = self.centroid.y + dy self.centroid.y = self.centroid.y + dy
end end
function Polygon:rotate(angle, center, cy) function Polygon:rotate(angle, cx, cy)
local center = center or self.centroid if not (cx and cy) then
if cy then center = vector(center, cy) end cx,cy = self.centroid.x, self.centroid.y
for i,v in ipairs(self.vertices) do
self.vertices[i] = (self.vertices[i] - center):rotate_inplace(angle) + center
end end
self.centroid = (self.centroid - center):rotate_inplace(angle) + center for i,v in ipairs(self.vertices) do
-- v = (v - center):rotate(angle) + center
v.x,v.y = vector.add(cx,cy, vector.rotate(angle, v.x-cx, v.y-cy))
end
local v = self.centroid
v.x,v.y = vector.add(cx,cy, vector.rotate(angle, v.x-cx, v.y-cy))
end end
-- triangulation by the method of kong -- triangulation by the method of kong
@ -297,7 +307,7 @@ end
-- return merged polygon if possible or nil otherwise -- return merged polygon if possible or nil otherwise
function Polygon:mergedWith(other) function Polygon:mergedWith(other)
local p,q = getSharedEdge(self.vertices, other.vertices) local p,q = getSharedEdge(self.vertices, other.vertices)
if not (p and q) then return nil end assert(p and q, "Polygons do not share an edge")
local ret = {} local ret = {}
for i = 1, p do ret[#ret+1] = self.vertices[i] end for i = 1, p do ret[#ret+1] = self.vertices[i] end
@ -325,8 +335,8 @@ function Polygon:splitConvex()
local p = convex[i] local p = convex[i]
local k = i + 1 local k = i + 1
while k <= #convex do while k <= #convex do
local _, merged = pcall(function() return p:mergedWith(convex[k]) end) local success, merged = pcall(function() return p:mergedWith(convex[k]) end)
if merged and merged:isConvex() then if success and merged:isConvex() then
convex[i] = merged convex[i] = merged
p = convex[i] p = convex[i]
table.remove(convex, k) table.remove(convex, k)
@ -357,8 +367,9 @@ function Polygon:contains(x,y)
local v = self.vertices local v = self.vertices
local in_polygon = false local in_polygon = false
local p,q = v[#v],v[#v]
for i = 1, #v do for i = 1, #v do
local p, q = v[i], v[(i % #v) + 1] p,q = q,v[i]
if cut_ray(p,q) or cross_boundary(p,q) then if cut_ray(p,q) or cross_boundary(p,q) then
in_polygon = not in_polygon in_polygon = not in_polygon
end end
@ -367,24 +378,24 @@ function Polygon:contains(x,y)
end end
function Polygon:intersectsRay(x,y, dx,dy) function Polygon:intersectsRay(x,y, dx,dy)
local p = vector(x,y) --local p = vector(x,y)
local v = vector(dx,dy) --local v = vector(dx,dy)
local n = v:perpendicular() local nx,ny = vector.perpendicular(dx,dy)
local w,det local wx,xy,det
local tmin = math.huge local tmin = math.huge
local q1,q2 = nil, self.vertices[#self.vertices] local q1,q2 = nil, self.vertices[#self.vertices]
for i = 1, #self.vertices do for i = 1, #self.vertices do
q1,q2 = q2,self.vertices[i] q1,q2 = q2,self.vertices[i]
w = q2 - q1 wx,wy = q2.x - q1.x, q2.y - q1.y
det = v:cross(w) det = vector.det(dx,dy, wx,wy)
if det ~= 0 then if det ~= 0 then
-- there is an intersection point. check if it lies on both -- there is an intersection point. check if it lies on both
-- the ray and the segment. -- the ray and the segment.
local r = q2 - p local rx,ry = q2.x - x, q2.y - y
local l = r:cross(w)/det local l = vector.det(rx,ry, wx,wy) / det
local m = v:cross(r)/det local m = vector.det(dx,dy, rx,ry) / det
if l >= 0 and m >= 0 and m <= 1 then if l >= 0 and m >= 0 and m <= 1 then
-- we cannot jump out early here (i.e. when l > tmin) because -- we cannot jump out early here (i.e. when l > tmin) because
-- the polygon might be concave -- the polygon might be concave
@ -394,9 +405,10 @@ function Polygon:intersectsRay(x,y, dx,dy)
-- lines parralel or incident. get distance of line to -- lines parralel or incident. get distance of line to
-- anchor point. if they are incident, check if an endpoint -- anchor point. if they are incident, check if an endpoint
-- lies on the ray -- lies on the ray
local dist = (q1 - p) * n local dist = vector.dot(q1.x-x,q1.y-y, nx,ny)
if dist == 0 then if dist == 0 then
local l,m = v * (q1 - p), v * (q2 - p) local l = vector.dot(dx,dy, q1.x-x,q1.y-y)
local m = vector.dot(dx,dy, q2.x-x,q2.y-y)
if l >= 0 and l >= m then if l >= 0 and l >= m then
tmin = math.min(tmin, l) tmin = math.min(tmin, l)
elseif m >= 0 then elseif m >= 0 then

View File

@ -32,11 +32,11 @@ if not common and common.class then
class_commons = true class_commons = true
require(_PACKAGE .. '.class') require(_PACKAGE .. '.class')
end end
local vector = require(_PACKAGE .. '.vector') local vector = require(_PACKAGE .. '.vector-light')
local Polygon = require(_PACKAGE .. '.polygon') local Polygon = require(_PACKAGE .. '.polygon')
local function math_absmin(a,b) return math_abs(a) < math_abs(b) and a or b end local function math_absmin(a,b) return math_abs(a) < math_abs(b) and a or b end
local function test_axes(axes, shape_one, shape_two, sep, min_overlap) local function test_axes(axes, shape_one, shape_two, sx,sy, min_overlap)
for _,axis in ipairs(axes) do for _,axis in ipairs(axes) do
local l1,r1 = shape_one:projectOn(axis) local l1,r1 = shape_one:projectOn(axis)
local l2,r2 = shape_two:projectOn(axis) local l2,r2 = shape_two:projectOn(axis)
@ -46,24 +46,27 @@ local function test_axes(axes, shape_one, shape_two, sep, min_overlap)
-- get the smallest absolute overlap -- get the smallest absolute overlap
local overlap = math_absmin(l2-r1, r2-l1) local overlap = math_absmin(l2-r1, r2-l1)
if math_abs(overlap) < min_overlap then if math_abs(overlap) < min_overlap then
sep, min_overlap = overlap * axis, math_abs(overlap) sx,sy = vector.mul(overlap, axis.x, axis.y)
min_overlap = math_abs(overlap)
end end
end end
return true, sep, min_overlap return true, sx,sy, min_overlap
end end
local function SAT(shape_one, axes_one, shape_two, axes_two) local function SAT(shape_one, axes_one, shape_two, axes_two)
local collide, sep, overlap = false, vector(0,0), math_huge local collide, sx,sy, overlap = false, 0,0, math_huge
collide, sep, overlap = test_axes(axes_one, shape_one, shape_two, sep, overlap) collide, sx,sy, overlap = test_axes(axes_one, shape_one, shape_two, sx,sy, overlap)
print('1', collide, sx,sy, overlap)
if not collide then return false end if not collide then return false end
collide, sep = test_axes(axes_two, shape_one, shape_two, sep, overlap) collide, sx,sy, overlap = test_axes(axes_two, shape_one, shape_two, sx,sy, overlap)
return collide, sep print('2', collide, sx,sy, overlap)
return collide, sx,sy
end end
local function outcircles_intersect(shape_one, shape_two) local function outcircles_intersect(shape_one, shape_two)
local x1,y1,r1 = shape_one:outcircle() local x1,y1,r1 = shape_one:outcircle()
local x2,y2,r2 = shape_two:outcircle() local x2,y2,r2 = shape_two:outcircle()
return (x1-x2)*(x1-x2) + (y1-y2)*(y1-y2) <= (r1+r2)*(r1+r2) return vector.len2(x1-x2, y1-y2) <= (r1+r2)*(r1+r2)
end end
-- --
@ -121,14 +124,14 @@ end
local CircleShape = {} local CircleShape = {}
function CircleShape:init(cx,cy, radius) function CircleShape:init(cx,cy, radius)
Shape.init(self, Shape.CIRCLE) Shape.init(self, Shape.CIRCLE)
self._center = vector(cx,cy) self._center = {x = cx, y = cy}
self._radius = radius self._radius = radius
end end
local PointShape = {} local PointShape = {}
function PointShape:init(x,y) function PointShape:init(x,y)
Shape.init(self, Shape.POINT) Shape.init(self, Shape.POINT)
self._pos = vector(x,y) self._pos = {x = x, y = y}
end end
-- --
@ -137,26 +140,31 @@ end
function ConvexPolygonShape:getAxes() function ConvexPolygonShape:getAxes()
local axes = {} local axes = {}
local vert = self._polygon.vertices local vert = self._polygon.vertices
local p,q = vert[#vert], vert[#vert]
for i = 1,#vert do for i = 1,#vert do
axes[#axes+1] = (vert[i]-vert[(i%#vert)+1]):perpendicular():normalize_inplace() p,q = q, vert[i]
local x,y = vector.normalize(vector.perpendicular(p.x-q.x, p.y-q.y))
axes[#axes+1] = {x = x, y = y}
end end
return axes return axes
end end
function ConvexPolygonShape:projectOn(axis) function ConvexPolygonShape:projectOn(axis)
local vertices = self._polygon.vertices local v = self._polygon.vertices
local projection = {} local min,max = math_huge,-math_huge
for i = 1,#vertices do for i = 1,#v do
projection[i] = vertices[i] * axis -- same as vertices[i]:projectOn(axis) * axis local p = vector.dot(v[i].x,v[i].y, axis.x,axis.y) -- = v[i]:projectOn(axis) * axis
min = math_min(p, min)
max = math_max(p, max)
end end
return math_min(unpack(projection)), math_max(unpack(projection)) return min, max
end end
function CircleShape:projectOn(axis) function CircleShape:projectOn(axis)
-- v:projectOn(a) * a = v * a (see ConvexPolygonShape) -- v:projectOn(a) * a = v * a (see ConvexPolygonShape)
-- therefore: (c +- a*r) * a = c*a +- |a|^2 * r -- therefore: (c +- a*r) * a = c*a +- |a|^2 * r
local center = self._center * axis local center = vector.dot(self._center.x,self._center.y, axis.x,axis.y)
local shift = self._radius * axis:len2() local shift = self._radius * vector.len2(axis.x, axis.y)
return center - shift, center + shift return center - shift, center + shift
end end
@ -164,8 +172,8 @@ end
-- let circle shape or compund shape handle the collision -- let circle shape or compund shape handle the collision
function ConvexPolygonShape:collidesWith(other) function ConvexPolygonShape:collidesWith(other)
if other._type ~= Shape.POLYGON then if other._type ~= Shape.POLYGON then
local collide, sep = other:collidesWith(self) local collide, sx,sy = other:collidesWith(self)
return collide, sep and -sep return collide, sx and -sx, sy and -sy
end end
-- else: type is POLYGON, use the SAT -- else: type is POLYGON, use the SAT
@ -180,26 +188,29 @@ function ConcavePolygonShape:collidesWith(other)
if not outcircles_intersect(self, other) then return false end if not outcircles_intersect(self, other) then return false end
local sep, collide, collisions = vector(0,0), false, 0 -- TODO: better way of doing this. report all the separations?
local collide,dx,dy,count = false,0,0,0
for _,s in ipairs(self._shapes) do for _,s in ipairs(self._shapes) do
local status, separating_vector = s:collidesWith(other) local status, sx,sy = s:collidesWith(other)
collide = collide or status collide = collide or status
if status then if status then
sep, collisions = sep + separating_vector, collisions + 1 dx,dy = dx+sx, dy+sy
count = count + 1
end end
end end
return collide, sep / collisions return collide, dx/count, dy/count
end end
function CircleShape:collidesWith(other) function CircleShape:collidesWith(other)
if other._type == Shape.CIRCLE then if other._type == Shape.CIRCLE then
local d = self._center:dist(other._center) 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 local radii = self._radius + other._radius
if d < radii then if d < radii*radii then
-- if circles overlap, push it out upwards -- if circles overlap, push it out upwards
if d == 0 then return true, radii * vector(0,1) end if d == 0 then return true, 0,radii end
-- otherwise push out in best direction -- otherwise push out in best direction
return true, (radii - d) * (self._center - other._center):normalize_inplace() return true, vector.mul(radii - math_sqrt(d), vector.normalize(px,py))
end end
return false return false
elseif other._type == Shape.COMPOUND then elseif other._type == Shape.COMPOUND then
@ -212,24 +223,26 @@ function CircleShape:collidesWith(other)
-- else: other._type == POLYGON -- else: other._type == POLYGON
if not outcircles_intersect(self, other) then return false end if not outcircles_intersect(self, other) then return false end
-- retrieve closest edge to center -- retrieve closest edge to center
local points = other._polygon.vertices local vertices = other._polygon.vertices
local closest, dist = points[1], (self._center - points[1]):len2() local closest, dist = vertices[1], vector.len2(self._center.x-vertices[1].x, self._center.y-vertices[1].y)
for i = 2,#points do for i = 2,#vertices do
local d = (self._center - points[i]):len2() local d = vector.len2(self._center.x-vertices[i].x, self._center.y-vertices[i].y)
if d < dist then if d < dist then
closest, dist = points[i], d closest, dist = vertices[i], d
end end
end end
local axis = vector(0,1) local axis = {x=0,y=1}
if dist ~= 0 then axis = (closest - self._center):normalize_inplace() end if dist ~= 0 then
axis.x,axis.y = vector.normalize(closest.x-self._center.x, closest.y-self._center.y)
end
return SAT(self, {axis}, other, other:getAxes()) return SAT(self, {axis}, other, other:getAxes())
end end
function PointShape:collidesWith(other) function PointShape:collidesWith(other)
if other._type == Shape.POINT then if other._type == Shape.POINT then
return (self._pos == other._pos), vector(0,0) return (self._pos == other._pos), 0,0
end end
return other:contains(self._pos.x, self._pos.y), vector(0,0) return other:contains(self._pos.x, self._pos.y), 0,0
end end
-- --
@ -244,7 +257,7 @@ function ConcavePolygonShape:contains(x,y)
end end
function CircleShape:contains(x,y) function CircleShape:contains(x,y)
return (vector(x,y) - self._center):len2() < self._radius * self._radius return vector.len2(x-self._center.x, y-self._center.y) < self._radius * self._radius
end end
function PointShape:contains(x,y) function PointShape:contains(x,y)
@ -268,12 +281,11 @@ end
-- --
-- d*d s^2 + 2 d*(p-c) s + (p-c)*(p-c)-r^2 = 0 -- d*d s^2 + 2 d*(p-c) s + (p-c)*(p-c)-r^2 = 0
function CircleShape:intersectsRay(x,y, dx,dy) function CircleShape:intersectsRay(x,y, dx,dy)
local pc = vector(x,y) - self._center local pcx,pcy = x-self._center.x, y-self._center.y
local d = vector(dx,dy)
local a = d * d local a = vector.len2(dx,dy)
local b = 2 * d * pc local b = 2 * vector.dot(dx,dy, pcx,pcy)
local c = pc * pc - self._radius * self._radius local c = vector.len2(pcx,pcy) - self._radius * self._radius
local discr = b*b - 4*a*c local discr = b*b - 4*a*c
if discr < 0 then return false end if discr < 0 then return false end
@ -290,9 +302,8 @@ end
-- point shape intersects ray if it lies on the ray -- point shape intersects ray if it lies on the ray
function PointShape:intersectsRay(x,y,dx,dy) function PointShape:intersectsRay(x,y,dx,dy)
local p = self._pos - vector(x,y) local px,py = self._pos.x-x, self._pos.y-y
local d = vector(dx,dy) local t = vector.dot(px,py, dx,dy) / vector.len2(dx,dy)
local t = p * d / d:len2()
return t >= 0, t return t >= 0, t
end end
@ -300,19 +311,19 @@ end
-- auxiliary -- auxiliary
-- --
function ConvexPolygonShape:center() function ConvexPolygonShape:center()
return self._polygon.centroid:unpack() return self._polygon.centroid.x, self._polygon.centroid.y
end end
function ConcavePolygonShape:center() function ConcavePolygonShape:center()
return self._polygon.centroid:unpack() return self._polygon.centroid.x, self._polygon.centroid.y
end end
function CircleShape:center() function CircleShape:center()
return self._center:unpack() return self._center.x, self._center.y
end end
function PointShape:center() function PointShape:center()
return self._pos:unpack() return self._pos.x, self._pos.y
end end
function ConvexPolygonShape:outcircle() function ConvexPolygonShape:outcircle()
@ -343,13 +354,13 @@ function ConcavePolygonShape:bbox()
end end
function CircleShape:bbox() function CircleShape:bbox()
local cx,cy = self._center:unpack() local cx,cy = self:center()
local r = self._radius local r = self._radius
return cx-r,cy-r, cx+r,cy+r return cx-r,cy-r, cx+r,cy+r
end end
function PointShape:bbox() function PointShape:bbox()
local x,y = self._pos:unpack() local x,y = self:center()
return x,y,x,y return x,y,x,y
end end
@ -366,7 +377,8 @@ function ConcavePolygonShape:move(x,y)
end end
function CircleShape:move(x,y) function CircleShape:move(x,y)
self._center = self._center + vector(x,y) self._center.x = self._center.x + x
self._center.y = self._center.y + y
end end
function PointShape:move(x,y) function PointShape:move(x,y)
@ -377,9 +389,12 @@ end
function ConcavePolygonShape:rotate(angle,cx,cy) function ConcavePolygonShape:rotate(angle,cx,cy)
Shape.rotate(self, angle) Shape.rotate(self, angle)
self._polygon:rotate(angle,cx) if not (cx and cy) then
cx,cy = self:center()
end
self._polygon:rotate(angle,cx,cy)
for _,p in ipairs(self._shapes) do for _,p in ipairs(self._shapes) do
p:rotate(angle, cx and vector(cx,cy) or self._polygon.centroid) p:rotate(angle, cx,cy)
end end
end end
@ -390,16 +405,14 @@ end
function CircleShape:rotate(angle, cx,cy) function CircleShape:rotate(angle, cx,cy)
Shape.rotate(self, angle) Shape.rotate(self, angle)
if not cx then return end if not (cx and cy) then return end
local c = vector(cx,cy) self._center.x,self._center.y = vector.add(cx,cy, vector.rotate(angle, self._center.x-cx, self._center.y-cy))
self._center = (self._center - c):rotate_inplace(angle) + c
end end
function PointShape:rotate(angle, cx,cy) function PointShape:rotate(angle, cx,cy)
Shape.rotate(self, angle) Shape.rotate(self, angle)
if not cx then return end if not (cx and cy) then return end
local c = vector(cx,cy) self._pos.x,self._pos.y = vector.add(cx,cy, vector.rotate(angle, self._pos.x-cx, self._pos.y-cy))
self._pos = (self._pos - c):rotate_inplace(angle) + c
end end
@ -420,11 +433,11 @@ function ConcavePolygonShape:draw(mode)
end end
function CircleShape:draw(mode, segments) function CircleShape:draw(mode, segments)
love.graphics.circle(mode or 'line', self._center.x, self._center.y, self._radius) love.graphics.circle(mode or 'line', self:outcircle())
end end
function PointShape:draw() function PointShape:draw()
love.graphics.point(self._pos.x, self._pos.y) love.graphics.point(self:center())
end end

View File

@ -32,20 +32,33 @@ if not (common and common.class and common.instance) then
class_commons = true class_commons = true
require(_PACKAGE .. '.class') require(_PACKAGE .. '.class')
end end
local vector = require(_PACKAGE .. '.vector')
-- special cell accesor metamethods, so vectors are converted -- transparent cell accessor methods
-- to a string before using as keys -- cells = {[0] = {[0] = <>, [1] = <>, ... }, [1] = {...}, ...}
local cell_meta = {} local cells_meta = {}
function cell_meta.__newindex(tbl, key, val) function cells_meta.__newindex(tbl, key, val)
return rawset(tbl, key.x..","..key.y, val) print('newindex')
local cell = rawget(tbl, key.x)
if not cell then
rawset(tbl, key.x, {[key.y] = val})
else
rawset(cell, key.y, val)
end
end end
function cell_meta.__index(tbl, key)
local key = key.x..","..key.y function cells_meta.__index(tbl, key)
local ret = rawget(tbl, key) local cell = rawget(tbl, key.x)
if not cell then
local ret = setmetatable({}, {__mode = "kv"})
cell = {[key.y] = ret}
rawset(tbl, key.x, cell)
return ret
end
local ret = rawget(cell, key.y)
if not ret then if not ret then
ret = setmetatable({}, {__mode = "kv"}) ret = setmetatable({}, {__mode = "kv"})
rawset(tbl, key, ret) rawset(cell, key.y, ret)
end end
return ret return ret
end end
@ -53,7 +66,7 @@ end
local Spatialhash = {} local Spatialhash = {}
function Spatialhash:init(cell_size) function Spatialhash:init(cell_size)
self.cell_size = cell_size or 100 self.cell_size = cell_size or 100
self.cells = setmetatable({}, cell_meta) self.cells = setmetatable({}, cells_meta)
end end
function Spatialhash:cellCoords(v) function Spatialhash:cellCoords(v)
@ -69,7 +82,7 @@ function Spatialhash:insert(obj, ul, lr)
local lr = self:cellCoords(lr) local lr = self:cellCoords(lr)
for i = ul.x,lr.x do for i = ul.x,lr.x do
for k = ul.y,lr.y do for k = ul.y,lr.y do
rawset(self.cells[ {x=i,y=k} ], obj, obj)
end end
end end
end end

138
vector-light.lua Normal file
View File

@ -0,0 +1,138 @@
--[[
Copyright (c) 2012 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 sqrt, cos, sin = math.sqrt, math.cos, math.sin
local function str(x,y)
return "("..tonumber(x)..","..tonumber(y)..")"
end
local function mul(s, x,y)
return s*x, s*y
end
local function div(s, x,y)
return x/s, y/s
end
local function add(x1,y1, x2,y2)
return x1+x2, y1+y2
end
local function sub(x1,y1, x2,y2)
return x1-x2, y1-y2
end
local function permul(x1,y1, x2,y2)
return x1*x2, y1*y2
end
local function dot(x1,y1, x2,y2)
return x1*x2 + y1*y2
end
local function det(x1,y1, x2,y2)
return x1*y2 - y1*x2
end
local function eq(x1,y1, x2,y2)
return x1 == x2 and y1 == y2
end
local function lt(x1,y1, x2,y2)
return x1 < x2 or (x1 == x2 and y1 < y2)
end
local function le(x1,y1, x2,y2)
return x1 <= x2 and y1 <= y2
end
local function len2(x,y)
return x*x + y*y
end
local function len(x,y)
return sqrt(x*x + y*y)
end
local function dist(x1,y1, x2,y2)
return len(x1-x2, y1-y2)
end
local function normalize(x,y)
local l = len(x,y)
return x/l, y/l
end
local function rotate(phi, x,y)
local c, s = cos(phi), sin(phi)
return c*x - s*y, s*x + c*y
end
local function perpendicular(x,y)
return -y, x
end
local function project(x,y, u,v)
local s = (x*u + y*v) / (u*u + v*v)
return s*u, s*v
end
local function mirror(x,y, u,v)
local s = 2 * (x*u + y*v) / (u*u + v*v)
return s*u - x, s*v - y
end
-- the module
return {
str = str,
-- arithmetic
mul = mul,
div = div,
add = add,
sub = sub,
permul = permul,
dot = dot,
det = det,
cross = det,
-- relation
eq = eq,
lt = lt,
le = le,
-- misc operations
len2 = len2,
len = len,
dist = dist,
normalize = normalize,
rotate = rotate,
perpendicular = perpendicular,
project = project,
mirror = mirror,
}

View File

@ -1,151 +0,0 @@
--[[
Copyright (c) 2010 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.
]]--
-- somewhat speed optimized version of hump.vector
local sqrt, cos, sin = math.sqrt, math.cos, math.sin
local vector = {}
vector.__index = vector
local function new(x,y)
local v = {x = x or 0, y = y or 0}
setmetatable(v, vector)
return v
end
function vector:clone()
return new(self.x, self.y)
end
function vector:unpack()
return self.x, self.y
end
function vector:__tostring()
return "("..tonumber(self.x)..","..tonumber(self.y)..")"
end
function vector.__unm(a)
return new(-a.x, -a.y)
end
function vector.__add(a,b)
return new(a.x+b.x, a.y+b.y)
end
function vector.__sub(a,b)
return new(a.x-b.x, a.y-b.y)
end
function vector.__mul(a,b)
if type(a) == "number" then
return new(a*b.x, a*b.y)
elseif type(b) == "number" then
return new(b*a.x, b*a.y)
else
return a.x*b.x + a.y*b.y
end
end
function vector.__div(a,b)
return new(a.x / b, a.y / b)
end
function vector.__eq(a,b)
return a.x == b.x and a.y == b.y
end
function vector.__lt(a,b)
return a.x < b.x or (a.x == b.x and a.y < b.y)
end
function vector.__le(a,b)
return a.x <= b.x and a.y <= b.y
end
function vector.permul(a,b)
return new(a.x*b.x, a.y*b.y)
end
function vector:len2()
return self.x * self.x + self.y * self.y
end
function vector:len()
return sqrt(self.x * self.x + self.y * self.y)
end
function vector.dist(a, b)
local dx = a.x - b.x
local dy = a.y - b.y
return sqrt( dx*dx + dy*dy )
end
function vector:normalize_inplace()
local l = sqrt(self.x * self.x + self.y * self.y)
self.x, self.y = self.x / l, self.y / l
return self
end
function vector:normalized()
return self / sqrt(self.x * self.x + self.y * self.y)
end
function vector:rotate_inplace(phi)
local c, s = cos(phi), sin(phi)
self.x, self.y = c * self.x - s * self.y, s * self.x + c * self.y
return self
end
function vector:rotated(phi)
local c, s = cos(phi), sin(phi)
return new(c * self.x - s * self.y, s * self.x + c * self.y)
end
function vector:perpendicular()
return new(-self.y, self.x)
end
function vector:projectOn(v)
-- (self * v) * v / v:len2()
local s = (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y)
return new(s * v.x, s * v.y)
end
function vector:mirrorOn(v)
-- 2 * self:projectOn(other) - self
local s = 2 * (self.x * v.x + self.y * v.y) / (v.x * v.x + v.y * v.y)
return new(s * v.x - self.x, s * v.y - self.y)
end
function vector:cross(other)
return self.x * other.y - self.y * other.x
end
-- the module
return setmetatable({new = new}, {__call = function(_, ...) return new(...) end})