/****************************************************************************** libx42make - skinned vertex animation library (exporter utilities) Copyright (C) 2007 HermitWorks Entertainment Corporation 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 2 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, write to the Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA. ******************************************************************************/ #include "local.h" namespace x42 { namespace make { inline vec3 InterpElem( const vec3 &s, const vec3 &e, float t ) { return lerp( s, e, t ); } inline quat InterpElem( const quat &s, const quat &e, float t ) { return slerp( s, e, t ); } template< typename T > static T SampleTrack( const std::vector< AnimTrackElem< T > > &track, float t ) { if( track.size() == 0 ) return T(); if( track[0].frame > t ) return track[0].value; for( uint i = 1; i < track.size(); i++ ) { if( track[i].frame >= t ) { //value is between i - 1 and i, interp it out const AnimTrackElem< T > &s = track[i - 1]; const AnimTrackElem< T > &e = track[i]; float ti = (float)(t - s.frame) / (float)(e.frame - s.frame); return InterpElem( s.value, e.value, ti ); } } //t >= last time in track return track[track.size() - 1].value; } inline float GetElemDelta( const vec3 &s, const vec3 &e, float t, const vec3 &m ) { return dist( m, lerp( s, e, t ) ); } inline float GetElemDelta( const quat &s, const quat &e, float t, const quat &m ) { quat l = slerp( s, e, t ); return max( fabsf( m.i - l.i ), fabsf( m.j - l.j ), fabsf( m.k - l.k ), fabsf( m.w - l.w ) ); } template< typename T > static void CleanTrack( std::vector< AnimTrackElem< T > > &track, float tol_val, const std::vector< uint > &pinFrames, float pin_tol_val ) { std::vector< float > tol( track.size(), tol_val * 0.5F ); for( ; ; ) { uint iMin = uint_traits::max_val; float dMin = tol_val; std::vector< uint >::const_iterator pinIter = pinFrames.begin(); uint pinFrame = 0; for( uint i = 1; i < track.size() - 1; i++ ) { const AnimTrackElem< T > &ps = track[i - 1]; const AnimTrackElem< T > &pm = track[i]; const AnimTrackElem< T > &pe = track[i + 1]; float t = (float)(pm.frame - ps.frame) / (float)(pe.frame - ps.frame); float d = GetElemDelta( ps.value, pe.value, t, pm.value ); while( pinIter < pinFrames.end() && pinFrame < ps.frame ) pinFrame = *(pinIter++); float max_err = (pinFrame >= ps.frame && pinFrame <= pe.frame) ? pin_tol_val : tol[i - 1] + tol[i + 1]; if( d <= max_err ) { if( d < dMin ) { iMin = i; dMin = d; } } } if( iMin == uint_traits::max_val ) break; const AnimTrackElem< T > &ps = track[iMin - 1]; const AnimTrackElem< T > &pm = track[iMin]; const AnimTrackElem< T > &pe = track[iMin + 1]; float t = (float)(pm.frame - ps.frame) / (float)(pe.frame - ps.frame); float d = GetElemDelta( ps.value, pe.value, t, pm.value ); //adjust the tolerances based on their distance from the dead entry tol[iMin - 1] = max( tol[iMin - 1] - d * (1.0F - t), 0.0F ); tol[iMin + 1] = max( tol[iMin + 1] - d * t, 0.0F ); track.erase( track.begin() + iMin ); tol.erase( tol.begin() + iMin ); } if( track.size() == 2 ) { /* The above loop works on groups of three track elements. If it trims the track down to two elements (or if the track came in with only two elements) it won't be able to simplify the data further. This handles that case. */ const AnimTrackElem< T > &a = track[0]; const AnimTrackElem< T > &b = track[1]; float d = GetElemDelta( a.value, b.value, 0.5F, a.value ) + GetElemDelta( a.value, b.value, 0.5F, b.value ); if( d < tol[0] + tol[1] ) track.erase( track.begin() ); } } template< typename T > static void CleanStaticTrack( std::vector< AnimTrackElem< T > > &track, uint numFrames ) { track.resize( 1 ); track[0].frame = numFrames - 1; } /***** Bone */ void Bone::CleanTracks( void ) { ModelData *mod = GetModel(); if( staticAnim ) { CleanStaticTrack( posTrack, mod->numFrames ); CleanStaticTrack( rotTrack, mod->numFrames ); CleanStaticTrack( scaleTrack, mod->numFrames ); } else { CleanTrack( posTrack, posTol >= 0.0F ? posTol : mod->posTol, mod->pinFrames, pinPosTol >= 0.0F ? pinPosTol : mod->pinPosTol ); CleanTrack( rotTrack, rotTol >= 0.0F ? rotTol : mod->rotTol, mod->pinFrames, pinRotTol >= 0.0F ? pinRotTol : mod->pinRotTol ); CleanTrack( scaleTrack, scaleTol >= 0.0F ? scaleTol : mod->scaleTol, mod->pinFrames, pinScaleTol >= 0.0F ? pinScaleTol : mod->pinScaleTol ); } } void Bone::GenLocalMatricesFromWorldMatrices( void ) { if( !parent ) { //root bone, there's no difference between //world space and local space in this case localMats = worldMats; return; } demand( worldMats.size() == parent->worldMats.size(), "Non-parallell matrix representation." ); localMats.resize( worldMats.size() ); for( uint i = 0; i < worldMats.size(); i++ ) { const MtxTrackElem &parentMtx = parent->worldMats[i]; const MtxTrackElem &mtx = worldMats[i]; demand( parentMtx.frame == mtx.frame, "Non-parallell matrix representation." ); MtxTrackElem &local = localMats[i]; local.frame = mtx.frame; local.value = inverse( parentMtx.value ) * mtx.value; } } void Bone::GenWorldMatricesFromLocalMatrices( void ) { if( !parent ) { //root bone, there's no difference between //world space and local space in this case worldMats = localMats; return; } demand( localMats.size() == parent->worldMats.size(), "Non-parallell matrix representation." ); worldMats.resize( localMats.size() ); for( uint i = 0; i < localMats.size(); i++ ) { const MtxTrackElem &parentMtx = parent->worldMats[i]; const MtxTrackElem &mtx = localMats[i]; demand( parentMtx.frame == mtx.frame, "Non-parallell matrix representation." ); MtxTrackElem &world = worldMats[i]; world.frame = mtx.frame; world.value = parentMtx.value * mtx.value; } } void Bone::GenTracksFromLocalMatrices( void ) { posTrack.resize( localMats.size() ); rotTrack.resize( localMats.size() ); scaleTrack.resize( localMats.size() ); for( uint i = 0; i < localMats.size(); i++ ) { const MtxTrackElem &me = localMats[i]; PosTrackElem &pe = posTrack[i]; RotTrackElem &re = rotTrack[i]; ScaleTrackElem &se = scaleTrack[i]; //update the time value pe.frame = me.frame; re.frame = me.frame; se.frame = me.frame; //decompose the matrix affine m = me.value; pe.value = m.col( 3 ); m.col( 3 ) = vec3c( 0 ); se.value.x = length( m.col( 0 ) ); se.value.y = length( m.col( 1 ) ); se.value.z = length( m.col( 2 ) ); m.col( 0 ) /= se.value.x; m.col( 1 ) /= se.value.y; m.col( 2 ) /= se.value.z; m = orthonormalize( m ); re.value = quat_from_affine( m ); } } void Bone::GenLocalMatricesFromTracks( void ) { const uint numFrames = GetModel()->numFrames; localMats.resize( numFrames ); for( uint i = 0; i < numFrames; i++ ) { MtxTrackElem &me = localMats[i]; me.frame = i; me.value = GetMatrix( (float)i ); } } affine Bone::GetMatrix( float time ) { float t = clamp( time, 0.0F, GetModel()->numFrames - 1.0F ); vec3 p = SampleTrack( posTrack, t ); quat r = SampleTrack( rotTrack, t ); vec3 s = SampleTrack( scaleTrack, t ); affine m = quat_to_affine( r ); m.col( 0 ) *= s.x; m.col( 1 ) *= s.y; m.col( 2 ) *= s.z; m.col( 3 ) = p; return m; } void Bone::OffsetMatrices( CoordinateSpace which, const affine &m ) { std::vector< MtxTrackElem > *target; switch( which ) { case CoordinateSpace::Local: target = &localMats; break; case CoordinateSpace::World: target = &worldMats; break; default: fail( ErrCode::BadParams, "Invalid coordinate space." ); } for( uint i = 0; i < target->size(); i++ ) (*target)[i].value = m * (*target)[i].value; } void Bone::OffsetMatrices( CoordinateSpace which, const std::vector< MtxTrackElem > &mats ) { std::vector< MtxTrackElem > *target; switch( which ) { case CoordinateSpace::Local: target = &localMats; break; case CoordinateSpace::World: target = &worldMats; break; default: fail( ErrCode::BadParams, "Invalid coordinate space." ); } demand( mats.size() == target->size(), "Mismatched arrays." ); for( uint i = 0; i < target->size(); i++ ) (*target)[i].value = mats[i].value * (*target)[i].value; } void Bone::MakeScaleValuesUniform( void ) { const float tol = 0.05F; for( uint i = 0; i < scaleTrack.size(); i++ ) { vec3 &sv = scaleTrack[i].value; float mid = (sv.x + sv.y + sv.z) * (1.0F / 3.0F); if( fabsf( sv.x - mid ) > tol || fabsf( sv.y - mid ) > tol || fabsf( sv.z - mid ) > tol ) { sstr< 2048 > msg; msg.Append( "Stomping a large scale value on bone '%s'.", name.c_str() ); warn( ErrCode::Generic, msg ); } sv.x = mid; sv.y = mid; sv.z = mid; } } /***** ModelData */ void ModelData::GenLocalMatricesFromTracks( void ) { for( uint i = 0; i < bones.size(); i++ ) bones[i]->GenLocalMatricesFromTracks(); } void ModelData::GenTracksFromLocalMatrices( void ) { for( uint i = 0; i < bones.size(); i++ ) bones[i]->GenTracksFromLocalMatrices(); } void ModelData::GenLocalMatricesFromWorldMatrices( void ) { for( uint i = 0; i < bones.size(); i++ ) { BonePtr b = bones[i]; demand( !b->parent || b->parent->index < i, "Bones aren't sorted." ); b->GenLocalMatricesFromWorldMatrices(); } } void ModelData::GenWorldMatricesFromLocalMatrices( void ) { for( uint i = 0; i < bones.size(); i++ ) { BonePtr b = bones[i]; demand( !b->parent || b->parent->index < i, "Bones aren't sorted." ); b->GenWorldMatricesFromLocalMatrices(); } } void ModelData::SetDefaultTolerances( float posTol, float rotTol, float scaleTol, float pinPosTol, float pinRotTol, float pinScaleTol ) { ModelData::posTol = max( 0.0F, posTol ); ModelData::rotTol = max( 0.0F, rotTol ); ModelData::scaleTol = max( 0.0F, scaleTol ); ModelData::pinPosTol = max( 0.0F, pinPosTol ); ModelData::pinRotTol = max( 0.0F, pinRotTol ); ModelData::pinScaleTol = max( 0.0F, pinScaleTol ); } void ModelData::CleanAnimTracks( void ) { for( uint i = 0; i < bones.size(); i++ ) bones[i]->CleanTracks(); } void ModelData::CollapseInteriorBones( CoordinateSpace workingSpace ) { switch( workingSpace ) { case CoordinateSpace::Local: case CoordinateSpace::World: break; default: fail( ErrCode::BadParams, "CollapseInteriorBones: invalid space param." ); } for( uint i = 0; i < bones.size(); i++ ) { BonePtr b = bones[i]; if( b->localMats.size() != numFrames ) fail( ErrCode::InvalidOp, "CollapseInteriorBones requires all bones to have full matrix representations." ); if( b->parent && b->parent->index > i ) fail( ErrCode::InvalidOp, "CollapseInteriorBones requires the bones to be sorted." ); if( b->flags & BoneFlags::UseInvParentScale ) fail( ErrCode::InvalidOp, "CollapseInteriorBones can't handle the use of BoneFlags::UseInvParentScale." ); } std::vector< bool > nonInteriorFlags( bones.size() ); for( uint i = 0; i < influences.size(); i++ ) if( influences[i].bone ) nonInteriorFlags[influences[i].bone->index] = true; for( uint i = 0; i < tags.size(); i++ ) if( tags[i].bone ) nonInteriorFlags[tags[i].bone->index] = true; for( uint i = 0; i < bones.size(); i++ ) { if( nonInteriorFlags[i] ) //skip non-interior bone continue; BonePtr bi = bones[i]; for( uint j = i + 1; j < bones.size(); j++ ) { BonePtr bj = bones[j]; if( bj->parent == bi ) { if( workingSpace == CoordinateSpace::Local ) bj->OffsetMatrices( CoordinateSpace::Local, bi->localMats ); bj->parent = bi->parent; } } } if( workingSpace == CoordinateSpace::World ) { for( uint i = 0; i < bones.size(); i++ ) bones[i]->GenLocalMatricesFromWorldMatrices(); } else { for( uint i = 0; i < bones.size(); i++ ) bones[i]->GenWorldMatricesFromLocalMatrices(); } } void ModelData::CollapseConstantBones( void ) { for( uint i = 0; i < bones.size(); i++ ) { BonePtr b = bones[i]; if( b->localMats.size() != numFrames ) fail( ErrCode::InvalidOp, "CollapseConstantBones requires all bones to have full matrix representations." ); if( b->posTrack.size() == 0 || b->rotTrack.size() == 0 || b->scaleTrack.size() == 0 ) fail( ErrCode::InvalidOp, "CollapseConstantBones requires all bones to have track representations." ); if( b->parent && b->parent->index > i ) fail( ErrCode::InvalidOp, "CollapseConstantBones requires the bones to be sorted." ); if( b->flags & BoneFlags::UseInvParentScale ) fail( ErrCode::InvalidOp, "CollapseConstantBones can't handle the use of BoneFlags::UseInvParentScale." ); } for( uint i = 0; i < bones.size(); i++ ) { BonePtr bi = bones[i]; if( bi->posTrack.size() != 1 || bi->rotTrack.size() != 1 || bi->scaleTrack.size() != 1 ) //bone isn't constant, skip continue; affine m = bi->GetMatrix( 0 ); //push it down to its children for( uint j = i + 1; j < bones.size(); j++ ) { BonePtr bj = bones[j]; if( bj->parent == bi ) { bj->OffsetMatrices( CoordinateSpace::Local, m ); bj->parent = bi->parent; //ugh... bj->GenTracksFromLocalMatrices(); bj->CleanTracks(); bj->GenLocalMatricesFromTracks(); } } //push it down to its influences for( uint j = 0; j < influences.size(); j++ ) { Influence &inf = influences[j]; if( inf.bone == bi ) { inf.objToBone = m * influences[j].objToBone; inf.bone = bi->parent; } } //push it down to its tags for( uint j = 0; j < tags.size(); j++ ) { if( tags[j].bone == bi ) { tags[j].tagMatrix = m * tags[j].tagMatrix; tags[j].bone = bi->parent; } } } } void ModelData::ApplyRootOffset( const vec3 &posOfs, const quat &rotOfs, const vec3 &innerScaleOfs, const vec3 &outerScaleOfs ) { BonePtr root = CreateBone(); root->animGroup = 0; root->extent = 0; root->flags = 0; root->name = "root-offset"; root->parent = ConstBonePtr(); root->flags = BoneFlags::None; root->staticAnim = true; PosTrackElem ptm; ptm.frame = numFrames - 1; ptm.value = posOfs; root->posTrack.push_back( ptm ); RotTrackElem rtm; rtm.frame = numFrames - 1; rtm.value = rotOfs; root->rotTrack.push_back( rtm ); ScaleTrackElem stm; stm.frame = numFrames - 1; stm.value = innerScaleOfs * outerScaleOfs; root->scaleTrack.push_back( stm ); //in case its used in subsequent operations root->GenLocalMatricesFromTracks(); root->GenWorldMatricesFromLocalMatrices(); affine offsetMatrix = root->worldMats[0].value; std::vector< uint > remap( bones.size() ); for( uint i = 0; i < remap.size() - 1; i++ ) remap[i] = i + 1; remap[root->index] = 0; DoRemapBones( remap ); assert( root->index == 0, "remap failed" ); for( uint i = 1; i < bones.size(); i++ ) { BonePtr b = bones[i]; if( !b->parent ) b->parent = root; if( b->worldMats.size() ) b->OffsetMatrices( CoordinateSpace::World, offsetMatrix ); } for( uint i = 0; i < influences.size(); i++ ) { Influence &inf = influences[i]; if( !inf.bone ) inf.bone = root; } affine invScale = { { { 1.0F / innerScaleOfs.x, 0, 0 }, { 0, 1.0F / innerScaleOfs.y, 0 }, { 0, 0, 1.0F / innerScaleOfs.z }, { 0, 0, 0 } } }; for( uint i = 0; i < tags.size(); i++ ) { Tag &tag = tags[i]; if( !tag.bone ) tag.bone = root; tag.tagMatrix = invScale * tag.tagMatrix; } } void ModelData::MakeScaleValuesUniform( void ) { for( uint i = 0; i < bones.size(); i++ ) bones[i]->MakeScaleValuesUniform(); } }; };