Source code for udaan.models.quadrotor.mujoco
"""QuadrotorMujoco — MuJoCo physics backend with GLFW viewer."""
import copy
import numpy as np
from scipy.spatial.transform import Rotation as sp_rot
from ...manif import SO3, TSO3
from ...utils.logging import get_logger
from ..mujoco import MujocoModel
from .base import QuadrotorBase
_logger = get_logger(__name__)
[docs]
class QuadrotorMujoco(QuadrotorBase):
"""Quadrotor backed by MuJoCo physics engine.
Overrides step/reset to use MuJoCo instead of Euler ZOH.
Reads mass/inertia from the MJCF XML model.
"""
[docs]
def __init__(self, **kwargs):
self.render = kwargs.get("render", False)
super().__init__(**kwargs)
self._mjMdl = None
self._mj_quad_index = 1
self._attitude_zoh = kwargs.get("attitude_zoh", False)
self._mjDt = 1.0 / 500.0
self._step_iter = max(1, int(self.dt / self._mjDt))
self._nFrames = 1
if self._attitude_zoh:
self._step_iter, self._nFrames = self._nFrames, self._step_iter
# Load MuJoCo model
self._mjMdl = MujocoModel("quadrotor_mj.xml", render=self.render)
if self._force_type == QuadrotorBase.FORCE_TYPE.PROP_FORCES:
self._ctrl_index = 0
else:
self._ctrl_index = 4
# Read params from MuJoCo model
self.mass = copy.deepcopy(self._mjMdl.model.body_mass[self._mj_quad_index])
self.inertia = copy.deepcopy(self._mjMdl.model.body_inertia[self._mj_quad_index])
# Reinit controllers with MuJoCo params
self._init_default_controllers()
if self.verbose:
_logger.success("MuJoCo quadrotor loaded")
@QuadrotorBase.mass.setter
def mass(self, m):
QuadrotorBase.mass.fset(self, m)
if self._mjMdl is not None and self._mj_quad_index is not None:
self._mjMdl.model.body_mass[self._mj_quad_index] = m
else:
_logger.warning("MuJoCo model not loaded, cannot set mass")
@QuadrotorBase.inertia.setter
def inertia(self, I):
QuadrotorBase.inertia.fset(self, I)
if self._mjMdl is not None and self._mj_quad_index is not None:
if I.ndim == 2:
I = np.diag(I)
self._mjMdl.model.body_inertia[self._mj_quad_index] = I
[docs]
def reset(self, **kwargs):
self.t = 0.0
super().reset(**kwargs)
self._mjMdl.reset()
# Write state to MuJoCo
self._mjMdl.data.qpos[:3] = self.state.position
self._mjMdl.data.qvel[:3] = self.state.velocity
qQ = sp_rot.from_matrix(np.asarray(self.state.orientation)).as_quat()
self._mjMdl.data.qpos[3:7] = np.array([qQ[3], qQ[0], qQ[1], qQ[2]])
self._mjMdl.data.qvel[3:6] = np.asarray(self.state.angular_velocity)
self._query_latest_state()
# Set visual markers
if self.render and self._mjMdl._viewer is not None:
self._mjMdl._viewer.set_start(self.state.position.copy())
target = self._pos_controller.setpoint(0.0)[0]
self._mjMdl._viewer.set_target(target)
[docs]
def step(self, u, desired_att=None):
"""Step MuJoCo simulation."""
for _ in range(self._step_iter):
wrench = self._repackage_input(u, desired_att=desired_att)
# Convert to prop forces if MuJoCo expects individual motor commands
if self._force_type == QuadrotorBase.FORCE_TYPE.PROP_FORCES:
ctrl = self._wrench_to_propforces(wrench)
else:
ctrl = wrench
self._mjMdl.data.ctrl[self._ctrl_index : self._ctrl_index + 4] = ctrl
self._mjMdl._step_mujoco_simulation(self._nFrames)
self._query_latest_state()
self.t = self._mjMdl.data.time
if self.render and self._mjMdl._viewer is not None:
self._mjMdl._viewer.add_trail_point(self.state.position)
self._mjMdl._viewer.set_target(self._pos_controller.setpoint(self.t)[0])
def _query_latest_state(self):
"""Sync state from MuJoCo data."""
self.t = self._mjMdl.data.time
self.state.position = copy.deepcopy(self._mjMdl.data.qpos[:3])
q = copy.deepcopy(self._mjMdl.data.qpos[3:7])
self.state.velocity = copy.deepcopy(self._mjMdl.data.qvel[:3])
ang_vel = copy.deepcopy(self._mjMdl.data.qvel[3:6])
self.state.orientation = SO3(self._mjMdl._quat2rot(q))
self.state.angular_velocity = TSO3(np.array(ang_vel))
[docs]
def add_reference_marker(self, x):
self._mjMdl.add_marker_at(x, label="xQd")