diff --git a/src/experimental/studio/app.cc b/src/experimental/studio/app.cc index a6bfde4d..134bd91f 100644 --- a/src/experimental/studio/app.cc +++ b/src/experimental/studio/app.cc @@ -811,6 +811,8 @@ void App::HandleKeyboardEvents() { ToggleFlag(vis_options_.flags[mjVIS_COM]); } else if (!is_freecam_wasd && ImGui_IsChordJustPressed(ImGuiKey_D)) { ToggleFlag(vis_options_.flags[mjVIS_STATIC]); + } else if (!is_freecam_wasd && ImGui_IsChordJustPressed(ImGuiKey_W)) { + ToggleFlag(renderer_->GetRenderFlags()[mjRND_WIREFRAME]); } else if (ImGui_IsChordJustPressed(ImGuiKey_Semicolon)) { ToggleFlag(vis_options_.flags[mjVIS_SKIN]); } else if (ImGui_IsChordJustPressed(ImGuiKey_GraveAccent)) { @@ -2152,6 +2154,7 @@ void App::HelpGui() { ImGui::Text("Tendon"); ImGui::Text("Texture"); ImGui::Text("Transparent"); + ImGui::Text("Wireframe"); ImGui::NextColumn(); ImGui::Indent(indent); @@ -2181,6 +2184,7 @@ void App::HelpGui() { ImGui::Text("V"); ImGui::Text("X"); ImGui::Text("T"); + ImGui::Text("W"); ImGui::Columns(); } diff --git a/src/render/filament/core/mesh.cc b/src/render/filament/core/mesh.cc index 1f045e1b..ecdb848b 100644 --- a/src/render/filament/core/mesh.cc +++ b/src/render/filament/core/mesh.cc @@ -146,6 +146,9 @@ Mesh::~Mesh() { if (index_buffer_) { engine_->destroy(index_buffer_); } + if (wireframe_index_buffer_) { + engine_->destroy(wireframe_index_buffer_); + } if (vertex_buffer_) { engine_->destroy(vertex_buffer_); } @@ -308,6 +311,55 @@ void Mesh::UpdateIndexBuffer(const mjrfMeshData& data) { // callback for the vertex buffer will call release_callbacks_. filament::backend::BufferDescriptor desc(indices, num_bytes); index_buffer_->setBuffer(*engine_, std::move(desc)); + + UpdateWireframeIndexBuffer(indices); +} + +template +static void FillWireframeIndices(const void* src, int num_indices, T* out) { + const T* in = static_cast(src); + for (int i = 0; i < num_indices - 2; i += 3) { + *out++ = in[i + 0]; + *out++ = in[i + 1]; + *out++ = in[i + 1]; + *out++ = in[i + 2]; + *out++ = in[i + 2]; + *out++ = in[i + 0]; + } +} + +void Mesh::UpdateWireframeIndexBuffer(const void* src_indices) { + // Line meshes are their own wireframe. Interior edges shared between two + // triangles are deliberately not deduplicated so that triangle index ranges + // map to line index ranges by doubling; see GetWireframeIndexBuffer(). + if (config_.primitive_type != mjMESH_PRIMITIVE_TYPE_TRIANGLES) { + return; + } + const int num_line_indices = 2 * config_.max_indices; + if (wireframe_index_buffer_ == nullptr) { + filament::IndexBuffer::Builder ib_builder; + ib_builder.indexCount(num_line_indices); + ib_builder.bufferType(config_.index_type == mjINDEX_TYPE_U16 + ? filament::IndexBuffer::IndexType::USHORT + : filament::IndexBuffer::IndexType::UINT); + wireframe_index_buffer_ = ib_builder.build(*engine_); + } + + const int element_size = config_.index_type == mjINDEX_TYPE_U16 + ? sizeof(uint16_t) + : sizeof(uint32_t); + const int num_bytes = num_line_indices * element_size; + std::byte* lines = new std::byte[num_bytes]; + shared_state_->callbacks.push_back([=]() { delete[] lines; }); + if (config_.index_type == mjINDEX_TYPE_U16) { + FillWireframeIndices(src_indices, config_.max_indices, + reinterpret_cast(lines)); + } else { + FillWireframeIndices(src_indices, config_.max_indices, + reinterpret_cast(lines)); + } + filament::backend::BufferDescriptor desc(lines, num_bytes); + wireframe_index_buffer_->setBuffer(*engine_, std::move(desc)); } void Mesh::UpdateBounds(const mjrfMeshData& data) { @@ -361,6 +413,10 @@ filament::IndexBuffer* Mesh::GetFilamentIndexBuffer() const { return index_buffer_; } +filament::IndexBuffer* Mesh::GetWireframeIndexBuffer() const { + return wireframe_index_buffer_; +} + filament::VertexBuffer* Mesh::GetFilamentVertexBuffer() const { return vertex_buffer_; } diff --git a/src/render/filament/core/mesh.h b/src/render/filament/core/mesh.h index 77a0661b..21cfd472 100644 --- a/src/render/filament/core/mesh.h +++ b/src/render/filament/core/mesh.h @@ -49,6 +49,12 @@ class Mesh : public mjrfMesh { // Returns the filament IndexBuffer for the mesh. filament::IndexBuffer* GetFilamentIndexBuffer() const; + // Returns the filament IndexBuffer of line indices for wireframe rendering, + // or nullptr for non-triangle meshes. Each triangle contributes its three + // edges, so a triangle index range [offset, count) maps to the line index + // range [2*offset, 2*count). + filament::IndexBuffer* GetWireframeIndexBuffer() const; + // Returns the filament VertexBuffer for the mesh. filament::VertexBuffer* GetFilamentVertexBuffer() const; @@ -75,6 +81,7 @@ class Mesh : public mjrfMesh { void UpdateVertexBuffer(const mjrfMeshData& data); void UpdateIndexBuffer(const mjrfMeshData& data); + void UpdateWireframeIndexBuffer(const void* indices); void UpdateBounds(const mjrfMeshData& data); filament::math::float4* BuildOrientationsFromNormals(int num_vertices, @@ -86,6 +93,7 @@ class Mesh : public mjrfMesh { mjrfMeshConfig config_; filament::IndexBuffer* index_buffer_ = nullptr; + filament::IndexBuffer* wireframe_index_buffer_ = nullptr; filament::VertexBuffer* vertex_buffer_ = nullptr; std::optional bounds_; diff --git a/src/render/filament/core/renderable.cc b/src/render/filament/core/renderable.cc index 7bbb30f7..96dda1be 100644 --- a/src/render/filament/core/renderable.cc +++ b/src/render/filament/core/renderable.cc @@ -461,11 +461,22 @@ void Renderable::SetWireframe(bool wireframe) { 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); + filament::IndexBuffer* wireframe_index_buffer = + part.mesh->GetWireframeIndexBuffer(); + if (wireframe && wireframe_index_buffer) { + // Triangle meshes have a dedicated line index buffer enumerating the + // three edges of each triangle, addressed by doubling the range. + rm.setGeometryAt(rm.getInstance(part.entity), 0, kWireframeType, + vertex_buffer, wireframe_index_buffer, + 2 * part.elem_offset, 2 * part.elem_count); + } else { + // Line meshes are drawn as-is in both modes. + rm.setGeometryAt(rm.getInstance(part.entity), 0, + wireframe ? kWireframeType + : part.mesh->GetPrimitiveType(), + vertex_buffer, part.mesh->GetFilamentIndexBuffer(), + part.elem_offset, part.elem_count); + } } } }