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;