diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt
index d44f826820..46afa96751 100644
--- a/application/testing/CMakeLists.txt
+++ b/application/testing/CMakeLists.txt
@@ -707,6 +707,11 @@ f3d_test(NAME TestProgress DATA cow.vtp ARGS --progress NO_BASELINE)
f3d_test(NAME TestProgressScene DATA WaterBottle.glb ARGS --progress NO_BASELINE)
f3d_test(NAME TestInteractionProgressReload DATA cow.vtp ARGS --progress NO_BASELINE INTERACTION) #Up;Up;Up;Up
+# For some reasons this animation test goes "too far" when run with sanitizer
+if(F3D_SANITIZER STREQUAL "none")
+ f3d_test(NAME TestInteractionCycleAnimation DATA InterpolationTest.glb INTERACTION DEFAULT_LIGHTS) #WWWWWWWWWWW;Space;Space;
+endif()
+
# Drop file test needs https://gitlab.kitware.com/vtk/vtk/-/merge_requests/9199
if(VTK_VERSION VERSION_GREATER_EQUAL 9.1.20220519) # Drop file test uses stream version 1.2
f3d_test(NAME TestInteractionDropFiles INTERACTION_CONFIGURE)#X;DropEvent cow.vtp;DropEvent dragon.vtu suzanne.stl;
diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md
index a0fa6338ff..794ad4fb56 100644
--- a/doc/CHANGELOG.md
+++ b/doc/CHANGELOG.md
@@ -5,6 +5,8 @@
For F3D users:
- Added a new option `--point-type` used to specify how to display points sprites
- Add support for 3D Gaussians Splatting in binary .splat format
+- Added ability to cycle through available animations by pressing "W" hotkey
+- Added display of current animation name within cheatsheet
For libf3d users:
- Added a new option `model.point-sprites.type` used to specify how to display points (only if `model.point-sprites.enable` is true)
diff --git a/doc/user/INTERACTIONS.md b/doc/user/INTERACTIONS.md
index 8b8b927bb0..e7af1d9a5a 100644
--- a/doc/user/INTERACTIONS.md
+++ b/doc/user/INTERACTIONS.md
@@ -28,6 +28,7 @@ See the [coloring cycle](#cycling-coloring) section for more info.
Other options can be toggled directly by pressing the following hotkeys:
+* W: cycle animations.
* B: display of the scalar bar, only when coloring and not using direct scalars.
* V: volume rendering.
* I: opacity function inversion during volume rendering.
diff --git a/doc/user/USAGE.md b/doc/user/USAGE.md
index 49b1348cae..eda05654aa 100644
--- a/doc/user/USAGE.md
+++ b/doc/user/USAGE.md
@@ -51,7 +51,7 @@ to modify this behavior. For file formats that do not support it, **a default sc
F3D can play animations for a number of file formats (.ex2/.e/.exo/.g, .gltf/.glb, .fbx, .dae, .x, .usd) if the file contains an animation.
It is possible to select the animation to play using `--animation-index`, or to play all animations at once using `--animation-index=-1` (.gltf/.glb only).
When F3D plays an animation, it assumes the time unit is in seconds to show accurate speed of animation. Use `--animation-speed-factor` if
-an adjustment is needed. By default, F3D will try update the scene 60 times per seconds, use `--animation-frame-rate` to change that if needed.
+an adjustment is needed. By default, F3D will try update the scene 60 times per seconds, use `--animation-frame-rate` to change that if needed. Press "W" hotkey to cycle through available animations.
## Plugins
diff --git a/library/private/animationManager.h b/library/private/animationManager.h
index aed4b3c19c..cf2c19d841 100644
--- a/library/private/animationManager.h
+++ b/library/private/animationManager.h
@@ -47,6 +47,27 @@ class animationManager
void StartAnimation();
void StopAnimation();
+ /**
+ * Cycle onto and play the next available animation
+ */
+ void CycleAnimation();
+
+ /**
+ * Enable only the current animation
+ */
+ void EnableOnlyCurrentAnimation();
+
+ /**
+ * Get the current animation index
+ */
+ int GetAnimationIndex();
+
+ /**
+ * Return the current animation name if any
+ * Can be called before initialization safely
+ */
+ std::string GetAnimationName();
+
/**
* Return true if the animation manager is playing the animation
*/
@@ -85,6 +106,8 @@ class animationManager
unsigned long CallBackId = 0;
double CurrentTime = 0;
bool CurrentTimeSet = false;
+ int AnimationIndex = 0;
+ int AvailAnimations = -1;
std::chrono::steady_clock::time_point PreviousTick;
vtkSmartPointer ProgressWidget;
diff --git a/library/private/window_impl.h b/library/private/window_impl.h
index e6e8d08a15..43da739a42 100644
--- a/library/private/window_impl.h
+++ b/library/private/window_impl.h
@@ -47,6 +47,7 @@ class window_impl : public window
image renderToImage(bool noBackground = false) override;
int getWidth() const override;
int getHeight() const override;
+ window& setAnimationNameInfo(const std::string& name);
window& setSize(int width, int height) override;
window& setPosition(int x, int y) override;
window& setIcon(const unsigned char* icon, size_t iconSize) override;
diff --git a/library/src/animationManager.cxx b/library/src/animationManager.cxx
index bc8ce6628d..e7277e1a8f 100644
--- a/library/src/animationManager.cxx
+++ b/library/src/animationManager.cxx
@@ -19,20 +19,20 @@ namespace f3d::detail
bool animationManager::Initialize(
const options* options, window* window, interactor_impl* interactor, vtkImporter* importer)
{
+ assert(importer);
this->HasAnimation = false;
this->Playing = false;
this->CurrentTime = 0;
this->CurrentTimeSet = false;
-
this->Options = options;
this->Interactor = interactor;
this->Window = window;
this->Importer = importer;
// This can be -1 if animation support is not implemented in the importer
- vtkIdType availAnimations = this->Importer->GetNumberOfAnimations();
+ this->AvailAnimations = this->Importer->GetNumberOfAnimations();
- if (availAnimations > 0 && interactor)
+ if (this->AvailAnimations > 0 && interactor)
{
this->ProgressWidget = vtkSmartPointer::New();
interactor->SetInteractorOn(this->ProgressWidget);
@@ -48,7 +48,6 @@ bool animationManager::Initialize(
progressRep->DrawBackgroundOff();
progressRep->DragableOff();
progressRep->SetShowBorderToOff();
-
// Complete vtkProgressBarRepresentation needs
// https://gitlab.kitware.com/vtk/vtk/-/merge_requests/7359
#if VTK_VERSION_NUMBER >= VTK_VERSION_CHECK(9, 0, 20201027)
@@ -66,7 +65,7 @@ bool animationManager::Initialize(
int animationIndex = options->getAsInt("scene.animation.index");
double animationTime = options->getAsDouble("scene.animation.time");
- if (availAnimations <= 0)
+ if (this->AvailAnimations <= 0)
{
log::debug("No animation available in this file");
if (animationIndex > 0)
@@ -85,13 +84,19 @@ bool animationManager::Initialize(
{
log::debug("Animation(s) available in this file are:");
}
- for (int i = 0; i < availAnimations; i++)
+ for (int i = 0; i < this->AvailAnimations; i++)
{
log::debug(i, ": ", this->Importer->GetAnimationName(i));
}
log::debug("");
- if (animationIndex > 0 && animationIndex >= availAnimations)
+ this->AnimationIndex = options->getAsInt("scene.animation.index");
+
+ if (this->AnimationIndex != 0 && this->AvailAnimations <= 0)
+ {
+ log::warn("An animation index has been specified but there are no animation available.");
+ }
+ else if (this->AnimationIndex > 0 && this->AnimationIndex >= this->AvailAnimations)
{
log::warn(
"Specified animation index is greater than the highest possible animation index, enabling "
@@ -99,22 +104,15 @@ bool animationManager::Initialize(
this->Importer->EnableAnimation(0);
}
- else if (animationIndex <= -1)
- {
- for (int i = 0; i < availAnimations; i++)
- {
- this->Importer->EnableAnimation(i);
- }
- }
else
{
- this->Importer->EnableAnimation(animationIndex);
+ this->EnableOnlyCurrentAnimation();
}
// Recover time ranges for all enabled animations
this->TimeRange[0] = std::numeric_limits::infinity();
this->TimeRange[1] = -std::numeric_limits::infinity();
- for (vtkIdType animIndex = 0; animIndex < availAnimations; animIndex++)
+ for (vtkIdType animIndex = 0; animIndex < this->AvailAnimations; animIndex++)
{
if (this->Importer->IsAnimationEnabled(animIndex))
{
@@ -255,6 +253,7 @@ void animationManager::Tick()
//----------------------------------------------------------------------------
bool animationManager::LoadAtTime(double timeValue)
{
+ assert(this->Importer);
if (!this->HasAnimation)
{
return false;
@@ -265,7 +264,6 @@ bool animationManager::LoadAtTime(double timeValue)
this->TimeRange[0], ", ", this->TimeRange[1], "] .");
return false;
}
-
this->CurrentTime = timeValue;
this->CurrentTimeSet = true;
this->Importer->UpdateTimeStep(this->CurrentTime);
@@ -283,10 +281,68 @@ bool animationManager::LoadAtTime(double timeValue)
return true;
}
+// ---------------------------------------------------------------------------------
+void animationManager::CycleAnimation()
+{
+ assert(this->Importer);
+ if (this->AvailAnimations <= 0)
+ {
+ return;
+ }
+ this->AnimationIndex += 1;
+
+ if (this->AnimationIndex == this->AvailAnimations)
+ {
+ this->AnimationIndex = -1;
+ }
+
+ this->EnableOnlyCurrentAnimation();
+ this->LoadAtTime(this->TimeRange[0]);
+}
+
+// ---------------------------------------------------------------------------------
+int animationManager::GetAnimationIndex()
+{
+ return this->AnimationIndex;
+}
+
+// ---------------------------------------------------------------------------------
+std::string animationManager::GetAnimationName()
+{
+ if (!this->Importer || this->AvailAnimations <= 0)
+ {
+ return "";
+ }
+
+ if (this->AnimationIndex == -1)
+ {
+ return "All Animations";
+ }
+ return this->Importer->GetAnimationName(this->AnimationIndex);
+}
+
+//----------------------------------------------------------------------------
+void animationManager::EnableOnlyCurrentAnimation()
+{
+ assert(this->Importer);
+ for (int i = 0; i < this->AvailAnimations; i++)
+ {
+ this->Importer->DisableAnimation(i);
+ }
+ for (int i = 0; i < this->AvailAnimations; i++)
+ {
+ if (this->AnimationIndex == -1 || i == this->AnimationIndex)
+ {
+ this->Importer->EnableAnimation(i);
+ }
+ }
+}
+
//----------------------------------------------------------------------------
void animationManager::GetTimeRange(double timeRange[2])
{
timeRange[0] = this->TimeRange[0];
timeRange[1] = this->TimeRange[1];
}
+
}
diff --git a/library/src/interactor_impl.cxx b/library/src/interactor_impl.cxx
index f73434488e..f5f56ef5a7 100644
--- a/library/src/interactor_impl.cxx
+++ b/library/src/interactor_impl.cxx
@@ -167,9 +167,15 @@ class interactor_impl::internals
bool checkColoring = false;
bool render = false;
- // Available keycodes: W
+ // Available keycodes: None
switch (keyCode)
{
+ case 'W':
+ self->AnimationManager->CycleAnimation();
+ self->Options.set("scene.animation.index", self->AnimationManager->GetAnimationIndex());
+ ren->SetAnimationnameInfo(self->AnimationManager->GetAnimationName());
+ render = true;
+ break;
case 'C':
if (renWithColor)
{
@@ -356,6 +362,7 @@ class interactor_impl::internals
self->Options.set("model.scivis.array-name", renWithColor->GetColoringArrayName());
self->Options.set("model.scivis.component", renWithColor->GetColoringComponent());
}
+
if (render)
{
self->Window.render();
diff --git a/library/src/loader_impl.cxx b/library/src/loader_impl.cxx
index e36c408b4a..b848d5f794 100644
--- a/library/src/loader_impl.cxx
+++ b/library/src/loader_impl.cxx
@@ -163,6 +163,9 @@ class loader_impl::internals
}
}
+ // Set the name for animation
+ this->Window.setAnimationNameInfo(this->AnimationManager.GetAnimationName());
+
// Display the importer description
loader_impl::internals::DisplayImporterDescription(this->GenericImporter);
@@ -232,7 +235,6 @@ loader& loader_impl::loadGeometry(const std::string& filePath, bool reset)
throw loader::load_failure_exception(
filePath + " is not a file of a supported 3D geometry file format for default scene");
}
-
// Read the file
log::debug("Loading 3D geometry: ", filePath, "\n");
@@ -325,6 +327,10 @@ loader& loader_impl::loadScene(const std::string& filePath)
}
}
+ // Set the name for animation
+ this->Internals->Window.setAnimationNameInfo(
+ this->Internals->AnimationManager.GetAnimationName());
+
// Display output description
loader_impl::internals::DisplayImporterDescription(this->Internals->CurrentFullSceneImporter);
diff --git a/library/src/window_impl.cxx b/library/src/window_impl.cxx
index fc170bb165..c637a2b7a6 100644
--- a/library/src/window_impl.cxx
+++ b/library/src/window_impl.cxx
@@ -216,6 +216,13 @@ int window_impl::getHeight() const
return this->Internals->RenWin->GetSize()[1];
}
+//----------------------------------------------------------------------------
+window& window_impl::setAnimationNameInfo(const std::string& name)
+{
+ this->Internals->Renderer->SetAnimationnameInfo(name);
+ return *this;
+}
+
//----------------------------------------------------------------------------
window& window_impl::setSize(int width, int height)
{
diff --git a/testing/baselines/TestInteractionCycleAnimation.png b/testing/baselines/TestInteractionCycleAnimation.png
new file mode 100644
index 0000000000..8092cefdfc
--- /dev/null
+++ b/testing/baselines/TestInteractionCycleAnimation.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:33883ed26e71ce92c2361bcb423a01bca9a8425a8be52f490b301a3d99cf9ad0
+size 4773
diff --git a/testing/recordings/TestInteractionCycleAnimation.log b/testing/recordings/TestInteractionCycleAnimation.log
new file mode 100644
index 0000000000..342db1aab7
--- /dev/null
+++ b/testing/recordings/TestInteractionCycleAnimation.log
@@ -0,0 +1,81 @@
+# StreamVersion 1.2
+ConfigureEvent 2 1381 0 0 0 0 0
+ExposeEvent 0 1409 0 0 0 0 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1195 379 0 119 1 w 0
+CharEvent 1195 379 0 119 1 w 0
+KeyReleaseEvent 1195 379 0 119 1 w 0
+RenderEvent 0 1409 0 0 0 0 0
+
+KeyPressEvent 1698 319 0 32 1 space 0
+CharEvent 1698 319 0 32 1 space 0
+KeyReleaseEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+TimerEvent 1698 319 0 32 1 space 0
+KeyPressEvent 1698 319 0 32 1 space 0
+CharEvent 1698 319 0 32 1 space 0
+KeyReleaseEvent 1698 319 0 32 1 space 0
diff --git a/vtkext/private/module/vtkF3DRenderer.cxx b/vtkext/private/module/vtkF3DRenderer.cxx
index 73f2a9fffc..21bb4167d4 100644
--- a/vtkext/private/module/vtkF3DRenderer.cxx
+++ b/vtkext/private/module/vtkF3DRenderer.cxx
@@ -221,6 +221,7 @@ void vtkF3DRenderer::Initialize(const std::string& up)
this->HDRISpecularConfigured = false;
this->HDRISkyboxConfigured = false;
+ this->AnimationNameInfo = "";
this->GridInfo = "";
// Importer rely on the Environment being set, so this is needed in the initialization
@@ -1287,6 +1288,10 @@ void vtkF3DRenderer::ShowHDRISkybox(bool show)
//----------------------------------------------------------------------------
void vtkF3DRenderer::FillCheatSheetHotkeys(std::stringstream& cheatSheetText)
{
+
+
+ cheatSheetText << (!this->AnimationNameInfo.empty() ? " W: Cycle animation [" +
+ vtkF3DRenderer::ShortName(this->AnimationNameInfo, 19) + "]\n" : "");
cheatSheetText << " P: Translucency support " << (this->UseDepthPeelingPass ? "[ON]" : "[OFF]")
<< "\n";
cheatSheetText << " Q: Ambient occlusion " << (this->UseSSAOPass ? "[ON]" : "[OFF]") << "\n";
@@ -1311,8 +1316,7 @@ void vtkF3DRenderer::FillCheatSheetHotkeys(std::stringstream& cheatSheetText)
cheatSheetText << std::fixed;
cheatSheetText << " L: Light (increase, shift+L: decrease) [" << this->LightIntensity << "]"
<< " \n";
-}
-
+ }
//----------------------------------------------------------------------------
void vtkF3DRenderer::ConfigureActorsProperties()
{
@@ -1546,3 +1550,23 @@ void vtkF3DRenderer::CreateCacheDirectory()
// Create the folder if it does not exists
vtksys::SystemTools::MakeDirectory(currentCachePath);
}
+
+//----------------------------------------------------------------------------
+void vtkF3DRenderer::SetAnimationnameInfo(const std::string& info)
+{
+ this->AnimationNameInfo = info;
+ this->CheatSheetConfigured = false;
+}
+
+//----------------------------------------------------------------------------
+std::string vtkF3DRenderer::ShortName(const std::string& name, int maxChar)
+{
+ if (name.size() <= static_cast(maxChar) || maxChar <= 3)
+ {
+ return name;
+ }
+ else
+ {
+ return name.substr(0, maxChar - 3) + "...";
+ }
+}
diff --git a/vtkext/private/module/vtkF3DRenderer.h b/vtkext/private/module/vtkF3DRenderer.h
index 5d2a5c3ee3..e09350dbf5 100644
--- a/vtkext/private/module/vtkF3DRenderer.h
+++ b/vtkext/private/module/vtkF3DRenderer.h
@@ -56,6 +56,7 @@ class vtkF3DRenderer : public vtkOpenGLRenderer
void SetBackground(const double* backgroundColor) override;
void SetLightIntensity(const double intensity);
void SetFilenameInfo(const std::string& info);
+ void SetAnimationnameInfo(const std::string& info);
void SetDropZoneInfo(const std::string& info);
void SetGridAbsolute(bool absolute);
void SetGridUnitSquare(double unitSquare);
@@ -233,6 +234,10 @@ class vtkF3DRenderer : public vtkOpenGLRenderer
* Create a cache directory if a HDRIHash is set
*/
void CreateCacheDirectory();
+ /**
+ * Shorten a provided name with "..."
+ */
+ static std::string ShortName(const std::string& name, int maxChar);
vtkNew InitialCamera;
@@ -316,6 +321,7 @@ class vtkF3DRenderer : public vtkOpenGLRenderer
std::string GridInfo;
std::string CachePath;
+ std::string AnimationNameInfo;
};
#endif
diff --git a/vtkext/private/module/vtkF3DRendererWithColoring.cxx b/vtkext/private/module/vtkF3DRendererWithColoring.cxx
index 80cd526913..6e3cefb3ba 100644
--- a/vtkext/private/module/vtkF3DRendererWithColoring.cxx
+++ b/vtkext/private/module/vtkF3DRendererWithColoring.cxx
@@ -962,7 +962,7 @@ void vtkF3DRendererWithColoring::FillCheatSheetHotkeys(std::stringstream& cheatS
cheatSheetText << " C: Cell scalars coloring [" << (this->UseCellColoring ? "ON" : "OFF")
<< "]\n";
cheatSheetText << " S: Scalars coloring ["
- << (hasColoring ? vtkF3DRendererWithColoring::ShortName(info.Name, 19) : "OFF")
+ << (hasColoring ? vtkF3DRenderer::ShortName(info.Name, 19) : "OFF")
<< "]\n";
cheatSheetText << " Y: Coloring component ["
<< vtkF3DRendererWithColoring::ComponentToString(this->ComponentForColoring)
@@ -1091,16 +1091,3 @@ std::string vtkF3DRendererWithColoring::ComponentToString(int component)
return componentName;
}
}
-
-//----------------------------------------------------------------------------
-std::string vtkF3DRendererWithColoring::ShortName(const std::string& name, int maxChar)
-{
- if (name.size() <= static_cast(maxChar) || maxChar <= 3)
- {
- return name;
- }
- else
- {
- return name.substr(0, maxChar - 3) + "...";
- }
-}
diff --git a/vtkext/private/module/vtkF3DRendererWithColoring.h b/vtkext/private/module/vtkF3DRendererWithColoring.h
index d8063dd5af..1684ea4f43 100644
--- a/vtkext/private/module/vtkF3DRendererWithColoring.h
+++ b/vtkext/private/module/vtkF3DRendererWithColoring.h
@@ -266,12 +266,7 @@ class vtkF3DRendererWithColoring : public vtkF3DRenderer
* Otherwise, use component #index as the default value.
*/
std::string ComponentToString(int component);
-
- /**
- * Shorten a provided name with "..."
- */
- static std::string ShortName(const std::string& name, int component);
-
+
vtkWeakPointer Importer = nullptr;
vtkNew ScalarBarActor;