/* ** Command & Conquer Generals(tm) ** Copyright 2025 Electronic Arts Inc. ** ** 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 . */ //////////////////////////////////////////////////////////////////////////////// // // // (c) 2001-2003 Electronic Arts Inc. // // // //////////////////////////////////////////////////////////////////////////////// // FILE: W3DModelDraw.cpp /////////////////////////////////////////////////////////////////////// // Author: Colin Day, November 2001 // Desc: Default w3d draw module /////////////////////////////////////////////////////////////////////////////////////////////////// // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #define DEFINE_W3DANIMMODE_NAMES #define DEFINE_WEAPONSLOTTYPE_NAMES #define NO_DEBUG_CRC #include "Common/CRC.h" #include "Common/CRCDebug.h" #include "Common/GameState.h" #include "Common/GlobalData.h" #include "Common/PerfTimer.h" #include "Common/RandomValue.h" #include "Common/ThingTemplate.h" #include "Common/GameLOD.h" #include "Common/Xfer.h" #include "Common/GameState.h" #include "GameClient/Drawable.h" #include "GameClient/FXList.h" #include "GameClient/Shadow.h" #include "GameLogic/GameLogic.h" // for real-time frame #include "GameLogic/Object.h" #include "GameLogic/WeaponSet.h" #include "GameLogic/FPUControl.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Module/PhysicsUpdate.h" #include "W3DDevice/GameClient/Module/W3DModelDraw.h" #include "W3DDevice/GameClient/W3DAssetManager.h" #include "W3DDevice/GameClient/W3DDisplay.h" #include "W3DDevice/GameClient/W3DScene.h" #include "W3DDevice/GameClient/W3DShadow.h" #include "W3DDevice/GameClient/W3DTerrainTracks.h" #include "W3DDevice/GameClient/WorldHeightMap.h" #include "WW3D2/HAnim.h" #include "WW3D2/HLod.h" #include "WW3D2/RendObj.h" #include "WW3D2/Mesh.h" #include "WW3D2/MeshMdl.h" #include "Common/BitFlagsIO.h" #ifdef _INTERNAL // for occasional debugging... //#pragma optimize("", off) //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif //------------------------------------------------------------------------------------------------- static inline Bool isValidTimeToCalcLogicStuff() { return (TheGameLogic && TheGameLogic->isInGameLogicUpdate()) || (TheGameState && TheGameState->isInLoadGame()); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- #if defined(DEBUG_CRC) && (defined(_DEBUG) || defined(_INTERNAL)) #include class LogClass { public: LogClass(const char *fname); ~LogClass(); void log(const char *fmt, ...); void dumpMatrix3D(const Matrix3D *m, AsciiString name, AsciiString fname, Int line); void dumpReal(Real r, AsciiString name, AsciiString fname, Int line); protected: FILE *m_fp; }; LogClass::LogClass(const char *fname) { char buffer[ _MAX_PATH ]; GetModuleFileName( NULL, buffer, sizeof( buffer ) ); char *pEnd = buffer + strlen( buffer ); while( pEnd != buffer ) { if( *pEnd == '\\' ) { *pEnd = 0; break; } pEnd--; } AsciiString fullPath; fullPath.format("%s\\%s", buffer, fname); m_fp = fopen(fullPath.str(), "wt"); } LogClass::~LogClass() { if (m_fp) { fclose(m_fp); } } void LogClass::log(const char *fmt, ...) { DEBUG_ASSERTCRASH(isValidTimeToCalcLogicStuff(), ("Calc'ing logic bone pos in client!!!")); if (!m_fp /*|| !isValidTimeToCalcLogicStuff()*/) return; static char buf[1024]; static Int lastFrame = 0; static Int lastIndex = 0; if (lastFrame != TheGameLogic->getFrame()) { lastFrame = TheGameLogic->getFrame(); lastIndex = 0; } va_list va; va_start( va, fmt ); _vsnprintf(buf, 1024, fmt, va ); buf[1023] = 0; va_end( va ); char *tmp = buf; while (tmp && *tmp) { if (*tmp == '\r' || *tmp == '\n') { *tmp = ' '; } ++tmp; } fprintf(m_fp, "%d:%d %s\n", lastFrame, lastIndex++, buf); fflush(m_fp); } void LogClass::dumpMatrix3D(const Matrix3D *m, AsciiString name, AsciiString fname, Int line) { fname.toLower(); fname = fname.reverseFind('\\') + 1; const Real *matrix = (const Real *)m; log("dumpMatrix3D() %s:%d %s\n", fname.str(), line, name.str()); for (Int i=0; i<3; ++i) log(" 0x%08X 0x%08X 0x%08X 0x%08X\n", AS_INT(matrix[(i<<2)+0]), AS_INT(matrix[(i<<2)+1]), AS_INT(matrix[(i<<2)+2]), AS_INT(matrix[(i<<2)+3])); } void LogClass::dumpReal(Real r, AsciiString name, AsciiString fname, Int line) { if (!m_fp || !isValidTimeToCalcLogicStuff()) return; fname.toLower(); fname = fname.reverseFind('\\') + 1; log("dumpReal() %s:%d %s %8.8X (%f)\n", fname.str(), line, name.str(), AS_INT(r), r); } LogClass BonePosLog("bonePositions.txt"); #define BONEPOS_LOG(x) BonePosLog.log x #define BONEPOS_DUMPMATRIX3D(x) BONEPOS_DUMPMATRIX3DNAMED(x, #x) #define BONEPOS_DUMPMATRIX3DNAMED(x, y) BonePosLog.dumpMatrix3D(x, y, __FILE__, __LINE__) #define BONEPOS_DUMPREAL(x) BONEPOS_DUMPREALNAMED(x, #x) #define BONEPOS_DUMPREALNAMED(x, y) BonePosLog.dumpReal(x, y, __FILE__, __LINE__) #else // DEBUG_CRC #define BONEPOS_LOG(x) {} #define BONEPOS_DUMPMATRIX3D(x) {} #define BONEPOS_DUMPMATRIX3DNAMED(x, y) {} #define BONEPOS_DUMPREAL(x) {} #define BONEPOS_DUMPREALNAMED(x, y) {} #endif // DEBUG_CRC //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- #if defined(_DEBUG) || defined(_INTERNAL) extern AsciiString TheThingTemplateBeingParsedName; extern Real TheSkateDistOverride; #endif // flags that aren't read directly from INI, but set in response to various other situations enum INIReadFlagsType { ANIMS_COPIED_FROM_DEFAULT_STATE = 0, GOT_NONIDLE_ANIMS, GOT_IDLE_ANIMS, }; enum ACBits { RANDOMIZE_START_FRAME = 0, START_FRAME_FIRST, START_FRAME_LAST, ADJUST_HEIGHT_BY_CONSTRUCTION_PERCENT, PRISTINE_BONE_POS_IN_FINAL_FRAME, MAINTAIN_FRAME_ACROSS_STATES, RESTART_ANIM_WHEN_COMPLETE, MAINTAIN_FRAME_ACROSS_STATES2, MAINTAIN_FRAME_ACROSS_STATES3, MAINTAIN_FRAME_ACROSS_STATES4, }; static const char *ACBitsNames[] = { "RANDOMSTART", "START_FRAME_FIRST", "START_FRAME_LAST", "ADJUST_HEIGHT_BY_CONSTRUCTION_PERCENT", "PRISTINE_BONE_POS_IN_FINAL_FRAME", "MAINTAIN_FRAME_ACROSS_STATES", "RESTART_ANIM_WHEN_COMPLETE", "MAINTAIN_FRAME_ACROSS_STATES2", "MAINTAIN_FRAME_ACROSS_STATES3", "MAINTAIN_FRAME_ACROSS_STATES4", NULL }; static const Int ALL_MAINTAIN_FRAME_FLAGS = (1<Add_Ref(); #endif } //------------------------------------------------------------------------------------------------- W3DAnimationInfo& W3DAnimationInfo::operator=(const W3DAnimationInfo &r) { m_name = r.m_name; m_distanceCovered = r.m_distanceCovered; m_naturalDurationInMsec = r.m_naturalDurationInMsec; m_isIdleAnim = r.m_isIdleAnim; #ifdef RETAIN_ANIM_HANDLES REF_PTR_RELEASE(m_handle); m_handle = r.m_handle; if (m_handle) m_handle->Add_Ref(); #endif return (*this); } //------------------------------------------------------------------------------------------------- // note that this now returns an ADDREFED handle, which must be released by the caller! HAnimClass* W3DAnimationInfo::getAnimHandle() const { #ifdef RETAIN_ANIM_HANDLES if (m_handle == NULL) { // Get_HAnim addrefs it, so we'll have to release it in our dtor. m_handle = W3DDisplay::m_assetManager->Get_HAnim(m_name.str()); DEBUG_ASSERTCRASH(m_handle, ("*** ASSET ERROR: animation %s not found\n",m_name.str())); if (m_handle) { m_naturalDurationInMsec = m_handle->Get_Num_Frames() * 1000.0f / m_handle->Get_Frame_Rate(); } } // since we have it locally, must addref. if (m_handle) m_handle->Add_Ref(); return m_handle; #else HAnimClass* handle = W3DDisplay::m_assetManager->Get_HAnim(m_name.str()); DEBUG_ASSERTCRASH(handle, ("*** ASSET ERROR: animation %s not found\n",m_name.str())); if (handle != NULL && m_naturalDurationInMsec < 0) { m_naturalDurationInMsec = handle->Get_Num_Frames() * 1000.0f / handle->Get_Frame_Rate(); } // since Get_HAnim() returns an addrefed handle, we must NOT addref here. //if (handle) // handle->Add_Ref(); return handle; #endif } //------------------------------------------------------------------------------------------------- W3DAnimationInfo::~W3DAnimationInfo() { #ifdef RETAIN_ANIM_HANDLES REF_PTR_RELEASE(m_handle); m_handle = NULL; #endif } //------------------------------------------------------------------------------------------------- void ModelConditionInfo::preloadAssets( TimeOfDay timeOfDay, Real scale ) { // load this asset if( m_modelName.isEmpty() == FALSE ) { TheDisplay->preloadModelAssets( m_modelName ); } // this can be called from the client, which is problematic // validateStuff(NULL, getDrawable()->getScale()); //validateCachedBones(NULL, scale); //validateTurretInfo(); //validateWeaponBarrelInfo(); } //------------------------------------------------------------------------------------------------- void ModelConditionInfo::addPublicBone(const AsciiString& boneName) const { if (boneName.isEmpty() || boneName.isNone()) return; AsciiString tmp = boneName; tmp.toLower(); if (std::find(m_publicBones.begin(), m_publicBones.end(), tmp) == m_publicBones.end()) { m_publicBones.push_back(tmp); } } //------------------------------------------------------------------------------------------------- Bool ModelConditionInfo::matchesMode(Bool night, Bool snowy) const { for (std::vector::const_iterator it = m_conditionsYesVec.begin(); it != m_conditionsYesVec.end(); ++it) { if (it->test(MODELCONDITION_NIGHT) == (night) && it->test(MODELCONDITION_SNOW) == (snowy)) { return true; } } return false; } //------------------------------------------------------------------------------------------------- inline Bool testFlagBit(Int flags, Int bit) { return (flags & (1<Get_Bone_Index(boneName.str()); if (boneIndex != 0) { mtx = robj->Get_Bone_Transform(boneIndex); return true; } else { return false; } } //------------------------------------------------------------------------------------------------- static Bool findSingleSubObj(RenderObjClass* robj, const AsciiString& boneName, Matrix3D& mtx, Int& boneIndex) { if (boneName.isNone() || boneName.isEmpty()) return false; RenderObjClass* childObject = robj->Get_Sub_Object_By_Name(boneName.str()); if (childObject) { mtx = childObject->Get_Transform(); // you'd think this would work, but, it does not. do it the hard way. // boneIndex = childObject->Get_Sub_Object_Bone_Index(childObject); for (Int subObj = 0; subObj < robj->Get_Num_Sub_Objects(); subObj++) { RenderObjClass* test = robj->Get_Sub_Object(subObj); if (test == childObject) { boneIndex = robj->Get_Sub_Object_Bone_Index(0, subObj); #if defined(_DEBUG) || defined(_INTERNAL) test->Release_Ref(); test = robj->Get_Sub_Object_On_Bone(0, boneIndex); DEBUG_ASSERTCRASH(test != NULL && test == childObject, ("*** ASSET ERROR: Hmm, bone problem")); #endif } if (test) test->Release_Ref(); } childObject->Release_Ref(); return true; } else { return false; } } //------------------------------------------------------------------------------------------------- static Bool doSingleBoneName(RenderObjClass* robj, const AsciiString& boneName, PristineBoneInfoMap& map) { Bool foundAsBone = false; Bool foundAsSubObj = false; PristineBoneInfo info; AsciiString tmp; AsciiString boneNameTmp = boneName; boneNameTmp.toLower(); // convert to all-lowercase to avoid case sens issues later setFPMode(); if (findSingleBone(robj, boneNameTmp, info.mtx, info.boneIndex)) { //DEBUG_LOG(("added bone %s\n",boneNameTmp.str())); BONEPOS_LOG(("Caching bone %s (index %d)\n", boneNameTmp.str(), info.boneIndex)); BONEPOS_DUMPMATRIX3D(&(info.mtx)); map[NAMEKEY(boneNameTmp)] = info; foundAsBone = true; } for (Int i = 1; i <= 99; ++i) { tmp.format("%s%02d", boneNameTmp.str(), i); if (findSingleBone(robj, tmp, info.mtx, info.boneIndex)) { //DEBUG_LOG(("added bone %s\n",tmp.str())); BONEPOS_LOG(("Caching bone %s (index %d)\n", tmp.str(), info.boneIndex)); BONEPOS_DUMPMATRIX3D(&(info.mtx)); map[NAMEKEY(tmp)] = info; foundAsBone = true; } else { break; } } if (!foundAsBone) { if (findSingleSubObj(robj, boneNameTmp, info.mtx, info.boneIndex)) { //DEBUG_LOG(("added subobj %s\n",boneNameTmp.str())); BONEPOS_LOG(("Caching bone from subobject %s (index %d)\n", boneNameTmp.str(), info.boneIndex)); BONEPOS_DUMPMATRIX3D(&(info.mtx)); map[NAMEKEY(boneNameTmp)] = info; foundAsSubObj = true; } for (Int i = 1; i <= 99; ++i) { tmp.format("%s%02d", boneNameTmp.str(), i); if (findSingleSubObj(robj, tmp, info.mtx, info.boneIndex)) { //DEBUG_LOG(("added subobj %s\n",tmp.str())); BONEPOS_LOG(("Caching bone from subobject %s (index %d)\n", tmp.str(), info.boneIndex)); BONEPOS_DUMPMATRIX3D(&(info.mtx)); map[NAMEKEY(tmp)] = info; foundAsSubObj = true; } else { break; } } } return foundAsBone || foundAsSubObj; } //------------------------------------------------------------------------------------------------- void ModelConditionInfo::validateStuff(RenderObjClass* robj, Real scale, const std::vector& extraPublicBones) const { // srj sez: hm, this doesn't make sense; I think we really do need to validate transition states. // if (m_transition != NO_TRANSITION) // return; loadAnimations(); if (!(m_validStuff & PUBLIC_BONES_VALID) && isValidTimeToCalcLogicStuff()) { for (std::vector::const_iterator bone_it = extraPublicBones.begin(); bone_it != extraPublicBones.end(); ++bone_it) { addPublicBone(*bone_it); } m_validStuff |= PUBLIC_BONES_VALID; } validateCachedBones(robj, scale); validateTurretInfo(); validateWeaponBarrelInfo(); } //------------------------------------------------------------------------------------------------- void ModelConditionInfo::validateCachedBones(RenderObjClass* robj, Real scale) const { //DEBUG_ASSERTCRASH(isValidTimeToCalcLogicStuff(), ("calling validateCachedBones() from in GameClient!")); if (m_validStuff & PRISTINE_BONES_VALID) return; if (!isValidTimeToCalcLogicStuff()) { m_pristineBones.clear(); m_validStuff &= ~PRISTINE_BONES_VALID; return; } setFPMode(); BONEPOS_LOG(("Validating bones for %s: %s", m_modelName.str(), getDescription().str())); //BONEPOS_LOG(("Passing in valid render obj: %d\n", (robj != 0))); BONEPOS_DUMPREAL(scale); m_pristineBones.clear(); // go ahead and set this here, in case we return early. m_validStuff |= PRISTINE_BONES_VALID; Bool tossRobj = false; if (robj == NULL) { if (m_modelName.isEmpty()) { //BONEPOS_LOG(("Bailing: model name is empty\n")); return; } robj = W3DDisplay::m_assetManager->Create_Render_Obj(m_modelName.str(), scale, 0); DEBUG_ASSERTCRASH(robj, ("*** ASSET ERROR: Model %s not found!\n",m_modelName.str())); if (!robj) { //BONEPOS_LOG(("Bailing: could not load render object\n")); return; } tossRobj = true; } Matrix3D originalTransform = robj->Get_Transform(); // save the transform HLodClass* hlod = NULL; HAnimClass* curAnim = NULL; int numFrames = 0; float frame = 0.0f; int mode = 0; float mult = 1.0f; if (robj->Class_ID() == RenderObjClass::CLASSID_HLOD) { hlod = (HLodClass*)robj; curAnim = hlod->Peek_Animation_And_Info(frame, numFrames, mode, mult); } // if we have any animations in this state, always choose the first, since the animations // vary on a per-client basis. HAnimClass* animToUse; if (m_animations.size() > 0) { animToUse = m_animations.front().getAnimHandle(); // return an AddRef'ed handle } else { animToUse = curAnim; // Peek_Animation_And_Info does not addref, so we must do so here if (animToUse) animToUse->Add_Ref(); } if (animToUse != NULL) { // make sure we're in frame zero. Int whichFrame = testFlagBit(m_flags, PRISTINE_BONE_POS_IN_FINAL_FRAME) ? animToUse->Get_Num_Frames()-1 : 0; robj->Set_Animation(animToUse, whichFrame, RenderObjClass::ANIM_MODE_MANUAL); // must balance the addref, above REF_PTR_RELEASE(animToUse); animToUse = NULL; } Matrix3D tmp(true); tmp.Scale(scale); robj->Set_Transform(tmp); // set to identity transform if (TheGlobalData) { for (std::vector::const_iterator it = TheGlobalData->m_standardPublicBones.begin(); it != TheGlobalData->m_standardPublicBones.end(); ++it) { if (!doSingleBoneName(robj, *it, m_pristineBones)) { // don't crash here, since these are catch-all global bones and won't be present in most models. //DEBUG_CRASH(("public bone %s (and variations thereof) not found in model %s!\n",it->str(),m_modelName.str())); } //else //{ // DEBUG_LOG(("global bone %s (or variations thereof) found in model %s\n",it->str(),m_modelName.str())); //} } } for (std::vector::const_iterator it = m_publicBones.begin(); it != m_publicBones.end(); ++it) { if (!doSingleBoneName(robj, *it, m_pristineBones)) { // DO crash here, since we specifically requested this bone for this model DEBUG_CRASH(("*** ASSET ERROR: public bone '%s' (and variations thereof) not found in model %s!\n",it->str(),m_modelName.str())); } //else //{ // DEBUG_LOG(("extra bone %s (or variations thereof) found in model %s\n",it->str(),m_modelName.str())); //} } robj->Set_Transform(originalTransform); // restore previous transform if (curAnim != NULL) { robj->Set_Animation(curAnim, frame, mode); hlod->Set_Animation_Frame_Rate_Multiplier(mult); } if (tossRobj) { REF_PTR_RELEASE(robj); } } //------------------------------------------------------------------------------------------------- void ModelConditionInfo::validateWeaponBarrelInfo() const { //DEBUG_ASSERTCRASH(isValidTimeToCalcLogicStuff(), ("calling validateWeaponBarrelInfo() from in GameClient!")); if (m_validStuff & BARRELS_VALID) return; if (!isValidTimeToCalcLogicStuff()) { return; } setFPMode(); for (int wslot = 0; wslot < WEAPONSLOT_COUNT; ++wslot) { m_weaponBarrelInfoVec[wslot].clear(); m_hasRecoilBonesOrMuzzleFlashes[wslot] = false; const AsciiString& fxBoneName = m_weaponFireFXBoneName[wslot]; const AsciiString& recoilBoneName = m_weaponRecoilBoneName[wslot]; const AsciiString& mfName = m_weaponMuzzleFlashName[wslot]; const AsciiString& plbName = m_weaponProjectileLaunchBoneName[wslot]; if (fxBoneName.isNotEmpty() || recoilBoneName.isNotEmpty() || mfName.isNotEmpty() || plbName.isNotEmpty()) { Int prevFxBone = 0; char buffer[256]; for (Int i = 1; i <= 99; ++i) { WeaponBarrelInfo info; info.m_projectileOffsetMtx.Make_Identity(); if (!recoilBoneName.isEmpty()) { sprintf(buffer, "%s%02d", recoilBoneName.str(), i); findPristineBone(NAMEKEY(buffer), &info.m_recoilBone); } if (!mfName.isEmpty()) { sprintf(buffer, "%s%02d", mfName.str(), i); findPristineBone(NAMEKEY(buffer), &info.m_muzzleFlashBone); #if defined(_DEBUG) || defined(_INTERNAL) if (info.m_muzzleFlashBone) info.m_muzzleFlashBoneName = buffer; #endif } if (!fxBoneName.isEmpty()) { sprintf(buffer, "%s%02d", fxBoneName.str(), i); findPristineBone(NAMEKEY(buffer), &info.m_fxBone); // special case: if we have multiple muzzleflashes, but only one fxbone, use that fxbone for everything. if (info.m_fxBone == 0 && info.m_muzzleFlashBone != 0) info.m_fxBone = prevFxBone; } Int plbBoneIndex = 0; if (!plbName.isEmpty()) { sprintf(buffer, "%s%02d", plbName.str(), i); const Matrix3D* mtx = findPristineBone(NAMEKEY(buffer), &plbBoneIndex); if (mtx != NULL) info.m_projectileOffsetMtx = *mtx; } if (info.m_fxBone == 0 && info.m_recoilBone == 0 && info.m_muzzleFlashBone == 0 && plbBoneIndex == 0) break; CRCDEBUG_LOG(("validateWeaponBarrelInfo() - model name %s wslot %d\n", m_modelName.str(), wslot)); DUMPMATRIX3D(&(info.m_projectileOffsetMtx)); BONEPOS_LOG(("validateWeaponBarrelInfo() - model name %s wslot %d\n", m_modelName.str(), wslot)); BONEPOS_DUMPMATRIX3D(&(info.m_projectileOffsetMtx)); m_weaponBarrelInfoVec[wslot].push_back(info); if (info.m_recoilBone != 0 || info.m_muzzleFlashBone != 0) m_hasRecoilBonesOrMuzzleFlashes[wslot] = true; prevFxBone = info.m_fxBone; } if (m_weaponBarrelInfoVec[wslot].empty()) { // try the unadorned names WeaponBarrelInfo info; if (!recoilBoneName.isEmpty()) findPristineBone(NAMEKEY(recoilBoneName), &info.m_recoilBone); if (!mfName.isEmpty()) findPristineBone(NAMEKEY(mfName), &info.m_muzzleFlashBone); #if defined(_DEBUG) || defined(_INTERNAL) if (info.m_muzzleFlashBone) info.m_muzzleFlashBoneName = mfName; #endif const Matrix3D* plbMtx = plbName.isEmpty() ? NULL : findPristineBone(NAMEKEY(plbName), NULL); if (plbMtx != NULL) info.m_projectileOffsetMtx = *plbMtx; else info.m_projectileOffsetMtx.Make_Identity(); if (!fxBoneName.isEmpty()) findPristineBone(NAMEKEY(fxBoneName), &info.m_fxBone); if (info.m_fxBone != 0 || info.m_recoilBone != 0 || info.m_muzzleFlashBone != 0 || plbMtx != NULL) { CRCDEBUG_LOG(("validateWeaponBarrelInfo() - model name %s (unadorned) wslot %d\n", m_modelName.str(), wslot)); DUMPMATRIX3D(&(info.m_projectileOffsetMtx)); BONEPOS_LOG(("validateWeaponBarrelInfo() - model name %s (unadorned) wslot %d\n", m_modelName.str(), wslot)); BONEPOS_DUMPMATRIX3D(&(info.m_projectileOffsetMtx)); m_weaponBarrelInfoVec[wslot].push_back(info); if (info.m_recoilBone != 0 || info.m_muzzleFlashBone != 0) m_hasRecoilBonesOrMuzzleFlashes[wslot] = true; } else { CRCDEBUG_LOG(("validateWeaponBarrelInfo() - model name %s (unadorned) found nothing\n", m_modelName.str())); BONEPOS_LOG(("validateWeaponBarrelInfo() - model name %s (unadorned) found nothing\n", m_modelName.str())); } } // if empty DEBUG_ASSERTCRASH(!(m_modelName.isNotEmpty() && m_weaponBarrelInfoVec[wslot].empty()), ("*** ASSET ERROR: No fx bone named '%s' found in model %s!\n",fxBoneName.str(),m_modelName.str())); } } m_validStuff |= BARRELS_VALID; } //------------------------------------------------------------------------------------------------- void ModelConditionInfo::validateTurretInfo() const { //DEBUG_ASSERTCRASH(isValidTimeToCalcLogicStuff(), ("calling validateTurretInfo() from in GameClient!")); if (m_validStuff & TURRETS_VALID) return; setFPMode(); for (int tslot = 0; tslot < MAX_TURRETS; ++tslot) { TurretInfo& tur = m_turrets[tslot]; if (!isValidTimeToCalcLogicStuff() || m_modelName.isEmpty()) { tur.m_turretAngleBone = 0; tur.m_turretPitchBone = 0; continue; } if (tur.m_turretAngleNameKey != NAMEKEY_INVALID) { if (findPristineBone(tur.m_turretAngleNameKey, &tur.m_turretAngleBone) == NULL) { DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found! (%s)\n",KEYNAME(tur.m_turretAngleNameKey).str(),m_modelName.str())); tur.m_turretAngleBone = 0; } } else { tur.m_turretAngleBone = 0; } if (tur.m_turretPitchNameKey != NAMEKEY_INVALID) { if (findPristineBone(tur.m_turretPitchNameKey, &tur.m_turretPitchBone) == NULL) { DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found! (%s)\n",KEYNAME(tur.m_turretPitchNameKey).str(),m_modelName.str())); tur.m_turretPitchBone = 0; } } else { tur.m_turretPitchBone = 0; } } if (isValidTimeToCalcLogicStuff()) { m_validStuff |= TURRETS_VALID; } } //------------------------------------------------------------------------------------------------- const Matrix3D* ModelConditionInfo::findPristineBone(NameKeyType boneName, Int* boneIndex) const { DEBUG_ASSERTCRASH((m_validStuff & PRISTINE_BONES_VALID), ("*** ASSET ERROR: bones are not valid")); if (!(m_validStuff & PRISTINE_BONES_VALID)) { // set it to zero, some callers rely on this if (boneIndex) *boneIndex = 0; return NULL; } if (boneName == NAMEKEY_INVALID) { // set it to zero, some callers rely on this if (boneIndex) *boneIndex = 0; return NULL; } PristineBoneInfoMap::const_iterator it = m_pristineBones.find(boneName); if (it != m_pristineBones.end()) { if (boneIndex) *boneIndex = it->second.boneIndex; return &it->second.mtx; } else { // set it to zero -- some callers rely on this! if (boneIndex) *boneIndex = 0; return NULL; } } //------------------------------------------------------------------------------------------------- Bool ModelConditionInfo::findPristineBonePos(NameKeyType boneName, Coord3D& pos) const { const Matrix3D* mtx = findPristineBone(boneName, NULL); if (mtx) { Vector3 v = mtx->Get_Translation(); pos.x = v.X; pos.y = v.Y; pos.z = v.Z; return true; } else { pos.zero(); return false; } } //------------------------------------------------------------------------------------------------- void ModelConditionInfo::loadAnimations() const { #ifdef RETAIN_ANIM_HANDLES for (W3DAnimationVector::const_iterator it2 = m_animations.begin(); it2 != m_animations.end(); ++it2) { HAnimClass* h = it2->getAnimHandle(); // just force it to get loaded REF_PTR_RELEASE(h); h = NULL; } #else // srj sez: I think there is no real reason to preload these all anymore. things that need the anims // (for bones) will force an implicit load anyway, but there's no point in forcibly loading the anims // that don't contain interesting logical bones. #endif } //------------------------------------------------------------------------------------------------- void ModelConditionInfo::clear() { int i; #if defined(_DEBUG) || defined(_INTERNAL) m_description.clear(); #endif m_conditionsYesVec.clear(); m_modelName.clear(); for (i = 0; i < MAX_TURRETS; ++i) { m_turrets[i].clear(); } m_hideShowVec.clear(); for (i = 0; i < WEAPONSLOT_COUNT; ++i) { m_weaponFireFXBoneName[i].clear(); m_weaponRecoilBoneName[i].clear(); m_weaponMuzzleFlashName[i].clear(); m_weaponProjectileLaunchBoneName[i].clear(); m_weaponBarrelInfoVec[i].clear(); m_hasRecoilBonesOrMuzzleFlashes[i] = false; } m_particleSysBones.clear(); m_animations.clear(); m_flags = 0; m_transitionKey = NAMEKEY_INVALID; m_allowToFinishKey = NAMEKEY_INVALID; m_iniReadFlags = 0; m_mode = RenderObjClass::ANIM_MODE_ONCE; m_transitionSig = NO_TRANSITION; m_animMinSpeedFactor = 1.0f; m_animMaxSpeedFactor = 1.0f; m_pristineBones.clear(); m_validStuff = 0; } //------------------------------------------------------------------------------------------------- W3DModelDrawModuleData::W3DModelDrawModuleData() : m_validated(0), m_okToChangeModelColor(false), m_animationsRequirePower(true), #ifdef CACHE_ATTACH_BONE m_attachToDrawableBoneOffsetValid(false), #endif m_minLODRequired(STATIC_GAME_LOD_LOW), m_defaultState(-1) { const Real MAX_SHIFT = 3.0f; const Real INITIAL_RECOIL_RATE = 2.0f; const Real RECOIL_DAMPING = 0.4f; const Real SETTLE_RATE = 0.065f; m_projectileBoneFeedbackEnabledSlots = 0; m_initialRecoil = INITIAL_RECOIL_RATE; m_maxRecoil = MAX_SHIFT; m_recoilDamping = RECOIL_DAMPING; m_recoilSettle = SETTLE_RATE; // m_ignoreConditionStates defaults to all zero, which is what we want } //------------------------------------------------------------------------------------------------- void W3DModelDrawModuleData::validateStuffForTimeAndWeather(const Drawable* draw, Bool night, Bool snowy) const { if (!isValidTimeToCalcLogicStuff()) return; enum { NORMAL = 0x0001, NIGHT = 0x0002, SNOWY = 0x0004, NIGHT_SNOWY = 0x0008 }; Int mode; if (night) { mode = (snowy) ? NIGHT_SNOWY : NIGHT; } else { mode = (snowy) ? SNOWY : NORMAL; } if (m_validated & mode) return; m_validated |= mode; for (ModelConditionVector::iterator c_it = m_conditionStates.begin(); c_it != m_conditionStates.end(); ++c_it) { if (!c_it->matchesMode(false, false) && !c_it->matchesMode(night, snowy)) continue; c_it->validateStuff(NULL, draw->getScale(), m_extraPublicBones); } for (TransitionMap::iterator t_it = m_transitionMap.begin(); t_it != m_transitionMap.end(); ++t_it) { // here's the tricky part: only want to load the anims for this one if there is at least one // source AND at least one dest state that matches the current mode NameKeyType src = recoverSrcState(t_it->first); NameKeyType dst = recoverDstState(t_it->first); Bool a = false; Bool b = false; for (c_it = m_conditionStates.begin(); c_it != m_conditionStates.end(); ++c_it) { if (!a && c_it->m_transitionKey == src && c_it->matchesMode(night, snowy)) a = true; if (!b && c_it->m_transitionKey == dst && c_it->matchesMode(night, snowy)) b = true; } if (a && b) { t_it->second.loadAnimations(); // nope -- transition states don't get public bones. //it->addPublicBone(m_extraPublicBones); // srj sez: hm, this doesn't make sense; I think we really do need to validate transition states. t_it->second.validateStuff(NULL, draw->getScale(), m_extraPublicBones); } } } //------------------------------------------------------------------------------------------------- W3DModelDrawModuleData::~W3DModelDrawModuleData() { m_conditionStateMap.clear(); } //------------------------------------------------------------------------------------------------- void W3DModelDrawModuleData::preloadAssets( TimeOfDay timeOfDay, Real scale ) const { for( ModelConditionVector::iterator it = m_conditionStates.begin(); it != m_conditionStates.end(); ++it ) { it->preloadAssets( timeOfDay, scale ); } } //------------------------------------------------------------------------------------------------- AsciiString W3DModelDrawModuleData::getBestModelNameForWB(const ModelConditionFlags& c) const { const ModelConditionInfo* info = findBestInfo(c); if (info) return info->m_modelName; return AsciiString::TheEmptyString; } #ifdef CACHE_ATTACH_BONE //------------------------------------------------------------------------------------------------- const Vector3* W3DModelDrawModuleData::getAttachToDrawableBoneOffset(const Drawable* draw) const { if (m_attachToDrawableBone.isEmpty()) { return NULL; } else { if (!m_attachToDrawableBoneOffsetValid) { // must use pristine bone here since the result is used by logic Matrix3D boneMtx; if (draw->getPristineBonePositions(m_attachToDrawableBone.str(), 0, NULL, &boneMtx, 1) == 1) { m_attachToDrawableBoneOffset = boneMtx.Get_Translation(); } else { m_attachToDrawableBoneOffset.X = 0; m_attachToDrawableBoneOffset.Y = 0; m_attachToDrawableBoneOffset.Z = 0; } m_attachToDrawableBoneOffsetValid = true; } return &m_attachToDrawableBoneOffset; } } #endif //------------------------------------------------------------------------------------------------- enum ParseCondStateType { PARSE_NORMAL, PARSE_DEFAULT, PARSE_TRANSITION, PARSE_ALIAS }; //------------------------------------------------------------------------------------------------- static void parseAsciiStringLC( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) { AsciiString* asciiString = (AsciiString *)store; *asciiString = ini->getNextAsciiString(); asciiString->toLower(); } //------------------------------------------------------------------------------------------------- void W3DModelDrawModuleData::buildFieldParse(MultiIniFieldParse& p) { ModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { { "InitialRecoilSpeed", INI::parseVelocityReal, NULL, offsetof(W3DModelDrawModuleData, m_initialRecoil) }, { "MaxRecoilDistance", INI::parseReal, NULL, offsetof(W3DModelDrawModuleData, m_maxRecoil) }, { "RecoilDamping", INI::parseReal, NULL, offsetof(W3DModelDrawModuleData, m_recoilDamping) }, { "RecoilSettleSpeed", INI::parseVelocityReal, NULL, offsetof(W3DModelDrawModuleData, m_recoilSettle) }, { "OkToChangeModelColor", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_okToChangeModelColor) }, { "AnimationsRequirePower", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_animationsRequirePower) }, { "MinLODRequired", INI::parseStaticGameLODLevel, NULL, offsetof(W3DModelDrawModuleData, m_minLODRequired) }, { "ProjectileBoneFeedbackEnabledSlots", INI::parseBitString32, TheWeaponSlotTypeNames, offsetof(W3DModelDrawModuleData, m_projectileBoneFeedbackEnabledSlots) }, { "DefaultConditionState", W3DModelDrawModuleData::parseConditionState, (void*)PARSE_DEFAULT, 0 }, { "ConditionState", W3DModelDrawModuleData::parseConditionState, (void*)PARSE_NORMAL, 0 }, { "AliasConditionState", W3DModelDrawModuleData::parseConditionState, (void*)PARSE_ALIAS, 0 }, { "TransitionState", W3DModelDrawModuleData::parseConditionState, (void*)PARSE_TRANSITION, 0 }, { "TrackMarks", parseAsciiStringLC, NULL, offsetof(W3DModelDrawModuleData, m_trackFile) }, { "ExtraPublicBone", INI::parseAsciiStringVectorAppend, NULL, offsetof(W3DModelDrawModuleData, m_extraPublicBones) }, { "AttachToBoneInAnotherModule", parseAsciiStringLC, NULL, offsetof(W3DModelDrawModuleData, m_attachToDrawableBone) }, { "IgnoreConditionStates", ModelConditionFlags::parseFromINI, NULL, offsetof(W3DModelDrawModuleData, m_ignoreConditionStates) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } //------------------------------------------------------------------------------------------------- enum AnimParseType { ANIM_NORMAL, ANIM_IDLE }; //------------------------------------------------------------------------------------------------- static void parseAnimation(INI* ini, void *instance, void * /*store*/, const void* userData) { AnimParseType animType = (AnimParseType)(UnsignedInt)userData; AsciiString animName = ini->getNextAsciiString(); animName.toLower(); const char* distanceCoveredToken = ini->getNextTokenOrNull(); Real distanceCovered = distanceCoveredToken ? INI::scanReal(distanceCoveredToken) : 0; DEBUG_ASSERTCRASH(!(animType == ANIM_IDLE && distanceCovered != 0), ("You should not specify nonzero DistanceCovered values for Idle Anims")); const char* timesToRepeatToken = ini->getNextTokenOrNull(); Int timesToRepeat = timesToRepeatToken ? INI::scanInt(timesToRepeatToken) : 1; if (timesToRepeat < 1) timesToRepeat = 1; W3DAnimationInfo animInfo(animName, (animType == ANIM_IDLE), distanceCovered); ModelConditionInfo* self = (ModelConditionInfo*)instance; if (self->m_iniReadFlags & (1<m_iniReadFlags &= ~((1<m_animations.clear(); } if (animInfo.isIdleAnim()) self->m_iniReadFlags |= (1<m_iniReadFlags |= (1<m_animations.push_back(animInfo); } } //------------------------------------------------------------------------------------------------- static void parseShowHideSubObject(INI* ini, void *instance, void *store, const void* userData) { std::vector* vec = (std::vector*)store; AsciiString subObjName = ini->getNextAsciiString(); subObjName.toLower(); // handle "None" if (subObjName.isNone()) { vec->clear(); return; } while (subObjName.isNotEmpty()) { Bool found = false; for (std::vector::iterator it = vec->begin(); it != vec->end(); ++it) { if (stricmp(it->subObjName.str(), subObjName.str()) == 0) { it->hide = (userData != NULL); found = true; } } if (!found) { ModelConditionInfo::HideShowSubObjInfo info; info.subObjName = subObjName; info.hide = (userData != NULL); vec->push_back(info); } subObjName = ini->getNextAsciiString(); subObjName.toLower(); } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::showSubObject( const AsciiString& name, Bool show ) { if( name.isNotEmpty() ) { Bool found = false; for( std::vector::iterator it = m_subObjectVec.begin(); it != m_subObjectVec.end(); ++it ) { if( stricmp( it->subObjName.str(), name.str() ) == 0 ) { it->hide = !show; found = true; } } if( !found ) { ModelConditionInfo::HideShowSubObjInfo info; info.subObjName = name; info.hide = !show; m_subObjectVec.push_back( info ); } } } //------------------------------------------------------------------------------------------------- static void parseWeaponBoneName(INI* ini, void *instance, void * store, const void* /*userData*/) { ModelConditionInfo* self = (ModelConditionInfo*)instance; AsciiString* arr = (AsciiString*)store; WeaponSlotType wslot = (WeaponSlotType)INI::scanIndexList(ini->getNextToken(), TheWeaponSlotTypeNames); arr[wslot] = ini->getNextAsciiString(); arr[wslot].toLower(); if (arr[wslot].isNone()) arr[wslot].clear(); if (self) self->addPublicBone(arr[wslot]); } //------------------------------------------------------------------------------------------------- static void parseParticleSysBone(INI* ini, void *instance, void * store, const void * /*userData*/) { ParticleSysBoneInfo info; info.boneName = ini->getNextAsciiString(); info.boneName.toLower(); ini->parseParticleSystemTemplate(ini, instance, &(info.particleSystemTemplate), NULL); ModelConditionInfo *self = (ModelConditionInfo *)instance; self->m_particleSysBones.push_back(info); } //------------------------------------------------------------------------------------------------- static void parseRealRange( INI *ini, void *instance, void *store, const void* /*userData*/ ) { ModelConditionInfo *self = (ModelConditionInfo *)instance; const char *token = ini->getNextToken(); self->m_animMinSpeedFactor = ini->scanReal( token ); token = ini->getNextToken(); self->m_animMaxSpeedFactor = ini->scanReal( token ); } //------------------------------------------------------------------------------------------------- static void parseLowercaseNameKey(INI* ini, void *instance, void * store, const void * /*userData*/) { NameKeyType* key = (NameKeyType*)store; AsciiString tmp = ini->getNextToken(); tmp.toLower(); *key = NAMEKEY(tmp.str()); } //------------------------------------------------------------------------------------------------- static void parseBoneNameKey(INI* ini, void *instance, void * store, const void * /*userData*/) { ModelConditionInfo* self = (ModelConditionInfo*)instance; NameKeyType* key = (NameKeyType*)store; AsciiString tmp = ini->getNextToken(); tmp.toLower(); if (self) self->addPublicBone(tmp); if (tmp.isEmpty() || tmp.isNone()) *key = NAMEKEY_INVALID; else *key = NAMEKEY(tmp.str()); } //------------------------------------------------------------------------------------------------- static Bool doesStateExist(const ModelConditionVector& v, const ModelConditionFlags& f) { for (ModelConditionVector::const_iterator it = v.begin(); it != v.end(); ++it) { for (Int i = it->getConditionsYesCount()-1; i >= 0; --i) { if (f == it->getNthConditionsYes(i)) return true; } } return false; } //------------------------------------------------------------------------------------------------- void W3DModelDrawModuleData::parseConditionState(INI* ini, void *instance, void * /*store*/, const void* userData) { static const FieldParse myFieldParse[] = { { "Model", parseAsciiStringLC, NULL, offsetof(ModelConditionInfo, m_modelName) }, { "Turret", parseBoneNameKey, NULL, offsetof(ModelConditionInfo, m_turrets[0].m_turretAngleNameKey) }, { "TurretArtAngle", INI::parseAngleReal, NULL, offsetof(ModelConditionInfo, m_turrets[0].m_turretArtAngle) }, { "TurretPitch", parseBoneNameKey, NULL, offsetof(ModelConditionInfo, m_turrets[0].m_turretPitchNameKey) }, { "TurretArtPitch", INI::parseAngleReal, NULL, offsetof(ModelConditionInfo, m_turrets[0].m_turretArtPitch) }, { "AltTurret", parseBoneNameKey, NULL, offsetof(ModelConditionInfo, m_turrets[1].m_turretAngleNameKey) }, { "AltTurretArtAngle", INI::parseAngleReal, NULL, offsetof(ModelConditionInfo, m_turrets[1].m_turretArtAngle) }, { "AltTurretPitch", parseBoneNameKey, NULL, offsetof(ModelConditionInfo, m_turrets[1].m_turretPitchNameKey) }, { "AltTurretArtPitch", INI::parseAngleReal, NULL, offsetof(ModelConditionInfo, m_turrets[1].m_turretArtPitch) }, { "ShowSubObject", parseShowHideSubObject, (void*)0, offsetof(ModelConditionInfo, m_hideShowVec) }, { "HideSubObject", parseShowHideSubObject, (void*)1, offsetof(ModelConditionInfo, m_hideShowVec) }, { "WeaponFireFXBone", parseWeaponBoneName, NULL, offsetof(ModelConditionInfo, m_weaponFireFXBoneName[0]) }, { "WeaponRecoilBone", parseWeaponBoneName, NULL, offsetof(ModelConditionInfo, m_weaponRecoilBoneName[0]) }, { "WeaponMuzzleFlash", parseWeaponBoneName, NULL, offsetof(ModelConditionInfo, m_weaponMuzzleFlashName[0]) }, { "WeaponLaunchBone", parseWeaponBoneName, NULL, offsetof(ModelConditionInfo, m_weaponProjectileLaunchBoneName[0]) }, { "WeaponHideShowBone", parseWeaponBoneName, NULL, offsetof(ModelConditionInfo, m_weaponProjectileHideShowName[0]) }, { "Animation", parseAnimation, (void*)ANIM_NORMAL, offsetof(ModelConditionInfo, m_animations) }, { "IdleAnimation", parseAnimation, (void*)ANIM_IDLE, offsetof(ModelConditionInfo, m_animations) }, { "AnimationMode", INI::parseIndexList, TheAnimModeNames, offsetof(ModelConditionInfo, m_mode) }, { "TransitionKey", parseLowercaseNameKey, NULL, offsetof(ModelConditionInfo, m_transitionKey) }, { "WaitForStateToFinishIfPossible", parseLowercaseNameKey, NULL, offsetof(ModelConditionInfo, m_allowToFinishKey) }, { "Flags", INI::parseBitString32, ACBitsNames, offsetof(ModelConditionInfo, m_flags) }, { "ParticleSysBone", parseParticleSysBone, NULL, 0 }, { "AnimationSpeedFactorRange", parseRealRange, NULL, 0 }, { 0, 0, 0, 0 } }; ModelConditionInfo info; W3DModelDrawModuleData* self = (W3DModelDrawModuleData*)instance; ParseCondStateType cst = (ParseCondStateType)(UnsignedInt)userData; switch (cst) { case PARSE_DEFAULT: { if (self->m_defaultState >= 0) { DEBUG_CRASH(("*** ASSET ERROR: you may have only one default state!\n")); throw INI_INVALID_DATA; } else if (ini->getNextTokenOrNull()) { DEBUG_CRASH(("*** ASSET ERROR: unknown keyword\n")); throw INI_INVALID_DATA; } else { if (!self->m_conditionStates.empty()) { DEBUG_CRASH(("*** ASSET ERROR: when using DefaultConditionState, it must be the first state listed (%s)\n",TheThingTemplateBeingParsedName.str())); throw INI_INVALID_DATA; } // note, this is size(), not size()-1, since we haven't actually modified the list yet self->m_defaultState = self->m_conditionStates.size(); //DEBUG_LOG(("set default state to %d\n",self->m_defaultState)); // add an empty conditionstateflag set ModelConditionFlags blankConditions; info.m_conditionsYesVec.clear(); info.m_conditionsYesVec.push_back(blankConditions); #if defined(_DEBUG) || defined(_INTERNAL) info.m_description.clear(); info.m_description.concat(TheThingTemplateBeingParsedName); info.m_description.concat(" DEFAULT"); #endif } } break; case PARSE_TRANSITION: { AsciiString firstNm = ini->getNextToken(); firstNm.toLower(); AsciiString secondNm = ini->getNextToken(); secondNm.toLower(); NameKeyType firstKey = NAMEKEY(firstNm); NameKeyType secondKey = NAMEKEY(secondNm); if (firstKey == secondKey) { DEBUG_CRASH(("*** ASSET ERROR: You may not declare a transition between two identical states\n")); throw INI_INVALID_DATA; } if (self->m_defaultState >= 0) { info = self->m_conditionStates.at(self->m_defaultState); info.m_iniReadFlags |= (1<m_conditionStates.empty()) { DEBUG_CRASH(("*** ASSET ERROR: AliasConditionState must refer to the previous state!\n")); throw INI_INVALID_DATA; } ModelConditionInfo& prevState = self->m_conditionStates.at(self->m_conditionStates.size()-1); ModelConditionFlags conditionsYes; #if defined(_DEBUG) || defined(_INTERNAL) AsciiString description; conditionsYes.parse(ini, &description); prevState.m_description.concat("\nAKA: "); prevState.m_description.concat(description); #else conditionsYes.parse(ini, NULL); #endif if (conditionsYes.anyIntersectionWith(self->m_ignoreConditionStates)) { DEBUG_CRASH(("You should not specify bits in a state once they are used in IgnoreConditionStates (%s)\n", TheThingTemplateBeingParsedName.str())); throw INI_INVALID_DATA; } if (doesStateExist(self->m_conditionStates, conditionsYes)) { DEBUG_CRASH(("*** ASSET ERROR: duplicate condition states are not currently allowed")); throw INI_INVALID_DATA; } if (!conditionsYes.any() && self->m_defaultState >= 0) { DEBUG_CRASH(("*** ASSET ERROR: you may not specify both a Default state and a Conditions=None state")); throw INI_INVALID_DATA; } prevState.m_conditionsYesVec.push_back(conditionsYes); // yes, return, NOT break! return; } case PARSE_NORMAL: { if (self->m_defaultState >= 0 && cst != PARSE_ALIAS) { info = self->m_conditionStates.at(self->m_defaultState); info.m_iniReadFlags |= (1<m_ignoreConditionStates)) { DEBUG_CRASH(("You should not specify bits in a state once they are used in IgnoreConditionStates (%s)\n", TheThingTemplateBeingParsedName.str())); throw INI_INVALID_DATA; } if (self->m_defaultState < 0 && self->m_conditionStates.empty() && conditionsYes.any()) { // it doesn't actually NEED to be first, but it does need to be present, and this is the simplest way to enforce... DEBUG_CRASH(("*** ASSET ERROR: when not using DefaultConditionState, the first ConditionState must be for NONE (%s)\n",TheThingTemplateBeingParsedName.str())); throw INI_INVALID_DATA; } if (!conditionsYes.any() && self->m_defaultState >= 0) { DEBUG_CRASH(("*** ASSET ERROR: you may not specify both a Default state and a Conditions=None state")); throw INI_INVALID_DATA; } if (doesStateExist(self->m_conditionStates, conditionsYes)) { DEBUG_CRASH(("*** ASSET ERROR: duplicate condition states are not currently allowed (%s)",info.m_description.str())); throw INI_INVALID_DATA; } DEBUG_ASSERTCRASH(info.m_conditionsYesVec.size() == 0, ("*** ASSET ERROR: nonempty m_conditionsYesVec.size(), see srj")); info.m_conditionsYesVec.clear(); info.m_conditionsYesVec.push_back(conditionsYes); } break; } ini->initFromINI(&info, myFieldParse); if (info.m_modelName.isEmpty()) { DEBUG_CRASH(("*** ASSET ERROR: you must specify a model name")); throw INI_INVALID_DATA; } else if (info.m_modelName.isNone()) { info.m_modelName.clear(); } if ((info.m_iniReadFlags & (1<m_transitionMap[info.m_transitionSig] = info; } else { self->m_conditionStates.push_back(info); } } //------------------------------------------------------------------------------------------------- static Int countOnBits(UnsignedInt val) { Int count = 0; for (Int i = 0; i < 32; ++i) { if (val & 1) ++count; val >>= 1; } return count; } //------------------------------------------------------------------------------------------------- const ModelConditionInfo* W3DModelDrawModuleData::findBestInfo(const ModelConditionFlags& c) const { ModelConditionFlags bits = c; bits.clear(m_ignoreConditionStates); return m_conditionStateMap.findBestInfo(m_conditionStates, bits); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- W3DModelDraw::W3DModelDraw(Thing *thing, const ModuleData* moduleData) : DrawModule(thing, moduleData) { int i; m_animationMode = RenderObjClass::ANIM_MODE_LOOP; m_hideHeadlights = true; m_pauseAnimation = false; m_curState = NULL; m_hexColor = 0; m_renderObject = NULL; m_shadow = NULL; m_shadowEnabled = TRUE; m_terrainDecal = NULL; m_trackRenderObject = NULL; m_whichAnimInCurState = -1; m_nextState = NULL; m_nextStateAnimLoopDuration = NO_NEXT_DURATION; for (i = 0; i < WEAPONSLOT_COUNT; ++i) { m_weaponRecoilInfoVec[i].clear(); } m_needRecalcBoneParticleSystems = false; m_fullyObscuredByShroud = false; // only validate the current time-of-day and weather conditions by default. getW3DModelDrawModuleData()->validateStuffForTimeAndWeather(getDrawable(), TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT, TheGlobalData->m_weather == WEATHER_SNOWY); ModelConditionFlags emptyFlags; const ModelConditionInfo* info = findBestInfo(emptyFlags); if (!info) { DEBUG_CRASH(("*** ASSET ERROR: all draw modules must have an IDLE state\n")); throw INI_INVALID_DATA; } Drawable* draw = getDrawable(); Object* obj = draw ? draw->getObject() : NULL; if (obj) { if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT) m_hexColor = obj->getNightIndicatorColor(); else m_hexColor = obj->getIndicatorColor(); } setModelState(info); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void W3DModelDraw::onDrawableBoundToObject(void) { getW3DModelDrawModuleData()->validateStuffForTimeAndWeather(getDrawable(), TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT, TheGlobalData->m_weather == WEATHER_SNOWY); } //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- W3DModelDraw::~W3DModelDraw(void) { if (m_trackRenderObject && TheTerrainTracksRenderObjClassSystem) { TheTerrainTracksRenderObjClassSystem->unbindTrack(m_trackRenderObject); m_trackRenderObject = NULL; } nukeCurrentRender(NULL); } //------------------------------------------------------------------------------------------------- void W3DModelDraw::doStartOrStopParticleSys() { Bool hidden = getDrawable()->isDrawableEffectivelyHidden() || m_fullyObscuredByShroud; for (std::vector::const_iterator it = m_particleSystemIDs.begin(); it != m_particleSystemIDs.end(); ++it) //for (std::vector::const_iterator it = m_particleSystemIDs.begin(); it != m_particleSystemIDs.end(); ++it) { ParticleSystem *sys = TheParticleSystemManager->findParticleSystem((*it).id); if (sys != NULL) { // this can be NULL if (hidden) { sys->stop(); } else { sys->start(); } } } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::setHidden(Bool hidden) { if (m_renderObject) m_renderObject->Set_Hidden(hidden); if (m_shadow) m_shadow->enableShadowRender(!hidden); m_shadowEnabled = hidden; if (m_terrainDecal) m_terrainDecal->enableShadowRender(!hidden); if (m_trackRenderObject && hidden) { const Coord3D* pos = getDrawable()->getPosition(); m_trackRenderObject->addCapEdgeToTrack(pos->x,pos->y); } doStartOrStopParticleSys(); } /**Free all data used by this model's shadow. This is used to dynamically enable/disable shadows by the options screen*/ void W3DModelDraw::releaseShadows(void) ///< frees all shadow resources used by this module - used by Options screen. { if (m_shadow) m_shadow->release(); m_shadow = NULL; } /** Create shadow resources if not already present. This is used to dynamically enable/disable shadows by the options screen*/ void W3DModelDraw::allocateShadows(void) { const ThingTemplate *tmplate=getDrawable()->getTemplate(); //Check if we don't already have a shadow but need one for this type of model. if (m_shadow == NULL && m_renderObject && TheW3DShadowManager && tmplate->getShadowType() != SHADOW_NONE) { Shadow::ShadowTypeInfo shadowInfo; strcpy(shadowInfo.m_ShadowName, tmplate->getShadowTextureName().str()); DEBUG_ASSERTCRASH(shadowInfo.m_ShadowName[0] != '\0', ("this should be validated in ThingTemplate now")); shadowInfo.allowUpdates = FALSE; //shadow image will never update shadowInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects shadowInfo.m_type = (ShadowType)tmplate->getShadowType(); shadowInfo.m_sizeX = tmplate->getShadowSizeX(); shadowInfo.m_sizeY = tmplate->getShadowSizeY(); shadowInfo.m_offsetX = tmplate->getShadowOffsetX(); shadowInfo.m_offsetY = tmplate->getShadowOffsetY(); m_shadow = TheW3DShadowManager->addShadow(m_renderObject, &shadowInfo); if (m_shadow) { m_shadow->enableShadowInvisible(m_fullyObscuredByShroud); if (m_renderObject->Is_Hidden() || !m_shadowEnabled) m_shadow->enableShadowRender(FALSE); } } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::setShadowsEnabled(Bool enable) { if (m_shadow) m_shadow->enableShadowRender(enable); m_shadowEnabled = enable; } /**collect some stats about the rendering cost of this draw module */ #if defined(_DEBUG) || defined(_INTERNAL) void W3DModelDraw::getRenderCost(RenderCost & rc) const { getRenderCostRecursive(rc,m_renderObject); if (m_shadow) m_shadow->getRenderCost(rc); } #endif //_DEBUG || _INTERNAL /**recurse through sub-objs to collect stats about the rendering cost of this draw module */ #if defined(_DEBUG) || defined(_INTERNAL) void W3DModelDraw::getRenderCostRecursive(RenderCost & rc,RenderObjClass * robj) const { if (robj == NULL) return; // recurse through sub-objects for (int i=0; iGet_Num_Sub_Objects(); i++) { RenderObjClass * sub_obj = robj->Get_Sub_Object(i); getRenderCostRecursive(rc,sub_obj); REF_PTR_RELEASE(sub_obj); } // Only consider visible sub-objects. Some vehicles have hidden headlight cones for example // that are not normally rendered. if (robj->Is_Not_Hidden_At_All()) { // collect stats from meshes if (robj->Class_ID() == RenderObjClass::CLASSID_MESH) { MeshClass * mesh = (MeshClass*)robj; MeshModelClass * model = mesh->Peek_Model(); if (model != NULL) { if (model->Get_Flag(MeshGeometryClass::SORT)) rc.addSortedMeshes(1); if (model->Get_Flag(MeshGeometryClass::SKIN)) rc.addSkinMeshes(1); } rc.addDrawCalls(mesh->Get_Draw_Call_Count()); } // collect bone stats. const HTreeClass * htree = robj->Get_HTree(); if (htree != NULL) { rc.addBones(htree->Num_Pivots()); } } } #endif //_DEBUG || _INTERNAL //------------------------------------------------------------------------------------------------- void W3DModelDraw::setFullyObscuredByShroud(Bool fullyObscured) { if (m_fullyObscuredByShroud != fullyObscured) { m_fullyObscuredByShroud = fullyObscured; if (m_shadow) m_shadow->enableShadowInvisible(m_fullyObscuredByShroud); if (m_terrainDecal) m_terrainDecal->enableShadowInvisible(m_fullyObscuredByShroud); doStartOrStopParticleSys(); } } //------------------------------------------------------------------------------------------------- static Bool isAnimationComplete(RenderObjClass* r) { if (r && r->Class_ID() == RenderObjClass::CLASSID_HLOD) { HLodClass *hlod = (HLodClass*)r; return hlod->Is_Animation_Complete(); } return true; } //------------------------------------------------------------------------------------------------- void W3DModelDraw::adjustAnimSpeedToMovementSpeed() { // if the cur animation has a "distance covered" number, try to throttle // the animation rate to match our actual speed. Real dist = getCurAnimDistanceCovered(); if (dist > 0.0f) { const Object *obj = getDrawable()->getObject(); if (obj) { const PhysicsBehavior* physics = obj->getPhysics(); if (physics) { Real speed = physics->getVelocityMagnitude(); // in dist/frame if (speed > 0.0f) { // if distance-covered is specified for this anim, adjust the frame rate // so it appears to sync with our unit speed Real desiredDurationInMsec = dist / speed * MSEC_PER_LOGICFRAME_REAL; setCurAnimDurationInMsec(desiredDurationInMsec); } } } } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::adjustTransformMtx(Matrix3D& mtx) const { const W3DModelDrawModuleData* d = getW3DModelDrawModuleData(); #ifdef CACHE_ATTACH_BONE const Vector3* offset = d->getAttachToDrawableBoneOffset(getDrawable()); if (offset) { Vector3 tmp = mtx.Rotate_Vector(*offset); mtx.Adjust_X_Translation(tmp.X); mtx.Adjust_Y_Translation(tmp.Y); mtx.Adjust_Z_Translation(tmp.Z); } #else if (d->m_attachToDrawableBone.isNotEmpty()) { // override the mtx in this case. yes, call drawable, since the bone in question is // likely to be in another module! Matrix3D boneMtx; if (getDrawable()->getCurrentWorldspaceClientBonePositions(d->m_attachToDrawableBone.str(), boneMtx)) { mtx = boneMtx; } else { DEBUG_LOG(("m_attachToDrawableBone %s not found\n",getW3DModelDrawModuleData()->m_attachToDrawableBone.str())); } } #endif if (m_curState->m_flags & (1<getObject(); if (obj) { Real pct = obj->getConstructionPercent(); if (pct >= 0.0f) { Real height = obj->getGeometryInfo().getMaxHeightAbovePosition(); mtx.Translate_Z(-height + (height * pct / 100.0f)); } } } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::doDrawModule(const Matrix3D* transformMtx) { // update whether or not we should be animating. setPauseAnimation( !getDrawable()->getShouldAnimate(getW3DModelDrawModuleData()->m_animationsRequirePower) ); Matrix3D scaledTransform; if (getDrawable()->getInstanceScale() != 1.0f) { // do custom scaling of the W3D model. scaledTransform = *transformMtx; scaledTransform.Scale(getDrawable()->getInstanceScale()); transformMtx = &scaledTransform; if (m_renderObject) m_renderObject->Set_ObjectScale(getDrawable()->getInstanceScale()); } if (isAnimationComplete(m_renderObject)) { if (m_curState != NULL && m_nextState != NULL) { //DEBUG_LOG(("transition %s is complete\n",m_curState->m_description.str())); const ModelConditionInfo* nextState = m_nextState; UnsignedInt nextDuration = m_nextStateAnimLoopDuration; m_nextState = NULL; m_nextStateAnimLoopDuration = NO_NEXT_DURATION; setModelState(nextState); if (nextDuration != NO_NEXT_DURATION) { //DEBUG_LOG(("restoring pending duration of %d frames\n",nextDuration)); setAnimationLoopDuration(nextDuration); } } if (m_renderObject && m_curState != NULL && m_whichAnimInCurState != -1) { if (m_curState->m_animations[m_whichAnimInCurState].isIdleAnim()) { //DEBUG_LOG(("randomly switching to new idle state!\n")); // state hasn't changed, if it's been awhile, switch the idle anim // (yes, that's right: pass curState for prevState) adjustAnimation(m_curState, -1.0); } else if (testFlagBit(m_curState->m_flags, RESTART_ANIM_WHEN_COMPLETE)) { adjustAnimation(m_curState, -1.0); } } } adjustAnimSpeedToMovementSpeed(); // set our position in the render object to our position of the drawable if (m_renderObject) { Matrix3D mtx = *transformMtx; adjustTransformMtx(mtx); m_renderObject->Set_Transform(mtx); } handleClientTurretPositioning(); recalcBonesForClientParticleSystems(); handleClientRecoil(); } //------------------------------------------------------------------------------------------------- const ModelConditionInfo* W3DModelDraw::findTransitionForSig(TransitionSig sig) const { const TransitionMap& transitionMap = getW3DModelDrawModuleData()->m_transitionMap; TransitionMap::const_iterator it = transitionMap.find(sig); if (it != transitionMap.end()) { return &(*it).second; } return NULL; } //------------------------------------------------------------------------------------------------- Real W3DModelDraw::getCurrentAnimFraction() const { if (m_curState != NULL && isAnyMaintainFrameFlagSet(m_curState->m_flags) && m_renderObject != NULL && m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) { float framenum, dummy; int mode, numFrames; HLodClass* hlod = (HLodClass*)m_renderObject; /*HAnimClass* anim =*/ hlod->Peek_Animation_And_Info(framenum, numFrames, mode, dummy); if (framenum < 0.0) return 0.0; else if (framenum >= numFrames) return 1.0; else return (Real)framenum / ((Real)numFrames - 1); } else { return -1.0; } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::adjustAnimation(const ModelConditionInfo* prevState, Real prevAnimFraction) { if (!m_curState) return; // if the current state has m_animations associated, do the right thing Int numAnims = m_curState->m_animations.size(); if (numAnims > 0) { if (numAnims == 1) { m_whichAnimInCurState = 0; } else if (prevState == m_curState) { // select an idle anim different than the currently playing one Int animToAvoid = m_whichAnimInCurState; while (m_whichAnimInCurState == animToAvoid) m_whichAnimInCurState = GameClientRandomValue(0, numAnims-1); } else { m_whichAnimInCurState = GameClientRandomValue(0, numAnims-1); } const W3DAnimationInfo& animInfo = m_curState->m_animations[m_whichAnimInCurState]; HAnimClass* animHandle = animInfo.getAnimHandle(); // note that this now returns an ADDREFED handle, which must be released by the caller! if (m_renderObject && animHandle) { Int startFrame = 0; if (m_curState->m_mode == RenderObjClass::ANIM_MODE_ONCE_BACKWARDS || m_curState->m_mode == RenderObjClass::ANIM_MODE_LOOP_BACKWARDS) { startFrame = animHandle->Get_Num_Frames()-1; } if (testFlagBit(m_curState->m_flags, RANDOMIZE_START_FRAME)) { startFrame = GameClientRandomValue(0, animHandle->Get_Num_Frames()-1); } else if (testFlagBit(m_curState->m_flags, START_FRAME_FIRST)) { startFrame = 0; } else if (testFlagBit(m_curState->m_flags, START_FRAME_LAST)) { startFrame = animHandle->Get_Num_Frames()-1; } // order is important here: MAINTAIN_FRAME_ACROSS_STATES is overridden by the other bits, above. else if (isAnyMaintainFrameFlagSet(m_curState->m_flags) && prevState && prevState != m_curState && isAnyMaintainFrameFlagSet(prevState->m_flags) && isCommonMaintainFrameFlagSet(m_curState->m_flags, prevState->m_flags) && prevAnimFraction >= 0.0) { startFrame = REAL_TO_INT(prevAnimFraction * animHandle->Get_Num_Frames()-1); } m_renderObject->Set_Animation(animHandle, startFrame, m_curState->m_mode); REF_PTR_RELEASE(animHandle); animHandle = NULL; if (m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) { HLodClass *hlod = (HLodClass*)m_renderObject; Real factor = GameClientRandomValueReal( m_curState->m_animMinSpeedFactor, m_curState->m_animMaxSpeedFactor ); hlod->Set_Animation_Frame_Rate_Multiplier( factor ); } } } else { m_whichAnimInCurState = -1; } } //------------------------------------------------------------------------------------------------- Bool W3DModelDraw::setCurAnimDurationInMsec(Real desiredDurationInMsec) { if (m_renderObject && m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) { HLodClass* hlod = (HLodClass*)m_renderObject; HAnimClass* anim = hlod->Peek_Animation(); if (anim) { Real naturalDurationInMsec = anim->Get_Num_Frames() * 1000.0f / anim->Get_Frame_Rate(); if (naturalDurationInMsec > 0.0f && desiredDurationInMsec > 0.0f) { Real multiplier = naturalDurationInMsec / desiredDurationInMsec; hlod->Set_Animation_Frame_Rate_Multiplier(multiplier); return true; } } } return false; } //------------------------------------------------------------------------------------------------- Real W3DModelDraw::getCurAnimDistanceCovered() const { if (m_curState != NULL && m_whichAnimInCurState >= 0) { const W3DAnimationInfo& animInfo = m_curState->m_animations[m_whichAnimInCurState]; #if defined(_DEBUG) || defined(_INTERNAL) if (TheSkateDistOverride != 0.0f) return TheSkateDistOverride; #endif return animInfo.getDistanceCovered(); } return 0.0f; } //------------------------------------------------------------------------------------------------- /** Utility function to make it easier to recursively hide all objects connected to a certain bone. We will hide all objects connected to bones which are children of boneIdx */ static void doHideShowBoneSubObjs(Bool state, Int numSubObjects, Int boneIdx, RenderObjClass *fullObject, const HTreeClass *htree) { #if 1 //(gth) fixed and tested this version for (Int i=0; i < numSubObjects; i++) { bool is_child = false; Int parentBoneIndex = fullObject->Get_Sub_Object_Bone_Index(0, i); while (parentBoneIndex != 0) { parentBoneIndex = htree->Get_Parent_Index(parentBoneIndex); if (parentBoneIndex == boneIdx) { is_child = true; break; } } if (is_child) { RenderObjClass* childObject = fullObject->Get_Sub_Object(i); childObject->Set_Hidden(state); childObject->Release_Ref(); } } #endif #if 0 //old slow version for (Int i=0; i < numSubObjects; i++) { Int childBoneIndex = fullObject->Get_Sub_Object_Bone_Index(0, i); Int parentIndex = htree->Get_Parent_Index(childBoneIndex); if (childBoneIndex == parentIndex) continue; if (parentIndex == boneIdx) // this object has our subobject as parent so copy hide state { RenderObjClass* childObject = fullObject->Get_Sub_Object(i); // recurse down the hierarchy to hide all sub-children doHideShowBoneSubObjs(state, numSubObjects, childBoneIndex, fullObject, htree); childObject->Set_Hidden(state); childObject->Release_Ref(); } } #endif } //------------------------------------------------------------------------------------------------- void ModelConditionInfo::WeaponBarrelInfo::setMuzzleFlashHidden(RenderObjClass *fullObject, Bool hide) const { if (fullObject) { RenderObjClass* childObject = fullObject->Get_Sub_Object_On_Bone(0, m_muzzleFlashBone); if (childObject) { childObject->Set_Hidden(hide); childObject->Release_Ref(); } else { DEBUG_CRASH(("*** ASSET ERROR: childObject %s not found in setMuzzleFlashHidden()\n",m_muzzleFlashBoneName.str())); } } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::doHideShowSubObjs(const std::vector* vec) { if (!m_renderObject) return; if (!vec->empty()) { for (std::vector::const_iterator it = vec->begin(); it != vec->end(); ++it) { Int objIndex; RenderObjClass* subObj; if ((subObj = m_renderObject->Get_Sub_Object_By_Name(it->subObjName.str(), &objIndex)) != NULL) { subObj->Set_Hidden(it->hide); const HTreeClass *htree = m_renderObject->Get_HTree(); if (htree) { //get the bone of this subobject so we can hide all other child objects that use this bone //as a parent. Int boneIdx = m_renderObject->Get_Sub_Object_Bone_Index(0, objIndex); doHideShowBoneSubObjs(it->hide, m_renderObject->Get_Num_Sub_Objects(), boneIdx, m_renderObject, htree); } subObj->Release_Ref(); } else { DEBUG_CRASH(("*** ASSET ERROR: SubObject %s not found (%s)!\n",it->subObjName.str(),getDrawable()->getTemplate()->getName().str())); } } } //Kris (added Aug 2002) //This is really important as it allows a person to override modelcondition show/hide objects. Used by the A10 strike //September 2002 -- now used by the UpgradeSubObject system. if( !m_subObjectVec.empty() ) { updateSubObjects(); } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::stopClientParticleSystems() { for (std::vector::const_iterator it = m_particleSystemIDs.begin(); it != m_particleSystemIDs.end(); ++it) //for (std::vector::const_iterator it = m_particleSystemIDs.begin(); it != m_particleSystemIDs.end(); ++it) { ParticleSystem *sys = TheParticleSystemManager->findParticleSystem((*it).id); if (sys != NULL) { // this can be NULL sys->destroy(); } } m_particleSystemIDs.clear(); } //------------------------------------------------------------------------------------------------- /* DANGER WARNING READ ME DANGER WARNING READ ME DANGER WARNING READ ME This function must not EVER do ANYTHING which can in any way, shape, or form affect GameLogic in any way; if it does, net desyncs will occur and we will all lose our jobs. This must remain pure-client-only, and ensure that nothing it does can be detected, even in read-only form, by GameLogic! DANGER WARNING READ ME DANGER WARNING READ ME DANGER WARNING READ ME */ void W3DModelDraw::handleClientTurretPositioning() { if (!m_curState || !(m_curState->m_validStuff & ModelConditionInfo::TURRETS_VALID)) return; for (int tslot = 0; tslot < MAX_TURRETS; ++tslot) { const ModelConditionInfo::TurretInfo& tur = m_curState->m_turrets[tslot]; Real turretAngle = 0; Real turretPitch = 0; if (tur.m_turretAngleBone || tur.m_turretPitchBone) { const Object *obj = getDrawable()->getObject(); if (obj) { const AIUpdateInterface* ai = obj->getAIUpdateInterface(); if (ai) ai->getTurretRotAndPitch((WhichTurretType)tslot, &turretAngle, &turretPitch); } // do turret, if any if (tur.m_turretAngleBone != 0) { if (m_curState) turretAngle += tur.m_turretArtAngle; Matrix3D turretXfrm(1); turretXfrm.Rotate_Z(turretAngle); if (m_renderObject) { m_renderObject->Capture_Bone( tur.m_turretAngleBone ); m_renderObject->Control_Bone( tur.m_turretAngleBone, turretXfrm ); } } // do turret pitch, if any if (tur.m_turretPitchBone != 0) { if (m_curState) turretPitch += tur.m_turretArtPitch; Matrix3D turretPitchXfrm(1); turretPitchXfrm.Rotate_Y(-turretPitch); if (m_renderObject) { m_renderObject->Capture_Bone( tur.m_turretPitchBone ); m_renderObject->Control_Bone( tur.m_turretPitchBone, turretPitchXfrm ); } } } } // next tslot } //void W3DModelDraw::handleClientFlagPositioning() //{ // const ModelConditionInfo::TurretInfo& tur = m_curState->m_turrets[tslot]; // Real turretAngle = TheGlobalData->m_downwindAngle; // if (tur.m_flagAngleBone ) // { // Matrix3D turretXfrm(1); // turretXfrm.Rotate_Z(turretAngle); // if (m_renderObject) // { // m_renderObject->Capture_Bone( tur.m_turretAngleBone ); // m_renderObject->Control_Bone( tur.m_turretAngleBone, turretXfrm ); // } // } //} //------------------------------------------------------------------------------------------------- /* Note that, strictly speaking this code is WRONG, since it assumes it will get called every frame, but in fact, will only get called every frame that it is visible. In practice, this isn't a big problem, but... @todo fix me someday (srj) */ void W3DModelDraw::handleClientRecoil() { const W3DModelDrawModuleData* d = getW3DModelDrawModuleData(); if (!(m_curState->m_validStuff & ModelConditionInfo::BARRELS_VALID)) { return; } // do recoil, if any for (int wslot = 0; wslot < WEAPONSLOT_COUNT; ++wslot) { if (!m_curState->m_hasRecoilBonesOrMuzzleFlashes[wslot]) continue; const ModelConditionInfo::WeaponBarrelInfoVec& barrels = m_curState->m_weaponBarrelInfoVec[wslot]; WeaponRecoilInfoVec& recoils = m_weaponRecoilInfoVec[wslot]; Int count = barrels.size(); Int recoilCount = recoils.size(); DEBUG_ASSERTCRASH(count == recoilCount, ("Barrel count != recoil count!")); count = (count>recoilCount)?recoilCount:count; for (Int i = 0; i < count; ++i) { if (barrels[i].m_muzzleFlashBone != 0) { Bool hidden = recoils[i].m_state != WeaponRecoilInfo::RECOIL_START; //DEBUG_LOG(("adjust muzzleflash %08lx for Draw %08lx state %s to %d at frame %d\n",subObjToHide,this,m_curState->m_description.str(),hidden?1:0,TheGameLogic->getFrame())); barrels[i].setMuzzleFlashHidden(m_renderObject, hidden); } const Real TINY_RECOIL = 0.01f; if (barrels[i].m_recoilBone != 0) { switch (recoils[i].m_state ) { case WeaponRecoilInfo::IDLE: // nothing break; case WeaponRecoilInfo::RECOIL_START: case WeaponRecoilInfo::RECOIL: recoils[i].m_shift += recoils[i].m_recoilRate; recoils[i].m_recoilRate *= d->m_recoilDamping; // recoil decelerates if (recoils[i].m_shift >= d->m_maxRecoil) { recoils[i].m_shift = d->m_maxRecoil; recoils[i].m_state = WeaponRecoilInfo::SETTLE; } else if (fabs(recoils[i].m_recoilRate) < TINY_RECOIL) { recoils[i].m_state = WeaponRecoilInfo::SETTLE; } break; case WeaponRecoilInfo::SETTLE: recoils[i].m_shift -= d->m_recoilSettle; if (recoils[i].m_shift <= 0.0f) { recoils[i].m_shift = 0.0f; recoils[i].m_state = WeaponRecoilInfo::IDLE; } break; } Matrix3D gunXfrm; gunXfrm.Make_Identity(); gunXfrm.Translate_X( -recoils[i].m_shift ); //DEBUG_ASSERTLOG(recoils[i].m_shift==0.0f,("adjust bone %d by %f\n",recoils[i].m_recoilBone,recoils[i].m_shift)); if (m_renderObject) { m_renderObject->Capture_Bone( barrels[i].m_recoilBone ); m_renderObject->Control_Bone( barrels[i].m_recoilBone, gunXfrm ); } } else { recoils[i].m_state = WeaponRecoilInfo::IDLE; //DEBUG_LOG(("reset Draw %08lx state %08lx\n",this,m_curState)); } } } } //------------------------------------------------------------------------------------------------- /* DANGER WARNING READ ME DANGER WARNING READ ME DANGER WARNING READ ME This function must not EVER do ANYTHING which can in any way, shape, or form affect GameLogic in any way; if it does, net desyncs will occur and we will all lose our jobs. This must remain pure-client-only, and ensure that nothing it does can be detected, even in read-only form, by GameLogic! DANGER WARNING READ ME DANGER WARNING READ ME DANGER WARNING READ ME */ void W3DModelDraw::recalcBonesForClientParticleSystems() { if (m_needRecalcBoneParticleSystems) { const Drawable* drawable = getDrawable(); if (drawable != NULL ) { if( m_curState != NULL && drawable->testDrawableStatus( DRAWABLE_STATUS_NO_STATE_PARTICLES ) == FALSE ) { for (std::vector::const_iterator it = m_curState->m_particleSysBones.begin(); it != m_curState->m_particleSysBones.end(); ++it) { ParticleSystem *sys = TheParticleSystemManager->createParticleSystem(it->particleSystemTemplate); if (sys != NULL) { Coord3D pos; pos.zero(); Real rotation = 0.0f; Int boneIndex = m_renderObject ? m_renderObject->Get_Bone_Index(it->boneName.str()) : 0; if (boneIndex != 0) { // ugh... kill the mtx so we get it in modelspace, not world space Matrix3D originalTransform = m_renderObject->Get_Transform(); // save the transform Matrix3D tmp(true); tmp.Scale(getDrawable()->getScale()); m_renderObject->Set_Transform(tmp); // set to identity transform const Matrix3D boneTransform = m_renderObject->Get_Bone_Transform(boneIndex); Vector3 vpos = boneTransform.Get_Translation(); rotation = boneTransform.Get_Z_Rotation(); m_renderObject->Set_Transform(originalTransform); // restore it pos.x = vpos.X; pos.y = vpos.Y; pos.z = vpos.Z; } // got the bone position... sys->setPosition(&pos); // and the direction, so that the system is oriented like the bone is sys->rotateLocalTransformZ(rotation); // Attatch it to the object... sys->attachToDrawable(drawable); // important: mark it as do-not-save, since we'll just re-create it when we reload. sys->setSaveable(FALSE); if (drawable->isDrawableEffectivelyHidden() || m_fullyObscuredByShroud) { sys->stop(); // don't start the systems for drawables that are hidden. } // store the particle system id so we can kill it later. ParticleSysTrackerType tracker; tracker.id = sys->getSystemID(); tracker.boneIndex = boneIndex; m_particleSystemIDs.push_back( tracker ); } } } } m_needRecalcBoneParticleSystems = false; } } //------------------------------------------------------------------------------------------------- /* DANGER WARNING READ ME DANGER WARNING READ ME DANGER WARNING READ ME This function must not EVER do ANYTHING which can in any way, shape, or form affect GameLogic in any way; if it does, net desyncs will occur and we will all lose our jobs. This must remain pure-client-only, and ensure that nothing it does can be detected, even in read-only form, by GameLogic! DANGER WARNING READ ME DANGER WARNING READ ME DANGER WARNING READ ME */ Bool W3DModelDraw::updateBonesForClientParticleSystems() { const Drawable* drawable = getDrawable(); if (drawable != NULL && m_curState != NULL && m_renderObject != NULL ) { for (std::vector::const_iterator it = m_particleSystemIDs.begin(); it != m_particleSystemIDs.end(); ++it) { ParticleSystem *sys = TheParticleSystemManager->findParticleSystem((*it).id); Int boneIndex = (*it).boneIndex; if ( (sys != NULL) && (boneIndex != 0) ) { Matrix3D originalTransform = m_renderObject->Get_Transform(); Matrix3D tmp(1); tmp.Scale(drawable->getScale()); m_renderObject->Set_Transform(tmp); const Matrix3D boneTransform = m_renderObject->Get_Bone_Transform(boneIndex);// just a little worried about state changes Vector3 vpos = boneTransform.Get_Translation(); Real rotation = boneTransform.Get_Z_Rotation(); m_renderObject->Set_Transform(originalTransform); Coord3D pos; pos.x = vpos.X; pos.y = vpos.Y; pos.z = vpos.Z; sys->setPosition(&pos); sys->rotateLocalTransformZ(rotation); } } }// end if Drawable return TRUE; } //------------------------------------------------------------------------------------------------- void W3DModelDraw::setTerrainDecal(TerrainDecalType type) { if (m_terrainDecal) m_terrainDecal->release(); m_terrainDecal = NULL; if (type == TERRAIN_DECAL_NONE || type >= TERRAIN_DECAL_MAX) //turning off decals on this object. (or bad value.) return; const ThingTemplate *tmplate=getDrawable()->getTemplate(); //create a new terrain decal Shadow::ShadowTypeInfo decalInfo; decalInfo.allowUpdates = FALSE; //shadow image will never update decalInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects decalInfo.m_type = SHADOW_ALPHA_DECAL; //decalInfo.m_type = SHADOW_ADDITIVE_DECAL;//temporary kluge to test graphics strcpy(decalInfo.m_ShadowName,TerrainDecalTextureName[type]); decalInfo.m_sizeX = tmplate->getShadowSizeX(); decalInfo.m_sizeY = tmplate->getShadowSizeY(); decalInfo.m_offsetX = tmplate->getShadowOffsetX(); decalInfo.m_offsetY = tmplate->getShadowOffsetY(); if (TheProjectedShadowManager) m_terrainDecal = TheProjectedShadowManager->addDecal(m_renderObject,&decalInfo); if (m_terrainDecal) { m_terrainDecal->enableShadowInvisible(m_fullyObscuredByShroud); m_terrainDecal->enableShadowRender(m_shadowEnabled); } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::setTerrainDecalSize(Real x, Real y) { if (m_terrainDecal) { m_terrainDecal->setSize(x,y); } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::setTerrainDecalOpacity(Real o) { if (m_terrainDecal) { m_terrainDecal->setOpacity((Int)(255.0f * o)); } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::nukeCurrentRender(Matrix3D* xform) { // this needs to be "dirtied" so that the new pausing of the animation will be triggered. m_pauseAnimation = false; // changing geometry, so we need to remove shadow if present if (m_shadow) m_shadow->release(); m_shadow = NULL; if(m_terrainDecal) m_terrainDecal->release(); m_terrainDecal = NULL; // remove existing render object from the scene if (m_renderObject) { // save the transform for the new model if (xform) *xform = m_renderObject->Get_Transform(); W3DDisplay::m_3DScene->Remove_Render_Object(m_renderObject); REF_PTR_RELEASE(m_renderObject); m_renderObject = NULL; } else { if (xform) { *xform = *getDrawable()->getTransformMatrix(); } } } //------------------------------------------------------------------------------------------------- #if defined(_DEBUG) || defined(_INTERNAL) //art wants to see buildings without flags as a test. void W3DModelDraw::hideGarrisonFlags(Bool hide) { if (!m_renderObject) return; Int objIndex; RenderObjClass* subObj; if ((subObj = m_renderObject->Get_Sub_Object_By_Name("POLE", &objIndex)) != NULL) { subObj->Set_Hidden(hide); const HTreeClass *htree = m_renderObject->Get_HTree(); if (htree) { //get the bone of this subobject so we can hide all other child objects that use this bone //as a parent. Int boneIdx = m_renderObject->Get_Sub_Object_Bone_Index(0, objIndex); doHideShowBoneSubObjs(hide, m_renderObject->Get_Num_Sub_Objects(), boneIdx, m_renderObject, htree); } subObj->Release_Ref(); } } #endif //------------------------------------------------------------------------------------------------- /** Hides all subobjects which are headlights. Used to disable lights on models during the day.*/ void W3DModelDraw::hideAllHeadlights(Bool hide) { if (m_renderObject) { for (Int subObj = 0; subObj < m_renderObject->Get_Num_Sub_Objects(); subObj++) { RenderObjClass* test = m_renderObject->Get_Sub_Object(subObj); if (strstr(test->Get_Name(),"HEADLIGHT")) { test->Set_Hidden(hide); } test->Release_Ref(); } } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::hideAllMuzzleFlashes(const ModelConditionInfo* state, RenderObjClass* renderObject) { if (!state || !renderObject) return; if (!(state->m_validStuff & ModelConditionInfo::BARRELS_VALID)) { return; } for (int wslot = 0; wslot < WEAPONSLOT_COUNT; ++wslot) { if (!state->m_hasRecoilBonesOrMuzzleFlashes[wslot]) continue; const ModelConditionInfo::WeaponBarrelInfoVec& barrels = state->m_weaponBarrelInfoVec[wslot]; for (ModelConditionInfo::WeaponBarrelInfoVec::const_iterator it = barrels.begin(); it != barrels.end(); ++it) { if (it->m_muzzleFlashBone != 0) { it->setMuzzleFlashHidden(renderObject, true); } } } } //------------------------------------------------------------------------------------------------- static Bool turretNamesDiffer(const ModelConditionInfo* a, const ModelConditionInfo* b) { if (!(a->m_validStuff & ModelConditionInfo::TURRETS_VALID) || !(b->m_validStuff & ModelConditionInfo::TURRETS_VALID)) { return true; } for (int i = 0; i < MAX_TURRETS; ++i) if (a->m_turrets[i].m_turretAngleNameKey != b->m_turrets[i].m_turretAngleNameKey || a->m_turrets[i].m_turretPitchNameKey != b->m_turrets[i].m_turretPitchNameKey) return true; return false; } //------------------------------------------------------------------------------------------------- /** Change the model for this drawable. If color is non-zero, it will also apply team color to the model */ //------------------------------------------------------------------------------------------------- void W3DModelDraw::setModelState(const ModelConditionInfo* newState) { DEBUG_ASSERTCRASH(newState, ("invalid state in W3DModelDraw::setModelState\n")); #ifdef DEBUG_OBJECT_ID_EXISTS if (getDrawable() && getDrawable()->getObject() && getDrawable()->getObject()->getID() == TheObjectIDToDebug) { DEBUG_LOG(("REQUEST switching to state %s for obj %s %d\n",newState->m_description.str(),getDrawable()->getObject()->getTemplate()->getName().str(),getDrawable()->getObject()->getID())); } #endif const ModelConditionInfo* nextState = NULL; if (m_curState != NULL && newState != NULL) { // if the requested state is the current state (and nothing is pending), // or if the requested state is pending, just punt. // // note that if the requested state matches the current state AND // we also have a pending state, then we nuke both of 'em. This sounds // weird, but is deliberate: nextState and newState will always be "real" states (or null), // leaving us these possibilities: // // -- curState is a transition state // --in this case, new state should take precedence (unless there's a wait-to-finish marking, // which would be handled by the else clause) // // -- curState is a real state, nextState is a real state with a wait-to-finish clause // -- if curState == newState, presume that we should restart curState, thus punting thepending nextState // -- if curState != newState, presume we should switch to it, thus punting curState and nextState // // -- curState is a real state, nextState is a real state without a wait-to-finish clause // -- should be impossible! if ((m_curState == newState && m_nextState == NULL) || (m_curState != NULL && m_nextState == newState)) { #ifdef DEBUG_OBJECT_ID_EXISTS if (getDrawable() && getDrawable()->getObject() && getDrawable()->getObject()->getID() == TheObjectIDToDebug) { DEBUG_LOG(("IGNORE duplicate state %s for obj %s %d\n",newState->m_description.str(),getDrawable()->getObject()->getTemplate()->getName().str(),getDrawable()->getObject()->getID())); } #endif // I don't think he'll be interested... // he's already got one, you see return; } // check for allow-to-finish implicit transitions else if (newState != m_curState && newState->m_allowToFinishKey != NAMEKEY_INVALID && newState->m_allowToFinishKey == m_curState->m_transitionKey && m_renderObject && !isAnimationComplete(m_renderObject)) { #ifdef DEBUG_OBJECT_ID_EXISTS if (getDrawable() && getDrawable()->getObject() && getDrawable()->getObject()->getID() == TheObjectIDToDebug) { DEBUG_LOG(("ALLOW_TO_FINISH state %s for obj %s %d\n",newState->m_description.str(),getDrawable()->getObject()->getTemplate()->getName().str(),getDrawable()->getObject()->getID())); } #endif m_nextState = newState; m_nextStateAnimLoopDuration = NO_NEXT_DURATION; return; } // check for a normal->normal state transition else if (newState != m_curState && m_curState->m_transitionKey != NAMEKEY_INVALID && newState->m_transitionKey != NAMEKEY_INVALID) { TransitionSig sig = buildTransitionSig(m_curState->m_transitionKey, newState->m_transitionKey); const ModelConditionInfo* transState = findTransitionForSig(sig); if (transState != NULL) { #ifdef DEBUG_OBJECT_ID_EXISTS if (getDrawable() && getDrawable()->getObject() && getDrawable()->getObject()->getID() == TheObjectIDToDebug) { DEBUG_LOG(("using TRANSITION state %s before requested state %s for obj %s %d\n",transState->m_description.str(),newState->m_description.str(),getDrawable()->getObject()->getTemplate()->getName().str(),getDrawable()->getObject()->getID())); } #endif nextState = newState; newState = transState; } } } // get this here, before we change anything... we'll need it to pass to adjustAnimation (srj) Real prevAnimFraction = getCurrentAnimFraction(); // // Set up any particle effects for the new model. Also stop the old ones, note that we // will never allow the placement of new ones if the status bit is set that tells us // we should not automatically create particle systems for the model condition states // if( getDrawable()->testDrawableStatus( DRAWABLE_STATUS_NO_STATE_PARTICLES ) == FALSE ) m_needRecalcBoneParticleSystems = true; // always stop particle systems now stopClientParticleSystems(); // and always hide muzzle flashes, in case modelstate and model have crossed at the same time hideAllMuzzleFlashes(newState, m_renderObject); // note that different states might use the same model; for these, don't go thru the // expense of creating a new render-object. (exception: if color is changing, or subobjs are changing, // or a few other things...) if (m_curState == NULL || newState->m_modelName != m_curState->m_modelName || turretNamesDiffer(newState, m_curState) // srj sez: I'm not sure why we want to do the "hard stuff" if we have projectile bones; I think // it is a holdover from days gone by when bones were handled quite differently, rather than being cached. // but doing this hard stuff is a lot more work, and I think it's unnecessary, so let's remove this. //|| (newState->m_validStuff & ModelConditionInfo::HAS_PROJECTILE_BONES) ) { Matrix3D transform; nukeCurrentRender(&transform); Drawable* draw = getDrawable(); // create a new render object and set into drawable if (newState->m_modelName.isEmpty()) { m_renderObject = NULL; } else { m_renderObject = W3DDisplay::m_assetManager->Create_Render_Obj(newState->m_modelName.str(), draw->getScale(), m_hexColor); DEBUG_ASSERTCRASH(m_renderObject, ("*** ASSET ERROR: Model %s not found!\n",newState->m_modelName.str())); } //BONEPOS_LOG(("validateStuff() from within W3DModelDraw::setModelState()\n")); //BONEPOS_DUMPREAL(draw->getScale()); newState->validateStuff(m_renderObject, draw->getScale(), getW3DModelDrawModuleData()->m_extraPublicBones); // ensure that any muzzle flashes from the *new* state, start out hidden... // hideAllMuzzleFlashes(newState, m_renderObject);//moved to above rebuildWeaponRecoilInfo(newState); doHideShowSubObjs(&newState->m_hideShowVec); #if defined(_DEBUG) || defined(_INTERNAL) //art wants to see buildings without flags as a test. if (TheGlobalData->m_hideGarrisonFlags && draw->isKindOf(KINDOF_STRUCTURE)) hideGarrisonFlags(TRUE); #endif const ThingTemplate *tmplate = draw->getTemplate(); // set up tracks, if not already set. if (m_renderObject && TheGlobalData->m_makeTrackMarks && !m_trackRenderObject && TheTerrainTracksRenderObjClassSystem != NULL && !getW3DModelDrawModuleData()->m_trackFile.isEmpty()) { m_trackRenderObject = TheTerrainTracksRenderObjClassSystem->bindTrack(m_renderObject, 1.0f*MAP_XY_FACTOR, getW3DModelDrawModuleData()->m_trackFile.str()); if (draw && m_trackRenderObject) m_trackRenderObject->setOwnerDrawable(draw); } // set up shadows if (m_renderObject && TheW3DShadowManager && tmplate->getShadowType() != SHADOW_NONE) { Shadow::ShadowTypeInfo shadowInfo; strcpy(shadowInfo.m_ShadowName, tmplate->getShadowTextureName().str()); DEBUG_ASSERTCRASH(shadowInfo.m_ShadowName[0] != '\0', ("this should be validated in ThingTemplate now")); shadowInfo.allowUpdates = FALSE; //shadow image will never update shadowInfo.allowWorldAlign = TRUE; //shadow image will wrap around world objects shadowInfo.m_type = (ShadowType)tmplate->getShadowType(); shadowInfo.m_sizeX = tmplate->getShadowSizeX(); shadowInfo.m_sizeY = tmplate->getShadowSizeY(); shadowInfo.m_offsetX = tmplate->getShadowOffsetX(); shadowInfo.m_offsetY = tmplate->getShadowOffsetY(); m_shadow = TheW3DShadowManager->addShadow(m_renderObject, &shadowInfo, draw); if (m_shadow) { m_shadow->enableShadowInvisible(m_fullyObscuredByShroud); m_shadow->enableShadowRender(m_shadowEnabled); } } if( m_renderObject ) { // set collision type for render object. Used by WW3D2 collision code. if (tmplate->isKindOf(KINDOF_SELECTABLE)) { m_renderObject->Set_Collision_Type( PICK_TYPE_SELECTABLE ); } if( tmplate->isKindOf( KINDOF_SHRUBBERY )) { m_renderObject->Set_Collision_Type( PICK_TYPE_SHRUBBERY ); } if( tmplate->isKindOf( KINDOF_MINE )) { m_renderObject->Set_Collision_Type( PICK_TYPE_MINES ); } if( tmplate->isKindOf( KINDOF_FORCEATTACKABLE )) { m_renderObject->Set_Collision_Type( PICK_TYPE_FORCEATTACKABLE ); } if( tmplate->isKindOf( KINDOF_CLICK_THROUGH )) { m_renderObject->Set_Collision_Type( 0 ); } Object *obj = draw->getObject(); if( obj ) { // for non bridge objects we adjust some collision types if( obj->isKindOf( KINDOF_BRIDGE ) == FALSE && obj->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE ) { if( obj->isKindOf( KINDOF_STRUCTURE ) && draw->getModelConditionFlags().test( MODELCONDITION_RUBBLE ) ) { //A dead building, -- don't allow the user to click on rubble! Treat it as a location instead. m_renderObject->Set_Collision_Type( 0 ); } else if( obj->isEffectivelyDead() ) { //A dead object, -- don't allow the user to click on rubble/hulks! Treat it as a location instead. m_renderObject->Set_Collision_Type( 0 ); } } } // add render object to our scene W3DDisplay::m_3DScene->Add_Render_Object(m_renderObject); // tie in our drawable as the user data pointer in the render object m_renderObject->Set_User_Data(draw->getDrawableInfo()); setTerrainDecal(draw->getTerrainDecalType()); //We created a new render object so we need to preserve the visibility state //of the previous render object. if (draw->isDrawableEffectivelyHidden()) { m_renderObject->Set_Hidden(TRUE); if (m_shadow) m_shadow->enableShadowRender(FALSE); m_shadowEnabled = FALSE; } // // set the transform for the new model to that we saved before, we do this so that the // model transition is smooth and will immediately appear at the same orientation and location // as the previous one // m_renderObject->Set_Transform(transform); onRenderObjRecreated(); } } else { //BONEPOS_LOG(("validateStuff() from within W3DModelDraw::setModelState()\n")); //BONEPOS_DUMPREAL(getDrawable()->getScale()); newState->validateStuff(m_renderObject, getDrawable()->getScale(), getW3DModelDrawModuleData()->m_extraPublicBones); rebuildWeaponRecoilInfo(newState); // ensure that any muzzle flashes from the *previous* state, are hidden... // hideAllMuzzleFlashes(m_curState, m_renderObject);// moved to above doHideShowSubObjs(&newState->m_hideShowVec); } hideAllHeadlights(m_hideHeadlights); const ModelConditionInfo* prevState = m_curState; // save, to pass to adjustAnimation (could be null) m_curState = newState; m_nextState = nextState; m_nextStateAnimLoopDuration = NO_NEXT_DURATION; adjustAnimation(prevState, prevAnimFraction); } //------------------------------------------------------------------------------------------------- void W3DModelDraw::replaceModelConditionState(const ModelConditionFlags& c) { m_hideHeadlights = c.test(MODELCONDITION_NIGHT) ? false : true; const ModelConditionInfo* info = findBestInfo(c); if (info) setModelState(info); hideAllHeadlights(m_hideHeadlights); } //------------------------------------------------------------------------------------------------- void W3DModelDraw::setSelectable(Bool selectable) { // set collision type for render object. Used by WW3D2 collision code. if( m_renderObject ) { int current = m_renderObject->Get_Collision_Type(); if (selectable) { current |= PICK_TYPE_SELECTABLE; } else { current &= ~PICK_TYPE_SELECTABLE; } m_renderObject->Set_Collision_Type(current); } // end if } //------------------------------------------------------------------------------------------------- void W3DModelDraw::replaceIndicatorColor(Color color) { if (!getW3DModelDrawModuleData()->m_okToChangeModelColor) return; if (getRenderObject()) { Int newColor = (color == 0) ? 0 : (color | 0xFF000000); if (newColor != m_hexColor) { m_hexColor = newColor; // set these to NULL to force a regen of everything in setModelState. const ModelConditionInfo* tmp = m_curState; m_curState = NULL; m_nextState = NULL; m_nextStateAnimLoopDuration = NO_NEXT_DURATION; setModelState(tmp); } } } //------------------------------------------------------------------------------------------------- // this method must ONLY be called from the client, NEVER From the logic, not even indirectly. Bool W3DModelDraw::clientOnly_getRenderObjInfo(Coord3D* pos, Real* boundingSphereRadius, Matrix3D* transform) const { if (!m_renderObject) return false; Vector3 objPos = m_renderObject->Get_Position(); //get position of object pos->x = objPos.X; pos->y = objPos.Y; pos->z = objPos.Z; *transform = m_renderObject->Get_Transform(); *boundingSphereRadius = m_renderObject->Get_Bounding_Sphere().Radius; return true; } //------------------------------------------------------------------------------------------------- Bool W3DModelDraw::getProjectileLaunchOffset( const ModelConditionFlags& condition, WeaponSlotType wslot, Int specificBarrelToUse, Matrix3D* launchPos, WhichTurretType tur, Coord3D* turretRotPos, Coord3D* turretPitchPos ) const { /* Note, this recalcs the state every time, rather than using m_curState, because m_curState could be a transition state, and we really need to get the pristine bone(s) for the state that logic believes to be current, not the one the client might currently be using... */ const ModelConditionInfo* stateToUse = findBestInfo(condition); if (!stateToUse) { CRCDEBUG_LOG(("can't find best info\n")); //BONEPOS_LOG(("can't find best info\n")); return false; } #if defined(_DEBUG) || defined(_INTERNAL) CRCDEBUG_LOG(("W3DModelDraw::getProjectileLaunchOffset() for %s\n", stateToUse->getDescription().str())); #endif #ifdef INTENSE_DEBUG AsciiString flags; for (Int i=0; igetScale()); //BONEPOS_LOG(("validateStuffs() from within W3DModelDraw::getProjectileLaunchOffset()\n")); //BONEPOS_DUMPREAL(getDrawable()->getScale()); stateToUse->validateStuff(NULL, getDrawable()->getScale(), d->m_extraPublicBones); DEBUG_ASSERTCRASH(stateToUse->m_transitionSig == NO_TRANSITION, ("It is never legal to getProjectileLaunchOffset from a Transition state (they vary on a per-client basis)... however, we can fix this (see srj)\n")); DEBUG_ASSERTCRASH(specificBarrelToUse >= 0, ("specificBarrelToUse should now always be explicit")); #ifdef CACHE_ATTACH_BONE #else Coord3D techOffset; techOffset.zero(); // must use pristine bone here since the result is used by logic Matrix3D pivot; if (d->m_attachToDrawableBone.isNotEmpty() && getDrawable()->getPristineBonePositions( d->m_attachToDrawableBone.str(), 0, NULL, &pivot, 1 ) == 1) { pivot.Pre_Rotate_Z(getDrawable()->getOrientation()); techOffset.x = pivot.Get_X_Translation(); techOffset.y = pivot.Get_Y_Translation(); techOffset.z = pivot.Get_Z_Translation(); } #endif CRCDEBUG_LOG(("wslot = %d\n", wslot)); const ModelConditionInfo::WeaponBarrelInfoVec& wbvec = stateToUse->m_weaponBarrelInfoVec[wslot]; if( wbvec.empty() ) { // Can't find the launch pos, but they might still want the other info they asked for CRCDEBUG_LOG(("empty wbvec\n")); //BONEPOS_LOG(("empty wbvec\n")); launchPos = NULL; } else { if (specificBarrelToUse < 0 || specificBarrelToUse >= wbvec.size()) specificBarrelToUse = 0; if (launchPos) { CRCDEBUG_LOG(("specificBarrelToUse = %d\n", specificBarrelToUse)); *launchPos = wbvec[specificBarrelToUse].m_projectileOffsetMtx; if (tur != TURRET_INVALID) { launchPos->Pre_Rotate_Z(stateToUse->m_turrets[tur].m_turretArtAngle); launchPos->Pre_Rotate_Y(-stateToUse->m_turrets[tur].m_turretArtPitch); } #ifdef CACHE_ATTACH_BONE #else launchPos->Adjust_Translation( Vector3( techOffset.x, techOffset.y, techOffset.z ) ); #endif DUMPMATRIX3D(launchPos); } } if (turretRotPos) turretRotPos->zero(); if (turretPitchPos) turretPitchPos->zero(); if (tur != TURRET_INVALID) { #ifdef CACHE_ATTACH_BONE const Vector3* offset = d->getAttachToDrawableBoneOffset(getDrawable()); #endif const ModelConditionInfo::TurretInfo& turInfo = stateToUse->m_turrets[tur]; if (turretRotPos) { if (turInfo.m_turretAngleNameKey != NAMEKEY_INVALID && !stateToUse->findPristineBonePos(turInfo.m_turretAngleNameKey, *turretRotPos)) { DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found!\n",KEYNAME(turInfo.m_turretAngleNameKey).str())); } #ifdef CACHE_ATTACH_BONE if (offset) { turretRotPos->x += offset->X; turretRotPos->y += offset->Y; turretRotPos->z += offset->Z; } #endif } if (turretPitchPos) { if (turInfo.m_turretPitchNameKey != NAMEKEY_INVALID && !stateToUse->findPristineBonePos(turInfo.m_turretPitchNameKey, *turretPitchPos)) { DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found!\n",KEYNAME(turInfo.m_turretPitchNameKey).str())); } #ifdef CACHE_ATTACH_BONE if (offset) { turretPitchPos->x += offset->X; turretPitchPos->y += offset->Y; turretPitchPos->z += offset->Z; } #endif } } return launchPos != NULL;// return if LaunchPos is valid or not } //------------------------------------------------------------------------------------------------- Int W3DModelDraw::getPristineBonePositionsForConditionState( const ModelConditionFlags& condition, const char* boneNamePrefix, Int startIndex, Coord3D* positions, Matrix3D* transforms, Int maxBones ) const { /* Note, this recalcs the state every time, rather than using m_curState, because m_curState could be a transition state, and we really need to get the pristine bone(s) for the state that logic believes to be current, not the one the client might currently be using... */ const ModelConditionInfo* stateToUse = findBestInfo(condition); if (!stateToUse) return 0; // if (isValidTimeToCalcLogicStuff()) // { // CRCDEBUG_LOG(("W3DModelDraw::getPristineBonePositionsForConditionState() - state = '%s'\n", // stateToUse->getDescription().str())); // //CRCDEBUG_LOG(("renderObject == NULL: %d\n", (stateToUse==m_curState)?(m_renderObject == NULL):1)); // } //BONEPOS_LOG(("validateStuff() from within W3DModelDraw::getPristineBonePositionsForConditionState()\n")); //BONEPOS_DUMPREAL(getDrawable()->getScale()); // we must call this every time we set m_nextState, to ensure cached bones are happy stateToUse->validateStuff( // if the state is the current state, pass in the current render object // so that we don't have to re-create it! stateToUse == m_curState ? m_renderObject : NULL, getDrawable()->getScale(), getW3DModelDrawModuleData()->m_extraPublicBones); const int MAX_BONE_GET = 64; Matrix3D tmpMtx[MAX_BONE_GET]; if (maxBones > MAX_BONE_GET) maxBones = MAX_BONE_GET; if (transforms == NULL) transforms = tmpMtx; Int posCount = 0; Int endIndex = (startIndex == 0) ? 0 : 99; char buffer[256]; for (Int i = startIndex; i <= endIndex; ++i) { if (i == 0) strcpy(buffer, boneNamePrefix); else sprintf(buffer, "%s%02d", boneNamePrefix, i); for (char *c = buffer; c && *c; ++c) { // convert to all-lowercase since that's how we filled in the map *c = tolower(*c); } const Matrix3D* mtx = stateToUse->findPristineBone(NAMEKEY(buffer), NULL); if (mtx) { transforms[posCount] = *mtx; // if (isValidTimeToCalcLogicStuff()) // { // DUMPMATRIX3D(mtx); // } } else { //DEBUG_CRASH(("*** ASSET ERROR: Bone %s not found!\n",buffer)); const Object *obj = getDrawable()->getObject(); if (obj) transforms[posCount] = *obj->getTransformMatrix(); else transforms[posCount].Make_Identity(); break; } ++posCount; if (posCount >= maxBones) break; } if (positions && transforms) { for (i = 0; i < posCount; ++i) { Vector3 pos = transforms[i].Get_Translation(); positions[i].x = pos.X; positions[i].y = pos.Y; positions[i].z = pos.Z; // if (isValidTimeToCalcLogicStuff()) // { // DUMPCOORD3D(&(positions[i])); // } } } // if (isValidTimeToCalcLogicStuff()) // { // CRCDEBUG_LOG(("end of W3DModelDraw::getPristineBonePositionsForConditionState()\n")); // } return posCount; } //------------------------------------------------------------------------------------------------- Bool W3DModelDraw::getCurrentWorldspaceClientBonePositions(const char* boneName, Matrix3D& transform) const { if (!m_renderObject) return false; Int boneIndex = m_renderObject->Get_Bone_Index(boneName); if (boneIndex == 0) return false; transform = m_renderObject->Get_Bone_Transform(boneIndex); return true; } //------------------------------------------------------------------------------------------------- Int W3DModelDraw::getCurrentBonePositions( const char* boneNamePrefix, Int startIndex, Coord3D* positions, Matrix3D* transforms, Int maxBones ) const { const int MAX_BONE_GET = 64; Matrix3D tmpMtx[MAX_BONE_GET]; if (maxBones > MAX_BONE_GET) maxBones = MAX_BONE_GET; if (transforms == NULL) transforms = tmpMtx; if( !m_renderObject ) return 0; // ugh... kill the mtx so we get it in modelspace, not world space Matrix3D originalTransform = m_renderObject->Get_Transform(); // save the transform // which is faster: slamming the xform (and invalidating the objects xforms) // or inverting it ourself? gotta profile to see. (srj) #define DO_INV #ifdef DO_INV Matrix3D inverse; originalTransform.Get_Orthogonal_Inverse(inverse); inverse.Scale(getDrawable()->getScale()); #else Matrix3D tmp(true); tmp.Scale(getDrawable()->getScale()); m_renderObject->Set_Transform(tmp); // set to identity transform #endif Int posCount = 0; Int endIndex = (startIndex == 0) ? 0 : 99; char buffer[256]; for (Int i = startIndex; i <= endIndex; ++i) { if (i == 0) strcpy(buffer, boneNamePrefix); else sprintf(buffer, "%s%02d", boneNamePrefix, i); Int boneIndex = m_renderObject->Get_Bone_Index(buffer); if (boneIndex == 0) break; transforms[posCount] = m_renderObject->Get_Bone_Transform(boneIndex); #ifdef DO_INV transforms[posCount].preMul(inverse); #endif //DEBUG_ASSERTCRASH(!m_renderObject->Is_Bone_Captured(boneIndex), ("bone is captured!")); ++posCount; if (posCount >= maxBones) break; } if (positions && transforms) { for (i = 0; i < posCount; ++i) { Vector3 pos = transforms[i].Get_Translation(); positions[i].x = pos.X; positions[i].y = pos.Y; positions[i].z = pos.Z; } } #ifdef DO_INV #else m_renderObject->Set_Transform(originalTransform); // restore it #endif return posCount; } //------------------------------------------------------------------------------------------------- void W3DModelDraw::reactToTransformChange( const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle ) { // set the position of our render object if( m_renderObject ) { Matrix3D mtx = *getDrawable()->getTransformMatrix(); adjustTransformMtx(mtx); m_renderObject->Set_Transform(mtx); } if (m_trackRenderObject) { Object *obj = getDrawable()->getObject(); const Coord3D* pos = getDrawable()->getPosition(); if (m_fullyObscuredByShroud) { m_trackRenderObject->addCapEdgeToTrack(pos->x, pos->y); } else { if (obj && obj->isSignificantlyAboveTerrain()) { m_trackRenderObject->setAirborne(); } m_trackRenderObject->addEdgeToTrack(pos->x, pos->y); } } } //------------------------------------------------------------------------------------------------- const ModelConditionInfo* W3DModelDraw::findBestInfo(const ModelConditionFlags& c) const { return getW3DModelDrawModuleData()->findBestInfo(c); } //------------------------------------------------------------------------------------------------- Int W3DModelDraw::getBarrelCount(WeaponSlotType wslot) const { return (m_curState && (m_curState->m_validStuff & ModelConditionInfo::BARRELS_VALID)) ? m_curState->m_weaponBarrelInfoVec[wslot].size() : 0; } //------------------------------------------------------------------------------------------------- Bool W3DModelDraw::handleWeaponFireFX(WeaponSlotType wslot, Int specificBarrelToUse, const FXList* fxl, Real weaponSpeed, const Coord3D* victimPos, Real damageRadius) { DEBUG_ASSERTCRASH(specificBarrelToUse >= 0, ("specificBarrelToUse should now always be explicit")); if (!m_curState || !(m_curState->m_validStuff & ModelConditionInfo::BARRELS_VALID)) return false; const ModelConditionInfo::WeaponBarrelInfoVec& wbvec = m_curState->m_weaponBarrelInfoVec[wslot]; if (wbvec.empty()) { // don't do this... some other module of our drawable may have handled it. // just return false and let the caller sort it out. // FXList::doFXPos(fxl, getDrawable()->getPosition(), getDrawable()->getTransformMatrix(), weaponSpeed, victimPos); return false; } Bool handled = false; if (specificBarrelToUse < 0 || specificBarrelToUse > wbvec.size()) specificBarrelToUse = 0; const ModelConditionInfo::WeaponBarrelInfo& info = wbvec[specificBarrelToUse]; if (fxl) { if (info.m_fxBone && m_renderObject) { const Object *logicObject = getDrawable()->getObject();// This is slow, so store it if( ! m_renderObject->Is_Hidden() || (logicObject == NULL) ) { // I can ask the drawable's bone position if I am not hidden (if I have no object I have no choice) Matrix3D mtx = m_renderObject->Get_Bone_Transform(info.m_fxBone); Coord3D pos; pos.x = mtx.Get_X_Translation(); pos.y = mtx.Get_Y_Translation(); pos.z = mtx.Get_Z_Translation(); FXList::doFXPos(fxl, &pos, &mtx, weaponSpeed, victimPos, damageRadius); } else { // Else, I should just use my logic position for the effect placement. // Things in transports regularly fire from inside (hidden), so this is not weird. const Matrix3D *mtx; Coord3D pos; mtx = logicObject->getTransformMatrix(); pos = *(logicObject->getPosition()); /** @todo Once Firepoint bones are actually implemented, this matrix will become correct. // Unless of course they decide to not have the tracers come out of the windows, but rather go towards the target. // In that case, tracers will have to be rewritten to be "point towards secondary" instead of // "point straight ahead" which assumes we are facing the target. */ FXList::doFXPos(fxl, &pos, mtx, weaponSpeed, victimPos, damageRadius); } handled = true; } else { DEBUG_LOG(("*** no FXBone found for a non-null FXL\n")); } } if (info.m_recoilBone || info.m_muzzleFlashBone) { //DEBUG_LOG(("START muzzleflash %08lx for Draw %08lx state %s at frame %d\n",info.m_muzzleFlashBone,this,m_curState->m_description.str(),TheGameLogic->getFrame())); WeaponRecoilInfo& recoil = m_weaponRecoilInfoVec[wslot][specificBarrelToUse]; recoil.m_state = WeaponRecoilInfo::RECOIL_START; recoil.m_recoilRate = getW3DModelDrawModuleData()->m_initialRecoil; if (info.m_muzzleFlashBone != 0) info.setMuzzleFlashHidden(m_renderObject, false); } return handled; } //------------------------------------------------------------------------------------------------- void W3DModelDraw::setAnimationLoopDuration(UnsignedInt numFrames) { // this is never defined -- srj #ifdef NO_DURATIONS_ON_TRANSITIONS if (m_curState != NULL && m_curState->m_transition != NO_TRANSITION && m_nextState != NULL && m_nextState->m_transition == NO_TRANSITION) { DEBUG_LOG(("deferring pending duration of %d frames\n",numFrames)); m_nextStateAnimLoopDuration = numFrames; return; } m_nextStateAnimLoopDuration = NO_NEXT_DURATION; DEBUG_ASSERTCRASH(m_curState != NULL && m_curState->m_transition == NO_TRANSITION, ("Hmm, setAnimationLoopDuration on a transition state is probably not right... see srj")); #else m_nextStateAnimLoopDuration = NO_NEXT_DURATION; #endif Real desiredDurationInMsec = ceilf(numFrames * MSEC_PER_LOGICFRAME_REAL); setCurAnimDurationInMsec(desiredDurationInMsec); } //------------------------------------------------------------------------------------------------- /** similar to the above, but assumes that the current state is a "ONCE", and is smart about transition states... if there is a transition state "inbetween", it is included in the completion time. */ void W3DModelDraw::setAnimationCompletionTime(UnsignedInt numFrames) { if (m_curState != NULL && m_curState->m_transitionSig != NO_TRANSITION && m_curState->m_animations.size() > 0 && m_nextState != NULL && m_nextState->m_transitionSig == NO_TRANSITION && m_nextState->m_animations.size() > 0) { // we have a transition; split up the time suitably. // note that this is just a guess, and assumes that the states // have only one anim each (or, if multiple, that they are // all pretty close in natural duration) const Real t1 = m_curState->m_animations.front().getNaturalDurationInMsec(); const Real t2 = m_nextState->m_animations.front().getNaturalDurationInMsec(); UnsignedInt transTime = REAL_TO_INT_FLOOR((numFrames * t1) / (t1 + t2)); setAnimationLoopDuration(transTime); m_nextStateAnimLoopDuration = numFrames - transTime; } else { setAnimationLoopDuration(numFrames); } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::setPauseAnimation(Bool pauseAnim) { if (m_pauseAnimation == pauseAnim) { return; } m_pauseAnimation = pauseAnim; if (m_renderObject && m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD) { float framenum, dummy; int mode, numFrames; HLodClass* hlod = (HLodClass*)m_renderObject; HAnimClass* anim = hlod->Peek_Animation_And_Info(framenum, numFrames, mode, dummy); if (anim) { if (m_pauseAnimation) { m_animationMode = mode; hlod->Set_Animation(anim, framenum, RenderObjClass::ANIM_MODE_MANUAL); } else { hlod->Set_Animation(anim, framenum, m_animationMode); } } } } //------------------------------------------------------------------------------------------------- #ifdef ALLOW_ANIM_INQUIRIES // srj sez: not sure if this is a good idea, for net sync reasons... Real W3DModelDraw::getAnimationScrubScalar( void ) const { return getCurAnimDistanceCovered(); } #endif //------------------------------------------------------------------------------------------------- void W3DModelDraw::rebuildWeaponRecoilInfo(const ModelConditionInfo* state) { Int wslot; if (state == NULL) { for (wslot = 0; wslot < WEAPONSLOT_COUNT; ++wslot) { m_weaponRecoilInfoVec[wslot].clear(); } } else { for (wslot = 0; wslot < WEAPONSLOT_COUNT; ++wslot) { Int ncount = state->m_weaponBarrelInfoVec[wslot].size(); if (m_weaponRecoilInfoVec[wslot].size() != ncount) { WeaponRecoilInfo tmp; m_weaponRecoilInfoVec[wslot].resize(ncount, tmp); } for (WeaponRecoilInfoVec::iterator it = m_weaponRecoilInfoVec[wslot].begin(); it != m_weaponRecoilInfoVec[wslot].end(); ++it) { it->clear(); } } } } //------------------------------------------------------------------------------------------------- /** Preload any assets for the time of day requested */ //------------------------------------------------------------------------------------------------- void W3DModelDraw::preloadAssets( TimeOfDay timeOfDay ) { const W3DModelDrawModuleData *modData = getW3DModelDrawModuleData(); if( modData ) modData->preloadAssets( timeOfDay, getDrawable()->getScale() ); } //------------------------------------------------------------------------------------------------- Bool W3DModelDraw::isVisible() const { return (m_renderObject && m_renderObject->Is_Really_Visible()); } //------------------------------------------------------------------------------------------------- void W3DModelDraw::updateProjectileClipStatus( UnsignedInt shotsRemaining, UnsignedInt maxShots, WeaponSlotType slot ) { if ((getW3DModelDrawModuleData()->m_projectileBoneFeedbackEnabledSlots & (1 << slot)) == 0) return; // Only if specified. doHideShowProjectileObjects( shotsRemaining, maxShots, slot );// Means effectively, show m of n. } //------------------------------------------------------------------------------------------------- void W3DModelDraw::updateDrawModuleSupplyStatus( Int , Int currentSupply ) { // This level only cares if you have anything or not if( currentSupply > 0 ) { getDrawable()->setModelConditionState( MODELCONDITION_CARRYING ); } else { getDrawable()->clearModelConditionState( MODELCONDITION_CARRYING ); } } //------------------------------------------------------------------------------------------------- void W3DModelDraw::doHideShowProjectileObjects( UnsignedInt showCount, UnsignedInt maxCount, WeaponSlotType slot ) { // Loop through the projectile bones, and start hiding off the front. That is, 8,9 means to hide the first only. if (maxCount < showCount) { DEBUG_CRASH(("Someone is trying to show more projectiles than they have.") ); return; } Int hideCount = maxCount - showCount; std::vector showHideVector; ModelConditionInfo::HideShowSubObjInfo oneEntry; if (m_curState->m_weaponProjectileHideShowName[slot].isEmpty()) { for( Int projectileIndex = 0; projectileIndex < maxCount; projectileIndex++ ) { oneEntry.subObjName.format("%s%02d", m_curState->m_weaponProjectileLaunchBoneName[slot].str(), (projectileIndex + 1)); oneEntry.hide = (projectileIndex < hideCount); showHideVector.push_back( oneEntry ); } } else { oneEntry.subObjName = m_curState->m_weaponProjectileHideShowName[slot]; oneEntry.hide = (0 < hideCount); showHideVector.push_back( oneEntry ); } // Rather than duplicate recursive utility stuff, I will build a vector like those used by the main ShowHide setting doHideShowSubObjs(&showHideVector); } //------------------------------------------------------------------------------------------------- void W3DModelDraw::updateSubObjects() { if (!m_renderObject) return; if (!m_subObjectVec.empty()) { for (std::vector::const_iterator it = m_subObjectVec.begin(); it != m_subObjectVec.end(); ++it) { Int objIndex; RenderObjClass* subObj; if ((subObj = m_renderObject->Get_Sub_Object_By_Name(it->subObjName.str(), &objIndex)) != NULL) { subObj->Set_Hidden(it->hide); const HTreeClass *htree = m_renderObject->Get_HTree(); if (htree) { //get the bone of this subobject so we can hide all other child objects that use this bone //as a parent. Int boneIdx = m_renderObject->Get_Sub_Object_Bone_Index(0, objIndex); doHideShowBoneSubObjs( it->hide, m_renderObject->Get_Num_Sub_Objects(), boneIdx, m_renderObject, htree); } subObj->Release_Ref(); } else { DEBUG_CRASH(("*** ASSET ERROR: SubObject %s not found (%s)!\n",it->subObjName.str(),getDrawable()->getTemplate()->getName().str())); } } } } // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ void W3DModelDraw::crc( Xfer *xfer ) { // extend base class DrawModule::crc( xfer ); } // end crc // ------------------------------------------------------------------------------------------------ /** Xfer method * Version Info: * 1: Initial version * 2: Added animation frame (CBD) */ // ------------------------------------------------------------------------------------------------ void W3DModelDraw::xfer( Xfer *xfer ) { // version const XferVersion currentVersion = 2; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); // extend base class DrawModule::xfer( xfer ); // weapon recoil info vectors UnsignedByte recoilInfoCount; WeaponRecoilInfo weaponRecoilInfo; for( Int i = 0; i < WEAPONSLOT_COUNT; ++i ) { // count of data here recoilInfoCount = m_weaponRecoilInfoVec[ i ].size(); xfer->xferUnsignedByte( &recoilInfoCount ); if( xfer->getXferMode() == XFER_SAVE ) { WeaponRecoilInfoVec::const_iterator it; for( it = m_weaponRecoilInfoVec[ i ].begin(); it != m_weaponRecoilInfoVec[ i ].end(); ++it ) { // state weaponRecoilInfo.m_state = (*it).m_state; xfer->xferUser( &weaponRecoilInfo.m_state, sizeof( WeaponRecoilInfo::RecoilState ) ); // shift weaponRecoilInfo.m_shift = (*it).m_shift; xfer->xferReal( &weaponRecoilInfo.m_shift ); // recoil rate weaponRecoilInfo.m_recoilRate = (*it).m_recoilRate; xfer->xferReal( &weaponRecoilInfo.m_recoilRate ); } // end for, it } // end if, save else { // clear this list before loading m_weaponRecoilInfoVec[ i ].clear(); // read each data item for( Int j = 0; j < recoilInfoCount; ++j ) { // read state xfer->xferUser( &weaponRecoilInfo.m_state, sizeof( WeaponRecoilInfo::RecoilState ) ); // read shift xfer->xferReal( &weaponRecoilInfo.m_shift ); // read recoil rate xfer->xferReal( &weaponRecoilInfo.m_recoilRate ); // stuff it in the vector m_weaponRecoilInfoVec[ i ].push_back( weaponRecoilInfo ); } // end for, j } // end else, load } // end for, i // sub object vector UnsignedByte subObjectCount = m_subObjectVec.size(); xfer->xferUnsignedByte( &subObjectCount ); ModelConditionInfo::HideShowSubObjInfo hideShowSubObjInfo; if( xfer->getXferMode() == XFER_SAVE ) { std::vector::const_iterator it; for( it = m_subObjectVec.begin(); it != m_subObjectVec.end(); ++it ) { // sub object name hideShowSubObjInfo.subObjName = (*it).subObjName; xfer->xferAsciiString( &hideShowSubObjInfo.subObjName ); // hide hideShowSubObjInfo.hide = (*it).hide; xfer->xferBool( &hideShowSubObjInfo.hide ); } // end for, it } // end if, save else { // the vector must be emtpy m_subObjectVec.clear(); // read each data item for( UnsignedByte i = 0; i < subObjectCount; ++i ) { // name xfer->xferAsciiString( &hideShowSubObjInfo.subObjName ); // hide xfer->xferBool( &hideShowSubObjInfo.hide ); // stuff in vector m_subObjectVec.push_back( hideShowSubObjInfo ); } // end for, i } // end else, load // animation if( version >= 2 ) { if( xfer->getXferMode() == XFER_SAVE ) { // srj sez: don't save info for transition states, since we can't really // restore them effectively. if ( m_renderObject && m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD && m_curState && m_curState->m_transitionSig == NO_TRANSITION ) { // cast to HLod HLodClass *hlod = (HLodClass*)m_renderObject; // get animation info Int mode, numFrames; Real frame, dummy; HAnimClass *anim = hlod->Peek_Animation_And_Info( frame, numFrames, mode, dummy ); // animation data is present Bool present = anim ? TRUE : FALSE; xfer->xferBool( &present ); // if animation data is present if( anim ) { // xfer mode xfer->xferInt( &mode ); // // xfer frame as a fraction (this will allow the animations to change // in future patches but will still be mostly correct) // Real percent = frame / INT_TO_REAL( anim->Get_Num_Frames()-1 ); xfer->xferReal( &percent ); } // end if, anim } // end if else { // animation data is *NOT* present Bool present = FALSE; xfer->xferBool( &present ); } // end else } // end if, save else { // read animation present flag Bool present; xfer->xferBool( &present ); if( present ) { { // read mode Int mode; xfer->xferInt( &mode ); // note, this will be ignored } // read percent Real percent; xfer->xferReal( &percent ); // // cast render object to HLod, if this is no longer possible we have read the // data already and can just ignore it // if( m_renderObject && m_renderObject->Class_ID() == RenderObjClass::CLASSID_HLOD ) { HLodClass *hlod = (HLodClass *)m_renderObject; // get anim HAnimClass *anim = hlod->Peek_Animation(); // set animation data if( anim ) { // figure out frame number given percent written in file and total frames on anim Real frame = percent * INT_TO_REAL( anim->Get_Num_Frames()-1 ); float dummy1, dummy2; int curMode, dummy3; hlod->Peek_Animation_And_Info(dummy1, dummy3, curMode, dummy2); // srj sez: do not change the animation mode. it's too risky, since if you change (say) a nonlooping // to a looping, something might break since it could rely on that anim terminating. // set animation data hlod->Set_Animation( anim, frame, curMode ); } // end if, anim } // end if } // end if } // end else, load } // end if, version with animation info // when loading, update the sub objects if we have any if( xfer->getXferMode() == XFER_LOAD && m_subObjectVec.empty() == FALSE ) updateSubObjects(); } // end xfer // ------------------------------------------------------------------------------------------------ /** Load post process */ // ------------------------------------------------------------------------------------------------ void W3DModelDraw::loadPostProcess( void ) { // extend base class DrawModule::loadPostProcess(); } // end loadPostProcess // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ void W3DModelDrawModuleData::crc( Xfer *x ) { xfer(x); } // ------------------------------------------------------------------------------------------------ void W3DModelDrawModuleData::xfer( Xfer *x ) { // version const XferVersion currentVersion = 1; XferVersion version = currentVersion; x->xferVersion( &version, currentVersion ); for (ModelConditionVector::iterator it = m_conditionStates.begin(); it != m_conditionStates.end(); ++it) { ModelConditionInfo *info = &(*it); x->xferByte(&(info->m_validStuff)); #if defined(_DEBUG) || defined(_INTERNAL) x->xferAsciiString(&(info->m_description)); #endif if (info->m_validStuff) { for (PristineBoneInfoMap::iterator bit = info->m_pristineBones.begin(); bit != info->m_pristineBones.end(); ++bit) { PristineBoneInfo *bone = &(bit->second); x->xferInt(&(bone->boneIndex)); x->xferUser(&(bone->mtx), sizeof(Matrix3D)); } for (Int i=0; ixferInt(&(info->m_turrets[i].m_turretAngleBone)); x->xferInt(&(info->m_turrets[i].m_turretPitchBone)); } for (i=0; im_weaponBarrelInfoVec[i].begin(); wit != info->m_weaponBarrelInfoVec[i].end(); ++wit) { x->xferUser(&(wit->m_projectileOffsetMtx), sizeof(Matrix3D)); } } } } } // ------------------------------------------------------------------------------------------------ void W3DModelDrawModuleData::loadPostProcess( void ) { }