diff --git a/src/experimental/filament/filament/filament_context.cc b/src/experimental/filament/filament/filament_context.cc index 09f26cfa..427306b8 100644 --- a/src/experimental/filament/filament/filament_context.cc +++ b/src/experimental/filament/filament/filament_context.cc @@ -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; diff --git a/src/experimental/filament/filament/imgui_bridge.cc b/src/experimental/filament/filament/imgui_bridge.cc index 5ee492eb..97dd852a 100644 --- a/src/experimental/filament/filament/imgui_bridge.cc +++ b/src/experimental/filament/filament/imgui_bridge.cc @@ -21,6 +21,8 @@ #include #include +#include +#include #include #include #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); diff --git a/src/experimental/filament/filament/imgui_bridge.h b/src/experimental/filament/filament/imgui_bridge.h index 4e36b333..54b5f494 100644 --- a/src/experimental/filament/filament/imgui_bridge.h +++ b/src/experimental/filament/filament/imgui_bridge.h @@ -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); diff --git a/src/experimental/filament/filament/renderable.cc b/src/experimental/filament/filament/renderable.cc index 3e518938..2a9659ce 100644 --- a/src/experimental/filament/filament/renderable.cc +++ b/src/experimental/filament/filament/renderable.cc @@ -16,20 +16,26 @@ #include #include +#include #include #include #include #include +#include +#include #include #include #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 meshes, + std::span transforms) { + if (meshes.size() != transforms.size()) { + mju_error("Number of meshes does not match number of transforms."); } - if (index < 0 || index >= static_cast(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(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()); diff --git a/src/experimental/filament/filament/renderable.h b/src/experimental/filament/filament/renderable.h index 50674c82..380eea3b 100644 --- a/src/experimental/filament/filament/renderable.h +++ b/src/experimental/filament/filament/renderable.h @@ -16,13 +16,16 @@ #define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_RENDERABLE_H_ #include +#include #include #include #include +#include #include #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 meshes, + std::span 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 entities_; - std::vector meshes_; + std::vector parts_; + filament::math::mat4f transform_; + 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 f68f620e..3b15b32d 100644 --- a/src/experimental/filament/filament/scene_geom_util.cc +++ b/src/experimental/filament/filament/scene_geom_util.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -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 meshes; + std::vector 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(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(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 CreateGeomRenderable( config.shading_model = shading_model; auto renderable = std::make_unique(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; diff --git a/src/experimental/filament/filament/scene_view.cc b/src/experimental/filament/filament/scene_view.cc index 18d950d7..efcbd76f 100644 --- a/src/experimental/filament/filament/scene_view.cc +++ b/src/experimental/filament/filament/scene_view.cc @@ -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_); diff --git a/src/experimental/filament/filament/scene_view.h b/src/experimental/filament/filament/scene_view.h index f50a86fa..a9a2874b 100644 --- a/src/experimental/filament/filament/scene_view.h +++ b/src/experimental/filament/filament/scene_view.h @@ -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.