Files
cocos-engine/cocos/core/geometry/intersect.ts
pandamicro 8ef4d3c4a9 Fix API docs for 3d module and part of core module (#11126)
* Refactor morph and morph-rendering module

* Fix API docs for 3d module

* Fix some API docs for core module

* Tweaks and fixes of links
2022-05-19 14:19:05 +08:00

1480 lines
51 KiB
TypeScript

/*
Copyright (c) 2020 Xiamen Yaji Software Co., Ltd.
https://www.cocos.com/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated engine source code (the "Software"), a limited,
worldwide, royalty-free, non-assignable, revocable and non-exclusive license
to use Cocos Creator solely to develop games on your target platforms. You shall
not use Cocos Creator software for developing other software or tools that's
used for developing games. You are not granted to publish, distribute,
sublicense, and/or sell copies of Cocos Creator.
The software or tools in this License Agreement are licensed, not sold.
Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { EPSILON, Mat3, Vec3, Mat4 } from '../math';
import { AABB } from './aabb';
import { Capsule } from './capsule';
import * as distance from './distance';
import enums from './enums';
import { Frustum } from './frustum';
import { Line } from './line';
import { OBB } from './obb';
import { Plane } from './plane';
import { Ray } from './ray';
import { Sphere } from './sphere';
import { Triangle } from './triangle';
import { PrimitiveMode } from '../gfx';
import { Mesh } from '../../3d/assets/mesh';
import { IBArray, RenderingSubMesh } from '../assets/rendering-sub-mesh';
import { IRaySubMeshOptions, ERaycastMode, IRaySubMeshResult, IRayMeshOptions, IRayModelOptions } from './spec';
import { IVec3Like } from '../math/type-define';
import { scene } from '../renderer';
/**
* @en
* ray-plane intersect detect.
* @zh
* 射线与平面的相交性检测。
* @param {Ray} ray 射线
* @param {Plane} plane 平面
* @return {number} 0 或 非0
*/
const rayPlane = (function () {
const pt = new Vec3(0, 0, 0);
return function (ray: Ray, plane: Plane): number {
const denom = Vec3.dot(ray.d, plane.n);
if (Math.abs(denom) < Number.EPSILON) { return 0; }
Vec3.multiplyScalar(pt, plane.n, plane.d);
const t = Vec3.dot(Vec3.subtract(pt, pt, ray.o), plane.n) / denom;
if (t < 0) { return 0; }
return t;
};
}());
// based on http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/raytri/
/**
* @en
* ray-triangle intersect detect.
* @zh
* 射线与三角形的相交性检测。
* @param {Ray} ray 射线
* @param {Triangle} triangle 三角形
* @param {boolean} doubleSided 三角形是否为双面
* @return {number} 0 或 非0
*/
const rayTriangle = (function () {
const ab = new Vec3(0, 0, 0);
const ac = new Vec3(0, 0, 0);
const pvec = new Vec3(0, 0, 0);
const tvec = new Vec3(0, 0, 0);
const qvec = new Vec3(0, 0, 0);
return function (ray: Ray, triangle: Triangle, doubleSided?: boolean) {
Vec3.subtract(ab, triangle.b, triangle.a);
Vec3.subtract(ac, triangle.c, triangle.a);
Vec3.cross(pvec, ray.d, ac);
const det = Vec3.dot(ab, pvec);
if (det < Number.EPSILON && (!doubleSided || det > -Number.EPSILON)) { return 0; }
const inv_det = 1 / det;
Vec3.subtract(tvec, ray.o, triangle.a);
const u = Vec3.dot(tvec, pvec) * inv_det;
if (u < 0 || u > 1) { return 0; }
Vec3.cross(qvec, tvec, ab);
const v = Vec3.dot(ray.d, qvec) * inv_det;
if (v < 0 || u + v > 1) { return 0; }
const t = Vec3.dot(ac, qvec) * inv_det;
return t < 0 ? 0 : t;
};
}());
/**
* @en
* ray-sphere intersect detect.
* @zh
* 射线和球的相交性检测。
* @param {Ray} ray 射线
* @param {Sphere} sphere 球
* @return {number} 0 或 非0
*/
const raySphere = (function () {
const e = new Vec3(0, 0, 0);
return function (ray: Ray, sphere: Sphere): number {
const r = sphere.radius;
const c = sphere.center;
const o = ray.o;
const d = ray.d;
const rSq = r * r;
Vec3.subtract(e, c, o);
const eSq = e.lengthSqr();
const aLength = Vec3.dot(e, d); // assume ray direction already normalized
const fSq = rSq - (eSq - aLength * aLength);
if (fSq < 0) { return 0; }
const f = Math.sqrt(fSq);
const t = eSq < rSq ? aLength + f : aLength - f;
if (t < 0) { return 0; }
return t;
};
}());
/**
* @en
* ray-aabb intersect detect.
* @zh
* 射线和轴对齐包围盒的相交性检测。
* @param {Ray} ray 射线
* @param {AABB} aabb 轴对齐包围盒
* @return {number} 0 或 非0
*/
const rayAABB = (function () {
const min = new Vec3();
const max = new Vec3();
return function (ray: Ray, aabb: AABB): number {
Vec3.subtract(min, aabb.center, aabb.halfExtents);
Vec3.add(max, aabb.center, aabb.halfExtents);
return rayAABB2(ray, min, max);
};
}());
function rayAABB2 (ray: Ray, min: IVec3Like, max: IVec3Like) {
const o = ray.o; const d = ray.d;
const ix = 1 / d.x; const iy = 1 / d.y; const iz = 1 / d.z;
const t1 = (min.x - o.x) * ix;
const t2 = (max.x - o.x) * ix;
const t3 = (min.y - o.y) * iy;
const t4 = (max.y - o.y) * iy;
const t5 = (min.z - o.z) * iz;
const t6 = (max.z - o.z) * iz;
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));
if (tmax < 0 || tmin > tmax) { return 0; }
return tmin > 0 ? tmin : tmax; // ray origin inside aabb
}
/**
* @en
* ray-obb intersect detect.
* @zh
* 射线和方向包围盒的相交性检测。
* @param {Ray} ray 射线
* @param {OBB} obb 方向包围盒
* @return {number} 0 或 非0
*/
const rayOBB = (function () {
let center = new Vec3();
let o = new Vec3();
let d = new Vec3();
const X = new Vec3();
const Y = new Vec3();
const Z = new Vec3();
const p = new Vec3();
const size = new Array<number>(3);
const f = new Array<number>(3);
const e = new Array<number>(3);
const t = new Array<number>(6);
return function (ray: Ray, obb: OBB): number {
size[0] = obb.halfExtents.x;
size[1] = obb.halfExtents.y;
size[2] = obb.halfExtents.z;
center = obb.center;
o = ray.o;
d = ray.d;
Vec3.set(X, obb.orientation.m00, obb.orientation.m01, obb.orientation.m02);
Vec3.set(Y, obb.orientation.m03, obb.orientation.m04, obb.orientation.m05);
Vec3.set(Z, obb.orientation.m06, obb.orientation.m07, obb.orientation.m08);
Vec3.subtract(p, center, o);
// The cos values of the ray on the X, Y, Z
f[0] = Vec3.dot(X, d);
f[1] = Vec3.dot(Y, d);
f[2] = Vec3.dot(Z, d);
// The projection length of P on X, Y, Z
e[0] = Vec3.dot(X, p);
e[1] = Vec3.dot(Y, p);
e[2] = Vec3.dot(Z, p);
for (let i = 0; i < 3; ++i) {
if (f[i] === 0) {
if (-e[i] - size[i] > 0 || -e[i] + size[i] < 0) {
return 0;
}
// Avoid div by 0!
f[i] = 0.0000001;
}
// min
t[i * 2 + 0] = (e[i] + size[i]) / f[i];
// max
t[i * 2 + 1] = (e[i] - size[i]) / f[i];
}
const tmin = Math.max(
Math.max(
Math.min(t[0], t[1]),
Math.min(t[2], t[3]),
),
Math.min(t[4], t[5]),
);
const tmax = Math.min(
Math.min(
Math.max(t[0], t[1]),
Math.max(t[2], t[3]),
),
Math.max(t[4], t[5]),
);
if (tmax < 0 || tmin > tmax) {
return 0;
}
return tmin > 0 ? tmin : tmax; // ray origin inside aabb
};
}());
/**
* @en
* ray-capsule intersect detect.
* @zh
* 射线和胶囊体的相交性检测。
* @param {Ray} ray 射线
* @param {Capsule} capsule 胶囊体
* @return {number} 0 或 非0
*/
const rayCapsule = (function () {
const v3_0 = new Vec3();
const v3_1 = new Vec3();
const v3_2 = new Vec3();
const v3_3 = new Vec3();
const v3_4 = new Vec3();
const v3_5 = new Vec3();
const v3_6 = new Vec3();
const sphere_0 = new Sphere();
return function (ray: Ray, capsule: Capsule) {
const radiusSqr = capsule.radius * capsule.radius;
const vRayNorm = Vec3.normalize(v3_0, ray.d);
const A = capsule.ellipseCenter0;
const B = capsule.ellipseCenter1;
const BA = Vec3.subtract(v3_1, B, A);
if (BA.equals(Vec3.ZERO)) {
sphere_0.radius = capsule.radius;
sphere_0.center.set(capsule.ellipseCenter0);
return intersect.raySphere(ray, sphere_0);
}
const O = ray.o;
const OA = Vec3.subtract(v3_2, O, A);
const VxBA = Vec3.cross(v3_3, vRayNorm, BA);
const a = VxBA.lengthSqr();
if (a === 0) {
sphere_0.radius = capsule.radius;
const BO = Vec3.subtract(v3_4, B, O);
if (OA.lengthSqr() < BO.lengthSqr()) {
sphere_0.center.set(capsule.ellipseCenter0);
} else {
sphere_0.center.set(capsule.ellipseCenter1);
}
return intersect.raySphere(ray, sphere_0);
}
const OAxBA = Vec3.cross(v3_4, OA, BA);
const ab2 = BA.lengthSqr();
const b = 2 * Vec3.dot(VxBA, OAxBA);
const c = OAxBA.lengthSqr() - (radiusSqr * ab2);
const d = b * b - 4 * a * c;
if (d < 0) { return 0; }
const t = (-b - Math.sqrt(d)) / (2 * a);
if (t < 0) {
sphere_0.radius = capsule.radius;
const BO = Vec3.subtract(v3_5, B, O);
if (OA.lengthSqr() < BO.lengthSqr()) {
sphere_0.center.set(capsule.ellipseCenter0);
} else {
sphere_0.center.set(capsule.ellipseCenter1);
}
return intersect.raySphere(ray, sphere_0);
} else {
// Limit intersection between the bounds of the cylinder's end caps.
const iPos = Vec3.scaleAndAdd(v3_5, ray.o, vRayNorm, t);
const iPosLen = Vec3.subtract(v3_6, iPos, A);
const tLimit = Vec3.dot(iPosLen, BA) / ab2;
if (tLimit >= 0 && tLimit <= 1) {
return t;
} else if (tLimit < 0) {
sphere_0.radius = capsule.radius;
sphere_0.center.set(capsule.ellipseCenter0);
return intersect.raySphere(ray, sphere_0);
} else if (tLimit > 1) {
sphere_0.radius = capsule.radius;
sphere_0.center.set(capsule.ellipseCenter1);
return intersect.raySphere(ray, sphere_0);
} else {
return 0;
}
}
};
}());
/**
* @en
* ray-subMesh intersect detect, in model space.
* @zh
* 在模型空间中,射线和子三角网格的相交性检测。
* @param {Ray} ray
* @param {RenderingSubMesh} subMesh
* @param {IRaySubMeshOptions} options
* @return {number} 0 or !0
*/
const raySubMesh = (function () {
const tri = Triangle.create();
const deOpt: IRaySubMeshOptions = { distance: Infinity, doubleSided: false, mode: ERaycastMode.ANY };
let minDis = 0;
const fillResult = (m: ERaycastMode, d: number, i0: number, i1: number, i2: number, r?: IRaySubMeshResult[]) => {
if (m === ERaycastMode.CLOSEST) {
if (minDis > d || minDis === 0) {
minDis = d;
if (r) {
if (r.length === 0) {
r.push({ distance: d, vertexIndex0: i0 / 3, vertexIndex1: i1 / 3, vertexIndex2: i2 / 3 });
} else {
r[0].distance = d; r[0].vertexIndex0 = i0 / 3; r[0].vertexIndex1 = i1 / 3; r[0].vertexIndex2 = i2 / 3;
}
}
}
} else {
minDis = d;
if (r) r.push({ distance: d, vertexIndex0: i0 / 3, vertexIndex1: i1 / 3, vertexIndex2: i2 / 3 });
}
};
const narrowphase = (vb: Float32Array, ib: IBArray, pm: PrimitiveMode, ray: Ray, opt: IRaySubMeshOptions) => {
if (pm === PrimitiveMode.TRIANGLE_LIST) {
const cnt = ib.length;
for (let j = 0; j < cnt; j += 3) {
const i0 = ib[j] * 3;
const i1 = ib[j + 1] * 3;
const i2 = ib[j + 2] * 3;
Vec3.set(tri.a, vb[i0], vb[i0 + 1], vb[i0 + 2]);
Vec3.set(tri.b, vb[i1], vb[i1 + 1], vb[i1 + 2]);
Vec3.set(tri.c, vb[i2], vb[i2 + 1], vb[i2 + 2]);
const dist = intersect.rayTriangle(ray, tri, opt.doubleSided);
if (dist === 0 || dist > opt.distance) continue;
fillResult(opt.mode, dist, i0, i1, i2, opt.result);
if (opt.mode === ERaycastMode.ANY) return dist;
}
} else if (pm === PrimitiveMode.TRIANGLE_STRIP) {
const cnt = ib.length - 2;
let rev = 0;
for (let j = 0; j < cnt; j += 1) {
const i0 = ib[j - rev] * 3;
const i1 = ib[j + rev + 1] * 3;
const i2 = ib[j + 2] * 3;
Vec3.set(tri.a, vb[i0], vb[i0 + 1], vb[i0 + 2]);
Vec3.set(tri.b, vb[i1], vb[i1 + 1], vb[i1 + 2]);
Vec3.set(tri.c, vb[i2], vb[i2 + 1], vb[i2 + 2]);
rev = ~rev;
const dist = intersect.rayTriangle(ray, tri, opt.doubleSided);
if (dist === 0 || dist > opt.distance) continue;
fillResult(opt.mode, dist, i0, i1, i2, opt.result);
if (opt.mode === ERaycastMode.ANY) return dist;
}
} else if (pm === PrimitiveMode.TRIANGLE_FAN) {
const cnt = ib.length - 1;
const i0 = ib[0] * 3;
Vec3.set(tri.a, vb[i0], vb[i0 + 1], vb[i0 + 2]);
for (let j = 1; j < cnt; j += 1) {
const i1 = ib[j] * 3;
const i2 = ib[j + 1] * 3;
Vec3.set(tri.b, vb[i1], vb[i1 + 1], vb[i1 + 2]);
Vec3.set(tri.c, vb[i2], vb[i2 + 1], vb[i2 + 2]);
const dist = intersect.rayTriangle(ray, tri, opt.doubleSided);
if (dist === 0 || dist > opt.distance) continue;
fillResult(opt.mode, dist, i0, i1, i2, opt.result);
if (opt.mode === ERaycastMode.ANY) return dist;
}
}
return minDis;
};
return function (ray: Ray, submesh: RenderingSubMesh, options?: IRaySubMeshOptions) {
minDis = 0;
if (submesh.geometricInfo.positions.length === 0) return minDis;
const opt = options === undefined ? deOpt : options;
const min = submesh.geometricInfo.boundingBox.min;
const max = submesh.geometricInfo.boundingBox.max;
if (rayAABB2(ray, min, max)) {
const pm = submesh.primitiveMode;
const { positions: vb, indices: ib } = submesh.geometricInfo;
narrowphase(vb, ib!, pm, ray, opt);
}
return minDis;
};
}());
/**
* @en
* ray-mesh intersect detect, in model space.
* @zh
* 在模型空间中,射线和三角网格资源的相交性检测。
* @param {Ray} ray
* @param {Mesh} mesh
* @param {IRayMeshOptions} options
* @return {number} 0 or !0
*/
const rayMesh = (function () {
let minDis = 0;
const deOpt: IRayMeshOptions = { distance: Infinity, doubleSided: false, mode: ERaycastMode.ANY };
return function (ray: Ray, mesh: Mesh, options?: IRayMeshOptions) {
minDis = 0;
const opt = options === undefined ? deOpt : options;
const length = mesh.renderingSubMeshes.length;
const min = mesh.struct.minPosition;
const max = mesh.struct.maxPosition;
if (min && max && !rayAABB2(ray, min, max)) return minDis;
for (let i = 0; i < length; i++) {
const sm = mesh.renderingSubMeshes[i];
const dis = raySubMesh(ray, sm, opt);
if (dis) {
if (opt.mode === ERaycastMode.CLOSEST) {
if (minDis === 0 || minDis > dis) {
minDis = dis;
if (opt.subIndices) opt.subIndices[0] = i;
}
} else {
minDis = dis;
if (opt.subIndices) opt.subIndices.push(i);
if (opt.mode === ERaycastMode.ANY) {
return dis;
}
}
}
}
if (minDis && opt.mode === ERaycastMode.CLOSEST) {
if (opt.result) {
opt.result[0].distance = minDis;
opt.result.length = 1;
}
if (opt.subIndices) opt.subIndices.length = 1;
}
return minDis;
};
}());
/**
* @en
* ray-model intersect detect, in world space.
* @zh
* 在世界空间中,射线和渲染模型的相交性检测。
* @param ray
* @param model
* @param options
* @return 0 or !0
*/
const rayModel = (function () {
let minDis = 0;
const deOpt: IRayModelOptions = { distance: Infinity, doubleSided: false, mode: ERaycastMode.ANY };
const modelRay = new Ray();
const m4 = new Mat4();
return function (r: Ray, model: scene.Model, options?: IRayModelOptions) {
minDis = 0;
const opt = options === undefined ? deOpt : options;
const wb = model.worldBounds;
if (wb && !rayAABB(r, wb)) return minDis;
Ray.copy(modelRay, r);
if (model.node) {
Mat4.invert(m4, model.node.getWorldMatrix(m4));
Vec3.transformMat4(modelRay.o, r.o, m4);
Vec3.transformMat4Normal(modelRay.d, r.d, m4);
}
const subModels = model.subModels;
for (let i = 0; i < subModels.length; i++) {
const subMesh = subModels[i].subMesh;
const dis = raySubMesh(modelRay, subMesh, opt);
if (dis) {
if (opt.mode === ERaycastMode.CLOSEST) {
if (minDis === 0 || minDis > dis) {
minDis = dis;
if (opt.subIndices) opt.subIndices[0] = i;
}
} else {
minDis = dis;
if (opt.subIndices) opt.subIndices.push(i);
if (opt.mode === ERaycastMode.ANY) {
return dis;
}
}
}
}
if (minDis && opt.mode === ERaycastMode.CLOSEST) {
if (opt.result) {
opt.result[0].distance = minDis;
opt.result.length = 1;
}
if (opt.subIndices) opt.subIndices.length = 1;
}
return minDis;
};
}());
/**
* @en
* line-plane intersect detect.
* @zh
* 线段与平面的相交性检测。
* @param {line} line 线段
* @param {Plane} plane 平面
* @return {number} 0 或 非0
*/
const linePlane = (function () {
const ab = new Vec3(0, 0, 0);
return function (line: Line, plane: Plane): number {
Vec3.subtract(ab, line.e, line.s);
const t = (plane.d - Vec3.dot(line.s, plane.n)) / Vec3.dot(ab, plane.n);
if (t < 0 || t > 1) { return 0; }
return t;
};
}());
/**
* @en
* line-triangle intersect detect.
* @zh
* 线段与三角形的相交性检测。
* @param {line} line 线段
* @param {Triangle} triangle 三角形
* @param {Vec3} outPt 可选,相交点
* @return {number} 0 或 非0
*/
const lineTriangle = (function () {
const ab = new Vec3(0, 0, 0);
const ac = new Vec3(0, 0, 0);
const qp = new Vec3(0, 0, 0);
const ap = new Vec3(0, 0, 0);
const n = new Vec3(0, 0, 0);
const e = new Vec3(0, 0, 0);
return function (line: Line, triangle: Triangle, outPt?: Vec3): number {
Vec3.subtract(ab, triangle.b, triangle.a);
Vec3.subtract(ac, triangle.c, triangle.a);
Vec3.subtract(qp, line.s, line.e);
Vec3.cross(n, ab, ac);
const det = Vec3.dot(qp, n);
if (det <= 0.0) {
return 0;
}
Vec3.subtract(ap, line.s, triangle.a);
const t = Vec3.dot(ap, n);
if (t < 0 || t > det) {
return 0;
}
Vec3.cross(e, qp, ap);
let v = Vec3.dot(ac, e);
if (v < 0 || v > det) {
return 0;
}
let w = -Vec3.dot(ab, e);
if (w < 0.0 || v + w > det) {
return 0;
}
if (outPt) {
const invDet = 1.0 / det;
v *= invDet;
w *= invDet;
const u = 1.0 - v - w;
// outPt = u*a + v*d + w*c;
Vec3.set(outPt,
triangle.a.x * u + triangle.b.x * v + triangle.c.x * w,
triangle.a.y * u + triangle.b.y * v + triangle.c.y * w,
triangle.a.z * u + triangle.b.z * v + triangle.c.z * w);
}
return 1;
};
}());
const r_t = new Ray();
/**
* @en
* line-aabb intersect detect.
* @zh
* 线段与轴对齐包围盒的相交性检测
* @param line 线段
* @param aabb 轴对齐包围盒
* @return {number} 0 或 非0
*/
function lineAABB (line: Line, aabb: AABB): number {
r_t.o.set(line.s);
Vec3.subtract(r_t.d, line.e, line.s);
r_t.d.normalize();
const min = rayAABB(r_t, aabb);
const len = line.length();
if (min <= len) {
return min;
} else {
return 0;
}
}
/**
* @en
* line-obb intersect detect.
* @zh
* 线段与方向包围盒的相交性检测
* @param line 线段
* @param obb 方向包围盒
* @return {number} 0 或 非0
*/
function lineOBB (line: Line, obb: OBB): number {
r_t.o.set(line.s);
Vec3.subtract(r_t.d, line.e, line.s);
r_t.d.normalize();
const min = rayOBB(r_t, obb);
const len = line.length();
if (min <= len) {
return min;
} else {
return 0;
}
}
/**
* @en
* line-sphere intersect detect.
* @zh
* 线段与球的相交性检测
* @param line 线段
* @param sphere 球
* @return {number} 0 或 非0
*/
function lineSphere (line: Line, sphere: Sphere): number {
r_t.o.set(line.s);
Vec3.subtract(r_t.d, line.e, line.s);
r_t.d.normalize();
const min = raySphere(r_t, sphere);
const len = line.length();
if (min <= len) {
return min;
} else {
return 0;
}
}
/**
* @en
* aabb-aabb intersect detect.
* @zh
* 轴对齐包围盒和轴对齐包围盒的相交性检测。
* @param {AABB} aabb1 轴对齐包围盒1
* @param {AABB} aabb2 轴对齐包围盒2
* @return {number} 0 或 非0
*/
const aabbWithAABB = (function () {
const aMin = new Vec3();
const aMax = new Vec3();
const bMin = new Vec3();
const bMax = new Vec3();
return function (aabb1: AABB, aabb2: AABB) {
Vec3.subtract(aMin, aabb1.center, aabb1.halfExtents);
Vec3.add(aMax, aabb1.center, aabb1.halfExtents);
Vec3.subtract(bMin, aabb2.center, aabb2.halfExtents);
Vec3.add(bMax, aabb2.center, aabb2.halfExtents);
return (aMin.x <= bMax.x && aMax.x >= bMin.x)
&& (aMin.y <= bMax.y && aMax.y >= bMin.y)
&& (aMin.z <= bMax.z && aMax.z >= bMin.z);
};
}());
function getAABBVertices (min: Vec3, max: Vec3, out: Vec3[]) {
Vec3.set(out[0], min.x, max.y, max.z);
Vec3.set(out[1], min.x, max.y, min.z);
Vec3.set(out[2], min.x, min.y, max.z);
Vec3.set(out[3], min.x, min.y, min.z);
Vec3.set(out[4], max.x, max.y, max.z);
Vec3.set(out[5], max.x, max.y, min.z);
Vec3.set(out[6], max.x, min.y, max.z);
Vec3.set(out[7], max.x, min.y, min.z);
}
function getOBBVertices (c: Vec3, e: Vec3, a1: Vec3, a2: Vec3, a3: Vec3, out: Vec3[]) {
Vec3.set(out[0],
c.x + a1.x * e.x + a2.x * e.y + a3.x * e.z,
c.y + a1.y * e.x + a2.y * e.y + a3.y * e.z,
c.z + a1.z * e.x + a2.z * e.y + a3.z * e.z);
Vec3.set(out[1],
c.x - a1.x * e.x + a2.x * e.y + a3.x * e.z,
c.y - a1.y * e.x + a2.y * e.y + a3.y * e.z,
c.z - a1.z * e.x + a2.z * e.y + a3.z * e.z);
Vec3.set(out[2],
c.x + a1.x * e.x - a2.x * e.y + a3.x * e.z,
c.y + a1.y * e.x - a2.y * e.y + a3.y * e.z,
c.z + a1.z * e.x - a2.z * e.y + a3.z * e.z);
Vec3.set(out[3],
c.x + a1.x * e.x + a2.x * e.y - a3.x * e.z,
c.y + a1.y * e.x + a2.y * e.y - a3.y * e.z,
c.z + a1.z * e.x + a2.z * e.y - a3.z * e.z);
Vec3.set(out[4],
c.x - a1.x * e.x - a2.x * e.y - a3.x * e.z,
c.y - a1.y * e.x - a2.y * e.y - a3.y * e.z,
c.z - a1.z * e.x - a2.z * e.y - a3.z * e.z);
Vec3.set(out[5],
c.x + a1.x * e.x - a2.x * e.y - a3.x * e.z,
c.y + a1.y * e.x - a2.y * e.y - a3.y * e.z,
c.z + a1.z * e.x - a2.z * e.y - a3.z * e.z);
Vec3.set(out[6],
c.x - a1.x * e.x + a2.x * e.y - a3.x * e.z,
c.y - a1.y * e.x + a2.y * e.y - a3.y * e.z,
c.z - a1.z * e.x + a2.z * e.y - a3.z * e.z);
Vec3.set(out[7],
c.x - a1.x * e.x - a2.x * e.y + a3.x * e.z,
c.y - a1.y * e.x - a2.y * e.y + a3.y * e.z,
c.z - a1.z * e.x - a2.z * e.y + a3.z * e.z);
}
function getInterval (vertices: any[] | Vec3[], axis: Vec3) {
let min = Vec3.dot(axis, vertices[0]); let max = min;
for (let i = 1; i < 8; ++i) {
const projection = Vec3.dot(axis, vertices[i]);
min = (projection < min) ? projection : min;
max = (projection > max) ? projection : max;
}
return [min, max];
}
/**
* @en
* aabb-obb intersect detect.
* @zh
* 轴对齐包围盒和方向包围盒的相交性检测。
* @param {AABB} aabb 轴对齐包围盒
* @param {OBB} obb 方向包围盒
* @return {number} 0 或 非0
*/
const aabbWithOBB = (function () {
const test = new Array(15);
for (let i = 0; i < 15; i++) {
test[i] = new Vec3(0, 0, 0);
}
const vertices = new Array(8);
const vertices2 = new Array(8);
for (let i = 0; i < 8; i++) {
vertices[i] = new Vec3(0, 0, 0);
vertices2[i] = new Vec3(0, 0, 0);
}
const min = new Vec3();
const max = new Vec3();
return function (aabb: AABB, obb: OBB): number {
Vec3.set(test[0], 1, 0, 0);
Vec3.set(test[1], 0, 1, 0);
Vec3.set(test[2], 0, 0, 1);
Vec3.set(test[3], obb.orientation.m00, obb.orientation.m01, obb.orientation.m02);
Vec3.set(test[4], obb.orientation.m03, obb.orientation.m04, obb.orientation.m05);
Vec3.set(test[5], obb.orientation.m06, obb.orientation.m07, obb.orientation.m08);
for (let i = 0; i < 3; ++i) { // Fill out rest of axis
Vec3.cross(test[6 + i * 3 + 0], test[i], test[3]);
Vec3.cross(test[6 + i * 3 + 1], test[i], test[4]);
Vec3.cross(test[6 + i * 3 + 1], test[i], test[5]);
}
Vec3.subtract(min, aabb.center, aabb.halfExtents);
Vec3.add(max, aabb.center, aabb.halfExtents);
getAABBVertices(min, max, vertices);
getOBBVertices(obb.center, obb.halfExtents, test[3], test[4], test[5], vertices2);
for (let j = 0; j < 15; ++j) {
const a = getInterval(vertices, test[j]);
const b = getInterval(vertices2, test[j]);
if (b[0] > a[1] || a[0] > b[1]) {
return 0; // Seperating axis found
}
}
return 1;
};
}());
/**
* @en
* aabb-plane intersect detect.
* @zh
* 轴对齐包围盒和平面的相交性检测。
* @param {AABB} aabb 轴对齐包围盒
* @param {Plane} plane 平面
* @return {number} inside(back) = -1, outside(front) = 0, intersect = 1
*/
const aabbPlane = function (aabb: AABB, plane: Plane): number {
const r = aabb.halfExtents.x * Math.abs(plane.n.x)
+ aabb.halfExtents.y * Math.abs(plane.n.y)
+ aabb.halfExtents.z * Math.abs(plane.n.z);
const dot = Vec3.dot(plane.n, aabb.center);
if (dot + r < plane.d) { return -1; } else if (dot - r > plane.d) { return 0; }
return 1;
};
/**
* @en
* aabb-frustum intersect detect, faster but has false positive corner cases.
* @zh
* 轴对齐包围盒和锥台相交性检测,速度快,但有错误情况。
* @param {AABB} aabb 轴对齐包围盒
* @param {Frustum} frustum 锥台
* @return {number} 0 或 非0
*/
const aabbFrustum = function (aabb: AABB, frustum: Frustum): number {
for (let i = 0; i < frustum.planes.length; i++) {
// frustum plane normal points to the inside
if (aabbPlane(aabb, frustum.planes[i]) === -1) {
return 0;
}
} // completely outside
return 1;
};
// https://cesium.com/blog/2017/02/02/tighter-frustum-culling-and-why-you-may-want-to-disregard-it/
/**
* @en
* aabb-frustum intersect, handles most of the false positives correctly.
* @zh
* 轴对齐包围盒和锥台相交性检测,正确处理大多数错误情况。
* @param {AABB} aabb 轴对齐包围盒
* @param {Frustum} frustum 锥台
* @return {number}
*/
const aabbFrustumAccurate = (function () {
const tmp = new Array(8);
let out1 = 0; let out2 = 0;
for (let i = 0; i < tmp.length; i++) {
tmp[i] = new Vec3(0, 0, 0);
}
return function (aabb: AABB, frustum: Frustum): number {
let result = 0; let intersects = false;
// 1. aabb inside/outside frustum test
for (let i = 0; i < frustum.planes.length; i++) {
result = aabbPlane(aabb, frustum.planes[i]);
// frustum plane normal points to the inside
if (result === -1) return 0; // completely outside
else if (result === 1) { intersects = true; }
}
if (!intersects) { return 1; } // completely inside
// in case of false positives
// 2. frustum inside/outside aabb test
for (let i = 0; i < frustum.vertices.length; i++) {
Vec3.subtract(tmp[i], frustum.vertices[i], aabb.center);
}
out1 = 0, out2 = 0;
for (let i = 0; i < frustum.vertices.length; i++) {
if (tmp[i].x > aabb.halfExtents.x) { out1++; } else if (tmp[i].x < -aabb.halfExtents.x) { out2++; }
}
if (out1 === frustum.vertices.length || out2 === frustum.vertices.length) { return 0; }
out1 = 0; out2 = 0;
for (let i = 0; i < frustum.vertices.length; i++) {
if (tmp[i].y > aabb.halfExtents.y) { out1++; } else if (tmp[i].y < -aabb.halfExtents.y) { out2++; }
}
if (out1 === frustum.vertices.length || out2 === frustum.vertices.length) { return 0; }
out1 = 0; out2 = 0;
for (let i = 0; i < frustum.vertices.length; i++) {
if (tmp[i].z > aabb.halfExtents.z) { out1++; } else if (tmp[i].z < -aabb.halfExtents.z) { out2++; }
}
if (out1 === frustum.vertices.length || out2 === frustum.vertices.length) { return 0; }
return 1;
};
}());
/**
* @en
* obb contains the point.
* @zh
* 方向包围盒和点的相交性检测。
* @param {OBB} obb 方向包围盒
* @param {Vec3} point 点
* @return {boolean} true or false
*/
const obbPoint = (function () {
const tmp = new Vec3(0, 0, 0); const m3 = new Mat3();
const lessThan = function (a: Vec3, b: Vec3): boolean { return Math.abs(a.x) < b.x && Math.abs(a.y) < b.y && Math.abs(a.z) < b.z; };
return function (obb: OBB, point: Vec3): boolean {
Vec3.subtract(tmp, point, obb.center);
Vec3.transformMat3(tmp, tmp, Mat3.transpose(m3, obb.orientation));
return lessThan(tmp, obb.halfExtents);
};
}());
/**
* @en
* obb-plane intersect detect.
* @zh
* 方向包围盒和平面的相交性检测。
* @param {OBB} obb 方向包围盒
* @param {Plane} plane 平面
* @return {number} inside(back) = -1, outside(front) = 0, intersect = 1
*/
const obbPlane = (function () {
const absDot = function (n: Vec3, x: number, y: number, z: number) {
return Math.abs(n.x * x + n.y * y + n.z * z);
};
return function (obb: OBB, plane: Plane): number {
// Real-Time Collision Detection, Christer Ericson, p. 163.
const r = obb.halfExtents.x * absDot(plane.n, obb.orientation.m00, obb.orientation.m01, obb.orientation.m02)
+ obb.halfExtents.y * absDot(plane.n, obb.orientation.m03, obb.orientation.m04, obb.orientation.m05)
+ obb.halfExtents.z * absDot(plane.n, obb.orientation.m06, obb.orientation.m07, obb.orientation.m08);
const dot = Vec3.dot(plane.n, obb.center);
if (dot + r < plane.d) { return -1; } else if (dot - r > plane.d) { return 0; }
return 1;
};
}());
/**
* @en
* obb-frustum intersect, faster but has false positive corner cases.
* @zh
* 方向包围盒和锥台相交性检测,速度快,但有错误情况。
* @param {OBB} obb 方向包围盒
* @param {Frustum} frustum 锥台
* @return {number} 0 或 非0
*/
const obbFrustum = function (obb: OBB, frustum: Frustum): number {
for (let i = 0; i < frustum.planes.length; i++) {
// frustum plane normal points to the inside
if (obbPlane(obb, frustum.planes[i]) === -1) {
return 0;
}
} // completely outside
return 1;
};
// https://cesium.com/blog/2017/02/02/tighter-frustum-culling-and-why-you-may-want-to-disregard-it/
/**
* @en
* obb-frustum intersect, handles most of the false positives correctly.
* @zh
* 方向包围盒和锥台相交性检测,正确处理大多数错误情况。
* @param {OBB} obb 方向包围盒
* @param {Frustum} frustum 锥台
* @return {number} 0 或 非0
*/
const obbFrustumAccurate = (function () {
const tmp = new Array(8);
let dist = 0; let out1 = 0; let out2 = 0;
for (let i = 0; i < tmp.length; i++) {
tmp[i] = new Vec3(0, 0, 0);
}
const dot = function (n: Vec3, x: number, y: number, z: number): number {
return n.x * x + n.y * y + n.z * z;
};
return function (obb: OBB, frustum: Frustum): number {
let result = 0; let intersects = false;
// 1. obb inside/outside frustum test
for (let i = 0; i < frustum.planes.length; i++) {
result = obbPlane(obb, frustum.planes[i]);
// frustum plane normal points to the inside
if (result === -1) return 0; // completely outside
else if (result === 1) { intersects = true; }
}
if (!intersects) { return 1; } // completely inside
// in case of false positives
// 2. frustum inside/outside obb test
for (let i = 0; i < frustum.vertices.length; i++) {
Vec3.subtract(tmp[i], frustum.vertices[i], obb.center);
}
out1 = 0, out2 = 0;
for (let i = 0; i < frustum.vertices.length; i++) {
dist = dot(tmp[i], obb.orientation.m00, obb.orientation.m01, obb.orientation.m02);
if (dist > obb.halfExtents.x) { out1++; } else if (dist < -obb.halfExtents.x) { out2++; }
}
if (out1 === frustum.vertices.length || out2 === frustum.vertices.length) { return 0; }
out1 = 0; out2 = 0;
for (let i = 0; i < frustum.vertices.length; i++) {
dist = dot(tmp[i], obb.orientation.m03, obb.orientation.m04, obb.orientation.m05);
if (dist > obb.halfExtents.y) { out1++; } else if (dist < -obb.halfExtents.y) { out2++; }
}
if (out1 === frustum.vertices.length || out2 === frustum.vertices.length) { return 0; }
out1 = 0; out2 = 0;
for (let i = 0; i < frustum.vertices.length; i++) {
dist = dot(tmp[i], obb.orientation.m06, obb.orientation.m07, obb.orientation.m08);
if (dist > obb.halfExtents.z) { out1++; } else if (dist < -obb.halfExtents.z) { out2++; }
}
if (out1 === frustum.vertices.length || out2 === frustum.vertices.length) { return 0; }
return 1;
};
}());
/**
* @en
* obb-obb intersect detect.
* @zh
* 方向包围盒和方向包围盒的相交性检测。
* @param {OBB} obb1 方向包围盒1
* @param {OBB} obb2 方向包围盒2
* @return {number} 0 或 非0
*/
const obbWithOBB = (function () {
const test = new Array(15);
for (let i = 0; i < 15; i++) {
test[i] = new Vec3(0, 0, 0);
}
const vertices = new Array(8);
const vertices2 = new Array(8);
for (let i = 0; i < 8; i++) {
vertices[i] = new Vec3(0, 0, 0);
vertices2[i] = new Vec3(0, 0, 0);
}
return function (obb1: OBB, obb2: OBB): number {
Vec3.set(test[0], obb1.orientation.m00, obb1.orientation.m01, obb1.orientation.m02);
Vec3.set(test[1], obb1.orientation.m03, obb1.orientation.m04, obb1.orientation.m05);
Vec3.set(test[2], obb1.orientation.m06, obb1.orientation.m07, obb1.orientation.m08);
Vec3.set(test[3], obb2.orientation.m00, obb2.orientation.m01, obb2.orientation.m02);
Vec3.set(test[4], obb2.orientation.m03, obb2.orientation.m04, obb2.orientation.m05);
Vec3.set(test[5], obb2.orientation.m06, obb2.orientation.m07, obb2.orientation.m08);
for (let i = 0; i < 3; ++i) { // Fill out rest of axis
Vec3.cross(test[6 + i * 3 + 0], test[i], test[3]);
Vec3.cross(test[6 + i * 3 + 1], test[i], test[4]);
Vec3.cross(test[6 + i * 3 + 2], test[i], test[5]);
}
getOBBVertices(obb1.center, obb1.halfExtents, test[0], test[1], test[2], vertices);
getOBBVertices(obb2.center, obb2.halfExtents, test[3], test[4], test[5], vertices2);
for (let i = 0; i < 15; ++i) {
const a = getInterval(vertices, test[i]);
const b = getInterval(vertices2, test[i]);
if (b[0] > a[1] || a[0] > b[1]) {
return 0; // Seperating axis found
}
}
return 1;
};
}());
// https://github.com/diku-dk/bvh-tvcg18/blob/1fd3348c17bc8cf3da0b4ae60fdb8f2aa90a6ff0/FOUNDATION/GEOMETRY/GEOMETRY/include/overlap/geometry_overlap_obb_capsule.h
/**
* @en
* obb-capsule intersect detect.
* @zh
* 方向包围盒和胶囊体的相交性检测。
* @param obb 方向包围盒
* @param capsule 胶囊体
*/
const obbCapsule = (function () {
const sphere_0 = new Sphere();
const v3_0 = new Vec3();
const v3_1 = new Vec3();
const v3_2 = new Vec3();
const v3_verts8 = new Array<Vec3>(8);
for (let i = 0; i < 8; i++) { v3_verts8[i] = new Vec3(); }
const v3_axis8 = new Array<Vec3>(8);
for (let i = 0; i < 8; i++) { v3_axis8[i] = new Vec3(); }
return function (obb: OBB, capsule: Capsule) {
const h = Vec3.squaredDistance(capsule.ellipseCenter0, capsule.ellipseCenter1);
if (h === 0) {
sphere_0.radius = capsule.radius;
sphere_0.center.set(capsule.ellipseCenter0);
return intersect.sphereOBB(sphere_0, obb);
} else {
v3_0.x = obb.orientation.m00;
v3_0.y = obb.orientation.m01;
v3_0.z = obb.orientation.m02;
v3_1.x = obb.orientation.m03;
v3_1.y = obb.orientation.m04;
v3_1.z = obb.orientation.m05;
v3_2.x = obb.orientation.m06;
v3_2.y = obb.orientation.m07;
v3_2.z = obb.orientation.m08;
getOBBVertices(obb.center, obb.halfExtents, v3_0, v3_1, v3_2, v3_verts8);
const axes = v3_axis8;
const a0 = Vec3.copy(axes[0], v3_0);
const a1 = Vec3.copy(axes[1], v3_1);
const a2 = Vec3.copy(axes[2], v3_2);
const C = Vec3.subtract(axes[3], capsule.center, obb.center);
C.normalize();
const B = Vec3.subtract(axes[4], capsule.ellipseCenter0, capsule.ellipseCenter1);
B.normalize();
Vec3.cross(axes[5], a0, B);
Vec3.cross(axes[6], a1, B);
Vec3.cross(axes[7], a2, B);
for (let i = 0; i < 8; ++i) {
const a = getInterval(v3_verts8, axes[i]);
const d0 = Vec3.dot(axes[i], capsule.ellipseCenter0);
const d1 = Vec3.dot(axes[i], capsule.ellipseCenter1);
const max_d = Math.max(d0, d1);
const min_d = Math.min(d0, d1);
const d_min = min_d - capsule.radius;
const d_max = max_d + capsule.radius;
if (d_min > a[1] || a[0] > d_max) {
return 0; // Seperating axis found
}
}
return 1;
}
};
}());
/**
* @en
* sphere-plane intersect, not necessarily faster than obb-plane,due to the length calculation of the
* plane normal to factor out the unnomalized plane distance.
* @zh
* 球与平面的相交性检测。
* @param {Sphere} sphere 球
* @param {Plane} plane 平面
* @return {number} inside(back) = -1, outside(front) = 0, intersect = 1
*/
const spherePlane = function (sphere: Sphere, plane: Plane): number {
const dot = Vec3.dot(plane.n, sphere.center);
const r = sphere.radius * plane.n.length();
if (dot + r < plane.d) { return -1; } else if (dot - r > plane.d) { return 0; }
return 1;
};
/**
* @en
* sphere-frustum intersect, faster but has false positive corner cases.
* @zh
* 球和锥台的相交性检测,速度快,但有错误情况。
* @param {Sphere} sphere 球
* @param {Frustum} frustum 锥台
* @return {number} 0 或 非0
*/
const sphereFrustum = function (sphere: Sphere, frustum: Frustum): number {
for (let i = 0; i < frustum.planes.length; i++) {
// frustum plane normal points to the inside
if (spherePlane(sphere, frustum.planes[i]) === -1) {
return 0;
}
} // completely outside
return 1;
};
// https://stackoverflow.com/questions/20912692/view-frustum-culling-corner-cases
/**
* @en
* sphere-frustum intersect, handles the false positives correctly.
* @zh
* 球和锥台的相交性检测,正确处理大多数错误情况。
* @param {Sphere} sphere 球
* @param {Frustum} frustum 锥台
* @return {number} 0 或 非0
*/
const sphereFrustumAccurate = (function () {
const pt = new Vec3(0, 0, 0); const map = [1, -1, 1, -1, 1, -1];
return function (sphere: Sphere, frustum: Frustum): number {
for (let i = 0; i < 6; i++) {
const plane = frustum.planes[i];
const r = sphere.radius; const c = sphere.center;
const n = plane.n; const d = plane.d;
const dot = Vec3.dot(n, c);
// frustum plane normal points to the inside
if (dot + r < d) return 0; // completely outside
else if (dot - r > d) { continue; }
// in case of false positives
// has false negatives, still working on it
Vec3.add(pt, c, Vec3.multiplyScalar(pt, n, r));
for (let j = 0; j < 6; j++) {
if (j === i || j === i + map[i]) { continue; }
const test = frustum.planes[j];
if (Vec3.dot(test.n, pt) < test.d) { return 0; }
}
}
return 1;
};
}());
/**
* @en
* sphere-sphere intersect detect.
* @zh
* 球和球的相交性检测。
* @param {Sphere} sphere0 球0
* @param {Sphere} sphere1 球1
* @return {boolean} true or false
*/
const sphereWithSphere = function (sphere0: Sphere, sphere1: Sphere): boolean {
const r = sphere0.radius + sphere1.radius;
return Vec3.squaredDistance(sphere0.center, sphere1.center) < r * r;
};
/**
* @en
* sphere-aabb intersect detect.
* @zh
* 球和轴对齐包围盒的相交性检测。
* @param {Sphere} sphere 球
* @param {AABB} aabb 轴对齐包围盒
* @return {boolean} true or false
*/
const sphereAABB = (function () {
const pt = new Vec3();
return function (sphere: Sphere, aabb: AABB): boolean {
distance.pt_point_aabb(pt, sphere.center, aabb);
return Vec3.squaredDistance(sphere.center, pt) < sphere.radius * sphere.radius;
};
}());
/**
* @en
* sphere-obb intersect detect.
* @zh
* 球和方向包围盒的相交性检测。
* @param {Sphere} sphere 球
* @param {OBB} obb 方向包围盒
* @return {boolean} true or false
*/
const sphereOBB = (function () {
const pt = new Vec3();
return function (sphere: Sphere, obb: OBB): boolean {
distance.pt_point_obb(pt, sphere.center, obb);
return Vec3.squaredDistance(sphere.center, pt) < sphere.radius * sphere.radius;
};
}());
/**
* @en
* sphere-capsule intersect detect.
* @zh
* 球和胶囊体的相交性检测。
*/
const sphereCapsule = (function () {
const v3_0 = new Vec3();
const v3_1 = new Vec3();
return function (sphere: Sphere, capsule: Capsule) {
const r = sphere.radius + capsule.radius;
const squaredR = r * r;
const h = Vec3.squaredDistance(capsule.ellipseCenter0, capsule.ellipseCenter1);
if (h === 0) {
return Vec3.squaredDistance(sphere.center, capsule.center) < squaredR;
} else {
Vec3.subtract(v3_0, sphere.center, capsule.ellipseCenter0);
Vec3.subtract(v3_1, capsule.ellipseCenter1, capsule.ellipseCenter0);
const t = Vec3.dot(v3_0, v3_1) / h;
if (t < 0) {
return Vec3.squaredDistance(sphere.center, capsule.ellipseCenter0) < squaredR;
} else if (t > 1) {
return Vec3.squaredDistance(sphere.center, capsule.ellipseCenter1) < squaredR;
} else {
Vec3.scaleAndAdd(v3_0, capsule.ellipseCenter0, v3_1, t);
return Vec3.squaredDistance(sphere.center, v3_0) < squaredR;
}
}
};
}());
// http://www.geomalgorithms.com/a07-_distance.html
/**
* @en
* capsule-capsule intersect detect.
* @zh
* 胶囊体和胶囊体的相交性检测。
*/
const capsuleWithCapsule = (function () {
const v3_0 = new Vec3();
const v3_1 = new Vec3();
const v3_2 = new Vec3();
const v3_3 = new Vec3();
const v3_4 = new Vec3();
const v3_5 = new Vec3();
return function capsuleWithCapsule (capsuleA: Capsule, capsuleB: Capsule) {
const u = Vec3.subtract(v3_0, capsuleA.ellipseCenter1, capsuleA.ellipseCenter0);
const v = Vec3.subtract(v3_1, capsuleB.ellipseCenter1, capsuleB.ellipseCenter0);
const w = Vec3.subtract(v3_2, capsuleA.ellipseCenter0, capsuleB.ellipseCenter0);
const a = Vec3.dot(u, u); // always >= 0
const b = Vec3.dot(u, v);
const c = Vec3.dot(v, v); // always >= 0
const d = Vec3.dot(u, w);
const e = Vec3.dot(v, w);
const D = a * c - b * b; // always >= 0
let sN: number;
let sD = D; // sc = sN / sD, default sD = D >= 0
let tN: number;
let tD = D; // tc = tN / tD, default tD = D >= 0
// compute the line parameters of the two closest points
if (D < EPSILON) { // the lines are almost parallel
sN = 0.0; // force using point P0 on segment S1
sD = 1.0; // to prevent possible division by 0.0 later
tN = e;
tD = c;
} else { // get the closest points on the infinite lines
sN = (b * e - c * d);
tN = (a * e - b * d);
if (sN < 0.0) { // sc < 0 => the s=0 edge is visible
sN = 0.0;
tN = e;
tD = c;
} else if (sN > sD) { // sc > 1 => the s=1 edge is visible
sN = sD;
tN = e + b;
tD = c;
}
}
if (tN < 0.0) { // tc < 0 => the t=0 edge is visible
tN = 0.0;
// recompute sc for this edge
if (-d < 0.0) {
sN = 0.0;
} else if (-d > a) {
sN = sD;
} else {
sN = -d;
sD = a;
}
} else if (tN > tD) { // tc > 1 => the t=1 edge is visible
tN = tD;
// recompute sc for this edge
if ((-d + b) < 0.0) {
sN = 0;
} else if ((-d + b) > a) {
sN = sD;
} else {
sN = (-d + b);
sD = a;
}
}
// finally do the division to get sc and tc
const sc = (Math.abs(sN) < EPSILON ? 0.0 : sN / sD);
const tc = (Math.abs(tN) < EPSILON ? 0.0 : tN / tD);
// get the difference of the two closest points
const dP = v3_3;
dP.set(w);
dP.add(Vec3.multiplyScalar(v3_4, u, sc));
dP.subtract(Vec3.multiplyScalar(v3_5, v, tc));
const radius = capsuleA.radius + capsuleB.radius;
return dP.lengthSqr() < radius * radius;
};
}());
/**
* @en
* Algorithm of intersect detect for basic geometry.
* @zh
* 基础几何的相交性检测算法。
*/
const intersect = {
raySphere,
rayAABB,
rayOBB,
rayPlane,
rayTriangle,
rayCapsule,
raySubMesh,
rayMesh,
rayModel,
lineSphere,
lineAABB,
lineOBB,
linePlane,
lineTriangle,
sphereWithSphere,
sphereAABB,
sphereOBB,
spherePlane,
sphereFrustum,
sphereFrustumAccurate,
sphereCapsule,
aabbWithAABB,
aabbWithOBB,
aabbPlane,
aabbFrustum,
aabbFrustumAccurate,
obbWithOBB,
obbPlane,
obbFrustum,
obbFrustumAccurate,
obbPoint,
obbCapsule,
capsuleWithCapsule,
/**
* @en Check intersection between two geometries, it accept all basic geometry types in [[geometry]] module.
* @zh 两个几何体的相交性检测,可填入 [[geometry]] 模块中的基础几何形状。
* @param g1 @en The first geometry. @zh 第一个几何体。
* @param g2 @en The second geometry. @zh 第二个几何体。
* @param outPt @en A 3d point to store the intersection point result, only part of the geometries support this.
* @zh 可选,用于保存相交点的输出对象。(注:仅部分形状的检测带有这个返回值)
*/
resolve (g1: any, g2: any, outPt = null) {
const type1 = g1._type; const type2 = g2._type;
const resolver = this[type1 | type2] as (...args: any) => number;
return type1 < type2 ? resolver(g1, g2, outPt) : resolver(g2, g1, outPt);
},
};
intersect[enums.SHAPE_RAY | enums.SHAPE_SPHERE] = raySphere;
intersect[enums.SHAPE_RAY | enums.SHAPE_AABB] = rayAABB;
intersect[enums.SHAPE_RAY | enums.SHAPE_OBB] = rayOBB;
intersect[enums.SHAPE_RAY | enums.SHAPE_PLANE] = rayPlane;
intersect[enums.SHAPE_RAY | enums.SHAPE_TRIANGLE] = rayTriangle;
intersect[enums.SHAPE_RAY | enums.SHAPE_CAPSULE] = rayCapsule;
intersect[enums.SHAPE_LINE | enums.SHAPE_SPHERE] = lineSphere;
intersect[enums.SHAPE_LINE | enums.SHAPE_AABB] = lineAABB;
intersect[enums.SHAPE_LINE | enums.SHAPE_OBB] = lineOBB;
intersect[enums.SHAPE_LINE | enums.SHAPE_PLANE] = linePlane;
intersect[enums.SHAPE_LINE | enums.SHAPE_TRIANGLE] = lineTriangle;
intersect[enums.SHAPE_SPHERE] = sphereWithSphere;
intersect[enums.SHAPE_SPHERE | enums.SHAPE_AABB] = sphereAABB;
intersect[enums.SHAPE_SPHERE | enums.SHAPE_OBB] = sphereOBB;
intersect[enums.SHAPE_SPHERE | enums.SHAPE_PLANE] = spherePlane;
intersect[enums.SHAPE_SPHERE | enums.SHAPE_FRUSTUM] = sphereFrustum;
intersect[enums.SHAPE_SPHERE | enums.SHAPE_FRUSTUM_ACCURATE] = sphereFrustumAccurate;
intersect[enums.SHAPE_SPHERE | enums.SHAPE_CAPSULE] = sphereCapsule;
intersect[enums.SHAPE_AABB] = aabbWithAABB;
intersect[enums.SHAPE_AABB | enums.SHAPE_OBB] = aabbWithOBB;
intersect[enums.SHAPE_AABB | enums.SHAPE_PLANE] = aabbPlane;
intersect[enums.SHAPE_AABB | enums.SHAPE_FRUSTUM] = aabbFrustum;
intersect[enums.SHAPE_AABB | enums.SHAPE_FRUSTUM_ACCURATE] = aabbFrustumAccurate;
intersect[enums.SHAPE_OBB] = obbWithOBB;
intersect[enums.SHAPE_OBB | enums.SHAPE_PLANE] = obbPlane;
intersect[enums.SHAPE_OBB | enums.SHAPE_FRUSTUM] = obbFrustum;
intersect[enums.SHAPE_OBB | enums.SHAPE_FRUSTUM_ACCURATE] = obbFrustumAccurate;
intersect[enums.SHAPE_OBB | enums.SHAPE_CAPSULE] = obbCapsule;
intersect[enums.SHAPE_CAPSULE] = capsuleWithCapsule;
export default intersect;