From a7c18641a6674a24036051d124546316b335b988 Mon Sep 17 00:00:00 2001 From: Ian Jauslin Date: Wed, 24 Nov 2021 19:22:05 -0500 Subject: [PATCH] Painter: draw only squares --- jam | 5 +- painter.py | 167 +++++++++++++++++++++++++++------------------------ polyomino.py | 61 +++++++++++++------ 3 files changed, 131 insertions(+), 102 deletions(-) diff --git a/jam b/jam index d824808..da0a3ad 100755 --- a/jam +++ b/jam @@ -7,8 +7,7 @@ from kivy.config import Config import sys import os.path -from cross import Cross -from painter import Cross_painter +from painter import Painter from status_bar import Status_bar from command_prompt import Command_prompt import filecheck @@ -32,7 +31,7 @@ class Jam_app(App): layout=BoxLayout(orientation="vertical") # painter - self.painter=Cross_painter(self) + self.painter=Painter(self) # status bar self.status_bar=Status_bar(self) # command prompt diff --git a/painter.py b/painter.py index c9b751b..484cf62 100644 --- a/painter.py +++ b/painter.py @@ -3,21 +3,25 @@ from kivy.uix.widget import Widget from kivy.core.window import Window from point import Point +from polyomino import Square -# cross painter -class Cross_painter(Widget): +# painter class +class Painter(Widget): def __init__(self,app,**kwargs): - # list of crosses - self.crosses=[] + # list of particles + self.particles=[] - # cross under mouse + # particle under mouse self.undermouse=None - # list of selected crosses + # list of selected particles self.selected=[] + # relative position of mouse when moving + self.offset=Point(0,0) + # app is used to share information between widgets self.app=app @@ -25,7 +29,7 @@ class Cross_painter(Widget): self.modifiers=[] # init Widget - super(Cross_painter,self).__init__(**kwargs) + super(Painter,self).__init__(**kwargs) # init keyboard self.keyboard = Window.request_keyboard(None,self,"text") @@ -33,17 +37,17 @@ class Cross_painter(Widget): def reset(self): - self.crosses=[] + self.particles=[] self.undermouse=None self.draw() - # draw all crosses + # draw all particles def draw(self): self.canvas.clear() with self.canvas: - for cross in self.crosses: - cross.draw() + for particle in self.particles: + particle.draw() # respond to keyboard @@ -66,32 +70,36 @@ class Cross_painter(Widget): # respond to mouse down def on_touch_down(self,touch): - # only respond to touch in area + # only respond to touch in drawing area if self.collide_point(*touch.pos): # create new cross if touch.button=="right": - if self.check_interaction_any(Point(touch.x/Cross.size,touch.y/Cross.size),None): - new=Cross(touch.x/Cross.size,touch.y/Cross.size) + new=Square(touch.x/Square_element.size,touch.y/Square_element.size) + if not self.check_interaction_any(new,Point(0,0)): # add to list - self.crosses.append(new) + self.particles.append(new) - # unselect all crosses + # unselect all particles for sel in self.selected: sel.selected=False self.selected=[] self.draw() - # select cross + # select particle if touch.button=="left": - # find cross under touch - self.undermouse=self.find_cross(Point(touch.x/Cross.size,touch.y/Cross.size)) + # find particle under touch + self.undermouse=self.find_particle(Point(touch.x/Square_element.size,touch.y/Square_element.size)) + + # record relative position of click with respect to reference + if self.undermouse!=None: + self.offset=Point(touch.x/Square_element.size,touch.y/Square_element.size)-self.undermouse.squares[0].pos # no modifiers if self.modifiers==[]: if self.undermouse==None or not self.undermouse in self.selected: - # unselect all crosses + # unselect all particles for sel in self.selected: sel.selected=False self.selected=[] @@ -114,91 +122,92 @@ class Cross_painter(Widget): # respond to drag def on_touch_move(self,touch): + # only respond to touch in drawing area if self.collide_point(*touch.pos): # only move on left click if touch.button=="left" and self.modifiers==[] and self.undermouse!=None: - # single cross - if not self.undermouse in self.selected: - self.undermouse.pos=self.check_move(Point(touch.x/Cross.size,touch.y/Cross.size),self.undermouse) - # multiple crosses - else: - self.group_move(touch) + # change in position + delta=self.check_move(Point(touch.x/Square_element.size,touch.y/Square_element.size)-(self.offset+self.undermouse.squares[0].pos),self.undermouse) + # multiple particles + # TODO: group moves + #else: + # self.group_move(touch) # redraw self.draw() - # move all selected crosses - def group_move(self,touch): - # save position of undermouse (to use it after it has been reset) - relative_position=self.undermouse.pos + ## move all selected particles + #def group_move(self,touch): + # # save position of undermouse (to use it after it has been reset) + # relative_position=self.undermouse.pos - # determine order in which to move - # direction of motion - direction=Point(touch.x/Cross.size,touch.y/Cross.size)-self.undermouse.pos - # sort according to scalar product with direction - self.selected.sort(key=(lambda cross: direction.dot(cross.pos-self.undermouse.pos)),reverse=True) + # # determine order in which to move + # # direction of motion + # direction=Point(touch.x/Square_element.size,touch.y/Square_element.size)-self.undermouse.pos + # # sort according to scalar product with direction + # self.selected.sort(key=(lambda particle: direction.dot(particle.pos-self.undermouse.pos)),reverse=True) - # move - for cross in self.selected: - cross.pos=self.check_move(Point(touch.x/Cross.size-relative_position.x+cross.pos.x,touch.y/Cross.size-relative_position.y+cross.pos.y),cross) - - ## new position of undermouse - #self.undermouse.pos=self.check_move(Point(touch.x/Cross.size,touch.y/Cross.size),self.undermouse) - ## move other crosses by the same amount as undermouse - #for cross in self.selected: - # if cross!=self.undermouse: - # cross.pos=self.check_move(Point(self.undermouse.pos.x-relative_position.x+cross.pos.x,self.undermouse.pos.y-relative_position.y+cross.pos.y),cross) - # - #for cross in self.selected: - # cross.pos=self.check_move(Point(touch.x/Cross.size-relative_position.x+cross.pos.x,touch.y/Cross.size-relative_position.y+cross.pos.y),cross) + # # move + # for particle in self.selected: + # particle.pos=self.check_move(Point(touch.x/Square_element.size-relative_position.x+particle.pos.x,touch.y/Square_element.size-relative_position.y+particle.pos.y),particle) + # + # ## new position of undermouse + # #self.undermouse.pos=self.check_move(Point(touch.x/Square_element.size,touch.y/Square_element.size),self.undermouse) + # ## move other particles by the same amount as undermouse + # #for particle in self.selected: + # # if particle!=self.undermouse: + # # particle.pos=self.check_move(Point(self.undermouse.pos.x-relative_position.x+particle.pos.x,self.undermouse.pos.y-relative_position.y+particle.pos.y),particle) + # # + # #for particle in self.selected: + # # particle.pos=self.check_move(Point(touch.x/Square_element.size-relative_position.x+particle.pos.x,touch.y/Square_element.size-relative_position.y+particle.pos.y),particle) - # find the cross at position pos - def find_cross(self,pos): - for cross in self.crosses: - if cross_distx(pos,cross.pos)<=0.5 or cross_disty(pos,cross.pos)<=0.5: - return cross + # find the particle at position pos + def find_particle(self,pos): + for particle in self.particles: + if particle.in_support(pos): + return particle # none found return None - # check whether a position intersects with any of the crosses - def check_interaction_any(self,pos,exception): - for other in self.crosses: - if other!=exception: - if other.check_interaction(pos)==False: - return False - return True + # check whether a position intersects with any of the particles + def check_interaction_any(self,candidate,offset): + for particle in self.particles: + if particle.check_interaction(candidate,offset): + return True + return False - # check that a cross can move to new position - def check_move(self,newpos,cross): + # check that a particle can move by delta, and return the closest allowed relative motion + def check_move(self,delta,particle): # whether newpos is acceptable accept_newpos=True - for other in self.crosses: - # do not compare a cross to itself - if other!=cross: - # move would make cross overlap with other - if other.check_interaction(newpos)==False: + for other in self.particles: + # do not compare a particle to itself + if other!=particle: + # move would make particle overlap with other + if other.check_interaction(particle,delta): accept_newpos=False - # check if cross touches other - if other.check_touch(cross.pos): + # check if particle already touches other + if other.check_touch(particle): # move along other while remaining stuck - candidate=other.move_along(newpos,cross.pos) + # TODO: this assumes other is a square + newdelta=other.squares[0].move_along(delta,particle.squares[0].pos) else: - candidate=other.move_on_line_to_stick(cross.pos,newpos-cross.pos) - if self.check_interaction_any(candidate,cross): - return candidate + newdelta=other.move_on_line_to_stick(particle.squares[0].pos,delta) + if not self.check_interaction_any(particle,newdelta): + return newdelta if accept_newpos: - return newpos + return delta else: - # cannot move cross at all, try again - return self.check_move(candidate,cross) + # cannot move particle at all, try again + return self.check_move(newdelta,particle) # write configuration to file def write(self,file): ff=open(file,"w") - for cross in self.crosses: - ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(cross.pos.x,cross.pos.y,cross.color[0],cross.color[1],cross.color[2])) + for particle in self.particles: + ff.write("{:05.2f},{:05.2f};{:3.1f},{:3.1f},{:3.1f}\n".format(particle.pos.x,particle.pos.y,particle.color[0],particle.color[1],particle.color[2])) ff.close() # read configuration from file @@ -267,7 +276,7 @@ class Cross_painter(Widget): if self.check_interaction_any(pos,None): # add to list - self.crosses.append(Cross(pos.x,pos.y,color=color)) + self.particles.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) diff --git a/polyomino.py b/polyomino.py index 5196d72..25af5a3 100644 --- a/polyomino.py +++ b/polyomino.py @@ -41,12 +41,35 @@ class Polyomino(): *((square.pos.x+0.5)*square.size,(square.pos.y+0.5)*square.size), *((square.pos.x+0.5)*square.size,(square.pos.y-0.5)*square.size) )) + + # 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: + 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): + return True + return False + + # check whether self touches candidate + def check_touch(self,candidate): + for square1 in self.squares: + for square2 in candidate.squares: + if square1.check_touch(square2.pos): + return True + return False # square class Square(Polyomino): def __init__(self,x,y,**kwargs): - super(Square,self).__init__(squares=[Square_element(x,y)],kwargs) + super(Square,self).__init__(kwargs,squares=[Square_element(x,y)]) @@ -66,7 +89,7 @@ class Square_element(): # check whether a square at pos interacts with square def check_interaction(self,pos): - return l_infinity(pos-self.pos)>=1 + return l_infinity(pos-self.pos)<1 # check whether a square at position pos is touching self def check_touch(self,pos): @@ -102,60 +125,58 @@ class Square_element(): return closest # move along edge of square - def move_along(self,newpos,pos): + 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(newpos.y-pos.y)==-sgn(rel.y): + if sgn(delta.y)==-sgn(rel.y): # stuck in x direction - return self.move_stuck_x(newpos,pos) - elif sgn(newpos.x-pos.x)==-sgn(rel.x): + return self.move_stuck_x(delta,pos) + elif sgn(delta.x)==-sgn(rel.x): # stuck in y direction - return self.move_stuck_y(newpos,pos) + return self.move_stuck_y(delta,pos) # stuck in both directions return pos else: # stuck in x direction - return self.move_stuck_x(newpos,pos) + return self.move_stuck_x(delta,pos) elif isint_nonzero(rel.y): # stuck in y direction - return self.move_stuck_y(newpos,pos) + 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,newpos,pos): + def move_stuck_x(self,delta,pos): # only move in y direction - candidate=Point(pos.x,newpos.y) + candidate=Point(pos.x,pos.x+delta.y) # do not move past corners rel=pos.y-self.pos.y - newrel=newpos.y-self.pos.y - if newpos.y>pos.y: - if relmath.ceil(rel)+1e-11 and math.ceil(rel)!=0: + if delta.y>0: + if relmath.ceil(rel)+1e-11 and math.ceil(rel)!=0: # stick to corner candidate.y=math.ceil(rel)+self.pos.y else: - if rel>math.floor(rel)+1e-11 and newrelmath.floor(rel)+1e-11 and delta.y+relpos.x: - if relmath.ceil(rel)+1e-11 and math.ceil(rel)!=0: + if delta.x>0: + if relmath.ceil(rel)+1e-11 and math.ceil(rel)!=0: # stick to corner candidate.x=math.ceil(rel)+self.pos.x else: - if rel>math.floor(rel)+1e-11 and newrelmath.floor(rel)+1e-11 and delta.x+rel