comparison orpg/mapper/grid.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 78407d627cba
comparison
equal deleted inserted replaced
-1:000000000000 0:4385a7d0efd1
1 # Copyright (C) 2000-2001 The OpenRPG Project
2 #
3 # openrpg-dev@lists.sourceforge.net
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 # --
19 #
20 # File: mapper/gird.py
21 # Author: OpenRPG Team
22 # Maintainer:
23 # Version:
24 # $Id: grid.py,v 1.29 2007/12/07 20:39:49 digitalxero Exp $
25 #
26 # Description:
27 #
28 __version__ = "$Id: grid.py,v 1.29 2007/12/07 20:39:49 digitalxero Exp $"
29
30 from base import *
31 from isometric import *
32 from miniatures import SNAPTO_ALIGN_CENTER
33 from miniatures import SNAPTO_ALIGN_TL
34 from math import floor
35
36 # Grid mode constants
37 GRID_RECTANGLE = 0
38 GRID_HEXAGON = 1
39 GRID_ISOMETRIC = 2
40 LINE_NONE = 0
41 LINE_DOTTED = 1
42 LINE_SOLID = 2
43 RATIO_DEFAULT = 2.0
44
45 ##-----------------------------
46 ## grid layer
47 ##-----------------------------
48 class grid_layer(layer_base):
49
50 def __init__(self, canvas):
51 layer_base.__init__(self)
52 self.canvas = canvas
53 self.iso_ratio = RATIO_DEFAULT #2:1 isometric ratio
54 self.mapscale = 1.0
55 self.unit_size = 100
56 self.unit_size_y = 100
57 #unit_widest and unit_offset are for the Hex Grid only. these are mathmatics to figure out the exact center of the hex
58 self.unit_widest = 100
59 self.unit_offset = 100
60 #size_ratio is the size ajustment for Hex and ISO to make them more accurate
61 self.size_ratio = 1.5
62 self.snap = True
63 self.color = wx.BLACK# = color.Get()
64 #self.color = cmpColour(r,g,b)
65 self.r_h = RGBHex()
66 self.mode = GRID_RECTANGLE
67 self.line = LINE_NONE
68 # Keep logic for different modes in different functions
69 self.grid_hit_test = self.grid_hit_test_rect
70 self.get_top_corner = self.get_top_corner_rect
71 self.layerDraw = self.draw_rect
72 self.isUpdated = True
73
74 def get_unit_size(self):
75 return self.unit_size
76
77 def get_iso_ratio(self):
78 return self.iso_ratio
79
80 def get_mode(self):
81 return self.mode
82
83 def get_color(self):
84 return self.color
85
86 def get_line_type(self):
87 return self.line
88
89 def is_snap(self):
90 return self.snap
91
92 def get_snapped_to_pos(self, pos, snap_to_align, mini_width, mini_height):
93 grid_pos = self.grid_hit_test(pos)
94 if grid_pos is not None:
95 topLeft = self.get_top_corner(grid_pos)# get the top corner for this grid cell
96 if snap_to_align == SNAPTO_ALIGN_CENTER:
97 if self.mode == GRID_HEXAGON:
98 x = topLeft.x + (((self.unit_size/1.75) - mini_width) /2)
99 y = topLeft.y + ((self.unit_size - mini_height) /2)
100 elif self.mode == GRID_ISOMETRIC:
101 x = (topLeft.x)-(mini_width/2)
102 y = (topLeft.y)-(mini_height)
103 else:# GRID_RECTANGLE
104 x = topLeft.x + ((self.unit_size - mini_width) / 2)
105 y = topLeft.y + ((self.unit_size_y - mini_height) /2)
106 else:
107 x = topLeft.x
108 y = topLeft.y
109 return cmpPoint(int(x),int(y)) # Set the pos attribute
110 else:
111 return cmpPoint(int(pos.x),int(pos.y))
112
113 def set_rect_mode(self):
114 "switch grid to rectangular mode"
115 self.mode = GRID_RECTANGLE
116 self.grid_hit_test = self.grid_hit_test_rect
117 self.get_top_corner = self.get_top_corner_rect
118 self.layerDraw = self.draw_rect
119 self.unit_size_y = self.unit_size
120
121 def set_hex_mode(self):
122 "switch grid to hexagonal mode"
123 self.mode = GRID_HEXAGON
124 self.grid_hit_test = self.grid_hit_test_hex
125 self.get_top_corner = self.get_top_corner_hex
126 self.layerDraw = self.draw_hex
127 self.unit_size_y = self.unit_size
128 self.unit_offset = sqrt(pow((self.unit_size/self.size_ratio ),2)-pow((self.unit_size/2),2))
129 self.unit_widest = (self.unit_offset*2)+(self.unit_size/self.size_ratio )
130
131 def set_iso_mode(self):
132 "switch grid to hexagonal mode"
133 self.mode = GRID_ISOMETRIC
134 self.grid_hit_test = self.grid_hit_test_iso
135 self.get_top_corner = self.get_top_corner_iso
136 self.layerDraw = self.draw_iso
137 self.unit_size_y = self.unit_size
138
139 def set_line_none(self):
140 "switch to no line mode for grid"
141 self.line = LINE_NONE
142
143 def set_line_dotted(self):
144 "switch to dotted line mode for grid"
145 self.line = LINE_DOTTED
146
147 def set_line_solid(self):
148 "switch to solid line mode for grid"
149 self.line = LINE_SOLID
150
151 def grid_hit_test_rect(self,pos):
152 "return grid pos (w,h) on rect map from pos"
153 if self.unit_size and self.snap:
154 return cmpPoint(int(pos.x/self.unit_size), int(pos.y/self.unit_size))
155 else:
156 return None
157
158 def grid_hit_test_hex(self,pos):
159 "return grid pos (w,h) on hex map from pos"
160 if self.unit_size and self.snap:
161 # rectangular repeat patern is as follows (unit_size is the height of a hex)
162 hex_side = int(self.unit_size/1.75)
163 half_height = int(self.unit_size/2)
164 height = int(self.unit_size)
165 #_____
166 # \ /
167 # \_____/
168 # / \
169 #_____/ \
170 col = int(pos.x/(hex_side*1.5))
171 row = int(pos.y/height)
172 (px, py) = (pos.x-(col*(hex_side*1.5)), pos.y-(row*height))
173 # adjust for the odd columns' rows being staggered lower
174 if col % 2 == 1:
175 if py < half_height:
176 row = row - 1
177 py = py + half_height
178 else:
179 py = py - half_height
180 # adjust for top right corner
181 if (px * height - py * hex_side) > height * hex_side:
182 if col % 2 == 0:
183 row = row - 1
184 col = col + 1
185 # adjust for bottom right corner
186 elif (px * height + py * hex_side) > 2 * height * hex_side:
187 if col%2==1:
188 row = row + 1
189 col = col + 1
190 return cmpPoint(col, row)
191 else:
192 return None
193
194 def grid_hit_test_iso(self,pos):
195 "return grid pos (w,h) on isometric map from pos"
196 if self.unit_size and self.snap:
197 height = self.unit_size*self.size_ratio/self.iso_ratio
198 width = self.unit_size*self.size_ratio
199 iso_unit_size = height * width
200 # convert to isometric pos which has an origin of cell (0,0)
201 # x-ord increasing as you go up and right, y-ord increasing as you go down and right
202 # this is the transformation from grid co-ord to iso co-ords
203 iso_x = (pos.x*height) - (pos.y*width) + (iso_unit_size/2)
204 iso_y = (pos.x*height) + (pos.y*width) - (iso_unit_size/2)
205 #
206 # /\
207 # / \
208 #/ \
209 #\ /
210 # \ /
211 # \/
212 # so the exact isomorphic (0,0) is the left corner of the first (ie. top left) diamond
213 # this is at grid co-ordinate (0, height/2)
214 # the top corner of the first diamond is grid co-ord (width/2, 0)
215 # and therefore (per transformation above) is at iso co-ord (iso_unit_size, 0)
216 # the bottom corner of the first diamond is grid co-ord (width/2, height)
217 # and therefore (per transformation above) is at iso co-ord (0, iso_unit_size)
218
219 # the calculation is now as simple as the rectangle case, but using iso co-ords
220 return cmpPoint(floor(iso_x/iso_unit_size), floor(iso_y/iso_unit_size))
221 else:
222 return None
223
224 def get_top_corner_iso(self, iso_pos):
225 "return upper left of a iso grid pos"
226 # for whatever reason the iso grid returns the center of the diamond for "top left corner"
227 if self.unit_size:
228 half_height = self.unit_size*self.size_ratio/(2*self.iso_ratio)
229 half_width = self.unit_size*self.size_ratio/2
230 # convert back into grid co-ordinates of center of diamond
231 grid_x = (iso_pos.y*half_width) + (iso_pos.x*half_width) + half_width
232 grid_y = (iso_pos.y*half_height) - (iso_pos.x*half_height) + half_height
233 return cmpPoint(int(grid_x), int(grid_y))
234 else:
235 return None
236
237 def get_top_corner_rect(self,grid_pos):
238 "return upper left of a rect grid pos"
239 if self.unit_size:
240 return cmpPoint(grid_pos[0]*self.unit_size,grid_pos[1]*self.unit_size)
241 else:
242 return None
243
244 def get_top_corner_hex(self,grid_pos):
245 "return upper left of a hex grid pos"
246 if self.unit_size:
247 # We can get our x value directly, y is trickier
248 temp_x = (((self.unit_size/1.75)*1.5)*grid_pos[0])
249 temp_y = self.unit_size*grid_pos[1]
250 # On odd columns we have to slide down slightly
251 if grid_pos[0] % 2:
252 temp_y += self.unit_size/2
253 return cmpPoint(temp_x,temp_y)
254 else:
255 return None
256
257 def set_grid(self, unit_size, snap, color, mode, line, ratio=None):
258 self.unit_size = unit_size
259 if ratio != None:
260 self.iso_ratio = ratio
261 self.snap = snap
262 self.set_color(color)
263 self.SetMode(mode)
264 self.SetLine(line)
265
266 def SetLine(self,line):
267 if line == LINE_NONE:
268 self.set_line_none()
269 elif line == LINE_DOTTED:
270 self.set_line_dotted()
271 elif line == LINE_SOLID:
272 self.set_line_solid()
273
274 def SetMode(self, mode):
275 if mode == GRID_RECTANGLE:
276 self.set_rect_mode()
277 elif mode == GRID_HEXAGON:
278 self.set_hex_mode()
279 elif mode == GRID_ISOMETRIC:
280 self.set_iso_mode()
281
282 def return_grid(self):
283 return self.canvas.size
284
285 def set_color(self,color):
286 (r,g,b) = color.Get()
287 self.color = cmpColour(r,g,b)
288
289 def draw_iso(self,dc,topleft,clientsize):
290 if not self.unit_size: return
291 if self.line == LINE_NONE: return
292 if self.line == LINE_SOLID:
293 dc.SetPen(wx.Pen(self.color,1,wx.SOLID))
294 else:
295 dc.SetPen(wx.Pen(self.color,1,wx.DOT))
296 sz = self.canvas.size
297
298 # Enable DC optimizations if available on a platform
299 dc.BeginDrawing()
300
301 # create IsoGrid helper object
302 IG = IsoGrid(self.unit_size*self.size_ratio)
303 IG.Ratio(self.iso_ratio)
304 rows = int(min(clientsize[1]+topleft[1],sz[1])/IG.height)
305 cols = int(min(clientsize[0]+topleft[0],sz[0])/IG.width)
306 for y in range(rows+1):
307 for x in range(cols+1):
308 IG.BoundPlace((x*IG.width),(y*IG.height))
309 x1,y1 = IG.Top()
310 x2,y2 = IG.Left()
311 dc.DrawLine(x1,y1,x2,y2)
312 x1,y1 = IG.Left()
313 x2,y2 = IG.Bottom()
314 dc.DrawLine(x1,y1,x2,y2)
315 x1,y1 = IG.Bottom()
316 x2,y2 = IG.Right()
317 dc.DrawLine(x1,y1,x2,y2)
318 x1,y1 = IG.Right()
319 x2,y2 = IG.Top()
320 dc.DrawLine(x1,y1,x2,y2)
321 # Enable DC optimizations if available on a platform
322 dc.EndDrawing()
323 dc.SetPen(wx.NullPen)
324 # Disable pen/brush optimizations to prevent any odd effects elsewhere
325
326 def draw_rect(self,dc,topleft,clientsize):
327 if self.unit_size:
328 draw = 1
329 # Enable pen/brush optimizations if available on a platform
330 if self.line == LINE_NONE:
331 draw = 0
332 elif self.line == LINE_SOLID:
333 dc.SetPen(wx.Pen(self.color,1,wx.SOLID))
334 else:
335 dc.SetPen(wx.Pen(self.color,1,wx.DOT))
336 if draw:
337 sz = self.canvas.size
338 # Enable DC optimizations if available on a platform
339 dc.BeginDrawing()
340 # Now, draw the map grid
341 x = 0
342 s = self.unit_size
343 x = int(topleft[0]/s)*s
344 mx = min(clientsize[0]+topleft[0],sz[0])
345 my = min(clientsize[1]+topleft[1],sz[1])
346 while x < mx:
347 dc.DrawLine(x,topleft[1],x,my)
348 x += self.unit_size
349 y = 0
350 y = int (topleft[1]/s)*s
351 while y < my:
352 dc.DrawLine(topleft[0],y,mx,y)
353 y += self.unit_size
354 # Enable DC optimizations if available on a platform
355 dc.EndDrawing()
356 dc.SetPen(wx.NullPen)
357 # Disable pen/brush optimizations to prevent any odd effects elsewhere
358
359 def draw_hex(self,dc,topleft,clientsize):
360 if self.unit_size:
361 draw = 1
362 # Enable pen/brush optimizations if available on a platform
363 if self.line == LINE_NONE:
364 draw = 0
365 elif self.line == LINE_SOLID:
366 dc.SetPen(wx.Pen(self.color,1,wx.SOLID))
367 else:
368 dc.SetPen(wx.Pen(self.color,1,wx.DOT))
369 if draw:
370 sz = self.canvas.size
371 x = 0
372 A = self.unit_size/1.75 #Side Length
373 B = self.unit_size #The width between any two sides
374 D = self.unit_size/2 #The distance from the top to the middle of the hex
375 C = self.unit_size/3.5 #The distance from the point of the hex to the point where the top line starts
376
377 # _____
378 # / \
379 # / \
380 # \ /
381 # \_____/
382
383 startx=int(topleft[0]/(3*A))*(3*A)
384 starty=int(topleft[1]/B)*B
385 y = starty
386 mx = min(clientsize[0]+topleft[0],sz[0])
387 my = min(clientsize[1]+topleft[1],sz[1])
388 while y < my:
389 x = startx
390 lineArray = []
391 while x < mx:
392 #The top / Bottom of the Hex
393 lineArray.append((x, y))
394 lineArray.append((x+A, y))
395 #The Right Top Side of the Hex
396 lineArray.append((x+A, y))
397 lineArray.append((x+A+C, y+D))
398 #The Right Bottom Side of the Hex
399 lineArray.append((x+A+C, y+D))
400 lineArray.append((x+A, y+B))
401 #The Top / of the Middle Hex
402 lineArray.append((x+A+C, y+D))
403 lineArray.append((x+A+C+A, y+D))
404 #The Left Bottom Side of the Hex
405 lineArray.append((x+A+C+A, y+D))
406 lineArray.append((x+A+C+A+C, y+B))
407 #The left Top Side of the Hex
408 lineArray.append((x+A+C+A, y+D))
409 lineArray.append((x+A+C+A+C, y))
410 x += A*3
411 y += B
412 dc.DrawLines(lineArray)
413 dc.SetPen(wx.NullPen)
414 # Disable pen/brush optimizations to prevent any odd effects elsewhere
415
416 def layerToXML(self,action = "update"):
417 xml_str = "<grid"
418 if self.color != None:
419 (red,green,blue) = self.color.Get()
420 hexcolor = self.r_h.hexstring(red, green, blue)
421 xml_str += " color='" + hexcolor + "'"
422 if self.unit_size != None:
423 xml_str += " size='" + str(self.unit_size) + "'"
424 if self.iso_ratio != None:
425 xml_str += " ratio='" + str(self.iso_ratio) + "'"
426 if self.snap != None:
427 if self.snap:
428 xml_str += " snap='1'"
429 else:
430 xml_str += " snap='0'"
431 if self.mode != None:
432 xml_str+= " mode='" + str(self.mode) + "'"
433 if self.line != None:
434 xml_str+= " line='" + str(self.line) + "'"
435 xml_str += "/>"
436 if (action == "update" and self.isUpdated) or action == "new":
437 self.isUpdated = False
438 return xml_str
439 else:
440 return ''
441
442 def layerTakeDOM(self, xml_dom):
443 if xml_dom.hasAttribute("color"):
444 r,g,b = self.r_h.rgb_tuple(xml_dom.getAttribute("color"))
445 self.set_color(cmpColour(r,g,b))
446 #backwards compatible with non-isometric map formated clients
447 ratio = RATIO_DEFAULT
448 if xml_dom.hasAttribute("ratio"):
449 ratio = xml_dom.getAttribute("ratio")
450 if xml_dom.hasAttribute("mode"):
451 self.SetMode(int(xml_dom.getAttribute("mode")))
452 if xml_dom.hasAttribute("size"):
453 self.unit_size = int(xml_dom.getAttribute("size"))
454 self.unit_size_y = self.unit_size
455 if xml_dom.hasAttribute("snap"):
456 if (xml_dom.getAttribute("snap") == 'True') or (xml_dom.getAttribute("snap") == "1"):
457 self.snap = True
458 else:
459 self.snap = False
460 if xml_dom.hasAttribute("line"):
461 self.SetLine(int(xml_dom.getAttribute("line")))