From 8e7787ad09007e0e5fd6311a00eb4d343ecfb2a4 Mon Sep 17 00:00:00 2001 From: Haroon Qureshi Date: Mon, 20 Apr 2026 10:38:58 -0700 Subject: [PATCH] Use function for applying transforms to multimesh Renderables. PiperOrigin-RevId: 902712241 Change-Id: I5bfdab8bce12ffb1a1f030ae0a32a61e9b8157e1 --- .../filament/filament/renderable.cc | 29 ++- .../filament/filament/renderable.h | 12 +- .../filament/filament/scene_geom_util.cc | 234 ++++++++++-------- 3 files changed, 156 insertions(+), 119 deletions(-) diff --git a/src/experimental/filament/filament/renderable.cc b/src/experimental/filament/filament/renderable.cc index 2a9659ce..f1a4e2bc 100644 --- a/src/experimental/filament/filament/renderable.cc +++ b/src/experimental/filament/filament/renderable.cc @@ -135,11 +135,23 @@ void Renderable::InitPartEntity(Part& part) { } void Renderable::SetTransform(const Trs& trs) { - transform_ = trs.ToTransform(); - filament::TransformManager& tm = GetEngine()->getTransformManager(); - for (Part& part : parts_) { - tm.setTransform(tm.getInstance(part.entity), transform_); + if (parts_.empty()) { + transform_ = trs.ToTransform(); + return; } + + filament::TransformManager& tm = GetEngine()->getTransformManager(); + if (get_transform_fn_) { + for (int i = 0; i < parts_.size(); ++i) { + const mat4f& transform = get_transform_fn_(i, trs); + tm.setTransform(tm.getInstance(parts_[i].entity), transform); + } + } else { + for (Part& part : parts_) { + tm.setTransform(tm.getInstance(part.entity), trs.ToTransform()); + } + } + transform_ = tm.getTransform(tm.getInstance(parts_[0].entity)); } const mat4f& Renderable::GetTransform() const { @@ -147,23 +159,18 @@ const mat4f& Renderable::GetTransform() const { } void Renderable::SetMeshes(std::span meshes, - std::span transforms) { - if (meshes.size() != transforms.size()) { - mju_error("Number of meshes does not match number of transforms."); - } + GetTransformFn get_transform_fn) { if (!parts_.empty()) { mju_error("Cannot set meshes for renderable with multiple parts."); } - filament::TransformManager& tm = GetEngine()->getTransformManager(); + get_transform_fn_ = get_transform_fn; for (int i = 0; i < meshes.size(); ++i) { Part& part = parts_.emplace_back(); part.mesh = meshes[i]; part.elem_offset = 0; part.elem_count = part.mesh->GetFilamentIndexBuffer()->getIndexCount(); InitPartEntity(part); - - tm.setTransform(tm.getInstance(part.entity), transforms[i]); } } diff --git a/src/experimental/filament/filament/renderable.h b/src/experimental/filament/filament/renderable.h index 380eea3b..9227e634 100644 --- a/src/experimental/filament/filament/renderable.h +++ b/src/experimental/filament/filament/renderable.h @@ -16,6 +16,7 @@ #define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_RENDERABLE_H_ #include +#include #include #include @@ -83,10 +84,13 @@ class Renderable { // Returns the current transform of the renderable. const filament::math::mat4f& GetTransform() const; - // Sets multiple meshes for a renderable. Each mesh is assigned a specific - // transform to allow for assembly of compound shapes. + // 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, - std::span transforms); + 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 @@ -158,7 +162,7 @@ class Renderable { filament::Scene* assigned_scene_ = nullptr; std::vector parts_; filament::math::mat4f transform_; - + GetTransformFn get_transform_fn_; std::uint8_t priority_ = kDefaultPriority; std::uint8_t layer_mask_ = kDefaultLayerMask; std::uint16_t blend_order_ = 0; diff --git a/src/experimental/filament/filament/scene_geom_util.cc b/src/experimental/filament/filament/scene_geom_util.cc index 3b15b32d..80c653a7 100644 --- a/src/experimental/filament/filament/scene_geom_util.cc +++ b/src/experimental/filament/filament/scene_geom_util.cc @@ -103,7 +103,7 @@ static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom, const mjvScene* scene, ModelObjects* model_objects) { std::vector meshes; - std::vector transforms; + Renderable::GetTransformFn get_transforms; Trs trs = { .translation = ReadFloat3(geom.pos), @@ -115,12 +115,12 @@ static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom, case mjGEOM_MESH: meshes.push_back(GetMesh(model_objects, geom.dataid)); // Ignore size for meshes. - transforms.push_back(mat4f(trs.rotation, trs.translation)); + trs.size = float3{1.0f, 1.0f, 1.0f}; break; case mjGEOM_HFIELD: meshes.push_back(GetHeightField(model_objects, geom.dataid)); // Ignore size for height fields. - transforms.push_back(mat4f(trs.rotation, trs.translation)); + trs.size = float3{1.0f, 1.0f, 1.0f}; break; case mjGEOM_PLANE: { meshes.push_back(GetShape(model_objects, ModelObjects::kPlane)); @@ -134,20 +134,16 @@ static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom, } // Planes only define an xy size, so set the z-dimension to 1.0f. trs.size.z = 1.0f; - transforms.push_back(trs.ToTransform()); break; } case mjGEOM_SPHERE: meshes.push_back(GetShape(model_objects, ModelObjects::kSphere)); - transforms.push_back(trs.ToTransform()); break; case mjGEOM_ELLIPSOID: meshes.push_back(GetShape(model_objects, ModelObjects::kSphere)); - transforms.push_back(trs.ToTransform()); break; case mjGEOM_BOX: meshes.push_back(GetShape(model_objects, ModelObjects::kBox)); - transforms.push_back(trs.ToTransform()); break; case mjGEOM_CAPSULE: { // Capsules are a tube with two domes at the ends. @@ -155,25 +151,31 @@ static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom, meshes.push_back(GetShape(model_objects, ModelObjects::kDome)); meshes.push_back(GetShape(model_objects, ModelObjects::kDome)); - transforms.push_back(trs.ToTransform()); - - // 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); - - // 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}); - transforms.push_back(top); - - // 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}); - transforms.push_back(bottom); + 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(); + } + }; break; } case mjGEOM_CYLINDER: { @@ -182,21 +184,28 @@ static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom, meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); - transforms.push_back(trs.ToTransform()); - - // 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); - transforms.push_back(top); - - // 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); - transforms.push_back(bottom); + 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(); + } + }; break; } case mjGEOM_ARROW: { @@ -205,27 +214,33 @@ static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom, meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); - mat4f base = mat4f(trs.rotation, trs.translation); - base *= mat4f::scaling(float3{1, 1, kArrowScale}); - base *= mat4f::translation(float3{0, 0, trs.size.z}); - transforms.push_back(base * mat4f::scaling(trs.size)); - - mat4f top = base; - top *= mat4f::translation(float3{0, 0, trs.size.z}); - top *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); - transforms.push_back(top * mat4f::scaling(trs.size)); - - 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}); - transforms.push_back(top_disk * mat4f::scaling(trs.size)); - - mat4f bottom = base; - bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); - bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); - transforms.push_back(bottom * mat4f::scaling(trs.size)); - + 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(); + } + }; break; } case mjGEOM_ARROW1: { @@ -233,19 +248,26 @@ static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom, 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}); - transforms.push_back(base * mat4f::scaling(trs.size)); - - mat4f top = base; - top *= mat4f::translation(float3{0, 0, trs.size.z}); - transforms.push_back(top * mat4f::scaling(trs.size)); - - mat4f bottom = base; - bottom *= mat4f::translation(float3{0, 0, -trs.size.z}); - bottom *= mat4f::rotation(std::numbers::pi, float3{1, 0, 0}); - transforms.push_back(bottom * mat4f::scaling(trs.size)); + 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: { @@ -255,55 +277,58 @@ static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom, meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); meshes.push_back(GetShape(model_objects, ModelObjects::kDisk)); - mat4f base = mat4f(trs.rotation, trs.translation); - base *= mat4f::scaling(float3{1, 1, kArrowScale}); - base *= mat4f::translation(float3{0, 0, trs.size.z}); - transforms.push_back(base * mat4f::scaling(trs.size)); - - mat4f top = base; - top *= mat4f::translation(float3{0, 0, trs.size.z}); - top *= mat4f::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f}); - transforms.push_back(top * mat4f::scaling(trs.size)); - - 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}); - transforms.push_back(bottom * mat4f::scaling(trs.size)); - - 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}); - transforms.push_back(top_disk * mat4f::scaling(trs.size)); - - mat4f bottom_disk = base; - bottom_disk *= mat4f::translation(float3{0, 0, -trs.size.z}); - transforms.push_back(bottom_disk * mat4f::scaling(trs.size)); - + 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(); + } + }; break; } case mjGEOM_LINE: meshes.push_back(GetShape(model_objects, ModelObjects::kLine)); - transforms.push_back(trs.ToTransform()); break; case mjGEOM_LINEBOX: meshes.push_back(GetShape(model_objects, ModelObjects::kLineBox)); - transforms.push_back(trs.ToTransform()); break; case mjGEOM_TRIANGLE: meshes.push_back(GetShape(model_objects, ModelObjects::kTriangle)); - transforms.push_back(trs.ToTransform()); break; case mjGEOM_FLEX: meshes.push_back(GetSkinFlexMesh(model_objects, geom.objid)); // Flexes are defined in global space. - transforms.push_back(mat4f()); + trs = Trs(); break; case mjGEOM_SKIN: meshes.push_back(GetSkinFlexMesh(model_objects, geom.objid)); // Skins are defined in global space. - transforms.push_back(mat4f()); + trs = Trs(); break; case mjGEOM_NONE: case mjGEOM_LABEL: @@ -315,7 +340,8 @@ static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom, break; } - renderable.SetMeshes(meshes, transforms); + renderable.SetMeshes(meshes, get_transforms); + renderable.SetTransform(trs); } static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,