delaunay.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. #!/usr/bin/env lua
  2. ---------------
  3. -- ## Delaunay, Lua module for convex polygon triangulation
  4. -- @author Roland Yonaba
  5. -- @copyright 2013-2016
  6. -- @license MIT
  7. -- @script delaunay
  8. -- ================
  9. -- Private helpers
  10. -- ================
  11. local setmetatable = setmetatable
  12. local tostring = tostring
  13. local assert = assert
  14. local unpack = unpack or table.unpack
  15. local remove = table.remove
  16. local sqrt = math.sqrt
  17. local max = math.max
  18. -- Internal class constructor
  19. local class = function(...)
  20. local klass = {}
  21. klass.__index = klass
  22. klass.__call = function(_,...) return klass:new(...) end
  23. function klass:new(...)
  24. local instance = setmetatable({}, klass)
  25. klass.__init(instance, ...)
  26. return instance
  27. end
  28. return setmetatable(klass,{__call = klass.__call})
  29. end
  30. -- Triangle semi-perimeter by Heron's formula
  31. local function quatCross(a, b, c)
  32. local p = (a + b + c) * (a + b - c) * (a - b + c) * (-a + b + c)
  33. return sqrt(p)
  34. end
  35. -- Cross product (p1-p2, p2-p3)
  36. local function crossProduct(p1, p2, p3)
  37. local x1, x2 = p2.x - p1.x, p3.x - p2.x
  38. local y1, y2 = p2.y - p1.y, p3.y - p2.y
  39. return x1 * y2 - y1 * x2
  40. end
  41. -- Checks if angle (p1-p2-p3) is flat
  42. local function isFlatAngle(p1, p2, p3)
  43. return (crossProduct(p1, p2, p3) == 0)
  44. end
  45. -- ================
  46. -- Module classes
  47. -- ================
  48. --- `Edge` class
  49. -- @type Edge
  50. local Edge = class()
  51. Edge.__eq = function(a, b) return (a.p1 == b.p1 and a.p2 == b.p2) or (a.p1 == b.p2 and a.p2 == b.p1) end
  52. Edge.__tostring = function(e)
  53. return (('Edge :\n %s\n %s'):format(tostring(e.p1), tostring(e.p2)))
  54. end
  55. --- Creates a new `Edge`
  56. -- @name Edge:new
  57. -- @param p1 a `Point`
  58. -- @param p2 a `Point`
  59. -- @return a new `Edge`
  60. -- @usage
  61. -- local Delaunay = require 'Delaunay'
  62. -- local Edge = Delaunay.Edge
  63. -- local Point = Delaunay.Point
  64. -- local e = Edge:new(Point(1,1), Point(2,5))
  65. -- local e = Edge(Point(1,1), Point(2,5)) -- Alias to Edge.new
  66. -- print(e) -- print the edge members p1 and p2
  67. --
  68. function Edge:__init(p1, p2)
  69. self.p1, self.p2 = p1, p2
  70. end
  71. --- Test if `otherEdge` is similar to self. It does not take into account the direction.
  72. -- @param otherEdge an `Edge`
  73. -- @return `true` or `false`
  74. -- @usage
  75. -- local e1 = Edge(Point(1,1), Point(2,5))
  76. -- local e2 = Edge(Point(2,5), Point(1,1))
  77. -- print(e1:same(e2)) --> true
  78. -- print(e1 == e2)) --> false, == operator considers the direction
  79. --
  80. function Edge:same(otherEdge)
  81. return ((self.p1 == otherEdge.p1) and (self.p2 == otherEdge.p2))
  82. or ((self.p1 == otherEdge.p2) and (self.p2 == otherEdge.p1))
  83. end
  84. --- Returns the length.
  85. -- @return the length of self
  86. -- @usage
  87. -- local e = Edge(Point(), Point(10,0))
  88. -- print(e:length()) --> 10
  89. --
  90. function Edge:length()
  91. return self.p1:dist(self.p2)
  92. end
  93. --- Returns the midpoint coordinates.
  94. -- @return the x-coordinate of self midpoint
  95. -- @return the y-coordinate of self midpoint
  96. -- @usage
  97. -- local e = Edge(Point(), Point(10,0))
  98. -- print(e:getMidPoint()) --> 5, 0
  99. --
  100. function Edge:getMidPoint()
  101. local x = self.p1.x + (self.p2.x - self.p1.x) / 2
  102. local y = self.p1.y + (self.p2.y - self.p1.y) / 2
  103. return x, y
  104. end
  105. --- Point class
  106. -- @type Point
  107. local Point = class()
  108. Point.__eq = function(a,b) return (a.x == b.x and a.y == b.y) end
  109. Point.__tostring = function(p)
  110. return ('Point (%s) x: %.2f y: %.2f'):format(p.id, p.x, p.y)
  111. end
  112. --- Creates a new `Point`
  113. -- @name Point:new
  114. -- @param x the x-coordinate
  115. -- @param y the y-coordinate
  116. -- @return a new `Point`
  117. -- @usage
  118. -- local Delaunay = require 'Delaunay'
  119. -- local Point = Delaunay.Point
  120. -- local p = Point:new(1,1)
  121. -- local p = Point(1,1) -- Alias to Point.new
  122. -- print(p) -- print the point members x and y
  123. --
  124. function Point:__init(x, y)
  125. self.x, self.y, self.id = x or 0, y or 0, '?'
  126. end
  127. --- Returns the square distance to another `Point`.
  128. -- @param p a `Point`
  129. -- @return the square distance from self to `p`.
  130. -- @usage
  131. -- local p1, p2 = Point(), Point(1,1)
  132. -- print(p1:dist2(p2)) --> 2
  133. --
  134. function Point:dist2(p)
  135. local dx, dy = (self.x - p.x), (self.y - p.y)
  136. return dx * dx + dy * dy
  137. end
  138. --- Returns the distance to another `Point`.
  139. -- @param p a `Point`
  140. -- @return the distance from self to `p`.
  141. -- @usage
  142. -- local p1, p2 = Point(), Point(1,1)
  143. -- print(p1:dist2(p2)) --> 1.4142135623731
  144. --
  145. function Point:dist(p)
  146. return sqrt(self:dist2(p))
  147. end
  148. --- Checks if self lies into the bounds of a circle
  149. -- @param cx the x-coordinate of the circle center
  150. -- @param cy the y-coordinate of the circle center
  151. -- @param r the radius of the circle
  152. -- @return `true` or `false`
  153. -- @usage
  154. -- local p = Point()
  155. -- print(p:isInCircle(0,0,1)) --> true
  156. --
  157. function Point:isInCircle(cx, cy, r)
  158. local dx = (cx - self.x)
  159. local dy = (cy - self.y)
  160. return ((dx * dx + dy * dy) <= (r * r))
  161. end
  162. --- `Triangle` class
  163. -- @type Triangle
  164. local Triangle = class()
  165. Triangle.__tostring = function(t)
  166. return (('Triangle: \n %s\n %s\n %s')
  167. :format(tostring(t.p1), tostring(t.p2), tostring(t.p3)))
  168. end
  169. --- Creates a new `Triangle`
  170. -- @name Triangle:new
  171. -- @param p1 a `Point`
  172. -- @param p2 a `Point`
  173. -- @param p3 a `Point`
  174. -- @return a new `Triangle`
  175. -- @usage
  176. -- local Delaunay = require 'Delaunay'
  177. -- local Triangle = Delaunay.Triangle
  178. -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1)
  179. -- local t = Triangle:new(p1, p2, p3)
  180. -- local t = Triangle(p1, p2, p3) -- Alias to Triangle.new
  181. -- print(t) -- print the triangle members p1, p2 and p3
  182. --
  183. function Triangle:__init(p1, p2, p3)
  184. assert(not isFlatAngle(p1, p2, p3), ("angle (p1, p2, p3) is flat:\n %s\n %s\n %s")
  185. :format(tostring(p1), tostring(p2), tostring(p3)))
  186. self.p1, self.p2, self.p3 = p1, p2, p3
  187. self.e1, self.e2, self.e3 = Edge(p1, p2), Edge(p2, p3), Edge(p3, p1)
  188. end
  189. --- Checks if the triangle is defined clockwise (sequence p1-p2-p3)
  190. -- @return `true` or `false`
  191. -- @usage
  192. -- local p1, p2, p3 = Point(), Point(1,1), Point(2,0)
  193. -- local t = Triangle(p1, p2, p3)
  194. -- print(t:isCW()) --> true
  195. --
  196. function Triangle:isCW()
  197. return (crossProduct(self.p1, self.p2, self.p3) < 0)
  198. end
  199. --- Checks if the triangle is defined counter-clockwise (sequence p1-p2-p3)
  200. -- @return `true` or `false`
  201. -- @usage
  202. -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1)
  203. -- local t = Triangle(p1, p2, p3)
  204. -- print(t:isCCW()) --> true
  205. --
  206. function Triangle:isCCW()
  207. return (crossProduct(self.p1, self.p2, self.p3) > 0)
  208. end
  209. --- Returns the length of the edges
  210. -- @return the length of the edge p1-p2
  211. -- @return the length of the edge p2-p3
  212. -- @return the length of the edge p3-p1
  213. -- @usage
  214. -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1)
  215. -- local t = Triangle(p1, p2, p3)
  216. -- print(t:getSidesLength()) --> 2 1.4142135623731 1.4142135623731
  217. --
  218. function Triangle:getSidesLength()
  219. return self.e1:length(), self.e2:length(), self.e3:length()
  220. end
  221. --- Returns the coordinates of the center
  222. -- @return the x-coordinate of the center
  223. -- @return the y-coordinate of the center
  224. -- @usage
  225. -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1)
  226. -- local t = Triangle(p1, p2, p3)
  227. -- print(t:getCenter()) --> 1 0.33333333333333
  228. --
  229. function Triangle:getCenter()
  230. local x = (self.p1.x + self.p2.x + self.p3.x) / 3
  231. local y = (self.p1.y + self.p2.y + self.p3.y) / 3
  232. return x, y
  233. end
  234. --- Returns the coordinates of the circumcircle center and its radius
  235. -- @return the x-coordinate of the circumcircle center
  236. -- @return the y-coordinate of the circumcircle center
  237. -- @return the radius of the circumcircle
  238. -- @usage
  239. -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1)
  240. -- local t = Triangle(p1, p2, p3)
  241. -- print(t:getCircumCircle()) --> 1 0 1
  242. --
  243. function Triangle:getCircumCircle()
  244. local x, y = self:getCircumCenter()
  245. local r = self:getCircumRadius()
  246. return x, y, r
  247. end
  248. --- Returns the coordinates of the circumcircle center
  249. -- @return the x-coordinate of the circumcircle center
  250. -- @return the y-coordinate of the circumcircle center
  251. -- @usage
  252. -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1)
  253. -- local t = Triangle(p1, p2, p3)
  254. -- print(t:getCircumCenter()) --> 1 0
  255. --
  256. function Triangle:getCircumCenter()
  257. local p1, p2, p3 = self.p1, self.p2, self.p3
  258. local D = ( p1.x * (p2.y - p3.y) +
  259. p2.x * (p3.y - p1.y) +
  260. p3.x * (p1.y - p2.y)) * 2
  261. local x = (( p1.x * p1.x + p1.y * p1.y) * (p2.y - p3.y) +
  262. ( p2.x * p2.x + p2.y * p2.y) * (p3.y - p1.y) +
  263. ( p3.x * p3.x + p3.y * p3.y) * (p1.y - p2.y))
  264. local y = (( p1.x * p1.x + p1.y * p1.y) * (p3.x - p2.x) +
  265. ( p2.x * p2.x + p2.y * p2.y) * (p1.x - p3.x) +
  266. ( p3.x * p3.x + p3.y * p3.y) * (p2.x - p1.x))
  267. return (x / D), (y / D)
  268. end
  269. --- Returns the radius of the circumcircle
  270. -- @return the radius of the circumcircle
  271. -- @usage
  272. -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1)
  273. -- local t = Triangle(p1, p2, p3)
  274. -- print(t:getCircumRadius()) --> 1
  275. --
  276. function Triangle:getCircumRadius()
  277. local a, b, c = self:getSidesLength()
  278. return ((a * b * c) / quatCross(a, b, c))
  279. end
  280. --- Returns the area
  281. -- @return the area
  282. -- @usage
  283. -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1)
  284. -- local t = Triangle(p1, p2, p3)
  285. -- print(t:getArea()) --> 1
  286. --
  287. function Triangle:getArea()
  288. local a, b, c = self:getSidesLength()
  289. return (quatCross(a, b, c) / 4)
  290. end
  291. --- Checks if a given point lies into the triangle circumcircle
  292. -- @param p a `Point`
  293. -- @return `true` or `false`
  294. -- @usage
  295. -- local p1, p2, p3 = Point(), Point(2,0), Point(1,1)
  296. -- local t = Triangle(p1, p2, p3)
  297. -- print(t:inCircumCircle(Point(1,-1))) --> true
  298. --
  299. function Triangle:inCircumCircle(p)
  300. return p:isInCircle(self:getCircumCircle())
  301. end
  302. --- Delaunay module
  303. -- @section public
  304. --- Delaunay module
  305. -- @table Delaunay
  306. -- @field Point reference to the `Point` class
  307. -- @field Edge reference to the `Edge` class
  308. -- @field Triangle reference to the `Triangle` class
  309. -- @field convexMultiplier multiplier heuristic for bounding triangle calculation. When small (~1) produces convex-hull, when large, produces concave hulls. Defaults to 1000.
  310. -- @field _VERSION the version of the current module
  311. local Delaunay = {
  312. Point = Point,
  313. Edge = Edge,
  314. Triangle = Triangle,
  315. convexMultiplier = 1e3,
  316. _VERSION = "0.1"
  317. }
  318. --- Triangulates a set of given vertices
  319. -- @param ... a `vargarg` list of objects of type `Point`
  320. -- @return a set of objects of type `Triangle`
  321. -- @usage
  322. -- local Delaunay = require 'Delaunay'
  323. -- local Point = Delaunay.Point
  324. -- local p1, p2, p3, p4 = Point(), Point(2,0), Point(1,1), Point(1,-1)
  325. -- local triangles = Delaunay.triangulate(p1, p2, p3, p4)
  326. -- for i = 1, #triangles do
  327. -- print(triangles[i])
  328. -- end
  329. --
  330. function Delaunay.triangulate(...)
  331. local vertices = {...}
  332. local nvertices = #vertices
  333. assert(nvertices > 2, "Cannot triangulate, needs more than 3 vertices")
  334. if nvertices == 3 then
  335. return {Triangle(unpack(vertices))}
  336. end
  337. local trmax = nvertices * 4
  338. local minX, minY = vertices[1].x, vertices[1].y
  339. local maxX, maxY = minX, minY
  340. for i = 1, #vertices do
  341. local vertex = vertices[i]
  342. vertex.id = i
  343. if vertex.x < minX then minX = vertex.x end
  344. if vertex.y < minY then minY = vertex.y end
  345. if vertex.x > maxX then maxX = vertex.x end
  346. if vertex.y > maxY then maxY = vertex.y end
  347. end
  348. local convex_mult = Delaunay.convexMultiplier
  349. local dx, dy = (maxX - minX) * convex_mult, (maxY - minY) * convex_mult
  350. local deltaMax = max(dx, dy)
  351. local midx, midy = (minX + maxX) * 0.5, (minY + maxY) * 0.5
  352. local p1 = Point(midx - 2 * deltaMax, midy - deltaMax)
  353. local p2 = Point(midx, midy + 2 * deltaMax)
  354. local p3 = Point(midx + 2 * deltaMax, midy - deltaMax)
  355. p1.id, p2.id, p3.id = nvertices + 1, nvertices + 2, nvertices + 3
  356. vertices[p1.id] = p1
  357. vertices[p2.id] = p2
  358. vertices[p3.id] = p3
  359. local triangles = {}
  360. triangles[#triangles + 1] = Triangle(vertices[nvertices + 1],
  361. vertices[nvertices + 2],
  362. vertices[nvertices + 3]
  363. )
  364. for i = 1, nvertices do
  365. local edges = {}
  366. local ntriangles = #triangles
  367. for j = #triangles, 1, -1 do
  368. local curTriangle = triangles[j]
  369. if curTriangle:inCircumCircle(vertices[i]) then
  370. edges[#edges + 1] = curTriangle.e1
  371. edges[#edges + 1] = curTriangle.e2
  372. edges[#edges + 1] = curTriangle.e3
  373. remove(triangles, j)
  374. end
  375. end
  376. for j = #edges - 1, 1, -1 do
  377. for k = #edges, j + 1, -1 do
  378. if edges[j] and edges[k] and edges[j]:same(edges[k]) then
  379. remove(edges, j)
  380. remove(edges, k-1)
  381. end
  382. end
  383. end
  384. for j = 1, #edges do
  385. local n = #triangles
  386. assert(n <= trmax, "Generated more than needed triangles")
  387. triangles[n + 1] = Triangle(edges[j].p1, edges[j].p2, vertices[i])
  388. end
  389. end
  390. for i = #triangles, 1, -1 do
  391. local triangle = triangles[i]
  392. if (triangle.p1.id > nvertices or
  393. triangle.p2.id > nvertices or
  394. triangle.p3.id > nvertices) then
  395. remove(triangles, i)
  396. end
  397. end
  398. for _ = 1,3 do remove(vertices) end
  399. return triangles
  400. end
  401. return Delaunay