Files
tech5/source/engine/gamelib/effectphysics/effectphysicspieceemitter.cpp
T

360 lines
13 KiB
C++

#include "gamelib/effectphysics/effectphysicspieceemitter.h"
#include "gamelib/physics/tracemodelcache.h"
#include "idlib/geometry/tracemodel.h"
#include <algorithm>
// These operations cross from GameLib into the renderer/decl/sound modules.
// Their recovered implementations live with those owning subsystems; GameLib
// consumes them without inventing the very large render-model layouts here.
int GameLib_DiscreteAnimationPieceCount(
const idRenderModelDiscreteAnimation* model);
const idDeclBreakable* GameLib_DiscreteAnimationDecl(
const idRenderModelDiscreteAnimation* model);
bool GameLib_DiscreteAnimationGetPiece(
const idRenderModelDiscreteAnimation* model, int index,
idVec3& position, idMat3& orientation, idTraceModel& traceModel,
const idMaterial*& material);
void GameLib_DiscreteAnimationSetPiece(idRenderModelDiscreteAnimation* model,
int index, const idVec3& position, const idMat3& orientation);
void GameLib_DiscreteAnimationCollapsePiece(
idRenderModelDiscreteAnimation* model, int index);
void GameLib_DiscreteAnimationReset(idRenderModelDiscreteAnimation* model);
void GameLib_DiscreteAnimationUpdate(idRenderModelDiscreteAnimation* model);
void GameLib_DiscreteAnimationSetHidden(
idRenderModelDiscreteAnimation* model, bool hidden);
void GameLib_DiscreteAnimationDelete(idRenderModelDiscreteAnimation* model);
bool GameLib_GetBreakablePhysicsSettings(const idDeclBreakable* decl,
idEffectPhysicsPieceSettings& settings);
void GameLib_DrawEffectTraceModel(const idTraceModelCache* cache,
int traceModelIndex, int pieceIndex, const idVec3& origin,
const idMat3& axis);
void GameLib_PlayEffectPhysicsCollision(
idEffectPhysicsPieceEmitter::pieceEmitterSound_t& soundInfo,
idRenderModelEffects* effects, const idEffectPhysicsCollision& collision,
const idVec3& origin, float speedSqr, float volumeDb, int currentTime);
idEffectPhysicsPieceEmitter::idEffectPhysicsPieceEmitter()
: properties(0)
, pieces(0)
, broadPhase()
, random(0)
, pieceMass(10.0f)
, pieceCollisionAgeDecay(0.0f)
, pieceCollisionVolumeDecay(0.0f)
, model(nullptr)
, decl(nullptr)
, modelEffects(nullptr)
, soundInfo{nullptr, nullptr, 0, 0.0f}
, pieceLifeTime(0)
, pieceFriction(0.0f)
, pieceIndex(0)
, numPiecesInUse(0)
, hidden(false)
, hiddenOverride(false) {
}
idEffectPhysicsPieceEmitter::~idEffectPhysicsPieceEmitter() {
if (broadPhase.traceModelCache != nullptr) {
for (int index = 0; index < properties.Num(); ++index) {
if (properties[index] != nullptr) {
broadPhase.traceModelCache->FreeTraceModel(
properties[index]->traceModelIndex);
}
}
}
for (int index = 0; index < pieces.Num(); ++index) {
delete pieces[index].rigidBody;
pieces[index].rigidBody = nullptr;
}
for (int index = 0; index < properties.Num(); ++index) {
delete properties[index];
}
pieces.ClearFree();
properties.ClearFree();
if (model != nullptr) {
GameLib_DiscreteAnimationDelete(model);
model = nullptr;
}
}
void idEffectPhysicsPieceEmitter::ApplyDeclSettings(
idEffectPhysicsProperties& target) const {
idEffectPhysicsPieceSettings settings{};
if (decl == nullptr ||
!GameLib_GetBreakablePhysicsSettings(decl, settings)) {
return;
}
target.linearFriction = settings.linearFriction;
target.angularFriction = settings.angularFriction;
target.contactFriction = settings.contactFriction;
target.linearFrictionWater = settings.linearFrictionWater;
target.angularFrictionWater = settings.angularFrictionWater;
target.bouncyness = settings.bouncyness;
target.gravityVector = settings.gravityVector;
target.worldCollisionOnly = settings.worldCollisionOnly;
target.simplePointCollision = settings.simplePointCollision;
target.crazyBounceChance = settings.crazyBounceChance;
target.stopSpeed = settings.stopSpeed;
target.maxLinearVelocity = settings.maxLinearVelocity;
target.maxAngularVelocity = settings.maxAngularVelocity;
target.clipMask = settings.clipMask;
}
idEffectPhysicsProperties*
idEffectPhysicsPieceEmitter::GetPropertiesForTraceModel(
const idTraceModel& traceModel, const idMaterial* const material) {
if (broadPhase.traceModelCache == nullptr) {
return nullptr;
}
const int traceModelIndex = broadPhase.traceModelCache->AllocTraceModel(
traceModel, material);
for (int index = 0; index < properties.Num(); ++index) {
if (properties[index]->traceModelIndex == traceModelIndex) {
// AllocTraceModel returned another reference to an existing entry.
broadPhase.traceModelCache->FreeTraceModel(traceModelIndex);
return properties[index];
}
}
idEffectPhysicsProperties* const result =
new idEffectPhysicsProperties();
result->Setup(&broadPhase, traceModelIndex, pieceMass);
ApplyDeclSettings(*result);
properties.Append(result);
return result;
}
void idEffectPhysicsPieceEmitter::Init(idClip* const clip,
idTraceModelCache* const traceModelCache,
idRenderModelDiscreteAnimation* const renderModel,
idRenderModelEffects* const renderModelEffects,
const pieceEmitterSound_t* const sound, const int entityNumber) {
broadPhase.Init(clip, traceModelCache);
model = renderModel;
modelEffects = renderModelEffects;
decl = renderModel != nullptr
? GameLib_DiscreteAnimationDecl(renderModel) : nullptr;
if (sound != nullptr) {
soundInfo = *sound;
}
pieceIndex = 0;
numPiecesInUse = 0;
for (int index = 0; index < pieces.Num(); ++index) {
delete pieces[index].rigidBody;
}
pieces.Clear();
const int pieceCount = renderModel != nullptr
? GameLib_DiscreteAnimationPieceCount(renderModel) : 0;
pieces.SetNum((std::max)(0, pieceCount));
for (int index = 0; index < pieces.Num(); ++index) {
idVec3 position;
idMat3 orientation(1.0f);
idTraceModel traceModel;
const idMaterial* material = nullptr;
idBreakablePiece& piece = pieces[index];
piece.rigidBody = nullptr;
piece.collision.Clear();
piece.emitTime = 0;
piece.firstCollisionTime = 0;
if (!GameLib_DiscreteAnimationGetPiece(renderModel, index,
position, orientation, traceModel, material)) {
continue;
}
traceModel.Shrink(0.1875f);
idEffectPhysicsProperties* const pieceProperties =
GetPropertiesForTraceModel(traceModel, material);
if (pieceProperties == nullptr) {
continue;
}
piece.rigidBody = new idEffectPhysicsRigidBody();
piece.rigidBody->Setup(pieceProperties);
piece.rigidBody->SetPosition(position, orientation);
piece.rigidBody->SPObject.entityNum = entityNumber;
}
}
void idEffectPhysicsPieceEmitter::Reset() {
random.SetSeed(0);
pieceIndex = 0;
numPiecesInUse = 0;
if (model != nullptr) {
GameLib_DiscreteAnimationReset(model);
}
for (int index = 0; index < pieces.Num(); ++index) {
idBreakablePiece& piece = pieces[index];
piece.emitTime = 0;
piece.firstCollisionTime = 0;
piece.collision.Clear();
if (piece.rigidBody == nullptr) {
continue;
}
idVec3 position;
idMat3 orientation(1.0f);
idTraceModel ignoredTraceModel;
const idMaterial* ignoredMaterial = nullptr;
if (model != nullptr && GameLib_DiscreteAnimationGetPiece(model,
index, position, orientation, ignoredTraceModel,
ignoredMaterial)) {
piece.rigidBody->SetPosition(position, orientation);
}
piece.rigidBody->active = false;
piece.rigidBody->settled = false;
piece.rigidBody->currentState.linearMomentum.Zero();
piece.rigidBody->currentState.angularMomentum.Zero();
piece.rigidBody->SPObject.motion.ignore = false;
}
for (int index = 0; index < properties.Num(); ++index) {
ApplyDeclSettings(*properties[index]);
}
hiddenOverride = true;
if (model != nullptr) {
GameLib_DiscreteAnimationSetHidden(model, true);
}
}
void idEffectPhysicsPieceEmitter::EmitPiece(const int emitTime,
const idVec3& position, const idMat3& orientation,
const idVec3& impulsePoint, const idVec3& impulse) {
if (pieces.IsEmpty()) {
return;
}
idBreakablePiece& piece = pieces[pieceIndex];
if (piece.rigidBody == nullptr) {
pieceIndex = (pieceIndex + 1) % pieces.Num();
return;
}
if (!piece.rigidBody->active) {
++numPiecesInUse;
}
hiddenOverride = false;
piece.emitTime = emitTime;
piece.firstCollisionTime = 0;
piece.collision.Clear();
piece.rigidBody->SetPosition(position, orientation);
piece.rigidBody->currentState.linearMomentum.Zero();
piece.rigidBody->currentState.angularMomentum.Zero();
piece.rigidBody->ApplyImpulse(position + impulsePoint, impulse);
piece.rigidBody->active = true;
piece.rigidBody->settled = false;
piece.rigidBody->SPObject.motion.ignore = false;
pieceIndex = (pieceIndex + 1) % pieces.Num();
}
void idEffectPhysicsPieceEmitter::UpdateSimulation(const idVec3& origin,
const idMat3& axis, const int currentTime, const int gameMsPerFrame) {
(void)origin;
(void)axis;
const float deltaTime = (std::max)(0, gameMsPerFrame) * 0.001f;
for (int index = 0; index < pieces.Num(); ++index) {
idBreakablePiece& piece = pieces[index];
idEffectPhysicsRigidBody* const body = piece.rigidBody;
if (body == nullptr || !body->active) {
continue;
}
if (pieceLifeTime > 0 && currentTime >= piece.emitTime + pieceLifeTime) {
body->active = false;
body->SPObject.motion.ignore = true;
numPiecesInUse = (std::max)(0, numPiecesInUse - 1);
continue;
}
float momentumDecay = 0.0f;
if (piece.firstCollisionTime > 0 && pieceCollisionAgeDecay > 0.0f &&
pieceLifeTime > 0) {
momentumDecay = (std::min)(1.0f,
(currentTime - piece.firstCollisionTime) *
pieceCollisionAgeDecay /
static_cast<float>(pieceLifeTime));
}
body->Update(deltaTime, momentumDecay, piece.collision);
if (piece.collision.collisionNormalVelocity.LengthSqr() > 0.0f) {
if (piece.firstCollisionTime == 0) {
piece.firstCollisionTime = currentTime;
}
const idVec3 velocity = body->currentState.linearMomentum *
body->properties->inverseMass;
const float volumeDb = pieceCollisionVolumeDecay > 0.0f
? -(currentTime - piece.firstCollisionTime) *
pieceCollisionVolumeDecay * 0.001f
: 0.0f;
GameLib_PlayEffectPhysicsCollision(soundInfo, modelEffects,
piece.collision, body->currentState.position,
velocity.LengthSqr(), volumeDb, currentTime);
}
if (pieceFriction > 0.0f) {
const float damping = (std::max)(0.0f,
1.0f - pieceFriction * deltaTime);
body->currentState.linearMomentum =
body->currentState.linearMomentum * damping;
}
}
}
void idEffectPhysicsPieceEmitter::UpdateModel(
const idVec3& activeDrawScale3D) {
if (model == nullptr) {
return;
}
const idMat3 scale(activeDrawScale3D.x, 0.0f, 0.0f,
0.0f, activeDrawScale3D.y, 0.0f,
0.0f, 0.0f, activeDrawScale3D.z);
for (int index = 0; index < pieces.Num(); ++index) {
const idBreakablePiece& piece = pieces[index];
if (piece.rigidBody != nullptr && piece.rigidBody->active) {
GameLib_DiscreteAnimationSetPiece(model, index,
piece.rigidBody->currentState.position,
piece.rigidBody->currentState.orientation * scale);
} else {
GameLib_DiscreteAnimationCollapsePiece(model, index);
}
}
if (!hidden && !hiddenOverride) {
GameLib_DiscreteAnimationUpdate(model);
}
}
void idEffectPhysicsPieceEmitter::SubmitCollisionQueries(
const bool worldCollisionModelOnly) {
broadPhase.SubmitQueries(worldCollisionModelOnly);
}
void idEffectPhysicsPieceEmitter::DrawCollisionModels(const int single,
const idVec3& offset, const idMat3& axis) {
for (int index = 0; index < pieces.Num(); ++index) {
if (single >= 0 && single != index) {
continue;
}
const idBreakablePiece& piece = pieces[index];
if (piece.rigidBody == nullptr || piece.rigidBody->properties == nullptr) {
continue;
}
idVec3 drawOrigin = piece.rigidBody->currentState.position;
idMat3 drawAxis = piece.rigidBody->currentState.orientation;
if (!piece.rigidBody->active) {
drawOrigin = offset + axis * drawOrigin;
drawAxis *= axis;
}
GameLib_DrawEffectTraceModel(broadPhase.traceModelCache,
piece.rigidBody->properties->traceModelIndex, index,
drawOrigin, drawAxis);
}
}
void idEffectPhysicsPieceEmitter::Hide() {
hidden = true;
if (model != nullptr) {
GameLib_DiscreteAnimationSetHidden(model, true);
}
}
void idEffectPhysicsPieceEmitter::Show() {
hidden = false;
if (model != nullptr && !hiddenOverride) {
GameLib_DiscreteAnimationSetHidden(model, false);
}
}