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.
- Learn how to attach chrono::ChAsset shapes to rigid bodies.
- Learn how to output data for POVray.
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 or POV postprocessing, etc... Note: these assets are independent from collision shapes!
 
    
    
    
    
 
    
    
    auto mfloor = chrono_types::make_shared<ChBody>();
    mfloor->SetBodyFixed(true);
 
    
    mfloor->GetCollisionModel()->ClearModel();
    mfloor->GetCollisionModel()->AddBox(10, 0.5, 10, ChVector<>(0, -1, 0));
    mfloor->GetCollisionModel()->BuildModel();
    mfloor->SetCollide(true);
 
    
    mphysicalSystem.
Add(mfloor);
 
    
    
    
    auto mboxfloor = chrono_types::make_shared<ChBoxShape>();
    mboxfloor->GetBoxGeometry().Pos = ChVector<>(0, -1, 0);
    mboxfloor->GetBoxGeometry().Size = ChVector<>(10, 0.5, 10);
    mfloor->AddAsset(mboxfloor);
 
    
    auto mfloorcolor = chrono_types::make_shared<ChColorAsset>();
    mfloorcolor->SetColor(ChColor(0.3f, 0.3f, 0.6f));
    mfloor->AddAsset(mfloorcolor);
 
Example 2
Textures, colors, asset levels with transformations. This section shows how to add more advanced types of assets and how to group assets in chrono::ChAssetLevel containers.
 
    
    
    
 
    
    
    auto mbody = chrono_types::make_shared<ChBody>();
    mbody->SetBodyFixed(true);
    mphysicalSystem.
Add(mbody);
 
    
    auto msphere = chrono_types::make_shared<ChSphereShape>();
    msphere->GetSphereGeometry().rad = 0.5;
    msphere->GetSphereGeometry().center = ChVector<>(-1, 0, 0);
    mbody->AddAsset(msphere);
 
    
    auto mbox = chrono_types::make_shared<ChBoxShape>();
    mbox->GetBoxGeometry().Pos = ChVector<>(1, 0, 0);
    mbox->GetBoxGeometry().Size = ChVector<>(0.2, 0.5, 0.1);
    mbody->AddAsset(mbox);
 
    
    auto mcyl = chrono_types::make_shared<ChCylinderShape>();
    mcyl->GetCylinderGeometry().p1 = ChVector<>(2, -0.2, 0);
    mcyl->GetCylinderGeometry().p2 = ChVector<>(2.2, 0.5, 0);
    mcyl->GetCylinderGeometry().rad = 0.3;
    mbody->AddAsset(mcyl);
 
    
    
    auto mvisual = chrono_types::make_shared<ChColorAsset>();
    mvisual->SetColor(ChColor(0.9f, 0.4f, 0.2f));
    mbody->AddAsset(mvisual);
 
    
    
    
    auto mlevelA = chrono_types::make_shared<ChAssetLevel>();
 
    
    
    auto mobjmesh = chrono_types::make_shared<ChObjShapeFile>();
    mlevelA->AddAsset(mobjmesh);
 
    
    
    auto mtexture = chrono_types::make_shared<ChTexture>();
    mlevelA->AddAsset(mtexture);
 
    
    mlevelA->GetFrame().SetPos(ChVector<>(0, 0, 2));
    mbody->AddAsset(mlevelA);
 
    
    
    
    auto mlevelB = chrono_types::make_shared<ChAssetLevel>();
    for (int j = 0; j < 20; j++) {
        
        auto mlevelC = chrono_types::make_shared<ChAssetLevel>();
 
        
        auto msmallbox = chrono_types::make_shared<ChBoxShape>();
        msmallbox->GetBoxGeometry().Pos = ChVector<>(0.4, 0, 0);
        msmallbox->GetBoxGeometry().Size = ChVector<>(0.1, 0.1, 0.01);
        mlevelC->AddAsset(msmallbox);
 
        ChQuaternion<> mrot;
        mlevelC->GetFrame().SetRot(mrot);
        mlevelC->GetFrame().SetPos(ChVector<>(0, j * 0.02, 0));
 
        mlevelB->AddAsset(mlevelC);
    }
 
    mbody->AddAsset(mlevelB);
 
    
    
    
    auto mcamera = chrono_types::make_shared<ChCamera>();
    mcamera->SetAngle(50);
    mcamera->SetPosition(ChVector<>(-3, 4, -5));
    mcamera->SetAimPoint(ChVector<>(0, 1, 0));
    mbody->AddAsset(mcamera);
 
Example 3
Create a chrono::ChParticlesClones cluster, and attach 'assets' that define a single "sample" 3D shape. This will be shown N times in POV or Irrlicht.
 
    
    
    
 
    
    
    auto mparticles = chrono_types::make_shared<ChParticlesClones>();
 
    
    mparticles->GetCollisionModel()->ClearModel();
    mparticles->GetCollisionModel()->AddSphere(0.05);
    mparticles->GetCollisionModel()->BuildModel();
    mparticles->SetCollide(true);
 
    
    for (int np = 0; np < 100; ++np)
        mparticles->AddParticle(ChCoordsys<>(ChVector<>(
ChRandom() - 2, 1, 
ChRandom() - 0.5)));
 
    
    mphysicalSystem.
Add(mparticles);
 
    
    
    auto mspherepart = chrono_types::make_shared<ChSphereShape>();
    mspherepart->GetSphereGeometry().rad = 0.05;
    mparticles->AddAsset(mspherepart);
 
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:
 
    
    
    
 
    
    if (!filesystem::create_directory(filesystem::path(demo_dir))) {
        std::cout << "Error creating directory " << demo_dir << std::endl;
        return 1;
    }
 
    
    ChPovRay pov_exporter = ChPovRay(&mphysicalSystem);
 
    
    pov_exporter.SetOutputScriptFile(demo_dir + "/rendering_frames.pov");
    pov_exporter.SetOutputDataFilebase("my_state");
    pov_exporter.SetPictureFilebase("picture");
 
    
    
    const std::string out_dir = demo_dir + "/output";
    const std::string anim_dir = demo_dir + "/anim";
    filesystem::create_directory(filesystem::path(out_dir));
    filesystem::create_directory(filesystem::path(anim_dir));
 
    pov_exporter.SetOutputDataFilebase(out_dir + "/my_state");
    pov_exporter.SetPictureFilebase(anim_dir + "/picture");
 
    
    pov_exporter.SetLight(ChVector<>(-3, 4, 2), ChColor(0.15f, 0.15f, 0.12f), false);
 
    
    
    
    
    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>  } \
    ");
 
    
    
    
    
    auto mPOVcustom = chrono_types::make_shared<ChPovRayAssetCustom>();
    mPOVcustom->SetCommands((char*)"pigment { checker rgb<0.9,0.9,0.9>, rgb<0.75,0.8,0.8> }");
    mfloor->AddAsset(mPOVcustom);
 
    
    
    
 
    pov_exporter.AddAll();
 
    
    
    
    
    
    
 
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.
 
    
    
    
 
    
    
 
    pov_exporter.ExportScript();
 
 
 
        
        
        pov_exporter.ExportData();
    }
 
    
    
    
    
    
 
Executing and rendering with POVray
Once you created your program, compile it, then:
- execute the demo_POST_povray.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.inifile, 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.
 
#include "chrono/assets/ChAssetLevel.h"
#include "chrono/assets/ChBoxShape.h"
#include "chrono/assets/ChCamera.h"
#include "chrono/assets/ChColorAsset.h"
#include "chrono/assets/ChCylinderShape.h"
#include "chrono/assets/ChObjShapeFile.h"
#include "chrono/assets/ChSphereShape.h"
#include "chrono/assets/ChTexture.h"
#include "chrono/physics/ChParticlesClones.h"
#include "chrono/physics/ChSystemNSC.h"
 
#include "chrono_postprocess/ChPovRay.h"
#include "chrono_postprocess/ChPovRayAssetCustom.h"
 
#include "chrono_thirdparty/filesystem/path.h"
 
using namespace postprocess;
 
int main(int argc, char* argv[]) {
    GetLog() << 
"Copyright (c) 2017 projectchrono.org\nChrono version: " << CHRONO_VERSION << 
"\n\n";
 
 
    
 
    
 
    
    
    
    
 
    
    
    auto mfloor = chrono_types::make_shared<ChBody>();
    mfloor->SetBodyFixed(true);
 
    
    mfloor->GetCollisionModel()->ClearModel();
    mfloor->GetCollisionModel()->AddBox(10, 0.5, 10, 
ChVector<>(0, -1, 0));
    mfloor->GetCollisionModel()->BuildModel();
    mfloor->SetCollide(true);
 
    
    mphysicalSystem.
Add(mfloor);
 
    
    
    
    auto mboxfloor = chrono_types::make_shared<ChBoxShape>();
    mboxfloor->GetBoxGeometry().Pos = 
ChVector<>(0, -1, 0);
    mboxfloor->GetBoxGeometry().Size = 
ChVector<>(10, 0.5, 10);
    mfloor->AddAsset(mboxfloor);
 
    
    auto mfloorcolor = chrono_types::make_shared<ChColorAsset>();
    mfloorcolor->SetColor(
ChColor(0.3f, 0.3f, 0.6f));
    mfloor->AddAsset(mfloorcolor);
 
    
 
    
 
    
    
    
 
    
    
    auto mbody = chrono_types::make_shared<ChBody>();
    mbody->SetBodyFixed(true);
    mphysicalSystem.
Add(mbody);
 
    
    auto msphere = chrono_types::make_shared<ChSphereShape>();
    msphere->GetSphereGeometry().rad = 0.5;
    msphere->GetSphereGeometry().center = 
ChVector<>(-1, 0, 0);
    mbody->AddAsset(msphere);
 
    
    auto mbox = chrono_types::make_shared<ChBoxShape>();
    mbox->GetBoxGeometry().Size = 
ChVector<>(0.2, 0.5, 0.1);
    mbody->AddAsset(mbox);
 
    
    auto mcyl = chrono_types::make_shared<ChCylinderShape>();
    mcyl->GetCylinderGeometry().p1 = 
ChVector<>(2, -0.2, 0);
    mcyl->GetCylinderGeometry().p2 = 
ChVector<>(2.2, 0.5, 0);
    mcyl->GetCylinderGeometry().rad = 0.3;
    mbody->AddAsset(mcyl);
 
    
    
    auto mvisual = chrono_types::make_shared<ChColorAsset>();
    mvisual->SetColor(
ChColor(0.9f, 0.4f, 0.2f));
    mbody->AddAsset(mvisual);
 
    
    
    
    auto mlevelA = chrono_types::make_shared<ChAssetLevel>();
 
    
    
    auto mobjmesh = chrono_types::make_shared<ChObjShapeFile>();
    mlevelA->AddAsset(mobjmesh);
 
    
    
    auto mtexture = chrono_types::make_shared<ChTexture>();
    mlevelA->AddAsset(mtexture);
 
    
    mbody->AddAsset(mlevelA);
 
    
    
    
    auto mlevelB = chrono_types::make_shared<ChAssetLevel>();
    for (int j = 0; j < 20; j++) {
        
        auto mlevelC = chrono_types::make_shared<ChAssetLevel>();
 
        
        auto msmallbox = chrono_types::make_shared<ChBoxShape>();
        msmallbox->GetBoxGeometry().Pos = 
ChVector<>(0.4, 0, 0);
        msmallbox->GetBoxGeometry().Size = 
ChVector<>(0.1, 0.1, 0.01);
        mlevelC->AddAsset(msmallbox);
 
        mlevelC->GetFrame().SetRot(mrot);
        mlevelC->GetFrame().SetPos(
ChVector<>(0, j * 0.02, 0));
 
        mlevelB->AddAsset(mlevelC);
    }
 
    mbody->AddAsset(mlevelB);
 
    
    
    
    auto mcamera = chrono_types::make_shared<ChCamera>();
    mcamera->SetAngle(50);
    mbody->AddAsset(mcamera);
 
    
 
    
 
    
    
    
 
    
    
    auto mparticles = chrono_types::make_shared<ChParticlesClones>();
 
    
    mparticles->GetCollisionModel()->ClearModel();
    mparticles->GetCollisionModel()->AddSphere(0.05);
    mparticles->GetCollisionModel()->BuildModel();
    mparticles->SetCollide(true);
 
    
    for (int np = 0; np < 100; ++np)
 
    
    mphysicalSystem.
Add(mparticles);
 
    
    
    auto mspherepart = chrono_types::make_shared<ChSphereShape>();
    mspherepart->GetSphereGeometry().rad = 0.05;
    mparticles->AddAsset(mspherepart);
 
    
 
    
 
    
    
    
 
    
    if (!filesystem::create_directory(filesystem::path(demo_dir))) {
        std::cout << "Error creating directory " << demo_dir << std::endl;
        return 1;
    }
 
    
    ChPovRay pov_exporter = ChPovRay(&mphysicalSystem);
 
    
    pov_exporter.SetOutputScriptFile(demo_dir + "/rendering_frames.pov");
    pov_exporter.SetOutputDataFilebase("my_state");
    pov_exporter.SetPictureFilebase("picture");
 
    
    
    const std::string out_dir = demo_dir + "/output";
    const std::string anim_dir = demo_dir + "/anim";
    filesystem::create_directory(filesystem::path(out_dir));
    filesystem::create_directory(filesystem::path(anim_dir));
 
    pov_exporter.SetOutputDataFilebase(out_dir + "/my_state");
    pov_exporter.SetPictureFilebase(anim_dir + "/picture");
 
    
 
    
    
    
    
    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>  } \
    ");
 
    
    
    
    
    auto mPOVcustom = chrono_types::make_shared<ChPovRayAssetCustom>();
    mPOVcustom->SetCommands((char*)"pigment { checker rgb<0.9,0.9,0.9>, rgb<0.75,0.8,0.8> }");
    mfloor->AddAsset(mPOVcustom);
 
    
    
    
 
    pov_exporter.AddAll();
 
    
    
    
    
    
    
 
    
 
    
 
    
    
    
 
    
    
 
    pov_exporter.ExportScript();
 
 
 
        
        
        pov_exporter.ExportData();
    }
 
    
    
    
    
    
 
    
 
    return 0;
}