Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
46f925a79This file is pinned to the selected release. Download the original to open it in your design tool.
// 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 <math.h>
#include <stddef.h>
#include <mujoco/mjdata.h>
#include <mujoco/mjmacro.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjsan.h> // IWYU pragma: keep
#include <mujoco/mjvisualize.h>
#include "engine/engine_collision_sdf.h"
#include "engine/engine_memory.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[3], const mjtNum mat[9],
const mjtNum pnt[3], const mjtNum vec[3],
mjtNum lpnt[3], mjtNum lvec[3]) {
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];
}
// map to azimuth angle in spherical coordinates
static mjtNum longitude(const mjtNum vec[3]) {
return mju_atan2(vec[1], vec[0]);
}
// map to elevation angle in spherical coordinates
static mjtNum latitude(const mjtNum vec[3]) {
return mju_atan2(mju_sqrt(vec[0]*vec[0] + vec[1]*vec[1]), vec[2]);
}
// eliminate geom
static int ray_eliminate(const mjModel* m, const mjData* d, int geomid,
const mjtByte* geomgroup, mjtBool 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->body_weldid[m->geom_bodyid[geomid]] == 0) {
return 1;
}
// 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 both real solutions of a*x^2 + 2*b*x + c = 0, return smallest non-negative solution if any
static mjtNum ray_quad(mjtNum a, mjtNum b, mjtNum c, mjtNum x[2]) {
// compute determinant
mjtNum det = b*b - a*c;
// return if real finite solutions don't exist
if (det < 0 || a < mjMINVAL) {
x[0] = -1;
x[1] = -1;
return -1;
}
// compute the two solutions, x[0] <= x[1] is guaranteed
det = mju_sqrt(det);
x[0] = (-b-det)/a;
x[1] = (-b+det)/a;
// return smallest non-negative solution
if (x[0] >= 0) {
return x[0];
} else if (x[1] >= 0) {
return x[1];
}
// both solutions are negative
return -1;
}
// intersect ray with triangle
mjtNum ray_triangle(mjtNum v[][3], const mjtNum lpnt[3], const mjtNum lvec[3],
const mjtNum b0[3], const mjtNum b1[3], mjtNum normal[3]) {
// clear normal if given
if (normal) mju_zero3(normal);
// 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) < mjMINVAL) {
return -1;
}
mjtNum t0 = ( A[3]*b[0] - A[1]*b[1]) / det;
mjtNum t1 = (-A[2]*b[0] + A[0]*b[1]) / det;
// check if outside
if (t0 < 0 || t1 < 0|| t0+t1 > 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;
}
// compute distance
mjtNum x = -mju_dot3(dif[2], nrm) / denom;
// compute normal if given
if (normal) {
mju_normalize3(nrm);
mju_copy3(normal, nrm);
}
return x;
}
//---------------------------- geom-specific intersection functions --------------------------------
// plane
static mjtNum ray_plane(const mjtNum pos[3], const mjtNum mat[9], const mjtNum size[3],
const mjtNum pnt[3], const mjtNum vec[3], mjtNum normal[3]) {
// clear normal if given
if (normal) mju_zero3(normal);
// map to local frame
mjtNum lpnt[3], lvec[3];
ray_map(pos, mat, pnt, vec, lpnt, lvec);
// z-vec not pointing towards front face: reject
if (lvec[2] > -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])) {
if (normal) {
normal[0] = mat[2];
normal[1] = mat[5];
normal[2] = mat[8];
}
return x;
} else {
return -1;
}
}
// sphere
static mjtNum ray_sphere(const mjtNum pos[3], const mjtNum mat[9], mjtNum dist_sqr,
const mjtNum pnt[3], const mjtNum vec[3], mjtNum normal[3]) {
// (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] - dist_sqr;
// solve a*x^2 + 2*b*x + c = 0
mjtNum xx[2];
mjtNum x = ray_quad(a, b, c, xx);
// compute normal if required
if (normal) {
if (x < 0) {
mju_zero3(normal);
} else {
// normal at surface intersection s (global frame)
mjtNum s[3];
mju_addScl3(s, pnt, vec, x);
mju_sub3(normal, s, pos);
mju_normalize3(normal);
}
}
return x;
}
// capsule
static mjtNum ray_capsule(const mjtNum pos[3], const mjtNum mat[9], const mjtNum size[3],
const mjtNum pnt[3], const mjtNum vec[3], mjtNum normal[3]) {
// bounding sphere test
mjtNum ssz = size[0] + size[1];
if (ray_sphere(pos, NULL, ssz * ssz, pnt, vec, NULL) < 0) {
if (normal) mju_zero3(normal);
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];
int type; // -1: bottom, 0: cylinder, 1: top
// 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 < x) {
x = sol;
type = 0;
}
}
// top cap
mjtNum ldif[3] = {lpnt[0], lpnt[1], lpnt[2]-size[1]};
a = lvec[0]*lvec[0] + lvec[1]*lvec[1] + lvec[2]*lvec[2];
b = lvec[0]*ldif[0] + lvec[1]*ldif[1] + lvec[2]*ldif[2];
c = ldif[0]*ldif[0] + ldif[1]*ldif[1] + ldif[2]*ldif[2] - size[0]*size[0];
ray_quad(a, b, c, xx);
// accept only top half of sphere
for (int i=0; i < 2; i++) {
if (xx[i] >= 0 && lpnt[2]+xx[i]*lvec[2] >= size[1]) {
if (x < 0 || xx[i] < x) {
x = xx[i];
type = 1;
}
}
}
// bottom cap
ldif[2] = lpnt[2]+size[1];
b = lvec[0]*ldif[0] + lvec[1]*ldif[1] + lvec[2]*ldif[2];
c = ldif[0]*ldif[0] + ldif[1]*ldif[1] + ldif[2]*ldif[2] - size[0]*size[0];
ray_quad(a, b, c, xx);
// accept only bottom half of sphere
for (int i=0; i < 2; i++) {
if (xx[i] >= 0 && lpnt[2]+xx[i]*lvec[2] <= -size[1]) {
if (x < 0 || xx[i] < x) {
x = xx[i];
type = -1;
}
}
}
// compute normal if required
if (normal) {
if (x < 0) {
mju_zero3(normal);
} else {
normal[0] = lpnt[0] + lvec[0] * x;
normal[1] = lpnt[1] + lvec[1] * x;
normal[2] = (type == 0) ? 0 : lpnt[2] + lvec[2] * x - size[1] * type;
// normalize, rotate into global frame
mju_normalize3(normal);
mju_mulMatVec3(normal, mat, normal);
}
}
return x;
}
// ellipsoid
static mjtNum ray_ellipsoid(const mjtNum pos[3], const mjtNum mat[9], const mjtNum size[3],
const mjtNum pnt[3], const mjtNum vec[3], mjtNum normal[3]) {
// map to local frame
mjtNum lpnt[3], lvec[3];
ray_map(pos, mat, pnt, vec, lpnt, lvec);
// invert size^2
mjtNum s[3] = {1/(size[0]*size[0]), 1/(size[1]*size[1]), 1/(size[2]*size[2])};
// (x*lvec+lpnt)' * diag(1./size^2) * (x*lvec+lpnt) = 1
mjtNum a = s[0]*lvec[0]*lvec[0] + s[1]*lvec[1]*lvec[1] + s[2]*lvec[2]*lvec[2];
mjtNum b = s[0]*lvec[0]*lpnt[0] + s[1]*lvec[1]*lpnt[1] + s[2]*lvec[2]*lpnt[2];
mjtNum c = s[0]*lpnt[0]*lpnt[0] + s[1]*lpnt[1]*lpnt[1] + s[2]*lpnt[2]*lpnt[2] - 1;
// solve a*x^2 + 2*b*x + c = 0
mjtNum xx[2];
mjtNum x = ray_quad(a, b, c, xx);
// compute normal if required
if (normal) {
if (x < 0) {
mju_zero3(normal);
} else {
// surface intersection (local frame)
mjtNum l[3];
mju_addScl3(l, lpnt, lvec, x);
// gradient of ellipsoid function
normal[0] = s[0] * l[0];
normal[1] = s[1] * l[1];
normal[2] = s[2] * l[2];
// normalize, rotate into global frame
mju_normalize3(normal);
mju_mulMatVec3(normal, mat, normal);
}
}
return x;
}
// cylinder
static mjtNum ray_cylinder(const mjtNum pos[3], const mjtNum mat[9], const mjtNum size[3],
const mjtNum pnt[3], const mjtNum vec[3], mjtNum normal[3]) {
// bounding sphere test
mjtNum ssz = size[0]*size[0] + size[1]*size[1];
if (ray_sphere(pos, NULL, ssz, pnt, vec, NULL) < 0) {
if (normal) mju_zero3(normal);
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;
int type = 0; // -1: bottom, 0: round, 1: top
// flat sides
int side;
if (mju_abs(lvec[2]) > mjMINVAL) {
for (side=-1; side <= 1; side+=2) {
// solution 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 < x) {
x = sol;
type = side;
}
}
}
}
}
// 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
mjtNum xx[2];
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 < x) {
x = sol;
type = 0;
}
}
// compute normal if required
if (normal) {
if (x < 0) {
mju_zero3(normal);
} else {
// round side
if (type == 0) {
// normal at surface intersection (local frame)
normal[0] = lpnt[0] + lvec[0] * x;
normal[1] = lpnt[1] + lvec[1] * x;
normal[2] = 0;
mju_normalize3(normal);
}
// flat sides
else {
normal[0] = 0;
normal[1] = 0;
normal[2] = type;
}
// rotate into global frame
mju_mulMatVec3(normal, mat, normal);
}
}
return x;
}
// box
static mjtNum ray_box(const mjtNum pos[3], const mjtNum mat[9], const mjtNum size[3],
const mjtNum pnt[3], const mjtNum vec[3], mjtNum all[6], mjtNum normal[3]) {
// clear outputs
if (all) all[0] = all[1] = all[2] = all[3] = all[4] = all[5] = -1;
if (normal) mju_zero3(normal);
// bounding sphere test
mjtNum ssz = size[0]*size[0] + size[1]*size[1] + size[2]*size[2];
if (ray_sphere(pos, NULL, ssz, pnt, vec, NULL) < 0) {
return -1;
}
// faces
const int iface[3][2] = {
{1, 2},
{0, 2},
{0, 1}
};
// map to local frame
mjtNum lpnt[3], lvec[3];
ray_map(pos, mat, pnt, vec, lpnt, lvec);
// init solution
mjtNum x = -1, sol;
int face_side, face_axis = -1;
// loop over axes with non-zero vec
for (int i=0; i < 3; i++) {
if (mju_abs(lvec[i]) > mjMINVAL) {
for (int side=-1; side <= 1; side+=2) {
// solution 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 || sol < x) {
x = sol;
face_axis = i;
face_side = side;
}
// save in all
if (all) {
all[2*i+(side+1)/2] = sol;
}
}
}
}
}
}
// compute normal if required
if (normal && x >= 0) {
mjtNum n_local[3] = {0, 0, 0};
n_local[face_axis] = face_side;
mju_mulMatVec3(normal, mat, n_local);
}
return x;
}
// intersect ray with hfield, compute normal if given
mjtNum mj_rayHfield(const mjModel* m, const mjData* d, int geomid,
const mjtNum pnt[3], const mjtNum vec[3], mjtNum normal[3]) {
// clear normal if given
if (normal) mju_zero3(normal);
// check geom type
if (m->geom_type[geomid] != mjGEOM_HFIELD) {
mjERROR("geom with hfield type expected");
}
// hfield id and dimensions
int hid = m->geom_dataid[geomid];
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};
const mjtNum* xmat = d->geom_xmat + 9*geomid;
const mjtNum* xpos = d->geom_xpos + 3*geomid;
mjtNum base_pos[3] = {
xpos[0] - xmat[2]*size[3]*0.5,
xpos[1] - xmat[5]*size[3]*0.5,
xpos[2] - xmat[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] = {
xpos[0] + xmat[2]*size[2]*0.5,
xpos[1] + xmat[5]*size[2]*0.5,
xpos[2] + xmat[8]*size[2]*0.5
};
// init: intersection with base box
mjtNum normal_base[3];
mjtNum x = ray_box(base_pos, xmat, base_size, pnt, vec,
NULL, normal ? normal_base : NULL);
// check top box: done if no intersection
mjtNum all[6];
mjtNum top_intersect = ray_box(top_pos, xmat, top_size, pnt, vec, all, NULL);
if (top_intersect < 0) {
if (normal && x >= 0) mju_copy3(normal, normal_base);
return x;
}
// map to local frame
mjtNum lpnt[3], lvec[3];
ray_map(xpos, xmat, 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);
// local normal, initialize with base box normal (if any), in local frame
mjtNum normal_local[3] = {0, 0, 0};
if (normal && x >= 0) {
mju_mulMatTVec3(normal_local, xmat, normal_base);
}
// check triangles within bounds
for (int r=rmin; r < rmax; r++) {
for (int c=cmin; c < cmax; c++) {
// triangle normal
mjtNum normal_tri[3];
// first triangle: swap v1 and v2 for consistent CCW winding (normals point up)
mjtNum va[3][3] = {
{dx*c-size[0], dy*r-size[1], data[r*ncol+c]*size[2]},
{dx*(c+1)-size[0], dy*(r+0)-size[1], data[(r+0)*ncol+(c+1)]*size[2]},
{dx*(c+1)-size[0], dy*(r+1)-size[1], data[(r+1)*ncol+(c+1)]*size[2]}
};
mjtNum sol = ray_triangle(va, lpnt, lvec, b0, b1, normal ? normal_tri : NULL);
if (sol >= 0 && (x < 0 || sol < x)) {
x = sol;
if (normal) mju_copy3(normal_local, normal_tri);
}
// second triangle
mjtNum vb[3][3] = {
{dx*c-size[0], dy*r-size[1], data[r*ncol+c]*size[2]},
{dx*(c+1)-size[0], dy*(r+1)-size[1], data[(r+1)*ncol+(c+1)]*size[2]},
{dx*(c+0)-size[0], dy*(r+1)-size[1], data[(r+1)*ncol+(c+0)]*size[2]}
};
sol = ray_triangle(vb, lpnt, lvec, b0, b1, normal ? normal_tri : NULL);
if (sol >= 0 && (x < 0 || sol < x)) {
x = sol;
if (normal) mju_copy3(normal_local, normal_tri);
}
}
}
// check viable sides of top box
for (int i=0; i < 4; i++) {
if (all[i] >= 0 && (all[i] < x || x < 0)) {
// normalized height of intersection point
mjtNum z = (lpnt[2] + all[i]*lvec[2]) / size[2];
// rectangle points
mjtNum y, y0, z0, z1;
// side normal to x-axis
if (i < 2) {
y = (lpnt[1] + all[i]*lvec[1] + size[1]) / dy;
y0 = mjMAX(0, mjMIN(nrow-2, mju_floor(y)));
z0 = (mjtNum)data[mju_round(y0+0)*ncol + (i == 1 ? ncol-1 : 0)];
z1 = (mjtNum)data[mju_round(y0+1)*ncol + (i == 1 ? ncol-1 : 0)];
}
// side normal to y-axis
else {
y = (lpnt[0] + all[i]*lvec[0] + size[0]) / dx;
y0 = mjMAX(0, mjMIN(ncol-2, mju_floor(y)));
z0 = (mjtNum)data[mju_round(y0+0) + (i == 3 ? (nrow-1)*ncol : 0)];
z1 = (mjtNum)data[mju_round(y0+1) + (i == 3 ? (nrow-1)*ncol : 0)];
}
// check if point is below line segment
if (z < z0*(y0+1-y) + z1*(y-y0)) {
x = all[i];
// compute normal
if (normal) {
mju_zero3(normal_local);
if (i == 0) normal_local[0] = -1;
else if (i == 1) normal_local[0] = 1;
else if (i == 2) normal_local[1] = -1;
else if (i == 3) normal_local[1] = 1;
}
}
}
}
// rotate normal to global frame
if (normal && x >= 0) {
mju_mulMatVec3(normal, xmat, normal_local);
}
return x;
}
// ray vs axis-aligned bounding box using slab method
// see Ericson, Real-time Collision Detection section 5.3.3.
int mju_raySlab(const mjtNum aabb[6], const mjtNum xpos[3],
const mjtNum xmat[9], const mjtNum pnt[3], const mjtNum vec[3]) {
mjtNum tmin = 0.0, tmax = INFINITY;
// compute min and max
mjtNum min[3] = {aabb[0]-aabb[3], aabb[1]-aabb[4], aabb[2]-aabb[5]};
mjtNum max[3] = {aabb[0]+aabb[3], aabb[1]+aabb[4], aabb[2]+aabb[5]};
// compute ray in local coordinates
mjtNum src[3], dir[3];
ray_map(xpos, xmat, pnt, vec, src, dir);
// check intersections
mjtNum invdir[3] = { 1.0 / dir[0], 1.0 / dir[1], 1.0 / dir[2] };
for (int d = 0; d < 3; ++d) {
mjtNum t1 = (min[d] - src[d]) * invdir[d];
mjtNum t2 = (max[d] - src[d]) * invdir[d];
mjtNum minval = t1 < t2 ? t1 : t2;
mjtNum maxval = t1 < t2 ? t2 : t1;
tmin = tmin > minval ? tmin : minval;
tmax = tmax < maxval ? tmax : maxval;
}
return tmin < tmax;
}
// ray vs tree intersection
mjtNum mju_rayTree(const mjModel* m, const mjData* d, int id, const mjtNum pnt[3],
const mjtNum vec[3], mjtNum normal[3]) {
// clear normal if given
if (normal) mju_zero3(normal);
int mark_active = m->vis.global.bvactive;
const int meshid = m->geom_dataid[id];
const int bvhadr = m->mesh_bvhadr[meshid];
const int* faceid = m->bvh_nodeid + bvhadr;
const mjtNum* bvh = m->bvh_aabb + 6*bvhadr;
const int* child = m->bvh_child + 2*bvhadr;
if (meshid == -1) {
mjERROR("mesh id of geom %d is -1", meshid); // SHOULD NOT OCCUR
}
// initialize stack
int stack[mjMAXTREEDEPTH];
int nstack = 0;
stack[nstack] = 0;
nstack++;
// 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;
mjtNum normal_local[3];
while (nstack) {
// pop from stack
nstack--;
int node = stack[nstack];
// intersection test
int intersect = mju_raySlab(bvh+6*node, d->geom_xpos+3*id, d->geom_xmat+9*id, pnt, vec);
// if no intersection, skip
if (!intersect) {
continue;
}
// node1 is a leaf
if (faceid[node] != -1) {
int face = faceid[node] + m->mesh_faceadr[meshid];
// get float vertices
float* vf[3];
vf[0] = m->mesh_vert + 3*(m->mesh_face[3*face+0] + 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, normal ? normal_local : NULL);
// update
if (sol >= 0 && (x < 0 || sol < x)) {
x = sol;
if (normal) mju_copy3(normal, normal_local);
if (mark_active) d->bvh_active[node + bvhadr] = true;
}
continue;
}
// used for rendering
if (mark_active) {
d->bvh_active[node + bvhadr] = true;
}
// add children to the stack
for (int i=0; i < 2; i++) {
if (child[2*node+i] != -1) {
if (nstack >= mjMAXTREEDEPTH) {
mjERROR("BVH stack depth exceeded in geom %d.", id);
}
stack[nstack] = child[2*node+i];
nstack++;
}
}
}
// rotate normal to global frame
if (normal && x >= 0) {
mju_mulMatVec3(normal, d->geom_xmat+9*id, normal);
}
return x;
}
// intersect ray with signed distance field, compute normal if given
static mjtNum mj_raySdf(const mjModel* m, const mjData* d, int g,
const mjtNum pnt[3], const mjtNum vec[3], mjtNum normal[3]) {
if (normal) mju_zero3(normal);
mjtNum distance_total = 0;
mjtNum p[3];
mjtNum kMinDist = 1e-7;
// exclude using bounding box
if (ray_box(d->geom_xpos+3*g, d->geom_xmat+9*g, m->geom_size+3*g, pnt, vec, NULL, NULL) < 0) {
return -1;
}
// get sdf plugin
int instance = m->geom_plugin[g];
const mjpPlugin* sdf_ptr = instance == -1 ? NULL : mjc_getSDF(m, g);
instance = instance == -1 ? m->geom_dataid[g] : instance;
mjtGeom geomtype = mjGEOM_SDF;
// construct sdf struct
mjSDF sdf;
sdf.id = &instance;
sdf.type = mjSDFTYPE_SINGLE;
sdf.plugin = &sdf_ptr;
sdf.geomtype = &geomtype;
// reset counter
if (sdf_ptr) {
sdf_ptr->reset(m, NULL, (void*)(d->plugin_data[instance]), instance);
}
// map to local frame
mjtNum lpnt[3], lvec[3];
ray_map(d->geom_xpos + 3*g, d->geom_xmat + 9*g, pnt, vec, lpnt, lvec);
// unit direction
mju_normalize3(lvec);
// ray marching, see e.g. https://en.wikipedia.org/wiki/Ray_marching
for (int i=0; i < 40; i++) {
mju_addScl3(p, lpnt, lvec, distance_total);
mjtNum distance = mju_abs(mjc_distance(m, d, &sdf, p));
distance_total += distance;
if (mju_abs(distance) < kMinDist) {
if (normal) {
mju_addScl3(p, lpnt, lvec, distance_total);
mjc_gradient(m, d, &sdf, normal, p);
mju_normalize3(normal);
mju_mulMatVec3(normal, d->geom_xmat + 9*g, normal);
}
return distance_total;
}
if (distance > 1e6) {
// no intersection
break;
}
}
// reset counter
if (sdf_ptr) {
sdf_ptr->reset(m, NULL, (void*)(d->plugin_data[instance]), instance);
}
return -1;
}
// intersect ray with mesh, compute normal if given
mjtNum mj_rayMesh(const mjModel* m, const mjData* d, int id, const mjtNum pnt[3],
const mjtNum vec[3], mjtNum normal[3]) {
// clear normal if given
if (normal) mju_zero3(normal);
// check geom type
if (m->geom_type[id] != mjGEOM_MESH) {
mjERROR("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, NULL) < 0) {
return -1;
}
return mju_rayTree(m, d, id, pnt, vec, normal);
}
// intersect ray with primitive geom, no meshes or hfields, compute normal if given
mjtNum mju_rayGeom(const mjtNum pos[3], const mjtNum mat[9], const mjtNum size[3],
const mjtNum pnt[3], const mjtNum vec[3], int geomtype,
mjtNum normal[3]) {
switch ((mjtGeom) geomtype) {
case mjGEOM_PLANE:
return ray_plane(pos, mat, size, pnt, vec, normal);
case mjGEOM_SPHERE:
return ray_sphere(pos, mat, size[0] * size[0], pnt, vec, normal);
case mjGEOM_CAPSULE:
return ray_capsule(pos, mat, size, pnt, vec, normal);
case mjGEOM_ELLIPSOID:
return ray_ellipsoid(pos, mat, size, pnt, vec, normal);
case mjGEOM_CYLINDER:
return ray_cylinder(pos, mat, size, pnt, vec, normal);
case mjGEOM_BOX:
return ray_box(pos, mat, size, pnt, vec, NULL, normal);
default:
mjERROR("unexpected geom type %d", geomtype);
return -1;
}
}
// intersect ray with flex, return nearest vertex id, compute normal if given
mjtNum mj_rayFlex(const mjModel* m, const mjData* d, int flex_layer,
mjtBool flg_vert, mjtBool flg_edge, mjtBool flg_face,
mjtBool flg_skin, int flexid, const mjtNum pnt[3],
const mjtNum vec[3], int vertid[1], mjtNum normal[3]) {
int dim = m->flex_dim[flexid];
// clear normal if given
if (normal) mju_zero3(normal);
// compute bounding box
mjtNum box[3][2] = {{0, 0}, {0, 0}, {0, 0}};
mjtNum* vert = d->flexvert_xpos + 3*m->flex_vertadr[flexid];
for (int i=0; i < m->flex_vertnum[flexid]; i++) {
for (int j=0; j < 3; j++) {
// update minimum along side j
if (box[j][0] > vert[3*i+j] || i == 0) {
box[j][0] = vert[3*i+j];
}
// update maximum along side j
if (box[j][1] < vert[3*i+j] || i == 0) {
box[j][1] = vert[3*i+j];
}
}
}
// adjust box for radius
mjtNum radius = m->flex_radius[flexid];
for (int j=0; j < 3; j++) {
box[j][0] -= radius;
box[j][1] += radius;
}
// construct box geom
mjtNum pos[3], size[3], mat[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
for (int j=0; j < 3; j++) {
pos[j] = 0.5*(box[j][0]+box[j][1]);
size[j] = 0.5*(box[j][1]-box[j][0]);
}
// apply bounding-box filter
if (ray_box(pos, mat, size, pnt, vec, NULL, NULL) < 0) {
return -1;
}
// construct basis vectors of normal plane
mjtNum b0[3] = {1, 1, 1}, b1[3];
if (mju_abs(vec[0]) >= 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;
mjtNum normal_local[3];
// check edges if rendered, or if skin
if (flg_edge || (dim > 1 && flg_skin)) {
int edge_end = m->flex_edgeadr[flexid]+m->flex_edgenum[flexid];
for (int e=m->flex_edgeadr[flexid]; e < edge_end; e++) {
// get vertices for this edge
mjtNum* v1 = d->flexvert_xpos + 3*(m->flex_vertadr[flexid]+m->flex_edge[2*e]);
mjtNum* v2 = d->flexvert_xpos + 3*(m->flex_vertadr[flexid]+m->flex_edge[2*e+1]);
// construct capsule geom
mju_add3(pos, v1, v2);
mju_scl3(pos, pos, 0.5);
mjtNum dif[3] = {v2[0]-v1[0], v2[1]-v1[1], v2[2]-v1[2]};
size[0] = radius;
size[1] = 0.5*mju_normalize3(dif);
mjtNum quat[4];
mju_quatZ2Vec(quat, dif);
mju_quat2Mat(mat, quat);
// intersect ray with capsule
mjtNum sol = mju_rayGeom(pos, mat, size, pnt, vec, mjGEOM_CAPSULE,
normal ? normal_local : NULL);
// update
if (sol >= 0 && (x < 0 || sol < x)) {
x = sol;
if (normal) mju_copy3(normal, normal_local);
// construct intersection point
mjtNum intersect[3];
mju_addScl3(intersect, pnt, vec, sol);
// find nearest vertex
if (vertid) {
if (mju_dist3(v1, intersect) < mju_dist3(v2, intersect)) {
*vertid = m->flex_edge[2*e];
} else {
*vertid = m->flex_edge[2*e+1];
}
}
}
}
}
// check vertices if rendered (and edges not checked)
else if (flg_vert && !(dim > 1 && flg_skin)) {
for (int v=0; v < m->flex_vertnum[flexid]; v++) {
// get vertex
mjtNum* vpos = d->flexvert_xpos + 3*(m->flex_vertadr[flexid] + v);
// construct sphere geom
size[0] = radius;
// intersect ray with sphere
mjtNum sol = mju_rayGeom(vpos, NULL, size, pnt, vec, mjGEOM_SPHERE,
normal ? normal_local : NULL);
// update
if (sol >= 0 && (x < 0 || sol < x)) {
x = sol;
if (normal) mju_copy3(normal, normal_local);
if (vertid) *vertid = v;
}
}
}
// check faces if rendered
if (dim > 1 && (flg_face || flg_skin)) {
for (int e=0; e < m->flex_elemnum[flexid]; e++) {
// skip if 3D element is not visible
int elayer = m->flex_elemlayer[m->flex_elemadr[flexid]+e];
if (dim == 3 && ((flg_skin && elayer > 0) || (!flg_skin && elayer != flex_layer))) {
continue;
}
// get element data
const int* edata = m->flex_elem + m->flex_elemdataadr[flexid] + e*(dim+1);
mjtNum* v1 = d->flexvert_xpos + 3*(m->flex_vertadr[flexid] + edata[0]);
mjtNum* v2 = d->flexvert_xpos + 3*(m->flex_vertadr[flexid] + edata[1]);
mjtNum* v3 = d->flexvert_xpos + 3*(m->flex_vertadr[flexid] + edata[2]);
mjtNum* v4 = dim == 2 ? NULL : d->flexvert_xpos + 3*(m->flex_vertadr[flexid] + edata[3]);
mjtNum* vptr[4][3] = {{v1, v2, v3}, {v1, v2, v4}, {v1, v3, v4}, {v2, v3, v4}};
int vid[4][3] = {{0, 1, 2}, {0, 1, 3}, {0, 2, 3}, {1, 2, 3}};
// process triangles of this element
for (int i = 0; i < (dim == 2 ? 1 : 4); i++) {
// copy vertices into triangle representation
mjtNum v[3][3];
for (int j=0; j < 3; j++)
mju_copy3(v[j], vptr[i][j]);
// intersect ray with triangle
mjtNum sol = ray_triangle(v, pnt, vec, b0, b1, normal ? normal_local : NULL);
// update
if (sol >= 0 && (x < 0 || sol < x)) {
x = sol;
if (normal) mju_copy3(normal, normal_local);
// construct intersection point
mjtNum intersect[3];
mju_addScl3(intersect, pnt, vec, sol);
// find nearest vertex
mjtNum dist[3] = {
mju_dist3(v[0], intersect),
mju_dist3(v[1], intersect),
mju_dist3(v[2], intersect)
};
if (vertid) {
if (dist[0] <= dist[1] && dist[0] <= dist[2]) {
*vertid = edata[vid[i][0]];
} else if (dist[1] <= dist[2]){
*vertid = edata[vid[i][1]];
} else {
*vertid = edata[vid[i][2]];
}
}
}
}
}
}
return x;
}
// intersect ray with skin, return nearest vertex id
mjtNum mju_raySkin(int nface, int nvert, const int* face, const float* vert,
const mjtNum pnt[3], const mjtNum vec[3], int vertid[1]) {
// compute bounding box
mjtNum box[3][2] = {{0, 0}, {0, 0}, {0, 0}};
for (int i=0; i < nvert; i++) {
for (int j=0; j < 3; j++) {
// update minimum along side j
if (box[j][0] > vert[3*i+j] || i == 0) {
box[j][0] = vert[3*i+j];
}
// update maximum along side j
if (box[j][1] < vert[3*i+j] || i == 0) {
box[j][1] = vert[3*i+j];
}
}
}
// construct box geom
mjtNum pos[3], size[3], mat[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
for (int j=0; j < 3; j++) {
pos[j] = 0.5*(box[j][0]+box[j][1]);
size[j] = 0.5*(box[j][1]-box[j][0]);
}
// apply bounding-box filter
if (ray_box(pos, mat, size, pnt, vec, NULL, NULL) < 0) {
return -1;
}
// construct basis vectors of normal plane
mjtNum b0[3] = {1, 1, 1}, b1[3];
if (mju_abs(vec[0]) >= 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;
// process all faces
for (int i=0; i < nface; i++) {
// get float vertices
const float* vf[3];
vf[0] = vert + 3*(face[3*i]);
vf[1] = vert + 3*(face[3*i+1]);
vf[2] = vert + 3*(face[3*i+2]);
// convert to mjtNum
mjtNum v[3][3];
for (int j=0; j < 3; j++) {
for (int k=0; k < 3; k++) {
v[j][k] = (mjtNum)vf[j][k];
}
}
// solve
mjtNum sol = ray_triangle(v, pnt, vec, b0, b1, NULL);
// update
if (sol >= 0 && (x < 0 || sol < x)) {
x = sol;
// construct intersection point
mjtNum intersect[3];
mju_addScl3(intersect, pnt, vec, sol);
// find nearest vertex
mjtNum dist = mju_dist3(intersect, v[0]);
if (vertid) *vertid = face[3*i];
for (int j=1; j < 3; j++) {
mjtNum newdist = mju_dist3(intersect, v[j]);
if (newdist < dist) {
dist = newdist;
if (vertid) *vertid = face[3*i+j];
}
}
}
}
return x;
}
// return 1 if point is inside object-aligned bounding box, 0 otherwise
static int point_in_box(const mjtNum aabb[6], const mjtNum xpos[3],
const mjtNum xmat[9], const mjtNum pnt[3]) {
mjtNum point[3];
// compute point in local coordinates of the box
mju_sub3(point, pnt, xpos);
mju_mulMatTVec3(point, xmat, point);
mju_subFrom3(point, aabb);
// check intersections
for (int j=0; j < 3; j++) { // directions
if (mju_abs(point[j]) > aabb[3+j]) {
return 0;
}
}
return 1;
}
//---------------------------- main entry point ----------------------------------------------------
// intersect ray (pnt+x*vec, x>=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[3], const mjtNum vec[3],
const mjtByte* geomgroup, mjtBool flg_static, int bodyexclude,
int geomid[1], mjtNum normal[3]) {
int ngeom = m->ngeom;
mjtNum dist, newdist;
mjtNum normal_local[3];
mjtNum* p_normal = normal ? normal_local : NULL;
// check vector length
if (mju_norm3(vec) < mjMINVAL) {
mjERROR("vector length is too small");
}
// clear result
dist = -1;
if (geomid) *geomid = -1;
if (normal) mju_zero3(normal);
// loop over geoms not eliminated by mask and bodyexclude
for (int i=0; i < ngeom; i++) {
if (!ray_eliminate(m, d, i, geomgroup, flg_static, bodyexclude)) {
int type = m->geom_type[i];
if (type == mjGEOM_MESH) {
newdist = mj_rayMesh(m, d, i, pnt, vec, p_normal);
} else if (type == mjGEOM_HFIELD) {
newdist = mj_rayHfield(m, d, i, pnt, vec, p_normal);
} else if (type == mjGEOM_SDF) {
newdist = mj_raySdf(m, d, i, pnt, vec, p_normal);
} else {
newdist = mju_rayGeom(d->geom_xpos+3*i, d->geom_xmat+9*i,
m->geom_size+3*i, pnt, vec, type, p_normal);
}
// update if closer intersection found
if (newdist >= 0 && (newdist < dist || dist < 0)) {
dist = newdist;
if (geomid) *geomid = i;
if (normal) mju_copy3(normal, normal_local);
}
}
}
return dist;
}
// Initializes spherical bounding angles (geom_ba) and flag vector for a given source
void mju_multiRayPrepare(const mjModel* m, const mjData* d, const mjtNum pnt[3],
const mjtNum ray_xmat[9], const mjtByte* geomgroup, mjtBool flg_static,
int bodyexclude, mjtNum cutoff, mjtNum* geom_ba, int* geom_eliminate) {
if (ray_xmat) {
mjERROR("ray_xmat is currently unused, should be NULL");
}
// compute eliminate flag for all geoms
for (int geomid=0; geomid < m->ngeom; geomid++)
geom_eliminate[geomid] = ray_eliminate(m, d, geomid, geomgroup, flg_static, bodyexclude);
for (int b=0; b < m->nbody; b++) {
// skip precomputation if no bounding volume is available
if (m->body_bvhadr[b] == -1) {
continue;
}
// loop over child geoms, compute bounding angles
for (int i=0; i < m->body_geomnum[b]; i++) {
int g = i + m->body_geomadr[b];
mjtNum AABB[4] = {mjMAXVAL, mjMAXVAL, -mjMAXVAL, -mjMAXVAL};
mjtNum* aabb = m->geom_aabb + 6*g;
mjtNum* xpos = d->geom_xpos + 3*g;
mjtNum* xmat = d->geom_xmat + 9*g;
// skip if eliminated by flags
if (geom_eliminate[g]) {
continue;
}
// add to geom_eliminate if distance of bounding sphere is above cutoff
if (mju_dist3(d->geom_xpos+3*g, pnt) > cutoff+m->geom_rbound[g]) {
geom_eliminate[g] = 1;
continue;
}
if (point_in_box(aabb, xpos, xmat, pnt)) {
(geom_ba+4*g)[0] = -mjPI;
(geom_ba+4*g)[1] = 0;
(geom_ba+4*g)[2] = mjPI;
(geom_ba+4*g)[3] = mjPI;
continue;
}
// loop over box vertices, compute spherical aperture
for (int v=0; v < 8; v++) {
mjtNum vert[3], box[3];
vert[0] = (v&1 ? aabb[0]+aabb[3] : aabb[0]-aabb[3]);
vert[1] = (v&2 ? aabb[1]+aabb[4] : aabb[1]-aabb[4]);
vert[2] = (v&4 ? aabb[2]+aabb[5] : aabb[2]-aabb[5]);
// rotate to the world frame
mju_mulMatVec3(box, xmat, vert);
mju_addTo3(box, xpos);
// spherical coordinates
mju_sub3(vert, box, pnt);
mjtNum azimuth = longitude(vert);
mjtNum elevation = latitude(vert);
// update bounds
AABB[0] = mju_min(AABB[0], azimuth);
AABB[1] = mju_min(AABB[1], elevation);
AABB[2] = mju_max(AABB[2], azimuth);
AABB[3] = mju_max(AABB[3], elevation);
}
// add distance-dependent angular margin to account for edge/face curvature
// margin = atan(max_half_size / dist) bounds the angular deviation of face centers
mjtNum max_half = mju_max(aabb[3], mju_max(aabb[4], aabb[5]));
mjtNum dist = mju_dist3(pnt, xpos);
if (dist > mjMINVAL) {
mjtNum margin = mju_atan2(max_half, dist);
AABB[0] -= margin;
AABB[1] -= margin;
AABB[2] += margin;
AABB[3] += margin;
}
// azimuth crosses discontinuity, fall back to no angular culling
if (AABB[2]-AABB[0] > mjPI) {
AABB[0] = -mjPI;
AABB[1] = 0;
AABB[2] = mjPI;
AABB[3] = mjPI;
}
// elevation overflow, fall back to no angular culling
if (AABB[3]-AABB[1] > mjPI) {
AABB[0] = -mjPI;
AABB[1] = 0;
AABB[2] = mjPI;
AABB[3] = mjPI;
}
mju_copy(geom_ba+4*g, AABB, 4);
}
}
}
// Performs single ray intersection, compute normal if given
static mjtNum mju_singleRay(const mjModel* m, mjData* d, const mjtNum pnt[3], const mjtNum vec[3],
int* ray_eliminate, mjtNum* geom_ba, int geomid[1],
mjtNum normal[3]) {
mjtNum dist, newdist;
mjtNum normal_local[3];
mjtNum* p_normal = normal ? normal_local : NULL;
// clear result
dist = -1;
if (geomid) *geomid = -1;
if (normal) mju_zero3(normal);
// get ray spherical coordinates
mjtNum azimuth = longitude(vec);
mjtNum elevation = latitude(vec);
// loop over bodies not eliminated by bodyexclude
for (int b=0; b < m->nbody; b++) {
// exclude body using bounding sphere test
if (m->body_bvhadr[b] != -1) {
mjtNum* pos = m->bvh_aabb + 6*m->body_bvhadr[b];
mjtNum center[3];
mjtNum* size = pos + 3;
mjtNum ssz = size[0]*size[0] + size[1]*size[1] + size[2]*size[2];
mju_add3(center, pos, d->xipos+3*b);
if (ray_sphere(center, NULL, ssz, pnt, vec, NULL) < 0) {
continue;
}
}
// loop over geoms if bounding sphere test fails
for (int g=0; g < m->body_geomnum[b]; g++) {
int i = m->body_geomadr[b] + g;
if (ray_eliminate[i]) {
continue;
}
// exclude geom using bounding angles
if (m->body_bvhadr[b] != -1) {
mjtNum az_min = (geom_ba+4*i)[0];
mjtNum az_max = (geom_ba+4*i)[2];
mjtNum el_min = (geom_ba+4*i)[1];
mjtNum el_max = (geom_ba+4*i)[3];
// check elevation
if (elevation < el_min || elevation > el_max) {
continue;
}
// check azimuth with wraparound
mjtNum az_center = (az_min + az_max) * 0.5;
mjtNum az_half_width = (az_max - az_min) * 0.5;
mjtNum az_diff = azimuth - az_center;
if (az_diff > mjPI) az_diff -= 2*mjPI;
else if (az_diff < -mjPI) az_diff += 2*mjPI;
if (mju_abs(az_diff) > az_half_width) {
continue;
}
}
// dispatch to type-specific ray function
int type = m->geom_type[i];
if (type == mjGEOM_MESH) {
newdist = mj_rayMesh(m, d, i, pnt, vec, p_normal);
} else if (type == mjGEOM_HFIELD) {
newdist = mj_rayHfield(m, d, i, pnt, vec, p_normal);
} else if (type == mjGEOM_SDF) {
newdist = mj_raySdf(m, d, i, pnt, vec, p_normal);
} else {
newdist = mju_rayGeom(d->geom_xpos+3*i, d->geom_xmat+9*i,
m->geom_size+3*i, pnt, vec, type, p_normal);
}
// update if closer intersection found
if (newdist >= 0 && (newdist < dist || dist < 0)) {
dist = newdist;
if (geomid) *geomid = i;
if (normal) mju_copy3(normal, normal_local);
}
}
}
return dist;
}
// performs multiple ray intersections, compute normals if given
void mj_multiRay(const mjModel* m, mjData* d, const mjtNum pnt[3], const mjtNum* vec,
const mjtByte* geomgroup, mjtBool flg_static, int bodyexclude,
int* geomid, mjtNum* dist, mjtNum* normal, int nray, mjtNum cutoff) {
mj_markStack(d);
// allocate source
mjtNum* geom_ba = mjSTACKALLOC(d, 4*m->ngeom, mjtNum);
int* geom_eliminate = mjSTACKALLOC(d, m->ngeom, int);
// initialize source
mju_multiRayPrepare(m, d, pnt, NULL, geomgroup, flg_static, bodyexclude,
cutoff, geom_ba, geom_eliminate);
// loop over rays
for (int i=0; i < nray; i++) {
if (mju_dot3(vec+3*i, vec+3*i) < mjMINVAL) {
dist[i] = -1;
} else {
int* p_geomid = geomid ? geomid + i : NULL;
dist[i] = mju_singleRay(m, d, pnt, vec+3*i, geom_eliminate, geom_ba, p_geomid,
normal ? normal+3*i : NULL);
}
}
mj_freeStack(d);
}