/* 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 . * */ /* * Copyright (C) 2006-2010 - Frictional Games * * This file is part of HPL1 Engine. */ #include "hpl1/engine/scene/MeshEntity.h" #include "hpl1/engine/graphics/Material.h" #include "hpl1/engine/graphics/Mesh.h" #include "hpl1/engine/graphics/RenderList.h" #include "hpl1/engine/graphics/SubMesh.h" #include "hpl1/engine/graphics/VertexBuffer.h" #include "hpl1/engine/resources/AnimationManager.h" #include "hpl1/engine/resources/MaterialManager.h" #include "hpl1/engine/resources/MeshManager.h" #include "hpl1/engine/resources/Resources.h" #include "hpl1/engine/resources/FileSearcher.h" #include "hpl1/engine/resources/MeshLoaderHandler.h" #include "hpl1/engine/graphics/Animation.h" #include "hpl1/engine/graphics/AnimationTrack.h" #include "hpl1/engine/graphics/Bone.h" #include "hpl1/engine/graphics/BoneState.h" #include "hpl1/engine/graphics/Skeleton.h" #include "hpl1/engine/scene/AnimationState.h" #include "hpl1/engine/scene/NodeState.h" #include "hpl1/engine/scene/Scene.h" #include "hpl1/engine/scene/SoundEntity.h" #include "hpl1/engine/scene/World3D.h" #include "hpl1/engine/physics/PhysicsBody.h" #include "hpl1/engine/physics/PhysicsWorld.h" #include "hpl1/engine/math/Math.h" #include "hpl1/engine/game/Game.h" namespace hpl { ////////////////////////////////////////////////////////////////////////// // CONSTRUCTORS ////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------- cMeshEntity::cMeshEntity(const tString asName, cMesh *apMesh, cMaterialManager *apMaterialManager, cMeshManager *apMeshManager, cAnimationManager *apAnimationManager) : iRenderable(asName) { mpMaterialManager = apMaterialManager; mpMeshManager = apMeshManager; mpAnimationManager = apAnimationManager; mpWorld = NULL; mpCallback = NULL; mbCastShadows = false; mpMesh = apMesh; mpRootNode = NULL; mpRootCallback = NULL; mpBody = NULL; mbSkeletonPhysics = false; mfSkeletonPhysicsWeight = 1.0f; mbSkeletonPhysicsFading = false; mfSkeletonPhysicsFadeSpeed = 1.0f; mbSkeletonPhysicsSleeping = false; mbSkeletonPhysicsCanSleep = true; mbSkeletonColliders = false; mbUpdatedBones = false; mlStartSleepCount = 0; mlUpdateCount = 0; mfTimeStepAccum = 0; //////////////////////////////////////////////// // Create sub entities for (int i = 0; i < mpMesh->GetSubMeshNum(); i++) { cSubMesh *pSubMesh = mpMesh->GetSubMesh(i); cSubMeshEntity *pSub = hplNew(cSubMeshEntity, (pSubMesh->GetName(), this, pSubMesh, mpMaterialManager)); // Log("Creating sub entity %s\n",pSub->GetName().c_str()); mvSubMeshes.push_back(pSub); m_mapSubMeshes.insert(tSubMeshEntityMap::value_type(mpMesh->GetSubMesh(i)->GetName(), pSub)); iVertexBuffer *pVtxBuffer = mpMesh->GetSubMesh(i)->GetVertexBuffer(); if (mpMesh->GetNodeNum() <= 0) { mBoundingVolume.AddArrayPoints(pVtxBuffer->GetArray(eVertexFlag_Position), pVtxBuffer->GetVertexNum()); } else { pSub->mBoundingVolume.AddArrayPoints(pVtxBuffer->GetArray(eVertexFlag_Position), pVtxBuffer->GetVertexNum()); pSub->mBoundingVolume.CreateFromPoints(kvVertexElements[cMath::Log2ToInt(eVertexFlag_Position)]); } } if (mpMesh->GetNodeNum() <= 0) { mBoundingVolume.CreateFromPoints(kvVertexElements[cMath::Log2ToInt(eVertexFlag_Position)]); // Log("CREATED BV Min: %s Max: %s\n", mBoundingVolume.GetMin().ToString().c_str(), // mBoundingVolume.GetMax().ToString().c_str()); } //////////////////////////////////////////////// // Create animation states mvAnimationStates.reserve(mpMesh->GetAnimationNum()); for (int i = 0; i < mpMesh->GetAnimationNum(); i++) { cAnimation *pAnimation = mpMesh->GetAnimation(i); cAnimationState *pAnimState = hplNew(cAnimationState, (pAnimation, pAnimation->GetName(), NULL)); mvAnimationStates.push_back(pAnimState); tAnimationStateIndexMap::value_type value(pAnimState->GetName(), (int)mvAnimationStates.size() - 1); m_mapAnimationStateIndices.insert(value); } //////////////////////////////////////////////// // Create Nodes mbHasNodes = false; if (mpMesh->GetNodeNum() > 0 && (mpMesh->GetPhysicsJointNum() <= 0 || mpMesh->GetSkeleton() == NULL) && mpMesh->GetAnimationNum() > 0) { mbHasNodes = true; mvNodeStates.reserve(mpMesh->GetNodeNum()); // Create the root node and attach all node without parents to this. mpRootNode = hplNew(cNode3D, ("NodeRoot", false)); // Create the root callback mpRootCallback = hplNew(cMeshEntityRootNodeUpdate, ()); this->AddCallback(mpRootCallback); // Fill the node array. for (int i = 0; i < mpMesh->GetNodeNum(); i++) { cNode3D *pMeshNode = mpMesh->GetNode(i); cBoneState *pNode = hplNew(cBoneState, (pMeshNode->GetName(), false)); pNode->SetMatrix(pMeshNode->GetLocalMatrix()); /*Log("Node: %s has local translate: %s world translate: %s\n", pNode->GetName(), pMeshNode->GetLocalMatrix().GetTranslation().ToString().c_str(), pMeshNode->GetWorldMatrix().GetTranslation().ToString().c_str());*/ // Add node to array and add it's index to the map. mvNodeStates.push_back(pNode); m_mapNodeStateIndices.insert(tNodeStateIndexMap::value_type(pNode->GetName(), i)); // Connect with sub mesh entity cSubMeshEntity *pSubEntity = GetSubMeshEntityName(pMeshNode->GetSource()); if (pSubEntity) { pSubEntity->SetLocalNode(pNode); } } // Set parents and children of the nodes in the array for (int i = 0; i < (int)mvNodeStates.size(); i++) { cNode3D *pStateNode = mvNodeStates[i]; cNode3D *pMeshNode = mpMesh->GetNode(i); // Set the parent if there is one if (pMeshNode->GetParent()) { cNode3D *pParentNode = GetNodeStateFromName(pMeshNode->GetParent()->GetName()); if (pParentNode) pStateNode->SetParent(pParentNode); else pStateNode->SetParent(mpRootNode); } // If not set root node as parent. else { pStateNode->SetParent(mpRootNode); } // Add children if there are any. cNodeIterator it = pMeshNode->GetChildIterator(); while (it.HasNext()) { cNode3D *pChildNode = static_cast(it.Next()); pStateNode->AddChild(GetNodeStateFromName(pChildNode->GetName())); } } // make sure all nodes are updated. mpRootNode->SetMatrix(cMatrixf::Identity); UpdateBVFromSubs(); } //////////////////////////////////////////////// // Create Joint Nodes // These are created to support joints. else if (mpMesh->GetPhysicsJointNum() > 0 || mpMesh->HasSeveralBodies()) { mbHasNodes = true; // Fill the node array. for (int i = 0; i < mpMesh->GetNodeNum(); i++) { cNode3D *pMeshNode = mpMesh->GetNode(i); // Log("MeshNode %s\n local: (%s)\n world: (%s)\n",pMeshNode->GetName(), // pMeshNode->GetLocalPosition().ToString().c_str(), // pMeshNode->GetWorldPosition().ToString().c_str()); // Set the sub entity with the mesh node world matrix cSubMeshEntity *pSubEntity = GetSubMeshEntityName(pMeshNode->GetSource()); if (pSubEntity) { /*cSubMesh *pSubMesh = */ pSubEntity->GetSubMesh(); cMatrixf mtxSub = pMeshNode->GetWorldMatrix(); pSubEntity->SetMatrix(mtxSub); // Log(" SubEnity: %s\n",pSubEntity->GetWorldPosition().ToString().c_str()); } } UpdateBVFromSubs(); } //////////////////////////////////////////////// // Create Skeleton // Create the node states from skeleton cSkeleton *pSkeleton = mpMesh->GetSkeleton(); // Create bones states if there is a skeleton. if (pSkeleton) { mbApplyTransformToBV = false; mbHasNodes = false; if (mpRootNode == NULL) { // Create the root node and attach all node without parents to this. mpRootNode = hplNew(cNode3D, ("NodeRoot", false)); // Create the root callback mpRootCallback = hplNew(cMeshEntityRootNodeUpdate, ()); this->AddCallback(mpRootCallback); } mvBoneStates.reserve(pSkeleton->GetBoneNum()); // Fill the state array with the bones so // that each state has the same index as the bones. for (int i = 0; i < pSkeleton->GetBoneNum(); i++) { cBone *pBone = pSkeleton->GetBoneByIndex(i); cBoneState *pState = hplNew(cBoneState, (pBone->GetName(), false)); pState->SetMatrix(pBone->GetLocalTransform()); // Log("Created bone state: '%s'\n",pState->GetName()); // Add bone to array and add it's index to the map. mvBoneStates.push_back(pState); m_mapBoneStateIndices.insert(tBoneIdxNameMap::value_type(pState->GetName(), i)); } // Set parents and children of the nodes in the array for (int i = 0; i < (int)mvBoneStates.size(); i++) { cNode3D *pState = mvBoneStates[i]; cBone *pBone = pSkeleton->GetBoneByIndex(i); // Log("State: %s\n",pState->GetName()); // Set the parent if there is one // TODO: Perhaps this should be removed. if (pBone->GetParent()) { cNode3D *pParentState = GetBoneStateFromName(pBone->GetParent()->GetName()); if (pParentState) pState->SetParent(pParentState); else pState->SetParent(mpRootNode); // else // Error("Couldn't find parent bone state '%s' in bone\n",pBone->GetParent()->GetName().c_str(), // pBone->GetName().c_str()); // if(pParentState)Log(" Parent: %s\n",pParentState->GetName()); } else { pState->SetParent(mpRootNode); } // Add children if there are any. /*cBoneIterator it = pBone->GetChildIterator(); while(it.HasNext()) { cBone* pChildBone = it.Next(); cNode3D *pChildState = GetBoneStateFromName(pChildBone->GetName()); if(pChildState) pState->AddChild(pChildState); //else // Error("Couldn't find child bone state '%s'",pChildBone->GetName()); //if(pChildState)Log(" Child: %s\n",pChildState->GetName()); }*/ } // Create an array to fill with bone matrices mvBoneMatrices.resize(pSkeleton->GetBoneNum()); // Reset all bones states for (size_t i = 0; i < mvBoneStates.size(); i++) { cNode3D *pState = mvBoneStates[i]; cBone *pBone = mpMesh->GetSkeleton()->GetBoneByIndex((int)i); pState->SetMatrix(pBone->GetLocalTransform()); } // Create temp bone nodes mvTempBoneStates.resize(mvBoneStates.size()); for (size_t i = 0; i < mvTempBoneStates.size(); i++) { mvTempBoneStates[i] = hplNew(cBoneState, (mvBoneStates[i]->GetName(), false)); } } //////////////////////////////////////////////////// // If it has no nodes, attach the sub entities to the // main mesh if (mbHasNodes == false) { for (size_t i = 0; i < mvSubMeshes.size(); ++i) { AddChild(mvSubMeshes[i]); } } } //----------------------------------------------------------------------- cMeshEntity::~cMeshEntity() { for (tEntity3DListIt it = mlstAttachedEntities.begin(); it != mlstAttachedEntities.end(); ++it) { // iEntity3D *pEntity = *it; // TODO: if(mpWorld) mpWorld->DestroyUnknownEntity(pEntity); } for (int i = 0; i < (int)mvSubMeshes.size(); i++) { hplDelete(mvSubMeshes[i]); } if (mpRootNode) hplDelete(mpRootNode); if (mpRootCallback) hplDelete(mpRootCallback); mpMeshManager->Destroy(mpMesh); STLDeleteAll(mvNodeStates); STLDeleteAll(mvBoneStates); STLDeleteAll(mvTempBoneStates); STLDeleteAll(mvAnimationStates); } //----------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////////// // ROOT NODE UDPATE CALLBACK ////////////////////////////////////////////////////////////////////////// void cMeshEntityRootNodeUpdate::OnTransformUpdate(iEntity3D *apEntity) { cMeshEntity *pMeshEntity = static_cast(apEntity); pMeshEntity->mpRootNode->SetMatrix(apEntity->GetWorldMatrix()); } ////////////////////////////////////////////////////////////////////////// // PUBLIC METHODS ////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------- // Ths functions sets the matrices of the bones according the matrices of // the body that is attached to each bone. // To get max speed all this is doen recursivly from the root and then down. // To usre function iterate notes of root bone and call this function for each. void cMeshEntity::SetBoneMatrixFromBodyRec(const cMatrixf &a_mtxParentWorld, cBoneState *apBoneState) { iPhysicsBody *pBody = apBoneState->GetBody(); if (pBody) { cMatrixf mtxBoneWorld = cMath::MatrixMul(pBody->GetWorldMatrix(), apBoneState->GetInvBodyMatrix()); cMatrixf mtxParentInv = cMath::MatrixInverse(a_mtxParentWorld); apBoneState->SetMatrix(cMath::MatrixMul(mtxParentInv, mtxBoneWorld), false); cNodeIterator BoneIt = apBoneState->GetChildIterator(); while (BoneIt.HasNext()) { cBoneState *pBoneState = static_cast(BoneIt.Next()); SetBoneMatrixFromBodyRec(mtxBoneWorld, pBoneState); } } else { apBoneState->UpdateWorldTransform(); const cMatrixf &mtxBoneWorld = apBoneState->GetWorldMatrix(); cNodeIterator BoneIt = apBoneState->GetChildIterator(); while (BoneIt.HasNext()) { cBoneState *pBoneState = static_cast(BoneIt.Next()); SetBoneMatrixFromBodyRec(mtxBoneWorld, pBoneState); } } } //----------------------------------------------------------------------- // int glDebugTabs=0; void cMeshEntity::UpdateNodeMatrixRec(const cMatrixf &a_mtxParentWorld, cNode3D *apNode) { if (apNode->IsActive()) { // tString sTemp="";for(int i=0; iGetName()); apNode->UpdateMatrix(false); } apNode->UpdateWorldTransform(); const cMatrixf &mtxWorld = apNode->GetWorldMatrix(); // glDebugTabs++; cNodeIterator NodeIt = apNode->GetChildIterator(); while (NodeIt.HasNext()) { cNode3D *pChildNode = static_cast(NodeIt.Next()); UpdateNodeMatrixRec(mtxWorld, pChildNode); } // glDebugTabs--; } //----------------------------------------------------------------------- void cMeshEntity::UpdateLogic(float afTimeStep) { // LogUpdate("---- Start mesh update ----------------------\n"); // if(!IsStatic()){START_TIMING_TAB(Skeleton);} ///////////////////////////////////////////////////// // If not rendered the previous frame then we do not update as often. bool bRegularUpdate = GetGlobalRenderCount() == cRenderList::GetGlobalRenderCount(); // Check if any skeleton child has been rendered. if (mpMesh->GetSkeleton() && bRegularUpdate == false) { for (size_t i = 0; i < mvBoneStates.size(); ++i) { cBoneState *pState = mvBoneStates[i]; cEntityIterator it = pState->GetEntityIterator(); while (it.HasNext()) { iEntity3D *pEntity = static_cast(it.Next()); if (pEntity->GetGlobalRenderCount() == cRenderList::GetGlobalRenderCount()) { bRegularUpdate = true; break; } } if (bRegularUpdate) break; } } const int lMaxSleepCount = 30; if (bRegularUpdate == false) { if (mlStartSleepCount < lMaxSleepCount) ++mlStartSleepCount; } else { mlStartSleepCount = 0; } if (mlStartSleepCount >= lMaxSleepCount) { ++mlUpdateCount; mfTimeStepAccum += afTimeStep; if (mlUpdateCount < 20) { // if(cString::GetLastStringPos(msName, "infected")>=0) LogUpdate(" return\n"); return; } mlUpdateCount = 0; afTimeStep = mfTimeStepAccum; mfTimeStepAccum = 0; } else { mlUpdateCount = 0; mfTimeStepAccum = 0; } // if(cString::GetLastStringPos(msName, "infected")>=0) LogUpdate(" do stuff\n"); ///////////////////////////////////////////// // Update the skeleton physics fade if (mbSkeletonPhysicsFading && mbSkeletonPhysics) { mfSkeletonPhysicsWeight -= afTimeStep * mfSkeletonPhysicsFadeSpeed; if (mfSkeletonPhysicsWeight <= 0) { mbSkeletonPhysicsFading = false; mfSkeletonPhysicsWeight = 1.0f; SetSkeletonPhysicsActive(false); } } // LogUpdate(" -skeleton physics\n"); ///////////////////////////////////////////// // Check if all bodies connected to the skeleton is at rest, // If so we can skip skinning the body and simply just use the mesh as is. //(has some problems so turned off at the moment) mbSkeletonPhysicsSleeping = false; if (mbSkeletonPhysics && mfSkeletonPhysicsWeight == 1.0f && mbSkeletonPhysicsCanSleep) { bool bEnabled = false; for (int bone = 0; bone < GetBoneStateNum(); ++bone) { cBoneState *pState = GetBoneState(bone); iPhysicsBody *pBody = pState->GetBody(); if (pBody && pBody->GetEnabled()) { bEnabled = true; break; } } if (bEnabled == false) { // mbSkeletonPhysicsSleeping = true; } } // if(!IsStatic()){STOP_TIMING_TAB(Skeleton);} ///////////////////////////////////////////// // Update animations ////////////////////////////////////// // Check if it has nodes, then use special animation update, // else it as skepetn which needs another update. if (mbHasNodes) { // Reset all node states to prepare for animations. if (mvAnimationStates.size() > 0) { for (size_t i = 0; i < mvNodeStates.size(); i++) { cNode3D *pState = mvNodeStates[i]; if (pState->IsActive()) pState->SetMatrix(cMatrixf::Identity); } } ///////////////////////// // Go through all animations states and set the node's // temporary states bool bAnimated = false; for (size_t i = 0; i < mvAnimationStates.size(); i++) { cAnimationState *pAnimState = mvAnimationStates[i]; if (pAnimState->IsActive()) { bAnimated = true; cAnimation *pAnim = pAnimState->GetAnimation(); for (int j = 0; j < pAnim->GetTrackNum(); j++) { cAnimationTrack *pTrack = pAnim->GetTrack(j); if (pTrack->GetNodeIndex() < 0) { pTrack->SetNodeIndex(GetNodeStateIndex(pTrack->GetName())); } cNode3D *pNodeState = GetNodeState(pTrack->GetNodeIndex()); if (pNodeState->IsActive()) pTrack->ApplyToNode(pNodeState, pAnimState->GetTimePosition(), pAnimState->GetWeight()); } pAnimState->Update(afTimeStep); } } ////////////////////// // Go through all states and update the matrices (and thereby adding the animations together). if (mvAnimationStates.size() > 0 && bAnimated) { cNodeIterator NodeIt = mpRootNode->GetChildIterator(); while (NodeIt.HasNext()) { cNode3D *pBoneState = static_cast(NodeIt.Next()); UpdateNodeMatrixRec(mpRootNode->GetWorldMatrix(), pBoneState); } } ////////////////////// // Call callback to be run after animation if (mpCallback) mpCallback->AfterAnimationUpdate(this, afTimeStep); UpdateBVFromSubs(); // Update the entity transform, this so that the portal info, attached entities, callbacks and stuff is updated. SetTransformUpdated(true); } ////////////////////////////////////// // If the entity has a skeleton: else if (mpMesh->GetSkeleton()) { // if(!IsStatic()){START_TIMING_TAB(Animation);} //////////////////////// // Check if it is animated bool bAnimationActive = false; for (size_t i = 0; i < mvAnimationStates.size(); i++) { if (mvAnimationStates[i]->IsActive()) { bAnimationActive = true; break; } } ////////// // Reset all bones states // LogUpdate(" -reset bones\n"); if (bAnimationActive || mbUpdatedBones == false || (mbSkeletonPhysics && !mbSkeletonPhysicsSleeping)) { for (size_t i = 0; i < mvBoneStates.size(); i++) { cNode3D *pState = mvBoneStates[i]; cBone *pBone = mpMesh->GetSkeleton()->GetBoneByIndex((int)i); if (pState->IsActive()) { pState->SetMatrix(pBone->GetLocalTransform(), false); } // can optimize this by doing it in the order of the tree // and using recursive. (should be enough as is...) if (mbSkeletonPhysics && mfSkeletonPhysicsWeight != 1.0f) { mvTempBoneStates[i]->SetMatrix(pBone->GetLocalTransform(), false); } } } /////////////////////////// // Update skeleton physics // LogUpdate(" -reset physics again\n"); if (mbSkeletonPhysics && (!mbSkeletonPhysicsSleeping || mbUpdatedBones == false)) { mbUpdatedBones = true; cNodeIterator BoneIt = mpRootNode->GetChildIterator(); while (BoneIt.HasNext()) { cBoneState *pBoneState = static_cast(BoneIt.Next()); SetBoneMatrixFromBodyRec(mpRootNode->GetWorldMatrix(), pBoneState); } // Interpolate matrices if (mfSkeletonPhysicsWeight != 1.0f) { for (size_t i = 0; i < mvBoneStates.size(); i++) { cMatrixf mtxMixLocal = cMath::MatrixSlerp(mfSkeletonPhysicsWeight, mvTempBoneStates[i]->GetLocalMatrix(), mvBoneStates[i]->GetLocalMatrix(), true); mvBoneStates[i]->SetMatrix(mtxMixLocal, false); } } } // if(!IsStatic()){STOP_TIMING_TAB(Animation);} // if(!IsStatic()){START_TIMING_TAB(Bones);} ////////////////////////////////// // Go through all animations states and update the bones // LogUpdate(" -animation states\n"); for (size_t i = 0; i < mvAnimationStates.size(); i++) { cAnimationState *pAnimState = mvAnimationStates[i]; // Log("Testing state: '%s'\n",pAnimState->GetName()); if (pAnimState->IsActive()) { cAnimation *pAnim = pAnimState->GetAnimation(); for (int i2 = 0; i2 < pAnim->GetTrackNum(); i2++) { cAnimationTrack *pTrack = pAnim->GetTrack(i2); cNode3D *pState = GetBoneState(pTrack->GetNodeIndex()); // Log("Animating bone %s\n",pState->GetName()); if (pState->IsActive()) pTrack->ApplyToNode(pState, pAnimState->GetTimePosition(), pAnimState->GetWeight()); } // Log("Time: %f\n",pAnimState->GetTimePosition()); pAnimState->Update(afTimeStep); } } // if(!IsStatic()){STOP_TIMING_TAB(Bones);} // if(!IsStatic()){START_TIMING_TAB(States);} ////////////////////////////////// // Go through all states and update the matrices (and thereby adding the animations together). // LogUpdate(" -Bone update\n"); if (bAnimationActive) { cNodeIterator NodeIt = mpRootNode->GetChildIterator(); while (NodeIt.HasNext()) { cNode3D *pBoneState = static_cast(NodeIt.Next()); UpdateNodeMatrixRec(mpRootNode->GetWorldMatrix(), pBoneState); } // Entities are updated after BV is calculated, as the entity has the rootnode attached to it. } // if(!IsStatic()){STOP_TIMING_TAB(States);} // if(!IsStatic()){START_TIMING_TAB(Colliders);} ////////////////////////////////// // Update the colliders if they are active // Note this must be done after all bone states are updated. // LogUpdate(" -Colliders\n"); if (mbSkeletonColliders && mbSkeletonPhysics == false) { for (size_t i = 0; i < mvBoneStates.size(); i++) { cBoneState *pState = mvBoneStates[i]; iPhysicsBody *pColliderBody = pState->GetColliderBody(); if (pColliderBody) { cMatrixf mtxBody = cMath::MatrixMul(pState->GetWorldMatrix(), pState->GetBodyMatrix()); pColliderBody->SetMatrix(mtxBody); } } } // if(!IsStatic()){STOP_TIMING_TAB(Colliders);} // Call callback if (mpCallback) mpCallback->AfterAnimationUpdate(this, afTimeStep); /////////////////////////// // Update bounding volume // LogUpdate(" -BV update\n"); UpdateBVFromSubs(); // Update the entity transform, this so that the portal info, attached entities, callbacks and stuff is updated. SetTransformUpdated(true); } // if(!IsStatic()){START_TIMING_TAB(SubEntities);} ///////////////////////////////////////// /// Update sub entities // LogUpdate(" -Subs\n"); for (size_t i = 0; i < mvSubMeshes.size(); ++i) { cSubMeshEntity *pSub = mvSubMeshes[i]; pSub->UpdateLogic(afTimeStep); } // if(!IsStatic()){STOP_TIMING_TAB(SubEntities);} ///////////////////////////////////////// /// Update animation events for (size_t i = 0; i < mvAnimationStates.size(); ++i) { cAnimationState *pState = mvAnimationStates[i]; if (pState->IsActive() == false || pState->IsPaused()) continue; for (int j = 0; j < pState->GetEventNum(); ++j) { cAnimationEvent *pEvent = pState->GetEvent(j); if (pEvent->mfTime >= pState->GetPreviousTimePosition() && pEvent->mfTime < pState->GetTimePosition()) { HandleAnimationEvent(pEvent); } } } // LogUpdate("---- End mesh update ----------------------\n"); } //----------------------------------------------------------------------- cAnimationState *cMeshEntity::AddAnimation(cAnimation *apAnimation, const tString &asName, float afBaseSpeed) { cAnimationState *pAnimState = hplNew(cAnimationState, (apAnimation, asName, mpAnimationManager)); pAnimState->SetBaseSpeed(afBaseSpeed); mvAnimationStates.push_back(pAnimState); tAnimationStateIndexMap::value_type value(pAnimState->GetName(), (int)mvAnimationStates.size() - 1); m_mapAnimationStateIndices.insert(value); return pAnimState; } void cMeshEntity::ClearAnimations() { STLDeleteAll(mvAnimationStates); } //----------------------------------------------------------------------- cAnimationState *cMeshEntity::GetAnimationState(int alIndex) { return mvAnimationStates[alIndex]; } int cMeshEntity::GetAnimationStateIndex(const tString &asName) { tAnimationStateIndexMapIt it = m_mapAnimationStateIndices.find(asName); if (it != m_mapAnimationStateIndices.end()) { return it->second; } else { return -1; } } cAnimationState *cMeshEntity::GetAnimationStateFromName(const tString &asName) { int lIdx = GetAnimationStateIndex(asName); if (lIdx >= 0) { return mvAnimationStates[lIdx]; } else { return NULL; } } int cMeshEntity::GetAnimationStateNum() { return (int)mvAnimationStates.size(); } //----------------------------------------------------------------------- void cMeshEntity::Play(int alIndex, bool abLoop, bool bStopPrev) { if (bStopPrev) Stop(); mvAnimationStates[alIndex]->SetActive(true); mvAnimationStates[alIndex]->SetTimePosition(0); mvAnimationStates[alIndex]->SetLoop(abLoop); mvAnimationStates[alIndex]->SetWeight(1); } void cMeshEntity::PlayName(const tString &asName, bool abLoop, bool bStopPrev) { int lIdx = GetAnimationStateIndex(asName); if (lIdx >= 0) { Play(lIdx, abLoop, bStopPrev); } else { Warning("Can not find animation '%s' in meshentity '%s'\n", asName.c_str(), msName.c_str()); } } void cMeshEntity::Stop() { for (size_t i = 0; i < mvAnimationStates.size(); i++) { mvAnimationStates[i]->SetActive(false); mvAnimationStates[i]->SetTimePosition(0); } } //----------------------------------------------------------------------- cBoneState *cMeshEntity::GetBoneState(int alIndex) { return mvBoneStates[alIndex]; } int cMeshEntity::GetBoneStateIndex(const tString &asName) { tNodeStateIndexMapIt it = m_mapBoneStateIndices.find(asName); if (it != m_mapBoneStateIndices.end()) { return it->second; } else { return -1; } } cBoneState *cMeshEntity::GetBoneStateFromName(const tString &asName) { int lIdx = GetBoneStateIndex(asName); if (lIdx >= 0) { return mvBoneStates[lIdx]; } else { return NULL; } } int cMeshEntity::GetBoneStateNum() { return (int)mvBoneStates.size(); } //---------------------------------------------------------------------- void cMeshEntity::SetSkeletonPhysicsActive(bool abX) { mbSkeletonPhysics = abX; mbUpdatedBones = false; ResetGraphicsUpdated(); mbSkeletonPhysicsFading = false; mfSkeletonPhysicsWeight = 1.0f; for (int bone = 0; bone < GetBoneStateNum(); ++bone) { cBoneState *pState = GetBoneState(bone); iPhysicsBody *pBody = pState->GetBody(); iPhysicsBody *pColliderBody = pState->GetColliderBody(); if (pBody) { pBody->SetActive(abX); pBody->SetEnabled(abX); if (abX == false) { pBody->SetLinearVelocity(0); pBody->SetAngularVelocity(0); } if (mbSkeletonColliders) { pColliderBody->SetActive(!abX); } } } } bool cMeshEntity::GetSkeletonPhysicsActive() { return mbSkeletonPhysics; } //---------------------------------------------------------------------- void cMeshEntity::FadeSkeletonPhysicsWeight(float afTime) { if (mbSkeletonPhysics) { mbSkeletonPhysicsFading = true; mfSkeletonPhysicsFadeSpeed = 1.0f / afTime; for (int bone = 0; bone < GetBoneStateNum(); ++bone) { cBoneState *pState = GetBoneState(bone); iPhysicsBody *pBody = pState->GetBody(); /*iPhysicsBody *pColliderBody = */ pState->GetColliderBody(); if (pBody) pBody->SetActive(false); } } } //---------------------------------------------------------------------- float cMeshEntity::GetSkeletonPhysicsWeight() { return mfSkeletonPhysicsWeight; } void cMeshEntity::SetSkeletonPhysicsWeight(float afX) { mfSkeletonPhysicsWeight = afX; } //---------------------------------------------------------------------- void cMeshEntity::SetSkeletonCollidersActive(bool abX) { mbSkeletonColliders = abX; // Set active to the correct state. for (int bone = 0; bone < GetBoneStateNum(); ++bone) { cBoneState *pState = GetBoneState(bone); iPhysicsBody *pColliderBody = pState->GetColliderBody(); if (pColliderBody) { if (abX && !mbSkeletonPhysics) pColliderBody->SetActive(true); else if (!abX) pColliderBody->SetActive(false); } } } bool cMeshEntity::GetSkeletonCollidersActive() { return mbSkeletonColliders; } //---------------------------------------------------------------------- void cMeshEntity::AlignBodiesToSkeleton(bool abCalculateSpeed) { for (int bone = 0; bone < GetBoneStateNum(); ++bone) { cBoneState *pState = GetBoneState(bone); iPhysicsBody *pBody = pState->GetBody(); if (pBody) { cMatrixf mtxBody = cMath::MatrixMul(pState->GetWorldMatrix(), pState->GetBodyMatrix()); pBody->SetMatrix(mtxBody); if (abCalculateSpeed) { // TODO: calculate speed based on the previous frame of the animation. } } } } //---------------------------------------------------------------------- cMatrixf cMeshEntity::CalculateTransformFromSkeleton(cVector3f *apPostion, cVector3f *apAngles) { // Root bone cNodeIterator StateIt = GetRootNode()->GetChildIterator(); cBoneState *pBoneState = static_cast(StateIt.Next()); // Get the root bone (should only be one) cBoneIterator BoneIt = GetMesh()->GetSkeleton()->GetRootBone()->GetChildIterator(); cBone *pBone = BoneIt.Next(); // Rotation and position cMatrixf mtxInvBind = pBone->GetInvWorldTransform(); cMatrixf mtxInvBone = cMath::MatrixInverse(pBoneState->GetWorldMatrix()); cVector3f vStateForward = mtxInvBone.GetForward(); cVector3f vBindForward = mtxInvBind.GetForward(); float fBindYAngle = -cMath::GetAngleFromPoints2D(0, cVector2f(-vBindForward.x, -vBindForward.z)); float fStateYAngle = -cMath::GetAngleFromPoints2D(0, cVector2f(-vStateForward.x, -vStateForward.z)); float fYAngle = fStateYAngle - fBindYAngle; cMatrixf mtxTransform = cMath::MatrixRotateY(fYAngle); cVector3f vRootBoneOffset = pBone->GetLocalTransform().GetTranslation(); vRootBoneOffset.y = 0; vRootBoneOffset = cMath::MatrixMul(mtxTransform, vRootBoneOffset); mtxTransform.SetTranslation(pBoneState->GetWorldPosition()); // - vRootBoneOffset); if (apPostion) *apPostion = pBoneState->GetWorldPosition(); // - vRootBoneOffset; if (apAngles) *apAngles = cVector3f(0, fYAngle, 0); return mtxTransform; } //---------------------------------------------------------------------- bool cMeshEntity::CheckColliderShapeCollision(iPhysicsWorld *apWorld, iCollideShape *apShape, const cMatrixf &a_mtxShape, tVector3fList *apPosList, tIntList *apNumList) { bool bCollision = false; cCollideData collideData; collideData.SetMaxSize(1); for (size_t i = 0; i < mvBoneStates.size(); ++i) { cBoneState *pState = mvBoneStates[i]; iPhysicsBody *pBody = pState->GetColliderBody(); if (pBody == NULL) continue; cMatrixf mtxBody = cMath::MatrixMul(pState->GetWorldMatrix(), pState->GetBodyMatrix()); pBody->SetMatrix(mtxBody); bool bRet = apWorld->CheckShapeCollision(pBody->GetShape(), pBody->GetLocalMatrix(), apShape, a_mtxShape, collideData, 1); if (bRet) { bCollision = true; if (!apPosList && !apNumList) break; if (apPosList) apPosList->push_back(collideData.mvContactPoints[0].mvPoint); if (apNumList) apNumList->push_back((int)i); } } return bCollision; } //---------------------------------------------------------------------- void cMeshEntity::ResetGraphicsUpdated() { for (size_t i = 0; i < mvSubMeshes.size(); ++i) { mvSubMeshes[i]->mbGraphicsUpdated = false; } mbUpdatedBones = false; } //---------------------------------------------------------------------- cNode3D *cMeshEntity::GetNodeState(int alIndex) { return mvNodeStates[alIndex]; } int cMeshEntity::GetNodeStateIndex(const tString &asName) { tNodeStateIndexMapIt it = m_mapNodeStateIndices.find(asName); if (it != m_mapNodeStateIndices.end()) { return it->second; } else { return -1; } } cNode3D *cMeshEntity::GetNodeStateFromName(const tString &asName) { int lIdx = GetNodeStateIndex(asName); if (lIdx >= 0) { return mvNodeStates[lIdx]; } else { return NULL; } } int cMeshEntity::GetNodeStateNum() { return (int)mvNodeStates.size(); } //----------------------------------------------------------------------- void cMeshEntity::SetCastsShadows(bool abX) { if (abX == mbCastShadows) return; mbCastShadows = abX; for (int i = 0; i < (int)mvSubMeshes.size(); i++) { mvSubMeshes[i]->SetCastsShadows(abX); } } //----------------------------------------------------------------------- cSubMeshEntity *cMeshEntity::GetSubMeshEntity(unsigned int alIdx) { if (alIdx >= mvSubMeshes.size()) return NULL; return mvSubMeshes[alIdx]; } cSubMeshEntity *cMeshEntity::GetSubMeshEntityName(const tString &asName) { tSubMeshEntityMapIt it = m_mapSubMeshes.find(asName); if (it == m_mapSubMeshes.end()) return NULL; return it->second; } int cMeshEntity::GetSubMeshEntityNum() { return (int)mvSubMeshes.size(); } //----------------------------------------------------------------------- void cMeshEntity::UpdateGraphics(cCamera3D *apCamera, float afFrameTime, cRenderList *apRenderList) { // Update the bone matrices cSkeleton *pSkeleton = mpMesh->GetSkeleton(); if (pSkeleton) { // Save the root node to a temp matrix /*cMatrixf mtxTemp; if(mpRootNode) { mtxTemp = mpRootNode->GetWorldMatrix(); mpRootNode->SetMatrix(cMatrixf::Identity); }*/ cMatrixf *pInvWorldMtx = GetInvModelMatrix(); for (int i = 0; i < pSkeleton->GetBoneNum(); i++) { cBone *pBone = pSkeleton->GetBoneByIndex(i); cNode3D *pState = mvBoneStates[i]; // Transform the movement of the bone into the // Bind pose's local space. cMatrixf mtxLocal = cMath::MatrixMul(*pInvWorldMtx, pState->GetWorldMatrix()); mvBoneMatrices[i] = cMath::MatrixMul(mtxLocal, pBone->GetInvWorldTransform()); } // Set back the matrix. /*if(mpRootNode){ mpRootNode->SetMatrix(mtxTemp); SetTransformUpdated(true); } else { SetTransformUpdated(true); }*/ } } //----------------------------------------------------------------------- bool cMeshEntity::AttachEntityToParent(iEntity3D *apEntity, const tString &asParent) { mlstAttachedEntities.push_back(apEntity); // If no parent is specified add to first sub entity. if (asParent == "") { // Log("Added %s to mesh\n",apEntity->GetName().c_str()); AddChild(apEntity); return true; } // Check submeshes cSubMeshEntity *pSubEntity = GetSubMeshEntityName(asParent); if (pSubEntity) { // Log("Added %s to subentity %s\n",apEntity->GetName().c_str(),pSubEntity->GetName().c_str()); pSubEntity->AddChild(apEntity); return true; } // Check nodes cNode3D *pNode = GetNodeStateFromName(asParent); if (pNode) { // Log("Added %s to node %s\n",apEntity->GetName().c_str(),pNode->GetName()); pNode->AddEntity(apEntity); return true; } // Check bones cNode3D *pBone = GetBoneStateFromName(asParent); if (pBone) { // Log("Added %s to bone\n",apEntity->GetName().c_str()); pBone->AddEntity(apEntity); return true; } Warning("Parent '%s' couldn't be found! Failed to attach '%s' to '%s'.Attaching directly to mesh.\n", asParent.c_str(), apEntity->GetName().c_str(), GetName().c_str()); AddChild(apEntity); return false; } //----------------------------------------------------------------------- void cMeshEntity::SetRendered(bool abX) { if (abX == mbRendered) return; mbRendered = abX; for (int i = 0; i < (int)mvSubMeshes.size(); i++) { mvSubMeshes[i]->SetRendered(abX); mvSubMeshes[i]->SetGlobalRenderCount(cRenderList::GetGlobalRenderCount()); } SetGlobalRenderCount(cRenderList::GetGlobalRenderCount()); mlStartSleepCount = 0; } //----------------------------------------------------------------------- iMaterial *cMeshEntity::GetMaterial() { return NULL; } //----------------------------------------------------------------------- iVertexBuffer *cMeshEntity::GetVertexBuffer() { return NULL; } //----------------------------------------------------------------------- bool cMeshEntity::IsShadowCaster() { return mbCastShadows; } //----------------------------------------------------------------------- cBoundingVolume *cMeshEntity::GetBoundingVolume() { return &mBoundingVolume; } //----------------------------------------------------------------------- cMatrixf *cMeshEntity::GetModelMatrix(cCamera3D *apCamera) { mtxTemp = GetWorldMatrix(); return &mtxTemp; } //----------------------------------------------------------------------- int cMeshEntity::GetMatrixUpdateCount() { return GetTransformUpdateCount(); } //----------------------------------------------------------------------- eRenderableType cMeshEntity::GetRenderType() { return eRenderableType_Mesh; } //----------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////////// // PRIAVTE METHODS ////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------- void cMeshEntity::HandleAnimationEvent(cAnimationEvent *apEvent) { if (apEvent->msValue == "") return; switch (apEvent->mType) { case eAnimationEventType_PlaySound: { cSoundEntity *pSound = mpWorld->CreateSoundEntity("AnimEvent", apEvent->msValue, true); if (pSound) { cNodeIterator nodeIt = mpRootNode->GetChildIterator(); if (nodeIt.HasNext()) { iNode *pNode = nodeIt.Next(); pNode->AddEntity(pSound); } else { pSound->SetPosition(mBoundingVolume.GetWorldCenter()); } } break; } default: break; } } //----------------------------------------------------------------------- void cMeshEntity::UpdateBVFromSubs() { ///////////////////////////////////// // Skeleton if (mpMesh->GetSkeleton()) { if (mvBoneStates.empty()) { //////////////////////////////// // Using vertices // Go through all the sub meshes and build BV from vertices. for (int i = 0; i < GetSubMeshEntityNum(); i++) { cSubMeshEntity *pSub = GetSubMeshEntity(i); iVertexBuffer *pVtxBuffer = pSub->GetVertexBuffer(); mBoundingVolume.AddArrayPoints(pVtxBuffer->GetArray(eVertexFlag_Position), pVtxBuffer->GetVertexNum()); } mBoundingVolume.CreateFromPoints(kvVertexElements[cMath::Log2ToInt(eVertexFlag_Position)]); } else { //////////////////////////////// // Using bones cVector3f vMin = mvBoneStates[0]->GetWorldPosition(); cVector3f vMax = vMin; for (size_t i = 1; i < mvBoneStates.size(); ++i) { cBoneState *pState = mvBoneStates[i]; cVector3f vPos = pState->GetWorldPosition(); if (vMax.x < vPos.x) vMax.x = vPos.x; if (vMax.y < vPos.y) vMax.y = vPos.y; if (vMax.z < vPos.z) vMax.z = vPos.z; if (vMin.x > vPos.x) vMin.x = vPos.x; if (vMin.y > vPos.y) vMin.y = vPos.y; if (vMin.z > vPos.z) vMin.z = vPos.z; } // Add 10 percent to the box borders. vMin -= (vMax - vMin) * 0.1f + 0.1f; vMax += (vMax - vMin) * 0.1f + 0.1f; mBoundingVolume.SetTransform(cMatrixf::Identity); mBoundingVolume.SetLocalMinMax(vMin, vMax); } } //////////////////////////////////// // Nodes else { // Use this to make sure the the nodes are in the same positions as when // exported. This is to give a working Bounding Volume. // This feels kind of slow...but might be the only way. cMatrixf mtxTemp2; if (mpRootNode) { mtxTemp2 = mpRootNode->GetWorldMatrix(); mpRootNode->SetMatrix(cMatrixf::Identity); } cVector3f vFinalMin = mvSubMeshes[0]->mBoundingVolume.GetMin(); cVector3f vFinalMax = mvSubMeshes[0]->mBoundingVolume.GetMax(); for (int i = 1; i < (int)mvSubMeshes.size(); i++) { cVector3f vMin = mvSubMeshes[i]->mBoundingVolume.GetMin(); cVector3f vMax = mvSubMeshes[i]->mBoundingVolume.GetMax(); if (vFinalMin.x > vMin.x) vFinalMin.x = vMin.x; if (vFinalMax.x < vMax.x) vFinalMax.x = vMax.x; if (vFinalMin.y > vMin.y) vFinalMin.y = vMin.y; if (vFinalMax.y < vMax.y) vFinalMax.y = vMax.y; if (vFinalMin.z > vMin.z) vFinalMin.z = vMin.z; if (vFinalMax.z < vMax.z) vFinalMax.z = vMax.z; } mBoundingVolume.SetLocalMinMax(vFinalMin, vFinalMax); if (mpRootNode) { mpRootNode->SetMatrix(mtxTemp2); } else { } } } //----------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////////// // SAVE OBJECT STUFF ////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------- kBeginSerialize(cSaveData_cMeshEntity, cSaveData_iRenderable) kSerializeVar(msMeshName, eSerializeType_String) kSerializeVar(mbCastShadows, eSerializeType_Bool) kSerializeVar(mlBodyId, eSerializeType_Int32) kSerializeClassContainer(mvSubEntities, cSaveData_cSubMeshEntity, eSerializeType_Class) kSerializeClassContainer(mvAnimStates, cSaveData_cAnimationState, eSerializeType_Class) kEndSerialize() //----------------------------------------------------------------------- iSaveObject *cSaveData_cMeshEntity::CreateSaveObject(cSaveObjectHandler *apSaveObjectHandler, cGame *apGame) { cResources *pResources = apGame->GetResources(); cWorld3D *pWorld = apGame->GetScene()->GetWorld3D(); /*cMeshLoaderHandler *pMeshLoadHandler = */ pResources->GetMeshLoaderHandler(); /*cFileSearcher *pFileSearcher = */ pResources->GetFileSearcher(); /////////////////////////////////// // Load mesh cMesh *pMesh = pResources->GetMeshManager()->CreateMesh(msMeshName); if (pMesh == NULL) return NULL; /////////////////////// // Load Animations // TODO: bad, bad bad...redo animation storing. /*if(pMesh->GetAnimationNum()<=1 && mvAnimStates.Size()>1) { pMesh->ClearAnimations(true); for(size_t i=0; i< mvAnimStates.Size(); ++i) { cAnimation *pAnimation = pResources->GetAnimationManager()->CreateAnimation(mvAnimStates[i].msAnimationName); pAnimation->SetAnimationName(mvAnimStates[i].msName); pAnimation->SetDefaultSpeed(mvAnimStates[i].mfDefaultSpeed); pMesh->AddAnimation(pAnimation); } }*/ /////////////////////// /// Create entity cMeshEntity *pEntity = pWorld->CreateMeshEntity(msName, pMesh); /////////////////////// /// Add sub meshes to the save object handler. for (int sub = 0; sub < pEntity->GetSubMeshEntityNum(); ++sub) { cSubMeshEntity *pSub = pEntity->GetSubMeshEntity(sub); pSub->LoadFromSaveData(&mvSubEntities[sub]); apSaveObjectHandler->Add(pSub); } return pEntity; } //----------------------------------------------------------------------- int cSaveData_cMeshEntity::GetSaveCreatePrio() { return 2; } //----------------------------------------------------------------------- iSaveData *cMeshEntity::CreateSaveData() { return hplNew(cSaveData_cMeshEntity, ()); } //----------------------------------------------------------------------- void cMeshEntity::SaveToSaveData(iSaveData *apSaveData) { kSaveData_SaveToBegin(cMeshEntity); //////////////////////////// // Set the name of the data pData->msMeshName = mpMesh->GetName(); //////////////////////////// // Sub Entities pData->mvSubEntities.Resize(GetSubMeshEntityNum()); for (int i = 0; i < GetSubMeshEntityNum(); i++) { cSubMeshEntity *pSubEntity = GetSubMeshEntity(i); pSubEntity->SaveToSaveData(&pData->mvSubEntities[i]); } //////////////////////////// // Animation states pData->mvAnimStates.Resize(GetAnimationStateNum()); for (int i = 0; i < GetAnimationStateNum(); i++) { cAnimationState *pAnimState = GetAnimationState(i); pAnimState->SaveToSaveData(&pData->mvAnimStates[i]); } // Log("MeshEntity %s has %d animation states saved\n",GetName().c_str(),pData->mvAnimStates.Size()); //////////////////////////// // Variables kSaveData_SaveTo(mbCastShadows); //////////////////////////// // Pointers kSaveData_SaveObject(mpBody, mlBodyId); } //----------------------------------------------------------------------- void cMeshEntity::LoadFromSaveData(iSaveData *apSaveData) { kSaveData_LoadFromBegin(cMeshEntity); /////////////////////// // Sub entities // This is done on creation instead. /*for(size_t i=0; imvSubEntities.Size(); i++) { cSubMeshEntity *pSubEntity = GetSubMeshEntity((int)i); pSubEntity->LoadFromSaveData(&pData->mvSubEntities[i]); }*/ /////////////////////// // Set Animation states data for (size_t i = 0; i < pData->mvAnimStates.Size(); ++i) { cAnimationState *pAnimationState = GetAnimationState((int)i); pAnimationState->LoadFromSaveData(&pData->mvAnimStates[i]); } // Log("LOADED: MeshEntity %s has %d animation states, in entity: %d\n",GetName().c_str(), // pData->mvAnimStates.Size(),GetAnimationStateNum()); ////////////////////// // Variables kSaveData_LoadFrom(mbCastShadows); } //----------------------------------------------------------------------- void cMeshEntity::SaveDataSetup(cSaveObjectHandler *apSaveObjectHandler, cGame *apGame) { kSaveData_SetupBegin(cMeshEntity); /////////////////////// // Sub entities // Should not be needed since they are added to the save object handler. for (int i = 0; i < GetSubMeshEntityNum(); i++) { cSubMeshEntity *pSubEntity = GetSubMeshEntity(i); pSubEntity->SaveDataSetup(apSaveObjectHandler, apGame); } /////////////////////// // Body kSaveData_LoadObject(mpBody, mlBodyId, iPhysicsBody *); if (mpBody) { mpBody->CreateNode()->AddEntity(this); } } //----------------------------------------------------------------------- } // namespace hpl