Table of contents
1.
Introduction
1.1.
About Cinder
2.
Introducing Batches
2.1.
Easing function
3.
Frequently Asked Questions
3.1.
What is Cinder?
3.2.
What is the use of Cinder?
3.3.
What is the use of Batches in Cinder?
3.4.
What is the use of the Easing Function?
3.5.
How can we rotate a slice?
4.
Conclusion
Last Updated: Mar 27, 2024
Medium

Cinder-Introducing Batches

Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Heyy, Ninjas, we are back with another article in the Cinder series, i.e., Introduction to batches in Cinder, which is a very important tool in Cinder, as it improves the performance of the 3D objects by adding more dynamicity.

Cinder-Introducing Batches

Before moving into detail about this, let's see what Cinder is.

About Cinder

Cinder is an open-source C++ free library for creative coding. It is designed to add visualization abilities like graphics, geometry pairing, textures, third dimensions, etc., to C++ language. It was released in the spring of 2010 as a public tool. It is generally used in a non-browser environment. 

It uses one of the most powerful languages - C++, to add visualization effects to our coding.

Let’s see about batches in Cinder using various examples.

Introducing Batches

Batches in Cinder are an important tool to represent the pairing of pieces of geometry using a GLSL program. Basically, GLSL is a language that is used for writing OpenGL shaders. Batches in Cinder help to perform transformations and drawings with better performance.

To pair the pieces of geometry together, we use gl::Batch, which is a convenient and scalable technique in terms of performance.

Let's see an example of gl::Batch and other new concepts to understand batches in Cinder.

Code:

#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/FileWatcher.h"
#include "cinder/ImageIo.h"
#include "cinder/Log.h"
#include "cinder/gl/gl.h"
 
using namespace ci;
using namespace ci::app;
using namespace std;
 
class CubeMappingApp : public App {
public:
    void setup() override;
    void draw() override;
 
    CameraPersp      mCam;
    gl::BatchRef mBox;
};
 
void CubeMappingApp::setup()
{
    auto lambert = gl::ShaderDef().lambert().color();
    gl::GlslProgRef shader = gl::getStockShader(lambert);
    mBox = gl::Batch::create(geom::Cube(), shader);
 
    mCam.lookAt(vec3(3, 5.5, 5.5), vec3(0, 1, 0));
}
 
void CubeMappingApp::draw()
{
    gl::clear();
    gl::enableDepthRead();
    gl::enableDepthWrite();
 
    gl::setMatrices(mCam);
 
    int numSpheres = 60;
    float maxAngle = M_PI * 6;
    float spiralRadius = 1.5;
    float height = 3;
    float boxSize = 0.08f;
    float anim = getElapsedFrames() / 20.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, 2, 1.5));
          mBox->draw();
          gl::popModelMatrix();
    }
}
CINDER_APP(CubeMappingApp, RendererGl(RendererGl::Options().msaa(16)))
You can also try this code with Online C++ Compiler
Run Code


Output:

example of gl::Batch

Explanation:

Below are the steps of the explanation of the above example of Batches in Cinder.

  • We will prepare CameraPersp in setup() now and store it in a variable called mCam.
     
  • In order to reset the Projection matrices and model, we will call gl::setMatrices() for every frame.
     
  • We will initialize an instance of gl::Batch called mBox in setup().
     
  • With the help of gl::ShaderDef, we will call gl::getShockShader, and capture the result to a gl::GlslProgRef.
     
  • Now we will instantiate gl::Batch using GlslProgRef(class representing GLSL program) and another fresh class, geom::Cube.gl:: Batch, which represents the pairing of a piece of geometry(3D objects) with a GLSL program. 
     
  • In our case, geometry comes from geom::Cube, which is a part of geom::Source classes; it has many siblings like geom::Icosahedron, geom::Sphere, and many others.
     
  • The gl::Batch::draw() method draws the Batch or the entire geometry.
     

Now let's have a look at geom::Sources in much more detail. Using the example below, we will be able to explore a number of inbuilt geom::Source to get the clear idea of batches in Cinder.

Code:

The code is as follows:

#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/FileWatcher.h"
#include "cinder/ImageIo.h"
#include "cinder/Log.h"
#include "cinder/gl/gl.h"
 
using namespace ci;
using namespace ci::app;
using namespace std;
 
class CubeMappingApp : public App {
public:
    void setup() override;
    void draw() override;
 
    CameraPersp      mCam;
    gl::BatchRef mShapes[3][3];
};
 
void CubeMappingApp::setup()
{
    auto lambert = gl::ShaderDef().lambert().color();
    gl::GlslProgRef  shader = gl::getStockShader(lambert);
 
    auto sphere = geom::Sphere().subdivisions(30);
    mShapes[0][1] = gl::Batch::create(sphere, shader);
    auto cylinder = geom::Cylinder().subdivisionsAxis(35)
          .subdivisionsHeight(3);
    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 capsule = geom::Capsule().subdivisionsAxis(10)
          .subdivisionsHeight(10);
    mShapes[0][0] = gl::Batch::create(capsule, 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 CubeMappingApp::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 / 4.0f, 1 - i * j, j / 2.0f);
                 mShapes[i][j]->draw();
          }
    }
}
 
CINDER_APP(CubeMappingApp, RendererGl(RendererGl::Options().msaa(16)))
You can also try this code with Online C++ Compiler
Run Code


Output:

example of geom::Sources

Explanation:

  • We call subdivisionsAxis() and subdivisionsHeight() to easily adjust the geom::Capsule.
     
  • The main use of our concept is mentioned in this line-> geom::Teapot() >> geom::Scale( 1.5f ).
     
  • In our example, the geom::Modifier is used to modify the geom::Teapot, especially the geom::Scale modifier, and it grows the Teapot by 50%. 
     
  • There are multiple in-built geom::Modifiers in Cinder, and they can be chained together using the (>>) operator.
     

Let's see another example that combines the gl::Source with gl::Batch.


Code:

#include "cinder/Easing.h"
#include "cinder/app/App.h"
#include "cinder/app/RendererGl.h"
#include "cinder/FileWatcher.h"
#include "cinder/ImageIo.h"
#include "cinder/Log.h"
#include "cinder/gl/gl.h"
 
using namespace ci;
using namespace ci::app;
using namespace std;
 
class CubeMappingApp : public App {
public:
    void setup() override;
    void draw() override;
 
    static const int total_no_slices = 8;
 
    CameraPersp      cameraPerson;
    gl::BatchRef maxSlices[total_no_slices];
};
 
void CubeMappingApp::setup()
{
    auto lambert = gl::ShaderDef().lambert().color();
    gl::GlslProgRef  shader = gl::getStockShader(lambert);
 
    for (int i = 0; i < total_no_slices; ++i) {
          float rel = i / (float)total_slices;
          float height_slice = 1.0f / total_no_slices;
          auto slice = geom::Cube().size(1, height_slice, 2);
          auto trans = geom::Translate(0, rel, 0);
          auto color = geom::Constant(geom::COLOR,
                 Color(CM_HSV, rel, 2, 1.5));
          maxSlices[i] = gl::Batch::create(slice >> trans >> color,
                 shader);
    }
 
    cameraPerson.lookAt(vec3(3, 3, 3), vec3(0, 0.5f, 0));
}
 
void CubeMappingApp::draw()
{
    gl::clear();
    gl::enableDepthRead();
    gl::enableDepthWrite();
 
    gl::setMatrices(cameraPerson);
 
    const float delay_in_time = 0.25f;
    const float rotation_of_time = 1.5f;
    const float rotation_off_set = 0.1f; // Seconds
    // Total time for entire animation
    const float totalTime = delay_in_time + rotation_of_time +
          total_slices * rotation_off_set;
 
    // Loop every 'totalTime' seconds
    float seconds = fmod(getElapsedFrames() / 50.0f, totalTime);
 
    for (int j = 0; j < total_no_slices; ++j) {
           float rotation = 0;
           float start_of_time = j * rotation_off_set;
         
          float endTime = start_of_time + rotation_of_time;
                   if (seconds > start_of_time && seconds < endTime)
                 rotation = (seconds - start_of_time) / rotation_of_time;
          // Ease fn on rotation, then convert to radians
          float rotation_angle = easeInOutQuint(rotation) * M_PI / 1.0f;
 
          gl::ScopedModelMatrix scp_model_mtx;
          gl::rotate(angleAxis(rotation_angle, vec3(0, 1, 0)));
          maxSlices[j]->draw();
    }
}
 
CINDER_APP(CubeMappingApp, RendererGl(RendererGl::Options().msaa(16)))
You can also try this code with Online C++ Compiler
Run Code


Output

example that combines the gl::Source with gl::Batch.

Explanation:

In this example, we are creating gl::Batches by passing slice>>trans>>color as the geometry portion of the constructor.

In the above example, 12 slices are put on top of one another, creating a cube. 

geom::translate - The instance trans of this is used to offset the slice vertically.

The modifier geom::Constant is used to supply a constant for an attribute. In our example, we are using it to provide the color value using HSV color math. 

In this example, we have also used quaternions. For the rotation of the slice, we have used the angleAxis() function, which takes an angle in radians and an axis to rotate and returns ci::quat. angleAxis().

As you can see above, we are animating an angle from 0 to pie/2 radians and rotating it by Y-axis.

The result of angleAxis() is stored in gl::rotate(), which is used to modify the Model matrix just what gl::translate() and gl::scale() do.

Easing function

At last, we are using an easing function. These functions are useful for adding interest and character to animation. Through our animation, it is clearly visible that the slice first accelerates and decelerates into place rather than rotating linearly. 

For this, we call out easeInOutQuint() on our rotation variable before converting it into radians. This function is one of the Cinder functions used for this particular purpose, and the function is defined in the header "cinder/Easing.h".

Frequently Asked Questions

What is Cinder?

Cinder is an open-source, free programming toolkit that provides C++ language with visualization capabilities. It is generally used in a non-browser environment. 

What is the use of Cinder?

Cinder is designed to add visualization abilities like graphics, geometry pairing, textures, third dimensions, etc., to C++ language. It is used for creative coding.

What is the use of Batches in Cinder?

Batches in Cinder is used to add textures to our 3d objects with great performance using gl::Batch. Rotations and colors can be added to the objects with certain functions of Cinder.

What is the use of the Easing Function?

The easing function can be used to add interest and character to animation.The function is used to nonlinearly remap the domain (0-1 into the range 0-1).

How can we rotate a slice?

To rotate a slice, we have used the angleAxis() function, which takes an angle in radians and an axis to rotate and returns ci::quat. angleAxis().

Conclusion

This article has discussed Introduction to Batches in Cinder, where the transformation to 3d objects is given with great performance. In this article, we have used several methods and their examples, along with their code and detailed explanation.

Refer to more blogs based on Cinder:

  1. OpenGL in Cinder
  2. Cinder - The Third Dimension
  3. Cinder - Shaders
  4. Cinder-Path2d
     

Refer to our Guided Path to upskill yourself in DSACompetitive ProgrammingJavaScriptSystem Design, and many more! If you want to test your competency in coding, check out the mock test series and participate in the contests hosted on Coding Ninjas Studio!

Happy Learning!

Live masterclass