Project 10: Non-Photorealistic Rendering Assigned: Wed Apr 23 2014 Due: 11:59:59 PM on Tue Apr 29 2014 Team Size: 1 Language: Python Lab: Not Quite Straight Lines
The purpose of this lab is to introduce you to the concept of
non-photorealistic rendering [NPR] and give you some practice with
designing modifications to your current system to make NPR easy to
implement.
If you're interested in seeing more examples of NPR, check out the
NPR resources
page.
The other piece we'll be implementing this week is how to handle
parameterized L-systems. These will give us much more flexibility in
defining shapes and complex L-system objects. We'll continue to use
material from the ABOP
book.
Tasks
In lab today we'll be editing the Shape class and the TurtleInterpreter
class to implement one version of NPR that does a reasonable job of
simulating a crayon sketch. The goal is to make the change in such a
way that all of the classes you've developed so far will work without
modification. The design choice in our existing system that made it
possible to do this was the use of the TurtleInterpreter class to handle all
of the actual drawing commands.
To implement various NPR methods, we're going to enable the
TurtleInterpreter class to execute the 'F' case in different ways. We'll
create a field in the TurtleInterpreter class that holds the current style
and then draw the line corresponding to the 'F' symbol differently,
depending on the style field.
To give the capability to select styles to the Shape objects, we'll
also add a style field to the Shape class so different objects can
draw themselves using different styles.
-
Create a new Proj10 folder. Copy your lsystem.py, turtle_interpreter.py,
and shapes.py files from your prior assignment (version 3). This week
we are writing version 4 of all three files. Label them with a
comment as as version 4.
-
Open your turtle interpreter file. In the TurtleInterpreter class __init__
function, add two fields to the object called style and
jitterSigma. Give the style field the value 'normal' and
the jitterSigma field the value 2. Be sure to put these assignments
before initializing the rest of the fields (meaning, directly after the docstring). Note that we will not be adding parameters to __init__ for jitterSigma or style. Instead, we will rely only on the mutator methods to change their values to anything other than the defaults. Our rationale is that we don't want too many parameters in __init__. Looking forward, we realize there will be additional fields added to the TurtleInterpreter, and we don't want to add parameters for every single field. So we won't add them for these fields.
-
In the TurtleInterpreter class, create a mutator method def setStyle(self,
style) that assigns the style field the value of the parameter
style. Then create a mutator method def setJitter(self, jitter) that
assigns the jitterSigma field the value of the parameter jitter.
-
In the TurtleInterpreter class, create a method def forward(self,
distance) that implements the following algorithm.
def forward(self, distance):
# if self.style is 'normal'
# have the turtle go foward by distance
# else if self.style is 'jitter'
# assign to x0 and y0 the result of turtle.position()
# pick up the turtle
# have the turtle go forward by distance
# assign to xf and yf the result of turtle.position()
# assign to curwidth the results of turtle.width()
# assign to jx the result of random.gauss(0, self.jitterSigma)
# assign to jy the result of random.gauss(0, self.jitterSigma)
# assign to kx the result of random.gauss(0, self.jitterSigma)
# assign to ky the result of random.gauss(0, self.jitterSigma)
# set the turtle width to (curwidth + random.randint(0, 2))
# have the turtle go to (x0 + jx, y0 + jy)
# put the turtle down
# have the turtle go to (xf + kx, yf + ky)
# pick up the turtle
# have the turtle go to (xf, yf)
# set the turtle width to curwidth
# put the turtle down
-
Once you have completed the above function, edit your 'F' case in
drawString so that it calls self.forward(distance) instead of
turtle.forward(distance). Then download the following
test function and try it out.
-
Open your shapes.py file. In the Shape class, update your __init__
method to add fields for the style, jitterSigma, and line
width. Then make mutators called setStyle, setJitter, and setWidth to
enable a programmer to set those values. Then edit the draw method so
that it calls the turtle interpreter's setStyle, setJitter, and width methods before calling drawString, just like it currently does with color.
Then run the following test function.
-
Go back to your turtle_interpreter.py file. Now we're going to modify the
drawString function to handle parameters on symbols. We're going to
represent parameters as a number inside parentheses in front of
the symbol it modifies. The string
FF(120)+F(60)+F(60)+F(120)+, for example, should draw a
trapezoid by modifying the left turns (+ symbols).
There are notes that talk about the strategy for making this function work. Please read them.
At the top of the drawString method, initialize three local variables
along with your stack and colorstack.
# assign to modstring the empty string
# assign to modval the value None
# assign to modgrab the value False
At the beginning of the main for loop over the input string, put the
following conditional statement, separate from the main one already
there. This section handles modifiers separately from symbols.
# if char is '('
# assign to modstring the empty string
# assign to modgrab the value True
# continue
# else if char is ')'
# assign to modval the result of casting modstring to a float
# assign to modgrab False
# continue
# else if modgrab (is True)
# add to modstring the character char
# continue
Edit your 'F' case so it looks like the following.
# if modval is None
# call self.forward with the argument distance
# else
# call self.forward with the argument distance * modval
Edit your '+', '-', and '!' cases so they all do their normal action
if modval is None, but they use modval as the argument to turtle.left,
turtle.right, or turtle.width, respectively, if it is not None. If you
don't have a case for '!', make one now that follows the logic below.
# if char is '!'
# if modval is None
# assign to width the result of calling turtle.width()
# if width is greater than 1
# call turtle.width with width-1 as the argument
# else
# call turtle.width with modval as the argument
Finally, assign to modval the value None at the end of the for loop
over the input string. This should be inside the for loop, but
outside of the big if-else structure. It is important that this is
indented properly.
When you are done, run the following test file.
-
The goal of this last change is to enable L-system files of the form:
base (100)F
rule (x)F (x)F[!+(x*0.67)F][!-(x*0.67)F]
The above should replace a trunk with a trunk and two branches, where
the branches are shorter than the trunk. The only variable we're
going to allow is x.
We will be supplying the code for these changes.
Open your lsystem.py file and delete the contents of your applyRules function, replacing it with the following code.
def applyRules(self, inputString):
""" Replace all characters in the istring with strings from the
right-hand side of the appropriate rule. This version handles
parameterized rules.
"""
resultString = ''
parstring = ''
parval = None
pargrab = False
for char in inputString:
if char == '(':
# put us into number-parsing-mode
pargrab = True
parstring = ''
continue
# elif the character is )
elif char == ')':
# put us out of number-parsing-mode
pargrab = False
parval = float(parstring)
continue
# elif we are in number-parsing-mode
elif pargrab:
# add this character to the number string
parstring += char
continue
if parval != None:
key = '(x)' + char
if key in self.rules:
replacement = random.choice(self.rules[key])
resultString += self.substitute( replacement, parval )
else:
if char in self.rules:
replacement = random.choice(self.rules[char])
resultString += self.insertmod( replacement, parstring, char )
else:
resultString += '(' + parstring + ")" + char
parval = None
else:
if char in self.rules:
resultString += random.choice(self.rules[char])
else:
resultString += char
return resultString
-
Copy the following two methods, substitute and
insertmod into your LSystem class. Make sure to Detab
the file before continuing.
def substitute(self, sequence, value ):
""" given: a sequence of parameterized symbols using expressions
of the variable x and a value for x
substitute the value for x and evaluate the expressions
"""
expr = ''
exprgrab = False
outsequence = ''
for c in sequence:
# parameter expression starts
if c == '(':
# set the state variable to True (grabbing the expression)
exprgrab = True
expr = ''
continue
# parameter expression ends
elif c == ')':
exprgrab = False
# create a function out of the expression
lambdafunc = eval( 'lambda x: ' + expr )
# execute the function and put the result in a (string)
newpar = '(' + str( lambdafunc( value ) ) + ')'
outsequence += newpar
# grabbing an expression
elif exprgrab:
expr += c
# not grabbing an expression and not a parenthesis
else:
outsequence += c
return outsequence
def insertmod(self, sequence, modstring, symbol):
""" given: a sequence, a parameter string, a symbol
inserts the parameter, with parentheses,
before each
instance of the symbol in the sequence
"""
tstring = ''
for c in sequence:
if c == symbol:
# add the parameter string in parentheses
tstring += '(' + modstring + ')'
tstring += c
return tstring
-
The next step is to test the new code with one of the L-systems given below. These L-systems have many interesting features. To understand them better, see these notes. In particular they tells us that the f symbol should make the turtle move forward, just like the F symbol does.
So, as a mini-step, add support to drawString for the f symbol.
Run the final test function using one of the
L-systems given above. E.g, you can run it with sysTree.txt, 3 iterations, a distance of 3, and an angle of 22.5.
-
As a final test to show what is possible, try out this test function. It requires your
turtle_interpreter.py, shapes.py, and tree.py to generate the scene. Run it
with sysTree2.txt or sysTree3.txt as the command-line argument.
Assignment:
For the project, you'll need to implement two more styles of
drawing. To implement a new style, you just need to add another case
to the if statement in the forward method of the Interpreter class.
The last step in the project is to enhance your scene from the last
project by making use of the various styles you implement this week.
You should be able to run the scene with different styles with
minimal changes to your code.
Tasks
-
Implement a style 'jitter3' that draws the line segment as three, criss-crossing jittered lines. The implementation is similar to the 'jitter' case, but
instead of drawing one line, you draw three. All three lines should begin at a point that is a short, random distance from the turtle's initial position and end at at a point that is a short, random distance from the desired end point.
This will involve several goto statements, e.g.
jx = random.gauss(0,self.jitterSigma)
jy = random.gauss(0,self.jitterSigma)
turtle.goto( x0+jx, y0+jy )
For each goto statement, the jx and jy values should be
regenerated from a Gaussian distribution (random.gauss) with a zero
mean and jitterSigma as the standard deviation. They should not all be
the same.
Make a file taskA.py that draws three copies of one of your shapes
from last week. Show the shape drawn in 'normal', 'jitter', and
'jitter3' style.
An image with three copies of a shape in different styles is required
image 1.
-
Create a 'dotted' style that draws a series of circles separated by spaces. Create a field in the TurtleInterpreter to hold the dotSize (the radius of the circle). You'll also need a
setDotSize method in the TurtleInterpreter class and a setDotSize
method and associated dotSize field in the Shape class, just as we did
with the setStyle and setJitter information.
Make a file taskB.py that generates a collection of shapes that show
the 'normal', 'jitter', 'jitter3', and 'dotted' drawing modes. Your
writeup should point out which examples are which.
An image of the collection of shapes in four different styles is
required image 2.
-
Make a copy of your indoor scene code--or create a brand new
scene--from last week and put it in a new file taskC.py. Edit your
scene so that it makes use of the different drawing styles. Feel free
to enhance it, but focus on enhancements that make use of the
different drawing styles and shape classes you've created. When you
are done, you should have something that looks a bit more like a real
painting or drawing.
The updated indoor scene is required image 3.
-
Make your own new parameterized stochastic multi-rule L-system. You can create a
variation on one of the given files or look in ABOP for inspiration.
If you create a variation, you need to do more than just add ornaments
(berries or leaves). You need to make the shape structurally
different so the difference is obvious.
Your new L-system does not have to be a tree, but it does need to
include branching, multiple rules, and at least one rule with more
than one replacement string. Describe the L-system you designed in
your writeup and explain your design choices. Make a scene or image
that includes your L-system.
A picture of the new L-system is required image 4.
Extensions
Each assignment will have a set of suggested extensions. The required tasks constitute about 85% of the assignment, and if you do only the required tasks and do them well you will earn a B+. To earn a higher grade, you need to undertake one or more extensions. The difficulty and quality of the extension or extensions will determine your final grade for the assignment. One complex extension, done well, or 2-3 simple extensions are typical.
-
Create more L-systems. Show how they differ in their design.
-
Add other drawing styles. For example, try making one that simulates a
brush by drawing many approximately parallel lines. Slight variations
in color between the different lines makes the effect better. You
might also try a pen-and-ink style with cross-hatching or just a
series of straight lines at an angle to the direction of the actual
line. Note although you are welcome to implement dashed and broken styles, they will not be counted as extensions.
-
Modify drawString so that when drawing a tree the branches droop down
like gravity is pulling at them. This involves determining the turtle's current angle. If the current heading is in the first or fourth quadrant, then gravity will make a right turn angle larger. If the current heading is in second or third quadrant, then gravity will make a right turn angle smaller.
-
Create a sequence of images to build an animation.
-
Make more shape classes that do interesting things. Making a fixed
sequence of characters is easy. Make a shape class where the strings
are the result of executing a function. L-systems are one example of
a dynamically created string, but there are many other ways to do
that.
-
Be more creative with tasks A and B. In other words, use programming
structures, user input, and code to go beyond the minimal required
image.
-
Use a Python language feature new to you (not just a new library feature or function)
Writeup and Hand-In
Before handing in your code, double check that it is well-styled:
- All variable names and function names use either camelCase or snake_case.
- All files and functions have docstrings.
- Comments are added to explain complicated code blocks.
- All variable and function names are appropriately descriptive.
- Functions are defined before any other code is added at the top level of each file.
- In a file with any functions defined, top level code is wrapped so that it won't execute if that file is imported by another.
Make a new wiki page for your assignment. Put the label cs151s14project10
on the page. Each of you needs to make your own writeup.
In addition to making the wiki page writeup, put the python files you
wrote on the Academics server in your private handin directory.
Colby Wiki
In general, your writeup should follow the outline below.
-
A brief summary of the task, in your own words. This should be no
more than a few sentences. Give the reader context and identify the
key purpose of the assignment.
-
A description of your solution to the tasks, including any images
you created. (Make sure your images are appropriately sized to fit onto the wiki page.) This should be a description of the form and
functionality of your final code. You may want to incorporate code
snippets in your description to point out relevant features. Note
any unique computational solutions you developed.
-
A description of any extensions you undertook, including images
demonstrating those extensions. If you added any modules,
functions, or other design components, note their structure and the
algorithms you used.
-
A brief description (1-3 sentences) of what you learned.
-
A list of people you worked with, including students who took the course in previous semesters, TAs, and professors. Include in this list anyone whose code you may have seen. If you didn't work with anyone, please say so.
-
Don't forget to label your writeup so that it is easy for others to find. For this lab, use cs151s14project10
|