Mercurial > traipse_dev
diff orpg/mapper/region.py @ 0:4385a7d0efd1 grumpy-goblin
Deleted and repushed it with the 'grumpy-goblin' branch. I forgot a y
author | sirebral |
---|---|
date | Tue, 14 Jul 2009 16:41:58 -0500 |
parents | |
children | 072ffc1d466f |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/orpg/mapper/region.py Tue Jul 14 16:41:58 2009 -0500 @@ -0,0 +1,645 @@ +# Copyright (C) 2000-2001 The OpenRPG Project +# +# openrpg-dev@lists.sourceforge.net +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# -- +# +# File: mapper/region.py +# Author: Mark Tarrabain +# Maintainer: +# Version: +# $Id: region.py,v 1.10 2006/11/04 21:24:21 digitalxero Exp $ +# +__version__ = "$Id: region.py,v 1.10 2006/11/04 21:24:21 digitalxero Exp $" + +import sys + +# This class manages the edge of a polygon +# it is employed by scan_convert to trace each edge of the polygon as the +# scan converter advances past each Y coordinate +# A bresenham-type of algorithm is used to avoid expensive computation during +# use - no floating point arithmetic is needed. +class Edge: + def __init__(self,start,end): + self.startx=start.X + self.starty=start.Y + self.endx=end.X + self.endy=end.Y + self.dy=self.endy-self.starty + self.cx=self.startx + self.bottom=0 + self.dir = 0 + if self.endx>self.startx: + self.dx = int((self.endx-self.startx)/self.dy) + else: + self.dx = -int((self.startx-self.endx)/self.dy) + self.error = 0 + if self.endx >= self.startx: + self.inc = (self.endx-self.startx)%self.dy + else: + self.inc = (self.startx-self.endx)%self.dy + + def advance(self): + self.cx += self.dx + self.error += self.inc + if (self.error>=self.dy): + if (self.endx<self.startx): + self.cx -= 1 + else: + self.cx += 1 + self.error -= self.dy + +# Utilitarian class for describing a coordinate in 2D space +class IPoint: + def __init__(self): + X=0 + Y=0 + + def __str__(self): + return "(X:" + str(self.X) + ", Y:" + str(self.Y) + ")" + + def pluseq(self,b): + self.X += b.X + self.Y += b.Y + + def minuseq(self,b): + self.X -= b.X + self.Y -= b.Y + + def make(self,x,y): + self.X=x + self.Y=y + return self + + def equals(self,b): + if self.X==b.X and self.Y==b.Y: + return 1 + return 0 + + def less(self,b): + if self.Y<b.Y or (self.Y==b.Y and self.X<b.X): + return 1 + return 0 + + def greater(self,b): + if self.Y>b.Y or (self.Y==b.Y and self.X>b.X): + return 1 + return 0 + +# generic rectangle class +class IRect: + def __init__(self): #initializes to an invalid rectangle + self.left=999999 + self.top=999999 + self.right=-999999 + self.bottom=-999999 + + def __str__(self): + return "[ left:"+str(self.left)+", top:"+str(self.top)+", right:"+str(self.right)+", bottom:"+str(self.bottom)+" ]" + + def ToPoly(self): + thelist = [] + thelist.append(IPoint().make(self.left,self.top)) + thelist.append(IPoint().make(self.right,self.top)) + thelist.append(IPoint().make(self.right,self.bottom)) + thelist.append(IPoint().make(self.left,self.bottom)) + return thelist + + def Width(self): + return self.right-self.left+1 + + def Height(self): + return self.bottom-self.top+1 + + def GetX(self): + return self.left + + def GetY(self): + return self.top + + def GetW(self): + return self.right-self.left+1 + + def GetH(self): + return self.bottom-self.top+1 + + def Size(self): + return IPoint().make(self.right-self.left+1,self.bottom-self.top+1) + + def TopLeft(self): + return IPoint().make(self.left,self.top) + + def BottomRight(self): + return IPoint().make(self.right,self.bottom) + + def Bounds(self): + return IRect().make(0,0,self.right-self.left,self.bottom-self.top) + + def Resize(self,nL,nT,nR,nB): + self.left+=nL + self.top+=nT + self.right+=nR + sel.bottom+=nB + + def IsValid(self): + if (self.left<=self.right and self.top<=self.bottom): + return 1 + return 0 + + def add(self,pt): + return IRect().make(self.left+pt.X,self.top+pt.Y,self.right+pt.X,self.bottom+pt.Y) + + def subtract(self,pt): + return IRect().make(self.left+pt.X,self.top+pt.Y,self.right+pt.X,self.bottom+pt.Y) + + def intersect(self,rect): + return IRect().make(max(self.left,rect.left),max(self.top,rect.top),min(self.right,rect.right),min(self.bottom,rect.bottom)) + + def union(self,rect): + return IRect().make(min(self.left,rect.left),min(self.top,rect.top),max(self.right,rect.right),max(self.bottom,rect.bottom)) + + def equals(self,rect): + if (self.top==rect.top and self.bottom==rect.bottom and self.left==rect.left and self.right==rect.right): + return 1 + return 0 + + def make(self,l,t,r,b): + self.left=l + self.right=r + self.top=t + self.bottom=b + return self + +# A span is a single, contiguous horizontal line. The routine scan_convert +# returns a list of these that describe the polygon +class ISpan: + def __init__(self,x1,x2,ypos): + self.left=x1 + self.right=x2 + self.y=ypos + + def __str__(self): + return "(" + str(self.left) + " to " + str(self.right) + " at " + str(self.y) + ")" + +# clipping rectangle class -- this class is a single node in a linked list +# of rectangles that describe a region + +class ClipRect: + def __init__(self): + self.next=None + self.prev=None + self.bounds=IRect().make(0,0,0,0) + + +# cliprectlist -- the container class that manages a list of cliprects +class ClipRectList: + def __init__(self): + self.first=None + self.last=None + self.count=0 + + def __str__(self): + x="[" + for y in self.GetList(): + x+=" "+str(y.bounds)+" " + x+="]" + return x + +#remove all rectangles from list + def Clear(self): + while(self.first): + rect=self.first + self.first=self.first.next + del rect + self.last=None + self.count=0 + +#add a new clipping rectangle to list + def AddRect(self,rect): + rect.prev=None + rect.next=self.first + if not (self.first is None): + self.first.prev=rect + self.first=rect + if self.last is None: + self.last=rect + self.count += 1 + +#removes the passed clipping rectangle from the list + def RemoveRect(self,rect): + if not (rect.prev is None): + rect.prev.next=rect.next + else: + self.first=rect.next + if not (rect.next is None): + rect.next.prev=rect.prev + else: + self.last=rect.prev + self.count -= 1 + +# find the clipping rectangle at the the beginning of the list, remove it, +# and return it + def RemoveHead(self): + if self.count==0: + return None + rect=self.first + self.first=rect.next + if (self.first is None): + self.last=None + self.count -= 1 + return rect + +# stealrects -- appends the list of clipping rectangles in pclist to the current +# list. removes the entries from pclist + def StealRects(self,pclist): + if pclist.first is None: + return + if self.first is None: + self.first=pclist.first + self.last=pclist.last + else: + self.last.next=pclist.first + pclist.first.prev=self.last + self.last=pclist.last + self.count += pclist.count + pclist.first = None + pclist.last = None + pclist.count = 0 + +# utilitarian procedure to return all clipping rectangles as a Python list + def GetList(self): + result=[] + f = self.first + while f: + result.append(f) + f = f.next + return result + +# some utility procedures, defined outside the scope of any class to ensure +# efficiency + +def _hSortCmp(rect1,rect2): + if (rect1.bounds.left<rect2.bounds.left): + return -1 + if (rect1.bounds.left>rect2.bounds.left): + return 1 + if (rect1.bounds.top<rect2.bounds.top): + return -1 + if (rect1.bounds.top>rect2.bounds.top): + return 1 + return 0 + +def _vSortCmp(rect1,rect2): + if (rect1.bounds.top<rect2.bounds.top): + return -1 + if (rect1.bounds.top>rect2.bounds.top): + return 1 + if (rect1.bounds.left<rect2.bounds.left): + return -1 + if (rect1.bounds.left>rect2.bounds.left): + return 1 + return 0 + +# this is the class for which this whole source file is designed! +# a Region is maintained as an optimal set of non-intersecting rectangles +# whose union is the 2D area of interest. At the end of each of the public +# procedures that may alter the region's structure, the set of rectangles is +# passed through an optimization phase, that joins any rectangles that are found +# to be adjacent and can be combined into a single, larger rectangle + +class IRegion: + def __init__(self): + self.crects=ClipRectList() + self.firstclip=None + + def __AllocClipRect(self): + return ClipRect() + + def __FreeClipRect(self,p): + del p + + def Clear(self): + self.crects.Clear() + + def isEmpty(self): + if self.crects.first: + return 0 + return 1 + + def Copy(self,dest): + dest.Clear() + p=self.crects.first + while p: + dest.__AddRect(p.bounds) + p=p.next + + def __AddRect(self,rect): + cr=self.__AllocClipRect() + cr.bounds=IRect().make(rect.left,rect.top,rect.right,rect.bottom) + self.crects.AddRect(cr) + return cr + +# This is the magic procedure that always makes it possible to merge adjacent +# rectangles later. It is called once for each rectangle to be combined +# with the current region. Basically, it dices the current region into +# horizontal bands where any rectangle is found to be beside the one being +# examined. This tends to leave more rectangles than necessary in the region, +# but they can be culled during the optimization phase when they are combined +# with adjacent rectangles. + + def __Examine(self,rect): + newlist = ClipRectList() + while not (self.crects.first is None): + p = self.crects.RemoveHead() + if (p.bounds.right+1==rect.left or p.bounds.left==rect.right+1): + if (p.bounds.top<rect.top and p.bounds.bottom>rect.bottom): + cnew=[IRect().make(p.bounds.left,p.bounds.top,p.bounds.right,rect.top-1), + IRect().make(p.bounds.left,rect.top,p.bounds.right,rect.bottom), + IRect().make(p.bounds.left,rect.bottom+1,p.bounds.right,p.bounds.bottom)] + elif (p.bounds.top<rect.top and p.bounds.bottom>rect.top): + cnew=[IRect().make(p.bounds.left,p.bounds.top,p.bounds.right,rect.top-1), + IRect().make(p.bounds.left,rect.top,p.bounds.right,p.bounds.bottom)] + elif (p.bounds.top<rect.bottom and p.bounds.bottom>rect.bottom): + cnew=[IRect().make(p.bounds.left,p.bounds.top,p.bounds.right,rect.bottom), + IRect().make(p.bounds.left,rect.bottom+1,p.bounds.right,p.bounds.bottom)] + else: + cnew=[IRect().make(p.bounds.left,p.bounds.top,p.bounds.right,p.bounds.bottom)] + self.__FreeClipRect(p) + for i in cnew: + if (i.IsValid()): + newclip=self.__AllocClipRect() + newclip.bounds=i + newlist.AddRect(newclip) + else: + newlist.AddRect(p) + self.crects.StealRects(newlist) + + def __IncludeRect(self,rect): + tmprg=IRegion() + tmprg.__AddRect(rect) + p = self.crects.first + while p: + tmprg.__ExcludeRect(p.bounds) + p = p.next + self.crects.StealRects(tmprg.crects) + + def IncludeRect(self,rect): + tmprg = IRegion() + tmprg.Clear() + tmprg.__AddRect(rect) + self.__Examine(rect) + p= self.crects.first + while p: + tmprg.__Examine(p.bounds) + p=p.next + p=tmprg.crects.first + while p: + self.__IncludeRect(p.bounds) + p=p.next + self.Optimize() + + def IncludeRegion(self,regn): + tmprg = IRegion() + regn.Copy(tmprg) + p = self.crects.first + while p: + tmprg.__Examine(p.bounds) + p=p.next + p = tmprg.crects.first + while p: + self.__Examine(p.bounds) + self.__IncludeRect(p.bounds) + p = p.next + self.Optimize() + + def __ExcludeRect(self,rect): + newlist=ClipRectList() + while not (self.crects.first is None): + pcclip=self.crects.RemoveHead() + hide=rect.intersect(pcclip.bounds) + if not hide.IsValid(): + newlist.AddRect(pcclip) + else: + #make 4 new rectangles to replace the one taken out + # + # AAAAAAAAAAAA + # AAAAAAAAAAAA + # BBBB CCCC + # BBBB CCCC + # DDDDDDDDDDDD + # DDDDDDDDDDDD + # the center rectangle is the one to remove, rectangles A,B,C and D + # get created. If the rectangle is not in the middle, then the + # corresponding rectangle that should have been on "that side" of + # the current rectangle will have IsValid==False, so it will be + # rejected. + cnew=[IRect().make(pcclip.bounds.left,hide.top,hide.left-1,hide.bottom), + IRect().make(hide.right+1,hide.top,pcclip.bounds.right,hide.bottom), + IRect().make(pcclip.bounds.left,hide.bottom+1,pcclip.bounds.right,pcclip.bounds.bottom), + IRect().make(pcclip.bounds.left,pcclip.bounds.top,pcclip.bounds.right,hide.top-1) ] + self.__FreeClipRect(pcclip) + for i in cnew: + if (i.IsValid()): + newclip=self.__AllocClipRect() + newclip.bounds=i + newlist.AddRect(newclip) + self.crects.last=None + self.crects.StealRects(newlist) + self.Optimize() + + def ExcludeRect(self,rect): + self.__ExcludeRect(rect) + self.Optimize() + + def ExcludeRegion(self,reg): + pclist = reg.crects.first + while pclist: + self.__ExcludeRect(pclist.bounds) + pclist = pclist.next + self.Optimize() + + def __IntersectRect(self,rect): + newlist=ClipRectList() + while not self.crects.first is None: + pcclip=self.crects.RemoveHead() + hide=rect.intersect(pcclip.bounds) + if not hide.IsValid(): + newlist.AddRect(pcclip) + else: + cnew=[IRect().make(pcclip.bounds.left,hide.top,hide.left-1,hide.bottom), + IRect().make(hide.right+1,hide.top,pcclip.bounds.right,hide.bottom), + IRect().make(pcclip.bounds.left,hide.bottom+1,pcclip.bounds.right,pcclip.bounds.bottom), + IRect().make(pcclip.bounds.left,pcclip.bounds.top,pcclip.bounds.right,hide.top-1) ] + self.__FreeClipRect(pcclip) + for i in cnew: + if (i.IsValid()): + newclip=self.__AllocClipRect() + newclip.bounds=i + newlist.AddRect(newclip) + self.crects.last = None + self.crects.StealRects(newlist) + + def IntersectRect(self,rect): + self.__IntersectRect(rect) + self.Optimize() + + def IntersectRegion(self,reg): + clist=ClipRectList() + pcvclip = reg.crects.first + while pcvclip: + pchclip = self.crects.first + while pchclip: + crect=pcvclip.bounds.intersect(pchclip.bounds) + if crect.IsValid(): + newclip=self.__AllocClipRect() + newclip.bounds=crect + clist.AddRect(newclip) + pchclip = pchclip.next + pcvclip = pcvclip.next + self.Clear() + self.crects.StealRects(clist) + self.Optimize() + + def GetBounds(self): + bounds=IRect() + pcclip = self.crects.first + while pcclip: + bounds=bounds.union(pcclip.bounds) + pcclip = pcclip.next + return bounds + + def Optimize(self): + clist=self.crects.GetList() + removed=[] + keepgoing = 1 + while len(clist)>1 and keepgoing: + keepgoing=0 + clist.sort(lambda a,b:_vSortCmp(a,b)) + keys=range(len(clist)-1) + keys.reverse() + for i in keys: + if (clist[i].bounds.right==clist[i+1].bounds.left-1 and + clist[i].bounds.top==clist[i+1].bounds.top and + clist[i].bounds.bottom==clist[i+1].bounds.bottom): + clist[i].bounds.right=clist[i+1].bounds.right + x=clist[i+1] + del clist[i+1] + self.crects.RemoveRect(x) + self.__FreeClipRect(x) + keepgoing=1 + if len(clist)<=1: + break + clist.sort(lambda a,b:_hSortCmp(a,b)) + keys=range(len(clist)-1) + keys.reverse() + for i in keys: + if (clist[i].bounds.bottom==clist[i+1].bounds.top-1 and + clist[i].bounds.left==clist[i+1].bounds.left and + clist[i].bounds.right==clist[i+1].bounds.right): + clist[i].bounds.bottom=clist[i+1].bounds.bottom + x=clist[i+1] + del clist[i+1] + self.crects.RemoveRect(x) + self.__FreeClipRect(x) + keepgoing=1 + + def FromPolygon(self,polypt,mode): + thelist=self.scan_Convert(polypt) + for i in thelist: + r=IRect().make(i.left,i.y,i.right,i.y) + if mode: + self.__Examine(r) + self.__IncludeRect(r) + else: + self.__ExcludeRect(r) + self.Optimize() + + def GetRectList(self): + result=[] + i = self.crects.first + while i: + result.append(i.bounds) + i = i.next + return result + +#Portable implementation to scan-convert a polygon +#from algoritm described in Section 3.6.3 of Foley, et al +#returns a (possibly redundant) list of spans that comprise the polygon +#this routine does not manipulate the current region in any way, but +#is enclosed in this class to isolate its name from the global namespace +#invoke via IRegion().scan_Convert(polypt) + + def scan_Convert(self,polypt): + result=[] + ET = {} # edge table + i = 0 + size = len(polypt) + polylines=[] # list of lines in polygon + y = -1 + for i in xrange(size): + n = (i+1) % size + if polypt[i].Y < polypt[n].Y: + e = Edge(polypt[i],polypt[n]) + e.dir = 1 #moving top to bottom + polylines.append(e) + elif polypt[i].Y > polypt[n].Y: + e = Edge(polypt[n],polypt[i]) + e.dir = -1 #moving bottom to top + polylines.append(e) + elif polypt[i].X != polypt[n].X: # any horizontal lines just get added directly + sx = min(polypt[i].X,polypt[n].X) + ex = max(polypt[i].X,polypt[n].X) + result.append(ISpan(sx,ex,polypt[i].Y)) + size = len(polylines) + for i in xrange(size): + if i == 0 and polylines[i].dir == -1: + n = size-1 + elif (polylines[i].dir == -1): + n = i-1 + else: + n = (i+1) % size + if (polylines[i].dir != polylines[n].dir): #find out if at bottom end + polylines[i].bottom = 1 + if i == 0 or polylines[i].starty < y: + y = polylines[i].starty + if not ET.has_key(polylines[i].starty): + ET[polylines[i].starty] = [] + ET[polylines[i].starty].append(polylines[i]) #add to edge table, indexed by smaller y coordinate + AET = [] #active edge table + while len(AET) > 0 or len(ET) > 0: + if ET.has_key(y): #if current y value has entries in edge tabe + AET.extend(ET[y]) # add them to active edge table + del ET[y] #and delete the entries in the edge table + i = len(AET)-1 + vals = [] + while i >= 0: + if (AET[i].endy == y): #if at the end of this edge's run + if (AET[i].bottom): #check and see if we need one more point + vals.append(AET[i].cx) #we do, add it + del AET[i] # y=end of edge, so remove it + i -= 1 + i = 0 + while i < len(AET): # for every edge in AET + vals.append(AET[i].cx) # add x intercept + AET[i].advance() #and advance the edge one down + i += 1 + vals.sort() + i = 0 + while i < len(vals)-1: #split vals[] array into pairs and output them + result.append(ISpan(vals[i],vals[i+1],y)) + i += 2 + y += 1 + return result