// 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_ray.h" #include #include #include #include #include "engine/engine_macro.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 functions --------------------------------------------------- // map ray to local geom frame static void ray_map(const mjtNum* pos, const mjtNum* mat, const mjtNum* pnt, const mjtNum* vec, mjtNum* lpnt, mjtNum* lvec) { const mjtNum dif[3] = {pnt[0]-pos[0], pnt[1]-pos[1], pnt[2]-pos[2]}; // lpnt = mat' * dif lpnt[0] = mat[0]*dif[0] + mat[3]*dif[1] + mat[6]*dif[2]; lpnt[1] = mat[1]*dif[0] + mat[4]*dif[1] + mat[7]*dif[2]; lpnt[2] = mat[2]*dif[0] + mat[5]*dif[1] + mat[8]*dif[2]; // lvec = mat' * vec lvec[0] = mat[0]*vec[0] + mat[3]*vec[1] + mat[6]*vec[2]; lvec[1] = mat[1]*vec[0] + mat[4]*vec[1] + mat[7]*vec[2]; lvec[2] = mat[2]*vec[0] + mat[5]*vec[1] + mat[8]*vec[2]; } // eliminate geom static int ray_eliminate(const mjModel* m, const mjData* d, int geomid, const mjtByte* geomgroup, mjtByte flg_static, int bodyexclude) { // body exclusion if (m->geom_bodyid[geomid]==bodyexclude) { return 1; } // invisible geom exclusion if (m->geom_matid[geomid]<0 && m->geom_rgba[4*geomid+3]==0) { return 1; } // invisible material exclusion if (m->geom_matid[geomid]>=0 && m->mat_rgba[4*m->geom_matid[geomid]+3]==0) { return 1; } // static exclusion if (!flg_static && m->geom_bodyid[geomid]==0) { return 1; } // plane and hfield inclusion if (m->geom_type[geomid]==mjGEOM_PLANE || m->geom_type[geomid]==mjGEOM_HFIELD) { return 0; } // no geomgroup inclusion if (!geomgroup) { return 0; } // group inclusion/exclusion int groupid = mjMIN(mjNGROUP-1, mjMAX(0, m->geom_group[geomid])); return (geomgroup[groupid]==0); } // compute solution from quadratic: a*x^2 + 2*b*x + c = 0 static mjtNum ray_quad(mjtNum a, mjtNum b, mjtNum c, mjtNum* x) { // compute determinant and check mjtNum det = b*b - a*c; if (det=0) { return x[0]; } else if (x[1]>=0) { return x[1]; } else { return -1; } } // intersect ray with triangle static mjtNum ray_triangle(mjtNum v[][3], const mjtNum* lpnt, const mjtNum* lvec, const mjtNum* b0, const mjtNum* b1) { // dif = v[i] - lpnt mjtNum dif[3][3]; for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { dif[i][j] = v[i][j] - lpnt[j]; } } // project difference vectors in normal plane mjtNum planar[3][2]; for (int i=0; i<3; i++) { planar[i][0] = mju_dot3(b0, dif[i]); planar[i][1] = mju_dot3(b1, dif[i]); } // reject if on the same side of any coordinate axis if ((planar[0][0]>0 && planar[1][0]>0 && planar[2][0]>0) || (planar[0][0]<0 && planar[1][0]<0 && planar[2][0]<0) || (planar[0][1]>0 && planar[1][1]>0 && planar[2][1]>0) || (planar[0][1]<0 && planar[1][1]<0 && planar[2][1]<0)) { return -1; } // determine if origin is inside planar projection of triangle // A = (p0-p2, p1-p2), b = -p2, solve A*t = b mjtNum A[4] = {planar[0][0]-planar[2][0], planar[1][0]-planar[2][0], planar[0][1]-planar[2][1], planar[1][1]-planar[2][1]}; mjtNum b[2] = {-planar[2][0], -planar[2][1]}; mjtNum det = A[0]*A[3] - A[1]*A[2]; if (mju_abs(det)1) { return -1; } // intersect ray with plane of triangle mju_sub3(dif[0], v[0], v[2]); // v0-v2 mju_sub3(dif[1], v[1], v[2]); // v1-v2 mju_sub3(dif[2], lpnt, v[2]); // lp-v2 mjtNum nrm[3]; mju_cross(nrm, dif[0], dif[1]); // normal to triangle plane mjtNum denom = mju_dot3(lvec, nrm); if (mju_abs(denom)-mjMINVAL) { return -1; } // intersection with plane const mjtNum x = -lpnt[2]/lvec[2]; if (x<0) { return -1; } mjtNum p0 = lpnt[0] + x*lvec[0]; mjtNum p1 = lpnt[1] + x*lvec[1]; // accept only within rendered rectangle if ((size[0]<=0 || mju_abs(p0)<=size[0]) && (size[1]<=0 || mju_abs(p1)<=size[1])) { return x; } else { return -1; } } // sphere static mjtNum ray_sphere(const mjtNum* pos, const mjtNum* mat, const mjtNum* size, const mjtNum* pnt, const mjtNum* vec) { // (x*vec+pnt-pos)'*(x*vec+pnt-pos) = size[0]*size[0] mjtNum dif[3] = {pnt[0]-pos[0], pnt[1]-pos[1], pnt[2]-pos[2]}; mjtNum a = vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2]; mjtNum b = vec[0]*dif[0] + vec[1]*dif[1] + vec[2]*dif[2]; mjtNum c = dif[0]*dif[0] + dif[1]*dif[1] + dif[2]*dif[2] - size[0]*size[0]; // solve a*x^2 + 2*b*x + c = 0 mjtNum xx[2]; return ray_quad(a, b, c, xx); } // capsule static mjtNum ray_capsule(const mjtNum* pos, const mjtNum* mat, const mjtNum* size, const mjtNum* pnt, const mjtNum* vec) { // bounding sphere test mjtNum ssz = size[0] + size[1]; if (ray_sphere(pos, NULL, &ssz, pnt, vec)<0) { return -1; } // map to local frame mjtNum lpnt[3], lvec[3]; ray_map(pos, mat, pnt, vec, lpnt, lvec); // init solution mjtNum x = -1, sol, xx[2]; // cylinder round side: (x*lvec+lpnt)'*(x*lvec+lpnt) = size[0]*size[0] mjtNum a = lvec[0]*lvec[0] + lvec[1]*lvec[1]; mjtNum b = lvec[0]*lpnt[0] + lvec[1]*lpnt[1]; mjtNum c = lpnt[0]*lpnt[0] + lpnt[1]*lpnt[1] - size[0]*size[0]; // solve a*x^2 + 2*b*x + c = 0 sol = ray_quad(a, b, c, xx); // make sure round solution is between flat sides if (sol>=0 && mju_abs(lpnt[2]+sol*lvec[2])<=size[1]) { if (x<0 || sol=0 && lpnt[2]+xx[i]*lvec[2]>=size[1]) { if (x<0 || xx[i]=0 && lpnt[2]+xx[i]*lvec[2]<=-size[1]) { if (x<0 || xx[i]mjMINVAL) { for (side=-1; side<=1; side+=2) { // soludion of: lpnt[2] + x*lvec[2] = side*height_size sol = (side*size[1]-lpnt[2])/lvec[2]; // process if non-negative if (sol>=0) { // intersection with horizontal face mjtNum p0 = lpnt[0] + sol*lvec[0]; mjtNum p1 = lpnt[1] + sol*lvec[1]; // accept within radius if (p0*p0 + p1*p1 <= size[0]*size[0]) { if (x<0 || sol=0 && mju_abs(lpnt[2]+sol*lvec[2])<=size[1]) { if (x<0 || solmjMINVAL) { for (int side=-1; side<=1; side+=2) { // soludion of: lpnt[i] + x*lvec[i] = side*size[i] sol = (side*size[i]-lpnt[i])/lvec[i]; // process if non-negative if (sol>=0) { // intersection with face mjtNum p0 = lpnt[iface[i][0]] + sol*lvec[iface[i][0]]; mjtNum p1 = lpnt[iface[i][1]] + sol*lvec[iface[i][1]]; // accept within rectangle if (mju_abs(p0)<=size[iface[i][0]] && mju_abs(p1)<=size[iface[i][1]]) { // update if (x<0 || solgeom_type[id]!=mjGEOM_HFIELD) { mju_error("mj_rayHfield: geom with hfield type expected"); } // hfield id and dimensions int hid = m->geom_dataid[id]; int nrow = m->hfield_nrow[hid]; int ncol = m->hfield_ncol[hid]; const mjtNum* size = m->hfield_size + 4*hid; const float* data = m->hfield_data + m->hfield_adr[hid]; // compute size and pos of base box mjtNum base_size[3] = {size[0], size[1], size[3]*0.5}; mjtNum base_pos[3] = { d->geom_xpos[3*id] - d->geom_xmat[9*id+2]*size[3]*0.5, d->geom_xpos[3*id+1] - d->geom_xmat[9*id+5]*size[3]*0.5, d->geom_xpos[3*id+2] - d->geom_xmat[9*id+8]*size[3]*0.5 }; // compute size and pos of top box mjtNum top_size[3] = {size[0], size[1], size[2]*0.5}; mjtNum top_pos[3] = { d->geom_xpos[3*id] + d->geom_xmat[9*id+2]*size[2]*0.5, d->geom_xpos[3*id+1] + d->geom_xmat[9*id+5]*size[2]*0.5, d->geom_xpos[3*id+2] + d->geom_xmat[9*id+8]*size[2]*0.5 }; // init: intersection with base box mjtNum x = ray_box(base_pos, d->geom_xmat+9*id, base_size, pnt, vec, NULL); // check top box: done if no intersection mjtNum all[6]; mjtNum top_intersect = ray_box(top_pos, d->geom_xmat+9*id, top_size, pnt, vec, all); if (top_intersect<0) { return x; } // map to local frame mjtNum lpnt[3], lvec[3]; ray_map(d->geom_xpos+3*id, d->geom_xmat+9*id, pnt, vec, lpnt, lvec); // construct basis vectors of normal plane mjtNum b0[3] = {1, 1, 1}, b1[3]; if (mju_abs(lvec[0])>=mju_abs(lvec[1]) && mju_abs(lvec[0])>=mju_abs(lvec[2])) { b0[0] = 0; } else if (mju_abs(lvec[1])>=mju_abs(lvec[2])) { b0[1] = 0; } else { b0[2] = 0; } mju_addScl3(b1, b0, lvec, -mju_dot3(lvec, b0)/mju_dot3(lvec, lvec)); mju_normalize3(b1); mju_cross(b0, b1, lvec); mju_normalize3(b0); // find ray segment intersecting top box mjtNum seg[2] = {0, top_intersect}; for (int i=0; i<6; i++) { if (all[i]>seg[1]) { seg[0] = top_intersect; seg[1] = all[i]; } } // project segment endpoints in horizontal plane, discretize mjtNum dx = (2.0*size[0]) / (ncol-1); mjtNum dy = (2.0*size[1]) / (nrow-1); mjtNum SX[2], SY[2]; for (int i=0; i<2; i++) { SX[i] = (lpnt[0] + seg[i]*lvec[0] + size[0]) / dx; SY[i] = (lpnt[1] + seg[i]*lvec[1] + size[1]) / dy; } // compute ranges, with +1 padding int cmin = mjMAX(0, (int)mju_floor(mjMIN(SX[0], SX[1]))-1); int cmax = mjMIN(ncol-1, (int)mju_ceil(mjMAX(SX[0], SX[1]))+1); int rmin = mjMAX(0, (int)mju_floor(mjMIN(SY[0], SY[1]))-1); int rmax = mjMIN(nrow-1, (int)mju_ceil(mjMAX(SY[0], SY[1]))+1); // check triangles within bounds for (int r=rmin; r=0 && (x<0 || sol=0 && (x<0 || sol=0 && (all[i]geom_type[id]!=mjGEOM_MESH) { mju_error("mj_rayMesh: geom with mesh type expected"); } // bounding box test if (ray_box(d->geom_xpos+3*id, d->geom_xmat+9*id, m->geom_size+3*id, pnt, vec, NULL)<0) { return -1; } // map to local frame mjtNum lpnt[3], lvec[3]; ray_map(d->geom_xpos+3*id, d->geom_xmat+9*id, pnt, vec, lpnt, lvec); // construct basis vectors of normal plane mjtNum b0[3] = {1, 1, 1}, b1[3]; if (mju_abs(lvec[0])>=mju_abs(lvec[1]) && mju_abs(lvec[0])>=mju_abs(lvec[2])) { b0[0] = 0; } else if (mju_abs(lvec[1])>=mju_abs(lvec[2])) { b0[1] = 0; } else { b0[2] = 0; } mju_addScl3(b1, b0, lvec, -mju_dot3(lvec, b0)/mju_dot3(lvec, lvec)); mju_normalize3(b1); mju_cross(b0, b1, lvec); mju_normalize3(b0); // init solution mjtNum x = -1, sol; // process all triangles int face, meshid = m->geom_dataid[id]; for (face = m->mesh_faceadr[meshid]; face < m->mesh_faceadr[meshid] + m->mesh_facenum[meshid]; face++) { // get float vertices float* vf[3]; vf[0] = m->mesh_vert + 3*(m->mesh_face[3*face] + m->mesh_vertadr[meshid]); vf[1] = m->mesh_vert + 3*(m->mesh_face[3*face+1] + m->mesh_vertadr[meshid]); vf[2] = m->mesh_vert + 3*(m->mesh_face[3*face+2] + m->mesh_vertadr[meshid]); // convert to mjtNum mjtNum v[3][3]; for (int i=0; i<3; i++) { for (int j=0; j<3; j++) { v[i][j] = (mjtNum)vf[i][j]; } } // solve sol = ray_triangle(v, lpnt, lvec, b0, b1); // update if (sol>=0 && (x<0 || solvert[3*i+j] || i==0) { box[j][0] = vert[3*i+j]; } // update maximum along side j if (box[j][1]=mju_abs(vec[1]) && mju_abs(vec[0])>=mju_abs(vec[2])) { b0[0] = 0; } else if (mju_abs(vec[1])>=mju_abs(vec[2])) { b0[1] = 0; } else { b0[2] = 0; } mju_addScl3(b1, b0, vec, -mju_dot3(vec, b0)/mju_dot3(vec, vec)); mju_normalize3(b1); mju_cross(b0, b1, vec); mju_normalize3(b0); // init solution mjtNum x = -1, sol; // process all faces for (int i=0; i=0 && (x<0 || sol=0) with visible geoms, except geoms on bodyexclude // return geomid and distance (x) to nearest surface, or -1 if no intersection // geomgroup, flg_static are as in mjvOption; geomgroup==NULL skips group exclusion mjtNum mj_ray(const mjModel* m, const mjData* d, const mjtNum* pnt, const mjtNum* vec, const mjtByte* geomgroup, mjtByte flg_static, int bodyexclude, int geomid[1]) { mjtNum dist, newdist; // check vector length if (mju_norm3(vec)ngeom; i++) { if (!ray_eliminate(m, d, i, geomgroup, flg_static, bodyexclude)) { // handle mesh and hfield separately if (m->geom_type[i]==mjGEOM_MESH) { newdist = mj_rayMesh(m, d, i, pnt, vec); } else if (m->geom_type[i]==mjGEOM_HFIELD) { newdist = mj_rayHfield(m, d, i, pnt, vec); } // otherwise general dispatch else { newdist = mju_rayGeom(d->geom_xpos+3*i, d->geom_xmat+9*i, m->geom_size+3*i, pnt, vec, m->geom_type[i]); } // update if closer intersection found if (newdist>=0 && (newdist