예제 #1
0
        /// <summary>
        /// Accessor for the full unified MeshGroup level Vertex list.
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public TTVertex GetVertexAt(int id)
        {
            if (Parts.Count == 0)
            {
                return(null);
            }

            var        startingOffset = 0;
            TTMeshPart part           = Parts[0];

            foreach (var p in Parts)
            {
                if (startingOffset + p.Vertices.Count < id)
                {
                    startingOffset += p.Vertices.Count;
                }
                else
                {
                    part = p;
                    break;
                }
            }

            var realId = id - startingOffset;

            return(part.Vertices[realId]);
        }
예제 #2
0
        /*private static float[] NewIdentityMatrix()
         * {
         *      var arr = new float [16];
         *      arr[0] = 1f;
         *      arr[1] = 0f;
         *      arr[2] = 0f;
         *      arr[3] = 0f;
         *
         *      arr[4] = 0f;
         *      arr[5] = 1f;
         *      arr[6] = 0f;
         *      arr[7] = 0f;
         *
         *      arr[8] = 0f;
         *      arr[9] = 0f;
         *      arr[10] = 1f;
         *      arr[11] = 0f;
         *
         *      arr[12] = 0f;
         *      arr[13] = 0f;
         *      arr[14] = 0f;
         *      arr[15] = 1f;
         *      return arr;
         * }
         *
         * public Dictionary<string, SkeletonData> ResolveBoneHeirarchy(List<XivDependencyRootInfo> roots = null, XivRace race = XivRace.All_Races, List<string> bones = null, Action<bool, string> loggingFunction = null)
         * {
         *      if (roots == null || roots.Count == 0)
         *      {
         *              if (!IsInternal)
         *              {
         *                      throw new Exception("Cannot dynamically resolve bone heirarchy for external model.");
         *              }
         *
         *
         *              // We can use the raw function here since we know this is a valid internal model file.
         *              XivDependencyRootInfo root = XivDependencyGraph.ExtractRootInfo(Source);
         *
         *              if (race == XivRace.All_Races)
         *              {
         *                      race = IOUtil.GetRaceFromPath(Source);
         *              }
         *
         *
         *              // Just our one dynamically found root.
         *              roots = new List<XivDependencyRootInfo>() { root };
         *      }
         *
         *      return TTModel.ResolveBoneHeirarchyRaw(roots, race, bones, loggingFunction);
         * }
         *
         * /// <summary>
         * /// Creates a row-by-row Matrix from a column order float set.
         * /// </summary>
         * /// <param name="data"></param>
         * /// <returns></returns>
         * private static float[] RowsFromColumns(float[] data)
         * {
         *      var formatted = new float[16];
         *
         *      formatted[0] = data[0];
         *      formatted[1] = data[4];
         *      formatted[2] = data[8];
         *      formatted[3] = data[12];
         *
         *
         *      formatted[4] = data[1];
         *      formatted[5] = data[5];
         *      formatted[6] = data[9];
         *      formatted[7] = data[13];
         *
         *      formatted[8] = data[2];
         *      formatted[9] = data[6];
         *      formatted[10] = data[10];
         *      formatted[11] = data[14];
         *
         *      formatted[12] = data[3];
         *      formatted[13] = data[7];
         *      formatted[14] = data[11];
         *      formatted[15] = data[15];
         *
         *      return formatted;
         * }
         *
         * /// <summary>
         * /// Resolves the full bone heirarchy necessary to animate this TTModel.
         * /// Used when saving the file to DB.  (Or potentially animating it)
         * /// </summary>
         * /// <returns></returns>
         * public static Dictionary<string, SkeletonData> ResolveBoneHeirarchyRaw(List<XivDependencyRootInfo> roots, XivRace race, List<string> bones = null, Action<bool, string> loggingFunction = null)
         * {
         *      if (loggingFunction == null)
         *      {
         *              loggingFunction = ModelModifiers.NoOp;
         *      }
         *
         *      var fullSkel = new Dictionary<string, SkeletonData>();
         *      var skelDict = new Dictionary<string, SkeletonData>();
         *
         *      bool parsedBase = false;
         *      var baseSkeletonPath = "";
         *      var extraSkeletonPath = "";
         *
         *      foreach (var root in roots)
         *      {
         *              // Do we need to get the base skel still?
         *              string[] skeletonData;
         *              if (!parsedBase)
         *              {
         *                      try
         *                      {
         *                              baseSkeletonPath = Sklb.GetBaseSkeletonFile(root, race);
         *                              skeletonData = File.ReadAllLines(baseSkeletonPath);
         *
         *                              // Parse both skeleton files, starting with the base file.
         *                              foreach (var b in skeletonData)
         *                              {
         *                                      if (b == "") continue;
         *                                      var j = JsonConvert.DeserializeObject<SkeletonData>(b);
         *                                      j.PoseMatrix = RowsFromColumns(j.PoseMatrix);
         *                                      fullSkel.Add(j.BoneName, j);
         *                              }
         *
         *                      } catch(Exception ex)
         *                      {
         *                              // If we failed to resolve the bones for some reason, log the error message and use a blank skel.
         *                              loggingFunction(true, "Error Parsing Skeleton ("+ baseSkeletonPath.ToString() +"):" + ex.Message);
         *                      }
         *                      parsedBase = true;
         *              }
         *
         *
         *              extraSkeletonPath = Sklb.GetExtraSkeletonFile(root, race);
         *              // Did this root have an extra skeleton in use?
         *              if (!String.IsNullOrEmpty(extraSkeletonPath))
         *              {
         *                      try
         *                      {
         *                              // If it did, add its bones to the resulting skeleton.
         *                              Dictionary<int, int> exTranslationTable = new Dictionary<int, int>();
         *                              skeletonData = File.ReadAllLines(extraSkeletonPath);
         *                              foreach (var b in skeletonData)
         *                              {
         *                                      if (b == "") continue;
         *                                      var j = JsonConvert.DeserializeObject<SkeletonData>(b);
         *                                      j.PoseMatrix = RowsFromColumns(j.PoseMatrix);
         *
         *                                      if (fullSkel.ContainsKey(j.BoneName))
         *                                      {
         *                                              // This is a parent level reference to a base bone.
         *                                              exTranslationTable.Add(j.BoneNumber, fullSkel[j.BoneName].BoneNumber);
         *                                      }
         *                                      else if (exTranslationTable.ContainsKey(j.BoneParent))
         *                                      {
         *                                              // Run it through the translation to match up with the base skeleton.
         *                                              j.BoneParent = exTranslationTable[j.BoneParent];
         *
         *                                              // And generate its own new bone number
         *                                              var originalNumber = j.BoneNumber;
         *                                              j.BoneNumber = fullSkel.Select(x => x.Value.BoneNumber).Max() + 1;
         *
         *                                              fullSkel.Add(j.BoneName, j);
         *                                              exTranslationTable.Add(originalNumber, j.BoneNumber);
         *                                      } else
         *                                      {
         *                                              // This is a root bone in the EX skeleton that has no parent element in the base skeleton.
         *                                              // Just stick it onto the root bone.
         *                                              j.BoneParent = fullSkel["n_root"].BoneNumber;
         *
         *                                              // And generate its own new bone number
         *                                              var originalNumber = j.BoneNumber;
         *                                              j.BoneNumber = fullSkel.Select(x => x.Value.BoneNumber).Max() + 1;
         *
         *                                              fullSkel.Add(j.BoneName, j);
         *                                              exTranslationTable.Add(originalNumber, j.BoneNumber);
         *
         *                                      }
         *                              }
         *                      } catch(Exception ex)
         *                      {
         *                              // If we failed to resolve the bones for some reason, log the error message and use a blank skel.
         *                              loggingFunction(true, "Error Parsing Extra Skeleton (" + extraSkeletonPath.ToString() + "):" + ex.Message);
         *                      }
         *              }
         *      }
         *
         *
         *      // If no bones were specified, include all of them.
         *      if(bones == null)
         *      {
         *              bones = new List<string>();
         *              foreach(var e in fullSkel)
         *              {
         *                      bones.Add(e.Value.BoneName);
         *              }
         *
         *              bones = bones.Distinct().ToList();
         *      }
         *
         *
         *      var badBoneId = 900;
         *      foreach (var s in bones)
         *      {
         *              var fixedBone = Regex.Replace(s, "[0-9]+$", string.Empty);
         *
         *              if (fullSkel.ContainsKey(fixedBone))
         *              {
         *                      var skel = fullSkel[fixedBone];
         *
         *                      if (skel.BoneParent == -1 && !skelDict.ContainsKey(skel.BoneName))
         *                      {
         *                              skelDict.Add(skel.BoneName, skel);
         *                      }
         *
         *                      while (skel.BoneParent != -1)
         *                      {
         *                              if (!skelDict.ContainsKey(skel.BoneName))
         *                              {
         *                                      skelDict.Add(skel.BoneName, skel);
         *                              }
         *                              skel = fullSkel.First(x => x.Value.BoneNumber == skel.BoneParent).Value;
         *
         *                              if (skel.BoneParent == -1 && !skelDict.ContainsKey(skel.BoneName))
         *                              {
         *                                      skelDict.Add(skel.BoneName, skel);
         *                              }
         *                      }
         *              }
         *              else
         *              {
         *                      // Create a fake bone for this, rather than strictly crashing out.
         *                      var skel = new SkeletonData();
         *                      skel.BoneName = s;
         *                      skel.BoneNumber = badBoneId;
         *                      badBoneId++;
         *                      skel.BoneParent = 0;
         *                      skel.InversePoseMatrix = NewIdentityMatrix();
         *                      skel.PoseMatrix = NewIdentityMatrix();
         *
         *                      skelDict.Add(s, skel);
         *                      loggingFunction(true, $"The base game skeleton did not contain bone {s}. It has been parented to the root bone.");
         *              }
         *      }
         *
         *      return skelDict;
         * }*/


        /// <summary>
        /// Performs a basic sanity check on an incoming TTModel
        /// Returns true if there were no errors or errors that were resolvable.
        /// Returns false if the model was deemed insane.
        /// </summary>
        /// <param name="model"></param>
        /// <param name="loggingFunction"></param>
        /// <returns></returns>
        public static bool SanityCheck(TTModel model, Action <bool, string> loggingFunction = null)
        {
            if (loggingFunction == null)
            {
                loggingFunction = ModelModifiers.NoOp;
            }
            loggingFunction(false, "Validating model sanity...");

            bool hasWeights = model.HasWeights;

            if (model.MeshGroups.Count == 0)
            {
                loggingFunction(true, "Model has no data. - Model must have at least one valid Mesh Group.");
                return(false);
            }

            var mIdx = 0;

            foreach (var m in model.MeshGroups)
            {
                if (m.Parts.Count == 0)
                {
                    var part = new TTMeshPart();
                    part.Name = "Part 0";
                    m.Parts.Add(part);
                }

                // Meshes in animated models must have at least one bone in their bone set in order to not generate a crash.
                if (hasWeights && m.Bones.Count == 0)
                {
                    m.Bones.Add("n_root");
                }
                mIdx++;
            }

            return(true);
        }
예제 #3
0
        /// <summary>
        /// Loads a TTModel file from a given SQLite3 DB filepath.
        /// </summary>
        public static TTModel FromDb(FileInfo file, Action <bool, string> loggingFunction = null)
        {
            if (loggingFunction == null)
            {
                loggingFunction = ModelModifiers.NoOp;
            }

            string  connectionString = "Data Source=" + file + ";";
            TTModel model            = new TTModel();

            model.Source = file.FullName;

            // Spawn a DB connection to do the raw queries.
            using (SqliteConnection db = new SqliteConnection(connectionString))
            {
                db.Open();

                // Load Mesh Groups
                string query = "select * from meshes order by mesh asc;";
                using (SqliteCommand cmd = new SqliteCommand(query, db))
                {
                    using (CacheReader reader = new CacheReader(cmd.ExecuteReader()))
                    {
                        while (reader.NextRow())
                        {
                            int meshNum = reader.GetInt32("mesh");

                            // Spawn mesh groups as needed.
                            while (model.MeshGroups.Count <= meshNum)
                            {
                                model.MeshGroups.Add(new TTMeshGroup());
                            }

                            model.MeshGroups[meshNum].Name = reader.GetString("name");
                        }
                    }
                }

                // Load Mesh Parts
                query = "select * from parts order by mesh asc, part asc;";
                using (SqliteCommand cmd = new SqliteCommand(query, db))
                {
                    using (CacheReader reader = new CacheReader(cmd.ExecuteReader()))
                    {
                        while (reader.NextRow())
                        {
                            int meshNum = reader.GetInt32("mesh");
                            int partNum = reader.GetInt32("part");

                            // Spawn mesh groups if needed.
                            while (model.MeshGroups.Count <= meshNum)
                            {
                                model.MeshGroups.Add(new TTMeshGroup());
                            }

                            // Spawn parts as needed.
                            while (model.MeshGroups[meshNum].Parts.Count <= partNum)
                            {
                                model.MeshGroups[meshNum].Parts.Add(new TTMeshPart());
                            }

                            model.MeshGroups[meshNum].Parts[partNum].Name = reader.GetString("name");
                        }
                    }
                }

                // Load Bones
                query = "select * from bones where mesh >= 0 order by mesh asc, bone_id asc;";
                using (SqliteCommand cmd = new SqliteCommand(query, db))
                {
                    using (CacheReader reader = new CacheReader(cmd.ExecuteReader()))
                    {
                        while (reader.NextRow())
                        {
                            int meshId = reader.GetInt32("mesh");
                            model.MeshGroups[meshId].Bones.Add(reader.GetString("name"));
                        }
                    }
                }
            }

            // Loop for each part, to populate their internal data structures.
            for (int mId = 0; mId < model.MeshGroups.Count; mId++)
            {
                TTMeshGroup m = model.MeshGroups[mId];
                for (int pId = 0; pId < m.Parts.Count; pId++)
                {
                    TTMeshPart p = m.Parts[pId];
                    WhereClause where = new WhereClause();
                    WhereClause mWhere = new WhereClause();
                    mWhere.Column = "mesh";
                    mWhere.Value  = mId;
                    WhereClause pWhere = new WhereClause();
                    pWhere.Column = "part";
                    pWhere.Value  = pId;

                    where.Inner.Add(mWhere);
                    where.Inner.Add(pWhere);

                    // Load Vertices
                    // The reader handles coalescing the null types for us.
                    p.Vertices = BuildListFromTable(connectionString, "vertices", where, (reader) =>
                    {
                        TTVertex vertex = new TTVertex();

                        // Positions
                        vertex.Position.X = reader.GetFloat("position_x");
                        vertex.Position.Y = reader.GetFloat("position_y");
                        vertex.Position.Z = reader.GetFloat("position_z");

                        // Normals
                        vertex.Normal.X = reader.GetFloat("normal_x");
                        vertex.Normal.Y = reader.GetFloat("normal_y");
                        vertex.Normal.Z = reader.GetFloat("normal_z");

                        // Vertex Colors - Vertex color is RGBA
                        vertex.VertexColor[0] = (byte)Math.Round(reader.GetFloat("color_r") * 255);
                        vertex.VertexColor[1] = (byte)Math.Round(reader.GetFloat("color_g") * 255);
                        vertex.VertexColor[2] = (byte)Math.Round(reader.GetFloat("color_b") * 255);
                        vertex.VertexColor[3] = (byte)Math.Round(reader.GetFloat("color_a") * 255);

                        // UV Coordinates
                        vertex.UV1.X = reader.GetFloat("uv_1_u");
                        vertex.UV1.Y = reader.GetFloat("uv_1_v");
                        vertex.UV2.X = reader.GetFloat("uv_2_u");
                        vertex.UV2.Y = reader.GetFloat("uv_2_v");

                        // Bone Ids
                        vertex.BoneIds[0] = (byte)reader.GetByte("bone_1_id");
                        vertex.BoneIds[1] = (byte)reader.GetByte("bone_2_id");
                        vertex.BoneIds[2] = (byte)reader.GetByte("bone_3_id");
                        vertex.BoneIds[3] = (byte)reader.GetByte("bone_4_id");

                        // Weights
                        vertex.Weights[0] = (byte)Math.Round(reader.GetFloat("bone_1_weight") * 255);
                        vertex.Weights[1] = (byte)Math.Round(reader.GetFloat("bone_2_weight") * 255);
                        vertex.Weights[2] = (byte)Math.Round(reader.GetFloat("bone_3_weight") * 255);
                        vertex.Weights[3] = (byte)Math.Round(reader.GetFloat("bone_4_weight") * 255);

                        return(vertex);
                    });

                    p.TriangleIndices = BuildListFromTable(connectionString, "indices", where, (reader) =>
                    {
                        try
                        {
                            return(reader.GetInt32("vertex_id"));
                        }
                        catch (Exception ex)
                        {
                            throw ex;
                        }
                    });
                }
            }

            // Spawn a DB connection to do the raw queries.
            using (SqliteConnection db = new SqliteConnection(connectionString))
            {
                db.Open();

                // Load Shape Verts
                string query = "select * from shape_vertices order by shape asc, mesh asc, part asc, vertex_id asc;";
                using (SqliteCommand cmd = new SqliteCommand(query, db))
                {
                    using (CacheReader reader = new CacheReader(cmd.ExecuteReader()))
                    {
                        while (reader.NextRow())
                        {
                            string shapeName = reader.GetString("shape");
                            int    meshNum   = reader.GetInt32("mesh");
                            int    partNum   = reader.GetInt32("part");
                            int    vertexId  = reader.GetInt32("vertex_id");

                            TTMeshPart part = model.MeshGroups[meshNum].Parts[partNum];

                            // Copy the original vertex and update position.
                            TTVertex vertex = (TTVertex)part.Vertices[vertexId].Clone();
                            vertex.Position.X = reader.GetFloat("position_x");
                            vertex.Position.Y = reader.GetFloat("position_y");
                            vertex.Position.Z = reader.GetFloat("position_z");

                            TTVertex repVert = part.Vertices[vertexId];
                            if (repVert.Position.Equals(vertex.Position))
                            {
                                // Skip morphology which doesn't actually change anything.
                                continue;
                            }

                            if (!part.ShapeParts.ContainsKey(shapeName))
                            {
                                TTShapePart shpPt = new TTShapePart();
                                shpPt.Name = shapeName;
                                part.ShapeParts.Add(shapeName, shpPt);
                            }

                            part.ShapeParts[shapeName].VertexReplacements.Add(vertexId, part.ShapeParts[shapeName].Vertices.Count);
                            part.ShapeParts[shapeName].Vertices.Add(vertex);
                        }
                    }
                }
            }

            // Convert the model to FFXIV's internal weirdness.
            ModelModifiers.MakeImportReady(model, loggingFunction);
            return(model);
        }