Use logarithmic sliders in the object launcher.

Projectile parameters are positive scale factors, best adjusted
multiplicatively. Replace the input boxes with logarithmic sliders
spanning 0.1x to 10x the seeded default, which sits at the geometric
midpoint of the range; mass spans 0.1x to 100x since heavy projectiles
are a common want. Ctrl+Click still allows typing values outside the
slider range, and each slider gets a reset button restoring its
default. Adds an ImGui_SliderLog widget operating on mjtNum.

PiperOrigin-RevId: 957071873
Change-Id: Id46eae28f73308d98681ccbeb2764c949f98f9d9
This commit is contained in:
Yuval Tassa
2026-07-31 05:05:05 -07:00
committed by Copybara-Service
parent de26b05861
commit e9197e1e9d
3 changed files with 31 additions and 8 deletions
@@ -343,6 +343,14 @@ bool ImGui_Slider(const char* name, mjtNum* value, mjtNum min, mjtNum max) {
return res;
}
bool ImGui_SliderLog(const char* name, mjtNum* value, mjtNum min, mjtNum max) {
constexpr ImGuiDataType type = sizeof(mjtNum) == sizeof(double)
? ImGuiDataType_Double
: ImGuiDataType_Float;
return ImGui::SliderScalar(name, type, value, &min, &max, "%.3g",
ImGuiSliderFlags_Logarithmic);
}
bool ImGui_ResetButton(const char* id, const char* icon,
const char* tooltip) {
const float size = ImGui::GetFrameHeight();
@@ -476,6 +476,10 @@ void ImGui_EndHSplit(bool open);
// ImGui Slider that supports both float and double types.
bool ImGui_Slider(const char* name, mjtNum* value, mjtNum min, mjtNum max);
// Logarithmic slider; the geometric mean of min and max sits mid-slider.
// Ctrl+Click values typed by the user may exceed [min, max].
bool ImGui_SliderLog(const char* name, mjtNum* value, mjtNum min, mjtNum max);
// Small right-aligned reset button on the current line; returns true when
// clicked. Use after a widget to give it a visible reset affordance.
bool ImGui_ResetButton(const char* id, const char* icon = ICON_FA_UNDO,
@@ -33,15 +33,26 @@ class ObjectLauncher {
ObjectLauncher() : rng_(std::random_device{}()) {}
void UpdateGui() {
using platform::ImGui_Input;
using platform::ImGui_ResetButton;
using platform::ImGui_SliderLog;
ImGui_Input("Size", &size_,
{size_seed_ * 1e-3, size_seed_ * 1e4, size_seed_ * 0.1, size_seed_});
ImGui_Input("Speed", &speed_,
{speed_seed_ * 1e-3, speed_seed_ * 1e4, speed_seed_ * 0.1, speed_seed_});
ImGui_Input("Mass", &mass_,
{mass_seed_ * 1e-3, mass_seed_ * 1e4, mass_seed_ * 0.1, mass_seed_});
ImGui_Input("Life", &lifetime_, {0.0f, 60.0f, 0.1, 1.0});
constexpr mjtNum kLifeDefault = 5.0;
ImGui_SliderLog("Size", &size_, size_seed_ * 0.1, size_seed_ * 10);
ImGui::BeginDisabled(size_ == size_seed_);
if (ImGui_ResetButton("Size")) size_ = size_seed_;
ImGui::EndDisabled();
ImGui_SliderLog("Speed", &speed_, speed_seed_ * 0.1, speed_seed_ * 10);
ImGui::BeginDisabled(speed_ == speed_seed_);
if (ImGui_ResetButton("Speed")) speed_ = speed_seed_;
ImGui::EndDisabled();
ImGui_SliderLog("Mass", &mass_, mass_seed_ * 0.1, mass_seed_ * 100);
ImGui::BeginDisabled(mass_ == mass_seed_);
if (ImGui_ResetButton("Mass")) mass_ = mass_seed_;
ImGui::EndDisabled();
ImGui_SliderLog("Life", &lifetime_, 0.5, 50.0);
ImGui::BeginDisabled(lifetime_ == kLifeDefault);
if (ImGui_ResetButton("Life")) lifetime_ = kLifeDefault;
ImGui::EndDisabled();
int shape = type_ == mjGEOM_BOX ? 0 : 1;
const char* names[] = {"Box", "Sphere"};