import math import sys from kivy.graphics import Color,Line,Rectangle from kivy.uix.widget import Widget from kivy.core.window import Window from point import Point from tools import isint_nonzero,sgn class Cross(): # size of central square size=50 def __init__(self,x,y,**kwargs): self.pos=Point(x,y) self.color=kwargs.get("color",(0,0,1)) self.selected=False # set position def setpos(self,x,y): self.pos.x=x self.pos.y=y def draw(self,**kwargs): # set color if not self.selected: Color(*self.color) else: (r,g,b)=self.color # darken selected Color(r/2,g/2,b/2) Rectangle(pos=((self.pos.x-1.5)*self.size,(self.pos.y-0.5)*self.size),size=(3*self.size,self.size)) Rectangle(pos=((self.pos.x-0.5)*self.size,(self.pos.y-1.5)*self.size),size=(self.size,3*self.size)) # stroke Color(1,1,1) Line(points=( *((self.pos.x-0.5)*self.size,(self.pos.y-0.5)*self.size), *((self.pos.x-0.5)*self.size,(self.pos.y-1.5)*self.size), *((self.pos.x+0.5)*self.size,(self.pos.y-1.5)*self.size), *((self.pos.x+0.5)*self.size,(self.pos.y-0.5)*self.size), *((self.pos.x+1.5)*self.size,(self.pos.y-0.5)*self.size), *((self.pos.x+1.5)*self.size,(self.pos.y+0.5)*self.size), *((self.pos.x+0.5)*self.size,(self.pos.y+0.5)*self.size), *((self.pos.x+0.5)*self.size,(self.pos.y+1.5)*self.size), *((self.pos.x-0.5)*self.size,(self.pos.y+1.5)*self.size), *((self.pos.x-0.5)*self.size,(self.pos.y+0.5)*self.size), *((self.pos.x-1.5)*self.size,(self.pos.y+0.5)*self.size), *((self.pos.x-1.5)*self.size,(self.pos.y-0.5)*self.size), *((self.pos.x-0.5)*self.size,(self.pos.y-0.5)*self.size), )) # check whether a cross at pos interacts with cross def check_interaction(self,pos): # allow for error return int(pos.x-self.pos.x+sgn(pos.x-self.pos.x)*1e-11)**2+int(pos.y-self.pos.y+sgn(pos.y-self.pos.y)*1e-11)**2>=5 # check whether a cross at position pos is touching self def check_touch(self,pos): rel=pos-self.pos for i in [-3,-2,-1,1,2,3]: # allow for error if abs(rel.x-i)<1e-11 and abs(rel.y)<=4-abs(i)+1e-11 and abs(rel.y)>=3-abs(i)-1e-11: return True if abs(rel.y-i)<1e-11 and abs(rel.x)<=4-abs(i)+1e-11 and abs(rel.x)>=3-abs(i)-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): # relative to cross return self.move_on_line_to_stick_relative(pos-self.pos,v)+self.pos def move_on_line_to_stick_relative(self,x,v): # if x is in the right quadrant if abs(x.y)<=x.x: # find all stuck positions on lines stuck=[] # check intersections with vertical lines if v.x!=0: for i in range(1,4): # candidate y=Point(i,x.y+(i-x.x)*v.y/v.x) # check that it is in the right range if abs(y.y)<=4-i and abs(y.y)>=3-i: stuck.append(y) # check intersections with horizontal lines if v.y!=0: for i in [-3,-2,-1,1,2,3]: # candidate y=Point(x.x+(i-x.y)*v.x/v.y,i) # check that it is in the right range if y.x<=4-abs(i) and y.x>=3-abs(i): stuck.append(y) return x.closest(stuck) # reflect other quadrants to the right one # top quadrant elif abs(x.x)<=x.y: closest=self.move_on_line_to_stick_relative(Point(x.y,x.x),Point(v.y,v.x)) return Point(closest.y,closest.x) # bottom quadrant elif abs(x.x)<=-x.y: closest=self.move_on_line_to_stick_relative(Point(-x.y,x.x),Point(-v.y,v.x)) return Point(closest.y,-closest.x) # left quadrant else: closest=self.move_on_line_to_stick_relative(Point(-x.x,x.y),Point(-v.x,v.y)) return Point(-closest.x,closest.y) # move along edge of cross def move_along(self,newpos,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 # two types of corners: |x|_1=3 or |x|_1=4 if abs(rel.x)+abs(rel.y)<3.5: if sgn(newpos.y-pos.y)==sgn(rel.y): # stuck in x direction return self.move_stuck_x(newpos,pos) elif sgn(newpos.x-pos.x)==sgn(rel.x): # stuck in y direction return self.move_stuck_y(newpos,pos) else: if sgn(newpos.y-pos.y)==-sgn(rel.y): # stuck in x direction return self.move_stuck_x(newpos,pos) elif sgn(newpos.x-pos.x)==-sgn(rel.x): # stuck in y direction return self.move_stuck_y(newpos,pos) # stuck in both directions return pos else: # stuck in x direction return self.move_stuck_x(newpos,pos) elif isint_nonzero(rel.y): # stuck in y direction return self.move_stuck_y(newpos,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,newpos,pos): # only move in y direction candidate=Point(pos.x,newpos.y) # do not move past corners rel=pos.y-self.pos.y newrel=newpos.y-self.pos.y if newpos.y>pos.y: if self.check_interaction(candidate)==False or (relmath.ceil(rel)+1e-11 and math.ceil(rel)!=0): # in open corner if rel>math.ceil(rel)-1e-11: candidate.y=math.ceil(rel)+1+self.pos.y else: candidate.y=math.ceil(rel)+self.pos.y else: if self.check_interaction(candidate)==False or (rel>math.floor(rel)+1e-11 and newrelpos.x: if self.check_interaction(candidate)==False or (relmath.ceil(rel)+1e-11 and math.ceil(rel)!=0): # in open corner if rel>math.ceil(rel)-1e-11: candidate.x=math.ceil(rel)+1+self.pos.x else: candidate.x=math.ceil(rel)+self.pos.x else: if self.check_interaction(candidate)==False or (rel>math.floor(rel)+1e-11 and newrel2: print("warning: ignoring line "+str(i)+" in file '"+file+"': more than two ';' spearated entries in '"+line+"'",file=sys.stderr) continue # position pos_str=entries[0].split(",") # skip line if improperly formatted if len(pos_str)!=2: print("warning: ignoring line "+str(i)+" in file '"+file+"': position '"+entries[0]+"' does not have two components",file=sys.stderr) continue try: pos=Point(float(pos_str[0]),float(pos_str[1])) except: print("warning: ignoring line "+str(i)+" in file '"+file+"': position '"+entries[0]+"' cannot be read",file=sys.stderr) continue # color color=(0,0,1) if len(entries)==2: color_str=entries[1].split(",") # skip line if improperly formatted if len(color_str)!=3: print("warning: ignoring line "+str(i)+" in file '"+file+"': color '"+entries[1]+"' does not have three components",file=sys.stderr) continue try: color=(float(color_str[0]),float(color_str[1]),float(color_str[2])) except: print("warning: ignoring line "+str(i)+" in file '"+file+"': color '"+entries[1]+"' cannot be read",file=sys.stderr) continue if self.check_interaction_any(pos,None): # add to list self.crosses.append(Cross(pos.x,pos.y,color=color)) else: print("warning: ignoring line "+str(i)+" in file '"+file+"': particle overlaps with existing particles",file=sys.stderr) ff.close() self.draw()