Compute discrete Voronoi cell

This commit is contained in:
Ian Jauslin 2024-02-21 19:12:07 -05:00
parent 0070094b94
commit fd61a4620f
5 changed files with 118 additions and 5 deletions

View File

@ -345,6 +345,9 @@ class Command_prompt(Label):
elif argv[1]=="grid": elif argv[1]=="grid":
self.run_set_grid(argv) self.run_set_grid(argv)
return return
elif argv[1]=="voronoi":
self.run_set_voronoi(argv)
return
elif argv[1]=="zoom": elif argv[1]=="zoom":
self.run_set_zoom(argv) self.run_set_zoom(argv)
else: else:
@ -409,6 +412,19 @@ class Command_prompt(Label):
return return
self.app.painter.set_grid(mesh) self.app.painter.set_grid(mesh)
# toggle Voronoi cells
def run_set_voronoi(self,argv):
if len(argv)==2:
# no argument: set to toggle
self.app.painter.set_voronoi(-1)
elif argv[2]=="on":
self.app.painter.set_voronoi(1)
elif argv[2]=="off":
self.app.painter.set_voronoi(0)
else:
self.message="error: unrecognized argument '"+argv[2]+"' -- usage 'set voronoi [on|off]'"
return
# set zoom level (changes size of elements) # set zoom level (changes size of elements)
def run_set_zoom(self,argv): def run_set_zoom(self,argv):
if len(argv)==2: if len(argv)==2:

View File

@ -95,8 +95,20 @@ class Element_square(Element):
*(coordx-0.5*self.size*painter.base_size,coordy-0.5*self.size*self.aspect*painter.base_size) *(coordx-0.5*self.size*painter.base_size,coordy-0.5*self.size*self.aspect*painter.base_size)
)) ))
# for use with lattices
# list of lattice points covered by square
def lattice_points(self,lattice):
out=[]
dx=math.floor(0.5*self.size/lattice.spacing)
dy=math.floor(0.5*self.size*self.aspect/lattice.spacing)
for i in range(-dx,dx+1):
for j in range(-dy,dy+1):
out.append(Point(self.pos.x+i*lattice.spacing,self.pos.y+j*lattice.spacing))
return out
# check whether an element interacts with square # check whether an element interacts with square
# TODO: this only works if element is a square! # TODO: this only works if element is a rectangle!
def check_interaction(self,element): def check_interaction(self,element):
# allow for error # allow for error
return max(abs(element.pos.x-self.pos.x)/(self.size+element.size),abs(element.pos.y-self.pos.y)/(self.size*self.aspect+element.size*element.aspect))<1/2-1e-11 return max(abs(element.pos.x-self.pos.x)/(self.size+element.size),abs(element.pos.y-self.pos.y)/(self.size*self.aspect+element.size*element.aspect))<1/2-1e-11
@ -106,7 +118,7 @@ class Element_square(Element):
return max(abs(self.pos.x-x.x),abs(self.pos.y-x.y)/self.aspect)<=1/2 return max(abs(self.pos.x-x.x),abs(self.pos.y-x.y)/self.aspect)<=1/2
# check whether an element is touching self # check whether an element is touching self
# TODO: this only works if element is a square! # TODO: this only works if element is a rectangle!
def check_touch(self,element): def check_touch(self,element):
# allow for error # allow for error
if in_interval(max(abs(element.pos.x-self.pos.x)/(self.size+element.size),abs(element.pos.y-self.pos.y)/(self.size*self.aspect+element.size*element.aspect)),1/2-1e-11,1/2+1e-11): if in_interval(max(abs(element.pos.x-self.pos.x)/(self.size+element.size),abs(element.pos.y-self.pos.y)/(self.size*self.aspect+element.size*element.aspect)),1/2-1e-11,1/2+1e-11):
@ -114,7 +126,7 @@ class Element_square(Element):
return False return False
# find position along a line that comes in contact with the line going through element.pos in direction v # find position along a line that comes in contact with the line going through element.pos in direction v
# TODO: this only works if element is a square! # TODO: this only works if element is a rectangle!
def move_on_line_to_stick(self,element,v): def move_on_line_to_stick(self,element,v):
size_x=(self.size+element.size)/2 size_x=(self.size+element.size)/2
size_y=(self.size*self.aspect+element.size*element.aspect)/2 size_y=(self.size*self.aspect+element.size*element.aspect)/2
@ -160,7 +172,7 @@ class Element_square(Element):
return closest-element.pos return closest-element.pos
# move along edge of square # move along edge of square
# TODO: this only works if element is a square! # TODO: this only works if element is a rectangle!
def move_along(self,delta,element): def move_along(self,delta,element):
size_x=(self.size+element.size)/2 size_x=(self.size+element.size)/2
size_y=(self.size*self.aspect+element.size*element.aspect)/2 size_y=(self.size*self.aspect+element.size*element.aspect)/2

View File

@ -73,4 +73,22 @@ class Lattice_square(Lattice):
return (Lattice_square(spacing=spacing),"") return (Lattice_square(spacing=spacing),"")
except: except:
return (None,"error: '"+spec+"' is not a valid specification for the square lattice: should be 'square[:spacing]'") return (None,"error: '"+spec+"' is not a valid specification for the square lattice: should be 'square[:spacing]'")
# distance on the lattice between (x1,x2) and (y1,y2)
def distance(self, x1, x2, y1, y2):
return round((abs(x1-y1)+abs(x2-y2))/self.spacing)
# distance between a lattice site and a particle
def distance_to_particle(self, x1, x2, particle):
mindist=self.distance_to_element(x1, x2, particle.elements[0])
for i in range(1,len(particle.elements)):
mindist=min(self.distance_to_element(x1, x2, particle.elements[i]),mindist)
return mindist
# distance between a lattice site and an element
def distance_to_element(self, x1, x2, element):
pts=element.lattice_points(self)
mindist=self.distance(x1, x2, pts[0].x, pts[0].y)
for i in range(1,len(pts)):
mindist=min(self.distance(x1, x2, pts[i].x, pts[i].y),mindist)
return mindist

View File

@ -18,7 +18,7 @@ import sys
import math import math
from kivy.uix.widget import Widget from kivy.uix.widget import Widget
from kivy.core.window import Window from kivy.core.window import Window
from kivy.graphics import Color,Line from kivy.graphics import Color,Line,Rectangle
from point import Point from point import Point
from polyomino import Cross,Disk from polyomino import Cross,Disk
@ -127,6 +127,12 @@ class Painter(Widget):
if particle.grid>0: if particle.grid>0:
self.draw_grid(particle.elements[0].pos,particle.grid) self.draw_grid(particle.elements[0].pos,particle.grid)
# draw Voronoi cells
if self.lattice!=None:
for particle in self.particles:
if particle.voronoi>0:
self.draw_voronoi(particle)
for particle in self.particles: for particle in self.particles:
particle.draw(self,alpha=0.5) particle.draw(self,alpha=0.5)
@ -160,6 +166,53 @@ class Painter(Widget):
Line(points=(0,self.pos_tocoord_y(yy),self.width,self.pos_tocoord_y(yy))) Line(points=(0,self.pos_tocoord_y(yy),self.width,self.pos_tocoord_y(yy)))
yy-=mesh yy-=mesh
# draw the discrete Voronoi cell of a particle
def draw_voronoi(self,particle):
# only works for lattices
if self.lattice!=None:
pos=particle.elements[0].pos
# loop over all points
xx=pos.x
while self.pos_tocoord_x(xx)<self.width:
yy=pos.y
while self.pos_tocoord_y(yy)<self.height:
if self.is_in_voronoi(xx,yy,particle):
self.draw_voronoi_site(xx,yy,particle.color)
yy+=self.lattice.spacing
yy=pos.y-self.lattice.spacing
while self.pos_tocoord_y(yy)>0:
if self.is_in_voronoi(xx,yy,particle):
self.draw_voronoi_site(xx,yy,particle.color)
yy-=self.lattice.spacing
xx+=self.lattice.spacing
xx=pos.x-self.lattice.spacing
while self.pos_tocoord_x(xx)>0:
yy=pos.y
while self.pos_tocoord_y(yy)<self.height:
if self.is_in_voronoi(xx,yy,particle):
self.draw_voronoi_site(xx,yy,particle.color)
yy+=self.lattice.spacing
yy=pos.y-self.lattice.spacing
while self.pos_tocoord_y(yy)>0:
if self.is_in_voronoi(xx,yy,particle):
self.draw_voronoi_site(xx,yy,particle.color)
yy-=self.lattice.spacing
xx-=self.lattice.spacing
# check whether a site is in the Voronoi cell of a particle
def is_in_voronoi(self,x,y,particle):
d_to_particle=self.lattice.distance_to_particle(x,y,particle)
# TODO: start with a particle that is close to x,y
for q in self.particles:
if q!=particle and self.lattice.distance_to_particle(x,y,q)<d_to_particle:
return False
return True
# draw a site in a Voronoi cell
def draw_voronoi_site(self,x,y,color):
Color(color[0],color[1],color[2],0.25)
#Color(1,0,0,alpha=0.25)
Rectangle(pos=(self.pos_tocoord_x(x-0.5),self.pos_tocoord_y(y-0.5)),size=(self.base_size,self.base_size))
# respond to keyboard # respond to keyboard
def on_key_down(self, keyboard, keycode, text, modifiers): def on_key_down(self, keyboard, keycode, text, modifiers):
@ -465,6 +518,17 @@ class Painter(Widget):
# redraw # redraw
self.draw() self.draw()
# set voronoi for selected particles
def set_voronoi(self, onoff):
for particle in self.selected:
if onoff==0:
particle.voronoi=False
elif onoff==1:
particle.voronoi=True
elif onoff==-1:
particle.voronoi=not particle.voronoi
# redraw
self.draw()
# write configuration to file # write configuration to file
def write(self,file): def write(self,file):

View File

@ -29,6 +29,9 @@ class Polyomino():
# mesh of background grid (no grid for mesh size 0) # mesh of background grid (no grid for mesh size 0)
self.grid=kwargs.get("grid",0) self.grid=kwargs.get("grid",0)
# draw Voronoi cell
self.voronoi=kwargs.get("voronoi",False)
# draw function # draw function
def draw(self,painter,**kwargs): def draw(self,painter,**kwargs):
alpha=kwargs.get("alpha",1) alpha=kwargs.get("alpha",1)