/// <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);
     }
 }
예제 #2
0
        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;
                }
            }
        }
예제 #3
0
        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);
     }
 }
예제 #7
0
        /// <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);
        }
예제 #9
0
        /// <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;
        }
예제 #18
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);
                }
            }
        }