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 = 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(class, unpack(super))

View File

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

View File

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

View File

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

View File

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