]> git.saurik.com Git - wxWidgets.git/blob - wxPython/samples/roses/clroses.py
fixed wxVsnprintf() to write as much as it can if the output buffer is too short
[wxWidgets.git] / wxPython / samples / roses / clroses.py
1 #----------------------------------------------------------------------------
2 # Name: clroses.py
3 # Purpose: Class definitions for Roses interactive display programs.
4 #
5 # Author: Ric Werme
6 # WWW: http://WermeNH.com/roses
7 #
8 # Created: June 2007
9 # CVS-ID: $Id$
10 # Copyright: Public Domain, please give credit where credit is due.
11 # License: Sorry, no EULA.
12 #----------------------------------------------------------------------------
13
14 # This is yet another incarnation of an old graphics hack based around
15 # misdrawing an analytic geometry curve called a rose. The basic form is
16 # simply the polar coordinate function r = cos(a * theta). "a" is the
17 # "order" of the rose, a zero value degenerates to r = 1, a circle. While
18 # this program is happy to draw that, much more interesting things happen when
19 # one or more of the following is in effect:
20
21 # 1) The "delta theta" between points is large enough to distort the curve,
22 # e.g. 90 degrees will draw a square, slightly less will be interesting.
23
24 # 2) The order of the rose is too large to draw it accurately.
25
26 # 3) Vectors are drawn at less than full speed.
27
28 # 4) The program is stepping through different patterns on its own.
29
30 # While you will be able to predict some aspects of the generated patterns,
31 # a lot of what there is to be found is found at random!
32
33 # The rose class has all the knowledge to implement generating vector data for
34 # roses and handles all the timing issues. It does not have the user interface
35 # for changing all the drawing parameters. It offers a "vision" of what an
36 # ideal Roses program should be, however, callers are welcome to assert their
37 # independence, override defaults, ignore features, etc.
38
39 from math import sin, cos, pi
40
41 # Rose class knows about:
42 # > Generating points and vectors (returning data as a list of points)
43 # > Starting a new rose (e.g. telling user to erase old vectors)
44 # > Stepping from one pattern to the next.
45
46 class rose:
47 "Defines everything needed for drawing a rose with timers."
48
49 # The following data is accessible by callers, but there are set
50 # methods for most everything and various method calls to client methods
51 # to display current values.
52 style = 100 # Angular distance along curve between points
53 sincr = -1 # Amount to increment style by in auto mode
54 petals = 2 # Lobes on the rose (even values have 2X lobes)
55 pincr = 1 # Amount to increment petals by in auto mode
56 nvec = 399 # Number of vectors to draw the rose
57 minvec = 0 # Minimum number acceptable in automatic mode
58 maxvec = 3600 # Maximum number acceptable in automatic mode
59 skipvec = 0 # Don't draw this many at the start (cheap animations)
60 drawvec = 3600 # Draw only this many (cheap animations)
61 step = 20 # Number of vectors to draw each clock tick
62 draw_delay = 50 # Time between roselet calls to watch pattern draw
63 wait_delay = 2000 # Time between roses in automatic mode
64
65 # Other variables that the application shouldn't access.
66 verbose = 0 # No good way to set this at the moment.
67 nextpt = 0 # Next position to draw on next clock tick
68
69 # Internal states:
70 INT_IDLE, INT_DRAW, INT_SEARCH, INT_WAIT, INT_RESIZE = range(5)
71 int_state = INT_IDLE
72
73 # Command states
74 CMD_STOP, CMD_GO = range(2)
75 cmd_state = CMD_STOP
76
77 # Return full rose line (a tuple of (x, y) tuples). Not used by interactive
78 # clients but still useful for command line and batch clients.
79 # This is the "purest" code and doesn't require the App* methods defined
80 # by the caller.
81 def rose(self, style, petals, vectors):
82 self.nvec = vectors
83 self.make_tables(vectors)
84 line = [(1.0, 0.0)]
85 for i in range (1, vectors):
86 theta = (style * i) % vectors
87 r = self.cos_table[(petals * theta) % vectors]
88 line.append((r * self.cos_table[theta], r * self.sin_table[theta]))
89 line.append((1.0, 0.0))
90 return line
91
92 # Generate vectors for the next chunk of rose.
93
94 # This is not meant to be called from an external module, as it is closely
95 # coupled to parameters set up within the class and limits set up by
96 # restart(). Restart() initializes all data this needs to start drawing a
97 # pattern, and clock() calls this to compute the next batch of points and
98 # hear if that is the last batch. We maintain all data we need to draw each
99 # batch after the first. theta should be 2.0*pi * style*i/self.nvec
100 # radians, but we deal in terms of the lookup table so it's just the index
101 # that refers to the same spot.
102 def roselet(self):
103 line = []
104 stop = self.nextpt + self.step
105 keep_running = True
106 if stop >= self.endpt:
107 stop = self.endpt
108 keep_running = False
109 for i in range (self.nextpt, stop + 1):
110 theta = (self.style * i) % self.nvec
111 r = self.cos_table[(self.petals * theta) % self.nvec]
112 line.append((r * self.cos_table[theta], r * self.sin_table[theta]))
113 self.nextpt = stop
114 return line, keep_running
115
116 # Generate sine and cosine lookup tables. We could create data for just
117 # 1/4 of a circle, at least if vectors was a multiple of 4, and share a
118 # table for both sine and cosine, but memory is cheaper than it was in
119 # PDP-11 days. OTOH, small, shared tables would be more cache friendly,
120 # but if we were that concerned, this would be in C.
121 def make_tables(self, vectors):
122 self.sin_table = [sin(2.0 * pi * i / vectors) for i in range(vectors)]
123 self.cos_table = [cos(2.0 * pi * i / vectors) for i in range(vectors)]
124
125 # Rescale (x,y) data to match our window. Note the negative scaling in the
126 # Y direction, this compensates for Y moving down the screen, but up on
127 # graph paper.
128 def rescale(self, line, offset, scale):
129 for i in range(len(line)):
130 line[i] = (line[i][0] * scale + offset[0],
131 line[i][1] * (-scale) + offset[1])
132 return line
133
134 # Euler's Method for computing the greatest common divisor. Knuth's
135 # "The Art of Computer Programming" vol.2 is the standard reference,
136 # but the web has several good ones too. Basically this sheds factors
137 # that aren't in the GCD and returns when there's nothing left to shed.
138 # N.B. Call with a >= b.
139 def gcd(self, a, b):
140 while b != 0:
141 a, b = b, a % b
142 return a
143
144 # Erase any old vectors and start drawing a new rose. When the program
145 # starts, the sine and cosine tables don't exist, build them here. (Of
146 # course, if an __init__() method is added, move the call there.
147 # If we're in automatic mode, check to see if the new pattern has neither
148 # too few or too many vectors and skip it if so. Skip by setting up for
149 # a one tick wait to let us get back to the main loop so the user can
150 # update parameters or stop.
151 def restart(self):
152 if self.verbose:
153 print 'restart: int_state', self.int_state, 'cmd_state', self.cmd_state
154 try:
155 tmp = self.sin_table[0]
156 except:
157 self.make_tables(self.nvec)
158
159 new_state = self.INT_DRAW
160 self.takesvec = self.nvec / self.gcd(self.nvec, self.style)
161 if not self.takesvec & 1 and self.petals & 1:
162 self.takesvec /= 2
163 if self.cmd_state == self.CMD_GO:
164 if self.minvec > self.takesvec or self.maxvec < self.takesvec:
165 new_state = self.INT_SEARCH
166 self.AppSetTakesVec(self.takesvec)
167 self.AppClear()
168 self.nextpt = self.skipvec
169 self.endpt = min(self.takesvec, self.skipvec + self.drawvec)
170 old_state, self.int_state = self.int_state, new_state
171 if old_state == self.INT_IDLE: # Clock not running
172 self.clock()
173 elif old_state == self.INT_WAIT: # May be long delay, restart
174 self.AppCancelTimer()
175 self.clock()
176 else:
177 return 1 # If called by clock(), return and start clock
178 return 0 # We're in INT_IDLE or INT_WAIT, clock running
179
180 # Called from App. Recompute the center and scale values for the subsequent pattern.
181 # Force us into INT_RESIZE state if not already there so that in 100 ms we'll start
182 # to draw something to give an idea of the new size.
183 def resize(self, size, delay):
184 xsize, ysize = size
185 self.center = (xsize / 2, ysize / 2)
186 self.scale = min(xsize, ysize) / 2.1
187 self.repaint(delay)
188
189 # Called from App or above. From App, called with small delay because
190 # some window managers will produce a flood of expose events or call us
191 # before initialization is done.
192 def repaint(self, delay):
193 if self.int_state != self.INT_RESIZE:
194 # print 'repaint after', delay
195 self.int_state = self.INT_RESIZE
196 self.AppCancelTimer()
197 self.AppAfter(delay, self.clock)
198
199 # Method that returns the next style and petal values for automatic
200 # mode and remembers them internally. Keep things scaled in the
201 # range [0:nvec) because there's little reason to exceed that.
202 def next(self):
203 self.style += self.sincr
204 self.petals += self.pincr
205 if self.style <= 0 or self.petals < 0:
206 self.style, self.petals = \
207 abs(self.petals) + 1, abs(self.style)
208 if self.style >= self.nvec:
209 self.style %= self.nvec # Don't bother defending against 0
210 if self.petals >= self.nvec:
211 self.petals %= self.nvec
212 self.AppSetParam(self.style, self.petals, self.nvec)
213
214 # Resume pattern drawing with the next one to display.
215 def resume(self):
216 self.next()
217 return self.restart()
218
219 # Go/Stop button.
220 def cmd_go_stop(self):
221 if self.cmd_state == self.CMD_STOP:
222 self.cmd_state = self.CMD_GO
223 self.resume() # Draw next pattern
224 elif self.cmd_state == self.CMD_GO:
225 self.cmd_state = self.CMD_STOP
226 self.update_labels()
227
228 # Centralize button naming to share with initialization.
229 # Leave colors to the application (assuming it cares), we can't guess
230 # what's available.
231 def update_labels(self):
232 if self.cmd_state == self.CMD_STOP:
233 self.AppCmdLabels(('Go', 'Redraw', 'Backward', 'Forward'))
234 else: # Must be in state CMD_GO
235 self.AppCmdLabels(('Stop', 'Redraw', 'Reverse', 'Skip'))
236
237 # Redraw/Redraw button
238 def cmd_redraw(self):
239 self.restart() # Redraw current pattern
240
241 # Backward/Reverse button
242 # Useful for when you see an interesting pattern and want
243 # to go back to it. If running, just change direction. If stopped, back
244 # up one step. The resume code handles the step, then we change the
245 # incrementers back to what they were. (Unless resume changed them too.)
246 def cmd_backward(self):
247 self.sincr = -self.sincr
248 self.pincr = -self.pincr
249 if self.cmd_state == self.CMD_STOP:
250 self.resume();
251 self.sincr = -self.sincr # Go forward again
252 self.pincr = -self.pincr
253 else:
254 self.AppSetIncrs(self.sincr, self.pincr)
255
256 # Forward/Skip button. CMD_STOP & CMD_GO both just call resume.
257 def cmd_step(self):
258 self.resume() # Draw next pattern
259
260 # Handler called on each timer event. This handles the metered drawing
261 # of a rose and the delays between them. It also registers for the next
262 # timer event unless we're idle (rose is done and the delay between
263 # roses is 0.)
264 def clock(self):
265 if self.int_state == self.INT_IDLE:
266 # print 'clock called in idle state'
267 delay = 0
268 elif self.int_state == self.INT_DRAW:
269 line, run = self.roselet()
270 self.AppCreateLine(self.rescale(line, self.center, self.scale))
271 if run:
272 delay = self.draw_delay
273 else:
274 if self.cmd_state == self.CMD_GO:
275 self.int_state = self.INT_WAIT
276 delay = self.wait_delay
277 else:
278 self.int_state = self.INT_IDLE
279 delay = 0
280 elif self.int_state == self.INT_SEARCH:
281 delay = self.resume() # May call us to start drawing
282 if self.int_state == self.INT_SEARCH:
283 delay = self.draw_delay # but not if searching.
284 elif self.int_state == self.INT_WAIT:
285 if self.cmd_state == self.CMD_GO:
286 delay = self.resume() # Calls us to start drawing
287 else:
288 self.int_state = self.INT_IDLE
289 delay = 0
290 elif self.int_state == self.INT_RESIZE: # Waiting for resize event stream to settle
291 self.AppSetParam(self.style, self.petals, self.nvec)
292 self.AppSetIncrs(self.sincr, self.pincr)
293 delay = self.restart() # Calls us to start drawing
294
295 if delay == 0:
296 if self.verbose:
297 print 'clock: going idle from state', self.int_state
298 else:
299 self.AppAfter(delay, self.clock)
300
301 # Methods to allow App to change the parameters on the screen.
302 # These expect to be called when the associated paramenter changes,
303 # but work reasonably well if several are called at once. (E.g.
304 # tkroses.py groups them into things that affect the visual display
305 # and warrant a new start, and things that just change and don't affect
306 # the ultimate pattern. All parameters within a group are updated
307 # at once even if the value hasn't changed.
308
309 # We restrict the style and petals parameters to the range [0: nvec)
310 # since numbers outside of that range aren't interesting. We don't
311 # immediately update the value in the application, we probably should.
312
313 # NW control window - key parameters
314 def SetStyle(self, value):
315 self.style = value % self.nvec
316 self.restart()
317
318 def SetSincr(self, value):
319 self.sincr = value
320
321 def SetPetals(self, value):
322 self.petals = value % self.nvec
323 self.restart()
324
325 def SetPincr(self, value):
326 self.pincr = value
327
328
329 # SW control window - vectors
330 def SetVectors(self, value):
331 self.nvec = value
332 self.style %= value
333 self.petals %= value
334 self.AppSetParam(self.style, self.petals, self.nvec)
335 self.make_tables(value)
336 self.restart()
337
338 def SetMinVec(self, value):
339 if self.maxvec >= value and self.nvec >= value:
340 self.minvec = value
341
342 def SetMaxVec(self, value):
343 if self.minvec < value:
344 self.maxvec = value
345
346 def SetSkipFirst(self, value):
347 self.skipvec = value
348 self.restart()
349
350 def SetDrawOnly(self, value):
351 self.drawvec = value
352 self.restart()
353
354
355 # SE control window - timings
356 def SetStep(self, value):
357 self.step = value
358
359 def SetDrawDelay(self, value):
360 self.draw_delay = value
361
362 def SetWaitDelay(self, value):
363 self.wait_delay = value
364
365 # Method for client to use to have us supply our defaults.
366 def SupplyControlValues(self):
367 self.update_labels()
368 self.AppSetParam(self.style, self.petals, self.nvec)
369 self.AppSetIncrs(self.sincr, self.pincr)
370 self.AppSetVectors(self.nvec, self.minvec, self.maxvec,
371 self.skipvec, self.drawvec)
372 self.AppSetTiming(self.step, self.draw_delay, self.wait_delay)