Lab 9: Blending curves
Note: Whenever you design or write a function, you need to follow the design recipe.
Note: Use the “Intermediate Student” language on this assignment.
Note: This lab is long, but don’t worry. You’ll still get credit even if you don’t finish the whole thing.
In this lab, we’ll write some programs to generate and draw lines and curves. Given two lines or curves, we will also be able to generate a line or curve that is a blend of the two. This technique is often used in computer graphics to create images and videos of different things that morph or blend together.
For example, check out the following Animorphs image.
There are two images, one of a child and the other of a tiger, along with the progression of images from child to tiger. The image closest to the child is more child than tiger, but as the images get closer to the tiger they become more tiger than child.
Doing the same with straight lines instead of images can create some beautiful art:
(The photo above is of a sculpture by Bailee Koi entitled “Over. Under. Through. Tighten. Over. Under. Through. Tighten. Over. Under. Through. Tighten. Over. Under. Through. Tighten.”, shown at our Grunwald Gallery of Art in April 2019.)
1 Blending numbers
Let’s start by blending numbers.
For example, if the input n is 0 then the output should be 20, since 20 is 0% on the way from 20 to 180.
If the input n is 50 then the output should be 100, since 100 is 50% on the way from 20 to 180.
Finally, if the input n is 100 then the output should be 180, since 180 is 100% on the way from 20 to 180.
Hint 2: Think about the distance between the starting point 20 and the destination 180. What is 1% of that distance? Add a check-expect for when the input n is 1.
Hint 3: Don’t do any arithmetic in your head. Make DrRacket do it by putting the arithmetic in your definition.
For example, if the input n is 0 then the output should be 90, since 90 is 0% on the way from 90 to 10.
If the input n is 50 then the output should be 50, since 50 is 50% on the way from 90 to 10.
Finally, if the input n is 100 then the output should be 10, since 10 is 100% on the way from 90 to 10.
Hint 2: Think about the distance between the starting point 90 and the destination 10. What is 1% of that distance? Add a check-expect for when the input n is 1.
Hint 3: The formula should be very similar to the previous one. The more similar, the better. That’s why you shouldn’t do any arithmetic in your head.
Design at least 2 similar functions. You just did this.
Identify inessential differences and revise functions to remove them.
Hint! You may need to revise the arithmetic formulas to make the two functions more similar. For instance, one student had to change (- 90 (* 0.8 n)) to (+ 90 (* -0.8 n)). Another student had to change 1.6 back to (/ (- 180 20) 100).
Circle essential differences and pick input names for each of them.
Hint! If you circle anything bigger than a number, it is too big and you must go back to making the two functions more similar. You should end up with just two essential differences: the number 20 or 90, and the number 180 or 10.
Design the abstraction by replacing differences with input names. (The new abstraction should have its own signature and purpose statement. If the original tests do not provide sufficient coverage for the abstraction, include additional tests.)
Redefine the original functions using the new abstraction. (You don’t need to submit the original versions.) The redefined functions should have the same signatures and purpose statements as before as well as pass the same tests as before.
2 Blending points
Let’s work our way up to points.
; line1 : NaturalNumber -> Posn
For example, if the input n is 0 then the output should be the starting point (make-posn 20 90).
If the input n is 50 then the output should be (make-posn 100 50), since that is the midpoint between (make-posn 20 90) and (make-posn 180 10).
Finally, if the input n is 100 then the output should be the destination (make-posn 180 10).
Hint 2: You don’t need the Pythagorean theorem or trigonometry. Don’t even do any arithmetic. Just use number-between twice (once for X and once for Y), and package the results into a Posn.
Hint 3: If you’re not sure how to package two numbers into a Posn, review Lecture 14: Built-in structures Exercise 1.
; line2 : NaturalNumber -> Posn
For example, if the input n is 0 then the output should be the starting point (make-posn 30 200).
If the input n is 50 then the output should be (make-posn 20 100), since that is the midpoint between (make-posn 30 200) and (make-posn 10 0).
Finally, if the input n is 100 then the output should be the destination (make-posn 10 0).
Hint 2: You don’t need the Pythagorean theorem or trigonometry. Don’t even do any arithmetic. Just use number-between twice (once for X and once for Y), and package the results into a Posn.
Hint 3: The formula should be very similar to the previous one. The more similar, the better. That’s why you shouldn’t do any arithmetic.
; posn-between : Posn Posn NaturalNumber -> Posn
As with number-between, if the input n is 0 then the output should be start.
If the input n is 50 then the output should be the midpoint between start and end.
Finally, if the input n is 100 then the output should be end.
Hint 2: You don’t need the Pythagorean theorem or trigonometry. Don’t even do any arithmetic. Just use number-between twice (once for X and once for Y), and package the results into a Posn.
Hint 3: Follow the template for processing Posns. It has no cond.
Exercise 7. Redefine line1 and line2 to use posn-between, without affecting what those functions do at all.
3 Drawing curves
(require 2htdp/image) (define background (empty-scene 200 200))
; draw-wave : NaturalNumber Image -> Image ; draw the first that-many points of a wave on the given image (check-expect (draw-wave 0 background) background) (check-expect (draw-wave 3 background) ) (check-expect (draw-wave 100 background) ) (define (draw-wave n bg) (cond [(= n 0) bg] [else (place-image (circle 1 "solid" "dark green") (* n 2) (+ 70 (* 30 (sin (/ n 5)))) (draw-wave (- n 1) bg))]))
Exercise 8. Which data definition’s processing template does draw-wave follow? If you’re not sure, review Lecture 13: More self-reference Exercise 3.
; draw-line1 : NaturalNumber Image -> Image ; draw the first that-many points of a straight line on the given image (check-expect (draw-line1 0 background) background) (check-expect (draw-line1 3 background) ) (check-expect (draw-line1 100 background) ) (define (draw-line1 n bg) (cond [(= n 0) bg] [else (place-image (circle 1 "solid" "blue") (posn-x (line1 n)) (posn-y (line1 n)) (draw-line1 (- n 1) bg))]))
Exercise 9. Which data definition’s processing template does draw-line1 follow? If you’re not sure, review Lecture 14: Built-in structures Exercise 5.
Exercise 10. How many dots is (draw-line1 99 background) made of?
Let’s now abstract from the two functions above, draw-wave and draw-line1. First we need to make them more similar. For that, we often need to introduce a little helper function.
Exercise 11. Download a clean copy of the two functions. Make sure the tests pass; they rely on your line1 function.
; wave : NaturalNumber -> Posn
[else (place-image (circle 1 "solid" "dark green") (posn-x (wave n)) (posn-y (wave n)) (draw-wave (- n 1) bg))]
the function wave or line1,
and the color string "dark green" or "blue".
4 Blending curves
Helper functions like wave, line1, and line2 each represent a curve as a function from numbers to positions. We can make more curves by blending existing curves.
; curve1 : NaturalNumber -> Posn ; start like line1 and end like wave (check-within (curve1 0) (line1 0) 0.001) (check-within (curve1 100) (wave 100) 0.001) (check-within (curve1 50) (posn-between (line1 50) (wave 50) 50) 0.001) (define (curve1 n) (posn-between (line1 n) (wave n) n))
; curve2 : NaturalNumber -> Posn ; start like line2 and end like line1 (check-expect (curve2 0) (line2 0)) (check-expect (curve2 100) (line1 100)) (check-expect (curve2 50) (posn-between (line2 50) (line1 50) 50)) (define (curve2 n) (posn-between (line2 n) (line1 n) n))
Exercise 15. Draw the two pictures to the side above. You don’t have to use the same colors.
Exercise 16. Abstract from the previous two functions, curve1 and curve2. There are just two differences, which are both functions from NaturalNumber to Posn. Call your new abstraction blend. Remember that it should have its own signature and purpose statement. And don’t forget to redefine the original functions using the new abstraction.
; curve3 : NaturalNumber -> Posn
; curve4 : NaturalNumber -> Posn
; curve5 : NaturalNumber -> Posn
Exercise 20. Redefine line1 and line2 to use blend instead of posn-between. You’ll have to design helper functions that return the same Posn no matter what NaturalNumber they get.
A fixed point has order 0.
A straight line has order 1.
Blending straight lines yields curves of order 2, like curve2 and curve4.
Exercise 21. What is the order of curve5?
5 Challenges
Exercise 22. Design a function that takes a function from NaturalNumbers to Posns and estimates the length of the curve it represents. Hint: Use the Pythagorean theorem to measure the distance between adjacent points.
; A Style is (make-style [NaturalNumber -> Posn] String) (define-struct style [curve color]) ; A ListOfStyles is one of: ; - empty ; - (cons Style ListOfStyles) ; draw-styles : ListOfStyles Image -> Image ; draw the given styles on the given image (check-expect (draw-styles empty background) background) (check-expect (draw-styles (list (make-style curve5 (make-color 255 0 255)) (make-style curve2 (make-color 150 0 255)) (make-style curve4 (make-color 255 0 150)) (make-style line1 (make-color 0 0 255)) (make-style line2 (make-color 255 0 0))) background) )
Add a fixed point, which is a Bézier curve of order 0.
Add a curve by blending two existing curves.
Remove a curve.
Exercise 25. This lab and the secret messages part of Lab 7: Lists were both written by Paulette D. Koronkevich, who was an amazing student in this course. When Paulette was an undergraduate instructor, she made videos for this course. Now she is doing research in graduate school. If you’ve enjoyed this course so far, now is a good time to start thinking about becoming an undergraduate instructor and about doing research!