/// <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); } }
static void DoSingleExportSet(XivMdl mdlData, Dictionary <string, ModelTextureData> textures, ExportSetMetadata set) { TTModel model = TTModel.FromRaw(mdlData); foreach (var meshData in model.MeshGroups) { var modelMetadata = new ExportModelMetadata(); // Obj var objString = _ttObj.ExportObj(meshData); var objBytes = System.Text.Encoding.ASCII.GetBytes(objString); modelMetadata.Obj = _repo.Write(".obj", objBytes); // Textures try { var textureData = textures[meshData.Material]; var pixelSettings = new PixelReadSettings(textureData.Width, textureData.Height, StorageType.Char, PixelMapping.RGBA); modelMetadata.Alpha = _repo.Write(textureData.Alpha, pixelSettings, false); modelMetadata.Diffuse = _repo.Write(textureData.Diffuse, pixelSettings, true); modelMetadata.Emissive = _repo.Write(textureData.Emissive, pixelSettings, false); modelMetadata.Normal = _repo.Write(textureData.Normal, pixelSettings, true); modelMetadata.Specular = _repo.Write(textureData.Specular, pixelSettings, false); set.Models.Add(modelMetadata); } catch (Exception e) { Console.WriteLine(e.Message); continue; } } }
public ApplyShapesView(TTModel model, List <string> currentShapes) { InitializeComponent(); AllShapes = model.ShapeNames; var count = 0; foreach (var shape in AllShapes) { if (shape == "original") { continue; } if (count % 2 == 0) { ShapesGrid.Rows++; } var cb = new CheckBox(); // Annoying replacement for the fact that WPF eats the first underscore as a special character. cb.Content = shape.Replace("_", "__"); cb.Margin = new Thickness(10, 5, 10, 5); cb.IsChecked = currentShapes.Contains(shape); ShapeBoxes.Add(shape, cb); ShapesGrid.Children.Add(cb); count++; } }
private MeshGeometry3D GetMeshGeometry(TTModel model, int meshGroupId) { var group = model.MeshGroups[meshGroupId]; var mg = new MeshGeometry3D { Positions = new Vector3Collection((int)group.VertexCount), Normals = new Vector3Collection((int)group.VertexCount), Colors = new Color4Collection((int)group.VertexCount), TextureCoordinates = new Vector2Collection((int)group.VertexCount), BiTangents = new Vector3Collection((int)group.VertexCount), Tangents = new Vector3Collection((int)group.VertexCount), Indices = new IntCollection((int)group.IndexCount) }; var indexCount = 0; var vertCount = 0; for (int mi = 0; mi < meshGroupId; mi++) { var g = model.MeshGroups[mi]; //vertCount += (int) g.VertexCount; //indexCount += (int)g.IndexCount; } foreach (var p in group.Parts) { foreach (var v in p.Vertices) { // I don't think our current shader actually utilizes this data anyways // but may as well include it correctly. var color = new Color4(); color.Red = v.VertexColor[0] / 255f; color.Green = v.VertexColor[1] / 255f; color.Blue = v.VertexColor[2] / 255f; color.Alpha = v.VertexColor[3] / 255f; mg.Positions.Add(v.Position); mg.Normals.Add(v.Normal); mg.TextureCoordinates.Add(v.UV1); mg.Colors.Add(color); mg.BiTangents.Add(v.Binormal); mg.Tangents.Add(v.Tangent); } foreach (var vertexId in p.TriangleIndices) { // Have to bump these to account for merging the lists together. mg.Indices.Add(vertCount + vertexId); } vertCount += p.Vertices.Count; indexCount += p.TriangleIndices.Count; } return(mg); }
/// <summary> /// Adds the model to the viewport /// </summary> /// <param name="ttModel">The model to add</param> /// <param name="materialDictionary">The dictionary of texture data for the model</param> /// <param name="item">The item associated with the model</param> /// <param name="race">The race of the model</param> public async Task AddModel(TTModel ttModel, Dictionary <int, ModelTextureData> materialDictionary, IItemModel item, XivRace race) { try { _fmvm.AddModelToView(ttModel, materialDictionary, item, race); } catch (Exception ex) { FlexibleMessageBox.Show(ex.Message, UIMessages.ModelAddErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); } }
/// <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> /// Adds the model to the viewport /// </summary> /// <param name="ttModel">The model to add</param> /// <param name="materialDictionary">The dictionary of texture data for the model</param> /// <param name="item">The item associated with the model</param> /// <param name="race">The race of the model</param> public async Task AddModel(TTModel ttModel, Dictionary <int, ModelTextureData> materialDictionary, IItemModel item, XivRace race) { // Because the full model is skinned, it requires the bones to exist so we check them here var sklb = new Sklb(_gameDirectory); var skel = await sklb.CreateParsedSkelFile(ttModel.Source); // If we have weights, but can't find a skel, bad times. if (skel == null) { throw new InvalidDataException("Unable to resolve model skeleton."); } try { _fmvm.AddModelToView(ttModel, materialDictionary, item, race); } catch (Exception ex) { FlexibleMessageBox.Show(ex.Message, UIMessages.ModelAddErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); } }
/// <summary> /// This is called by the importer when the TTModel has been populated, but /// before it is injected into the XIV files. This lets us step in and do whatever /// other manipulations we want on it. /// </summary> /// <param name="model"></param> /// <returns></returns> private async Task <bool> IntermediateStep(TTModel newModel, TTModel oldModel) { var result = false; await _view.Dispatcher.BeginInvoke((ThreadStart) delegate() { try { var editorWindow = new ImportModelEditView(newModel, oldModel) { Owner = _view }; result = editorWindow.ShowDialog() == true ? true : false; } catch (Exception Ex) { throw Ex; } }); return(result); }
/// <summary> /// Exports each mesh as an obj file /// </summary> public void ExportObj(TTModel model, string path) { var meshGroups = model.MeshGroups; Directory.CreateDirectory(Path.GetDirectoryName(path)); try { var meshNum = 0; foreach (var mesh in meshGroups) { var modelName = $"{Path.GetFileNameWithoutExtension(path)}_{meshNum}.obj"; var savePath = Path.GetDirectoryName(path) + "\\" + modelName; meshNum++; File.WriteAllText(savePath, ExportObj(mesh)); } } catch (Exception ex) { throw ex; } }
/// <summary> /// Creates the composite bone dictionary for all the models. /// </summary> /// <returns></returns> private Dictionary <string, SkeletonData> GetBoneDictionary(XivRace race) { // Just build a quick and dirty temp model we can use to call the full bone heirarchy function. var tempModel = new TTModel(); var models = new HashSet <string>(); foreach (var kv in shownModels) { models.Add(kv.Value.TtModel.Source); } var boneDict = TTModel.ResolveFullBoneHeirarchy(race, models.ToList()); // Fill in any missing bones that we couldn't otherwise figure out, if possible. var missingBones = new List <string>(); var boneList = tempModel.Bones; foreach (var bone in boneList) { if (!boneDict.ContainsKey(bone)) { // Create a dummy bone for anything missing. boneDict.Add(bone, new SkeletonData() { BoneName = bone, BoneParent = 0, BoneNumber = boneDict.Select(x => x.Value.BoneNumber).Max() + 1, InversePoseMatrix = Matrix.Identity.ToArray(), PoseMatrix = Matrix.Identity.ToArray() }); } } return(boneDict); }
/// <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> /// Gets the Mesh Geometry /// </summary> /// <remarks> /// This is mostly the same as the single model viewport but contains bone data /// </remarks> /// <param name="model">The model to get the geometry from</param> /// <param name="meshGroupId">The mesh group ID</param> /// <returns>The Skinned Mesh Geometry</returns> private BoneSkinnedMeshGeometry3D GetMeshGeometry(TTModel model, int meshGroupId) { var group = model.MeshGroups[meshGroupId]; var mg = new BoneSkinnedMeshGeometry3D { Positions = new Vector3Collection((int)group.VertexCount), Normals = new Vector3Collection((int)group.VertexCount), Colors = new Color4Collection((int)group.VertexCount), TextureCoordinates = new Vector2Collection((int)group.VertexCount), BiTangents = new Vector3Collection((int)group.VertexCount), Tangents = new Vector3Collection((int)group.VertexCount), Indices = new IntCollection((int)group.IndexCount), VertexBoneIds = new List <BoneIds>((int)group.IndexCount) }; var indexCount = 0; var vertCount = 0; foreach (var p in group.Parts) { foreach (var v in p.Vertices) { // I don't think our current shader actually utilizes this data anyways // but may as well include it correctly. var color = new Color4(); color.Red = v.VertexColor[0] / 255f; color.Green = v.VertexColor[1] / 255f; color.Blue = v.VertexColor[2] / 255f; color.Alpha = v.VertexColor[3] / 255f; mg.Positions.Add(v.Position); mg.Normals.Add(v.Normal); mg.TextureCoordinates.Add(v.UV1); mg.Colors.Add(color); mg.BiTangents.Add(v.Binormal); mg.Tangents.Add(v.Tangent); } foreach (var vertexId in p.TriangleIndices) { // Get the bone indices and weights for current index var boneIndices = p.Vertices[vertexId].BoneIds; var boneWeights = p.Vertices[vertexId].Weights; var bw1 = boneWeights[0] / 255f; var bw2 = boneWeights[1] / 255f; var bw3 = boneWeights[2] / 255f; var bw4 = boneWeights[3] / 255f; // Add BoneIds to mesh geometry mg.VertexBoneIds.Add(new BoneIds { Bone1 = boneIndices[0], Bone2 = boneIndices[1], Bone3 = boneIndices[2], Bone4 = boneIndices[3], Weights = new Vector4(bw1, bw2, bw3, bw4) }); // Have to bump these to account for merging the lists together. mg.Indices.Add(vertCount + vertexId); } vertCount += p.Vertices.Count; indexCount += p.TriangleIndices.Count; } return(mg); }
public ImportModelEditView(TTModel newModel, TTModel oldModel) { InitializeComponent(); _newModel = newModel; _oldModel = oldModel; MeshNumberBox.Items.Clear(); PartNumberBox.Items.Clear(); ScaleComboBox.Items.Clear(); var itemName = Path.GetFileNameWithoutExtension(oldModel.Source); for (var mIdx = 0; mIdx < _newModel.MeshGroups.Count; mIdx++) { var m = _newModel.MeshGroups[mIdx]; if (m.Name == null) { MeshSource.Add(new KeyValuePair <int, string>(mIdx, "#" + mIdx.ToString() + ": " + "Unknown")); } else { var name = m.Name.Replace(itemName, ""); name = name.Trim(); MeshSource.Add(new KeyValuePair <int, string>(mIdx, "#" + mIdx.ToString() + ": " + name)); } } SizeMultiplierSource.Add(new KeyValuePair <double, string>(1.0D, "1x")); SizeMultiplierSource.Add(new KeyValuePair <double, string>(10.0D, "10x")); SizeMultiplierSource.Add(new KeyValuePair <double, string>(100.0D, "100x")); SizeMultiplierSource.Add(new KeyValuePair <double, string>(.1D, "0.1x")); SizeMultiplierSource.Add(new KeyValuePair <double, string>(.01D, "0.01x")); SizeMultiplierSource.Add(new KeyValuePair <double, string>(0.03937007874D, "0.039x (Legacy Fix)")); MeshNumberBox.ItemsSource = MeshSource; MeshNumberBox.DisplayMemberPath = "Value"; MeshNumberBox.SelectedValuePath = "Key"; PartNumberBox.ItemsSource = PartSource; PartNumberBox.DisplayMemberPath = "Value"; PartNumberBox.SelectedValuePath = "Key"; MaterialSelectorBox.ItemsSource = MaterialsSource; MaterialSelectorBox.DisplayMemberPath = "Value"; MaterialSelectorBox.SelectedValuePath = "Key"; ShapesListBox.ItemsSource = ShapesSource; ShapesListBox.DisplayMemberPath = "Value"; ShapesListBox.SelectedValuePath = "Key"; AttributesListBox.ItemsSource = AttributesSource; AttributesListBox.DisplayMemberPath = "Value"; AttributesListBox.SelectedValuePath = "Key"; AddAttributeBox.ItemsSource = AllAttributesSource; AddAttributeBox.DisplayMemberPath = "Value"; AddAttributeBox.SelectedValuePath = "Key"; ScaleComboBox.ItemsSource = SizeMultiplierSource; ScaleComboBox.DisplayMemberPath = "Value"; ScaleComboBox.SelectedValuePath = "Key"; ScaleComboBox.SelectedValue = 1.0f; _viewModel = new ImportModelEditViewModel(this, _newModel, _oldModel); this.DataContext = _viewModel; }
public ImportModelEditViewModel(ImportModelEditView view, TTModel newModel, TTModel oldModel) { _view = view; _newModel = newModel; _oldModel = oldModel; // Get all the materials available. // Merge all the default skin materials together, since FFXIV auto-handles them anyways. foreach (var m in _newModel.MeshGroups) { if (m.Material == null) { // Sanity assurance. m.Material = _newModel.MeshGroups[0].Material; } var result = DefaultSkinRegex.Match(m.Material); if (result.Success) { m.Material = SkinMaterial; } } // Calculate the model bounding box sizes. float minX = 9999.0f, minY = 9999.0f, minZ = 9999.0f; float maxX = -9999.0f, maxY = -9999.0f, maxZ = -9999.0f; foreach (var m in _newModel.MeshGroups) { foreach (var p in m.Parts) { foreach (var v in p.Vertices) { minX = minX < v.Position.X ? minX : v.Position.X; minY = minY < v.Position.Y ? minY : v.Position.Y; minZ = minZ < v.Position.Z ? minZ : v.Position.Z; maxX = maxX > v.Position.X ? maxX : v.Position.X; maxY = maxY > v.Position.Y ? maxY : v.Position.Y; maxZ = maxZ > v.Position.Z ? maxZ : v.Position.Z; } } } Vector3 min = new Vector3(minX, minY, minZ); Vector3 max = new Vector3(maxX, maxY, maxZ); NewModelSize = Vector3.Distance(min, max); minX = 9999.0f; minY = 9999.0f; minZ = 9999.0f; maxX = -9999.0f; maxY = -9999.0f; maxZ = -9999.0f; foreach (var m in _oldModel.MeshGroups) { foreach (var p in m.Parts) { foreach (var v in p.Vertices) { minX = minX < v.Position.X ? minX : v.Position.X; minY = minY < v.Position.Y ? minY : v.Position.Y; minZ = minZ < v.Position.Z ? minZ : v.Position.Z; maxX = maxX > v.Position.X ? maxX : v.Position.X; maxY = maxY > v.Position.Y ? maxY : v.Position.Y; maxZ = maxZ > v.Position.Z ? maxZ : v.Position.Z; } } } min = new Vector3(minX, minY, minZ); max = new Vector3(maxX, maxY, maxZ); OldModelSize = Vector3.Distance(min, max); AsyncInit(); }
/// <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; }
/// <summary> /// Exports the model /// </summary> /// <param name="fullModelName">The name chosen by the user for the full model export</param> private async Task Export(string fullModelName) { var pc = await this.ShowProgressAsync(UIMessages.ExportingFullModelTitle, UIMessages.PleaseStandByMessage); var fileFormat = "fbx"; var savePath = new DirectoryInfo(Settings.Default.Save_Directory); var outputFilePath = $"{savePath}\\FullModel\\{fullModelName}\\{fullModelName}.{fileFormat}"; // Create output directory Directory.CreateDirectory($"{savePath}\\FullModel\\{fullModelName}"); var fmViewPortVM = viewport3DX.DataContext as FullModelViewport3DViewModel; var converterFolder = Directory.GetCurrentDirectory() + "\\converters\\" + fileFormat; Directory.CreateDirectory(converterFolder); var dbPath = converterFolder + "\\input.db"; File.Delete(dbPath); // Create the DB where all models will be added and fill the metadata fmViewPortVM.shownModels.FirstOrDefault().Value.TtModel.SetFullModelDBMetaData(dbPath, fullModelName); // Export the materials for each model and save model to DB foreach (var model in fmViewPortVM.shownModels) { var mtrlVariant = 1; try { var imc = new Imc(_gameDirectory); mtrlVariant = (await imc.GetImcInfo(model.Value.ItemModel)).MaterialSet; } catch (Exception ex) { // No-op, defaulted to 1. } await Mdl.ExportMaterialsForModel(model.Value.TtModel, outputFilePath, _gameDirectory, mtrlVariant, _fmvm.SelectedSkeleton.XivRace); // Save model to DB } TTModel.SaveFullToFile(dbPath, _fmvm.SelectedSkeleton.XivRace, fmViewPortVM.shownModels.Select(x => x.Value.TtModel).ToList()); var proc = new Process { StartInfo = new ProcessStartInfo { FileName = converterFolder + "\\converter.exe", Arguments = "\"" + dbPath + "\"", RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, WorkingDirectory = "" + converterFolder + "", CreateNoWindow = true } }; proc.Start(); proc.WaitForExit(); var code = proc.ExitCode; if (code != 0) { throw new Exception("Exporter threw error code: " + proc.ExitCode); } var outputFile = converterFolder + "\\result." + fileFormat; // Just move the result file if we need to. if (!Path.Equals(outputFilePath, outputFile)) { File.Delete(outputFilePath); File.Move(outputFile, outputFilePath); } await pc.CloseAsync(); await this.ShowMessageAsync(UIMessages.FullModelExportSuccessTitle, string.Format(UIMessages.FullModelExportSuccessMessage, outputFilePath)); }
public ImportModelEditViewModel(ImportModelEditView view, TTModel newModel, TTModel oldModel) { _view = view; _newModel = newModel; _oldModel = oldModel; // Merge all the default skin materials together, since FFXIV auto-handles them anyways. foreach (var m in _newModel.MeshGroups) { if (m.Material == null) { // Sanity assurance. m.Material = _newModel.MeshGroups[0].Material; } var result = DefaultSkinRegex.Match(m.Material); if (result.Success) { m.Material = SkinMaterial; } } // Calculate the model bounding box sizes. float minX = 9999.0f, minY = 9999.0f, minZ = 9999.0f; float maxX = -9999.0f, maxY = -9999.0f, maxZ = -9999.0f; foreach (var m in _newModel.MeshGroups) { foreach (var p in m.Parts) { foreach (var v in p.Vertices) { minX = minX < v.Position.X ? minX : v.Position.X; minY = minY < v.Position.Y ? minY : v.Position.Y; minZ = minZ < v.Position.Z ? minZ : v.Position.Z; maxX = maxX > v.Position.X ? maxX : v.Position.X; maxY = maxY > v.Position.Y ? maxY : v.Position.Y; maxZ = maxZ > v.Position.Z ? maxZ : v.Position.Z; } } } Vector3 min = new Vector3(minX, minY, minZ); Vector3 max = new Vector3(maxX, maxY, maxZ); NewModelSize = Vector3.Distance(min, max); minX = 9999.0f; minY = 9999.0f; minZ = 9999.0f; maxX = -9999.0f; maxY = -9999.0f; maxZ = -9999.0f; foreach (var m in _oldModel.MeshGroups) { foreach (var p in m.Parts) { foreach (var v in p.Vertices) { minX = minX < v.Position.X ? minX : v.Position.X; minY = minY < v.Position.Y ? minY : v.Position.Y; minZ = minZ < v.Position.Z ? minZ : v.Position.Z; maxX = maxX > v.Position.X ? maxX : v.Position.X; maxY = maxY > v.Position.Y ? maxY : v.Position.Y; maxZ = maxZ > v.Position.Z ? maxZ : v.Position.Z; } } } min = new Vector3(minX, minY, minZ); max = new Vector3(maxX, maxY, maxZ); OldModelSize = Vector3.Distance(min, max); UpdateModelSizeWarning(); UpdateMaterialsList(); _view.MeshNumberBox.SelectionChanged += MeshNumberBox_SelectionChanged; _view.PartNumberBox.SelectionChanged += PartNumberBox_SelectionChanged; _view.MaterialSelectorBox.SelectionChanged += MaterialSelectorBox_SelectionChanged; _view.MaterialPathTextBox.KeyDown += MaterialPathTextBox_KeyDown; _view.MaterialPathTextBox.LostFocus += MaterialPathTextBox_LostFocus; _view.ShapesListBox.SelectionChanged += ShapesListBox_SelectionChanged; _view.AttributesListBox.SelectionChanged += AttributesListBox_SelectionChanged; _view.RemoveShapeButton.Click += RemoveShapeButton_Click; _view.RemoveAttributeButton.Click += RemoveAttributeButton_Click; _view.AddAttributeBox.SelectionChanged += AddAttributeBox_SelectionChanged; _view.AddAttributeTextBox.KeyDown += AddAttributeTextBox_KeyDown; _view.ScaleComboBox.SelectionChanged += ScaleComboBox_SelectionChanged; _view.MeshNumberBox.SelectedIndex = 0; }
/// <summary> /// Copies the entirety of a given root to a new root. /// </summary> /// <param name="Source">Original Root to copy from.</param> /// <param name="Destination">Destination root to copy to.</param> /// <param name="ApplicationSource">Application to list as the source for the resulting mod entries.</param> /// <returns>Returns a Dictionary of all the file conversion</returns> public static async Task <Dictionary <string, string> > CloneRoot(XivDependencyRoot Source, XivDependencyRoot Destination, string ApplicationSource, int singleVariant = -1, string saveDirectory = null, IProgress <string> ProgressReporter = null, IndexFile index = null, ModList modlist = null, ModPack modPack = null) { if (!IsSupported(Source) || !IsSupported(Destination)) { throw new InvalidDataException("Cannot clone unsupported root."); } if (ProgressReporter != null) { ProgressReporter.Report("Stopping Cache Worker..."); } var workerStatus = XivCache.CacheWorkerEnabled; XivCache.CacheWorkerEnabled = false; try { var df = IOUtil.GetDataFileFromPath(Source.ToString()); var _imc = new Imc(XivCache.GameInfo.GameDirectory); var _mdl = new Mdl(XivCache.GameInfo.GameDirectory, df); var _dat = new Dat(XivCache.GameInfo.GameDirectory); var _index = new Index(XivCache.GameInfo.GameDirectory); var _mtrl = new Mtrl(XivCache.GameInfo.GameDirectory); var _modding = new Modding(XivCache.GameInfo.GameDirectory); var doSave = false; if (index == null) { doSave = true; index = await _index.GetIndexFile(df); modlist = await _modding.GetModListAsync(); } bool locked = _index.IsIndexLocked(df); if (locked) { throw new Exception("Game files currently in use."); } if (ProgressReporter != null) { ProgressReporter.Report("Analyzing items and variants..."); } // First, try to get everything, to ensure it's all valid. ItemMetadata originalMetadata = await GetCachedMetadata(index, modlist, Source, df, _dat); var originalModelPaths = await Source.GetModelFiles(index, modlist); var originalMaterialPaths = await Source.GetMaterialFiles(-1, index, modlist); var originalTexturePaths = await Source.GetTextureFiles(-1, index, modlist); var originalVfxPaths = new HashSet <string>(); if (Imc.UsesImc(Source)) { var avfxSets = originalMetadata.ImcEntries.Select(x => x.Vfx).Distinct(); foreach (var avfx in avfxSets) { var avfxStuff = await ATex.GetVfxPath(Source.Info, avfx); if (String.IsNullOrEmpty(avfxStuff.Folder) || String.IsNullOrEmpty(avfxStuff.File)) { continue; } var path = avfxStuff.Folder + "/" + avfxStuff.File; if (index.FileExists(path)) { originalVfxPaths.Add(path); } } } // Time to start editing things. // First, get a new, clean copy of the metadata, pointed at the new root. var newMetadata = await GetCachedMetadata(index, modlist, Source, df, _dat); newMetadata.Root = Destination.Info.ToFullRoot(); ItemMetadata originalDestinationMetadata = null; try { originalDestinationMetadata = await GetCachedMetadata(index, modlist, Destination, df, _dat); } catch { originalDestinationMetadata = new ItemMetadata(Destination); } // Set 0 needs special handling. if (Source.Info.PrimaryType == XivItemType.equipment && Source.Info.PrimaryId == 0) { var set1Root = new XivDependencyRoot(Source.Info.PrimaryType, 1, null, null, Source.Info.Slot); var set1Metadata = await GetCachedMetadata(index, modlist, set1Root, df, _dat); newMetadata.EqpEntry = set1Metadata.EqpEntry; if (Source.Info.Slot == "met") { newMetadata.GmpEntry = set1Metadata.GmpEntry; } } else if (Destination.Info.PrimaryType == XivItemType.equipment && Destination.Info.PrimaryId == 0) { newMetadata.EqpEntry = null; newMetadata.GmpEntry = null; } // Now figure out the path names for all of our new paths. // These dictionarys map Old Path => New Path Dictionary <string, string> newModelPaths = new Dictionary <string, string>(); Dictionary <string, string> newMaterialPaths = new Dictionary <string, string>(); Dictionary <string, string> newMaterialFileNames = new Dictionary <string, string>(); Dictionary <string, string> newTexturePaths = new Dictionary <string, string>(); Dictionary <string, string> newAvfxPaths = new Dictionary <string, string>(); if (ProgressReporter != null) { ProgressReporter.Report("Calculating files to copy..."); } // For each path, replace any instances of our primary and secondary types. foreach (var path in originalModelPaths) { newModelPaths.Add(path, UpdatePath(Source, Destination, path)); } foreach (var path in originalMaterialPaths) { var nPath = UpdatePath(Source, Destination, path); newMaterialPaths.Add(path, nPath); var fName = Path.GetFileName(path); if (!newMaterialFileNames.ContainsKey(fName)) { newMaterialFileNames.Add(fName, Path.GetFileName(nPath)); } } foreach (var path in originalTexturePaths) { newTexturePaths.Add(path, UpdatePath(Source, Destination, path)); } foreach (var path in originalVfxPaths) { newAvfxPaths.Add(path, UpdatePath(Source, Destination, path)); } var destItem = Destination.GetFirstItem(); var srcItem = (await Source.GetAllItems(singleVariant))[0]; var iCat = destItem.SecondaryCategory; var iName = destItem.Name; var files = newModelPaths.Select(x => x.Value).Union( newMaterialPaths.Select(x => x.Value)).Union( newAvfxPaths.Select(x => x.Value)).Union( newTexturePaths.Select(x => x.Value)); var allFiles = new HashSet <string>(); foreach (var f in files) { allFiles.Add(f); } allFiles.Add(Destination.Info.GetRootFile()); if (ProgressReporter != null) { ProgressReporter.Report("Getting modlist..."); } if (ProgressReporter != null) { ProgressReporter.Report("Removing existing modifications to destination root..."); } if (Destination != Source) { var dPath = Destination.Info.GetRootFolder(); var allMods = modlist.Mods.ToList(); foreach (var mod in allMods) { if (mod.fullPath.StartsWith(dPath) && !mod.IsInternal()) { if (Destination.Info.SecondaryType != null || Destination.Info.Slot == null) { // If this is a slotless root, purge everything. await _modding.DeleteMod(mod.fullPath, false, index, modlist); } else if (allFiles.Contains(mod.fullPath) || mod.fullPath.Contains(Destination.Info.GetBaseFileName(true))) { // Otherwise, only purge the files we're replacing, and anything else that // contains our slot name. await _modding.DeleteMod(mod.fullPath, false, index, modlist); } } } } if (ProgressReporter != null) { ProgressReporter.Report("Copying models..."); } // Load, Edit, and resave the model files. foreach (var kv in newModelPaths) { var src = kv.Key; var dst = kv.Value; var offset = index.Get8xDataOffset(src); var xmdl = await _mdl.GetRawMdlData(src, false, offset); var tmdl = TTModel.FromRaw(xmdl); if (xmdl == null || tmdl == null) { continue; } tmdl.Source = dst; xmdl.MdlPath = dst; // Replace any material references as needed. foreach (var m in tmdl.MeshGroups) { foreach (var matKv in newMaterialFileNames) { m.Material = m.Material.Replace(matKv.Key, matKv.Value); } } // Save new Model. var bytes = await _mdl.MakeNewMdlFile(tmdl, xmdl, null); await _dat.WriteModFile(bytes, dst, ApplicationSource, destItem, index, modlist); } if (ProgressReporter != null) { ProgressReporter.Report("Copying textures..."); } // Raw Copy all Texture files to the new destinations to avoid having the MTRL save functions auto-generate blank textures. foreach (var kv in newTexturePaths) { var src = kv.Key; var dst = kv.Value; await _dat.CopyFile(src, dst, ApplicationSource, true, destItem, index, modlist); } if (ProgressReporter != null) { ProgressReporter.Report("Copying materials..."); } HashSet <string> CopiedMaterials = new HashSet <string>(); // Load every Material file and edit the texture references to the new texture paths. foreach (var kv in newMaterialPaths) { var src = kv.Key; var dst = kv.Value; try { var offset = index.Get8xDataOffset(src); if (offset == 0) { continue; } var xivMtrl = await _mtrl.GetMtrlData(offset, src, 11); xivMtrl.MTRLPath = dst; for (int i = 0; i < xivMtrl.TexturePathList.Count; i++) { foreach (var tkv in newTexturePaths) { xivMtrl.TexturePathList[i] = xivMtrl.TexturePathList[i].Replace(tkv.Key, tkv.Value); } } await _mtrl.ImportMtrl(xivMtrl, destItem, ApplicationSource, index, modlist); CopiedMaterials.Add(dst); } catch (Exception ex) { // Let functions later handle this mtrl then. } } if (ProgressReporter != null) { ProgressReporter.Report("Copying VFX..."); } // Copy VFX files. foreach (var kv in newAvfxPaths) { var src = kv.Key; var dst = kv.Value; await _dat.CopyFile(src, dst, ApplicationSource, true, destItem, index, modlist); } if (ProgressReporter != null) { ProgressReporter.Report("Creating missing variants..."); } // Check to see if we need to add any variants var cloneNum = newMetadata.ImcEntries.Count >= 2 ? 1 : 0; while (originalDestinationMetadata.ImcEntries.Count > newMetadata.ImcEntries.Count) { // Clone Variant 1 into the variants we are missing. newMetadata.ImcEntries.Add((XivImc)newMetadata.ImcEntries[cloneNum].Clone()); } if (singleVariant >= 0) { if (ProgressReporter != null) { ProgressReporter.Report("Setting single-variant data..."); } if (singleVariant < newMetadata.ImcEntries.Count) { var v = newMetadata.ImcEntries[singleVariant]; for (int i = 0; i < newMetadata.ImcEntries.Count; i++) { newMetadata.ImcEntries[i] = (XivImc)v.Clone(); } } } // Update Skeleton references to be for the correct set Id. var setId = Destination.Info.SecondaryId == null ? (ushort)Destination.Info.PrimaryId : (ushort)Destination.Info.SecondaryId; foreach (var entry in newMetadata.EstEntries) { entry.Value.SetId = setId; } if (ProgressReporter != null) { ProgressReporter.Report("Copying metdata..."); } // Poke through the variants and adjust any that point to null Material Sets to instead use a valid one. if (newMetadata.ImcEntries.Count > 0 && originalMetadata.ImcEntries.Count > 0) { var valid = newMetadata.ImcEntries.FirstOrDefault(x => x.MaterialSet != 0).MaterialSet; if (valid <= 0) { valid = originalMetadata.ImcEntries.FirstOrDefault(x => x.MaterialSet != 0).MaterialSet; } for (int i = 0; i < newMetadata.ImcEntries.Count; i++) { var entry = newMetadata.ImcEntries[i]; if (entry.MaterialSet == 0) { entry.MaterialSet = valid; } } } await ItemMetadata.SaveMetadata(newMetadata, ApplicationSource, index, modlist); // Save the new Metadata file via the batch function so that it's only written to the memory cache for now. await ItemMetadata.ApplyMetadataBatched(new List <ItemMetadata>() { newMetadata }, index, modlist, false); if (ProgressReporter != null) { ProgressReporter.Report("Filling in missing material sets..."); } // Validate all variants/material sets for valid materials, and copy materials as needed to fix. if (Imc.UsesImc(Destination)) { var mSets = newMetadata.ImcEntries.Select(x => x.MaterialSet).Distinct(); foreach (var mSetId in mSets) { var path = Destination.Info.GetRootFolder() + "material/v" + mSetId.ToString().PadLeft(4, '0') + "/"; foreach (var mkv in newMaterialFileNames) { // See if the material was copied over. var destPath = path + mkv.Value; if (CopiedMaterials.Contains(destPath)) { continue; } string existentCopy = null; // If not, find a material where one *was* copied over. foreach (var mSetId2 in mSets) { var p2 = Destination.Info.GetRootFolder() + "material/v" + mSetId2.ToString().PadLeft(4, '0') + "/"; foreach (var cmat2 in CopiedMaterials) { if (cmat2 == p2 + mkv.Value) { existentCopy = cmat2; break; } } } // Shouldn't ever actually hit this, but if we do, nothing to be done about it. if (existentCopy == null) { continue; } // Copy the material over. await _dat.CopyFile(existentCopy, destPath, ApplicationSource, true, destItem, index, modlist); } } } if (ProgressReporter != null) { ProgressReporter.Report("Updating modlist..."); } if (modPack == null) { modPack = new ModPack() { author = "System", name = "Item Copy - " + srcItem.Name + " to " + iName, url = "", version = "1.0" }; } List <Mod> mods = new List <Mod>(); foreach (var mod in modlist.Mods) { if (allFiles.Contains(mod.fullPath)) { // Ensure all of our modified files are attributed correctly. mod.name = iName; mod.category = iCat; mod.source = ApplicationSource; mod.modPack = modPack; mods.Add(mod); } } if (!modlist.ModPacks.Any(x => x.name == modPack.name)) { modlist.ModPacks.Add(modPack); } if (doSave) { // Save everything. await _index.SaveIndexFile(index); await _modding.SaveModListAsync(modlist); } XivCache.QueueDependencyUpdate(allFiles.ToList()); if (saveDirectory != null) { ProgressReporter.Report("Creating TTMP File..."); var desc = "Item Converter Modpack - " + srcItem.Name + " -> " + iName + "\nCreated at: " + DateTime.Now.ToString(); // Time to save the modlist to file. var dir = new DirectoryInfo(saveDirectory); var _ttmp = new TTMP(dir, ApplicationSource); var smpd = new SimpleModPackData() { Author = modPack.author, Description = desc, Url = modPack.url, Version = new Version(1, 0, 0), Name = modPack.name, SimpleModDataList = new List <SimpleModData>() }; foreach (var mod in mods) { var size = await _dat.GetCompressedFileSize(mod.data.modOffset, df); var smd = new SimpleModData() { Name = iName, FullPath = mod.fullPath, DatFile = df.GetDataFileName(), Category = iCat, IsDefault = false, ModSize = size, ModOffset = mod.data.modOffset }; smpd.SimpleModDataList.Add(smd); } await _ttmp.CreateSimpleModPack(smpd, XivCache.GameInfo.GameDirectory, null, true); } if (ProgressReporter != null) { ProgressReporter.Report("Root copy complete."); } // Return the final file conversion listing. var ret = newModelPaths.Union(newMaterialPaths).Union(newAvfxPaths).Union(newTexturePaths); var dict = ret.ToDictionary(x => x.Key, x => x.Value); dict.Add(Source.Info.GetRootFile(), Destination.Info.GetRootFile()); return(dict); } finally { XivCache.CacheWorkerEnabled = workerStatus; } }
private static async Task Export(string part, IItemModel item, XivRace desiredRace) { if (item == null) { return; } MainWin.LockProgress.Report($"Exporting {part}: {item.Name}"); Mdl _mdl = new Mdl(GameDirectory, item.DataFile); TTModel model = await _mdl.GetModel(item, desiredRace); XivRace modelRace = desiredRace; if (model == null) { List <XivRace> priority = desiredRace.GetModelPriorityList(); foreach (XivRace newRace in priority) { model = await _mdl.GetModel(item, newRace); modelRace = newRace; if (model != null) { break; } } } if (model == null) { throw new Exception($"Failed to get model for item: {item}"); } if (!Directory.Exists($"/{name}/{part}/")) { Directory.CreateDirectory($"/{name}/{part}/"); } string path = $"/{name}/{part}/{item.Name}.fbx"; if (desiredRace != modelRace) { ApplyDeformers(model, modelRace, desiredRace); } await _mdl.ExportModel(model, path); MainWin.LockProgress.Report($"Converting Normals {part}: {item.Name}"); string[] normalMaps = Directory.GetFiles(Path.GetDirectoryName(path), "*_n.png"); foreach (string normalMap in normalMaps) { string disMap = normalMap.Replace("_n.png", "_dis.png"); ProcessStartInfo processStartInfo = new ProcessStartInfo(); processStartInfo.FileName = "NormalToHeight.exe"; processStartInfo.Arguments = $"{normalMap} {disMap} -normalise"; processStartInfo.CreateNoWindow = true; processStartInfo.WindowStyle = ProcessWindowStyle.Hidden; Process proc = Process.Start(processStartInfo); while (!proc.HasExited) { await Task.Delay(100); } } }