While Cinder's convenience methods are indeed convenient, they should be avoided when performance matters. Let's replicate the last example in the previous section, but upgrade it to use
class BasicApp : public App {
public:
void setup() override;
void draw() override;
CameraPersp mCam;
gl::BatchRef mBox;
};
void BasicApp::setup()
{
auto lambert = gl::ShaderDef().lambert().color();
gl::GlslProgRef shader = gl::getStockShader( lambert );
mBox = gl::Batch::create( geom::Cube(), shader );
mCam.lookAt( vec3( 3, 4.5, 4.5 ), vec3( 0, 1, 0 ) );
}
void BasicApp::draw()
{
gl::clear();
gl::enableDepthRead();
gl::enableDepthWrite();
gl::setMatrices( mCam );
int numSpheres = 64;
float maxAngle = M_PI * 7;
float spiralRadius = 1;
float height = 2;
float boxSize = 0.05f;
float anim = getElapsedFrames() / 30.0f;
for( int s = 0; s < numSpheres; ++s ) {
float rel = s / (float)numSpheres;
float angle = rel * maxAngle;
float y = fabs( cos( rel * M_PI + anim ) ) * height;
float r = rel * spiralRadius;
vec3 offset( r * cos( angle ), y / 2, r * sin( angle ) );
gl::pushModelMatrix();
gl::translate( offset );
gl::scale( vec3( boxSize, y, boxSize ) );
gl::color( Color( CM_HSV, rel, 1, 1 ) );
mBox->draw();
gl::popModelMatrix();
}
}
This example contains several new concepts, so let's look at each one individually. First, we're preparing our setup()
now, storing it in the member variable mCam. We still call Model
and Projection
matrices.
The most important change though is that we're no longer calling setup()
. We still call
Finally, we're instantiating our geom::Sphere
, geom::Icosahedron
and others. Although there are others, we're using the primary gl::Batch constructor here, supplying a draw()
is quite similar to the previous version, the primary change being that we now call mBox->draw()
. The Model
matrix, just as
Let's look at
class BasicApp : public App {
public:
void setup() override;
void draw() override;
CameraPersp mCam;
gl::BatchRef mShapes[3][3];
};
void BasicApp::setup()
{
auto lambert = gl::ShaderDef().lambert().color();
gl::GlslProgRef shader = gl::getStockShader( lambert );
auto capsule = geom::Capsule().subdivisionsAxis( 10 )
.subdivisionsHeight( 10 );
mShapes[0][0] = gl::Batch::create( capsule, shader );
auto sphere = geom::Sphere().subdivisions( 30 );
mShapes[0][1] = gl::Batch::create( sphere, shader );
auto cylinder = geom::Cylinder().subdivisionsAxis( 40 )
.subdivisionsHeight( 2 );
mShapes[0][2] = gl::Batch::create( cylinder, shader );
auto cube = geom::Cube();
mShapes[1][0] = gl::Batch::create( cube, shader );
auto cone = geom::Cone();
mShapes[1][1] = gl::Batch::create( cone, shader );
auto torus = geom::Torus();
mShapes[1][2] = gl::Batch::create( torus, shader );
auto helix = geom::Helix().subdivisionsAxis( 20 )
.subdivisionsHeight( 10 );
mShapes[2][0] = gl::Batch::create( helix, shader );
auto icosahedron = geom::Icosahedron();
mShapes[2][1] = gl::Batch::create( icosahedron, shader );
auto teapot = geom::Teapot() >> geom::Scale( 1.5f );
mShapes[2][2] = gl::Batch::create( teapot, shader );
mCam.lookAt( vec3( 5, 11, 5 ), vec3( 0 ) );
}
void BasicApp::draw()
{
gl::clear();
gl::enableDepthRead();
gl::enableDepthWrite();
gl::setMatrices( mCam );
float gridSize = 5;
for( int i = 0; i < 3; ++i ) {
for( int j = 0; j < 3; ++j ) {
float x = ( -0.5f + i / 2.0f ) * gridSize;
float z = ( -0.5f + j / 2.0f ) * gridSize;
gl::ScopedModelMatrix scpModelMatrix;
gl::translate( x, 1, z );
gl::color( i / 2.0f, 1 - i * j, j / 2.0f );
mShapes[i][j]->draw();
}
}
}
This example explores a number of the built-in geom::Teapot() >> geom::Scale( 1.5f )
line. This example uses a >>
operator.
The instance of gl::Scoped*
classes, such as Model
matrix initially, and then will restore this value when it goes out of scope at the end of the inner for-loop. This is equivalent to separate calls to gl::Scoped*
classes more convenient and generally less error-prone.
Let's continue looking at another example of combining
#include "cinder/Easing.h"
…
class BasicApp : public App {
public:
void setup() override;
void draw() override;
static const int NUM_SLICES = 12;
CameraPersp mCam;
gl::BatchRef mSlices[NUM_SLICES];
};
void BasicApp::setup()
{
auto lambert = gl::ShaderDef().lambert().color();
gl::GlslProgRef shader = gl::getStockShader( lambert );
for( int i = 0; i < NUM_SLICES; ++i ) {
float rel = i / (float)NUM_SLICES;
float sliceHeight = 1.0f / NUM_SLICES;
auto slice = geom::Cube().size( 1, sliceHeight, 1 );
auto trans = geom::Translate( 0, rel, 0 );
auto color = geom::Constant( geom::COLOR,
Color( CM_HSV, rel, 1, 1 ) );
mSlices[i] = gl::Batch::create( slice >> trans >> color,
shader );
}
mCam.lookAt( vec3( 2, 3, 2 ), vec3( 0, 0.5f, 0 ) );
}
void BasicApp::draw()
{
gl::clear();
gl::enableDepthRead();
gl::enableDepthWrite();
gl::setMatrices( mCam );
const float delay = 0.25f;
// time in seconds for one slice to rotate
const float rotationTime = 1.5f;
// time in seconds to delay each slice's rotation
const float rotationOffset = 0.1f; // seconds
// total time for entire animation
const float totalTime = delay + rotationTime +
NUM_SLICES * rotationOffset;
// loop every 'totalTime' seconds
float time = fmod( getElapsedFrames() / 30.0f, totalTime );
for( int i = 0; i < NUM_SLICES; ++i ) {
// animates from 0->1
float rotation = 0;
// when does the slice begin rotating
float startTime = i * rotationOffset;
// when does it complete
float endTime = startTime + rotationTime;
// are we in the middle of our time section?
if( time > startTime && time < endTime )
rotation = ( time - startTime ) / rotationTime;
// ease fn on rotation, then convert to radians
float angle = easeInOutQuint( rotation ) * M_PI / 2.0f;
gl::ScopedModelMatrix scpModelMtx;
gl::rotate( angleAxis( angle, vec3( 0, 1, 0 ) ) );
mSlices[i]->draw();
}
}
In this example we're creating a cube out of 12 slices stacked on top of each other vertically. Each slice rotates around the Y-axis in 1.5 seconds, and each rotation is delayed 0.1 seconds to create a cascading animation.
One thing to notice in this example is how we're constructing our slice >> trans >> color
as the geometry portion of the constructor. Another way to read this is that we're piping the result of a geom::Attrib::COLOR
. The color value itself is determined using some simple HSV color math. Note that one implication of "baking" this value into the geom::Attrib::COLOR
the shader uses that, otherwise the global color is used.
This example also represents our first usage of quaternions, though they're not mentioned by name. To rotate the slices we're using the vec3( 0, 1, 0 )
. The result of Model
matrix just as
Finally, we're using our first easing function. You may be familiar with this concept from other tools, classically Flash in particular. Ease functions are useful for adding character and interest to animation. They generally remap the domain 0-1 into the range 0-1 nonlinearly. If you watch the our animation carefully, you'll see that each slice first accelerates and then decelerates into place, rather than simply rotating linearly. This is because we call "cinder/Easing.h"
. A graph of this specific example looks like the image to the right, which is taken from the EaseGallery sample.