Mujoco KDL Wrapper  0.1.0
MuJoCo + KDL bridge for robot kinematics and dynamics
Loading...
Searching...
No Matches
mj_kdl_wrapper.hpp
Go to the documentation of this file.
1/* SPDX-License-Identifier: MIT
2 * Copyright (c) 2026 Vamsi Kalagaturu
3 * See LICENSE for details. */
4
5#pragma once
6
7#include <mujoco/mujoco.h>
8#include <GLFW/glfw3.h>
9#include <kdl/chain.hpp>
10#include <kdl/frames.hpp>
11#include <kdl/jntarray.hpp>
12#include <functional>
13#include <string>
14#include <vector>
15#include <chrono>
16
17namespace mj_kdl {
18
19/**
20 * @ingroup grp_logging
21 * Log verbosity level. Each level includes all levels below it:
22 * NONE - nothing printed.
23 * INFO - informational messages only (scene/chain construction progress).
24 * WARN - INFO + recoverable warnings (e.g. fallback to headless mode).
25 * ERROR - all messages, including errors that cause functions to fail. Default.
26 */
27enum class LogLevel { NONE = 0, INFO = 1, WARN = 2, ERROR = 3 };
28
29/** @ingroup grp_logging
30 * Library-wide log verbosity (inline so one shared instance across all TUs). */
32
33/** @ingroup grp_logging
34 * Set the library-wide log verbosity. */
35inline void set_log_level(LogLevel level) { g_log_level = level; }
36/** @ingroup grp_logging
37 * Get the library-wide log verbosity. */
39
40/**
41 * @ingroup grp_types
42 * One link in an ordered attachment chain for a robot.
43 * An attachment is any MJCF body (end effector, mount, FT sensor, tool, additional arm
44 * on a mobile base, etc.) attached under a named body in the accumulated robot spec.
45 * Attachments are applied in declaration order; attach_to may reference any body present
46 * after all prior attachments have been applied.
47 */
49{
50 const char *mjcf_path = nullptr; // MJCF file for this attachment
51 const char *attach_to = nullptr; // body to attach under (in root or prior attachment)
52 const char *prefix = ""; // element name prefix (avoids name conflicts)
53 double pos[3] = { 0, 0, 0 }; // position offset [m]
54 double euler[3] = { 0, 0, 0 }; // extrinsic XYZ Euler offset [degrees]
55
56 /* Contact exclusion pairs registered by attach_to_spec(). */
57 std::vector<std::pair<std::string, std::string>> contact_exclusions; // (body1, body2) pairs
58};
59
60/**
61 * @ingroup grp_types
62 * One robot in a scene: a root MJCF (arm, mobile base, ...) with an ordered attachment
63 * chain and world-frame placement.
64 *
65 * attachments is applied in order by build_scene() / attach_to_spec(): each entry's
66 * attach_to may reference any body in the accumulated spec (root + all prior attachments).
67 * This naturally supports: fixed arm, arm+gripper, arm+mount+FT+gripper, mobile base,
68 * mobile manipulator (base root, arm as first attachment), and so on.
69 *
70 * path is the root MJCF passed to build_scene(). prefix must be unique per robot
71 * in multi-robot scenes.
72 */
74{
75 const char *path = nullptr; // root MJCF path
76 const char *prefix = ""; // element name prefix
77 double pos[3] = { 0, 0, 0 }; // world-frame position [m]
78 double euler[3] = { 0, 0, 0 }; // extrinsic XYZ Euler angles [degrees]
79 std::vector<AttachmentSpec> attachments; // ordered attachment chain; empty = none
80};
81
82/** @ingroup grp_types
83 * Shape type for scene objects. */
84enum class Shape { BOX, SPHERE, CYLINDER };
85
86/**
87 * @ingroup grp_types
88 * A free-floating or fixed rigid body to place in the scene.
89 *
90 * size:
91 * BOX - half-extents (x, y, z)
92 * SPHERE - {radius, 0, 0}
93 * CYLINDER - {radius, half-length, 0}
94 * Ignored when mjcf_path is set.
95 *
96 * pos:
97 * World-frame position. For MJCF assets, this is the placement frame for the
98 * asset's first root body.
99 *
100 * fixed:
101 * If true the body is welded to the world (no freejoint); useful for
102 * static obstacles or fixtures. Ignored when mjcf_path is set.
103 */
105{
106 std::string name;
107 std::string mjcf_path; // optional MJCF asset; when set, shape/size/mass/friction are ignored
109 double size[3] = { 0.03, 0.03, 0.03 };
110 double pos[3] = { 0.0, 0.0, 0.0 };
111 float rgba[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
112 bool fixed = false;
113 double mass = 0.1;
114 int condim = 3;
115 double friction[3] = { 0.5, 0.005, 0.0001 }; // MuJoCo defaults
116};
117
118/**
119 * @ingroup grp_types
120 * A named fixed camera to add to the world body of the scene.
121 * After build_scene() the camera is accessible by name via get_camera_names()
122 * and can be activated on a Viewer or VideoRecorder with use_camera().
123 */
125{
126 std::string name;
127 double pos[3] = { 0.0, 0.0, 1.0 }; // world-frame position [m]
128 double euler[3] = { 0.0, 0.0, 0.0 }; // extrinsic XYZ Euler [degrees]
129 double fovy = 45.0; // vertical field of view [degrees]
130};
131
132/** @ingroup grp_types
133 * Full scene description passed to build_scene(). */
135{
136 std::vector<RobotSpec> robots;
137 double timestep = 0.002;
138 double gravity_z = -9.81;
139 bool add_floor = true; // checker groundplane geom
140 bool add_skybox = true; // gradient sky texture + directional light
141 std::vector<SceneObject> objects;
142 std::vector<CameraSpec> cameras; // static world cameras added to worldbody
143};
144
145/**
146 * @ingroup grp_types
147 * Optional tool/end-effector description used while building the KDL chain.
148 *
149 * tool_body names the root of the attached tool subtree whose mass/inertia is
150 * lumped into the arm dynamics. tcp_site names an authored MuJoCo site that
151 * becomes the KDL terminal frame for FK/IK (takes priority when set). When
152 * the model has no suitable site, tcp_frame provides an equivalent manual
153 * transform expressed in the tip body's local frame.
154 * For the prefixed Robotiq 2F-85 this is typically {"g_base", "g_pinch"}.
155 */
157{
158 const char *tool_body = nullptr;
159 const char *tcp_site = nullptr; // MuJoCo site name (takes priority)
160 KDL::Frame tcp_frame = KDL::Frame::Identity(); // manual TCP in tip frame (fallback)
161};
162
163/**
164 * @ingroup grp_types
165 * Joint-space control mode for update().
166 * POSITION - writes jnt_pos_cmd to actuator ctrl inputs.
167 * TORQUE - writes jnt_trq_cmd to qfrc_applied (generalized forces).
168 */
169enum class CtrlMode { POSITION, TORQUE };
170
171/**
172 * @ingroup grp_types
173 * Runtime handle for one KDL-tracked articulation inside a MuJoCo scene.
174 * model/data are borrowed (never freed by cleanup()); call destroy_scene() separately.
175 *
176 * Workflow:
177 * 1. Call init_robot_from_mjcf() - populates configuration and sizes port vectors to n_joints.
178 * 2. Each control step: read *_msr ports (updated by update()), fill *_cmd ports,
179 * call update() to apply commands to MuJoCo and read back sensor state.
180 */
181struct Robot
182{
183 /* Configuration - set once by init_robot() / init_from_mjcf(). */
184 mjModel *model = nullptr;
185 mjData *data = nullptr;
186 KDL::Chain chain;
187 KDL::Frame tip_T_tcp = KDL::Frame::Identity();
188 bool has_tcp_frame = false;
189 std::string tcp_site;
190 int n_joints = 0;
191 std::vector<std::string> joint_names;
192 std::vector<std::pair<double, double>> joint_limits;
193
194 /* Ports - read/written each control cycle. */
196 bool paused = false;
197 std::vector<double> jnt_pos_msr; // [rad] - measured joint positions (written by update())
198 std::vector<double> jnt_vel_msr; // [rad/s] - measured joint velocities (written by update())
199 std::vector<double> jnt_trq_msr; // [Nm] - actuator output torques (written by update())
200 std::vector<double> jnt_pos_cmd; // [rad] - position setpoints (POSITION mode)
201 std::vector<double> jnt_trq_cmd; // [Nm] - torque commands (TORQUE mode)
202
203 /* Internal state - populated by init_robot() / init_from_mjcf(). */
204 std::vector<int> kdl_to_mj_qpos; // KDL index -> MuJoCo qpos address
205 std::vector<int> kdl_to_mj_dof; // KDL index -> MuJoCo dof address
206 std::vector<int> kdl_to_mj_ctrl; // KDL index -> MuJoCo ctrl index (-1 if none)
207};
208
209/**
210 * @ingroup grp_viewer
211 * GLFW window and MuJoCo visualization state for the manual render loop.
212 * Created by init_window(); freed by cleanup(Viewer *).
213 */
214struct Viewer
215{
216 GLFWwindow *window = nullptr;
217 mjvScene scn{};
218 mjvCamera cam{};
219 mjvOption opt{};
220 mjvPerturb pert{};
221 mjrContext con{};
222 /* Real-time factor controlling simulation speed in step()/tick().
223 * 1.0 = real-time (default), 2.0 = 2x faster, 0.5 = half speed.
224 * Keyboard: ',' slows down, '.' speeds up in both viewer modes.
225 * 0.0 means run as fast as possible (no sleep). */
226 double realtime_factor = 1.0;
227 /* internal: real-time pacing state used by tick(). */
228 std::chrono::steady_clock::time_point _tick_t{};
229 /* internal: non-null when init_window_sim() is used; holds SimUiState*. */
230 void *_sim_ui = nullptr;
231};
232
233/**
234 * @ingroup grp_recorder
235 * Standard output resolution presets for init_video_recorder().
236 * Each maps to a 16:9 frame size at the named quality level.
237 */
238enum class VideoResolution {
239 R360p = 360, // 640 x 360
240 R480p = 480, // 854 x 480
241 R720p = 720, // 1280 x 720
242 R1080p = 1080, // 1920 x 1080
243 R2K = 1440, // 2560 x 1440
244 R4K = 2160, // 3840 x 2160
245};
246
247/**
248 * @ingroup grp_recorder
249 * Headless video recorder. Renders frames to an EGL offscreen buffer and
250 * pipes raw RGB data to an ffmpeg process, producing an H.264 MP4 without a
251 * display server or GLFW window.
252 *
253 * Requirements: EGL (libegl-dev) and ffmpeg available in PATH.
254 *
255 * Typical usage:
256 *
257 * VideoRecorder vr;
258 * init_video_recorder(&vr, model, "sim.mp4", VideoResolution::R1080p);
259 * vr.cam.azimuth = 135; vr.cam.elevation = -20; vr.cam.distance = 2.5;
260 *
261 * for (int i = 0; i < steps; ++i) {
262 * mj_step(model, data);
263 * record_frame(&vr, model, data);
264 * }
265 *
266 * cleanup(&vr);
267 */
269{
270 mjvCamera cam{}; // camera configuration; modify freely between frames
271 mjvOption opt{}; // rendering options; modify freely between frames
272 void *_impl = nullptr; // opaque EGL + ffmpeg state
273};
274
275struct Env;
276
277/** @ingroup grp_env
278 * Options controlling an environment reset. */
280{
281 int keyframe = 0; // keyframe index to use when available
282 bool use_keyframe = true; // fall back to mj_resetData when false or invalid
283};
284
285/** @ingroup grp_env
286 * Information returned by reset(). */
288{
289 bool used_keyframe = false;
290 int keyframe = -1;
291};
292
293/** @ingroup grp_env
294 * Runtime context passed to Env::on_reset after MuJoCo data has been reset and
295 * before mj_forward() and robot command-port synchronisation. */
297{
298 Env *env = nullptr;
299 mjModel *model = nullptr;
300 mjData *data = nullptr;
301 const ResetOptions *options = nullptr;
302 ResetInfo *info = nullptr;
303};
304
305using ResetHook = std::function<void(ResetContext *)>;
306
307/** @ingroup grp_env
308 * Runtime environment instance: declarative SceneSpec plus compiled MuJoCo
309 * model/data and Robot handles that should be synchronised after reset.
310 *
311 * Env owns model/data created by init_env(); registered Robot pointers are
312 * borrowed and are never deleted by cleanup(Env *). Robot::model/data remain
313 * borrowed aliases for compatibility with the existing robot-centric API.
314 */
315struct Env
316{
318 mjModel *model = nullptr;
319 mjData *data = nullptr;
320 std::vector<Robot *> robots;
322};
323
324/**
325 * @ingroup grp_scene
326 * Save the compiled model to an MJCF XML file for later reloading via build_scene().
327 * Must be called with the model returned by the most recent build_scene() call -
328 * MuJoCo only retains the last compiled model's XML internally.
329 * Typical use: build a combined scene (dual-arm, arm+gripper, ...) once, save it,
330 * then reload via build_scene() in subsequent runs to skip all build steps.
331 * @param model Model to save; must be the most recently compiled model.
332 * @param path Output path for the MJCF XML file.
333 * @return true on success.
334 */
335bool save_model_xml(const mjModel *model, const char *path);
336
337/**
338 * @ingroup grp_robot
339 * Build KDL chain from a compiled MuJoCo model and optional tool/TCP metadata.
340 *
341 * If tool->tcp_site is set, that authored site becomes the KDL terminal frame
342 * for FK/IK. The joint count and MuJoCo joint/actuator maps still cover only
343 * the controllable joints from base_body to tip_body.
344 * Pass tool = nullptr (the default) for an arm with no attached tool.
345 */
347 Robot *r,
348 mjModel *model,
349 mjData *data,
350 const char *base_body,
351 const char *tip_body,
352 const char *prefix = "",
353 const ToolFrameSpec *tool = nullptr
354);
355
356/**
357 * @ingroup grp_scene
358 * Apply one attachment to an arm spec using the MuJoCo spec API (mjs_attach).
359 * Parses a->mjcf_path, attaches its first root body under a->attach_to with the given
360 * pos/euler offset, prefixes all element names with a->prefix, and registers contact
361 * exclusions via mjs_addExclude. Can be called repeatedly to build a chain: each
362 * subsequent a->attach_to may reference any body added by prior calls.
363 * @param[in,out] robot_spec Accumulated robot spec to attach into.
364 * @param[in] a Attachment; a->mjcf_path must not be null.
365 * @return true on success.
366 */
367bool attach_to_spec(mjSpec *robot_spec, const AttachmentSpec *a);
368
369/**
370 * @ingroup grp_scene
371 * Build a MuJoCo scene from one or more robots using the MuJoCo spec API.
372 * This is the primary scene-building function.
373 *
374 * For each RobotSpec: mj_parseXML loads the root MJCF, then attach_to_spec() applies
375 * each entry in RobotSpec::attachments in order (mount, sensor, gripper, etc.),
376 * and mjs_attach places the complete robot spec at the given position. A single
377 * mj_compile produces the final model -- no intermediate XML files are written.
378 *
379 * @param[out] out_model Newly allocated MuJoCo model; caller frees via destroy_scene().
380 * @param[out] out_data Newly allocated MuJoCo data; caller frees via destroy_scene().
381 * @param[in] spec Scene description: robots (with attachment chains), table,
382 * objects, timestep, gravity, floor, skybox.
383 * @return true on success.
384 */
385bool build_scene(mjModel **out_model, mjData **out_data, const SceneSpec *spec);
386
387/**
388 * @ingroup grp_scene
389 * Free a model/data pair allocated by any scene-building function.
390 * @param[in] model Model to free (may be null).
391 * @param[in] data Data to free (may be null).
392 */
393void destroy_scene(mjModel *model, mjData *data);
394
395/**
396 * @ingroup grp_env
397 * Build a runtime environment from a declarative SceneSpec.
398 * The resulting model/data are owned by env and freed by cleanup(Env *).
399 */
400bool init_env(Env *env, const SceneSpec *spec);
401
402/**
403 * @ingroup grp_env
404 * Register a Robot handle to be synchronised after environment reset.
405 * The robot is borrowed; the Env does not delete or clean it up.
406 */
407void env_add_robot(Env *env, Robot *robot);
408
409/**
410 * @ingroup grp_env
411 * Reset the whole environment to a keyframe/default state, call Env::on_reset
412 * for user-specific robot/object/task restoration, then sync all registered
413 * Robot command ports and clear stale robot forces.
414 */
415ResetInfo reset(Env *env, const ResetOptions *options = nullptr);
416
417
418/**
419 * @ingroup grp_viewer
420 * Open a GLFW window and initialise MuJoCo visualization contexts.
421 * Must be called after init_robot() or init_from_mjcf().
422 * @param[out] v Viewer to initialise; must be zero-initialised before call.
423 * @param[in] r Robot whose model drives the rendering context.
424 * @param[in] title Window title string.
425 * @param[in] width Window width in pixels.
426 * @param[in] height Window height in pixels.
427 * @return true on success, false if GLFW or MuJoCo context creation fails.
428 */
430 Viewer *v,
431 Robot *r,
432 const char *title = "MuJoCo",
433 int width = 1280,
434 int height = 720
435);
436
437/**
438 * @ingroup grp_viewer
439 * Open the full MuJoCo simulate UI (panels, physics controls, joint viewer)
440 * in a background render thread, then return so the caller can drive the
441 * physics loop with tick().
442 *
443 * Use this instead of init_window() when you want the simulate UI panels
444 * alongside a user-owned loop. tick() automatically acquires the render
445 * mutex, steps physics, and handles pause / perturbation / speed controls.
446 *
447 * Note: the render thread owns the GLFW window; on Linux (X11 / Wayland)
448 * this works correctly. Not supported on macOS.
449 *
450 * @param[out] v Viewer to initialise; freed by cleanup(Viewer *).
451 * @param[in] r Robot to simulate. r is registered globally; pass the same
452 * Robot to every subsequent step() call so that keyboard and
453 * mouse perturbation callbacks operate on the correct model.
454 * Only one (Viewer, Robot) pair may be active at a time.
455 * @param[in] title Label shown in the window title bar (default "MuJoCo").
456 * @return true on success.
457 */
458bool init_window_sim(Viewer *v, Robot *r, const char *title = "MuJoCo");
459
460/**
461 * @ingroup grp_robot
462 * Zero all Robot fields. Does not free model or data; call destroy_scene() for that.
463 * @param[in,out] r Robot to tear down.
464 */
465void cleanup(Robot *r);
466
467/**
468 * @ingroup grp_env
469 * Destroy model/data owned by Env and clear borrowed Robot registrations.
470 * Registered Robot objects are not deleted.
471 */
472void cleanup(Env *env);
473
474/**
475 * @ingroup grp_viewer
476 * Release the GLFW window and MuJoCo visualization contexts owned by v.
477 * @param[in,out] v Viewer to tear down; all pointers set to null afterwards.
478 */
480
481/**
482 * @ingroup grp_recorder
483 * Initialise a headless EGL video recorder.
484 * Creates an EGL context, an offscreen render target, and launches an ffmpeg
485 * process (H.264/MP4) via a pipe. The MuJoCo model is used to size the scene
486 * and initialise the rendering context; it must remain valid until cleanup().
487 *
488 * @param vr VideoRecorder to initialise; freed by cleanup(VideoRecorder*).
489 * @param model MuJoCo model for the rendering context.
490 * @param out_path Output MP4 path (e.g. "sim.mp4").
491 * @param width Frame width in pixels (default 1280).
492 * @param height Frame height in pixels (default 720).
493 * @param fps Playback frame rate (default 60).
494 * @return true on success; false if EGL init or ffmpeg launch fails.
495 */
497 VideoRecorder *vr,
498 mjModel *model,
499 const char *out_path,
500 int width = 1280,
501 int height = 720,
502 int fps = 60
503);
504
505/**
506 * @ingroup grp_recorder
507 * Convenience overload: initialise a VideoRecorder using a named resolution preset.
508 * Frame width is derived from the preset at 16:9 aspect ratio.
509 *
510 * @param vr VideoRecorder to initialise.
511 * @param model MuJoCo model.
512 * @param out_path Output MP4 path.
513 * @param resolution VideoResolution preset (e.g. VideoResolution::R1080p).
514 * @param fps Playback frame rate (default 60).
515 * @return true on success.
516 */
518 VideoRecorder *vr,
519 mjModel *model,
520 const char *out_path,
521 VideoResolution resolution,
522 int fps = 60
523);
524
525/**
526 * @ingroup grp_recorder
527 * Render the current simulation state and write one frame to the video stream.
528 * Call mj_step() (or equivalent) before each record_frame() call.
529 *
530 * @param vr VideoRecorder initialised by init_video_recorder().
531 * @param model MuJoCo model.
532 * @param data MuJoCo data (current state).
533 * @return true on success; false on render or pipe write error.
534 */
535bool record_frame(VideoRecorder *vr, mjModel *model, mjData *data);
536
537/**
538 * @ingroup grp_recorder
539 * Flush the ffmpeg pipe, finalise the MP4, and release all EGL resources.
540 * After this call vr->_impl is null and the VideoRecorder may be discarded.
541 *
542 * @param vr VideoRecorder to tear down.
543 */
545
546/**
547 * @ingroup grp_robot
548 * Advance one physics timestep.
549 *
550 * Headless (no viewer active): calls mj_step() and returns true.
551 * GUI (init_window_sim() was called): advances physics, renders, syncs to real
552 * time, and polls GLFW events -- exactly what tick() used to do. Returns false
553 * once the user closes the window.
554 *
555 * This replaces the old headless/GUI split:
556 *
557 * // Before:
558 * if (headless) { mj_kdl::step(&r); }
559 * else if (!mj_kdl::tick(&v, m, d)) break;
560 *
561 * // After:
562 * if (!mj_kdl::step(&r)) break;
563 *
564 * Coupling note: in GUI mode the keyboard and mouse perturbation callbacks
565 * operate on the Robot registered via init_window_sim(). Always pass the
566 * same Robot to both init_window_sim() and step(); passing a different Robot
567 * causes perturbation forces to be applied to the wrong model.
568 *
569 * @param[in,out] s Simulation state; must be the Robot passed to init_window_sim().
570 * @return true while the window is open (or always true in headless mode).
571 */
572bool step(Robot *s);
573
574/**
575 * @ingroup grp_robot
576 * Advance the simulation by n timesteps.
577 * Returns false immediately if the viewer window is closed mid-sequence.
578 * @param[in,out] s Simulation state.
579 * @param[in] n Number of steps.
580 */
581bool step_n(Robot *s, int n);
582
583/**
584 * @ingroup grp_viewer
585 * Model/data overload of step() for multi-robot or no-robot GUI loops.
586 * Equivalent to the former tick(Viewer*, mjModel*, mjData*).
587 * @param[in,out] v Viewer initialised by init_window() or init_window_sim().
588 * @param[in] m Shared MuJoCo model.
589 * @param[in] d Shared MuJoCo data.
590 * @return true while the window is open; false once the user closes it.
591 */
592bool step(Viewer *v, mjModel *m, mjData *d);
593
594/**
595 * @ingroup grp_viewer
596 * Returns true if the viewer window is open and not scheduled for closing.
597 * @param[in] v Viewer created by init_window().
598 */
599bool is_running(const Viewer *v);
600
601/**
602 * @ingroup grp_viewer
603 * Render the current simulation frame to the viewer window.
604 * @param[in,out] v Viewer created by init_window().
605 * @param[in] r Robot whose model and data are rendered.
606 * @return true if the window is still open after rendering.
607 */
608bool render(Viewer *v, const Robot *r);
609
610/**
611 * @ingroup grp_viewer
612 * Render the current simulation frame to the viewer window.
613 * Model/data overload -- use when no single Robot owns the scene (e.g. multi-robot).
614 * @param[in,out] v Viewer created by init_window().
615 * @param[in] m MuJoCo model.
616 * @param[in] d MuJoCo data.
617 * @return true if the window is still open after rendering.
618 */
619bool render(Viewer *v, mjModel *m, mjData *d);
620
621/**
622 * @ingroup grp_robot
623 * One control cycle: read MuJoCo into *_msr, then apply *_cmd to MuJoCo.
624 * Read step: qpos -> jnt_pos_msr, qvel -> jnt_vel_msr, qfrc_actuator -> jnt_trq_msr.
625 * Apply step: POSITION -> data->ctrl,
626 * TORQUE -> qfrc_applied; also sets ctrl = qpos to neutralize
627 * position actuators (zeroes kp*(ctrl-qpos) restoring force).
628 * Joints with kdl_to_mj_ctrl[i] == -1 are skipped for ctrl writes.
629 */
630void update(Robot *r);
631
632/**
633 * @ingroup grp_robot
634 * Write KDL joint positions into MuJoCo qpos (KDL chain order -> MuJoCo addresses).
635 * @param[in,out] r Robot with a valid data pointer.
636 * @param[in] q Joint positions in KDL chain order; size must equal r->n_joints.
637 * @param[in] call_forward If true (default), calls mj_forward() after writing qpos
638 * so that body poses and sensor data are updated immediately.
639 */
640void set_joint_pos(Robot *r, const KDL::JntArray &q, bool call_forward = true);
641
642/**
643 * @ingroup grp_robot
644 * Teleport a free-floating body to a world-frame position and optionally a
645 * world-frame orientation, then zero its velocity.
646 * body_name must identify a body that owns a mjJNT_FREE joint.
647 * quat is MuJoCo convention [w, x, y, z]; pass nullptr to keep identity orientation.
648 * @param[in,out] model MuJoCo model.
649 * @param[in,out] data MuJoCo data.
650 * @param[in] body_name Name of the free-floating body to teleport.
651 * @param[in] pos World-frame position [x, y, z].
652 * @param[in] quat World-frame orientation [w, x, y, z], or nullptr for identity.
653 */
655 mjModel *model,
656 mjData *data,
657 const char *body_name,
658 const double pos[3],
659 const double *quat = nullptr
660);
661
662/**
663 * @ingroup grp_scene
664 * Add an object to the scene by appending it to spec->objects and rebuilding
665 * the model. The old model/data are freed; new ones replace them.
666 * Any Robot handles sharing the old model/data become stale - call init_robot()
667 * again on the new model/data after this call.
668 * @param[in,out] model Current model pointer; updated to new model on success.
669 * @param[in,out] data Current data pointer; updated to new data on success.
670 * @param[in,out] spec Scene spec; obj is appended to spec->objects.
671 * @param[in] obj Object to add.
672 * @return true on success; model/data and spec->objects unchanged on failure.
673 */
674bool scene_add_object(mjModel **model, mjData **data, SceneSpec *spec, const SceneObject &obj);
675
676/**
677 * @ingroup grp_scene
678 * Env overload: adds obj, rebuilds, and re-initialises all robots registered in env.
679 * env->model, env->data, and each Robot's model/data pointers are updated automatically.
680 * @return true on success; env unchanged on failure.
681 */
682bool scene_add_object(Env *env, const SceneObject &obj);
683
684/**
685 * @ingroup grp_scene
686 * Remove a named object from the scene by erasing it from spec->objects and
687 * rebuilding the model. The old model/data are freed; new ones replace them.
688 * Any Robot handles sharing the old model/data become stale - call init_robot()
689 * again on the new model/data after this call.
690 * @param[in,out] model Current model pointer; updated to new model on success.
691 * @param[in,out] data Current data pointer; updated to new data on success.
692 * @param[in,out] spec Scene spec; named object removed from spec->objects.
693 * @param[in] name Name of the object to remove.
694 * @return true on success; false if name not found or rebuild fails.
695 */
696bool scene_remove_object(mjModel **model, mjData **data, SceneSpec *spec, const std::string &name);
697
698/**
699 * @ingroup grp_scene
700 * Env overload: removes the named object, rebuilds, and re-initialises all robots registered
701 * in env. env->model, env->data, and each Robot's model/data pointers are updated automatically.
702 * @return true on success; false if name not found or rebuild fails.
703 */
704bool scene_remove_object(Env *env, const std::string &name);
705
706/**
707 * @ingroup grp_scene
708 * Return the compiled MuJoCo name for a site inside an MJCF-backed SceneObject.
709 * build_scene() prefixes all MJCF asset element names with obj.name + "_".
710 */
711std::string scene_object_site_name(const SceneObject &obj, const char *site_name);
712
713/**
714 * @ingroup grp_scene
715 * Read a named MuJoCo site as a world-frame KDL frame.
716 * Calls mj_forward() before reading site_xpos/site_xmat.
717 */
718bool get_site_frame(const mjModel *model, mjData *data, const char *site_name, KDL::Frame *out);
719
720/**
721 * @ingroup grp_scene
722 * Read a named MuJoCo body as a world-frame KDL frame.
723 * Calls mj_forward() before reading xpos/xmat.
724 */
725bool get_body_frame(const mjModel *model, mjData *data, const char *body_name, KDL::Frame *out);
726
727/**
728 * @ingroup grp_scene
729 * Return the names of all cameras in a compiled model.
730 * Includes cameras from robot MJCFs (e.g. the Kinova wrist camera) and any
731 * cameras added via SceneSpec::cameras.
732 */
733std::vector<std::string> get_camera_names(const mjModel *model);
734
735/**
736 * @ingroup grp_viewer
737 * Switch the viewer to a named fixed camera defined in the model.
738 * Works for both init_window() and init_window_sim() paths.
739 * Pass nullptr or an empty string to return to the free camera.
740 * @return true if the camera name was found; false if not found (viewer unchanged).
741 */
742bool use_camera(Viewer *v, const mjModel *model, const char *name);
743
744/**
745 * @ingroup grp_recorder
746 * Switch the video recorder to a named fixed camera defined in the model.
747 * @return true if the camera name was found; false if not found (recorder unchanged).
748 */
749bool use_camera(VideoRecorder *vr, const mjModel *model, const char *name);
750
751/**
752 * Internal spec-building helpers.
753 *
754 * These are used internally by build_scene() and configure_spec(), but are
755 * exposed here for advanced callers that construct mjSpec objects directly.
756 * They are not part of the stable public API and may change between releases.
757 */
758
759/**
760 * @ingroup grp_advanced
761 * Add a sky gradient texture and overhead directional light to spec.
762 * Corresponds to SceneSpec::add_skybox.
763 */
764void add_skybox_to_spec(mjSpec *spec);
765
766/**
767 * @ingroup grp_advanced
768 * Add a checker groundplane texture, material, and floor plane geom to spec.
769 * Corresponds to SceneSpec::add_floor.
770 */
771void add_floor_to_spec(mjSpec *spec);
772
773/**
774 * @ingroup grp_advanced
775 * Add free-floating or fixed rigid bodies to the world body of spec.
776 * @param spec MuJoCo spec to modify.
777 * @param objects List of objects to add.
778 */
779void add_objects_to_spec(mjSpec *spec, const std::vector<SceneObject> &objects);
780
781/**
782 * @ingroup grp_advanced
783 * Apply all SceneSpec settings to a parsed mjSpec:
784 * physics options (timestep, gravity), compiler flags, and scene decorations
785 * (skybox, floor, table, objects) according to the flags in sc.
786 */
787void configure_spec(mjSpec *spec, const SceneSpec *sc);
788
789/**
790 * @ingroup grp_advanced
791 * Compile spec into a model and create its data buffer.
792 * spec is always deleted (on success and failure).
793 * @param[in] spec MuJoCo spec to compile; always freed by this call.
794 * @param[out] out_model Newly allocated model on success; null on failure.
795 * @param[out] out_data Newly allocated data on success; null on failure.
796 * @return true on success.
797 */
798bool compile_and_make_data(mjSpec *spec, mjModel **out_model, mjData **out_data);
799
800/**
801 * @ingroup grp_advanced
802 * Load MuJoCo decoder plugins (STL, OBJ, ...) once at first use.
803 * Required for external mesh decoder plugin libraries.
804 * Called automatically by all scene-building functions; call explicitly only
805 * when building a scene via raw mjSpec APIs without going through the library.
806 */
808
809} // namespace mj_kdl
void add_skybox_to_spec(mjSpec *spec)
bool compile_and_make_data(mjSpec *spec, mjModel **out_model, mjData **out_data)
void add_objects_to_spec(mjSpec *spec, const std::vector< SceneObject > &objects)
void add_floor_to_spec(mjSpec *spec)
void ensure_plugins_loaded()
void configure_spec(mjSpec *spec, const SceneSpec *sc)
void set_log_level(LogLevel level)
LogLevel get_log_level()
LogLevel g_log_level
bool init_video_recorder(VideoRecorder *vr, mjModel *model, const char *out_path, int width=1280, int height=720, int fps=60)
bool record_frame(VideoRecorder *vr, mjModel *model, mjData *data)
void set_body_pose(mjModel *model, mjData *data, const char *body_name, const double pos[3], const double *quat=nullptr)
bool step_n(Robot *s, int n)
bool step(Robot *s)
void cleanup(Robot *r)
bool init_robot_from_mjcf(Robot *r, mjModel *model, mjData *data, const char *base_body, const char *tip_body, const char *prefix="", const ToolFrameSpec *tool=nullptr)
void set_joint_pos(Robot *r, const KDL::JntArray &q, bool call_forward=true)
void update(Robot *r)
bool get_site_frame(const mjModel *model, mjData *data, const char *site_name, KDL::Frame *out)
bool get_body_frame(const mjModel *model, mjData *data, const char *body_name, KDL::Frame *out)
bool build_scene(mjModel **out_model, mjData **out_data, const SceneSpec *spec)
bool attach_to_spec(mjSpec *robot_spec, const AttachmentSpec *a)
bool scene_add_object(mjModel **model, mjData **data, SceneSpec *spec, const SceneObject &obj)
bool scene_remove_object(mjModel **model, mjData **data, SceneSpec *spec, const std::string &name)
void destroy_scene(mjModel *model, mjData *data)
std::vector< std::string > get_camera_names(const mjModel *model)
bool save_model_xml(const mjModel *model, const char *path)
std::string scene_object_site_name(const SceneObject &obj, const char *site_name)
bool init_window_sim(Viewer *v, Robot *r, const char *title="MuJoCo")
bool use_camera(Viewer *v, const mjModel *model, const char *name)
bool init_window(Viewer *v, Robot *r, const char *title="MuJoCo", int width=1280, int height=720)
bool render(Viewer *v, const Robot *r)
bool is_running(const Viewer *v)
void env_add_robot(Env *env, Robot *robot)
ResetInfo reset(Env *env, const ResetOptions *options=nullptr)
bool init_env(Env *env, const SceneSpec *spec)
std::function< void(ResetContext *)> ResetHook
std::vector< std::pair< std::string, std::string > > contact_exclusions
ResetHook on_reset
std::vector< Robot * > robots
const ResetOptions * options
std::vector< AttachmentSpec > attachments
std::vector< int > kdl_to_mj_ctrl
std::vector< int > kdl_to_mj_qpos
std::vector< int > kdl_to_mj_dof
std::vector< double > jnt_pos_msr
std::string tcp_site
std::vector< double > jnt_pos_cmd
std::vector< std::pair< double, double > > joint_limits
std::vector< double > jnt_vel_msr
std::vector< double > jnt_trq_cmd
std::vector< double > jnt_trq_msr
std::vector< std::string > joint_names
KDL::Frame tip_T_tcp
std::vector< RobotSpec > robots
std::vector< SceneObject > objects
std::vector< CameraSpec > cameras
GLFWwindow * window
std::chrono::steady_clock::time_point _tick_t