Files
2026-02-02 04:50:13 +01:00

703 lines
21 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* Copyright (C) 2006-2010 - Frictional Games
*
* This file is part of HPL1 Engine.
*/
#include "hpl1/engine/physics/PhysicsJoint.h"
#include "hpl1/engine/physics/CollideShape.h"
#include "hpl1/engine/physics/PhysicsBody.h"
#include "hpl1/engine/physics/PhysicsController.h"
#include "hpl1/engine/physics/PhysicsWorld.h"
#include "hpl1/engine/system/low_level_system.h"
#include "hpl1/engine/scene/SoundEntity.h"
#include "hpl1/engine/sound/Sound.h"
#include "hpl1/engine/sound/SoundChannel.h"
#include "hpl1/engine/sound/SoundHandler.h"
#include "hpl1/engine/scene/Scene.h"
#include "hpl1/engine/scene/World3D.h"
#include "hpl1/engine/game/Game.h"
#include "hpl1/engine/math/Math.h"
#include "hpl1/engine/game/ScriptFuncs.h"
namespace hpl {
//////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
iPhysicsJoint::iPhysicsJoint(const tString &asName, iPhysicsBody *apParentBody, iPhysicsBody *apChildBody,
iPhysicsWorld *apWorld, const cVector3f &avPivotPoint)
: msName(asName), mpParentBody(apParentBody), mpChildBody(apChildBody), mpWorld(apWorld) {
mMaxLimit.msSound = "";
mMinLimit.msSound = "";
if (apParentBody) {
apParentBody->AddJoint(this);
m_mtxParentBodySetup = apParentBody->GetLocalMatrix();
} else {
m_mtxParentBodySetup = cMatrixf::Identity;
}
m_mtxPrevChild = cMatrixf::Identity;
m_mtxPrevParent = cMatrixf::Identity;
apChildBody->AddJoint(this);
m_mtxChildBodySetup = apChildBody->GetLocalMatrix();
cMatrixf m_mtxInvChild = cMath::MatrixInverse(apChildBody->GetLocalMatrix());
mvLocalPivot = cMath::MatrixMul(m_mtxInvChild, avPivotPoint);
mvStartPivotPoint = avPivotPoint;
msMoveSound = "";
mbHasCollided = false;
mpSound = NULL;
mpCallback = NULL;
mbAutoDeleteCallback = false;
mpUserData = NULL;
mbBreakable = false;
mfBreakForce = 0;
msBreakSound = "";
mbBroken = false;
mfStickyMinDistance = 0;
mfStickyMaxDistance = 0;
mlLimitStepCount = 0;
mlSpeedCount = 0;
mbLimitAutoSleep = false;
mfLimitAutoSleepDist = 0.02f;
mlLimitAutoSleepNumSteps = 10;
// Log("Created joint '%s'\n",msName.c_str());
}
iPhysicsJoint::~iPhysicsJoint() {
if (mbAutoDeleteCallback && mpCallback)
hplDelete(mpCallback);
// Destroy all controllers.
tPhysicsControllerMapIt it = m_mapControllers.begin();
for (; it != m_mapControllers.end(); ++it) {
mpWorld->DestroyController(it->second);
}
if (mpChildBody)
mpChildBody->RemoveJoint(this);
if (mpParentBody)
mpParentBody->RemoveJoint(this);
if (mpSound)
mpWorld->GetWorld3D()->DestroySoundEntity(mpSound);
// Log("Deleted joint '%s'\n",msName.c_str());
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
/**
* This should only be used by PhysicsBody.
*/
void iPhysicsJoint::RemoveBody(iPhysicsBody *apBody) {
if (mpParentBody == apBody)
mpParentBody = NULL;
if (mpChildBody == apBody)
mpChildBody = NULL;
}
//-----------------------------------------------------------------------
void iPhysicsJoint::AddController(iPhysicsController *apController) {
// Add controller top map
m_mapControllers.insert(tPhysicsControllerMap::value_type(apController->GetName(), apController));
// Set properties
apController->SetBody(mpChildBody);
apController->SetJoint(this);
}
//-----------------------------------------------------------------------
iPhysicsController *iPhysicsJoint::GetController(const tString &asName) {
tPhysicsControllerMapIt it = m_mapControllers.find(asName);
if (it == m_mapControllers.end())
return NULL;
return it->second;
}
//-----------------------------------------------------------------------
bool iPhysicsJoint::ChangeController(const tString &asName) {
iPhysicsController *pNewCtrl = GetController(asName);
if (pNewCtrl == NULL)
return false;
tPhysicsControllerMapIt it = m_mapControllers.begin();
for (; it != m_mapControllers.end(); ++it) {
iPhysicsController *pCtrl = it->second;
if (pCtrl == pNewCtrl) {
pCtrl->SetActive(true);
} else {
pCtrl->SetActive(false);
}
}
return true;
}
//-----------------------------------------------------------------------
void iPhysicsJoint::SetAllControllersPaused(bool abX) {
tPhysicsControllerMapIt it = m_mapControllers.begin();
for (; it != m_mapControllers.end(); ++it) {
iPhysicsController *pCtrl = it->second;
pCtrl->SetPaused(abX);
}
}
//-----------------------------------------------------------------------
cPhysicsControllerIterator iPhysicsJoint::GetControllerIterator() {
return cPhysicsControllerIterator(&m_mapControllers);
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// PROTECTED METHODS
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
void iPhysicsJoint::OnMaxLimit() {
if (mbHasCollided == false && mpCallback) {
mpCallback->OnMaxLimit(this);
}
//////////////////////////////////////////////////
// Check if any of the controllers has a OnMax end.
if (mbHasCollided == false) {
// Log("OnMax!\n");
tPhysicsControllerMapIt it = m_mapControllers.begin();
for (; it != m_mapControllers.end(); ++it) {
iPhysicsController *pCtrl = it->second;
// Log("Ctrl %s: %d\n",pCtrl->GetName().c_str(),(int)pCtrl->GetEndType());
if (pCtrl->IsActive() && pCtrl->GetEndType() == ePhysicsControllerEnd_OnMax) {
pCtrl->SetActive(false);
iPhysicsController *pNextCtrl = GetController(pCtrl->GetNextController());
if (pNextCtrl)
pNextCtrl->SetActive(true);
else
Warning("Controller '%s' does not exist in joint '%s'\n", pCtrl->GetNextController().c_str(), msName.c_str());
}
}
}
LimitEffect(&mMaxLimit);
}
//-----------------------------------------------------------------------
void iPhysicsJoint::OnMinLimit() {
if (mbHasCollided == false && mpCallback) {
mpCallback->OnMinLimit(this);
}
//////////////////////////////////////////////////
// Check if any of the controllers has a OnMin end.
if (mbHasCollided == false) {
// Log("OnMin!\n");
tPhysicsControllerMapIt it = m_mapControllers.begin();
for (; it != m_mapControllers.end(); ++it) {
iPhysicsController *pCtrl = it->second;
if (pCtrl->IsActive() && pCtrl->GetEndType() == ePhysicsControllerEnd_OnMin) {
pCtrl->SetActive(false);
iPhysicsController *pNextCtrl = GetController(pCtrl->GetNextController());
if (pNextCtrl)
pNextCtrl->SetActive(true);
else
Warning("Controller '%s' does not exist in joint '%s'\n", pCtrl->GetNextController().c_str(), msName.c_str());
}
}
}
LimitEffect(&mMinLimit);
}
//-----------------------------------------------------------------------
void iPhysicsJoint::CalcSoundFreq(float afSpeed, float *apFreq, float *apVol) {
float fAbsSpeed = ABS(afSpeed);
float fFreq = 1;
float fVolume = 1;
// Higher than middle
if (fAbsSpeed >= mfMiddleMoveSpeed) {
if (fAbsSpeed >= mfMaxMoveFreqSpeed) {
fFreq = mfMaxMoveFreq;
fVolume = mfMaxMoveVolume;
} else {
// Calculate how close the speed is to max.
float fT = (fAbsSpeed - mfMiddleMoveSpeed) /
(mfMaxMoveFreqSpeed - mfMiddleMoveSpeed);
fFreq = (1 - fT) + fT * mfMaxMoveFreq;
fVolume = mfMiddleMoveVolume * (1 - fT) + fT * mfMaxMoveVolume;
}
}
// Below middle
else {
if (fAbsSpeed <= mfMinMoveFreqSpeed) {
fFreq = mfMinMoveFreq;
fVolume = mfMinMoveVolume;
} else {
// Calculate how close the speed is to max.
float fT = (mfMiddleMoveSpeed - fAbsSpeed) /
(mfMiddleMoveSpeed - mfMinMoveFreqSpeed);
fFreq = (1 - fT) + fT * mfMinMoveFreq;
fVolume = mfMiddleMoveVolume * (1 - fT) + fT * mfMinMoveVolume;
}
}
*apFreq = fFreq;
*apVol = fVolume;
}
//-----------------------------------------------------------------------
void iPhysicsJoint::OnPhysicsUpdate() {
// Get the pivot point, if there is no parent, it is stuck.
if (mpParentBody)
mvPivotPoint = cMath::MatrixMul(mpChildBody->GetLocalMatrix(), mvLocalPivot);
cWorld3D *pWorld3D = mpWorld->GetWorld3D();
if (pWorld3D == NULL)
return;
if (msMoveSound == "")
return;
if (mpWorld->GetWorld3D()->GetSound()->GetSoundHandler()->GetSilent())
return;
//////////////////////////////////////
// Get the speed
cVector3f vVel(0, 0, 0);
// Linear
if (mMoveSpeedType == ePhysicsJointSpeed_Linear) {
if (mpParentBody) {
vVel = mpChildBody->GetLinearVelocity() - mpParentBody->GetLinearVelocity();
} else {
vVel = mpChildBody->GetLinearVelocity();
}
}
// Angular
else {
if (mpParentBody) {
vVel = mpChildBody->GetAngularVelocity() - mpParentBody->GetAngularVelocity();
} else {
vVel = mpChildBody->GetAngularVelocity();
}
}
// Check so the body is not still
if (mpParentBody) {
if (m_mtxPrevChild == mpChildBody->GetLocalMatrix() &&
m_mtxPrevParent == mpParentBody->GetLocalMatrix()) {
vVel = 0;
}
m_mtxPrevChild = mpChildBody->GetLocalMatrix();
m_mtxPrevParent = mpParentBody->GetLocalMatrix();
} else {
if (m_mtxPrevChild == mpChildBody->GetLocalMatrix()) {
vVel = 0;
}
m_mtxPrevChild = mpChildBody->GetLocalMatrix();
}
float fSpeed = vVel.Length();
if (pWorld3D->SoundEntityExists(mpSound) == false) {
mpSound = NULL;
}
//////////////////////////////////////
// Create and update sound if speed is high enough
// Joint has sound
if (mpSound) {
// Log("Updating %s\n",mpSound->GetName().c_str());
float fMin = cMath::Max(mfMinMoveSpeed - 0.2f, 0.1f);
if (fSpeed <= fMin) {
mpSound->FadeOut(4.3f);
mpSound = NULL;
} else {
// Log("Getting entry!\n");
cSoundEntry *pEntry = mpSound->GetSoundEntry(eSoundEntityType_Main);
if (pEntry) {
// Log("Update entry!\n");
float fFreq, fVolume;
CalcSoundFreq(fSpeed, &fFreq, &fVolume);
pEntry->mfNormalSpeed = fFreq;
pEntry->mfNormalVolumeMul = fVolume;
// Log("Speed: %f Vol: %f Freq: %f\n",fSpeed,fVolume,fFreq);
} else {
// Log("Null entry!\n");
}
mpSound->SetPosition(mvPivotPoint);
}
}
//////////////////////
// Joint has no sound
else {
/////////////////////////////
// Speed is over limit
if (fSpeed > mfMinMoveSpeed) {
if (mlSpeedCount >= 3) {
mlSpeedCount = 0;
mpSound = pWorld3D->CreateSoundEntity("MoveSound", msMoveSound, true);
if (mpSound) {
mpSound->SetIsSaved(false);
mpSound->FadeIn(3.3f);
}
// Log("Starting!\n");
} else {
mlSpeedCount++;
}
}
/////////////////////////////
// Speed is under limit
else {
mlSpeedCount = 0;
}
}
}
//-----------------------------------------------------------------------
void iPhysicsJoint::LimitEffect(cJointLimitEffect *pEffect) {
cWorld3D *pWorld3D = mpWorld->GetWorld3D();
if (pWorld3D && pEffect->msSound != "") {
cVector3f vVel(0, 0, 0);
if (mpParentBody)
vVel = mpChildBody->GetLinearVelocity() - mpParentBody->GetLinearVelocity();
else
vVel = mpChildBody->GetLinearVelocity();
float fSpeed = vVel.Length();
if (fSpeed > pEffect->mfMaxSpeed)
fSpeed = pEffect->mfMaxSpeed;
// Log("Speed: %f\n",fSpeed);
if (fSpeed >= pEffect->mfMinSpeed && mbHasCollided == false && pEffect->msSound != "") {
float fVolume = (fSpeed - pEffect->mfMinSpeed) / (pEffect->mfMaxSpeed - pEffect->mfMinSpeed);
cSoundEntity *pSound = pWorld3D->CreateSoundEntity("LimitSound", pEffect->msSound, true);
if (pSound) {
pSound->SetVolume(fVolume);
pSound->SetPosition(mpChildBody->GetLocalPosition());
}
}
}
mbHasCollided = true;
}
//-----------------------------------------------------------------------
void iPhysicsJoint::OnNoLimit() {
mbHasCollided = false;
}
//-----------------------------------------------------------------------
void iPhysicsJoint::Break() {
mbBroken = true;
mbBreakable = true;
}
//-----------------------------------------------------------------------
bool iPhysicsJoint::CheckBreakage() {
if (mbBreakable == false)
return false;
float fForcesSize = GetForce().Length();
if (fForcesSize >= mfBreakForce || mbBroken) {
if (msBreakSound != "") {
cWorld3D *pWorld3D = mpWorld->GetWorld3D();
cSoundEntity *pSound = pWorld3D->CreateSoundEntity("BreakSound", msBreakSound, true);
if (pSound)
pSound->SetPosition(mvPivotPoint);
}
return true;
}
return false;
}
//-----------------------------------------------------------------------
void iPhysicsJoint::CheckLimitAutoSleep(iPhysicsJoint *apJoint,
const float afMin, const float afMax,
const float afDist) {
if (apJoint->mbLimitAutoSleep) {
float fMinDiff = ABS(afMin - afDist);
float fMaxDiff = ABS(afMax - afDist);
if (fMaxDiff < apJoint->mfLimitAutoSleepDist ||
fMinDiff < apJoint->mfLimitAutoSleepDist) {
if (apJoint->mlLimitStepCount >= apJoint->mlLimitAutoSleepNumSteps)
apJoint->mpChildBody->DisableAfterSimulation();
else
apJoint->mlLimitStepCount++;
} else {
apJoint->mlLimitStepCount = 0;
}
}
}
//-----------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////
// SAVE OBJECT STUFF
//////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------
kBeginSerializeBase(cJointLimitEffect)
kSerializeVar(msSound, eSerializeType_String)
kSerializeVar(mfMinSpeed, eSerializeType_Float32)
kSerializeVar(mfMaxSpeed, eSerializeType_Float32)
kEndSerialize()
//-----------------------------------------------------------------------
kBeginSerializeVirtual(cSaveData_iPhysicsJoint, iSaveData)
kSerializeVar(msName, eSerializeType_String)
kSerializeVar(mlParentBodyId, eSerializeType_Int32)
kSerializeVar(mlChildBodyId, eSerializeType_Int32)
kSerializeVar(m_mtxParentBodySetup, eSerializeType_Matrixf)
kSerializeVar(m_mtxChildBodySetup, eSerializeType_Matrixf)
kSerializeVar(mvPinDir, eSerializeType_Vector3f)
kSerializeVar(mvStartPivotPoint, eSerializeType_Vector3f)
kSerializeClassContainer(mlstControllers, cSaveData_iPhysicsController, eSerializeType_Class)
kSerializeVar(mMaxLimit, eSerializeType_Class)
kSerializeVar(mMinLimit, eSerializeType_Class)
kSerializeVar(msMoveSound, eSerializeType_String)
kSerializeVar(mfMinMoveSpeed, eSerializeType_Float32)
kSerializeVar(mfMinMoveFreq, eSerializeType_Float32)
kSerializeVar(mfMinMoveFreqSpeed, eSerializeType_Float32)
kSerializeVar(mfMinMoveVolume, eSerializeType_Float32)
kSerializeVar(mfMaxMoveFreq, eSerializeType_Float32)
kSerializeVar(mfMaxMoveFreqSpeed, eSerializeType_Float32)
kSerializeVar(mfMaxMoveVolume, eSerializeType_Float32)
kSerializeVar(mfMiddleMoveSpeed, eSerializeType_Float32)
kSerializeVar(mfMiddleMoveVolume, eSerializeType_Float32)
kSerializeVar(mMoveSpeedType, eSerializeType_Int32)
kSerializeVar(mbBreakable, eSerializeType_Bool)
kSerializeVar(mfBreakForce, eSerializeType_Float32)
kSerializeVar(msBreakSound, eSerializeType_String)
kSerializeVar(mbBroken, eSerializeType_Bool)
kSerializeVar(msCallbackMaxFunc, eSerializeType_String)
kSerializeVar(msCallbackMinFunc, eSerializeType_String)
kSerializeVar(mbAutoDeleteCallback, eSerializeType_Bool)
kEndSerialize()
//-----------------------------------------------------------------------
iSaveData *iPhysicsJoint::CreateSaveData() {
return NULL;
}
//-----------------------------------------------------------------------
void iPhysicsJoint::SaveToSaveData(iSaveData *apSaveData) {
kSaveData_SaveToBegin(iPhysicsJoint);
//////////////////////////
// Variables
kSaveData_SaveTo(msName);
kSaveData_SaveTo(m_mtxParentBodySetup);
kSaveData_SaveTo(m_mtxChildBodySetup);
kSaveData_SaveTo(mvPinDir);
kSaveData_SaveTo(mvStartPivotPoint);
kSaveData_SaveTo(mMaxLimit);
kSaveData_SaveTo(mMinLimit);
kSaveData_SaveTo(msMoveSound);
kSaveData_SaveTo(mfMinMoveSpeed);
kSaveData_SaveTo(mfMinMoveFreq);
kSaveData_SaveTo(mfMinMoveFreqSpeed);
kSaveData_SaveTo(mfMinMoveVolume);
kSaveData_SaveTo(mfMaxMoveFreq);
kSaveData_SaveTo(mfMaxMoveFreqSpeed);
kSaveData_SaveTo(mfMaxMoveVolume);
kSaveData_SaveTo(mfMiddleMoveSpeed);
kSaveData_SaveTo(mfMiddleMoveVolume);
kSaveData_SaveTo(mMoveSpeedType);
kSaveData_SaveTo(mbBreakable);
kSaveData_SaveTo(mfBreakForce);
kSaveData_SaveTo(msBreakSound);
kSaveData_SaveTo(mbBroken);
kSaveData_SaveTo(mbAutoDeleteCallback);
// Callback
if (mpCallback && mpCallback->IsScript()) {
cScriptJointCallback *pScriptCallback = static_cast<cScriptJointCallback *>(mpCallback);
pData->msCallbackMaxFunc = pScriptCallback->msMaxFunc;
pData->msCallbackMinFunc = pScriptCallback->msMinFunc;
} else {
pData->msCallbackMaxFunc = "";
pData->msCallbackMinFunc = "";
}
//////////////////////////
// Controllers
pData->mlstControllers.Clear();
tPhysicsControllerMapIt it = m_mapControllers.begin();
for (; it != m_mapControllers.end(); ++it) {
iPhysicsController *pController = it->second;
cSaveData_iPhysicsController saveController;
pController->SaveToSaveData(&saveController);
pData->mlstControllers.Add(saveController);
}
//////////////////////////
// Pointers
kSaveData_SaveObject(mpParentBody, mlParentBodyId);
kSaveData_SaveObject(mpChildBody, mlChildBodyId);
}
//-----------------------------------------------------------------------
void iPhysicsJoint::LoadFromSaveData(iSaveData *apSaveData) {
kSaveData_LoadFromBegin(iPhysicsJoint);
//////////////////////////
// Variables
kSaveData_LoadFrom(msName);
kSaveData_LoadFrom(m_mtxParentBodySetup);
kSaveData_LoadFrom(m_mtxChildBodySetup);
kSaveData_LoadFrom(mvPinDir);
kSaveData_LoadFrom(mvStartPivotPoint);
kSaveData_LoadFrom(mMaxLimit);
kSaveData_LoadFrom(mMinLimit);
kSaveData_LoadFrom(msMoveSound);
kSaveData_LoadFrom(mfMinMoveSpeed);
kSaveData_LoadFrom(mfMinMoveFreq);
kSaveData_LoadFrom(mfMinMoveFreqSpeed);
kSaveData_LoadFrom(mfMinMoveVolume);
kSaveData_LoadFrom(mfMaxMoveFreq);
kSaveData_LoadFrom(mfMaxMoveFreqSpeed);
kSaveData_LoadFrom(mfMaxMoveVolume);
kSaveData_LoadFrom(mfMiddleMoveSpeed);
kSaveData_LoadFrom(mfMiddleMoveVolume);
kSaveData_LoadFrom(mbBreakable);
kSaveData_LoadFrom(mfBreakForce);
kSaveData_LoadFrom(msBreakSound);
kSaveData_LoadFrom(mbBroken);
mMoveSpeedType = (ePhysicsJointSpeed)pData->mMoveSpeedType;
kSaveData_LoadFrom(mbAutoDeleteCallback);
//////////////////////////
// Controllers
cContainerListIterator<cSaveData_iPhysicsController> CtrlIt = pData->mlstControllers.GetIterator();
while (CtrlIt.HasNext()) {
cSaveData_iPhysicsController &saveCtrl = CtrlIt.Next();
iPhysicsController *pController = mpWorld->CreateController(saveCtrl.msName);
pController->LoadFromSaveData(&saveCtrl);
AddController(pController);
}
}
//-----------------------------------------------------------------------
void iPhysicsJoint::SaveDataSetup(cSaveObjectHandler *apSaveObjectHandler, cGame *apGame) {
kSaveData_SetupBegin(iPhysicsJoint);
if (pData->msCallbackMaxFunc != "" || pData->msCallbackMinFunc != "") {
cScriptJointCallback *pCallback = hplNew(cScriptJointCallback, (apGame->GetScene()));
pCallback->msMaxFunc = pData->msCallbackMaxFunc;
pCallback->msMinFunc = pData->msCallbackMinFunc;
SetCallback(pCallback, true);
}
}
//-----------------------------------------------------------------------
} // namespace hpl