Compare commits

..

10 Commits

8 changed files with 377 additions and 210 deletions

258
src/element.py Normal file
View File

@@ -0,0 +1,258 @@
## elements that polyominoes are made of
import math
import sys
from point import Point,l_infinity,l_2
from tools import isint_nonzero,sgn,in_interval,ceil_grid,floor_grid
from kivy.graphics import Rectangle,Ellipse,Line
# parent class of all elements
class Element():
def __init__(self,x,y,size,**kwargs):
self.pos=Point(x,y)
self.size=size
# set position
def setpos(self,x,y):
self.pos.x=x
self.pos.y=y
# override in each subclass
# draw element
def draw(self,painter):
return
# override in each subclass
# draw boundary
def stroke(self,painter):
return
# override in each subclass
# check whether an element interacts with square
def check_interaction(self,element):
return False
# override in each subclass
# whether x is in the support of the element
def in_support(self,x):
return False
# override in each subclass
# check whether an element is touching self
def check_touch(self,element):
return False
# override in each subclass
# find position along a line that comes in contact with the line going through element.pos in direction v
def move_on_line_to_stick(self,element,v):
return Point(0,0)
# override in each subclass
# move along edge of element
# delta is the impossible move that was asked for
def move_along(self,delta,element):
return element
# rectangular element
# the size of the y component is specified by an aspect ratio: size_x=size, size_y=size*aspect
class Element_square(Element):
def __init__(self,x,y,size,**kwargs):
self.pos=Point(x,y)
self.size=size
self.aspect=kwargs.get("aspect",1.0)
# draw element
def draw(self,painter):
Rectangle(pos=(painter.pos_tocoord_x(self.pos.x-0.5*self.size),painter.pos_tocoord_y(self.pos.y-0.5*self.size*self.aspect)),size=(self.size*painter.base_size,self.size*self.aspect*painter.base_size))
# draw boundary
def stroke(self,painter):
# convert to graphical coordinates
coordx=painter.pos_tocoord_x(square.pos.x)
coordy=painter.pos_tocoord_y(square.pos.y)
Line(points=(
*(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),
*(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),
*(coordx-0.5*self.size*painter.base_size,coordy-0.5*self.size*self.aspect*painter.base_size)
))
# check whether an element interacts with square
# TODO: this only works if element is a square!
def check_interaction(self,element):
# 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
# whether x is in the support of the element
def in_support(self,x):
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
# TODO: this only works if element is a square!
def check_touch(self,element):
# 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):
return True
return False
# 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!
def move_on_line_to_stick(self,element,v):
size_x=(self.size+element.size)/2
size_y=(self.size*self.aspect+element.size*element.aspect)/2
# compute intersections with four lines making up square
if v.x!=0:
if v.y!=0:
intersections=[\
Point(self.pos.x+size_x,element.pos.y+v.y/v.x*(self.pos.x+size_x-element.pos.x)),\
Point(self.pos.x-size_x,element.pos.y+v.y/v.x*(self.pos.x-size_x-element.pos.x)),\
Point(element.pos.x+v.x/v.y*(self.pos.y+size_y-element.pos.y),self.pos.y+size_y),\
Point(element.pos.x+v.x/v.y*(self.pos.y-size_y-element.pos.y),self.pos.y-size_y)\
]
else:
intersections=[\
Point(self.pos.x+size_x,element.pos.y),\
Point(self.pos.x-size_x,element.pos.y)
]
else:
if v.y!=0:
intersections=[\
Point(element.pos.x,self.pos.y+size_y),\
Point(element.pos.x,self.pos.y-size_y)\
]
else:
print("error: move_on_line_to_stick called with v=0, please file a bug report with the developer",file=sys.stderr)
exit(-1)
# compute closest one, on square
closest=None
dist=math.inf
for i in range(0,len(intersections)):
# check that it is on square
if abs(intersections[i].x-self.pos.x)<=size_x+1e-11 and abs(intersections[i].y-self.pos.y)<=size_y+1e-11:
if (intersections[i]-element.pos)**2<dist:
closest=intersections[i]
dist=(intersections[i]-element.pos)**2
if closest==None:
print("error: cannot move particle at (",element.pos.x,",",element.pos.y,") to the boundary of (",self.pos.x,",",self.pos.y,") in direction (",v.x,",",v.y,")",file=sys.stderr)
exit(-1)
# return difference to pos
return closest-element.pos
# move along edge of square
# TODO: this only works if element is a square!
def move_along(self,delta,element):
size_x=(self.size+element.size)/2
size_y=(self.size*self.aspect+element.size*element.aspect)/2
rel=element.pos-self.pos
# check if the particle is stuck in the x direction
if isint_nonzero(rel.x/size_x):
# check y direction
if isint_nonzero(rel.y/size_y):
# in corner
if sgn(delta.y)==-sgn(rel.y):
# stuck in x direction
return self.move_stuck_x(delta,element)
elif sgn(delta.x)==-sgn(rel.x):
# stuck in y direction
return self.move_stuck_y(delta,element)
# stuck in both directions
return element.pos
else:
# stuck in x direction
return self.move_stuck_x(delta,element)
elif isint_nonzero(rel.y/size_y):
# stuck in y direction
return self.move_stuck_y(delta,element)
# this should never happen
else:
print("error: stuck particle has non-integer relative position: (",rel.x,",",rel.y,")",file=sys.stderr)
exit(-1)
# move when stuck in the x direction
def move_stuck_x(self,delta,element):
size_y=(self.size*self.aspect+element.size*element.aspect)/2
# only move in y direction
candidate=Point(0,delta.y)
# do not move past corners
rel=element.pos.y-self.pos.y
if delta.y>0:
if rel<ceil_grid(rel,size_y)-1e-11 and delta.y+rel>ceil_grid(rel,size_y)+1e-11 and ceil_grid(rel,size_y)!=0:
# stick to corner
candidate.y=ceil_grid(rel,size_y)+self.pos.y-element.pos.y
else:
if rel>floor_grid(rel,size_y)+1e-11 and delta.y+rel<floor_grid(rel,size_y)-1e-11 and floor_grid(rel,size_y)!=0:
# stick to corner
candidate.y=floor_grid(rel,size_y)+self.pos.y-element.pos.y
return candidate
# move when stuck in the y direction
def move_stuck_y(self,delta,element):
size_x=(self.size+element.size)/2
# onlx move in x direction
candidate=Point(delta.x,0)
# do not move past corners
rel=element.pos.x-self.pos.x
if delta.x>0:
if rel<ceil_grid(rel,size_x)-1e-11 and delta.x+rel>ceil_grid(rel,size_x)+1e-11 and ceil_grid(rel,size_x)!=0:
# stick to corner
candidate.x=ceil_grid(rel,size_x)+self.pos.x-element.pos.x
else:
if rel>floor_grid(rel,size_x)+1e-11 and delta.x+rel<floor_grid(rel,size_x)-1e-11 and floor_grid(rel,size_x)!=0:
# stick to corner
candidate.x=floor_grid(rel,size_x)+self.pos.x-element.pos.x
return candidate
# circular elements
# (size is the diameter)
class Element_circle(Element):
# draw element
def draw(self,painter):
Ellipse(pos=(painter.pos_tocoord_x(self.pos.x-0.5*self.size),painter.pos_tocoord_y(self.pos.y-0.5*self.size)),size=(self.size*painter.base_size,self.size*painter.base_size))
# draw boundary
def stroke(self,painter):
Line(circle=(painter.pos_tocoord_x(self.pos.x),painter.pos_tocoord_y(self.pos.y),self.size*0.5*painter.base_size))
# check whether an element interacts with square
# TODO: this only works if element is a circle!
def check_interaction(self,element):
# allow for error
return l_2(element.pos-self.pos)<(self.size+element.size)/2-1e-11
# whether x is in the support of the element
def in_support(self,x):
return l_2(self.pos-x)<=1/2
# check whether an element is touching self
# TODO: this only works if element is a circle!
def check_touch(self,element):
# allow for error
if in_interval(l_2(element.pos-self.pos),(self.size+element.size)/2-1e-11,(self.size+element.size)/2+1e-11):
return True
return False
# 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 circle!
def move_on_line_to_stick(self,element,v):
# relative position
x=element.pos-self.pos
# radius of collision circle
R=(element.size+self.size)/2
# smallest root of t^2 v^2+2x.v t+x^2-R^2
t=(-v.dot(x)-math.sqrt(v.dot(x)*v.dot(x)-v.dot(v)*(x.dot(x)-R*R)))/v.dot(v)
# return difference to pos
return v*t
# move along edge of circle
# TODO: this only works if element is a circle!
def move_along(self,delta,element):
x=element.pos-self.pos+delta
return x/l_2(x)*(element.size+self.size)/2+self.pos-element.pos

View File

@@ -1,3 +1,5 @@
# check that a file is creatable/writable/editable
import os.path
# check that a file can be edited

View File

@@ -1,3 +1,5 @@
# define background lattices
from point import Point
# parent class of all lattices
@@ -24,18 +26,18 @@ class Lattice():
specs=spec.split(":")
# check type of lattice
if specs[0]=="square":
return Square_lattice.new_square(specs[1:],spec)
return Lattice_square.new_square(specs[1:],spec)
else:
return(None,"error: unrecognized lattice type: '"+specs[0]+"'")
# square lattice
class Square_lattice(Lattice):
class Lattice_square(Lattice):
def __init__(self,**kwargs):
self.spacing=kwargs.get("spacing",1.)
super(Square_lattice,self).__init__(**kwargs,type="square")
super(Lattice_square,self).__init__(**kwargs,type="square")
# lattice point nearest to point
def nearest(self,point):
@@ -49,12 +51,12 @@ class Square_lattice(Lattice):
def new_square(specs,spec):
# no optional args
if len(specs)==0:
return (Square_lattice(),"")
return (Lattice_square(),"")
if len(specs)>1:
return (None,"error: '"+spec+"' is not a valid specification for the square lattice: should be 'square[:spacing]'")
try:
spacing=float(specs[0])
return (Square_lattice(spacing=spacing),"")
return (Lattice_square(spacing=spacing),"")
except:
return (None,"error: '"+spec+"' is not a valid specification for the square lattice: should be 'square[:spacing]'")

View File

@@ -1,3 +1,5 @@
# main drawing class
import sys
import math
from kivy.uix.widget import Widget
@@ -5,9 +7,7 @@ from kivy.core.window import Window
from kivy.graphics import Color,Line
from point import Point
from polyomino import Cross
from polyomino import Square_element
from lattice import Square_lattice
from polyomino import Cross,Disk
from tools import remove_fromlist
@@ -44,6 +44,9 @@ class Painter(Widget):
# modifiers
self.modifiers=[]
# base size for all particles
self.base_size=50
# init Widget
super(Painter,self).__init__(**kwargs)
@@ -69,20 +72,20 @@ class Painter(Widget):
# snap all existing particles to grid
for particle in self.particles:
delta=self.lattice.nearest_delta(particle.squares[0].pos)
delta=self.lattice.nearest_delta(particle.elements[0].pos)
if not self.check_interaction_any(particle,delta):
particle.move(delta)
# convert logical coordinates (normalized and centered) to the ones that are plotted
def pos_tocoord_x(self,x):
return self.width/2+x*Square_element.size
return self.width/2+x*self.base_size
def pos_tocoord_y(self,y):
return self.height/2+y*Square_element.size
return self.height/2+y*self.base_size
def coord_topos_x(self,x):
return (x-self.width/2)/Square_element.size
return (x-self.width/2)/self.base_size
def coord_topos_y(self,y):
return (y-self.height/2)/Square_element.size
return (y-self.height/2)/self.base_size
@@ -102,7 +105,7 @@ class Painter(Widget):
# draw grids
for particle in self.particles:
if particle.grid>0:
self.draw_grid(particle.squares[0].pos,particle.grid)
self.draw_grid(particle.elements[0].pos,particle.grid)
for particle in self.particles:
particle.draw(self,alpha=0.5)
@@ -177,10 +180,10 @@ class Painter(Widget):
# zoom
elif text=="+":
# increment by 10%
self.set_zoom(Square_element.size/50*1.1)
self.set_zoom(self.base_size/50*1.1)
elif text=="-":
# decrease by 10%
self.set_zoom(Square_element.size/50*0.9)
self.set_zoom(self.base_size/50*0.9)
elif text=="=":
# reset
self.set_zoom(1)
@@ -210,12 +213,13 @@ class Painter(Widget):
touchx=self.coord_topos_x(touch.x)
touchy=self.coord_topos_y(touch.y)
# create new cross
# create new particle
if touch.button=="right":
new=Cross(touchx,touchy)
#new=Disk(touchx,touchy)
# snap to lattice
if self.lattice!=None:
new.move(self.lattice.nearest_delta(new.squares[0].pos))
new.move(self.lattice.nearest_delta(new.elements[0].pos))
if not self.check_interaction_any(new,Point(0,0)):
# add to list
@@ -236,7 +240,7 @@ class Painter(Widget):
# record relative position of click with respect to reference
if self.undermouse!=None:
self.offset=Point(touchx,touchy)-self.undermouse.squares[0].pos
self.offset=Point(touchx,touchy)-self.undermouse.elements[0].pos
# no modifiers
if self.modifiers==[]:
@@ -289,7 +293,7 @@ class Painter(Widget):
# only move on left click
if touch.button=="left" and self.modifiers==[] and self.undermouse!=None:
# attempted move determined by the relative position to the relative position of click within self.undermouse
delta=self.adjust_move(Point(touchx,touchy)-(self.offset+self.undermouse.squares[0].pos),0)
delta=self.adjust_move(Point(touchx,touchy)-(self.offset+self.undermouse.elements[0].pos),0)
# snap to lattice
if self.lattice!=None:
@@ -345,9 +349,15 @@ class Painter(Widget):
# check whether a candidate particle element with any of the unselected particles
def check_interaction_unselected_element(self,element,offset):
for particle in self.unselected:
for square in particle.squares:
if square.check_interaction(element.pos+offset):
for elt in particle.elements:
# add offset
element.pos+=offset
if elt.check_interaction(element):
# reset offset
element.pos-=offset
return True
# reset offset
element.pos-=offset
return False
@@ -357,7 +367,7 @@ class Painter(Widget):
# actual_delta is the smallest (componentwise) of all the computed delta's
actual_delta=Point(math.inf,math.inf)
for particle in self.selected:
for element in particle.squares:
for element in particle.elements:
# compute adjustment move due to unselected obstacles
adjusted_delta=self.adjust_move_element(delta,element,0)
# only keep the smallest delta's (in absolute value)
@@ -383,18 +393,23 @@ class Painter(Widget):
# whether newpos is acceptable
accept_newpos=True
for other in self.unselected:
for obstacle in other.squares:
for obstacle in other.elements:
# move would make element overlap with obstacle
if obstacle.check_interaction(element.pos+delta):
element.pos+=delta
if obstacle.check_interaction(element):
element.pos-=delta
accept_newpos=False
# check if particle already touches obstacle
if obstacle.check_touch(element.pos):
if obstacle.check_touch(element):
# move along obstacle while remaining stuck
newdelta=obstacle.move_along(delta,element.pos)
newdelta=obstacle.move_along(delta,element)
else:
newdelta=obstacle.move_on_line_to_stick(element.pos,delta)
newdelta=obstacle.move_on_line_to_stick(element,delta)
if not self.check_interaction_unselected_element(element,newdelta):
return newdelta
else:
# reset offset
element.pos-=delta
if accept_newpos:
return delta
else:
@@ -435,7 +450,9 @@ class Painter(Widget):
for particle in self.particles:
if type(particle)==Cross:
ff.write("{:d};".format(CROSS_INDEX))
ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(particle.squares[0].pos.x,particle.squares[0].pos.y,particle.color[0],particle.color[1],particle.color[2]))
elif type(particle)==Disk:
ff.write("{:d};".format(DISK_INDEX))
ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(particle.elements[0].pos.x,particle.elements[0].pos.y,particle.color[0],particle.color[1],particle.color[2]))
ff.close()
# read configuration from file
@@ -512,6 +529,8 @@ class Painter(Widget):
continue
if particle_type==CROSS_INDEX:
candidate=Cross(pos.x,pos.y,color=color)
elif particle_type==DISK_INDEX:
candidate=Disk(pos.x,pos.y,color=color)
else:
print("warning: ignoring line "+str(i)+" in file '"+file+"': unrecognized particle type: '"+entries[0]+"'",file=sys.stderr)
continue
@@ -548,7 +567,7 @@ class Painter(Widget):
for particle in self.particles:
if type(particle)==Cross:
ff.write("\cross{"+colors.closest_color(particle.color,colors.xcolor_names)+"}")
ff.write("{{({:05.2f},{:05.2f})}};\n".format(particle.squares[0].pos.x-self.particles[0].squares[0].pos.x,particle.squares[0].pos.y-self.particles[0].squares[0].pos.y))
ff.write("{{({:05.2f},{:05.2f})}};\n".format(particle.elements[0].pos.x-self.particles[0].elements[0].pos.x,particle.elements[0].pos.y-self.particles[0].elements[0].pos.y))
ff.write("\\end{tikzpicture}\n")
ff.write("\\end{document}\n")
@@ -558,11 +577,12 @@ class Painter(Widget):
# set zoom level
def set_zoom(self,level):
Square_element.size=level*50
self.base_size=level*50
self.draw()
# global variables (used like precompiler variables)
CROSS_INDEX=1
DISK_INDEX=2

View File

@@ -1,3 +1,5 @@
# two-dimensional point structure
import math
# point in two dimensions
@@ -47,3 +49,7 @@ class Point:
# L infinity norm
def l_infinity(x):
return max(abs(x.x),abs(x.y))
# L 2 norm
def l_2(x):
return math.sqrt(x.x*x.x+x.y*x.y)

View File

@@ -1,15 +1,14 @@
import math
import sys
from kivy.graphics import Color,Line,Rectangle
# a polyomino is a collection of elements, defined in elements.py
from kivy.graphics import Color,Line
from point import Point,l_infinity
from tools import isint_nonzero,sgn,in_interval
from point import l_infinity
from element import Element_square,Element_circle
# parent class of all polyominos
class Polyomino():
def __init__(self,**kwargs):
# square elements that make up the polyomino
self.squares=kwargs.get("squares",[])
# elements that make up the polyomino
self.elements=kwargs.get("elements",[])
self.color=kwargs.get("color",(0,0,1))
self.selected=False
@@ -28,212 +27,83 @@ class Polyomino():
# darken selected
Color(r/2,g/2,b/2,alpha)
for square in self.squares:
Rectangle(pos=(painter.pos_tocoord_x(square.pos.x-0.5),painter.pos_tocoord_y(square.pos.y-0.5)),size=(square.size,square.size))
for element in self.elements:
element.draw(painter)
# draw boundary
self.stroke(painter)
# draw boundary (override for connected polyominos)
def stroke(self,painter):
# convert to graphical coordinates
coordx=painter.pos_tocoord_x(square.pos.x)
coordy=painter.pos_tocoord_y(square.pos.y)
# white
Color(1,1,1)
for square in self.squares:
Line(points=(
*(coordx-0.5*square.size,coordy-0.5*square.size),
*(coordx-0.5*square.size,coordy+0.5*square.size),
*(coordx+0.5*square.size,coordy+0.5*square.size),
*(coordx+0.5*square.size,coordy-0.5*square.size),
*(coordx-0.5*square.size,coordy-0.5*square.size)
))
for element in self.elements:
element.stroke(painter)
# move by delta
def move(self,delta):
for square in self.squares:
square.pos+=delta
for element in self.elements:
element.pos+=delta
# whether x is in the support of the polyomino
def in_support(self,x):
for square in self.squares:
if l_infinity(square.pos-x)<=1/2:
for element in self.elements:
if element.in_support(x):
return True
return False
# check whether self interacts with candidate if candidate were moved by offset
def check_interaction(self,candidate,offset):
for square1 in self.squares:
for square2 in candidate.squares:
if square1.check_interaction(square2.pos+offset):
for element1 in self.elements:
for element2 in candidate.elements:
# add offset
element2.pos+=offset
if element1.check_interaction(element2):
# reset offset
element2.pos-=offset
return True
# reset offset
element2.pos-=offset
return False
# square
class Square(Polyomino):
def __init__(self,x,y,**kwargs):
super(Square,self).__init__(**kwargs,squares=[Square_element(x,y)])
super(Square,self).__init__(**kwargs,elements=[Element_square(x,y,size=kwargs.get("size",1.0))])
# cross
class Cross(Polyomino):
def __init__(self,x,y,**kwargs):
super(Cross,self).__init__(**kwargs,squares=[\
Square_element(x,y),\
Square_element(x+1,y),\
Square_element(x-1,y),\
Square_element(x,y+1),\
Square_element(x,y-1)\
super(Cross,self).__init__(**kwargs,elements=[\
Element_square(x,y,1,aspect=3),\
Element_square(x+1,y,1),\
Element_square(x-1,y,1)\
])
# redefine stroke to avoid lines between touching squares
# redefine stroke to avoid lines between touching elements
def stroke(self,painter):
# convert to graphical coordinates
coordx=painter.pos_tocoord_x(self.squares[0].pos.x)
coordy=painter.pos_tocoord_y(self.squares[0].pos.y)
coordx=painter.pos_tocoord_x(self.elements[0].pos.x)
coordy=painter.pos_tocoord_y(self.elements[0].pos.y)
Color(1,1,1)
Line(points=(
*(coordx-0.5*Square_element.size,coordy-0.5*Square_element.size),
*(coordx-0.5*Square_element.size,coordy-1.5*Square_element.size),
*(coordx+0.5*Square_element.size,coordy-1.5*Square_element.size),
*(coordx+0.5*Square_element.size,coordy-0.5*Square_element.size),
*(coordx+1.5*Square_element.size,coordy-0.5*Square_element.size),
*(coordx+1.5*Square_element.size,coordy+0.5*Square_element.size),
*(coordx+0.5*Square_element.size,coordy+0.5*Square_element.size),
*(coordx+0.5*Square_element.size,coordy+1.5*Square_element.size),
*(coordx-0.5*Square_element.size,coordy+1.5*Square_element.size),
*(coordx-0.5*Square_element.size,coordy+0.5*Square_element.size),
*(coordx-1.5*Square_element.size,coordy+0.5*Square_element.size),
*(coordx-1.5*Square_element.size,coordy-0.5*Square_element.size),
*(coordx-0.5*Square_element.size,coordy-0.5*Square_element.size),
*(coordx-0.5*painter.base_size,coordy-0.5*painter.base_size),
*(coordx-0.5*painter.base_size,coordy-1.5*painter.base_size),
*(coordx+0.5*painter.base_size,coordy-1.5*painter.base_size),
*(coordx+0.5*painter.base_size,coordy-0.5*painter.base_size),
*(coordx+1.5*painter.base_size,coordy-0.5*painter.base_size),
*(coordx+1.5*painter.base_size,coordy+0.5*painter.base_size),
*(coordx+0.5*painter.base_size,coordy+0.5*painter.base_size),
*(coordx+0.5*painter.base_size,coordy+1.5*painter.base_size),
*(coordx-0.5*painter.base_size,coordy+1.5*painter.base_size),
*(coordx-0.5*painter.base_size,coordy+0.5*painter.base_size),
*(coordx-1.5*painter.base_size,coordy+0.5*painter.base_size),
*(coordx-1.5*painter.base_size,coordy-0.5*painter.base_size),
*(coordx-0.5*painter.base_size,coordy-0.5*painter.base_size),
))
# square building block of polyominos
class Square_element():
# size
size=50
# disk
class Disk(Polyomino):
def __init__(self,x,y,**kwargs):
self.pos=Point(x,y)
# set position
def setpos(self,x,y):
self.pos.x=x
self.pos.y=y
# check whether a square at pos interacts with square
def check_interaction(self,pos):
return l_infinity(pos-self.pos)<1
# check whether a square at position pos is touching self
def check_touch(self,pos):
# allow for error
if in_interval(l_infinity(pos-self.pos),1-1e-11,1+1e-11):
return True
return False
# find position along a line that comes in contact with the line going through pos in direction v
def move_on_line_to_stick(self,pos,v):
# compute intersections with four lines making up square
if v.x!=0:
if v.y!=0:
intersections=[\
Point(self.pos.x+1,pos.y+v.y/v.x*(self.pos.x+1-pos.x)),\
Point(self.pos.x-1,pos.y+v.y/v.x*(self.pos.x-1-pos.x)),\
Point(pos.x+v.x/v.y*(self.pos.y+1-pos.y),self.pos.y+1),\
Point(pos.x+v.x/v.y*(self.pos.y-1-pos.y),self.pos.y-1)\
]
else:
intersections=[\
Point(self.pos.x+1,pos.y+v.y/v.x*(self.pos.x+1-pos.x)),\
Point(self.pos.x-1,pos.y+v.y/v.x*(self.pos.x-1-pos.x))
]
else:
if v.y!=0:
intersections=[\
Point(pos.x+v.x/v.y*(self.pos.y+1-pos.y),self.pos.y+1),\
Point(pos.x+v.x/v.y*(self.pos.y-1-pos.y),self.pos.y-1)\
]
else:
print("error: move_on_line_to_stick called with v=0, please file a bug report with the developer",file=sys.stderr)
exit(-1)
# compute closest one, on square
closest=None
dist=math.inf
for i in range(0,len(intersections)):
# check that it is on square
if abs(intersections[i].x-self.pos.x)<=1+1e-11 and abs(intersections[i].y-self.pos.y)<=1+1e-11:
if (intersections[i]-pos)**2<dist:
closest=intersections[i]
dist=(intersections[i]-pos)**2
if closest==None:
print("error: cannot move particle at (",pos.x,",",pos.y,") to the boundary of (",self.pos.x,",",self.pos.y,") in direction (",v.x,",",v.y,")",file=sys.stderr)
exit(-1)
# return difference to pos
return closest-pos
# move along edge of square
def move_along(self,delta,pos):
rel=pos-self.pos
# check if the particle is stuck in the x direction
if isint_nonzero(rel.x):
# check y direction
if isint_nonzero(rel.y):
# in corner
if sgn(delta.y)==-sgn(rel.y):
# stuck in x direction
return self.move_stuck_x(delta,pos)
elif sgn(delta.x)==-sgn(rel.x):
# stuck in y direction
return self.move_stuck_y(delta,pos)
# stuck in both directions
return pos
else:
# stuck in x direction
return self.move_stuck_x(delta,pos)
elif isint_nonzero(rel.y):
# stuck in y direction
return self.move_stuck_y(delta,pos)
# this should never happen
else:
print("error: stuck particle has non-integer relative position: (",rel.x,",",rel.y,")",file=sys.stderr)
exit(-1)
# move when stuck in the x direction
def move_stuck_x(self,delta,pos):
# only move in y direction
candidate=Point(0,delta.y)
# do not move past corners
rel=pos.y-self.pos.y
if delta.y>0:
if rel<math.ceil(rel)-1e-11 and delta.y+rel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
# stick to corner
candidate.y=math.ceil(rel)+self.pos.y-pos.y
else:
if rel>math.floor(rel)+1e-11 and delta.y+rel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
# stick to corner
candidate.y=math.floor(rel)+self.pos.y-pos.y
return candidate
# move when stuck in the y direction
def move_stuck_y(self,delta,pos):
# onlx move in x direction
candidate=Point(delta.x,0)
# do not move past corners
rel=pos.x-self.pos.x
if delta.x>0:
if rel<math.ceil(rel)-1e-11 and delta.x+rel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
# stick to corner
candidate.x=math.ceil(rel)+self.pos.x-pos.x
else:
if rel>math.floor(rel)+1e-11 and delta.x+rel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
# stick to corner
candidate.x=math.floor(rel)+self.pos.x-pos.x
return candidate
super(Disk,self).__init__(**kwargs,elements=[Element_circle(x,y,size=kwargs.get("size",1.0))])

View File

@@ -45,9 +45,9 @@ class Status_bar(Label):
spaces=int(self.width/self.char_width)-len(self.raw_text)-13
if spaces>0:
if self.app.painter.reference==None:
self.raw_text+=" "*spaces+"({:05.2f},{:05.2f})\n".format(self.app.painter.selected[0].squares[0].pos.x,self.app.painter.selected[0].squares[0].pos.y)
self.raw_text+=" "*spaces+"({:05.2f},{:05.2f})\n".format(self.app.painter.selected[0].elements[0].pos.x,self.app.painter.selected[0].elements[0].pos.y)
else:
self.raw_text+=" "*spaces+"({:05.2f},{:05.2f})\n".format(self.app.painter.selected[0].squares[0].pos.x-self.app.painter.reference.squares[0].pos.x,self.app.painter.selected[0].squares[0].pos.y-self.app.painter.reference.squares[0].pos.y)
self.raw_text+=" "*spaces+"({:05.2f},{:05.2f})\n".format(self.app.painter.selected[0].elements[0].pos.x-self.app.painter.reference.elements[0].pos.x,self.app.painter.selected[0].elements[0].pos.y-self.app.painter.reference.elements[0].pos.y)
# do not wrap
self.text=self.raw_text[:min(len(self.raw_text),int(self.width/self.char_width))]

View File

@@ -1,3 +1,5 @@
import math
# sign function
def sgn(x):
if x>=0:
@@ -20,3 +22,10 @@ def remove_fromlist(a,x):
a[a.index(x)]=a[len(a)-1]
a=a[:len(a)-1]
return a
# snap to a grid: ceiling
def ceil_grid(x,size):
return math.ceil(x/size)*size
# snap to a grid: floor
def floor_grid(x,size):
return math.floor(x/size)*size