Mercurial > fife-parpg
comparison tools/geometry_twister.py @ 378:64738befdf3b
bringing in the changes from the build_system_rework branch in preparation for the 0.3.0 release. This commit will require the Jan2010 devkit. Clients will also need to be modified to the new way to import fife.
author | vtchill@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Mon, 11 Jan 2010 23:34:52 +0000 |
parents | |
children | 81641655bc38 |
comparison
equal
deleted
inserted
replaced
377:fe6fb0e0ed23 | 378:64738befdf3b |
---|---|
1 #!/usr/bin/env python | |
2 import Tkinter as TK | |
3 import math, time | |
4 | |
5 LMB = 0x100 | |
6 RMB = 0x400 | |
7 DBLCLICK_TRESHOLD = 0.3 # secs | |
8 | |
9 class Shape(object): | |
10 def __init__(self, center_pt, sidecount): | |
11 assert (sidecount in (4, 6)) | |
12 self.rotation = 0 | |
13 self.tilting = 0 | |
14 self.zoomval = 70 | |
15 self.center_pt = center_pt | |
16 startangle = 0 | |
17 if sidecount == 6: | |
18 startangle = math.pi / 6.0 | |
19 self.angles = [startangle + 2 * math.pi * (s / float(sidecount)) for s in xrange(sidecount-1)] | |
20 shape_pts = [(0,0,0)] | |
21 maxy, miny = 0, 0 | |
22 for a in self.angles: | |
23 x, y, z = shape_pts[-1] | |
24 newx, newy = math.cos(a) + x, math.sin(a) + y | |
25 maxy, miny = max(maxy, newy), min(miny, newy) | |
26 shape_pts.append((newx, newy, 0)) | |
27 xmove, ymove = 0.5, (abs(miny) + maxy) / 2 | |
28 self.shape_pts = [(pt[0]-xmove, pt[1]-ymove, 0) for pt in shape_pts] | |
29 self.reflections = self.create_shape_reflections() | |
30 | |
31 def create_shape_reflections(self): | |
32 reflections = [] | |
33 sides = len(self.shape_pts) | |
34 if sides == 4: | |
35 for x,y in ((1,0), (1,1), (0,1), (-1,1), (-1,0), (-1,-1), (0,-1), (1,-1)): | |
36 reflections.append([(pt[0]+x, pt[1]+y, 0) for pt in self.shape_pts]) | |
37 elif sides == 6: | |
38 W = math.cos(math.pi/6.0) | |
39 H = math.sin(math.pi/6.0) | |
40 for x,y in ((2*W,0), (W,1+H), (-W,1+H), (-2*W,0), (-W,-1-H)): | |
41 reflections.append([(pt[0]+x, pt[1]+y, 0) for pt in self.shape_pts]) | |
42 return reflections | |
43 | |
44 def tilt(self, degrees): | |
45 self.tilting += math.radians(degrees) | |
46 if self.tilting < 0: | |
47 self.tilting = 0 | |
48 elif self.tilting > math.pi/2 * 0.95: | |
49 self.tilting = math.pi/2 * 0.95 | |
50 | |
51 def rotate(self, degrees): | |
52 self.rotation += math.radians(degrees) | |
53 if self.rotation < 0: | |
54 self.rotation = 0 | |
55 elif self.rotation > math.pi/2 * 0.95: | |
56 self.rotation = math.pi/2 * 0.95 | |
57 | |
58 def zoom(self, amount): | |
59 self.zoomval += amount | |
60 if self.zoomval < 10: | |
61 self.zoomval = 10 | |
62 if self.zoomval > 150: | |
63 self.zoomval = 150 | |
64 | |
65 def transform_pts(self, pts): | |
66 rotated_pts = [] | |
67 for x, y, z in pts: | |
68 rotated_pts.append((x * math.cos(self.rotation) - y * math.sin(self.rotation), | |
69 -(x * math.sin(self.rotation) + y * math.cos(self.rotation)), | |
70 z)) | |
71 tilted_pts = [] | |
72 for x, y, z in rotated_pts: | |
73 tilted_pts.append((x, | |
74 y * math.cos(self.tilting) - z * math.sin(self.tilting), | |
75 z * math.sin(self.tilting) + z * math.cos(self.tilting))) | |
76 | |
77 zoomed_pts = [] | |
78 for x, y, z in tilted_pts: | |
79 zoomed_pts.append((int(round(self.center_pt[0] + self.zoomval * x)), | |
80 int(round(self.center_pt[1] + self.zoomval * y)))) | |
81 return zoomed_pts | |
82 | |
83 def get_screen_pts(self): | |
84 return self.transform_pts(self.shape_pts) | |
85 | |
86 def get_screen_bounding_box(self, screenpts): | |
87 x2, y2 = x1, y1 = screenpts[0] | |
88 for pt in screenpts: | |
89 x1, y1 = min(x1, pt[0]), min(y1, pt[1]) | |
90 x2, y2 = max(x2, pt[0]), max(y2, pt[1]) | |
91 return (x1, y1), (x2, y2) | |
92 | |
93 def get_reflections(self): | |
94 return [self.transform_pts(reflection) for reflection in self.reflections] | |
95 | |
96 def get_tilting(self): | |
97 return math.degrees(self.tilting) | |
98 | |
99 def get_rotation(self): | |
100 return math.degrees(self.rotation) | |
101 | |
102 class Gui(object): | |
103 W = 400 | |
104 H = 400 | |
105 def __init__(self): | |
106 self.sides = (4, 6) | |
107 self.cur_side_index = 0 | |
108 self.shape = Shape((Gui.W/2, Gui.H/2), self.sides[0]) | |
109 self.prev_mouse_pt = (0, 0) | |
110 self.root = TK.Tk() | |
111 self.canvas = TK.Canvas(self.root, width=Gui.W, height=Gui.H) | |
112 self.canvas.create_window(285, 280, window=TK.Frame(self.canvas, relief=TK.GROOVE, borderwidth=2), anchor=TK.CENTER) | |
113 self.canvas.bind('<Motion>', self.on_move) | |
114 self.canvas.bind('<ButtonPress-1>', self.on_mb) | |
115 self.canvas.bind('<ButtonPress-3>', self.on_mb) | |
116 self.canvas.bind('<Configure>', self.on_resize) | |
117 self.canvas.pack() | |
118 self.update_view() | |
119 self.last_click_time = time.time() | |
120 | |
121 def on_resize(self, event): | |
122 pass | |
123 | |
124 def on_move(self, event): | |
125 if (event.state & RMB): | |
126 self.shape.zoom((self.prev_mouse_pt[1] - event.y) / 3) | |
127 self.update_view() | |
128 elif (event.state & LMB): | |
129 self.shape.rotate((self.prev_mouse_pt[0] - event.x) / 2) | |
130 self.shape.tilt((self.prev_mouse_pt[1] - event.y) / 2) | |
131 self.update_view() | |
132 self.prev_mouse_pt = (event.x, event.y) | |
133 | |
134 def on_mb(self, event): | |
135 self.prev_mouse_pt = (event.x, event.y) | |
136 if (time.time() - self.last_click_time) < DBLCLICK_TRESHOLD: | |
137 self.cur_side_index = (self.cur_side_index+1) % len(self.sides) | |
138 newshape = Shape((Gui.W/2, Gui.H/2), self.sides[self.cur_side_index]) | |
139 newshape.rotation = self.shape.rotation | |
140 newshape.tilting = self.shape.tilting | |
141 newshape.zoomval = self.shape.zoomval | |
142 self.shape = newshape | |
143 self.update_view() | |
144 self.last_click_time = time.time() | |
145 | |
146 def draw_shape(self, shapepts, boundpts=None, fillshape=True): | |
147 if boundpts: | |
148 pt1, pt2 = boundpts | |
149 self.canvas.create_polygon(pt1[0], pt1[1], pt2[0], pt1[1], pt2[0], pt2[1], pt1[0], pt2[1], outline="blue", fill="", tag="boundingbox") | |
150 fillcolor = "white" | |
151 if not fillshape: | |
152 fillcolor = "" | |
153 self.canvas.create_polygon(fill=fillcolor, outline="black", tag="shape", *self.flatten_pts(shapepts)) | |
154 | |
155 def update_texts(self, size, transform): | |
156 self.canvas.create_text(10, 20, text="Bounding box size x:%d, y:%d" % (size[0], size[1]), tag="size", anchor=TK.NW) | |
157 self.canvas.create_text(10, 40, text="Transform x:%d, y:%d" % (transform[0], transform[1]), tag="transform", anchor=TK.NW) | |
158 self.canvas.create_text(10, 60, text="Tilting: %d" % self.shape.get_tilting(), tag="tilting", anchor=TK.NW) | |
159 self.canvas.create_text(10, 80, text="Rotation: %d" % self.shape.get_rotation(), tag="rotation", anchor=TK.NW) | |
160 self.canvas.create_text(10, 330, text="Doubleclick to change shape", anchor=TK.NW) | |
161 self.canvas.create_text(10, 350, text="Right button + drag = zoom", anchor=TK.NW) | |
162 self.canvas.create_text(10, 370, text="Left button + drag = tilt/rotate", anchor=TK.NW) | |
163 | |
164 def flatten_pts(self, pts): | |
165 return [c for pt in pts for c in pt] | |
166 | |
167 def update_view(self): | |
168 self.canvas.delete("all") | |
169 shapepts = self.shape.get_screen_pts() | |
170 boundpts = self.shape.get_screen_bounding_box(shapepts) | |
171 size = boundpts[1][0] - boundpts[0][0], boundpts[1][1] - boundpts[0][1] | |
172 reflections = self.shape.get_reflections() | |
173 transform = (reflections[0][0][0] - shapepts[0][0], -(reflections[0][0][1] - shapepts[0][1])) | |
174 | |
175 for reflection_pts in reflections[1:]: | |
176 self.draw_shape(reflection_pts, fillshape=False) | |
177 | |
178 self.draw_shape(shapepts, boundpts) | |
179 transform_boundpts = self.shape.get_screen_bounding_box(reflections[0]) | |
180 self.draw_shape(reflections[0], transform_boundpts, fillshape=False) | |
181 | |
182 self.update_texts(size, transform) | |
183 | |
184 def run(self): | |
185 self.root.mainloop() | |
186 | |
187 if __name__ == '__main__': | |
188 gui = Gui() | |
189 gui.run() |