polygon.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. --[[
  2. Copyright (c) 2011 Matthias Richter
  3. Permission is hereby granted, free of charge, to any person obtaining a copy
  4. of this software and associated documentation files (the "Software"), to deal
  5. in the Software without restriction, including without limitation the rights
  6. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. copies of the Software, and to permit persons to whom the Software is
  8. furnished to do so, subject to the following conditions:
  9. The above copyright notice and this permission notice shall be included in
  10. all copies or substantial portions of the Software.
  11. Except as contained in this notice, the name(s) of the above copyright holders
  12. shall not be used in advertising or otherwise to promote the sale, use or
  13. other dealings in this Software without prior written authorization.
  14. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. THE SOFTWARE.
  21. ]]--
  22. local _PACKAGE, common_local = (...):match("^(.+)%.[^%.]+"), common
  23. if not (type(common) == 'table' and common.class and common.instance) then
  24. assert(common_class ~= false, 'No class commons specification available.')
  25. require(_PACKAGE .. '.class')
  26. common_local, common = common, common_local
  27. end
  28. local vector = require(_PACKAGE .. '.vector-light')
  29. ----------------------------
  30. -- Private helper functions
  31. --
  32. -- create vertex list of coordinate pairs
  33. local function toVertexList(vertices, x,y, ...)
  34. if not (x and y) then return vertices end -- no more arguments
  35. vertices[#vertices + 1] = {x = x, y = y} -- set vertex
  36. return toVertexList(vertices, ...) -- recurse
  37. end
  38. -- returns true if three vertices lie on a line
  39. local function areCollinear(p, q, r, eps)
  40. return math.abs(vector.det(q.x-p.x, q.y-p.y, r.x-p.x,r.y-p.y)) <= (eps or 1e-32)
  41. end
  42. -- remove vertices that lie on a line
  43. local function removeCollinear(vertices)
  44. local ret = {}
  45. local i,k = #vertices - 1, #vertices
  46. for l=1,#vertices do
  47. if not areCollinear(vertices[i], vertices[k], vertices[l]) then
  48. ret[#ret+1] = vertices[k]
  49. end
  50. i,k = k,l
  51. end
  52. return ret
  53. end
  54. -- get index of rightmost vertex (for testing orientation)
  55. local function getIndexOfleftmost(vertices)
  56. local idx = 1
  57. for i = 2,#vertices do
  58. if vertices[i].x < vertices[idx].x then
  59. idx = i
  60. end
  61. end
  62. return idx
  63. end
  64. -- returns true if three points make a counter clockwise turn
  65. local function ccw(p, q, r)
  66. return vector.det(q.x-p.x, q.y-p.y, r.x-p.x, r.y-p.y) >= 0
  67. end
  68. -- test wether a and b lie on the same side of the line c->d
  69. local function onSameSide(a,b, c,d)
  70. local px, py = d.x-c.x, d.y-c.y
  71. local l = vector.det(px,py, a.x-c.x, a.y-c.y)
  72. local m = vector.det(px,py, b.x-c.x, b.y-c.y)
  73. return l*m >= 0
  74. end
  75. local function pointInTriangle(p, a,b,c)
  76. return onSameSide(p,a, b,c) and onSameSide(p,b, a,c) and onSameSide(p,c, a,b)
  77. end
  78. -- test whether any point in vertices (but pqr) lies in the triangle pqr
  79. -- note: vertices is *set*, not a list!
  80. local function anyPointInTriangle(vertices, p,q,r)
  81. for v in pairs(vertices) do
  82. if v ~= p and v ~= q and v ~= r and pointInTriangle(v, p,q,r) then
  83. return true
  84. end
  85. end
  86. return false
  87. end
  88. -- test is the triangle pqr is an "ear" of the polygon
  89. -- note: vertices is *set*, not a list!
  90. local function isEar(p,q,r, vertices)
  91. return ccw(p,q,r) and not anyPointInTriangle(vertices, p,q,r)
  92. end
  93. local function segmentsInterset(a,b, p,q)
  94. return not (onSameSide(a,b, p,q) or onSameSide(p,q, a,b))
  95. end
  96. -- returns starting/ending indices of shared edge, i.e. if p and q share the
  97. -- edge with indices p1,p2 of p and q1,q2 of q, the return value is p1,q2
  98. local function getSharedEdge(p,q)
  99. local pindex = setmetatable({}, {__index = function(t,k)
  100. local s = {}
  101. t[k] = s
  102. return s
  103. end})
  104. -- record indices of vertices in p by their coordinates
  105. for i = 1,#p do
  106. pindex[p[i].x][p[i].y] = i
  107. end
  108. -- iterate over all edges in q. if both endpoints of that
  109. -- edge are in p as well, return the indices of the starting
  110. -- vertex
  111. local i,k = #q,1
  112. for k = 1,#q do
  113. local v,w = q[i], q[k]
  114. if pindex[v.x][v.y] and pindex[w.x][w.y] then
  115. return pindex[w.x][w.y], k
  116. end
  117. i = k
  118. end
  119. end
  120. -----------------
  121. -- Polygon class
  122. --
  123. local Polygon = {}
  124. function Polygon:init(...)
  125. local vertices = removeCollinear( toVertexList({}, ...) )
  126. assert(#vertices >= 3, "Need at least 3 non collinear points to build polygon (got "..#vertices..")")
  127. -- assert polygon is oriented counter clockwise
  128. local r = getIndexOfleftmost(vertices)
  129. local q = r > 1 and r - 1 or #vertices
  130. local s = r < #vertices and r + 1 or 1
  131. if not ccw(vertices[q], vertices[r], vertices[s]) then -- reverse order if polygon is not ccw
  132. local tmp = {}
  133. for i=#vertices,1,-1 do
  134. tmp[#tmp + 1] = vertices[i]
  135. end
  136. vertices = tmp
  137. end
  138. -- assert polygon is not self-intersecting
  139. -- outer: only need to check segments #vert;1, 1;2, ..., #vert-3;#vert-2
  140. -- inner: only need to check unconnected segments
  141. local q,p = vertices[#vertices]
  142. for i = 1,#vertices-2 do
  143. p, q = q, vertices[i]
  144. for k = i+1,#vertices-1 do
  145. local a,b = vertices[k], vertices[k+1]
  146. assert(not segmentsInterset(p,q, a,b), 'Polygon may not intersect itself')
  147. end
  148. end
  149. self.vertices = vertices
  150. -- make vertices immutable
  151. setmetatable(self.vertices, {__newindex = function() error("Thou shall not change a polygon's vertices!") end})
  152. -- compute polygon area and centroid
  153. local p,q = vertices[#vertices], vertices[1]
  154. local det = vector.det(p.x,p.y, q.x,q.y) -- also used below
  155. self.area = det
  156. for i = 2,#vertices do
  157. p,q = q,vertices[i]
  158. self.area = self.area + vector.det(p.x,p.y, q.x,q.y)
  159. end
  160. self.area = self.area / 2
  161. p,q = vertices[#vertices], vertices[1]
  162. self.centroid = {x = (p.x+q.x)*det, y = (p.y+q.y)*det}
  163. for i = 2,#vertices do
  164. p,q = q,vertices[i]
  165. det = vector.det(p.x,p.y, q.x,q.y)
  166. self.centroid.x = self.centroid.x + (p.x+q.x) * det
  167. self.centroid.y = self.centroid.y + (p.y+q.y) * det
  168. end
  169. self.centroid.x = self.centroid.x / (6 * self.area)
  170. self.centroid.y = self.centroid.y / (6 * self.area)
  171. -- get outcircle
  172. self._radius = 0
  173. for i = 1,#vertices do
  174. self._radius = math.max(self._radius,
  175. vector.dist(vertices[i].x,vertices[i].y, self.centroid.x,self.centroid.y))
  176. end
  177. end
  178. local newPolygon
  179. -- return vertices as x1,y1,x2,y2, ..., xn,yn
  180. function Polygon:unpack()
  181. local v = {}
  182. for i = 1,#self.vertices do
  183. v[2*i-1] = self.vertices[i].x
  184. v[2*i] = self.vertices[i].y
  185. end
  186. return unpack(v)
  187. end
  188. -- deep copy of the polygon
  189. function Polygon:clone()
  190. return Polygon( self:unpack() )
  191. end
  192. -- get bounding box
  193. function Polygon:bbox()
  194. local ulx,uly = self.vertices[1].x, self.vertices[1].y
  195. local lrx,lry = ulx,uly
  196. for i=2,#self.vertices do
  197. local p = self.vertices[i]
  198. if ulx > p.x then ulx = p.x end
  199. if uly > p.y then uly = p.y end
  200. if lrx < p.x then lrx = p.x end
  201. if lry < p.y then lry = p.y end
  202. end
  203. return ulx,uly, lrx,lry
  204. end
  205. -- a polygon is convex if all edges are oriented ccw
  206. function Polygon:isConvex()
  207. local function isConvex()
  208. local v = self.vertices
  209. if #v == 3 then return true end
  210. if not ccw(v[#v], v[1], v[2]) then
  211. return false
  212. end
  213. for i = 2,#v-1 do
  214. if not ccw(v[i-1], v[i], v[i+1]) then
  215. return false
  216. end
  217. end
  218. if not ccw(v[#v-1], v[#v], v[1]) then
  219. return false
  220. end
  221. return true
  222. end
  223. -- replace function so that this will only be computed once
  224. local status = isConvex()
  225. self.isConvex = function() return status end
  226. return status
  227. end
  228. function Polygon:move(dx, dy)
  229. if not dy then
  230. dx, dy = dx:unpack()
  231. end
  232. for i,v in ipairs(self.vertices) do
  233. v.x = v.x + dx
  234. v.y = v.y + dy
  235. end
  236. self.centroid.x = self.centroid.x + dx
  237. self.centroid.y = self.centroid.y + dy
  238. end
  239. function Polygon:rotate(angle, cx, cy)
  240. if not (cx and cy) then
  241. cx,cy = self.centroid.x, self.centroid.y
  242. end
  243. for i,v in ipairs(self.vertices) do
  244. -- v = (v - center):rotate(angle) + center
  245. v.x,v.y = vector.add(cx,cy, vector.rotate(angle, v.x-cx, v.y-cy))
  246. end
  247. local v = self.centroid
  248. v.x,v.y = vector.add(cx,cy, vector.rotate(angle, v.x-cx, v.y-cy))
  249. end
  250. function Polygon:scale(s, cx,cy)
  251. if not (cx and cy) then
  252. cx,cy = self.centroid.x, self.centroid.y
  253. end
  254. for i,v in ipairs(self.vertices) do
  255. -- v = (v - center) * s + center
  256. v.x,v.y = vector.add(cx,cy, vector.mul(s, v.x-cx, v.y-cy))
  257. end
  258. self._radius = self._radius * s
  259. end
  260. -- triangulation by the method of kong
  261. function Polygon:triangulate()
  262. if #self.vertices == 3 then return {self:clone()} end
  263. local vertices = self.vertices
  264. local next_idx, prev_idx = {}, {}
  265. for i = 1,#vertices do
  266. next_idx[i], prev_idx[i] = i+1,i-1
  267. end
  268. next_idx[#next_idx], prev_idx[1] = 1, #prev_idx
  269. local concave = {}
  270. for i, v in ipairs(vertices) do
  271. if not ccw(vertices[prev_idx[i]], v, vertices[next_idx[i]]) then
  272. concave[v] = true
  273. end
  274. end
  275. local triangles = {}
  276. local n_vert, current, skipped, next, prev = #vertices, 1, 0
  277. while n_vert > 3 do
  278. next, prev = next_idx[current], prev_idx[current]
  279. local p,q,r = vertices[prev], vertices[current], vertices[next]
  280. if isEar(p,q,r, concave) then
  281. triangles[#triangles+1] = newPolygon(p.x,p.y, q.x,q.y, r.x,r.y)
  282. next_idx[prev], prev_idx[next] = next, prev
  283. concave[q] = nil
  284. n_vert, skipped = n_vert - 1, 0
  285. else
  286. skipped = skipped + 1
  287. assert(skipped <= n_vert, "Cannot triangulate polygon")
  288. end
  289. current = next
  290. end
  291. next, prev = next_idx[current], prev_idx[current]
  292. local p,q,r = vertices[prev], vertices[current], vertices[next]
  293. triangles[#triangles+1] = newPolygon(p.x,p.y, q.x,q.y, r.x,r.y)
  294. return triangles
  295. end
  296. -- return merged polygon if possible or nil otherwise
  297. function Polygon:mergedWith(other)
  298. local p,q = getSharedEdge(self.vertices, other.vertices)
  299. assert(p and q, "Polygons do not share an edge")
  300. local ret = {}
  301. for i = 1,p-1 do
  302. ret[#ret+1] = self.vertices[i].x
  303. ret[#ret+1] = self.vertices[i].y
  304. end
  305. for i = 0,#other.vertices-2 do
  306. i = ((i-1 + q) % #other.vertices) + 1
  307. ret[#ret+1] = other.vertices[i].x
  308. ret[#ret+1] = other.vertices[i].y
  309. end
  310. for i = p+1,#self.vertices do
  311. ret[#ret+1] = self.vertices[i].x
  312. ret[#ret+1] = self.vertices[i].y
  313. end
  314. return newPolygon(unpack(ret))
  315. end
  316. -- split polygon into convex polygons.
  317. -- note that this won't be the optimal split in most cases, as
  318. -- finding the optimal split is a really hard problem.
  319. -- the method is to first triangulate and then greedily merge
  320. -- the triangles.
  321. function Polygon:splitConvex()
  322. -- edge case: polygon is a triangle or already convex
  323. if #self.vertices <= 3 or self:isConvex() then return {self:clone()} end
  324. local convex = self:triangulate()
  325. local i = 1
  326. repeat
  327. local p = convex[i]
  328. local k = i + 1
  329. while k <= #convex do
  330. local success, merged = pcall(function() return p:mergedWith(convex[k]) end)
  331. if success and merged:isConvex() then
  332. convex[i] = merged
  333. p = convex[i]
  334. table.remove(convex, k)
  335. else
  336. k = k + 1
  337. end
  338. end
  339. i = i + 1
  340. until i >= #convex
  341. return convex
  342. end
  343. function Polygon:contains(x,y)
  344. -- test if an edge cuts the ray
  345. local function cut_ray(p,q)
  346. return ((p.y > y and q.y < y) or (p.y < y and q.y > y)) -- possible cut
  347. and (x - p.x < (y - p.y) * (q.x - p.x) / (q.y - p.y)) -- x < cut.x
  348. end
  349. -- test if the ray crosses boundary from interior to exterior.
  350. -- this is needed due to edge cases, when the ray passes through
  351. -- polygon corners
  352. local function cross_boundary(p,q)
  353. return (p.y == y and p.x > x and q.y < y)
  354. or (q.y == y and q.x > x and p.y < y)
  355. end
  356. local v = self.vertices
  357. local in_polygon = false
  358. local p,q = v[#v],v[#v]
  359. for i = 1, #v do
  360. p,q = q,v[i]
  361. if cut_ray(p,q) or cross_boundary(p,q) then
  362. in_polygon = not in_polygon
  363. end
  364. end
  365. return in_polygon
  366. end
  367. function Polygon:intersectionsWithRay(x,y, dx,dy)
  368. local nx,ny = vector.perpendicular(dx,dy)
  369. local wx,xy,det
  370. local ts = {} -- ray parameters of each intersection
  371. local q1,q2 = nil, self.vertices[#self.vertices]
  372. for i = 1, #self.vertices do
  373. q1,q2 = q2,self.vertices[i]
  374. wx,wy = q2.x - q1.x, q2.y - q1.y
  375. det = vector.det(dx,dy, wx,wy)
  376. if det ~= 0 then
  377. -- there is an intersection point. check if it lies on both
  378. -- the ray and the segment.
  379. local rx,ry = q2.x - x, q2.y - y
  380. local l = vector.det(rx,ry, wx,wy) / det
  381. local m = vector.det(dx,dy, rx,ry) / det
  382. if m >= 0 and m <= 1 then
  383. -- we cannot jump out early here (i.e. when l > tmin) because
  384. -- the polygon might be concave
  385. ts[#ts+1] = l
  386. end
  387. else
  388. -- lines parralel or incident. get distance of line to
  389. -- anchor point. if they are incident, check if an endpoint
  390. -- lies on the ray
  391. local dist = vector.dot(q1.x-x,q1.y-y, nx,ny)
  392. if dist == 0 then
  393. local l = vector.dot(dx,dy, q1.x-x,q1.y-y)
  394. local m = vector.dot(dx,dy, q2.x-x,q2.y-y)
  395. if l >= m then
  396. ts[#ts+1] = l
  397. else
  398. ts[#ts+1] = m
  399. end
  400. end
  401. end
  402. end
  403. return ts
  404. end
  405. function Polygon:intersectsRay(x,y, dx,dy)
  406. local tmin = math.huge
  407. for _, t in ipairs(self:intersectionsWithRay(x,y,dx,dy)) do
  408. tmin = math.min(tmin, t)
  409. end
  410. return tmin ~= math.huge, tmin
  411. end
  412. Polygon = common_local.class('Polygon', Polygon)
  413. newPolygon = function(...) return common_local.instance(Polygon, ...) end
  414. return Polygon