Merge Physics functionality into App.
The Physics class is no longer needed. This makes Studio similar to other toolbox-based apps (like web demo and python viewer) which manage their own mjModel and mjData instances. PiperOrigin-RevId: 836588076 Change-Id: I3f695b49bf6a7bba1a2b639d3d75d0c996582f06
This commit is contained in:
committed by
Copybara-Service
parent
67b09f9afb
commit
eac8e60037
+222
-140
@@ -26,6 +26,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
@@ -38,8 +39,8 @@
|
|||||||
#include "experimental/toolbox/helpers.h"
|
#include "experimental/toolbox/helpers.h"
|
||||||
#include "experimental/toolbox/imgui_widgets.h"
|
#include "experimental/toolbox/imgui_widgets.h"
|
||||||
#include "experimental/toolbox/interaction.h"
|
#include "experimental/toolbox/interaction.h"
|
||||||
#include "experimental/toolbox/physics.h"
|
|
||||||
#include "experimental/toolbox/renderer.h"
|
#include "experimental/toolbox/renderer.h"
|
||||||
|
#include "experimental/toolbox/step_control.h"
|
||||||
#include "experimental/toolbox/window.h"
|
#include "experimental/toolbox/window.h"
|
||||||
|
|
||||||
#if defined(USE_FILAMENT_OPENGL) || defined(USE_FILAMENT_VULKAN)
|
#if defined(USE_FILAMENT_OPENGL) || defined(USE_FILAMENT_VULKAN)
|
||||||
@@ -186,9 +187,6 @@ App::App(int width, int height, std::string ini_path,
|
|||||||
};
|
};
|
||||||
renderer_ = std::make_unique<toolbox::Renderer>(make_context_fn);
|
renderer_ = std::make_unique<toolbox::Renderer>(make_context_fn);
|
||||||
|
|
||||||
physics_ = std::make_unique<toolbox::Physics>(
|
|
||||||
[this](std::string_view model_file) { this->OnModelLoaded(model_file); });
|
|
||||||
|
|
||||||
mjv_defaultPerturb(&perturb_);
|
mjv_defaultPerturb(&perturb_);
|
||||||
mjv_defaultCamera(&camera_);
|
mjv_defaultCamera(&camera_);
|
||||||
mjv_defaultOption(&vis_options_);
|
mjv_defaultOption(&vis_options_);
|
||||||
@@ -201,14 +199,52 @@ App::App(int width, int height, std::string ini_path,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void App::LoadModel(std::string model_file) {
|
void App::LoadModel(std::string model_file) {
|
||||||
model_file_ = std::move(model_file);
|
pending_load_ = std::move(model_file);
|
||||||
physics_->LoadModel(model_file_);
|
}
|
||||||
|
|
||||||
|
void App::ProcessPendingLoad() {
|
||||||
|
if (!pending_load_.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (model_) {
|
||||||
|
mj_deleteData(data_);
|
||||||
|
data_ = nullptr;
|
||||||
|
mj_deleteModel(model_);
|
||||||
|
model_ = nullptr;
|
||||||
|
error_ = "";
|
||||||
|
|
||||||
|
step_control_.SetSpeed(100.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string model_file = std::move(pending_load_.value());
|
||||||
|
pending_load_.reset();
|
||||||
|
|
||||||
|
model_ = toolbox::LoadMujocoModel(model_file, nullptr);
|
||||||
|
if (!model_) {
|
||||||
|
error_ = "Error loading model!";
|
||||||
|
step_control_.Pause();
|
||||||
|
model_ = toolbox::LoadMujocoModel("", nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
data_ = mj_makeData(model_);
|
||||||
|
if (!data_) {
|
||||||
|
error_ = "Error making data!";
|
||||||
|
step_control_.Pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnModelLoaded(model_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::OnModelLoaded(std::string_view model_file) {
|
void App::OnModelLoaded(std::string_view model_file) {
|
||||||
renderer_->Init(Model());
|
model_file_ = std::move(model_file);
|
||||||
|
|
||||||
|
renderer_->Init(model_);
|
||||||
tmp_ = UiTempState();
|
tmp_ = UiTempState();
|
||||||
mjv_defaultOption(&vis_options_);
|
mjv_defaultOption(&vis_options_);
|
||||||
|
|
||||||
|
const int state_size = mj_stateSize(model_, mjSTATE_INTEGRATION);
|
||||||
|
history_.Init(state_size);
|
||||||
profiler_.Clear();
|
profiler_.Clear();
|
||||||
|
|
||||||
std::string base_path = "/";
|
std::string base_path = "/";
|
||||||
@@ -233,6 +269,68 @@ void App::OnModelLoaded(std::string_view model_file) {
|
|||||||
tmp_.last_save_screenshot_file = base_path + "screenshot.webp";
|
tmp_.last_save_screenshot_file = base_path + "screenshot.webp";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void App::ResetPhysics() {
|
||||||
|
mj_resetData(model_, data_);
|
||||||
|
mj_forward(model_, data_);
|
||||||
|
error_ = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::UpdatePhysics() {
|
||||||
|
ProcessPendingLoad();
|
||||||
|
if (!model_ || !data_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!step_control_.IsPaused()) {
|
||||||
|
mju_zero(data_->xfrc_applied, 6 * model_->nbody);
|
||||||
|
mjv_applyPerturbPose(model_, data_, &perturb_, 0);
|
||||||
|
mjv_applyPerturbForce(model_, data_, &perturb_);
|
||||||
|
} else {
|
||||||
|
mjv_applyPerturbPose(model_, data_, &perturb_, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_) {
|
||||||
|
for (int i = 0; i < mjNTIMER; i++) {
|
||||||
|
data_->timer[i].duration = 0;
|
||||||
|
data_->timer[i].number = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toolbox::StepControl::Status status = step_control_.Advance(model_, data_);
|
||||||
|
if (status == toolbox::StepControl::Status::kPaused) {
|
||||||
|
// do nothing
|
||||||
|
} else if (status == toolbox::StepControl::Status::kOk) {
|
||||||
|
std::span<mjtNum> state = history_.AddToHistory();
|
||||||
|
if (!state.empty()) {
|
||||||
|
mj_getState(model_, data_, state.data(), mjSTATE_INTEGRATION);
|
||||||
|
}
|
||||||
|
// If we are adding to the history we didn't have a divergence error
|
||||||
|
error_ = "";
|
||||||
|
} else if (status == toolbox::StepControl::Status::kAutoReset) {
|
||||||
|
ResetPhysics();
|
||||||
|
} else if (status == toolbox::StepControl::Status::kDiverged) {
|
||||||
|
for (mjtWarning w : toolbox::StepControl::kDivergedWarnings) {
|
||||||
|
if (data_->warning[w].number > 0) {
|
||||||
|
error_ = mju_warningText(w, data_->warning[w].lastinfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profiler_.Update(model_, data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::LoadHistory(int offset) {
|
||||||
|
std::span<mjtNum> state = history_.SetIndex(offset);
|
||||||
|
if (!state.empty()) {
|
||||||
|
// Pause simulation when entering history mode.
|
||||||
|
step_control_.Pause();
|
||||||
|
|
||||||
|
// Load the state into the data buffer.
|
||||||
|
mj_setState(model_, data_, state.data(), mjSTATE_INTEGRATION);
|
||||||
|
mj_forward(model_, data_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool App::Update() {
|
bool App::Update() {
|
||||||
const toolbox::Window::Status status = window_->NewFrame();
|
const toolbox::Window::Status status = window_->NewFrame();
|
||||||
|
|
||||||
@@ -252,18 +350,7 @@ bool App::Update() {
|
|||||||
// Only update the simulation if a popup window is not open. Note that the
|
// Only update the simulation if a popup window is not open. Note that the
|
||||||
// simulation itself will only update if it is not paused.
|
// simulation itself will only update if it is not paused.
|
||||||
if (!tmp_.modal_open) {
|
if (!tmp_.modal_open) {
|
||||||
// Apply any active perturbations.
|
UpdatePhysics();
|
||||||
if (!physics_->GetStepControl().IsPaused() && Model() && Data()) {
|
|
||||||
mju_zero(Data()->xfrc_applied, 6 * Model()->nbody);
|
|
||||||
mjv_applyPerturbPose(Model(), Data(), &perturb_, 0);
|
|
||||||
mjv_applyPerturbForce(Model(), Data(), &perturb_);
|
|
||||||
} else {
|
|
||||||
mjv_applyPerturbPose(Model(), Data(), &perturb_, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (physics_->Update()) {
|
|
||||||
profiler_.Update(Model(), Data());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return status == toolbox::Window::Status::kRunning;
|
return status == toolbox::Window::Status::kRunning;
|
||||||
@@ -274,7 +361,7 @@ void App::Render() {
|
|||||||
const float height = window_->GetHeight();
|
const float height = window_->GetHeight();
|
||||||
const float scale = window_->GetScale();
|
const float scale = window_->GetScale();
|
||||||
|
|
||||||
renderer_->Render(Model(), Data(), &perturb_, &camera_, &vis_options_,
|
renderer_->Render(model_, data_, &perturb_, &camera_, &vis_options_,
|
||||||
width * scale, height * scale);
|
width * scale, height * scale);
|
||||||
|
|
||||||
#ifdef USE_CLASSIC_OPENGL
|
#ifdef USE_CLASSIC_OPENGL
|
||||||
@@ -286,10 +373,10 @@ void App::Render() {
|
|||||||
window_->EndFrame();
|
window_->EndFrame();
|
||||||
window_->Present();
|
window_->Present();
|
||||||
|
|
||||||
if (Data()) {
|
if (data_) {
|
||||||
for (int i = 0; i < mjNTIMER; i++) {
|
for (int i = 0; i < mjNTIMER; i++) {
|
||||||
Data()->timer[i].duration = 0;
|
data_->timer[i].duration = 0;
|
||||||
Data()->timer[i].number = 0;
|
data_->timer[i].number = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,7 +387,7 @@ void App::HandleMouseEvents() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Model() || !Data()) {
|
if (!model_ || !data_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,9 +422,9 @@ void App::HandleMouseEvents() {
|
|||||||
const mjtPertBit active =
|
const mjtPertBit active =
|
||||||
action == mjMOUSE_MOVE_V ? mjPERT_TRANSLATE : mjPERT_ROTATE;
|
action == mjMOUSE_MOVE_V ? mjPERT_TRANSLATE : mjPERT_ROTATE;
|
||||||
if (active != perturb_.active) {
|
if (active != perturb_.active) {
|
||||||
toolbox::InitPerturb(Model(), Data(), &camera_, &perturb_, active);
|
toolbox::InitPerturb(model_, data_, &camera_, &perturb_, active);
|
||||||
}
|
}
|
||||||
toolbox::MovePerturb(Model(), Data(), &camera_, &perturb_, action,
|
toolbox::MovePerturb(model_, data_, &camera_, &perturb_, action,
|
||||||
mouse_dx, mouse_dy);
|
mouse_dx, mouse_dy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -372,7 +459,7 @@ void App::HandleMouseEvents() {
|
|||||||
// Left double click.
|
// Left double click.
|
||||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||||
toolbox::PickResult picked =
|
toolbox::PickResult picked =
|
||||||
toolbox::Pick(Model(), Data(), &camera_, mouse_x, mouse_y,
|
toolbox::Pick(model_, data_, &camera_, mouse_x, mouse_y,
|
||||||
window_->GetAspectRatio(), &vis_options_);
|
window_->GetAspectRatio(), &vis_options_);
|
||||||
if (picked.body >= 0) {
|
if (picked.body >= 0) {
|
||||||
perturb_.select = picked.body;
|
perturb_.select = picked.body;
|
||||||
@@ -381,8 +468,8 @@ void App::HandleMouseEvents() {
|
|||||||
|
|
||||||
// Compute the local position of the selected object in the world.
|
// Compute the local position of the selected object in the world.
|
||||||
mjtNum tmp[3];
|
mjtNum tmp[3];
|
||||||
mju_sub3(tmp, picked.point, Data()->xpos + 3 * picked.body);
|
mju_sub3(tmp, picked.point, data_->xpos + 3 * picked.body);
|
||||||
mju_mulMatTVec(perturb_.localpos, Data()->xmat + 9 * picked.body, tmp, 3,
|
mju_mulMatTVec(perturb_.localpos, data_->xmat + 9 * picked.body, tmp, 3,
|
||||||
3);
|
3);
|
||||||
} else {
|
} else {
|
||||||
perturb_.select = 0;
|
perturb_.select = 0;
|
||||||
@@ -394,7 +481,7 @@ void App::HandleMouseEvents() {
|
|||||||
// Right double click.
|
// Right double click.
|
||||||
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right)) {
|
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Right)) {
|
||||||
toolbox::PickResult picked =
|
toolbox::PickResult picked =
|
||||||
toolbox::Pick(Model(), Data(), &camera_, mouse_x, mouse_y,
|
toolbox::Pick(model_, data_, &camera_, mouse_x, mouse_y,
|
||||||
window_->GetAspectRatio(), &vis_options_);
|
window_->GetAspectRatio(), &vis_options_);
|
||||||
mju_copy3(camera_.lookat, picked.point);
|
mju_copy3(camera_.lookat, picked.point);
|
||||||
if (picked.body > 0 && io.KeyCtrl) {
|
if (picked.body > 0 && io.KeyCtrl) {
|
||||||
@@ -428,14 +515,14 @@ void App::HandleKeyboardEvents() {
|
|||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_P | ImGuiMod_Ctrl)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_P | ImGuiMod_Ctrl)) {
|
||||||
ShowPopup(tmp_.save_screenshot_popup);
|
ShowPopup(tmp_.save_screenshot_popup);
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_C | ImGuiMod_Ctrl)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_C | ImGuiMod_Ctrl)) {
|
||||||
std::string keyframe = toolbox::KeyframeToString(Model(), Data(), false);
|
std::string keyframe = toolbox::KeyframeToString(model_, data_, false);
|
||||||
toolbox::MaybeSaveToClipboard(keyframe);
|
toolbox::MaybeSaveToClipboard(keyframe);
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_R | ImGuiMod_Ctrl)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_R | ImGuiMod_Ctrl)) {
|
||||||
LoadModel(model_file_);
|
LoadModel(model_file_);
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Q | ImGuiMod_Ctrl)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_Q | ImGuiMod_Ctrl)) {
|
||||||
tmp_.should_exit = true;
|
tmp_.should_exit = true;
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_A | ImGuiMod_Ctrl)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_A | ImGuiMod_Ctrl)) {
|
||||||
mjv_defaultFreeCamera(Model(), &camera_);
|
mjv_defaultFreeCamera(model_, &camera_);
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Tab | ImGuiMod_Shift)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_Tab | ImGuiMod_Shift)) {
|
||||||
tmp_.inspector_panel = !tmp_.inspector_panel;
|
tmp_.inspector_panel = !tmp_.inspector_panel;
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Tab)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_Tab)) {
|
||||||
@@ -445,23 +532,23 @@ void App::HandleKeyboardEvents() {
|
|||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Equal)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_Equal)) {
|
||||||
SetSpeedIndex(tmp_.speed_index - 1);
|
SetSpeedIndex(tmp_.speed_index - 1);
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_LeftArrow)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_LeftArrow)) {
|
||||||
if (physics_->GetStepControl().IsPaused()) {
|
if (step_control_.IsPaused()) {
|
||||||
physics_->LoadHistory(physics_->GetHistoryIndex() - 1);
|
LoadHistory(history_.GetIndex() - 1);
|
||||||
}
|
}
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_RightArrow)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_RightArrow)) {
|
||||||
if (physics_->GetStepControl().IsPaused()) {
|
if (step_control_.IsPaused()) {
|
||||||
if (physics_->GetHistoryIndex() == 0) {
|
if (history_.GetIndex() == 0) {
|
||||||
physics_->GetStepControl().RequestSingleStep();
|
step_control_.RequestSingleStep();
|
||||||
} else {
|
} else {
|
||||||
physics_->LoadHistory(physics_->GetHistoryIndex() + 1);
|
LoadHistory(history_.GetIndex() + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Space)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_Space)) {
|
||||||
physics_->GetStepControl().TogglePause();
|
step_control_.TogglePause();
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Backspace)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_Backspace)) {
|
||||||
physics_->Reset();
|
ResetPhysics();
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_PageUp)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_PageUp)) {
|
||||||
SelectParentPerturb(Model(), perturb_);
|
SelectParentPerturb(model_, perturb_);
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_F1)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_F1)) {
|
||||||
ToggleWindow(tmp_.help);
|
ToggleWindow(tmp_.help);
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_F2)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_F2)) {
|
||||||
@@ -550,13 +637,13 @@ void App::HandleKeyboardEvents() {
|
|||||||
ToggleFlag(vis_options_.geomgroup[4]);
|
ToggleFlag(vis_options_.geomgroup[4]);
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_5)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_5)) {
|
||||||
ToggleFlag(vis_options_.geomgroup[5]);
|
ToggleFlag(vis_options_.geomgroup[5]);
|
||||||
} else if (Model()) {
|
} else if (model_) {
|
||||||
if (ImGui_IsChordJustPressed(ImGuiKey_Escape)) {
|
if (ImGui_IsChordJustPressed(ImGuiKey_Escape)) {
|
||||||
ui_.camera_idx = toolbox::SetCamera(Model(), &camera_, toolbox::kTumbleCameraIdx);
|
ui_.camera_idx = toolbox::SetCamera(model_, &camera_, toolbox::kTumbleCameraIdx);
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_LeftBracket)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_LeftBracket)) {
|
||||||
ui_.camera_idx = toolbox::SetCamera(Model(), &camera_, ui_.camera_idx - 1);
|
ui_.camera_idx = toolbox::SetCamera(model_, &camera_, ui_.camera_idx - 1);
|
||||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_RightBracket)) {
|
} else if (ImGui_IsChordJustPressed(ImGuiKey_RightBracket)) {
|
||||||
ui_.camera_idx = toolbox::SetCamera(Model(), &camera_, ui_.camera_idx + 1);
|
ui_.camera_idx = toolbox::SetCamera(model_, &camera_, ui_.camera_idx + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// WASD camera controls for free camera.
|
// WASD camera controls for free camera.
|
||||||
@@ -629,11 +716,11 @@ void App::SetSpeedIndex(int idx) {
|
|||||||
|
|
||||||
tmp_.speed_index = std::clamp<int>(idx, 0, kPercentRealTime.size() - 1);
|
tmp_.speed_index = std::clamp<int>(idx, 0, kPercentRealTime.size() - 1);
|
||||||
float speed = std::stof(kPercentRealTime[tmp_.speed_index]);
|
float speed = std::stof(kPercentRealTime[tmp_.speed_index]);
|
||||||
physics_->GetStepControl().SetSpeed(speed);
|
step_control_.SetSpeed(speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::MoveCamera(toolbox::CameraMotion motion, mjtNum reldx, mjtNum reldy) {
|
void App::MoveCamera(toolbox::CameraMotion motion, mjtNum reldx, mjtNum reldy) {
|
||||||
toolbox::MoveCamera(Model(), Data(), &camera_, motion, reldx, reldy);
|
toolbox::MoveCamera(model_, data_, &camera_, motion, reldx, reldy);
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::BuildGui() {
|
void App::BuildGui() {
|
||||||
@@ -1042,18 +1129,18 @@ void App::HelpGui() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void App::InfoGui() {
|
void App::InfoGui() {
|
||||||
const int num_islands = std::clamp(Data()->nisland, 1, mjNISLAND);
|
const int num_islands = std::clamp(data_->nisland, 1, mjNISLAND);
|
||||||
|
|
||||||
// compute solver error (maximum over islands)
|
// compute solver error (maximum over islands)
|
||||||
mjtNum solver_err = 0;
|
mjtNum solver_err = 0;
|
||||||
int solver_iter = 0;
|
int solver_iter = 0;
|
||||||
for (int i = 0; i < num_islands; i++) {
|
for (int i = 0; i < num_islands; i++) {
|
||||||
solver_iter += Data()->solver_niter[i];
|
solver_iter += data_->solver_niter[i];
|
||||||
|
|
||||||
mjtNum solerr_i = 0;
|
mjtNum solerr_i = 0;
|
||||||
if (Data()->solver_niter[i]) {
|
if (data_->solver_niter[i]) {
|
||||||
const int ind = mjMIN(Data()->solver_niter[i], mjNSOLVER) - 1;
|
const int ind = mjMIN(data_->solver_niter[i], mjNSOLVER) - 1;
|
||||||
const mjSolverStat* stat = Data()->solver + i * mjNSOLVER + ind;
|
const mjSolverStat* stat = data_->solver + i * mjNSOLVER + ind;
|
||||||
solerr_i = mju_min(stat->improvement, stat->gradient);
|
solerr_i = mju_min(stat->improvement, stat->gradient);
|
||||||
if (solerr_i == 0) {
|
if (solerr_i == 0) {
|
||||||
solerr_i = mju_max(stat->improvement, stat->gradient);
|
solerr_i = mju_max(stat->improvement, stat->gradient);
|
||||||
@@ -1063,12 +1150,10 @@ void App::InfoGui() {
|
|||||||
}
|
}
|
||||||
solver_err = mju_log10(mju_max(mjMINVAL, solver_err));
|
solver_err = mju_log10(mju_max(mjMINVAL, solver_err));
|
||||||
|
|
||||||
auto type =
|
auto type = step_control_.IsPaused() ? mjTIMER_FORWARD : mjTIMER_STEP;
|
||||||
physics_->GetStepControl().IsPaused() ? mjTIMER_FORWARD : mjTIMER_STEP;
|
auto cpu = data_->timer[type].duration / mjMAX(1, data_->timer[type].number);
|
||||||
auto cpu =
|
auto mempct = 100 * data_->maxuse_arena / (double)(data_->narena);
|
||||||
Data()->timer[type].duration / mjMAX(1, Data()->timer[type].number);
|
auto memlimit = mju_writeNumBytes(data_->narena);
|
||||||
auto mempct = 100 * Data()->maxuse_arena / (double)(Data()->narena);
|
|
||||||
auto memlimit = mju_writeNumBytes(Data()->narena);
|
|
||||||
|
|
||||||
ImGui::Columns(2);
|
ImGui::Columns(2);
|
||||||
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.4f);
|
ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.4f);
|
||||||
@@ -1080,33 +1165,33 @@ void App::InfoGui() {
|
|||||||
ImGui::Text("Solver");
|
ImGui::Text("Solver");
|
||||||
ImGui::Text("FPS");
|
ImGui::Text("FPS");
|
||||||
ImGui::Text("Memory");
|
ImGui::Text("Memory");
|
||||||
if (Model()->opt.enableflags & mjENBL_ENERGY) {
|
if (model_->opt.enableflags & mjENBL_ENERGY) {
|
||||||
ImGui::Text("Energy");
|
ImGui::Text("Energy");
|
||||||
}
|
}
|
||||||
if (Model()->opt.enableflags & mjENBL_FWDINV) {
|
if (model_->opt.enableflags & mjENBL_FWDINV) {
|
||||||
ImGui::Text("FwdInv");
|
ImGui::Text("FwdInv");
|
||||||
}
|
}
|
||||||
if (!(Model()->opt.disableflags & mjDSBL_ISLAND)) {
|
if (!(model_->opt.disableflags & mjDSBL_ISLAND)) {
|
||||||
ImGui::Text("Islands");
|
ImGui::Text("Islands");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::NextColumn();
|
ImGui::NextColumn();
|
||||||
ImGui::Text("%-9.3f", Data()->time);
|
ImGui::Text("%-9.3f", data_->time);
|
||||||
ImGui::Text("%d (%d con)", Data()->nefc, Data()->ncon);
|
ImGui::Text("%d (%d con)", data_->nefc, data_->ncon);
|
||||||
ImGui::Text("%.3f", cpu);
|
ImGui::Text("%.3f", cpu);
|
||||||
ImGui::Text("%.1f (%d it)", solver_err, solver_iter);
|
ImGui::Text("%.1f (%d it)", solver_err, solver_iter);
|
||||||
ImGui::Text("%0.1f", renderer_->GetFrameRate());
|
ImGui::Text("%0.1f", renderer_->GetFrameRate());
|
||||||
ImGui::Text("%.1f%% of %s", mempct, memlimit);
|
ImGui::Text("%.1f%% of %s", mempct, memlimit);
|
||||||
if (Model()->opt.enableflags & mjENBL_ENERGY) {
|
if (model_->opt.enableflags & mjENBL_ENERGY) {
|
||||||
ImGui::Text("%.3f", Data()->energy[0] + Data()->energy[1]);
|
ImGui::Text("%.3f", data_->energy[0] + data_->energy[1]);
|
||||||
}
|
}
|
||||||
if (Model()->opt.enableflags & mjENBL_FWDINV) {
|
if (model_->opt.enableflags & mjENBL_FWDINV) {
|
||||||
ImGui::Text("%.1f %.1f",
|
ImGui::Text("%.1f %.1f",
|
||||||
mju_log10(mju_max(mjMINVAL, Data()->solver_fwdinv[0])),
|
mju_log10(mju_max(mjMINVAL, data_->solver_fwdinv[0])),
|
||||||
mju_log10(mju_max(mjMINVAL, Data()->solver_fwdinv[1])));
|
mju_log10(mju_max(mjMINVAL, data_->solver_fwdinv[1])));
|
||||||
}
|
}
|
||||||
if (!(Model()->opt.disableflags & mjDSBL_ISLAND)) {
|
if (!(model_->opt.disableflags & mjDSBL_ISLAND)) {
|
||||||
ImGui::Text("%d", Data()->nisland);
|
ImGui::Text("%d", data_->nisland);
|
||||||
}
|
}
|
||||||
ImGui::Columns();
|
ImGui::Columns();
|
||||||
}
|
}
|
||||||
@@ -1120,16 +1205,16 @@ void App::ToolBarGui() {
|
|||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
// Play/pause button.
|
// Play/pause button.
|
||||||
const bool paused = physics_->GetStepControl().IsPaused();
|
const bool paused = step_control_.IsPaused();
|
||||||
if (ImGui::Button(paused ? ICON_PLAY : ICON_PAUSE, ImVec2(144, 32))) {
|
if (ImGui::Button(paused ? ICON_PLAY : ICON_PAUSE, ImVec2(144, 32))) {
|
||||||
physics_->GetStepControl().TogglePause();
|
step_control_.TogglePause();
|
||||||
}
|
}
|
||||||
ImGui::SetItemTooltip("%s", paused ? "Play" : "Pause");
|
ImGui::SetItemTooltip("%s", paused ? "Play" : "Pause");
|
||||||
|
|
||||||
// Reset/Reload/Unload.
|
// Reset/Reload/Unload.
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_RESET_MODEL, ImVec2(48, 32))) {
|
if (ImGui::Button(ICON_RESET_MODEL, ImVec2(48, 32))) {
|
||||||
physics_->Reset();
|
ResetPhysics();
|
||||||
}
|
}
|
||||||
ImGui::SetItemTooltip("%s", "Reset");
|
ImGui::SetItemTooltip("%s", "Reset");
|
||||||
|
|
||||||
@@ -1155,11 +1240,11 @@ void App::ToolBarGui() {
|
|||||||
int camera_idx = ui_.camera_idx - toolbox::kTumbleCameraIdx;
|
int camera_idx = ui_.camera_idx - toolbox::kTumbleCameraIdx;
|
||||||
if (ImGui::Combo("##Camera", &camera_idx, cameras.data(), cameras.size())) {
|
if (ImGui::Combo("##Camera", &camera_idx, cameras.data(), cameras.size())) {
|
||||||
ui_.camera_idx = ::mujoco::toolbox::SetCamera(
|
ui_.camera_idx = ::mujoco::toolbox::SetCamera(
|
||||||
Model(), &camera_, camera_idx + toolbox::kTumbleCameraIdx);
|
model_, &camera_, camera_idx + toolbox::kTumbleCameraIdx);
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_COPY_CAMERA)) {
|
if (ImGui::Button(ICON_COPY_CAMERA)) {
|
||||||
std::string camera_string = toolbox::CameraToString(Data(), &camera_);
|
std::string camera_string = toolbox::CameraToString(data_, &camera_);
|
||||||
toolbox::MaybeSaveToClipboard(camera_string);
|
toolbox::MaybeSaveToClipboard(camera_string);
|
||||||
}
|
}
|
||||||
ImGui::SetItemTooltip("%s", "Copy Camera");
|
ImGui::SetItemTooltip("%s", "Copy Camera");
|
||||||
@@ -1217,13 +1302,13 @@ void App::StatusBarGui() {
|
|||||||
|
|
||||||
if (model_file_.empty()) {
|
if (model_file_.empty()) {
|
||||||
ImGui::Text("Not loaded");
|
ImGui::Text("Not loaded");
|
||||||
} else if (Model() == nullptr) {
|
} else if (model_ == nullptr) {
|
||||||
ImGui::Text("Not loaded");
|
ImGui::Text("Not loaded");
|
||||||
} else if (physics_->GetStepControl().IsPaused()) {
|
} else if (step_control_.IsPaused()) {
|
||||||
ImGui::Text("Paused");
|
ImGui::Text("Paused");
|
||||||
} else {
|
} else {
|
||||||
const float desired_realtime = physics_->GetStepControl().GetSpeed();
|
const float desired_realtime = step_control_.GetSpeed();
|
||||||
const float measured_realtime = physics_->GetStepControl().GetSpeedMeasured();
|
const float measured_realtime = step_control_.GetSpeedMeasured();
|
||||||
const float realtime_offset = mju_abs(measured_realtime - desired_realtime);
|
const float realtime_offset = mju_abs(measured_realtime - desired_realtime);
|
||||||
const bool misaligned = realtime_offset > 0.1 * desired_realtime;
|
const bool misaligned = realtime_offset > 0.1 * desired_realtime;
|
||||||
if (misaligned) {
|
if (misaligned) {
|
||||||
@@ -1233,9 +1318,9 @@ void App::StatusBarGui() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!physics_->GetError().empty()) {
|
if (!error_.empty()) {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Text(" | Error: %s", std::string(physics_->GetError()).c_str());
|
ImGui::Text(" | Error: %s", error_.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
@@ -1264,34 +1349,33 @@ void App::StatusBarGui() {
|
|||||||
style.Color(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
|
style.Color(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_PREV_FRAME)) {
|
if (ImGui::Button(ICON_PREV_FRAME)) {
|
||||||
physics_->LoadHistory(physics_->GetHistoryIndex() - 1);
|
LoadHistory(history_.GetIndex() - 1);
|
||||||
}
|
}
|
||||||
ImGui::SetItemTooltip("%s", "Previous Frame");
|
ImGui::SetItemTooltip("%s", "Previous Frame");
|
||||||
|
|
||||||
style.Reset();
|
style.Reset();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::SetNextItemWidth(450);
|
ImGui::SetNextItemWidth(450);
|
||||||
int index = physics_->GetHistoryIndex();
|
int index = history_.GetIndex();
|
||||||
int history_size = physics_->GetSimHistory().Size();
|
if (ImGui::SliderInt("##ScrubIndex", &index, 1 - history_.Size(), 0)) {
|
||||||
if (ImGui::SliderInt("##ScrubIndex", &index, 1 - history_size, 0)) {
|
LoadHistory(index);
|
||||||
physics_->LoadHistory(index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
style.Var(ImGuiStyleVar_FrameBorderSize, 0);
|
style.Var(ImGuiStyleVar_FrameBorderSize, 0);
|
||||||
style.Color(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
|
style.Color(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_NEXT_FRAME)) {
|
if (ImGui::Button(ICON_NEXT_FRAME)) {
|
||||||
if (physics_->GetHistoryIndex() == 0) {
|
if (history_.GetIndex() == 0) {
|
||||||
physics_->GetStepControl().RequestSingleStep();
|
step_control_.RequestSingleStep();
|
||||||
} else {
|
} else {
|
||||||
physics_->LoadHistory(physics_->GetHistoryIndex() + 1);
|
LoadHistory(history_.GetIndex() + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::SetItemTooltip("%s", "Next Frame");
|
ImGui::SetItemTooltip("%s", "Next Frame");
|
||||||
|
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(ICON_CURR_FRAME)) {
|
if (ImGui::Button(ICON_CURR_FRAME)) {
|
||||||
physics_->LoadHistory(0);
|
LoadHistory(0);
|
||||||
}
|
}
|
||||||
ImGui::SetItemTooltip("%s", "Current Frame");
|
ImGui::SetItemTooltip("%s", "Current Frame");
|
||||||
|
|
||||||
@@ -1333,12 +1417,11 @@ void App::MainMenuGui() {
|
|||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
if (ImGui::BeginMenu("Simulation")) {
|
if (ImGui::BeginMenu("Simulation")) {
|
||||||
if (ImGui::MenuItem("Pause", "Space",
|
if (ImGui::MenuItem("Pause", "Space", step_control_.IsPaused())) {
|
||||||
physics_->GetStepControl().IsPaused())) {
|
step_control_.TogglePause();
|
||||||
physics_->GetStepControl().TogglePause();
|
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Reset", "Backspace")) {
|
if (ImGui::MenuItem("Reset", "Backspace")) {
|
||||||
physics_->Reset();
|
ResetPhysics();
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Reload", "Ctrl+R")) {
|
if (ImGui::MenuItem("Reload", "Ctrl+R")) {
|
||||||
LoadModel(model_file_);
|
LoadModel(model_file_);
|
||||||
@@ -1346,17 +1429,17 @@ void App::MainMenuGui() {
|
|||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::BeginMenu("Keyframes")) {
|
if (ImGui::BeginMenu("Keyframes")) {
|
||||||
ImGui::SetNextItemWidth(200);
|
ImGui::SetNextItemWidth(200);
|
||||||
ImGui::SliderInt("##Key", &ui_.key_idx, 0, Model()->nkey);
|
ImGui::SliderInt("##Key", &ui_.key_idx, 0, model_->nkey);
|
||||||
if (ImGui::MenuItem("Load")) {
|
if (ImGui::MenuItem("Load")) {
|
||||||
mj_resetDataKeyframe(Model(), Data(), ui_.key_idx);
|
mj_resetDataKeyframe(model_, data_, ui_.key_idx);
|
||||||
mj_forward(Model(), Data());
|
mj_forward(model_, data_);
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Save")) {
|
if (ImGui::MenuItem("Save")) {
|
||||||
mj_setKeyframe(Model(), Data(), ui_.key_idx);
|
mj_setKeyframe(model_, data_, ui_.key_idx);
|
||||||
}
|
}
|
||||||
if (ImGui::MenuItem("Copy")) {
|
if (ImGui::MenuItem("Copy")) {
|
||||||
std::string keyframe = toolbox::KeyframeToString(Model(), Data(), false);
|
std::string str = toolbox::KeyframeToString(model_, data_, false);
|
||||||
toolbox::MaybeSaveToClipboard(keyframe);
|
toolbox::MaybeSaveToClipboard(str);
|
||||||
}
|
}
|
||||||
ImGui::EndMenu();
|
ImGui::EndMenu();
|
||||||
}
|
}
|
||||||
@@ -1482,7 +1565,7 @@ void App::FileDialogGui() {
|
|||||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
if (ImGui_FileDialog(tmp_.filename, sizeof(tmp_.filename))) {
|
if (ImGui_FileDialog(tmp_.filename, sizeof(tmp_.filename))) {
|
||||||
char err[1000] = "";
|
char err[1000] = "";
|
||||||
mj_saveLastXML(tmp_.filename, Model(), err, 1000);
|
mj_saveLastXML(tmp_.filename, model_, err, 1000);
|
||||||
tmp_.last_save_xml_file = tmp_.filename;
|
tmp_.last_save_xml_file = tmp_.filename;
|
||||||
}
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
@@ -1490,7 +1573,7 @@ void App::FileDialogGui() {
|
|||||||
if (ImGui::BeginPopupModal("SaveMJB", NULL,
|
if (ImGui::BeginPopupModal("SaveMJB", NULL,
|
||||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
if (ImGui_FileDialog(tmp_.filename, sizeof(tmp_.filename))) {
|
if (ImGui_FileDialog(tmp_.filename, sizeof(tmp_.filename))) {
|
||||||
mj_saveModel(Model(), tmp_.filename, nullptr, 0);
|
mj_saveModel(model_, tmp_.filename, nullptr, 0);
|
||||||
tmp_.last_save_mjb_file = tmp_.filename;
|
tmp_.last_save_mjb_file = tmp_.filename;
|
||||||
}
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
@@ -1507,7 +1590,7 @@ void App::FileDialogGui() {
|
|||||||
if (ImGui::BeginPopupModal("PrintModel", NULL,
|
if (ImGui::BeginPopupModal("PrintModel", NULL,
|
||||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
if (ImGui_FileDialog(tmp_.filename, sizeof(tmp_.filename))) {
|
if (ImGui_FileDialog(tmp_.filename, sizeof(tmp_.filename))) {
|
||||||
mj_printModel(Model(), tmp_.filename);
|
mj_printModel(model_, tmp_.filename);
|
||||||
tmp_.last_print_model_file = tmp_.filename;
|
tmp_.last_print_model_file = tmp_.filename;
|
||||||
}
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
@@ -1515,7 +1598,7 @@ void App::FileDialogGui() {
|
|||||||
if (ImGui::BeginPopupModal("PrintData", NULL,
|
if (ImGui::BeginPopupModal("PrintData", NULL,
|
||||||
ImGuiWindowFlags_AlwaysAutoResize)) {
|
ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||||
if (ImGui_FileDialog(tmp_.filename, sizeof(tmp_.filename))) {
|
if (ImGui_FileDialog(tmp_.filename, sizeof(tmp_.filename))) {
|
||||||
mj_printData(Model(), Data(), tmp_.filename);
|
mj_printData(model_, data_, tmp_.filename);
|
||||||
tmp_.last_print_data_file = tmp_.filename;
|
tmp_.last_print_data_file = tmp_.filename;
|
||||||
}
|
}
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
@@ -1593,7 +1676,7 @@ void App::StateGui() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tmp_.state_sig != prev_state_sig) {
|
if (tmp_.state_sig != prev_state_sig) {
|
||||||
const int size = mj_stateSize(Model(), tmp_.state_sig);
|
const int size = mj_stateSize(model_, tmp_.state_sig);
|
||||||
tmp_.state.resize(size);
|
tmp_.state.resize(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1607,7 +1690,7 @@ void App::StateGui() {
|
|||||||
: "Selected state components do not exist in the model.");
|
: "Selected state components do not exist in the model.");
|
||||||
ImGui::EndDisabled();
|
ImGui::EndDisabled();
|
||||||
} else {
|
} else {
|
||||||
mj_getState(Model(), Data(), tmp_.state.data(), tmp_.state_sig);
|
mj_getState(model_, data_, tmp_.state.data(), tmp_.state_sig);
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
|
|
||||||
if (ImGui::BeginTable(
|
if (ImGui::BeginTable(
|
||||||
@@ -1628,7 +1711,7 @@ void App::StateGui() {
|
|||||||
int global = 0;
|
int global = 0;
|
||||||
for (int i = 0; i < mjNSTATE; ++i) {
|
for (int i = 0; i < mjNSTATE; ++i) {
|
||||||
if (tmp_.state_sig & (1 << i)) {
|
if (tmp_.state_sig & (1 << i)) {
|
||||||
for (int local = 0; local < mj_stateSize(Model(), (1 << i));
|
for (int local = 0; local < mj_stateSize(model_, (1 << i));
|
||||||
++local, ++global) {
|
++local, ++global) {
|
||||||
if (global < clipper.DisplayStart) {
|
if (global < clipper.DisplayStart) {
|
||||||
continue;
|
continue;
|
||||||
@@ -1662,7 +1745,7 @@ void App::StateGui() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
mj_setState(Model(), Data(), tmp_.state.data(), tmp_.state_sig);
|
mj_setState(model_, data_, tmp_.state.data(), tmp_.state_sig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1673,7 +1756,7 @@ void App::WatchGui() {
|
|||||||
ImGui::InputText("Field", ui_.watch_field, sizeof(ui_.watch_field));
|
ImGui::InputText("Field", ui_.watch_field, sizeof(ui_.watch_field));
|
||||||
ImGui::InputInt("Index", &ui_.watch_index);
|
ImGui::InputInt("Index", &ui_.watch_index);
|
||||||
const mjtNum* value = static_cast<const mjtNum*>(
|
const mjtNum* value = static_cast<const mjtNum*>(
|
||||||
toolbox::GetValue(Model(), Data(), ui_.watch_field, ui_.watch_index));
|
toolbox::GetValue(model_, data_, ui_.watch_field, ui_.watch_index));
|
||||||
|
|
||||||
toolbox::ScopedStyle style;
|
toolbox::ScopedStyle style;
|
||||||
style.Color(ImGuiCol_FrameBg, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
|
style.Color(ImGuiCol_FrameBg, ImGui::GetStyle().Colors[ImGuiCol_WindowBg]);
|
||||||
@@ -1698,7 +1781,7 @@ void App::PhysicsGui() {
|
|||||||
const int num_cols = std::clamp(
|
const int num_cols = std::clamp(
|
||||||
static_cast<int>(std::floor(available_width / min_width)), 1, 6);
|
static_cast<int>(std::floor(available_width / min_width)), 1, 6);
|
||||||
|
|
||||||
auto& opt = Model()->opt;
|
auto& opt = model_->opt;
|
||||||
|
|
||||||
const char* opts0[] = {"Euler", "RK4", "implicit", "implicitfast"};
|
const char* opts0[] = {"Euler", "RK4", "implicit", "implicitfast"};
|
||||||
ImGui::Combo("Integrator", &opt.integrator, opts0, IM_ARRAYSIZE(opts0));
|
ImGui::Combo("Integrator", &opt.integrator, opts0, IM_ARRAYSIZE(opts0));
|
||||||
@@ -1789,8 +1872,8 @@ void App::PhysicsGui() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void App::VisualizationGui() {
|
void App::VisualizationGui() {
|
||||||
auto& vis = Model()->vis;
|
auto& vis = model_->vis;
|
||||||
auto& stat = Model()->stat;
|
auto& stat = model_->stat;
|
||||||
|
|
||||||
ImGui::SliderInt("Tree depth", &vis_options_.bvh_depth, 0, 20);
|
ImGui::SliderInt("Tree depth", &vis_options_.bvh_depth, 0, 20);
|
||||||
ImGui::SliderInt("Flex layer", &vis_options_.flex_layer, 0, 10);
|
ImGui::SliderInt("Flex layer", &vis_options_.flex_layer, 0, 10);
|
||||||
@@ -1809,7 +1892,7 @@ void App::VisualizationGui() {
|
|||||||
ImGui_Input("Azimuth", &vis.global.azimuth, {.format = "%0.2f"});
|
ImGui_Input("Azimuth", &vis.global.azimuth, {.format = "%0.2f"});
|
||||||
ImGui_Input("Elevation", &vis.global.elevation, {.format = "%0.2f"});
|
ImGui_Input("Elevation", &vis.global.elevation, {.format = "%0.2f"});
|
||||||
if (ImGui::Button("Align")) {
|
if (ImGui::Button("Align")) {
|
||||||
mjv_defaultFreeCamera(Model(), &camera_);
|
mjv_defaultFreeCamera(model_, &camera_);
|
||||||
}
|
}
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
@@ -1976,25 +2059,25 @@ void App::GroupsGui() {
|
|||||||
|
|
||||||
void App::NoiseGui() {
|
void App::NoiseGui() {
|
||||||
float noise_scale, noise_rate;
|
float noise_scale, noise_rate;
|
||||||
physics_->GetStepControl().GetNoiseParameters(noise_scale, noise_rate);
|
step_control_.GetNoiseParameters(noise_scale, noise_rate);
|
||||||
ImGui::SliderFloat("Scale", &noise_scale, 0, 1);
|
ImGui::SliderFloat("Scale", &noise_scale, 0, 1);
|
||||||
ImGui::SliderFloat("Rate", &noise_rate, 0, 4);
|
ImGui::SliderFloat("Rate", &noise_rate, 0, 4);
|
||||||
physics_->GetStepControl().SetNoiseParameters(noise_scale, noise_rate);
|
step_control_.SetNoiseParameters(noise_scale, noise_rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::JointsGui() {
|
void App::JointsGui() {
|
||||||
char name[100];
|
char name[100];
|
||||||
for (int i = 0; i < Model()->njnt; ++i) {
|
for (int i = 0; i < model_->njnt; ++i) {
|
||||||
if (Model()->jnt_type[i] != mjJNT_HINGE &&
|
if (model_->jnt_type[i] != mjJNT_HINGE &&
|
||||||
Model()->jnt_type[i] != mjJNT_SLIDE) {
|
model_->jnt_type[i] != mjJNT_SLIDE) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const int group = std::clamp(Model()->jnt_group[i], 0, mjNGROUP - 1);
|
const int group = std::clamp(model_->jnt_group[i], 0, mjNGROUP - 1);
|
||||||
if (!vis_options_.jointgroup[group]) {
|
if (!vis_options_.jointgroup[group]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* jnt_name = Model()->names + Model()->name_jntadr[i];
|
const char* jnt_name = model_->names + model_->name_jntadr[i];
|
||||||
if (*jnt_name) {
|
if (*jnt_name) {
|
||||||
std::snprintf(name, sizeof(name), "%s", jnt_name);
|
std::snprintf(name, sizeof(name), "%s", jnt_name);
|
||||||
} else {
|
} else {
|
||||||
@@ -2003,10 +2086,10 @@ void App::JointsGui() {
|
|||||||
|
|
||||||
double min = -1.0;
|
double min = -1.0;
|
||||||
double max = 1.0;
|
double max = 1.0;
|
||||||
if (Model()->jnt_limited[i]) {
|
if (model_->jnt_limited[i]) {
|
||||||
min = Model()->jnt_range[2 * i + 0];
|
min = model_->jnt_range[2 * i + 0];
|
||||||
max = Model()->jnt_range[2 * i + 1];
|
max = model_->jnt_range[2 * i + 1];
|
||||||
} else if (Model()->jnt_type[i] == mjJNT_SLIDE) {
|
} else if (model_->jnt_type[i] == mjJNT_SLIDE) {
|
||||||
min = -1.0;
|
min = -1.0;
|
||||||
max = 1.0;
|
max = 1.0;
|
||||||
} else {
|
} else {
|
||||||
@@ -2014,28 +2097,28 @@ void App::JointsGui() {
|
|||||||
max = 3.1416;
|
max = 3.1416;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int data_adr = Model()->jnt_qposadr[i];
|
const int data_adr = model_->jnt_qposadr[i];
|
||||||
ImGui_Slider(name, &Data()->qpos[data_adr], min, max);
|
ImGui_Slider(name, &data_->qpos[data_adr], min, max);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void App::ControlsGui() {
|
void App::ControlsGui() {
|
||||||
if (ImGui::Button("Clear All")) {
|
if (ImGui::Button("Clear All")) {
|
||||||
mju_zero(Data()->ctrl, Model()->nu);
|
mju_zero(data_->ctrl, model_->nu);
|
||||||
}
|
}
|
||||||
|
|
||||||
char name[100];
|
char name[100];
|
||||||
for (int i = 0; i < Model()->nu; i++) {
|
for (int i = 0; i < model_->nu; i++) {
|
||||||
int group = std::clamp(Model()->actuator_group[i], 0, mjNGROUP - 1);
|
int group = std::clamp(model_->actuator_group[i], 0, mjNGROUP - 1);
|
||||||
if (!vis_options_.actuatorgroup[group]) {
|
if (!vis_options_.actuatorgroup[group]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (group >= 0 && group <= 30 &&
|
if (group >= 0 && group <= 30 &&
|
||||||
Model()->opt.disableactuator & (1 << group)) {
|
model_->opt.disableactuator & (1 << group)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* ctrl_name = Model()->names + Model()->name_actuatoradr[i];
|
const char* ctrl_name = model_->names + model_->name_actuatoradr[i];
|
||||||
if (*ctrl_name) {
|
if (*ctrl_name) {
|
||||||
std::snprintf(name, sizeof(name), "%s", ctrl_name);
|
std::snprintf(name, sizeof(name), "%s", ctrl_name);
|
||||||
} else {
|
} else {
|
||||||
@@ -2044,11 +2127,11 @@ void App::ControlsGui() {
|
|||||||
|
|
||||||
double min = -1.0;
|
double min = -1.0;
|
||||||
double max = 1.0;
|
double max = 1.0;
|
||||||
if (!Model()->actuator_ctrllimited[i]) {
|
if (!model_->actuator_ctrllimited[i]) {
|
||||||
min = Model()->actuator_ctrlrange[2 * i + 0];
|
min = model_->actuator_ctrlrange[2 * i + 0];
|
||||||
max = Model()->actuator_ctrlrange[2 * i + 1];
|
max = model_->actuator_ctrlrange[2 * i + 1];
|
||||||
}
|
}
|
||||||
ImGui_Slider(name, &Data()->ctrl[i], min, max);
|
ImGui_Slider(name, &data_->ctrl[i], min, max);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2074,16 +2157,15 @@ float App::GetExpectedLabelWidth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const char*> App::GetCameraNames() {
|
std::vector<const char*> App::GetCameraNames() {
|
||||||
const mjModel* model = Model();
|
|
||||||
if (tmp_.camera_names.empty()) {
|
if (tmp_.camera_names.empty()) {
|
||||||
tmp_.camera_names.reserve(model->ncam + 3);
|
tmp_.camera_names.reserve(model_->ncam + 3);
|
||||||
|
|
||||||
tmp_.camera_names.push_back("Tumble");
|
tmp_.camera_names.push_back("Tumble");
|
||||||
tmp_.camera_names.push_back("Free");
|
tmp_.camera_names.push_back("Free");
|
||||||
tmp_.camera_names.push_back("Tracking (-1)");
|
tmp_.camera_names.push_back("Tracking (-1)");
|
||||||
for (int i = 0; i < model->ncam; i++) {
|
for (int i = 0; i < model_->ncam; i++) {
|
||||||
if (model->names[model->name_camadr[i]]) {
|
if (model_->names[model_->name_camadr[i]]) {
|
||||||
tmp_.camera_names.push_back(model->names + model->name_camadr[i]);
|
tmp_.camera_names.push_back(model_->names + model_->name_camadr[i]);
|
||||||
} else {
|
} else {
|
||||||
tmp_.camera_names.push_back("Unnamed");
|
tmp_.camera_names.push_back("Unnamed");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <ratio>
|
#include <ratio>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -28,9 +29,10 @@
|
|||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
#include "experimental/toolbox/helpers.h"
|
#include "experimental/toolbox/helpers.h"
|
||||||
#include "experimental/toolbox/interaction.h"
|
#include "experimental/toolbox/interaction.h"
|
||||||
#include "experimental/toolbox/physics.h"
|
|
||||||
#include "experimental/toolbox/renderer.h"
|
#include "experimental/toolbox/renderer.h"
|
||||||
|
#include "experimental/toolbox/sim_history.h"
|
||||||
#include "experimental/toolbox/sim_profiler.h"
|
#include "experimental/toolbox/sim_profiler.h"
|
||||||
|
#include "experimental/toolbox/step_control.h"
|
||||||
#include "experimental/toolbox/window.h"
|
#include "experimental/toolbox/window.h"
|
||||||
|
|
||||||
namespace mujoco::studio {
|
namespace mujoco::studio {
|
||||||
@@ -130,10 +132,16 @@ class App {
|
|||||||
};
|
};
|
||||||
|
|
||||||
void OnModelLoaded(std::string_view model_file);
|
void OnModelLoaded(std::string_view model_file);
|
||||||
|
void ProcessPendingLoad();
|
||||||
|
|
||||||
|
void ResetPhysics();
|
||||||
|
void UpdatePhysics();
|
||||||
|
|
||||||
void LoadSettings();
|
void LoadSettings();
|
||||||
void SaveSettings();
|
void SaveSettings();
|
||||||
|
|
||||||
|
void LoadHistory(int offset);
|
||||||
|
|
||||||
void SetSpeedIndex(int idx);
|
void SetSpeedIndex(int idx);
|
||||||
|
|
||||||
void HandleMouseEvents();
|
void HandleMouseEvents();
|
||||||
@@ -163,21 +171,23 @@ class App {
|
|||||||
void JointsGui();
|
void JointsGui();
|
||||||
void ControlsGui();
|
void ControlsGui();
|
||||||
|
|
||||||
mjModel* Model() { return physics_->GetModel(); };
|
|
||||||
mjData* Data() { return physics_->GetData(); };
|
|
||||||
|
|
||||||
float GetExpectedLabelWidth();
|
float GetExpectedLabelWidth();
|
||||||
std::vector<const char*> GetCameraNames();
|
std::vector<const char*> GetCameraNames();
|
||||||
|
|
||||||
|
std::string error_;
|
||||||
std::string ini_path_;
|
std::string ini_path_;
|
||||||
std::string model_file_;
|
std::string model_file_;
|
||||||
|
std::optional<std::string> pending_load_;
|
||||||
|
|
||||||
std::unique_ptr<toolbox::Window> window_;
|
std::unique_ptr<toolbox::Window> window_;
|
||||||
std::unique_ptr<toolbox::Renderer> renderer_;
|
std::unique_ptr<toolbox::Renderer> renderer_;
|
||||||
std::unique_ptr<toolbox::Physics> physics_;
|
|
||||||
toolbox::LoadAssetFn load_asset_fn_;
|
toolbox::LoadAssetFn load_asset_fn_;
|
||||||
|
toolbox::StepControl step_control_;
|
||||||
toolbox::SimProfiler profiler_;
|
toolbox::SimProfiler profiler_;
|
||||||
|
toolbox::SimHistory history_;
|
||||||
|
|
||||||
|
mjModel* model_ = nullptr;
|
||||||
|
mjData* data_ = nullptr;
|
||||||
|
|
||||||
mjvCamera camera_;
|
mjvCamera camera_;
|
||||||
mjvPerturb perturb_;
|
mjvPerturb perturb_;
|
||||||
|
|||||||
@@ -1,161 +0,0 @@
|
|||||||
// 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/toolbox/physics.h"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <functional>
|
|
||||||
#include <ratio>
|
|
||||||
#include <span>
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include "experimental/toolbox/helpers.h"
|
|
||||||
#include "experimental/toolbox/step_control.h"
|
|
||||||
#include <mujoco/mujoco.h>
|
|
||||||
|
|
||||||
namespace mujoco::toolbox {
|
|
||||||
|
|
||||||
static mjtNum Timer() {
|
|
||||||
using Clock = std::chrono::steady_clock;
|
|
||||||
using Milliseconds = std::chrono::duration<double, std::milli>;
|
|
||||||
static Clock::time_point start = Clock::now();
|
|
||||||
return Milliseconds(Clock::now() - start).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
Physics::Physics(OnModelLoadedFn on_model_loaded)
|
|
||||||
: on_model_loaded_(std::move(on_model_loaded)) {
|
|
||||||
mjcb_time = Timer;
|
|
||||||
}
|
|
||||||
|
|
||||||
Physics::~Physics() { Clear(); }
|
|
||||||
|
|
||||||
void Physics::LoadModel(std::string model_file, const mjVFS* vfs) {
|
|
||||||
pending_load_ = std::move(model_file);
|
|
||||||
vfs_ = vfs;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Physics::ProcessPendingLoad() {
|
|
||||||
if (!pending_load_.has_value()) {
|
|
||||||
return model_ && data_;
|
|
||||||
}
|
|
||||||
|
|
||||||
Clear();
|
|
||||||
|
|
||||||
step_control_.SetSpeed(100.f);
|
|
||||||
|
|
||||||
std::string model_file = std::move(pending_load_.value());
|
|
||||||
pending_load_.reset();
|
|
||||||
|
|
||||||
model_ = LoadMujocoModel(model_file, vfs_);
|
|
||||||
if (!model_) {
|
|
||||||
error_ = "Error loading model!";
|
|
||||||
step_control_.Pause();
|
|
||||||
model_ = LoadMujocoModel("", vfs_);
|
|
||||||
}
|
|
||||||
|
|
||||||
data_ = mj_makeData(model_);
|
|
||||||
if (!data_) {
|
|
||||||
error_ = "Error making data!";
|
|
||||||
step_control_.Pause();
|
|
||||||
}
|
|
||||||
|
|
||||||
on_model_loaded_(model_file);
|
|
||||||
|
|
||||||
const int state_size = mj_stateSize(model_, mjSTATE_INTEGRATION);
|
|
||||||
sim_history_.Init(state_size);
|
|
||||||
|
|
||||||
return model_ && data_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Physics::Clear() {
|
|
||||||
if (model_) {
|
|
||||||
mj_deleteData(data_);
|
|
||||||
data_ = nullptr;
|
|
||||||
mj_deleteModel(model_);
|
|
||||||
model_ = nullptr;
|
|
||||||
error_ = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Physics::Reset() {
|
|
||||||
mj_resetData(model_, data_);
|
|
||||||
mj_forward(model_, data_);
|
|
||||||
error_ = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Physics::Update() {
|
|
||||||
ProcessPendingLoad();
|
|
||||||
|
|
||||||
if (!model_ || !data_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data_) {
|
|
||||||
for (int i = 0; i < mjNTIMER; i++) {
|
|
||||||
data_->timer[i].duration = 0;
|
|
||||||
data_->timer[i].number = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StepControl::Status status = step_control_.Advance(model_, data_);
|
|
||||||
if (status == StepControl::Status::kOk) {
|
|
||||||
std::span<mjtNum> state = sim_history_.AddToHistory();
|
|
||||||
if (!state.empty()) {
|
|
||||||
mj_getState(model_, data_, state.data(), mjSTATE_INTEGRATION);
|
|
||||||
}
|
|
||||||
// If we are adding to the history we didn't have a divergence error
|
|
||||||
error_ = "";
|
|
||||||
} else if (status == StepControl::Status::kPaused) {
|
|
||||||
// do nothing
|
|
||||||
} else if (status == StepControl::Status::kAutoReset) {
|
|
||||||
Reset();
|
|
||||||
} else if (status == StepControl::Status::kDiverged) {
|
|
||||||
for (mjtWarning w : StepControl::kDivergedWarnings) {
|
|
||||||
if (data_->warning[w].number > 0) {
|
|
||||||
error_ = mju_warningText(w, data_->warning[w].lastinfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Physics::UpdateState(mjtNum* state, unsigned int state_sig) {
|
|
||||||
ProcessPendingLoad();
|
|
||||||
if (!model_ || !data_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mj_setState(model_, data_, state, state_sig);
|
|
||||||
mj_forward(model_, data_);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Physics::LoadHistory(int offset) {
|
|
||||||
std::span<mjtNum> state = sim_history_.SetIndex(offset);
|
|
||||||
if (!state.empty()) {
|
|
||||||
// Pause simulation when entering history mode.
|
|
||||||
step_control_.Pause();
|
|
||||||
|
|
||||||
// Load the state into the data buffer.
|
|
||||||
mj_setState(model_, data_, state.data(), mjSTATE_INTEGRATION);
|
|
||||||
mj_forward(model_, data_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int Physics::GetHistoryIndex() const {
|
|
||||||
return sim_history_.GetIndex();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace mujoco::toolbox
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
#ifndef MUJOCO_SRC_EXPERIMENTAL_TOOLBOX_PHYSICS_H_
|
|
||||||
#define MUJOCO_SRC_EXPERIMENTAL_TOOLBOX_PHYSICS_H_
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include <mujoco/mujoco.h>
|
|
||||||
#include "experimental/toolbox/sim_history.h"
|
|
||||||
#include "experimental/toolbox/step_control.h"
|
|
||||||
|
|
||||||
namespace mujoco::toolbox {
|
|
||||||
|
|
||||||
// Owns the MuJoCo simulation state (e.g. mjModel and mjData) and is responsible
|
|
||||||
// for updating the state of the simulation.
|
|
||||||
class Physics {
|
|
||||||
public:
|
|
||||||
using OnModelLoadedFn = std::function<void(std::string_view)>;
|
|
||||||
|
|
||||||
explicit Physics(OnModelLoadedFn on_model_loaded);
|
|
||||||
~Physics();
|
|
||||||
|
|
||||||
Physics(const Physics&) = delete;
|
|
||||||
Physics& operator=(const Physics&) = delete;
|
|
||||||
|
|
||||||
StepControl& GetStepControl() { return step_control_; }
|
|
||||||
SimHistory& GetSimHistory() { return sim_history_; }
|
|
||||||
|
|
||||||
// Loads a model from the given path. An empty string will load an empty
|
|
||||||
// scene.
|
|
||||||
void LoadModel(std::string model_file, const mjVFS* vfs = nullptr);
|
|
||||||
|
|
||||||
// Resets the simulation using mj_resetData
|
|
||||||
void Reset();
|
|
||||||
|
|
||||||
// Advances the state of the simulation.
|
|
||||||
bool Update();
|
|
||||||
|
|
||||||
// Sets the state of the simulation.
|
|
||||||
bool UpdateState(mjtNum* state, unsigned int state_sig);
|
|
||||||
|
|
||||||
// Loads a state from the history buffer at the given offset in the past.
|
|
||||||
void LoadHistory(int offset);
|
|
||||||
int GetHistoryIndex() const;
|
|
||||||
|
|
||||||
// Returns the MuJoCo data structures owned by this Simulation object.
|
|
||||||
mjModel* GetModel() { return model_; }
|
|
||||||
mjData* GetData() { return data_; }
|
|
||||||
|
|
||||||
// Returns the error message from the simulation.
|
|
||||||
std::string_view GetError() const { return error_; }
|
|
||||||
|
|
||||||
bool ProcessPendingLoad();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Clears the simulation, clearing all loaded state.
|
|
||||||
void Clear();
|
|
||||||
|
|
||||||
mjModel* model_ = nullptr;
|
|
||||||
mjData* data_ = nullptr;
|
|
||||||
|
|
||||||
OnModelLoadedFn on_model_loaded_;
|
|
||||||
|
|
||||||
std::optional<std::string> pending_load_;
|
|
||||||
const mjVFS* vfs_;
|
|
||||||
|
|
||||||
std::string error_;
|
|
||||||
|
|
||||||
StepControl step_control_;
|
|
||||||
SimHistory sim_history_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace mujoco::toolbox
|
|
||||||
|
|
||||||
#endif // MUJOCO_SRC_EXPERIMENTAL_TOOLBOX_PHYSICS_H_
|
|
||||||
@@ -16,10 +16,22 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <ratio>
|
||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
|
|
||||||
namespace mujoco::toolbox {
|
namespace mujoco::toolbox {
|
||||||
|
|
||||||
|
static mjtNum Timer() {
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
using Milliseconds = std::chrono::duration<double, std::milli>;
|
||||||
|
static Clock::time_point start = Clock::now();
|
||||||
|
return Milliseconds(Clock::now() - start).count();
|
||||||
|
}
|
||||||
|
|
||||||
|
StepControl::StepControl() {
|
||||||
|
mjcb_time = Timer;
|
||||||
|
}
|
||||||
|
|
||||||
float StepControl::GetSpeedMeasured() const {
|
float StepControl::GetSpeedMeasured() const {
|
||||||
return speed_measured_;
|
return speed_measured_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ using Clock = std::chrono::steady_clock;
|
|||||||
// State and logic for physics synchronization and stepping.
|
// State and logic for physics synchronization and stepping.
|
||||||
class StepControl {
|
class StepControl {
|
||||||
public:
|
public:
|
||||||
|
StepControl();
|
||||||
|
|
||||||
enum class Status {
|
enum class Status {
|
||||||
kOk,
|
kOk,
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user