#include "gamelib/physics/physics_af.h" #include "gamelib/physics/clipmodel.h" #include "idlib/math/rotation.h" #include #include #include bool GameLib_GetMasterPhysicsTransform(idPhysicsCallbacks* callbacks, idVec3& origin, idMat3& axis); void GameLib_NotifyPhysicsActivated(idPhysicsCallbacks* callbacks, int physicsId); void GameLib_NotifyPhysicsDeactivated(idPhysicsCallbacks* callbacks, int physicsId); void GameLib_NotifyPhysicsCollision(idPhysicsCallbacks* callbacks, int physicsId, const trace_t& collision, const idVec3& velocity); void GameLib_SerializeAFPhysics(idSerializer* serializer, idPhysics_AF& physics); namespace { constexpr int ENTITYNUM_NONE = 0x1FFF; constexpr float RAD2DEG = 57.29577951308232f; const idVec3 kZeroVector(0.0f, 0.0f, 0.0f); const idMat3 kIdentityAxis(1.0f); const idBounds kZeroBounds{{idVec3(0.0f, 0.0f, 0.0f), idVec3(0.0f, 0.0f, 0.0f)}}; idAFBody* SelectBody(idList& bodies, const int id) { if (bodies.Num() == 0) return nullptr; if (id < 0) return bodies[0]; return id < bodies.Num() ? bodies[id] : nullptr; } const idAFBody* SelectBody(const idList& bodies, const int id) { if (bodies.Num() == 0) return nullptr; if (id < 0) return bodies[0]; return id < bodies.Num() ? bodies[id] : nullptr; } idVec3 LinearVelocity(const idAFBody& body) { return idVec3(body.current.spatialVelocity[0], body.current.spatialVelocity[1], body.current.spatialVelocity[2]); } idVec3 AngularVelocity(const idAFBody& body) { return idVec3(body.current.spatialVelocity[3], body.current.spatialVelocity[4], body.current.spatialVelocity[5]); } void SetLinearVelocity(idAFBody& body, const idVec3& velocity) { body.current.spatialVelocity[0] = velocity.x; body.current.spatialVelocity[1] = velocity.y; body.current.spatialVelocity[2] = velocity.z; } void SetAngularVelocity(idAFBody& body, const idVec3& velocity) { body.current.spatialVelocity[3] = velocity.x; body.current.spatialVelocity[4] = velocity.y; body.current.spatialVelocity[5] = velocity.z; } void ZeroSpatial(idVec6& vector) { for (int i = 0; i < 6; ++i) vector[i] = 0.0f; } float RowVelocity(const idAFConstraint& constraint, const int row) { float velocity = 0.0f; if (constraint.body1 != nullptr) { for (int i = 0; i < 6; ++i) velocity += constraint.J1(row, i) * constraint.body1->current.spatialVelocity[i]; } if (constraint.body2 != nullptr) { for (int i = 0; i < 6; ++i) velocity += constraint.J2(row, i) * constraint.body2->current.spatialVelocity[i]; } return velocity; } float RowEffectiveMass(const idAFConstraint& constraint, const int row) { float inverseMass = 0.0f; const idAFBody* bodyList[2] = {constraint.body1, constraint.body2}; const idSpatialMat* jacobians[2] = {&constraint.J1, &constraint.J2}; for (int bodyIndex = 0; bodyIndex < 2; ++bodyIndex) { const idAFBody* const body = bodyList[bodyIndex]; if (body == nullptr) continue; const idSpatialMat& jacobian = *jacobians[bodyIndex]; const idVec3 linear(jacobian(row, 0), jacobian(row, 1), jacobian(row, 2)); const idVec3 angular(jacobian(row, 3), jacobian(row, 4), jacobian(row, 5)); inverseMass += linear.LengthSqr() * body->invMass; inverseMass += angular.Dot(body->inverseInertiaTensor * angular); } return (std::max)(inverseMass, 1.0e-6f); } void ApplyConstraintImpulse(idAFBody* const body, const idSpatialMat& jacobian, const int row, const float impulse) { if (body == nullptr) return; idVec3 linear(jacobian(row, 0), jacobian(row, 1), jacobian(row, 2)); idVec3 angular(jacobian(row, 3), jacobian(row, 4), jacobian(row, 5)); SetLinearVelocity(*body, LinearVelocity(*body) + linear * (impulse * body->invMass)); SetAngularVelocity(*body, AngularVelocity(*body) + body->inverseInertiaTensor * angular * impulse); } void SolveConstraintRows(idList& list, const int iterations) { for (int iteration = 0; iteration < iterations; ++iteration) { for (int index = 0; index < list.Num(); ++index) { idAFConstraint* const constraint = list[index]; if (constraint == nullptr) continue; const int rows = constraint->J1.GetNumRows(); for (int row = 0; row < rows; ++row) { float lambda = -(RowVelocity(*constraint, row) + constraint->c1[row]) / RowEffectiveMass(*constraint, row); const float oldLambda = constraint->lm[row]; const float next = (std::max)(constraint->lo[row], (std::min)(constraint->hi[row], oldLambda + lambda)); lambda = next - oldLambda; constraint->lm[row] = next; ApplyConstraintImpulse(constraint->body1, constraint->J1, row, lambda); ApplyConstraintImpulse(constraint->body2, constraint->J2, row, lambda); } } } } bool IsDescendantOf(const idAFBody* body, const idAFBody* ancestor) { for (const idAFBody* current = body; current != nullptr; current = current->parent) { if (current == ancestor) return true; } return false; } void ExpandBounds(idBounds& destination, const idBounds& source) { for (int axis = 0; axis < 3; ++axis) { destination[0][axis] = (std::min)(destination[0][axis], source[0][axis]); destination[1][axis] = (std::max)(destination[1][axis], source[1][axis]); } } } // namespace // Constraint code calls through these ownership-boundary functions so the // circular AF/constraint dependency remains link-clean. void GameLib_AddAFFrameConstraint(idPhysics_AF* const physics, idAFConstraint* const constraint) { if (physics != nullptr) physics->AddFrameConstraint(constraint); } float GameLib_GetAFJointFrictionScale(const idPhysics_AF* const physics) { return physics != nullptr ? physics->GetJointFrictionScale() : 1.0f; } float GameLib_GetAFContactFrictionScale(const idPhysics_AF* const physics) { return physics != nullptr ? physics->GetContactFrictionScale() : 1.0f; } idPhysics_AF::idPhysics_AF() : idPhysics_DynamicBase() , trees(4) , bodies(4) , constraints(4) , primaryConstraints(4) , auxiliaryConstraints(4) , frameConstraints(4) , contactConstraints(4) , contactBodies(4) , noclipBodies() , changedAF(true) , linearFriction(0.005f) , angularFriction(0.005f) , contactFriction(0.8f) , bouncyness(0.5f) , totalMass(-1.0f) , suspendVelocity(10.0f, 15.0f) , suspendAcceleration(20.0f, 20.0f) , noMoveTime(1.0f) , noMoveTranslation(10.0f) , noMoveRotation(10.0f) , minMoveTime(-1.0f) , maxMoveTime(-1.0f) , impulseThreshold(1.0f) , timeScale(1.0f) , timeScaleRampStart(0.0f) , timeScaleRampEnd(0.0f) , jointFrictionScale(0.0f) , jointFrictionDent(0.0f) , jointFrictionDentStart(0.0f) , jointFrictionDentEnd(0.0f) , jointFrictionDentScale(0.0f) , contactFrictionScale(0.0f) , contactFrictionDent(0.0f) , contactFrictionDentStart(0.0f) , contactFrictionDentEnd(0.0f) , contactFrictionDentScale(0.0f) , errorReduction(idFader::FADE_LINEAR, 0.5f) , errorReductionMax(idFader::FADE_LINEAR, 0.5f) , lcpEpsilon(idFader::FADE_LINEAR, 1.0e-7f) , limitErrorReduction(idFader::FADE_LINEAR, 0.3f) , limitErrorReductionMax(idFader::FADE_LINEAR, 0.5f) , limitLcpEpsilon(idFader::FADE_LINEAR, 1.0e-6f) , contactErrorReduction(idFader::FADE_LINEAR, 0.7f) , contactErrorReductionMax(idFader::FADE_LINEAR, 0.9f) , contactLcpEpsilon(idFader::FADE_LINEAR, 1.0e-6f) , universalErrorReduction(idFader::FADE_LINEAR, 0.5f) , universalErrorReductionMax(idFader::FADE_LINEAR, 0.5f) , universalTorsionLcpEpsilon(idFader::FADE_LINEAR, 1.0e-6f) , passEntityNum(ENTITYNUM_NONE) , selfCollision(true) , comeToRest(true) , linearTime(true) , noImpact(false) , worldConstraintsLocked(false) , forcePushable(false) , addContactConstraints(true) , addGravity(true) , masterBody(nullptr) , current{} , saved{} , lastTimeStep(1.0f / 60.0f) , endTime(0) , timeStep(0.0f) , lastImpulse(0.0f, 0.0f, 0.0f) , lcp(nullptr) { type = PHYSICS_AF; current.atRest = false; saved = current; for (int i = 0; i < 8; ++i) { noclipBodies.staticList[i].bodyId = -1; noclipBodies.staticList[i].originalClipMask = 0; noclipBodies.staticList[i].query.index = 0x100000000ULL; } } idPhysics_AF::~idPhysics_AF() { Shutdown(); } void idPhysics_AF::Shutdown() { UnlinkClip(); for (int i = 0; i < contactConstraints.Num(); ++i) delete contactConstraints[i]; contactConstraints.Clear(); frameConstraints.Clear(); for (int i = 0; i < constraints.Num(); ++i) delete constraints[i]; constraints.Clear(); primaryConstraints.Clear(); auxiliaryConstraints.Clear(); for (int i = 0; i < trees.Num(); ++i) delete trees[i]; trees.Clear(); for (int i = 0; i < bodies.Num(); ++i) delete bodies[i]; bodies.Clear(); contactBodies.Clear(); noclipBodies.Clear(); delete lcp; lcp = nullptr; masterBody = nullptr; } void idPhysics_AF::SetSuspendTime(const float minimum, const float maximum) { minMoveTime = minimum; maxMoveTime = maximum; } void idPhysics_AF::SetSuspendTolerance(const float quietTime, const float translation, const float rotation) { noMoveTime = quietTime; noMoveTranslation = translation; noMoveRotation = rotation; } void idPhysics_AF::SetSuspendSpeed(const idVec2& velocity, const idVec2& acceleration) { suspendVelocity = velocity; suspendAcceleration = acceleration; } void idPhysics_AF::SetTimeScaleRamp(const float start, const float end) { timeScaleRampStart = start; timeScaleRampEnd = end; } void idPhysics_AF::SetJointFrictionDent(const float dent, const float start, const float end) { jointFrictionDent = dent; jointFrictionDentStart = start; jointFrictionDentEnd = end; } void idPhysics_AF::SetContactFrictionDent(const float dent, const float start, const float end) { contactFrictionDent = dent; contactFrictionDentStart = start; contactFrictionDentEnd = end; } void idPhysics_AF::SetDefaultFriction(const float linear, const float angular, const float contact) { if (linear < 0.0f || linear > 1.0f || angular < 0.0f || angular > 1.0f || contact < 0.0f || contact > 1.0f) return; linearFriction = linear; angularFriction = angular; contactFriction = contact; } float idPhysics_AF::GetJointFrictionScale() const { float scale = jointFrictionScale > 0.0f ? jointFrictionScale : 1.0f; if (current.activateTime >= jointFrictionDentStart && current.activateTime <= jointFrictionDentEnd) scale *= (std::max)(0.0f, 1.0f - jointFrictionDent); return scale; } float idPhysics_AF::GetContactFrictionScale() const { float scale = contactFrictionScale > 0.0f ? contactFrictionScale : 1.0f; if (current.activateTime >= contactFrictionDentStart && current.activateTime <= contactFrictionDentEnd) scale *= (std::max)(0.0f, 1.0f - contactFrictionDent); return scale; } void idPhysics_AF::SetClipModel(idClipModel* const model, const float density, const int id, const bool freeOld) { idAFBody* const body = SelectBody(bodies, id); if (body == nullptr) { if (model != nullptr && freeOld) model->Delete(); return; } if (!freeOld && body->clipModel != nullptr && body->clipModel != model) body->clipModel->Unlink(); body->SetClipModel(model); if (density > 0.0f) body->SetDensity(density, kIdentityAxis); changedAF = true; Activate(); } idClipModel* idPhysics_AF::GetClipModel(const int id) { idAFBody* const body = SelectBody(bodies, id); return body != nullptr ? body->clipModel : nullptr; } int idPhysics_AF::GetNumClipModels() { return bodies.Num(); } void idPhysics_AF::SetMass(const float newMass, const int id) { idAFBody* const body = SelectBody(bodies, id); if (body == nullptr || newMass <= 0.0f) return; const float scale = newMass / (std::max)(body->mass, 1.0e-6f); body->mass = newMass; body->invMass = 1.0f / newMass; for (int row = 0; row < 3; ++row) for (int column = 0; column < 3; ++column) body->inertiaTensor[row][column] *= scale; body->inverseInertiaTensor = body->inertiaTensor; body->inverseInertiaTensor.InverseSelf(); totalMass = -1.0f; } float idPhysics_AF::GetMass(const int id) { if (id >= 0) { const idAFBody* const body = SelectBody(bodies, id); return body != nullptr ? body->mass : 0.0f; } if (totalMass < 0.0f) { totalMass = 0.0f; for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr) totalMass += bodies[i]->mass; } return totalMass; } void idPhysics_AF::SetContents(const int contents, const int id) { if (id >= 0) { idClipModel* const model = GetClipModel(id); if (model != nullptr) model->SetContents(contents); return; } for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr && bodies[i]->clipModel != nullptr) bodies[i]->clipModel->SetContents(contents); } int idPhysics_AF::GetContents(const int id) { if (id >= 0) { const idClipModel* const model = GetClipModel(id); return model != nullptr ? model->GetContents() : 0; } int contents = 0; for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr && bodies[i]->clipModel != nullptr) contents |= bodies[i]->clipModel->GetContents(); return contents; } void idPhysics_AF::SetClipMask(const int mask, const int id) { if (id >= 0) { idAFBody* const body = SelectBody(bodies, id); if (body != nullptr) { body->clipMask = mask; body->fl.clipMaskSet = 1; } return; } clipMask = mask; for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr && !bodies[i]->fl.clipMaskSet) bodies[i]->clipMask = mask; } int idPhysics_AF::GetClipMask(const int id) { const idAFBody* const body = SelectBody(bodies, id); return body != nullptr ? body->clipMask : clipMask; } const idBounds* idPhysics_AF::GetBounds(const int id) { if (id >= 0) { const idClipModel* const model = GetClipModel(id); return model != nullptr ? &model->GetBounds() : &kZeroBounds; } static thread_local idBounds bounds; bool initialized = false; for (int i = 0; i < bodies.Num(); ++i) { const idClipModel* const model = bodies[i] != nullptr ? bodies[i]->clipModel : nullptr; if (model == nullptr) continue; if (!initialized) { bounds = model->GetBounds(); initialized = true; } else ExpandBounds(bounds, model->GetBounds()); } if (!initialized) bounds = kZeroBounds; return &bounds; } const idBounds* idPhysics_AF::GetAbsBounds(const int id) { if (id >= 0) { const idClipModel* const model = GetClipModel(id); return model != nullptr ? &model->GetAbsBounds() : &kZeroBounds; } static thread_local idBounds bounds; bool initialized = false; for (int i = 0; i < bodies.Num(); ++i) { const idClipModel* const model = bodies[i] != nullptr ? bodies[i]->clipModel : nullptr; if (model == nullptr) continue; if (!initialized) { bounds = model->GetAbsBounds(); initialized = true; } else ExpandBounds(bounds, model->GetAbsBounds()); } if (!initialized) bounds = kZeroBounds; return &bounds; } void idPhysics_AF::SetOrigin(const idVec3* const origin, const int id) { if (origin == nullptr) return; idAFBody* const reference = SelectBody(bodies, id); if (reference == nullptr) return; const idVec3 delta = *origin - reference->current.worldOrigin; Translate(&delta, id); } void idPhysics_AF::SetAxis(const idMat3* const axis, const int id) { if (axis == nullptr) return; idAFBody* const reference = SelectBody(bodies, id); if (reference == nullptr) return; const idMat3 rotationMatrix = *axis * reference->current.worldAxis.Transpose(); const idVec3 pivot = reference->current.worldOrigin; for (int i = 0; i < bodies.Num(); ++i) { idAFBody* const body = bodies[i]; if (body == nullptr || (id >= 0 && i != id)) continue; body->current.worldOrigin = pivot + rotationMatrix * (body->current.worldOrigin - pivot); body->current.worldAxis = rotationMatrix * body->current.worldAxis; } UpdateClipModels(); Activate(); } void idPhysics_AF::Translate(const idVec3* const translation, const int id) { if (translation == nullptr) return; for (int i = 0; i < bodies.Num(); ++i) { idAFBody* const body = bodies[i]; if (body == nullptr || (id >= 0 && i != id)) continue; body->current.worldOrigin = body->current.worldOrigin + *translation; } if (id < 0) for (int i = 0; i < constraints.Num(); ++i) if (constraints[i] != nullptr) constraints[i]->Translate(*translation); UpdateClipModels(); Activate(); } void idPhysics_AF::Rotate(const idRotation* const rotation, const int id) { if (rotation == nullptr) return; const idMat3 matrix = rotation->ToMat3(); for (int i = 0; i < bodies.Num(); ++i) { idAFBody* const body = bodies[i]; if (body == nullptr || (id >= 0 && i != id)) continue; body->current.worldOrigin = *rotation * body->current.worldOrigin; body->current.worldAxis = matrix * body->current.worldAxis; } if (id < 0) for (int i = 0; i < constraints.Num(); ++i) if (constraints[i] != nullptr) constraints[i]->Rotate(*rotation); UpdateClipModels(); Activate(); } const idVec3* idPhysics_AF::GetOrigin(const int id) { const idAFBody* const body = SelectBody(bodies, id); return body != nullptr ? &body->current.worldOrigin : &kZeroVector; } const idMat3* idPhysics_AF::GetAxis(const int id) { const idAFBody* const body = SelectBody(bodies, id); return body != nullptr ? &body->current.worldAxis : &kIdentityAxis; } const idVec3* idPhysics_AF::GetLocalOrigin(const int id) { return GetOrigin(id); } const idMat3* idPhysics_AF::GetLocalAxis(const int id) { return GetAxis(id); } void idPhysics_AF::SetLinearVelocity(const idVec3* const velocity, const int id) { if (velocity == nullptr) return; if (id >= 0) { idAFBody* const body = SelectBody(bodies, id); if (body != nullptr) ::SetLinearVelocity(*body, *velocity); } else { for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr) ::SetLinearVelocity(*bodies[i], *velocity); } Activate(); } void idPhysics_AF::SetAngularVelocity(const idVec3* const velocity, const int id) { if (velocity == nullptr) return; if (id >= 0) { idAFBody* const body = SelectBody(bodies, id); if (body != nullptr) ::SetAngularVelocity(*body, *velocity); } else { for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr) ::SetAngularVelocity(*bodies[i], *velocity); } Activate(); } idVec3* idPhysics_AF::GetLinearVelocity(idVec3* const result, const int id) { if (result == nullptr) return nullptr; const idAFBody* const body = SelectBody(bodies, id); *result = body != nullptr ? LinearVelocity(*body) : kZeroVector; return result; } idVec3* idPhysics_AF::GetAngularVelocity(idVec3* const result, const int id) { if (result == nullptr) return nullptr; const idAFBody* const body = SelectBody(bodies, id); *result = body != nullptr ? AngularVelocity(*body) : kZeroVector; return result; } void idPhysics_AF::SetWaterEntNum(int) {} int idPhysics_AF::GetWaterEntNum() { return -1; } void idPhysics_AF::SetWaterSurfaceWrldHeight(float) {} float idPhysics_AF::GetWaterSurfaceWrldHeight() { return 0.0f; } void idPhysics_AF::GetImpactInfo(const int id, const idVec3* const point, impactInfo_t* const info) { if (info == nullptr) return; info->Zero(); const idAFBody* const body = SelectBody(bodies, id); if (body == nullptr) return; info->invMass = body->invMass; info->invInertiaTensor = body->inverseInertiaTensor; info->position = point != nullptr ? *point : body->current.worldOrigin; info->velocity = body->GetPointVelocity(info->position); } void idPhysics_AF::ApplyImpulse(const int bodyId, const idVec3* const point, const idVec3* const impulse) { idAFBody* const body = SelectBody(bodies, bodyId); if (body == nullptr || impulse == nullptr || noImpact) return; const idVec3 applicationPoint = point != nullptr ? *point : body->current.worldOrigin; ::SetLinearVelocity(*body, LinearVelocity(*body) + *impulse * body->invMass); const idVec3 torque = (applicationPoint - body->current.worldOrigin) .Cross(*impulse); ::SetAngularVelocity(*body, AngularVelocity(*body) + body->inverseInertiaTensor * torque); lastImpulse = *impulse; Activate(); } void idPhysics_AF::ApplyForce(const int bodyId, const idVec3* const point, const idVec3* const force) { idAFBody* const body = SelectBody(bodies, bodyId); if (body == nullptr || force == nullptr) return; body->AddForce(point != nullptr ? *point : body->current.worldOrigin, *force); Activate(); } void idPhysics_AF::Activate() { current.atRest = false; current.noMoveTime = 0.0f; current.activateTime = 0.0f; if (callbacks != nullptr) GameLib_NotifyPhysicsActivated(callbacks, GetPhysicsId()); } void idPhysics_AF::PutToRest() { current.atRest = true; for (int i = 0; i < bodies.Num(); ++i) { if (bodies[i] == nullptr) continue; ZeroSpatial(bodies[i]->current.spatialVelocity); ZeroSpatial(bodies[i]->current.externalForce); } } bool idPhysics_AF::IsAtRest() { return current.atRest; } bool idPhysics_AF::IsPushable(int) { return !noImpact && (masterBody == nullptr || forcePushable); } void idPhysics_AF::SaveState() { saved = current; for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr) bodies[i]->saved = bodies[i]->current; } void idPhysics_AF::RestoreState() { current = saved; for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr) bodies[i]->current = bodies[i]->saved; UpdateClipModels(); } void idPhysics_AF::UpdateTime(const int newEndTime) { endTime = newEndTime; } void idPhysics_AF::SetPushed(const int deltaTime) { ZeroSpatial(current.pushVelocity); if (deltaTime <= 0 || bodies.Num() == 0) return; const float scale = 1000.0f / static_cast(deltaTime); int count = 0; for (int i = 0; i < bodies.Num(); ++i) { const idAFBody* const body = bodies[i]; if (body == nullptr) continue; const idVec3 velocity = (body->current.worldOrigin - body->saved.worldOrigin) * scale; current.pushVelocity[0] += velocity.x; current.pushVelocity[1] += velocity.y; current.pushVelocity[2] += velocity.z; ++count; } if (count > 0) for (int i = 0; i < 3; ++i) current.pushVelocity[i] /= count; } idVec3* idPhysics_AF::GetPushedLinearVelocity(idVec3* const result, int) { if (result != nullptr) result->Set(current.pushVelocity[0], current.pushVelocity[1], current.pushVelocity[2]); return result; } idVec3* idPhysics_AF::GetPushedAngularVelocity(idVec3* const result, int) { if (result != nullptr) result->Set(current.pushVelocity[3], current.pushVelocity[4], current.pushVelocity[5]); return result; } void idPhysics_AF::SetAuxAngularVelocity(const idVec3& angular) { current.auxVelocity[3] = angular.x; current.auxVelocity[4] = angular.y; current.auxVelocity[5] = angular.z; } void idPhysics_AF::SetMaster(const bool enable, const idVec3* const masterOrigin, const idMat3* const masterAxis, bindFlags_t) { if (enable && masterOrigin != nullptr && masterAxis != nullptr && bodies.Num() > 0) { masterBody = bodies[0]; const idVec3 oldOrigin = masterBody->current.worldOrigin; const idMat3 oldAxis = masterBody->current.worldAxis; const idMat3 rotation = *masterAxis * oldAxis.Transpose(); for (int i = 0; i < bodies.Num(); ++i) { idAFBody* const body = bodies[i]; if (body == nullptr) continue; body->current.worldOrigin = *masterOrigin + rotation * (body->current.worldOrigin - oldOrigin); body->current.worldAxis = rotation * body->current.worldAxis; } worldConstraintsLocked = true; UpdateClipModels(); } else { masterBody = nullptr; worldConstraintsLocked = false; Activate(); } } void idPhysics_AF::SetLocalOrigin(const idVec3* const origin, const int id) { SetOrigin(origin, id); } void idPhysics_AF::SetLocalAxis(const idMat3* const axis, const int id) { SetAxis(axis, id); } int idPhysics_AF::GetBlockingEntityNum() { return contacts.Num() > 0 ? contacts[0].entityNum : ENTITYNUM_NONE; } int idPhysics_AF::GetLinearEndTime() { return 0; } int idPhysics_AF::GetAngularEndTime() { return 0; } void idPhysics_AF::Serialize(idSerializer* const serializer) { if (serializer != nullptr) GameLib_SerializeAFPhysics(serializer, *this); } void idPhysics_AF::DisableClip() { for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr && bodies[i]->clipModel != nullptr) bodies[i]->clipModel->Disable(); } void idPhysics_AF::EnableClip() { for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr && bodies[i]->clipModel != nullptr) bodies[i]->clipModel->Enable(); } void idPhysics_AF::UnlinkClip() { for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr && bodies[i]->clipModel != nullptr) bodies[i]->clipModel->Unlink(); } void idPhysics_AF::LinkClip() { UpdateClipModels(); } void idPhysics_AF::UpdateClipModels() { for (int i = 0; i < bodies.Num(); ++i) { idAFBody* const body = bodies[i]; if (body == nullptr || body->clipModel == nullptr) continue; body->clipModel->Link(GetEntityNumber(), GetEntityNumber(), i, body->current.worldOrigin, body->current.worldAxis); } } void idPhysics_AF::ClipTranslation(trace_t* const result, const idVec3* const translation, const idClipModel* const model) { if (result == nullptr) return; std::memset(result, 0, sizeof(*result)); result->fraction = 1.0f; if (translation == nullptr || clip == nullptr) return; for (int i = 0; i < bodies.Num(); ++i) { const idAFBody* const body = bodies[i]; if (body == nullptr || body->clipModel == nullptr) continue; trace_t local{}; local.fraction = 1.0f; if (model != nullptr) { clip->TranslationModel(local, body->current.worldOrigin, body->current.worldOrigin + *translation, body->clipModel, body->current.worldAxis, body->clipMask, model->GetOrigin(), model, model->GetAxis()); } else { clip->Translation(&local, body->current.worldOrigin, body->current.worldOrigin + *translation, body->clipModel, body->current.worldAxis, body->clipMask, passEntityNum, false, "idPhysics_AF::ClipTranslation"); } if (local.fraction < result->fraction) *result = local; } } void idPhysics_AF::ClipRotation(trace_t* const result, const idRotation* const rotation, const idClipModel* const model) { if (result == nullptr) return; std::memset(result, 0, sizeof(*result)); result->fraction = 1.0f; if (rotation == nullptr || clip == nullptr) return; for (int i = 0; i < bodies.Num(); ++i) { const idAFBody* const body = bodies[i]; if (body == nullptr || body->clipModel == nullptr) continue; trace_t local{}; local.fraction = 1.0f; if (model != nullptr) { clip->RotationModel(local, body->current.worldOrigin, *rotation, body->clipModel, body->current.worldAxis, body->clipMask, model->GetOrigin(), model, model->GetAxis()); } else { clip->Rotation(&local, body->current.worldOrigin, *rotation, body->clipModel, body->current.worldAxis, body->clipMask, passEntityNum, false, "idPhysics_AF::ClipRotation"); } if (local.fraction < result->fraction) *result = local; } } int idPhysics_AF::ClipContents(const idClipModel* const model, const int contentMask) { if (clip == nullptr) return 0; int contents = 0; for (int i = 0; i < bodies.Num(); ++i) { const idAFBody* const body = bodies[i]; if (body == nullptr || body->clipModel == nullptr) continue; trace_t local{}; if (model != nullptr) { clip->ContentsModel(local, body->current.worldOrigin, body->clipModel, body->current.worldAxis, contentMask, model->GetOrigin(), model, model->GetAxis()); } else { clip->Contents(&local, body->current.worldOrigin, body->clipModel, body->current.worldAxis, contentMask, passEntityNum, "idPhysics_AF::ClipContents"); } contents |= local.c.contentFlags; } return contents; } int idPhysics_AF::AddBody(idAFBody* const body) { if (body == nullptr) return -1; for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] == body) return i; if (!body->fl.clipMaskSet) body->clipMask = clipMask; if (body->linearFriction < 0.0f) body->linearFriction = linearFriction; if (body->angularFriction < 0.0f) body->angularFriction = angularFriction; if (body->contactFriction < 0.0f) body->contactFriction = contactFriction; if (body->bouncyness < 0.0f) body->bouncyness = bouncyness; bodies.Append(body); totalMass = -1.0f; changedAF = true; return bodies.Num() - 1; } void idPhysics_AF::ForceBodyId(idAFBody* const body, const int newId) { const int oldId = GetBodyId(body); if (oldId < 0 || newId < 0 || newId >= bodies.Num() || oldId == newId) return; idAFBody* const displaced = bodies[newId]; bodies[newId] = body; bodies[oldId] = displaced; changedAF = true; } int idPhysics_AF::GetBodyId(const idAFBody* const body) const { for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] == body) return i; return -1; } idAFBody* idPhysics_AF::GetBody(const char* const bodyName) const { if (bodyName == nullptr) return nullptr; for (int i = 0; i < bodies.Num(); ++i) if (bodies[i] != nullptr && std::strcmp(bodies[i]->name.c_str(), bodyName) == 0) return bodies[i]; return nullptr; } idAFBody* idPhysics_AF::GetBody(const int id) const { return const_cast(SelectBody(bodies, id)); } idAFConstraint* idPhysics_AF::GetConstraint( const char* const constraintName) const { if (constraintName == nullptr) return nullptr; for (int i = 0; i < constraints.Num(); ++i) if (constraints[i] != nullptr && std::strcmp(constraints[i]->name.c_str(), constraintName) == 0) return constraints[i]; return nullptr; } idAFConstraint* idPhysics_AF::GetConstraint(const int id) const { return id >= 0 && id < constraints.Num() ? constraints[id] : nullptr; } void idPhysics_AF::AddConstraint(idAFConstraint* const constraint) { if (constraint == nullptr) return; for (int i = 0; i < constraints.Num(); ++i) if (constraints[i] == constraint) return; constraint->physics = this; constraints.Append(constraint); changedAF = true; } void idPhysics_AF::DeleteConstraint(const int id) { if (id < 0 || id >= constraints.Num()) return; idAFConstraint* const constraint = constraints[id]; constraints.RemoveIndex(id); primaryConstraints.Remove(constraint); auxiliaryConstraints.Remove(constraint); frameConstraints.Remove(constraint); delete constraint; changedAF = true; } void idPhysics_AF::DeleteConstraint(const char* const constraintName) { idAFConstraint* const constraint = GetConstraint(constraintName); if (constraint == nullptr) return; DeleteConstraint(constraints.FindIndex(constraint)); } void idPhysics_AF::DeleteBody(const int id) { if (id < 0 || id >= bodies.Num()) return; idAFBody* const body = bodies[id]; for (int i = constraints.Num() - 1; i >= 0; --i) if (constraints[i] != nullptr && (constraints[i]->body1 == body || constraints[i]->body2 == body)) DeleteConstraint(i); bodies.RemoveIndex(id); delete body; totalMass = -1.0f; changedAF = true; } void idPhysics_AF::AddFrameConstraint(idAFConstraint* const constraint) { if (constraint == nullptr) return; for (int i = 0; i < frameConstraints.Num(); ++i) if (frameConstraints[i] == constraint) return; constraint->physics = this; constraint->fl.frameConstraint = 1; frameConstraints.Append(constraint); } int idPhysics_AF::AddNoclipBody(const int bodyId) { idAFBody* const body = GetBody(bodyId); if (body == nullptr || noclipBodies.Num() >= noclipBodies.Max()) return -1; for (int i = 0; i < noclipBodies.Num(); ++i) if (noclipBodies[i].bodyId == bodyId) return i; noclipBodyInfo_t info{}; info.bodyId = bodyId; info.originalClipMask = body->clipMask; info.query.index = 0; noclipBodies.Append(info); body->clipMask = 0; return noclipBodies.Num() - 1; } void idPhysics_AF::TestNoclipBodies() { if (clip == nullptr) return; for (int i = noclipBodies.Num() - 1; i >= 0; --i) { noclipBodyInfo_t& info = noclipBodies[i]; idAFBody* const body = GetBody(info.bodyId); if (body == nullptr || body->clipModel == nullptr) { noclipBodies.RemoveIndex(i); continue; } trace_t result{}; info.query = clip->Contents(&result, body->current.worldOrigin, body->clipModel, body->current.worldAxis, info.originalClipMask, passEntityNum, "idPhysics_AF::TestNoclipBodies"); if (result.c.contentFlags == 0) { body->clipMask = info.originalClipMask; noclipBodies.RemoveIndex(i); } } } void idPhysics_AF::ApplyFriction(const float step, float) { const float jointScale = GetJointFrictionScale(); for (int i = 0; i < bodies.Num(); ++i) { idAFBody* const body = bodies[i]; if (body == nullptr) continue; const float linear = body->linearFriction >= 0.0f ? body->linearFriction : linearFriction; const float angular = body->angularFriction >= 0.0f ? body->angularFriction : angularFriction; ::SetLinearVelocity(*body, LinearVelocity(*body) * (std::max)(0.0f, 1.0f - linear * step)); ::SetAngularVelocity(*body, AngularVelocity(*body) * (std::max)(0.0f, 1.0f - angular * jointScale * step)); } } void idPhysics_AF::AddGravity() { if (!addGravity) return; for (int i = 0; i < bodies.Num(); ++i) { idAFBody* const body = bodies[i]; if (body == nullptr) continue; body->current.externalForce[0] += gravityVector.x * body->mass; body->current.externalForce[1] += gravityVector.y * body->mass; body->current.externalForce[2] += gravityVector.z * body->mass; } } void idPhysics_AF::PrimaryFactor() { for (int i = 0; i < trees.Num(); ++i) if (trees[i] != nullptr) trees[i]->Factor(); } void idPhysics_AF::PrimaryForces(const float step) { for (int i = 0; i < trees.Num(); ++i) if (trees[i] != nullptr) trees[i]->CalculateForces(step); } void idPhysics_AF::AuxiliaryForces(float) { SolveConstraintRows(auxiliaryConstraints, 8); SolveConstraintRows(frameConstraints, 8); } void idPhysics_AF::AddFrameConstraints() { frameConstraints.Clear(); const float inverseStep = timeStep > 0.0f ? 1.0f / timeStep : 0.0f; for (int i = 0; i < constraints.Num(); ++i) if (constraints[i] != nullptr) constraints[i]->ApplyFriction(inverseStep); for (int i = 0; i < contactConstraints.Num(); ++i) if (contactConstraints[i] != nullptr) contactConstraints[i]->ApplyFriction(inverseStep); } void idPhysics_AF::RemoveFrameConstraints() { frameConstraints.Clear(); } void idPhysics_AF::EvaluateConstraints(const float step) { const float inverseStep = step > 0.0f ? 1.0f / step : 0.0f; for (int i = 0; i < primaryConstraints.Num(); ++i) if (primaryConstraints[i] != nullptr) primaryConstraints[i]->Evaluate(this, inverseStep); for (int i = 0; i < auxiliaryConstraints.Num(); ++i) if (auxiliaryConstraints[i] != nullptr) auxiliaryConstraints[i]->Evaluate(this, inverseStep); for (int i = 0; i < contactConstraints.Num(); ++i) if (contactConstraints[i] != nullptr) contactConstraints[i]->Evaluate(this, inverseStep); AddFrameConstraints(); for (int i = 0; i < frameConstraints.Num(); ++i) if (frameConstraints[i] != nullptr && frameConstraints[i]->J1.GetNumRows() == 0) frameConstraints[i]->Evaluate(this, inverseStep); SolveConstraintRows(primaryConstraints, 8); SolveConstraintRows(auxiliaryConstraints, 8); // Contact rows are kept in their own authoritative list. idList contactRows(4); for (int i = 0; i < contactConstraints.Num(); ++i) contactRows.Append(contactConstraints[i]); SolveConstraintRows(contactRows, 8); SolveConstraintRows(frameConstraints, 8); } void idPhysics_AF::AddContacts(idAFBody* const body, const contactsResult_t& result) { if (body == nullptr) return; const int count = (std::min)(12, result.numContacts); for (int i = 0; i < count; ++i) { const contactInfo_t& info = result.contacts[i]; contacts.Append(info); UpdateCollisionResidency(info); bool bodyListed = false; const int bodyId = GetBodyId(body); for (int j = 0; j < contactBodies.Num(); ++j) bodyListed |= contactBodies[j] == bodyId; if (!bodyListed) contactBodies.Append(bodyId); if (!addContactConstraints) continue; idAFBody* otherBody = nullptr; idPhysics* const otherPhysics = idPhysics::GetPhysicsForId( info.physicsId); if (otherPhysics == this) otherBody = GetBody(info.bodyId); idAFConstraint_Contact* const contact = new idAFConstraint_Contact(); contact->physics = this; contact->Setup(body, otherBody, info, info.separation, timeStep > 0.0f ? 1.0f / timeStep : 0.0f); contactConstraints.Append(contact); } } void idPhysics_AF::SetupContactConstraints(const float step) { const float inverseStep = step > 0.0f ? 1.0f / step : 0.0f; for (int i = 0; i < contactConstraints.Num(); ++i) { idAFConstraint_Contact* const constraint = contactConstraints[i]; if (constraint != nullptr) constraint->Setup(constraint->body1, constraint->body2, constraint->contact, constraint->separation, inverseStep); } } void idPhysics_AF::CollisionImpulse(idAFBody* const body, const trace_t& collision, float) { if (body == nullptr || collision.fraction >= 1.0f) return; idVec3 velocity = body->GetPointVelocity(collision.c.point); const float into = velocity.Dot(collision.c.normal); if (into >= 0.0f) return; const float bounce = body->bouncyness >= 0.0f ? body->bouncyness : bouncyness; const idVec3 impulse = collision.c.normal * (-(1.0f + bounce) * into / (std::max)(body->invMass, 1.0e-6f)); ApplyImpulse(GetBodyId(body), &collision.c.point, &impulse); if (callbacks != nullptr) GameLib_NotifyPhysicsCollision(callbacks, GetPhysicsId(), collision, velocity); } void idPhysics_AF::IssueCollisionQueries() { for (int i = 0; i < constraints.Num(); ++i) if (constraints[i] != nullptr) constraints[i]->IssueCollisionQueries(); } void idPhysics_AF::EvaluateBodies(const float step) { for (int i = 0; i < bodies.Num(); ++i) { idAFBody* const body = bodies[i]; if (body == nullptr) continue; idVec3 linear = LinearVelocity(*body); idVec3 angular = AngularVelocity(*body); linear = linear + idVec3(body->current.externalForce[0], body->current.externalForce[1], body->current.externalForce[2]) * (body->invMass * step); angular = angular + body->inverseInertiaTensor * idVec3(body->current.externalForce[3], body->current.externalForce[4], body->current.externalForce[5]) * step; ::SetLinearVelocity(*body, linear); ::SetAngularVelocity(*body, angular); const idVec3 start = body->current.worldOrigin; const idVec3 end = start + linear * step; idVec3 angularAxis = angular; const float angularSpeed = angularAxis.NormalizeFast(); idRotation rotation(start, angularSpeed > 0.0f ? angularAxis : idVec3(0.0f, 0.0f, 1.0f), angularSpeed * step * RAD2DEG); trace_t collision{}; collision.fraction = 1.0f; collision.endpos = end; collision.endAxis = rotation.ToMat3() * body->current.worldAxis; contactsResult_t contactResult{}; if (clip != nullptr && body->clipModel != nullptr && body->clipMask != 0) { body->motionQuery = clip->MotionContacts(&collision, &contactResult, start, end, rotation, 0.25f, body->clipModel, body->current.worldAxis, body->clipMask, passEntityNum, false, "idPhysics_AF::EvaluateBodies"); } body->current.worldOrigin = collision.endpos; body->current.worldAxis = collision.endAxis; if (contactResult.numContacts > 0) AddContacts(body, contactResult); if (collision.fraction < 1.0f) CollisionImpulse(body, collision, step); ZeroSpatial(body->current.externalForce); } UpdateClipModels(); } void idPhysics_AF::ResolveCollisions(float) { AddContactPhysicsForContacts(); } bool idPhysics_AF::EvaluateContacts() { ClearContacts(); for (int i = 0; i < contactConstraints.Num(); ++i) delete contactConstraints[i]; contactConstraints.Clear(); contactBodies.Clear(); if (clip == nullptr) return false; for (int i = 0; i < bodies.Num(); ++i) { idAFBody* const body = bodies[i]; if (body == nullptr || body->clipModel == nullptr) continue; contactsResult_t result{}; body->motionQuery = clip->Contacts(&result, body->current.worldOrigin, gravityNormal, 0.25f, body->clipModel, body->current.worldAxis, body->clipMask, passEntityNum, "idPhysics_AF::EvaluateContacts"); AddContacts(body, result); } AddContactPhysicsForContacts(); return contacts.Num() > 0; } bool idPhysics_AF::TestIfAtRest(const float step) { float maximumLinear = 0.0f; float maximumAngular = 0.0f; float maximumAcceleration = 0.0f; for (int i = 0; i < bodies.Num(); ++i) { const idAFBody* const body = bodies[i]; if (body == nullptr) continue; maximumLinear = (std::max)(maximumLinear, LinearVelocity(*body).Length()); maximumAngular = (std::max)(maximumAngular, AngularVelocity(*body).Length()); maximumAcceleration = (std::max)(maximumAcceleration, idVec3(body->acceleration[0], body->acceleration[1], body->acceleration[2]).Length()); } const bool quiet = maximumLinear <= suspendVelocity.x && maximumAngular <= suspendVelocity.y && maximumAcceleration <= (std::max)(suspendAcceleration.x, suspendAcceleration.y); current.activateTime += step; if (quiet) current.noMoveTime += step; else current.noMoveTime = 0.0f; const bool minimumElapsed = minMoveTime < 0.0f || current.activateTime >= minMoveTime; const bool maximumElapsed = maxMoveTime >= 0.0f && current.activateTime >= maxMoveTime; if (comeToRest && ((minimumElapsed && current.noMoveTime >= noMoveTime) || maximumElapsed)) { PutToRest(); return true; } return false; } void idPhysics_AF::Evolve(const float step) { timeStep = step; lastTimeStep = step; ClearContacts(); for (int i = 0; i < contactConstraints.Num(); ++i) delete contactConstraints[i]; contactConstraints.Clear(); contactBodies.Clear(); RemoveFrameConstraints(); TestNoclipBodies(); ApplyFriction(step, static_cast(endTime)); AddGravity(); IssueCollisionQueries(); EvaluateConstraints(step); EvaluateBodies(step); ResolveCollisions(step); EvaluateContacts(); SetupContactConstraints(step); TestIfAtRest(step); } void idPhysics_AF::BuildTrees() { for (int i = 0; i < trees.Num(); ++i) delete trees[i]; trees.Clear(); primaryConstraints.Clear(); auxiliaryConstraints.Clear(); for (int i = 0; i < bodies.Num(); ++i) { if (bodies[i] == nullptr) continue; bodies[i]->parent = nullptr; bodies[i]->children.Clear(); bodies[i]->primaryConstraint = nullptr; bodies[i]->tree = nullptr; } for (int i = 0; i < constraints.Num(); ++i) { idAFConstraint* const constraint = constraints[i]; if (constraint == nullptr || !constraint->fl.allowPrimary || constraint->body1 == nullptr || constraint->body2 == nullptr || constraint->body1->parent != nullptr || IsDescendantOf(constraint->body2, constraint->body1)) { if (constraint != nullptr) auxiliaryConstraints.Append(constraint); continue; } constraint->fl.isPrimary = 1; constraint->body1->parent = constraint->body2; constraint->body1->primaryConstraint = constraint; constraint->body2->children.Append(constraint->body1); primaryConstraints.Append(constraint); } for (int i = 0; i < bodies.Num(); ++i) { idAFBody* const body = bodies[i]; if (body == nullptr || body->parent != nullptr) continue; idAFTree* const tree = new idAFTree(); tree->sortedBodies.Append(body); tree->SortBodies(); tree->SetMaxSubTreeAuxiliaryIndex(); trees.Append(tree); } changedAF = false; } void idPhysics_AF::DebugDraw() { for (int i = 0; i < constraints.Num(); ++i) if (constraints[i] != nullptr) constraints[i]->DebugDraw(); const idVec4 color(0.2f, 0.8f, 1.0f, 1.0f); for (int i = 0; i < trees.Num(); ++i) if (trees[i] != nullptr) trees[i]->DebugDraw(color); } float idPhysics_AF::GetLcpEpsilon(float) const { return lcpEpsilon.GetValue(endTime); } float idPhysics_AF::GetErrorReduction(float) const { return errorReduction.GetValue(endTime); } float idPhysics_AF::GetErrorReductionMax(float) const { return errorReductionMax.GetValue(endTime); } float idPhysics_AF::GetLimitErrorReduction(float) const { return limitErrorReduction.GetValue(endTime); } float idPhysics_AF::GetLimitErrorReductionMax(float) const { return limitErrorReductionMax.GetValue(endTime); } float idPhysics_AF::GetLimitLcpEpsilon(float) const { return limitLcpEpsilon.GetValue(endTime); } float idPhysics_AF::GetContactErrorReduction(float) const { return contactErrorReduction.GetValue(endTime); } float idPhysics_AF::GetContactErrorReductionMax(float) const { return contactErrorReductionMax.GetValue(endTime); } float idPhysics_AF::GetContactLcpEpsilon(float) const { return contactLcpEpsilon.GetValue(endTime); } float idPhysics_AF::GetUniversalErrorReduction(float) const { return universalErrorReduction.GetValue(endTime); } float idPhysics_AF::GetUniversalTorsionLcpEpsilon(float) const { return universalTorsionLcpEpsilon.GetValue(endTime); } bool idPhysics_AF::Evaluate(const int timeStepMSec, const int newEndTime) { endTime = newEndTime; if (changedAF) BuildTrees(); if (masterBody != nullptr && callbacks != nullptr) { idVec3 masterOrigin; idMat3 masterAxis; if (GameLib_GetMasterPhysicsTransform(callbacks, masterOrigin, masterAxis)) { SetMaster(true, &masterOrigin, &masterAxis, static_cast(0)); } } if (current.atRest) return false; float step = timeStepMSec * 0.001f * timeScale; if (timeScaleRampEnd > timeScaleRampStart && current.activateTime < timeScaleRampEnd) { const float fraction = (std::max)(0.0f, (std::min)(1.0f, (current.activateTime - timeScaleRampStart) / (timeScaleRampEnd - timeScaleRampStart))); step *= fraction; } if (step <= 0.0f) return false; Evolve(step); if (IsOutsideWorld()) { PutToRest(); if (callbacks != nullptr) GameLib_NotifyPhysicsDeactivated(callbacks, GetPhysicsId()); } return true; }