Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions library/private/scene_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class scene_impl : public scene
scene& add(const std::vector<std::filesystem::path>& filePath) override;
scene& add(const std::vector<std::string>& filePathStrings) override;
scene& add(const mesh_t& mesh) override;
scene& add(std::vector<double> times, MeshCallback&& callback) override;
scene& clear() override;
int addLight(const light_state_t& lightState) const override;
int getLightCount() const override;
Expand Down
12 changes: 12 additions & 0 deletions library/public/scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

/// @cond
#include <filesystem>
#include <functional>
#include <string>
#include <vector>
/// @endcond
Expand Down Expand Up @@ -45,6 +46,12 @@ class F3D_EXPORT scene
: exception(what) {};
};

/**
* Callback type for animated meshes.
* Called with the current time value, should return the mesh for that time.
*/
using MeshCallback = std::function<mesh_t(double time)>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think of this name @Meakk ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds fine


///@{
/**
* Add and load provided files into the scene
Expand All @@ -62,6 +69,11 @@ class F3D_EXPORT scene
*/
virtual scene& add(const mesh_t& mesh) = 0;

/**
* Add and load provided mesh into the scene
*/
virtual scene& add(std::vector<double> times, MeshCallback&& callback) = 0;

///@{
/**
* Convenience initializer list signature for add method
Expand Down
38 changes: 34 additions & 4 deletions library/src/scene_impl.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,35 @@ scene& scene_impl::add(const std::vector<fs::path>& filePaths)
}

//----------------------------------------------------------------------------
scene& scene_impl::add(std::vector<double> times, MeshCallback&& callback)
{
if (times[1] < times[0])
Copy link
Member

@mwestphal mwestphal Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont check anything here, let vtkF3DMemnoryMesh handle it

{
throw scene::load_failure_exception("startTime must be less than or equal to endTime");
}

vtkNew<vtkF3DMemoryMesh> vtkSource;

auto wrappedCallback = [cb = std::move(callback)](double time, vtkF3DMemoryMesh* vtkMesh)
{
mesh_t mesh = cb(time);
vtkMesh->SetPoints(mesh.points);
vtkMesh->SetNormals(mesh.normals);
vtkMesh->SetTCoords(mesh.texture_coordinates);
vtkMesh->SetFaces(mesh.face_sides, mesh.face_indices);
};

vtkSource->SetAnimatedMesh(times[0], times[1], std::move(wrappedCallback));

auto importer = vtkSmartPointer<vtkF3DGenericImporter>::New();
importer->SetInternalReader(vtkSource);

log::debug("Loading animated 3D scene from memory");

this->Internals->Load({ importer });
return *this;
}

scene& scene_impl::add(const mesh_t& mesh)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should be able to call the other method here ?

{
// sanity checks
Expand All @@ -306,16 +335,18 @@ scene& scene_impl::add(const mesh_t& mesh)
}

vtkNew<vtkF3DMemoryMesh> vtkSource;
auto importer = vtkSmartPointer<vtkF3DGenericImporter>::New();

log::debug("Loading 3D scene from memory");

vtkSource->SetPoints(mesh.points);
vtkSource->SetNormals(mesh.normals);
vtkSource->SetTCoords(mesh.texture_coordinates);
vtkSource->SetFaces(mesh.face_sides, mesh.face_indices);

vtkSmartPointer<vtkF3DGenericImporter> importer = vtkSmartPointer<vtkF3DGenericImporter>::New();
importer->SetInternalReader(vtkSource);
Internals->Load({ importer });

log::debug("Loading 3D scene from memory");
this->Internals->Load({ importer });
return *this;
}

Expand All @@ -327,7 +358,6 @@ scene& scene_impl::clear()

// Clear the window of all actors
this->Internals->Window.Initialize();

return *this;
}

Expand Down
1 change: 1 addition & 0 deletions library/testing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ list(APPEND libf3dSDKTests_list
TestSDKInteractorDropFullScene.cxx
TestSDKInteractorCommand.cxx
TestSDKSceneFromMemory.cxx
TestSDKSceneTemporalFromMemory.cxx
TestSDKScene.cxx
TestSDKLog.cxx
TestSDKMultiColoring.cxx
Expand Down
61 changes: 61 additions & 0 deletions library/testing/TestSDKSceneTemporalFromMemory.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "PseudoUnitTest.h"

#include <engine.h>
#include <image.h>
#include <log.h>
#include <scene.h>
#include <window.h>

#include <cmath>
#include <sstream>
#include <string>
#include <vector>

int TestSDKSceneTemporalFromMemory([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
PseudoUnitTest test;

f3d::log::setVerboseLevel(f3d::log::VerboseLevel::DEBUG);
f3d::engine eng = f3d::engine::create(true);
f3d::scene& sce = eng.getScene();
f3d::window& win = eng.getWindow().setSize(300, 300);

// Define mesh data for two frames
const std::vector<float> basePoints{ 0.f, 0.f, 0.f, 0.f, 1.f, 0.f, 1.f, 0.f, 0.f, 1.f, 1.f, 0.f };
const std::vector<float> translatedPoints{ 0.25f, 0.f, 0.f, 0.25f, 1.f, 0.f, 1.25f, 0.f, 0.f,
1.25f, 1.f, 0.f };
const std::vector<float> normals{ 0.f, 0.f, -1.f, 0.f, 0.f, -1.f, 0.f, 0.f, -1.f, 0.f, 0.f,
-1.f };
const std::vector<float> tcoords{ 0.f, 0.f, 0.f, 1.f, 1.f, 0.f, 1.f, 1.f };
const std::vector<unsigned int> faceSides{ 3, 3 };
const std::vector<unsigned int> faceIndices{ 0, 1, 2, 1, 3, 2 };

auto meshCallback = [&](double time) -> f3d::mesh_t {
if (time < 0.5)
{
return f3d::mesh_t{ basePoints, normals, tcoords, faceSides, faceIndices };
}
else
{
return f3d::mesh_t{ translatedPoints, normals, tcoords, faceSides, faceIndices };
}
};

test("add animated mesh with callback", [&]() { sce.add({ 0.0, 1.0 }, meshCallback); });

test("animation time range", [&]() {
auto range = sce.animationTimeRange();
return std::abs(range.first - 0.0) < 1e-6 && std::abs(range.second - 1.0) < 1e-6;
});

test("quantitative temporal difference", [&]() {
sce.loadAnimationTime(0.0);
f3d::image frame0 = win.renderToImage();
sce.loadAnimationTime(1.0);
f3d::image frame1 = win.renderToImage();
double error = frame0.compare(frame1);
return error > 0.01;
});

return test.result();
}
65 changes: 55 additions & 10 deletions vtkext/private/module/vtkF3DMemoryMesh.cxx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
#include "vtkF3DMemoryMesh.h"

#include "vtkDataArrayRange.h"
#include "vtkCellArray.h"
#include "vtkFloatArray.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkSMPTools.h"

#include <numeric>
#include "vtkStreamingDemandDrivenPipeline.h"

vtkStandardNewMacro(vtkF3DMemoryMesh);

Expand Down Expand Up @@ -51,20 +53,28 @@ void vtkF3DMemoryMesh::SetPoints(const std::vector<float>& positions)
vtkNew<vtkPoints> points;
points->SetDataTypeToFloat();
points->SetData(ConvertToFloatArray<3>(positions));

this->Mesh->SetPoints(points);
this->StaticMesh->SetPoints(points);
this->Modified();
}

//------------------------------------------------------------------------------
void vtkF3DMemoryMesh::SetNormals(const std::vector<float>& normals)
{
this->Mesh->GetPointData()->SetNormals(ConvertToFloatArray<3>(normals));
if (!normals.empty())
{
this->StaticMesh->GetPointData()->SetNormals(ConvertToFloatArray<3>(normals));
this->Modified();
}
}

//------------------------------------------------------------------------------
void vtkF3DMemoryMesh::SetTCoords(const std::vector<float>& tcoords)
{
this->Mesh->GetPointData()->SetTCoords(ConvertToFloatArray<2>(tcoords));
if (!tcoords.empty())
{
this->StaticMesh->GetPointData()->SetTCoords(ConvertToFloatArray<2>(tcoords));
this->Modified();
}
}

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -96,17 +106,52 @@ void vtkF3DMemoryMesh::SetFaces(

vtkNew<vtkCellArray> polys;
polys->SetData(offsets, connectivity);
this->StaticMesh->SetPolys(polys);
this->Modified();
}

this->Mesh->SetPolys(polys);
//------------------------------------------------------------------------------
void vtkF3DMemoryMesh::SetAnimatedMesh(double startTime, double endTime, MeshCallback callback)
{
this->AnimatedCallback = std::move(callback);
this->TimeRange[0] = startTime;
this->TimeRange[1] = endTime;
this->IsAnimated = true;
this->Modified();
}

//------------------------------------------------------------------------------
int vtkF3DMemoryMesh::RequestInformation(vtkInformation* vtkNotUsed(request),
vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
vtkInformation* outInfo = outputVector->GetInformationObject(0);

if (this->IsAnimated)
{
outInfo->Set(vtkStreamingDemandDrivenPipeline::TIME_RANGE(), this->TimeRange, 2);
}

return 1;
}

//------------------------------------------------------------------------------
int vtkF3DMemoryMesh::RequestData(vtkInformation* vtkNotUsed(request),
vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
vtkPolyData* output = vtkPolyData::GetData(outputVector->GetInformationObject(0));
vtkInformation* outInfo = outputVector->GetInformationObject(0);
vtkPolyData* output = vtkPolyData::GetData(outInfo);

if (this->IsAnimated && this->AnimatedCallback)
{
double requestedTime = this->TimeRange[0];
if (outInfo->Has(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP()))
{
requestedTime = outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_TIME_STEP());
}
this->AnimatedCallback(requestedTime, this);
}

output->ShallowCopy(this->Mesh);
output->ShallowCopy(this->StaticMesh);

return 1;
}
38 changes: 29 additions & 9 deletions vtkext/private/module/vtkF3DMemoryMesh.h
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
/**
* @class vtkF3DMemoryMesh
* @brief create vtkPolyData from vectors.
* @brief create vtkPolyData from vectors or a callback.
*
* Simple source which convert and copy vectors provided by the user
* to internal structure of vtkPolyData.
* Does not support point data (normals, tcoords...) nor cell data yet.
* Simple source which converts vectors provided by the user
* to internal structure of vtkPolyData, or uses a callback for animated mesh generation.
*/
#ifndef vtkF3DMemoryMesh_h
#define vtkF3DMemoryMesh_h

#include "vtkPolyDataAlgorithm.h"

#include <functional>

class vtkF3DMemoryMesh : public vtkPolyDataAlgorithm
{
public:
static vtkF3DMemoryMesh* New();
vtkTypeMacro(vtkF3DMemoryMesh, vtkPolyDataAlgorithm);

/**
* Set contiguous list of positions.
* Callback type for animated meshes.
* Called with the current time value, should populate the mesh using the Set* methods.
*/
using MeshCallback = std::function<void(double time, vtkF3DMemoryMesh* mesh)>;

/**
* Set contiguous list of positions for a static mesh.
* Length of the list must be a multiple of 3.
* The list is copied internally.
*/
void SetPoints(const std::vector<float>& positions);

/**
* Set contiguous list of normals.
* Set contiguous list of normals for a static mesh.
* Length of the list must be a multiple of 3 (or left empty).
* Must match the number of points specified in SetPoints.
* The list is copied internally.
Expand All @@ -34,7 +41,7 @@ class vtkF3DMemoryMesh : public vtkPolyDataAlgorithm
void SetNormals(const std::vector<float>& normals);

/**
* Set contiguous list of texture coordinates.
* Set contiguous list of texture coordinates for a static mesh.
* Length of the list must be a multiple of 2 (or left empty).
* Must match the number of points specified in SetPoints.
* The list is copied internally.
Expand All @@ -43,7 +50,7 @@ class vtkF3DMemoryMesh : public vtkPolyDataAlgorithm
void SetTCoords(const std::vector<float>& tcoords);

/**
* Set faces by vertex indices.
* Set faces by vertex indices for a static mesh.
* faceSizes contains the size of each face (3 is triangle, 4 is quad, etc...)
* cellIndices is a contiguous array of all face indices
* The length of faceIndices should be the sum of all values in faceSizes
Expand All @@ -53,17 +60,30 @@ class vtkF3DMemoryMesh : public vtkPolyDataAlgorithm
void SetFaces(
const std::vector<unsigned int>& faceSizes, const std::vector<unsigned int>& faceIndices);

/**
* Set a callback for animated mesh generation.
* The callback is invoked at each requested time and should populate the mesh
* using SetPoints, SetNormals, SetTCoords, and SetFaces methods.
* This switches the mesh to animated mode with the given time range.
*/
void SetAnimatedMesh(double startTime, double endTime, MeshCallback callback);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use std::vector here too


protected:
vtkF3DMemoryMesh();
~vtkF3DMemoryMesh() override;

int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override;
int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override;

private:
vtkF3DMemoryMesh(const vtkF3DMemoryMesh&) = delete;
void operator=(const vtkF3DMemoryMesh&) = delete;

vtkNew<vtkPolyData> Mesh;
vtkNew<vtkPolyData> StaticMesh;

MeshCallback AnimatedCallback;
double TimeRange[2] = { 0.0, 0.0 };
bool IsAnimated = false;
};

#endif