void CAnimating::GetBoneTransform( int iBone, matrix3x4_t &pBoneToWorld ) { CStudioHdr *pStudioHdr = GetModelPtr( ); if (!pStudioHdr) { Assert(!"CBaseAnimating::GetBoneTransform: model missing"); return; } if (iBone < 0 || iBone >= pStudioHdr->numbones()) { Assert(!"CBaseAnimating::GetBoneTransform: invalid bone index"); return; } CBoneCache *pcache = GetBoneCache( ); matrix3x4_t *pmatrix = pcache->GetCachedBone( iBone ); if ( !pmatrix ) { MatrixCopy( EntityToWorldTransform(), pBoneToWorld ); return; } Assert( pmatrix ); // FIXME MatrixCopy( *pmatrix, pBoneToWorld ); }
void CRagdollProp::SetupBones( matrix3x4_t *pBoneToWorld, int boneMask ) { // no ragdoll, fall through to base class if ( !m_ragdoll.listCount ) { BaseClass::SetupBones( pBoneToWorld, boneMask ); return; } // Not really ideal, but it'll work for now UpdateModelWidthScale(); MDLCACHE_CRITICAL_SECTION(); CStudioHdr *pStudioHdr = GetModelPtr( ); bool sim[MAXSTUDIOBONES]; memset( sim, 0, pStudioHdr->numbones() ); int i; CBoneAccessor boneaccessor( pBoneToWorld ); for ( i = 0; i < m_ragdoll.listCount; i++ ) { // during restore this may be NULL if ( !m_ragdoll.list[i].pObject ) continue; if ( RagdollGetBoneMatrix( m_ragdoll, boneaccessor, i ) ) { sim[m_ragdoll.boneIndex[i]] = true; } } mstudiobone_t *pbones = pStudioHdr->pBone( 0 ); for ( i = 0; i < pStudioHdr->numbones(); i++ ) { if ( sim[i] ) continue; if ( !(pbones[i].flags & boneMask) ) continue; matrix3x4_t matBoneLocal; AngleMatrix( pbones[i].rot, pbones[i].pos, matBoneLocal ); ConcatTransforms( pBoneToWorld[pbones[i].parent], matBoneLocal, pBoneToWorld[i]); } }
void StudioModel::scaleBones (float scale) { CStudioHdr *pStudioHdr = GetStudioHdr(); if (!pStudioHdr) return; mstudiobone_t *pbones = pStudioHdr->pBone( 0 ); for (int i = 0; i < pStudioHdr->numbones(); i++) { pbones[i].pos *= scale; pbones[i].posscale *= scale; } }
//========================================================= //========================================================= void CAnimating::GetBonePosition ( int iBone, Vector &origin, QAngle &angles ) { CStudioHdr *pStudioHdr = GetModelPtr( ); if (!pStudioHdr) { Assert(!"CBaseAnimating::GetBonePosition: model missing"); return; } if (iBone < 0 || iBone >= pStudioHdr->numbones()) { Assert(!"CBaseAnimating::GetBonePosition: invalid bone index"); return; } matrix3x4_t bonetoworld; GetBoneTransform( iBone, bonetoworld ); MatrixAngles( bonetoworld, angles, origin ); }
void ControlPanel::setModelInfo() { static char str[2048]; CStudioHdr *hdr = models->GetActiveStudioModel()->GetStudioHdr(); if (!hdr) return; int hbcount = 0; for ( int s = 0; s < hdr->numhitboxsets(); s++ ) { hbcount += hdr->iHitboxCount( s ); } sprintf (str, "Bones: %d\n" "Bone Controllers: %d\n" "Hit Boxes: %d in %d sets\n" "Sequences: %d\n", hdr->numbones(), hdr->numbonecontrollers(), hbcount, hdr->numhitboxsets(), hdr->GetNumSeq() ); lModelInfo1->setLabel (str); sprintf (str, "Textures: %d\n" "Skin Families: %d\n" "Bodyparts: %d\n" "Attachments: %d\n", hdr->numtextures(), hdr->numskinfamilies(), hdr->numbodyparts(), hdr->GetNumAttachments()); lModelInfo2->setLabel (str); }
bool StudioModel::LoadModel( const char *pModelName ) { MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); if (!pModelName) return 0; // In the case of restore, m_pModelName == modelname if (m_pModelName != pModelName) { // Copy over the model name; we'll need it later... if (m_pModelName) { delete[] m_pModelName; } m_pModelName = new char[Q_strlen(pModelName) + 1]; strcpy( m_pModelName, pModelName ); } m_MDLHandle = g_pMDLCache->FindMDL( pModelName ); // allocate a pool for a studiohdr cache if (m_pStudioHdr != NULL) { delete m_pStudioHdr; } m_pStudioHdr = new CStudioHdr( g_pMDLCache->GetStudioHdr( m_MDLHandle ), g_pMDLCache ); // manadatory to access correct verts SetCurrentModel(); m_pPhysics = LoadPhysics( m_MDLHandle ); // Copy over all of the hitboxes; we may add and remove elements m_HitboxSets.RemoveAll(); CStudioHdr *pStudioHdr = GetStudioHdr(); int i; for ( int s = 0; s < pStudioHdr->numhitboxsets(); s++ ) { mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( s ); if ( !set ) continue; m_HitboxSets.AddToTail(); for ( i = 0; i < set->numhitboxes; ++i ) { mstudiobbox_t *pHit = set->pHitbox(i); int nIndex = m_HitboxSets[ s ].AddToTail( ); m_HitboxSets[s][nIndex] = *pHit; } // Set the name hbsetname_s *n = &m_HitboxSetNames[ m_HitboxSetNames.AddToTail() ]; strcpy( n->name, set->pszName() ); } // Copy over all of the surface props; we may change them... for ( i = 0; i < pStudioHdr->numbones(); ++i ) { mstudiobone_t* pBone = pStudioHdr->pBone(i); CUtlSymbol prop( pBone->pszSurfaceProp() ); m_SurfaceProps.AddToTail( prop ); } m_physPreviewBone = -1; bool forceOpaque = (pStudioHdr->flags() & STUDIOHDR_FLAGS_FORCE_OPAQUE) != 0; bool vertexLit = false; m_bIsTransparent = false; m_bHasProxy = false; studiohwdata_t *pHardwareData = g_pMDLCache->GetHardwareData( m_MDLHandle ); if ( !pHardwareData ) { Assert( 0 ); return false; } for( int lodID = pHardwareData->m_RootLOD; lodID < pHardwareData->m_NumLODs; lodID++ ) { studioloddata_t *pLODData = &pHardwareData->m_pLODs[lodID]; for ( i = 0; i < pLODData->numMaterials; ++i ) { if (pLODData->ppMaterials[i]->IsVertexLit()) { vertexLit = true; } if ((!forceOpaque) && pLODData->ppMaterials[i]->IsTranslucent()) { m_bIsTransparent = true; //Msg("Translucent material %s for model %s\n", pLODData->ppMaterials[i]->GetName(), pStudioHdr->name ); } if (pLODData->ppMaterials[i]->HasProxy()) { m_bHasProxy = true; } } } return true; }
void C_ClientPartialRagdoll::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName ) { BaseClass::ImpactTrace( pTrace, iDamageType, pCustomImpactName ); // client entities have the network index -1 so lots of effects don't work easily if ( BloodColor() != DONT_BLEED ) { const char *pszDecalName = NULL; switch ( BloodColor() ) { case BLOOD_COLOR_RED: pszDecalName = "Blood"; break; } int index = decalsystem->GetDecalIndexForName( pszDecalName ); Vector vecDir = pTrace->endpos - pTrace->startpos; if ( vecDir.LengthSqr() > FLT_EPSILON ) { vecDir.NormalizeInPlace(); } if ( index >= 0 ) { SetShrinkingEnabled( false ); InvalidateBoneCache(); SetupBones( NULL, -1, BONE_USED_BY_ANYTHING, gpGlobals->curtime ); // add decal to model AddDecal( pTrace->startpos, pTrace->endpos, pTrace->endpos, pTrace->hitbox, index, false, *pTrace ); SetShrinkingEnabled( true ); InvalidateBoneCache(); } // add decal to world trace_t tr; UTIL_TraceLine( pTrace->endpos, pTrace->endpos + vecDir * 35.0f, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); UTIL_BloodDecalTrace( &tr, BloodColor() ); if ( ShouldCreateBloodParticles() ) { UTIL_BloodImpact( pTrace->endpos, -vecDir, BloodColor(), 5 ); } } if ( m_bReleaseRagdoll || m_bFadingOut ) return; const float flGibbingChance = ( ( iDamageType & DMG_BLAST ) != 0 ) ? gstring_gibbing_explosion_chance.GetFloat() : gstring_gibbing_chance.GetFloat(); const bool bSniperImpact = ( iDamageType & DMG_SNIPER ) != 0; if ( !bSniperImpact && RandomFloat() > flGibbingChance / 100.0f ) return; CStudioHdr *pHdr = GetModelPtr(); if ( pHdr == NULL ) return; int iStudioBone = ConvertPhysBoneToStudioBone( this, pTrace->physicsbone ); const bool bExplosionImpact = ( iDamageType & DMG_BLAST ) != 0; // if the hit bone is a valid studio bone and we know that this bone // is drawn as a normal bone (not shrunken) if ( iStudioBone >= 0 && m_normalBones.IsBitSet( iStudioBone ) || bExplosionImpact ) { CUtlVector< ragdollparams_partial_t > gibModels; // we've been analysed already so use the known hit group // and try recusive splitting GibbingParamsRecursive_t params; params.pHdr = pHdr; params.pszHitBone = ( iStudioBone >= 0 && !bExplosionImpact ) ? pHdr->pBone( iStudioBone )->pszName() : NULL; params.pszParentName = STRING( m_strRecursiveParent ); params.pszRootBone = ( m_iBranchRootBone >= 0 && m_iBranchRootBone < pHdr->numbones() ) ? pHdr->pBone( m_iBranchRootBone )->pszName() : NULL; params.pJointBones = &m_jointBones; const char *pszParentSplitBone; // find a suitable joint to cut if ( C_GibConfig::GetInstance()->GetGibsForGroup( params, gibModels, &pszParentSplitBone ) || bExplosionImpact && C_GibConfig::GetInstance()->GetRandomGibsForGroup( params, gibModels, &pszParentSplitBone ) ) { int iSplitboneIndex = Studio_BoneIndexByName( pHdr, pszParentSplitBone ); // don't do cutting if we cut this joint in the past // or if this joint is our current branch root if ( iSplitboneIndex < 0 || m_jointBones.IsBitSet( iSplitboneIndex ) || m_iBranchRootBone == iSplitboneIndex ) return; matrix3x4_t boneDelta0[MAXSTUDIOBONES]; matrix3x4_t boneDelta1[MAXSTUDIOBONES]; matrix3x4_t currentBones[MAXSTUDIOBONES]; const float boneDt = 0.1f; // setup bones without shrinking to position the new gibs SetShrinkingEnabled( false ); GetRagdollInitBoneArrays( boneDelta0, boneDelta1, currentBones, boneDt ); SetShrinkingEnabled( true ); InvalidateBoneCache(); // create the new gibmodels FOR_EACH_VEC( gibModels, i ) { ragdollparams_partial_t &partial = gibModels[ i ]; // add old trunk joints for ( int iBit = m_jointBones.FindNextSetBit( 0 ); iBit >= 0; iBit = m_jointBones.FindNextSetBit( iBit + 1 ) ) { if ( m_iBranchRootBone == iBit ) continue; const char *pszName = pHdr->pBone( iBit )->pszName(); partial.trunkBones.AddToTail( pszName ); } // if we create a trunk from an existing branch // we have to propagate the branch root bone if ( partial.rootBone.IsEmpty() && m_iBranchRootBone >= 0 ) { partial.rootBone = pHdr->pBone( m_iBranchRootBone )->pszName(); } C_BaseAnimating *pGib = CreateRagdollCopy( false ); C_ClientPartialRagdoll *pRecursiveRagdoll = dynamic_cast< C_ClientPartialRagdoll* >( pGib ); Assert( pRecursiveRagdoll ); // apply force and propagate cut information if ( pRecursiveRagdoll != NULL ) { pRecursiveRagdoll->SetRecursiveGibData( STRING( m_strRecursiveParent ), STRING( m_strRecursiveGoreName ), STRING( m_strRecursiveGoreMaterialName ) ); pRecursiveRagdoll->SetShrinkingEnabled( true ); Vector vecDelta = pTrace->endpos - pTrace->startpos; if ( vecDelta.LengthSqr() <= FLT_EPSILON ) { vecDelta = RandomVector( -1, 1 ); } vecDelta.NormalizeInPlace(); pRecursiveRagdoll->m_vecForce = vecDelta * RandomFloat( 15000.0f, 35000.0f ); pRecursiveRagdoll->m_nForceBone = pTrace->physicsbone; pRecursiveRagdoll->SetBloodColor( BloodColor() ); pRecursiveRagdoll->m_jointBones.Or( m_jointBones, &pRecursiveRagdoll->m_jointBones ); //FOR_EACH_VEC( m_Gore, i ) //{ // const int iBone = m_Gore[ i ].m_iBone; // if ( iBone >= 0 && iBone == m_iBranchRootBone ) // { // } // pRecursiveRagdoll->m_Gore.AddToTail( m_Gore[ i ] ); // m_Gore.Remove( i ); // i--; //} } pGib->InitAsClientRagdoll( boneDelta0, boneDelta1, currentBones, boneDt, false, &partial ); if ( bExplosionImpact && RandomFloat() <= gstring_gibbing_explosion_recursive_chance.GetFloat() / 100.0f ) { pGib->ImpactTrace( pTrace, iDamageType, pCustomImpactName ); } if ( BloodColor() == BLOOD_COLOR_RED && ( !pRecursiveRagdoll || !pRecursiveRagdoll->m_bReleaseRagdoll ) && ShouldCreateBloodParticles() ) { Assert( pszParentSplitBone != NULL ); DispatchGibParticle( pGib, pszParentSplitBone, bExplosionImpact, BloodColor() ); } }
//----------------------------------------------------------------------------- // Purpose: Attaches fire to the hitboxes of an animating character. The fire // is distributed based on hitbox volumes -- it attaches to the larger // hitboxes first. //----------------------------------------------------------------------------- void C_EntityFlame::AttachToHitBoxes( void ) { m_pCachedModel = NULL; C_BaseCombatCharacter *pAnimating = (C_BaseCombatCharacter *)m_hEntAttached.Get(); if (!pAnimating || !pAnimating->GetModel()) { return; } CStudioHdr *pStudioHdr = pAnimating->GetModelPtr(); if (!pStudioHdr) { return; } mstudiohitboxset_t *set = pStudioHdr->pHitboxSet( pAnimating->m_nHitboxSet ); if ( !set ) { return; } if ( !set->numhitboxes ) { return; } m_pCachedModel = pAnimating->GetModel(); CBoneCache *pCache = pAnimating->GetBoneCache( pStudioHdr ); matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() ); // // Sort the hitboxes by volume. // HitboxVolume_t hitboxvolume[MAXSTUDIOBONES]; for ( int i = 0; i < set->numhitboxes; i++ ) { mstudiobbox_t *pBox = set->pHitbox(i); hitboxvolume[i].nIndex = i; hitboxvolume[i].flVolume = CalcBoxVolume(pBox->bbmin, pBox->bbmax); } qsort(hitboxvolume, set->numhitboxes, sizeof(hitboxvolume[0]), (int (__cdecl *)(const void *, const void *))SortHitboxVolumes); // // Attach fire to the hitboxes. // for ( int i = 0; i < NUM_HITBOX_FIRES; i++ ) { int hitboxindex; // // Pick the 5 biggest hitboxes, or random ones if there are less than 5 hitboxes, // then pick random ones after that. // if (( i < 5 ) && ( i < set->numhitboxes )) { hitboxindex = i; } else { hitboxindex = random->RandomInt( 0, set->numhitboxes - 1 ); } mstudiobbox_t *pBox = set->pHitbox( hitboxvolume[hitboxindex].nIndex ); Assert( hitboxbones[pBox->bone] ); m_nHitbox[i] = hitboxvolume[hitboxindex].nIndex; m_pFireSmoke[i] = new C_FireSmoke; // // Calculate a position within the hitbox to place the fire. // m_vecFireOrigin[i] = Vector(random->RandomFloat(pBox->bbmin.x, pBox->bbmax.x), random->RandomFloat(pBox->bbmin.y, pBox->bbmax.y), random->RandomFloat(pBox->bbmin.z, pBox->bbmax.z)); Vector vecAbsOrigin; VectorTransform( m_vecFireOrigin[i], *hitboxbones[pBox->bone], vecAbsOrigin); m_pFireSmoke[i]->SetLocalOrigin( vecAbsOrigin ); // // The first fire emits smoke, the rest do not. // m_pFireSmoke[i]->m_nFlags = bitsFIRESMOKE_ACTIVE; m_pFireSmoke[i]->m_nFlameModelIndex = modelinfo->GetModelIndex("sprites/fire1.vmt"); m_pFireSmoke[i]->m_nFlameFromAboveModelIndex = modelinfo->GetModelIndex("sprites/flamefromabove.vmt"); m_pFireSmoke[i]->m_flScale = 0; m_pFireSmoke[i]->m_flStartScale = 0; m_pFireSmoke[i]->m_flScaleTime = 1.5; m_pFireSmoke[i]->m_flScaleRegister = 0.1; m_pFireSmoke[i]->m_flChildFlameSpread = 20.0; m_pFireSmoke[i]->m_flScaleStart = 0; m_pFireSmoke[i]->SetOwnerEntity( this ); // Do a simple That Looks About Right clamp on the volumes // so that we don't get flames too large or too tiny. float flVolume = hitboxvolume[hitboxindex].flVolume; Assert( IsFinite(flVolume) ); #define FLAME_HITBOX_MIN_VOLUME 1000.0f #define FLAME_HITBOX_MAX_VOLUME 4000.0f if( flVolume < FLAME_HITBOX_MIN_VOLUME ) { flVolume = FLAME_HITBOX_MIN_VOLUME; } else if( flVolume > FLAME_HITBOX_MAX_VOLUME ) { flVolume = FLAME_HITBOX_MAX_VOLUME; } m_pFireSmoke[i]->m_flScaleEnd = 0.00012f * flVolume; m_pFireSmoke[i]->m_flScaleTimeStart = Helper_GetTime(); m_pFireSmoke[i]->m_flScaleTimeEnd = Helper_GetTime() + 2.0; m_pFireSmoke[i]->StartClientOnly(); } m_bAttachedToHitboxes = true; }
void CDmeMDL::SetUpBones( CStudioHdr &studioHdr, const matrix3x4_t& shapeToWorld, int nMaxBoneCount, matrix3x4_t *pBoneToWorld ) { // Default to middle of the pose parameter range float pPoseParameter[MAXSTUDIOPOSEPARAM]; for ( int i = 0; i < MAXSTUDIOPOSEPARAM; ++i ) { pPoseParameter[i] = 0.5f; } int nFrameCount = Studio_MaxFrame( &studioHdr, m_nSequence, pPoseParameter ); if ( nFrameCount == 0 ) { nFrameCount = 1; } float flCycle = ( m_flTime * m_flPlaybackRate ) / nFrameCount; // FIXME: We're always wrapping; may want to determing if we should clamp flCycle -= (int)(flCycle); Vector pos[MAXSTUDIOBONES]; Quaternion q[MAXSTUDIOBONES]; InitPose( &studioHdr, pos, q, BoneMask( ) ); AccumulatePose( &studioHdr, NULL, pos, q, m_nSequence, flCycle, pPoseParameter, BoneMask( ), 1.0f, m_flTime ); // FIXME: Try enabling this? // CalcAutoplaySequences( pStudioHdr, NULL, pos, q, pPoseParameter, BoneMask( ), flTime ); // Root transform matrix3x4_t rootToWorld, temp; // Rotate the root transform to make it align with DMEs // DMEs up vector is the y axis if ( !m_bDrawInEngine ) { matrix3x4_t engineToDme; EngineToDmeMatrix( engineToDme ); ConcatTransforms( engineToDme, shapeToWorld, rootToWorld ); } else { MatrixCopy( shapeToWorld, rootToWorld ); } if ( nMaxBoneCount > studioHdr.numbones() ) { nMaxBoneCount = studioHdr.numbones(); } for ( int i = 0; i < nMaxBoneCount; i++ ) { // If it's not being used, fill with NAN for errors #ifdef _DEBUG if ( !(studioHdr.pBone( i )->flags & BoneMask())) { int j, k; for (j = 0; j < 3; j++) { for (k = 0; k < 4; k++) { pBoneToWorld[i][j][k] = VEC_T_NAN; } } continue; } #endif matrix3x4_t boneMatrix; QuaternionMatrix( q[i], boneMatrix ); MatrixSetColumn( pos[i], 3, boneMatrix ); if (studioHdr.pBone(i)->parent == -1) { ConcatTransforms( rootToWorld, boneMatrix, pBoneToWorld[i] ); } else { ConcatTransforms( pBoneToWorld[ studioHdr.pBone(i)->parent ], boneMatrix, pBoneToWorld[i] ); } } }
//----------------------------------------------------------------------------- // Purpose: Recompute my rendering box //----------------------------------------------------------------------------- void C_Strider::ClientThink() { int i; Vector vecMins, vecMaxs; Vector vecAbsMins, vecAbsMaxs; matrix3x4_t worldToStrider, hitboxToStrider; Vector vecBoxMins, vecBoxMaxs; Vector vecBoxAbsMins, vecBoxAbsMaxs; mstudiohitboxset_t *set; CBoneCache *pCache = NULL; // The reason why this is here, as opposed to in SetObjectCollisionBox, // is because of IK. The code below recomputes bones so as to get at the hitboxes, // which causes IK to trigger, which causes raycasts against the other entities to occur, // which is illegal to do while in the Relink phase. CStudioHdr *pStudioHdr = GetModelPtr(); if (!pStudioHdr) goto doneWithComputation; set = pStudioHdr->pHitboxSet( m_nHitboxSet ); if ( !set || !set->numhitboxes ) goto doneWithComputation; pCache = GetBoneCache( pStudioHdr ); matrix3x4_t *hitboxbones[MAXSTUDIOBONES]; pCache->ReadCachedBonePointers( hitboxbones, pStudioHdr->numbones() ); // Compute a box in world space that surrounds this entity m_vecRenderMins.Init( FLT_MAX, FLT_MAX, FLT_MAX ); m_vecRenderMaxs.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX ); MatrixInvert( EntityToWorldTransform(), worldToStrider ); for ( i = 0; i < set->numhitboxes; i++ ) { mstudiobbox_t *pbox = set->pHitbox(i); ConcatTransforms( worldToStrider, *hitboxbones[pbox->bone], hitboxToStrider ); TransformAABB( hitboxToStrider, pbox->bbmin, pbox->bbmax, vecBoxMins, vecBoxMaxs ); VectorMin( m_vecRenderMins, vecBoxMins, m_vecRenderMins ); VectorMax( m_vecRenderMaxs, vecBoxMaxs, m_vecRenderMaxs ); } // UNDONE: Disabled this until we can get closer to a final map and tune #if 0 // Cut ropes. if ( gpGlobals->curtime >= m_flNextRopeCutTime ) { // Blow the bbox out a little. Vector vExtendedMins = vecMins - Vector( 50, 50, 50 ); Vector vExtendedMaxs = vecMaxs + Vector( 50, 50, 50 ); C_RopeKeyframe *ropes[512]; int nRopes = C_RopeKeyframe::GetRopesIntersectingAABB( ropes, ARRAYSIZE( ropes ), GetAbsOrigin() + vExtendedMins, GetAbsOrigin() + vExtendedMaxs ); for ( int i=0; i < nRopes; i++ ) { C_RopeKeyframe *pRope = ropes[i]; if ( pRope->GetEndEntity() ) { Vector vPos; if ( pRope->GetEndPointPos( 1, vPos ) ) { // Detach the endpoint. pRope->SetEndEntity( NULL ); // Make some spark effect here.. g_pEffects->Sparks( vPos ); } } } m_flNextRopeCutTime = gpGlobals->curtime + 0.5; } #endif doneWithComputation: // True argument because the origin may have stayed the same, but the size is expected to always change g_pClientShadowMgr->AddToDirtyShadowList( this, true ); }