Painter: draw only squares
This commit is contained in:
parent
221aa0a713
commit
a7c18641a6
5
jam
5
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
|
||||
|
167
painter.py
167
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)
|
||||
|
||||
|
61
polyomino.py
61
polyomino.py
@ -42,11 +42,34 @@ class Polyomino():
|
||||
*((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 rel<math.ceil(rel)-1e-11 and newrel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
|
||||
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
|
||||
else:
|
||||
if rel>math.floor(rel)+1e-11 and newrel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
|
||||
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
|
||||
return candidate
|
||||
# move when stuck in the y direction
|
||||
def move_stuck_y(self,newpos,pos):
|
||||
def move_stuck_y(self,delta,pos):
|
||||
# onlx move in x direction
|
||||
candidate=Point(pos.x,newpos.x)
|
||||
# do not move past corners
|
||||
rel=pos.x-self.pos.x
|
||||
newrel=newpos.x-self.pos.x
|
||||
if newpos.x>pos.x:
|
||||
if rel<math.ceil(rel)-1e-11 and newrel>math.ceil(rel)+1e-11 and math.ceil(rel)!=0:
|
||||
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
|
||||
else:
|
||||
if rel>math.floor(rel)+1e-11 and newrel<math.floor(rel)-1e-11 and math.floor(rel)!=0:
|
||||
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
|
||||
return candidate
|
||||
|
Loading…
Reference in New Issue
Block a user