diff --git a/polygon.lua b/polygon.lua index d628580..ad1a92e 100644 --- a/polygon.lua +++ b/polygon.lua @@ -419,56 +419,11 @@ function Polygon:contains(x,y) return in_polygon end -function Polygon:intersectsRay(x,y, dx,dy) - --local p = vector(x,y) - --local v = vector(dx,dy) +function Polygon:intersectionsWithRay(x,y, 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] - 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 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 - tmin = math.min(tmin, l) - end - else - -- 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 = vector.dot(q1.x-x,q1.y-y, nx,ny) - if dist == 0 then - 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 - tmin = math.min(tmin, m) - end - end - end - end - return tmin ~= math.huge, tmin -end - -function Polygon:intersectionsRay(x,y, dx,dy) - --local p = vector(x,y) - --local v = vector(dx,dy) - local nx,ny = vector.perpendicular(dx,dy) - local wx,xy,det - - local ts = {} + local ts = {} -- ray parameters of each intersection local q1,q2 = nil, self.vertices[#self.vertices] for i = 1, #self.vertices do q1,q2 = q2,self.vertices[i] @@ -484,7 +439,7 @@ function Polygon:intersectionsRay(x,y, dx,dy) if m >= 0 and m <= 1 then -- we cannot jump out early here (i.e. when l > tmin) because -- the polygon might be concave - table.insert(ts, l) + ts[#ts+1] = l end else -- lines parralel or incident. get distance of line to @@ -495,9 +450,9 @@ function Polygon:intersectionsRay(x,y, dx,dy) 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 >= m then - table.insert(ts, l) + ts[#ts+1] = l else - table.insert(ts, m) + ts[#ts+1] = m end end end @@ -506,6 +461,14 @@ function Polygon:intersectionsRay(x,y, dx,dy) return ts end +function Polygon:intersectsRay(x,y, dx,dy) + local tmin = math.huge + for _, t in ipairs(self:intersectionsWithRay(x,y,dx,dy)) do + tmin = math.min(tmin, t) + end + return tmin ~= math.huge, tmin +end + Polygon = common_local.class('Polygon', Polygon) newPolygon = function(...) return common_local.instance(Polygon, ...) end return Polygon diff --git a/shapes.lua b/shapes.lua index 23bdea7..9b4a48d 100644 --- a/shapes.lua +++ b/shapes.lua @@ -213,12 +213,12 @@ function ConvexPolygonShape:intersectsRay(x,y, dx,dy) return self._polygon:intersectsRay(x,y, dx,dy) end -function ConcavePolygonShape:intersectionsRay(x,y, dx,dy) - return self._polygon:intersectionsRay(x,y, dx,dy) +function ConcavePolygonShape:intersectionsWithRay(x,y, dx,dy) + return self._polygon:intersectionsWithRay(x,y, dx,dy) end -function ConvexPolygonShape:intersectionsRay(x,y, dx,dy) - return self._polygon:intersectionsRay(x,y, dx,dy) +function ConvexPolygonShape:intersectionsWithRay(x,y, dx,dy) + return self._polygon:intersectionsWithRay(x,y, dx,dy) end -- circle intersection if distance of ray/center is smaller @@ -228,27 +228,7 @@ end -- solving [with c = (cx,cy)]: -- -- d*d s^2 + 2 d*(p-c) s + (p-c)*(p-c)-r^2 = 0 -function CircleShape:intersectsRay(x,y, dx,dy) - local pcx,pcy = x-self._center.x, y-self._center.y - - 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 - - discr = math_sqrt(discr) - local s1,s2 = discr-b, -discr-b - if s1 < 0 then -- first solution is off the ray - return s2 >= 0, s2/(2*a) - elseif s2 < 0 then -- second solution is off the ray - return s1 >= 0, s1/(2*a) - end - -- both solutions on the ray - return true, math_min(s1,s2)/(2*a) -end - -function CircleShape:intersectionsRay(x,y, dx,dy) +function CircleShape:intersectionsWithRay(x,y, dx,dy) local pcx,pcy = x-self._center.x, y-self._center.y local a = vector.len2(dx,dy) @@ -259,9 +239,18 @@ function CircleShape:intersectionsRay(x,y, dx,dy) if discr < 0 then return {} end discr = math_sqrt(discr) - local s1, s2 = (discr-b)/(2*a), (-discr-b)/(2*a) + local ts, t1, t2 = {}, discr-b, -discr-b + if t1 >= 0 then ts[#ts+1] = t1/(2*a) end + if t2 >= 0 then ts[#ts+1] = t2/(2*a) end + return ts +end - return {math.min(s1, s2), math.max(s1, s2)} +function CircleShape:intersectsRay(x,y, dx,dy) + local tmin = math_huge + for _, t in ipaits(self:intersectionsWithRay(x,y,dx,dy)) do + tmin = math_min(t, tmin) + end + return tmin ~= math_huge, tmin end -- point shape intersects ray if it lies on the ray @@ -271,10 +260,9 @@ function PointShape:intersectsRay(x,y, dx,dy) return t >= 0, t end -function PointShape:intersectionsRay(x,y, dx,dy) - 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} +function PointShape:intersectionsWithRay(x,y, dx,dy) + local intersects, t = self:intersectsRay(x,y, dx,dy) + return intersects and {t} or {} end --