Files
Mujoco_WASM/src/experimental/toolbox/physics.cc
T
Haroon Qureshi 4a5a36f3fc Display error when loading model.
PiperOrigin-RevId: 830862449
Change-Id: Iab1e8786d79d55db9a15a788ecb37e0b0418111c
2025-11-11 05:04:48 -08:00

232 lines
6.1 KiB
C++

// 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 <algorithm>
#include <chrono>
#include <climits>
#include <cstring>
#include <functional>
#include <ratio>
#include <string>
#include <utility>
#include <vector>
#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();
std::string model_file = std::move(pending_load_.value());
pending_load_.reset();
model_ = LoadMujocoModel(model_file, vfs_);
if (!model_) {
error_ = "Error loading model!";
paused_ = true;
model_ = LoadMujocoModel("", vfs_);
}
data_ = mj_makeData(model_);
if (!data_) {
error_ = "Error making data!";
paused_ = true;
}
on_model_loaded_(model_file);
InitHistory();
return model_ && data_;
}
void Physics::Clear() {
if (model_) {
mj_deleteData(data_);
data_ = nullptr;
mj_deleteModel(model_);
model_ = nullptr;
history_.clear();
history_cursor_ = 0;
steps_ = 0;
GetStepControl().SetSpeed(100.f);
error_ = "";
}
}
void Physics::Reset() {
mj_resetData(model_, data_);
mj_forward(model_, data_);
error_ = "";
history_cursor_ = 0;
}
bool Physics::Update(const mjvPerturb* perturb) {
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;
}
}
if (!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 (IsPaused() && !single_step_) {
// run mj_forward, to update rendering and joint sliders
mj_forward(model_, data_);
if (pause_update_) {
mju_copy(data_->qacc_warmstart, data_->qacc, model_->nv);
}
// When unpaused 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 (especially for large slowdowns)
GetStepControl().ForceSync();
} else {
if (single_step_) {
GetStepControl().ForceSync();
single_step_ = false;
}
StepControl::Status status = GetStepControl().Advance(model_, data_);
if (status == StepControl::Status::kOk) {
AddToHistory();
} else if (status == StepControl::Status::kAutoReset) {
Reset();
} else if (status == StepControl::Status::kDiverged) {
for (mjtWarning w : StepControl::kDivergedWarnings) {
if (data_->warning[w].number > 0) {
paused_ = true;
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::TogglePause() { paused_ = !paused_; }
void Physics::RequestSingleStep() { single_step_ = true; }
void Physics::InitHistory() {
const int state_size = mj_stateSize(model_, mjSTATE_INTEGRATION);
// History buffer will be smaller of 2000 states or 100 MB.
constexpr int kMaxBytes = 1e8;
constexpr int kMaxHistory = 2000;
const int state_bytes = state_size * sizeof(mjtNum);
const int history_length = std::min(INT_MAX / state_bytes, kMaxHistory);
const int history_bytes = std::min(state_bytes * history_length, kMaxBytes);
const int num_history = history_bytes / state_bytes;
history_.resize(num_history);
for (std::vector<mjtNum>& state : history_) {
state.resize(state_size, 0);
}
history_cursor_ = 0;
}
void Physics::AddToHistory() {
if (!history_.empty()) {
mjtNum* state = history_[history_cursor_].data();
mj_getState(model_, data_, state, mjSTATE_INTEGRATION);
history_cursor_ = (history_cursor_ + 1) % history_.size();
steps_++;
// If we are adding to the history we didn't have a divergence error
error_ = "";
}
}
int Physics::LoadHistory(int offset) {
// No history to load.
if (steps_ == 0) {
return 0;
}
// Pause simulation when entering history mode.
paused_ = true;
// Ensure the offset is within a valid range. It's a negative value since
// we will be going backwards from the "latest" frame.
const int max_history = std::min<int>(steps_, history_.size());
offset = std::clamp(offset, -max_history + 1, 0);
// Determine the index in the history buffer that corresponds to the frame
// index.
const int idx = (history_cursor_ + offset - 1) % history_.size();
const mjtNum* state = history_[idx].data();
// Load the state into the data buffer.
mj_setState(model_, data_, state, mjSTATE_INTEGRATION);
mj_forward(model_, data_);
return offset;
}
} // namespace mujoco::toolbox