diff --git a/plugins/native/CMakeLists.txt b/plugins/native/CMakeLists.txt index 990f8ccace..910f164f30 100644 --- a/plugins/native/CMakeLists.txt +++ b/plugins/native/CMakeLists.txt @@ -379,7 +379,7 @@ f3d_plugin_declare_reader( NAME QuakeMDL EXTENSIONS mdl MIMETYPES application/vnd.mdl - OPTIONS skin_index + OPTIONS skin_index animation_interpolation VTK_IMPORTER vtkF3DQuakeMDLImporter FORMAT_DESCRIPTION "Quake 1 MDL model" ${_SUPPORTS_STREAM} diff --git a/plugins/native/mdl.inl b/plugins/native/mdl.inl index 81b44a6578..dacdc42c1c 100644 --- a/plugins/native/mdl.inl +++ b/plugins/native/mdl.inl @@ -13,4 +13,10 @@ void applyCustomImporter(vtkImporter* importer, const std::string& vtkNotUsed(fi nullptr, "QuakeMDL.skin_index must be positive. Defaulting to 0."); } mdlImporter->SetSkinIndex(skinIndex); + + std::string interpOptName = "QuakeMDL.animation_interpolation"; + std::string interpOptStr = this->ReaderOptions.at(interpOptName); + + bool interpolate = (F3DUtils::ParseToDouble(interpOptStr, 0, interpOptName) != 0); + mdlImporter->SetInterpolate(interpolate); } diff --git a/plugins/native/module/vtk.module b/plugins/native/module/vtk.module index ace0f28960..d646e69be6 100644 --- a/plugins/native/module/vtk.module +++ b/plugins/native/module/vtk.module @@ -7,6 +7,7 @@ DEPENDS VTK::CommonExecutionModel f3d::vtkext PRIVATE_DEPENDS + VTK::FiltersGeneral VTK::IOPLY VTK::RenderingOpenGL2 VTK::zlib diff --git a/plugins/native/module/vtkF3DQuakeMDLImporter.cxx b/plugins/native/module/vtkF3DQuakeMDLImporter.cxx index 1cd07ee12b..34b29b86a2 100644 --- a/plugins/native/module/vtkF3DQuakeMDLImporter.cxx +++ b/plugins/native/module/vtkF3DQuakeMDLImporter.cxx @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -13,6 +14,8 @@ #include #include #include +#include +#include #include #include @@ -653,6 +656,7 @@ struct vtkF3DQuakeMDLImporter::vtkInternals vtkF3DQuakeMDLImporter* Parent; vtkSmartPointer Mapper; vtkSmartPointer Texture; + vtkNew AttrInterp; std::vector AnimationNames; std::vector> AnimationTimes; @@ -734,10 +738,68 @@ bool vtkF3DQuakeMDLImporter::UpdateAtTimeValue(double timeValue) (found == times.end() - 1) ? times.size() - 2 : std::distance(times.begin(), found); // If time at index i > timeValue, then choose the previous frame const size_t frameIndex = times[i] > timeValue && i > 0 ? i - 1 : i; + size_t upperFrameIndex = + std::distance(times.begin(), std::lower_bound(times.begin(), times.end() - 1, timeValue)); + size_t lowerFrameIndex; + double alpha; + if (upperFrameIndex >= times.size() - 1) + { + // Last frame + upperFrameIndex = lowerFrameIndex = times.size() - 2; + alpha = 0.0; + } + else + { + lowerFrameIndex = upperFrameIndex > 0 ? upperFrameIndex - 1 : 0; + alpha = vtkMathUtilities::SafeDivision( + timeValue - times[lowerFrameIndex], times[upperFrameIndex] - times[lowerFrameIndex]); + } if (isMeshAnimation) { - this->Internals->Mapper->SetInputData( - this->Internals->AnimationFrames[animIndex][frameIndex]); + vtkPolyData* upperFrame = this->Internals->AnimationFrames[animIndex][upperFrameIndex]; + vtkPolyData* lowerFrame = this->Internals->AnimationFrames[animIndex][lowerFrameIndex]; + if (!this->Interpolate || upperFrame == lowerFrame) + { + // Interpolation disabled or no difference between frames + this->Internals->Mapper->SetInputData(lowerFrame); + } + else + { + // Interpolate every point between the two frames to get + // resulting interpolatedPoints + vtkPoints* lowerPoints = lowerFrame->GetPoints(); + vtkPoints* upperPoints = upperFrame->GetPoints(); + + const vtkIdType numPoints = lowerPoints->GetNumberOfPoints(); + vtkNew interpolatedPoints; + interpolatedPoints->SetNumberOfPoints(numPoints); + for (vtkIdType p = 0; p < numPoints; ++p) + { + // Get x, y, z coordinates from both frames + double lowerCoord[3]; + double upperCoord[3]; + lowerPoints->GetPoint(p, lowerCoord); + upperPoints->GetPoint(p, upperCoord); + + interpolatedPoints->SetPoint(p, + lowerCoord[0] + alpha * (upperCoord[0] - lowerCoord[0]), // x + lowerCoord[1] + alpha * (upperCoord[1] - lowerCoord[1]), // y + lowerCoord[2] + alpha * (upperCoord[2] - lowerCoord[2])); // z + } + + // Interpolate attributes only + vtkInterpolateDataSetAttributes* attrInterp = this->Internals->AttrInterp; + attrInterp->RemoveAllInputs(); + attrInterp->AddInputData(lowerFrame); + attrInterp->AddInputData(upperFrame); + attrInterp->SetT(alpha); + attrInterp->Update(); + + vtkNew interpolatedMesh; + interpolatedMesh->ShallowCopy(attrInterp->GetOutput()); + interpolatedMesh->SetPoints(interpolatedPoints); + this->Internals->Mapper->SetInputData(interpolatedMesh); + } } else { diff --git a/plugins/native/module/vtkF3DQuakeMDLImporter.h b/plugins/native/module/vtkF3DQuakeMDLImporter.h index aecdc50653..c0331328b7 100644 --- a/plugins/native/module/vtkF3DQuakeMDLImporter.h +++ b/plugins/native/module/vtkF3DQuakeMDLImporter.h @@ -70,6 +70,14 @@ class vtkF3DQuakeMDLImporter : public vtkF3DImporter vtkGetMacro(SkinIndex, unsigned int); ///@} + ///@{ + /** + * Set/Get whether to interpolate between animation frames. Default is false. + */ + vtkSetMacro(Interpolate, bool); + vtkGetMacro(Interpolate, bool); + ///@} + /** * Return true if, after a quick check of file header, it looks like the provided stream * can be read. Return false if it is sure it cannot be read as a strean. @@ -92,6 +100,7 @@ class vtkF3DQuakeMDLImporter : public vtkF3DImporter struct vtkInternals; unsigned int SkinIndex = 0; + bool Interpolate = false; std::unique_ptr Internals; };