139a5b6494
PiperOrigin-RevId: 838701951 Change-Id: I339eafd35d919710cb47bb04e9a8e29f92739513
628 lines
20 KiB
C++
628 lines
20 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/platform/interaction.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <vector>
|
|
|
|
#include <mujoco/mujoco.h>
|
|
#include "engine/engine_util_errmem.h"
|
|
#include "engine/engine_util_misc.h"
|
|
#include "engine/engine_vis_visualize.h"
|
|
|
|
namespace mujoco::platform {
|
|
|
|
static mjtNum CalculateMovementScale(const mjModel* m, const mjvCamera* cam) {
|
|
float zclip[2] = {0, 0}, zver[2] = {0, 0};
|
|
mjv_cameraFrustum(zver, nullptr, zclip, m, cam);
|
|
if (cam->orthographic) {
|
|
// TODO(b/346130949): multiply by mystery coefficient
|
|
return (zver[1] + zver[0]) * 0.15;
|
|
} else if (zclip[0] >= mjMINVAL) {
|
|
return (zver[1] + zver[0]) / zclip[0];
|
|
} else {
|
|
mjERROR("mjvScene frustum_near too small");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void AlignToCamera(mjtNum res[3], mjtMouse action, mjtNum dx, mjtNum dy,
|
|
const mjtNum forward[3]) {
|
|
mjtNum vec[3];
|
|
switch (action) {
|
|
case mjMOUSE_ROTATE_V:
|
|
vec[0] = dy;
|
|
vec[1] = 0;
|
|
vec[2] = dx;
|
|
break;
|
|
case mjMOUSE_ROTATE_H:
|
|
vec[0] = dy;
|
|
vec[1] = dx;
|
|
vec[2] = 0;
|
|
break;
|
|
case mjMOUSE_MOVE_V:
|
|
case mjMOUSE_MOVE_V_REL:
|
|
vec[0] = dx;
|
|
vec[1] = 0;
|
|
vec[2] = -dy;
|
|
break;
|
|
case mjMOUSE_MOVE_H:
|
|
case mjMOUSE_MOVE_H_REL:
|
|
vec[0] = dx;
|
|
vec[1] = -dy;
|
|
vec[2] = 0;
|
|
break;
|
|
case mjMOUSE_ZOOM:
|
|
break;
|
|
default:
|
|
mjERROR("unexpected mouse action %d in AlignToCamera", action);
|
|
}
|
|
|
|
// call 3D converter
|
|
mjv_alignToCamera(res, vec, forward);
|
|
}
|
|
|
|
void InitPerturb(const mjModel* m, const mjData* d, const mjvCamera* cam,
|
|
mjvPerturb* pert, mjtPertBit active) {
|
|
// compute selection point in world coordinates
|
|
const int sel = pert->select;
|
|
mjtNum selpos[3];
|
|
mju_mulMatVec3(selpos, d->xmat + 9 * sel, pert->localpos);
|
|
mju_addTo3(selpos, d->xpos + 3 * sel);
|
|
|
|
// compute average spatial inertia at selection point
|
|
const int nv = m->nv;
|
|
std::vector<mjtNum> sqrtInvD(nv);
|
|
for (int i = 0; i < nv; i++) {
|
|
sqrtInvD[i] = mju_sqrt(d->qLDiagInv[i]);
|
|
}
|
|
|
|
std::vector<mjtNum> jac(3 * nv);
|
|
mj_jac(m, d, jac.data(), nullptr, selpos, sel);
|
|
|
|
std::vector<mjtNum> jacM2(3 * nv);
|
|
mj_solveM2(m, const_cast<mjData*>(d), jacM2.data(), jac.data(),
|
|
sqrtInvD.data(), 3);
|
|
mjtNum invmass = mju_dot(jacM2.data() + 0 * nv, jacM2.data() + 0 * nv, nv) +
|
|
mju_dot(jacM2.data() + 1 * nv, jacM2.data() + 1 * nv, nv) +
|
|
mju_dot(jacM2.data() + 2 * nv, jacM2.data() + 2 * nv, nv);
|
|
pert->localmass = (invmass == 0) ? 1 : 3 / mju_max(invmass, mjMINVAL);
|
|
|
|
// scale localmass with flex average number of edges per vertex
|
|
if (pert->flexselect >= 0 && !m->flex_rigid[pert->flexselect]) {
|
|
pert->localmass *= (2.0 * m->flex_edgenum[pert->flexselect]) /
|
|
(mjtNum)m->flex_vertnum[pert->flexselect];
|
|
}
|
|
|
|
// 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->refselpos, selpos);
|
|
|
|
// get camera info
|
|
mjtNum headpos[3], forward[3];
|
|
mjv_cameraFrame(headpos, forward, nullptr, nullptr, d, cam);
|
|
|
|
// compute scaling: rendered pert->refselpos displacement = mouse displacement
|
|
mjtNum dif[3];
|
|
mju_sub3(dif, pert->refselpos, headpos);
|
|
pert->scale = CalculateMovementScale(m, cam) * mju_dot3(dif, forward);
|
|
pert->active = active;
|
|
}
|
|
|
|
void MovePerturb(const mjModel* m, const mjData* d, const mjvCamera* cam,
|
|
mjvPerturb* pert, mjtMouse action, mjtNum reldx,
|
|
mjtNum reldy) {
|
|
const mjtNum xaxis[3] = {1, 0, 0};
|
|
const mjtNum yaxis[3] = {0, 1, 0};
|
|
const mjtNum zaxis[3] = {0, 0, 1};
|
|
|
|
int sel = pert->select;
|
|
const mjtNum* xmat = d->xmat + 9 * sel;
|
|
mjtNum forward[3], vec[3], scl, q1[4], xiquat[4];
|
|
|
|
// get camera info and align
|
|
mjv_cameraFrame(nullptr, forward, nullptr, nullptr, d, cam);
|
|
AlignToCamera(vec, action, reldx, reldy, forward);
|
|
|
|
// process action
|
|
switch ((mjtMouse)action) {
|
|
case mjMOUSE_MOVE_V:
|
|
case mjMOUSE_MOVE_H:
|
|
// move along world-space horizontal/vertical planes relative to camera
|
|
mju_addToScl3(pert->refpos, vec, pert->scale);
|
|
mju_addToScl3(pert->refselpos, vec, pert->scale);
|
|
break;
|
|
|
|
case mjMOUSE_MOVE_V_REL:
|
|
case mjMOUSE_MOVE_H_REL:
|
|
// move along object's local coordinate frame
|
|
if (action == mjMOUSE_MOVE_H_REL) {
|
|
mju_mulMatVec3(vec, xmat, xaxis);
|
|
mju_addToScl3(pert->refpos, vec, pert->scale * reldy);
|
|
mju_addToScl3(pert->refselpos, vec, pert->scale * reldy);
|
|
} else {
|
|
mju_mulMatVec3(vec, xmat, zaxis);
|
|
mju_addToScl3(pert->refpos, vec, pert->scale * reldy);
|
|
mju_addToScl3(pert->refselpos, vec, pert->scale * reldy);
|
|
}
|
|
|
|
mju_mulMatVec3(vec, xmat, yaxis);
|
|
mju_addToScl3(pert->refpos, vec, pert->scale * reldx);
|
|
mju_addToScl3(pert->refselpos, vec, pert->scale * reldx);
|
|
break;
|
|
|
|
case mjMOUSE_ROTATE_V:
|
|
case mjMOUSE_ROTATE_H:
|
|
// normalize vector, get length
|
|
scl = mju_normalize3(vec);
|
|
|
|
// make quaternion and apply
|
|
mju_axisAngle2Quat(q1, vec, scl * mjPI * 2);
|
|
mju_mulQuat(pert->refquat, q1, pert->refquat);
|
|
mju_normalize4(pert->refquat);
|
|
|
|
// compute xiquat
|
|
mju_mulQuat(xiquat, d->xquat + 4 * sel, m->body_iquat + 4 * sel);
|
|
|
|
// limit rotation relative to selected body
|
|
if (sel > 0 && sel < m->nbody) {
|
|
// q2 = neg(selbody) * refquat
|
|
mjtNum q2[4];
|
|
mju_negQuat(q1, xiquat);
|
|
mju_mulQuat(q2, q1, pert->refquat);
|
|
|
|
// convert q2 to axis-angle
|
|
mjtNum dif[3];
|
|
mju_quat2Vel(dif, q2, 1);
|
|
scl = mju_normalize3(dif);
|
|
|
|
// check limit: +/- 90 deg allowed
|
|
if (scl < -mjPI * 0.5 || scl > mjPI * 0.5) {
|
|
// clamp angle
|
|
scl = mju_max(-mjPI * 0.5, mju_min(mjPI * 0.5, scl));
|
|
|
|
// reconstruct q2
|
|
mju_axisAngle2Quat(q2, dif, scl);
|
|
|
|
// set refquat = selbody * q2_new
|
|
mju_mulQuat(pert->refquat, xiquat, q2);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case mjMOUSE_ZOOM:
|
|
break;
|
|
|
|
default:
|
|
mjERROR("unexpected mouse action %d", action);
|
|
}
|
|
}
|
|
|
|
void MoveCamera(const mjModel* m, const mjData* d, mjvCamera* cam,
|
|
CameraMotion motion, mjtNum dx, mjtNum dy) {
|
|
if (cam->type == mjCAMERA_FIXED) {
|
|
return;
|
|
}
|
|
|
|
mjtNum headpos[3], forward[3], up[3], right[3];
|
|
mjtNum vec[3], dif[3], scl;
|
|
|
|
switch (motion) {
|
|
case CameraMotion::ZOOM:
|
|
// Zoom the camera towards the target by adjusting its distance to the
|
|
// target.
|
|
cam->distance -= mju_log(1 + cam->distance / m->stat.extent / 3) * dy *
|
|
9 * m->stat.extent;
|
|
break;
|
|
|
|
case CameraMotion::ORBIT:
|
|
cam->azimuth -= dx * 180.0;
|
|
cam->elevation -= dy * 180.0;
|
|
break;
|
|
|
|
case CameraMotion::TRUCK_PEDESTAL:
|
|
case CameraMotion::TRUCK_DOLLY:
|
|
if (cam->type == mjCAMERA_TRACKING) {
|
|
return;
|
|
}
|
|
|
|
mjv_cameraFrame(headpos, forward, up, nullptr, d, cam);
|
|
mju_cross(right, forward, up);
|
|
|
|
// y movement: either dolly (forward/back) or pedestal (up/down)
|
|
mju_addToScl3(cam->lookat,
|
|
(motion == CameraMotion::TRUCK_PEDESTAL) ? up : forward, dy);
|
|
|
|
// x movement: camera truck (left/right)
|
|
mju_addToScl3(cam->lookat, right, dx);
|
|
|
|
break;
|
|
|
|
case CameraMotion::PAN_TILT:
|
|
if (cam->type == mjCAMERA_TRACKING) {
|
|
return;
|
|
}
|
|
|
|
mjv_cameraFrame(headpos, forward, nullptr, nullptr, d, cam);
|
|
cam->azimuth -= dx * 180.0;
|
|
cam->elevation -= dy * 180.0;
|
|
mjv_cameraFrame(nullptr, forward, nullptr, nullptr, d, cam);
|
|
mju_addScl3(cam->lookat, headpos, forward, cam->distance);
|
|
break;
|
|
|
|
case CameraMotion::PLANAR_MOVE_V:
|
|
case CameraMotion::PLANAR_MOVE_H:
|
|
// do not move lookat point of tracking camera
|
|
if (cam->type == mjCAMERA_TRACKING) {
|
|
return;
|
|
}
|
|
|
|
// get camera info and align
|
|
mjv_cameraFrame(headpos, forward, nullptr, nullptr, d, cam);
|
|
AlignToCamera(vec,
|
|
(motion == CameraMotion::PLANAR_MOVE_V) ? mjMOUSE_MOVE_V
|
|
: mjMOUSE_MOVE_H,
|
|
dx, dy, forward);
|
|
|
|
// compute scaling: rendered lookat displacement = mouse displacement
|
|
mju_sub3(dif, cam->lookat, headpos);
|
|
scl = CalculateMovementScale(m, cam) * mju_dot3(dif, forward);
|
|
|
|
// move lookat point in opposite direction
|
|
mju_addToScl3(cam->lookat, vec, -scl);
|
|
break;
|
|
}
|
|
|
|
// clamp camera parameters
|
|
if (cam->azimuth > 180) {
|
|
cam->azimuth -= 360;
|
|
}
|
|
if (cam->azimuth < -180) {
|
|
cam->azimuth += 360;
|
|
}
|
|
if (cam->elevation > 89) {
|
|
cam->elevation = 89;
|
|
}
|
|
if (cam->elevation < -89) {
|
|
cam->elevation = -89;
|
|
}
|
|
if (cam->distance < 0.01 * m->stat.extent) {
|
|
cam->distance = 0.01 * m->stat.extent;
|
|
}
|
|
if (cam->distance > 100 * m->stat.extent) {
|
|
cam->distance = 100 * m->stat.extent;
|
|
}
|
|
}
|
|
|
|
static void MakePickRay(mjtNum pos[3], mjtNum ray[3], const mjModel* m,
|
|
const mjData* d, const mjvCamera* camera, float relx,
|
|
float rely, float aspect_ratio) {
|
|
mjtNum forward[3], up[3], right[3];
|
|
mjv_cameraFrame(pos, forward, up, right, d, camera);
|
|
|
|
float zver[2], zhor[2], zclip[2] = {0, 0};
|
|
mjv_cameraFrustum(zver, zhor, zclip, m, camera);
|
|
|
|
// compute frustum halfwidth to match viewport aspect ratio
|
|
mjtNum half_width = 0.5 * aspect_ratio * (zver[0] + zver[1]);
|
|
mjtNum frustum_center = (zhor[1] - zhor[0]) / 2;
|
|
|
|
// compute up and right offsets from normalized cursor
|
|
mjtNum d_up = -zver[0] + rely * (zver[0] + zver[1]);
|
|
mjtNum d_right = frustum_center + (2 * relx - 1) * half_width;
|
|
|
|
if (camera->orthographic) {
|
|
mju_copy3(ray, forward);
|
|
mju_addToScl3(pos, up, d_up);
|
|
mju_addToScl3(pos, right, d_right);
|
|
} else {
|
|
mju_scl3(ray, forward, zclip[0]);
|
|
mju_addToScl3(ray, up, d_up);
|
|
mju_addToScl3(ray, right, d_right);
|
|
mju_normalize3(ray);
|
|
}
|
|
}
|
|
|
|
static PickResult PickGeom(const mjModel* m, const mjData* d,
|
|
const mjtNum ray_pos[3], const mjtNum ray_dir[3],
|
|
const mjvOption* vis_options) {
|
|
PickResult result;
|
|
result.dist = mj_ray(m, d, ray_pos, ray_dir, vis_options->geomgroup,
|
|
vis_options->flags[mjVIS_STATIC], -1, &result.geom);
|
|
mju_addScl3(result.point, ray_pos, ray_dir, result.dist);
|
|
result.body = m->geom_bodyid[result.geom];
|
|
return result;
|
|
}
|
|
|
|
static PickResult PickFlex(const mjModel* m, const mjData* d,
|
|
const mjtNum ray_pos[3], const mjtNum ray_dir[3],
|
|
const mjvOption* vis_options) {
|
|
const mjtByte flag_vert = vis_options->flags[mjVIS_FLEXVERT];
|
|
const mjtByte flag_edge = vis_options->flags[mjVIS_FLEXEDGE];
|
|
const mjtByte flag_face = vis_options->flags[mjVIS_FLEXFACE];
|
|
const mjtByte flag_skin = vis_options->flags[mjVIS_FLEXSKIN];
|
|
|
|
PickResult result;
|
|
if (!flag_vert && !flag_edge && !flag_face && !flag_skin) {
|
|
return result;
|
|
}
|
|
|
|
for (int i = 0; i < m->nflex; i++) {
|
|
int vertid;
|
|
const mjtNum test_dist =
|
|
mju_rayFlex(m, d, vis_options->flex_layer, flag_vert, flag_edge,
|
|
flag_face, flag_skin, i, ray_pos, ray_dir, &vertid);
|
|
|
|
if (test_dist < 0) {
|
|
continue;
|
|
} else if (result.dist >= 0 && test_dist >= result.dist) {
|
|
continue;
|
|
}
|
|
|
|
result.dist = test_dist;
|
|
if (m->flex_interp[i]) {
|
|
const mjtNum* coord = m->flex_vert0 + 3 * (m->flex_vertadr[i] + vertid);
|
|
mjtNum w = 0;
|
|
int nodeid = -1;
|
|
int nstart = m->flex_nodeadr[i];
|
|
int nend = nstart + m->flex_nodenum[i];
|
|
for (int j = nstart; j < nend; j++) {
|
|
if (mju_evalBasis(coord, j - nstart, m->flex_interp[i]) > w) {
|
|
w = mju_evalBasis(coord, j - nstart, m->flex_interp[i]);
|
|
nodeid = j;
|
|
}
|
|
}
|
|
if (nodeid < 0) {
|
|
mjERROR("flex %d: node closest to vertex %d not found", i, vertid);
|
|
}
|
|
result.body = m->flex_nodebodyid[m->flex_nodeadr[i] + nodeid];
|
|
|
|
if (m->flex_centered[i]) {
|
|
mju_copy3(result.point, d->xpos + 3 * result.body);
|
|
} else {
|
|
mju_mulMatVec3(result.point, d->xmat + 9 * result.body,
|
|
m->flex_node + 3 * nodeid);
|
|
mju_addTo3(result.point, d->xpos + 3 * result.body);
|
|
}
|
|
} else {
|
|
result.body = m->flex_vertbodyid[m->flex_vertadr[i] + vertid];
|
|
mju_copy3(result.point,
|
|
d->flexvert_xpos + 3 * (m->flex_vertadr[i] + vertid));
|
|
}
|
|
result.flex = i;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void MakeSkin(const mjModel* m, const mjData* d, const mjvOption* opt,
|
|
int i, float* skinnormal, float* skinvert) {
|
|
int vertadr = m->skin_vertadr[i];
|
|
int vertnum = m->skin_vertnum[i];
|
|
int faceadr = m->skin_faceadr[i];
|
|
int facenum = m->skin_facenum[i];
|
|
|
|
// accumulate positions from all bones
|
|
for (int j = m->skin_boneadr[i]; j < m->skin_boneadr[i] + m->skin_bonenum[i];
|
|
j++) {
|
|
// get bind pose
|
|
mjtNum bindpos[3] = {(mjtNum)m->skin_bonebindpos[3 * j + 0],
|
|
(mjtNum)m->skin_bonebindpos[3 * j + 1],
|
|
(mjtNum)m->skin_bonebindpos[3 * j + 2]};
|
|
mjtNum bindquat[4] = {(mjtNum)m->skin_bonebindquat[4 * j + 0],
|
|
(mjtNum)m->skin_bonebindquat[4 * j + 1],
|
|
(mjtNum)m->skin_bonebindquat[4 * j + 2],
|
|
(mjtNum)m->skin_bonebindquat[4 * j + 3]};
|
|
|
|
// compute rotation
|
|
int bodyid = m->skin_bonebodyid[j];
|
|
mjtNum quat[4], quatneg[4], rotate[9];
|
|
mju_negQuat(quatneg, bindquat);
|
|
mju_mulQuat(quat, d->xquat + 4 * bodyid, quatneg);
|
|
mju_quat2Mat(rotate, quat);
|
|
|
|
// compute translation
|
|
mjtNum translate[3];
|
|
mju_mulMatVec3(translate, rotate, bindpos);
|
|
mju_sub3(translate, d->xpos + 3 * bodyid, translate);
|
|
|
|
// process all bone vertices
|
|
for (int k = m->skin_bonevertadr[j];
|
|
k < m->skin_bonevertadr[j] + m->skin_bonevertnum[j]; k++) {
|
|
// vertex id and weight
|
|
int vid = m->skin_bonevertid[k];
|
|
float vweight = m->skin_bonevertweight[k];
|
|
|
|
// get original position
|
|
mjtNum pos[3] = {
|
|
(mjtNum)m->skin_vert[3 * (vertadr + vid)],
|
|
(mjtNum)m->skin_vert[3 * (vertadr + vid) + 1],
|
|
(mjtNum)m->skin_vert[3 * (vertadr + vid) + 2],
|
|
};
|
|
|
|
// transform
|
|
mjtNum pos1[3];
|
|
mju_mulMatVec3(pos1, rotate, pos);
|
|
mju_addTo3(pos1, translate);
|
|
|
|
// accumulate position
|
|
skinvert[(3 * vid)] += vweight * (float)pos1[0];
|
|
skinvert[(3 * vid) + 1] += vweight * (float)pos1[1];
|
|
skinvert[(3 * vid) + 2] += vweight * (float)pos1[2];
|
|
}
|
|
}
|
|
|
|
// inflate
|
|
if (m->skin_inflate[i] && skinnormal != nullptr) {
|
|
// compute vertex normals from face normals
|
|
for (int k = faceadr; k < faceadr + facenum; k++) {
|
|
// get face vertex indices
|
|
int vid[3] = {m->skin_face[3 * k], m->skin_face[3 * k + 1],
|
|
m->skin_face[3 * k + 2]};
|
|
|
|
// get triangle edges
|
|
mjtNum vec01[3], vec02[3];
|
|
for (int r = 0; r < 3; r++) {
|
|
vec01[r] = skinvert[3 * (vid[1]) + r] - skinvert[3 * (vid[0]) + r];
|
|
vec02[r] = skinvert[3 * (vid[2]) + r] - skinvert[3 * (vid[0]) + r];
|
|
}
|
|
|
|
// compute face normal
|
|
mjtNum nrm[3];
|
|
mju_cross(nrm, vec01, vec02);
|
|
|
|
// add normal to each vertex with weight = area
|
|
for (int r = 0; r < 3; r++) {
|
|
for (int t = 0; t < 3; t++) {
|
|
skinnormal[3 * (vid[r]) + t] += nrm[t];
|
|
}
|
|
}
|
|
}
|
|
|
|
// normalize normals
|
|
for (int k = 0; k < vertnum; k++) {
|
|
float s = sqrtf(skinnormal[3 * (k) + 0] * skinnormal[3 * k + 0] +
|
|
skinnormal[3 * (k) + 1] * skinnormal[3 * k + 1] +
|
|
skinnormal[3 * (k) + 2] * skinnormal[3 * k + 2]);
|
|
|
|
float scl = 1 / mjMAX(mjMINVAL, s);
|
|
skinnormal[3 * k] *= scl;
|
|
skinnormal[3 * k + 1] *= scl;
|
|
skinnormal[3 * k + 2] *= scl;
|
|
}
|
|
|
|
float inflate = m->skin_inflate[i];
|
|
for (int k = 0; k < vertnum; k++) {
|
|
skinvert[3 * k] += inflate * skinnormal[3 * k];
|
|
skinvert[3 * k + 1] += inflate * skinnormal[3 * k + 1];
|
|
skinvert[3 * k + 2] += inflate * skinnormal[3 * k + 2];
|
|
}
|
|
}
|
|
}
|
|
|
|
static PickResult PickSkin(const mjModel* m, const mjData* d,
|
|
const mjtNum ray_pos[3], const mjtNum ray_dir[3],
|
|
const mjvOption* vis_options) {
|
|
PickResult result;
|
|
if (!vis_options->flags[mjVIS_SKIN]) {
|
|
return result;
|
|
}
|
|
|
|
std::vector<float> vertex_buffer;
|
|
std::vector<float> normal_buffer;
|
|
|
|
for (int i = 0; i < m->nskin; i++) {
|
|
const int skin_group = mjMAX(0, mjMIN(mjNGROUP - 1, m->skin_group[i]));
|
|
if (!vis_options->skingroup[skin_group]) {
|
|
continue;
|
|
}
|
|
|
|
vertex_buffer.resize(3 * m->skin_vertnum[i]);
|
|
if (m->skin_inflate[i]) {
|
|
normal_buffer.resize(3 * m->skin_vertnum[i]);
|
|
}
|
|
|
|
float* skinvert = vertex_buffer.data();
|
|
float* skinnormal = m->skin_inflate[i] ? normal_buffer.data() : nullptr;
|
|
MakeSkin(m, d, vis_options, i, skinvert, skinnormal);
|
|
|
|
int vertid;
|
|
mjtNum test_dist = mju_raySkin(m->skin_facenum[i], m->skin_vertnum[i],
|
|
m->skin_face + 3 * m->skin_faceadr[i],
|
|
skinvert, ray_pos, ray_dir, &vertid);
|
|
if (test_dist < 0) {
|
|
continue;
|
|
} else if (result.dist >= 0 && test_dist >= result.dist) {
|
|
continue;
|
|
}
|
|
|
|
result.dist = test_dist;
|
|
|
|
// find body with largest weight for this vertex
|
|
float best_weight = -1;
|
|
for (int j = m->skin_boneadr[i];
|
|
j < m->skin_boneadr[i] + m->skin_bonenum[i]; j++) {
|
|
for (int k = m->skin_bonevertadr[j];
|
|
k < m->skin_bonevertadr[j] + m->skin_bonevertnum[j]; k++) {
|
|
// get vertex id and weight
|
|
const int vertex_id = m->skin_bonevertid[k];
|
|
const float vertex_weight = m->skin_bonevertweight[k];
|
|
|
|
// update if matching id and bigger weight
|
|
if (vertex_id == vertid && vertex_weight > best_weight) {
|
|
best_weight = vertex_weight;
|
|
result.body = m->skin_bonebodyid[j];
|
|
result.skin = i;
|
|
mju_f2n(result.point, skinvert + 3 * vertid, 3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
PickResult Pick(const mjModel* m, const mjData* d, const mjvCamera* camera,
|
|
float x, float y, float aspect_ratio,
|
|
const mjvOption* vis_options) {
|
|
mjtNum ray_pos[3];
|
|
mjtNum ray_dir[3];
|
|
MakePickRay(ray_pos, ray_dir, m, d, camera, x, 1.0 - y, aspect_ratio);
|
|
|
|
PickResult results[3];
|
|
results[0] = PickGeom(m, d, ray_pos, ray_dir, vis_options);
|
|
results[1] = PickFlex(m, d, ray_pos, ray_dir, vis_options);
|
|
results[2] = PickSkin(m, d, ray_pos, ray_dir, vis_options);
|
|
|
|
PickResult best_result;
|
|
for (int i = 0; i < 3; i++) {
|
|
if (results[i].dist < 0) {
|
|
continue;
|
|
}
|
|
if (best_result.dist < 0 || results[i].dist < best_result.dist) {
|
|
best_result = results[i];
|
|
}
|
|
}
|
|
return best_result;
|
|
}
|
|
|
|
int SetCamera(const mjModel* m, mjvCamera* camera, int request_idx) {
|
|
const int ncam = m ? m->ncam : 0;
|
|
const int camera_idx = std::clamp(request_idx, kTumbleCameraIdx, ncam - 1);
|
|
|
|
if (camera_idx == kTumbleCameraIdx) {
|
|
camera->type = mjCAMERA_FREE;
|
|
camera->fixedcamid = -1;
|
|
} else if (camera_idx == kFreeCameraIdx) {
|
|
camera->type = mjCAMERA_FREE;
|
|
camera->distance = 2.0f;
|
|
camera->fixedcamid = -1;
|
|
} else if (camera_idx == kTrackingCameraIdx) {
|
|
if (camera->trackbodyid >= 0) {
|
|
camera->type = mjCAMERA_TRACKING;
|
|
} else {
|
|
camera->type = mjCAMERA_FREE;
|
|
}
|
|
camera->fixedcamid = -1;
|
|
} else {
|
|
camera->type = mjCAMERA_FIXED;
|
|
camera->fixedcamid = camera_idx;
|
|
}
|
|
|
|
return camera_idx;
|
|
}
|
|
} // namespace mujoco::platform
|