local poly = { 10,10, 20,10, 20,20, 10,20 } for x = 1, #poly, 2 do local y = x + 1 local px, py = poly[x], poly[y] ... endThe second representation stores each vertex as a separate table. This technique takes more memory, but allows us to reference and modify vertices externally:
local poly = { { x=10,y=10 }, { x=20,y=10 }, { x=20,y=20 }, { x=10,y=20 }, } local first = poly[1] first.x = first.x + 1Ideally, it's good to stick to one polygonal representation. In reality, we are going to encounter different representations within the same API (Love2D is guilty of this too). Sooner or later we have to convert between formats.
-- Converts to vertex table format function toVecs(p) local t = {} local j = 1 for i = 1, #p, 2 do t[j] = { x = p[i], y = p[i + 1] } j = j + 1 end return t end -- Converts to flat list of coords function toCoords(p) local t = {} local j = 1 for i = 1, #p do local v = p[i] t[j], t[j + 1] = v.x, v.y j = j + 2 end return t end
-- Generates a regular polygon given a circumradius (r) function regularPoly(n, r) local out = {} for i = 1, n do local a = (i - 1)/n*pi2 out[i] = { x = math.cos(a)*r, y = math.sin(a)*r } end return out end -- Generates a regular polygon, given a side length (s) function regularPoly2(n, s) local r = s/(2*math.sin(pi/n)) return regular(n, r) end -- Generates a regular polygon given an apothem (a) function regularPoly3(n, a) local r = a/(math.cos(pi/n)) return regular(n, r) end
-- Finds twice the signed area of a polygon function signedPolyArea(p) local s = 0 local n = #p local a = p[n] for i = 1, n do local b = p[i] s = s + (b.x + a.x)*(b.y - a.y) a = b end return s end -- Finds the actual area of a polygon function polyArea(p) local s = signedPolyArea(p) return math.abs(s/2) end -- Checks if the winding of a polygon is counter-clockwise function isPolyCCW(p) return signedPolyArea(p) > 0 end -- Reverses the vertex winding function polyReverse(p) local n = #p for i = 1, math.floor(n/2) do local i2 = n - i + 1 p[i], p[i2] = p[i2], p[i] end end
-- Checks if a polygon is convex function isPolyConvex(p) local ccw = isPolyCCW(p) local a = p[2] local b = p[1] for i = #p, 1, -1 do local c = p[i] local s = (c.x - a.x)*(b.y - a.y) - (c.y - a.y)*(b.x - a.x) if ccw then s = -s end if s > 0 then return false end a = b b = c end return true end
-- Checks if two segments intersect function segmentOnSegment(a, b, c, d) local dx1, dy1 = b.x - a.x, b.y - a.y local dx2, dy2 = d.x - c.x, d.y - c.y local dx3, dy3 = a.x - c.x, a.y - c.y local q = dx1*dy2 - dy1*dx2 if q == 0 then return false end local t1 = (dx2*dy3 - dy2*dx3)/q if t1 < 0 or t1 > 1 then return false end local t2 = (dx1*dy3 - dy1*dx3)/q if t2 < 0 or t2 > 1 then return false end return true end -- Checks if a polygon is simple function isPolySimple(p) local n = #p local finish = n - 1 local a = p[n] for i = 1, n do local b = p[i] local c = p[i + 1] for j = i + 2, finish do local d = p[j] if segmentOnSegment(a, b, c, d) then return false end c = d end a = b finish = n end return true end
-- Checks if a point is inside the polygon function pointInPoly(p, q) local inside = false local n = #p local a = p[n] for i = 1, n do local b = p[i] if (q.x < a.x) ~= (q.y < b.y) then if q.x < a.x + ((q.y - a.y)/(b.y - a.y)*(b.x - a.x)) then inside = not inside end end a = b end return inside end
-- Finds twice the signed area of a triangle function signedTriArea(p1, p2, p3) return (p1.x - p3.x)*(p2.y - p3.y) - (p1.y - p3.y)*(p2.x - p3.x) end -- Checks if a point is inside a triangle function pointInTri(p, p1, p2, p3) local ox, oy = p.x, p.y local px1, py1 = p1.x - ox, p1.y - oy local px2, py2 = p2.x - ox, p2.y - oy local px3, py3 = p3.x - ox, p3.y - oy local sab = px1*py2 - py1*px2 < 0 if sab ~= (px2*py3 - py2*px3 < 0) then return false end return sab == (px3*py1 - py3*px1 < 0) end -- Checks if the vertex is an "ear" or "mouth" local left, right = {}, {} local function isEar(i0, i1, i2) if signedTriArea(i0, i1, i2) >= 0 then local j1 = right[i2] repeat local j0, j2 = left[j1], right[j1] if signedTriArea(j0, j1, j2) <= 0 then if pointInTri(j1, i0, i1, i2) then return false end end j1 = j2 until j1 == i0 return true end return false end -- Triangulates a counter-clockwise polygon function triangulatePoly(p) if not isPolyCCW(p) then polyReverse(p) end for i = 1, #p do local v = p[i] left[v], right[v] = p[i - 1], p[i + 1] end local first, last = p[1], p[#p] left[first], right[last] = last, first local out = {} local nskip = 0 local i1 = first while #p >= 3 and nskip <= #p do local i0, i2 = left[i1], right[i1] if #p > 3 and isEar(i0, i1, i2) then table.insert(out, { i0, i1, i2 }) left[i2], right[i0] = i0, i2 left[i1], right[i1] = nil, nil nskip = 0 i1 = i0 else nskip = nskip + 1 i1 = i2 end end return out end
-- Finds a point along the perimeter of a polygon/polyline function tracePerimeter(p, t, loop) local n = 0 local e = loop and #p or #p - 1 for i = 1, e do local a = p[i] local b = p[i + 1] or p[1] local dx = b.x - a.x local dy = b.y - a.y local d = math.sqrt(dx*dx + dy*dy) if n + d > t then local r = (t - n)/d return a.x + r*dx, a.y + r*dy end n = n + d end end