import logging from .epoint import EPoint log=logging.getLogger(__name__) class Corners: def __init__(self,cornerList=[]): self._corners=list(cornerList) self._is_canon=False self._canonizeOrder() ## Adds a new corner if there are less than four, replaces the closest otherwise. def add(self,x,y): a=EPoint(x,y) # for i,c in enumerate(self.corners): # move an improperly placed point # if a.dist(c)<20: # self.corners[i]=a # return if len(self._corners)<4: # add a new corner self._corners.append(a) if len(self._corners)<4: return index,minDist=0,float('inf') # replace the corner closest to the clicked point for i,c in enumerate(self._corners): if a.dist(c) KLMN | ABC | ABD | ACD | BCD | index | swap # ------------ | :-: | :-: | :-: | :-: | ----: | ---- # A BCD | + | + | + | + | 15 | 0 # A BDC | + | + | - | - | 12 | CD # A CBD | - | + | + | - | 6 | BC # A CDB | - | - | + | + | 3 | AB # A DBC | + | - | - | + | 9 | AD # A DCB | - | - | - | - | 0 | BD # # For every non-degenerate quadrangle, there must be 1-3 edges going right-left (from a higher to a lower x coordinate). # From these pick the one with the lowest slope (dy/dx) and declare its ending point the upper left corner. For the same slope pick the one further left. # # @return True for a convex quadrangle, False for concave and degenerate cases. def _canonizeOrder(self): def false(): self._is_canon=False return False if len(self._corners)!=4: return false() a,b,c,d=self._corners abc=doubleTriangleArea(a,b,c) abd=doubleTriangleArea(a,b,d) acd=doubleTriangleArea(a,c,d) bcd=doubleTriangleArea(b,c,d) if any(x==0 for x in (abc,abd,acd,bcd)): return false() # collinear degenerate swaps=[(1,3),(0,1),(1,2),(0,3),(2,3),(0,0)] index=(8 if abc>0 else 0)|(4 if abd>0 else 0)|(2 if acd>0 else 0)|(1 if bcd>0 else 0) if index%3!=0: return false() # concave degenerate swap=swaps[index//3] self._corners[swap[0]], self._corners[swap[1]] = self._corners[swap[1]], self._corners[swap[0]] # counter-clockwise order kIndex=None lowestSlope=float("inf") for i,corner in enumerate(self._corners): # find the NK edge: going right-left with the lowest slope, secondarily the one going down ii=(i+1)%4 slope=abs(getSlope(corner, self._corners[ii])) if corner.x>self._corners[ii].x and (slope < lowestSlope or (slope == lowestSlope and corner.y < self._corners[ii].y)): kIndex=ii lowestSlope=slope self._corners= self._corners[kIndex:] + self._corners[:kIndex] # rotate the upper left corner to the first place self._is_canon=True # success return True def scale(self,scale): self._corners=[c * scale for c in self._corners] def __iter__(self): return iter(self._corners) def __len__(self): return len(self._corners) def is_canon(self): return self._is_canon ## Computes twice the area of the triangle formed by points a,b,c. # # @return positive value for points oriented counter-clockwise, negative for clockwise, zero for degenerate cases. def doubleTriangleArea(a,b,c): return (a.x-b.x)*(c.y-a.y)-(c.x-a.x)*(a.y-b.y) def getSlope(a,b): if(b.x==a.x): return float("inf") return (b.y-a.y)/(b.x-a.x)