mirror of
https://github.com/vrld/HC.git
synced 2024-11-18 12:54:23 +00:00
Switch to light vector module
This commit is contained in:
parent
8a182a902f
commit
5b12e72aae
16
class.lua
16
class.lua
@ -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))
|
||||
|
||||
|
5
init.lua
5
init.lua
@ -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
|
||||
|
126
polygon.lua
126
polygon.lua
@ -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
|
||||
|
141
shapes.lua
141
shapes.lua
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
function cell_meta.__index(tbl, key)
|
||||
local key = key.x..","..key.y
|
||||
local ret = rawget(tbl, key)
|
||||
end
|
||||
|
||||
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
138
vector-light.lua
Normal 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,
|
||||
}
|
151
vector.lua
151
vector.lua
@ -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})
|
Loading…
Reference in New Issue
Block a user