Fundamentals of Computer Science I: Media Computing (CS151.01 2008S)
Primary: [Front Door] [Syllabus] - [Academic Honesty] [Instructions]
Current: [Outline] [EBoard] [Reading] [Lab] [Assignment]
Groupings: [Assignments] [EBoards] [Examples] [Exams] [Handouts] [Labs] [Outlines] [Projects] [Readings]
References: [A-Z] [Primary] [Scheme Report (R5RS)] [Scheme Reference] [DrScheme Manual]
Related Courses: [CSC151.02 2008S (Davis)] [CSC151 2007F (Rebelsky)] [CSC151 2007S (Rebelsky)] [CSCS151 2005S (Stone)]
Due: 9:00 a.m., Wednesday, 2 April 2008
Summary: In this assignment, you will experiment with drawings that are formed by rolling an outer “stamp” around an inner “disc”. The technique for making these diagrams is based, in part, on the structure of the Spirograph® toy.
Purposes: To give you more experience with numeric recursion. To give you an opportunity to play with interesting images.
Expected Time: Three to four hours.
Collaboration: We encourage you to work in groups of size three. You may, however, work alone or work in a group of size two or size four. You may discuss this assignment with anyone, provided you credit such discussions when you submit the assignment.
Submitting:
Email your answer to <rebelsky@grinnell.edu>
. The title of your
email should have the form CSC151.01 2008S Assignment 7: Spirograph-Like Drawings
and should contain your answers to all parts of the assignment.
Scheme code should be in the body of the message. Do not
attach any images. We should be able to regenerate any
images you create just from the instructions you submit.
Warning: So that this assignment is a learning experience for everyone, I may spend class time publicly critiquing your work.
Many of you may have played with a Spirograph® when you were growing up. These toys provide an interesting way to draw complex shapes with simple tools. In a typical Spirograph®, you work with two circular discs. You place a pen through a hole in one disc and rotate that disc around another disc. Together, the two discs describe a complex path for the pen.
While the more mathematically inclined among you might be able to write an equation for spirographic curves, our goal here is to use them as inspiration for techniques for creating interesting drawings algorithmically. In particular, we're going to consider how to simulate rolling one circle around another and how that might provide a mechanism for making drawings.
Here's what we'll do: Rather than trying to trace a line, we'll simply rotate the outer disc (which we'll call the “stamp”) around an inner disc, pausing every so often to place the contents of the stamp on the image. To implement that idea, we'll write three procedures:
(
, which draws one stamp,
centered at (draw-stamp!
image
col
row
radius
rotation
)col
,row
),
rotated by rotation
(an angle in radians)
from its original orientation;
(
, which draws one stamp
(with radius of draw-rolled!
image
col
row
inner-radius
stamp-radius
revolution
)stamp-radius
) that has been
rolled by an angle of revolution
around a
disc with radius inner-radius
centered at
(col
,row
); and
(
, which draws
roll!
image
n
col
row
inner-radius
stamp-radius
theta
)n
copies of a stamp of radius
stamp-radius
, rolled around a disc
with radius inner-radius
centered at
(col
,row
), rotating
each subsequent stamp by theta
from the
previous stamp.
The first procedure, draw-stamp!
,
should be fairly simple. To write the second procedure,
draw-rolled!
, we will need to figure out the
center and angle of rotation for the rolled stamp. To write the
third procedure, roll!
, we will simply need to
use numeric recursion to call draw-rolled!
an
appropriate number of times.
We'll start with a relatively straightforward version of
draw-stamp!
. In this version, we'll draw a circle
(so that we can visualize where the stamp is) and a line pointing in
the direction of the angle (with horizontally to the right representing
an angle of 0).
Drawing the circle should be easy, since we've written an
image-draw-circle!
procedure in the past. In case you've
forgotten, here's what that procedure looks like.
;;; Procedure: ;;; image-draw-circle! ;;; Parameters: ;;; image, an image ;;; col, an integer ;;; row, an integer ;;; radius, an integer ;;; Purpose: ;;; Draws a circle with the specified radius in the current brush and color, ;;; centered at (col,row). ;;; Produces: ;;; [Nothing; Called for the side effect] ;;; Preconditions: ;;; 0 <= col < (image-width image) ;;; 0 <= row < (image-height image) ;;; 0 < radius ;;; Postconditions: ;;; The image now contains the specified circle. (The circle may ;;; not be visible.) (define image-draw-circle! (lambda (image col row radius) (image-select-ellipse! image selection-replace (- col radius) (- row radius) (+ radius radius) (+ radius radius)) (image-stroke! image) (image-select-nothing! image)))
Drawing the circular part of the stamp means we have to do little more than call that procedure.
(define draw-stamp! (lambda (image col row radius rotation) (image-draw-circle! image col row radius) ...))
Now, what do we do about the rotated radial line? Well, if we remember
our trigonometry correctly, the cosine of an angle gives the x coordinate
and the sine of the angle gives the y coordinate of a unit vector from
the origin. Since we're not using a unit vector, we need to scale by the
radius. Since we're not starting at the origin, we need to offset by
col
and row
. Putting
those instructions together with those above, we get our complete
draw-stamp!
procedure.
;;; Procedure: ;;; draw-stamp ;;; Parameters: ;;; image, an image ;;; col, an integer ;;; row, an integer ;;; radius, an integer ;;; rotation, a real number ;;; Purpose: ;;; Draws a rotatable stamp, centered at (col,row) and rotated by rotation ;;; (an angle expressed in radians). ;;; The details of the stamp drawn are intentionally left unspecified so ;;; that implementers can choose different stamps for different effects. ;;; Produces: ;;; [Nothing; Called for the side effect] ;;; Preconditions: ;;; 0 <= col < (image-width image) ;;; 0 <= row < (image-height image) ;;; Postconditions: ;;; image has been extended with the stamp. (define draw-stamp! (lambda (image col row radius rotation) (image-draw-circle! image col row radius) (image-draw-line! image col row (+ col (* radius (cos rotation))) (+ row (* radius (sin rotation))))))
You may find it useful to use the preceding to draw a few stamps to make sure that you understand what the procedure does.
>
(define canvas (image-new 200 200))
>
(image-show canvas)
>
(context-set-fgcolor! "blue")
>
(context-set-brush! "Circle (01)")
>
(draw-stamp! canvas 20 20 20 0)
>
(context-update-displays!)
>
(draw-stamp! canvas 100 20 20 (* pi 0.25))
>
(context-update-displays!)
>
(draw-stamp! canvas 180 20 20 (* pi 0.50))
>
(context-update-displays!)
>
(draw-stamp! canvas 20 100 20 (* pi 0.625))
>
(context-update-displays!)
>
(draw-stamp! canvas 100 100 20 (* pi -0.5))
>
(context-update-displays!)
>
(draw-stamp! canvas 180 100 20 pi)
>
(context-update-displays!)
>
(draw-stamp! canvas 20 180 20 (* pi 1.5))
>
(context-update-displays!)
>
(draw-stamp! canvas 100 180 20 (* pi -.5))
>
(context-update-displays!)
>
(draw-stamp! canvas 180 180 20 (* pi 3.5))
>
(context-update-displays!)
Now, how do we roll the stamp around a center disc? As we indicated
above, we need to find out the center of the rolled stamp and the angle
the stamp has rolled. (The angle of rotation is not the same as the
angle of revolution, since a smaller disc (the stamp) rotates more
than it revolves when it revolves around a larger disc.) Finding
the center should be a simple variant of the line drawing above.
The center of the rolled stamp is inner-radius
plus stamp-radius
away from the center of the
inner disc. As before, we find the cosine and sine of the angle of
rotation, scale by this new distance, and then offset by the column
and row of the center. That is, the column and row at the center of
the stamp are given by the following two lines.
(+ col (* (+ inner-radius stamp-radius) (cos revolution))) (+ row (* (+ inner-radius stamp-radius) (sin revolution)))
Warning! Math ahead. You may need to read the following a few times, and even draw some diagrams to help yourself understand.
What about the angle of rotation? We need a way to compute that from the values we have so far: The angle of revolution (which we'll call theta), the inner radius (which we'll call R), and the stamp radius (which we'll call r). A reasonable first step is to figure out how far along the inner disc the stamp has traveled. We know that when we've for an angle of theta, we traverse theta/2pi of the circumference. (That is, we compute the ratio of the angle traveled to the full angle from start back to start.) What's the circumference of the inner disc? That's 2*pi*R. So, the distance traveled is theta*R.
Now, we need to compute what angle or rotation, alpha, is made when the stamp rotates enough to roll a distance of d. Using the same analysis as in the preceding paragraph, we get that the distance is alpha*r, so alpha is d/r. Putting this conclusion together with that from the previous analysis, we now know that the angle of rotation is theta*R/r.
We are now ready to express all of the above in code.
;;; Procedure: ;;; draw-rolled! ;;; Parameters: ;;; image, an image ;;; col, an integer ;;; row, an integer ;;; inner-radius, an integer ;;; stamp-radius, an integer ;;; revolution, a real number ;;; Purpose: ;;; Draws one rotated stamp, after rolling it clockwise from the starting ;;; position by an angle of revolution (expressed in radians). ;;; Produces: ;;; [Nothing, called for its side effects] ;;; Preconditions: ;;; 0 <= col < (image.width image) ;;; 0 <= row < (image.height image) ;;; 0 < inner-radius ;;; Postconditions: ;;; The image has been extended appropriately. ;;; Package: ;;; Part of the "roller pipe" drawing utilities. ;;; Props: ;;; Inspired by James Clayson's "Visual Modeling with LOGO". (define draw-rolled! (lambda (image col row inner-radius stamp-radius revolution) (draw-stamp! image (+ col (* (+ inner-radius stamp-radius) (cos revolution))) (+ row (* (+ inner-radius stamp-radius) (sin revolution))) stamp-radius (/ (* revolution inner-radius) stamp-radius))))
Once again, it is useful to see what this does by drawing a few sample images.
>
(define canvas (image-new 200 200))
>
(image-show canvas)
>
(context-set-brush! "Circle (01)")
>
(context-set-fgcolor! "green")
>
(image-draw-circle! canvas 100 100 50)
>
(context-update-displays!)
>
(context-set-fgcolor! "blue")
>
(draw-rolled! canvas 100 100 50 25 0)
>
(context-update-displays!)
>
(draw-rolled! canvas 100 100 50 25 (* pi 0.125))
>
(context-update-displays!)
>
(draw-rolled! canvas 100 100 50 25 (* pi 0.25))
>
(context-update-displays!)
>
(draw-rolled! canvas 100 100 50 25 (* pi 0.5))
>
(context-update-displays!)
>
(draw-rolled! canvas 100 100 50 25 pi)
>
(context-update-displays!)
We are now ready to draw multiple stamps. As we draw each stamp, we'll need
to keep track of how many stamps we've drawn (so that we know when we're
done) and the current angle of revolution (which we need for computation).
Hence, we write a helper procedure that includes two additional parameters:
the step and the angle of revolution. We start the step at 0 and the
angle at 0. We stop when the step equals the desired number of steps,
n
. At each recursive call, we increment the angle of revolution
and the number of steps.
(define roll! (lambda (image col row inner-radius stamp-radius theta n) (roll-kernel! image col row inner-radius stamp-radius theta n 0 0))) (define roll-kernel! (lambda (image col row inner-radius stamp-radius theta n step revolution) (cond ((< step n) (draw-rolled! image col row inner-radius stamp-radius revolution) (roll-kernel! image col row inner-radius stamp-radius theta n (+ step 1) (+ revolution theta))))))
Okay, we're ready for a simple test. Let's draw about sixteen stamps around the center.
>
(define canvas (image-new 200 200))
>
(image-show canvas)
>
(context-set-brush! "Circle (01)")
>
(context-set-fgcolor! "green")
>
(image-draw-circle! canvas 100 100 50)
>
(context-update-displays!)
>
(context-set-fgcolor! "blue")
>
(roll! canvas 100 100 50 25 (/ pi 8) 16)
>
(context-update-displays!)
If all went well, you should see an extended version of our example from the previous section.
Of course, the images we've created previously are not nearly as interesting as those we get from a Spirograph®. How do we get more spirographic images? We draw many more stamps. For example, we might repeat the previous instructions using 256 stamps, rather than sixteen.
>
(define canvas (image-new 200 200))
>
(image-show canvas)
>
(context-set-brush! "Circle (01)")
>
(context-set-fgcolor! "green")
>
(image-draw-circle! canvas 100 100 50)
>
(context-update-displays!)
>
(context-set-fgcolor! "blue")
>
(roll! canvas 100 100 50 25 (/ pi 128) 256)
>
(context-update-displays!)
You may note that in the result, the circles are starting to get in the way.
Try commenting out the call to image-draw-circle!
in
draw-stamp!
and trying the previous instructions again.
Now, let's think about varying the parameters to the
roll!
procedure. For each of the following,
first consider what you think will happen when we make the change and
then check your answer experimentally. In each case, assume that the
change is from the original call to roll!
.
What will happen if we change the inner radius from 50 to 25?
What will happen if we change the inner radius from 50 to 75?
What will happen if we change the stamp radius from 25 to 12.5?
What will happen if we change the stamp radius from 25 to 50?
What will happen if we change the stamp radius from 25 to -25?
What will happen if we change the stamp radius from 25 to 20 and the number of steps from 256 to 512?
What will happen if we change the angle from (/ pi 8)
to (/ pi 16)
and leave the number of steps at 256?
What will happen if we change the angle from (/ pi 8)
to (/ pi 16)
and the number of steps from 256 to 512?
Boy, that was a lot of preliminary exploration, wasn't it? (The amount of exploration is one of the reasons we've suggested this assignment might take a bit more time.) Okay, you're now ready to have an assignment.
Using your own versions of draw-stamp!
,
draw-rolled!
, and roll!
, generate three
interesting 512x512 images. You may use a different variant for each
image. Or, you may use the same variant for all three images, and just vary
the parameters. You may also find that you want to use multiple calls
to roll!
for the same image.
How might you vary the procedures? You could have
draw-stamp!
draw more than a single
line or a line of different lengths. You could have
roll!
set the foreground color or brush
before each call to draw-rolled!
. You could
have draw-rolled!
send different radii to
draw-stamp!
, making the radius depend upon the
angle. You may wish to experiment with techniques similar to those
you used in the lab on
geometric art. The options are nearly endless.
We strongly recommend that you get some simple variations working before trying something more complicated. Think about how you can make (and test!) your changes in small steps.
For each image, write a short paragraph explaining the techniques you've used to construct the image.
In assessing your assignments, we will emphasize the modifications you have made to the code. We will consider whether these modifications were interesting and subtle, or relatively straightforward. We will also assess assignments based on our impressions of the images and corresponding paragraphs.
roll!
, revisited
The roll!
procedure given above is not written as
elegantly as possible. A programmer who has learned named let might
rewrite it using a local kernel as follows. (In fact, this is how we
first wrote roll!
.)
(define roll! (lambda (image col row inner-radius stamp-radius theta n) (let kernel ((step 0) (revolution 0)) (cond ((< step n) (draw-rolled! image col row inner-radius stamp-radius revolution) (kernel (+ step 1) (+ revolution theta)))))))
Primary: [Front Door] [Syllabus] - [Academic Honesty] [Instructions]
Current: [Outline] [EBoard] [Reading] [Lab] [Assignment]
Groupings: [Assignments] [EBoards] [Examples] [Exams] [Handouts] [Labs] [Outlines] [Projects] [Readings]
References: [A-Z] [Primary] [Scheme Report (R5RS)] [Scheme Reference] [DrScheme Manual]
Related Courses: [CSC151.02 2008S (Davis)] [CSC151 2007F (Rebelsky)] [CSC151 2007S (Rebelsky)] [CSCS151 2005S (Stone)]
Copyright (c) 2007-8 Janet Davis, Matthew Kluber, and Samuel A. Rebelsky. (Selected materials copyright by John David Stone and Henry Walker and used by permission.)
This material is based upon work partially supported by the National Science Foundation under Grant No. CCLI-0633090. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation.
This work is licensed under a Creative Commons
Attribution-NonCommercial 2.5 License. To view a copy of this
license, visit http://creativecommons.org/licenses/by-nc/2.5/
or send a letter to Creative Commons, 543 Howard Street, 5th Floor,
San Francisco, California, 94105, USA.