Files

263 lines
9.3 KiB
C++

#include "gamelib/effectphysics/effectphysicsrigidbody.h"
#include "cm/jobs/collisionresults.h"
#include "gamelib/physics/physics.h"
#include <algorithm>
#include <cmath>
namespace {
constexpr float kRadToDeg = 57.29577951308232f;
constexpr float kVelocityEpsilon = 1.0e-8f;
idMat3 WorldInverseInertia(const idMat3& orientation,
const idMat3& inverseInertiaTensor) {
return orientation * inverseInertiaTensor * orientation.Transpose();
}
idVec3 PointVelocity(const idEffectPhysicsRigidBody::effectRBState_t& state,
const idEffectPhysicsProperties& properties, const idVec3& point) {
const idVec3 linearVelocity =
state.linearMomentum * properties.inverseMass;
const idVec3 angularVelocity =
WorldInverseInertia(state.orientation,
properties.inverseInertiaTensor) * state.angularMomentum;
return linearVelocity + angularVelocity.Cross(point - state.position);
}
} // namespace
idEffectPhysicsRigidBody::idEffectPhysicsRigidBody()
: properties(nullptr)
, currentState{}
, SPObject()
, active(false)
, settled(false) {
}
idEffectPhysicsRigidBody::~idEffectPhysicsRigidBody() {
if (properties != nullptr && properties->broadPhase != nullptr) {
properties->broadPhase->RemoveObject(&SPObject);
}
properties = nullptr;
active = false;
}
void idEffectPhysicsRigidBody::Setup(
const idEffectPhysicsProperties* const props) {
if (properties != nullptr && properties->broadPhase != nullptr) {
properties->broadPhase->RemoveObject(&SPObject);
}
properties = props;
currentState.position.Zero();
currentState.orientation = idMat3(1.0f);
currentState.linearMomentum.Zero();
currentState.angularMomentum.Zero();
currentState.externalForce.Zero();
currentState.externalTorque.Zero();
active = false;
settled = false;
if (props == nullptr) {
SPObject.traceModelIndex = -1;
return;
}
SPObject.traceModelIndex = props->traceModelIndex;
SPObject.motion.worldCollisionOnly = props->worldCollisionOnly;
SPObject.clipMask = props->clipMask;
if (props->broadPhase != nullptr) {
props->broadPhase->AddObject(&SPObject, currentState.position,
currentState.orientation, nullptr);
}
}
void idEffectPhysicsRigidBody::CapVelocity(
const float deltaTime, effectRBState_t& current) const {
(void)deltaTime;
if (properties == nullptr) {
return;
}
const idVec3 linearVelocity =
current.linearMomentum * properties->inverseMass;
const float linearSpeed = linearVelocity.Length();
if (properties->maxLinearVelocity > 0.0f &&
linearSpeed > properties->maxLinearVelocity) {
current.linearMomentum = current.linearMomentum *
(properties->maxLinearVelocity / linearSpeed);
}
const idVec3 angularVelocity =
WorldInverseInertia(current.orientation,
properties->inverseInertiaTensor) * current.angularMomentum;
const float angularSpeed = angularVelocity.Length();
if (properties->maxAngularVelocity > 0.0f &&
angularSpeed > properties->maxAngularVelocity) {
current.angularMomentum = current.angularMomentum *
(properties->maxAngularVelocity / angularSpeed);
}
}
void idEffectPhysicsRigidBody::CollisionImpulse(trace_t& collision,
const impactInfo_t& impact, const float extraMomentumDecay,
effectRBState_t& state, idVec3& normalVelocity, idVec3& impulse) const {
normalVelocity.Zero();
impulse.Zero();
if (properties == nullptr) {
return;
}
idVec3 normal = collision.c.normal;
if (normal.NormalizeFast() <= kVelocityEpsilon) {
return;
}
const idVec3 bodyVelocity =
PointVelocity(state, *properties, collision.c.point);
const idVec3 relativeVelocity = bodyVelocity - impact.velocity;
const float velocityIntoSurface = relativeVelocity.Dot(normal);
normalVelocity = normal * velocityIntoSurface;
if (velocityIntoSurface >= 0.0f) {
return;
}
const idVec3 arm = collision.c.point - state.position;
const idVec3 cross = arm.Cross(normal);
const idVec3 inertiaCross =
WorldInverseInertia(state.orientation,
properties->inverseInertiaTensor) * cross;
const float denominator = properties->inverseMass + impact.invMass +
normal.Dot(inertiaCross.Cross(arm));
if (denominator <= kVelocityEpsilon) {
return;
}
const float restitution = (std::max)(0.0f, properties->bouncyness);
float magnitude = -(1.0f + restitution) * velocityIntoSurface /
denominator;
magnitude *= (std::max)(0.0f, 1.0f - extraMomentumDecay);
impulse = normal * magnitude;
// The recovered path applies Coulomb contact friction after the normal
// impulse. Preserve that behavior without the original VMX shortcuts.
idVec3 tangent = relativeVelocity - normal * velocityIntoSurface;
const float tangentSpeed = tangent.NormalizeFast();
if (tangentSpeed > kVelocityEpsilon && properties->contactFriction > 0.0f) {
const float frictionMagnitude = (std::min)(
tangentSpeed / (properties->inverseMass + impact.invMass +
kVelocityEpsilon),
magnitude * properties->contactFriction);
impulse = impulse - tangent * frictionMagnitude;
}
state.linearMomentum = state.linearMomentum + impulse;
state.angularMomentum = state.angularMomentum + arm.Cross(impulse);
}
void idEffectPhysicsRigidBody::ApplyImpulse(const idVec3& point,
const idVec3& impulse) {
settled = false;
currentState.linearMomentum = currentState.linearMomentum + impulse;
// The Xbox dump spells this as impulse x (point-position), which is the
// negative of the conventional r x J ordering.
currentState.angularMomentum = currentState.angularMomentum +
impulse.Cross(point - currentState.position);
}
void idEffectPhysicsRigidBody::Evolve(const float deltaTime,
const effectRBState_t& current, effectRBDelta_t& delta) const {
delta.translation.Zero();
delta.rotation = idRotation();
delta.linearForce.Zero();
delta.angularForce.Zero();
if (properties == nullptr) {
return;
}
const idVec3 linearVelocity =
current.linearMomentum * properties->inverseMass;
const idVec3 angularVelocity =
WorldInverseInertia(current.orientation,
properties->inverseInertiaTensor) * current.angularMomentum;
delta.translation = linearVelocity * deltaTime;
idVec3 rotationAxis = angularVelocity;
const float angularSpeed = rotationAxis.NormalizeFast();
if (angularSpeed <= kVelocityEpsilon) {
rotationAxis.Set(0.0f, 0.0f, 1.0f);
}
delta.rotation = idRotation(current.position, rotationAxis,
-angularSpeed * deltaTime * kRadToDeg);
const idVec3 totalForce = current.externalForce +
properties->gravityVector * properties->mass -
linearVelocity * properties->linearFriction;
const idVec3 totalTorque = current.externalTorque -
angularVelocity * properties->angularFriction;
delta.linearForce = totalForce * deltaTime;
delta.angularForce = totalTorque * deltaTime;
}
void idEffectPhysicsRigidBody::Update(const float deltaTime,
const float extraMomentumDecay, idEffectPhysicsCollision& collision) {
collision.Clear();
if (!active || settled || properties == nullptr ||
properties->broadPhase == nullptr || deltaTime <= 0.0f) {
return;
}
bool testForSettling = false;
trace_t trace{};
if (SPObject.query.offset != 0 &&
properties->broadPhase->GetMotionResult(&trace, &SPObject)) {
currentState.position = SPObject.origin;
currentState.orientation = SPObject.axis;
if (trace.fraction < 1.0f) {
impactInfo_t impact{};
impact.invInertiaTensor = idMat3(1.0f);
idVec3 impulse;
CollisionImpulse(trace, impact, extraMomentumDecay,
currentState, collision.collisionNormalVelocity, impulse);
collision.collisionPoint = trace.c.point;
collision.collisionSurfaceType = trace.c.surfaceType;
const float horizontalSpeed = std::sqrt(
currentState.linearMomentum.x * currentState.linearMomentum.x +
currentState.linearMomentum.y * currentState.linearMomentum.y);
if (horizontalSpeed <= 10.0f &&
currentState.linearMomentum.z < 0.0f) {
testForSettling = horizontalSpeed >= 2.0f;
settled = horizontalSpeed < 2.0f;
}
}
}
CapVelocity(deltaTime, currentState);
effectRBDelta_t delta;
Evolve(deltaTime, currentState, delta);
properties->broadPhase->ApplyMotion(
&SPObject, delta.translation, delta.rotation);
if (testForSettling &&
delta.translation.x * delta.translation.x +
delta.translation.y * delta.translation.y < 1.0e-4f) {
settled = true;
}
if (settled) {
SPObject.motion.valid = false;
currentState.linearMomentum.Zero();
currentState.angularMomentum.Zero();
collision.Clear();
} else {
currentState.linearMomentum =
currentState.linearMomentum + delta.linearForce;
currentState.angularMomentum =
currentState.angularMomentum + delta.angularForce;
}
currentState.externalForce.Zero();
currentState.externalTorque.Zero();
}