From 39c891e358d44f1b23f8d856f96fa5d20f4f35f1 Mon Sep 17 00:00:00 2001 From: Haroon Qureshi Date: Thu, 7 May 2026 00:12:09 -0700 Subject: [PATCH] Add function for creating Renderable meshes from mjtGeom types. The Renderable class now internally knows which built-in meshes to use for a given geom type. It gets these meshes from the ObjectManager which now stores the collection of built-ins based on the nstack/nslice/nquad quality arguments. PiperOrigin-RevId: 911782530 Change-Id: I6698ec3964a5c657f6a9495809cc795eec2e346f --- .../filament/compat/model_objects.cc | 24 -- .../filament/compat/model_objects.h | 17 -- .../filament/compat/scene_geom_util.cc | 277 ++++-------------- .../filament/filament/builtins.cc | 67 ++--- src/experimental/filament/filament/builtins.h | 45 ++- .../filament/filament/object_manager.cc | 18 ++ .../filament/filament/object_manager.h | 7 + .../filament/filament/renderable.cc | 225 +++++++++++++- .../filament/filament/renderable.h | 17 +- .../filament/render_context_filament.cc | 12 +- .../filament/render_context_filament.h | 6 + 11 files changed, 371 insertions(+), 344 deletions(-) diff --git a/src/experimental/filament/compat/model_objects.cc b/src/experimental/filament/compat/model_objects.cc index 23ced1e0..2fd77a75 100644 --- a/src/experimental/filament/compat/model_objects.cc +++ b/src/experimental/filament/compat/model_objects.cc @@ -29,7 +29,6 @@ #include #include #include -#include "experimental/filament/filament/builtins.h" #include "experimental/filament/filament/math_util.h" #include "experimental/filament/filament/model_util.h" #include "experimental/filament/render_context_filament.h" @@ -493,19 +492,6 @@ void UpdateSkinFlexMeshData(mjrMeshData* data, const mjModel* model, ModelObjects::ModelObjects(const mjModel* model, mjrfContext* ctx) : model_(model), ctx_(ctx) { - const int nstack = model->vis.quality.numstacks; - const int nslice = model->vis.quality.numslices; - const int nquad = model->vis.quality.numquads; - shapes_.insert({kLine, CreateLine(ctx_)}); - shapes_.insert({kBox, CreateBox(ctx_, nquad)}); - shapes_.insert({kLineBox, CreateLineBox(ctx_)}); - shapes_.insert({kCone, CreateCone(ctx_, nstack, nslice)}); - shapes_.insert({kDisk, CreateDisk(ctx_, nslice)}); - shapes_.insert({kDome, CreateDome(ctx_, nstack / 2, nslice)}); - shapes_.insert({kTube, CreateTube(ctx_, nstack, nslice)}); - shapes_.insert({kPlane, CreatePlane(ctx_, nquad)}); - shapes_.insert({kSphere, CreateSphere(ctx_, nstack, nslice)}); - shapes_.insert({kTriangle, CreateTriangle(ctx_)}); for (int i = 0; i < model_->ntex; ++i) { UploadTexture(model_, i); @@ -525,11 +511,6 @@ ModelObjects::ModelObjects(const mjModel* model, mjrfContext* ctx) model_, "filament.phong.emissive_multiplier", emissive_multiplier_); } -ModelObjects::~ModelObjects() { - meshes_.clear(); - textures_.clear(); -} - void ModelObjects::UploadMesh(const mjModel* model, int id) { if (model != model_) { mju_error("Model mismatch."); @@ -641,11 +622,6 @@ const mjrMesh* ModelObjects::GetHeightFieldBuffer(int hfield_id) const { return it != height_fields_.end() ? it->second.get() : nullptr; } -const mjrMesh* ModelObjects::GetShapeBuffer(ShapeType shape) const { - auto it = shapes_.find(shape); - return it != shapes_.end() ? it->second.get() : nullptr; -} - const mjrMesh* ModelObjects::GetFlexSkinGeomMesh(int geom_id) const { auto it = dynamic_meshes_.find(geom_id); return it != dynamic_meshes_.end() ? it->second.get() : nullptr; diff --git a/src/experimental/filament/compat/model_objects.h b/src/experimental/filament/compat/model_objects.h index 0a7f980f..b0bf9036 100644 --- a/src/experimental/filament/compat/model_objects.h +++ b/src/experimental/filament/compat/model_objects.h @@ -28,21 +28,6 @@ namespace mujoco { class ModelObjects { public: ModelObjects(const mjModel* model, mjrfContext* ctx); - ~ModelObjects(); - - enum ShapeType { - kLine, - kLineBox, - kPlane, - kTriangle, - kBox, - kSphere, - kCone, - kDisk, - kDome, - kTube, - kNumShapes, - }; void UploadMesh(const mjModel* model, int id); @@ -53,7 +38,6 @@ class ModelObjects { void CreateSkinFlexMesh(const mjvScene* scene, const mjvGeom& geom); // Returns the cached instance of a filament object created from the mjModel. - const mjrMesh* GetShapeBuffer(ShapeType shape) const; const mjrMesh* GetMeshBuffer(int data_id) const; const mjrMesh* GetHeightFieldBuffer(int hfield_id) const; const mjrMesh* GetFlexSkinGeomMesh(int geom_id) const; @@ -73,7 +57,6 @@ class ModelObjects { private: const mjModel* model_ = nullptr; mjrfContext* ctx_ = nullptr; - std::unordered_map> shapes_; std::unordered_map> meshes_; std::unordered_map> convex_hulls_; std::unordered_map> height_fields_; diff --git a/src/experimental/filament/compat/scene_geom_util.cc b/src/experimental/filament/compat/scene_geom_util.cc index 2a131781..35c6f380 100644 --- a/src/experimental/filament/compat/scene_geom_util.cc +++ b/src/experimental/filament/compat/scene_geom_util.cc @@ -18,32 +18,15 @@ #include #include #include -#include -#include -#include -#include -#include -#include #include #include #include "experimental/filament/compat/model_objects.h" -#include "experimental/filament/filament/math_util.h" -#include "experimental/filament/filament/renderable.h" #include "experimental/filament/render_context_filament.h" #include "experimental/filament/render_context_filament_cpp.h" namespace mujoco { -using filament::math::float2; -using filament::math::float3; -using filament::math::float4; -using filament::math::mat4f; - -// An arbitrary scale factor for arrows. -static constexpr float kArrowScale = 1.f / 6.f; -static constexpr float kArrowHeadSize = 1.75f; - // Returns the tile size for infinite plane texture alignment. // This is duplicated from engine_vis_visualize.c (re-center infinite plane) // to ensure UV scaling matches the re-centering increments. @@ -83,245 +66,103 @@ static const mjrMesh* GetHeightField(ModelObjects* model_objs, int hfield_id) { return mesh; } -static const mjrMesh* GetShape(ModelObjects* model_objs, - ModelObjects::ShapeType shape_type) { - const mjrMesh* mesh = model_objs->GetShapeBuffer(shape_type); - if (mesh == nullptr) { - mju_error("Unknown shape %d", shape_type); - } - return mesh; -} - static void PrepareGeomMeshes(mjrRenderable* renderable, const mjvGeom& geom, const mjvScene* scene, ModelObjects* model_objects) { - std::vector meshes; - Renderable::GetTransformFn get_transforms; + const mjModel* model = model_objects->GetModel(); + const int nstack = model->vis.quality.numstacks; + const int nslice = model->vis.quality.numslices; + const int nquad = model->vis.quality.numquads; - Trs trs = { - .translation = ReadFloat3(geom.pos), - .rotation = ReadMat3(geom.mat), - .size = ReadFloat3(geom.size), - }; + float position[3]; + std::memcpy(position, &geom.pos, 3 * sizeof(float)); + float rotation[9]; + std::memcpy(rotation, &geom.mat, 9 * sizeof(float)); + float size[3]; + std::memcpy(size, &geom.size, 3 * sizeof(float)); switch ((mjtGeom)geom.type) { case mjGEOM_MESH: - meshes.push_back(GetMesh(model_objects, geom.dataid)); + mjrf_setRenderableMesh(renderable, GetMesh(model_objects, geom.dataid), 0, 0); // Ignore size for meshes. - trs.size = float3{1.0f, 1.0f, 1.0f}; + size[0] = 1.f; + size[1] = 1.f; + size[2] = 1.f; break; case mjGEOM_HFIELD: - meshes.push_back(GetHeightField(model_objects, geom.dataid)); - // Ignore size for height fields. - trs.size = float3{1.0f, 1.0f, 1.0f}; + mjrf_setRenderableMesh(renderable, GetHeightField(model_objects, geom.dataid), 0, 0); + // Ignore size for meshes. + size[0] = 1.f; + size[1] = 1.f; + size[2] = 1.f; break; case mjGEOM_PLANE: { - meshes.push_back(GetShape(model_objects, ModelObjects::kPlane)); - const bool is_infinite = !(trs.size.x > 0 && trs.size.y > 0); + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); + + const bool is_infinite = !(size[0] > 0 && size[1] > 0); if (is_infinite) { // Infinite planes are scaled to match the tile size used by // re-centering in engine_vis_visualize.c. const float plane_scale = static_cast(mjMAXPLANEGRID) / 2.0f; - trs.size.x = plane_scale; - trs.size.y = plane_scale; + size[0] = plane_scale; + size[1] = plane_scale; } // Planes only define an xy size, so set the z-dimension to 1.0f. - trs.size.z = 1.0f; + size[2] = 1.0f; break; } case mjGEOM_SPHERE: - meshes.push_back(GetShape(model_objects, ModelObjects::kSphere)); + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; case mjGEOM_ELLIPSOID: - meshes.push_back(GetShape(model_objects, ModelObjects::kSphere)); + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; case mjGEOM_BOX: - meshes.push_back(GetShape(model_objects, ModelObjects::kBox)); + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; - case mjGEOM_CAPSULE: { - // Capsules are a tube with two domes at the ends. - meshes.push_back(GetShape(model_objects, ModelObjects::kTube)); - meshes.push_back(GetShape(model_objects, ModelObjects::kDome)); - meshes.push_back(GetShape(model_objects, ModelObjects::kDome)); - - get_transforms = [](int index, const Trs& trs) { - // We apply an inverse scale to the domes to counteract the capsule's - // overall scale so that the domes remain spherical in shape. - const float xz_size = 0.5f * (trs.size.x + trs.size.y); - if (index == 0) { - return trs.ToTransform(); - } else if (index == 1) { - // Move the first dome to the top of the capsule. - mat4f top = mat4f(trs.rotation, trs.translation); - top *= mat4f::translation(float3{0, 0, trs.size.z}); - top *= mat4f::scaling(float3{trs.size.x, trs.size.y, xz_size}); - return top; - } else if (index == 2) { - // Move the second dome to the bottom of the capsule and rotate it 180 - // degrees so that it's facing the right way. - mat4f bottom = mat4f(trs.rotation, trs.translation); - bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); - bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); - bottom *= mat4f::scaling(float3{trs.size.x, trs.size.y, xz_size}); - return bottom; - } else { - mju_error("Invalid index for capsule geom: %d (expected [0,2])", index); - return trs.ToTransform(); - } - }; + case mjGEOM_CAPSULE: + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; - } - case mjGEOM_CYLINDER: { - // Cylinders are a tube with two disks at the ends. - meshes.push_back(GetShape(model_objects, ModelObjects::kTube)); - meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); - meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); - - get_transforms = [](int index, const Trs& trs) { - if (index == 0) { - return trs.ToTransform(); - } else if (index == 1) { - // Move the first disk to the top of the cylinder. - mat4f top = mat4f(trs.rotation, trs.translation); - top *= mat4f::translation(float3{0, 0, trs.size.z}); - top *= mat4f::scaling(trs.size); - return top; - } else if (index == 2) { - // Move the second disk to the bottom of the cylinder. Rotate the disk - // 180 degrees so that the normals point outwards. - mat4f bottom = mat4f(trs.rotation, trs.translation); - bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); - bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); - bottom *= mat4f::scaling(trs.size); - return bottom; - } else { - mju_error("Invalid index for cylinder geom: %d (expected [0,2])", index); - return trs.ToTransform(); - } - }; + case mjGEOM_CYLINDER: + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; - } - case mjGEOM_ARROW: { - meshes.push_back(GetShape(model_objects, ModelObjects::kTube)); - meshes.push_back(GetShape(model_objects, ModelObjects::kCone)); - meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); - meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); - - get_transforms = [](int index, const Trs& trs) { - mat4f base = mat4f(trs.rotation, trs.translation); - base *= mat4f::scaling(float3{1, 1, kArrowScale}); - base *= mat4f::translation(float3{0, 0, trs.size.z}); - if (index == 0) { - return base * mat4f::scaling(trs.size); - } else if (index == 1) { - mat4f top = base; - top *= mat4f::translation(float3{0, 0, trs.size.z}); - top *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); - return top * mat4f::scaling(trs.size); - } else if (index == 2) { - mat4f top_disk = base; - top_disk *= mat4f::translation(float3{0, 0, trs.size.z}); - top_disk *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); - top_disk *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); - return top_disk * mat4f::scaling(trs.size); - } else if (index == 3) { - mat4f bottom = base; - bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); - bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); - return bottom * mat4f::scaling(trs.size); - } else { - mju_error("Invalid index for arrow geom: %d (expected [0,3])", index); - return trs.ToTransform(); - } - }; + case mjGEOM_ARROW: + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; - } - case mjGEOM_ARROW1: { - meshes.push_back(GetShape(model_objects, ModelObjects::kTube)); - meshes.push_back(GetShape(model_objects, ModelObjects::kCone)); - meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); - - get_transforms = [](int index, const Trs& trs) { - mat4f base = mat4f(trs.rotation, trs.translation); - base *= mat4f::scaling(float3{1, 1, kArrowScale}); - base *= mat4f::translation(float3{0, 0, trs.size.z}); - if (index == 0) { - return base * mat4f::scaling(trs.size); - } else if (index == 1) { - mat4f top = base; - top *= mat4f::translation(float3{0, 0, trs.size.z}); - return top * mat4f::scaling(trs.size); - } else if (index == 2) { - mat4f bottom = base; - bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); - bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); - return bottom * mat4f::scaling(trs.size); - } else { - mju_error("Invalid index for arrow1 geom: %d (expected [0,2])", index); - return trs.ToTransform(); - } - }; + case mjGEOM_ARROW1: + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; - } - case mjGEOM_ARROW2: { - meshes.push_back(GetShape(model_objects, ModelObjects::kTube)); - meshes.push_back(GetShape(model_objects, ModelObjects::kCone)); - meshes.push_back(GetShape(model_objects, ModelObjects::kCone)); - meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); - meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); - - get_transforms = [](int index, const Trs& trs) { - mat4f base = mat4f(trs.rotation, trs.translation); - base *= mat4f::scaling(float3{1, 1, kArrowScale}); - base *= mat4f::translation(float3{0, 0, trs.size.z}); - if (index == 0) { - return base * mat4f::scaling(trs.size); - } else if (index == 1) { - mat4f top = base; - top *= mat4f::translation(float3{0, 0, trs.size.z}); - top *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); - return top * mat4f::scaling(trs.size); - } else if (index == 2) { - mat4f bottom = base; - bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); - bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); - bottom *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); - return bottom * mat4f::scaling(trs.size); - } else if (index == 3) { - mat4f top_disk = base; - top_disk *= mat4f::translation(float3{0, 0, trs.size.z}); - top_disk *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); - top_disk *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); - return top_disk * mat4f::scaling(trs.size); - } else if (index == 4) { - mat4f bottom_disk = base; - bottom_disk *= mat4f::translation(float3{0, 0, -trs.size.z}); - return bottom_disk * mat4f::scaling(trs.size); - } else { - mju_error("Invalid index for arrow2 geom: %d (expected [0,4])", index); - return trs.ToTransform(); - } - }; + case mjGEOM_ARROW2: + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; - } case mjGEOM_LINE: - meshes.push_back(GetShape(model_objects, ModelObjects::kLine)); + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; case mjGEOM_LINEBOX: - meshes.push_back(GetShape(model_objects, ModelObjects::kLineBox)); + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; case mjGEOM_TRIANGLE: - meshes.push_back(GetShape(model_objects, ModelObjects::kTriangle)); + mjrf_setRenderableGeomMesh(renderable, (mjtGeom)geom.type, nstack, nslice, nquad); break; case mjGEOM_FLEX: - meshes.push_back(GetSkinFlexMesh(model_objects, geom.objid)); + mjrf_setRenderableMesh(renderable, GetSkinFlexMesh(model_objects, geom.objid), 0, 0); // Flexes are defined in global space. - trs = Trs(); + std::memset(position, 0, sizeof(position)); + std::memset(rotation, 0, sizeof(rotation)); + rotation[0] = 1.f; + rotation[4] = 1.f; + rotation[8] = 1.f; + std::memset(size, 0, sizeof(size)); break; case mjGEOM_SKIN: - meshes.push_back(GetSkinFlexMesh(model_objects, geom.objid)); + mjrf_setRenderableMesh(renderable, GetSkinFlexMesh(model_objects, geom.objid), 0, 0); // Skins are defined in global space. - trs = Trs(); + std::memset(position, 0, sizeof(position)); + std::memset(rotation, 0, sizeof(rotation)); + rotation[0] = 1.f; + rotation[4] = 1.f; + rotation[8] = 1.f; + std::memset(size, 0, sizeof(size)); break; case mjGEOM_NONE: case mjGEOM_LABEL: @@ -333,14 +174,6 @@ static void PrepareGeomMeshes(mjrRenderable* renderable, const mjvGeom& geom, break; } - Renderable::downcast(renderable)->SetMeshes(meshes, get_transforms); - - float position[3]; - std::memcpy(position, &trs.translation[0], 3 * sizeof(float)); - float rotation[9]; - std::memcpy(rotation, &trs.rotation[0], 9 * sizeof(float)); - float size[3]; - std::memcpy(size, &trs.size[0], 3 * sizeof(float)); mjrf_setRenderableTransform(renderable, position, rotation, size); } diff --git a/src/experimental/filament/filament/builtins.cc b/src/experimental/filament/filament/builtins.cc index 90b96baf..28468e50 100644 --- a/src/experimental/filament/filament/builtins.cc +++ b/src/experimental/filament/filament/builtins.cc @@ -19,16 +19,16 @@ #include #include #include +#include #include +#include #include #include #include -#include "experimental/filament/filament/filament_context.h" #include "experimental/filament/filament/math_util.h" #include "experimental/filament/filament/mesh.h" #include "experimental/filament/render_context_filament.h" -#include "experimental/filament/render_context_filament_cpp.h" namespace mujoco { @@ -65,14 +65,15 @@ class BuiltinBuilder : public mjrMeshData { virtual ~BuiltinBuilder() = default; template - static UniquePtr Create(mjrfContext* ctx, Args&&... args) { + static std::unique_ptr Create(filament::Engine* engine, + Args&&... args) { auto builder = new T(std::forward(args)...); mjrMeshData* mesh_data = builder->PrepareMeshData(); mesh_data->release_callback = +[](void* user_data) { delete static_cast(user_data); }; mesh_data->user_data = builder; - return CreateMesh(ctx, *mesh_data); + return std::make_unique(engine, *mesh_data); } mjrMeshData* PrepareMeshData() { @@ -617,44 +618,28 @@ class DomeBuilder : public BuiltinBuilder { } }; -UniquePtr CreateLine(mjrfContext* ctx) { - return BuiltinBuilder::Create(ctx); +Builtins::Builtins(filament::Engine* engine, int nstack, int nslice, int nquad) { + line_ = BuiltinBuilder::Create(engine); + plane_ = BuiltinBuilder::Create(engine, nquad); + triangle_ = BuiltinBuilder::Create(engine); + box_ = BuiltinBuilder::Create(engine, nquad); + line_box_ = BuiltinBuilder::Create(engine); + sphere_ = BuiltinBuilder::Create(engine, nstack, nslice); + tube_ = BuiltinBuilder::Create(engine, nstack, nslice); + disk_ = BuiltinBuilder::Create(engine, nslice); + dome_ = BuiltinBuilder::Create(engine, nstack, nslice); + cone_ = BuiltinBuilder::Create(engine, nstack, nslice); } -UniquePtr CreatePlane(mjrfContext* ctx, int nquad) { - return BuiltinBuilder::Create(ctx, nquad); -} - -UniquePtr CreateTriangle(mjrfContext* ctx) { - return BuiltinBuilder::Create(ctx); -} - -UniquePtr CreateBox(mjrfContext* ctx, int nquad) { - return BuiltinBuilder::Create(ctx, nquad); -} - -UniquePtr CreateLineBox(mjrfContext* ctx) { - return BuiltinBuilder::Create(ctx); -} - -UniquePtr CreateSphere(mjrfContext* ctx, int nstack, int nslice) { - return BuiltinBuilder::Create(ctx, nstack, nslice); -} - -UniquePtr CreateTube(mjrfContext* ctx, int nstack, int nslice) { - return BuiltinBuilder::Create(ctx, nstack, nslice); -} - -UniquePtr CreateDisk(mjrfContext* ctx, int nslice) { - return BuiltinBuilder::Create(ctx, nslice); -} - -UniquePtr CreateDome(mjrfContext* ctx, int nstack, int nslice) { - return BuiltinBuilder::Create(ctx, nstack, nslice); -} - -UniquePtr CreateCone(mjrfContext* ctx, int nstack, int nslice) { - return BuiltinBuilder::Create(ctx, nstack, nslice); -} +const Mesh* Builtins::Line() { return line_.get(); } +const Mesh* Builtins::LineBox() { return line_box_.get(); } +const Mesh* Builtins::Plane() { return plane_.get(); } +const Mesh* Builtins::Triangle() { return triangle_.get(); } +const Mesh* Builtins::Box() { return box_.get(); } +const Mesh* Builtins::Sphere() { return sphere_.get(); } +const Mesh* Builtins::Cone() { return cone_.get(); } +const Mesh* Builtins::Disk() { return disk_.get(); } +const Mesh* Builtins::Dome() { return dome_.get(); } +const Mesh* Builtins::Tube() { return tube_.get(); } } // namespace mujoco diff --git a/src/experimental/filament/filament/builtins.h b/src/experimental/filament/filament/builtins.h index 2cf2c591..8044b14b 100644 --- a/src/experimental/filament/filament/builtins.h +++ b/src/experimental/filament/filament/builtins.h @@ -15,22 +15,41 @@ #ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_BUILTINS_H_ #define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_BUILTINS_H_ -#include "experimental/filament/render_context_filament.h" -#include "experimental/filament/render_context_filament_cpp.h" +#include + +#include +#include "experimental/filament/filament/mesh.h" -// Generates buffers for built-in shapes. namespace mujoco { -UniquePtr CreateLine(mjrfContext* ctx); -UniquePtr CreatePlane(mjrfContext* ctx, int nquad); -UniquePtr CreateTriangle(mjrfContext* ctx); -UniquePtr CreateBox(mjrfContext* ctx, int nquad); -UniquePtr CreateLineBox(mjrfContext* ctx); -UniquePtr CreateSphere(mjrfContext* ctx, int nstack, int nslice); -UniquePtr CreateTube(mjrfContext* ctx, int nstack, int nslice); -UniquePtr CreateDisk(mjrfContext* ctx, int nslice); -UniquePtr CreateDome(mjrfContext* ctx, int nstack, int nslice); -UniquePtr CreateCone(mjrfContext* ctx, int nstack, int nslice); +// A collection of meshes that "built in" to the renderer. +class Builtins { + public: + Builtins(filament::Engine* engine, int nstack, int nslice, int nquad); + + const Mesh* Line(); + const Mesh* LineBox(); + const Mesh* Plane(); + const Mesh* Triangle(); + const Mesh* Box(); + const Mesh* Sphere(); + const Mesh* Cone(); + const Mesh* Disk(); + const Mesh* Dome(); + const Mesh* Tube(); + + private: + std::unique_ptr line_; + std::unique_ptr line_box_; + std::unique_ptr plane_; + std::unique_ptr triangle_; + std::unique_ptr box_; + std::unique_ptr sphere_; + std::unique_ptr cone_; + std::unique_ptr disk_; + std::unique_ptr dome_; + std::unique_ptr tube_; +}; } // namespace mujoco diff --git a/src/experimental/filament/filament/object_manager.cc b/src/experimental/filament/filament/object_manager.cc index 2db47f0d..fb2e31d6 100644 --- a/src/experimental/filament/filament/object_manager.cc +++ b/src/experimental/filament/filament/object_manager.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -27,6 +28,7 @@ #include #include #include +#include "experimental/filament/filament/builtins.h" #include "user/user_resource.h" namespace mujoco { @@ -131,6 +133,22 @@ filament::Material* ObjectManager::GetMaterial(MaterialType type) const { return materials_[type]; } +Builtins* ObjectManager::GetBuiltins(int nstack, int nslice, int nquad) { + // Assumes nstack, nslice, and nquad are non-negative and less than 2^20. + std::uint64_t key = (static_cast(nstack) << 20) | + (static_cast(nslice) << 40) | + static_cast(nquad); + + auto iter = builtins_.find(key); + if (iter == builtins_.end()) { + auto builtins = std::make_unique(engine_, nstack, nslice, nquad); + Builtins* ptr = builtins.get(); + builtins_[key] = std::move(builtins); + return ptr; + } + return iter->second.get(); +} + const filament::Texture* ObjectManager::GetFallbackTexture( mjtTextureRole role) const { if (role < 0 || role >= mjNTEXROLE) { diff --git a/src/experimental/filament/filament/object_manager.h b/src/experimental/filament/filament/object_manager.h index 140d08fe..05dab205 100644 --- a/src/experimental/filament/filament/object_manager.h +++ b/src/experimental/filament/filament/object_manager.h @@ -17,15 +17,18 @@ #include #include +#include #include #include #include +#include #include #include #include #include #include +#include "experimental/filament/filament/builtins.h" namespace mujoco { @@ -84,6 +87,9 @@ class ObjectManager { // Returns the fallback Texture with the given role. const filament::Texture* GetFallbackTexture(mjtTextureRole role) const; + // Returns the built-in mesh collection with the given parameters. + Builtins* GetBuiltins(int nstack, int nslice, int nquad); + // Loads the given asset from the filament resource directory. std::unique_ptr LoadAsset(std::string_view filename); @@ -97,6 +103,7 @@ class ObjectManager { filament::Engine* engine_ = nullptr; std::array materials_; std::array fallback_textures_; + std::unordered_map> builtins_; filament::Texture* fallback_white_ = nullptr; filament::Texture* fallback_black_ = nullptr; filament::Texture* fallback_normal_ = nullptr; diff --git a/src/experimental/filament/filament/renderable.cc b/src/experimental/filament/filament/renderable.cc index 636473f7..8f4af490 100644 --- a/src/experimental/filament/filament/renderable.cc +++ b/src/experimental/filament/filament/renderable.cc @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include @@ -24,8 +24,12 @@ #include #include #include +#include +#include +#include #include #include +#include "experimental/filament/filament/builtins.h" #include "experimental/filament/filament/filament_context.h" #include "experimental/filament/filament/material.h" #include "experimental/filament/filament/math_util.h" @@ -36,8 +40,15 @@ namespace mujoco { +using filament::math::float2; +using filament::math::float3; +using filament::math::float4; using filament::math::mat4f; +// An arbitrary scale factor for arrows. +static constexpr float kArrowScale = 1.f / 6.f; +static constexpr float kArrowHeadSize = 1.75f; + Renderable::Renderable(FilamentContext* ctx, const mjrRenderableParams& params) : object_mgr_(ctx->GetObjectManager()), params_(params) { mjr_defaultMaterialParams(&material_params_); @@ -159,20 +170,12 @@ const mat4f& Renderable::GetTransform() const { return transform_; } -void Renderable::SetMeshes(std::span meshes, - GetTransformFn get_transform_fn) { - if (!parts_.empty()) { - mju_error("Cannot set meshes for renderable with multiple parts."); - } - - get_transform_fn_ = get_transform_fn; - for (int i = 0; i < meshes.size(); ++i) { - Part& part = parts_.emplace_back(); - part.mesh = Mesh::downcast(meshes[i]); - part.elem_offset = 0; - part.elem_count = part.mesh->GetFilamentIndexBuffer()->getIndexCount(); - InitPartEntity(part); - } +void Renderable::AppendMesh(const Mesh* mesh) { + Part& part = parts_.emplace_back(); + part.mesh = Mesh::downcast(mesh); + part.elem_offset = 0; + part.elem_count = part.mesh->GetFilamentIndexBuffer()->getIndexCount(); + InitPartEntity(part); } void Renderable::AddToScene(filament::Scene* scene) { @@ -410,6 +413,198 @@ ObjectManager::MaterialType Renderable::GetColorMaterialType() const { } } +void Renderable::SetGeomMesh(mjtGeom type, int nstack, int nslice, int nquad) { + Builtins* builtins = object_mgr_->GetBuiltins(nstack, nslice, nquad); + + switch (type) { + case mjGEOM_PLANE: + AppendMesh(builtins->Plane()); + break; + case mjGEOM_SPHERE: + AppendMesh(builtins->Sphere()); + break; + case mjGEOM_ELLIPSOID: + AppendMesh(builtins->Sphere()); + break; + case mjGEOM_BOX: + AppendMesh(builtins->Box()); + break; + case mjGEOM_CAPSULE: + // Capsules are a tube with two domes at the ends. + AppendMesh(builtins->Tube()); + AppendMesh(builtins->Dome()); + AppendMesh(builtins->Dome()); + + get_transform_fn_ = [](int index, const Trs& trs) { + // We apply an inverse scale to the domes to counteract the capsule's + // overall scale so that the domes remain spherical in shape. + const float xz_size = 0.5f * (trs.size.x + trs.size.y); + if (index == 0) { + return trs.ToTransform(); + } else if (index == 1) { + // Move the first dome to the top of the capsule. + mat4f top = mat4f(trs.rotation, trs.translation); + top *= mat4f::translation(float3{0, 0, trs.size.z}); + top *= mat4f::scaling(float3{trs.size.x, trs.size.y, xz_size}); + return top; + } else if (index == 2) { + // Move the second dome to the bottom of the capsule and rotate it 180 + // degrees so that it's facing the right way. + mat4f bottom = mat4f(trs.rotation, trs.translation); + bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); + bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); + bottom *= mat4f::scaling(float3{trs.size.x, trs.size.y, xz_size}); + return bottom; + } else { + mju_error("Invalid index for capsule geom: %d (expected [0,2])", index); + return trs.ToTransform(); + } + }; + break; + case mjGEOM_CYLINDER: + // Cylinders are a tube with two disks at the ends. + AppendMesh(builtins->Tube()); + AppendMesh(builtins->Disk()); + AppendMesh(builtins->Disk()); + + get_transform_fn_ = [](int index, const Trs& trs) { + if (index == 0) { + return trs.ToTransform(); + } else if (index == 1) { + // Move the first disk to the top of the cylinder. + mat4f top = mat4f(trs.rotation, trs.translation); + top *= mat4f::translation(float3{0, 0, trs.size.z}); + top *= mat4f::scaling(trs.size); + return top; + } else if (index == 2) { + // Move the second disk to the bottom of the cylinder. Rotate the disk + // 180 degrees so that the normals point outwards. + mat4f bottom = mat4f(trs.rotation, trs.translation); + bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); + bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); + bottom *= mat4f::scaling(trs.size); + return bottom; + } else { + mju_error("Invalid index for cylinder geom: %d (expected [0,2])", index); + return trs.ToTransform(); + } + }; + break; + case mjGEOM_ARROW: + AppendMesh(builtins->Tube()); + AppendMesh(builtins->Cone()); + AppendMesh(builtins->Disk()); + AppendMesh(builtins->Disk()); + + get_transform_fn_ = [](int index, const Trs& trs) { + mat4f base = mat4f(trs.rotation, trs.translation); + base *= mat4f::scaling(float3{1, 1, kArrowScale}); + base *= mat4f::translation(float3{0, 0, trs.size.z}); + if (index == 0) { + return base * mat4f::scaling(trs.size); + } else if (index == 1) { + mat4f top = base; + top *= mat4f::translation(float3{0, 0, trs.size.z}); + top *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); + return top * mat4f::scaling(trs.size); + } else if (index == 2) { + mat4f top_disk = base; + top_disk *= mat4f::translation(float3{0, 0, trs.size.z}); + top_disk *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); + top_disk *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); + return top_disk * mat4f::scaling(trs.size); + } else if (index == 3) { + mat4f bottom = base; + bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); + bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); + return bottom * mat4f::scaling(trs.size); + } else { + mju_error("Invalid index for arrow geom: %d (expected [0,3])", index); + return trs.ToTransform(); + } + }; + break; + case mjGEOM_ARROW1: + AppendMesh(builtins->Tube()); + AppendMesh(builtins->Cone()); + AppendMesh(builtins->Disk()); + + get_transform_fn_ = [](int index, const Trs& trs) { + mat4f base = mat4f(trs.rotation, trs.translation); + base *= mat4f::scaling(float3{1, 1, kArrowScale}); + base *= mat4f::translation(float3{0, 0, trs.size.z}); + if (index == 0) { + return base * mat4f::scaling(trs.size); + } else if (index == 1) { + mat4f top = base; + top *= mat4f::translation(float3{0, 0, trs.size.z}); + return top * mat4f::scaling(trs.size); + } else if (index == 2) { + mat4f bottom = base; + bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); + bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); + return bottom * mat4f::scaling(trs.size); + } else { + mju_error("Invalid index for arrow1 geom: %d (expected [0,2])", index); + return trs.ToTransform(); + } + }; + break; + case mjGEOM_ARROW2: + AppendMesh(builtins->Tube()); + AppendMesh(builtins->Cone()); + AppendMesh(builtins->Cone()); + AppendMesh(builtins->Disk()); + AppendMesh(builtins->Disk()); + + get_transform_fn_ = [](int index, const Trs& trs) { + mat4f base = mat4f(trs.rotation, trs.translation); + base *= mat4f::scaling(float3{1, 1, kArrowScale}); + base *= mat4f::translation(float3{0, 0, trs.size.z}); + if (index == 0) { + return base * mat4f::scaling(trs.size); + } else if (index == 1) { + mat4f top = base; + top *= mat4f::translation(float3{0, 0, trs.size.z}); + top *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); + return top * mat4f::scaling(trs.size); + } else if (index == 2) { + mat4f bottom = base; + bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); + bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); + bottom *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); + return bottom * mat4f::scaling(trs.size); + } else if (index == 3) { + mat4f top_disk = base; + top_disk *= mat4f::translation(float3{0, 0, trs.size.z}); + top_disk *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); + top_disk *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); + return top_disk * mat4f::scaling(trs.size); + } else if (index == 4) { + mat4f bottom_disk = base; + bottom_disk *= mat4f::translation(float3{0, 0, -trs.size.z}); + return bottom_disk * mat4f::scaling(trs.size); + } else { + mju_error("Invalid index for arrow2 geom: %d (expected [0,4])", index); + return trs.ToTransform(); + } + }; + break; + case mjGEOM_LINE: + AppendMesh(builtins->Line()); + break; + case mjGEOM_LINEBOX: + AppendMesh(builtins->LineBox()); + break; + case mjGEOM_TRIANGLE: + AppendMesh(builtins->Triangle()); + break; + default: + mju_error("Unsupported geom type: %d", type); + break; + } +} + filament::Engine* Renderable::GetEngine() { return object_mgr_->GetEngine(); } } // namespace mujoco diff --git a/src/experimental/filament/filament/renderable.h b/src/experimental/filament/filament/renderable.h index e7a405f4..3d41b2a1 100644 --- a/src/experimental/filament/filament/renderable.h +++ b/src/experimental/filament/filament/renderable.h @@ -17,13 +17,13 @@ #include #include -#include #include #include #include #include #include +#include #include "experimental/filament/filament/filament_context.h" #include "experimental/filament/filament/math_util.h" #include "experimental/filament/filament/mesh.h" @@ -59,20 +59,15 @@ class Renderable : public mjrRenderable { // assumes the entire mesh should be appended. void SetMesh(const Mesh* mesh, int elem_offset = 0, int elem_count = 0); + // Sets the mesh of the renderable based on the given geom type. + void SetGeomMesh(mjtGeom type, int nstack, int nslice, int nquad); + // Sets the transform of the renderable. void SetTransform(const Trs& trs); // Returns the current transform of the renderable. const filament::math::mat4f& GetTransform() const; - // Sets multiple meshes for a renderable. Users can optionally provide a - // function that will be used to compute the transform for each (sub)mesh - // relative to the transform of the renderable itself. This allows users to - // construct compound (but rigid) objects from multiple meshes. - using GetTransformFn = std::function; - void SetMeshes(std::span meshes, - GetTransformFn get_transform = nullptr); - // Sets the layer mask for the managed filament Entities. Layer masks can be // used to show/hide the renderable in different views. Returns the previous // layer mask. @@ -128,6 +123,8 @@ class Renderable : public mjrRenderable { } private: + using GetTransformFn = std::function; + struct Part { utils::Entity entity; const Mesh* mesh = nullptr; @@ -135,6 +132,8 @@ class Renderable : public mjrRenderable { int elem_count = 0; }; + void AppendMesh(const Mesh* mesh); + void InitPartEntity(Part& part); void AssignMaterial(mjrDrawMode mode, ObjectManager::MaterialType material_type); diff --git a/src/experimental/filament/render_context_filament.cc b/src/experimental/filament/render_context_filament.cc index 68b58766..938f4d19 100644 --- a/src/experimental/filament/render_context_filament.cc +++ b/src/experimental/filament/render_context_filament.cc @@ -254,6 +254,12 @@ void mjrf_setRenderableMesh(mjrRenderable* renderable, const mjrMesh* mesh, ->SetMesh(mujoco::Mesh::downcast(mesh), elem_offset, elem_count); } +void mjrf_setRenderableGeomMesh(mjrRenderable* renderable, mjtGeom type, + int nstack, int nslice, int nquad) { + mujoco::Renderable::downcast(renderable)->SetGeomMesh(type, nstack, nslice, + nquad); +} + void mjrf_setRenderableMaterial(mjrRenderable* renderable, const mjrMaterialParams* params, const mjrMaterialTextures* textures) { @@ -265,9 +271,9 @@ void mjrf_setRenderableTransform(mjrRenderable* renderable, const float rotation[9], const float size[3]) { const filament::math::float3 fposition{position[0], position[1], position[2]}; const filament::math::float3 fsize{size[0], size[1], size[2]}; - const filament::math::mat3f frotation{rotation[0], rotation[1], rotation[2], - rotation[3], rotation[4], rotation[5], - rotation[6], rotation[7], rotation[8]}; + const filament::math::mat3f frotation{rotation[0], rotation[3], rotation[6], + rotation[1], rotation[4], rotation[7], + rotation[2], rotation[5], rotation[8]}; mujoco::Renderable::downcast(renderable) ->SetTransform({fposition, frotation, fsize}); } diff --git a/src/experimental/filament/render_context_filament.h b/src/experimental/filament/render_context_filament.h index fadc10ca..0881a0b6 100644 --- a/src/experimental/filament/render_context_filament.h +++ b/src/experimental/filament/render_context_filament.h @@ -500,6 +500,12 @@ mjrLightType mjrf_getLightType(const mjrLight* light); void mjrf_setRenderableMesh(mjrRenderable* renderable, const mjrMesh* mesh, int elem_offset, int elem_count); +// Sets the mesh of the renderable to a built-in mesh based on the geom type. +// Note: using the same parameters (nstack, nslice, nquad) will have better +// performance as the internal mesh data can be shared across renderables. +void mjrf_setRenderableGeomMesh(mjrRenderable* renderable, mjtGeom type, + int nstack, int nslice, int nquad); + // Sets the material properties and textures of the renderable. void mjrf_setRenderableMaterial(mjrRenderable* renderable, const mjrMaterialParams* params,