Files
axyz/src/Engine/Collision/Collision.cpp
T

445 lines
15 KiB
C++

#include <algorithm>
#include "Collision/Collision.h"
#include "Engine/GLM.h"
#include "Core/World.h"
#include "Rendering/Model.h"
#include "imgui/imgui.h"
namespace Collision
{
//note: this one hasnt been delta adjusted like RayVsAABB has
bool RayAABBIntr(const Ray& ray, const AABB& box)
{
glm::vec3 w = 75.0f * ray.Direction();
glm::vec3 v = glm::abs(w);
glm::vec3 c = ray.Origin() - box.Origin() + w;
glm::vec3 half = box.HalfSize();
if (abs(c.x) > v.x + half.x) {
return false;
}
if (abs(c.y) > v.y + half.y) {
return false;
}
if (abs(c.z) > v.z + half.z) {
return false;
}
if (abs(c.y*w.z - c.z*w.y) > half.y*v.z + half.z*v.y) {
return false;
}
if (abs(c.x*w.z - c.z*w.x) > half.x*v.z + half.z*v.x) {
return false;
}
return !(abs(c.x*w.y - c.y*w.x) > half.x*v.y + half.y*v.x);
}
bool RayVsAABB(const Ray& ray, const AABB& box)
{
float dummy;
return RayVsAABB(ray, box, dummy);
}
bool RayVsAABB(const Ray& ray, const AABB& box, float& outDistance)
{
glm::vec3 invdir = 1.0f / ray.Direction();
glm::vec3 origin = ray.Origin();
float t1 = (box.MinCorner().x - origin.x)*invdir.x;
float t2 = (box.MaxCorner().x - origin.x)*invdir.x;
float t3 = (box.MinCorner().y - origin.y)*invdir.y;
float t4 = (box.MaxCorner().y - origin.y)*invdir.y;
float t5 = (box.MinCorner().z - origin.z)*invdir.z;
float t6 = (box.MaxCorner().z - origin.z)*invdir.z;
float tmin = std::max(std::max(std::min(t1, t2), std::min(t3, t4)), std::min(t5, t6));
float tmax = std::min(std::min(std::max(t1, t2), std::max(t3, t4)), std::max(t5, t6));
//if (tmax < 0 || tmin > tmax)
//if tmin,tmax are almost the same (i.e. hitting exactly in the corner) then tmin might be slightly
//greater than tmax becuase of floating-precision problems. fixed by adding a small delta to tmax
if (tmax < 0 || tmin>(tmax + 0.0001f))
return false;
outDistance = (tmin > 0) ? tmin : tmax;
return true;
}
bool AABBVsAABB(const AABB& a, const AABB& b)
{
const glm::vec3& aCenter = a.Origin();
const glm::vec3& bCenter = b.Origin();
const glm::vec3& aHSize = a.HalfSize();
const glm::vec3& bHSize = b.HalfSize();
//Test will probably exit because of the X and Z axes more often, so test them first.
if (abs(aCenter[0] - bCenter[0]) > (aHSize[0] + bHSize[0])) {
return false;
}
if (abs(aCenter[2] - bCenter[2]) > (aHSize[2] + bHSize[2])) {
return false;
}
return (abs(aCenter[1] - bCenter[1]) <= (aHSize[1] + bHSize[1]));
}
bool AABBVsAABB(const AABB& a, const AABB& b, glm::vec3& minimumTranslation)
{
minimumTranslation = glm::vec3(0, 0, 0);
const glm::vec3& aMax = a.MaxCorner();
const glm::vec3& bMax = b.MaxCorner();
const glm::vec3& aMin = a.MinCorner();
const glm::vec3& bMin = b.MinCorner();
const glm::vec3& bSize = b.Size();
const glm::vec3& aSize = a.Size();
float minOffset = INFINITY;
float off;
auto axisesIntersecting = glm::tvec3<bool, glm::highp>(false, false, false);
for (int i = 0; i < 3; ++i) {
off = bMax[i] - aMin[i];
if (off > 0 && off < bSize[i] + aSize[i]) {
if (off < minOffset) {
minimumTranslation = glm::vec3();
minimumTranslation[i] = minOffset = off;
}
axisesIntersecting[i] = true;
}
off = aMax[i] - bMin[i];
if (off > 0 && off < bSize[i] + aSize[i]) {
if (off < minOffset) {
minOffset = off;
minimumTranslation = glm::vec3();
minimumTranslation[i] = -off;
}
axisesIntersecting[i] = true;
}
}
return glm::all(axisesIntersecting);
}
bool RayVsTriangle(const Ray& ray,
const glm::vec3& v0,
const glm::vec3& v1,
const glm::vec3& v2,
bool trueOnNegativeDistance)
{
glm::vec3 e1 = v1 - v0; //v1 - v0
glm::vec3 e2 = v2 - v0; //v2 - v0
glm::vec3 m = ray.Origin() - v0;
glm::vec3 MxE1 = glm::cross(m, e1);
glm::vec3 DxE2 = glm::cross(ray.Direction(), e2);
float DetInv = glm::dot(e1, DxE2);
if (std::abs(DetInv) < FLT_EPSILON) {
return false;
}
DetInv = 1.0f / DetInv;
float u = glm::dot(m, DxE2) * DetInv;
float v = glm::dot(ray.Direction(), MxE1) * DetInv;
//u,v can be very close to 0 but still negative sometimes. added a deltafactor to compensate for that problem
if ((u + 0.001f) < 0 || (v + 0.001f) < 0 || 1 < u + v) {
return false;
}
//Here, u and v are positive, u+v <= 1, and if distance is positive - triangle is hit.
return trueOnNegativeDistance || 0 <= glm::dot(e2, MxE1) * DetInv;
}
bool RayVsModel(const Ray& ray,
const std::vector<RawModel::Vertex>& modelVertices,
const std::vector<unsigned int>& modelIndices)
{
for (int i = 0; i < modelIndices.size(); ++i) {
glm::vec3 v0 = modelVertices[modelIndices[i]].Position;
glm::vec3 v1 = modelVertices[modelIndices[++i]].Position;
glm::vec3 v2 = modelVertices[modelIndices[++i]].Position;
if (RayVsTriangle(ray, v0, v1, v2)) {
return true;
}
}
return false;
}
bool RayVsTriangle(const Ray& ray,
const glm::vec3& v0,
const glm::vec3& v1,
const glm::vec3& v2,
float& outDistance,
float& outUCoord,
float& outVCoord,
bool trueOnNegativeDistance)
{
glm::vec3 e1 = v1 - v0; //v1 - v0
glm::vec3 e2 = v2 - v0; //v2 - v0
glm::vec3 m = ray.Origin() - v0;
glm::vec3 MxE1 = glm::cross(m, e1);
glm::vec3 DxE2 = glm::cross(ray.Direction(), e2);//pVec
float DetInv = glm::dot(e1, DxE2);
if (std::abs(DetInv) < FLT_EPSILON) {
return false;
}
DetInv = 1.0f / DetInv;
float dist = glm::dot(e2, MxE1) * DetInv;
if (dist >= outDistance) {
return false;
}
outDistance = dist;
outUCoord = glm::dot(m, DxE2) * DetInv;
outVCoord = glm::dot(ray.Direction(), MxE1) * DetInv;
//u,v can be very close to 0 but still negative sometimes. added a deltafactor to compensate for that problem
//If u and v are positive, u+v <= 1, dist is positive, and less than closest.
return (0 <= (outUCoord + 0.001f) && 0 <= (outVCoord + 0.001f) && outUCoord + outVCoord <= 1 && (trueOnNegativeDistance || 0 <= dist));
}
bool RayVsModel(const Ray& ray,
const std::vector<RawModel::Vertex>& modelVertices,
const std::vector<unsigned int>& modelIndices,
float& outDistance,
float& outUCoord,
float& outVCoord)
{
outDistance = INFINITY;
bool hit = false;
for (int i = 0; i < modelIndices.size(); ++i) {
glm::vec3 v0 = modelVertices[modelIndices[i]].Position;
glm::vec3 v1 = modelVertices[modelIndices[++i]].Position;
glm::vec3 v2 = modelVertices[modelIndices[++i]].Position;
float dist;
float u;
float v;
if (RayVsTriangle(ray, v0, v1, v2, dist, u, v)) {
outDistance = dist;
outUCoord = u;
outVCoord = v;
hit = true;
}
}
return hit;
}
bool RayVsModel(const Ray& ray,
const std::vector<RawModel::Vertex>& modelVertices,
const std::vector<unsigned int>& modelIndices,
glm::vec3& outHitPosition)
{
float u;
float v;
float dist;
bool hit = RayVsModel(ray, modelVertices, modelIndices, dist, u, v);
outHitPosition = ray.Origin() + dist * ray.Direction();
return hit;
}
constexpr inline int signNonZero(float x)
{
return x < 0 ? -1 : 1;
}
inline glm::vec3 signNonZero(const glm::vec3& x)
{
glm::vec3 r;
for (int i = 0; i < 3; ++i) {
r[i] = signNonZero(x[i]);
}
return r;
}
bool vectorHasLength(const glm::vec3& vec)
{
return glm::any(glm::greaterThan(glm::abs(vec), glm::vec3(0.0001f, 0.0001f, 0.0001f)));
}
enum BoxTriHit
{
Line0 = 0,
Line1,
Line2,
Ground,
Corner
};
bool AABBvsTriangle(const AABB& box,
const glm::vec3& v0,
const glm::vec3& v1,
const glm::vec3& v2,
glm::vec3& outVector,
BoxTriHit& outHit)
{
//Check so we don't have a zero area triangle when calculating the normal.
glm::vec3 triNormal = glm::cross(v1 - v0, v2 - v0);
if (!vectorHasLength(triNormal)) {
return false;
}
const glm::vec3& origin = box.Origin();
const glm::vec3& half = box.HalfSize();
const glm::vec3& min = box.MinCorner();
const glm::vec3& max = box.MaxCorner();
const glm::vec3 triPos[] = {
v0, v1, v2
};
for (int axis : {1, 0, 2}) {
//2D Triangle.
//for axis=0,1,2: 2d point takes from xy,xz,yx.
int dim1 = axis == 2 ? 0 : 1; //0,0,1
int dim2 = axis == 0 ? 1 : 2; //1,2,2
glm::vec2 t0(triPos[0][dim1], triPos[0][dim2]);
glm::vec2 t1(triPos[1][dim1], triPos[1][dim2]);
glm::vec2 t2(triPos[2][dim1], triPos[2][dim2]);
//Project tri,
//Project box,
//if projections don't overlap, return false.
}
//If the polygon does intersect any of the cube diagonals, it will
//intersect the cube diagonal that comes
//closest to being perpendicular to the plane of the polygon.
triNormal = glm::normalize(triNormal);
glm::vec3 diagonal = -signNonZero(triNormal) * half;
//The triangle plane contains all points P in dot(triNormal, P) == dot(triNormal, v0)
//The diagonal line contains all points P in P = origin + diagonal * t.
float t = glm::dot(triNormal, v0 - origin) / glm::dot(triNormal, diagonal);
//If intersection point between plane and diagonal is within the box.
if (glm::abs(t) > 1) {
return false;
}
//TODO: Resolve it.
return true;
}
bool AABBvsTriangles(const AABB& box, const std::vector<RawModel::Vertex>& modelVertices, const std::vector<unsigned int>& modelIndices, const glm::mat4& modelMatrix, glm::vec3& outResolutionVector)
{
AABB newBox = box;
struct Triangle
{
glm::vec3 v0, v1, v2;
};
bool hit = false;
bool cornerHitTODO = false;
outResolutionVector = glm::vec3(0.f);
std::vector<Triangle> hitTriangles;
std::vector<glm::vec3> hitNormals;
for (int i = 0; i < modelIndices.size(); ) {
glm::vec3 outVec;
glm::vec3 v0 = Transform::TransformPoint(modelVertices[modelIndices[i++]].Position, modelMatrix);
glm::vec3 v1 = Transform::TransformPoint(modelVertices[modelIndices[i++]].Position, modelMatrix);
glm::vec3 v2 = Transform::TransformPoint(modelVertices[modelIndices[i++]].Position, modelMatrix);
BoxTriHit hitCase;
if (AABBvsTriangle(newBox, v0, v1, v2, outVec, hitCase)) {
hit = true;
switch (hitCase) {
case Collision::Line0:
case Collision::Line1:
case Collision::Line2:
//TODO: Resolve.
//const glm::vec3 triPos[] = {
// v0, v1, v2
//};
//glm::vec3 edge = triPos[(hitCase + 1) % 3] - triPos[hitCase];
hitTriangles.push_back({ v0, v1, v2 });
hitNormals.push_back(outVec);
ImGui::Text("triangle edge collision.");
break;
case Collision::Corner:
//TODO: We might be able to return here instead, having only convex geometry.
cornerHitTODO = true;
//outResolutionVector += outVec;
//return true;
ImGui::Text("triangle corner collision.");
outResolutionVector += outVec;
newBox = AABB::FromOriginSize(newBox.Origin() + outVec, newBox.Size());
break;
case Collision::Ground:
default:
outResolutionVector += outVec;
newBox = AABB::FromOriginSize(newBox.Origin() + outVec, newBox.Size());
ImGui::Text("triangle ground collision.");
break;
}
}
}
if (hitTriangles.size() > 0) {
if (cornerHitTODO) {
ImGui::Text("Both edges and corners was hit on the same model.");
return true;
}
glm::vec3 lineResolve(0.f);
for (const glm::vec3& norm : hitNormals) {
lineResolve += norm;
}
//Normalize.
lineResolve /= hitNormals.size();
const glm::vec3& origin = newBox.Origin();
const glm::vec3& half = newBox.HalfSize();
float maxDist = -10;
for (const Triangle& tri : hitTriangles) {
float d = glm::dot(lineResolve, 0.333f * (tri.v0 + tri.v1 + tri.v2) - origin);
maxDist = std::max(maxDist, d);
}
glm::vec3 tmp = glm::clamp(2.0f * lineResolve, glm::vec3(-1, -1, -1), glm::vec3(1, 1, 1));
outResolutionVector += lineResolve * (maxDist + glm::length(tmp * half));
}
return hit;
}
bool IsSameBoxProbably(const AABB& first, const AABB& second, const float epsilon)
{
const glm::vec3& ma1 = first.MaxCorner();
const glm::vec3& ma2 = second.MaxCorner();
const glm::vec3& mi1 = first.MinCorner();
const glm::vec3& mi2 = second.MinCorner();
return (std::abs(ma1.x - ma2.x) < epsilon) &&
(std::abs(mi1.x - mi2.x) < epsilon) &&
(std::abs(ma1.z - ma2.z) < epsilon) &&
(std::abs(mi1.z - mi2.z) < epsilon) &&
(std::abs(ma1.y - ma2.y) < epsilon) &&
(std::abs(mi1.y - mi2.y) < epsilon);
}
bool attachAABBComponentFromModel(World* world, EntityID id)
{
if (!world->HasComponent(id, "Model")) {
return false;
}
ComponentWrapper model = world->GetComponent(id, "Model");
ComponentWrapper collision = world->AttachComponent(id, "AABB");
Model* modelRes = ResourceManager::Load<Model>(model["Resource"]);
if (modelRes == nullptr) {
return false;
}
glm::mat4 modelMatrix = modelRes->Matrix();
glm::vec3 mini = glm::vec3(INFINITY, INFINITY, INFINITY);
glm::vec3 maxi = glm::vec3(-INFINITY, -INFINITY, -INFINITY);
for (const auto& v : modelRes->Vertices()) {
const auto& wPos = modelMatrix * glm::vec4(v.Position.x, v.Position.y, v.Position.z, 1);
maxi.x = std::max(wPos.x, maxi.x);
maxi.y = std::max(wPos.y, maxi.y);
maxi.z = std::max(wPos.z, maxi.z);
mini.x = std::min(wPos.x, mini.x);
mini.y = std::min(wPos.y, mini.y);
mini.z = std::min(wPos.z, mini.z);
}
collision["Origin"] = 0.5f * (maxi + mini);
collision["Size"] = maxi - mini;
return true;
}
boost::optional<AABB> EntityAbsoluteAABB(EntityWrapper& entity)
{
if (!entity.HasComponent("AABB")) {
return boost::none;
}
ComponentWrapper& cAABB = entity["AABB"];
glm::vec3 absPosition = Transform::AbsolutePosition(entity.World, entity.ID);
glm::vec3 absScale = Transform::AbsoluteScale(entity.World, entity.ID);
glm::vec3 origin = absPosition + (glm::vec3)cAABB["Origin"];
glm::vec3 size = (glm::vec3)cAABB["Size"] * absScale;
return AABB::FromOriginSize(origin, size);
}
}