Allow mesh data to be reuploaded without having to recreate the mesh.
Splits mjrfMeshData into config+data parts. PiperOrigin-RevId: 952042545 Change-Id: I19a572a0f591fa0d33bbc2c32b974efc075310f4
This commit is contained in:
committed by
Copybara-Service
parent
d3166cb630
commit
ba9a65031b
@@ -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<mjrfMesh>`.
|
||||
|
||||
.. mujoco-include:: mjrfMeshConfig
|
||||
|
||||
|
||||
.. _mjrfMeshData:
|
||||
|
||||
mjrfMeshData
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Binary data used for creating a :ref:`mesh<mjrfMesh>`.
|
||||
Binary data for a :ref:`mesh<mjrfMesh>`.
|
||||
|
||||
.. mujoco-include:: mjrfMeshData
|
||||
|
||||
|
||||
@@ -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>`__
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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_ {
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -1078,7 +1078,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
|
||||
py::class_<raw::MjrVertexAttribute> 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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -103,8 +103,11 @@ static std::span<const int> 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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include <math/vec3.h>
|
||||
#include <math/vec4.h>
|
||||
#include <mujoco/mjrfilament.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#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 <typename T, typename... Args>
|
||||
static std::unique_ptr<Mesh> Create(filament::Engine* engine,
|
||||
Args&&... args) {
|
||||
auto builder = new T(std::forward<Args>(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<BuiltinBuilder*>(user_data);
|
||||
};
|
||||
mesh_data->user_data = builder;
|
||||
return std::make_unique<Mesh>(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<const void*>(positions_.data());
|
||||
attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS;
|
||||
attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4;
|
||||
attributes[1].bytes = reinterpret_cast<const void*>(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<Mesh>(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<float3> positions_;
|
||||
std::vector<float4> orientations_;
|
||||
std::vector<uint16_t> 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);
|
||||
|
||||
+152
-126
@@ -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<SharedState>()) {
|
||||
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<SharedState>()) {
|
||||
// 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<SharedState>(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<SharedState>(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<uint16_t>(sequence, num_bytes);
|
||||
} else {
|
||||
FillSequence<uint32_t>(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<const float*>(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<const float*>(data.attributes[0].bytes);
|
||||
reinterpret_cast<const float*>(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<const float*>(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(); }
|
||||
|
||||
@@ -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<filament::Box> bounds_;
|
||||
|
||||
struct SharedState {
|
||||
std::vector<std::function<void()>> callbacks;
|
||||
std::mutex mutex;
|
||||
bool called = false;
|
||||
};
|
||||
std::shared_ptr<SharedState> shared_state_;
|
||||
std::array<filament::VertexAttribute, mjMAX_VERTEX_ATTRIBUTES> attributes_;
|
||||
int num_attributes_ = 0;
|
||||
};
|
||||
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ inline UniquePtr<mjrfTexture> CreateTexture(mjrfContext* ctx,
|
||||
}
|
||||
|
||||
inline UniquePtr<mjrfMesh> CreateMesh(mjrfContext* ctx,
|
||||
const mjrfMeshData& data) {
|
||||
mjrfMesh* mesh = mjrf_createMesh(ctx, &data);
|
||||
const mjrfMeshConfig& config) {
|
||||
mjrfMesh* mesh = mjrf_createMesh(ctx, &config);
|
||||
return UniquePtr<mjrfMesh>(mesh, mjrf_destroyMesh);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<char*>(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<char*>(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<mjrfMesh> 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<mjrfMesh> 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<mjrfMesh> 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<mjrfMesh> 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,
|
||||
|
||||
@@ -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<uint16_t>::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<uint16_t>::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 {
|
||||
|
||||
@@ -6240,7 +6240,6 @@ public unsafe struct mjrRendererInfo_ {
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public unsafe struct mjrVertexAttribute_ {
|
||||
public void* bytes;
|
||||
public int usage;
|
||||
public int type;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user