Draw a Box in Open Framework
Graphics
By: Michael Hadley with generous editor back up from Abraham Avnisan, Brannon Dorsey and Christopher Baker.
This chapter builds off of the C++ Basics and Setup and Project Construction chapters, so if you aren't familiar with bones C++ and creating openFrameworks projects, check out those chapters get-go.
In sections 1 and two, we will create "paintbrushes" where the mouse is our brush and our code defines how our brush makes marks on the screen. In section iii, we will explore something called "coordinate system transformations" to create hypnotizing, spiraling rectangles. Source code for the projects is linked at the end of each section. If you lot feel lost at any point, don't hesitate to expect at the completed code! You lot tin can bank check out the whole collection of code here - both for standard evolution structure (Xcode, Lawmaking::Blocks, etc.) and for ofSketch.
If you are following forth using ofSketch, great! In that location are a couple things to note. Coding in ofSketch is a flake different than coding in other Xcode, Code::Blocks, etc. 1) At that place will be a few points where variables are added to something called a header file (.h
). When you see those instructions, that means you lot should put your variables in a higher place your setup()
office in ofSketch. 2) Yous'll want to utilise ofSetWindowShape(int width, int elevation)
in setup()
to control the size of your awarding. 3) Some of the applications yous write will salvage images to your computer. Yous can detect them in your ofSketch folder, past looking for ofSketch/data/Projects/YOUR_PROJECT_NAME/bin/data/
.
Brushes with Basic Shapes
To create brushes, nosotros demand to define some basic building blocks of graphics. We can classify the 2D graphics functions into 2 categories: bones shapes and freeform shapes. Bones shapes are rectangles, circles, triangles and direct lines. Freeform shapes are polygons and paths. In this department, we will focus on the basic shapes.
Basic Shapes
Earlier drawing any shape, we need to know how to specify locations on screen. Computer graphics use the Cartesian coordinate system. Remember effigy one (left) from math class? A pair of values (x, y)
told us how far away we were from (0, 0)
, the origin. Computer graphics are based on this same system, but with two twists. First, (0, 0)
is the upper leftmost pixel of the screen. 2d, the y centrality is flipped such that the positive y management is located below the origin effigy 1 (middle).
If nosotros apply this to the pinnacle left of my screen effigy i (correct), which happens to be my browser. Nosotros can see the pixels and identify their locations in our new coordinate arrangement. The summit left pixel is (0, 0)
. The top left pixel of the bluish calender icon (with the white "19") is (58, 5)
.
Now that we can talk nigh locations, let'southward spring into code. Create an openFrameworks project and call it "BasicShapes" (or something more imaginative). Open up the source file, ofApp.cpp
, and navigate to the draw()
office. Add the following:
ofBackground(0); // Clear the screen with a black color ofSetColor(255); // Gear up the drawing colour to white // Draw some shapes ofDrawRectangle(50, 50, 100, 100); // Top left corner at (fifty, 50), 100 wide x 100 high ofDrawCircle(250, 100, 50); // Centered at (250, 100), radius of 50 ofDrawEllipse(400, 100, 80, 100); // Centered at (400 100), eighty broad 10 100 high ofDrawTriangle(500, 150, 550, l, 600, 150); // Three corners: (500, 150), (550, fifty), (600, 150) ofDrawLine(700, fifty, 700, 150); // Line from (700, l) to (700, 150)
When nosotros run the code, we see white shapes on a black background. Success! Each time our draw()
function executes, three things happen. Showtime, we articulate the screen by drawing a solid black background using ofBackground(...)
. The 0
represents a grayscale color where 0
is completely black and 255
is completely white. Second, we specify what colour should be used for cartoon with ofSetColor(...)
. Nosotros can think of this lawmaking as telling openFrameworks to pull out a specific colored marker. When nosotros describe, we will draw in that color until nosotros specify that we desire some other color. Third, we describe our basic shapes with ofDrawRectangle(...)
, ofDrawCircle(...)
, ofDrawEllipse(...)
, ofDrawTriangle(...)
and ofDrawLine(...)
. Check out the comments in the example to improve understand how nosotros are using the drawing functions. The functions can be used in other ways every bit well, so check out the openFrameworks documentation if you lot are curious.
ofFill()
and ofNoFill()
toggle between drawing filled shapes and drawing outlines. The colored marker illustration doesn't fit, simply the concept still applies. ofFill()
tells openFrameworks to describe filled shapes until told otherwise. ofNoFill()
does the same simply with outlines. And so we can draw two rows of shapes on our screen (figure 2) - one filled and one outlines - if we modify our draw()
function to await like:
ofFill(); // If we omit this and leave ofNoFill(), all the shapes will be outlines! // Draw some shapes (lawmaking omitted) ofNoFill(); // If we omit this and leave ofFill(), all the shapes volition be filled! // Depict some shapes (lawmaking omitted)
The circle and ellipse are looking a bit jagged, so we can ready that with ofSetCircleResolution(...)
. Circles and ellipses are drawn by connecting a series of points with straight lines. If we have a shut wait at the circle in figure 2, and we'll be able to identify the 20 tiny direct lines. That'southward the default resolution. Try putting ofSetCircleResolution(l)
in the setup()
function.
The individual lines that brand up our outlines tin be jagged too. Nosotros can set that with a smoothing technique called anti-aliasing. We probably don't need to worry about this since anti-aliasing volition be turned on past default in contempo versions of openFrameworks. If it isn't, just add ofEnableAntiAliasing()
to setup()
. (For future reference, you can plow it off to save calculating power: ofDisableAntiAliasing()
.)
[Source code for this section]
[ofSketch file for this department]
Extensions
-
We can alter the thickness of lines using [`ofSetLineWidth(...)`](http://openframeworks.cc/documentation/graphics/ofGraphics.html#show_ofSetLineWidth). The default thickness is 1. Nosotros use this role similar `ofFill()` and `ofSetColor(...)` in that it changes the thickness of the "marker" we use to draw lines. Note: the range of widths supported by this feature is dependent on your graphics menu, so if it's not working, information technology might not exist your fault!
-
Draw some rounded rectangles using [`ofDrawRectRounded(...)`](http://openframeworks.cc/documentation/graphics/ofGraphics.html#!show_ofDrawRectRounded)\.
-
Explore the world of curved lines with [`ofDrawCurve(...)`](http://openframeworks.cc/documentation/graphics/ofGraphics.html#!show_ofDrawCurve) and [`ofDrawBezier(...)`](http://openframeworks.cc/documentation/graphics/ofGraphics.html#!show_ofDrawBezier). You tin control the resolution using [`ofSetCurveResolution(...)`](http://openframeworks.cc/documentation/graphics/ofGraphics.html#!show_ofSetCurveResolution)\.
Brushes from Basic Shapes
We survived the boring bits, but why draw one rectangle, when we can describe a million (effigy 3)? That is substantially what we will be doing in this department. We will build brushes that drop a burst of many small shapes whenever we printing the left mouse button. To make things more heady, nosotros will mix in some randomness. Beginning a new openFrameworks project, called "ShapeBrush."
Single Rectangle Brush: Using the Mouse
Nosotros are going to lay down the foundation for our brushes by making a simple one that draws a single rectangle when nosotros concord down the mouse. To get started, nosotros are going to need to know 1) the mouse location and 2) if the left mouse button is pressed.
For one), nosotros tin utilise 2 openFrameworks functions that return int
variables: ofGetMouseX()
and ofGetMouseY()
. Nosotros will utilize them in depict()
.
For 2), we can find out whether the left mouse button is pressed using ofGetMousePressed(...)
. The part asks united states to laissez passer in an int
that represents which mouse button is we want to know well-nigh. openFrameworks provides some "public constants" for use here: OF_MOUSE_BUTTON_LEFT
, OF_MOUSE_BUTTON_MIDDLE
and OF_MOUSE_BUTTON_RIGHT
. These public constants are merely int
variables that cannot be changed and tin be accessed anywhere you lot accept included openFrameworks. So ofGetMousePressed(OF_MOUSE_BUTTON_LEFT)
will return true
if the left button is pressed and will render simulated
otherwise.
Let's add some graphics. Hop over to the draw()
function where we can bring these new functions together:
if (ofGetMousePressed(OF_MOUSE_BUTTON_LEFT)) { // If the left mouse push button is pressed... ofSetColor(255); ofSetRectMode(OF_RECTMODE_CENTER); ofDrawRectangle(ofGetMouseX(), ofGetMouseY(), l, 50); // Describe a 50 ten fifty rect centered over the mouse }
ofSetRectMode(...)
allows us to control how the (x, y)
we laissez passer into ofDrawRectangle(...)
are used to draw. By default, they are interpreted as the upper left corner (OF_RECTMODE_CORNER
). For our purposes, we want them to be the center (OF_RECTMODE_CENTER
), so our rectangle is centered over the mouse.
Compile and run. A white rectangle is drawn at the mouse position when nosotros press the left mouse button ... but it disappears immediately. Past default, the screen is cleared with every draw()
call. We tin change that with ofSetBackgroundAuto(...)
. Passing in a value of imitation
turns off the automatic background clearing. Add the following lines into setup()
:
ofSetBackgroundAuto(fake); // We even so want to draw on a black groundwork, so nosotros need to draw // the background before nosotros do anything with the brush ofBackground(0);
First brush, done! We are going to brand this a bit more than interesting past adding i) randomness and two) repetition.
Randomness can make our code night, mysterious and unpredictable. See ofRandom(...)
. It can exist used in 2 different ways: past passing in 2 values ofRandom(float min, float max)
or by passing in a single value ofRandom(bladder max)
where the min is assumed to be 0
. The function returns a random value betwixt the min and max. We can inject some randomness into our rectangle color (figure iv) by using:
float randomColor = ofRandom(50, 255); ofSetColor(randomColor); // Exclude dark grayscale values (0 - 50) that won't show on black background
To finish off this single rectangle brush, let's add the power to erase by pressing the right mouse push button by calculation this to our depict()
function:
if (ofGetMousePressed(OF_MOUSE_BUTTON_RIGHT)) { // If the right mouse button is pressed... ofBackground(0); // Describe a black background over the screen }
[Source lawmaking for this section]
[ofSketch file for this section]
Bursting Rectangle Brush: Creating Randomized Bursts
We now have the basics in identify for a brush, only instead of drawing a unmarried rectangle in depict()
, let's draw a burst of randomized rectangles. We are going apply a for
loop to create multiple rectangles whose parameters are randomly chosen. What can we randomize? Grayscale color, width and height are like shooting fish in a barrel candidates. We can also use a modest positive or negative value to beginning each rectangle from mouse position. Modify draw()
to look similar this:
if (ofGetMousePressed(OF_MOUSE_BUTTON_LEFT)) { // If the left mouse button is pressed... ofSetRectMode(OF_RECTMODE_CENTER); int numRects = 10; for (int r=0; r<numRects; r++) { ofSetColor(ofRandom(50, 255)); float width = ofRandom(five, xx); float elevation = ofRandom(5, 20); float xOffset = ofRandom(-40, 40); float yOffset = ofRandom(-40, forty); ofDrawRectangle(ofGetMouseX()+xOffset, ofGetMouseY()+yOffset, width, elevation); } }
But! Add i more matter, inside of setup()
, earlier hitting run: ofSetFrameRate(60)
. The frame rate is the speed limit of our program, frames per second (fps). update()
and draw()
will not run more than 60
times per 2d. (ofSketch users - we'll talk nearly update()
later.) Note: this is a speed limit, non a speed minimum - our code tin run slower than 60
fps. We set the frame rate in order to control how many rectangles volition be fatigued. If 10
rectangles are drawn with the mouse pressed and we know draw()
won't be called more than 60
times per second, then nosotros will generate a max of 600
rectangles per second.
Compile, run. We get a box-shaped spread of random rectangles (figure 5, left). Why didn't nosotros go a circular spread (figure 5, correct)? Since xOffset
and yOffset
could be whatsoever value between -40
and 40
, think near what happens when xOffset
and yOffset
accept on their most extreme values, i.e. (xOffset, yOffset) values of (-twoscore, -40), (40, -forty), (40, forty) and (-40, twoscore).
If we want a random signal inside a circle, information technology helps to think in terms of angles. Imagine we are at the middle of a circle. If we rotate a random corporeality (the polar angle) and and then move a random altitude (the polar radius), nosotros stop upwardly in a random location inside the circle (bold we don't walk so far that we cross the boundary of our circle). We merely defined a point by a polar angle and a polar radius instead of using (x, y)
. We take just idea well-nigh space in terms of Polar coordinates, instead of Cartesian coordinates.
Dorsum to the code. When we figure out our offsets, we want to pick a random direction (polar bending) and random distance (polar distance) which nosotros can and then catechumen to Cartesian coordinates (run into lawmaking) to use every bit xOffset
and yOffset
. Our loop inside of draw()
will wait similar this:
for (int r=0; r<numRects; r++) { ofSetColor(ofRandom(50, 255)); float width = ofRandom(5, 20); float height = ofRandom(five, xx); bladder distance = ofRandom(35); // Formula for converting from polar to Cartesian coordinates: // x = cos(polar angle) * (polar distance) // y = sin(polar angle) * (polar distance) // We need our angle to be in radians if nosotros desire to use sin() or cos() // so we tin make use of an openFrameworks function to catechumen from degrees // to radians float angle = ofRandom(ofDegToRad(360.0)); bladder xOffset = cos(angle) * distance; float yOffset = sin(angle) * distance; ofDrawRectangle(ofGetMouseX()+xOffset, ofGetMouseY()+yOffset, width, height); }
[Source lawmaking for this section]
[ofSketch file for this section]
Glowing Circumvolve Brush: Using Transparency and Color
Unlike what we did with the rectangle brush, we are going to layer colorful, transparent circles on top of each to create a glowing brume. We volition draw a giant transparent circle, then describe a slightly smaller transparent circle on acme of it, then repeat, repeat, echo. We tin can add transparency to ofSetColor(...)
with a second parameter, the alpha channel (e.g.ofSetColor(255, 50)
), with a value from 0
(completely transparent) to 255
(completely opaque).
Before nosotros utilise alpha, nosotros need to enable something called "alpha blending." Using transparency costs calculating power, so ofEnableAlphaBlending()
and ofDisableAlphaBlending()
allow the states to turn on and off this blending at our discretion. Nosotros need it, so enable it in setup()
.
Annotate out the rectangle brush code within the if
statement that checks if the left mouse button is pressed. Now we can kickoff working on our circle brush. We will utilise the bending
, distance
, xOffset
and yOffset
code like earlier. Our for
loop will start with a large radius and step its value to 0
. Add the following:
int maxRadius = 100; // Increment for a wider castor int radiusStepSize = five; // Decrease for more circles (i.eastward. a more opaque brush) int alpha = 3; // Increase for a more opaque brush int maxOffsetDistance = 100; // Increase for a larger spread of circles // draw smaller and smaller circles and layering (increasing) opaqueness for (int radius=maxRadius; radius>0; radius-=radiusStepSize) { float angle = ofRandom(ofDegToRad(360.0)); float distance = ofRandom(maxOffsetDistance); float xOffset = cos(angle) * altitude; bladder yOffset = sin(angle) * distance; ofSetColor(255, blastoff); ofDrawCircle(ofGetMouseX()+xOffset, ofGetMouseY()+yOffset, radius); }
Nosotros terminate upwards with something like effigy 6, a glowing light except without colour. Tired of living in moody shades of gray? ofSetColor(...)
tin can brand use of the Red Blueish Green (RGB) color model in addition to the grayscale color model. We specify the amount (0
to 255
) of red, blue and greenish light respectively, e.g. ofSetColor(255, 0, 0)
for opaque reddish. We tin besides add alpha, east.thou. ofSetColor(0, 0, 255, ten)
for transparent blue. Go ahead and alter the ofSetColor(...)
in our circle brush to use a nice orangish: ofSetColor(255, 103, 0, alpha)
.
There's another style we can use ofSetColor(...)
. Encounter ofColor
, a handy class for treatment colors which allows for fancy color math (amidst other things). Hither are some examples of defining and modifying colors:
ofColor myOrange(255, 132, 0); // Defining an opaque orange colour - specified using RGB ofColor myBlue(0, 0, 255, fifty); // Defining a transparent blueish color - specified using RGBA // We can access the cerise, green, bluish and alpha channels like this: ofColor myGreen(0, 0, 255, 255); cout << "Red channel:" << myGreen.r << endl; cout << "Green aqueduct:" << myGreen.m << endl; cout << "Blueish channel:" << myGreen.b << endl; cout << "Blastoff channel:" << myGreen.a << endl; // Nosotros tin too gear up the red, green, bluish and alpha channels like this: ofColor myYellow; myYellow.r = 255; myYellow.b = 0; myYellow.m = 255; myYellow.a = 255; // Nosotros tin can likewise make use of some predefined colors provided by openFrameworks: ofColor myAqua = ofColor::aqua; ofColor myPurple = ofColor::plum; // Full list of colors available at: http://openframeworks.cc/documentation/types/ofColor.html
If nosotros wanted to make our castor fierier, nosotros would depict using random colors that are in-between orangish and blood-red. ofColor
gives us in-betweenness using something chosen "linear interpolation" with a office called getLerped(...)
. getLerped(...)
is a grade method of ofColor
, which ways that if we take an ofColor
variable, we can interpolate like this: myFirstColor.getLerped(mySecondColor, 0.3)
. (For an explanation of classes and methods, come across the OOPS! chapter.) We pass in ii arguments, an ofColor
and a float
value between 0.0
and 1.0
. The function returns a new ofColor
that is betwixt the 2 specified colors, and the float
determines how close the new color is to our original color (here, myFirstColor
). We tin use this in draw()
like this:
ofColor myOrange(255, 132, 0, alpha); ofColor myRed(255, 6, 0, alpha); ofColor inBetween = myOrange.getLerped(myRed, ofRandom(1.0)); ofSetColor(inBetween);
[Source code for this section]
[ofSketch file for this department]
Star Line Castor: Working with a Linear Map
What well-nigh lines? We are going to create a brush that draws lines that radiate out from the mouse to create something similar to an asterisk or a twinkling star (figure 7). Comment out the circumvolve brush and add together:
int numLines = xxx; int minRadius = 25; int maxRadius = 125; for (int i=0; i<numLines; i++) { float bending = ofRandom(ofDegToRad(360.0)); bladder distance = ofRandom(minRadius, maxRadius); float xOffset = cos(angle) * distance; float yOffset = sin(angle) * distance; float blastoff = ofMap(distance, minRadius, maxRadius, 50, 0); // Make shorter lines more opaque ofSetColor(255, alpha); ofDrawLine(ofGetMouseX(), ofGetMouseY(), ofGetMouseX()+xOffset, ofGetMouseY()+yOffset); }
What have we done with the alpha? Nosotros used ofMap(...)
to do a linear interpolation, similar to getLerped(...)
. ofMap(...)
transforms one range of values into a different range of values - like taking the "loudness" of a sound recorded on a microphone and using it to decide the color of a shape fatigued on the screen. To become a "twinkle" upshot, we want our shortest lines to be the near opaque and our longer lines to be the nigh transparent. ofMap(...)
takes a value from one range and maps it into another range like this: ofMap(value, inputMin, inputMax, outputMin, outputMax)
. We tell information technology that distance is a value
in-betwixt minRadius
and maxRadius
and that we want it mapped and so that a altitude value of 125 (maxRadius
) returns an blastoff value of l and a distance value of 25 (minRadius
) returns an alpha value of 0.
We can also vary the line width using: ofSetLineWidth(ofRandom(1.0, five.0))
, but remember that if we change the line width in this brush, we will need get dorsum and set our line width back to 1.0
in our other brushes.
[Source code for this department]
[ofSketch file for this section]
Fleeing Triangle Brush: Vectors and Rotations
Fourth dimension for the last brush in section 1: the triangle. We'll draw a bunch of triangles that are directed outward from the mouse position (figure 8, left). ofDrawTriangle(...)
requires us to specify the three corners of the triangle, which means that we will demand to calculate the rotation of the corners to make the triangle signal abroad from the mouse. A new class volition make that math easier: ofVec2f
.
We've been defining points by keeping two divide variables: x and y. ofVec2f
is a 2nd vector, and for our purposes, we can just think of information technology as a point in 2D space. ofVec2f
allows us to hold both ten and y in a single variable (and perform handy math operations):
ofVec2f mousePos(ofGetMouseX(), ofGetMouseY()); // Defining a new ofVec2f // Admission the x and y coordinates similar this: cout << "Mouse X: " << mousePos.x << endl; cout << "Mouse Y: " << mousePos.y << endl; // Or we can alter the coordinates like this: float xOffset = x.0; bladder yOffset = xxx.0; mousePos.x += xOffset; mousePos.y += yOffset; // Simply nosotros can do what nosotros just did above by calculation or subtracting two vectors directly ofVec2f start(10.0, 30.0); mousePos += starting time;
Permit's start using it to build the triangle brush. The first footstep is to draw a triangle (figure 8, right) at the mouse cursor. Information technology will become important afterward, but we are going to describe our triangle starting from the mouse cursor and pointing to the right. Comment out the line castor, and add together:
ofVec2f mousePos(ofGetMouseX(), ofGetMouseY()); // Ascertain a triangle at the origin (0,0) that points to the correct ofVec2f p1(0, 25.0); ofVec2f p2(100, 0); ofVec2f p3(0, -25.0); // Shift the triangle to the mouse position p1 += mousePos; p2 += mousePos; p3 += mousePos; ofSetColor(255, l); ofDrawTriangle(p1, p2, p3);
Run it and see what happens. We can add rotation with the ofVec2f
class method rotate(...)
like this: myPoint.rotate(45.0)
where myPoint
is rotated around the origin, (0, 0)
, by 45.0
degrees. Back to our lawmaking, add this correct before shifting the triangle to the mouse position:
// Rotate the triangle points around the origin float rotation = ofRandom(360); // The rotate function uses degrees! p1.rotate(rotation); p2.rotate(rotation); p3.rotate(rotation);
Our brush looks something similar figure viii (left). If nosotros were to motility that rotation code to subsequently we shifted the triangle position, the lawmaking wouldn't work very nicely because rotate(...)
assumes we want to rotate our point around the origin. (Check out the documentation for an alternating way to use rotate(...)
that rotates effectually an arbitrary signal.) Terminal pace, permit's integrate our prior approach of cartoon multiple shapes that are offset from the mouse:
ofVec2f mousePos(ofGetMouseX(), ofGetMouseY()); int numTriangles = 10; int minOffset = 5; int maxOffset = lxx; int alpha = 150; for (int t=0; t<numTriangles; t++) { float offsetDistance = ofRandom(minOffset, maxOffset); // Define a triangle at the origin (0,0) that points to the correct (code omitted) // The triangle size is a bit smaller than the last castor - see the source code // Rotate the triangle, and so shift it to the mouse position (code omitted) ofVec2f triangleOffset(offsetDistance, 0.0); triangleOffset.rotate(rotation); p1 += mousePos + triangleOffset; p2 += mousePos + triangleOffset; p3 += mousePos + triangleOffset; ofSetColor(255, alpha); ofDrawTriangle(p1, p2, p3); }
We are now using ofVec2f
for our offset. Nosotros started with a vector that points rightward, the same management our triangle starts out pointing. When we use the rotation to them both, they stay in sync (i.e. both pointing away from the mouse). We tin push them out of sync with: triangleOffset.rotate(rotation+90)
, and we get a swirling blob of triangles. Later on that, we can add together some colour using ofRandom(...)
and getLerped(...)
once more (figure nine) or play with fill up and line width.
[Source code for this department]
[ofSketch file for this section]
Extensions
-
Ascertain some public variables to control brush parameters like `transparency`, `brushWidth`, `offsetDistance`, `numberOfShapes`, etc.
-
Use the [`keyPressed(int cardinal)`](http://openframeworks.cc/documentation/application/ofBaseApp.html#!show_keyPressed) function (in `.cpp`) to control those parameters at run time (e.one thousand. increasing/decreasing `brushWidth` with the `+` and `-` keys). If you are using ofSketch, run across the adjacent section for how to employ that role.
-
Runway the mouse position and use the distance it moves betwixt frames to control those parameters (e.g. fast moving mouse draws a thicker brush).
Raster Graphics: Taking a Snapshot
Earlier we motion on, let's salvage a snapshot of our canvas. We'll want to use the keyPressed(int primal)
function. This function is built into your application by default. Whatsoever fourth dimension a key is pressed, the lawmaking you put into this function is called. The fundamental
variable is an integer that represents the key that was pressed.
If you are using projection generator, you lot'll find keyPressed(...)
in your .cpp
file. If y'all are using ofSketch, you might not encounter the function, but it is easy to add. Meet the ofSketch file for the last department.
In the keyPressed(...)
function, add together the post-obit:
if (key == 'due south') { // It's strange that we can compare the int key to a graphic symbol like `south`, right? Well, the super brusk // explanation is that characters are represented by numbers in programming. `s` and 115 are the same // affair. If you want to know more, check out the wiki for ASCII. glReadBuffer(GL_FRONT); // HACK: simply needed on windows, when using ofSetAutoBackground(false) ofSaveScreen("savedScreenshot_"+ofGetTimestampString()+".png"); }
ofSaveScreen(...)
grabs the current screen and saves it to a file within of our project's ./bin/data/
folder with a filename nosotros specify. The timestamp is used to create a unique filename, assuasive us to salve multiple screenshots without worrying about them overriding each other. So press the s
key and check out your screenshot!
Brushes from Freeform Shapes
In the last department, nosotros drew straight onto the screen. We were storing graphics (brush strokes) as pixels, and therefore working with raster graphics. For this reason, information technology is hard to isolate, move or erase a single brush stroke. Information technology also ways we can't re-render our graphics at a different resolution. In contrast, vector graphics store graphics as a list of geometric objects instead of pixel values. Those objects can be modified (erased, moved, rescaled, etc.) after nosotros "place" them on our screen.
In this section, we are going to make a kind of vector graphics by using custom ("freeform") shapes in openFrameworks. We volition utilize structures (ofPolyline
and vector<ofPolyline>
) that allow united states of america to store and draw the path that the mouse takes on the screen. Then we will play with those paths to create brushes that do more just trace out the cursor's movement.
Bones Polylines
Create a new projection chosen "Polylines," and say hullo to ofPolyline
. ofPolyline
is a information construction that allows us to store a series of sequential points and then connect them to draw a line. Permit'southward swoop into some lawmaking. In your header file (inside "class ofApp" in "ofApp.h" to be precise), define three ofPolylines
:
ofPolyline straightSegmentPolyline; ofPolyline curvedSegmentPolyline; ofPolyline closedShapePolyline;
Nosotros can fill up those ofPolylines
with points in setup()
:
straightSegmentPolyline.addVertex(100, 100); // Add a new point: (100, 100) straightSegmentPolyline.addVertex(150, 150); // Add a new bespeak: (150, 150) straightSegmentPolyline.addVertex(200, 100); // etc... straightSegmentPolyline.addVertex(250, 150); straightSegmentPolyline.addVertex(300, 100); curvedSegmentPolyline.curveTo(350, 100); // These curves are Catmull-Rom splines curvedSegmentPolyline.curveTo(350, 100); // Necessary Duplicate for Control Signal curvedSegmentPolyline.curveTo(400, 150); curvedSegmentPolyline.curveTo(450, 100); curvedSegmentPolyline.curveTo(500, 150); curvedSegmentPolyline.curveTo(550, 100); curvedSegmentPolyline.curveTo(550, 100); // Necessary Duplicate for Control Signal closedShapePolyline.addVertex(600, 125); closedShapePolyline.addVertex(700, 100); closedShapePolyline.addVertex(800, 125); closedShapePolyline.addVertex(700, 150); closedShapePolyline.close(); // Connect first and last vertices
Nosotros tin at present draw our polylines in the draw()
function:
ofBackground(0); ofSetLineWidth(2.0); // Line widths employ to polylines ofSetColor(255,100,0); straightSegmentPolyline.depict(); // This is how nosotros describe polylines curvedSegmentPolyline.describe(); // Dainty and easy, right? closedShapePolyline.draw();
We created three different types of polylines (effigy 11). straightSegmentPolyline
is composed of a series points connected with straight lines. curvedSegmentPolyline
uses the aforementioned points only connects them with curved lines. The curves that are created are Catmull–Rom splines, which utilize four points to define a bend: ii define the start and end, while two control points determine the curvature. These control points are the reason why we need to add the outset and last vertex twice. Lastly, closedShapePolyline
uses straight line segments that are closed, connecting the start and last vertices.
The advantage of drawing in this way (versus raster graphics) is that the polylines are modifiable. We could easily movement, add together, delete, scale our vertices on the the fly.
[Source code for this section]
[ofSketch file for this department]
Extensions
-
Bank check out [`arc(...)`](http://openframeworks.cc/documentation/graphics/ofPolyline.html#show_arc), [`arcNegative(...)`](http://openframeworks.cc/documentation/graphics/ofPolyline.html#show_arcNegative) and [`bezierTo(...)`](http://openframeworks.cc/documentation/graphics/ofPolyline.html#show_bezierTo) for other ways to draw shapes with `ofPolyline`.
Building a Castor from Polylines
Polyline Pen: Tracking the Mouse
Allow's apply polylines to describe brush strokes. Create a new project, "PolylineBrush." When the left mouse push is held down, nosotros will create an ofPolyline
and continually extend it to the current mouse position. We will use a bool
to tell us if the left mouse push button is being held down. If it is being held down, nosotros'll add the mouse position to the polyline, but instead of calculation every mouse position, we'll add together the mouse positions where the mouse has moved a distance abroad from the concluding signal in our polyline.
Let's move on to the code. Create four variables in the header:
ofPolyline currentPolyline; bool leftMouseButtonPressed; ofVec2f lastPoint; bladder minDistance;
Initialize minDistance
and leftMouseButtonPressed
in setup()
:
minDistance = x; leftMouseButtonPressed = simulated;
Now we are going to take reward of two new functions - mousePressed(int x, int y, int push button)
and mouseReleased(int x, int y, int button)
. These are functions that are built into your awarding by default. They are upshot handling functions, so whenever a mouse button is pressed, whatever code you put into mousePressed(...)
is chosen. It'due south important to note that mousePressed(...)
is only chosen when the mouse push button is initially pressed. No thing how long we concur the mouse button downwardly, the office is still but called one time. The same goes for mouseReleased(...)
.
The functions accept a few variables x
, y
and button
that allow you to know a fleck more most the detail mouse event that but occurred. x
and y
are the screen coordinates of the cursor, and button
is an int
that represents the particular button on the mouse that was pressed/released. Retrieve the public constants like OF_MOUSE_BUTTON_LEFT
and OF_MOUSE_BUTTON_RIGHT
? To figure out what button
is, we'll compare information technology against those constants.
Let's turn dorsum to the code. If you are using projection generator, you'll find these mouse functions in your .cpp
file. If you are using ofSketch, y'all might not encounter these functions, but they are easy to add. See the ofSketch file for this section. Inside of mousePressed(...)
, we want to start the polyline:
if (button == OF_MOUSE_BUTTON_LEFT) { leftMouseButtonPressed = truthful; currentPolyline.curveTo(ten, y); // Remember that x and y are the location of the mouse currentPolyline.curveTo(x, y); // Necessary duplicate for outset control signal lastPoint.gear up(x, y); // Set the 10 and y of a ofVec2f in a single line }
Inside of mouseReleased(...)
, we desire to end the polyline:
if (button == OF_MOUSE_BUTTON_LEFT) { leftMouseButtonPressed = faux; currentPolyline.curveTo(x, y); // Necessary duplicate for concluding control point currentPolyline.clear(); // Erase the vertices, allows us to start a new brush stroke }
At present permit'due south move over to the update()
function. For ofSketch users, this is another default function that you might not encounter in your sketch. It is a function that is called once per frame, and it is intended for doing non-drawing tasks. It'south like shooting fish in a barrel to add - see the ofSketch file for this section.
Let's add points to our polyline in update()
:
if (leftMouseButtonPressed) { ofVec2f mousePos(ofGetMouseX(), ofGetMouseY()); if (lastPoint.distance(mousePos) >= minDistance) { // a.distance(b) calculates the Euclidean distance between point a and b. It's // the length of the straight line distance between the points. currentPolyline.curveTo(mousePos); // Here we are using an ofVec2f with curveTo(...) lastPoint = mousePos; } }
Annotation that this simply adds points when the mouse has moved a certain threshold amount (minDistance
) away from the last point we added to the polyline. This uses the distance(...)
method of ofVec2f
.
All that is left is to add lawmaking to depict the polyline in draw()
, and we've got a basic curved polyline drawing program. But we don't have the ability to save multiple polylines, so we have something similar to an Etch A Sketch. Nosotros can only depict a single, continuous line. In club to be able to draw multiple lines that don't take to be connected to each other, we will turn to something chosen a vector
. This isn't the same kind of vector that we talked about before in the context of of2Vecf
. If you haven't seen vectors before, check out the stl::vector nuts tutorial on the site.
Ascertain vector <ofPolyline> polylines
in the header. We volition apply it to save our polyline brush strokes. When we finish a stroke, we desire to add the polyline to our vector. Then in the if argument inside of mouseReleased(...)
, before currentPolyline.articulate()
, add polylines.push_back(currentPolyline)
. So nosotros can draw the polylines like this:
ofBackground(0); ofSetColor(255); // White color for saved polylines for (int i=0; i<polylines.size(); i++) { ofPolyline polyline = polylines[i]; polyline.draw(); } ofSetColor(255,100,0); // Orange color for active polyline currentPolyline.draw();
And we have a simple pen-similar brush that tracks the mouse, and we can draw a dopey smiley face up (figure 12).
[Source code for this section]
[ofSketch file for this department]
Extensions
-
Add colour!
-
Explore [`ofBeginSaveScreenAsPDF(...)`](http://openframeworks.cc/documentation/graphics/ofGraphics.html#!show_ofBeginSaveScreenAsPDF) and [`ofEndSaveScreenAsPDF(...)`](http://openframeworks.cc/documentation/graphics/ofGraphics.html#!show_ofEndSaveScreenAsPDF) to salve your piece of work into a vector file format.
-
Try using the `keyPressed(...)` function in your source file to add an undo feature that deletes the most recent brush stroke.
-
Try restructuring the lawmaking to let for a redo feature too.
Polyline Brushes: Points, Normals and Tangents
Since we accept the bones drawing in place, now we play with how we are rendering our polylines. Nosotros will draw points, normals and tangents. Nosotros'll talk near what normals and tangents are in a little fleck. First, let'southward draw points (circles) at the vertices in our polylines. Inside the for
loop in depict()
(after polyline.draw()
), add together this:
vector<ofVec3f> vertices = polyline.getVertices(); for (int vertexIndex=0; vertexIndex<vertices.size(); vertexIndex++) { ofVec3f vertex = vertices[vertexIndex]; // ofVec3f is like ofVec2f, but with a 3rd dimension, z ofDrawCircle(vertex, 5); }
getVertices()
returns a vector
of ofVec3f
objects that represent the vertices of our polyline. This is basically what an ofPolyline
is - an ordered ready of ofVec3f
objects (with some extra math). We can loop through the indices of the vector to pull out the private vertex locations, and utilize them to draw circles.
What happens when we run it? Our white lines wait thicker. That's because our polyline is jam-packed with vertices! Every time we call the curveTo(...)
method, we create 20 extra vertices (by default). These assist make a smoothen-looking curve. We can accommodate how many vertices are added with an optional parameter, curveResolution
, in curveTo(...)
. We don't demand that many vertices, simply instead of lowering the curveResolution
, we can make use of simplify(...)
.
simplify(...)
is a method that will remove "duplicate" points from our polyline. We pass a single argument into it: tolerance
, a value between 0.0 and one.0. The tolerance
describes how dis-similar points must exist in order to exist considered 'unique' enough to not be deleted. The college the tolerance
, the more points will be removed. So right earlier we salve our polyline by putting information technology into our polylines
vector, we can simplify it. Inside of the if statement inside mouseReleased(...)
(earlier polylines.push_back(currentPolyline)
), add: currentPolyline.simplify(0.75)
. Now nosotros should see something like figure 13 (left).
We can besides sample points forth the polyline using getPointAtPercent(...)
, which takes a float
betwixt 0.0
and 1.0
and returns a ofVec3f
. Inside the describe()
function, annotate out the code that draws a circumvolve at each vertex. Beneath that, add:
for (int p=0; p<100; p+=10) { ofVec3f point = polyline.getPointAtPercent(p/100.0); // Returns a bespeak at a percentage forth the polyline ofDrawCircle(bespeak, 5); }
Now nosotros take evenly spaced points (figure xiii, right). (Notation that there is no circle drawn at the stop of the line.) Allow's endeavor creating a brush stroke where the thickness of the line changes. To do this we demand to use a normal vector. Effigy 14 shows normals drawn over some polylines - they point in the opposite (perpendicular) direction to the polyline. Imagine drawing a normal at every point along a polyline, similar figure 15. That is one way to add together "thickness" to our castor. We can comment out our circle cartoon code in draw()
, and add together these lines of code instead:
vector<ofVec3f> vertices = polyline.getVertices(); float normalLength = 50; for (int vertexIndex=0; vertexIndex<vertices.size(); vertexIndex++) { ofVec3f vertex = vertices[vertexIndex]; // Go the vertex ofVec3f normal = polyline.getNormalAtIndex(vertexIndex) * normalLength; // Scale the normal ofDrawLine(vertex-normal/ii, vertex+normal/2); // Center the scaled normal around the vertex }
We are getting all of the vertices in our ofPolyline
. But hither, we are too using getNormalAtIndex
which takes an index and returns an ofVec3f
that represents the normal vector for the vertex at that index. We have that normal, calibration it and and then display information technology centered around the vertex. So, we accept something like effigy 14 (left), but we tin also sample normals, using the function getNormalAtIndexInterpolated(...)
. So let'southward annotate out the code we but wrote, and try sampling our normals evenly along the polyline:
float normalLength = 50; for (int p=0; p<100; p+=10) { ofVec3f point = polyline.getPointAtPercent(p/100.0); float floatIndex = polyline.getIndexAtPercent(p/100.0); ofVec3f normal = polyline.getNormalAtIndexInterpolated(floatIndex) * normalLength; ofDrawLine(point-normal/2, point+normal/2); }
We tin get an evenly spaced point by using percents again, merely getNormalAtIndexInterpolated(...)
is asking for an index. Specifically, it is asking for a floatIndex
which ways that nosotros can pass in i.five and the polyline will render a normal that lives halfway between the signal at index one and halfway between the point at index 2. So we demand to catechumen our pct, p/100.0
, to a floatIndex
using getIndexAtPercent(...)
. Once we've washed that, nosotros'll have something similar figure 14 (right).
Now we can pump up the number of normals in our drawing. Permit's alter our loop increment from p+=x
to p+=1
, change our loop status from p<100
to p<500
and change our p/100.0
lines of code to p/500.0
. Nosotros might also desire to use a transparent white for drawing these normals, and so permit'southward add ofSetColor(255,100)
correct before our loop. We volition terminate up beingness able to draw ribbon lines, similar figure xv.
Nosotros've just added some thickness to our polylines. Now let's have a quick aside about tangents, the "reverse" of normals. These wonderful things are perpendicular to the normals that we just drew. So if we drew tangents along a perfectly direct line nosotros wouldn't really see anything. The fun part comes when nosotros draw tangents on a curved line, so allow's run into what that looks like. Same drill as before. Comment out the terminal code and add in the following:
vector<ofVec3f> vertices = polyline.getVertices(); float tangentLength = 80; for (int vertexIndex=0; vertexIndex<vertices.size(); vertexIndex++) { ofVec3f vertex = vertices[vertexIndex]; ofVec3f tangent = polyline.getTangentAtIndex(vertexIndex) * tangentLength; ofDrawLine(vertex-tangent/2, vertex+tangent/2); }
This should await very familiar except for getTangentAtIndex(...)
which is the equivalent of getNormalAtIndex(...)
just for tangents. Not much happens for directly and slightly curved lines, however, sharply curved lines reveal the tangents effigy xvi (left).
I'chiliad certain you tin guess what'south side by side... drawing a whole bunch of tangents at evenly spaced locations (effigy 16, right)! It'south more than fun that information technology sounds. getTangentAtIndexInterpolated(...)
works like getNormalAtIndexInterpolated(...)
. Same drill, comment out the last lawmaking, and add together the following:
ofSetColor(255, l); float tangentLength = 300; for (int p=0; p<500; p+=1) { ofVec3f point = polyline.getPointAtPercent(p/500.0); float floatIndex = polyline.getIndexAtPercent(p/500.0); ofVec3f tangent = polyline.getTangentAtIndexInterpolated(floatIndex) * tangentLength; ofDrawLine(point-tangent/2, point+tangent/ii); }
[Source code for this department]
[ofSketch file for this section]
Extensions
-
Attempt cartoon shapes other than `ofDrawLine(...)` and `ofDrawCircle(...)` along your polylines. You could use your brush lawmaking from department ane.
-
The density of tangents or normals drawn is dependent on the length of the castor stroke. Try making it independent (hint: y'all may need to adjust your loop and use `getPerimeter()` to calculate the length).
-
Check out how to draw polygons using `ofPath` and try drawing a castor stroke that is a giant, closed shape.
Vector Graphics: Taking a Snapshot (Part 2)
Remember how we saved our drawings that we fabricated with the basic shape brushes by doing a screen capture? Well, we can also save drawings equally a PDF. A PDF stores the graphics as a series of geometric objects rather than equally a series of pixel values. Then, if we render out our polylines equally a PDF, nosotros tin can open it in a vector graphics editor (like Inkscape or Adobe Illustrator) and modify our polylines in all sorts of ways. For example, see figure 17 where I colored and blurred the polylines to create a glowing effect.
Once nosotros have a PDF, nosotros could also utilize information technology to accident upwards our polyines to create a massive, loftier resolution print.
To exercise any of this, we demand to use ofBeginSaveScreenAsPDF(...)
and ofEndSaveScreenAsPDF()
. When we call ofBeginSaveScreenAsPDF(...)
, any subsequent drawing commands will output to a PDF instead of being drawn to the screen. ofBeginSaveScreenAsPDF(...)
takes one required argument, a string
that contains the desired filename for the PDF. (The PDF will be saved into ./bin/data/
unless you specify an alternate path). When we call ofEndSaveScreenAsPDF()
, the PDF is saved and cartoon commands begin outputting back to the screen.
Allow'due south use the polyline brush code from the last section to salvage a PDF. The manner nosotros saved a screenshot previously was to put ofSaveScreen()
within of keyPressed(...)
. We tin't do that here because ofBeginSaveScreenAsPDF(...)
and ofEndSaveScreenAsPDF()
need to be before and afterward (respectively) the drawing lawmaking. So we'll make employ of a bool
variable. Add bool isSavingPDF
to the header (.h) file, then alter your source code (.cpp) to look like this:
void ofApp::setup(){ // Setup lawmaking omitted for clarity... isSavingPDF = false; } void ofApp::draw(){ // If isSavingPDF is true (i.due east. the s primal has been pressed), so // anything in betwixt ofBeginSaveScreenAsPDF(...) and ofEndSaveScreenAsPDF() // is saved to the file. if (isSavingPDF) { ofBeginSaveScreenAsPDF("savedScreenshot_"+ofGetTimestampString()+".pdf"); } // Drawing code omitted for clarity... // Finish saving the PDF and reset the isSavingPDF flag to false // Ending the PDF tells openFrameworks to resume drawing to the screen. if (isSavingPDF) { ofEndSaveScreenAsPDF(); isSavingPDF = imitation; } } void ofApp::keyPressed(int key){ if (key == 'south') { // isSavingPDF is a flag that lets usa know whether or not save a PDF isSavingPDF = true; } }
[Source code for this department]
[ofSketch file for this section]
Moving The Globe
We've been making brushes for a long time, and then let's move onto something unlike: moving the world. Past the world, I really just mean the coordinate system (though it sounds more than exciting the other style).
Whenever we call a drawing function, like ofDrawRectangle(...)
for example, we pass in an x
and y
location at which nosotros want our shape to be fatigued. We know (0,0) to exist the upper left pixel of our window, that the positive x direction is rightward beyond our window and that positive y management is downward forth our window (recall effigy 1). We are nigh to violate this established knowledge.
Imagine that we have a piece of graphing newspaper in front of us. How would we draw a black rectangle at (v, ten) that is five units wide and two units high? We would probably grab a black pen, motion our easily to (v, 10) on our graphing paper, and offset filling in boxes? Pretty normal, only we could accept also have kept our pen manus stationary, moved our newspaper v units left and x units down and and so started filling in boxes. Seems odd, correct? This is actually a powerful concept. With openFrameworks, we tin can motility our coordinate organization like this using ofTranslate(...)
, merely we tin can also rotate and calibration with ofRotate(...)
and ofScale(...)
. We will starting time with translating to embrace our screen with stick figures, and then we will rotate and scale to create spiraling rectangles.
Translating: Stick Family
ofTranslate
first. ofTranslate(...)
takes an x, a y and an optional z parameter, and then shifts the coordinate system by those specified values. Why practise this? Create a new projection and add this to our draw()
function of our source file (.cpp):
// Depict the stick effigy family ofDrawCircle(30, 30, thirty); ofDrawRectangle(five, 70, l, 100); ofDrawCircle(95, 30, 30); ofDrawRectangle(seventy, seventy, 50, 100); ofDrawCircle(45, 90, 15); ofDrawRectangle(30, 110, 30, lx); ofDrawCircle(80, 90, 15); ofDrawRectangle(65, 110, thirty, 60);
Depict a white background and color the shapes, and we end up with something like figure 18 (left).
What if, later on figuring out where to put our shapes, we needed to draw them at a unlike spot on the screen, or to depict a row of copies? We could change all the positions manually, or we could use ofTranslate(...)
to move our coordinate arrangement and leave the positions lone:
// Loop and draw a row for (int cols=0; cols<iv; cols++) { // Draw the stick figure family (code omitted) ofTranslate(150, 0); }
So our original shapes are wrapped it in a loop with ofTranslate(150, 0)
, which shifts our coordinate organisation to the left 150 pixels each fourth dimension it executes. And we'll cease upwardly with figure 18 (2nd from left). Or something close to that, I randomized the colors in the figure - every family unit is different, right?
If we wanted to create a filigree of families, we will run into problems. After the beginning row of families, our coordinate system will have been moved quite far to the left. If we move our coordinate system upwards in order to commencement drawing our second row, we volition end up drawing off the screen in a diagonal. It would look like figure 18 (3rd from left).
And so what we need is to reset the coordinate system using ofPushMatrix()
and ofPopMatrix()
. ofPushMatrix()
saves the electric current coordinate system and ofPopMatrix()
returns the states to the last saved coordinate arrangement. These functions accept the word matrix in them considering openFrameworks stores all of our combined rotations, translations and scalings in a single matrix. And then we tin utilize these new functions like this:
for (int rows=0; rows<10; rows++) { ofPushMatrix(); // Save the coordinate organization before nosotros shift it horizontally // Information technology is often helpful to indent any code in-between button and popular matrix for readability // Loop and draw a row (code omitted) ofPopMatrix(); // Render to the coordinate system before we shifted information technology horizontally ofTranslate(0, 200); }
And we should finish up with a filigree. Encounter effigy 18, right. (I used ofScale
to jam many in one image.) Or if you hate grids, we can make a mess of a crowd using random rotations and translations, figure 19.
[Source code for this department]
[ofSketch file for this section]
Rotating and Scaling: Spiraling Rectangles
Onto ofScale(...)
and ofRotate(...)
! Allow'southward create a new project where rotating and scaling rectangles to go something like effigy 20.
Before knowing nigh ofRotate(...)
, we couldn't have drawn a rotated rectangle with ofDrawRectangle(...)
. ofRotate(...)
takes an angle (in degrees) and rotates our coordinate system effectually the current origin. Permit's endeavour a rotated rectangle:
ofBackground(255); ofPushMatrix(); // Original rectangle in blue ofSetColor(0, 0, 255); ofDrawRectangle(500, 200, 200, 200); // Rotated rectangle in red ofRotate(45); ofSetColor(255, 0, 0); ofDrawRectangle(500, 200, 200, 200); ofPopMatrix();
Hmm, non quite correct (effigy 21, left). ofRotate(...)
rotates around the current origin, the top left corner of the screen. To rotate in place, we need ofTranslate(...)
to move the origin to our rectangle earlier nosotros rotate. Add ofTranslate(500, 200)
before rotating (figure 21, second from left). Now we are rotating around the upper left corner of the rectangle. The easiest style to rotate the rectangle effectually its center is to utilise ofSetRectMode(OF_RECTMODE_CENTER)
draw the center at (500, 200). Do that, and we finally get figure 21, third from left.
Push button, translate, rotate, pop - no problem. Only thing left is ofScale(...)
. Information technology takes 2 arguments: the desired scaling in 10 and y directions (and an optional z scaling). Applying scaling to our rectangles:
ofSetRectMode(OF_RECTMODE_CENTER); ofBackground(255); ofPushMatrix(); // Original rectangle in blue ofSetColor(0, 0, 255); ofDrawRectangle(500, 200, 200, 200); // Scaled down rectangle in cherry ofTranslate(500, 200); ofScale(0.five, 0.5); // Nosotros are only working in x and y, so let'due south get out the z scale at its default (i.0) ofSetColor(255, 0, 0); ofDrawRectangle(0, 0, 200, 200); ofPopMatrix();
We'll run into the aforementioned issues that nosotros ran into with rotation and centering. The solution is the aforementioned - translating earlier scaling and using OF_RECTMODE_CENTER
. Instance scaling shown in figure 21 (right).
At present we can make trippy rectangles. Outset a new projection. The idea is actually simple, nosotros are going to draw a rectangle at the centre of the screen, calibration, rotate, depict a rectangle, repeat and repeat. Add the following to our depict()
function:
ofBackground(255); ofSetRectMode(OF_RECTMODE_CENTER); ofSetColor(0); ofNoFill(); ofPushMatrix(); ofTranslate(ofGetWidth()/2, ofGetHeight()/two); // Translate to the centre of the screen for (int i=0; i<100; i++) { ofScale(1.1, 1.1); ofRotate(5); ofDrawRectangle(0, 0, fifty, fifty); } ofPopMatrix();
That's it (figure 20). We can play with the scaling, rotation, size of the rectangle, etc. Three lines of code volition add some life to our rectangles and cause them to coil and uncoil over time. Put these in the place of ofRotate(5)
:
// Noise is a topic that deserves a department in a book unto itself // Check out Department i.6 of "The Nature of Code" for a good explanation // http://natureofcode.com/book/introduction/ bladder time = ofGetElapsedTimef(); float timeScale = 0.5; bladder noise = ofSignedNoise(time * timeScale) * twenty.0; ofRotate(racket);
Next, we can create a visual smear ("trail issue") as it rotates if we will turn off the background automatic clearing and partially erase the screen before drawing again. To do this add a few things to setup()
:
ofSetBackgroundAuto(false); ofEnableAlphaBlending(); // Think if we are using transparency, we demand to let openFrameworks know ofBackground(255);
Delete ofBackground(255)
from our describe()
function. Then, add together this to the beginning of our draw()
function:
float clearAlpha = 100; ofSetColor(255, clearAlpha); ofSetRectMode(OF_RECTMODE_CORNER); ofFill(); ofDrawRectangle(0, 0, ofGetWidth(), ofGetHeight()); // ofBackground doesn't work with alpha, and then describe a transparent rect
Pretty hypnotizing? If we turn upwardly the clearAlpha
, we will turn down the smear. If we refuse the clearAlpha
, we volition plough upward the smear.
Now we've got two parameters that drastically change the visual feel of our spirals, specifically: timeScale
of noise and clearAlpha
of the trail effect. Instead of manually tweaking their values in the code, we can use the mouse position to independently control the values during run time. Horizontal position can adjust the clearAlpha
while vertical position can adjust the timeScale
. This type of exploration of parameter settings is super important (specially when making generative graphics), and using the mouse is handy if we've got one or 2 parameters to explore.
mouseMoved(int 10, int y )
runs anytime the mouse moves (in our app). We can use this part to change our parameters. Like with mousePressed(...)
, if you are using projection generator, you'll find this with your mouse functions in your .cpp
file. If you are using ofSketch, you might non see this role, merely it'due south easy to add together. Encounter the ofSketch file for this section.
Earlier nosotros apply this function, nosotros demand to make our parameters global in their scope. Delete the lawmaking that defines timeScale
and clearAlpha
locally in draw()
and add them to the header. Initialize the values in setup()
to 100
and 0.5
respectively. Then add these to mouseMoved(...)
:
clearAlpha = ofMap(10, 0, ofGetWidth(), 0, 255); // clearAlpha goes from 0 to 255 as the mouse moves from left to right timeScale = ofMap(y, 0, ofGetHeight(), 0, 1); // timeScale goes from 0 to one as the mouse moves from top to bottom
One concluding extension. Nosotros tin can slowly flip the background and rectangle colors, by calculation this to the top of describe()
:
ofColor darkColor(0,0,0,255); // Opaque black ofColor lightColor(255,255,255,255); // Opaque white bladder time = ofGetElapsedTimef(); // Time in seconds float pct = ofMap(cos(fourth dimension/2.0), -1, i, 0, 1); // Create a value that oscillates between 0 to 1 ofColor bgColor = darkColor; // Color for the transparent rectangle we apply to articulate the screen bgColor.lerp(lightColor, percent); // This modifies our color "in place", check out the documentation page bgColor.a = clearAlpha; // Our initial colors were opaque, but our rectangle needs to be transparent ofColor fgColor = lightColor; // Color for the rectangle outlines fgColor.lerp(darkColor, per centum); // Modifies color in place
Now utilise bgColor
for the transparent rectangle we describe on the screen and fgColor
for the rectangle outlines to get figure 22.
[Source lawmaking for this section]
[ofSketch file for this section]
Extensions
-
Pass in a third parameter, `z`, into `ofTranslate(...)` and `ofScale(...)` or rotate around the x and y axes with `ofRotate(...)`.
-
Capture blithe works using an addon called [ofxVideoRecorder](https://github.com/timscaffidi/ofxVideoRecorder). If you are using Windows, like me, that won't piece of work for you, then endeavour screen capture software (like fraps) or saving out a series of images using `ofSaveScreen(...)` and using them to create a GIF or movie with your preferred tools (Photoshop, [ffmpeg](https://ffmpeg.org/), [ImageMagick](http://imagemagick.org/) etc.)
Side by side Steps
Congratulations on surviving the chapter :). You covered a lot of ground and (hopefully) made some fun things forth the way - which you should share on the forums!
If you are looking to learn more nearly graphics in openFrameworks, definitely keep on to Advanced Graphics affiliate to dive into more avant-garde graphical features. Yous tin also cheque out these iii tutorials: Nuts of Generating Meshes from an Image, for a gentle introduction to meshes; Basics of OpenGL, for a comprehensive look at graphics that helps explain what is happening under the hood of openFrameworks; and Introducing Shaders, for a swell way to beginning programming with your graphical processing unit (GPU).
이 책은 현재 번역작업중이므로, 오탈자나 여러 오류가 있을 수 있습니다. 원본 영문의 내용도 활발히 수정중임 또한 감안해주시기 바랍니다. 오류 발견시 이곳에 글을 남겨주시기 바라며, 한글 번역에 관한 내용은 오픈프레임웍스한글포럼에의견을 남겨주시기 바랍니다.
Source: http://openframeworks.kr/ofBook/chapters/intro_to_graphics.html