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
143 changes: 143 additions & 0 deletions c/scene_c_api.cxx
Comment thread
mwestphal marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#include "scene_c_api.h"
#include "mesh_view.h"
#include "scene.h"
#include "types.h"

#include <array>
#include <filesystem>
#include <log.h>
#include <memory>
#include <string>
#include <vector>

namespace
Expand Down Expand Up @@ -116,6 +120,108 @@ f3d::mesh_t to_cpp_mesh(const f3d_mesh_t* c_mesh)

return cpp_mesh;
}

//----------------------------------------------------------------------------
f3d::mesh_view::data_array_t to_cpp_data_array(const f3d_data_array_t& c)
{
f3d::mesh_view::data_array_t a;
if (c.name)
{
a.name = c.name;
}
a.type = static_cast<f3d::mesh_view::data_type>(c.type);
a.data = c.data;
a.components = c.components ? c.components : 1;
a.stride = c.stride ? c.stride : 1;
a.timeDependent = c.time_dependent != 0;
return a;
}

//----------------------------------------------------------------------------
f3d::mesh_view::cell_array_t to_cpp_cell_array(const f3d_cell_array_t& c)
{
f3d::mesh_view::cell_array_t a;
// 0 from a zero-initialized C struct means "no cell": map it to the C++ default of 1.
a.offsetCount = c.offset_count ? c.offset_count : 1;
a.offsets = to_cpp_data_array(c.offsets);
a.indexCount = c.index_count;
a.indices = to_cpp_data_array(c.indices);
return a;
}

//----------------------------------------------------------------------------
f3d::mesh_view::memory_view_t to_cpp_memory_view(const f3d_memory_view_t* c)
{
f3d::mesh_view::memory_view_t v;
v.pointCount = c->point_count;
v.points = to_cpp_data_array(c->points);
v.normals = to_cpp_data_array(c->normals);
v.textureCoordinates = to_cpp_data_array(c->texture_coordinates);
v.vertices = to_cpp_cell_array(c->vertices);
v.lines = to_cpp_cell_array(c->lines);
v.polygons = to_cpp_cell_array(c->polygons);

v.pointScalars.reserve(c->point_scalars_count);
for (size_t i = 0; i < c->point_scalars_count; ++i)
{
v.pointScalars.push_back(to_cpp_data_array(c->point_scalars[i]));
}

v.cellScalars.reserve(c->cell_scalars_count);
for (size_t i = 0; i < c->cell_scalars_count; ++i)
{
v.cellScalars.push_back(to_cpp_data_array(c->cell_scalars[i]));
}

if (c->base_color_texture != nullptr && c->base_color_texture_width > 0 &&
c->base_color_texture_height > 0)
{
const unsigned int comps = static_cast<unsigned int>(
c->base_color_texture_components ? c->base_color_texture_components : 3);
f3d::image img(static_cast<unsigned int>(c->base_color_texture_width),
static_cast<unsigned int>(c->base_color_texture_height), comps, f3d::image::ChannelType::BYTE);
// setContent deep-copies, so the caller's buffer need not outlive this call
img.setContent(const_cast<void*>(c->base_color_texture));
v.baseColorTexture = std::move(img);
v.baseColorTextureEmissive = c->base_color_texture_emissive != 0;
}
return v;
}

//----------------------------------------------------------------------------
// Concrete mesh_view holding a zero-copy snapshot of the caller's arrays. The data
// pointers inside View reference caller-owned memory; only the small metadata (names,
// layout, vectors of scalar descriptors) is owned here.
class c_mesh_view : public f3d::mesh_view
{
public:
c_mesh_view(std::string name, std::array<double, 2> range, f3d::mesh_view::memory_view_t view)
: Name(std::move(name))
, Range(range)
, View(std::move(view))
{
}

std::string getName() const override
{
return this->Name;
}

std::array<double, 2> getTimeRange() const override
{
return this->Range;
}

f3d::mesh_view::memory_view_t getMemoryView(double) const override
{
return this->View;
}

private:
std::string Name;
std::array<double, 2> Range;
f3d::mesh_view::memory_view_t View;
};
}

//----------------------------------------------------------------------------
Expand Down Expand Up @@ -221,6 +327,43 @@ int f3d_scene_add_mesh(f3d_scene_t* scene, const f3d_mesh_t* mesh)
return 1;
}

//----------------------------------------------------------------------------
int f3d_scene_add_mesh_view(
f3d_scene_t* scene, const f3d_memory_view_t* view, const char* name, double t_min, double t_max)
{
if (!scene || !view)
{
return 0;
}

f3d::scene* cpp_scene = reinterpret_cast<f3d::scene*>(scene);

try
{
cpp_scene->add(std::make_shared<c_mesh_view>(name ? std::string(name) : std::string(),
std::array<double, 2>{ t_min, t_max }, to_cpp_memory_view(view)));
}
catch (const f3d::scene::load_failure_exception& e)
{
f3d::log::error("Failed to add mesh view to scene: {}", e.what());
return 0;
}
catch (const std::exception& e)
{
// to_cpp_memory_view builds an f3d::image for the base-color texture, which can throw
// outside load_failure_exception; never let a C++ exception cross the C ABI boundary.
f3d::log::error("Failed to add mesh view to scene: {}", e.what());
return 0;
}
catch (...)
{
f3d::log::error("Failed to add mesh view to scene: unknown error");
return 0;
}

return 1;
}

//----------------------------------------------------------------------------
int f3d_scene_add_buffer(f3d_scene_t* scene, void* buffer, size_t size)
{
Expand Down
23 changes: 23 additions & 0 deletions c/scene_c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ extern "C"
*/
F3D_EXPORT int f3d_scene_add_mesh(f3d_scene_t* scene, const f3d_mesh_t* mesh);

/**
* @brief Add a zero-copy in-memory mesh view into the scene.
*
* Unlike f3d_scene_add_mesh (which copies all arrays into F3D), this keeps references
* to the caller-owned arrays described by @p view: no data is copied. All pointers in
* @p view must therefore remain valid and allocated until the scene is cleared. The
* array metadata (names, layout) is copied internally, so @p view itself may be a
* transient/stack value.
*
* Animation: provide a non-degenerate [t_min, t_max] range and mutate the referenced
* buffers in place between renders (the pointers stay the same). Use t_min == t_max for
* a static mesh.
*
* @param scene Scene handle.
* @param view Mesh memory view describing caller-owned arrays.
* @param name Optional mesh name (may be NULL).
* @param t_min Animation time range minimum.
* @param t_max Animation time range maximum.
* @return 1 on success, 0 on failure.
*/
F3D_EXPORT int f3d_scene_add_mesh_view(
f3d_scene_t* scene, const f3d_memory_view_t* view, const char* name, double t_min, double t_max);

/**
* @brief Add and load a memory buffer into the scene.
*
Expand Down
8 changes: 8 additions & 0 deletions c/testing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ if(VTK_VERSION VERSION_GREATER_EQUAL 9.4.20250501)
)
endif()

# Zero-copy mesh_view C API (f3d_scene_add_mesh_view). Needs strided arrays
# https://gitlab.kitware.com/vtk/vtk/-/merge_requests/12411
if(VTK_VERSION VERSION_GREATER_EQUAL 9.6.20251110)
list(APPEND f3d_c_api_tests_list
test_scene_mesh_view.c
)
endif()

set(CMAKE_TESTDRIVER_EXTRA_INCLUDES "")
set(CMAKE_TESTDRIVER_ARGVC_FUNCTION "")
set(CMAKE_TESTDRIVER_BEFORE_TESTMAIN "")
Expand Down
114 changes: 114 additions & 0 deletions c/testing/test_scene_mesh_view.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include <engine_c_api.h>
#include <scene_c_api.h>
#include <types_c_api.h>
#include <window_c_api.h>

#include <stdio.h>
#include <string.h>

int test_scene_mesh_view()
{
f3d_engine_t* engine = f3d_engine_create(1); // offscreen
if (!engine)
{
puts("[ERROR] Failed to create engine");
return 1;
}

f3d_scene_t* scene = f3d_engine_get_scene(engine);
if (!scene)
{
puts("[ERROR] Failed to get scene");
f3d_engine_delete(engine);
return 1;
}

// A single triangle with a per-point scalar. All arrays are caller-owned and stay alive
// for the whole function: f3d_scene_add_mesh_view keeps references, it does not copy them.
float points[] = { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 1.0f, 0.0f };
unsigned int offsets[] = { 0, 3 }; // one polygon of 3 corners (offset_count = cells + 1)
unsigned int indices[] = { 0, 1, 2 };
float height[] = { 0.0f, 0.5f, 1.0f };

f3d_data_array_t point_scalar;
memset(&point_scalar, 0, sizeof(point_scalar));
point_scalar.name = "height";
point_scalar.type = F3D_MESH_DATA_F32;
point_scalar.data = height;
point_scalar.components = 1;

f3d_memory_view_t view;
memset(&view, 0, sizeof(view)); // zero => no normals/texcoords/texture, identity transform
view.point_count = 3;
view.points.type = F3D_MESH_DATA_F32;
view.points.data = points;
view.points.components = 3;
view.polygons.offset_count = 2;
view.polygons.offsets.type = F3D_MESH_DATA_U32;
view.polygons.offsets.data = offsets;
view.polygons.index_count = 3;
view.polygons.indices.type = F3D_MESH_DATA_U32;
view.polygons.indices.data = indices;
view.point_scalars = &point_scalar;
view.point_scalars_count = 1;

// Static mesh (t_min == t_max).
if (f3d_scene_add_mesh_view(scene, &view, "triangle", 0.0, 0.0) != 1)
{
puts("[ERROR] f3d_scene_add_mesh_view failed");
f3d_engine_delete(engine);
return 1;
}

f3d_scene_clear(scene);

// A textured quad: exercises the optional in-memory base-color texture path
// (raw bytes -> f3d::image via setContent -> vtkTexture on the imported actor).
float qpoints[] = { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f };
float qtexcoords[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f };
unsigned int qoffsets[] = { 0, 4 }; // one quad polygon
unsigned int qindices[] = { 0, 1, 2, 3 };
// 2x2 RGB checker (row-major): red, green / blue, yellow.
unsigned char texels[] = { 255, 0, 0, 0, 255, 0, 0, 0, 255, 255, 255, 0 };

f3d_memory_view_t qview;
memset(&qview, 0, sizeof(qview));
qview.point_count = 4;
qview.points.type = F3D_MESH_DATA_F32;
qview.points.data = qpoints;
qview.points.components = 3;
qview.texture_coordinates.type = F3D_MESH_DATA_F32;
qview.texture_coordinates.data = qtexcoords;
qview.texture_coordinates.components = 2;
qview.polygons.offset_count = 2;
qview.polygons.offsets.type = F3D_MESH_DATA_U32;
qview.polygons.offsets.data = qoffsets;
qview.polygons.index_count = 4;
qview.polygons.indices.type = F3D_MESH_DATA_U32;
qview.polygons.indices.data = qindices;
qview.base_color_texture = texels;
qview.base_color_texture_width = 2;
qview.base_color_texture_height = 2;
qview.base_color_texture_components = 3;
qview.base_color_texture_emissive = 1; // also use as emissive (flat full-strength)

if (f3d_scene_add_mesh_view(scene, &qview, "textured_quad", 0.0, 0.0) != 1)
{
puts("[ERROR] f3d_scene_add_mesh_view (textured) failed");
f3d_engine_delete(engine);
return 1;
}

// Render once so the base-color texture is actually built and applied.
f3d_window_t* window = f3d_engine_get_window(engine);
if (!window || f3d_window_render(window) != 1)
{
puts("[ERROR] render of textured mesh_view failed");
f3d_engine_delete(engine);
return 1;
}

f3d_scene_clear(scene);
f3d_engine_delete(engine);
return 0;
}
Loading
Loading