/// <summary> /// Applies the deformer to a model /// </summary> /// <param name="model">The model being deformed</param> /// <param name="currentRace">The current model race</param> /// <param name="targetRace">The target race to convert the model to</param> private static void ApplyDeformers(TTModel model, XivRace currentRace, XivRace targetRace) { // Current race is already parent node // Direct conversion // [ Current > (apply deform) > Target ] if (currentRace.IsDirectParentOf(targetRace)) { ModelModifiers.ApplyRacialDeform(model, targetRace); } // Target race is parent node of Current race // Convert to parent (invert deform) // [ Current > (apply inverse deform) > Target ] else if (targetRace.IsDirectParentOf(currentRace)) { ModelModifiers.ApplyRacialDeform(model, currentRace, true); } // Current race is not parent of Target Race and Current race has parent // Make a recursive call with the current races parent race // [ Current > (apply inverse deform) > Current.Parent > Recursive Call ] else if (currentRace.GetNode().Parent != null) { ModelModifiers.ApplyRacialDeform(model, currentRace, true); ApplyDeformers(model, currentRace.GetNode().Parent.Race, targetRace); } // Current race has no parent // Make a recursive call with the target races parent race // [ Target > (apply deform on Target.Parent) > Target.Parent > Recursive Call ] else { ModelModifiers.ApplyRacialDeform(model, targetRace.GetNode().Parent.Race); ApplyDeformers(model, targetRace.GetNode().Parent.Race, targetRace); } }
/// <summary> /// Creates and populates a TTModel object from a raw XivMdl. /// </summary> public static TTModel FromRaw(Model rawMdl, Action <bool, string> loggingFunction = null) { if (rawMdl == null) { return(null); } if (loggingFunction == null) { loggingFunction = ModelModifiers.NoOp; } TTModel ttModel = new TTModel(); ModelModifiers.MergeGeometryData(ttModel, rawMdl, loggingFunction); ModelModifiers.MergeAttributeData(ttModel, rawMdl, loggingFunction); ModelModifiers.MergeMaterialData(ttModel, rawMdl, loggingFunction); try { ModelModifiers.MergeShapeData(ttModel, rawMdl, loggingFunction); } catch (Exception ex) { loggingFunction(true, "Unable to load shape data: " + ex.Message); ModelModifiers.ClearShapeData(ttModel, loggingFunction); } return(ttModel); }
private void ImportButton_Click(object sender, RoutedEventArgs e) { if (Convert.ToDouble(ScaleComboBox.SelectedValue) != 1.0) { ModelModifiers.ScaleModel(_newModel, (double)ScaleComboBox.SelectedValue); } DialogResult = true; }
void applyTricks(GameObject res, ModelModifiers mods) { MeshRenderer ren = res.GetComponent <MeshRenderer>(); if (mods._TrickFlags.HasFlag(TrickFlags.CameraFace)) { res.AddComponent <AlwaysFaceCamera>(); } if (mods._TrickFlags.HasFlag(TrickFlags.LightFace)) { res.AddComponent <AlwaysFaceSun>(); } }
/// <summary> /// Applies the deformer to a model /// </summary> /// <param name="model">The model being deformed</param> /// <param name="itemType">The item type of the model</param> /// <param name="currentRace">The current model race</param> /// <param name="targetRace">The target race to convert the model to</param> private void ApplyDeformers(TTModel model, string itemType, XivRace currentRace, XivRace targetRace) { try { // Current race is already parent node // Direct conversion // [ Current > (apply deform) > Target ] if (currentRace.IsDirectParentOf(targetRace)) { ModelModifiers.ApplyRacialDeform(model, targetRace); } // Target race is parent node of Current race // Convert to parent (invert deform) // [ Current > (apply inverse deform) > Target ] else if (targetRace.IsDirectParentOf(currentRace)) { ModelModifiers.ApplyRacialDeform(model, currentRace, true); } // Current race is not parent of Target Race and Current race has parent // Make a recursive call with the current races parent race // [ Current > (apply inverse deform) > Current.Parent > Recursive Call ] else if (currentRace.GetNode().Parent != null) { ModelModifiers.ApplyRacialDeform(model, currentRace, true); ApplyDeformers(model, itemType, currentRace.GetNode().Parent.Race, targetRace); } // Current race has no parent // Make a recursive call with the target races parent race // [ Target > (apply deform on Target.Parent) > Target.Parent > Recursive Call ] else { ModelModifiers.ApplyRacialDeform(model, targetRace.GetNode().Parent.Race); ApplyDeformers(model, itemType, targetRace.GetNode().Parent.Race, targetRace); } } catch (Exception ex) { // Show a warning that deforms are missing for the target race // This mostly happens with Face, Hair, Tails, Ears, and Female > Male deforms // The model is still added but no deforms are applied FlexibleMessageBox.Show(string.Format(UIMessages.MissingDeforms, targetRace.GetDisplayName(), itemType, ex.Message), UIMessages.MissingDeformsTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning); } }
/// <summary> /// Updates or Adds the Model to the viewport /// </summary> /// <param name="model">The TexTools Model</param> /// <param name="textureDataDictionary">The textures associated with the model</param> /// <param name="item">The item for the model</param> /// <param name="modelRace">The race of the model</param> /// <param name="targetRace">The target race the model should be</param> public void UpdateModel(TTModel model, Dictionary <int, ModelTextureData> textureDataDictionary, IItemModel item, XivRace modelRace, XivRace targetRace) { _targetRace = targetRace; var itemType = $"{item.PrimaryCategory}_{item.SecondaryCategory}"; // If target race is different than the model race Apply racial deforms if (modelRace != targetRace) { ApplyDeformers(model, itemType, modelRace, targetRace); } SharpDX.BoundingBox?boundingBox = null; ModelModifiers.CalculateTangents(model); // Remove any existing models of the same item type RemoveModel(itemType); var totalMeshCount = model.MeshGroups.Count; for (var i = 0; i < totalMeshCount; i++) { var meshGeometry3D = GetMeshGeometry(model, i); var textureData = textureDataDictionary[model.GetMaterialIndex(i)]; Stream diffuse = null, specular = null, normal = null, alpha = null, emissive = null; if (textureData.Diffuse != null && textureData.Diffuse.Length > 0) { using (var img = Image.LoadPixelData <Rgba32>(textureData.Diffuse, textureData.Width, textureData.Height)) { diffuse = new MemoryStream(); img.Save(diffuse, new PngEncoder()); } streamList.Add(diffuse); } if (textureData.Specular != null && textureData.Specular.Length > 0) { using (var img = Image.LoadPixelData <Rgba32>(textureData.Specular, textureData.Width, textureData.Height)) { specular = new MemoryStream(); img.Save(specular, new PngEncoder()); } streamList.Add(specular); } if (textureData.Normal != null && textureData.Normal.Length > 0) { using (var img = Image.LoadPixelData <Rgba32>(textureData.Normal, textureData.Width, textureData.Height)) { normal = new MemoryStream(); img.Save(normal, new PngEncoder()); } streamList.Add(normal); } if (textureData.Alpha != null && textureData.Alpha.Length > 0) { using (var img = Image.LoadPixelData <Rgba32>(textureData.Alpha, textureData.Width, textureData.Height)) { alpha = new MemoryStream(); img.Save(alpha, new PngEncoder()); } streamList.Add(alpha); } if (textureData.Emissive != null && textureData.Emissive.Length > 0) { using (var img = Image.LoadPixelData <Rgba32>(textureData.Emissive, textureData.Width, textureData.Height)) { emissive = new MemoryStream(); img.Save(emissive, new PngEncoder()); } streamList.Add(emissive); } var material = new PhongMaterial { DiffuseColor = PhongMaterials.ToColor(1, 1, 1, 1), SpecularShininess = 1f, DiffuseMap = diffuse, DiffuseAlphaMap = alpha, SpecularColorMap = specular, NormalMap = normal, EmissiveMap = emissive }; // Geometry that contains skeleton data var smgm3d = new CustomBoneSkinMeshGeometry3D { Geometry = meshGeometry3D, Material = material, ItemType = itemType, BoneMatrices = GetMatrices(targetRace), BoneList = model.Bones }; // Keep track of what bones are showing in the view foreach (var modelBone in model.Bones) { if (!shownBonesList.Contains(modelBone)) { shownBonesList.Add(modelBone); } } boundingBox = meshGeometry3D.Bound; smgm3d.CullMode = Properties.Settings.Default.Cull_Mode.Equals("None") ? CullMode.None : CullMode.Back; Models.Add(smgm3d); } SpecularShine = 1; var center = boundingBox.GetValueOrDefault().Center; _lightX = center.X; _lightY = center.Y; _lightZ = center.Z; Light3Direction = new Vector3D(_lightX, _lightY, _lightZ); Camera.UpDirection = new Vector3D(0, 1, 0); Camera.CameraInternal.PropertyChanged += CameraInternal_PropertyChanged; // Add the skeleton node for the target race AddSkeletonNode(targetRace); // Keep track of the models displayed in the viewport shownModels.Add(itemType, new DisplayedModelData { TtModel = model, ItemModel = item, ModelTextureData = textureDataDictionary }); }
/// <summary> /// Updates the model in the 3D viewport /// </summary> /// <param name="mdlData">The model data</param> /// <param name="textureDataDictionary">The texture dictionary for the model</param> public void UpdateModel(TTModel model, Dictionary <int, ModelTextureData> textureDataDictionary) { SharpDX.BoundingBox?boundingBox = null; ModelModifiers.CalculateTangents(model); var totalMeshCount = model.MeshGroups.Count; for (var i = 0; i < totalMeshCount; i++) { var meshGeometry3D = GetMeshGeometry(model, i); var textureData = textureDataDictionary[model.GetMaterialIndex(i)]; Stream diffuse = null, specular = null, normal = null, alpha = null, emissive = null; if (textureData.Diffuse != null && textureData.Diffuse.Length > 0) { using (var img = Image.LoadPixelData <Rgba32>(textureData.Diffuse, textureData.Width, textureData.Height)) { diffuse = new MemoryStream(); img.Save(diffuse, new PngEncoder()); } streamList.Add(diffuse); } if (textureData.Specular != null && textureData.Specular.Length > 0) { using (var img = Image.LoadPixelData <Rgba32>(textureData.Specular, textureData.Width, textureData.Height)) { specular = new MemoryStream(); img.Save(specular, new PngEncoder()); } streamList.Add(specular); } if (textureData.Normal != null && textureData.Normal.Length > 0) { using (var img = Image.LoadPixelData <Rgba32>(textureData.Normal, textureData.Width, textureData.Height)) { normal = new MemoryStream(); img.Save(normal, new PngEncoder()); } streamList.Add(normal); } if (textureData.Alpha != null && textureData.Alpha.Length > 0) { using (var img = Image.LoadPixelData <Rgba32>(textureData.Alpha, textureData.Width, textureData.Height)) { alpha = new MemoryStream(); img.Save(alpha, new PngEncoder()); } streamList.Add(alpha); } if (textureData.Emissive != null && textureData.Emissive.Length > 0) { using (var img = Image.LoadPixelData <Rgba32>(textureData.Emissive, textureData.Width, textureData.Height)) { emissive = new MemoryStream(); img.Save(emissive, new PngEncoder()); } streamList.Add(emissive); } var material = new PhongMaterial { DiffuseColor = PhongMaterials.ToColor(1, 1, 1, 1), SpecularShininess = 1f, DiffuseMap = diffuse, DiffuseAlphaMap = alpha, SpecularColorMap = specular, NormalMap = normal, EmissiveMap = emissive }; var mgm3d = new CustomMeshGeometryModel3D { Geometry = meshGeometry3D, Material = material //, //IsBody = mdlData.LoDList[0].MeshDataList[i].IsBody }; boundingBox = meshGeometry3D.Bound; mgm3d.CullMode = Properties.Settings.Default.Cull_Mode.Equals("None") ? CullMode.None : CullMode.Back; Models.Add(mgm3d); } SpecularShine = 1; var center = boundingBox.GetValueOrDefault().Center; _lightX = center.X; _lightY = center.Y; _lightZ = center.Z; Light3Direction = new Vector3D(_lightX, _lightY, _lightZ); Camera.UpDirection = new Vector3D(0, 1, 0); Camera.CameraInternal.PropertyChanged += CameraInternal_PropertyChanged; }
private static void convertMaterials(MeshRenderer ren, Model mdl, GameObject tgt) { ModelModifiers model_trick = mdl.trck_node; GeometryModifiersData geom_trick = mdl.src_mod; RuntimeData rd = RuntimeData.get(); MaterialDescriptor descriptor; string model_base_name = mdl.name.Split(new[] { "__" }, StringSplitOptions.None)[0]; string mesh_path = mdl.geoset.full_geo_path; int obj_lib_idx = mesh_path.IndexOf("object_library"); if (obj_lib_idx != -1) { mesh_path = "Assets/Materials/" + mesh_path.Substring(obj_lib_idx); } string material_base_path = mesh_path + "/" + Path.GetFileNameWithoutExtension(mdl.name); if (mdl.name == "Crate1_med_Wood__TintCrates") { Debug.LogFormat("Crate {0}", mdl.BlendMode.ToString()); } if (model_trick != null && model_trick._TrickFlags.HasFlag(TrickFlags.ColorOnly)) { // result = result.Clone(result.GetName()+"Colored"); // result.SetShaderParameter("MatDiffColor",Vector4(1.0, 1.0, 0.2f, 1.0f)); } // Select material based on the model blend state // Modify material based on the applied model tricks Color onlyColor; Color tint1 = new Color(1, 1, 1, 1); // Shader Constant 0 Color tint2 = new Color(1, 1, 1, 1); // Shader Constant 1 float alphaRef = 0.0f; descriptor.depthWrite = true; descriptor.isAdditive = false; descriptor.targetCulling = UnityEngine.Rendering.CullMode.Back; descriptor.depthTest = CompareFunction.LessEqual; descriptor.targetCulling = UnityEngine.Rendering.CullMode.Back; string shader_to_use = ""; if (null != model_trick && model_trick._TrickFlags != 0) { var tflags = model_trick._TrickFlags; if (tflags.HasFlag(TrickFlags.Additive)) { descriptor.isAdditive = true; } if (tflags.HasFlag(TrickFlags.ColorOnly)) { onlyColor = model_trick.TintColor0; } if (tflags.HasFlag(TrickFlags.DoubleSided)) { descriptor.targetCulling = UnityEngine.Rendering.CullMode.Off; } if (tflags.HasFlag(TrickFlags.NoZTest)) { // simulate disabled Z test descriptor.depthTest = CompareFunction.Always; //depthTest = CompareFunction.Always; descriptor.depthWrite = false; } if (tflags.HasFlag(TrickFlags.NoZWrite)) { descriptor.depthWrite = false; } if (tflags.HasFlag(TrickFlags.SetColor)) { tint1 = model_trick.TintColor0; tint2 = model_trick.TintColor1; tint1.a = 1.0f; tint2.a = 1.0f; } if (tflags.HasFlag(TrickFlags.ReflectTex0) || tflags.HasFlag(TrickFlags.ReflectTex1)) { shader_to_use = "Custom/ReflectGen"; if (mdl.flags.HasFlag(ModelFlags.OBJ_CUBEMAP)) { Debug.Log("Unhandled Cubemap"); } } if (tflags.HasFlag(TrickFlags.AlphaRef)) { //qDebug() << "Unhandled alpha ref"; alphaRef = geom_trick.AlphaRef; } if (tflags.HasFlag(TrickFlags.TexBias)) { Debug.Log("Unhandled TexBias"); } } CompareFunction depthTest = CompareFunction.LessEqual; TextureWrapper whitetex = GeoSet.loadTexHeader("white"); var pixel_defines = new List <string>(); pixel_defines.Add("DIFFMAP"); pixel_defines.Add("ALPHAMASK"); switch (mdl.BlendMode) { case CoHBlendMode.MULTIPLY: pixel_defines.Add("COH_MULTIPLY"); break; case CoHBlendMode.MULTIPLY_REG: if (!descriptor.depthWrite && descriptor.isAdditive) { descriptor.targetCulling = UnityEngine.Rendering.CullMode.Off; } pixel_defines.Add("COH_MULTIPLY"); break; case CoHBlendMode.COLORBLEND_DUAL: pixel_defines.Add("COH_COLOR_BLEND_DUAL"); break; case CoHBlendMode.ADDGLOW: pixel_defines.Add("COH_ADDGLOW"); break; case CoHBlendMode.ALPHADETAIL: pixel_defines.Add("COH_ALPHADETAIL"); break; case CoHBlendMode.BUMPMAP_MULTIPLY: pixel_defines.Add("COH_MULTIPLY"); break; case CoHBlendMode.BUMPMAP_COLORBLEND_DUAL: pixel_defines.Add("COH_COLOR_BLEND_DUAL"); break; } if (mdl.flags.HasFlag(ModelFlags.OBJ_TREE)) { shader_to_use = "CoH/Vegetation"; alphaRef = 0.4f; tint1.a = 254.0f / 255.0f; } Material[] materials = new Material[mdl.texture_bind_info.Count]; int idx = 0; Tools.EnsureDirectoryExists(material_base_path); var sup = tgt.GetComponent <ModelNodeMods>(); sup.GeomTricks = geom_trick; VBOPointers vbo = mdl.vbo; Shader selected = null; if (descriptor.isAdditive) { if (shader_to_use == "Custom/ReflectGen") { selected = Shader.Find("Custom/ReflectGen"); } else { selected = Shader.Find("Unlit/Additive"); } } else { if (!String.IsNullOrEmpty(shader_to_use)) { selected = Shader.Find(shader_to_use); } else { selected = Shader.Find("CoH/CoHMult"); } } sup.TexWrappers = vbo.assigned_textures; foreach (TextureWrapper wrap in vbo.assigned_textures) { string path_material_name = String.Format("{0}/{1}_{2}.mat", material_base_path, idx, descriptor.GetHashCode()); Material available = AssetDatabase.LoadAssetAtPath <Material>(path_material_name); if (available == null) { if (wrap != null) { TextureWrapper detail = null; if (!String.IsNullOrEmpty(wrap.detailname)) { detail = GeoSet.loadTexHeader(wrap.detailname); } Material mat = new Material(selected); mat.enableInstancing = true; mat.SetColor("_Color", tint1); mat.SetColor("_Color2", tint2); mat.SetFloat("_AlphaRef", alphaRef); mat.SetInt("_CoHMod", (int)mdl.BlendMode); mat.SetTexture("_MainTex", wrap.tex); if (detail != null) { mat.SetTexture("_Detail", detail.tex); } mat.SetInt("_ZWrite", descriptor.depthWrite ? 1 : 0); mat.SetInt(ZTest, (int)descriptor.depthTest); mat.SetInt(CullMode, (int)descriptor.targetCulling); mat.SetTextureScale(MainTex, wrap.scaleUV1); mat.SetTextureScale(Detail, wrap.scaleUV0); AssetDatabase.CreateAsset(mat, path_material_name); AssetDatabase.SaveAssets(); available = AssetDatabase.LoadAssetAtPath <Material>(path_material_name); } } materials[idx++] = available; } if (mdl.flags.HasFlag(ModelFlags.OBJ_HIDE)) { ren.enabled = false; } ren.sharedMaterials = materials; }
private static void convertModel(SceneNode n, GameObject res, bool convert_editor_markers) { string mesh_path; string model_path; int obj_lib_idx; Model mdl = n.m_model; ModelModifiers model_trick = mdl.trck_node; mesh_path = mdl.geoset.full_geo_path; obj_lib_idx = mesh_path.IndexOf("object_library"); if (obj_lib_idx != -1) { mesh_path = "Assets/Meshes/" + mesh_path.Substring(obj_lib_idx); } model_path = GetSafeFilename(mesh_path + "/" + Path.GetFileNameWithoutExtension(mdl.name) + ".asset"); MeshFilter mf = res.AddComponent <MeshFilter>(); MeshRenderer ren = res.AddComponent <MeshRenderer>(); var sup = res.AddComponent <ModelNodeMods>(); if (model_trick != null) { sup.ModelMod = model_trick; } mf.sharedMesh = AssetDatabase.LoadAssetAtPath <Mesh>(model_path); if (model_trick != null) { ren.shadowCastingMode = ShadowCastingMode.Off; if (model_trick._TrickFlags.HasFlag(TrickFlags.NoDraw)) { ren.receiveShadows = false; res.tag = "NoDraw"; res.layer = 9; } if (model_trick._TrickFlags.HasFlag(TrickFlags.EditorVisible)) { ren.receiveShadows = false; res.layer = 9; } if (model_trick._TrickFlags.HasFlag(TrickFlags.CastShadow)) { ren.receiveShadows = false; ren.shadowCastingMode = ShadowCastingMode.ShadowsOnly; } if (model_trick._TrickFlags.HasFlag(TrickFlags.ParticleSys)) { Debug.Log("Not converting particle sys:" + mdl.name); return; } } if (!mdl.geoset.data_loaded) { mdl.geoset.LoadData(); if (mdl.geoset.subs.Count != 0) { mdl.geoset.createEngineModelsFromPrefabSet(); } } if (mf.sharedMesh == null) { UnityModel res_static; RuntimeData rd = RuntimeData.get(); if (!rd.s_coh_model_to_engine_model.TryGetValue(mdl, out res_static)) { return; } if (res_static == null) { return; } SEGSRuntime.Tools.EnsureDirectoryExists(mesh_path); AssetDatabase.CreateAsset(res_static.m_mesh, model_path); AssetDatabase.SaveAssets(); mf.sharedMesh = AssetDatabase.LoadAssetAtPath <Mesh>(model_path); } convertMaterials(ren, mdl, res); }
/// <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); }
/// <summary> /// Saves the TTModel to a .DB file for use with external importers/exporters. /// </summary> public static void ToDb(TTModel model, FileInfo file, bool useAllBones, Action <bool, string> loggingFunction = null) { if (loggingFunction == null) { loggingFunction = ModelModifiers.NoOp; } file.Delete(); string directory = file.DirectoryName; ModelModifiers.MakeExportReady(model, loggingFunction); string connectionString = "Data Source=" + file + ";"; try { List <string> bones = useAllBones ? null : model.Bones; // TODO: Get bones for skeletons Console.WriteLine("WARNING: NO SKELETONS"); Dictionary <string, SkeletonData> boneDict = new Dictionary <string, SkeletonData>(); ////model.ResolveBoneHeirarchy(null, TTModel.XivRace.All_Races, bones, loggingFunction); const string creationScript = "CreateImportDB.sql"; // Spawn a DB connection to do the raw queries. // Using statements help ensure we don't accidentally leave any connections open and lock the file handle. using (SqliteConnection db = new SqliteConnection(connectionString)) { db.Open(); // Create the DB string[] lines = File.ReadAllLines("Resources\\SQL\\" + creationScript); string sqlCmd = string.Join("\n", lines); using (SqliteCommand cmd = new SqliteCommand(sqlCmd, db)) { cmd.ExecuteScalar(); } // Write the Data. using (SqliteTransaction transaction = db.BeginTransaction()) { // Metadata. string query = @"insert into meta (key, value) values ($key, $value)"; using (SqliteCommand cmd = new SqliteCommand(query, db)) { // FFXIV stores stuff in Meters. cmd.Parameters.AddWithValue("key", "unit"); cmd.Parameters.AddWithValue("value", "meter"); cmd.ExecuteScalar(); // Application that created the db. cmd.Parameters.AddWithValue("key", "application"); cmd.Parameters.AddWithValue("value", "FfxivResourceConverter"); cmd.ExecuteScalar(); cmd.Parameters.AddWithValue("key", "version"); cmd.Parameters.AddWithValue("value", typeof(TTModelDb).Assembly.GetName().Version); cmd.ExecuteScalar(); // Axis information cmd.Parameters.AddWithValue("key", "up"); cmd.Parameters.AddWithValue("value", "y"); cmd.ExecuteScalar(); cmd.Parameters.AddWithValue("key", "front"); cmd.Parameters.AddWithValue("value", "z"); cmd.ExecuteScalar(); cmd.Parameters.AddWithValue("key", "handedness"); cmd.Parameters.AddWithValue("value", "r"); cmd.ExecuteScalar(); // FFXIV stores stuff in Meters. cmd.Parameters.AddWithValue("key", "root_name"); cmd.Parameters.AddWithValue("value", Path.GetFileNameWithoutExtension(model.Source)); cmd.ExecuteScalar(); } // Skeleton query = @"insert into skeleton (name, parent, matrix_0, matrix_1, matrix_2, matrix_3, matrix_4, matrix_5, matrix_6, matrix_7, matrix_8, matrix_9, matrix_10, matrix_11, matrix_12, matrix_13, matrix_14, matrix_15) values ($name, $parent, $matrix_0, $matrix_1, $matrix_2, $matrix_3, $matrix_4, $matrix_5, $matrix_6, $matrix_7, $matrix_8, $matrix_9, $matrix_10, $matrix_11, $matrix_12, $matrix_13, $matrix_14, $matrix_15);" ; using (SqliteCommand cmd = new SqliteCommand(query, db)) { foreach (KeyValuePair <string, SkeletonData> b in boneDict) { KeyValuePair <string, SkeletonData> parent = boneDict.FirstOrDefault(x => x.Value.BoneNumber == b.Value.BoneParent); string parentName = parent.Key == null ? null : parent.Key; cmd.Parameters.AddWithValue("name", b.Value.BoneName); cmd.Parameters.AddWithValue("parent", parentName); for (int i = 0; i < 16; i++) { cmd.Parameters.AddWithValue("matrix_" + i.ToString(), b.Value.PoseMatrix[i]); } cmd.ExecuteScalar(); } } int modelIdx = 0; List <string> models = new List <string>() { Path.GetFileNameWithoutExtension(model.Source) }; foreach (string mdl in models) { query = @"insert into models (model, name) values ($model, $name);"; using (SqliteCommand cmd = new SqliteCommand(query, db)) { cmd.Parameters.AddWithValue("model", modelIdx); cmd.Parameters.AddWithValue("name", mdl); cmd.ExecuteScalar(); } modelIdx++; } int matIdx = 0; foreach (string material in model.Materials) { // Materials query = @"insert into materials (material_id, name, diffuse, normal, specular, opacity, emissive) values ($material_id, $name, $diffuse, $normal, $specular, $opacity, $emissive);"; using (SqliteCommand cmd = new SqliteCommand(query, db)) { string mtrl_prefix = directory + "\\" + Path.GetFileNameWithoutExtension(material.Substring(1)) + "_"; string mtrl_suffix = ".png"; string name = material; try { name = Path.GetFileNameWithoutExtension(material); } catch { } cmd.Parameters.AddWithValue("material_id", matIdx); cmd.Parameters.AddWithValue("name", name); cmd.Parameters.AddWithValue("diffuse", mtrl_prefix + "d" + mtrl_suffix); cmd.Parameters.AddWithValue("normal", mtrl_prefix + "n" + mtrl_suffix); cmd.Parameters.AddWithValue("specular", mtrl_prefix + "s" + mtrl_suffix); cmd.Parameters.AddWithValue("emissive", mtrl_prefix + "e" + mtrl_suffix); cmd.Parameters.AddWithValue("opacity", mtrl_prefix + "o" + mtrl_suffix); cmd.ExecuteScalar(); } matIdx++; } int meshIdx = 0; foreach (TTMeshGroup m in model.MeshGroups) { // Bones query = @"insert into bones (mesh, bone_id, name) values ($mesh, $bone_id, $name);"; int bIdx = 0; foreach (string b in m.Bones) { using (SqliteCommand cmd = new SqliteCommand(query, db)) { cmd.Parameters.AddWithValue("name", b); cmd.Parameters.AddWithValue("bone_id", bIdx); cmd.Parameters.AddWithValue("parent_id", null); cmd.Parameters.AddWithValue("mesh", meshIdx); cmd.ExecuteScalar(); } bIdx++; } // Groups query = @"insert into meshes (mesh, name, material_id, model) values ($mesh, $name, $material_id, $model);"; using (SqliteCommand cmd = new SqliteCommand(query, db)) { cmd.Parameters.AddWithValue("name", m.Name); cmd.Parameters.AddWithValue("mesh", meshIdx); // This is always 0 for now. Support element for Liinko's work on multi-model export. cmd.Parameters.AddWithValue("model", 0); cmd.Parameters.AddWithValue("material_id", model.GetMaterialIndex(meshIdx)); cmd.ExecuteScalar(); } // Parts int partIdx = 0; foreach (TTMeshPart p in m.Parts) { // Parts query = @"insert into parts (mesh, part, name) values ($mesh, $part, $name);"; using (SqliteCommand cmd = new SqliteCommand(query, db)) { cmd.Parameters.AddWithValue("name", p.Name); cmd.Parameters.AddWithValue("part", partIdx); cmd.Parameters.AddWithValue("mesh", meshIdx); cmd.ExecuteScalar(); } // Vertices int vIdx = 0; foreach (TTVertex v in p.Vertices) { query = @"insert into vertices ( mesh, part, vertex_id, position_x, position_y, position_z, normal_x, normal_y, normal_z, color_r, color_g, color_b, color_a, uv_1_u, uv_1_v, uv_2_u, uv_2_v, bone_1_id, bone_1_weight, bone_2_id, bone_2_weight, bone_3_id, bone_3_weight, bone_4_id, bone_4_weight) values ($mesh, $part, $vertex_id, $position_x, $position_y, $position_z, $normal_x, $normal_y, $normal_z, $color_r, $color_g, $color_b, $color_a, $uv_1_u, $uv_1_v, $uv_2_u, $uv_2_v, $bone_1_id, $bone_1_weight, $bone_2_id, $bone_2_weight, $bone_3_id, $bone_3_weight, $bone_4_id, $bone_4_weight);" ; using (SqliteCommand cmd = new SqliteCommand(query, db)) { cmd.Parameters.AddWithValue("part", partIdx); cmd.Parameters.AddWithValue("mesh", meshIdx); cmd.Parameters.AddWithValue("vertex_id", vIdx); cmd.Parameters.AddWithValue("position_x", v.Position.X); cmd.Parameters.AddWithValue("position_y", v.Position.Y); cmd.Parameters.AddWithValue("position_z", v.Position.Z); cmd.Parameters.AddWithValue("normal_x", v.Normal.X); cmd.Parameters.AddWithValue("normal_y", v.Normal.Y); cmd.Parameters.AddWithValue("normal_z", v.Normal.Z); cmd.Parameters.AddWithValue("color_r", v.VertexColor[0] / 255f); cmd.Parameters.AddWithValue("color_g", v.VertexColor[1] / 255f); cmd.Parameters.AddWithValue("color_b", v.VertexColor[2] / 255f); cmd.Parameters.AddWithValue("color_a", v.VertexColor[3] / 255f); cmd.Parameters.AddWithValue("uv_1_u", v.UV1.X); cmd.Parameters.AddWithValue("uv_1_v", v.UV1.Y); cmd.Parameters.AddWithValue("uv_2_u", v.UV2.X); cmd.Parameters.AddWithValue("uv_2_v", v.UV2.Y); cmd.Parameters.AddWithValue("bone_1_id", v.BoneIds[0]); cmd.Parameters.AddWithValue("bone_1_weight", v.Weights[0] / 255f); cmd.Parameters.AddWithValue("bone_2_id", v.BoneIds[1]); cmd.Parameters.AddWithValue("bone_2_weight", v.Weights[1] / 255f); cmd.Parameters.AddWithValue("bone_3_id", v.BoneIds[2]); cmd.Parameters.AddWithValue("bone_3_weight", v.Weights[2] / 255f); cmd.Parameters.AddWithValue("bone_4_id", v.BoneIds[3]); cmd.Parameters.AddWithValue("bone_4_weight", v.Weights[3] / 255f); cmd.ExecuteScalar(); vIdx++; } } // Indices for (int i = 0; i < p.TriangleIndices.Count; i++) { query = @"insert into indices (mesh, part, index_id, vertex_id) values ($mesh, $part, $index_id, $vertex_id);"; using (SqliteCommand cmd = new SqliteCommand(query, db)) { cmd.Parameters.AddWithValue("part", partIdx); cmd.Parameters.AddWithValue("mesh", meshIdx); cmd.Parameters.AddWithValue("index_id", i); cmd.Parameters.AddWithValue("vertex_id", p.TriangleIndices[i]); cmd.ExecuteScalar(); } } // Shape Parts foreach (KeyValuePair <string, TTShapePart> shpKv in p.ShapeParts) { if (!shpKv.Key.StartsWith("shp_")) { continue; } TTShapePart shp = shpKv.Value; query = @"insert into shape_vertices ( mesh, part, shape, vertex_id, position_x, position_y, position_z) values($mesh, $part, $shape, $vertex_id, $position_x, $position_y, $position_z);" ; using (SqliteCommand cmd = new SqliteCommand(query, db)) { foreach (KeyValuePair <int, int> vKv in shp.VertexReplacements) { TTVertex v = shp.Vertices[vKv.Value]; cmd.Parameters.AddWithValue("part", partIdx); cmd.Parameters.AddWithValue("mesh", meshIdx); cmd.Parameters.AddWithValue("shape", shpKv.Key); cmd.Parameters.AddWithValue("vertex_id", vKv.Key); cmd.Parameters.AddWithValue("position_x", v.Position.X); cmd.Parameters.AddWithValue("position_y", v.Position.Y); cmd.Parameters.AddWithValue("position_z", v.Position.Z); cmd.ExecuteScalar(); vIdx++; } } } partIdx++; } meshIdx++; } transaction.Commit(); } } } catch (Exception) { ModelModifiers.MakeImportReady(model, loggingFunction); throw; } // Undo the export ready at the start. ModelModifiers.MakeImportReady(model, loggingFunction); }