Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
48bcdf22cThis file is pinned to the selected release. Download the original to open it in your design tool.
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "experimental/platform/gui.h"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <limits>
#include <string>
#include <vector>
#include <imgui.h>
#include <imgui_internal.h>
#include <implot.h>
#include <mujoco/mujoco.h>
#include "experimental/platform/helpers.h"
#include "experimental/platform/imgui_widgets.h"
namespace mujoco::platform {
static ImVec2 GetFlexElementSize(int num_cols) {
const float width = (ImGui::GetContentRegionAvail().x / num_cols) -
ImGui::GetStyle().FramePadding.x * 2;
return ImVec2(width, 0);
}
void SetupTheme(GuiTheme theme) {
ImGuiStyle& s = ImGui::GetStyle();
ImVec4* c = s.Colors;
if (theme == GuiTheme::kDark) {
ImGui::StyleColorsDark(&s);
c[ImGuiCol_Text] = ImVec4(1.00, 1.00, 1.00, 1.00);
c[ImGuiCol_TextDisabled] = ImVec4(0.40, 0.40, 0.40, 1.00);
c[ImGuiCol_ChildBg] = ImVec4(0.25, 0.25, 0.25, 1.00);
c[ImGuiCol_WindowBg] = ImVec4(0.25, 0.25, 0.25, 1.00);
c[ImGuiCol_PopupBg] = ImVec4(0.25, 0.25, 0.25, 1.00);
c[ImGuiCol_Border] = ImVec4(0.12, 0.12, 0.12, 0.71);
c[ImGuiCol_BorderShadow] = ImVec4(1.00, 1.00, 1.00, 0.06);
c[ImGuiCol_FrameBg] = ImVec4(0.42, 0.42, 0.42, 0.54);
c[ImGuiCol_FrameBgHovered] = ImVec4(0.42, 0.42, 0.42, 0.40);
c[ImGuiCol_FrameBgActive] = ImVec4(0.56, 0.56, 0.56, 0.67);
c[ImGuiCol_TitleBg] = ImVec4(0.19, 0.19, 0.19, 1.00);
c[ImGuiCol_TitleBgActive] = ImVec4(0.22, 0.22, 0.22, 1.00);
c[ImGuiCol_TitleBgCollapsed] = ImVec4(0.17, 0.17, 0.17, 0.90);
c[ImGuiCol_MenuBarBg] = ImVec4(0.34, 0.34, 0.34, 1.00);
c[ImGuiCol_ScrollbarBg] = ImVec4(0.24, 0.24, 0.24, 0.53);
c[ImGuiCol_ScrollbarGrab] = ImVec4(0.41, 0.41, 0.41, 1.00);
c[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.52, 0.52, 0.52, 1.00);
c[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.76, 0.76, 0.76, 1.00);
c[ImGuiCol_CheckMark] = ImVec4(0.65, 0.65, 0.65, 1.00);
c[ImGuiCol_SliderGrab] = ImVec4(0.52, 0.52, 0.52, 1.00);
c[ImGuiCol_SliderGrabActive] = ImVec4(0.64, 0.64, 0.64, 1.00);
c[ImGuiCol_Button] = ImVec4(0.54, 0.54, 0.54, 0.35);
c[ImGuiCol_ButtonHovered] = ImVec4(0.52, 0.52, 0.52, 0.59);
c[ImGuiCol_ButtonActive] = ImVec4(0.76, 0.76, 0.76, 1.00);
c[ImGuiCol_Header] = ImVec4(0.38, 0.38, 0.38, 1.00);
c[ImGuiCol_HeaderHovered] = ImVec4(0.47, 0.47, 0.47, 1.00);
c[ImGuiCol_HeaderActive] = ImVec4(0.76, 0.76, 0.76, 0.77);
c[ImGuiCol_Separator] = ImVec4(0.00, 0.00, 0.00, 0.18);
c[ImGuiCol_SeparatorHovered] = ImVec4(0.70, 0.67, 0.60, 0.29);
c[ImGuiCol_SeparatorActive] = ImVec4(0.70, 0.67, 0.60, 0.67);
c[ImGuiCol_ResizeGrip] = ImVec4(0.26, 0.59, 0.98, 0.25);
c[ImGuiCol_ResizeGripHovered] = ImVec4(0.26, 0.59, 0.98, 0.67);
c[ImGuiCol_ResizeGripActive] = ImVec4(0.26, 0.59, 0.98, 0.95);
c[ImGuiCol_PlotLines] = ImVec4(0.61, 0.61, 0.61, 1.00);
c[ImGuiCol_PlotLinesHovered] = ImVec4(1.00, 0.43, 0.35, 1.00);
c[ImGuiCol_PlotHistogram] = ImVec4(0.90, 0.70, 0.00, 1.00);
c[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00, 0.60, 0.00, 1.00);
c[ImGuiCol_TextSelectedBg] = ImVec4(0.73, 0.73, 0.73, 0.35);
c[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80, 0.80, 0.80, 0.35);
c[ImGuiCol_DragDropTarget] = ImVec4(1.00, 1.00, 0.00, 0.90);
c[ImGuiCol_NavHighlight] = ImVec4(0.26, 0.59, 0.98, 1.00);
c[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00, 1.00, 1.00, 0.70);
c[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80, 0.80, 0.80, 0.20);
c[ImGuiCol_DockingEmptyBg] = ImVec4(0.38, 0.38, 0.38, 1.00);
c[ImGuiCol_Tab] = ImVec4(0.25, 0.25, 0.25, 1.00);
c[ImGuiCol_TabHovered] = ImVec4(0.40, 0.40, 0.40, 1.00);
c[ImGuiCol_TabActive] = ImVec4(0.33, 0.33, 0.33, 1.00);
c[ImGuiCol_TabUnfocused] = ImVec4(0.25, 0.25, 0.25, 1.00);
c[ImGuiCol_TabUnfocusedActive] = ImVec4(0.33, 0.33, 0.33, 1.00);
c[ImGuiCol_DockingPreview] = ImVec4(0.85, 0.85, 0.85, 0.28);
c[ImGuiCol_WindowBg].w = 1.0f;
} else if (theme == GuiTheme::kLight) {
ImGui::StyleColorsLight(&s);
ImVec4 white = ImVec4(1.00, 1.00, 1.00, 1.00);
ImVec4 transparent = ImVec4(0.00, 0.00, 0.00, 0.00);
ImVec4 dark = ImVec4(0.00, 0.00, 0.00, 0.20);
ImVec4 darker = ImVec4(0.00, 0.00, 0.00, 0.50);
ImVec4 background = ImVec4(0.95, 0.95, 0.95, 1.00);
ImVec4 text = ImVec4(0.10, 0.10, 0.10, 1.00);
ImVec4 border = ImVec4(0.60, 0.60, 0.60, 1.00);
ImVec4 grab = ImVec4(0.69, 0.69, 0.69, 1.00);
ImVec4 header = ImVec4(0.86, 0.86, 0.86, 1.00);
ImVec4 active = ImVec4(0.00, 0.47, 0.84, 1.00);
ImVec4 hover = ImVec4(0.00, 0.47, 0.84, 0.20);
c[ImGuiCol_Text] = text;
c[ImGuiCol_WindowBg] = background;
c[ImGuiCol_ChildBg] = background;
c[ImGuiCol_PopupBg] = white;
c[ImGuiCol_Border] = border;
c[ImGuiCol_BorderShadow] = transparent;
c[ImGuiCol_Button] = header;
c[ImGuiCol_ButtonHovered] = hover;
c[ImGuiCol_ButtonActive] = active;
c[ImGuiCol_FrameBg] = white;
c[ImGuiCol_FrameBgHovered] = hover;
c[ImGuiCol_FrameBgActive] = active;
c[ImGuiCol_MenuBarBg] = header;
c[ImGuiCol_Header] = header;
c[ImGuiCol_HeaderHovered] = hover;
c[ImGuiCol_HeaderActive] = active;
c[ImGuiCol_CheckMark] = text;
c[ImGuiCol_SliderGrab] = grab;
c[ImGuiCol_SliderGrabActive] = darker;
c[ImGuiCol_ScrollbarBg] = header;
c[ImGuiCol_ScrollbarGrab] = grab;
c[ImGuiCol_ScrollbarGrabHovered] = dark;
c[ImGuiCol_ScrollbarGrabActive] = darker;
} else {
ImGui::StyleColorsDark(&s);
ImVec4 black = ImVec4(0.00, 0.00, 0.00, 1.0);
ImVec4 window = ImVec4(0.25, 0.25, 0.25, 1.0);
ImVec4 font_active = ImVec4(1.00, 1.00, 1.00, 1.0);
ImVec4 font_inactive = ImVec4(0.50, 0.50, 0.50, 1.0);
ImVec4 thumb = ImVec4(0.12, 0.12, 0.12, 1.0);
ImVec4 section = ImVec4(0.40, 0.15, 0.15, 1.0);
ImVec4 button = ImVec4(0.60, 0.40, 0.40, 1.0);
ImVec4 check = ImVec4(0.40, 0.40, 0.70, 1.0);
ImVec4 frame = ImVec4(0.40, 0.30, 0.40, 1.0);
ImVec4 slider = ImVec4(0.60, 0.40, 0.60, 1.0);
c[ImGuiCol_WindowBg] = window;
c[ImGuiCol_ChildBg] = black;
c[ImGuiCol_PopupBg] = window;
c[ImGuiCol_Text] = font_active;
c[ImGuiCol_TextDisabled] = font_inactive;
c[ImGuiCol_CheckMark] = font_active;
c[ImGuiCol_Header] = section;
c[ImGuiCol_HeaderHovered] = section;
c[ImGuiCol_HeaderActive] = section;
c[ImGuiCol_TitleBgActive] = window;
c[ImGuiCol_ScrollbarBg] = window;
c[ImGuiCol_ScrollbarGrab] = thumb;
c[ImGuiCol_ScrollbarGrabHovered] = thumb;
c[ImGuiCol_ScrollbarGrabActive] = thumb;
c[ImGuiCol_FrameBg] = frame;
c[ImGuiCol_FrameBgHovered] = frame;
c[ImGuiCol_FrameBgActive] = frame;
c[ImGuiCol_SliderGrab] = slider;
c[ImGuiCol_SliderGrabActive] = slider;
c[ImGuiCol_Button] = window;
c[ImGuiCol_ButtonHovered] = button;
c[ImGuiCol_ButtonActive] = button;
c[ImGuiCol_Tab] = window;
c[ImGuiCol_TabHovered] = check;
c[ImGuiCol_TabSelected] = check;
c[ImGuiCol_TabDimmed] = window;
c[ImGuiCol_TabDimmedSelected] = check;
}
int hspacing = 4;
int vspacing = 6;
float rounding = 4.0f;
s.DisplaySafeAreaPadding = ImVec2(0, 0);
s.WindowPadding = ImVec2(hspacing, vspacing);
s.FramePadding = ImVec2(hspacing, vspacing);
s.ItemSpacing = ImVec2(hspacing, vspacing);
s.ItemInnerSpacing = ImVec2(hspacing, vspacing);
s.WindowRounding = rounding;
s.FrameRounding = rounding;
s.TabRounding = rounding;
s.ScrollbarRounding = rounding;
s.ChildRounding = rounding;
s.GrabRounding = rounding;
s.PopupRounding = rounding;
s.WindowBorderSize = 0.0f;
s.FrameBorderSize = 1.0f;
s.PopupBorderSize = 1.0f;
s.IndentSpacing = 20.0f;
s.ScrollbarSize = 12.0f;
s.GrabMinSize = 5.0f;
s.WindowMenuButtonPosition = ImGuiDir_None;
s.TabCloseButtonMinWidthSelected = 0.0f;
s.DockingNodeHasCloseButton = false;
}
ImVec4 ConfigureDockingLayout() {
ImGuiViewport* viewport = ImGui::GetMainViewport();
const float scale = ImGui::GetWindowDpiScale();
const float kOptionsRelWidth = 0.22f;
const float kInspectorRelWidth = 0.22f;
const float kStatsRelHeight = 0.3f;
const float kToolsBarHeight = 48.f * scale;
const float kStatusBarHeight = 32.f * scale;
const ImVec2 dockspace_pos{
viewport->WorkPos.x,
viewport->WorkPos.y + kToolsBarHeight
};
const ImVec2 dockspace_size{
viewport->WorkSize.x,
viewport->WorkSize.y - kToolsBarHeight - kStatusBarHeight
};
ImGuiID root = ImGui::GetID("Root");
const bool first_time = (ImGui::DockBuilderGetNode(root) == nullptr);
if (first_time) {
ImGui::DockBuilderRemoveNode(root);
ImGui::DockBuilderAddNode(root, ImGuiDockNodeFlags_DockSpace);
ImGui::DockBuilderSetNodeSize(root, dockspace_size);
// Slice up the main dock space.
ImGuiID main = root;
ImGuiID options = 0;
ImGui::DockBuilderSplitNode(main, ImGuiDir_Left, kOptionsRelWidth,
&options, &main);
ImGuiID inspector = 0;
ImGui::DockBuilderSplitNode(main, ImGuiDir_Right, kInspectorRelWidth,
&inspector, &main);
ImGuiID stats = 0;
ImGui::DockBuilderSplitNode(options, ImGuiDir_Down, kStatsRelHeight,
&stats, &options);
ImGuiID properties = 0;
ImGui::DockBuilderSplitNode(inspector, ImGuiDir_Down, kStatsRelHeight,
&properties, &inspector);
ImGui::DockBuilderDockWindow("Dockspace", main);
ImGui::DockBuilderDockWindow("Options", options);
ImGui::DockBuilderDockWindow("Explorer", inspector);
ImGui::DockBuilderDockWindow("Inspector", inspector);
ImGui::DockBuilderDockWindow("Properties", properties);
ImGui::DockBuilderDockWindow("Stats", stats);
ImGui::DockBuilderFinish(root);
}
// Create a dummy window filling the entire workspace in which we can perform
// docking.
ImGui::SetNextWindowPos(dockspace_pos);
ImGui::SetNextWindowSize(dockspace_size);
ImGui::SetNextWindowViewport(viewport->ID);
const ImGuiWindowFlags kWorkspaceFlags =
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBackground;
const ImGuiWindowFlags kFixedFlags = ImGuiWindowFlags_NoTitleBar |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoDocking;
// Main workspace area in which we can dock other windows.
{
platform::ScopedStyle style;
style.Var(ImGuiStyleVar_WindowRounding, 0.0f);
style.Var(ImGuiStyleVar_WindowBorderSize, 0.0f);
style.Var(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin("Dockspace", nullptr, kWorkspaceFlags);
const ImGuiDockNodeFlags kDockSpaceFlags =
ImGuiDockNodeFlags_PassthruCentralNode |
ImGuiDockNodeFlags_NoDockingOverCentralNode |
ImGuiDockNodeFlags_AutoHideTabBar;
ImGui::DockSpace(root, ImVec2(0.0f, 0.0f), kDockSpaceFlags);
ImGui::End();
}
// Toolbar is fixed at the top.
{
platform::ScopedStyle style;
style.Var(ImGuiStyleVar_WindowBorderSize, 1.0f);
style.Var(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::SetNextWindowPos(viewport->WorkPos, ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, kToolsBarHeight), ImGuiCond_Always);
ImGui::Begin("ToolBar", nullptr, kFixedFlags);
ImGui::End();
}
// StatusBar is fixed at the bottom.
{
platform::ScopedStyle style;
style.Var(ImGuiStyleVar_WindowBorderSize, 1.0f);
style.Var(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::SetNextWindowPos(ImVec2(0, viewport->Size.y - kStatusBarHeight), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x, kStatusBarHeight), ImGuiCond_Always);
ImGui::Begin("StatusBar", nullptr, kFixedFlags);
ImGui::End();
}
const int settings_width = dockspace_size.x * kOptionsRelWidth;
const int inspector_width = dockspace_size.x * kInspectorRelWidth;
const float workspace_x = dockspace_pos.x + settings_width;
const float workspace_y = dockspace_pos.y;
const float workspace_w = dockspace_size.x - settings_width - inspector_width;
const float workspace_h = dockspace_size.y;
return ImVec4(workspace_x, workspace_y, workspace_w, workspace_h);
}
void SensorGui(const mjModel* model, const mjData* data) {
if (model->nsensor == 0) {
return;
}
ImPlot::PushStyleVar(ImPlotStyleVar_FitPadding, ImVec2(0.1f, 0.1f));
if (ImPlot::BeginPlot("Sensors", ImVec2(-1, 0),
ImPlotFlags_NoLegend | ImPlotFlags_NoMouseText)) {
ImPlot::SetupLegend(ImPlotLocation_NorthEast, ImPlotLegendFlags_None);
ImPlot::SetupAxis(ImAxis_X1, "sensor",
ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_NoLabel);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, 5, ImPlotCond_Once);
ImPlot::SetupAxis(ImAxis_Y1, "value",
ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_NoLabel);
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.1f");
ImPlot::SetupAxisLimits(ImAxis_Y1, -100, 100, ImPlotCond_Once);
ImPlot::SetupFinish();
// The values to be plotted.
std::vector<ImPlotPoint> sensor_values;
// The x-value of the bar to be plotted. Multiple bars will belong to the
// same sensor (i.e. the sensor_dim), but each group of bars will be appear
// in sequence along the x-axis.
float x_value = 0.f;
// The index of the sensor being plotted, based on sensor_type.
int sensor_index = 0;
// Function that plots the current group of sensor bars.
auto plot_lines = [](int sensor_idx, const ImPlotPoint* values, int count) {
constexpr float bar_weight = 5.0f;
ImPlot::SetNextLineStyle(IMPLOT_AUTO_COL, bar_weight);
std::string sensor_label = "Sensor " + std::to_string(sensor_idx);
ImPlot::PlotLine(sensor_label.c_str(), &values->x, &values->y, count,
ImPlotLineFlags_Segments, 0, 2 * sizeof(double));
};
for (int n = 0; n < model->nsensor; n++) {
if (n > 0 && model->sensor_type[n] != model->sensor_type[n - 1]) {
plot_lines(sensor_index, sensor_values.data(), sensor_values.size());
sensor_values.clear();
++sensor_index;
}
const int adr = model->sensor_adr[n];
const int dim = model->sensor_dim[n];
const mjtNum cutoff =
(model->sensor_cutoff[n] > 0 ? model->sensor_cutoff[n] : 1);
for (int i = 0; i < dim; ++i) {
sensor_values.push_back({x_value, 0});
sensor_values.push_back({x_value, data->sensordata[adr + i] / cutoff});
x_value += 1.f;
}
}
// Ensure the last group of sensors is plotted.
plot_lines(sensor_index, sensor_values.data(), sensor_values.size());
ImPlot::EndPlot();
}
ImPlot::PopStyleVar();
}
void StateGui(const mjModel* model, mjData* data, std::vector<mjtNum>& state,
int& state_sig, float min_width) {
const float available_width =
ImGui::GetContentRegionAvail().x - ImGui::GetTreeNodeToLabelSpacing();
const int num_cols = std::clamp(
static_cast<int>(std::floor(available_width / min_width)), 1, 4);
const ImVec2 size = GetFlexElementSize(num_cols);
ImGui::Unindent(0.5f * ImGui::GetTreeNodeToLabelSpacing());
// State component names and tooltips.
static constexpr const char* name_and_tooltip[][2] = {
{"TIME", "Time"},
{"QPOS", "Position"},
{"QVEL", "Velocity"},
{"ACT", "Actuator activation"},
{"WARMSTART", "Acceleration used for warmstart"},
{"CTRL", "Control"},
{"QFRC_APPLIED", "Applied generalized force"},
{"XFRC_APPLIED", "Applied Cartesian force/torque"},
{"EQ_ACTIVE", "Enable/disable constraints"},
{"MOCAP_POS", "Positions of mocap bodies"},
{"MOCAP_QUAT", "Orientations of mocap bodies"},
{"USERDATA", "User data"},
{"PLUGIN", "Plugin state"},
};
int prev_state_sig = state_sig;
// State component checkboxes.
if (ImGui::BeginTable("##StateSignature", num_cols)) {
for (int i = 0; i < mjNSTATE; ++i) {
ImGui::TableNextColumn();
bool checked = state_sig & (1 << i);
ImGui::Checkbox(name_and_tooltip[i][0], &checked);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", name_and_tooltip[i][1]);
}
state_sig = checked ? (state_sig | (1 << i)) : (state_sig & ~(1 << i));
}
ImGui::EndTable();
}
// Buttons to select commonly used state signatures.
if (ImGui::BeginTable("##CommonSignatures", num_cols)) {
ImGui::TableNextColumn();
if (ImGui::Button("Physics", size)) {
state_sig = (state_sig == mjSTATE_PHYSICS) ? 0 : mjSTATE_PHYSICS;
}
ImGui::TableNextColumn();
if (ImGui::Button("Full Physics", size)) {
state_sig = (state_sig == mjSTATE_FULLPHYSICS) ? 0 : mjSTATE_FULLPHYSICS;
}
ImGui::TableNextColumn();
if (ImGui::Button("User", size)) {
state_sig = (state_sig == mjSTATE_USER) ? 0 : mjSTATE_USER;
}
ImGui::TableNextColumn();
if (ImGui::Button("Integration", size)) {
state_sig = (state_sig == mjSTATE_INTEGRATION) ? 0 : mjSTATE_INTEGRATION;
}
ImGui::EndTable();
}
if (state_sig != prev_state_sig) {
const int size = mj_stateSize(model, state_sig);
state.resize(size);
}
if (state.empty()) {
// The state size is 0, let the user know why.
ImGui::Separator();
ImGui::BeginDisabled();
ImGui::TextWrapped(
state_sig == 0
? "No state components are selected."
: "Selected state components do not exist in the model.");
ImGui::EndDisabled();
} else {
mj_getState(model, data, state.data(), state_sig);
bool changed = false;
if (ImGui::BeginTable(
"State", 3,
ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable |
ImGuiTableFlags_ScrollY,
ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * 20))) {
ImGui::TableSetupColumn("Index");
ImGui::TableSetupColumn("Name");
ImGui::TableSetupColumn("Value");
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableHeadersRow();
ImGuiListClipper clipper;
clipper.Begin(state.size());
while (clipper.Step()) {
int global = 0;
for (int i = 0; i < mjNSTATE; ++i) {
if (state_sig & (1 << i)) {
for (int local = 0; local < mj_stateSize(model, (1 << i));
++local, ++global) {
if (global < clipper.DisplayStart) {
continue;
}
if (global >= clipper.DisplayEnd) {
break;
}
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%d", global);
ImGui::TableNextColumn();
ImGui::Text("%s[%d]", name_and_tooltip[i][0], local);
ImGui::TableNextColumn();
float value = state[global];
ImGui::PushItemWidth(-std::numeric_limits<float>::min());
ImGui::PushID(global);
if (ImGui::DragFloat("##value", &value, 0.01f, 0, 0, "%.3f")) {
changed = true;
}
ImGui::PopID();
ImGui::PopItemWidth();
state[global] = value;
}
}
}
}
ImGui::EndTable();
}
if (changed) {
mj_setState(model, data, state.data(), state_sig);
}
}
ImGui::Indent(0.5f * ImGui::GetTreeNodeToLabelSpacing());
}
void WatchGui(const mjModel* model, const mjData* data, char* field_name,
int field_len, int& field_index) {
const float item_width = ImGui::GetWindowWidth() * .6f;
ImGui::PushItemWidth(item_width);
ImGui::InputText("Field", field_name, field_len);
ImGui::InputInt("Index", &field_index);
const mjtNum* value = static_cast<const mjtNum*>(
GetValue(model, data, field_name, field_index));
ScopedStyle style;
style.Color(ImGuiCol_FrameBg, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
if (value) {
char buf[100];
int size = std::snprintf(buf, sizeof(buf), "%0.3f", *value);
ImGui::InputText("Value", buf, size, ImGuiInputTextFlags_ReadOnly);
} else {
ImGui::BeginDisabled();
style.Color(ImGuiCol_Text, ImColor(255, 0, 0, 255));
char buf[] = "Invalid field/index!";
ImGui::InputText("Value", buf, sizeof(buf), ImGuiInputTextFlags_ReadOnly);
ImGui::EndDisabled();
}
ImGui::PopItemWidth();
}
void PhysicsGui(mjModel* model, float min_width) {
const float available_width =
ImGui::GetContentRegionAvail().x - ImGui::GetTreeNodeToLabelSpacing();
const int num_cols = std::clamp(
static_cast<int>(std::floor(available_width / min_width)), 1, 6);
const float item_width = ImGui::GetWindowWidth() * .6f;
ImGui::PushItemWidth(item_width);
auto& opt = model->opt;
const char* opts0[] = {"Euler", "RK4", "implicit", "implicitfast"};
ImGui::Combo("Integrator", &opt.integrator, opts0, IM_ARRAYSIZE(opts0));
const char* opts1[] = {"Pyramidal", "Elliptic"};
ImGui::Combo("Cone", &opt.cone, opts1, IM_ARRAYSIZE(opts1));
const char* opts2[] = {"Dense", "Sparse", "Auto"};
ImGui::Combo("Jacobian", &opt.jacobian, opts2, IM_ARRAYSIZE(opts2));
const char* opts3[] = {"PGS", "CG", "Newton"};
ImGui::Combo("Solver", &opt.solver, opts3, IM_ARRAYSIZE(opts3));
if (ImGui::TreeNodeEx("Flags", ImGuiTreeNodeFlags_DefaultOpen)) {
if (ImGui::BeginTable("##PhysicsFlagsTable", num_cols)) {
const ImVec2 size = GetFlexElementSize(num_cols);
for (int i = 0; i < mjNDISABLE; ++i) {
ImGui::TableNextColumn();
int flipped = ~opt.disableflags;
ImGui_BitToggle(mjDISABLESTRING[i], &flipped, 1 << i, size);
opt.disableflags = ~flipped;
}
for (int i = 0; i < mjNENABLE; ++i) {
ImGui::TableNextColumn();
ImGui_BitToggle(mjENABLESTRING[i], &opt.enableflags, 1 << i, size);
}
ImGui::EndTable();
}
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Actuator Groups")) {
if (ImGui::BeginTable("##ActuatorGroupsTable", num_cols)) {
const ImVec2 size = GetFlexElementSize(num_cols);
for (int i = 0; i < 6; ++i) {
char label[64];
std::snprintf(label, sizeof(label), "Act Group %d", i);
ImGui::TableNextColumn();
int flipped = ~opt.disableactuator;
ImGui_BitToggle(label, &flipped, 1 << i, size);
opt.disableactuator = ~flipped;
}
ImGui::EndTable();
}
ImGui::TreePop();
};
if (ImGui::TreeNodeEx("Algorithmic Parameters")) {
ImGui_Input("Timestep", &opt.timestep, {0, 1, 0.01, 0.1});
ImGui_Input("Iterations", &opt.iterations, {0, 1000, 1, 10});
ImGui_Input("Tolerance", &opt.tolerance, {0, 1, 1e-7, 1e-6});
ImGui_Input("LS Iter", &opt.ls_iterations, {0, 100, 1, 0.1});
ImGui_Input("LS Tol", &opt.ls_tolerance, {0, 0.1, 0.01, 0.1});
ImGui_Input("Noslip Iter", &opt.noslip_iterations, {0, 1000, 1, 100});
ImGui_Input("Noslip Tol", &opt.noslip_tolerance, {0, 1, 0.01, 0.1});
ImGui_Input("CCD Iter", &opt.ccd_iterations, {0, 1000, 1, 100});
ImGui_Input("CCD Tol", &opt.ccd_tolerance, {0, 1, 0.01, 0.1});
ImGui_Input("Sleep Tol", &opt.sleep_tolerance, {0, 1, 0.01, 0.1});
ImGui_Input("SDF Iter", &opt.sdf_iterations, {1, 20, 1, 10});
ImGui_Input("SDF Init", &opt.sdf_initpoints, {1, 100, 1, 10});
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Physical Parameters")) {
ImGui_InputN("Gravity", opt.gravity, 3);
ImGui_InputN("Wind", opt.wind, 3);
ImGui_InputN("Magnetic", opt.magnetic, 3);
ImGui_Input("Density", &opt.density, {.min = .1, .max = 1});
ImGui_Input("Viscosity", &opt.viscosity, {.min = .1, .max = 1});
ImGui_Input("Imp Ratio", &opt.impratio, {.min = .1, .max = 1});
ImGui::TreePop();
};
if (ImGui::TreeNodeEx("Contact Override")) {
ImGui_Input("Margin", &opt.o_margin, {.min = 0.1, .max = 1});
ImGui_InputN("Sol Imp", opt.o_solimp, 5, {.format = "%0.1f"});
ImGui_InputN("Sol Ref", opt.o_solref, 2, {.format = "%0.1f"});
ImGui_InputN("Friction", opt.o_friction, 5, {.format = "%.1f"});
ImGui::TreePop();
}
ImGui::PopItemWidth();
}
void VisualizationGui(mjModel* model, mjvOption* vis_options, mjvCamera* camera,
float min_width) {
auto& vis = model->vis;
auto& stat = model->stat;
const float item_width = ImGui::GetWindowWidth() * .6f;
ImGui::PushItemWidth(item_width);
ImGui::SliderInt("Tree depth", &vis_options->bvh_depth, 0, 20);
ImGui::SliderInt("Flex layer", &vis_options->flex_layer, 0, 10);
if (ImGui::TreeNodeEx("Headlight")) {
ImGui_SwitchToggle("Active", &vis.headlight.active);
ImGui::ColorEdit3("Ambient", vis.headlight.ambient);
ImGui::ColorEdit3("Diffuse", vis.headlight.diffuse);
ImGui::ColorEdit3("Specular", vis.headlight.specular);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Free Camera")) {
ImGui_SwitchToggle("Orthographic", &vis.global.orthographic);
ImGui_Input("FOV", &vis.global.fovy, {.format = "%0.2f"});
ImGui_InputN("Center", stat.center, 3, {.format = "%0.2f"});
ImGui_Input("Azimuth", &vis.global.azimuth, {.format = "%0.2f"});
ImGui_Input("Elevation", &vis.global.elevation, {.format = "%0.2f"});
if (ImGui::Button("Align")) {
mjv_defaultFreeCamera(model, camera);
}
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Global")) {
ImGui_Input("Extent", &stat.extent);
const char* opts[] = {"Box", "Ellipsoid"};
ImGui::SliderInt("Inertia", &vis.global.ellipsoidinertia, 0, 1,
opts[vis.global.ellipsoidinertia]);
ImGui_ButtonToggle("BVH active", &vis.global.bvactive);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Mapping")) {
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.3f);
ImGui_Input("Stiffness", &vis.map.stiffness);
ImGui_Input("Rot stiffness", &vis.map.stiffnessrot);
ImGui_Input("Force", &vis.map.force);
ImGui_Input("Torque", &vis.map.torque);
ImGui_Input("Alpha", &vis.map.alpha);
ImGui_Input("Fog start", &vis.map.fogstart);
ImGui_Input("Fog end", &vis.map.fogend);
ImGui_Input("Z near", &vis.map.znear);
ImGui_Input("Z far", &vis.map.zfar);
ImGui_Input("Haze", &vis.map.haze);
ImGui_Input("Shadow clip", &vis.map.shadowclip);
ImGui_Input("Shadow scale", &vis.map.shadowscale);
ImGui::PopItemWidth();
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Scale")) {
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.3f);
ImGui_Input("All (meansize)", &stat.meansize, {.format = "%0.3f"});
ImGui_Input("Force width", &vis.scale.forcewidth);
ImGui_Input("Contact width", &vis.scale.contactwidth);
ImGui_Input("Contact height", &vis.scale.contactheight);
ImGui_Input("Connect", &vis.scale.connect);
ImGui_Input("Com", &vis.scale.com);
ImGui_Input("Camera", &vis.scale.camera);
ImGui_Input("Light", &vis.scale.light);
ImGui_Input("Select point", &vis.scale.selectpoint);
ImGui_Input("Joint length", &vis.scale.jointlength);
ImGui_Input("Joint width", &vis.scale.jointwidth);
ImGui_Input("Actuator length", &vis.scale.actuatorlength);
ImGui_Input("Actuator width", &vis.scale.actuatorwidth);
ImGui_Input("Frame length", &vis.scale.framelength);
ImGui_Input("Frame width", &vis.scale.framewidth);
ImGui_Input("Constraint", &vis.scale.constraint);
ImGui_Input("Slider-crank", &vis.scale.slidercrank);
ImGui::PopItemWidth();
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Colors")) {
ImGui::ColorEdit4("Fog", vis.rgba.fog);
ImGui::ColorEdit4("Haze", vis.rgba.haze);
ImGui::ColorEdit4("Force", vis.rgba.force);
ImGui::ColorEdit4("Inertia", vis.rgba.inertia);
ImGui::ColorEdit4("Joint", vis.rgba.joint);
ImGui::ColorEdit4("Actuator", vis.rgba.actuator);
ImGui::ColorEdit4("Act. Negative", vis.rgba.actuatornegative);
ImGui::ColorEdit4("Act. Positive", vis.rgba.actuatorpositive);
ImGui::ColorEdit4("Center of Mass", vis.rgba.com);
ImGui::ColorEdit4("Camera", vis.rgba.camera);
ImGui::ColorEdit4("Light", vis.rgba.light);
ImGui::ColorEdit4("Select Point", vis.rgba.selectpoint);
ImGui::ColorEdit4("Auto Connect", vis.rgba.connect);
ImGui::ColorEdit4("Contact Point", vis.rgba.contactpoint);
ImGui::ColorEdit4("Contact Force", vis.rgba.contactforce);
ImGui::ColorEdit4("Contact Friction", vis.rgba.contactfriction);
ImGui::ColorEdit4("Contact Torque", vis.rgba.contacttorque);
ImGui::ColorEdit4("Contact Gap", vis.rgba.contactgap);
ImGui::ColorEdit4("Range Finder", vis.rgba.rangefinder);
ImGui::ColorEdit4("Constraint", vis.rgba.constraint);
ImGui::ColorEdit4("Slider Crank", vis.rgba.slidercrank);
ImGui::ColorEdit4("Crank Broken", vis.rgba.crankbroken);
ImGui::ColorEdit4("Frustum", vis.rgba.frustum);
ImGui::ColorEdit4("Bounding Vol.", vis.rgba.bv);
ImGui::ColorEdit4("BV Active", vis.rgba.bvactive);
ImGui::TreePop();
}
ImGui::PopItemWidth();
}
void RenderingGui(const mjModel* model, mjvOption* vis_options,
mjtByte* render_flags, float min_width) {
const float available_width =
ImGui::GetContentRegionAvail().x - ImGui::GetTreeNodeToLabelSpacing();
const int num_cols = std::clamp(
static_cast<int>(std::floor(available_width / min_width)), 1, 6);
if (ImGui::TreeNodeEx("Model Elements", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing() / 2);
if (ImGui::BeginTable("##ModelElementsTable", num_cols)) {
const ImVec2 size = GetFlexElementSize(num_cols);
for (int i = 0; i < mjNVISFLAG; ++i) {
ImGui::TableNextColumn();
ImGui_ButtonToggle(mjVISSTRING[i][0], &vis_options->flags[i], size);
}
ImGui::EndTable();
}
ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing() / 2);
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Render Flags", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing() / 2);
if (ImGui::BeginTable("##RenderFlagsTable", num_cols)) {
const ImVec2 size = GetFlexElementSize(num_cols);
for (int i = 0; i < mjNRNDFLAG; ++i) {
ImGui::TableNextColumn();
ImGui_ButtonToggle(mjRNDSTRING[i][0], &render_flags[i], size);
}
ImGui::EndTable();
}
ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing() / 2);
ImGui::TreePop();
}
}
void GroupsGui(const mjModel* model, mjvOption* vis_options, float min_width) {
const float available_width = ImGui::GetContentRegionAvail().x;
// We limit the number of columns to 1, 2, 3, or 6 depending on how much
// space the window has available.
int num_cols = std::clamp(
static_cast<int>(std::floor(available_width / min_width)), 1, 6);
if (num_cols == 4 || num_cols == 5) {
num_cols = 3;
}
auto GroupGui = [&](const char* name, mjtByte* group) {
if (ImGui::TreeNodeEx(name, ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing() / 2);
char label[64];
std::snprintf(label, sizeof(label), "##%s", name);
if (ImGui::BeginTable(label, num_cols)) {
const ImVec2 size = GetFlexElementSize(num_cols);
for (int i = 0; i < 6; ++i) {
ImGui::TableNextColumn();
std::snprintf(label, sizeof(label), "%s %d", name, i);
ImGui_ButtonToggle(label, &group[i], size);
}
ImGui::EndTable();
}
ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing() / 2);
ImGui::TreePop();
}
};
GroupGui("Geoms", vis_options->geomgroup);
GroupGui("Sites", vis_options->sitegroup);
GroupGui("Joints", vis_options->jointgroup);
GroupGui("Tendons", vis_options->tendongroup);
GroupGui("Actuators", vis_options->actuatorgroup);
GroupGui("Flexes", vis_options->flexgroup);
GroupGui("Skins", vis_options->skingroup);
}
void NoiseGui(const mjModel* model, const mjData* data, float& noise_scale,
float& noise_rate) {
const float item_width = ImGui::GetWindowWidth() * .6f;
ImGui::PushItemWidth(item_width);
ImGui::SliderFloat("Scale", &noise_scale, 0, 1);
ImGui::SliderFloat("Rate", &noise_rate, 0, 4);
ImGui::PopItemWidth();
}
void JointsGui(const mjModel* model, const mjData* data,
const mjvOption* vis_options) {
const float item_width = ImGui::GetWindowWidth() * .6f;
ImGui::PushItemWidth(item_width);
char name[100];
for (int i = 0; i < model->njnt; ++i) {
if (model->jnt_type[i] != mjJNT_HINGE &&
model->jnt_type[i] != mjJNT_SLIDE) {
continue;
}
const int group = std::clamp(model->jnt_group[i], 0, mjNGROUP - 1);
if (!vis_options->jointgroup[group]) {
continue;
}
const char* jnt_name = model->names + model->name_jntadr[i];
if (*jnt_name) {
std::snprintf(name, sizeof(name), "%s", jnt_name);
} else {
std::snprintf(name, sizeof(name), "joint %d", i);
}
double min = -1.0;
double max = 1.0;
if (model->jnt_limited[i]) {
min = model->jnt_range[2 * i + 0];
max = model->jnt_range[2 * i + 1];
} else if (model->jnt_type[i] == mjJNT_SLIDE) {
min = -1.0;
max = 1.0;
} else {
min = -3.1416;
max = 3.1416;
}
const int data_adr = model->jnt_qposadr[i];
ImGui_Slider(name, &data->qpos[data_adr], min, max);
}
ImGui::PopItemWidth();
}
void ControlsGui(const mjModel* model, const mjData* data,
const mjvOption* vis_options) {
const float item_width = ImGui::GetWindowWidth() * .6f;
ImGui::PushItemWidth(item_width);
if (ImGui::Button("Clear All")) {
mju_zero(data->ctrl, model->nu);
}
char name[100];
for (int i = 0; i < model->nu; i++) {
int group = std::clamp(model->actuator_group[i], 0, mjNGROUP - 1);
if (!vis_options->actuatorgroup[group]) {
continue;
}
if (group >= 0 && group <= 30 &&
model->opt.disableactuator & (1 << group)) {
continue;
}
const char* ctrl_name = model->names + model->name_actuatoradr[i];
if (*ctrl_name) {
std::snprintf(name, sizeof(name), "%s", ctrl_name);
} else {
std::snprintf(name, sizeof(name), "control %d", i);
}
double min = -1.0;
double max = 1.0;
if (!model->actuator_ctrllimited[i]) {
min = model->actuator_ctrlrange[2 * i + 0];
max = model->actuator_ctrlrange[2 * i + 1];
}
ImGui_Slider(name, &data->ctrl[i], min, max);
}
ImGui::PopItemWidth();
}
void ConvergenceGui(const mjModel* model, mjData* data) {
if (ImPlot::BeginPlot("Convergence (log 10)", ImVec2(-1, 0),
ImPlotFlags_NoMouseText)) {
ImPlot::PushStyleVar(ImPlotStyleVar_LineWeight, 2.0f);
ImPlot::SetupAxis(ImAxis_X1, "iteration", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, 20, ImPlotCond_Always);
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.1f");
ImPlot::SetupAxisLimits(ImAxis_Y1, -20, 5, ImPlotCond_Always);
ImPlot::SetupLegend(ImPlotLocation_NorthEast);
ImPlot::SetupFinish();
const int nisland = data->nefc ? mjMAX(1, mjMIN(data->nisland, mjNISLAND)) : 0;
for (int k = 0; k < nisland; k++) {
mjSolverStat* stats = data->solver + k * mjNSOLVER;
const int npoints =
mjMIN(mjMIN(data->solver_niter[k], mjNSOLVER), mjMAXLINEPNT);
ImPlot::PlotLineG("improvement", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = mju_log10(mju_max(mjMINVAL, stats[i].improvement));
return ImPlotPoint{x, y};
}, stats, npoints);
if (model->opt.solver == mjSOL_PGS) {
continue;
}
ImPlot::PlotLineG("gradient", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = mju_log10(mju_max(mjMINVAL, stats[i].gradient));
return ImPlotPoint{x, y};
}, stats, npoints);
ImPlot::PlotLineG("lineslope", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = mju_log10(mju_max(mjMINVAL, stats[i].lineslope));
return ImPlotPoint{x, y};
}, stats, npoints);
}
ImPlot::PopStyleVar();
ImPlot::EndPlot();
}
}
void CountsGui(const mjModel* model, mjData* data) {
if (ImPlot::BeginPlot("Counts", ImVec2(-1, 0), ImPlotFlags_NoMouseText)) {
ImPlot::PushStyleVar(ImPlotStyleVar_LineWeight, 2.0f);
ImPlot::SetupAxis(ImAxis_X1, "iteration", ImPlotAxisFlags_AutoFit);
ImPlot::SetupAxisLimits(ImAxis_X1, 0, 20, ImPlotCond_Always);
ImPlot::SetupAxisFormat(ImAxis_Y1, "%.0f");
ImPlot::SetupAxisLimits(ImAxis_Y1, 0, 80, ImPlotCond_Always);
ImPlot::SetupLegend(ImPlotLocation_NorthEast);
ImPlot::SetupFinish();
const int nisland = data->nefc ? mjMAX(1, mjMIN(data->nisland, mjNISLAND)) : 0;
for (int k = 0; k < nisland; k++) {
const int npoints =
mjMIN(mjMIN(data->solver_niter[k], mjNSOLVER), mjMAXLINEPNT);
mjSolverStat* stats = data->solver + k*mjNSOLVER;
int nefc = nisland == 1 ? data->nefc : data->island_nefc[k];
ImPlot::PlotLineG("total", +[](int i, void* user_data) {
const float x = static_cast<float>(i);
const float y = *(static_cast<int*>(user_data));
return ImPlotPoint{x, y};
}, &nefc, npoints);
ImPlot::PlotLineG("active", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = stats[i].nactive;
return ImPlotPoint{x, y};
}, stats, npoints);
ImPlot::PlotLineG("changed", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = stats[i].nchange;
return ImPlotPoint{x, y};
}, stats, npoints);
if (model->opt.solver == mjSOL_PGS) {
continue;
}
ImPlot::PlotLineG("evals", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = stats[i].neval;
return ImPlotPoint{x, y};
}, stats, npoints);
if (model->opt.solver == mjSOL_CG) {
continue;
}
ImPlot::PlotLineG("updates", +[](int i, void* user_data) {
const mjSolverStat* stats = static_cast<const mjSolverStat*>(user_data);
const float x = static_cast<float>(i);
const float y = stats[i].nupdate;
return ImPlotPoint{x, y};
}, stats, npoints);
}
ImPlot::PopStyleVar();
ImPlot::EndPlot();
}
}
void StatsGui(const mjModel* model, const mjData* data, bool paused,
float fps) {
const int num_islands = std::clamp(data->nisland, 1, mjNISLAND);
// compute solver error (maximum over islands)
mjtNum solver_err = 0;
int solver_iter = 0;
for (int i = 0; i < num_islands; i++) {
solver_iter += data->solver_niter[i];
mjtNum solerr_i = 0;
if (data->solver_niter[i]) {
const int ind = mjMIN(data->solver_niter[i], mjNSOLVER) - 1;
const mjSolverStat* stat = data->solver + i * mjNSOLVER + ind;
solerr_i = mju_min(stat->improvement, stat->gradient);
if (solerr_i == 0) {
solerr_i = mju_max(stat->improvement, stat->gradient);
}
}
solver_err = mju_max(solver_err, solerr_i);
}
solver_err = mju_log10(mju_max(mjMINVAL, solver_err));
auto type = paused ? mjTIMER_FORWARD : mjTIMER_STEP;
auto cpu = data->timer[type].duration / mjMAX(1, data->timer[type].number);
auto mempct = 100 * data->maxuse_arena / (double)(data->narena);
auto memlimit = mju_writeNumBytes(data->narena);
ImGui::Columns(2);
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.4f);
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.6f);
ImGui::Text("Time");
ImGui::Text("Size");
ImGui::Text("CPU");
ImGui::Text("Solver");
ImGui::Text("FPS");
ImGui::Text("Memory");
if (model->opt.enableflags & mjENBL_ENERGY) {
ImGui::Text("Energy");
}
if (model->opt.enableflags & mjENBL_FWDINV) {
ImGui::Text("FwdInv");
}
if (!(model->opt.disableflags & mjDSBL_ISLAND)) {
ImGui::Text("Islands");
}
ImGui::NextColumn();
ImGui::Text("%-9.3f", data->time);
ImGui::Text("%d (%d con)", data->nefc, data->ncon);
ImGui::Text("%.3f", cpu);
ImGui::Text("%.1f (%d it)", solver_err, solver_iter);
ImGui::Text("%0.1f", fps);
ImGui::Text("%.1f%% of %s", mempct, memlimit);
if (model->opt.enableflags & mjENBL_ENERGY) {
ImGui::Text("%.3f", data->energy[0] + data->energy[1]);
}
if (model->opt.enableflags & mjENBL_FWDINV) {
ImGui::Text("%.1f %.1f",
mju_log10(mju_max(mjMINVAL, data->solver_fwdinv[0])),
mju_log10(mju_max(mjMINVAL, data->solver_fwdinv[1])));
}
if (!(model->opt.disableflags & mjDSBL_ISLAND)) {
ImGui::Text("%d", data->nisland);
}
ImGui::Columns();
}
void BodyPropertiesGui(const mjModel* model, const mjData* data,
mjsElement* element, int id) {
const mjsBody* body = mjs_asBody(element);
ImGui::Columns(2);
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.4f);
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.6f);
std::string name = *mjs_getName(body->element);
if (name.empty()) {
name = "(Body " + std::to_string(id) + ")";
}
ImGui::Columns(2);
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.3f);
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.7f);
ImGui::Text("Name");
ImGui::Text("xpos[0]");
ImGui::Text("xpos[1]");
ImGui::Text("xpos[2]");
ImGui::Text("xquat[0]");
ImGui::Text("xquat[1]");
ImGui::Text("xquat[2]");
ImGui::Text("xquat[3]");
ImGui::Text("mass");
ImGui::NextColumn();
ImGui::Text("%s", name.c_str());
ImGui::Text("%f", data->xpos[3*id+0]);
ImGui::Text("%f", data->xpos[3*id+1]);
ImGui::Text("%f", data->xpos[3*id+2]);
ImGui::Text("%f", data->xquat[4*id+0]);
ImGui::Text("%f", data->xquat[4*id+1]);
ImGui::Text("%f", data->xquat[4*id+2]);
ImGui::Text("%f", data->xquat[4*id+3]);
ImGui::Text("%f", model->body_mass[id]);
}
void JointPropertiesGui(const mjModel* model, const mjData* data,
mjsElement* element, int id) {
const mjsJoint* joint = mjs_asJoint(element);
ImGui::Columns(2);
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.4f);
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.6f);
std::string name = *mjs_getName(joint->element);
if (name.empty()) {
name = "(Joint " + std::to_string(id) + ")";
}
ImGui::Columns(2);
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.3f);
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.7f);
ImGui::Text("Name");
ImGui::NextColumn();
ImGui::Text("%s", name.c_str());
}
void SitePropertiesGui(const mjModel* model, const mjData* data,
mjsElement* element, int id) {
const mjsSite* site = mjs_asSite(element);
ImGui::Columns(2);
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.4f);
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.6f);
std::string name = *mjs_getName(site->element);
if (name.empty()) {
name = "(Joint " + std::to_string(id) + ")";
}
ImGui::Columns(2);
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.3f);
ImGui::SetColumnWidth(1, ImGui::GetWindowWidth() * 0.7f);
ImGui::Text("Name");
ImGui::Text("site_xpos[0]");
ImGui::Text("site_xpos[1]");
ImGui::Text("site_xpos[2]");
ImGui::Text("site_xmat[0]");
ImGui::Text("site_xmat[1]");
ImGui::Text("site_xmat[2]");
ImGui::Text("site_xmat[3]");
ImGui::Text("site_xmat[4]");
ImGui::Text("site_xmat[5]");
ImGui::Text("site_xmat[6]");
ImGui::Text("site_xmat[7]");
ImGui::Text("site_xmat[8]");
ImGui::NextColumn();
ImGui::Text("%s", name.c_str());
ImGui::Text("%f", data->site_xpos[3*id+0]);
ImGui::Text("%f", data->site_xpos[3*id+1]);
ImGui::Text("%f", data->site_xpos[3*id+2]);
ImGui::Text("%f", data->site_xmat[4*id+0]);
ImGui::Text("%f", data->site_xmat[4*id+1]);
ImGui::Text("%f", data->site_xmat[4*id+2]);
ImGui::Text("%f", data->site_xmat[4*id+3]);
ImGui::Text("%f", data->site_xmat[4*id+4]);
ImGui::Text("%f", data->site_xmat[4*id+5]);
ImGui::Text("%f", data->site_xmat[4*id+6]);
ImGui::Text("%f", data->site_xmat[4*id+7]);
ImGui::Text("%f", data->site_xmat[4*id+8]);
}
} // namespace mujoco::platform