pex-geom
Version:
Geometry intersection and bounding volume helpers for PEX.
198 lines (161 loc) • 5.07 kB
JavaScript
/** @module ray */
import { vec3 } from "pex-math";
/**
* Enum for different intersections values
* @readonly
* @enum {number}
*/
export const Intersections = Object.freeze({
Intersect: 1,
NoIntersect: 0,
SamePlane: -1,
Parallel: -2,
TriangleDegenerate: -2,
});
const TEMP_0 = vec3.create();
const TEMP_1 = vec3.create();
const TEMP_2 = vec3.create();
const TEMP_3 = vec3.create();
const TEMP_4 = vec3.create();
const TEMP_5 = vec3.create();
const TEMP_6 = vec3.create();
const TEMP_7 = vec3.create();
const EPSILON = 1e-6;
/**
* Creates a new ray
* @returns {import("./types.js").ray}
*/
export function create() {
return [
[0, 0, 0],
[0, 0, 1],
];
}
/**
* Determines if a ray intersect a plane and set intersection point
* @see {@link https://www.cs.princeton.edu/courses/archive/fall00/cs426/lectures/raycast/sld017.htm}
* @param {import("./types.js").ray} ray
* @param {import("./types.js").plane} plane
* @param {import("./types.js").vec3} out
* @returns {number}
*/
export function hitTestPlane(
[origin, direction],
[point, normal],
out = vec3.create(),
) {
vec3.set(TEMP_0, origin);
vec3.set(TEMP_1, direction);
const dotDirectionNormal = vec3.dot(TEMP_1, normal);
if (dotDirectionNormal === 0) return Intersections.SamePlane;
vec3.set(TEMP_2, point);
const t = vec3.dot(vec3.sub(TEMP_2, TEMP_0), normal) / dotDirectionNormal;
if (t < 0) return Intersections.Parallel;
vec3.set(out, vec3.add(TEMP_0, vec3.scale(TEMP_1, t)));
return Intersections.Intersect;
}
/**
* Determines if a ray intersect a triangle and set intersection point
* @see {@link http://geomalgorithms.com/a06-_intersect-2.html#intersect3D_RayTriangle()}
* @param {import("./types.js").ray} ray
* @param {import("./types.js").triangle} triangle
* @param {import("./types.js").vec3} out
* @returns {number}
*/
export function hitTestTriangle(
[origin, direction],
[p0, p1, p2],
out = vec3.create(),
) {
// get triangle edge vectors and plane normal
const u = vec3.sub(vec3.set(TEMP_0, p1), p0);
const v = vec3.sub(vec3.set(TEMP_1, p2), p0);
const n = vec3.cross(vec3.set(TEMP_2, u), v);
if (vec3.length(n) < EPSILON) return Intersections.TriangleDegenerate;
// ray vectors
const w0 = vec3.sub(vec3.set(TEMP_3, origin), p0);
// params to calc ray-plane intersect
const a = -vec3.dot(n, w0);
const b = vec3.dot(n, direction);
if (Math.abs(b) < EPSILON) {
if (a === 0) return Intersections.SamePlane;
return Intersections.NoIntersect;
}
// get intersect point of ray with triangle plane
const r = a / b;
// ray goes away from triangle
if (r < -EPSILON) return Intersections.NoIntersect;
// for a segment, also test if (r > 1.0) => no intersect
// intersect point of ray and plane
const I = vec3.add(
vec3.set(TEMP_4, origin),
vec3.scale(vec3.set(TEMP_5, direction), r),
);
const uu = vec3.dot(u, u);
const uv = vec3.dot(u, v);
const vv = vec3.dot(v, v);
const w = vec3.sub(vec3.set(TEMP_6, I), p0);
const wu = vec3.dot(w, u);
const wv = vec3.dot(w, v);
const D = uv * uv - uu * vv;
// get and test parametric coords
const s = (uv * wv - vv * wu) / D;
if (s < -EPSILON || s > 1.0 + EPSILON) return Intersections.NoIntersect;
const t = (uv * wu - uu * wv) / D;
if (t < -EPSILON || s + t > 1.0 + EPSILON) return Intersections.NoIntersect;
vec3.set(out, u);
vec3.scale(out, s);
vec3.add(out, vec3.scale(vec3.set(TEMP_7, v), t));
vec3.add(out, p0);
return Intersections.Intersect;
}
/**
* Determines if a ray intersect an AABB bounding box
* @see {@link http://gamedev.stackexchange.com/questions/18436/most-efficient-aabb-vs-ray-collision-algorithms}
* @param {import("./types.js").ray} ray
* @param {import("./types.js").aabb} aabb
* @returns {boolean}
*/
export function hitTestAABB([origin, direction], aabb) {
const dirFracx = 1.0 / direction[0];
const dirFracy = 1.0 / direction[1];
const dirFracz = 1.0 / direction[2];
const min = aabb[0];
const max = aabb[1];
const minx = min[0];
const miny = min[1];
const minz = min[2];
const maxx = max[0];
const maxy = max[1];
const maxz = max[2];
const t1 = (minx - origin[0]) * dirFracx;
const t2 = (maxx - origin[0]) * dirFracx;
const t3 = (miny - origin[1]) * dirFracy;
const t4 = (maxy - origin[1]) * dirFracy;
const t5 = (minz - origin[2]) * dirFracz;
const t6 = (maxz - origin[2]) * dirFracz;
const tmin = Math.max(
Math.max(Math.min(t1, t2), Math.min(t3, t4)),
Math.min(t5, t6),
);
const tmax = Math.min(
Math.min(Math.max(t1, t2), Math.max(t3, t4)),
Math.max(t5, t6),
);
return !(tmax < 0 || tmin > tmax);
}
/**
* Alias for {@link hitTestAABB}
* @function
*/
export const intersectsAABB = hitTestAABB;
/**
* Prints a plane to a string.
* @param {import("./types.js").ray} a
* @param {number} [precision=4]
* @returns {string}
*/
export function toString(a, precision = 4) {
// prettier-ignore
return `[${vec3.toString(a[0], precision)}, ${vec3.toString(a[1], precision)}]`;
}