Allow Transforms to be set directly on Renderable.

Also allow multiple Meshes to be assigned at once rather than one at a
time.

This simplifies both the usage and implementation of Renderable.

PiperOrigin-RevId: 902665683
Change-Id: I23ff365a54fd6a3814ed3452b1e094ae9f698a1b
This commit is contained in:
Haroon Qureshi
2026-04-20 09:05:43 -07:00
committed by Copybara-Service
parent b2281883dd
commit 476e2e909e
8 changed files with 343 additions and 353 deletions
@@ -156,7 +156,6 @@ void FilamentContext::Render(const mjrRect& viewport, const mjvScene* scene) {
request.viewport = viewport;
request.camera = last_camera_;
request.enable_ux = (gui_swap_chain_target_ == kWindowSwapChain);
request.gui_scale = imgui_bridge_ ? imgui_bridge_->GetScale() : 1.0f;
scene_view_->Render(renderer_, request);
renderer_->endFrame();
}
@@ -233,7 +232,6 @@ void FilamentContext::ReadPixels(mjrRect viewport, unsigned char* rgb,
request.target = color_target_.get();
request.camera = last_camera_;
request.enable_ux = (gui_swap_chain_target_ == kOffscreenSwapChain);
request.gui_scale = imgui_bridge_ ? imgui_bridge_->GetScale() : 1.0f;
scene_view_->Render(renderer_, request);
const size_t num_bytes = viewport.width * viewport.height * 3;
@@ -21,6 +21,8 @@
#include <vector>
#include <imgui.h>
#include <math/mat3.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/material.h"
@@ -32,6 +34,9 @@
namespace mujoco {
using filament::math::float3;
using filament::math::mat3f;
ImguiBridge::ImguiBridge(ObjectManager* object_mgr, SceneView* scene_view)
: object_mgr_(object_mgr), scene_view_(scene_view) {}
@@ -235,11 +240,7 @@ void ImguiBridge::Update() {
const int height = size.y * scale.y;
auto& renderable = renderables_[renderable_index];
if (renderable->GetNumMeshes() == 0) {
renderable->AppendMesh(mesh, index_offset, command.ElemCount);
} else {
renderable->UpdateMesh(0, mesh, index_offset, command.ElemCount);
}
renderable->SetMesh(mesh, index_offset, command.ElemCount);
MaterialTextures textures;
textures.color = textures_[command.GetTexID()].get();
@@ -259,6 +260,8 @@ void ImguiBridge::Update() {
properties.scissor[3] = height;
}
renderable->UpdateMaterial(properties, textures);
renderable->SetTransform(
{float3{0, 0, 0}, mat3f(), float3(scale.x, scale.y, 1.0f)});
index_offset += command.ElemCount;
++renderable_index;
@@ -284,10 +287,6 @@ void ImguiBridge::PrepareRenderables(int count) {
}
}
float ImguiBridge::GetScale() const {
return ImGui::GetIO().DisplayFramebufferScale.x;
}
static ImVec2 ClipSpaceToWindowCoordinates(float x, float y) {
const ImVec2& display_size = ImGui::GetIO().DisplaySize;
const float pos_x = display_size.x * ((x + 1) * 0.5f);
@@ -40,9 +40,6 @@ class ImguiBridge {
// synced.
void Update();
// Returns the current ImGui scale factor.
float GetScale() const;
// Uploads texture to be used with ImGui's Image and ImageButton functions.
uintptr_t UploadImage(uintptr_t tex_id, const uint8_t* pixels, int width,
int height, int bpp);
+106 -107
View File
@@ -16,20 +16,26 @@
#include <algorithm>
#include <cstdint>
#include <span>
#include <filament/Engine.h>
#include <filament/Material.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/TransformManager.h>
#include <math/mat4.h>
#include <utils/EntityManager.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/draw_mode.h"
#include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/mesh.h"
#include "experimental/filament/filament/object_manager.h"
namespace mujoco {
using filament::math::mat4f;
void DefaultRenderableParams(RenderableParams* params) {
params->shading_model = ShadingModel::SceneObject;
}
@@ -38,68 +44,77 @@ Renderable::Renderable(ObjectManager* object_mgr, const RenderableParams& params
: object_mgr_(object_mgr), params_(params) {}
Renderable::~Renderable() noexcept {
while (!entities_.empty()) {
RemoveLastEntity();
filament::Engine* engine = GetEngine();
utils::EntityManager& em = utils::EntityManager::get();
for (Part& part : parts_) {
if (assigned_scene_) {
assigned_scene_->remove(part.entity);
}
engine->destroy(part.entity);
em.destroy(part.entity);
}
for (int i = 0; i < kNumDrawModes; ++i) {
if (instances_[i] != nullptr) {
GetEngine()->destroy(instances_[i]);
engine->destroy(instances_[i]);
instances_[i] = nullptr;
}
}
}
void Renderable::RemoveLastEntity() {
if (entities_.empty()) {
return;
void Renderable::SetMesh(const Mesh* mesh, int elem_offset, int elem_count) {
if (mesh == nullptr) {
mju_error("Cannot set mesh to nullptr.");
}
utils::EntityManager& em = utils::EntityManager::get();
utils::Entity entity = entities_.back();
if (assigned_scene_) {
assigned_scene_->remove(entity);
}
GetEngine()->destroy(entity);
em.destroy(entity);
entities_.pop_back();
meshes_.pop_back();
}
void Renderable::UpdateMesh(int index, const Mesh* mesh, int elem_offset,
int elem_count) {
MeshInfo& mesh_info = SetMesh(index, mesh, elem_offset, elem_count);
UpdateEntity(index, mesh_info);
}
void Renderable::AppendMesh(const Mesh* mesh, int elem_offset, int elem_count) {
MeshInfo& mesh_info = SetMesh(-1, mesh, elem_offset, elem_count);
AppendEntity(mesh_info);
}
void Renderable::AppendEntity(const MeshInfo& mesh_info) {
const Mesh* mesh = mesh_info.mesh;
filament::VertexBuffer* vertex_buffer = mesh->GetFilamentVertexBuffer();
if (vertex_buffer == nullptr) {
mju_error("Invalid (null) vertex buffer.");
}
filament::IndexBuffer* index_buffer = mesh->GetFilamentIndexBuffer();
if (index_buffer == nullptr) {
mju_error("Invalid (null) index buffer.");
}
utils::Entity entity = utils::EntityManager::get().create();
if (entity.isNull()) {
if (elem_count == 0) {
elem_count = index_buffer->getIndexCount() - elem_offset;
}
if (parts_.empty()) {
Part& part = parts_.emplace_back();
part.mesh = mesh;
part.elem_offset = elem_offset;
part.elem_count = elem_count;
InitPartEntity(part);
} else if (parts_.size() == 1) {
Part& part = parts_[0];
part.mesh = mesh;
part.elem_offset = elem_offset;
part.elem_count = elem_count;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
rm.setGeometryAt(rm.getInstance(part.entity), 0,
part.mesh->GetPrimitiveType(), vertex_buffer, index_buffer,
part.elem_offset, part.elem_count);
} else {
mju_error("Cannot set mesh for renderable with multiple parts.");
}
}
void Renderable::InitPartEntity(Part& part) {
part.entity = utils::EntityManager::get().create();
if (part.entity.isNull()) {
mju_error("Failed to create entity.");
}
filament::VertexBuffer* vertex_buffer = part.mesh->GetFilamentVertexBuffer();
filament::IndexBuffer* index_buffer = part.mesh->GetFilamentIndexBuffer();
filament::RenderableManager::Builder builder(1);
builder.geometry(0, mesh->GetPrimitiveType(), vertex_buffer, index_buffer,
mesh_info.elem_offset, mesh_info.elem_count);
if (mesh->HasBounds()) {
builder.boundingBox(mesh->GetBounds());
builder.geometry(0, part.mesh->GetPrimitiveType(), vertex_buffer, index_buffer,
part.elem_offset, part.elem_count);
if (part.mesh->HasBounds()) {
builder.boundingBox(part.mesh->GetBounds());
} else {
builder.culling(false);
}
@@ -113,56 +128,43 @@ void Renderable::AppendEntity(const MeshInfo& mesh_info) {
builder.blendOrder(0, blend_order_);
builder.screenSpaceContactShadows(true);
builder.build(*GetEngine(), entity);
builder.build(*GetEngine(), part.entity);
if (assigned_scene_) {
assigned_scene_->addEntity(entity);
assigned_scene_->addEntity(part.entity);
}
entities_.push_back(entity);
}
void Renderable::UpdateEntity(int index, const MeshInfo& mesh_info) {
if (index < 0 || index >= entities_.size()) {
mju_error("Invalid index %d for renderable.", index);
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_);
}
utils::Entity entity = entities_[index];
const Mesh* mesh = mesh_info.mesh;
filament::VertexBuffer* vertex_buffer = mesh->GetFilamentVertexBuffer();
if (vertex_buffer == nullptr) {
mju_error("Invalid (null) vertex buffer.");
}
filament::IndexBuffer* index_buffer = mesh->GetFilamentIndexBuffer();
if (index_buffer == nullptr) {
mju_error("Invalid (null) index buffer.");
}
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
rm.setGeometryAt(rm.getInstance(entity), 0, mesh->GetPrimitiveType(),
vertex_buffer, index_buffer, mesh_info.elem_offset,
mesh_info.elem_count);
}
Renderable::MeshInfo& Renderable::SetMesh(int index, const Mesh* mesh,
int elem_offset, int elem_count) {
if (index == -1) {
index = meshes_.size();
meshes_.emplace_back();
const mat4f& Renderable::GetTransform() const {
return transform_;
}
void Renderable::SetMeshes(std::span<const Mesh*> meshes,
std::span<const mat4f> transforms) {
if (meshes.size() != transforms.size()) {
mju_error("Number of meshes does not match number of transforms.");
}
if (index < 0 || index >= static_cast<int>(meshes_.size())) {
mju_error("Invalid index %d for renderable.", index);
if (!parts_.empty()) {
mju_error("Cannot set meshes for renderable with multiple parts.");
}
MeshInfo* mesh_info = &meshes_[index];
mesh_info->mesh = mesh;
mesh_info->elem_offset = elem_offset;
mesh_info->elem_count = elem_count;
if (mesh_info->elem_count == 0) {
const int total =
mesh_info->mesh->GetFilamentIndexBuffer()->getIndexCount();
mesh_info->elem_count = total - mesh_info->elem_offset;
filament::TransformManager& tm = GetEngine()->getTransformManager();
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]);
}
return *mesh_info;
}
void Renderable::AddToScene(filament::Scene* scene) {
@@ -173,8 +175,8 @@ void Renderable::AddToScene(filament::Scene* scene) {
// Entities are already added to the scene.
return;
}
for (utils::Entity& entity : entities_) {
scene->addEntity(entity);
for (Part& part : parts_) {
scene->addEntity(part.entity);
}
assigned_scene_ = scene;
}
@@ -183,8 +185,8 @@ void Renderable::RemoveFromScene(filament::Scene* scene) {
if (assigned_scene_ != scene) {
mju_error("Attempting to remove renderable from wrong scene.");
}
for (utils::Entity& entity : entities_) {
scene->remove(entity);
for (Part& part : parts_) {
scene->remove(part.entity);
}
assigned_scene_ = nullptr;
}
@@ -245,8 +247,8 @@ void Renderable::SetDrawMode(DrawMode mode) {
filament::MaterialInstance* instance = instances_[static_cast<int>(mode)];
if (instance) {
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (utils::Entity& entity : entities_) {
filament::RenderableManager::Instance ri = rm.getInstance(entity);
for (Part& part : parts_) {
filament::RenderableManager::Instance ri = rm.getInstance(part.entity);
rm.setMaterialInstanceAt(ri, 0, instance);
}
}
@@ -259,8 +261,8 @@ std::uint8_t Renderable::SetLayerMask(std::uint8_t mask) {
layer_mask_ = mask;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (utils::Entity& entity : entities_) {
rm.setLayerMask(rm.getInstance(entity), 0xff, layer_mask_);
for (Part& part : parts_) {
rm.setLayerMask(rm.getInstance(part.entity), 0xff, layer_mask_);
}
}
return prev;
@@ -272,8 +274,8 @@ std::uint8_t Renderable::SetPriority(std::uint8_t priority) {
priority_ = priority;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (utils::Entity& entity : entities_) {
rm.setPriority(rm.getInstance(entity), priority_);
for (Part& part : parts_) {
rm.setPriority(rm.getInstance(part.entity), priority_);
}
}
return prev;
@@ -285,8 +287,8 @@ std::uint16_t Renderable::SetBlendOrder(std::uint16_t blend_order) {
blend_order_ = blend_order;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (utils::Entity& entity : entities_) {
rm.setBlendOrderAt(rm.getInstance(entity), 0, blend_order_);
for (Part& part : parts_) {
rm.setBlendOrderAt(rm.getInstance(part.entity), 0, blend_order_);
}
}
return prev;
@@ -297,8 +299,8 @@ void Renderable::SetCastShadows(bool cast_shadows) {
cast_shadows_ = cast_shadows;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (utils::Entity& entity : entities_) {
rm.setCastShadows(rm.getInstance(entity), cast_shadows_);
for (Part& part : parts_) {
rm.setCastShadows(rm.getInstance(part.entity), cast_shadows_);
}
}
}
@@ -308,8 +310,8 @@ void Renderable::SetReceiveShadows(bool receive_shadows) {
receive_shadows_ = receive_shadows;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (utils::Entity& entity : entities_) {
rm.setReceiveShadows(rm.getInstance(entity), receive_shadows_);
for (Part& part : parts_) {
rm.setReceiveShadows(rm.getInstance(part.entity), receive_shadows_);
}
}
}
@@ -322,20 +324,17 @@ void Renderable::SetWireframe(bool wireframe) {
wireframe_ = wireframe;
filament::RenderableManager& rm = GetEngine()->getRenderableManager();
for (int i = 0; i < entities_.size(); ++i) {
utils::Entity& entity = entities_[i];
const Mesh* mesh = meshes_[i].mesh;
filament::VertexBuffer* vertex_buffer = mesh->GetFilamentVertexBuffer();
filament::IndexBuffer* index_buffer = mesh->GetFilamentIndexBuffer();
rm.setGeometryAt(rm.getInstance(entity), 0,
wireframe_ ? kWireframeType : mesh->GetPrimitiveType(),
vertex_buffer, index_buffer, meshes_[i].elem_offset,
meshes_[i].elem_count);
for (Part& part : parts_) {
filament::VertexBuffer* vertex_buffer = part.mesh->GetFilamentVertexBuffer();
filament::IndexBuffer* index_buffer = part.mesh->GetFilamentIndexBuffer();
rm.setGeometryAt(rm.getInstance(part.entity), 0,
wireframe_ ? kWireframeType : part.mesh->GetPrimitiveType(),
vertex_buffer, index_buffer, part.elem_offset,
part.elem_count);
}
}
}
ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
if (params_.shading_model == ShadingModel::DecorLines) {
return ObjectManager::kUnlitLine;
@@ -360,8 +359,8 @@ ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
// geometry) and `mesh_texcoordadr` stores the address of the mesh uvs if
// it has them.
bool has_texcoords = false;
if (!meshes_.empty()) {
const auto attribs = meshes_[0].mesh->GetVertexAttributes();
if (!parts_.empty()) {
const auto attribs = parts_[0].mesh->GetVertexAttributes();
auto it = std::find(attribs.begin(), attribs.end(),
filament::VertexAttribute::UV0);
has_texcoords = (it != attribs.end());
+36 -38
View File
@@ -16,13 +16,16 @@
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_RENDERABLE_H_
#include <cstdint>
#include <span>
#include <vector>
#include <filament/Engine.h>
#include <filament/Scene.h>
#include <math/mat4.h>
#include <utils/Entity.h>
#include "experimental/filament/filament/draw_mode.h"
#include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/mesh.h"
#include "experimental/filament/filament/object_manager.h"
@@ -43,14 +46,20 @@ struct RenderableParams {
void DefaultRenderableParams(RenderableParams* params);
// A collection of meshes and a material that, together, define an object that
// can be rendered in a scene.
// A Renderable is effectively two things: a mesh and a material.
//
// Meshes can be added to the Renderable either by unique_ptr or raw pointer.
// This determines whether or not the Renderable takes ownership of the mesh.
// The mesh describes the surface geometry of the object and the material
// describes how that surface interacts with light (i.e. the color of each point
// on the surface).
//
// Internally, the Renderable creates a filament::Entity for each mesh and
// assigns the same material instance to all of them.
// Defining the mesh is easy; just call SetMesh.
//
// Defining a Material happens in two stages. First, the user specifies the
// ShadingModel to use for Rendering. This describes the overall intent of
// how the Renderable will appear (e.g. lit, unlit, wireframe, etc.). Next,
// the user specifies the MaterialParams and MaterialTextures to use with the
// ShadingModel. Its these properties that ultimately define the actual material
// of the Renderable.
class Renderable {
public:
// Default filament values for priority and layer mask.
@@ -63,19 +72,21 @@ class Renderable {
Renderable(const Renderable&) = delete;
Renderable& operator=(const Renderable&) = delete;
// Appends a mesh to the renderable. The elem_offset and elem_count parameters
// can be used to specify a submesh to append. If elem_count is 0, assumes
// the entire mesh should be appended.
void AppendMesh(const Mesh* mesh, int elem_offset = 0, int elem_count = 0);
// Sets the mesh of the renderable. The elem_offset and elem_count parameters
// can be used to specify a submesh within the mesh. If elem_count is 0,
// assumes the entire mesh should be appended.
void SetMesh(const Mesh* mesh, int elem_offset = 0, int elem_count = 0);
// Replaces the mesh at the index with a new mesh. The elem_offset and
// elem_count parameters can be used to specify a submesh to append. If
// elem_count is 0, assumes the entire mesh should be appended.
void UpdateMesh(int index, const Mesh* mesh, int elem_offset = 0,
int elem_count = 0);
// Sets the transform of the renderable.
void SetTransform(const Trs& trs);
// Returns the number of meshes that define the renderable.
int GetNumMeshes() const { return meshes_.size(); }
// 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.
void SetMeshes(std::span<const Mesh*> meshes,
std::span<const filament::math::mat4f> transforms);
// 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
@@ -107,7 +118,8 @@ class Renderable {
// Removes the renderable from the given filament Scene.
void RemoveFromScene(filament::Scene* scene);
// Sets the material instance for all managed entities.
// Further defines the material of the renderable. Only applies to renderables
// with a SceneObject shading model.
void SetDrawMode(DrawMode mode);
// Updates the parameters for the material.
@@ -123,30 +135,15 @@ class Renderable {
// Returns the filament Engine managing the renderables.
filament::Engine* GetEngine();
// Returns the underlying filament::entity for the given mesh.
utils::Entity operator[](int index) { return entities_[index]; }
private:
struct MeshInfo {
struct Part {
utils::Entity entity;
const Mesh* mesh = nullptr;
int elem_offset = 0;
int elem_count = 0;
};
// Sets the mesh information for the mesh at the given index. If index is -1,
// a new mesh will be appended to the renderable.
MeshInfo& SetMesh(int index, const Mesh* mesh, int elem_offset,
int elem_count);
// Appends a new filament::Entity to the renderable, configured to use the
// given mesh.
void AppendEntity(const MeshInfo& mesh_info);
// Updates the filament::Entity at the given index to use the given mesh.
void UpdateEntity(int index, const MeshInfo& mesh_info);
// Removes the last filament::Entity from the renderable.
void RemoveLastEntity();
void InitPartEntity(Part& part);
void AssignMaterial(DrawMode mode, ObjectManager::MaterialType material_type);
@@ -159,8 +156,9 @@ class Renderable {
MaterialTextures material_textures_;
DrawMode draw_mode_ = DrawMode::Color;
filament::Scene* assigned_scene_ = nullptr;
std::vector<utils::Entity> entities_;
std::vector<MeshInfo> meshes_;
std::vector<Part> parts_;
filament::math::mat4f transform_;
std::uint8_t priority_ = kDefaultPriority;
std::uint8_t layer_mask_ = kDefaultLayerMask;
std::uint16_t blend_order_ = 0;
@@ -18,6 +18,7 @@
#include <cstdint>
#include <memory>
#include <numbers>
#include <vector>
#include <filament/Material.h>
#include <filament/RenderableManager.h>
@@ -44,30 +45,12 @@ namespace mujoco {
using filament::math::float2;
using filament::math::float3;
using filament::math::float4;
using filament::math::mat4;
using filament::math::mat4f;
// An arbitrary scale factor for arrows.
static constexpr float kArrowScale = 1.f / 6.f;
static constexpr float kArrowHeadSize = 1.75f;
// Some built-in geometries are actually composed of multiple simple shapes. A
// capsule, for example, is a open-ended tube with two dome ends. We use these
// constants to help identify which entity (by index) represents which part of
// the overall shape.
static constexpr int kCapsuleTopDome = 1;
static constexpr int kCapsuleBottomDome = 2;
static constexpr int kCylinderTopDisk = 1;
static constexpr int kCylinderBottomDisk = 2;
static constexpr int kArrow0Cone = 1;
static constexpr int kArrow0ConeDisk = 2;
static constexpr int kArrow0BottomDisk = 3;
static constexpr int kArrow1Cone = 1;
static constexpr int kArrow1BottomDisk = 2;
static constexpr int kArrow2TopCone = 1;
static constexpr int kArrow2BottomCone = 2;
static constexpr int kArrow2TopConeDisk = 3;
static constexpr int kArrow2BottomConeDisk = 4;
// 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.
@@ -87,102 +70,240 @@ static bool IsBehind(const float* headpos, const float* pos, const float* mat) {
0.0f);
}
static void AddMesh(Renderable& renderable, ModelObjects* model_objs,
int data_id) {
static const Mesh* GetMesh(ModelObjects* model_objs, int data_id) {
const Mesh* mesh = model_objs->GetMeshBuffer(data_id);
if (mesh == nullptr) {
mju_error("Unknown mesh %d", data_id);
}
renderable.AppendMesh(mesh);
return mesh;
}
static void AddSkinFlexMesh(Renderable& renderable, ModelObjects* model_objs,
int objid) {
renderable.AppendMesh(model_objs->GetFlexSkinGeomMesh(objid));
static const Mesh* GetSkinFlexMesh(ModelObjects* model_objs, int objid) {
return model_objs->GetFlexSkinGeomMesh(objid);
}
static void AddHeightField(Renderable& renderable, ModelObjects* model_objs,
int hfield_id) {
static const Mesh* GetHeightField(ModelObjects* model_objs, int hfield_id) {
const Mesh* mesh = model_objs->GetHeightFieldBuffer(hfield_id);
if (mesh == nullptr) {
mju_error("Unknown height field %d", hfield_id);
}
renderable.AppendMesh(mesh);
return mesh;
}
static void AddShape(Renderable& renderable, ModelObjects* model_objs,
ModelObjects::ShapeType shape_type) {
static const Mesh* GetShape(ModelObjects* model_objs,
ModelObjects::ShapeType shape_type) {
const Mesh* mesh = model_objs->GetShapeBuffer(shape_type);
if (mesh == nullptr) {
mju_error("Unknown shape %d", shape_type);
}
renderable.AppendMesh(mesh);
return mesh;
}
static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom,
const mjvScene* scene,
ModelObjects* model_objects) {
std::vector<const Mesh*> meshes;
std::vector<mat4f> transforms;
Trs trs = {
.translation = ReadFloat3(geom.pos),
.rotation = ReadMat3(geom.mat),
.size = ReadFloat3(geom.size),
};
switch ((mjtGeom)geom.type) {
case mjGEOM_MESH:
AddMesh(renderable, model_objects, geom.dataid);
meshes.push_back(GetMesh(model_objects, geom.dataid));
// Ignore size for meshes.
transforms.push_back(mat4f(trs.rotation, trs.translation));
break;
case mjGEOM_HFIELD:
AddHeightField(renderable, model_objects, geom.dataid);
meshes.push_back(GetHeightField(model_objects, geom.dataid));
// Ignore size for height fields.
transforms.push_back(mat4f(trs.rotation, trs.translation));
break;
case mjGEOM_PLANE:
AddShape(renderable, model_objects, ModelObjects::kPlane);
case mjGEOM_PLANE: {
meshes.push_back(GetShape(model_objects, ModelObjects::kPlane));
const bool is_infinite = !(trs.size.x > 0 && trs.size.y > 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;
}
// 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:
AddShape(renderable, model_objects, ModelObjects::kSphere);
meshes.push_back(GetShape(model_objects, ModelObjects::kSphere));
transforms.push_back(trs.ToTransform());
break;
case mjGEOM_ELLIPSOID:
AddShape(renderable, model_objects, ModelObjects::kSphere);
meshes.push_back(GetShape(model_objects, ModelObjects::kSphere));
transforms.push_back(trs.ToTransform());
break;
case mjGEOM_BOX:
AddShape(renderable, model_objects, ModelObjects::kBox);
meshes.push_back(GetShape(model_objects, ModelObjects::kBox));
transforms.push_back(trs.ToTransform());
break;
case mjGEOM_CAPSULE:
AddShape(renderable, model_objects, ModelObjects::kTube);
AddShape(renderable, model_objects, ModelObjects::kDome);
AddShape(renderable, model_objects, ModelObjects::kDome);
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));
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);
break;
case mjGEOM_CYLINDER:
AddShape(renderable, model_objects, ModelObjects::kTube);
AddShape(renderable, model_objects, ModelObjects::kDisk);
AddShape(renderable, model_objects, ModelObjects::kDisk);
}
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));
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);
break;
case mjGEOM_ARROW:
AddShape(renderable, model_objects, ModelObjects::kTube);
AddShape(renderable, model_objects, ModelObjects::kCone);
AddShape(renderable, model_objects, ModelObjects::kDisk);
}
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));
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));
break;
case mjGEOM_ARROW1:
AddShape(renderable, model_objects, ModelObjects::kTube);
AddShape(renderable, model_objects, ModelObjects::kCone);
AddShape(renderable, model_objects, ModelObjects::kDisk);
AddShape(renderable, model_objects, ModelObjects::kDisk);
}
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));
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));
break;
case mjGEOM_ARROW2:
AddShape(renderable, model_objects, ModelObjects::kTube);
AddShape(renderable, model_objects, ModelObjects::kCone);
AddShape(renderable, model_objects, ModelObjects::kCone);
AddShape(renderable, model_objects, ModelObjects::kDisk);
AddShape(renderable, model_objects, ModelObjects::kDisk);
}
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));
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));
break;
}
case mjGEOM_LINE:
AddShape(renderable, model_objects, ModelObjects::kLine);
meshes.push_back(GetShape(model_objects, ModelObjects::kLine));
transforms.push_back(trs.ToTransform());
break;
case mjGEOM_LINEBOX:
AddShape(renderable, model_objects, ModelObjects::kLineBox);
meshes.push_back(GetShape(model_objects, ModelObjects::kLineBox));
transforms.push_back(trs.ToTransform());
break;
case mjGEOM_TRIANGLE:
AddShape(renderable, model_objects, ModelObjects::kTriangle);
meshes.push_back(GetShape(model_objects, ModelObjects::kTriangle));
transforms.push_back(trs.ToTransform());
break;
case mjGEOM_FLEX:
AddSkinFlexMesh(renderable, model_objects, geom.objid);
meshes.push_back(GetSkinFlexMesh(model_objects, geom.objid));
// Flexes are defined in global space.
transforms.push_back(mat4f());
break;
case mjGEOM_SKIN:
AddSkinFlexMesh(renderable, model_objects, geom.objid);
meshes.push_back(GetSkinFlexMesh(model_objects, geom.objid));
// Skins are defined in global space.
transforms.push_back(mat4f());
break;
case mjGEOM_NONE:
case mjGEOM_LABEL:
@@ -193,124 +314,8 @@ static void PrepareGeomMeshes(Renderable& renderable, const mjvGeom& geom,
mju_warning("Unsupported geom type: %d", geom.type);
break;
}
}
static void SetGeomTransform(Renderable& renderable, const mjvGeom& geom) {
// Flex and skin geometries are in global space.
if (geom.type == mjGEOM_FLEX || geom.type == mjGEOM_SKIN) {
return;
}
mat4 transform = mat4(ReadMat3(geom.mat), ReadFloat3(geom.pos));
renderable.SetLayerMask(geom.category);
float3 size = ReadFloat3(geom.size);
filament::TransformManager& tm =
renderable.GetEngine()->getTransformManager();
for (int j = 0; j < renderable.GetNumMeshes(); ++j) {
const utils::Entity& entity = renderable[j];
// Update object transform.
mat4 entity_transform = transform;
// Some built-in drawables are composed of multiple entities. For example,
// capsules are a combination of a open tube and two dome end caps.
if (geom.type == mjGEOM_CYLINDER) {
// Cylinders are a tube with two disks at the ends. The "bottom" disk is
// rotated so that the normals point outwards.
if (j == kCylinderTopDisk) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
} else if (j == kCylinderBottomDisk) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *= mat4::rotation(std::numbers::pi, float3{1, 0, 0});
}
} else if (geom.type == mjGEOM_CAPSULE) {
// Capsules are a tube with two domes at the ends. 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 * (size.x + size.y);
if (j == kCapsuleTopDome) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
entity_transform *= mat4::scaling(float3{1, 1, xz_size / size.z});
} else if (j == kCapsuleBottomDome) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *= mat4::rotation(std::numbers::pi, float3{1, 0, 0});
entity_transform *= mat4::scaling(float3{1, 1, xz_size / size.z});
}
} else if (geom.type == mjGEOM_ARROW) {
// An arrow is a tube with a cone at the end and a disk cap at the other
// end. Because the cone head's base is larger than the tube, an extra
// disk is added to the base of the cone. This disk is rotated such that
// its normal points outwards.
entity_transform *= mat4::scaling(float3{1, 1, kArrowScale});
entity_transform *= mat4::translation(float3{0, 0, size.z});
if (j == kArrow0Cone) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
} else if (j == kArrow0ConeDisk) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
entity_transform *= mat4::rotation(std::numbers::pi, float3{1, 0, 0});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
} else if (j == kArrow0BottomDisk) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *= mat4::rotation(std::numbers::pi, float3{1, 0, 0});
}
} else if (geom.type == mjGEOM_ARROW1) {
// An arrow1 is a tube with a cone at the end and a disk cap at the other
// end.
entity_transform *= mat4::scaling(float3{1, 1, kArrowScale});
entity_transform *= mat4::translation(float3{0, 0, size.z});
if (j == kArrow1Cone) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
} else if (j == kArrow1BottomDisk) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *= mat4::rotation(std::numbers::pi, float3{1, 0, 0});
}
} else if (geom.type == mjGEOM_ARROW2) {
// An arrow2 is a tube with a cone at both ends. Like the standard arrow,
// an extra disk is added to the base of each cone.
entity_transform *= mat4::scaling(float3{1, 1, kArrowScale});
entity_transform *= mat4::translation(float3{0, 0, size.z});
if (j == kArrow2TopCone) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
} else if (j == kArrow2BottomCone) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *= mat4::rotation(std::numbers::pi, float3{1, 0, 0});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
} else if (j == kArrow2TopConeDisk) {
entity_transform *= mat4::translation(float3{0, 0, size.z});
entity_transform *= mat4::rotation(std::numbers::pi, float3{1, 0, 0});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
} else if (j == kArrow2BottomConeDisk) {
entity_transform *= mat4::translation(float3{0, 0, -size.z});
entity_transform *=
mat4::scaling(float3{kArrowHeadSize, kArrowHeadSize, 1.0f});
}
}
if (geom.type == mjGEOM_PLANE) {
const bool is_infinite = !(size.x > 0 && size.y > 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;
entity_transform *=
mat4::scaling(float3{plane_scale, plane_scale, 1.0f});
} else {
// Regular planes are scaled by geom.size.
entity_transform *= mat4::scaling(float3{size.x, size.y, 1.0f});
}
} else if (geom.type != mjGEOM_MESH && geom.type != mjGEOM_HFIELD) {
entity_transform *= mat4::scaling(size);
}
tm.setTransform(tm.getInstance(entity), entity_transform);
}
renderable.SetMeshes(meshes, transforms);
}
static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
@@ -334,10 +339,12 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
enable_reflection && geom.reflectance > 0 && params.color.a == 1.0f;
}
}
renderable.SetWireframe(scene->flags[mjRND_WIREFRAME]);
renderable.SetLayerMask(geom.category);
if (geom.category == mjCAT_DECOR) {
renderable.SetCastShadows(false);
renderable.SetReceiveShadows(false);
} else {
renderable.SetWireframe(scene->flags[mjRND_WIREFRAME]);
}
MaterialTextures textures;
@@ -471,10 +478,7 @@ std::unique_ptr<Renderable> CreateGeomRenderable(
config.shading_model = shading_model;
auto renderable = std::make_unique<Renderable>(object_mgr, config);
// The order of these calls is important. e.g. We need to create the filament
// renderable entities before we can set their transform.
PrepareGeomMeshes(*renderable, geom, scene, model_objs);
SetGeomTransform(*renderable, geom);
UpdateGeomMaterial(*renderable, geom, scene, model_objs, object_mgr, headpos);
return renderable;
@@ -277,13 +277,11 @@ void SceneView::Render(filament::Renderer* renderer,
// Render reflection passes.
if (request.draw_mode == DrawMode::Color) {
filament::TransformManager& tm = engine_->getTransformManager();
for (size_t i = 0; i < reflectives_.size(); ++i) {
Renderable* renderable = reflectives_[i];
// We assume the 0th entity is the reflective entity.
const utils::Entity entity = (*renderable)[0];
const mat4 transform(tm.getTransform(tm.getInstance(entity)));
mat4 transform(renderable->GetTransform());
SetupReflectionCamera(transform, camera_, reflect_camera_);
// Hide reflective surface from its own reflection pass.
@@ -305,8 +303,7 @@ void SceneView::Render(filament::Renderer* renderer,
if (request.enable_ux) {
ux_camera_->setProjection(filament::Camera::Projection::ORTHO, 0.0f,
viewport.width / request.gui_scale,
viewport.height / request.gui_scale, 0.0f, 0.0f,
viewport.width, viewport.height, 0.0f, 0.0f,
1.0f);
ux_view_->setRenderTarget(render_target);
renderer->render(ux_view_);
@@ -69,8 +69,6 @@ class SceneView {
RenderTarget* target = nullptr;
// Whether or not to render the UX as a separate pass.
bool enable_ux = false;
// The scale factor to use for UX rendering.
float gui_scale = 1.0f;
};
// Renders the scene.