view orpg/mapper/grid.py @ 32:c223c1051f4a traipse_dev

Update Manager moves into Beta 0.3 with a nice new look. The skeleton is designed, not the functionality will be added.
author sirebral
date Sun, 02 Aug 2009 16:43:55 -0500
parents 072ffc1d466f
children 449a8900f9ac
line wrap: on
line source

# 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/gird.py
# Author: OpenRPG Team
# Maintainer:
# Version:
#   $Id: grid.py,v 1.29 2007/12/07 20:39:49 digitalxero Exp $
#
# Description:
#
__version__ = "$Id: grid.py,v 1.29 2007/12/07 20:39:49 digitalxero Exp $"

from base import *
from isometric import *
from miniatures import SNAPTO_ALIGN_CENTER
from miniatures import SNAPTO_ALIGN_TL
from math import floor

# Grid mode constants
GRID_RECTANGLE = 0
GRID_HEXAGON = 1
GRID_ISOMETRIC = 2
LINE_NONE = 0
LINE_DOTTED = 1
LINE_SOLID = 2
RATIO_DEFAULT = 2.0

##-----------------------------
## grid layer
##-----------------------------
class grid_layer(layer_base):

    def __init__(self, canvas):
        layer_base.__init__(self)
        self.canvas = canvas
        self.iso_ratio = RATIO_DEFAULT  #2:1 isometric ratio
        self.mapscale = 1.0
        self.unit_size = 100
        self.unit_size_y = 100
        #unit_widest and unit_offset are for the Hex Grid only. 
        #These are mathematics to figure out the exact center of the hex
        self.unit_widest = 100
        self.unit_offset = 100
        #size_ratio is the size ajustment for Hex and ISO to make them more accurate
        self.size_ratio = 1.5
        self.snap = True
        self.color = wx.BLACK# = color.Get()
        #self.color = cmpColour(r,g,b)
        self.r_h = RGBHex()
        self.mode = GRID_RECTANGLE
        self.line = LINE_NONE
        # Keep logic for different modes in different functions
        self.grid_hit_test = self.grid_hit_test_rect
        self.get_top_corner = self.get_top_corner_rect
        self.layerDraw = self.draw_rect
        self.isUpdated = True

    def get_unit_size(self):
        return self.unit_size

    def get_iso_ratio(self):
        return self.iso_ratio

    def get_mode(self):
        return self.mode

    def get_color(self):
        return self.color

    def get_line_type(self):
        return self.line

    def is_snap(self):
        return self.snap

    def get_snapped_to_pos(self, pos, snap_to_align, mini_width, mini_height):
        grid_pos = self.grid_hit_test(pos)
        if grid_pos is not None:
            topLeft = self.get_top_corner(grid_pos)#  get the top corner for this grid cell
            if snap_to_align == SNAPTO_ALIGN_CENTER:
                if self.mode == GRID_HEXAGON:
                    x = topLeft.x + (((self.unit_size/1.75) - mini_width) /2)
                    y = topLeft.y + ((self.unit_size - mini_height) /2)
                elif self.mode == GRID_ISOMETRIC:
                    x = (topLeft.x)-(mini_width/2)
                    y = (topLeft.y)-(mini_height)
                else:# GRID_RECTANGLE
                    x = topLeft.x + ((self.unit_size - mini_width) / 2)
                    y = topLeft.y + ((self.unit_size_y - mini_height) /2)
            else:
                x = topLeft.x
                y = topLeft.y
            return cmpPoint(int(x),int(y))                                           #  Set the pos attribute
        else: return cmpPoint(int(pos.x),int(pos.y))

    def set_rect_mode(self):
        "switch grid to rectangular mode"
        self.mode = GRID_RECTANGLE
        self.grid_hit_test = self.grid_hit_test_rect
        self.get_top_corner = self.get_top_corner_rect
        self.layerDraw = self.draw_rect
        self.unit_size_y = self.unit_size

    def set_hex_mode(self):
        "switch grid to hexagonal mode"
        self.mode = GRID_HEXAGON
        self.grid_hit_test = self.grid_hit_test_hex
        self.get_top_corner = self.get_top_corner_hex
        self.layerDraw = self.draw_hex
        self.unit_size_y = self.unit_size
        self.unit_offset = sqrt(pow((self.unit_size/self.size_ratio ),2)-pow((self.unit_size/2),2))
        self.unit_widest = (self.unit_offset*2)+(self.unit_size/self.size_ratio )

    def set_iso_mode(self):
        "switch grid to hexagonal mode"
        self.mode = GRID_ISOMETRIC
        self.grid_hit_test = self.grid_hit_test_iso
        self.get_top_corner = self.get_top_corner_iso
        self.layerDraw = self.draw_iso
        self.unit_size_y = self.unit_size

    def set_line_none(self):
        "switch to no line mode for grid"
        self.line = LINE_NONE

    def set_line_dotted(self):
        "switch to dotted line mode for grid"
        self.line = LINE_DOTTED

    def set_line_solid(self):
        "switch to solid line mode for grid"
        self.line = LINE_SOLID

    def grid_hit_test_rect(self,pos):
        "return grid pos (w,h) on rect map from pos"
        if self.unit_size and self.snap: return cmpPoint(int(pos.x/self.unit_size), int(pos.y/self.unit_size))
        else: return None

    def grid_hit_test_hex(self,pos):
        "return grid pos (w,h) on hex map from pos"
        if self.unit_size and self.snap:
            # rectangular repeat patern is as follows (unit_size is the height of a hex)
            hex_side = int(self.unit_size/1.75)
            half_height = int(self.unit_size/2)
            height = int(self.unit_size)
            #_____
            #     \       /
            #      \_____/
            #      /     \
            #_____/       \
            col = int(pos.x/(hex_side*1.5))
            row = int(pos.y/height)
            (px, py) = (pos.x-(col*(hex_side*1.5)), pos.y-(row*height))
            # adjust for the odd columns' rows being staggered lower
            if col % 2 == 1:
                if py < half_height:
                    row = row - 1
                    py = py + half_height
                else: py = py - half_height
            # adjust for top right corner
            if (px * height - py * hex_side) > height * hex_side:
                if col % 2 == 0: row = row - 1
                col = col + 1
            # adjust for bottom right corner
            elif (px * height + py * hex_side) > 2 * height * hex_side:
                if col%2==1: row = row + 1
                col = col + 1
            return cmpPoint(col, row)
        else: return None

    def grid_hit_test_iso(self,pos):
        "return grid pos (w,h) on isometric map from pos"
        if self.unit_size and self.snap:
            height = self.unit_size*self.size_ratio/self.iso_ratio
            width = self.unit_size*self.size_ratio
            iso_unit_size = height * width
            # convert to isometric pos which has an origin of cell (0,0)
            # x-ord increasing as you go up and right, y-ord increasing as you go down and right
            # this is the transformation from grid co-ord to iso co-ords
            iso_x = (pos.x*height) - (pos.y*width) + (iso_unit_size/2)
            iso_y = (pos.x*height) + (pos.y*width) - (iso_unit_size/2)
            #
            #  /\
            # /  \
            #/    \
            #\    /
            # \  /
            #  \/
            # so the exact isomorphic (0,0) is the left corner of the first (ie. top left) diamond
            # this is at grid co-ordinate (0, height/2)
            # the top corner of the first diamond is grid co-ord (width/2, 0)
            # and therefore (per transformation above) is at iso co-ord (iso_unit_size, 0)
            # the bottom corner of the first diamond is grid co-ord (width/2, height)
            # and therefore (per transformation above) is at iso co-ord (0, iso_unit_size)

            # the calculation is now as simple as the rectangle case, but using iso co-ords
            return cmpPoint(floor(iso_x/iso_unit_size), floor(iso_y/iso_unit_size))
        else: return None

    def get_top_corner_iso(self, iso_pos):
        "return upper left of a iso grid pos"
        # for whatever reason the iso grid returns the center of the diamond for "top left corner"
        if self.unit_size:
            half_height = self.unit_size*self.size_ratio/(2*self.iso_ratio)
            half_width = self.unit_size*self.size_ratio/2
            # convert back into grid co-ordinates of center of diamond
            grid_x = (iso_pos.y*half_width) + (iso_pos.x*half_width) + half_width
            grid_y = (iso_pos.y*half_height) - (iso_pos.x*half_height) + half_height
            return cmpPoint(int(grid_x), int(grid_y))
        else: return None

    def get_top_corner_rect(self,grid_pos):
        "return upper left of a rect grid pos"
        if self.unit_size: return cmpPoint(grid_pos[0]*self.unit_size,grid_pos[1]*self.unit_size)
        else: return None

    def get_top_corner_hex(self,grid_pos):
        "return upper left of a hex grid pos"
        if self.unit_size:
            # We can get our x value directly, y is trickier
            temp_x = (((self.unit_size/1.75)*1.5)*grid_pos[0])
            temp_y = self.unit_size*grid_pos[1]
            # On odd columns we have to slide down slightly
            if grid_pos[0] % 2:
                temp_y += self.unit_size/2
            return cmpPoint(temp_x,temp_y)
        else: return None

    def set_grid(self, unit_size, snap, color, mode, line, ratio=None):
        self.unit_size = unit_size
        if ratio != None: self.iso_ratio = ratio
        self.snap = snap
        self.set_color(color)
        self.SetMode(mode)
        self.SetLine(line)

    def SetLine(self,line):
        if line == LINE_NONE: self.set_line_none()
        elif line == LINE_DOTTED: self.set_line_dotted()
        elif line == LINE_SOLID: self.set_line_solid()

    def SetMode(self, mode):
        if mode == GRID_RECTANGLE: self.set_rect_mode()
        elif mode == GRID_HEXAGON: self.set_hex_mode()
        elif mode == GRID_ISOMETRIC: self.set_iso_mode()

    def return_grid(self):
        return self.canvas.size

    def set_color(self,color):
        (r,g,b) = color.Get()
        self.color = cmpColour(r,g,b)

    def draw_iso(self,dc,topleft,clientsize):
        if not self.unit_size: return
        if self.line == LINE_NONE: return
        if self.line == LINE_SOLID: dc.SetPen(wx.Pen(self.color,1,wx.SOLID))
        else: dc.SetPen(wx.Pen(self.color,1,wx.DOT))
        sz = self.canvas.size

        # Enable DC optimizations if available on a platform
        dc.BeginDrawing()

        # create IsoGrid helper object
        IG = IsoGrid(self.unit_size*self.size_ratio)
        IG.Ratio(self.iso_ratio)
        rows = int(min(clientsize[1]+topleft[1],sz[1])/IG.height)
        cols = int(min(clientsize[0]+topleft[0],sz[0])/IG.width)
        for y in range(rows+1):
            for x in range(cols+1):
                IG.BoundPlace((x*IG.width),(y*IG.height))
                x1,y1 = IG.Top()
                x2,y2 = IG.Left()
                dc.DrawLine(x1,y1,x2,y2)
                x1,y1 = IG.Left()
                x2,y2 = IG.Bottom()
                dc.DrawLine(x1,y1,x2,y2)
                x1,y1 = IG.Bottom()
                x2,y2 = IG.Right()
                dc.DrawLine(x1,y1,x2,y2)
                x1,y1 = IG.Right()
                x2,y2 = IG.Top()
                dc.DrawLine(x1,y1,x2,y2)
        # Enable DC optimizations if available on a platform
        dc.EndDrawing()
        dc.SetPen(wx.NullPen)
        # Disable pen/brush optimizations to prevent any odd effects elsewhere

    def draw_rect(self,dc,topleft,clientsize):
        if self.unit_size:
            draw = 1
            # Enable pen/brush optimizations if available on a platform
            if self.line == LINE_NONE: draw = 0
            elif self.line == LINE_SOLID: dc.SetPen(wx.Pen(self.color,1,wx.SOLID))
            else: dc.SetPen(wx.Pen(self.color,1,wx.DOT))
            if draw:
                sz = self.canvas.size
                # Enable DC optimizations if available on a platform
                dc.BeginDrawing()
                # Now, draw the map grid
                x = 0
                s = self.unit_size
                x = int(topleft[0]/s)*s
                mx = min(clientsize[0]+topleft[0],sz[0])
                my = min(clientsize[1]+topleft[1],sz[1])
                while x < mx:
                    dc.DrawLine(x,topleft[1],x,my)
                    x += self.unit_size
                y = 0
                y = int (topleft[1]/s)*s
                while y < my:
                    dc.DrawLine(topleft[0],y,mx,y)
                    y += self.unit_size
                # Enable DC optimizations if available on a platform
                dc.EndDrawing()
                dc.SetPen(wx.NullPen)
            # Disable pen/brush optimizations to prevent any odd effects elsewhere

    def draw_hex(self,dc,topleft,clientsize):
        if self.unit_size:
            draw = 1
            # Enable pen/brush optimizations if available on a platform
            if self.line == LINE_NONE: draw = 0
            elif self.line == LINE_SOLID: dc.SetPen(wx.Pen(self.color,1,wx.SOLID))
            else: dc.SetPen(wx.Pen(self.color,1,wx.DOT))
            if draw:
                sz = self.canvas.size
                x = 0
                A = self.unit_size/1.75 #Side Length
                B = self.unit_size #The width between any two sides
                D = self.unit_size/2 #The distance from the top to the middle of the hex
                C = self.unit_size/3.5 #The distance from the point of the hex to the point where the top line starts

                #   _____
                #  /     \
                # /       \
                # \       /
                #  \_____/

                startx=int(topleft[0]/(3*A))*(3*A)
                starty=int(topleft[1]/B)*B
                y = starty
                mx = min(clientsize[0]+topleft[0],sz[0])
                my = min(clientsize[1]+topleft[1],sz[1])
                while y < my:
                    x = startx
                    lineArray = []
                    while x < mx:
                        #The top / Bottom of the Hex
                        lineArray.append((x, y))
                        lineArray.append((x+A, y))
                        #The Right Top Side of the Hex
                        lineArray.append((x+A, y))
                        lineArray.append((x+A+C, y+D))
                        #The Right Bottom Side of the Hex
                        lineArray.append((x+A+C, y+D))
                        lineArray.append((x+A, y+B))
                        #The Top / of the Middle Hex
                        lineArray.append((x+A+C, y+D))
                        lineArray.append((x+A+C+A, y+D))
                        #The Left Bottom Side of the Hex
                        lineArray.append((x+A+C+A, y+D))
                        lineArray.append((x+A+C+A+C, y+B))
                        #The left Top Side of the Hex
                        lineArray.append((x+A+C+A, y+D))
                        lineArray.append((x+A+C+A+C, y))
                        x += A*3
                    y += B
                    dc.DrawLines(lineArray)
                dc.SetPen(wx.NullPen)
            # Disable pen/brush optimizations to prevent any odd effects elsewhere

    def layerToXML(self,action = "update"):
        xml_str = "<grid"
        if self.color != None:
            (red,green,blue) = self.color.Get()
            hexcolor = self.r_h.hexstring(red, green, blue)
            xml_str += " color='" + hexcolor + "'"
        if self.unit_size != None: xml_str += " size='" + str(self.unit_size) + "'"
        if self.iso_ratio != None: xml_str += " ratio='" + str(self.iso_ratio) + "'"
        if self.snap != None:
            if self.snap: xml_str += " snap='1'"
            else: xml_str += " snap='0'"
        if self.mode != None: xml_str+= "  mode='" + str(self.mode) + "'"
        if self.line != None: xml_str+= " line='" + str(self.line) + "'"
        xml_str += "/>"
        if (action == "update" and self.isUpdated) or action == "new":
            self.isUpdated = False
            return xml_str
        else: return ''

    def layerTakeDOM(self, xml_dom):
        if xml_dom.hasAttribute("color"):
            r,g,b = self.r_h.rgb_tuple(xml_dom.getAttribute("color"))
            self.set_color(cmpColour(r,g,b))
        #backwards compatible with non-isometric map formated clients
        ratio = RATIO_DEFAULT
        if xml_dom.hasAttribute("ratio"): ratio = xml_dom.getAttribute("ratio")
        if xml_dom.hasAttribute("mode"):
            self.SetMode(int(xml_dom.getAttribute("mode")))
        if xml_dom.hasAttribute("size"):
            self.unit_size = int(xml_dom.getAttribute("size"))
            self.unit_size_y = self.unit_size
        if xml_dom.hasAttribute("snap"):
            if (xml_dom.getAttribute("snap") == 'True') or (xml_dom.getAttribute("snap") == "1"): self.snap = True
            else: self.snap = False
        if xml_dom.hasAttribute("line"):
            self.SetLine(int(xml_dom.getAttribute("line")))