Move all selected

This commit is contained in:
Ian Jauslin 2021-11-24 22:22:19 -05:00
parent bf365bc725
commit 7ea7b56556
3 changed files with 94 additions and 60 deletions

View File

@ -1,4 +1,5 @@
import sys import sys
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
@ -6,6 +7,8 @@ from point import Point
from polyomino import Square from polyomino import Square
from polyomino import Square_element from polyomino import Square_element
from tools import remove_fromlist
# painter class # painter class
class Painter(Widget): class Painter(Widget):
@ -19,6 +22,8 @@ class Painter(Widget):
# list of selected particles # list of selected particles
self.selected=[] self.selected=[]
# complement
self.unselected=self.particles
# relative position of mouse when moving # relative position of mouse when moving
self.offset=Point(0,0) self.offset=Point(0,0)
@ -85,6 +90,7 @@ class Painter(Widget):
for sel in self.selected: for sel in self.selected:
sel.selected=False sel.selected=False
self.selected=[] self.selected=[]
self.unselected=self.particles
self.draw() self.draw()
@ -99,11 +105,20 @@ class Painter(Widget):
# no modifiers # no modifiers
if self.modifiers==[]: if self.modifiers==[]:
if self.undermouse==None or not self.undermouse in self.selected: if self.undermouse==None:
# unselect all particles # unselect all particles
for sel in self.selected: for sel in self.selected:
sel.selected=False sel.selected=False
self.selected=[] self.selected=[]
self.unselected=self.particles
# select undermouse
elif not self.undermouse in self.selected:
for sel in self.selected:
sel.selected=False
self.selected=[self.undermouse]
self.unselected=self.particles.copy()
self.unselected=remove_fromlist(self.unselected,self.undermouse)
self.undermouse.selected=True
# shift-click # shift-click
@ -112,11 +127,14 @@ class Painter(Widget):
if self.undermouse not in self.selected: if self.undermouse not in self.selected:
self.selected.append(self.undermouse) self.selected.append(self.undermouse)
self.undermouse.selected=True self.undermouse.selected=True
# remove from unselected
self.unselected=remove_fromlist(self.unselected,self.undermouse)
else: else:
# remove # remove
self.selected[self.selected.index(self.undermouse)]=self.selected[len(self.selected)-1] self.selected=remove_fromlist(self.selected,self.undermouse)
self.selected=self.selected[:len(self.selected)-1]
self.undermouse.selected=False self.undermouse.selected=False
# add to unselected
self.unselected.append(self.undermouse)
self.draw() self.draw()
@ -127,42 +145,14 @@ class Painter(Widget):
if self.collide_point(*touch.pos): if self.collide_point(*touch.pos):
# only move on left click # only move on left click
if touch.button=="left" and self.modifiers==[] and self.undermouse!=None: if touch.button=="left" and self.modifiers==[] and self.undermouse!=None:
# change in position # attempted move determined by the relative position to the relative position of click within self.undermouse
delta=self.check_move(Point(touch.x/Square_element.size,touch.y/Square_element.size)-(self.offset+self.undermouse.squares[0].pos),self.undermouse) delta=self.adjust_move(Point(touch.x/Square_element.size,touch.y/Square_element.size)-(self.offset+self.undermouse.squares[0].pos))
self.undermouse.move(delta) for particle in self.selected:
# multiple particles particle.move(delta)
# TODO: group moves
#else:
# self.group_move(touch)
# redraw # redraw
self.draw() self.draw()
## 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/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 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 particle at position pos # find the particle at position pos
def find_particle(self,pos): def find_particle(self,pos):
@ -172,7 +162,7 @@ class Painter(Widget):
# none found # none found
return None return None
# check whether a position intersects with any of the particles # check whether a candidate particle intersects with any of the particles
def check_interaction_any(self,candidate,offset): def check_interaction_any(self,candidate,offset):
for particle in self.particles: for particle in self.particles:
# do not check interaction if candidate=particle # do not check interaction if candidate=particle
@ -180,32 +170,77 @@ class Painter(Widget):
return True return True
return False return False
# check that a particle can move by delta, and return the closest allowed relative motion # check whether shifting a list of particles by offset makes them interact with all particles
def check_move(self,delta,particle): def check_interaction_list(self,array,offset):
for candidate in array:
if self.check_interaction_any(candidate,offset):
return True
return False
# check whether shifting a list of particles by offset makes them interact with the unselected particles
def check_interaction_unselected_list(self,array,offset):
for candidate in array:
for particle in self.unselected:
if particle.check_interaction(candidate,offset):
return True
return False
# 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):
return True
return False
# try to move all selected particles by delta, adjust if needed to avoid overlap with unselected particles
# we only track whether these elements collide with unselected particles, not with each other
def adjust_move(self,delta):
# 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:
# compute adjustment move due to unselected obstacles
adjusted_delta=self.adjust_move_element(delta,element)
# only keep the smallest delta's (in absolute value)
if abs(adjusted_delta.x)<abs(actual_delta.x):
actual_delta.x=adjusted_delta.x
if abs(adjusted_delta.y)<abs(actual_delta.y):
actual_delta.y=adjusted_delta.y
# try to move by actual_delta
if not self.check_interaction_unselected_list(self.selected,actual_delta):
return actual_delta
else:
# cannot move particles at all, try again
return self.adjust_move(actual_delta)
# trying to move a single element by delta, adjust if needed to avoid overlap with unselected particles
def adjust_move_element(self,delta,element):
# whether newpos is acceptable # whether newpos is acceptable
accept_newpos=True accept_newpos=True
for other in self.particles: for other in self.unselected:
# do not compare a particle to itself for obstacle in other.squares:
if other!=particle: # move would make element overlap with obstacle
# move would make particle overlap with other if obstacle.check_interaction(element.pos+delta):
if other.check_interaction(particle,delta):
accept_newpos=False accept_newpos=False
# check if particle already touches other # check if particle already touches obstacle
if other.check_touch(particle): if obstacle.check_touch(element.pos):
# move along other while remaining stuck # move along obstacle while remaining stuck
# TODO: this assumes other is a square newdelta=obstacle.move_along(delta,element.pos)
newdelta=other.squares[0].move_along(delta,particle.squares[0].pos)
else: else:
newdelta=other.squares[0].move_on_line_to_stick(particle.squares[0].pos,delta) newdelta=obstacle.move_on_line_to_stick(element.pos,delta)
if not self.check_interaction_any(particle,newdelta): if not self.check_interaction_unselected_element(element,newdelta):
return newdelta return newdelta
if accept_newpos: if accept_newpos:
return delta return delta
else: else:
# cannot move particle at all, try again # cannot move particle at all, try again
return self.check_move(newdelta,particle) return self.adjust_move_element(newdelta,element)
# TODO adapt
# write configuration to file # write configuration to file
def write(self,file): def write(self,file):
ff=open(file,"w") ff=open(file,"w")
@ -213,6 +248,7 @@ class Painter(Widget):
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.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() ff.close()
# TODO adapt
# read configuration from file # read configuration from file
def read(self,file): def read(self,file):
self.reset() self.reset()

View File

@ -63,15 +63,6 @@ class Polyomino():
return True return True
return False 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 # square
class Square(Polyomino): class Square(Polyomino):
def __init__(self,x,y,**kwargs): def __init__(self,x,y,**kwargs):

View File

@ -13,3 +13,10 @@ def isint_nonzero(x):
# check that a number is in an interval # check that a number is in an interval
def in_interval(x,a,b): def in_interval(x,a,b):
return x>=a and x<=b return x>=a and x<=b
# remove x from list a
def remove_fromlist(a,x):
if x in a:
a[a.index(x)]=a[len(a)-1]
a=a[:len(a)-1]
return a