From 12fc4f3a7cc5d65aade4f6426255997b9637382b Mon Sep 17 00:00:00 2001 From: miku-666 <74728189+NessieHax@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:12:34 +0100 Subject: [PATCH] GameModelImporter - Add import functionality --- PCK-Studio/Forms/Editor/ModelEditor.cs | 18 ++-- PCK-Studio/Internal/GameModelImporter.cs | 101 ++++++++++++++++-- PCK-Studio/Resources/model/defaultModels.json | 5 +- 3 files changed, 109 insertions(+), 15 deletions(-) diff --git a/PCK-Studio/Forms/Editor/ModelEditor.cs b/PCK-Studio/Forms/Editor/ModelEditor.cs index a83c9ec0..20ec3e56 100644 --- a/PCK-Studio/Forms/Editor/ModelEditor.cs +++ b/PCK-Studio/Forms/Editor/ModelEditor.cs @@ -191,10 +191,16 @@ namespace PckStudio.Forms.Editor public Image GetTexture() => _namedTexture.Texture; } + private void LoadModels() + { + modelTreeView.Nodes.Clear(); + modelTreeView.Nodes.AddRange(_models.Select(ModelNode.Create).ToArray()); + } + protected override void OnLoad(EventArgs e) { base.OnLoad(e); - modelTreeView.Nodes.AddRange(_models.Select(ModelNode.Create).ToArray()); + LoadModels(); } private void exportToolStripMenuItem_Click(object sender, EventArgs e) @@ -289,24 +295,20 @@ namespace PckStudio.Forms.Editor return; } - if (!GameModelImporter.ModelMetaData.TryGetValue(modelInfo.Model.Name, out JsonModelMetaData modelMetaData)) - { - MessageBox.Show($"Couldn't get model meta data for: '{modelInfo.Model.Name}'.", ProductName); - return; - } - //if (models.Version < modelInfo.ModelVersion) //{ // MessageBox.Show("Model container version does not match with the model version.", ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); // return; //} - _models.Add(modelInfo.Model); + _models.SetModel(modelInfo.Model); foreach (NamedTexture texture in modelInfo.Textures) { _textures.TrySet(texture.Name, texture.Texture); } + + LoadModels(); } } diff --git a/PCK-Studio/Internal/GameModelImporter.cs b/PCK-Studio/Internal/GameModelImporter.cs index 98c0ce4b..4f419968 100644 --- a/PCK-Studio/Internal/GameModelImporter.cs +++ b/PCK-Studio/Internal/GameModelImporter.cs @@ -32,26 +32,35 @@ using OMI.Formats.Model; using PckStudio.External.Format; using PckStudio.Internal.Json; using PckStudio.Properties; +using PckStudio.Extensions; namespace PckStudio.Internal { internal sealed class GameModelImporter : ModelImporter { public static GameModelImporter Default { get; } = new GameModelImporter(); - - public sealed class ModelExportSettings + + public sealed class ModelExportSettings { - public bool CreateModelOutline { get; set; } = true; + public bool CreateModelOutline { get; set; } = true; } public ModelExportSettings ExportSettings { get; } = new ModelExportSettings(); + + public sealed class ModelImportSettings + { + public int ModelVersion { get; set; } = 1; + } + + public ModelImportSettings ImportSettings { get; } = new ModelImportSettings(); + internal static ReadOnlyDictionary ModelMetaData { get; } = JsonConvert.DeserializeObject>(Resources.modelMetaData); internal static ReadOnlyDictionary DefaultModels { get; } = JsonConvert.DeserializeObject>(Resources.defaultModels); private GameModelImporter() { // TODO: add import functionality -miku - InternalAddProvider(new FileDialogFilter("Block bench model(*.bbmodel)", "*.bbmodel"), null, ExportBlockBenchModel); + InternalAddProvider(new FileDialogFilter("Block bench model(*.bbmodel)", "*.bbmodel"), ImportBlockBenchModel, ExportBlockBenchModel); } private readonly Vector3 bbModelTransformAxis = new Vector3(1, 1, 0); @@ -79,7 +88,7 @@ namespace PckStudio.Internal { new Outline(modelInfo.Model.Name) { Children = JArray.FromObject(outlines) } }; - + blockBenchModel.Outliner = JArray.FromObject(outlines); string content = JsonConvert.SerializeObject(blockBenchModel, Formatting.Indented); @@ -116,7 +125,7 @@ namespace PckStudio.Internal } if (depth == 0 && keyValues.Count == 0) - { + { return model.GetParts().Select(CreateOutline).ToArray(); } @@ -144,5 +153,85 @@ namespace PckStudio.Internal Vector3 transformPos = TransformSpace(pos + origin, size, translationUnit) + offset; return Element.CreateCube(name, box.Uv, transformPos, size, box.Inflate, box.Mirror); } + + private GameModelInfo ImportBlockBenchModel(string filepath) + { + BlockBenchModel blockBenchModel = JsonConvert.DeserializeObject(File.ReadAllText(filepath)); + if (!blockBenchModel.Format.UseBoxUv) + { + Trace.TraceError($"[{nameof(GameModelImporter)}:{nameof(ImportBlockBenchModel)}] Failed to import model '{blockBenchModel.ModelIdentifier}': Model does not use box uv."); + return null; + } + + if (!ModelMetaData.TryGetValue(blockBenchModel.ModelIdentifier, out JsonModelMetaData modelMetaData)) + { + Trace.TraceError($"[{nameof(GameModelImporter)}:{nameof(ImportBlockBenchModel)}] Failed to import model '{blockBenchModel.ModelIdentifier}': No model meta data found."); + return null; + } + + IEnumerable textures = blockBenchModel.Textures + .Where(t => modelMetaData.TextureLocations.Any(texName => !string.IsNullOrEmpty(t.Name) && texName.EndsWith(Path.GetFileNameWithoutExtension(t.Name)))) + .Select(t => new NamedTexture(modelMetaData.TextureLocations.First(texName => texName.EndsWith(Path.GetFileNameWithoutExtension(t.Name))), (Image)t)); + + Model model = new Model(blockBenchModel.ModelIdentifier, blockBenchModel.TextureResolution); + + JArray rootOutline = blockBenchModel.Outliner + .FirstOrDefault(token => token.Type == JTokenType.Object && token.ToObject().Name == blockBenchModel.ModelIdentifier) + ?.ToObject().Children ?? blockBenchModel.Outliner; + + foreach (Outline outline in rootOutline.Where(token => token.Type == JTokenType.Object).Select(token => token.ToObject())) + { + foreach (ModelPart part in ConvertOutlineToModelPart(outline, blockBenchModel.Elements)) + { + model.AddPart(part); + } + } + + return new GameModelInfo(model, textures); + } + + private IEnumerable ConvertOutlineToModelPart(Outline root, IReadOnlyCollection elements) + { + List parts = new List( + root.Children + .Where(token => token.Type == JTokenType.Object) + .SelectMany(token => ConvertOutlineToModelPart(token.ToObject(), elements)) + ); + + IEnumerable modelBoxElements = root.Children + .Where(token => token.Type == JTokenType.String && Guid.TryParse(token.ToString(), out Guid _)) + .Select(token => elements.First(e => e.Uuid == Guid.Parse(token.ToString()))) + .Where(element => element.Type == "cube" && element.UseBoxUv && element.Export); + + Vector3 additionalRotation = new Vector3(); + Element first = modelBoxElements.FirstOrDefault() ?? new Element() { Rotation = Vector3.Zero }; + if (first.Rotation != Vector3.Zero) + { + if (!modelBoxElements.All(e => e.Rotation == first.Rotation)) + { + Trace.TraceError($"[{nameof(GameModelImporter)}:{nameof(ImportBlockBenchModel)}] Rotation can't be applied for single elements."); + return Enumerable.Empty(); + } + additionalRotation = first.Rotation; + } + Vector3 translation = TransformSpace(root.Origin - _heightOffset, Vector3.Zero, bbModelTransformAxis); + Vector3 rotation = TransformSpace(root.Rotation, Vector3.Zero, bbModelTransformAxis); + ModelPart part = new ModelPart(root.Name, string.Empty, translation, rotation, additionalRotation); + part.AddBoxes(modelBoxElements.Select(box => ConvertElementToModelBox(box, part.Translation))); + parts.Add(part); + return parts; + } + + private ModelBox ConvertElementToModelBox(Element element, Vector3 translation) + { + Rendering.BoundingBox boundingBox = new Rendering.BoundingBox(element.From, element.To); + + Vector3 pos = boundingBox.Start.ToNumericsVector(); + Vector3 size = boundingBox.Volume.ToNumericsVector(); + + Vector3 transformedPos = TransformSpace(pos, size, bbModelTransformAxis) - translation + _heightOffset; + + return new ModelBox(transformedPos, size, element.UvOffset, element.Inflate, element.MirrorUv); + } } } diff --git a/PCK-Studio/Resources/model/defaultModels.json b/PCK-Studio/Resources/model/defaultModels.json index 844b4bda..a63e77ce 100644 --- a/PCK-Studio/Resources/model/defaultModels.json +++ b/PCK-Studio/Resources/model/defaultModels.json @@ -42,7 +42,6 @@ }, { "name": "leftWing", - "boxes": [ { "pos": { "X": 2, "Y": 1, "Z": 1.5 }, "size": { "X": 10, "Y": 16, "Z": 1 }, "uv": { "X": 42, "Y": 0 }, "mirror": true } ] @@ -450,6 +449,10 @@ } ] }, + //"dragon_head": { + // "textureSize": { "X": 256, "Y": 256 }, + // "parts": [] + //}, "": { "textureSize": { "X": 64, "Y": 32 }, "parts": []