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
This commit is contained in:
Haroon Qureshi
2026-05-07 00:12:09 -07:00
committed by Copybara-Service
parent 0c05215e18
commit 39c891e358
11 changed files with 371 additions and 344 deletions
@@ -29,7 +29,6 @@
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mujoco.h>
#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;
@@ -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<ShapeType, UniquePtr<mjrMesh>> shapes_;
std::unordered_map<int, UniquePtr<mjrMesh>> meshes_;
std::unordered_map<int, UniquePtr<mjrMesh>> convex_hulls_;
std::unordered_map<int, UniquePtr<mjrMesh>> height_fields_;
@@ -18,32 +18,15 @@
#include <cstdint>
#include <cstring>
#include <memory>
#include <numbers>
#include <vector>
#include <math/mat4.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#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<const mjrMesh*> 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<float>(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);
}
+26 -41
View File
@@ -19,16 +19,16 @@
#include <cstdint>
#include <memory>
#include <numbers>
#include <utility>
#include <vector>
#include <filament/Engine.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#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 <typename T, typename... Args>
static UniquePtr<mjrMesh> Create(mjrfContext* ctx, Args&&... args) {
static std::unique_ptr<Mesh> Create(filament::Engine* engine,
Args&&... args) {
auto builder = new T(std::forward<Args>(args)...);
mjrMeshData* mesh_data = builder->PrepareMeshData();
mesh_data->release_callback = +[](void* user_data) {
delete static_cast<BuiltinBuilder*>(user_data);
};
mesh_data->user_data = builder;
return CreateMesh(ctx, *mesh_data);
return std::make_unique<Mesh>(engine, *mesh_data);
}
mjrMeshData* PrepareMeshData() {
@@ -617,44 +618,28 @@ class DomeBuilder : public BuiltinBuilder {
}
};
UniquePtr<mjrMesh> CreateLine(mjrfContext* ctx) {
return BuiltinBuilder::Create<LineBuilder>(ctx);
Builtins::Builtins(filament::Engine* engine, int nstack, int nslice, int nquad) {
line_ = BuiltinBuilder::Create<LineBuilder>(engine);
plane_ = BuiltinBuilder::Create<PlaneBuilder>(engine, nquad);
triangle_ = BuiltinBuilder::Create<TriangleBuilder>(engine);
box_ = BuiltinBuilder::Create<BoxBuilder>(engine, nquad);
line_box_ = BuiltinBuilder::Create<LineBoxBuilder>(engine);
sphere_ = BuiltinBuilder::Create<SphereBuilder>(engine, nstack, nslice);
tube_ = BuiltinBuilder::Create<TubeBuilder>(engine, nstack, nslice);
disk_ = BuiltinBuilder::Create<DiskBuilder>(engine, nslice);
dome_ = BuiltinBuilder::Create<DomeBuilder>(engine, nstack, nslice);
cone_ = BuiltinBuilder::Create<ConeBuilder>(engine, nstack, nslice);
}
UniquePtr<mjrMesh> CreatePlane(mjrfContext* ctx, int nquad) {
return BuiltinBuilder::Create<PlaneBuilder>(ctx, nquad);
}
UniquePtr<mjrMesh> CreateTriangle(mjrfContext* ctx) {
return BuiltinBuilder::Create<TriangleBuilder>(ctx);
}
UniquePtr<mjrMesh> CreateBox(mjrfContext* ctx, int nquad) {
return BuiltinBuilder::Create<BoxBuilder>(ctx, nquad);
}
UniquePtr<mjrMesh> CreateLineBox(mjrfContext* ctx) {
return BuiltinBuilder::Create<LineBoxBuilder>(ctx);
}
UniquePtr<mjrMesh> CreateSphere(mjrfContext* ctx, int nstack, int nslice) {
return BuiltinBuilder::Create<SphereBuilder>(ctx, nstack, nslice);
}
UniquePtr<mjrMesh> CreateTube(mjrfContext* ctx, int nstack, int nslice) {
return BuiltinBuilder::Create<TubeBuilder>(ctx, nstack, nslice);
}
UniquePtr<mjrMesh> CreateDisk(mjrfContext* ctx, int nslice) {
return BuiltinBuilder::Create<DiskBuilder>(ctx, nslice);
}
UniquePtr<mjrMesh> CreateDome(mjrfContext* ctx, int nstack, int nslice) {
return BuiltinBuilder::Create<DomeBuilder>(ctx, nstack, nslice);
}
UniquePtr<mjrMesh> CreateCone(mjrfContext* ctx, int nstack, int nslice) {
return BuiltinBuilder::Create<ConeBuilder>(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
+32 -13
View File
@@ -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 <memory>
#include <filament/Engine.h>
#include "experimental/filament/filament/mesh.h"
// Generates buffers for built-in shapes.
namespace mujoco {
UniquePtr<mjrMesh> CreateLine(mjrfContext* ctx);
UniquePtr<mjrMesh> CreatePlane(mjrfContext* ctx, int nquad);
UniquePtr<mjrMesh> CreateTriangle(mjrfContext* ctx);
UniquePtr<mjrMesh> CreateBox(mjrfContext* ctx, int nquad);
UniquePtr<mjrMesh> CreateLineBox(mjrfContext* ctx);
UniquePtr<mjrMesh> CreateSphere(mjrfContext* ctx, int nstack, int nslice);
UniquePtr<mjrMesh> CreateTube(mjrfContext* ctx, int nstack, int nslice);
UniquePtr<mjrMesh> CreateDisk(mjrfContext* ctx, int nslice);
UniquePtr<mjrMesh> CreateDome(mjrfContext* ctx, int nstack, int nslice);
UniquePtr<mjrMesh> 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<Mesh> line_;
std::unique_ptr<Mesh> line_box_;
std::unique_ptr<Mesh> plane_;
std::unique_ptr<Mesh> triangle_;
std::unique_ptr<Mesh> box_;
std::unique_ptr<Mesh> sphere_;
std::unique_ptr<Mesh> cone_;
std::unique_ptr<Mesh> disk_;
std::unique_ptr<Mesh> dome_;
std::unique_ptr<Mesh> tube_;
};
} // namespace mujoco
@@ -20,6 +20,7 @@
#include <span>
#include <string>
#include <string_view>
#include <utility>
#include <filament/Engine.h>
#include <filament/IndirectLight.h>
@@ -27,6 +28,7 @@
#include <filament/Skybox.h>
#include <filament/Texture.h>
#include <mujoco/mujoco.h>
#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<uint64_t>(nstack) << 20) |
(static_cast<uint64_t>(nslice) << 40) |
static_cast<uint64_t>(nquad);
auto iter = builtins_.find(key);
if (iter == builtins_.end()) {
auto builtins = std::make_unique<Builtins>(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) {
@@ -17,15 +17,18 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
#include <string_view>
#include <unordered_map>
#include <filament/Engine.h>
#include <filament/IndirectLight.h>
#include <filament/Skybox.h>
#include <filament/Texture.h>
#include <mujoco/mujoco.h>
#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<Asset> LoadAsset(std::string_view filename);
@@ -97,6 +103,7 @@ class ObjectManager {
filament::Engine* engine_ = nullptr;
std::array<filament::Material*, kNumMaterials> materials_;
std::array<filament::Texture*, mjNTEXROLE> fallback_textures_;
std::unordered_map<std::uint64_t, std::unique_ptr<Builtins>> builtins_;
filament::Texture* fallback_white_ = nullptr;
filament::Texture* fallback_black_ = nullptr;
filament::Texture* fallback_normal_ = nullptr;
+210 -15
View File
@@ -16,7 +16,7 @@
#include <algorithm>
#include <cstdint>
#include <span>
#include <numbers>
#include <filament/Engine.h>
#include <filament/Material.h>
@@ -24,8 +24,12 @@
#include <filament/Scene.h>
#include <filament/TransformManager.h>
#include <math/mat4.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <utils/EntityManager.h>
#include <mujoco/mujoco.h>
#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<const mjrMesh*> 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
@@ -17,13 +17,13 @@
#include <cstdint>
#include <functional>
#include <span>
#include <vector>
#include <filament/Engine.h>
#include <filament/Scene.h>
#include <math/mat4.h>
#include <utils/Entity.h>
#include <mujoco/mujoco.h>
#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<filament::math::mat4f(int, const Trs&)>;
void SetMeshes(std::span<const mjrMesh*> 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<filament::math::mat4f(int, const Trs&)>;
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);
@@ -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});
}
@@ -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,