// Copyright 2021 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 "engine/engine_sensor.h" #include #include #include #include "engine/engine_callback.h" #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" #include "engine/engine_util_errmem.h" #include "engine/engine_util_misc.h" #include "engine/engine_util_spatial.h" //-------------------------------- utility --------------------------------------------------------- // add sensor noise after each stage static void add_noise(const mjModel* m, mjData* d, mjtStage stage) { int adr, dim; mjtNum rnd[4], noise, quat[4], res[4]; // process sensors matching stage and having positive noise for (int i=0; insensor; i++) { if (m->sensor_needstage[i]==stage && m->sensor_noise[i]>0) { // get sensor info adr = m->sensor_adr[i]; dim = m->sensor_dim[i]; noise = m->sensor_noise[i]; // real or positive: add noise directly, with clamp for positive if (m->sensor_datatype[i]==mjDATATYPE_REAL || m->sensor_datatype[i]==mjDATATYPE_POSITIVE) for (int j=0; jsensor_datatype[i]==mjDATATYPE_POSITIVE) { // add noise only if positive, keep it positive if (d->sensordata[adr+j]>0) { d->sensordata[adr+j] = mjMAX(0, d->sensordata[adr+j]+rnd[0]*noise); } } // real else { d->sensordata[adr+j] += rnd[0]*noise; } } // axis or quat: rotate around random axis by random angle else { // get four random numbers rnd[0] = mju_standardNormal(rnd+1); rnd[2] = mju_standardNormal(rnd+3); // scale angle, normalize axis, make quaterion rnd[0] *= noise; mju_normalize3(rnd+1); mju_axisAngle2Quat(quat, rnd+1, rnd[0]); // axis if (m->sensor_datatype[i]==mjDATATYPE_AXIS) { // apply quaternion rotation to axis, assign mju_rotVecQuat(res, d->sensordata+adr, quat); mju_copy3(d->sensordata+adr, res); } // quaternion else if (m->sensor_datatype[i]==mjDATATYPE_QUATERNION) { // apply quaternion rotation to quaternion, assign mju_mulQuat(res, d->sensordata+adr, quat); mju_copy4(d->sensordata+adr, res); } // unknown datatype else { mju_error_i("Unknown datatype in sensor %d", i); } } } } } // apply cutoff after each stage static void apply_cutoff(const mjModel* m, mjData* d, mjtStage stage) { // process sensors matching stage and having positive cutoff for (int i=0; insensor; i++) { if (m->sensor_needstage[i]==stage && m->sensor_cutoff[i]>0) { // get sensor info int adr = m->sensor_adr[i]; int dim = m->sensor_dim[i]; mjtNum cutoff = m->sensor_cutoff[i]; // process all dimensions for (int j=0; jsensor_datatype[i]==mjDATATYPE_REAL) d->sensordata[adr+j] = mju_min(cutoff, mju_max(-cutoff, d->sensordata[adr+j])); // positive: apply on positive side only else if (m->sensor_datatype[i]==mjDATATYPE_POSITIVE) d->sensordata[adr+j] = mju_min(cutoff, d->sensordata[adr+j]); } } } // get xpos and xmat pointers to an object in mjData static void get_xpos_xmat(const mjData* d, int type, int id, int sensor_id, mjtNum **xpos, mjtNum **xmat) { switch (type) { case mjOBJ_XBODY: *xpos = d->xpos + 3*id; *xmat = d->xmat + 9*id; break; case mjOBJ_BODY: *xpos = d->xipos + 3*id; *xmat = d->ximat + 9*id; break; case mjOBJ_GEOM: *xpos = d->geom_xpos + 3*id; *xmat = d->geom_xmat + 9*id; break; case mjOBJ_SITE: *xpos = d->site_xpos + 3*id; *xmat = d->site_xmat + 9*id; break; case mjOBJ_CAMERA: *xpos = d->cam_xpos + 3*id; *xmat = d->cam_xmat + 9*id; break; default: mju_error_i("Invalid object type in sensor %d", sensor_id); } } // get global quaternion of an object in mjData static void get_xquat(const mjModel* m, const mjData* d, int type, int id, int sensor_id, mjtNum *quat) { switch (type) { case mjOBJ_XBODY: mju_copy4(quat, d->xquat+4*id); break; case mjOBJ_BODY: mju_mulQuat(quat, d->xquat+4*id, m->body_iquat+4*id); break; case mjOBJ_GEOM: mju_mulQuat(quat, d->xquat+4*m->geom_bodyid[id], m->geom_quat+4*id); break; case mjOBJ_SITE: mju_mulQuat(quat, d->xquat+4*m->site_bodyid[id], m->site_quat+4*id); break; case mjOBJ_CAMERA: mju_mulQuat(quat, d->xquat+4*m->cam_bodyid[id], m->cam_quat+4*id); break; default: mju_error_i("Invalid object type in sensor %d", sensor_id); } } //-------------------------------- sensor ---------------------------------------------------------- // position-dependent sensors void mj_sensorPos(const mjModel* m, mjData* d) { int rgeomid, objtype, objid, reftype, refid, adr, offset, nusersensor = 0; int ne = d->ne, nf = d->nf, nefc = d->nefc; mjtNum rvec[3], *xpos, *xmat, *xpos_ref, *xmat_ref; // process sensors matching stage for (int i=0; insensor; i++) { if (m->sensor_needstage[i]==mjSTAGE_POS) { // get sensor info objtype = m->sensor_objtype[i]; objid = m->sensor_objid[i]; refid = m->sensor_refid[i]; reftype = m->sensor_reftype[i]; adr = m->sensor_adr[i]; // process according to type switch (m->sensor_type[i]) { case mjSENS_MAGNETOMETER: // magnetometer mju_mulMatTVec(d->sensordata+adr, d->site_xmat+9*objid, m->opt.magnetic, 3, 3); break; case mjSENS_RANGEFINDER: // rangefinder rvec[0] = d->site_xmat[9*objid+2]; rvec[1] = d->site_xmat[9*objid+5]; rvec[2] = d->site_xmat[9*objid+8]; d->sensordata[adr] = mj_ray(m, d, d->site_xpos+3*objid, rvec, NULL, 1, m->site_bodyid[objid], &rgeomid); break; case mjSENS_JOINTPOS: // jointpos d->sensordata[adr] = d->qpos[m->jnt_qposadr[objid]]; break; case mjSENS_TENDONPOS: // tendonpos d->sensordata[adr] = d->ten_length[objid]; break; case mjSENS_ACTUATORPOS: // actuatorpos d->sensordata[adr] = d->actuator_length[objid]; break; case mjSENS_BALLQUAT: // ballquat mju_copy4(d->sensordata+adr, d->qpos+m->jnt_qposadr[objid]); break; case mjSENS_JOINTLIMITPOS: // jointlimitpos d->sensordata[adr] = 0; for (int j=ne+nf; jefc_type[j]==mjCNSTR_LIMIT_JOINT && d->efc_id[j]==objid) { d->sensordata[adr] = d->efc_pos[j] - d->efc_margin[j]; break; } } break; case mjSENS_TENDONLIMITPOS: // tendonlimitpos d->sensordata[adr] = 0; for (int j=ne+nf; jefc_type[j]==mjCNSTR_LIMIT_TENDON && d->efc_id[j]==objid) { d->sensordata[adr] = d->efc_pos[j] - d->efc_margin[j]; break; } } break; case mjSENS_FRAMEPOS: // framepos case mjSENS_FRAMEXAXIS: // framexaxis case mjSENS_FRAMEYAXIS: // frameyaxis case mjSENS_FRAMEZAXIS: // framezaxis // get xpos and xmat pointers for object frame get_xpos_xmat(d, objtype, objid, i, &xpos, &xmat); // reference frame unspecified: global frame if (refid == -1) { if (m->sensor_type[i]==mjSENS_FRAMEPOS) { mju_copy3(d->sensordata+adr, xpos); } else { // offset = (0 or 1 or 2) for (x or y or z)-axis sensors, respectively offset = m->sensor_type[i] - mjSENS_FRAMEXAXIS; d->sensordata[adr] = xmat[offset]; d->sensordata[adr+1] = xmat[offset+3]; d->sensordata[adr+2] = xmat[offset+6]; } } // reference frame specified else { get_xpos_xmat(d, reftype, refid, i, &xpos_ref, &xmat_ref); if (m->sensor_type[i]==mjSENS_FRAMEPOS) { mju_sub3(rvec, xpos, xpos_ref); mju_rotVecMatT(d->sensordata+adr, rvec, xmat_ref); } else { // offset = (0 or 1 or 2) for (x or y or z)-axis sensors, respectively offset = m->sensor_type[i] - mjSENS_FRAMEXAXIS; mjtNum axis[3] = {xmat[offset], xmat[offset+3], xmat[offset+6]}; mju_rotVecMatT(d->sensordata+adr, axis, xmat_ref); } } break; case mjSENS_FRAMEQUAT: // framequat { // get global object quaternion mjtNum objquat[4]; get_xquat(m, d, objtype, objid, i, objquat); // reference frame unspecified: copy object quaternion if (refid == -1) { mju_copy4(d->sensordata+adr, objquat); } else { // reference frame specified, get global reference quaternion mjtNum refquat[4]; get_xquat(m, d, reftype, refid, i, refquat); // relative quaternion mju_negQuat(refquat, refquat); mju_mulQuat(d->sensordata+adr, refquat, objquat); } } break; case mjSENS_SUBTREECOM: // subtreecom mju_copy3(d->sensordata+adr, d->subtree_com+3*objid); break; case mjSENS_USER: // user nusersensor++; break; default: mju_error_i("Invalid sensor type in POS stage, sensor %d", i); } } } // fill in user sensors if detected if (nusersensor && mjcb_sensor) { mjcb_sensor(m, d, mjSTAGE_POS); } // add noise if enabled if (mjENABLED(mjENBL_SENSORNOISE)) { add_noise(m, d, mjSTAGE_POS); } // cutoff apply_cutoff(m, d, mjSTAGE_POS); } // velocity-dependent sensors void mj_sensorVel(const mjModel* m, mjData* d) { int type, objtype, objid, reftype, refid, adr, nusersensor = 0; int ne = d->ne, nf = d->nf, nefc = d->nefc; mjtNum xvel[6]; // process sensors matching stage int subtreeVel = 0; for (int i=0; insensor; i++) { if (m->sensor_needstage[i]==mjSTAGE_VEL) { // get sensor info type = m->sensor_type[i]; objtype = m->sensor_objtype[i]; objid = m->sensor_objid[i]; refid = m->sensor_refid[i]; reftype = m->sensor_reftype[i]; adr = m->sensor_adr[i]; // call mj_subtreeVel when first relevant sensor is encountered if (subtreeVel==0 && (type==mjSENS_SUBTREELINVEL || type==mjSENS_SUBTREEANGMOM || type==mjSENS_USER)) { // compute subtree_linvel, subtree_angmom mj_subtreeVel(m, d); // mark computed subtreeVel = 1; } // process according to type switch (type) { case mjSENS_VELOCIMETER: // velocimeter // xvel = site velocity, in site frame mj_objectVelocity(m, d, mjOBJ_SITE, objid, xvel, 1); // assign linear velocity mju_copy3(d->sensordata+adr, xvel+3); break; case mjSENS_GYRO: // gyro // xvel = site velocity, in site frame mj_objectVelocity(m, d, mjOBJ_SITE, objid, xvel, 1); // assign angular velocity mju_copy3(d->sensordata+adr, xvel); break; case mjSENS_JOINTVEL: // jointvel d->sensordata[adr] = d->qvel[m->jnt_dofadr[objid]]; break; case mjSENS_TENDONVEL: // tendonvel d->sensordata[adr] = d->ten_velocity[objid]; break; case mjSENS_ACTUATORVEL: // actuatorvel d->sensordata[adr] = d->actuator_velocity[objid]; break; case mjSENS_BALLANGVEL: // ballangvel mju_copy3(d->sensordata+adr, d->qvel+m->jnt_dofadr[objid]); break; case mjSENS_JOINTLIMITVEL: // jointlimitvel d->sensordata[adr] = 0; for (int j=ne+nf; jefc_type[j]==mjCNSTR_LIMIT_JOINT && d->efc_id[j]==objid) { d->sensordata[adr] = d->efc_vel[j]; break; } } break; case mjSENS_TENDONLIMITVEL: // tendonlimitvel d->sensordata[adr] = 0; for (int j=ne+nf; jefc_type[j]==mjCNSTR_LIMIT_TENDON && d->efc_id[j]==objid) { d->sensordata[adr] = d->efc_vel[j]; break; } } break; case mjSENS_FRAMELINVEL: // framelinvel case mjSENS_FRAMEANGVEL: // frameangvel // xvel = 6D object velocity, in global frame mj_objectVelocity(m, d, objtype, objid, xvel, 0); if (refid > -1) { // reference frame specified mjtNum *xpos, *xmat, *xpos_ref, *xmat_ref, xvel_ref[6], rel_vel[6], cross[3], rvec[3]; // in global frame: object and reference position, reference orientation and velocity get_xpos_xmat(d, objtype, objid, i, &xpos, &xmat); get_xpos_xmat(d, reftype, refid, i, &xpos_ref, &xmat_ref); mj_objectVelocity(m, d, reftype, refid, xvel_ref, 0); // subtract velocities mju_sub(rel_vel, xvel, xvel_ref, 6); // linear velocity: add correction due to rotating reference frame mju_sub3(rvec, xpos, xpos_ref); mju_cross(cross, rvec, xvel_ref); mju_addTo3(rel_vel+3, cross); // project into reference frame mju_rotVecMatT(xvel, rel_vel, xmat_ref); mju_rotVecMatT(xvel+3, rel_vel+3, xmat_ref); } // copy linear or angular component if (m->sensor_type[i]==mjSENS_FRAMELINVEL) { mju_copy3(d->sensordata+adr, xvel+3); } else { mju_copy3(d->sensordata+adr, xvel); } break; case mjSENS_SUBTREELINVEL: // subtreelinvel mju_copy3(d->sensordata+adr, d->subtree_linvel+3*objid); break; case mjSENS_SUBTREEANGMOM: // subtreeangmom mju_copy3(d->sensordata+adr, d->subtree_angmom+3*objid); break; case mjSENS_USER: // user nusersensor++; break; default: mju_error_i("Invalid type in VEL stage, sensor %d", i); } } } // fill in user sensors if detected if (nusersensor && mjcb_sensor) { mjcb_sensor(m, d, mjSTAGE_VEL); } // add noise if enabled if (mjENABLED(mjENBL_SENSORNOISE)) { add_noise(m, d, mjSTAGE_VEL); } // cutoff apply_cutoff(m, d, mjSTAGE_VEL); } // acceleration/force-dependent sensors void mj_sensorAcc(const mjModel* m, mjData* d) { int rootid, bodyid, type, objtype, objid, body1, body2, adr, nusersensor = 0; int ne = d->ne, nf = d->nf, nefc = d->nefc; mjtNum tmp[6], conforce[6], conray[3]; mjContact* con; // process sensors matching stage int rnePost = 0; for (int i=0; insensor; i++) { if (m->sensor_needstage[i]==mjSTAGE_ACC) { // get sensor info type = m->sensor_type[i]; objtype = m->sensor_objtype[i]; objid = m->sensor_objid[i]; adr = m->sensor_adr[i]; // call mj_rnePostConstraint when first relevant sensor is encountered if (rnePost==0 && type!=mjSENS_TOUCH && type!=mjSENS_ACTUATORFRC && type!=mjSENS_JOINTLIMITFRC && type!=mjSENS_TENDONLIMITFRC) { // compute cacc, cfrc_int, cfrc_ext mj_rnePostConstraint(m, d); // mark computed rnePost = 1; } // process according to type switch (type) { case mjSENS_TOUCH: // touch // extract body data bodyid = m->site_bodyid[objid]; rootid = m->body_rootid[bodyid]; // clear result d->sensordata[adr] = 0; // find contacts in sensor zone, add normal forces for (int j=0; jncon; j++) { // contact pointer, contacting bodies con = d->contact + j; body1 = m->geom_bodyid[con->geom1]; body2 = m->geom_bodyid[con->geom2]; // select contacts involving sensorized body if (con->efc_address>=0 && (bodyid==body1 || bodyid==body2)) { // get contact force:torque in contact frame mj_contactForce(m, d, j, conforce); // nothing to do if normal is zero if (conforce[0]<=0) { continue; } // convert contact normal force to global frame, normalize mju_scl3(conray, con->frame, conforce[0]); mju_normalize3(conray); // flip ray direction if sensor is on body2 if (bodyid==body2) { mju_scl3(conray, conray, -1); } // add if ray-zone intersection (always true when con->pos inside zone) if (mju_rayGeom(d->site_xpos+3*objid, d->site_xmat+9*objid, m->site_size+3*objid, con->pos, conray, m->site_type[objid]) >= 0) { d->sensordata[adr] += conforce[0]; } } } break; case mjSENS_ACCELEROMETER: // accelerometer // tmp = site acceleration, in site frame mj_objectAcceleration(m, d, mjOBJ_SITE, objid, tmp, 1); // assign linear acceleration mju_copy3(d->sensordata+adr, tmp+3); break; case mjSENS_FORCE: // force // extract body data bodyid = m->site_bodyid[objid]; rootid = m->body_rootid[bodyid]; // tmp = interaction force between body and parent, in site frame mju_transformSpatial(tmp, d->cfrc_int+6*bodyid, 1, d->site_xpos+3*objid, d->subtree_com+3*rootid, d->site_xmat+9*objid); // assign force mju_copy3(d->sensordata+adr, tmp+3); break; case mjSENS_TORQUE: // torque // extract body data bodyid = m->site_bodyid[objid]; rootid = m->body_rootid[bodyid]; // tmp = interaction force between body and parent, in site frame mju_transformSpatial(tmp, d->cfrc_int+6*bodyid, 1, d->site_xpos+3*objid, d->subtree_com+3*rootid, d->site_xmat+9*objid); // assign torque mju_copy3(d->sensordata+adr, tmp); break; case mjSENS_ACTUATORFRC: // actuatorfrc d->sensordata[adr] = d->actuator_force[objid]; break; case mjSENS_JOINTLIMITFRC: // jointlimitfrc d->sensordata[adr] = 0; for (int j=ne+nf; jefc_type[j]==mjCNSTR_LIMIT_JOINT && d->efc_id[j]==objid) { d->sensordata[adr] = d->efc_force[j]; break; } } break; case mjSENS_TENDONLIMITFRC: // tendonlimitfrc d->sensordata[adr] = 0; for (int j=ne+nf; jefc_type[j]==mjCNSTR_LIMIT_TENDON && d->efc_id[j]==objid) { d->sensordata[adr] = d->efc_force[j]; break; } } break; case mjSENS_FRAMELINACC: // framelinacc case mjSENS_FRAMEANGACC: // frameangacc // get 6D object acceleration, in global frame mj_objectAcceleration(m, d, objtype, objid, tmp, 0); // copy linear or angular component if (m->sensor_type[i]==mjSENS_FRAMELINACC) { mju_copy3(d->sensordata+adr, tmp+3); } else { mju_copy3(d->sensordata+adr, tmp); } break; case mjSENS_USER: // user nusersensor++; break; default: mju_error_i("Invalid type in ACC stage, sensor %d", i); } } } // fill in user sensors if detected if (nusersensor && mjcb_sensor) { mjcb_sensor(m, d, mjSTAGE_ACC); } // add noise if enabled if (mjENABLED(mjENBL_SENSORNOISE)) { add_noise(m, d, mjSTAGE_ACC); } // cutoff apply_cutoff(m, d, mjSTAGE_ACC); } //-------------------------------- energy ---------------------------------------------------------- // position-dependent energy (potential) void mj_energyPos(const mjModel* m, mjData* d) { int padr; mjtNum dif[3], stiffness; // disabled: clear and return if (!mjENABLED(mjENBL_ENERGY)) { d->energy[0] = d->energy[1] = 0; return; } // init potential energy: -sum_i body(i).mass * mju_dot(body(i).pos, gravity) d->energy[0] = 0; if (!mjDISABLED(mjDSBL_GRAVITY)) { for (int i=1; inbody; i++) { d->energy[0] -= m->body_mass[i] * mju_dot3(m->opt.gravity, d->xipos+3*i); } } // add joint-level springs if (!mjDISABLED(mjDSBL_PASSIVE)) { for (int i=0; injnt; i++) { stiffness = m->jnt_stiffness[i]; padr = m->jnt_qposadr[i]; switch (m->jnt_type[i]) { case mjJNT_FREE: mju_sub3(dif, d->qpos+padr, m->qpos_spring+padr); d->energy[0] += 0.5*stiffness*mju_dot3(dif, dif); // continue with rotations padr += 3; case mjJNT_BALL: // covert quatertion difference into angular "velocity" mju_subQuat(dif, d->qpos + padr, m->qpos_spring + padr); d->energy[0] += 0.5*stiffness*mju_dot3(dif, dif); break; case mjJNT_SLIDE: case mjJNT_HINGE: d->energy[0] += 0.5*stiffness* (d->qpos[padr] - m->qpos_spring[padr])* (d->qpos[padr] - m->qpos_spring[padr]); break; } } } // add tendon-level springs if (!mjDISABLED(mjDSBL_PASSIVE)) { for (int i=0; intendon; i++) { stiffness = m->tendon_stiffness[i]; d->energy[0] += 0.5*stiffness*(d->ten_length[i] - m->tendon_lengthspring[i])* (d->ten_length[i] - m->tendon_lengthspring[i]); } } } // velocity-dependent energy (kinetic) void mj_energyVel(const mjModel* m, mjData* d) { mjtNum *vec; mjMARKSTACK; // return if disabled (already cleared in potential) if (!mjENABLED(mjENBL_ENERGY)) { return; } vec = mj_stackAlloc(d, m->nv); // kinetic energy: 0.5 * qvel' * M * qvel mj_mulM(m, d, vec, d->qvel); d->energy[1] = 0.5*mju_dot(vec, d->qvel, m->nv); mjFREESTACK; }