forked from wxWidgets/Phoenix
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@74263 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
- Loading branch information
Showing
2 changed files
with
916 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,372 @@ | ||
#---------------------------------------------------------------------------- | ||
# Name: clroses.py | ||
# Purpose: Class definitions for Roses interactive display programs. | ||
# | ||
# Author: Ric Werme | ||
# WWW: http://WermeNH.com/roses | ||
# | ||
# Created: June 2007 | ||
# CVS-ID: $Id$ | ||
# Copyright: Public Domain, please give credit where credit is due. | ||
# License: Sorry, no EULA. | ||
#---------------------------------------------------------------------------- | ||
|
||
# This is yet another incarnation of an old graphics hack based around | ||
# misdrawing an analytic geometry curve called a rose. The basic form is | ||
# simply the polar coordinate function r = cos(a * theta). "a" is the | ||
# "order" of the rose, a zero value degenerates to r = 1, a circle. While | ||
# this program is happy to draw that, much more interesting things happen when | ||
# one or more of the following is in effect: | ||
|
||
# 1) The "delta theta" between points is large enough to distort the curve, | ||
# e.g. 90 degrees will draw a square, slightly less will be interesting. | ||
|
||
# 2) The order of the rose is too large to draw it accurately. | ||
|
||
# 3) Vectors are drawn at less than full speed. | ||
|
||
# 4) The program is stepping through different patterns on its own. | ||
|
||
# While you will be able to predict some aspects of the generated patterns, | ||
# a lot of what there is to be found is found at random! | ||
|
||
# The rose class has all the knowledge to implement generating vector data for | ||
# roses and handles all the timing issues. It does not have the user interface | ||
# for changing all the drawing parameters. It offers a "vision" of what an | ||
# ideal Roses program should be, however, callers are welcome to assert their | ||
# independence, override defaults, ignore features, etc. | ||
|
||
from math import sin, cos, pi | ||
|
||
# Rose class knows about: | ||
# > Generating points and vectors (returning data as a list of points) | ||
# > Starting a new rose (e.g. telling user to erase old vectors) | ||
# > Stepping from one pattern to the next. | ||
|
||
class rose: | ||
"Defines everything needed for drawing a rose with timers." | ||
|
||
# The following data is accessible by callers, but there are set | ||
# methods for most everything and various method calls to client methods | ||
# to display current values. | ||
style = 100 # Angular distance along curve between points | ||
sincr = -1 # Amount to increment style by in auto mode | ||
petals = 2 # Lobes on the rose (even values have 2X lobes) | ||
pincr = 1 # Amount to increment petals by in auto mode | ||
nvec = 399 # Number of vectors to draw the rose | ||
minvec = 0 # Minimum number acceptable in automatic mode | ||
maxvec = 3600 # Maximum number acceptable in automatic mode | ||
skipvec = 0 # Don't draw this many at the start (cheap animations) | ||
drawvec = 3600 # Draw only this many (cheap animations) | ||
step = 20 # Number of vectors to draw each clock tick | ||
draw_delay = 50 # Time between roselet calls to watch pattern draw | ||
wait_delay = 2000 # Time between roses in automatic mode | ||
|
||
# Other variables that the application shouldn't access. | ||
verbose = 0 # No good way to set this at the moment. | ||
nextpt = 0 # Next position to draw on next clock tick | ||
|
||
# Internal states: | ||
INT_IDLE, INT_DRAW, INT_SEARCH, INT_WAIT, INT_RESIZE = range(5) | ||
int_state = INT_IDLE | ||
|
||
# Command states | ||
CMD_STOP, CMD_GO = range(2) | ||
cmd_state = CMD_STOP | ||
|
||
# Return full rose line (a tuple of (x, y) tuples). Not used by interactive | ||
# clients but still useful for command line and batch clients. | ||
# This is the "purest" code and doesn't require the App* methods defined | ||
# by the caller. | ||
def rose(self, style, petals, vectors): | ||
self.nvec = vectors | ||
self.make_tables(vectors) | ||
line = [(1.0, 0.0)] | ||
for i in range (1, vectors): | ||
theta = (style * i) % vectors | ||
r = self.cos_table[(petals * theta) % vectors] | ||
line.append((r * self.cos_table[theta], r * self.sin_table[theta])) | ||
line.append((1.0, 0.0)) | ||
return line | ||
|
||
# Generate vectors for the next chunk of rose. | ||
|
||
# This is not meant to be called from an external module, as it is closely | ||
# coupled to parameters set up within the class and limits set up by | ||
# restart(). Restart() initializes all data this needs to start drawing a | ||
# pattern, and clock() calls this to compute the next batch of points and | ||
# hear if that is the last batch. We maintain all data we need to draw each | ||
# batch after the first. theta should be 2.0*pi * style*i/self.nvec | ||
# radians, but we deal in terms of the lookup table so it's just the index | ||
# that refers to the same spot. | ||
def roselet(self): | ||
line = [] | ||
stop = self.nextpt + self.step | ||
keep_running = True | ||
if stop >= self.endpt: | ||
stop = self.endpt | ||
keep_running = False | ||
for i in range (self.nextpt, stop + 1): | ||
theta = (self.style * i) % self.nvec | ||
r = self.cos_table[(self.petals * theta) % self.nvec] | ||
line.append((r * self.cos_table[theta], r * self.sin_table[theta])) | ||
self.nextpt = stop | ||
return line, keep_running | ||
|
||
# Generate sine and cosine lookup tables. We could create data for just | ||
# 1/4 of a circle, at least if vectors was a multiple of 4, and share a | ||
# table for both sine and cosine, but memory is cheaper than it was in | ||
# PDP-11 days. OTOH, small, shared tables would be more cache friendly, | ||
# but if we were that concerned, this would be in C. | ||
def make_tables(self, vectors): | ||
self.sin_table = [sin(2.0 * pi * i / vectors) for i in range(vectors)] | ||
self.cos_table = [cos(2.0 * pi * i / vectors) for i in range(vectors)] | ||
|
||
# Rescale (x,y) data to match our window. Note the negative scaling in the | ||
# Y direction, this compensates for Y moving down the screen, but up on | ||
# graph paper. | ||
def rescale(self, line, offset, scale): | ||
for i in range(len(line)): | ||
line[i] = (line[i][0] * scale + offset[0], | ||
line[i][1] * (-scale) + offset[1]) | ||
return line | ||
|
||
# Euler's Method for computing the greatest common divisor. Knuth's | ||
# "The Art of Computer Programming" vol.2 is the standard reference, | ||
# but the web has several good ones too. Basically this sheds factors | ||
# that aren't in the GCD and returns when there's nothing left to shed. | ||
# N.B. Call with a >= b. | ||
def gcd(self, a, b): | ||
while b != 0: | ||
a, b = b, a % b | ||
return a | ||
|
||
# Erase any old vectors and start drawing a new rose. When the program | ||
# starts, the sine and cosine tables don't exist, build them here. (Of | ||
# course, if an __init__() method is added, move the call there. | ||
# If we're in automatic mode, check to see if the new pattern has neither | ||
# too few or too many vectors and skip it if so. Skip by setting up for | ||
# a one tick wait to let us get back to the main loop so the user can | ||
# update parameters or stop. | ||
def restart(self): | ||
if self.verbose: | ||
print 'restart: int_state', self.int_state, 'cmd_state', self.cmd_state | ||
try: | ||
tmp = self.sin_table[0] | ||
except: | ||
self.make_tables(self.nvec) | ||
|
||
new_state = self.INT_DRAW | ||
self.takesvec = self.nvec / self.gcd(self.nvec, self.style) | ||
if not self.takesvec & 1 and self.petals & 1: | ||
self.takesvec /= 2 | ||
if self.cmd_state == self.CMD_GO: | ||
if self.minvec > self.takesvec or self.maxvec < self.takesvec: | ||
new_state = self.INT_SEARCH | ||
self.AppSetTakesVec(self.takesvec) | ||
self.AppClear() | ||
self.nextpt = self.skipvec | ||
self.endpt = min(self.takesvec, self.skipvec + self.drawvec) | ||
old_state, self.int_state = self.int_state, new_state | ||
if old_state == self.INT_IDLE: # Clock not running | ||
self.clock() | ||
elif old_state == self.INT_WAIT: # May be long delay, restart | ||
self.AppCancelTimer() | ||
self.clock() | ||
else: | ||
return 1 # If called by clock(), return and start clock | ||
return 0 # We're in INT_IDLE or INT_WAIT, clock running | ||
|
||
# Called from App. Recompute the center and scale values for the subsequent pattern. | ||
# Force us into INT_RESIZE state if not already there so that in 100 ms we'll start | ||
# to draw something to give an idea of the new size. | ||
def resize(self, size, delay): | ||
xsize, ysize = size | ||
self.center = (xsize / 2, ysize / 2) | ||
self.scale = min(xsize, ysize) / 2.1 | ||
self.repaint(delay) | ||
|
||
# Called from App or above. From App, called with small delay because | ||
# some window managers will produce a flood of expose events or call us | ||
# before initialization is done. | ||
def repaint(self, delay): | ||
if self.int_state != self.INT_RESIZE: | ||
# print 'repaint after', delay | ||
self.int_state = self.INT_RESIZE | ||
self.AppCancelTimer() | ||
self.AppAfter(delay, self.clock) | ||
|
||
# Method that returns the next style and petal values for automatic | ||
# mode and remembers them internally. Keep things scaled in the | ||
# range [0:nvec) because there's little reason to exceed that. | ||
def next(self): | ||
self.style += self.sincr | ||
self.petals += self.pincr | ||
if self.style <= 0 or self.petals < 0: | ||
self.style, self.petals = \ | ||
abs(self.petals) + 1, abs(self.style) | ||
if self.style >= self.nvec: | ||
self.style %= self.nvec # Don't bother defending against 0 | ||
if self.petals >= self.nvec: | ||
self.petals %= self.nvec | ||
self.AppSetParam(self.style, self.petals, self.nvec) | ||
|
||
# Resume pattern drawing with the next one to display. | ||
def resume(self): | ||
self.next() | ||
return self.restart() | ||
|
||
# Go/Stop button. | ||
def cmd_go_stop(self): | ||
if self.cmd_state == self.CMD_STOP: | ||
self.cmd_state = self.CMD_GO | ||
self.resume() # Draw next pattern | ||
elif self.cmd_state == self.CMD_GO: | ||
self.cmd_state = self.CMD_STOP | ||
self.update_labels() | ||
|
||
# Centralize button naming to share with initialization. | ||
# Leave colors to the application (assuming it cares), we can't guess | ||
# what's available. | ||
def update_labels(self): | ||
if self.cmd_state == self.CMD_STOP: | ||
self.AppCmdLabels(('Go', 'Redraw', 'Backward', 'Forward')) | ||
else: # Must be in state CMD_GO | ||
self.AppCmdLabels(('Stop', 'Redraw', 'Reverse', 'Skip')) | ||
|
||
# Redraw/Redraw button | ||
def cmd_redraw(self): | ||
self.restart() # Redraw current pattern | ||
|
||
# Backward/Reverse button | ||
# Useful for when you see an interesting pattern and want | ||
# to go back to it. If running, just change direction. If stopped, back | ||
# up one step. The resume code handles the step, then we change the | ||
# incrementers back to what they were. (Unless resume changed them too.) | ||
def cmd_backward(self): | ||
self.sincr = -self.sincr | ||
self.pincr = -self.pincr | ||
if self.cmd_state == self.CMD_STOP: | ||
self.resume(); | ||
self.sincr = -self.sincr # Go forward again | ||
self.pincr = -self.pincr | ||
else: | ||
self.AppSetIncrs(self.sincr, self.pincr) | ||
|
||
# Forward/Skip button. CMD_STOP & CMD_GO both just call resume. | ||
def cmd_step(self): | ||
self.resume() # Draw next pattern | ||
|
||
# Handler called on each timer event. This handles the metered drawing | ||
# of a rose and the delays between them. It also registers for the next | ||
# timer event unless we're idle (rose is done and the delay between | ||
# roses is 0.) | ||
def clock(self): | ||
if self.int_state == self.INT_IDLE: | ||
# print 'clock called in idle state' | ||
delay = 0 | ||
elif self.int_state == self.INT_DRAW: | ||
line, run = self.roselet() | ||
self.AppCreateLine(self.rescale(line, self.center, self.scale)) | ||
if run: | ||
delay = self.draw_delay | ||
else: | ||
if self.cmd_state == self.CMD_GO: | ||
self.int_state = self.INT_WAIT | ||
delay = self.wait_delay | ||
else: | ||
self.int_state = self.INT_IDLE | ||
delay = 0 | ||
elif self.int_state == self.INT_SEARCH: | ||
delay = self.resume() # May call us to start drawing | ||
if self.int_state == self.INT_SEARCH: | ||
delay = self.draw_delay # but not if searching. | ||
elif self.int_state == self.INT_WAIT: | ||
if self.cmd_state == self.CMD_GO: | ||
delay = self.resume() # Calls us to start drawing | ||
else: | ||
self.int_state = self.INT_IDLE | ||
delay = 0 | ||
elif self.int_state == self.INT_RESIZE: # Waiting for resize event stream to settle | ||
self.AppSetParam(self.style, self.petals, self.nvec) | ||
self.AppSetIncrs(self.sincr, self.pincr) | ||
delay = self.restart() # Calls us to start drawing | ||
|
||
if delay == 0: | ||
if self.verbose: | ||
print 'clock: going idle from state', self.int_state | ||
else: | ||
self.AppAfter(delay, self.clock) | ||
|
||
# Methods to allow App to change the parameters on the screen. | ||
# These expect to be called when the associated paramenter changes, | ||
# but work reasonably well if several are called at once. (E.g. | ||
# tkroses.py groups them into things that affect the visual display | ||
# and warrant a new start, and things that just change and don't affect | ||
# the ultimate pattern. All parameters within a group are updated | ||
# at once even if the value hasn't changed. | ||
|
||
# We restrict the style and petals parameters to the range [0: nvec) | ||
# since numbers outside of that range aren't interesting. We don't | ||
# immediately update the value in the application, we probably should. | ||
|
||
# NW control window - key parameters | ||
def SetStyle(self, value): | ||
self.style = value % self.nvec | ||
self.restart() | ||
|
||
def SetSincr(self, value): | ||
self.sincr = value | ||
|
||
def SetPetals(self, value): | ||
self.petals = value % self.nvec | ||
self.restart() | ||
|
||
def SetPincr(self, value): | ||
self.pincr = value | ||
|
||
|
||
# SW control window - vectors | ||
def SetVectors(self, value): | ||
self.nvec = value | ||
self.style %= value | ||
self.petals %= value | ||
self.AppSetParam(self.style, self.petals, self.nvec) | ||
self.make_tables(value) | ||
self.restart() | ||
|
||
def SetMinVec(self, value): | ||
if self.maxvec >= value and self.nvec >= value: | ||
self.minvec = value | ||
|
||
def SetMaxVec(self, value): | ||
if self.minvec < value: | ||
self.maxvec = value | ||
|
||
def SetSkipFirst(self, value): | ||
self.skipvec = value | ||
self.restart() | ||
|
||
def SetDrawOnly(self, value): | ||
self.drawvec = value | ||
self.restart() | ||
|
||
|
||
# SE control window - timings | ||
def SetStep(self, value): | ||
self.step = value | ||
|
||
def SetDrawDelay(self, value): | ||
self.draw_delay = value | ||
|
||
def SetWaitDelay(self, value): | ||
self.wait_delay = value | ||
|
||
# Method for client to use to have us supply our defaults. | ||
def SupplyControlValues(self): | ||
self.update_labels() | ||
self.AppSetParam(self.style, self.petals, self.nvec) | ||
self.AppSetIncrs(self.sincr, self.pincr) | ||
self.AppSetVectors(self.nvec, self.minvec, self.maxvec, | ||
self.skipvec, self.drawvec) | ||
self.AppSetTiming(self.step, self.draw_delay, self.wait_delay) |
Oops, something went wrong.