/// <summary> /// /// </summary> /// <param name="FileName"></param> /// <param name="animation"></param> /// <param name="skeleton"></param> public void ExportSBAnimation(string FileName, SBAnimation animation, SBSkeleton skeleton) { var jointTable = GetJointTable(Settings.JVCPath).ToList(); Anim a = new Anim(); a.EndTime = animation.FrameCount / Settings.FrameScale; a.PlaySpeed = 0.01f; a.Joints.Add(new Joint() { BoneID = -1, Flag3 = 0x40, MaxTime = a.EndTime, }); List <double> AllKeys = new List <double>(); foreach (var v in animation.TransformNodes) { if (skeleton[v.Name] == null) { continue; } var sb = skeleton[v.Name]; if (sb == null) { continue; } var index = (short)jointTable.IndexOf((short)skeleton.IndexOfBone(sb)); Console.WriteLine(v.Name + " " + index); if (index == -1) { continue; } Joint Position = null; Joint Rotation = null; Joint Scale = null; if (v.HasTranslation) { Position = new Joint(); var j = Position; j.Flag1 = 0x02; j.Flag2 = 0x02; j.Flag3 = 0x21; j.BoneID = index; j.MaxTime = a.EndTime; } if (v.HasRotation) { Rotation = new Joint(); var j = Rotation; j.Flag1 = 0x02; j.Flag2 = 0x02; j.Flag3 = 0x28; j.BoneID = index; j.MaxTime = a.EndTime; } if (v.HasScale) { Scale = new Joint(); var j = Scale; j.Flag1 = 0x02; j.Flag2 = 0x02; j.Flag3 = 0x22; j.BoneID = index; j.MaxTime = a.EndTime; } // gather baked nodes List <Vector3> Positions = new List <Vector3>(); List <Vector4> Rotations = new List <Vector4>(); List <Vector3> Scales = new List <Vector3>(); for (int i = 0; i <= animation.FrameCount; i++) { var t = v.GetTransformAt(i, skeleton); if (v.HasTranslation) { Positions.Add(t.ExtractTranslation() - sb.Translation); } if (v.HasScale) { Positions.Add(t.ExtractScale() - sb.Scale); } if (v.HasRotation) { var quat = (t.ExtractRotation().Inverted() * sb.RotationQuaternion).Inverted(); var inv = new Quaternion(quat.X, 0, 0, quat.W).Normalized().Inverted(); var dir = Vector3.TransformNormal(Vector3.UnitX, Matrix4.CreateFromQuaternion(quat * inv)); var angle = Tools.CrossMath.ToEulerAngles(inv).X * 180 / (float)Math.PI; Rotations.Add(new Vector4(dir, angle)); } } // now we convert the bake nodes into linear tracks in the new time scale range if (Positions.Count > 0) { var X = SimplifyLines(Positions.Select(e => e.X)); var Y = SimplifyLines(Positions.Select(e => e.Y)); var Z = SimplifyLines(Positions.Select(e => e.Z)); var frames = X.Select(e => e.Item1).Union(Y.Select(e => e.Item1).Union(Z.Select(e => e.Item1))).ToList(); frames.Sort(); if (frames[frames.Count - 1] != animation.FrameCount) { frames.Add(animation.FrameCount); } AllKeys = AllKeys.Union(frames).ToList(); foreach (var f in frames) { Position.Keys.Add(new Key() { Time = (float)f / Settings.FrameScale, X = Positions[(int)f].X, Y = Positions[(int)f].Y, Z = Positions[(int)f].Z, W = 0 }); } } if (Rotations.Count > 0) { var X = SimplifyLines(Rotations.Select(e => e.X)); var Y = SimplifyLines(Rotations.Select(e => e.Y)); var Z = SimplifyLines(Rotations.Select(e => e.Z)); var W = SimplifyLines(Rotations.Select(e => e.W)); var frames = X.Select(e => e.Item1).Union(Y.Select(e => e.Item1).Union(Z.Select(e => e.Item1))).Union(W.Select(e => e.Item1)).ToList(); frames.Sort(); if (frames[frames.Count - 1] != animation.FrameCount) { frames.Add(animation.FrameCount); } AllKeys = AllKeys.Union(frames).ToList(); foreach (var f in frames) { Rotation.Keys.Add(new Key() { Time = (float)f / Settings.FrameScale, X = Rotations[(int)f].X, Y = Rotations[(int)f].Y, Z = Rotations[(int)f].Z, W = Rotations[(int)f].W, }); } } if (Position != null) { a.Joints.Add(Position); } if (Rotation != null) { a.Joints.Add(Rotation); } if (Scale != null) { a.Joints.Add(Scale); } } AllKeys.Sort(); foreach (var v in AllKeys) { a.Joints[0].Keys.Add(new Key() { Time = (float)v / Settings.FrameScale }); } a.Save(FileName); }
public IOModel ImportIOModel(string FileName) { IOModel model = new IOModel(); SBSkeleton skeleton = new SBSkeleton(); model.Skeleton = skeleton; var test = Fbx.FbxIO.ReadBinary(FileName); if (test.Version != Fbx.FbxVersion.v7_4) { throw new NotSupportedException($"Only FBX version 7.4 is currently supported: Imported version = {test.Version}"); } // global settings float Scale = 1; // read global settings var settings = test.GetNodesByName("GlobalSettings"); if (settings.Length != 0) { var prop70 = settings[0].GetNodesByName("Properties70"); if (prop70.Length != 0) { foreach (var property in prop70[0].Nodes) { if (property == null) { continue; } if (property.Properties.Count > 0 && property.Name == "P") { //TODO: this is inaccurate... //if (property.Properties[0].Equals("UnitScaleFactor")) // Scale = (float)(double)property.Properties[4]; } } } } FbxAccessor accessor = new FbxAccessor(FileName); //Bones var limbs = accessor.GetLimbNodes(); SBConsole.WriteLine($"Limb Node Count: {limbs.Length}"); foreach (var limb in limbs) { skeleton.AddRoot(ConvertLimbToSBBone(limb)); } foreach (var root in skeleton.Roots) { root.Scale *= Scale; } // Fast access to bone indices Dictionary <string, int> BoneNameToIndex = new Dictionary <string, int>(); foreach (var b in skeleton.Bones) { BoneNameToIndex.Add(b.Name, skeleton.IndexOfBone(b)); } // Mesh var models = accessor.GetModels(); SBConsole.WriteLine($"Model Node Count: {models.Count}"); int YupAxis = accessor.GetOriginalXAxis(); SBConsole.WriteLine("Yup: " + YupAxis); foreach (var mod in models) { // rotation 90 Matrix4 transform = (ImportSettings.Rotate90 ? Matrix4.CreateRotationX(-90 * DegToRag) : Matrix4.Identity) * GetModelTransform(mod); foreach (var geom in mod.Geometries) { IOMesh mesh = new IOMesh(); mesh.Name = mod.Name; model.Meshes.Add(mesh); // Create Rigging information Vector4[] BoneIndices = new Vector4[geom.Vertices.Length]; Vector4[] BoneWeights = new Vector4[geom.Vertices.Length]; foreach (var deformer in geom.Deformers) { //TODO: this shouldn't happen... if (!BoneNameToIndex.ContainsKey(deformer.Name)) { continue; } int index = BoneNameToIndex[deformer.Name]; for (int i = 0; i < deformer.Indices.Length; i++) { int vertexIndex = deformer.Indices[i]; for (int j = 0; j < 4; j++) { if (BoneWeights[vertexIndex][j] == 0) { BoneIndices[vertexIndex][j] = index; BoneWeights[vertexIndex][j] = (float)deformer.Weights[i]; break; } } } //SBConsole.WriteLine(deformer.Name + " " + deformer.Weights.Length + " " + deformer.Indices.Length + " " + index); } // Explanation: // negative values are used to indicate a stopping point for the face // so every 3rd index needed to be adjusted for (int i = 0; i < geom.Indices.Length; i += 3) { mesh.Indices.Add((uint)i); mesh.Indices.Add((uint)i + 1); mesh.Indices.Add((uint)i + 2); mesh.Vertices.Add(CreateVertex(transform, geom, i, BoneIndices, BoneWeights, Scale)); mesh.Vertices.Add(CreateVertex(transform, geom, i + 1, BoneIndices, BoneWeights, Scale)); mesh.Vertices.Add(CreateVertex(transform, geom, i + 2, BoneIndices, BoneWeights, Scale)); } mesh.HasPositions = true; //SBConsole.WriteLine(geom.Vertices.Length); foreach (var layer in geom.Layers) { switch (layer.Name) { case "LayerElementNormal": case "LayerElementUV": case "LayerElementColor": break; default: SBConsole.WriteLine(layer.Name + " " + layer.ReferenceInformationType + " " + layer.Data.Length + " " + (layer.ReferenceInformationType.Equals("IndexToDirect") ? layer.Indices.Length.ToString() : "")); break; } } mesh.Optimize(); } } return(model); }