Export POVray animations (demo_POST_povray.cpp)

Tutorial that teaches how to use the POSTPROCESS module to create animations with POVray.

When the simulation is run, a set of .pov and .ini files are saved on disk, so that one can use POVray later to do high-quality rendering of simulations.

Note: the same assets can be used to render animations in real-time in the interactive 3D view of Irrlicht, as explained in demo_IRR_assets.cpp

Example 1

Create a chrono::ChBody, and attach some 'assets' that define 3D shapes. These shapes can be shown by Irrlicht, OpenGL, or POV postprocessing. Note: these assets are independent from collision shapes.

// Create a ChBody, and attach some 'assets'
// that define 3D shapes. These shapes can be shown
// by Irrlicht or POV postprocessing, etc...
// Note: these assets are independent from collision shapes!
// Create a rigid body as usual, and add it
// to the physical system:
auto floor = chrono_types::make_shared<ChBody>();
floor->SetBodyFixed(true);
// Define a collision shape
auto floor_mat = chrono_types::make_shared<ChMaterialSurfaceNSC>();
floor->GetCollisionModel()->ClearModel();
floor->GetCollisionModel()->AddBox(floor_mat, 10, 0.5, 10, ChVector<>(0, -1, 0));
floor->GetCollisionModel()->BuildModel();
floor->SetCollide(true);
// Add body to system
sys.Add(floor);
// ==Asset== attach a 'box' shape.
auto boxfloor = chrono_types::make_shared<ChBoxShape>();
boxfloor->GetBoxGeometry().Size = ChVector<>(10, 0.5, 10);
boxfloor->SetColor(ChColor(0.3f, 0.3f, 0.6f));
floor->AddVisualShape(boxfloor, ChFrame<>(ChVector<>(0, -1, 0)));

Example 2

Textures, colors, asset levels with transformations. This section shows how to add more advanced types of assets.

// Textures, colors, asset levels with transformations.
// Create the rigid body as usual (this won't move, it is only for visualization tests)
auto body = chrono_types::make_shared<ChBody>();
body->SetBodyFixed(true);
sys.Add(body);
// ==Asset== Attach a 'sphere' shape
auto sphere = chrono_types::make_shared<ChSphereShape>();
sphere->GetSphereGeometry().rad = 0.5;
body->AddVisualShape(sphere, ChFrame<>(ChVector<>(-1, 0, 0)));
// ==Asset== Attach also a 'box' shape
auto mbox = chrono_types::make_shared<ChBoxShape>();
mbox->GetBoxGeometry().Size = ChVector<>(0.2, 0.5, 0.1);
body->AddVisualShape(mbox, ChFrame<>(ChVector<>(1, 0, 0)));
// ==Asset== Attach also a 'cylinder' shape
auto cyl = chrono_types::make_shared<ChCylinderShape>();
cyl->GetCylinderGeometry().p1 = ChVector<>(2, -0.2, 0);
cyl->GetCylinderGeometry().p2 = ChVector<>(2.2, 0.5, 0);
cyl->GetCylinderGeometry().rad = 0.3;
body->AddVisualShape(cyl);
// ==Asset== Attach a 'Wavefront mesh' asset, referencing a .obj file:
auto objmesh = chrono_types::make_shared<ChObjFileShape>();
objmesh->SetFilename(GetChronoDataFile("models/forklift/body.obj"));
objmesh->SetTexture(GetChronoDataFile("textures/bluewhite.png"));
body->AddVisualShape(objmesh, ChFrame<>(ChVector<>(0, 0, 2)));
// ==Asset== Attach an array of boxes, each rotated to make a spiral
for (int j = 0; j < 20; j++) {
auto smallbox = chrono_types::make_shared<ChBoxShape>();
smallbox->GetBoxGeometry().Size = ChVector<>(0.1, 0.1, 0.01);
smallbox->SetColor(ChColor(j * 0.05f, 1 - j * 0.05f, 0.0f));
ChMatrix33<> rot(Q_from_AngY(j * 21 * CH_C_DEG_TO_RAD));
ChVector<> pos = rot * ChVector<>(0.4, 0, 0) + ChVector<>(0, j * 0.02, 0);
body->AddVisualShape(smallbox, ChFrame<>(pos, rot));
}
// ==Asset== Attach a video camera.
// Note that a camera can also be attached to a moving object.
auto camera = chrono_types::make_shared<ChCamera>();
camera->SetAngle(50);
camera->SetPosition(ChVector<>(-3, 4, -5));
camera->SetAimPoint(ChVector<>(0, 1, 0));
body->AddCamera(camera);

Example 3

Create a chrono::ChParticleCloud cluster, and attach 'assets' that define a single "sample" 3D shape. This will be shown N times in POV or Irrlicht.

// Create a ChParticleClones cluster, and attach 'assets'
// that define a single "sample" 3D shape. This will be shown
// N times in Irrlicht.
// Create the ChParticleClones, populate it with some random particles,
// and add it to physical system:
auto particles = chrono_types::make_shared<ChParticleCloud>();
// Note: coll. shape, if needed, must be specified before creating particles
auto particle_mat = chrono_types::make_shared<ChMaterialSurfaceNSC>();
particles->GetCollisionModel()->ClearModel();
particles->GetCollisionModel()->AddSphere(particle_mat, 0.05);
particles->GetCollisionModel()->BuildModel();
particles->SetCollide(true);
// Create the random particles
for (int np = 0; np < 100; ++np)
particles->AddParticle(ChCoordsys<>(ChVector<>(ChRandom() - 2, 1, ChRandom() - 0.5)));
// Do not forget to add the particle cluster to the system:
sys.Add(particles);
// ==Asset== Attach a 'sphere' shape asset.. it will be used as a sample
// shape to display all particles when rendering in 3D!
auto sphereparticle = chrono_types::make_shared<ChSphereShape>();
sphereparticle->GetSphereGeometry().rad = 0.05;
particles->AddVisualShape(sphereparticle);

The POV exporter

The following part is very important because this is what makes this demo different from the demo_IRR_assets, that used Irrlicht. We need to create a postprocessor of type chrono::postprocess::ChPovRay and tell him that we are going to export our visualization assets:

// Create an exporter to POVray
ChPovRay pov_exporter = ChPovRay(&sys);
// Important: set the path to the template:
pov_exporter.SetTemplateFile(GetChronoDataFile("POVRay_chrono_template.pov"));
// Set the path where it will save all .pov, .ini, .asset and .dat files, a directory will be created if not
// existing
pov_exporter.SetBasePath(GetChronoOutputPath() + "POVRAY_1");
// Optional: change the default naming of the generated files:
// pov_exporter.SetOutputScriptFile("rendering_frames.pov");
// pov_exporter.SetOutputDataFilebase("my_state");
// pov_exporter.SetPictureFilebase("picture");
// --Optional: modify default light
pov_exporter.SetLight(ChVector<>(-3, 4, 2), ChColor(0.15f, 0.15f, 0.12f), false);
// --Optional: add further POV commands, for example in this case:
// create an area light for soft shadows
// create a Grid object; Grid() parameters: step, linewidth, linecolor, planecolor
// Remember to use \ at the end of each line for a multiple-line string.
pov_exporter.SetCustomPOVcommandsScript(
" \
light_source { \
<2, 10, -3> \
color rgb<1.2,1.2,1.2> \
area_light <4, 0, 0>, <0, 0, 4>, 8, 8 \
adaptive 1 \
jitter\
} \
object{ Grid(1,0.02, rgb<0.7,0.8,0.8>, rgbt<1,1,1,1>) rotate <0, 0, 90> } \
");

The simulation loop

Now you have to write the usual while() loop to perform the simulation. Note that before running the loop you need to use pov_exporter.ExportScript(); , and for each timestep you must use pov_exporter.ExportData(); actually this is the instruction that creates the many .dat and .pov files in the output directory.

//
// RUN THE SIMULATION AND SAVE THE POVray FILES AT EACH FRAME
//
// 1) Create the two .pov and .ini files for POV-Ray (this must be done
// only once at the beginning of the simulation).
pov_exporter.ExportScript();
while (sys.GetChTime() < 1.5) {
sys.DoStepDynamics(0.01);
GetLog() << "time= " << sys.GetChTime() << "\n";
// 2) Create the incremental nnnn.dat and nnnn.pov files that will be load
// by the pov .ini script in POV-Ray (do this at each simulation timestep)
pov_exporter.ExportData();
}
// That's all! If all worked ok, this python script should
// have created a "rendering_frames.pov.ini" file that you can
// load in POV-Ray, then when you press 'RUN' you will see that
// POV-Ray will start rendering a short animation, saving the frames
// in the directory 'anim'.

Executing and rendering with POVray

Once you created your program, compile it, then:

  • execute the demo_POST_povray1.exe
  • on the console you will see a time counter showing that the system is load and it is being simulated
  • when the program ends, you must open POVray and open the rendering_frames.pov.ini file, using the Open menu or button, or drag&drop (you can find this .ini file and other POVray as they are saved in the same directory of the executable)

  • press the Run button in POVray to execute the .ini file , and you should see that POVray generates lot of frames, being saved in the directory anim.

Optional encoding into an AVI or MPEG animation

If you want to generate a .mpeg or .avi animation from the rendered .bmp images, we suggest to use the VirtualDub tool:

  • drag&drop the first .jpg frame in its interface; it will automatically load all other frames in the timeline
  • use menu Video/Compression... to setup the proper video codec (suggested: Xvid, DivX, mpeg4, etc.)
  • use menu File/Save As Avi... to encode and save the animation on disk.

Listing

In the following we report the entire source code for reference.

// =============================================================================
// PROJECT CHRONO - http://projectchrono.org
//
// Copyright (c) 2014 projectchrono.org
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file at the top level of the distribution and at
// http://projectchrono.org/license-chrono.txt.
//
// =============================================================================
// Authors: Alessandro Tasora
// =============================================================================
//
// Demo code about using the assets system to create shapes that can be shown in
// the 3D postprocessing, by using POVray.
//
// =============================================================================
#include "chrono/assets/ChBoxShape.h"
#include "chrono/assets/ChCamera.h"
#include "chrono/assets/ChCylinderShape.h"
#include "chrono/assets/ChObjFileShape.h"
#include "chrono/assets/ChSphereShape.h"
#include "chrono/assets/ChTexture.h"
#include "chrono/physics/ChParticleCloud.h"
#include "chrono/physics/ChSystemNSC.h"
#include "chrono_postprocess/ChPovRay.h"
#include "chrono_thirdparty/filesystem/path.h"
using namespace chrono;
using namespace postprocess;
int main(int argc, char* argv[]) {
GetLog() << "Copyright (c) 2017 projectchrono.org\nChrono version: " << CHRONO_VERSION << "\n\n";
// Create a Chrono::Engine physical system
/* Start example */
// Create an exporter to POVray
ChPovRay pov_exporter = ChPovRay(&sys);
// Important: set the path to the template:
pov_exporter.SetTemplateFile(GetChronoDataFile("POVRay_chrono_template.pov"));
// Set the path where it will save all .pov, .ini, .asset and .dat files, a directory will be created if not
// existing
pov_exporter.SetBasePath(GetChronoOutputPath() + "POVRAY_1");
// Optional: change the default naming of the generated files:
// pov_exporter.SetOutputScriptFile("rendering_frames.pov");
// pov_exporter.SetOutputDataFilebase("my_state");
// pov_exporter.SetPictureFilebase("picture");
// --Optional: modify default light
pov_exporter.SetLight(ChVector<>(-3, 4, 2), ChColor(0.15f, 0.15f, 0.12f), false);
// --Optional: add further POV commands, for example in this case:
// create an area light for soft shadows
// create a Grid object; Grid() parameters: step, linewidth, linecolor, planecolor
// Remember to use \ at the end of each line for a multiple-line string.
pov_exporter.SetCustomPOVcommandsScript(
" \
light_source { \
<2, 10, -3> \
color rgb<1.2,1.2,1.2> \
area_light <4, 0, 0>, <0, 0, 4>, 8, 8 \
adaptive 1 \
jitter\
} \
object{ Grid(1,0.02, rgb<0.7,0.8,0.8>, rgbt<1,1,1,1>) rotate <0, 0, 90> } \
");
/* End example */
/* Start example */
// Create a ChBody, and attach some 'assets'
// that define 3D shapes. These shapes can be shown
// by Irrlicht or POV postprocessing, etc...
// Note: these assets are independent from collision shapes!
// Create a rigid body as usual, and add it
// to the physical system:
auto floor = chrono_types::make_shared<ChBody>();
floor->SetBodyFixed(true);
// Define a collision shape
auto floor_mat = chrono_types::make_shared<ChMaterialSurfaceNSC>();
floor->GetCollisionModel()->ClearModel();
floor->GetCollisionModel()->AddBox(floor_mat, 10, 0.5, 10, ChVector<>(0, -1, 0));
floor->GetCollisionModel()->BuildModel();
floor->SetCollide(true);
// Add body to system
sys.Add(floor);
// ==Asset== attach a 'box' shape.
auto boxfloor = chrono_types::make_shared<ChBoxShape>();
boxfloor->GetBoxGeometry().Size = ChVector<>(10, 0.5, 10);
boxfloor->SetColor(ChColor(0.3f, 0.3f, 0.6f));
floor->AddVisualShape(boxfloor, ChFrame<>(ChVector<>(0, -1, 0)));
/* End example */
/* Start example */
// Textures, colors, asset levels with transformations.
// Create the rigid body as usual (this won't move, it is only for visualization tests)
auto body = chrono_types::make_shared<ChBody>();
body->SetBodyFixed(true);
sys.Add(body);
// ==Asset== Attach a 'sphere' shape
auto sphere = chrono_types::make_shared<ChSphereShape>();
sphere->GetSphereGeometry().rad = 0.5;
body->AddVisualShape(sphere, ChFrame<>(ChVector<>(-1, 0, 0)));
// ==Asset== Attach also a 'box' shape
auto mbox = chrono_types::make_shared<ChBoxShape>();
mbox->GetBoxGeometry().Size = ChVector<>(0.2, 0.5, 0.1);
body->AddVisualShape(mbox, ChFrame<>(ChVector<>(1, 0, 0)));
// ==Asset== Attach also a 'cylinder' shape
auto cyl = chrono_types::make_shared<ChCylinderShape>();
cyl->GetCylinderGeometry().p1 = ChVector<>(2, -0.2, 0);
cyl->GetCylinderGeometry().p2 = ChVector<>(2.2, 0.5, 0);
cyl->GetCylinderGeometry().rad = 0.3;
body->AddVisualShape(cyl);
// ==Asset== Attach a 'Wavefront mesh' asset, referencing a .obj file:
auto objmesh = chrono_types::make_shared<ChObjFileShape>();
objmesh->SetFilename(GetChronoDataFile("models/forklift/body.obj"));
objmesh->SetTexture(GetChronoDataFile("textures/bluewhite.png"));
body->AddVisualShape(objmesh, ChFrame<>(ChVector<>(0, 0, 2)));
// ==Asset== Attach an array of boxes, each rotated to make a spiral
for (int j = 0; j < 20; j++) {
auto smallbox = chrono_types::make_shared<ChBoxShape>();
smallbox->GetBoxGeometry().Size = ChVector<>(0.1, 0.1, 0.01);
smallbox->SetColor(ChColor(j * 0.05f, 1 - j * 0.05f, 0.0f));
ChMatrix33<> rot(Q_from_AngY(j * 21 * CH_C_DEG_TO_RAD));
ChVector<> pos = rot * ChVector<>(0.4, 0, 0) + ChVector<>(0, j * 0.02, 0);
body->AddVisualShape(smallbox, ChFrame<>(pos, rot));
}
// ==Asset== Attach a video camera.
// Note that a camera can also be attached to a moving object.
auto camera = chrono_types::make_shared<ChCamera>();
camera->SetAngle(50);
camera->SetPosition(ChVector<>(-3, 4, -5));
camera->SetAimPoint(ChVector<>(0, 1, 0));
body->AddCamera(camera);
/* End example */
/* Start example */
// Create a ChParticleClones cluster, and attach 'assets'
// that define a single "sample" 3D shape. This will be shown
// N times in Irrlicht.
// Create the ChParticleClones, populate it with some random particles,
// and add it to physical system:
auto particles = chrono_types::make_shared<ChParticleCloud>();
// Note: coll. shape, if needed, must be specified before creating particles
auto particle_mat = chrono_types::make_shared<ChMaterialSurfaceNSC>();
particles->GetCollisionModel()->ClearModel();
particles->GetCollisionModel()->AddSphere(particle_mat, 0.05);
particles->GetCollisionModel()->BuildModel();
particles->SetCollide(true);
// Create the random particles
for (int np = 0; np < 100; ++np)
particles->AddParticle(ChCoordsys<>(ChVector<>(ChRandom() - 2, 1, ChRandom() - 0.5)));
// Do not forget to add the particle cluster to the system:
sys.Add(particles);
// ==Asset== Attach a 'sphere' shape asset.. it will be used as a sample
// shape to display all particles when rendering in 3D!
auto sphereparticle = chrono_types::make_shared<ChSphereShape>();
sphereparticle->GetSphereGeometry().rad = 0.05;
particles->AddVisualShape(sphereparticle);
/* End example */
// Export all existing visual shapes to POV-Ray
pov_exporter.AddAll();
// (Optional: tell selectively which physical items you
// want to render in the folllowing way...)
// pov_exporter.RemoveAll();
// pov_exporter.Add(floor);
// pov_exporter.Add(body);
// pov_exporter.Add(particles);
/* Start example */
//
// RUN THE SIMULATION AND SAVE THE POVray FILES AT EACH FRAME
//
// 1) Create the two .pov and .ini files for POV-Ray (this must be done
// only once at the beginning of the simulation).
pov_exporter.ExportScript();
while (sys.GetChTime() < 1.5) {
sys.DoStepDynamics(0.01);
GetLog() << "time= " << sys.GetChTime() << "\n";
// 2) Create the incremental nnnn.dat and nnnn.pov files that will be load
// by the pov .ini script in POV-Ray (do this at each simulation timestep)
pov_exporter.ExportData();
}
// That's all! If all worked ok, this python script should
// have created a "rendering_frames.pov.ini" file that you can
// load in POV-Ray, then when you press 'RUN' you will see that
// POV-Ray will start rendering a short animation, saving the frames
// in the directory 'anim'.
/* End example */
return 0;
}
std::string GetChronoDataFile(const std::string &filename)
Obtain the complete path to the specified filename, given relative to the Chrono data directory (thre...
Definition: ChGlobal.cpp:95
void Add(std::shared_ptr< ChPhysicsItem > item)
Attach an arbitrary ChPhysicsItem (e.g.
Definition: ChSystem.cpp:179
COORDSYS:
Definition: ChCoordsys.h:38
ChLog & GetLog()
Global function to get the current ChLog object.
Definition: ChLog.cpp:39
Definition of a 3x3 fixed size matrix to represent 3D rotations and inertia tensors.
Definition: ChMatrix33.h:31
Representation of a 3D transform.
Definition: ChFrame.h:34
Definition of a visual color.
Definition: ChColor.h:26
Definition of general purpose 3d vector variables, such as points in 3D.
Definition: ChVector.h:35
const std::string & GetChronoOutputPath()
Obtain the path to the output directory for Chrono demos.
Definition: ChGlobal.cpp:110
int DoStepDynamics(double step_size)
Advances the dynamical simulation for a single step, of length step_size.
Definition: ChSystem.cpp:1422
double ChRandom()
Returns random value in (0..1) interval with Park-Miller method.
Definition: ChMathematics.cpp:53
Main namespace for the Chrono package.
Definition: ChBarrelShape.cpp:17
double GetChTime() const
Get the simulation time of this system.
Definition: ChSystem.h:214
Class for a physical system in which contact is modeled using a non-smooth (complementarity-based) me...
Definition: ChSystemNSC.h:29