Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
125 changes: 125 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,102 @@ 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]));
}

v.baseColorTexture.data = c->base_color_texture;
v.baseColorTexture.width = c->base_color_texture_width;
v.baseColorTexture.height = c->base_color_texture_height;
v.baseColorTexture.components =
c->base_color_texture_components ? c->base_color_texture_components : 3;
v.baseColorTexture.emissive = 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 +321,31 @@ 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;
}

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 stride arrays
Comment thread
joa-quim marked this conversation as resolved.
Outdated
# 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
65 changes: 65 additions & 0 deletions c/testing/test_scene_mesh_view.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#include <engine_c_api.h>
#include <scene_c_api.h>
#include <types_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);
f3d_engine_delete(engine);
return 0;
}
86 changes: 86 additions & 0 deletions c/types_c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,92 @@ extern "C"
*/
F3D_EXPORT int f3d_mesh_is_valid(const f3d_mesh_t* mesh, char** error_message);

/**
* @brief Scalar data types supported by a zero-copy mesh view.
*
* Values mirror f3d::mesh_view::data_type and MUST stay in the same order.
*/
typedef enum f3d_mesh_data_type_t
{
F3D_MESH_DATA_U8 = 0,
F3D_MESH_DATA_I8,
F3D_MESH_DATA_U16,
F3D_MESH_DATA_I16,
F3D_MESH_DATA_U32,
F3D_MESH_DATA_I32,
F3D_MESH_DATA_U64,
F3D_MESH_DATA_I64,
F3D_MESH_DATA_F32,
F3D_MESH_DATA_F64
} f3d_mesh_data_type_t;

/**
* @brief Zero-copy view of an existing data array (mirrors f3d::mesh_view::data_array_t).
*
* `data` is NOT copied: it must stay valid and allocated for as long as the mesh view
* stays in the scene. `name` may be NULL. `components` and `stride` default to 1 when
* left at 0. `stride` is counted in elements (not bytes). Set `time_dependent` to 0 for
* arrays whose contents never change to help performance.
*/
typedef struct f3d_data_array_t
{
const char* name;
f3d_mesh_data_type_t type;
const void* data;
size_t components;
size_t stride;
int time_dependent;
} f3d_data_array_t;

/**
* @brief Zero-copy view of a cell array (mirrors f3d::mesh_view::cell_array_t).
*
* `offset_count` must equal the number of cells + 1 (1 means no cell). The last offset
* value must equal `index_count`. `offsets`/`indices` must use an integer type
* (I32/U32/I64/U64) and share the same type.
*/
typedef struct f3d_cell_array_t
{
size_t offset_count;
f3d_data_array_t offsets;
size_t index_count;
f3d_data_array_t indices;
} f3d_cell_array_t;

/**
* @brief Zero-copy view of a mesh in memory (mirrors f3d::mesh_view::memory_view_t).
*
* Every pointer referenced here must stay valid while the mesh view is in the scene.
* `points` must have 3 components and type F32 or F64. `normals` (3 comps) and
* `texture_coordinates` (2 comps) are optional (leave `.data` NULL to skip). The
* `*_scalars` arrays may be NULL when their count is 0.
*/
typedef struct f3d_memory_view_t
{
size_t point_count;
f3d_data_array_t points;
f3d_data_array_t normals;
f3d_data_array_t texture_coordinates;

f3d_cell_array_t vertices;
f3d_cell_array_t lines;
f3d_cell_array_t polygons;

const f3d_data_array_t* point_scalars;
size_t point_scalars_count;
const f3d_data_array_t* cell_scalars;
size_t cell_scalars_count;

/* Optional in-memory base-color texture (sampled via texture_coordinates). Leave
base_color_texture NULL for none. Pixels are row-major uint8, components 3 (RGB) or
4 (RGBA). Set base_color_texture_emissive != 0 to also use it as the emissive map. */
const void* base_color_texture;
size_t base_color_texture_width;
size_t base_color_texture_height;
size_t base_color_texture_components;
int base_color_texture_emissive;
} f3d_memory_view_t;

/**
* @brief Enumeration of light types.
*/
Expand Down
20 changes: 20 additions & 0 deletions library/public/mesh_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,23 @@ class F3D_EXPORT mesh_view
data_array_t indices;
};

/**
* Structure representing an in-memory base-color (albedo) texture, sampled through the
* mesh `textureCoordinates`. Avoids writing a temporary image file to disk. `data` is a
* row-major uint8 buffer of `width * height * components` bytes, with `components` equal
* to 3 (RGB) or 4 (RGBA). Leave `data` null (the default) for no texture. The pointer
* must remain valid until the mesh is removed from the scene. When `emissive` is true the
* same image is additionally installed as the emissive texture (for unlit/flat display).
*/
struct texture_t

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

as pointed in Discord, I still think we should use f3d::image here and add support for non-owning buffers in f3d::image later.
Since it's public API, it's important to make sure the design is future proof.

{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

texture_t C/Python bindings missing and not tested

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in the PR update

size_t width = 0;
size_t height = 0;
size_t components = 3;
const void* data = nullptr;
bool emissive = false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why emissive is needed here?
Are you using it? I think what you want is unlit option https://f3d.app/docs/libf3d/OPTIONS#model.unlit

};

/**
* Structure representing a view of the mesh in memory at a given time.
* The pointers provided in this structure must remain valid once the mesh is added to the scene.
Expand All @@ -155,6 +172,9 @@ class F3D_EXPORT mesh_view
// scalars
std::vector<data_array_t> pointScalars;
std::vector<data_array_t> cellScalars;

// optional in-memory base-color texture (sampled via textureCoordinates)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

what makes this optional ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The PR update has now a longer comments about this

texture_t baseColorTexture;
};

/**
Expand Down
Loading
Loading