/// <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); }