From 7542c90cd2f6ea358c2a118d3a548ba234675997 Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Mon, 20 Feb 2023 08:39:53 -0800 Subject: [PATCH] Improved behaviour of mouse-spring interactions. Also: - Renamed recently introduced `mjvPerturb` member `reflocalpos` -> `refselpos` and corrected docstring. - Added `mjvPerturb.localmass`: body spatial inertia at selection point. - Removed `const` qualifier to `mjData` argument of `mjv_initPerturb()`, to facilitate spatial inertia computation. - Improved `mj_Object{Velocity, Acceleration}` docstrings, clarifying that rotational velocities precede linear velocities. - Removed redundant const qualifiers from non pointer arguments to `mju_dot` and `mju_writeNumBytes`. PiperOrigin-RevId: 511002168 Change-Id: I27400f497947f330072d4abb7c9bf7e71e6c5d49 --- doc/APIreference/functions.rst | 4 +- doc/changelog.rst | 2 +- doc/includes/references.h | 10 +-- include/mujoco/mjvisualize.h | 3 +- include/mujoco/mujoco.h | 11 ++-- introspect/functions.py | 10 +-- src/engine/engine_util_blas.c | 2 +- src/engine/engine_util_blas.h | 2 +- src/engine/engine_util_misc.c | 2 +- src/engine/engine_util_misc.h | 2 +- src/engine/engine_vis_interact.c | 92 +++++++++++++++++++--------- src/engine/engine_vis_interact.h | 3 +- src/engine/engine_vis_visualize.c | 4 +- test/engine/testdata/select.xml | 23 +++++-- unity/Runtime/Bindings/MjBindings.cs | 3 +- 15 files changed, 111 insertions(+), 62 deletions(-) diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst index 152e3c7d..59fd8b4e 100644 --- a/doc/APIreference/functions.rst +++ b/doc/APIreference/functions.rst @@ -1029,7 +1029,7 @@ mj_objectVelocity .. mujoco-include:: mj_objectVelocity -Compute object 6D velocity in object-centered frame, world/local orientation. +Compute object 6D velocity (rot:lin) in object-centered frame, world/local orientation. .. _mj_objectAcceleration: @@ -1038,7 +1038,7 @@ mj_objectAcceleration .. mujoco-include:: mj_objectAcceleration -Compute object 6D acceleration in object-centered frame, world/local orientation. +Compute object 6D acceleration (rot:lin) in object-centered frame, world/local orientation. .. _mj_contactForce: diff --git a/doc/changelog.rst b/doc/changelog.rst index a628b448..a5f8578c 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -59,7 +59,7 @@ Bug fixes :align: right :width: 300px -#. The ``launch_repl`` functionality in the Python viewer has been fixed. +6. The ``launch_repl`` functionality in the Python viewer has been fixed. #. Set ``time`` correctly in ``mjd_transitionFD``, to support time-dependent user code. #. Fixed sensor data dimension validation when ``user`` type sensors are present. #. Fixed incorrect plugin error message when a null ``nsensordata`` callback is encountered during model compilation. diff --git a/doc/includes/references.h b/doc/includes/references.h index a8008935..312dfcda 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -1641,8 +1641,9 @@ struct mjvPerturb_ { // object selection and perturbation int active2; // secondary perturbation bitmask (mjtPertBit) mjtNum refpos[3]; // reference position for selected object mjtNum refquat[4]; // reference orientation for selected object - mjtNum reflocalpos[3]; // reference selection point in object coordinates + mjtNum refselpos[3]; // reference position for selection point mjtNum localpos[3]; // selection point in object coordinates + mjtNum localmass; // spatial inertia at selection point mjtNum scale; // relative mouse motion-to-space scaling (set by initPerturb) }; typedef struct mjvPerturb_ mjvPerturb; @@ -1973,8 +1974,7 @@ void mjv_movePerturb(const mjModel* m, const mjData* d, int action, mjtNum reldx mjtNum reldy, const mjvScene* scn, mjvPerturb* pert); void mjv_moveModel(const mjModel* m, int action, mjtNum reldx, mjtNum reldy, const mjtNum roomup[3], mjvScene* scn); -void mjv_initPerturb(const mjModel* m, const mjData* d, - const mjvScene* scn, mjvPerturb* pert); +void mjv_initPerturb(const mjModel* m, mjData* d, const mjvScene* scn, mjvPerturb* pert); void mjv_applyPerturbPose(const mjModel* m, mjData* d, const mjvPerturb* pert, int flg_paused); void mjv_applyPerturbForce(const mjModel* m, mjData* d, const mjvPerturb* pert); @@ -2087,7 +2087,7 @@ void mju_addToScl(mjtNum* res, const mjtNum* vec, mjtNum scl, int n); void mju_addScl(mjtNum* res, const mjtNum* vec1, const mjtNum* vec2, mjtNum scl, int n); mjtNum mju_normalize(mjtNum* res, int n); mjtNum mju_norm(const mjtNum* res, int n); -mjtNum mju_dot(const mjtNum* vec1, const mjtNum* vec2, const int n); +mjtNum mju_dot(const mjtNum* vec1, const mjtNum* vec2, int n); void mju_mulMatVec(mjtNum* res, const mjtNum* mat, const mjtNum* vec, int nr, int nc); void mju_mulMatTVec(mjtNum* res, const mjtNum* mat, const mjtNum* vec, int nr, int nc); mjtNum mju_mulVecMatVec(const mjtNum* vec1, const mjtNum* mat, const mjtNum* vec2, int n); @@ -2146,7 +2146,7 @@ mjtNum mju_sign(mjtNum x); int mju_round(mjtNum x); const char* mju_type2Str(int type); int mju_str2Type(const char* str); -const char* mju_writeNumBytes(const size_t nbytes); +const char* mju_writeNumBytes(size_t nbytes); const char* mju_warningText(int warning, size_t info); int mju_isBad(mjtNum x); int mju_isZero(mjtNum* vec, int n); diff --git a/include/mujoco/mjvisualize.h b/include/mujoco/mjvisualize.h index f3c8633d..d8f761ca 100644 --- a/include/mujoco/mjvisualize.h +++ b/include/mujoco/mjvisualize.h @@ -157,8 +157,9 @@ struct mjvPerturb_ { // object selection and perturbation int active2; // secondary perturbation bitmask (mjtPertBit) mjtNum refpos[3]; // reference position for selected object mjtNum refquat[4]; // reference orientation for selected object - mjtNum reflocalpos[3]; // reference selection point in object coordinates + mjtNum refselpos[3]; // reference position for selection point mjtNum localpos[3]; // selection point in object coordinates + mjtNum localmass; // spatial inertia at selection point mjtNum scale; // relative mouse motion-to-space scaling (set by initPerturb) }; typedef struct mjvPerturb_ mjvPerturb; diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h index a1864c66..8151e363 100644 --- a/include/mujoco/mujoco.h +++ b/include/mujoco/mujoco.h @@ -423,11 +423,11 @@ MJAPI void mj_addM(const mjModel* m, mjData* d, mjtNum* dst, int* rownnz, int* r MJAPI void mj_applyFT(const mjModel* m, mjData* d, const mjtNum force[3], const mjtNum torque[3], const mjtNum point[3], int body, mjtNum* qfrc_target); -// Compute object 6D velocity in object-centered frame, world/local orientation. +// Compute object 6D velocity (rot:lin) in object-centered frame, world/local orientation. MJAPI void mj_objectVelocity(const mjModel* m, const mjData* d, int objtype, int objid, mjtNum res[6], int flg_local); -// Compute object 6D acceleration in object-centered frame, world/local orientation. +// Compute object 6D acceleration (rot:lin) in object-centered frame, world/local orientation. MJAPI void mj_objectAcceleration(const mjModel* m, const mjData* d, int objtype, int objid, mjtNum res[6], int flg_local); @@ -546,8 +546,7 @@ MJAPI void mjv_moveModel(const mjModel* m, int action, mjtNum reldx, mjtNum reld const mjtNum roomup[3], mjvScene* scn); // Copy perturb pos,quat from selected body; set scale for perturbation. -MJAPI void mjv_initPerturb(const mjModel* m, const mjData* d, - const mjvScene* scn, mjvPerturb* pert); +MJAPI void mjv_initPerturb(const mjModel* m, mjData* d, const mjvScene* scn, mjvPerturb* pert); // Set perturb pos,quat in d->mocap when selected body is mocap, and in d->qpos otherwise. // Write d->qpos only if flg_paused and subtree root for selected body has free joint. @@ -919,7 +918,7 @@ MJAPI mjtNum mju_normalize(mjtNum* res, int n); MJAPI mjtNum mju_norm(const mjtNum* res, int n); // Return dot-product of vec1 and vec2. -MJAPI mjtNum mju_dot(const mjtNum* vec1, const mjtNum* vec2, const int n); +MJAPI mjtNum mju_dot(const mjtNum* vec1, const mjtNum* vec2, int n); // Multiply matrix and vector: res = mat * vec. MJAPI void mju_mulMatVec(mjtNum* res, const mjtNum* mat, const mjtNum* vec, int nr, int nc); @@ -1104,7 +1103,7 @@ MJAPI const char* mju_type2Str(int type); MJAPI int mju_str2Type(const char* str); // Return human readable number of bytes using standard letter suffix. -MJAPI const char* mju_writeNumBytes(const size_t nbytes); +MJAPI const char* mju_writeNumBytes(size_t nbytes); // Construct a warning message given the warning type and info. MJAPI const char* mju_warningText(int warning, size_t info); diff --git a/introspect/functions.py b/introspect/functions.py index b6c75e97..8aeb0c90 100644 --- a/introspect/functions.py +++ b/introspect/functions.py @@ -2432,7 +2432,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ type=ValueType(name='int'), ), ), - doc='Compute object 6D velocity in object-centered frame, world/local orientation.', # pylint: disable=line-too-long + doc='Compute object 6D velocity (rot:lin) in object-centered frame, world/local orientation.', # pylint: disable=line-too-long )), ('mj_objectAcceleration', FunctionDecl( @@ -2471,7 +2471,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ type=ValueType(name='int'), ), ), - doc='Compute object 6D acceleration in object-centered frame, world/local orientation.', # pylint: disable=line-too-long + doc='Compute object 6D acceleration (rot:lin) in object-centered frame, world/local orientation.', # pylint: disable=line-too-long )), ('mj_contactForce', FunctionDecl( @@ -3353,7 +3353,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ FunctionParameterDecl( name='d', type=PointerType( - inner_type=ValueType(name='mjData', is_const=True), + inner_type=ValueType(name='mjData'), ), ), FunctionParameterDecl( @@ -5651,7 +5651,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), FunctionParameterDecl( name='n', - type=ValueType(name='int', is_const=True), + type=ValueType(name='int'), ), ), doc='Return dot-product of vec1 and vec2.', @@ -6978,7 +6978,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ parameters=( FunctionParameterDecl( name='nbytes', - type=ValueType(name='size_t', is_const=True), + type=ValueType(name='size_t'), ), ), doc='Return human readable number of bytes using standard letter suffix.', # pylint: disable=line-too-long diff --git a/src/engine/engine_util_blas.c b/src/engine/engine_util_blas.c index 54ad5625..99c96b73 100644 --- a/src/engine/engine_util_blas.c +++ b/src/engine/engine_util_blas.c @@ -627,7 +627,7 @@ mjtNum mju_norm(const mjtNum* res, int n) { // vector dot-product -mjtNum mju_dot(const mjtNum* vec1, const mjtNum* vec2, const int n) { +mjtNum mju_dot(const mjtNum* vec1, const mjtNum* vec2, int n) { mjtNum res = 0; int i = 0; int n_4 = n - 4; diff --git a/src/engine/engine_util_blas.h b/src/engine/engine_util_blas.h index e6d03bc6..dfaccbe6 100644 --- a/src/engine/engine_util_blas.h +++ b/src/engine/engine_util_blas.h @@ -170,7 +170,7 @@ MJAPI mjtNum mju_normalize(mjtNum* res, int n); MJAPI mjtNum mju_norm(const mjtNum* res, int n); // vector dot-product -MJAPI mjtNum mju_dot(const mjtNum* vec1, const mjtNum* vec2, const int n); +MJAPI mjtNum mju_dot(const mjtNum* vec1, const mjtNum* vec2, int n); //------------------------------ matrix-vector operations ------------------------------------------ diff --git a/src/engine/engine_util_misc.c b/src/engine/engine_util_misc.c index 72597ddb..60a1e127 100644 --- a/src/engine/engine_util_misc.c +++ b/src/engine/engine_util_misc.c @@ -947,7 +947,7 @@ int mju_str2Type(const char* str) { // return human readable number of bytes using standard letter suffix -const char* mju_writeNumBytes(const size_t nbytes) { +const char* mju_writeNumBytes(size_t nbytes) { int i; static mjTHREADLOCAL char message[20]; static const char suffix[] = " KMGTPE"; diff --git a/src/engine/engine_util_misc.h b/src/engine/engine_util_misc.h index 4288d380..3e4f9f84 100644 --- a/src/engine/engine_util_misc.h +++ b/src/engine/engine_util_misc.h @@ -88,7 +88,7 @@ MJAPI const char* mju_type2Str(int type); MJAPI int mju_str2Type(const char* str); // return human readable number of bytes using standard letter suffix -MJAPI const char* mju_writeNumBytes(const size_t nbytes); +MJAPI const char* mju_writeNumBytes(size_t nbytes); // warning text MJAPI const char* mju_warningText(int warning, size_t info); diff --git a/src/engine/engine_vis_interact.c b/src/engine/engine_vis_interact.c index b4a79fe0..f4bc276c 100644 --- a/src/engine/engine_vis_interact.c +++ b/src/engine/engine_vis_interact.c @@ -21,6 +21,9 @@ #include #include #include +#include "engine/engine_core_smooth.h" +#include "engine/engine_io.h" +#include "engine/engine_macro.h" #include "engine/engine_ray.h" #include "engine/engine_support.h" #include "engine/engine_util_blas.h" @@ -385,7 +388,7 @@ void mjv_movePerturb(const mjModel* m, const mjData* d, int action, mjtNum reldx case mjMOUSE_MOVE_V: case mjMOUSE_MOVE_H: mju_addToScl3(pert->refpos, vec, pert->scale); - mju_addToScl3(pert->reflocalpos, vec, pert->scale); + mju_addToScl3(pert->refselpos, vec, pert->scale); break; case mjMOUSE_ROTATE_V: @@ -514,10 +517,16 @@ void mjv_moveModel(const mjModel* m, int action, mjtNum reldx, mjtNum reldy, // copy perturb pos,quat from selected body; set scale for perturbation -void mjv_initPerturb(const mjModel* m, const mjData* d, const mjvScene* scn, mjvPerturb* pert) { +void mjv_initPerturb(const mjModel* m, mjData* d, const mjvScene* scn, mjvPerturb* pert) { + mjMARKSTACK; + + int nv = m->nv; int sel = pert->select; mjtNum headpos[3], forward[3], dif[3]; + mjtNum* jac = mj_stackAlloc(d, 3*nv); + mjtNum* jacM2 = mj_stackAlloc(d, 3*nv); + // invalid selected body: return if (sel<=0 || sel>=m->nbody) { return; @@ -528,25 +537,34 @@ void mjv_initPerturb(const mjModel* m, const mjData* d, const mjvScene* scn, mjv mju_rotVecMat(selpos, pert->localpos, d->xmat+9*sel); mju_addTo3(selpos, d->xpos+3*sel); + // compute average spatial inertia at selection point + mj_jac(m, d, jac, NULL, selpos, sel); + mj_solveM2(m, d, jacM2, jac, 3); + mjtNum invmass = mju_dot(jacM2+0*nv, jacM2+0*nv, nv) + + mju_dot(jacM2+1*nv, jacM2+1*nv, nv) + + mju_dot(jacM2+2*nv, jacM2+2*nv, nv); + pert->localmass = 3 / mju_max(invmass, mjMINVAL); + // copy mju_copy3(pert->refpos, d->xipos+3*sel); mju_mulQuat(pert->refquat, d->xquat+4*sel, m->body_iquat+4*sel); - mju_copy3(pert->reflocalpos, selpos); + mju_copy3(pert->refselpos, selpos); // get camera info mjv_cameraInModel(headpos, forward, NULL, scn); - // compute scaling: rendered pert->refpos displacement = mouse displacement - mju_sub3(dif, pert->reflocalpos, headpos); + // compute scaling: rendered pert->refselpos displacement = mouse displacement + mju_sub3(dif, pert->refselpos, headpos); pert->scale = mjv_frustumHeight(scn) * mju_dot3(dif, forward); + + mjFREESTACK; } // set perturb pos,quat in d->mocap when selected body is mocap, and in d->qpos otherwise // d->qpos written only if flg_paused and subtree root for selected body has free joint -void mjv_applyPerturbPose(const mjModel* m, mjData* d, const mjvPerturb* pert, - int flg_paused) { +void mjv_applyPerturbPose(const mjModel* m, mjData* d, const mjvPerturb* pert, int flg_paused) { int rootid = 0, sel = pert->select; mjtNum pos1[3], quat1[4], pos2[3], quat2[4], refpos[3], refquat[4]; mjtNum *Rpos, *Rquat, *Cpos, *Cquat; @@ -600,7 +618,6 @@ void mjv_applyPerturbPose(const mjModel* m, mjData* d, const mjvPerturb* pert, // set perturb force,torque in d->xfrc_applied, if selected body is dynamic void mjv_applyPerturbForce(const mjModel* m, mjData* d, const mjvPerturb* pert) { - mjtNum xiquat[4], difquat[4], bvel[6], mass, stiffness, *result; int sel = pert->select; // exit if nothing to do @@ -608,11 +625,18 @@ void mjv_applyPerturbForce(const mjModel* m, mjData* d, const mjvPerturb* pert) return; } - // get pointer to body xfrc_applied - result = d->xfrc_applied + 6*sel; + // pointers to body xfrc_applied, force and torque + mjtNum *force = d->xfrc_applied + 6*sel; + mjtNum *torque = d->xfrc_applied + 6*sel + 3; - // global selbody velocity + // pointers to global selbody velocity, linear and rotational + mjtNum bvel[6]; mj_objectVelocity(m, d, mjOBJ_BODY, sel, bvel, 0); + mjtNum *body_linvel = bvel + 3; + mjtNum *body_rotvel = bvel; + + // body rotational inertia + mjtNum inertia = 1.0/mju_max(mjMINVAL, m->body_invweight0[2*sel+1]); if (((pert->active | pert->active2) & mjPERT_TRANSLATE)) { // compute selection point in world coordinates @@ -620,33 +644,45 @@ void mjv_applyPerturbForce(const mjModel* m, mjData* d, const mjvPerturb* pert) mju_rotVecMat(selpos, pert->localpos, d->xmat+9*sel); mju_addTo3(selpos, d->xpos+3*sel); - // spring perturbation force, with critical damping - stiffness = m->vis.map.stiffness; - mass = 1.0/mju_max(mjMINVAL, m->body_invweight0[2*sel]); - mju_sub3(result, pert->reflocalpos, selpos); - mju_scl3(result, result, stiffness*mass); - mju_addToScl3(result, bvel+3, -sqrtf(stiffness)*mass); + // displacement of selection point from reference point + mjtNum diff[3]; + mju_sub3(diff, selpos, pert->refselpos); - // torque w.r.t body com - mju_subFrom3(selpos, d->xipos+3*sel); - mju_cross(result+3, selpos, result); + // spring perturbation force + mjtNum stiffness = m->vis.map.stiffness; + mju_copy3(force, diff); + mju_scl3(force, force, -stiffness*pert->localmass); - // add critically damped torque (torsional only) + // moment arm w.r.t body com + mjtNum moment_arm[3]; + mju_sub3(moment_arm, selpos, d->xipos+3*sel); + + // translational velocity of selection point + mjtNum svel[3]; + mju_cross(svel, body_rotvel, moment_arm); + mju_addTo3(svel, body_linvel); + + // add critical damping force of selection point + mju_addToScl3(force, svel, -sqrtf(stiffness)*pert->localmass); + + // torque on body com due to force + mju_cross(torque, moment_arm, force); + + // add critically damped torsional torque along displacement axis stiffness = m->vis.map.stiffnessrot; - mass = 1.0/mju_max(mjMINVAL, m->body_invweight0[2*sel+1]); - mju_normalize3(selpos); - mju_addToScl3(result+3, selpos, -sqrtf(stiffness)*mass*mju_dot3(selpos, bvel)); + mju_normalize3(diff); + mju_addToScl3(torque, diff, -sqrtf(stiffness)*inertia*mju_dot3(diff, body_rotvel)); } if (((pert->active | pert->active2) & mjPERT_ROTATE)) { // spring perturbation torque, with critical damping - stiffness = m->vis.map.stiffnessrot; - mass = 1.0/mju_max(mjMINVAL, m->body_invweight0[2*sel+1]); + mjtNum stiffness = m->vis.map.stiffnessrot; + mjtNum xiquat[4], difquat[4]; mju_mulQuat(xiquat, d->xquat+4*sel, m->body_iquat+4*sel); mju_negQuat(xiquat, xiquat); mju_mulQuat(difquat, pert->refquat, xiquat); - mju_quat2Vel(result+3, difquat, 1.0/(stiffness*mass)); - mju_addToScl3(result+3, bvel, -sqrtf(stiffness)*mass); + mju_quat2Vel(torque, difquat, 1.0/(stiffness*inertia)); + mju_addToScl3(torque, body_rotvel, -sqrtf(stiffness)*inertia); } } diff --git a/src/engine/engine_vis_interact.h b/src/engine/engine_vis_interact.h index 4160ec9d..c78ffa3e 100644 --- a/src/engine/engine_vis_interact.h +++ b/src/engine/engine_vis_interact.h @@ -59,8 +59,7 @@ MJAPI void mjv_moveModel(const mjModel* m, int action, mjtNum reldx, mjtNum reld const mjtNum roomup[3], mjvScene* scn); // copy perturb pos,quat from selected body; set scale perturbation -MJAPI void mjv_initPerturb(const mjModel* m, const mjData* d, - const mjvScene* scn, mjvPerturb* pert); +MJAPI void mjv_initPerturb(const mjModel* m, mjData* d, const mjvScene* scn, mjvPerturb* pert); // set perturb pos,quat in d->mocap when selected body is mocap, and in d->qpos otherwise // d->qpos written only if flg_paused and subtree root for selected body has free joint diff --git a/src/engine/engine_vis_visualize.c b/src/engine/engine_vis_visualize.c index 01802d83..149d2eeb 100644 --- a/src/engine/engine_vis_visualize.c +++ b/src/engine/engine_vis_visualize.c @@ -587,7 +587,7 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt, sz[0] = scl * m->vis.scale.constraint; mjv_makeConnector(thisgeom, mjGEOM_CAPSULE, sz[0], selpos[0], selpos[1], selpos[2], - pert->reflocalpos[0], pert->reflocalpos[1], pert->reflocalpos[2]); + pert->refselpos[0], pert->refselpos[1], pert->refselpos[2]); // prepare color mixcolor(rgba, m->vis.rgba.constraint, @@ -605,7 +605,7 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt, sz[0] = 2*sz[0]; sz[1] = sz[2] = sz[0]; mju_quat2Mat(mat, pert->refquat); - mjv_initGeom(thisgeom, mjGEOM_SPHERE, sz, pert->reflocalpos, mat, rgba); + mjv_initGeom(thisgeom, mjGEOM_SPHERE, sz, pert->refselpos, mat, rgba); FINISH } diff --git a/test/engine/testdata/select.xml b/test/engine/testdata/select.xml index 2dc99505..50dceca8 100644 --- a/test/engine/testdata/select.xml +++ b/test/engine/testdata/select.xml @@ -1,24 +1,37 @@ + + + + - + - + - + - - + + + + + + + + + + + diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 220129a5..a866d56a 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -2472,8 +2472,9 @@ public unsafe struct mjvPerturb_ { public int active2; public fixed double refpos[3]; public fixed double refquat[4]; - public fixed double reflocalpos[3]; + public fixed double refselpos[3]; public fixed double localpos[3]; + public double localmass; public double scale; }