Implement viscous pause in Studio
Viscous pause is like pause but the simulation continues with zero gravity, high viscosity and passive springs disabled. It is useful to move things like cabinets with shelves---just move the shelf and the drawers come along as well. * Updated the simulation control widgets * Added Ctrl-space shortcut to toggle play/viscous pause PiperOrigin-RevId: 869810053 Change-Id: Icf649e1f2631c2a64a5d2f25af9537b6a12bdf2c
This commit is contained in:
committed by
Copybara-Service
parent
5274e5f720
commit
1808079c62
@@ -627,7 +627,7 @@ void PhysicsGui(mjModel* model, float min_width) {
|
||||
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("Viscosity", &opt.viscosity, {.min = .1, .max = 10});
|
||||
ImGui_Input("Imp Ratio", &opt.impratio, {.min = .1, .max = 1});
|
||||
ImGui::TreePop();
|
||||
};
|
||||
|
||||
@@ -39,6 +39,7 @@ static constexpr const char ICON_FA_DIAMOND[] = "\xEF\x88\x99";
|
||||
static constexpr const char ICON_FA_EJECT[] = "\xEF\x81\x92";
|
||||
static constexpr const char ICON_FA_FAST_FORWARD[] = "\xEF\x81\x90";
|
||||
static constexpr const char ICON_FA_MOON[] = "\xEF\x86\x86";
|
||||
static constexpr const char ICON_FA_MAGIC[] = "\xEF\x83\x90";
|
||||
static constexpr const char ICON_FA_PAUSE[] = "\xEF\x81\x8C";
|
||||
static constexpr const char ICON_FA_PLAY[] = "\xEF\x81\x8B";
|
||||
static constexpr const char ICON_FA_REFRESH[] = "\xEF\x80\xA1";
|
||||
@@ -262,6 +263,24 @@ inline bool ImGui_IsChordJustPressed(ImGuiKeyChord chord) {
|
||||
return ImGui::IsKeyChordPressed(chord, 0);
|
||||
}
|
||||
|
||||
// Stateful button that displays the given color when active, and shows a
|
||||
// semi-transparent hover color (controlled by hover_alpha) when inactive.
|
||||
inline bool ImGui_ColorButton(const char* label, bool active, ImColor color,
|
||||
const ImVec2& size = ImVec2(0, 0),
|
||||
float hover_alpha = 0.5f) {
|
||||
ScopedStyle style;
|
||||
const ImColor hover(color.Value.x, color.Value.y, color.Value.z,
|
||||
color.Value.w * hover_alpha);
|
||||
if (active) {
|
||||
style.Color(ImGuiCol_Button, color);
|
||||
style.Color(ImGuiCol_ButtonHovered, color);
|
||||
} else {
|
||||
style.Color(ImGuiCol_ButtonHovered, hover);
|
||||
}
|
||||
style.Color(ImGuiCol_ButtonActive, color);
|
||||
return ImGui::Button(label, size);
|
||||
}
|
||||
|
||||
// Begin a boxed section with outer borders - use EndBoxSection to close.
|
||||
inline bool BeginBoxSection(const char* id, ImGuiTableFlags extra_flags = 0) {
|
||||
ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | extra_flags;
|
||||
|
||||
@@ -13,10 +13,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include "experimental/platform/step_control.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <ratio>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
|
||||
namespace mujoco::platform {
|
||||
@@ -28,17 +30,11 @@ static mjtNum Timer() {
|
||||
return Milliseconds(Clock::now() - start).count();
|
||||
}
|
||||
|
||||
StepControl::StepControl() {
|
||||
mjcb_time = Timer;
|
||||
}
|
||||
StepControl::StepControl() { mjcb_time = Timer; }
|
||||
|
||||
float StepControl::GetSpeedMeasured() const {
|
||||
return speed_measured_;
|
||||
}
|
||||
float StepControl::GetSpeedMeasured() const { return speed_measured_; }
|
||||
|
||||
float StepControl::GetSpeed() const {
|
||||
return speed_;
|
||||
}
|
||||
float StepControl::GetSpeed() const { return speed_; }
|
||||
|
||||
void StepControl::SetSpeed(float speed_percent_real_time) {
|
||||
speed_ = std::clamp(speed_percent_real_time, .1f, 100.f);
|
||||
@@ -59,12 +55,34 @@ void StepControl::SetNoiseParameters(float ctrl_noise_scale,
|
||||
ctrl_noise_rate_ = ctrl_noise_rate;
|
||||
}
|
||||
|
||||
StepControl::Status StepControl::Advance(const mjModel* m, mjData* d) {
|
||||
void StepControl::SetPauseState(PauseState state, mjModel* m) {
|
||||
if (pause_state_ == PauseState::kViscousPaused &&
|
||||
state != PauseState::kViscousPaused && m) {
|
||||
mju_copy3(m->opt.gravity, saved_gravity_);
|
||||
m->opt.viscosity = saved_viscosity_;
|
||||
m->opt.disableflags = saved_disableflags_;
|
||||
}
|
||||
|
||||
if (state == PauseState::kViscousPaused &&
|
||||
pause_state_ != PauseState::kViscousPaused && m) {
|
||||
mju_copy3(saved_gravity_, m->opt.gravity);
|
||||
saved_viscosity_ = m->opt.viscosity;
|
||||
saved_disableflags_ = m->opt.disableflags;
|
||||
mju_zero3(m->opt.gravity);
|
||||
m->opt.viscosity = 10;
|
||||
m->opt.disableflags |= mjDSBL_SPRING;
|
||||
}
|
||||
|
||||
pause_state_ = state;
|
||||
}
|
||||
|
||||
StepControl::Status StepControl::Advance(mjModel* m, mjData* d) {
|
||||
if (!m) {
|
||||
return Status::kOk;
|
||||
}
|
||||
|
||||
if (paused_) {
|
||||
|
||||
if (pause_state_ == PauseState::kNormalPaused) {
|
||||
// When we eventually unpause, we need to make sure we sync to immediately
|
||||
// and step once. Without this we could step many times before rendering
|
||||
// resulting in a noticeable delay before the simulation restarts
|
||||
@@ -151,7 +169,7 @@ StepControl::Status StepControl::Advance(const mjModel* m, mjData* d) {
|
||||
for (mjtWarning w : kDivergedWarnings) {
|
||||
if (d->warning[w].number > 0) {
|
||||
// Stop stepping if the simulation diverged.
|
||||
paused_ = true;
|
||||
pause_state_ = PauseState::kNormalPaused;
|
||||
return Status::kDiverged;
|
||||
}
|
||||
}
|
||||
@@ -179,7 +197,7 @@ void StepControl::InjectNoise(const mjModel* m, mjData* d) {
|
||||
|
||||
// convert rate and scale to discrete time (Ornstein–Uhlenbeck)
|
||||
mjtNum rate = mju_exp(-m->opt.timestep / ctrl_noise_rate_);
|
||||
mjtNum scale = ctrl_noise_std_ * mju_sqrt(1-rate*rate);
|
||||
mjtNum scale = ctrl_noise_std_ * mju_sqrt(1 - rate * rate);
|
||||
|
||||
for (int i = 0; i < m->nu; i++) {
|
||||
mjtNum bottom = 0;
|
||||
@@ -187,14 +205,14 @@ void StepControl::InjectNoise(const mjModel* m, mjData* d) {
|
||||
mjtNum midpoint = 0;
|
||||
mjtNum halfrange = 1;
|
||||
if (m->actuator_ctrllimited[i]) {
|
||||
bottom = m->actuator_ctrlrange[2*i];
|
||||
top = m->actuator_ctrlrange[2*i+1];
|
||||
midpoint = 0.5 * (top + bottom); // target of exponential decay
|
||||
bottom = m->actuator_ctrlrange[2 * i];
|
||||
top = m->actuator_ctrlrange[2 * i + 1];
|
||||
midpoint = 0.5 * (top + bottom); // target of exponential decay
|
||||
halfrange = 0.5 * (top - bottom); // scales noise
|
||||
}
|
||||
|
||||
// exponential convergence to midpoint at ctrl_noise_rate
|
||||
d->ctrl[i] = rate * d->ctrl[i] + (1-rate) * midpoint;
|
||||
d->ctrl[i] = rate * d->ctrl[i] + (1 - rate) * midpoint;
|
||||
|
||||
// add noise
|
||||
d->ctrl[i] += scale * halfrange * mju_standardNormal(nullptr);
|
||||
|
||||
@@ -36,6 +36,10 @@ class StepControl {
|
||||
// Simulation was not stepped because it is paused.
|
||||
kPaused,
|
||||
|
||||
// Simulation is viscously paused (stepping with zero gravity, high
|
||||
// viscosity, and no spring forces).
|
||||
kViscousPaused,
|
||||
|
||||
// Simulation diverged with autoreset enabled.
|
||||
kAutoReset,
|
||||
|
||||
@@ -49,7 +53,7 @@ class StepControl {
|
||||
mjWARN_BADQACC, mjWARN_BADQVEL, mjWARN_BADQPOS};
|
||||
|
||||
// Steps physics forward, respecting speed settings and refresh budget.
|
||||
Status Advance(const mjModel* m, mjData* d);
|
||||
Status Advance(mjModel* m, mjData* d);
|
||||
|
||||
// Ensures the next call to Advance() will synchronize time and step once.
|
||||
void ForceSync();
|
||||
@@ -63,13 +67,14 @@ class StepControl {
|
||||
void GetNoiseParameters(float& noise_scale, float& noise_rate) const;
|
||||
void SetNoiseParameters(float noise_scale, float noise_rate);
|
||||
|
||||
// Returns true if the simulation is paused.
|
||||
bool IsPaused() { return paused_; }
|
||||
enum class PauseState { kUnpaused, kNormalPaused, kViscousPaused };
|
||||
|
||||
// Pauses/unpauses the simulation.
|
||||
void Pause() { paused_ = true; }
|
||||
void Unpause() { paused_ = false; }
|
||||
void TogglePause() { paused_ = !paused_; }
|
||||
// Sets the pause state of the simulation.
|
||||
// m must be non-null for viscous pausing.
|
||||
void SetPauseState(PauseState state, mjModel* m = nullptr);
|
||||
|
||||
// Gets the current pause state of the simulation.
|
||||
PauseState GetPauseState() const { return pause_state_; }
|
||||
|
||||
// If the simulation is paused, will perform a single step on the next
|
||||
// Advance() call.
|
||||
@@ -104,8 +109,12 @@ class StepControl {
|
||||
// Maximum mis-alignment before re-sync (simulation seconds)
|
||||
double sync_misalign_ = .1;
|
||||
|
||||
// Whether or not the simulation is paused.
|
||||
bool paused_ = false;
|
||||
PauseState pause_state_ = PauseState::kUnpaused;
|
||||
|
||||
// Viscous pause state variables
|
||||
mjtNum saved_gravity_[3] = {0};
|
||||
mjtNum saved_viscosity_ = 0;
|
||||
int saved_disableflags_ = 0;
|
||||
|
||||
// Perform only a single step on the next call to Advance() if the simulation
|
||||
// is paused.
|
||||
|
||||
+124
-42
@@ -17,6 +17,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cfloat>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
@@ -32,8 +33,8 @@
|
||||
#include <imgui.h>
|
||||
#include <implot.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "experimental/platform/gui.h"
|
||||
#include "experimental/platform/file_dialog.h"
|
||||
#include "experimental/platform/gui.h"
|
||||
#include "experimental/platform/helpers.h"
|
||||
#include "experimental/platform/imgui_widgets.h"
|
||||
#include "experimental/platform/interaction.h"
|
||||
@@ -46,6 +47,8 @@
|
||||
|
||||
namespace mujoco::studio {
|
||||
|
||||
using PauseState = platform::StepControl::PauseState;
|
||||
|
||||
static void ToggleFlag(mjtByte& flag) { flag = flag ? 0 : 1; }
|
||||
|
||||
static void ToggleWindow(bool& window) {
|
||||
@@ -66,6 +69,7 @@ static void SelectParentPerturb(const mjModel* model, mjvPerturb& perturb) {
|
||||
|
||||
static constexpr const char* ICON_PLAY = platform::ICON_FA_PLAY;
|
||||
static constexpr const char* ICON_PAUSE = platform::ICON_FA_PAUSE;
|
||||
static constexpr const char* ICON_VISCOUS_PAUSE = platform::ICON_FA_MAGIC;
|
||||
static constexpr const char* ICON_COPY_CAMERA = platform::ICON_FA_COPY;
|
||||
static constexpr const char* ICON_UNLOAD_MODEL = platform::ICON_FA_EJECT;
|
||||
static constexpr const char* ICON_RELOAD_MODEL = platform::ICON_FA_REFRESH;
|
||||
@@ -101,8 +105,7 @@ static constexpr std::array<const char*, 31> kPercentRealTime = {
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
App::App(Config config)
|
||||
: ini_path_(std::move(config.ini_path)) {
|
||||
App::App(Config config) : ini_path_(std::move(config.ini_path)) {
|
||||
platform::Window::Config window_config;
|
||||
window_config.renderer_backend = platform::Renderer::GetBackend();
|
||||
window_config.offscreen_mode = config.offscreen_mode;
|
||||
@@ -173,11 +176,11 @@ void App::OnModelLoaded(std::string filename, ModelKind model_kind) {
|
||||
model_path_ = std::move(filename);
|
||||
|
||||
if (model_kind_ == kEmptyModel) {
|
||||
step_control_.Unpause();
|
||||
step_control_.SetPauseState(PauseState::kUnpaused);
|
||||
}
|
||||
model_kind_ = model_kind;
|
||||
if (model_kind_ == kEmptyModel) {
|
||||
step_control_.Pause();
|
||||
step_control_.SetPauseState(PauseState::kNormalPaused);
|
||||
}
|
||||
|
||||
// Reset/reinitialize everything that depends on the new mjModel.
|
||||
@@ -251,7 +254,7 @@ void App::UpdatePhysics() {
|
||||
});
|
||||
|
||||
if (!stepped) {
|
||||
if (!step_control_.IsPaused()) {
|
||||
if (step_control_.GetPauseState() != PauseState::kNormalPaused) {
|
||||
mju_zero(data()->xfrc_applied, 6 * model()->nbody);
|
||||
mjv_applyPerturbPose(model(), data(), &perturb_, 0);
|
||||
mjv_applyPerturbForce(model(), data(), &perturb_);
|
||||
@@ -299,7 +302,7 @@ void App::LoadHistory(int offset) {
|
||||
std::span<mjtNum> state = history_.SetIndex(offset);
|
||||
if (!state.empty()) {
|
||||
// Pause simulation when entering history mode.
|
||||
step_control_.Pause();
|
||||
step_control_.SetPauseState(PauseState::kNormalPaused);
|
||||
|
||||
// Load the state into the data buffer.
|
||||
mj_setState(model(), data(), state.data(), mjSTATE_INTEGRATION);
|
||||
@@ -540,19 +543,32 @@ void App::HandleKeyboardEvents() {
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Equal)) {
|
||||
SetSpeedIndex(tmp_.speed_index - 1);
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_LeftArrow)) {
|
||||
if (step_control_.IsPaused()) {
|
||||
if (step_control_.GetPauseState() == PauseState::kNormalPaused) {
|
||||
LoadHistory(history_.GetIndex() - 1);
|
||||
}
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_RightArrow)) {
|
||||
if (step_control_.IsPaused()) {
|
||||
if (step_control_.GetPauseState() == PauseState::kNormalPaused) {
|
||||
if (history_.GetIndex() == 0) {
|
||||
step_control_.RequestSingleStep();
|
||||
} else {
|
||||
LoadHistory(history_.GetIndex() + 1);
|
||||
}
|
||||
}
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiMod_Ctrl | ImGuiKey_Space)) {
|
||||
if (step_control_.GetPauseState() == PauseState::kViscousPaused) {
|
||||
step_control_.SetPauseState(PauseState::kUnpaused, model());
|
||||
} else {
|
||||
step_control_.SetPauseState(PauseState::kViscousPaused, model());
|
||||
tmp_.viscous_pause_time = ImGui::GetTime();
|
||||
}
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Space)) {
|
||||
step_control_.TogglePause();
|
||||
if (step_control_.GetPauseState() == PauseState::kViscousPaused) {
|
||||
step_control_.SetPauseState(PauseState::kNormalPaused, model());
|
||||
} else if (step_control_.GetPauseState() == PauseState::kUnpaused) {
|
||||
step_control_.SetPauseState(PauseState::kNormalPaused);
|
||||
} else {
|
||||
step_control_.SetPauseState(PauseState::kUnpaused);
|
||||
}
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_Backspace)) {
|
||||
ResetPhysics();
|
||||
} else if (ImGui_IsChordJustPressed(ImGuiKey_PageUp)) {
|
||||
@@ -854,14 +870,16 @@ void App::BuildGui() {
|
||||
style.Var(ImGuiStyleVar_Alpha, 0.6f);
|
||||
if (ImGui::Begin("Stats", &tmp_.stats)) {
|
||||
const float fps = renderer_->GetFps();
|
||||
platform::StatsGui(model(), data(), step_control_.IsPaused(), fps);
|
||||
platform::StatsGui(
|
||||
model(), data(),
|
||||
step_control_.GetPauseState() == PauseState::kNormalPaused, fps);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// Display a drag-and-drop message if no model is loaded.
|
||||
if (model_kind_ == kEmptyModel) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifndef __EMSCRIPTEN__
|
||||
const char* text = "Load model file or drag-and-drop model file here.";
|
||||
|
||||
const float width = window_->GetWidth() * ImGui::GetWindowDpiScale();
|
||||
@@ -881,7 +899,7 @@ void App::BuildGui() {
|
||||
ImGui::Text("%s", text);
|
||||
}
|
||||
ImGui::End();
|
||||
#endif // !__EMSCRIPTEN__
|
||||
#endif // !__EMSCRIPTEN__
|
||||
}
|
||||
|
||||
FileDialogGui();
|
||||
@@ -1159,6 +1177,7 @@ void App::HelpGui() {
|
||||
ImGui::Text("Toggle Fullscreen");
|
||||
ImGui::Text("Free Camera");
|
||||
ImGui::Text("Toggle Pause");
|
||||
ImGui::Text("Toggle Visc Pause");
|
||||
ImGui::Text("Reset Sim");
|
||||
ImGui::Text("Toggle Left UI");
|
||||
ImGui::Text("Toggle Right UI");
|
||||
@@ -1184,6 +1203,7 @@ void App::HelpGui() {
|
||||
ImGui::Text("F11");
|
||||
ImGui::Text("Esc");
|
||||
ImGui::Text("Spc");
|
||||
ImGui::Text("Ctrl+Spc");
|
||||
ImGui::Text("Bksp");
|
||||
ImGui::Text("Tab");
|
||||
ImGui::Text("Sh+Tab");
|
||||
@@ -1265,7 +1285,7 @@ void App::ToolBarGui() {
|
||||
const float scale = ImGui::GetWindowDpiScale();
|
||||
const float right_width = 520.f * scale;
|
||||
const ImVec2 button_size(48.f * scale, 32.f * scale);
|
||||
const ImVec2 play_button_size(120.f * scale, 32.f * scale);
|
||||
const ImVec2 play_button_size(80.f * scale, 32.f * scale);
|
||||
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed, right_width);
|
||||
@@ -1273,21 +1293,35 @@ void App::ToolBarGui() {
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", " ");
|
||||
|
||||
// Unload button.
|
||||
ImGui::SameLine();
|
||||
style.Color(ImGuiCol_ButtonHovered, red);
|
||||
if (ImGui::Button(ICON_UNLOAD_MODEL, button_size)) {
|
||||
InitEmptyModel();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Unload");
|
||||
style.Reset();
|
||||
// Combined (Unload, Reload) widget
|
||||
{
|
||||
style.Var(ImGuiStyleVar_FrameRounding, 2.0f);
|
||||
|
||||
// Reload button.
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(ICON_RELOAD_MODEL, button_size)) {
|
||||
RequestModelReload();
|
||||
// Unload button.
|
||||
ImGui::SameLine();
|
||||
{
|
||||
const ImColor a = red;
|
||||
const ImColor h(a.Value.x, a.Value.y, a.Value.z, a.Value.w * 0.6f);
|
||||
style.Color(ImGuiCol_ButtonHovered, h);
|
||||
style.Color(ImGuiCol_ButtonActive, a);
|
||||
|
||||
if (ImGui::Button(ICON_UNLOAD_MODEL, button_size)) {
|
||||
InitEmptyModel();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Unload");
|
||||
style.Reset();
|
||||
}
|
||||
|
||||
// Reload button.
|
||||
ImGui::SameLine(0, 0);
|
||||
if (ImGui::Button(ICON_RELOAD_MODEL, button_size)) {
|
||||
RequestModelReload();
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Reload");
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Reload");
|
||||
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::Text(" ");
|
||||
|
||||
// Reset button.
|
||||
ImGui::SameLine();
|
||||
@@ -1296,15 +1330,54 @@ void App::ToolBarGui() {
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Reset");
|
||||
|
||||
// Play/pause button.
|
||||
ImGui::SameLine();
|
||||
const bool paused = step_control_.IsPaused();
|
||||
style.Color(ImGuiCol_Button, paused ? yellow : green);
|
||||
if (ImGui::Button(paused ? ICON_PLAY : ICON_PAUSE, play_button_size)) {
|
||||
step_control_.TogglePause();
|
||||
ImGui::SameLine(0, 0);
|
||||
ImGui::Text(" ");
|
||||
|
||||
// Combined (Normal Pause, Viscous Pause, Play) widget
|
||||
{
|
||||
style.Var(ImGuiStyleVar_FrameRounding, 2.0f);
|
||||
|
||||
// Normal pause button.
|
||||
ImGui::SameLine();
|
||||
ImColor paused_color = yellow;
|
||||
bool paused = step_control_.GetPauseState() == PauseState::kNormalPaused;
|
||||
if (platform::ImGui_ColorButton(ICON_PAUSE, paused, paused_color,
|
||||
button_size)) {
|
||||
if (!paused) {
|
||||
step_control_.SetPauseState(PauseState::kNormalPaused, model());
|
||||
}
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Pause");
|
||||
|
||||
// Viscous pause button.
|
||||
ImGui::SameLine(0, 0);
|
||||
ImColor vpaused_color = green;
|
||||
float t = 0.f;
|
||||
bool vpaused =
|
||||
step_control_.GetPauseState() == PauseState::kViscousPaused;
|
||||
if (vpaused) {
|
||||
t = ImGui::GetTime() - tmp_.viscous_pause_time;
|
||||
t = std::sqrt(std::min(t / 0.75f, 1.0f));
|
||||
vpaused_color = ImColor(ImLerp(green.Value, yellow.Value, t));
|
||||
}
|
||||
if (platform::ImGui_ColorButton(ICON_VISCOUS_PAUSE, vpaused,
|
||||
vpaused_color, button_size,
|
||||
t < 1.0f ? 1.0f : 0.5f)) {
|
||||
if (!vpaused) {
|
||||
step_control_.SetPauseState(PauseState::kViscousPaused, model());
|
||||
tmp_.viscous_pause_time = ImGui::GetTime();
|
||||
}
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", "Viscous Pause");
|
||||
|
||||
// Play button.
|
||||
ImGui::SameLine(0, 0);
|
||||
if (platform::ImGui_ColorButton(
|
||||
ICON_PLAY, step_control_.GetPauseState() == PauseState::kUnpaused,
|
||||
green, button_size, 0.6f)) {
|
||||
step_control_.SetPauseState(PauseState::kUnpaused, model());
|
||||
}
|
||||
}
|
||||
ImGui::SetItemTooltip("%s", paused ? "Play" : "Pause");
|
||||
style.Reset();
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("%s", " |");
|
||||
@@ -1433,7 +1506,10 @@ void App::StatusBarGui() {
|
||||
|
||||
if (!has_model()) {
|
||||
ImGui::Text("No model loaded");
|
||||
} else if (step_control_.IsPaused()) {
|
||||
} else if (step_control_.GetPauseState() == PauseState::kViscousPaused) {
|
||||
ImGui::Text("Viscous Pause");
|
||||
ImGui::SetItemTooltip("Zero gravity, high viscosity, no spring forces");
|
||||
} else if (step_control_.GetPauseState() == PauseState::kNormalPaused) {
|
||||
ImGui::Text("Paused");
|
||||
} else {
|
||||
const float desired_realtime = step_control_.GetSpeed();
|
||||
@@ -1504,7 +1580,7 @@ void App::StatusBarGui() {
|
||||
void App::MainMenuGui() {
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifndef __EMSCRIPTEN__
|
||||
if (ImGui::MenuItem("Open Model File", "Ctrl+O")) {
|
||||
tmp_.file_dialog = UiTempState::FileDialog_Load;
|
||||
}
|
||||
@@ -1526,22 +1602,28 @@ void App::MainMenuGui() {
|
||||
tmp_.file_dialog = UiTempState::FileDialog_PrintData;
|
||||
}
|
||||
ImGui::Separator();
|
||||
#endif // !__EMSCRIPTEN__
|
||||
#endif // !__EMSCRIPTEN__
|
||||
if (ImGui::MenuItem("Unload", "Ctrl+U")) {
|
||||
InitEmptyModel();
|
||||
}
|
||||
#ifndef __EMSCRIPTEN__
|
||||
#ifndef __EMSCRIPTEN__
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Quit", "Ctrl+Q")) {
|
||||
tmp_.should_exit = true;
|
||||
}
|
||||
#endif // !__EMSCRIPTEN__
|
||||
#endif // !__EMSCRIPTEN__
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("Simulation")) {
|
||||
if (ImGui::MenuItem("Pause", "Space", step_control_.IsPaused())) {
|
||||
step_control_.TogglePause();
|
||||
if (ImGui::MenuItem(
|
||||
"Pause", "Space",
|
||||
step_control_.GetPauseState() == PauseState::kNormalPaused)) {
|
||||
if (step_control_.GetPauseState() != PauseState::kNormalPaused) {
|
||||
step_control_.SetPauseState(PauseState::kNormalPaused);
|
||||
} else {
|
||||
step_control_.SetPauseState(PauseState::kUnpaused, model());
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem("Reset", "Backspace")) {
|
||||
ResetPhysics();
|
||||
|
||||
@@ -123,6 +123,9 @@ class App {
|
||||
|
||||
// Controls.
|
||||
bool perturb_active = false;
|
||||
// Time at which viscous pause was activated, for the green→yellow button
|
||||
// color animation.
|
||||
double viscous_pause_time = 0;
|
||||
int speed_index = 0;
|
||||
float cam_speed = 0.0f;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user