public CreateNodeTrack ( ushort handle ) : |
||
handle | ushort | Handle to give the track, used for accessing the track later. |
리턴 |
protected void TransformAnimation(Matrix4 exportTransform, Animation newAnim, Animation anim, Skeleton newSkeleton) { foreach (NodeAnimationTrack track in anim.NodeTracks.Values) { NodeAnimationTrack newTrack = newAnim.CreateNodeTrack(track.Handle); Bone targetBone = (Bone)track.TargetNode; newTrack.TargetNode = newSkeleton.GetBone(targetBone.Handle); TransformTrack(exportTransform, newTrack, track, targetBone); } }
/// <summary> /// Reads an animation track. /// </summary> protected void ReadAnimationTrack( BinaryReader reader, Animation anim ) { // read the bone handle to apply this track to ushort boneHandle = ReadUShort( reader ); // get a reference to the target bone Bone targetBone = skeleton.GetBone( boneHandle ); // create an animation track for this bone NodeAnimationTrack track = anim.CreateNodeTrack( boneHandle, targetBone ); // keep reading all keyframes for this track if ( !IsEOF( reader ) ) { SkeletonChunkID chunkID = ReadChunk( reader ); while ( !IsEOF( reader ) && ( chunkID == SkeletonChunkID.KeyFrame ) ) { // read the key frame ReadKeyFrame( reader, track ); // read the next chunk id // If we're not end of file get the next chunk ID if ( !IsEOF( reader ) ) { chunkID = ReadChunk( reader ); } } // backpedal to the start of the chunk if ( !IsEOF( reader ) ) { Seek( reader, -ChunkOverheadSize ); } } }
public static void CleanupAnimation(Skeleton skel, Animation anim) { Animation newAnim = skel.CreateAnimation("_replacement", anim.Length); Animation tmpAnim = skel.CreateAnimation("_temporary", anim.Length); foreach (NodeAnimationTrack track in anim.NodeTracks.Values) { Bone bone = skel.GetBone((ushort)track.Handle); NodeAnimationTrack newTrack = newAnim.CreateNodeTrack(track.Handle, bone); NodeAnimationTrack tmpTrack = tmpAnim.CreateNodeTrack(track.Handle, bone); int maxFrame = track.KeyFrames.Count; int lastKeyFrame = -1; for (int keyFrameIndex = 0; keyFrameIndex < track.KeyFrames.Count; ++keyFrameIndex) { if (anim.InterpolationMode == InterpolationMode.Linear) { // Linear is based on one point before and one after. TransformKeyFrame cur = track.GetTransformKeyFrame(keyFrameIndex); if (keyFrameIndex == 0 || keyFrameIndex == (track.KeyFrames.Count - 1)) { // Add the key frame if it is the first or last keyframe. lastKeyFrame = keyFrameIndex; DuplicateKeyFrame(newTrack, cur); } else { // Make sure tmpTrack is clean.. we just use it for interpolation tmpTrack.RemoveAllKeyFrames(); TransformKeyFrame prior = track.GetTransformKeyFrame(lastKeyFrame); TransformKeyFrame next = track.GetTransformKeyFrame(keyFrameIndex + 1); DuplicateKeyFrame(tmpTrack, prior); DuplicateKeyFrame(tmpTrack, next); // Check to see if removing this last keyframe will throw off // any of the other keyframes that were considered redundant. bool needKeyFrame = false; for (int i = lastKeyFrame + 1; i <= keyFrameIndex; ++i) { TransformKeyFrame orig = track.GetTransformKeyFrame(i); TransformKeyFrame interp = new TransformKeyFrame(tmpTrack, orig.Time); tmpTrack.GetInterpolatedKeyFrame(orig.Time, interp); // Is this interpolated frame useful or redundant? if (!CompareKeyFrames(interp, cur)) { needKeyFrame = true; break; } } if (needKeyFrame) { lastKeyFrame = keyFrameIndex; DuplicateKeyFrame(newTrack, cur); } } } else if (anim.InterpolationMode == InterpolationMode.Spline) { // Spline is based on two points before and two after. TransformKeyFrame cur = track.GetTransformKeyFrame(keyFrameIndex); #if DISABLED_CODE if (keyFrameIndex == 0 || keyFrameIndex == 1 || keyFrameIndex == (track.KeyFrames.Count - 1) || keyFrameIndex == (track.KeyFrames.Count - 2)) { // Add the key frame if it is the first, second, last or second to last keyframe. DuplicateKeyFrame(newTrack, cur); } else { // Make sure tmpTrack is clean.. we just use it for interpolation tmpTrack.RemoveAllKeyFrames(); TransformKeyFrame prior1 = track.GetTransformKeyFrame(keyFrameIndex - 2); TransformKeyFrame prior2 = track.GetTransformKeyFrame(keyFrameIndex - 1); TransformKeyFrame next1 = track.GetTransformKeyFrame(keyFrameIndex + 1); TransformKeyFrame next2 = track.GetTransformKeyFrame(keyFrameIndex + 2); DuplicateKeyFrame(tmpTrack, prior1); DuplicateKeyFrame(tmpTrack, prior2); DuplicateKeyFrame(tmpTrack, next1); DuplicateKeyFrame(tmpTrack, next2); TransformKeyFrame interp = new TransformKeyFrame(tmpTrack, cur.Time); tmpTrack.GetInterpolatedKeyFrame(cur.Time, interp); // Is this interpolated frame useful or redundant? if (!CompareKeyFrames(interp, cur)) DuplicateKeyFrame(newTrack, cur); } #else DuplicateKeyFrame(newTrack, cur); #endif } else { System.Diagnostics.Debug.Assert(false, "Invalid InterpolationMode: " + anim.InterpolationMode); } } } skel.RemoveAnimation(tmpAnim.Name); skel.RemoveAnimation(newAnim.Name); skel.RemoveAnimation(anim.Name); // Recreate the animation with the proper name (awkward) anim = skel.CreateAnimation(anim.Name, anim.Length); foreach (NodeAnimationTrack track in newAnim.NodeTracks.Values) { Bone bone = skel.GetBone((ushort)track.Handle); NodeAnimationTrack newTrack = anim.CreateNodeTrack(track.Handle, bone); foreach (KeyFrame keyFrame in track.KeyFrames) DuplicateKeyFrame(newTrack, (TransformKeyFrame)keyFrame); } }
protected void ReadTrack(XmlNode node, Animation anim) { string boneName = node.Attributes["bone"].Value; // get a reference to the target bone Bone targetBone = skeleton.GetBone(boneName); // create an animation track for this bone NodeAnimationTrack track = anim.CreateNodeTrack(targetBone.Handle, targetBone); foreach (XmlNode childNode in node.ChildNodes) { switch (childNode.Name) { case "keyframes": ReadKeyFrames(childNode, track); break; default: DebugMessage(childNode); break; } } }
/// <summary> /// Use the data in the channels structures to create animations on /// our skeleton. This variant ignores the morph/pose animations, /// and only considers the skeletal node animations. /// </summary> /// <param name="worldTransform"></param> /// <param name="anim">animation which will be populated with the data from channels</param> /// <param name="skeleton">the skeleton that contians the bone data</param> protected void ProcessChannels( Matrix4 worldTransform, Animation anim, Skeleton skeleton ) { // We will get the rotation and translation transforms // from our parent, but we need the scale transform // as well. Quaternion worldRotate; Vector3 worldTranslate, worldScale; Matrix4.DecomposeMatrix( ref worldTransform, out worldTranslate, out worldRotate, out worldScale ); // Run through the bones, find the channels associated with that bone. // create a track for that bone, and set up keyframes within the track. for( ushort i = 0; i < m_AxiomMesh.Skeleton.BoneCount; i++ ) { Bone bone = m_AxiomMesh.Skeleton.GetBone( i ); string boneName = bone.Name; // Build a list of the channels that apply to this bone. List<Channel> boneChannels = new List<Channel>(); foreach( Channel channel in Channels ) { if( channel.TargetNode != bone.Name ) // this channel isn't for this bone.. just continue; continue; boneChannels.Add( channel ); } // Get the bind pose data out of the skeleton Quaternion bindRotation = bone.Orientation; // bind translation -- the bone.Position has has already been // converted, and we are going to compare this to the // converted data in the joint transform chains Vector3 bindTranslation = bone.Position; // Get the scene data that shows the current bone position if( !JointTransformChains.ContainsKey( bone.Name ) ) { m_Log.WarnFormat( "Missing joint transform chain for possible tag point {0}.", bone.Name ); continue; } List<NamedTransform> transforms = JointTransformChains[ bone.Name ]; Matrix4 posedBoneTransform = MathHelpers.Normalize( worldTransform * GetBonePoseTransform( bone.Name ) ); Matrix4 posedParentTransform = Matrix4.Identity; if( bone.Parent != null ) posedParentTransform = MathHelpers.Normalize( worldTransform * GetBonePoseTransform( bone.Parent.Name ) ); Matrix4 initialPoseTransform = posedParentTransform.Inverse() * posedBoneTransform; m_Log.DebugFormat( "Matrices for {0}: posedBoneTransform:\n{1}\nposedParentTransform:\n{2}", bone.Name, posedBoneTransform, posedParentTransform ); if( boneChannels.Count == 0 ) { Matrix4 bindTransform = Multiverse.MathLib.MathUtil.GetTransform( bindRotation, bindTranslation ); // Check to see if the bind pose and the initial pose for this // scene are the same. If they are not the same, generate // a track that has one keyframe, just to put the bone in // the right position. m_Log.InfoFormat( "No bone channels for {0}", bone.Name ); if( initialPoseTransform == bindTransform ) { // No need to create an animation track to move the // bone to the initial pose, since this is the bind // pose already. continue; } // I used to skip bones for which I lacked bind pose data (from a skin cluster) // but now that I have moved the channel processing code after the skeleton // generation code, I am certain to have bind pose data for the bones in the // skeleton. } if( m_Log.IsDebugEnabled ) m_Log.DebugFormat( "Matrices for {0}: Bind:\n{1}\nInitial:\n{2}", bone.Name, Multiverse.MathLib.MathUtil.GetTransform( bindRotation, bindTranslation ), initialPoseTransform ); NodeAnimationTrack track = anim.CreateNodeTrack( bone.Handle, bone ); if( boneChannels.Count == 0 ) { // Generate a track that has one keyframe, just to put // the bone in the right position. m_Log.InfoFormat( "Generating initial keyframe based on bind pose for {0}", bone.Name ); float time = 0; TransformKeyFrame keyFrame = (TransformKeyFrame) track.CreateKeyFrame( time ); Matrix4 frameTransform = initialPoseTransform; Quaternion frameRotate; Vector3 frameTranslate, frameScale; Matrix4.DecomposeMatrix( ref frameTransform, out frameTranslate, out frameRotate, out frameScale ); m_Log.DebugFormat( "Initial pose for {0}:\n{1}", bone.Name, frameTransform ); keyFrame.Rotation = CleanupQuaternion( bindRotation.Inverse() * frameRotate ); keyFrame.Translate = frameTranslate - bindTranslation; continue; } // Set the interpolation mode based on the bone channel bool interpModeSet = false; foreach( Channel channel in boneChannels ) { InterpolationMode interpMode = InterpolationMode.Linear; for( int j = 0; j < channel.Sampler.Interpolation.Count; ++j ) { if( GetInterpolationMode( channel, j, ref interpMode ) ) { if( interpModeSet && anim.InterpolationMode != interpMode ) m_Log.WarnFormat( "Mismatched interpolation modes for different channels: {0} != {1}", anim.InterpolationMode, interpMode ); anim.InterpolationMode = interpMode; interpModeSet = true; } } } // Assert that all the time accessors for the controlling channels are the same. // This will break if we don't bake transforms, since in that case, we will get // independent timeChannels for the various boneChannels. if( boneChannels.Count != 1 ) m_Log.Warn( "Multiple bone channels for a single bone. Check that transforms have been baked." ); // Build a list of all the times that we access any bone channel List<float> times = new List<float>(); foreach( Channel channel in boneChannels ) { Accessor timeAccessor = channel.Sampler.Input.Accessor; for( int j = 0; j < timeAccessor.Count; ++j ) { // TODO: Hardcoded parameter names float time = 0.0f; if( timeAccessor.ContainsParam( "TIME" ) ) time = (float) timeAccessor.GetParam( "TIME", j ); else if( timeAccessor.ContainsParam( "time" ) ) time = (float) timeAccessor.GetParam( "time", j ); else Debug.Assert( false, "No time parameter for sampler" ); if( !times.Contains( time ) ) times.Add( time ); } } foreach( float time in times ) { TransformKeyFrame keyFrame = (TransformKeyFrame) track.CreateKeyFrame( time ); Dictionary<Channel, Triple<int, int, float>> channelIndices = GetKeyframeIndicesByTime( boneChannels, time ); foreach( Channel channel in boneChannels ) { Triple<int, int, float> interpInfo = channelIndices[ channel ]; SetTransformForChannel( transforms, channel, interpInfo.first, interpInfo.second, interpInfo.third ); } // Matrix4 fullTransform = worldTransform * GetBakedTransform(transforms) * bone.FullTransform; Matrix4 frameTransform = GetBakedTransform( transforms ); Quaternion frameRotate; Vector3 frameTranslate, frameScale; Matrix4.DecomposeMatrix( ref frameTransform, out frameTranslate, out frameRotate, out frameScale ); //log.InfoFormat("frameRotate = {0}", GetEulerString(frameRotate)); //{ //foreach (NamedTransform nmt in transforms) { // if (nmt is NamedRotateTransform) { // Matrix4 tmp = nmt.Transform; // Quaternion foo = Quaternion.Identity; // foo.FromRotationMatrix(tmp.GetMatrix3()); // log.InfoFormat("NamedRotateTransform for {0} = {1}", nmt.Name, GetEulerString(foo)); // } //} //} // FIXME if( bone.Parent != null ) { // we have a parent, so apply scale factor to our // translation, but any orientation or translation // from the world will already be included in our // parent. frameTranslate = MathHelpers.ScaleVector( worldScale, frameTranslate ); } else { // no parent, so we need to incorporate the world // transform's scale, orientation and translation. frameTranslate = worldTransform * frameTranslate; frameRotate = worldRotate * frameRotate; } // Frame transform is our transform relative to the // world-transformed parent bone. If there is no parent, // we are using the identity matrix. // transform from the dae file is based on the position of // the bone relative to the parent bone. however, in // axiom, the keyframes are based on the position of the // bone relative to the bind position of the bone. // log.DebugFormat("bindRotation = {0}", GetEulerString(bindRotation)); // log.DebugFormat("bindRotation.Inverse = {0}", GetEulerString(bindRotation.Inverse())); // log.DebugFormat("frameRotate = {0}", GetEulerString(frameRotate)); keyFrame.Rotation = CleanupQuaternion( bindRotation.Inverse() * frameRotate ); keyFrame.Translate = frameTranslate - bindTranslation; } } }