Guest User

Voronoi for Löve

a guest
Mar 19th, 2014
208
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 14.12 KB | None | 0 0
  1.  
  2.  
  3. -- Voronoi library, by zwzsg
  4.  
  5. -- Usage:
  6. -- local Voronoi=require("voronoi")-- Where "voronoi" is path and name to this file, with no .lua extension, and slashes replaced by dots
  7. -- Voronoi:CreateAll(xsize,ysize,mindist)
  8. -- Where xsize and ysize represents the dimension of the area to fill with voronoi
  9. -- And mindist is the minimum distance between voronoi centers
  10. -- If arguments are not supplied, then they use their previous value, unless it's the first time, in which case it errors out
  11.  
  12. -- Instead of CreateAll, you can do it step by step:
  13. -- Voronoi:CreateFresh(512,512,50)
  14. -- Voronoi:CreatePoints()
  15. -- Voronoi:CreateLines()
  16. -- Voronoi:CreateIntersections()
  17. -- Voronoi:CreatePolygons()
  18.  
  19. -- If you need more than one Voronoi, I think you can do it via:
  20. -- NewVoronoi=OldVoronoi:CreateFresh(nil,xsize,ysize,mindist)
  21. -- But I haven't tested yet!
  22.  
  23. -- The Voronoi table, is made of two parts:
  24. -- - The Voronoi library functions, in the hash part of the table
  25. -- - The voronoi points, in the array part of the table
  26. -- You can think of it as Voronoi={{x=x1,y=y1},{x=x2,y=y2},{x=x3,y=y3}...,CreateAll,...}
  27. -- So that by using ipairs on this table you will iterate on the points
  28. -- And yet with Voronoi:Funcname() you access the methods
  29.  
  30.  
  31. -- Voronoi:CreateFresh(xsize,ysize,mindist)
  32. -- Make it an empty table, with the methods, the sizes, the mindist, but no points
  33.  
  34.  
  35. -- Voronoi:CreatePoints()
  36. -- Seed with random center points
  37. -- We now have something like {{x=x1,x=y1},{x=x2,y=y2},{x=x3,y=y3}...,CreateAll,...}
  38. -- You can now access the coordinates of what will become voronoi centers with something like:
  39. -- for _,v in ipairs(Voronoi) do
  40. --   local x,y=v.x,v.y
  41. --   ...
  42. -- end
  43.  
  44.  
  45. -- Voronoi:CreateLines()
  46. -- Calculate the equation of the lines between voronois.
  47. -- It might create too much, that is, lines between voronois that are not touching.
  48. -- By definition, these are the lines equidistants to each pair of points.
  49. --
  50. -- We now have something like {{x=..,y=..},{x=..,y=..},{x=..,y=..}},
  51. --                             Lines={{a=..,b=..,c=..,between={{...},{...}}},{a=..,b=..,c=..,between={{...},{...}}},...},
  52. --                              ...,CreateAll,...}
  53. -- Where .. represents numbers and ... represents any number of extra things
  54. --
  55. -- Each entry in Lines is a line, being represented by the a,b,c of its parametric equation
  56. -- The "between" is a list of the two points that this line is bissecting
  57.  
  58. -- Voronoi:GetLineX(line,y)
  59. -- This function is to help you work with the parametric representation of lines
  60. -- It takes a line, as in, a table with {a=..,b=..,c=..}, and a number y, and outputs a number x, solution of a*x+b*y+c=0
  61. -- Oh, and if the line is horizontal, this will divide by zero!
  62.  
  63. -- Voronoi:GetLineY(line,x)
  64. -- This function is to help you work with the parametric representation of lines
  65. -- It takes a line, as in, a table with {a=..,b=..,c=..}, and a number x, and outputs a number y, solution of a*x+b*y+c=0
  66. -- Oh, and if the line is vertical, this will divide by zero!
  67.  
  68. -- Voronoi:CreateIntersections()
  69. -- Calculate the intersection of the lines separating voronois.
  70. -- There may be too many of them, and they aren't sorted, but some of them are actually coordinates of the Voronoi polygons!
  71. -- Each intersection has four fields: the x, the y, then a field called Lines that is a list of the two lines it is the intersection of,
  72. -- and a field called Centers that is a list of the three (but sometimes two) Centers it originate from.
  73.  
  74. -- Voronoi:CreatePolygons()
  75. -- Calculate the coordinates of the polygons of each voronoi.
  76. -- They are added to each voronoi as an array called c, that array contains a list of table, each table has a x and y.
  77. -- So we now have something like:
  78. -- {{['x']=..,['y']=..,['c']={{x=..,y=..},{x=..,y=..},{x=..,y=..},...},}
  79. --  {['x']=..,['y']=..,['c']={{x=..,y=..},{x=..,y=..},{x=..,y=..},...},['Lines']={{a=..,b=..,c=..,with=..,it={...}},{a=..,b=..,c=..,with=..,it={...}},...},['Intersections']={{x=..,y=..,lines={...}},{x=..,y=..,lines={...}},...}}
  80. --  Lines={{a=..,b=..,c=..,between={...}},{a=..,b=..,c=..,between={...}},...},
  81. --  Intersections={{x=..,y=..,Lines={...},Centers={...}},{x=..,y=..,Lines={...},Centers={...}},...}
  82. --  xsize=.., ysize=.., mindist=..,
  83. --  CreateFresh=...., CreatePoints=...., CreateLines=...., CreateIntersections=...., CreatePolygons=...., CreateAll=....,  GetLineX=...., GetLineY=.... }
  84. -- Where .. are numbers, ... are extra stuff, and .... are functions.
  85.  
  86.  
  87. local function CreatePoints(Voronoi)-- Require CreateFresh to have been run
  88.     assert(Voronoi.xsize>0 and Voronoi.ysize>0 and Voronoi.mindist>0,"Bad arguments, expected three stricly positive numbers")
  89.     local attempts=0
  90.     while attempts<50 do
  91.         attempts=attempts+1
  92.         local x=Voronoi.xsize*math.random()
  93.         local y=Voronoi.ysize*math.random()
  94.         local isAway=true
  95.         for _,p in ipairs(Voronoi) do
  96.             if (x-p.x)^2+(y-p.y)^2<Voronoi.mindist^2 then
  97.                 isAway=false
  98.             end
  99.         end
  100.         if isAway then
  101.             table.insert(Voronoi,{x=x,y=y})
  102.             attempts=0
  103.         end
  104.     end
  105. end
  106.  
  107. local function AssignTable(destination,source)
  108.     for k,_ in pairs(destination) do
  109.         destination[k]=nil
  110.     end
  111.     for k,v in pairs(source) do
  112.         destination[k]=v
  113.     end
  114.     return destination
  115. end
  116.  
  117. local function FillPolygonBoundingRect(poly)
  118.     local xmin,ymin,xmax,ymax=math.huge,math.huge,-math.huge,-math.huge
  119.     for i=#poly.c,1,-1 do
  120.         xmin=math.min(xmin,poly.c[i].x)
  121.         xmax=math.max(xmax,poly.c[i].x)
  122.         ymin=math.min(ymin,poly.c[i].y)
  123.         ymax=math.max(ymax,poly.c[i].y)
  124.     end
  125.     poly.xmin=xmin
  126.     poly.xmax=xmax
  127.     poly.ymin=ymin
  128.     poly.ymax=ymax
  129. end
  130.  
  131. -- To know if a point is inside a polygon, we count the number of intersection between
  132. -- the segments and a ray going from the point, to the outside, let's say to the right.
  133. -- If that number is even, it's outside, if that number is odd, it's inside.
  134. local function IsInsidePolygon(poly,x,y)-- From http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/
  135.     if x<poly.xmin or x>poly.xmax or y<poly.ymin or x>poly.ymax then -- Speedup
  136.         return false
  137.     end
  138.     local c=poly.c
  139.     local inside=false
  140.     local j=#c
  141.     for i=1,#c do
  142.         if y>=math.min(c[i].y,c[j].y) and y<math.max(c[i].y,c[j].y) and
  143.             x<(c[j].x-c[i].x)*(y-c[i].y)/(c[j].y-c[i].y)+c[i].x then
  144.             inside=not inside
  145.         end
  146.         j=i
  147.     end
  148.     return inside
  149. end
  150.  
  151. local function IsInsidePointList(c,x,y)
  152.     local inside=false
  153.     local j=#c
  154.     for i=1,#c do
  155.         if y>=math.min(c[i].y,c[j].y) and y<math.max(c[i].y,c[j].y) and
  156.             x<(c[j].x-c[i].x)*(y-c[i].y)/(c[j].y-c[i].y)+c[i].x then
  157.             inside=not inside
  158.         end
  159.         j=i
  160.     end
  161.     return inside
  162. end
  163.  
  164.  
  165. local function GetLineY(line,x)
  166.     -- a*x+b*y+c=0  =>  b*y=-a*x-c => y=(-a*x-c)/b
  167.     return (-line.a*x-line.c)/line.b
  168. end
  169.  
  170. local function GetLineX(line,y)
  171.     -- a*x+b*y+c=0  =>  a*x=-b*y-c => x=(-b*y-c)/a
  172.     return (-line.b*y-line.c)/line.a
  173. end
  174.  
  175. local function GetIntersection(line1,line2)
  176.     -- a1*x+b1*y+c1=0 and a2*x+b2*y+c2=0
  177.     -- => a1*x+b1*y+c1 = a2*x+b2*y+c2
  178.     -- => (a2-a1)*x+(b2-b1)*y+(c2-c1) = 0
  179.     -- =>  (b2-b1)*y+(c2-c1) = -(a2-a1)*x
  180.     -- =>  (b2-b1)*y+(c2-c1) = (a1-a2)*x
  181.     -- =>  (a1-a2)*x = (b2-b1)*y+(c2-c1)
  182.     -- =>  x = ((b2-b1)*y+(c2-c1))/(a1-a2)
  183.     -- and then replace x by that in one the initial equation
  184.     -- meh, too complicated, let's restart and use another way
  185.     --
  186.     -- a1*x+b1*y+c1=0 and a2*x+b2*y+c2=0
  187.     -- Let's the first by b2 and the second by b1
  188.     -- => b2*a1*x+b2*b1*y+b2*c1=0 and b1*a2*x+b1*b2*y+b1*c2=0
  189.     -- Let's substract the second to the first
  190.     -- => (b2*a1-b1*a2)*x+(b2*b1-b1*b2)*y+(b2*c1-b1*c2)=0
  191.     -- => (b2*a1-b1*a2)*x=(b1*c2-b2*c1)
  192.     -- => x=(b1*c2-b2*c1)/(b2*a1-b1*a2)
  193.     --
  194.     -- And for y:
  195.     -- a1*x+b1*y+c1=0 and a2*x+b2*y+c2=0
  196.     -- Let's the first by a2 and the second by a1
  197.     -- => a2*a1*x+a2*b1*y+a2*c1=0 and a1*a2*x+a1*b2*y+a1*c2=0
  198.     -- Let's substract the second to the first
  199.     -- => (a2*a1-a1*a2)*x+(a2*b1-a1*b2)*y+(a2*c1-a1*c2)=0
  200.     -- => (a2*b1-a1*b2)*y=(a1*c2-a2*c1)
  201.     -- => y=(a1*c2-a2*c1)/(a2*b1-a1*b2)
  202.     --
  203.     -- So:
  204.     -- x=(b1*c2-b2*c1)/(b2*a1-b1*a2)
  205.     -- y=(a2*c1-a1*c2)/(b2*a1-b1*a2)
  206.  
  207.     local bottom=(line2.b*line1.a-line1.b*line2.a)-- The (b2*a1-b1*a2) part
  208.     if bottom==0 then
  209.         -- Oops! Division by zero! Let's hope it doesn't happen
  210.         -- This is the case of parallel lines btw
  211.         return nil
  212.     end
  213.  
  214.     return (line1.b*line2.c-line2.b*line1.c)/bottom,(line2.a*line1.c-line1.a*line2.c)/bottom
  215. end
  216.  
  217. local function GetInterAsTable(line1,line2)
  218.     local bottom=(line2.b*line1.a-line1.b*line2.a)
  219.     if bottom==0 then
  220.         error("Parallel lines!")
  221.     end
  222.     return {x=(line1.b*line2.c-line2.b*line1.c)/bottom,y=(line2.a*line1.c-line1.a*line2.c)/bottom,lines={line1,line2}}
  223. end
  224.  
  225. local function CreateLines(Voronoi)-- Requires CreateFresh to have been run!
  226.     local xsize,ysize=Voronoi.xsize,Voronoi.ysize
  227.     Voronoi.Lines={}
  228.     for ka=2,#Voronoi do
  229.         for kb=1,ka-1 do
  230.             local pa,pb=Voronoi[ka],Voronoi[kb]
  231.             local ax,ay,bx,by=pa.x,pa.y,pb.x,pb.y
  232.             local xs,ys={},{}
  233.             if bx-ax>xsize/2 then
  234.                 xs={{ax=ax,bx=bx-xsize,ha=true},{ax=ax+xsize,bx=bx,hb=true}}
  235.             elseif bx-ax<-xsize/2 then
  236.                 xs={{ax=ax,bx=bx+xsize,ha=true},{ax=ax-xsize,bx=bx,hb=true}}
  237.             else
  238.                 xs={{ax=ax,bx=bx,ha=true,hb=true}}
  239.             end
  240.             if by-ay>ysize/2 then
  241.                 ys={{ay=ay,by=by-ysize,ha=true},{ay=ay+ysize,by=by,hb=true}}
  242.             elseif by-ay<-ysize/2 then
  243.                 ys={{ay=ay,by=by+ysize,ha=true},{ay=ay-ysize,by=by,hb=true}}
  244.             else
  245.                 ys={{ay=ay,by=by,ha=true,hb=true}}
  246.             end
  247.             for _,xe in ipairs(xs) do
  248.                 local ax,bx=xe.ax,xe.bx
  249.                 for _,ye in ipairs(ys) do
  250.                     local ay,by=ye.ay,ye.by
  251.                     local ha=xe.ha and ye.ha
  252.                     local hb=xe.hb and ye.hb
  253.                     if ha or hb then
  254.                         local a=ax-bx
  255.                         local b=ay-by
  256.                         local c=-a*(ax+bx)/2-b*(ay+by)/2
  257.                         table.insert(Voronoi.Lines,{a=a,b=b,c=c,between={pa,pb},ha=ha,hb=hb})
  258.                     end
  259.                 end
  260.             end
  261.         end
  262.     end
  263.     -- Sort the lines by order of distance between the two points they're bisecting
  264.     table.sort(Voronoi.Lines,
  265.         function(la,lb)
  266.             return (la.between[2].x-la.between[1].x)^2+(la.between[2].y-la.between[1].y)^2<(lb.between[2].x-lb.between[1].x)^2+(lb.between[1].y-lb.between[1].y)^2
  267.         end)
  268. end
  269.  
  270. local function CreateIntersections(Voronoi)-- Requires CreateLines to have been run!
  271.     Voronoi.Intersections={}
  272.     for kla=2,#Voronoi.Lines do
  273.         for klb=1,kla-1 do
  274.             local la,lb=Voronoi.Lines[kla],Voronoi.Lines[klb]
  275.             --local pa1,pa2=la.ha and la.between[1],la.hb and la.between[2]
  276.             --local pb1,pb2=lb.ha and lb.between[1],lb.hb and lb.between[2]
  277.             local pa1,pa2=la.between[1],la.between[2]
  278.             local pb1,pb2=lb.between[1],lb.between[2]
  279.             -- Only consider intersection if the two lines have in common one of the two points they're bisecting from
  280.             if (pa1 and (pa1==pb1 or pa1==pb2)) or (pa2 and (pa2==pb1 or pa2==pb2)) then
  281.                 -- Discard line intersecting with its doppledanger
  282.                 if pa1~=pb1 or pa2~=pb2 then
  283.                     local x,y=GetIntersection(la,lb)
  284.                     if x and y then
  285.                         local Centers={}
  286.                         if pa1 then
  287.                             table.insert(Centers,pa1)
  288.                         end
  289.                         if pa2 then
  290.                             table.insert(Centers,pa2)
  291.                         end
  292.                         if pb1 and pb1~=pa1 and pb1~=pa2 then
  293.                             table.insert(Centers,pb1)
  294.                         end
  295.                         if pb2 and pb2~=pa1 and pb2~=pa2 then
  296.                             table.insert(Centers,pb2)
  297.                         end
  298.                         --assert(#Centers==3,"Bad Centers count "..#Centers)
  299.                         if #Centers==3 then
  300.                             table.insert(Voronoi.Intersections,{x=x,y=y,Lines={Voronoi.Lines[kla],Voronoi.Lines[klb]},Centers=Centers})
  301.                         end
  302.                     end
  303.                 end
  304.             end
  305.         end
  306.     end
  307. end
  308.  
  309.  
  310. local function CreatePolygons(Voronoi)-- Requires CreateIntersections to have been run!
  311.     local epsilon=0.001
  312.     -- Helper function to calculate a distance in a wraparound zone
  313.     -- the square root is left out because results are only compared between each others
  314.     local xsize,ysize=Voronoi.xsize,Voronoi.ysize
  315.     local function SquaredGetDistance(p1,p2)
  316.         local dx=(p1.x-p2.x)%xsize
  317.         local dy=(p1.y-p2.y)%ysize
  318.         if dx>xsize/2 then
  319.             dx=xsize-dx
  320.         end
  321.         if dy>ysize/2 then
  322.             dy=ysize-dy
  323.         end
  324.         return dx*dx+dy*dy
  325.     end
  326.     -- Initialise corner list to empty for all voronois
  327.     for _,p in ipairs(Voronoi) do
  328.         p.c={}
  329.     end
  330.     -- For all intersections
  331.     for _,it in ipairs(Voronoi.Intersections) do
  332.         -- For all three voronoi center points that intersection is about
  333.         assert(#it.Centers==3,"Bad center count "..#it.Centers)
  334.         for _,pa in ipairs(it.Centers) do
  335.  
  336.             -- Both lines of the intersection must be about that voronoi center point
  337.             local good=true
  338.             for _,line in ipairs(it.Lines) do
  339.                 if line.between[1]~=pa and line.between[2]~=pa then
  340.                     good=false
  341.                 end
  342.             end
  343.             if good then
  344.                 -- Disregard intersection happening too far from the voronoi center point
  345.                 if math.abs(it.x-pa.x)<xsize/2 and math.abs(it.y-pa.y)<ysize/2 then
  346.                     local da=SquaredGetDistance(pa,it)
  347.                     -- For all other voronoi center points
  348.                     local bd=math.huge
  349.                     for _,pb in ipairs(Voronoi) do
  350.                         if pb~=pa then
  351.                             local d=SquaredGetDistance(pb,it)
  352.                             if d<bd then
  353.                                 -- Remember only the closest distance
  354.                                 bd=d
  355.                             end
  356.                         end
  357.                     end
  358.                     -- If the closest other is as close as me, then it's a corner
  359.                     if math.abs(bd-da)<epsilon then
  360.                         table.insert(pa.c,it)
  361.                     end
  362.                 end
  363.             end
  364.         end
  365.     end
  366.     -- Corners have been written in disorder, order them by angle
  367.     for _,p in ipairs(Voronoi) do
  368.         table.sort(p.c,function(c1,c2)
  369.                 return math.atan2(c1.y-p.y,c1.x-p.x)<math.atan2(c2.y-p.y,c2.x-p.x)
  370.             end)
  371.     end
  372.     -- Maybe here we should also remove double corners, in case these can happen
  373. end
  374.  
  375. local function CreateAll(Voronoi,xsize,ysize,mindist)
  376.     Voronoi:CreateFresh(xsize or Voronoi.xsize,ysize or Voronoi.ysize,mindist or Voronoi.mindist)
  377.     Voronoi:CreatePoints()
  378.     Voronoi:CreateLines()
  379.     Voronoi:CreateIntersections()
  380.     Voronoi:CreatePolygons()
  381. end
  382.  
  383. -- Arguments: Self, the two dimensions of the area, and the minimum distance between points
  384. local function CreateFresh(Voronoi,xsize,ysize,mindist)
  385.     Voronoi=AssignTable(Voronoi or {},
  386.         {xsize=xsize, ysize=ysize, mindist=mindist,
  387.             CreateFresh=CreateFresh,
  388.             CreatePoints=CreatePoints,
  389.             CreateLines=CreateLines,
  390.             CreateIntersections=CreateIntersections,
  391.             CreatePolygons=CreatePolygons,
  392.             CreateAll=CreateAll,
  393.             GetLineX=GetLineX,
  394.             GetLineY=GetLineY,
  395.         })
  396.     return Voronoi
  397. end
  398.  
  399.  
  400. return CreateFresh()
Advertisement
Add Comment
Please, Sign In to add comment