You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1100 lines
40 KiB
1100 lines
40 KiB
/****************************************************************************
|
|
Copyright (c) 2022-2023 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 documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights to
|
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
of the Software, and to permit persons to whom the Software is furnished to do so,
|
|
subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
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.
|
|
****************************************************************************/
|
|
|
|
#include "core/geometry/Intersect.h"
|
|
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include "3d/assets/Mesh.h"
|
|
#include "base/TemplateUtils.h"
|
|
#include "base/std/container/array.h"
|
|
#include "core/TypedArray.h"
|
|
#include "core/geometry/AABB.h"
|
|
#include "core/geometry/Capsule.h"
|
|
#include "core/geometry/Line.h"
|
|
#include "core/geometry/Obb.h"
|
|
#include "core/geometry/Plane.h"
|
|
#include "core/geometry/Ray.h"
|
|
#include "core/geometry/Spec.h"
|
|
#include "core/geometry/Sphere.h"
|
|
#include "core/geometry/Triangle.h"
|
|
#include "math/Mat3.h"
|
|
#include "math/Math.h"
|
|
#include "math/Vec3.h"
|
|
#include "renderer/gfx-base/GFXDef.h"
|
|
#include "scene/Model.h"
|
|
|
|
namespace cc {
|
|
namespace geometry {
|
|
|
|
float rayPlane(const Ray &ray, const Plane &plane) {
|
|
auto denom = Vec3::dot(ray.d, plane.n);
|
|
if (abs(denom) < math::EPSILON) {
|
|
return 0;
|
|
}
|
|
auto t = (plane.n * plane.d - ray.o).dot(plane.n) / denom;
|
|
return t < 0 ? 0 : t;
|
|
}
|
|
|
|
// based on http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/raytri/
|
|
float rayTriangle(const Ray &ray, const Triangle &triangle, bool doubleSided) {
|
|
Vec3 pvec{};
|
|
Vec3 qvec{};
|
|
auto ab = triangle.b - triangle.a;
|
|
auto ac = triangle.c - triangle.a;
|
|
Vec3::cross(ray.d, ac, &pvec);
|
|
float det = Vec3::dot(ab, pvec);
|
|
if (det < math::EPSILON && (!doubleSided || det > -math::EPSILON)) {
|
|
return 0;
|
|
}
|
|
float invDet = 1.0F / det;
|
|
auto ao = ray.o - triangle.a;
|
|
auto u = Vec3::dot(ao, pvec) * invDet;
|
|
if (u < 0 || u > 1) {
|
|
return 0;
|
|
}
|
|
Vec3::cross(ao, ab, &qvec);
|
|
auto v = Vec3::dot(ray.d, qvec) * invDet;
|
|
if (v < 0 || u + v > 1) {
|
|
return 0;
|
|
}
|
|
|
|
auto t = Vec3::dot(ac, qvec) * invDet;
|
|
return t < 0 ? 0 : t;
|
|
}
|
|
|
|
float raySphere(const Ray &ray, const Sphere &sphere) {
|
|
auto rSq = sphere.getRadius() * sphere.getRadius();
|
|
auto e = sphere.getCenter() - ray.o;
|
|
auto eSq = e.lengthSquared();
|
|
|
|
auto aLength = Vec3::dot(e, ray.d); // assume ray direction already normalized
|
|
auto fSq = rSq - (eSq - aLength * aLength);
|
|
if (fSq < 0) {
|
|
return 0;
|
|
}
|
|
|
|
auto f = sqrt(fSq);
|
|
auto t = eSq < rSq ? aLength + f : aLength - f;
|
|
return t < 0 ? 0 : t;
|
|
}
|
|
|
|
float rayAABB2(const Ray &ray, const Vec3 &min, const Vec3 &max) {
|
|
const auto &o = ray.o;
|
|
auto d = ray.d;
|
|
auto ix = 1.0F / d.x;
|
|
auto iy = 1.0F / d.y;
|
|
auto iz = 1.0F / d.z;
|
|
auto tx1 = (min.x - o.x) * ix;
|
|
auto tx2 = (max.x - o.x) * ix;
|
|
auto ty1 = (min.y - o.y) * iy;
|
|
auto ty2 = (max.y - o.y) * iy;
|
|
auto tz1 = (min.z - o.z) * iz;
|
|
auto tz2 = (max.z - o.z) * iz;
|
|
auto tmin = std::max(std::max(std::min(tx1, tx2), std::min(ty1, ty2)), std::min(tz1, tz2));
|
|
auto tmax = std::min(std::min(std::max(tx1, tx2), std::max(ty1, ty2)), std::max(tz1, tz2));
|
|
if (tmax < 0 || tmin > tmax) {
|
|
return 0;
|
|
}
|
|
return tmin > 0 ? tmin : tmax; // ray origin inside aabb
|
|
}
|
|
|
|
float rayAABB(const Ray &ray, const AABB &aabb) {
|
|
auto minV = aabb.getCenter() - aabb.getHalfExtents();
|
|
auto maxV = aabb.getCenter() + aabb.getHalfExtents();
|
|
return rayAABB2(ray, minV, maxV);
|
|
}
|
|
|
|
float rayOBB(const Ray &ray, const OBB &obb) {
|
|
ccstd::array<float, 6> t;
|
|
ccstd::array<float, 3> size = {obb.halfExtents.x, obb.halfExtents.y, obb.halfExtents.z};
|
|
const auto ¢er = obb.center;
|
|
auto const &d = ray.d;
|
|
|
|
Vec3 x = {obb.orientation.m[0], obb.orientation.m[1], obb.orientation.m[2]};
|
|
Vec3 y = {obb.orientation.m[3], obb.orientation.m[4], obb.orientation.m[5]};
|
|
Vec3 z = {obb.orientation.m[6], obb.orientation.m[7], obb.orientation.m[8]};
|
|
Vec3 p = center - ray.o;
|
|
|
|
// The cos values of the ray on the X, Y, Z
|
|
ccstd::array<float, 3> f = {Vec3::dot(x, d), Vec3::dot(y, d), Vec3::dot(z, d)};
|
|
|
|
// The projection length of P on X, Y, Z
|
|
ccstd::array<float, 3> e{Vec3::dot(x, p), Vec3::dot(y, p), Vec3::dot(z, p)};
|
|
|
|
for (auto 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.000001F;
|
|
}
|
|
// min
|
|
t[i * 2 + 0] = (e[i] + size[i]) / f[i];
|
|
// max
|
|
t[i * 2 + 1] = (e[i] - size[i]) / f[i];
|
|
}
|
|
auto tmin = std::max(std::max(std::min(t[0], t[1]), std::min(t[2], t[3])), std::min(t[4], t[5]));
|
|
auto tmax = std::min(std::min(std::max(t[0], t[1]), std::max(t[2], t[3])), std::max(t[4], t[5]));
|
|
if (tmax < 0 || tmin > tmax) {
|
|
return 0;
|
|
}
|
|
return tmin > 0 ? tmin : tmax; // ray origin inside aabb
|
|
}
|
|
|
|
float rayCapsule(const Ray &ray, const Capsule &capsule) {
|
|
Sphere sphere;
|
|
auto radiusSqr = capsule.radius * capsule.radius;
|
|
auto vRayNorm = ray.d.getNormalized();
|
|
auto aa = capsule.ellipseCenter0;
|
|
auto bb = capsule.ellipseCenter1;
|
|
auto ba = bb - aa;
|
|
if (ba == Vec3::ZERO) {
|
|
sphere.setRadius(capsule.radius);
|
|
sphere.setCenter(capsule.ellipseCenter0);
|
|
return raySphere(ray, sphere);
|
|
}
|
|
|
|
auto oa = ray.o - aa;
|
|
Vec3 VxBA; //NOLINT(readability-identifier-naming)
|
|
Vec3::cross(vRayNorm, ba, &VxBA);
|
|
auto a = VxBA.lengthSquared();
|
|
if (a == 0.0F) {
|
|
sphere.setRadius(capsule.radius);
|
|
auto bo = bb - ray.o;
|
|
if (oa.lengthSquared() < bo.lengthSquared()) {
|
|
sphere.setCenter(capsule.ellipseCenter0);
|
|
} else {
|
|
sphere.setCenter(capsule.ellipseCenter1);
|
|
}
|
|
return raySphere(ray, sphere);
|
|
}
|
|
|
|
Vec3 OAxBA; //NOLINT(readability-identifier-naming)
|
|
Vec3::cross(oa, ba, &OAxBA);
|
|
auto ab2 = ba.lengthSquared();
|
|
auto b = 2.0F * Vec3::dot(VxBA, OAxBA);
|
|
auto c = OAxBA.lengthSquared() - (radiusSqr * ab2);
|
|
auto d = b * b - 4 * a * c;
|
|
|
|
if (d < 0) {
|
|
return 0;
|
|
}
|
|
|
|
auto t = (-b - std::sqrt(d)) / (2.0F * a);
|
|
if (t < 0.0F) {
|
|
sphere.setRadius(capsule.radius);
|
|
auto bo = bb - ray.o;
|
|
if (oa.lengthSquared() < bo.lengthSquared()) {
|
|
sphere.setCenter(capsule.ellipseCenter0);
|
|
} else {
|
|
sphere.setCenter(capsule.ellipseCenter1);
|
|
}
|
|
return raySphere(ray, sphere);
|
|
}
|
|
// Limit intersection between the bounds of the cylinder's end caps.
|
|
auto iPos = ray.o + vRayNorm * t;
|
|
auto iPosLen = iPos - aa;
|
|
auto tLimit = Vec3::dot(iPosLen, ba) / ab2;
|
|
|
|
if (tLimit >= 0 && tLimit <= 1) {
|
|
return t;
|
|
}
|
|
if (tLimit < 0) {
|
|
sphere.setRadius(capsule.radius);
|
|
sphere.setCenter(capsule.ellipseCenter0);
|
|
return raySphere(ray, sphere);
|
|
}
|
|
if (tLimit > 1) {
|
|
sphere.setRadius(capsule.radius);
|
|
sphere.setCenter(capsule.ellipseCenter1);
|
|
return raySphere(ray, sphere);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
namespace {
|
|
void fillResult(float *minDis, ERaycastMode m, float d, float i0, float i1, float i2, ccstd::optional<ccstd::vector<IRaySubMeshResult>> &r) {
|
|
if (m == ERaycastMode::CLOSEST) {
|
|
if (*minDis > d || *minDis == 0.0F) {
|
|
*minDis = d;
|
|
if (r) {
|
|
if (r->empty()) {
|
|
r->emplace_back(IRaySubMeshResult{d, static_cast<uint32_t>(i0 / 3), static_cast<uint32_t>(i1 / 3), static_cast<uint32_t>(i2 / 3)});
|
|
} else {
|
|
(*r)[0].distance = d;
|
|
(*r)[0].vertexIndex0 = static_cast<uint32_t>(i0 / 3);
|
|
(*r)[0].vertexIndex1 = static_cast<uint32_t>(i1 / 3);
|
|
(*r)[0].vertexIndex2 = static_cast<uint32_t>(i2 / 3);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
*minDis = d;
|
|
if (r) r->emplace_back(IRaySubMeshResult{d, static_cast<uint32_t>(i0 / 3), static_cast<uint32_t>(i1 / 3), static_cast<uint32_t>(i2 / 3)});
|
|
}
|
|
}
|
|
|
|
float narrowphase(float *minDis, const Float32Array &vb, const IBArray &ib, gfx::PrimitiveMode pm, const Ray &ray, IRaySubMeshOptions *opt) {
|
|
Triangle tri;
|
|
auto ibSize = ccstd::visit(overloaded{
|
|
[](const auto &arr) {
|
|
return arr.length();
|
|
},
|
|
[](const ccstd::monostate & /*unused*/) {
|
|
return static_cast<uint32_t>(0);
|
|
}},
|
|
ib);
|
|
|
|
return ccstd::visit(overloaded{[&](const auto &ib) {
|
|
if (pm == gfx::PrimitiveMode::TRIANGLE_LIST) {
|
|
auto cnt = ibSize;
|
|
for (auto j = 0; j < cnt; j += 3) {
|
|
auto i0 = ib[j] * 3;
|
|
auto i1 = ib[j + 1] * 3;
|
|
auto i2 = ib[j + 2] * 3;
|
|
tri.a = {vb[i0], vb[i0 + 1], vb[i0 + 2]};
|
|
tri.b = {vb[i1], vb[i1 + 1], vb[i1 + 2]};
|
|
tri.c = {vb[i2], vb[i2 + 1], vb[i2 + 2]};
|
|
auto dist = rayTriangle(ray, tri, opt->doubleSided);
|
|
if (dist == 0.0F || dist > opt->distance) continue;
|
|
fillResult(minDis, opt->mode, dist, static_cast<float>(i0), static_cast<float>(i1), static_cast<float>(i2), opt->result);
|
|
if (opt->mode == ERaycastMode::ANY) return dist;
|
|
}
|
|
} else if (pm == gfx::PrimitiveMode::TRIANGLE_STRIP) {
|
|
auto cnt = ibSize - 2;
|
|
int32_t rev = 0;
|
|
for (auto j = 0; j < cnt; j += 1) {
|
|
auto i0 = ib[j - rev] * 3;
|
|
auto i1 = ib[j + rev + 1] * 3;
|
|
auto i2 = ib[j + 2] * 3;
|
|
tri.a = {vb[i0], vb[i0 + 1], vb[i0 + 2]};
|
|
tri.b = {vb[i1], vb[i1 + 1], vb[i1 + 2]};
|
|
tri.c = {vb[i2], vb[i2 + 1], vb[i2 + 2]};
|
|
rev = ~rev;
|
|
auto dist = rayTriangle(ray, tri, opt->doubleSided);
|
|
if (dist == 0.0F || dist > opt->distance) continue;
|
|
fillResult(minDis, opt->mode, dist, static_cast<float>(i0), static_cast<float>(i1), static_cast<float>(i2), opt->result);
|
|
if (opt->mode == ERaycastMode::ANY) return dist;
|
|
}
|
|
} else if (pm == gfx::PrimitiveMode::TRIANGLE_FAN) {
|
|
auto cnt = ibSize - 1;
|
|
auto i0 = ib[0] * 3;
|
|
tri.a = {vb[i0], vb[i0 + 1], vb[i0 + 2]};
|
|
for (auto j = 1; j < cnt; j += 1) {
|
|
auto i1 = ib[j] * 3;
|
|
auto i2 = ib[j + 1] * 3;
|
|
tri.b = {vb[i1], vb[i1 + 1], vb[i1 + 2]};
|
|
tri.c = {vb[i2], vb[i2 + 1], vb[i2 + 2]};
|
|
auto dist = rayTriangle(ray, tri, opt->doubleSided);
|
|
if (dist == 0.0 || dist > opt->distance) continue;
|
|
fillResult(minDis, opt->mode, dist, static_cast<float>(i0), static_cast<float>(i1), static_cast<float>(i2), opt->result);
|
|
if (opt->mode == ERaycastMode::ANY) return dist;
|
|
}
|
|
}
|
|
return *minDis;
|
|
},
|
|
[](const ccstd::monostate & /*unused*/) { return 0.F; }},
|
|
ib);
|
|
}
|
|
} // namespace
|
|
|
|
float raySubMesh(const Ray &ray, const RenderingSubMesh &submesh, IRaySubMeshOptions *options) {
|
|
Triangle tri;
|
|
IRaySubMeshOptions deOpt;
|
|
deOpt.mode = ERaycastMode::ANY;
|
|
deOpt.distance = FLT_MAX;
|
|
deOpt.doubleSided = false;
|
|
float minDis = 0.0F;
|
|
auto &mesh = const_cast<RenderingSubMesh &>(submesh);
|
|
if (mesh.getGeometricInfo().positions.empty()) return minDis;
|
|
IRaySubMeshOptions *opt = options ? options : &deOpt;
|
|
auto min = mesh.getGeometricInfo().boundingBox.min;
|
|
auto max = mesh.getGeometricInfo().boundingBox.max;
|
|
if (rayAABB2(ray, min, max) != 0.0F) {
|
|
const auto &pm = mesh.getPrimitiveMode();
|
|
const auto &info = mesh.getGeometricInfo();
|
|
narrowphase(&minDis, info.positions, info.indices.value(), pm, ray, opt);
|
|
}
|
|
return minDis;
|
|
}
|
|
|
|
float rayMesh(const Ray &ray, const Mesh &mesh, IRayMeshOptions *option) {
|
|
float minDis = 0.0F;
|
|
IRayMeshOptions deOpt;
|
|
deOpt.distance = std::numeric_limits<float>::max();
|
|
deOpt.doubleSided = false;
|
|
deOpt.mode = ERaycastMode::ANY;
|
|
IRayMeshOptions *opt = option ? option : &deOpt;
|
|
auto length = mesh.getSubMeshCount();
|
|
auto min = mesh.getStruct().minPosition;
|
|
auto max = mesh.getStruct().maxPosition;
|
|
if (min.has_value() && max.has_value() && rayAABB2(ray, min.value(), max.value()) == 0.0F) return minDis;
|
|
for (uint32_t i = 0; i < length; i++) {
|
|
const auto &sm = const_cast<Mesh &>(mesh).getRenderingSubMeshes()[i];
|
|
float dis = raySubMesh(ray, *sm, opt);
|
|
if (dis != 0.0F) {
|
|
if (opt->mode == ERaycastMode::CLOSEST) {
|
|
if (minDis == 0.0F || minDis > dis) {
|
|
minDis = dis;
|
|
if (opt->subIndices.has_value()) {
|
|
if (opt->subIndices->empty()) {
|
|
opt->subIndices->resize(1);
|
|
}
|
|
opt->subIndices.value()[0] = i;
|
|
}
|
|
}
|
|
} else {
|
|
minDis = dis;
|
|
if (opt->subIndices.has_value()) opt->subIndices->emplace_back(i);
|
|
if (opt->mode == ERaycastMode::ANY) {
|
|
return dis;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (minDis != 0.0F && opt->mode == ERaycastMode::CLOSEST) {
|
|
if (opt->result.has_value()) {
|
|
opt->result.value()[0].distance = minDis;
|
|
opt->result.value().resize(1);
|
|
}
|
|
if (opt->subIndices.has_value()) opt->subIndices.value().resize(1);
|
|
}
|
|
return minDis;
|
|
}
|
|
|
|
float rayModel(const Ray &ray, const scene::Model &model, IRayModelOptions *option) {
|
|
float minDis = 0.0F;
|
|
IRayModelOptions deOpt;
|
|
deOpt.distance = std::numeric_limits<float>::max();
|
|
deOpt.doubleSided = false;
|
|
deOpt.mode = ERaycastMode::ANY;
|
|
|
|
IRayModelOptions *opt = option ? option : &deOpt;
|
|
const auto *wb = model.getWorldBounds();
|
|
if (wb && rayAABB(ray, *wb) == 0.0F) {
|
|
return minDis;
|
|
}
|
|
Ray modelRay{ray};
|
|
if (model.getNode()) {
|
|
Mat4 m4 = model.getNode()->getWorldMatrix().getInversed();
|
|
Vec3::transformMat4(ray.o, m4, &modelRay.o);
|
|
Vec3::transformMat4Normal(ray.d, m4, &modelRay.d);
|
|
}
|
|
const auto &subModels = model.getSubModels();
|
|
for (auto i = 0; i < subModels.size(); i++) {
|
|
const auto &subMesh = subModels[i]->getSubMesh();
|
|
float dis = raySubMesh(modelRay, *subMesh, opt);
|
|
if (dis != 0.0F) {
|
|
if (opt->mode == ERaycastMode::CLOSEST) {
|
|
if (minDis == 0.0F || minDis > dis) {
|
|
minDis = dis;
|
|
if (opt->subIndices.has_value()) {
|
|
if (opt->subIndices->empty()) {
|
|
opt->subIndices->resize(1);
|
|
}
|
|
opt->subIndices.value()[0] = i;
|
|
}
|
|
}
|
|
} else {
|
|
minDis = dis;
|
|
if (opt->subIndices.has_value()) opt->subIndices->emplace_back(i);
|
|
if (opt->mode == ERaycastMode::ANY) {
|
|
return dis;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (minDis != 0.0F && opt->mode == ERaycastMode::CLOSEST) {
|
|
if (opt->result.has_value()) {
|
|
opt->result.value()[0].distance = minDis;
|
|
opt->result.value().resize(1);
|
|
}
|
|
if (opt->subIndices.has_value()) opt->subIndices->resize(1);
|
|
}
|
|
return minDis;
|
|
}
|
|
|
|
float linePlane(const Line &line, const Plane &plane) {
|
|
auto ab = line.e - line.s;
|
|
auto t = (plane.d - Vec3::dot(line.s, plane.n)) / Vec3::dot(ab, plane.n);
|
|
return (t < 0 || t > 1) ? 0 : t;
|
|
}
|
|
|
|
int lineTriangle(const Line &line, const Triangle &triangle, Vec3 *outPt) {
|
|
Vec3 n;
|
|
Vec3 e;
|
|
auto ab = triangle.b - triangle.a;
|
|
auto ac = triangle.c - triangle.a;
|
|
auto qp = line.s - line.e;
|
|
Vec3::cross(ab, ac, &n);
|
|
|
|
auto det = Vec3::dot(qp, n);
|
|
|
|
if (det <= 0.0F) {
|
|
return 0;
|
|
}
|
|
|
|
auto ap = line.s - triangle.a;
|
|
auto t = Vec3::dot(ap, n);
|
|
if (t < 0 || t > det) {
|
|
return 0;
|
|
}
|
|
|
|
Vec3::cross(qp, ap, &e);
|
|
auto v = Vec3::dot(ac, e);
|
|
if (v < 0.0F || v > det) {
|
|
return 0;
|
|
}
|
|
|
|
auto w = -Vec3::dot(ab, e);
|
|
if (w < 0.0F || v + w > det) {
|
|
return 0;
|
|
}
|
|
|
|
if (outPt) {
|
|
auto invDet = 1.0F / det;
|
|
v *= invDet;
|
|
w *= invDet;
|
|
auto u = 1.0F - v - w;
|
|
|
|
// outPt = u*a + v*d + w*c;
|
|
*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;
|
|
}
|
|
|
|
float lineAABB(const Line &line, const AABB &aabb) {
|
|
Ray ray;
|
|
ray.o.set(line.s);
|
|
ray.d = line.e - line.s;
|
|
ray.d.normalize();
|
|
auto min = rayAABB(ray, aabb);
|
|
return min < line.length() ? min : 0.0F;
|
|
}
|
|
|
|
float lineOBB(const Line &line, const OBB &obb) {
|
|
Ray ray;
|
|
ray.o.set(line.s);
|
|
ray.d = line.e - line.s;
|
|
ray.d.normalize();
|
|
auto min = rayOBB(ray, obb);
|
|
return min < line.length() ? min : 0.0F;
|
|
}
|
|
|
|
float lineSphere(const Line &line, const Sphere &sphere) {
|
|
Ray ray;
|
|
ray.o.set(line.s);
|
|
ray.d = line.e - line.s;
|
|
ray.d.normalize();
|
|
auto min = raySphere(ray, sphere);
|
|
return min < line.length() ? min : 0.0F;
|
|
}
|
|
|
|
bool aabbWithAABB(const AABB &aabb1, const AABB &aabb2) {
|
|
auto aMin = aabb1.getCenter() - aabb1.getHalfExtents();
|
|
auto aMax = aabb1.getCenter() + aabb1.getHalfExtents();
|
|
auto bMin = aabb2.getCenter() - aabb2.getHalfExtents();
|
|
auto bMax = aabb2.getCenter() + aabb2.getHalfExtents();
|
|
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);
|
|
}
|
|
|
|
static void getAABBVertices(const Vec3 &min, const Vec3 &max, ccstd::array<Vec3, 8> *out) {
|
|
*out = {
|
|
Vec3{min.x, max.y, max.z},
|
|
Vec3{min.x, max.y, min.z},
|
|
Vec3{min.x, min.y, max.z},
|
|
Vec3{min.x, min.y, min.z},
|
|
Vec3{max.x, max.y, max.z},
|
|
Vec3{max.x, max.y, min.z},
|
|
Vec3{max.x, min.y, max.z},
|
|
Vec3{max.x, min.y, min.z},
|
|
};
|
|
}
|
|
|
|
static void getOBBVertices(const Vec3 &c, const Vec3 &e,
|
|
const Vec3 &a1, const Vec3 &a2, const Vec3 &a3,
|
|
ccstd::array<Vec3, 8> *out) {
|
|
*out = {Vec3{
|
|
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{
|
|
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{
|
|
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{
|
|
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{
|
|
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{
|
|
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{
|
|
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{
|
|
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}};
|
|
}
|
|
|
|
struct Interval {
|
|
float min;
|
|
float max;
|
|
};
|
|
|
|
static Interval getInterval(const ccstd::array<Vec3, 8> &vertices, const Vec3 &axis) {
|
|
auto min = std::numeric_limits<float>::max();
|
|
auto max = std::numeric_limits<float>::min();
|
|
for (auto i = 0; i < 8; ++i) {
|
|
auto projection = Vec3::dot(axis, vertices[i]);
|
|
min = std::min(projection, min);
|
|
max = std::max(projection, max);
|
|
}
|
|
return Interval{min, max};
|
|
}
|
|
|
|
int aabbWithOBB(const AABB &aabb, const OBB &obb) {
|
|
ccstd::array<Vec3, 15> test;
|
|
ccstd::array<Vec3, 8> vertices;
|
|
ccstd::array<Vec3, 8> vertices2;
|
|
test[0] = {1, 0, 0};
|
|
test[1] = {0, 1, 0};
|
|
test[2] = {0, 0, 1};
|
|
test[3] = {obb.orientation.m[0], obb.orientation.m[1], obb.orientation.m[2]};
|
|
test[4] = {obb.orientation.m[3], obb.orientation.m[4], obb.orientation.m[5]};
|
|
test[5] = {obb.orientation.m[6], obb.orientation.m[7], obb.orientation.m[8]};
|
|
|
|
for (auto i = 0; i < 3; ++i) { // Fill out rest of axis
|
|
Vec3::cross(test[i], test[3], &test[6 + i * 3 + 0]);
|
|
Vec3::cross(test[i], test[4], &test[6 + i * 3 + 1]);
|
|
Vec3::cross(test[i], test[5], &test[6 + i * 3 + 2]);
|
|
}
|
|
|
|
auto min = aabb.getCenter() - aabb.getHalfExtents();
|
|
auto max = aabb.getCenter() + aabb.getHalfExtents();
|
|
getAABBVertices(min, max, &vertices);
|
|
getOBBVertices(obb.center, obb.halfExtents, test[3], test[4], test[5], &vertices2);
|
|
|
|
for (auto j = 0; j < 15; ++j) {
|
|
auto a = getInterval(vertices, test[j]);
|
|
auto b = getInterval(vertices2, test[j]);
|
|
if (b.min > a.max || a.min > b.max) {
|
|
return 0; // Seperating axis found
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int aabbPlane(const AABB &aabb, const Plane &plane) {
|
|
auto r = aabb.getHalfExtents().x * std::abs(plane.n.x) + aabb.getHalfExtents().y * std::abs(plane.n.y) + aabb.getHalfExtents().z * std::abs(plane.n.z);
|
|
auto dot = Vec3::dot(plane.n, aabb.getCenter());
|
|
if (dot + r < plane.d) {
|
|
return -1;
|
|
}
|
|
if (dot - r > plane.d) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int aabbFrustum(const AABB &aabb, const Frustum &frustum) {
|
|
for (const auto &plane : frustum.planes) {
|
|
// frustum plane normal points to the inside
|
|
if (aabbPlane(aabb, *plane) == -1) {
|
|
return 0;
|
|
}
|
|
} // completely outside
|
|
return 1;
|
|
}
|
|
|
|
int aabbFrustumCompletelyInside(const AABB &aabb, const Frustum &frustum) {
|
|
for (const auto &plane : frustum.planes) {
|
|
// frustum plane normal points to the inside
|
|
if (aabbPlane(aabb, *plane) != 0) {
|
|
return 0;
|
|
}
|
|
} // completely inside
|
|
return 1;
|
|
}
|
|
|
|
// https://cesium.com/blog/2017/02/02/tighter-frustum-culling-and-why-you-may-want-to-disregard-it/
|
|
int aabbFrustumAccurate(const AABB &aabb, const Frustum &frustum) {
|
|
ccstd::array<Vec3, 8> tmp;
|
|
int out1 = 0;
|
|
int out2 = 0;
|
|
int result = 0;
|
|
bool intersects = false;
|
|
// 1. aabb inside/outside frustum test
|
|
for (const auto &plane : frustum.planes) {
|
|
result = aabbPlane(aabb, *plane);
|
|
// frustum plane normal points to the inside
|
|
if (result == -1) {
|
|
return 0; // completely outside
|
|
}
|
|
if (result == 1) {
|
|
intersects = true;
|
|
}
|
|
}
|
|
if (!intersects) {
|
|
return 1;
|
|
} // completely inside
|
|
// in case of false positives
|
|
// 2. frustum inside/outside aabb test
|
|
for (auto i = 0; i < frustum.vertices.size(); i++) {
|
|
tmp[i] = frustum.vertices[i] - aabb.getCenter();
|
|
}
|
|
out1 = 0;
|
|
out2 = 0;
|
|
for (auto i = 0; i < frustum.vertices.size(); i++) {
|
|
if (tmp[i].x > aabb.getHalfExtents().x) {
|
|
out1++;
|
|
} else if (tmp[i].x < -aabb.getHalfExtents().x) {
|
|
out2++;
|
|
}
|
|
}
|
|
if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) {
|
|
return 0;
|
|
}
|
|
out1 = 0;
|
|
out2 = 0;
|
|
for (auto i = 0; i < frustum.vertices.size(); i++) {
|
|
if (tmp[i].y > aabb.getHalfExtents().y) {
|
|
out1++;
|
|
} else if (tmp[i].y < -aabb.getHalfExtents().y) {
|
|
out2++;
|
|
}
|
|
}
|
|
if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) {
|
|
return 0;
|
|
}
|
|
out1 = 0;
|
|
out2 = 0;
|
|
for (auto i = 0; i < frustum.vertices.size(); i++) {
|
|
if (tmp[i].z > aabb.getHalfExtents().z) {
|
|
out1++;
|
|
} else if (tmp[i].z < -aabb.getHalfExtents().z) {
|
|
out2++;
|
|
}
|
|
}
|
|
if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
bool obbPoint(const OBB &obb, const Vec3 &point) {
|
|
Mat3 m3;
|
|
auto lessThan = [](const Vec3 &a, const Vec3 &b) -> bool {
|
|
return std::abs(a.x) < b.x && std::abs(a.y) < b.y && std::abs(a.z) < b.z;
|
|
};
|
|
auto tmp = point - obb.center;
|
|
Mat3::transpose(obb.orientation, &m3);
|
|
tmp.transformMat3(tmp, m3);
|
|
return lessThan(tmp, obb.halfExtents);
|
|
};
|
|
|
|
int obbPlane(const OBB &obb, const Plane &plane) {
|
|
auto absDot = [](const Vec3 &n, float x, float y, float z) -> float {
|
|
return std::abs(n.x * x + n.y * y + n.z * z);
|
|
};
|
|
|
|
// Real-Time Collision Detection, Christer Ericson, p. 163.
|
|
auto r = obb.halfExtents.x * absDot(plane.n, obb.orientation.m[0], obb.orientation.m[1], obb.orientation.m[2]) +
|
|
obb.halfExtents.y * absDot(plane.n, obb.orientation.m[3], obb.orientation.m[4], obb.orientation.m[5]) +
|
|
obb.halfExtents.z * absDot(plane.n, obb.orientation.m[6], obb.orientation.m[7], obb.orientation.m[8]);
|
|
|
|
auto dot = Vec3::dot(plane.n, obb.center);
|
|
if (dot + r < plane.d) {
|
|
return -1;
|
|
}
|
|
if (dot - r > plane.d) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int obbFrustum(const OBB &obb, const Frustum &frustum) {
|
|
for (const auto &plane : frustum.planes) {
|
|
// frustum plane normal points to the inside
|
|
if (obbPlane(obb, *plane) == -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/
|
|
int obbFrustumAccurate(const OBB &obb, const Frustum &frustum) {
|
|
ccstd::array<Vec3, 8> tmp = {};
|
|
float dist = 0.0F;
|
|
size_t out1 = 0;
|
|
size_t out2 = 0;
|
|
auto dot = [](const Vec3 &n, float x, float y, float z) -> float {
|
|
return n.x * x + n.y * y + n.z * z;
|
|
};
|
|
int result = 0;
|
|
auto intersects = false;
|
|
// 1. obb inside/outside frustum test
|
|
for (const auto &plane : frustum.planes) {
|
|
result = obbPlane(obb, *plane);
|
|
// frustum plane normal points to the inside
|
|
if (result == -1) {
|
|
return 0; // completely outside
|
|
}
|
|
if (result == 1) {
|
|
intersects = true;
|
|
}
|
|
}
|
|
if (!intersects) {
|
|
return 1;
|
|
} // completely inside
|
|
// in case of false positives
|
|
// 2. frustum inside/outside obb test
|
|
for (auto i = 0; i < frustum.vertices.size(); i++) {
|
|
tmp[i] = frustum.vertices[i] - obb.center;
|
|
}
|
|
out1 = 0;
|
|
out2 = 0;
|
|
for (auto i = 0; i < frustum.vertices.size(); i++) {
|
|
dist = dot(tmp[i], obb.orientation.m[0], obb.orientation.m[1], obb.orientation.m[2]);
|
|
if (dist > obb.halfExtents.x) {
|
|
out1++;
|
|
} else if (dist < -obb.halfExtents.x) {
|
|
out2++;
|
|
}
|
|
}
|
|
if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) {
|
|
return 0.0F;
|
|
}
|
|
out1 = 0;
|
|
out2 = 0;
|
|
for (auto i = 0; i < frustum.vertices.size(); i++) {
|
|
dist = dot(tmp[i], obb.orientation.m[3], obb.orientation.m[4], obb.orientation.m[5]);
|
|
if (dist > obb.halfExtents.y) {
|
|
out1++;
|
|
} else if (dist < -obb.halfExtents.y) {
|
|
out2++;
|
|
}
|
|
}
|
|
if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) {
|
|
return 0;
|
|
}
|
|
out1 = 0;
|
|
out2 = 0;
|
|
for (auto i = 0; i < frustum.vertices.size(); i++) {
|
|
dist = dot(tmp[i], obb.orientation.m[6], obb.orientation.m[7], obb.orientation.m[8]);
|
|
if (dist > obb.halfExtents.z) {
|
|
out1++;
|
|
} else if (dist < -obb.halfExtents.z) {
|
|
out2++;
|
|
}
|
|
}
|
|
if (out1 == frustum.vertices.size() || out2 == frustum.vertices.size()) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int obbWithOBB(const OBB &obb1, const OBB &obb2) {
|
|
ccstd::array<Vec3, 8> vertices;
|
|
ccstd::array<Vec3, 8> vertices2;
|
|
ccstd::array<Vec3, 15> test;
|
|
|
|
test[0] = {obb1.orientation.m[0], obb1.orientation.m[1], obb1.orientation.m[2]};
|
|
test[1] = {obb1.orientation.m[3], obb1.orientation.m[4], obb1.orientation.m[5]};
|
|
test[2] = {obb1.orientation.m[6], obb1.orientation.m[7], obb1.orientation.m[8]};
|
|
test[3] = {obb2.orientation.m[0], obb2.orientation.m[1], obb2.orientation.m[2]};
|
|
test[4] = {obb2.orientation.m[3], obb2.orientation.m[4], obb2.orientation.m[5]};
|
|
test[5] = {obb2.orientation.m[6], obb2.orientation.m[7], obb2.orientation.m[8]};
|
|
|
|
for (auto i = 0; i < 3; ++i) { // Fill out rest of axis
|
|
Vec3::cross(test[i], test[3], &test[6 + i * 3 + 0]);
|
|
Vec3::cross(test[i], test[4], &test[6 + i * 3 + 1]);
|
|
Vec3::cross(test[i], test[5], &test[6 + i * 3 + 2]);
|
|
}
|
|
|
|
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 (auto i = 0; i < 15; ++i) {
|
|
auto a = getInterval(vertices, test[i]);
|
|
auto b = getInterval(vertices2, test[i]);
|
|
if (b.min > a.max || a.min > b.max) {
|
|
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
|
|
int obbCapsule(const OBB &obb, const Capsule &capsule) {
|
|
Sphere sphere{};
|
|
ccstd::array<Vec3, 8> v3Verts8{};
|
|
ccstd::array<Vec3, 8> v3Axis8{};
|
|
auto h = capsule.ellipseCenter0.distanceSquared(capsule.ellipseCenter1);
|
|
if (h == 0.0F) {
|
|
sphere.setRadius(capsule.radius);
|
|
sphere.setCenter(capsule.ellipseCenter0);
|
|
return sphereOBB(sphere, obb);
|
|
}
|
|
Vec3 v3Tmp0 = {
|
|
obb.orientation.m[0],
|
|
obb.orientation.m[1],
|
|
obb.orientation.m[2]};
|
|
Vec3 v3Tmp1 = {
|
|
obb.orientation.m[3],
|
|
obb.orientation.m[4],
|
|
obb.orientation.m[5]};
|
|
Vec3 v3Tmp2 = {
|
|
obb.orientation.m[6],
|
|
obb.orientation.m[7],
|
|
obb.orientation.m[8]};
|
|
getOBBVertices(obb.center, obb.halfExtents, v3Tmp0, v3Tmp1, v3Tmp2, &v3Verts8);
|
|
|
|
auto axes = v3Axis8;
|
|
auto a0 = axes[0] = v3Tmp0;
|
|
auto a1 = axes[1] = v3Tmp1;
|
|
auto a2 = axes[2] = v3Tmp2;
|
|
auto cc = axes[3] = capsule.center - obb.center;
|
|
auto bb = axes[4] = capsule.ellipseCenter0 - capsule.ellipseCenter1;
|
|
cc.normalize();
|
|
bb.normalize();
|
|
Vec3::cross(a0, bb, &axes[5]);
|
|
Vec3::cross(a1, bb, &axes[6]);
|
|
Vec3::cross(a2, bb, &axes[7]);
|
|
|
|
for (auto i = 0; i < 8; ++i) {
|
|
auto a = getInterval(v3Verts8, axes[i]);
|
|
auto d0 = Vec3::dot(axes[i], capsule.ellipseCenter0);
|
|
auto d1 = Vec3::dot(axes[i], capsule.ellipseCenter1);
|
|
auto dMin = std::min(d0, d1) - capsule.radius;
|
|
auto dMax = std::max(d0, d1) + capsule.radius;
|
|
if (dMin > a.max || a.min > dMax) {
|
|
return 0; // Seperating axis found
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int spherePlane(const Sphere &sphere, const Plane &plane) {
|
|
auto dot = Vec3::dot(plane.n, sphere.getCenter());
|
|
auto r = sphere.getRadius() * plane.n.length();
|
|
if (dot + r < plane.d) {
|
|
return -1;
|
|
}
|
|
if (dot - r > plane.d) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int sphereFrustum(const Sphere &sphere, const Frustum &frustum) {
|
|
for (const auto &plane : frustum.planes) {
|
|
// frustum plane normal points to the inside
|
|
if (spherePlane(sphere, *plane) == -1) {
|
|
return 0;
|
|
}
|
|
} // completely outside
|
|
return 1;
|
|
}
|
|
|
|
int sphereFrustumAccurate(const Sphere &sphere, const Frustum &frustum) {
|
|
const static ccstd::array<int, 6> MAP = {1, -1, 1, -1, 1, -1};
|
|
for (auto i = 0; i < 6; i++) {
|
|
const auto &plane = frustum.planes[i];
|
|
const auto &n = plane->n;
|
|
const auto &d = plane->d;
|
|
auto r = sphere.getRadius();
|
|
const auto &c = sphere.getCenter();
|
|
auto dot = Vec3::dot(n, c);
|
|
// frustum plane normal points to the inside
|
|
if (dot + r < d) {
|
|
return 0; // completely outside
|
|
}
|
|
if (dot - r > d) {
|
|
continue;
|
|
}
|
|
// in case of false positives
|
|
// has false negatives, still working on it
|
|
auto pt = c + n * r;
|
|
for (auto j = 0; j < 6; j++) {
|
|
if (j == i || j == i + MAP[i]) {
|
|
continue;
|
|
}
|
|
const auto &test = frustum.planes[j];
|
|
if (Vec3::dot(test->n, pt) < test->d) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
bool sphereWithSphere(const Sphere &sphere0, const Sphere &sphere1) {
|
|
auto r = sphere0.getRadius() + sphere1.getRadius();
|
|
return sphere0.getCenter().distanceSquared(sphere1.getCenter()) < r * r;
|
|
}
|
|
|
|
bool sphereAABB(const Sphere &sphere, const AABB &aabb) {
|
|
Vec3 pt;
|
|
ptPointAabb(&pt, sphere.getCenter(), aabb);
|
|
return sphere.getCenter().distanceSquared(pt) < sphere.getRadius() * sphere.getRadius();
|
|
}
|
|
|
|
bool sphereOBB(const Sphere &sphere, const OBB &obb) {
|
|
Vec3 pt;
|
|
ptPointObb(&pt, sphere.getCenter(), obb);
|
|
return sphere.getCenter().distanceSquared(pt) < sphere.getRadius() * sphere.getRadius();
|
|
}
|
|
|
|
bool sphereCapsule(const Sphere &sphere, const Capsule &capsule) {
|
|
auto r = sphere.getRadius() + capsule.radius;
|
|
auto squaredR = r * r;
|
|
auto h = capsule.ellipseCenter0.distanceSquared(capsule.ellipseCenter1);
|
|
if (h == 0.0F) {
|
|
return sphere.getCenter().distanceSquared(capsule.center) < squaredR;
|
|
}
|
|
auto v3Tmp0 = sphere.getCenter() - capsule.ellipseCenter0;
|
|
auto v3Tmp1 = capsule.ellipseCenter1 - capsule.ellipseCenter0;
|
|
auto t = Vec3::dot(v3Tmp0, v3Tmp1) / h;
|
|
if (t < 0) {
|
|
return sphere.getCenter().distanceSquared(capsule.ellipseCenter0) < squaredR;
|
|
}
|
|
if (t > 1) {
|
|
return sphere.getCenter().distanceSquared(capsule.ellipseCenter1) < squaredR;
|
|
}
|
|
v3Tmp0 = capsule.ellipseCenter0 + t * v3Tmp1;
|
|
return sphere.getCenter().distanceSquared(v3Tmp0) < squaredR;
|
|
}
|
|
|
|
bool capsuleWithCapsule(const Capsule &capsuleA, const Capsule &capsuleB) {
|
|
auto u = capsuleA.ellipseCenter1 - capsuleA.ellipseCenter0;
|
|
auto v = capsuleB.ellipseCenter1 - capsuleB.ellipseCenter0;
|
|
auto w = capsuleA.ellipseCenter0 - capsuleB.ellipseCenter0;
|
|
auto a = Vec3::dot(u, u); // always >= 0
|
|
auto b = Vec3::dot(u, v);
|
|
auto c = Vec3::dot(v, v); // always >= 0
|
|
auto d = Vec3::dot(u, w);
|
|
auto e = Vec3::dot(v, w);
|
|
auto dd = a * c - b * b; // always >= 0
|
|
float sN;
|
|
float sD = dd; // sc = sN / sD, default sD = dd >= 0
|
|
float tN;
|
|
float tD = dd; // tc = tN / tD, default tD = dd >= 0
|
|
|
|
// compute the line parameters of the two closest points
|
|
if (dd < math::EPSILON) { // the lines are almost parallel
|
|
sN = 0.0F; // force using point P0 on segment S1
|
|
sD = 1.0F; // 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.0F) { // sc < 0 => the s=0 edge is visible
|
|
sN = 0.0F;
|
|
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.0F) { // tc < 0 => the t=0 edge is visible
|
|
tN = 0.0F;
|
|
// recompute sc for this edge
|
|
if (-d < 0.0F) {
|
|
sN = 0.0F;
|
|
} 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.0F) {
|
|
sN = 0.0F;
|
|
} else if ((-d + b) > a) {
|
|
sN = sD;
|
|
} else {
|
|
sN = (-d + b);
|
|
sD = a;
|
|
}
|
|
}
|
|
// finally do the division to get sc and tc
|
|
auto sc = (std::abs(sN) < math::EPSILON ? 0.0F : sN / sD);
|
|
auto tc = (std::abs(tN) < math::EPSILON ? 0.0F : tN / tD);
|
|
|
|
// get the difference of the two closest points
|
|
auto dP = w;
|
|
dP = dP + u * sc;
|
|
dP = dP - v * tc;
|
|
auto radius = capsuleA.radius + capsuleB.radius;
|
|
return dP.lengthSquared() < radius * radius;
|
|
}
|
|
|
|
int dynObbFrustum(const OBB &obb, const Frustum &frustum) {
|
|
if (frustum.getType() == ShapeEnum::SHAPE_FRUSTUM_ACCURATE) {
|
|
return obbFrustumAccurate(obb, frustum);
|
|
}
|
|
return obbFrustum(obb, frustum);
|
|
}
|
|
|
|
int dynSphereFrustum(const Sphere &sphere, const Frustum &frustum) {
|
|
if (frustum.getType() == ShapeEnum::SHAPE_FRUSTUM_ACCURATE) {
|
|
return sphereFrustumAccurate(sphere, frustum);
|
|
}
|
|
return sphereFrustum(sphere, frustum);
|
|
}
|
|
|
|
int dynAabbFrustum(const AABB &aabb, const Frustum &frustum) {
|
|
if (frustum.getType() == ShapeEnum::SHAPE_FRUSTUM_ACCURATE) {
|
|
return aabbFrustumAccurate(aabb, frustum);
|
|
}
|
|
return aabbFrustum(aabb, frustum);
|
|
}
|
|
|
|
} // namespace geometry
|
|
} // namespace cc
|
|
|