<html>
I am working on a GUI for a bicycle simulation program in C++ using Qt for the GUI and VTK for all the fancy fun stuff. The program plays an animation of a bicycle in motion.
I have the animation working with VTK, but I would like some additional features with the video.
Feature A - Save the video to image files
Feature B - Update a vtk plot, just below the video in another QVTKWidget, of various quantitites that are evolving as the simulation progresses, e.g. X Y position of the bike.
Feature C - Draw a polyline "trace" in the animation that traces the path of the bike.
These features are not working properly for me right now. I understand that there are various ways to do animations in VTK and perhaps the way I have chosen is not adaptable to do those features above. As I see it, there are three ways to do animations in VTK.
(1) Initialize all actors, then just createa simple loop that changes the position/orientation of the actors, and just re-render.
(2) Subclass vtkCommand and create a repeating timer on the interactor (http://www.vtk.org/Wiki/VTK/Examples/Cxx/Utilities/Animation).
(3) Use vtk animation cue's (http://www.vtk.org/Wiki/VTK/Examples/Cxx/Utilities/AnimationScene).
I have pursued (2), as (1) did not work at all for me, and (3) seemed way complicated.
Now, I present minimalized code for my implementation of (2), first without the features I would like, then with my attempt to implement those features. I will then explain the way in which the features do not work, with the additional lines labeled as to which Feature they are for.
<pre>
=================================================
Implementation of (2) without additional features
=================================================
IN MYQWIDGET.H
--------------
class vtkTimerCallback2; // defined below in this file
class MyQWidget() : public QWidget
{
Q_OBJECT;
public:
MyQWidget(bicycle *bike1, QWidget *parent = 0);
private slots:
void startSimSlot(void);
private:
// my own class that holds data about the bike,
// has vtk sources, actors, etc.
bicycle *bike;
QGridLayout MyLayout;
QToolButton *startSimButton;
QVTKWidget *MyQVTKWidget;
vtkSmartPointer<vtkRenderer> MyRenderer;
vtkSmartPointer<vtkRenderWindow> MyRenderWindow;
vtkSmartPointer<vtkTimerCallback2> MyCallback;
};
class vtkTimerCallback2 : public vtkCommand {
public:
static vtkTimerCallback *New()
{
vtkTimerCallback2 *cb = new vtkTimerCallback2;
cb->TimerCount = 0;
return cb;
}
virtual void Execute(vtkObject *caller, unsigned long eventId,
void *vtkNotUsed(callData))
{
if (vtkCommand::TimerEvent == eventId)
++this->TimerCount;
}
time = (double)TimerCount/bike->fps;
bike->UpdateSim(time);
vtkRenderWindowInteractor *iren = vtkRenderWindowInteractor::SafeDownCast(caller);
iren->GetRenderWindow()->Render();
}
bicycle bike;
private:
int TimerCount;
double time;
};
IN MYQWIDGET.CPP
----------------
MyQWidget::MyQWidget(bicycle *bike1, QWidget *parent) : QWidget(parent)
{
bike = bike1;
// qt initialize
MyLayout = new QGridLayout(this);
MyQVTKWidget = new QVTKWidget(this);
startSimButton = new QToolButton;
// qt commands
setLayout(MyLayout);
MyLayout->addWidget(MyQVTKWidget,0,0);
// vtk initialize
MyRenderer = vtkRenderer::New();
MyRenderWindow = vtkRenderWindow::New();
MyCallback = vtkSmartPointer<vtkTimerCallback2>::New();
// vtk commands
MyRenderWindow->AddRenderer(MyRenderer);
MyQVTKWidget->SetRenderWindow(MyRenderWindow);
MyCallback->bike = bike;
MyQVTKWidget->GetInteractor()->AddObservor(vtkCommand::TimerEvent, MyCallback);
bike->initSim(MyRenderer);
}
MyQWidget::startSimSlot(void)
{
bike->UpdateSim(0); // 0 is the (initial) time in seconds of the animation
MyQVTKWidget->GetInteractor()->Initialize();
int timerId = MyQVTKWidget->GetInteractor()->CreateRepeatingTimer(1000/bike->fps);
MyQVTKWidget->GetInteractor()->Start();
}
IN BICYCLE.H
------------
class bicycle
{
public:
bicycle();
void initSim(vtkSmartPointer<vtkRenderer> ren);
void UpdateSim(double time);
int fps; // framerate of simulation
integrationstep(time); // lets say all the bike physics is contained here
// im not gonna show any such definition here
private:
// lets say the bike's configuration is defined by just x and y coordinates
double X; // bike's x position
double Y; // bike's y position
// vtk
vtkSmartPointer<vtkRenderer> renderer;
// for simplicity the bike consists of only a sphere
vtkSmartPointer<vtkSphereSource> sphereSource;
vtkSmartPointer<vtkTransform> sphereTransform;
vtkSmartPointer<vtkTransformPolyDataFilter> sphereFilter;
vtkSmartPointer<vtkPolyDataMapper> sphereMapper;
vtkSmartPointer<Actor> sphereActor;
}
IN BICYCLE.CPP
--------------
bicycle::bicycle()
{
fps = 100;
}
void bicycle::initSim(vtkSmartPointer<vtkRenderer> ren)
{
renderer = ren;
sphereSource = vtkSphereSource::New();
sphereTransform = vtkTransform::New();
sphereFilter = vtkTransformPolyDataFilter::New();
sphereMapper = vtkPolyDataMapper::New();
sphereActor = vtkActor::New();
sphereSource->Update();
sphereTransform->Translate(.5,.5,0); // orienting the bike, dummy values
sphereFilter->SetInputConnection(sphereSource->GetOutputPort());
sphereFilter->SetTransform(sphereTransform);
sphereMapper->SetInputConnection(sphereFilter->GetOutputPort());
sphereActor->SetMapper(sphereMapper);
}
void bicycle::UpdateSim(time)
{
// update bike's X and Y from differential equation integration
integrationstep(time);
sphereActor->SetPosition(X,Y);
}
</pre>
==============================================
Implementation of (2) WITH additional features
==============================================
<pre>
IN MYQWIDGET.H
--------------
class vtkTimerCallback2; // defined below in this file
class MyQWidget() : public QWidget
{
Q_OBJECT;
public:
MyQWidget(bicycle *bike1, QWidget *parent = 0);
private slots:
void startSimSlot(void);
private:
// my own class that holds data about the bike,
// has vtk sources, actors, etc.
bicycle *bike;
QGridLayout MyLayout;
QToolButton *startSimButton;
QVTKWidget *MyQVTKWidget;
vtkSmartPointer<vtkRenderer> MyRenderer;
vtkSmartPointer<vtkRenderWindow> MyRenderWindow;
vtkSmartPointer<vtkTimerCallback2> MyCallback;
// Feature A
vtkSmartPointer<vtkPNGWriter> VidWriter;
vtkSmartPointer<vtkWindowToImageFilter> VidW2I;
// end Feature A
// Feature B
QVTKWidget *PlotQVTKWidget;
vtkSmartPointer<vtkContextView> PlotView;
vtkSmartPointer<vtkChartXY> PlotChart;
// end Feature B
};
class vtkTimerCallback2 : public vtkCommand {
public:
static vtkTimerCallback *New()
{
vtkTimerCallback2 *cb = new vtkTimerCallback2;
cb->TimerCount = 0;
return cb;
}
virtual void Execute(vtkObject *caller, unsigned long eventId,
void *vtkNotUsed(callData))
{
if (vtkCommand::TimerEvent == eventId)
++this->TimerCount;
}
time = (double)TimerCount/bike->fps;
bike->UpdateSim(time);
vtkRenderWindowInteractor *iren = vtkRenderWindowInteractor::SafeDownCast(caller);
iren->GetRenderWindow()->Render();
// Feature A
VidW2I->SetInput(iren->GetRenderWindow());
VidWriter->SetInput(VidW2I->GetOutput());
VidWrter->SetFileName(QString("bikeimg + QString("%1").arg(TimerCount) +
".ps").toStdString().c_str());
VidWriter->Write();
// end Feature A
// Feature B
bike->SetSimValues(TimerCount); // puts X and Y into a vtkTable in bike.
PlotRenWin->Render(); // attempt 1 to update plot
PlotQVTKWidget->GetInteractor()->Initialize(); // attempt 2
PlotQVTKWidget->GetInteractor()->Start(); // attempt 2
PlotChart->Paint(PlotView->GetContext()); // attempt 3
PlotView->GetScene()->Paint(PlotView->GetContext()); // attempt 4
// end Feature B
}
bicycle bike;
vtkSmartPointer<vtkPNGWriter> VidWriter; // Feature A
vtkSmartPointer<WindowToImageFilter> VidW2I; // Feature A
QVTKWidget *PlotQVTKWidget; // Feature B
vtkSmartPointer<vtkRenderWindow> PlotRenWn; // Feature B
vtkSmartPointer<vtkChartXY> PlotChart; // Feature B
vtkSmartPointer<vtkContextView> PlotView; // Featur B
private:
int TimerCount;
double time;
};
IN MYQWIDGET.CPP
----------------
MyQWidget::MyQWidget(bicycle *bike1, QWidget *parent) : QWidget(parent)
{
bike = bike1;
// qt initialize
MyLayout = new QGridLayout(this);
MyQVTKWidget = new QVTKWidget(this);
startSimButton = new QToolButton;
// qt commands
setLayout(MyLayout);
MyLayout->addWidget(MyQVTKWidget,0,0);
// vtk initialize
MyRenderer = vtkRenderer::New();
MyRenderWindow = vtkRenderWindow::New();
MyCallback = vtkSmartPointer<vtkTimerCallback2>::New();
// vtk commands
MyRenderWindow->AddRenderer(MyRenderer);
MyQVTKWidget->SetRenderWindow(MyRenderWindow);
MyCallback->bike = bike;
MyQVTKWidget->GetInteractor()->AddObservor(vtkCommand::TimerEvent, MyCallback);
bike->initSim(MyRenderer);
// Feature A
VidWriter = vtkSmartPointer<vtkPNGWriter>::New();
VidW2I = vtkSmartPointer<vtkWindowToImageFilter>::New();
MyCallback->VidWriter = VidWriter;
MyCallback->VidW2I = VidW2I;
// end Feature A
// Feature B
PlotQVTKWidget = new QVTKWidget(this);
PlotView = vtkSmartPointer<vtkContextView>::New();
PlotChart = vtkSmartPointer<vtkChartXY>::New();
PlotQVTKWidget->GetRenderWindow()->Render();
MyLayout->addWidget(PlotQVTKWidget,1,0);
PlotView->SetInteractor(PlotQVTKWidget->GetInteractor());
PlotQVTKWidget->SetRenderWindow(PlotView->GetRenderWindow());
PlotView->GetScene()->AddItem(PlotChart);
PlotChart->AddPlot(vtkChart::Line);
PlotChart->GetPlot(0)->SetInput(bike->SimTable, 0, 1); // X data
PlotChart->GetPlot(1)->SetInput(bike->SimTable, 0, 2); // Y data
bike->SimTable->Update();
PlotChart->Update();
PlotQVTKWidget->GetInteractor()->Initialize();
PlotQVTKWidget->GetRenderWindow()->Render();
PlotQVTKWidget->GetInteractor()->Start();
MyCallback->PlotQVTKWidget = PlotQVTKWidget;
MyCallback->PlotRenWin = PlotQVTKWidget->GetRenderWindow();
// end Feature B
}
MyQWidget::startSimSlot(void)
{
bike->UpdateSim(0); // 0 is the time, initial time
MyQVTKWidget->GetInteractor()->Initialize();
int timerId = MyQVTKWidget->GetInteractor()->CreateRepeatingTimer(1000/bike->fps);
MyQVTKWidget->GetInteractor()->Start();
}
IN BICYCLE.H
------------
class bicycle
{
public:
bicycle();
void initSim(vtkSmartPointer<vtkRenderer> ren);
void UpdateSim(double time);
int fps; // framerate of simulation
integrationstep(time); // lets say all the bike physics is contained here
// im not gonna show any such definition here
// Feature B
void SetSimValues(int rowidx); // called in vtkTimerCallback2, stores
// new X and Y data for plotting
// end Feature B
private:
// lets say the bike's configuration is defined by just x and y coordinates
double t; // current time of the simulation
double X; // bike's x position
double Y; // bike's y position
// vtk
vtkSmartPointer<vtkRenderer> renderer;
// for simplicity the bike consists of only a sphere
vtkSmartPointer<vtkSphereSource> sphereSource;
vtkSmartPointer<vtkTransform> sphereTransform;
vtkSmartPointer<vtkTransformPolyDataFilter> sphereFilter;
vtkSmartPointer<vtkPolyDataMapper> sphereMapper;
vtkSmartPointer<Actor> sphereActor;
// Feature B
vtkSmartPointer<vtkTable> PlotTable;
std::vector< vtkSmartPointer<vtkFloatArray> > PlotArrays;
// end Feature B
// Feature C
vtkSmartPointer<vtkPoints> TracePoints;
vtkSmartPointer<vtkPolyLine> TraceLine;
vtkSmartPointer<vtkCellArray> TraceCell;
vtkSmartPointer<vtkPolyData> TraceData;
vtkSmartPointer<vtkPolyDataMapper> TraceMapper;
vtkSmartPointer<vtkActor> TraceActor;
// end Feature C
}
IN BICYCLE.CPP
--------------
bicycle::bicycle()
{
fps = 100;
// Feature B: initialize vtk objects for vtk plot of X and Y
PlotTable = vtkSmartPointer<vtkTable>::New();
PlotArrays.resize(2); // X column, Y column
PlotArrays[0] = vtkSmartPointer<vtkFloatArray>::New();
PlotArrays[0]->SetName("X");
PlotArrays[1] = vtkSmartPointer<vtkFloatArray>::New();
PlotArrays[1]->SetName("Y");
PlotTable->AddColumn(PlotArrays[0]);
PlotTable->AddColumn(PlotArrays[1]);
PlotTable->InsertNextBlankRow();
PlotTable->InsertNextBlankRow(1); // vtk plot needs to start with 2 points to
// draw a line
// end Feature B
}
void bicycle::initSim(vtkSmartPointer<vtkRenderer> ren)
{
renderer = ren;
sphereSource = vtkSphereSource::New();
sphereTransform = vtkTransform::New();
sphereFilter = vtkTransformPolyDataFilter::New();
sphereMapper = vtkPolyDataMapper::New();
sphereActor = vtkActor::New();
sphereSource->Update();
sphereTransform->Translate(.5,.5,0); // orienting the bike, dummy values
sphereFilter->SetInputConnection(sphereSource->GetOutputPort());
sphereFilter->SetTransform(sphereTransform);
sphereMapper->SetInputConnection(sphereFilter->GetOutputPort());
sphereActor->SetMapper(sphereMapper);
// Feature C: initialize vtk objects for the bike's polyline trace
TracePoints = vtkSmartPointer<vtkPoints>:New();
TraceLine = vtkSmartPointer<vtkPolyLine>::New();
TraceCell = vtkSmartPointer<vtkCellArray>::New();
TraceMapper = vtkSmartPointer<vtkPolyDataMapper>::New();
TraceActor = vtkSmartPointer<vtkActor>::New();
TraceData = vtkSmartPointer<vtkPolyData>::New();
TracePoints->SetNumberOfPoints(1000);
TraceLine->GetPointIds()->SetNumberOfIds(1000);
// initializing points to values just to ensure that a polyline shows up in the
// render window
for (unsigned int i = 0; i < 1000; i++) {
TracePoints->SetPoint(i, i, 0, 0);
TraceLine->GetPointIds()->SetId(i, i);
}
TraceCell->InsertNextCell(TraceLine);
TraceData->SetPoints(TracePoints);
TraceMapper->SetInput(TraceData);
TraceActor->SetMapper(TraceMapper);
// end Feature C
}
void bicycle::UpdateSim(time)
{
// update bike's X and Y from differential equation integration
integrationstep(time);
sphereActor->SetPosition(X,Y);
// Feature C
// adds a point to the trace of the current X Y position of the bike. the
// frame number of the animation is also the number of rows in the plot table
TracePoints->SetPoint(PlotTable->GetNumberOfRows(), X, Y, 0);
TraceMapper->Update();
// end Feature C
}
// Feature B: called at every frame by the vtkTimerCallback2
void bicycle::SetSimValues(int rowidx) {
PlotTable->InsertNextBlankRow();
PlotTable->SetValue(rowidx, 0, bike->t); // lets say the bicycle has a t field holding time
PlotTable->SetValue(rowidx, 1, bike->X);
PlotTable->SetValue(rowidx, 2, bike->Y);
PlotTable->Update();
}
// end Feature B
</pre>
=================== end of code
Note that the first implementation, without the features, works just fine. However, the code above will not work if you were to create the files and try to compile them; as I've left out the integration function (all the physics) and the rest of the Qt code, etc. I now describe the way in which all the features, A B and C, do not work:
Feature A - Images are saved with each updated frame, but the image is ALWAYS of the first frame of the animation.
The saved images do not update, though the actual animation does update/succeed. Perhaps one thing to do, which is the long term goal anyway, is to move the writing of image files to a separate Slot that is activated by a QToolButton. Are there any good resources/examples for how to do this "behind the scenes", e.g. so that the user does not see the video replay whilst writing image files of the already-played animation?
Feature B - The plot of the X Y data is not displaying anything as the animation evolves.
I know that the vtkTable is filling up with the X and Y values that evolve during the simulation, but the plot that is hooked up to the table is not updating "live" as the animation evolves. I know that in general the plot works because I tried rearranging the code so that the X Y data was plotted once I stopped the animation (by destroying the timer on the interactor), and this resulted in a successful plot of the X Y data. It won't update "live" though. Is one option to have the animation and plot be part of the same QVTKWidget and RenderWindow, and have two renderers in that one render window? I would say this is not the desired solution because it fixes the placement of the plot.
Feature C - The trace that I initialize, a very long straight line, IS drawn. However, this original trace remains the same throughout the animation; it is not updated EVEN THOUGH those initial straight-line values are replaced with actual X, Y data at every frame.
I tried to create a function, bicycle::UpdateTrace(), called at every new frame, that filled the entire TracePoints object with all the points again (didn't append just the most recent new point). I'm aware that after 1000 animation frames (I only gave TracePoints 1000 points) I should get a segfault, but lets ignore that for now.
I have been told that this mailing list is very good about helping inexperienced people like me, but I haven't posted here before and I'm not sure how much detail you need to understand the code above. If necessary, please ask me to provide some prose about how the code works. I'll gladly do so! I'll try to be responsive and clear.
If you would like to help me, I would be extremely grateful if you could address only one of the three issues (do not feel like you need to address all 3 issues/features).
Thank you for your time!
</html>
        
<br/><hr align="left" width="300" />
View this message in context: <a href="http://vtk.1045678.n5.nabble.com/Trouble-with-added-features-to-a-VTK-Animation-saving-frames-live-vtkPlot-vtkPoints-tp4705911p4705911.html">Trouble with added features to a VTK Animation: saving frames, "live" vtkPlot, vtkPoints</a><br/>
Sent from the <a href="http://vtk.1045678.n5.nabble.com/VTK-Users-f1224199.html">VTK - Users mailing list archive</a> at Nabble.com.<br/>