/// <summary> /// Converts the animations from the given imported data and creates /// them in the scene. /// </summary> /// <param name="scene">The scene to hold to converted animations</param> /// <param name="data">The data to read the animations from</param> protected void CreateAnimations(AssimpSharp.Scene scene, AssimpSharp.XFile.Scene data) { var newAnims = new List<AssimpSharp.Animation>(); for (int a = 0; a < data.Anims.Count; a++) { var anim = data.Anims[a]; if (anim.Anims.Count == 0) { continue; } var nanim = new AssimpSharp.Animation(); newAnims.Add(nanim); nanim.Name = anim.Name; nanim.Duration = 0; nanim.TicksPreSecond = data.AnimTicksPerSecond; nanim.Channels = new AssimpSharp.NodeAnim[anim.Anims.Count]; for (int b = 0; b < anim.Anims.Count; b++) { var bone = anim.Anims[b]; var nbone = new AssimpSharp.NodeAnim(); nbone.NodeName = bone.BoneName; nanim.Channels[b] = nbone; if (bone.TrafoKeys.Count > 0) { nbone.PositionKeys = new VectorKey[bone.TrafoKeys.Count]; nbone.RotationKeys = new QuatKey[bone.TrafoKeys.Count]; nbone.ScalingKeys = new VectorKey[bone.TrafoKeys.Count]; for (int c = 0; c < bone.TrafoKeys.Count; c++) { var time = bone.TrafoKeys[c].Key; var trafo = bone.TrafoKeys[c].Value; var pos = trafo.TranslationVector; nbone.PositionKeys[c].Time = time; nbone.PositionKeys[c].Value = pos; Vector3 scale; scale.X = new Vector3(trafo[0, 0], trafo[0, 1], trafo[0, 2]).Length(); scale.Y = new Vector3(trafo[1, 0], trafo[1, 1], trafo[1, 2]).Length(); scale.Z = new Vector3(trafo[2, 0], trafo[2, 1], trafo[2, 2]).Length(); nbone.ScalingKeys[c].Time = time; nbone.ScalingKeys[c].Value = scale; var rotmat = new Matrix3x3( trafo[0, 0] / scale.X, trafo[0, 1] / scale.Y, trafo[0, 2] / scale.Z, trafo[1, 0] / scale.X, trafo[1, 1] / scale.Y, trafo[1, 2] / scale.Z, trafo[2, 0] / scale.X, trafo[2, 1] / scale.Y, trafo[2, 2] / scale.Z); nbone.RotationKeys[c].Time = time; throw (new NotImplementedException()); //nbone.RotationKeys[c].Value = ; } nanim.Duration = Math.Max(nanim.Duration, bone.TrafoKeys[bone.TrafoKeys.Count].Key); } else { // separate key sequences for position, rotation, scaling nbone.PositionKeys = new VectorKey[bone.PosKeys.Count]; for (int c = 0; c < nbone.PositionKeys.Length; c++) { var pos = bone.PosKeys[c].Value; nbone.PositionKeys[c].Time = bone.PosKeys[c].Time; nbone.PositionKeys[c].Value = pos; } // rotation nbone.RotationKeys = new QuatKey[bone.RotKeys.Count]; for (int c = 0; c < nbone.RotationKeys.Length; c++) { var rotmat = Matrix3x3.RotationQuaternion(bone.RotKeys[c].Value); nbone.RotationKeys[c].Time = bone.RotKeys[c].Time; Quaternion.RotationMatrix(ref rotmat, out nbone.RotationKeys[c].Value); nbone.RotationKeys[c].Value.W *= -1.0f; } // longest lasting key sequence determines duration nbone.ScalingKeys = new VectorKey[bone.ScaleKeys.Count]; for (int c = 0; c < nbone.ScalingKeys.Length; c++) { nbone.ScalingKeys[c] = bone.ScaleKeys[c]; } // longest lasting key sequence determines duration if (bone.PosKeys.Count > 0) { nanim.Duration = Math.Max(nanim.Duration, bone.PosKeys[bone.PosKeys.Count - 1].Time); } if (bone.RotKeys.Count > 0) { nanim.Duration = Math.Max(nanim.Duration, bone.RotKeys[bone.RotKeys.Count - 1].Time); } if (bone.ScaleKeys.Count > 0) { nanim.Duration = Math.Max(nanim.Duration, bone.ScaleKeys[bone.ScaleKeys.Count - 1].Time); } } } } // store all converted animations in the scene if (newAnims.Count > 0) { scene.Animations.Clear(); scene.Animations.AddRange(newAnims); } }
private void ConvertAnimationStack(AnimationStack st) { var layers = st.Layers; if (layers.Count == 0) { return; } var anim = new AssimpSharp.Animation(); Animations.Add(anim); // strip AnimationStack:: prefix var name = st.Name; if (name.StartsWith("AnimationStack::")) { name = name.Substring(16); } else if (name.StartsWith("AnimStack::")) { name = name.Substring(11); } anim.Name = name; // need to find all nodes for which we need to generate node animations - // it may happen that we need to merge multiple layers, though. var nodeMap = new NodeMap(); // reverse mapping from curves to layers, much faster than querying // the FBX DOM for it. var layerMap = new LayerMap(); var propWhitelist = new string[] { "Lcl Scaling", "Lcl Rotation", "Lcl Translation" }; foreach (var layer in layers) { Debug.Assert(layer != null); var nodes = layer.Nodes(propWhitelist); foreach (var node in nodes) { var model = node.Target as Model; // this can happen - it could also be a NodeAttribute (i.e. for camera animations) if (model == null) { continue; } var name_ = FixNodeName(model.Name); if (nodeMap.ContainsKey(name)) { nodeMap[name].Add(node); } else { nodeMap[name] = new List<AnimationCurveNode>() { node }; } layerMap[node] = layer; } } // generate node animations var nodeAnims = new List<AssimpSharp.NodeAnim>(); double minTime = 1e10; double maxTime = -1e10; long startTime = st.LocalStart.Value; long stopTime = st.LocalStop.Value; double startTimeF = CONVERT_FBX_TIME(startTime); double stopTimeF = CONVERT_FBX_TIME(stopTime); try { foreach (var kv in nodeMap) { GenerateNodeAnimations(nodeAnims, kv.Key, kv.Value, layerMap, startTime, stopTime, ref maxTime, ref minTime); } } catch (Exception e) { nodeAnims.Clear(); throw e; } if (nodeAnims.Count > 0) { anim.Channels = nodeAnims.ToArray(); } else { // empty animations would fail validation, so drop them Animations.RemoveAt(Animations.Count - 1); FBXImporter.LogInfo("ignoring empty AnimationStack (using IK?): " + name); return; } //adjust relative timing for animation { var startFps = startTimeF * AnimFps; for (int c = 0; c < anim.Channels.Length; c++) { var channel = anim.Channels[c]; for (int i = 0; i < channel.PositionKeys.Length; i++) { channel.PositionKeys[i].Time -= startFps; } for (int i = 0; i < channel.RotationKeys.Length; i++) { channel.RotationKeys[i].Time -= startFps; } for (int i = 0; i < channel.ScalingKeys.Length; i++) { channel.ScalingKeys[i].Time -= startFps; } } maxTime -= minTime; } // for some mysterious reason, mDuration is simply the maximum key -- the // validator always assumes animations to start at zero. anim.Duration = (stopTimeF - startTimeF) * AnimFps; anim.TicksPreSecond = AnimFps; }