From ba9a65031b1d2d2064e64e0fb131757440f6bcf8 Mon Sep 17 00:00:00 2001 From: Haroon Qureshi Date: Wed, 22 Jul 2026 04:55:59 -0700 Subject: [PATCH] Allow mesh data to be reuploaded without having to recreate the mesh. Splits mjrfMeshData into config+data parts. PiperOrigin-RevId: 952042545 Change-Id: I19a572a0f591fa0d33bbc2c32b974efc075310f4 --- doc/APIreference/APItypes.rst | 12 +- doc/APIreference/functions.rst | 30 +- doc/changelog.rst | 3 + doc/includes/references.h | 22 +- include/mujoco/mjrender.h | 1 - include/mujoco/mjrfilament.h | 35 ++- python/mujoco/introspect/structs.py | 7 - python/mujoco/structs.cc | 2 +- .../filament/compat/scene_decorator.cc | 34 ++- .../filament/compat/scene_objects.cc | 70 +++-- src/experimental/platform/ux/imgui_bridge.cc | 37 ++- src/render/filament/core/builtins.cc | 71 +++-- src/render/filament/core/mesh.cc | 278 ++++++++++-------- src/render/filament/core/mesh.h | 23 +- src/render/filament/mjrfilament.cc | 12 +- src/render/filament/mjrfilament_cpp.h | 4 +- src/render/filament/support/mesh_util.cc | 77 +++-- src/render/filament/support/model_objects.cc | 71 +++-- unity/Runtime/Bindings/MjBindings.cs | 1 - 19 files changed, 469 insertions(+), 321 deletions(-) diff --git a/doc/APIreference/APItypes.rst b/doc/APIreference/APItypes.rst index b595c4df..e9388185 100644 --- a/doc/APIreference/APItypes.rst +++ b/doc/APIreference/APItypes.rst @@ -1452,12 +1452,22 @@ culling. Alternatively, the bounds can be computed at runtime (though there is a are provided (or calculated), then frustum culling will not be performed. +.. _mjrfMeshConfig: + +mjrfMeshConfig +~~~~~~~~~~~~~~~~~ + +Parameters for creating a :ref:`mesh`. + +.. mujoco-include:: mjrfMeshConfig + + .. _mjrfMeshData: mjrfMeshData ~~~~~~~~~~~~ -Binary data used for creating a :ref:`mesh`. +Binary data for a :ref:`mesh`. .. mujoco-include:: mjrfMeshData diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst index 885bfa59..19167361 100644 --- a/doc/APIreference/functions.rst +++ b/doc/APIreference/functions.rst @@ -3249,14 +3249,14 @@ Returns the height of the texture. Returns the sampler type (mjrSamplerType) used by the texture. [returns: mjrSamplerType] -.. _mjrf_defaultMeshData: +.. _mjrf_defaultMeshConfig: -`mjrf_defaultMeshData <#mjrf_defaultMeshData>`__ -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`mjrf_defaultMeshConfig <#mjrf_defaultMeshConfig>`__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. mujoco-include:: mjrf_defaultMeshData +.. mujoco-include:: mjrf_defaultMeshConfig -Initializes the mjrfMeshData to default values. +Initializes the mjrfMeshConfig to default values. .. _mjrf_createMesh: @@ -3265,7 +3265,7 @@ Initializes the mjrfMeshData to default values. .. mujoco-include:: mjrf_createMesh -Creates a mesh with the given data. +Creates an empty mesh with the given config. .. _mjrf_destroyMesh: @@ -3276,6 +3276,24 @@ Creates a mesh with the given data. Destroys the mesh. +.. _mjrf_defaultMeshData: + +`mjrf_defaultMeshData <#mjrf_defaultMeshData>`__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. mujoco-include:: mjrf_defaultMeshData + +Initializes the mjrfMeshData to default values. + +.. _mjrf_setMeshData: + +`mjrf_setMeshData <#mjrf_setMeshData>`__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. mujoco-include:: mjrf_setMeshData + +Uploads the given mesh data to the mesh. + .. _mjrf_defaultSceneParams: `mjrf_defaultSceneParams <#mjrf_defaultSceneParams>`__ diff --git a/doc/changelog.rst b/doc/changelog.rst index 073e5c90..95febfa0 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -66,6 +66,9 @@ Engine - :ref:`mju_round` now breaks ties away from zero rather than towards :math:`+\infty`. This only affects negative half-integers, e.g. ``mju_round(-2.5)`` now returns -3 rather than -2. - Removed unneeded `mjvScene` argument from :ref:`mjv_moveCamera`. + - Split up :ref:`mjrfMeshData` into `mjrfMeshData` and `mjrfMeshConfig` to allow reuploading of mesh data without + having to recreate the mesh object. Introduces :ref:`mjrfDefaultMeshConfig` and :ref:`mjrfSetMeshData` functions. + - Removed `bytes` field from :ref:`mjrVertexAttribute`. .. admonition:: Breaking ABI changes :class: caution diff --git a/doc/includes/references.h b/doc/includes/references.h index ddb52846..4410aed4 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -1425,7 +1425,6 @@ typedef struct mjrRendererInfo_ { // active renderer identity const char* backend; // graphics backend: opengl, vulkan; empty if uninitialized } mjrRendererInfo; typedef struct mjrVertexAttribute_ { // vertex attribute format specification - const void* bytes; // vertex data int usage; // position, normal, etc [mjrVertexAttributeUsage] int type; // float3, ubyte4, etc. [mjrVertexAttributeType] } mjrVertexAttribute; @@ -1580,15 +1579,20 @@ typedef struct mjrfTextureData_ { mjrfCallback release; // callback when data has finished uploading void* user_data; // user data for release callback } mjrfTextureData; -typedef struct mjrfMeshData_ { - mjtSize num_vertices; // number of vertices; all vertex attributes share this size - int num_attributes; // number of attributes defined +typedef struct mjrfMeshConfig_ { + mjtSize max_vertices; // maximum number of vertices + mjtSize max_indices; // maximum number of indices + int num_attributes; // number of defined attributes mjrVertexAttribute attributes[mjMAX_VERTEX_ATTRIBUTES]; // per-vertex attribute information mjtBool interleaved; // true if vertex attributes are interleaved - mjtSize num_indices; // number of indices - const void* indices; // indices data array int index_type; // index data format (e.g. UINT16 or UINT32) [mjrIndexType] int primitive_type; // index interpretation (e.g. TRIANGLES, etc.) [mjrMeshPrimitiveType] +} mjrfMeshConfig; +typedef struct mjrfMeshData_ { + mjtSize num_vertices; // number of vertices + const void* vertices[mjMAX_VERTEX_ATTRIBUTES]; // per-vertex attribute data arrays + mjtSize num_indices; // number of indices + const void* indices; // indices data array mjtBool compute_bounds; // if true, compute bounds from vertex positions float bounds_min[3]; // min/max bounds; assume unset if bounds_min == bounds_max float bounds_max[3]; @@ -3422,9 +3426,11 @@ void mjrf_setTextureData(mjrfTexture* texture, const mjrfTextureData* data); int mjrf_getTextureWidth(const mjrfTexture* texture); int mjrf_getTextureHeight(const mjrfTexture* texture); int mjrf_getTextureSamplerType(const mjrfTexture* texture); -void mjrf_defaultMeshData(mjrfMeshData* data); -mjrfMesh* mjrf_createMesh(mjrfContext* ctx, const mjrfMeshData* data); +void mjrf_defaultMeshConfig(mjrfMeshConfig* config); +mjrfMesh* mjrf_createMesh(mjrfContext* ctx, const mjrfMeshConfig* config); void mjrf_destroyMesh(mjrfMesh* mesh); +void mjrf_defaultMeshData(mjrfMeshData* data); +void mjrf_setMeshData(mjrfMesh* mesh, const mjrfMeshData* data); void mjrf_defaultSceneParams(mjrfSceneParams* params); mjrfScene* mjrf_createScene(mjrfContext* ctx, const mjrfSceneParams* params); void mjrf_destroyScene(mjrfScene* scene); diff --git a/include/mujoco/mjrender.h b/include/mujoco/mjrender.h index c683688f..2fd12e22 100644 --- a/include/mujoco/mjrender.h +++ b/include/mujoco/mjrender.h @@ -124,7 +124,6 @@ typedef struct mjrRendererInfo_ { // active renderer identity typedef struct mjrVertexAttribute_ { // vertex attribute format specification - const void* bytes; // vertex data int usage; // position, normal, etc [mjrVertexAttributeUsage] int type; // float3, ubyte4, etc. [mjrVertexAttributeType] } mjrVertexAttribute; diff --git a/include/mujoco/mjrfilament.h b/include/mujoco/mjrfilament.h index 04448652..ed4be639 100644 --- a/include/mujoco/mjrfilament.h +++ b/include/mujoco/mjrfilament.h @@ -182,16 +182,32 @@ int mjrf_getTextureSamplerType(const mjrfTexture* texture); // Maximum number of vertex attributes in a mesh. enum { mjMAX_VERTEX_ATTRIBUTES = 16 }; -// Binary data used for creating a mesh (mjrfMesh). -typedef struct mjrfMeshData_ { - mjtSize num_vertices; // number of vertices; all vertex attributes share this size - int num_attributes; // number of attributes defined +// Parameters describing a mesh (mjrfMesh). +typedef struct mjrfMeshConfig_ { + mjtSize max_vertices; // maximum number of vertices + mjtSize max_indices; // maximum number of indices + int num_attributes; // number of defined attributes mjrVertexAttribute attributes[mjMAX_VERTEX_ATTRIBUTES]; // per-vertex attribute information mjtBool interleaved; // true if vertex attributes are interleaved - mjtSize num_indices; // number of indices - const void* indices; // indices data array int index_type; // index data format (e.g. UINT16 or UINT32) [mjrIndexType] int primitive_type; // index interpretation (e.g. TRIANGLES, etc.) [mjrMeshPrimitiveType] +} mjrfMeshConfig; + +// Initializes the mjrfMeshConfig to default values. +void mjrf_defaultMeshConfig(mjrfMeshConfig* config); + +// Creates an empty mesh with the given config. +mjrfMesh* mjrf_createMesh(mjrfContext* ctx, const mjrfMeshConfig* config); + +// Destroys the mesh. +void mjrf_destroyMesh(mjrfMesh* mesh); + +// Binary data used for creating a mesh (mjrfMesh). +typedef struct mjrfMeshData_ { + mjtSize num_vertices; // number of vertices + const void* vertices[mjMAX_VERTEX_ATTRIBUTES]; // per-vertex attribute data arrays + mjtSize num_indices; // number of indices + const void* indices; // indices data array mjtBool compute_bounds; // if true, compute bounds from vertex positions float bounds_min[3]; // min/max bounds; assume unset if bounds_min == bounds_max float bounds_max[3]; @@ -202,11 +218,8 @@ typedef struct mjrfMeshData_ { // Initializes the mjrfMeshData to default values. void mjrf_defaultMeshData(mjrfMeshData* data); -// Creates a mesh with the given data. -mjrfMesh* mjrf_createMesh(mjrfContext* ctx, const mjrfMeshData* data); - -// Destroys the mesh. -void mjrf_destroyMesh(mjrfMesh* mesh); +// Uploads the given mesh data to the mesh. +void mjrf_setMeshData(mjrfMesh* mesh, const mjrfMeshData* data); // Parameters for creating a scene (mjrfScene). typedef struct mjrfSceneParams_ { diff --git a/python/mujoco/introspect/structs.py b/python/mujoco/introspect/structs.py index 8d302304..60f5f222 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -11332,13 +11332,6 @@ STRUCTS: Mapping[str, StructDecl] = dict([ name='mjrVertexAttribute', declname='struct mjrVertexAttribute_', fields=( - StructFieldDecl( - name='bytes', - type=PointerType( - inner_type=ValueType(name='void', is_const=True), - ), - doc='vertex data', - ), StructFieldDecl( name='usage', type=ValueType(name='int'), diff --git a/python/mujoco/structs.cc b/python/mujoco/structs.cc index bf0f249c..f8d05c59 100644 --- a/python/mujoco/structs.cc +++ b/python/mujoco/structs.cc @@ -1078,7 +1078,7 @@ This is useful for example when the MJB is not available as a file on disk.)")); py::class_ mjrVertexAttribute(m, "MjrVertexAttribute"); mjrVertexAttribute.def(py::init([](int usage, int type) { - return raw::MjrVertexAttribute{nullptr, usage, type}; + return raw::MjrVertexAttribute{usage, type}; }), py::arg("usage") = 0, py::arg("type") = 0); mjrVertexAttribute.def("__copy__", [](const raw::MjrVertexAttribute& other) { diff --git a/src/experimental/filament/compat/scene_decorator.cc b/src/experimental/filament/compat/scene_decorator.cc index 04968afd..af3d199d 100644 --- a/src/experimental/filament/compat/scene_decorator.cc +++ b/src/experimental/filament/compat/scene_decorator.cc @@ -243,27 +243,35 @@ void SceneDecorator::Update(mjData* data, const mjvOption* vis_option, const float* uvs = has_uvs ? mjv_scene_.flextexcoord + (6 * addr) : nullptr; + mjrfMeshConfig config; + mjrf_defaultMeshConfig(&config); + config.num_attributes = has_uvs ? 3 : 2; + config.max_vertices = 3 * mjv_scene_.flexfaceused[geom.objid]; + config.max_indices = 3 * mjv_scene_.flexfaceused[geom.objid]; + config.attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; + config.attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; + config.attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_NORMAL; + config.attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; + config.attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV; + config.attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; + config.index_type = mjINDEX_TYPE_U32; + config.primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES; + mjrfMeshData data; mjrf_defaultMeshData(&data); - data.num_attributes = has_uvs ? 3 : 2; - data.attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; - data.attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; - data.attributes[0].bytes = positions; - data.attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_NORMAL; - data.attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; - data.attributes[1].bytes = normals; - data.attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV; - data.attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; - data.attributes[2].bytes = uvs; + data.vertices[0] = positions; + data.vertices[1] = normals; + data.vertices[2] = uvs; data.num_vertices = 3 * mjv_scene_.flexfaceused[geom.objid]; data.num_indices = 3 * mjv_scene_.flexfaceused[geom.objid]; data.indices = nullptr; - data.index_type = mjINDEX_TYPE_U32; - data.primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES; data.compute_bounds = true; data.release = nullptr; data.user_data = nullptr; - meshes_.push_back(CreateMesh(ctx, data)); + + auto mesh = CreateMesh(ctx, config); + mjrf_setMeshData(mesh.get(), &data); + meshes_.push_back(std::move(mesh)); auto renderable = CreateRenderable(ctx, params); mjrf_setRenderableMesh(renderable.get(), meshes_.back().get(), 0, 0); diff --git a/src/experimental/filament/compat/scene_objects.cc b/src/experimental/filament/compat/scene_objects.cc index c4f2b7f6..656ed6e9 100644 --- a/src/experimental/filament/compat/scene_objects.cc +++ b/src/experimental/filament/compat/scene_objects.cc @@ -103,8 +103,11 @@ static std::span GetIndices(const mjModel* model, } } -static bool UpdateSkinFlexMeshData(mjrfMeshData* data, const mjModel* model, - const mjvScene* scene, const mjvGeom& geom) { +SceneObjects::SceneObjects(mjrfContext* ctx) : ctx_(ctx) {} + +bool SceneObjects::CreateSkinFlexMesh(const mjvScene* scene, + const mjModel* model, + const mjvGeom& geom) { auto positions = GetPositions(model, scene, geom); if (positions.empty()) { return false; @@ -119,41 +122,44 @@ static bool UpdateSkinFlexMeshData(mjrfMeshData* data, const mjModel* model, num_indices = 3 * scene->flexfaceused[geom.objid]; } - data->num_attributes = uvs.data() ? 3 : 2; - data->attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; - data->attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; - data->attributes[0].bytes = positions.data(); - data->attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_NORMAL; - data->attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; - data->attributes[1].bytes = normals.data(); - data->attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV; - data->attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; - data->attributes[2].bytes = uvs.data(); - data->num_vertices = positions.size() / 3; - data->num_indices = num_indices; - data->indices = indices.data(); - data->index_type = mjINDEX_TYPE_U32; - data->primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES; - data->compute_bounds = true; - data->release = nullptr; - data->user_data = nullptr; - return true; -} + mjrfMeshConfig config; + mjrf_defaultMeshConfig(&config); + config.num_attributes = uvs.data() ? 3 : 2; + config.attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; + config.attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; + config.attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_NORMAL; + config.attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; + config.attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV; + config.attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; + config.max_vertices = positions.size() / 3; + config.max_indices = num_indices; + config.index_type = mjINDEX_TYPE_U32; + config.primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES; -SceneObjects::SceneObjects(mjrfContext* ctx) : ctx_(ctx) {} - -bool SceneObjects::CreateSkinFlexMesh(const mjvScene* scene, - const mjModel* model, - const mjvGeom& geom) { mjrfMeshData data; mjrf_defaultMeshData(&data); - if (!UpdateSkinFlexMeshData(&data, model, scene, geom)) { - return false; - } + data.vertices[0] = positions.data(); + data.vertices[1] = normals.data(); + data.vertices[2] = uvs.data(); + data.num_vertices = positions.size() / 3; + data.num_indices = num_indices; + data.indices = indices.data(); + data.compute_bounds = true; + data.release = nullptr; + data.user_data = nullptr; + if (geom.type == mjGEOM_FLEX) { - flexes_.insert_or_assign(geom.objid, CreateMesh(ctx_, data)); + auto iter = flexes_.find(geom.objid); + if (iter == flexes_.end()) { + iter = flexes_.insert({geom.objid, CreateMesh(ctx_, config)}).first; + } + mjrf_setMeshData(iter->second.get(), &data); } else if (geom.type == mjGEOM_SKIN) { - skins_.insert_or_assign(geom.objid, CreateMesh(ctx_, data)); + auto iter = skins_.find(geom.objid); + if (iter == skins_.end()) { + iter = skins_.insert({geom.objid, CreateMesh(ctx_, config)}).first; + } + mjrf_setMeshData(iter->second.get(), &data); } else { mju_error("Unsupported dynamic mesh type: %d", geom.type); } diff --git a/src/experimental/platform/ux/imgui_bridge.cc b/src/experimental/platform/ux/imgui_bridge.cc index 28e3f331..1dac5060 100644 --- a/src/experimental/platform/ux/imgui_bridge.cc +++ b/src/experimental/platform/ux/imgui_bridge.cc @@ -219,27 +219,32 @@ void ImguiBridge::Update() { for (int n = 0; n < commands->CmdListsCount; ++n) { const ImDrawList* cmds = commands->CmdLists[n]; + mjrfMeshConfig config; + mjrf_defaultMeshConfig(&config); + config.num_attributes = 3; + config.attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; + config.attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; + config.attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_UV; + config.attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; + config.attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_COLOR; + config.attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_UBYTE4; + config.max_vertices = cmds->VtxBuffer.Size; + config.max_indices = cmds->IdxBuffer.Size; + config.interleaved = true; + config.index_type = mjINDEX_TYPE_U16; + config.primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES; + meshes_.push_back(CreateMesh(ctx_, config)); + mjrfMesh* mesh = meshes_.back().get(); + mjrfMeshData data; mjrf_defaultMeshData(&data); - data.num_attributes = 3; - data.attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; - data.attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; - data.attributes[0].bytes = cmds->VtxBuffer.Data; - data.attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_UV; - data.attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; - data.attributes[1].bytes = cmds->VtxBuffer.Data + sizeof(float) * 2; - data.attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_COLOR; - data.attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_UBYTE4; - data.attributes[2].bytes = cmds->VtxBuffer.Data + sizeof(float) * 4; - data.interleaved = true; data.num_vertices = cmds->VtxBuffer.Size; + data.vertices[0] = cmds->VtxBuffer.Data; + data.vertices[1] = cmds->VtxBuffer.Data + sizeof(float) * 2; + data.vertices[2] = cmds->VtxBuffer.Data + sizeof(float) * 4; data.num_indices = cmds->IdxBuffer.Size; data.indices = cmds->IdxBuffer.Data; - data.index_type = mjINDEX_TYPE_U16; - data.primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES; - meshes_.push_back(CreateMesh(ctx_, data)); - - const mjrfMesh* mesh = meshes_.back().get(); + mjrf_setMeshData(mesh, &data); int index_offset = 0; for (const ImDrawCmd& command : cmds->CmdBuffer) { diff --git a/src/render/filament/core/builtins.cc b/src/render/filament/core/builtins.cc index dd71d2bc..9e40f99a 100644 --- a/src/render/filament/core/builtins.cc +++ b/src/render/filament/core/builtins.cc @@ -27,6 +27,7 @@ #include #include #include +#include #include "render/filament/core/mesh.h" #include "render/filament/support/filament_util.h" @@ -59,59 +60,69 @@ static std::size_t NumIndicesPerSide(int num_quads_per_axis) { return kNumIndicesPerQuad * num_quads_per_axis * num_quads_per_axis; } -class BuiltinBuilder : public mjrfMeshData { +class BuiltinBuilder { public: - BuiltinBuilder() { mjrf_defaultMeshData(this); } + BuiltinBuilder() {} virtual ~BuiltinBuilder() = default; template static std::unique_ptr Create(filament::Engine* engine, Args&&... args) { auto builder = new T(std::forward(args)...); - mjrfMeshData* mesh_data = builder->PrepareMeshData(); - mesh_data->release = +[](void* user_data) { + + mjrfMeshConfig config; + mjrf_defaultMeshConfig(&config); + config.max_vertices = builder->positions_.size(); + config.max_indices = builder->indices_.size(); + config.index_type = mjINDEX_TYPE_U16; + config.primitive_type = builder->primitive_type_; + config.num_attributes = 2; + config.attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; + config.attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; + config.attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS; + config.attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4; + + mjrfMeshData data; + mjrf_defaultMeshData(&data); + data.num_vertices = builder->positions_.size(); + data.vertices[0] = builder->positions_.data(); + data.vertices[1] = builder->orientations_.data(); + data.num_indices = builder->indices_.size(); + data.indices = builder->indices_.data(); + data.bounds_min[0] = builder->bounds_min_.x; + data.bounds_min[1] = builder->bounds_min_.y; + data.bounds_min[2] = builder->bounds_min_.z; + data.bounds_max[0] = builder->bounds_max_.x; + data.bounds_max[1] = builder->bounds_max_.y; + data.bounds_max[2] = builder->bounds_max_.z; + data.release = +[](void* user_data) { delete static_cast(user_data); }; - mesh_data->user_data = builder; - return std::make_unique(engine, *mesh_data); - } + data.user_data = builder; - mjrfMeshData* PrepareMeshData() { - // Update the `mjrfMeshData` fields. - num_attributes = 2; - attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; - attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; - attributes[0].bytes = reinterpret_cast(positions_.data()); - attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS; - attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4; - attributes[1].bytes = reinterpret_cast(orientations_.data()); - num_vertices = positions_.size(); - - indices = indices_.data(); - num_indices = indices_.size(); - index_type = mjINDEX_TYPE_U16; - return this; + auto mesh = std::make_unique(engine, config); + mesh->Upload(data); + return mesh; } protected: void SetBounds(const float3& min, const float3& max) { - bounds_min[0] = min.x; - bounds_min[1] = min.y; - bounds_min[2] = min.z; - bounds_max[0] = max.x; - bounds_max[1] = max.y; - bounds_max[2] = max.z; + bounds_min_ = min; + bounds_max_ = max; } + int primitive_type_ = mjMESH_PRIMITIVE_TYPE_TRIANGLES; std::vector positions_; std::vector orientations_; std::vector indices_; + float3 bounds_min_ = {0, 0, 0}; + float3 bounds_max_ = {0, 0, 0}; }; class LineBuilder : public BuiltinBuilder { public: LineBuilder() { - primitive_type = mjMESH_PRIMITIVE_TYPE_LINES; + primitive_type_ = mjMESH_PRIMITIVE_TYPE_LINES; positions_.reserve(2); positions_.emplace_back(0, 0, 0); @@ -183,7 +194,7 @@ class TriangleBuilder : public BuiltinBuilder { class LineBoxBuilder : public BuiltinBuilder { public: explicit LineBoxBuilder() { - primitive_type = mjMESH_PRIMITIVE_TYPE_LINES; + primitive_type_ = mjMESH_PRIMITIVE_TYPE_LINES; positions_.reserve(8); positions_.emplace_back(-1.0f, -1.0f, -1.0f); diff --git a/src/render/filament/core/mesh.cc b/src/render/filament/core/mesh.cc index 11302ac6..1f045e1b 100644 --- a/src/render/filament/core/mesh.cc +++ b/src/render/filament/core/mesh.cc @@ -102,21 +102,43 @@ int FillSequence(std::byte* buffer, std::size_t num_bytes) { return num; } -Mesh::Mesh(filament::Engine* engine, const mjrfMeshData& data) - : engine_(engine), shared_state_(std::make_shared()) { - type_ = data.primitive_type == mjMESH_PRIMITIVE_TYPE_TRIANGLES - ? filament::RenderableManager::PrimitiveType::TRIANGLES - : filament::RenderableManager::PrimitiveType::LINES; - - // If the user has provided a release callback, then we need to ensure we - // call is when filament is done with the mesh data. - if (data.release) { - shared_state_->callbacks.push_back([=]() { data.release(data.user_data); }); +Mesh::Mesh(filament::Engine* engine, const mjrfMeshConfig& config) + : engine_(engine), + config_(config), + shared_state_(std::make_shared()) { + // Perform some validation on the config. + const mjrVertexAttribute* positions = nullptr; + const mjrVertexAttribute* normals = nullptr; + const mjrVertexAttribute* tangents = nullptr; + for (int i = 0; i < config_.num_attributes; ++i) { + if (config_.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_POSITION) { + positions = &config_.attributes[i]; + } else if (config_.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) { + normals = &config_.attributes[i]; + } else if (config_.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_TANGENTS) { + tangents = &config_.attributes[i]; + } + } + if (config_.max_vertices == 0) { + mju_error("Mesh has no vertices."); + } + if (!positions) { + mju_error("Mesh has no positions."); + } + if (config_.attributes[0].usage != mjVERTEX_ATTRIBUTE_USAGE_POSITION) { + mju_error("Positions must be the first attribute."); + } + if (normals && tangents) { + mju_error("Mesh has both normals and tangents."); + } + if (normals && config_.interleaved) { + // We need to build orientations from normals and so we require each + // attribute to be in a separate buffer. + mju_error("Cannot support normals with interleaved vertex attributes."); } - BuildVertexBuffer(data); - BuildIndexBuffer(data); - UpdateBounds(data); + InitVertexBuffer(); + InitIndexBuffer(); } Mesh::~Mesh() { @@ -129,9 +151,84 @@ Mesh::~Mesh() { } } -void Mesh::BuildVertexBuffer(const mjrfMeshData& data) { - if (data.num_vertices == 0) { - mju_error("mjrfMeshData has no vertices."); +void Mesh::Upload(const mjrfMeshData& data) { + // If the user has provided a release callback, then we need to ensure we + // call is when filament is done with the mesh data. + if (data.release) { + shared_state_->callbacks.push_back([=]() { data.release(data.user_data); }); + } + + UpdateVertexBuffer(data); + UpdateIndexBuffer(data); + UpdateBounds(data); +} + +void Mesh::InitVertexBuffer() { + filament::VertexBuffer::Builder vb_builder; + vb_builder.vertexCount(config_.max_vertices); + + if (config_.interleaved) { + // For an interleaved vertex buffer, we will create a single buffer which + // contains the data in the order specified by the attributes array, + // starting from the first attribute's payload. + vb_builder.bufferCount(1); + int total_vertex_size = 0; + for (int i = 0; i < config_.num_attributes; ++i) { + total_vertex_size += VertexAttributeTypeSize(config_.attributes[i]); + } + + // We assume the buffer is tightly packed with no padding between + // attributes. As such, the stride is equal to the total vertex size and + // each offset is the sum of the sizes of the preceding attributes. + int offset = 0; + for (int i = 0; i < config_.num_attributes; ++i) { + const mjrVertexAttribute& attrib = config_.attributes[i]; + const filament::VertexAttribute usage = GetUsage(attrib); + filament::VertexBuffer::AttributeType type = GetType(attrib); + vb_builder.attribute(usage, 0, type, offset, total_vertex_size); + if (usage == filament::VertexAttribute::COLOR) { + vb_builder.normalized(usage); + } + offset += VertexAttributeTypeSize(attrib); + } + vertex_buffer_ = vb_builder.build(*engine_); + } else { + // For a non-interleaved vertex buffer, we assign a separate buffer to each + // attribute. + vb_builder.bufferCount(config_.num_attributes); + for (int i = 0; i < config_.num_attributes; ++i) { + const mjrVertexAttribute& attrib = config_.attributes[i]; + const filament::VertexAttribute usage = GetUsage(attrib); + filament::VertexBuffer::AttributeType type = GetType(attrib); + if (attrib.usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) { + // We will replace normals with orientations. + type = filament::VertexBuffer::AttributeType::FLOAT4; + } + vb_builder.attribute(usage, i, type); + if (usage == filament::VertexAttribute::COLOR) { + vb_builder.normalized(usage); + } + } + vertex_buffer_ = vb_builder.build(*engine_); + } +} + +void Mesh::InitIndexBuffer() { + if (config_.max_indices == 0) { + return; + } + + filament::IndexBuffer::Builder ib_builder; + ib_builder.indexCount(config_.max_indices); + ib_builder.bufferType(config_.index_type == mjINDEX_TYPE_U16 + ? filament::IndexBuffer::IndexType::USHORT + : filament::IndexBuffer::IndexType::UINT); + index_buffer_ = ib_builder.build(*engine_); +} + +void Mesh::UpdateVertexBuffer(const mjrfMeshData& data) { + if (config_.max_vertices != data.num_vertices) { + mju_error("Vertex count does not match config."); } // The filament BufferDescriptor callback for releasing the memory. @@ -151,100 +248,27 @@ void Mesh::BuildVertexBuffer(const mjrfMeshData& data) { } }; - // Pointers to specific attributes in the mesh data, used for additional - // validation and processing. - const mjrVertexAttribute* positions = nullptr; - const mjrVertexAttribute* normals = nullptr; - const mjrVertexAttribute* tangents = nullptr; - for (int i = 0; i < data.num_attributes; ++i) { - if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_POSITION) { - positions = &data.attributes[i]; - } else if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) { - normals = &data.attributes[i]; - } else if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_TANGENTS) { - tangents = &data.attributes[i]; - } - } - if (!positions) { - mju_error("mjrfMeshData has no positions."); - } - if (data.attributes[0].usage != mjVERTEX_ATTRIBUTE_USAGE_POSITION) { - mju_error("Positions must be the first attribute."); - } - if (normals && tangents) { - mju_error("mjrfMeshData has both normals and tangents."); - } - if (normals && data.interleaved) { - // We need to build orientations from normals and so we require each - // attribute to be in a separate buffer. - mju_error("Cannot support normals with interleaved vertex attributes."); - } - - // Build the vertex buffer. - filament::VertexBuffer::Builder vb_builder; - vb_builder.vertexCount(data.num_vertices); - - if (data.interleaved) { - // For an interleaved vertex buffer, we will create a single buffer which - // contains the data in the order specified by the attributes array, - // starting from the first attribute's payload. - vb_builder.bufferCount(1); + if (config_.interleaved) { int total_vertex_size = 0; - for (int i = 0; i < data.num_attributes; ++i) { - total_vertex_size += VertexAttributeTypeSize(data.attributes[i]); + for (int i = 0; i < config_.num_attributes; ++i) { + total_vertex_size += VertexAttributeTypeSize(config_.attributes[i]); } - const void* bytes = data.attributes[0].bytes; - const size_t nbytes = data.num_vertices * total_vertex_size; - // We assume the buffer is tightly packed with no padding between - // attributes. As such, the stride is equal to the total vertex size and - // each offset is the sum of the sizes of the preceding attributes. - int offset = 0; - for (int i = 0; i < data.num_attributes; ++i) { - const mjrVertexAttribute& attrib = data.attributes[i]; - const filament::VertexAttribute usage = GetUsage(attrib); - filament::VertexBuffer::AttributeType type = GetType(attrib); - vb_builder.attribute(usage, 0, type, offset, total_vertex_size); - if (usage == filament::VertexAttribute::COLOR) { - vb_builder.normalized(usage); - } - offset += VertexAttributeTypeSize(attrib); - attributes_[i] = usage; - } - vertex_buffer_ = vb_builder.build(*engine_); auto* user_data = new std::shared_ptr(shared_state_); + const void* bytes = data.vertices[0]; + const size_t nbytes = data.num_vertices * total_vertex_size; vertex_buffer_->setBufferAt(*engine_, 0, {bytes, nbytes, callback, user_data}); } else { - // For a non-interleaved vertex buffer, we assign a separate buffer to each - // attribute. - vb_builder.bufferCount(data.num_attributes); - for (int i = 0; i < data.num_attributes; ++i) { - const mjrVertexAttribute& attrib = data.attributes[i]; - const filament::VertexAttribute usage = GetUsage(attrib); - filament::VertexBuffer::AttributeType type = GetType(attrib); - if (attrib.usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) { - // We will replace normals with orientations. - type = filament::VertexBuffer::AttributeType::FLOAT4; - } - vb_builder.attribute(usage, i, type); - if (usage == filament::VertexAttribute::COLOR) { - vb_builder.normalized(usage); - } - attributes_[i] = usage; - } - num_attributes_ = data.num_attributes; - vertex_buffer_ = vb_builder.build(*engine_); - // Assign the individual data buffers. - for (int i = 0; i < data.num_attributes; ++i) { - const mjrVertexAttribute& attrib = data.attributes[i]; - const void* bytes = attrib.bytes; + for (int i = 0; i < config_.num_attributes; ++i) { + const mjrVertexAttribute& attrib = config_.attributes[i]; + const void* bytes = data.vertices[i]; size_t nbytes = data.num_vertices * VertexAttributeTypeSize(attrib); if (attrib.usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) { // Replace normals with orientations. nbytes = data.num_vertices * sizeof(float4); - bytes = BuildOrientationsFromNormals(data.num_vertices, attrib); + bytes = BuildOrientationsFromNormals(data.num_vertices, bytes); } auto* user_data = new std::shared_ptr(shared_state_); vertex_buffer_->setBufferAt(*engine_, i, @@ -253,14 +277,17 @@ void Mesh::BuildVertexBuffer(const mjrfMeshData& data) { } } -void Mesh::BuildIndexBuffer(const mjrfMeshData& data) { +void Mesh::UpdateIndexBuffer(const mjrfMeshData& data) { if (data.num_indices == 0) { return; } + if (data.num_indices != config_.max_indices) { + mju_error("Index count does not match config."); + } const int element_size = - data.index_type == mjINDEX_TYPE_U16 ? sizeof(uint16_t) : sizeof(uint32_t); - const int num_bytes = data.num_indices * element_size; + config_.index_type == mjINDEX_TYPE_U16 ? sizeof(uint16_t) : sizeof(uint32_t); + const int num_bytes = config_.max_indices * element_size; // If indices == 0 and num_indices > 0, then the user is specifying that the // vertices are provided "in order", i.e. the indices are 0, 1, 2, 3, ... @@ -270,37 +297,19 @@ void Mesh::BuildIndexBuffer(const mjrfMeshData& data) { std::byte* sequence = new std::byte[num_bytes]; shared_state_->callbacks.push_back([=]() { delete[] sequence; }); - if (data.index_type == mjINDEX_TYPE_U16) { + if (config_.index_type == mjINDEX_TYPE_U16) { FillSequence(sequence, num_bytes); } else { FillSequence(sequence, num_bytes); } indices = sequence; } - - filament::IndexBuffer::Builder ib_builder; - ib_builder.indexCount(data.num_indices); - ib_builder.bufferType(data.index_type == mjINDEX_TYPE_U16 - ? filament::IndexBuffer::IndexType::USHORT - : filament::IndexBuffer::IndexType::UINT); - index_buffer_ = ib_builder.build(*engine_); // We don't worry about setting a release callback here because the release // callback for the vertex buffer will call release_callbacks_. filament::backend::BufferDescriptor desc(indices, num_bytes); index_buffer_->setBuffer(*engine_, std::move(desc)); } -float4* Mesh::BuildOrientationsFromNormals(int num_vertices, - const mjrVertexAttribute& normals) { - float4* orientations = new float4[num_vertices]; - shared_state_->callbacks.push_back([=]() { delete[] orientations; }); - const float* normals_ptr = reinterpret_cast(normals.bytes); - for (int i = 0; i < num_vertices; ++i) { - orientations[i] = CalculateOrientation(ReadFloat3(normals_ptr, i)); - } - return orientations; -} - void Mesh::UpdateBounds(const mjrfMeshData& data) { float3 bounds_min = ReadFloat3(data.bounds_min); float3 bounds_max = ReadFloat3(data.bounds_max); @@ -310,11 +319,11 @@ void Mesh::UpdateBounds(const mjrfMeshData& data) { bounds_min = float3(FLT_MAX, FLT_MAX, FLT_MAX); bounds_max = float3(-FLT_MAX, -FLT_MAX, -FLT_MAX); - if (data.attributes[0].usage != mjVERTEX_ATTRIBUTE_USAGE_POSITION) { + if (config_.attributes[0].usage != mjVERTEX_ATTRIBUTE_USAGE_POSITION) { mju_error("mjrfMeshData has no positions."); } const float* positions = - reinterpret_cast(data.attributes[0].bytes); + reinterpret_cast(data.vertices[0]); for (int i = 0; i < data.num_vertices; ++i) { const float3 position = ReadFloat3(positions, i); @@ -336,6 +345,18 @@ void Mesh::ReleaseResources() { } } +float4* Mesh::BuildOrientationsFromNormals(int num_vertices, + const void* normals) { + float4* orientations = new float4[num_vertices]; + shared_state_->callbacks.push_back([=]() { delete[] orientations; }); + + const float* normals_ptr = reinterpret_cast(normals); + for (int i = 0; i < num_vertices; ++i) { + orientations[i] = CalculateOrientation(ReadFloat3(normals_ptr, i)); + } + return orientations; +} + filament::IndexBuffer* Mesh::GetFilamentIndexBuffer() const { return index_buffer_; } @@ -345,13 +366,18 @@ filament::VertexBuffer* Mesh::GetFilamentVertexBuffer() const { } filament::RenderableManager::PrimitiveType Mesh::GetPrimitiveType() const { - return type_; + return config_.primitive_type == mjMESH_PRIMITIVE_TYPE_TRIANGLES + ? filament::RenderableManager::PrimitiveType::TRIANGLES + : filament::RenderableManager::PrimitiveType::LINES; } bool Mesh::HasVertexAttribute(mjrVertexAttributeUsage attrib) const { - auto fattrib = GetUsage(mjrVertexAttribute{.usage = attrib}); - auto it = std::find(attributes_.begin(), attributes_.end(), fattrib); - return it != attributes_.end(); + for (int i = 0; i < config_.num_attributes; ++i) { + if (config_.attributes[i].usage == attrib) { + return true; + } + } + return false; } bool Mesh::HasBounds() const { return bounds_.has_value(); } diff --git a/src/render/filament/core/mesh.h b/src/render/filament/core/mesh.h index f7077a66..77a0661b 100644 --- a/src/render/filament/core/mesh.h +++ b/src/render/filament/core/mesh.h @@ -37,12 +37,15 @@ namespace mujoco { class Mesh : public mjrfMesh { public: // Creates a Mesh from the given MeshData. - Mesh(filament::Engine* engine, const mjrfMeshData& data); + Mesh(filament::Engine* engine, const mjrfMeshConfig& config); ~Mesh(); Mesh(const Mesh&) = delete; Mesh& operator=(const Mesh&) = delete; + // Uploads the given MeshData to the mesh. + void Upload(const mjrfMeshData& data); + // Returns the filament IndexBuffer for the mesh. filament::IndexBuffer* GetFilamentIndexBuffer() const; @@ -67,29 +70,31 @@ class Mesh : public mjrfMesh { } private: - void BuildVertexBuffer(const mjrfMeshData& data); - void BuildIndexBuffer(const mjrfMeshData& data); + void InitVertexBuffer(); + void InitIndexBuffer(); + + void UpdateVertexBuffer(const mjrfMeshData& data); + void UpdateIndexBuffer(const mjrfMeshData& data); void UpdateBounds(const mjrfMeshData& data); - filament::math::float4* BuildOrientationsFromNormals( - int num_vertices, const mjrVertexAttribute& normals); + filament::math::float4* BuildOrientationsFromNormals(int num_vertices, + const void* normals); void ReleaseResources(); filament::Engine* engine_ = nullptr; + mjrfMeshConfig config_; + filament::IndexBuffer* index_buffer_ = nullptr; filament::VertexBuffer* vertex_buffer_ = nullptr; - filament::RenderableManager::PrimitiveType type_ = - filament::RenderableManager::PrimitiveType::TRIANGLES; std::optional bounds_; + struct SharedState { std::vector> callbacks; std::mutex mutex; bool called = false; }; std::shared_ptr shared_state_; - std::array attributes_; - int num_attributes_ = 0; }; } // namespace mujoco diff --git a/src/render/filament/mjrfilament.cc b/src/render/filament/mjrfilament.cc index d35b3e02..7635abcb 100644 --- a/src/render/filament/mjrfilament.cc +++ b/src/render/filament/mjrfilament.cc @@ -62,6 +62,10 @@ void mjrf_defaultTextureConfig(mjrfTextureConfig* config) { memset(config, 0, sizeof(mjrfTextureConfig)); } +void mjrf_defaultMeshConfig(mjrfMeshConfig* config) { + memset(config, 0, sizeof(mjrfMeshConfig)); +} + void mjrf_defaultMeshData(mjrfMeshData* data) { memset(data, 0, sizeof(mjrfMeshData)); } @@ -151,9 +155,9 @@ void mjrf_destroyTexture(mjrfTexture* texture) { delete mujoco::Texture::downcast(texture); } -mjrfMesh* mjrf_createMesh(mjrfContext* ctx, const mjrfMeshData* data) { +mjrfMesh* mjrf_createMesh(mjrfContext* ctx, const mjrfMeshConfig* config) { return new mujoco::Mesh(mujoco::FilamentContext::downcast(ctx)->GetEngine(), - *data); + *config); } void mjrf_destroyMesh(mjrfMesh* mesh) { delete mujoco::Mesh::downcast(mesh); } @@ -202,6 +206,10 @@ void mjrf_setTextureData(mjrfTexture* texture, const mjrfTextureData* data) { mujoco::Texture::downcast(texture)->Upload(*data); } +void mjrf_setMeshData(mjrfMesh* mesh, const mjrfMeshData* data) { + mujoco::Mesh::downcast(mesh)->Upload(*data); +} + int mjrf_getTextureWidth(const mjrfTexture* texture) { return mujoco::Texture::downcast(texture)->GetWidth(); } diff --git a/src/render/filament/mjrfilament_cpp.h b/src/render/filament/mjrfilament_cpp.h index d790dd9f..9eca9481 100644 --- a/src/render/filament/mjrfilament_cpp.h +++ b/src/render/filament/mjrfilament_cpp.h @@ -38,8 +38,8 @@ inline UniquePtr CreateTexture(mjrfContext* ctx, } inline UniquePtr CreateMesh(mjrfContext* ctx, - const mjrfMeshData& data) { - mjrfMesh* mesh = mjrf_createMesh(ctx, &data); + const mjrfMeshConfig& config) { + mjrfMesh* mesh = mjrf_createMesh(ctx, &config); return UniquePtr(mesh, mjrf_destroyMesh); } diff --git a/src/render/filament/support/mesh_util.cc b/src/render/filament/support/mesh_util.cc index fcc3a7b1..43bb7e26 100644 --- a/src/render/filament/support/mesh_util.cc +++ b/src/render/filament/support/mesh_util.cc @@ -362,43 +362,47 @@ static void FillSkinVertices(T* vertices, const mjModel* model, } } -static mjrfMeshData PrepareMeshData(int num_vertices, bool has_uvs) { - mjrfMeshData mesh_data; - mjrf_defaultMeshData(&mesh_data); +static void PrepareMeshData(mjrfMeshConfig* config, mjrfMeshData* data, + int num_vertices, bool has_uvs) { + mjrf_defaultMeshConfig(config); + config->max_vertices = num_vertices; + config->num_attributes = has_uvs ? 3 : 2; + config->interleaved = true; + config->primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES; + config->max_indices = num_vertices; + config->index_type = mjINDEX_TYPE_U32; + config->attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; + config->attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; + config->attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS; + config->attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4; + if (has_uvs) { + config->attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV; + config->attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; + } + mjrf_defaultMeshData(data); if (has_uvs) { const int nbytes = sizeof(VertexWithUv) * num_vertices; - mesh_data.user_data = new char[nbytes]; - std::memset(mesh_data.user_data, 0, nbytes); + data->user_data = new char[nbytes]; + std::memset(data->user_data, 0, nbytes); } else { const int nbytes = sizeof(VertexNoUv) * num_vertices; - mesh_data.user_data = new char[nbytes]; - std::memset(mesh_data.user_data, 0, nbytes); + data->user_data = new char[nbytes]; + std::memset(data->user_data, 0, nbytes); } - mesh_data.release = [](void* user_data) { + data->release = [](void* user_data) { delete[] (char*)(user_data); }; - char* buf = reinterpret_cast(mesh_data.user_data); - mesh_data.num_vertices = num_vertices; - mesh_data.num_attributes = has_uvs ? 3 : 2; - mesh_data.interleaved = true; - mesh_data.attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; - mesh_data.attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; - mesh_data.attributes[0].bytes = buf; - mesh_data.attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS; - mesh_data.attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4; - mesh_data.attributes[1].bytes = buf + sizeof(float[3]); + char* buf = reinterpret_cast(data->user_data); + data->num_vertices = num_vertices; + data->vertices[0] = buf; + data->vertices[1] = buf + sizeof(float[3]); if (has_uvs) { - mesh_data.attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV; - mesh_data.attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; - mesh_data.attributes[2].bytes = buf + sizeof(float[7]); + data->vertices[2] = buf + sizeof(float[7]); } - mesh_data.primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES; - mesh_data.num_indices = num_vertices; - mesh_data.index_type = mjINDEX_TYPE_U32; - mesh_data.indices = nullptr; - return mesh_data; + data->num_indices = num_vertices; + data->indices = nullptr; } static void SetBounds(mjrfMeshData* mesh_data, const float3& min_pt, @@ -428,7 +432,10 @@ UniquePtr CreateFlexMesh(mjrfContext* ctx, const mjModel* model, const int num_vertices = num_faces * 3; const bool has_uvs = model->flex_texcoordadr[flex_id] >= 0; - mjrfMeshData mesh_data = PrepareMeshData(num_vertices, has_uvs); + + mjrfMeshConfig mesh_config; + mjrfMeshData mesh_data; + PrepareMeshData(&mesh_config, &mesh_data, num_vertices, has_uvs); float3 min_pt = float3(FLT_MAX); float3 max_pt = float3(FLT_MIN); @@ -440,14 +447,21 @@ UniquePtr CreateFlexMesh(mjrfContext* ctx, const mjModel* model, FillFlexVertices(vertices, model, data, flex_id, &min_pt, &max_pt); } SetBounds(&mesh_data, min_pt, max_pt); - return CreateMesh(ctx, mesh_data); + + auto mesh = CreateMesh(ctx, mesh_config); + mjrf_setMeshData(mesh.get(), &mesh_data); + return mesh; } UniquePtr CreateSkinMesh(mjrfContext* ctx, const mjModel* model, const mjData* data, int skin_id) { const int num_vertices = model->skin_vertnum[skin_id]; const bool has_uvs = model->skin_texcoordadr[skin_id] >= 0; - mjrfMeshData mesh_data = PrepareMeshData(num_vertices, has_uvs); + + + mjrfMeshConfig mesh_config; + mjrfMeshData mesh_data; + PrepareMeshData(&mesh_config, &mesh_data, num_vertices, has_uvs); float3 min_pt = float3(FLT_MAX); float3 max_pt = float3(FLT_MIN); @@ -461,7 +475,10 @@ UniquePtr CreateSkinMesh(mjrfContext* ctx, const mjModel* model, mesh_data.num_indices = 3 * model->skin_facenum[skin_id]; mesh_data.indices = model->skin_face + 3 * model->skin_faceadr[skin_id]; SetBounds(&mesh_data, min_pt, max_pt); - return CreateMesh(ctx, mesh_data); + + auto mesh = CreateMesh(ctx, mesh_config); + mjrf_setMeshData(mesh.get(), &mesh_data); + return mesh; } void GatherSpatialTendonPoints(const mjModel* model, const mjData* data, diff --git a/src/render/filament/support/model_objects.cc b/src/render/filament/support/model_objects.cc index 98f64c96..3e342c2b 100644 --- a/src/render/filament/support/model_objects.cc +++ b/src/render/filament/support/model_objects.cc @@ -329,8 +329,8 @@ static int GetNumVertices(const mjModel* model, int id, MeshType mesh_type) { } } -static void UpdateMeshData(mjrfMeshData* data, const mjModel* model, int id, - MeshType mesh_type) { +static void UpdateMeshData(mjrfMeshConfig* config, mjrfMeshData* data, + const mjModel* model, int id, MeshType mesh_type) { if (!IsValidIndex(model, id, mesh_type)) { mju_error("Invalid index %d for type %d", id, mesh_type); return; @@ -357,24 +357,29 @@ static void UpdateMeshData(mjrfMeshData* data, const mjModel* model, int id, break; } - data->primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES; - data->num_vertices = num_vertices; - data->num_indices = data->num_vertices; - data->indices = nullptr; - data->index_type = data->num_vertices >= std::numeric_limits::max() - ? mjINDEX_TYPE_U32 - : mjINDEX_TYPE_U16; - data->num_attributes = has_uvs ? 3 : 2; - data->attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; - data->attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; - data->attributes[0].bytes = builder->positions.data(); - data->attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS; - data->attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4; - data->attributes[1].bytes = builder->orientations.data(); + config->max_vertices = num_vertices; + config->max_indices = num_vertices; + config->primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES; + config->index_type = num_vertices >= std::numeric_limits::max() + ? mjINDEX_TYPE_U32 + : mjINDEX_TYPE_U16; + config->num_attributes = has_uvs ? 3 : 2; + config->attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION; + config->attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; + config->attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS; + config->attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4; if (has_uvs) { - data->attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV; - data->attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; - data->attributes[2].bytes = builder->uvs.data(); + config->attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV; + config->attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; + } + + data->num_vertices = num_vertices; + data->num_indices = num_vertices; + data->indices = nullptr; + data->vertices[0] = builder->positions.data(); + data->vertices[1] = builder->orientations.data(); + if (has_uvs) { + data->vertices[2] = builder->uvs.data(); } data->bounds_min[0] = builder->bounds_min.x; data->bounds_min[1] = builder->bounds_min.y; @@ -415,16 +420,27 @@ void ModelObjects::UploadMesh(const mjModel* model, int id) { meshes_.erase(id); convex_hulls_.erase(id); + mjrfMeshConfig config; + mjrf_defaultMeshConfig(&config); mjrfMeshData data; mjrf_defaultMeshData(&data); - UpdateMeshData(&data, model, id, MeshType::kNormal); - meshes_.insert_or_assign(id, CreateMesh(ctx_, data)); + UpdateMeshData(&config, &data, model, id, MeshType::kNormal); + + auto mesh = CreateMesh(ctx_, config); + mjrf_setMeshData(mesh.get(), &data); + meshes_.insert_or_assign(id, std::move(mesh)); if (model->mesh_graphadr[id] >= 0) { + mjrfMeshConfig convex_hull_config; + mjrf_defaultMeshConfig(&convex_hull_config); mjrfMeshData convex_hull_data; mjrf_defaultMeshData(&convex_hull_data); - UpdateMeshData(&convex_hull_data, model, id, MeshType::kConvexHull); - convex_hulls_.insert_or_assign(id, CreateMesh(ctx_, convex_hull_data)); + UpdateMeshData(&convex_hull_config, &convex_hull_data, model, id, + MeshType::kConvexHull); + + auto convex_hull = CreateMesh(ctx_, convex_hull_config); + mjrf_setMeshData(convex_hull.get(), &convex_hull_data); + convex_hulls_.insert_or_assign(id, std::move(convex_hull)); } } @@ -484,10 +500,15 @@ void ModelObjects::UploadHeightField(const mjModel* model, int id) { height_fields_.erase(id); + mjrfMeshConfig config; + mjrf_defaultMeshConfig(&config); mjrfMeshData data; mjrf_defaultMeshData(&data); - UpdateMeshData(&data, model, id, MeshType::kHeightField); - height_fields_.insert_or_assign(id, CreateMesh(ctx_, data)); + UpdateMeshData(&config, &data, model, id, MeshType::kHeightField); + + auto mesh = CreateMesh(ctx_, config); + mjrf_setMeshData(mesh.get(), &data); + height_fields_.insert_or_assign(id, std::move(mesh)); } const mjrfMesh* ModelObjects::GetMesh(int data_id) const { diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 3f5ddeef..e494f5cf 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -6240,7 +6240,6 @@ public unsafe struct mjrRendererInfo_ { [StructLayout(LayoutKind.Sequential)] public unsafe struct mjrVertexAttribute_ { - public void* bytes; public int usage; public int type; }