shooter/engine/physics/RigidBody.cpp

361 lines
10 KiB
C++
Raw Normal View History

2021-09-13 15:53:43 +03:00
//
// Created by Иван Ильин on 05.02.2021.
//
2021-10-31 11:39:08 +03:00
#include <cmath>
#include <utility>
2021-09-13 15:53:43 +03:00
#include "RigidBody.h"
#include "../utils/Log.h"
#include "../utils/Time.h"
2021-10-28 16:58:02 +03:00
#include "../Consts.h"
2021-10-31 11:39:08 +03:00
RigidBody::RigidBody(ObjectNameTag nameTag, const std::string &filename, const Vec3D &scale) : Mesh(std::move(nameTag),
filename, scale),
_hitBox(*this) {
2021-10-28 16:58:02 +03:00
}
2021-09-13 15:53:43 +03:00
RigidBody::RigidBody(const Mesh &mesh) : Mesh(mesh), _hitBox(mesh) {
}
2021-10-31 11:39:08 +03:00
Vec3D RigidBody::_findFurthestPoint(const Vec3D &direction) {
2021-10-28 16:58:02 +03:00
Vec3D maxPoint{0, 0, 0};
double maxDistance = -std::numeric_limits<double>::max();
Vec3D transformedDirection = (view() * direction).normalized();
for(auto & it : _hitBox) {
2021-11-04 04:30:58 +03:00
double distance = it.dot(transformedDirection);
if (distance > maxDistance) {
maxDistance = distance;
2021-11-04 04:30:58 +03:00
maxPoint = it;
}
}
return model() * maxPoint + position();
2021-09-13 15:53:43 +03:00
}
2021-10-31 11:39:08 +03:00
Vec3D RigidBody::_support(std::shared_ptr<RigidBody> obj, const Vec3D &direction) {
Vec3D p1 = _findFurthestPoint(direction);
Vec3D p2 = obj->_findFurthestPoint(-direction);
Vec3D res = p1 - p2;
2021-09-13 15:53:43 +03:00
return p1 - p2;
}
NextSimplex RigidBody::_nextSimplex(const Simplex &points) {
switch (points.type()) {
2021-10-31 11:39:08 +03:00
case SimplexType::Line:
return _lineCase(points);
case SimplexType::Triangle:
return _triangleCase(points);
case SimplexType::Tetrahedron:
return _tetrahedronCase(points);
default:
throw std::logic_error{"RigidBody::_nextSimplex: simplex is not Line, Triangle or Tetrahedron"};
}
2021-09-13 15:53:43 +03:00
}
2021-10-31 11:39:08 +03:00
NextSimplex RigidBody::_lineCase(const Simplex &points) {
2021-10-28 16:58:02 +03:00
Simplex newPoints(points);
Vec3D newDirection;
2021-09-13 15:53:43 +03:00
Vec3D a = points[0];
Vec3D b = points[1];
Vec3D ab = b - a;
2021-10-31 11:39:08 +03:00
Vec3D ao = -a;
2021-09-13 15:53:43 +03:00
if (ab.dot(ao) > 0) {
2021-10-28 16:58:02 +03:00
newDirection = ab.cross(ao).cross(ab);
2021-09-13 15:53:43 +03:00
} else {
2021-10-28 16:58:02 +03:00
newPoints = Simplex{a};
newDirection = ao;
2021-09-13 15:53:43 +03:00
}
2021-10-28 16:58:02 +03:00
return NextSimplex{newPoints, newDirection, false};
2021-09-13 15:53:43 +03:00
}
NextSimplex RigidBody::_triangleCase(const Simplex &points) {
2021-10-28 16:58:02 +03:00
Simplex newPoints(points);
Vec3D newDirection;
Vec3D a = points[0];
Vec3D b = points[1];
Vec3D c = points[2];
2021-09-13 15:53:43 +03:00
Vec3D ab = b - a;
Vec3D ac = c - a;
2021-10-31 11:39:08 +03:00
Vec3D ao = -a;
2021-09-13 15:53:43 +03:00
Vec3D abc = ab.cross(ac);
2021-09-13 15:53:43 +03:00
if (abc.cross(ac).dot(ao) > 0) {
2021-09-13 15:53:43 +03:00
if (ac.dot(ao) > 0) {
2021-10-31 11:39:08 +03:00
newPoints = Simplex{a, c};
2021-10-28 16:58:02 +03:00
newDirection = ac.cross(ao).cross(ac);
2021-10-31 11:39:08 +03:00
} else {
return _lineCase(Simplex{a, b});
2021-09-13 15:53:43 +03:00
}
} else {
if (ab.cross(abc).dot(ao) > 0) {
2021-10-31 11:39:08 +03:00
return _lineCase(Simplex{a, b});
} else {
2021-09-13 15:53:43 +03:00
if (abc.dot(ao) > 0) {
2021-10-28 16:58:02 +03:00
newDirection = abc;
2021-09-13 15:53:43 +03:00
} else {
2021-10-31 11:39:08 +03:00
newPoints = Simplex{a, c, b};
2021-10-28 16:58:02 +03:00
newDirection = -abc;
2021-09-13 15:53:43 +03:00
}
}
}
2021-10-28 16:58:02 +03:00
return NextSimplex{newPoints, newDirection, false};
2021-09-13 15:53:43 +03:00
}
NextSimplex RigidBody::_tetrahedronCase(const Simplex &points) {
Vec3D a = points[0];
Vec3D b = points[1];
Vec3D c = points[2];
Vec3D d = points[3];
2021-09-13 15:53:43 +03:00
Vec3D ab = b - a;
Vec3D ac = c - a;
Vec3D ad = d - a;
2021-10-31 11:39:08 +03:00
Vec3D ao = -a;
2021-09-13 15:53:43 +03:00
Vec3D abc = ab.cross(ac);
Vec3D acd = ac.cross(ad);
Vec3D adb = ad.cross(ab);
2021-09-13 15:53:43 +03:00
if (abc.dot(ao) > 0) {
2021-10-31 11:39:08 +03:00
return _triangleCase(Simplex{a, b, c});
2021-09-13 15:53:43 +03:00
}
if (acd.dot(ao) > 0) {
2021-10-31 11:39:08 +03:00
return _triangleCase(Simplex{a, c, d});
2021-09-13 15:53:43 +03:00
}
if (adb.dot(ao) > 0) {
2021-10-31 11:39:08 +03:00
return _triangleCase(Simplex{a, d, b});
2021-09-13 15:53:43 +03:00
}
return NextSimplex{points, Vec3D(), true};
2021-09-13 15:53:43 +03:00
}
std::pair<bool, Simplex> RigidBody::checkGJKCollision(std::shared_ptr<RigidBody> obj) {
2021-10-17 19:53:30 +03:00
// This is implementation of GJK algorithm for collision detection.
// It builds a simplex (a simplest shape that can select point in space) around
// zero for Minkowski Difference. Collision happend when zero point is inside.
// See references:
// https://www.youtube.com/watch?v=MDusDn8oTSE
// https://blog.winter.dev/2020/gjk-algorithm/
2021-09-13 15:53:43 +03:00
// Get initial support point in any direction
2021-10-28 16:58:02 +03:00
Vec3D support = _support(obj, Vec3D{1, 0, 0});
2021-09-13 15:53:43 +03:00
// Simplex is an array of points, max count is 4
2021-10-28 16:58:02 +03:00
Simplex points{};
points.push_front(support);
2021-09-13 15:53:43 +03:00
// New direction is towards the origin
2021-10-28 16:58:02 +03:00
Vec3D direction = -support;
2021-09-13 15:53:43 +03:00
2021-10-31 13:00:38 +03:00
size_t iters = 0;
2021-10-28 16:58:02 +03:00
while (iters++ < size() + obj->size()) {
support = _support(obj, direction);
2021-10-28 16:58:02 +03:00
if (support.dot(direction) <= 0) {
return std::make_pair(false, points); // no collision
}
2021-09-13 15:53:43 +03:00
2021-10-28 16:58:02 +03:00
points.push_front(support);
2021-09-13 15:53:43 +03:00
2021-10-28 16:58:02 +03:00
NextSimplex nextSimplex = _nextSimplex(points);
2021-09-13 15:53:43 +03:00
2021-10-28 16:58:02 +03:00
direction = nextSimplex.newDirection;
points = nextSimplex.newSimplex;
if (nextSimplex.finishSearching) {
2021-10-31 11:39:08 +03:00
if (obj->isCollider()) {
2021-09-13 15:53:43 +03:00
_inCollision = true;
2021-10-28 16:58:02 +03:00
}
return std::make_pair(true, points);
2021-09-13 15:53:43 +03:00
}
}
2021-10-28 16:58:02 +03:00
return std::make_pair(false, points);
2021-09-13 15:53:43 +03:00
}
2021-10-31 11:39:08 +03:00
CollisionPoint RigidBody::EPA(const Simplex &simplex, std::shared_ptr<RigidBody> obj) {
2021-10-17 19:53:30 +03:00
// This is implementation of EPA algorithm for solving collision.
// It uses a simplex from GJK around and expand it to the border.
// The goal is to calculate the nearest normal and the intersection depth.
// See references:
// https://www.youtube.com/watch?v=0XQ2FSz3EK8
// https://blog.winter.dev/2020/epa-algorithm/
2021-09-13 15:53:43 +03:00
std::vector<Vec3D> polytope(simplex.begin(), simplex.end());
2021-10-31 11:39:08 +03:00
std::vector<size_t> faces = {
2021-09-13 15:53:43 +03:00
0, 1, 2,
0, 3, 1,
0, 2, 3,
1, 3, 2
};
auto faceNormals = _getFaceNormals(polytope, faces);
2021-10-28 16:58:02 +03:00
std::vector<FaceNormal> normals = faceNormals.first;
size_t minFace = faceNormals.second;
2021-09-13 15:53:43 +03:00
2021-10-28 16:58:02 +03:00
Vec3D minNormal = normals[minFace].normal;
double minDistance = std::numeric_limits<double>::max();
2021-09-13 15:53:43 +03:00
2021-10-31 13:00:38 +03:00
size_t iters = 0;
while (minDistance == std::numeric_limits<double>::max() && iters++ < size() + obj->size()) {
2021-10-28 16:58:02 +03:00
minNormal = normals[minFace].normal;
minDistance = normals[minFace].distance;
2021-09-13 15:53:43 +03:00
2021-10-28 16:58:02 +03:00
Vec3D support = _support(obj, minNormal);
double sDistance = minNormal.dot(support);
2021-09-13 15:53:43 +03:00
if (std::abs(sDistance - minDistance) > Consts::EPA_EPS) {
minDistance = std::numeric_limits<double>::max();
2021-09-13 15:53:43 +03:00
std::vector<std::pair<size_t, size_t>> uniqueEdges;
2021-10-28 16:58:02 +03:00
size_t f = 0;
2021-10-31 11:39:08 +03:00
for (auto &normal : normals) {
2021-10-28 16:58:02 +03:00
if (normal.normal.dot(support) > 0) {
uniqueEdges = _addIfUniqueEdge(uniqueEdges, faces, f + 0, f + 1);
uniqueEdges = _addIfUniqueEdge(uniqueEdges, faces, f + 1, f + 2);
uniqueEdges = _addIfUniqueEdge(uniqueEdges, faces, f + 2, f + 0);
2021-09-13 15:53:43 +03:00
faces.erase(faces.begin() + f);
faces.erase(faces.begin() + f);
faces.erase(faces.begin() + f);
2021-10-28 16:58:02 +03:00
} else {
f += 3;
2021-09-13 15:53:43 +03:00
}
}
2021-09-13 15:53:43 +03:00
std::vector<size_t> newFaces;
newFaces.reserve(uniqueEdges.size() * 3);
2021-10-31 11:39:08 +03:00
for (auto[edgeIndex1, edgeIndex2] : uniqueEdges) {
2021-09-13 15:53:43 +03:00
newFaces.push_back(edgeIndex1);
newFaces.push_back(edgeIndex2);
newFaces.push_back(polytope.size());
}
polytope.push_back(support);
faces.insert(faces.end(), newFaces.begin(), newFaces.end());
2021-09-13 15:53:43 +03:00
auto newFaceNormals = _getFaceNormals(polytope, faces);
2021-09-13 15:53:43 +03:00
2021-10-28 16:58:02 +03:00
normals = std::move(newFaceNormals.first);
minFace = newFaceNormals.second;
2021-09-13 15:53:43 +03:00
}
}
_collisionNormal = minNormal;
2021-10-31 11:39:08 +03:00
if (std::abs(minDistance - std::numeric_limits<double>::max()) < Consts::EPS) {
2021-10-28 16:58:02 +03:00
return CollisionPoint{minNormal, 0};
}
2021-10-28 16:58:02 +03:00
return CollisionPoint{minNormal, minDistance + Consts::EPA_EPS};
2021-09-13 15:53:43 +03:00
}
2021-10-31 11:39:08 +03:00
std::pair<std::vector<FaceNormal>, size_t>
RigidBody::_getFaceNormals(const std::vector<Vec3D> &polytope, const std::vector<size_t> &faces) {
2021-10-28 16:58:02 +03:00
std::vector<FaceNormal> normals;
normals.reserve(faces.size() / 3);
size_t nearestFaceIndex = 0;
double minDistance = std::numeric_limits<double>::max();
2021-09-13 15:53:43 +03:00
for (size_t i = 0; i < faces.size(); i += 3) {
Vec3D a = polytope[faces[i + 0]];
Vec3D b = polytope[faces[i + 1]];
Vec3D c = polytope[faces[i + 2]];
2021-09-13 15:53:43 +03:00
2021-10-28 16:58:02 +03:00
Vec3D normal = (b - a).cross(c - a).normalized();
2021-10-28 16:58:02 +03:00
double distance = normal.dot(a);
2021-09-13 15:53:43 +03:00
if (distance < -Consts::EPS) {
2021-10-28 16:58:02 +03:00
normal = -normal;
2021-09-13 15:53:43 +03:00
distance *= -1;
}
2021-10-28 16:58:02 +03:00
normals.emplace_back(FaceNormal{normal, distance});
2021-09-13 15:53:43 +03:00
if (distance < minDistance) {
nearestFaceIndex = i / 3;
2021-09-13 15:53:43 +03:00
minDistance = distance;
}
}
return {normals, nearestFaceIndex};
2021-09-13 15:53:43 +03:00
}
2021-10-31 11:39:08 +03:00
std::vector<std::pair<size_t, size_t>>
RigidBody::_addIfUniqueEdge(const std::vector<std::pair<size_t, size_t>> &edges, const std::vector<size_t> &faces,
size_t a, size_t b) {
2021-09-13 15:53:43 +03:00
std::vector<std::pair<size_t, size_t>> newEdges = edges;
2021-09-13 15:53:43 +03:00
// We are interested in reversed edge
// 0--<--3
// / \ B / A: 2-0
// / A \ / B: 0-2
// 1-->--2
auto reverse = std::find(newEdges.begin(), newEdges.end(), std::make_pair(faces[b], faces[a]));
2021-09-13 15:53:43 +03:00
if (reverse != newEdges.end()) {
newEdges.erase(reverse);
} else {
newEdges.emplace_back(faces[a], faces[b]);
2021-09-13 15:53:43 +03:00
}
return newEdges;
2021-09-13 15:53:43 +03:00
}
2021-10-31 11:39:08 +03:00
void RigidBody::solveCollision(const CollisionPoint &collision) {
2021-10-17 08:32:23 +03:00
Vec3D velocity_parallel = collision.normal * velocity().dot(collision.normal);
Vec3D velocity_perpendicular = velocity() - velocity_parallel;
2021-10-31 11:39:08 +03:00
if (velocity().dot(collision.normal) > 0) {
2021-10-17 08:32:23 +03:00
setVelocity(velocity_perpendicular);
2021-10-28 16:58:02 +03:00
}
2021-10-17 08:32:23 +03:00
translate(-collision.normal * collision.depth);
}
2021-09-13 15:53:43 +03:00
void RigidBody::updatePhysicsState() {
2021-10-28 16:58:02 +03:00
translate(_velocity * Time::deltaTime());
_velocity = _velocity + _acceleration * Time::deltaTime();
2021-09-13 15:53:43 +03:00
}
2021-10-31 11:39:08 +03:00
void RigidBody::setVelocity(const Vec3D &velocity) {
2021-10-28 16:58:02 +03:00
_velocity = velocity;
2021-09-13 15:53:43 +03:00
}
void RigidBody::addVelocity(const Vec3D &velocity) {
2021-10-28 16:58:02 +03:00
_velocity = _velocity + velocity;
2021-09-13 15:53:43 +03:00
}
2021-10-31 11:39:08 +03:00
void RigidBody::setAcceleration(const Vec3D &acceleration) {
2021-10-28 16:58:02 +03:00
_acceleration = acceleration;
}
void RigidBody::setSimpleHitBox(bool b) {
_simpleHitBox = b;
if (_simpleHitBox) {
_hitBox = HitBox::Box(*this);
} else {
_hitBox = HitBox(*this);
}
}