Skip to content

Commit

Permalink
ModelImporter - Add block bench export for models inside models.bin
Browse files Browse the repository at this point in the history
  • Loading branch information
NessieHax committed Jul 30, 2024
1 parent d950611 commit 293d4d2
Show file tree
Hide file tree
Showing 9 changed files with 705 additions and 117 deletions.
100 changes: 85 additions & 15 deletions PCK-Studio/External/Format/BlockBenchModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Buffers.Text;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
Expand All @@ -11,6 +12,7 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PckStudio.Internal;

namespace PckStudio.External.Format
{
Expand Down Expand Up @@ -49,7 +51,7 @@ internal class Element
[JsonProperty("inflate")]
internal float Inflate;

[JsonProperty("origin")]
[JsonProperty("origin", NullValueHandling = NullValueHandling.Ignore)]
private float[] origin;

[JsonProperty("from")]
Expand All @@ -61,6 +63,9 @@ internal class Element
[JsonProperty("uv_offset")]
private int[] uv_offset;

[JsonProperty("rotation", NullValueHandling = NullValueHandling.Ignore)]
private float[] rotation;

[JsonIgnore()]
internal Vector3 Origin
{
Expand Down Expand Up @@ -128,6 +133,23 @@ internal Vector2 UvOffset
}
}

[JsonIgnore()]
internal Vector3 Rotation
{
get
{
return new Vector3(rotation?[0] ?? 0, rotation?[1] ?? 0, rotation?[2] ?? 0);
}
set
{
if (rotation is null || rotation.Length < 3)
rotation = new float[3];
rotation[0] = value.X;
rotation[1] = value.Y;
rotation[2] = value.Z;
}
}

[JsonProperty("type")]
internal string Type;

Expand All @@ -137,38 +159,52 @@ internal Vector2 UvOffset

internal class Texture
{
public static implicit operator Texture(Image image) => new Texture(image);
public static implicit operator Image(Texture texture) => texture.GetImage();
public static implicit operator Texture(Image image) => new Texture(image);
public static implicit operator Texture(NamedTexture namedTexture) => new Texture(namedTexture.Name, namedTexture.Texture);

private Texture() { }
private const string _TEXTUREDATAHEAD = "data:image/png;base64,";

internal Texture(string name, Image image)
: this(image)
{
Name = name;
}

internal Texture(Image image)
{
var ms = new MemoryStream();
image.Save(ms, ImageFormat.Png);
TextureSource = "data:image/png;base64," + Convert.ToBase64String(ms.ToArray());
if (image is not null)
{
SetImage(image);
return;
}
Debug.WriteLine($"param: {nameof(image)} is null");
}

[JsonProperty("name")]
internal string Name;
internal string Name { get; }

[JsonProperty("source")]
internal string TextureSource;
internal string TextureSource { get; private set; }

private Image GetImage()
{
string data = TextureSource;
const string dataHead = "data:image/png;base64,";
if (data.StartsWith(dataHead))
if (data.StartsWith(_TEXTUREDATAHEAD))
{
byte[] encodedData = Convert.FromBase64String(data.Substring(dataHead.Length));
using (var ms = new MemoryStream(encodedData))
{
return Image.FromStream(ms);
}
byte[] encodedData = Convert.FromBase64String(data.Substring(_TEXTUREDATAHEAD.Length));
using var ms = new MemoryStream(encodedData);
return Image.FromStream(ms);
}
return null;
}

private void SetImage(Image image)
{
var ms = new MemoryStream();
image.Save(ms, ImageFormat.Png);
TextureSource = _TEXTUREDATAHEAD + Convert.ToBase64String(ms.ToArray());
}
}

internal class Outline
Expand All @@ -193,6 +229,40 @@ public Vector3 Origin
}
}

[JsonProperty("rotation")]
private float[] rotation;

[JsonIgnore]
public Vector3 Rotation
{
get => new Vector3(rotation?[0] ?? 0, rotation?[1] ?? 0, rotation?[2] ?? 0);
set
{
if (rotation is null || rotation.Length < 3)
rotation = new float[3];
rotation[0] = value.X;
rotation[1] = value.Y;
rotation[2] = value.Z;
}
}

[JsonProperty("pivot")]
private float[] pivot;

[JsonIgnore]
public Vector3 Pivot
{
get => new Vector3(pivot?[0] ?? 0, pivot?[1] ?? 0, pivot?[2] ?? 0);
set
{
if (pivot is null || pivot.Length < 3)
pivot = new float[3];
pivot[0] = value.X;
pivot[1] = value.Y;
pivot[2] = value.Z;
}
}

[JsonProperty("uuid")]
internal Guid Uuid;

Expand Down
19 changes: 19 additions & 0 deletions PCK-Studio/Internal/Json/JsonModelMetaData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace PckStudio.Internal.Json
{
internal class JsonModelMetaData
{
[JsonProperty("textureLocations", Required = Required.Always)]
public string[] TextureLocations { get; set; }

//[JsonProperty("parents", NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Populate)]
//public Dictionary<string, string> ParentBones { get; set; }
}
}
121 changes: 95 additions & 26 deletions PCK-Studio/Internal/ModelImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

using PckStudio.Rendering;
using PckStudio.Extensions;
using PckStudio.External.Format;
using PckStudio.Internal.IO.PSM;
using PckStudio.Internal.FileFormats;
using PckStudio.Forms.Additional_Popups;
using System.Drawing;
using PckStudio.Internal.Skin;
using OMI.Formats.Model;
using PckStudio.Internal.Json;
using System.Collections.ObjectModel;
using PckStudio.Properties;

namespace PckStudio.Internal
{
Expand All @@ -48,6 +51,13 @@ internal static class ModelImporter

internal static string SupportedModelFileFormatsFilter { get; } = string.Join("|", SupportedModelFormts);

internal static ReadOnlyDictionary<string, JsonModelMetaData> ModelTextureLocations { get; private set; }

static ModelImporter()
{
ModelTextureLocations = JsonConvert.DeserializeObject<ReadOnlyDictionary<string, JsonModelMetaData>>(Resources.modelTextureLocations);
}

internal static SkinModelInfo Import(string fileName)
{
string fileExtension = Path.GetExtension(fileName);
Expand Down Expand Up @@ -185,7 +195,7 @@ private static void LoadElement(string boxType, Element element, ref SkinModelIn
if (!element.UseBoxUv || !element.IsVisibile)
return;

BoundingBox boundingBox = new BoundingBox(element.From, element.To);
var boundingBox = new Rendering.BoundingBox(element.From, element.To);
Vector3 pos = boundingBox.Start;
Vector3 size = boundingBox.Volume;
Vector2 uv = element.UvOffset;
Expand All @@ -202,19 +212,7 @@ private static void LoadElement(string boxType, Element element, ref SkinModelIn
internal static void ExportBlockBenchModel(string fileName, SkinModelInfo modelInfo)
{
Image exportTexture = FixTexture(modelInfo);
BlockBenchModel blockBenchModel = new BlockBenchModel()
{
Name = Path.GetFileNameWithoutExtension(fileName),
Textures = [exportTexture],
TextureResolution = new TextureRes(64, exportTexture.Width == exportTexture.Height ? 64 : 32),
ModelIdentifier = "",
Metadata = new Meta()
{
FormatVersion = "4.5",
ModelFormat = "free",
UseBoxUv = true,
}
};
BlockBenchModel blockBenchModel = CreateBlockBenchModel(Path.GetFileNameWithoutExtension(fileName), new Size(64, exportTexture.Width == exportTexture.Height ? 64 : 32), [exportTexture]);

Dictionary<string, Outline> outliners = new Dictionary<string, Outline>(5);
List<Element> elements = new List<Element>(modelInfo.AdditionalBoxes.Count);
Expand Down Expand Up @@ -254,26 +252,97 @@ void AddElement(SkinBOX box)
File.WriteAllText(fileName, content);
}

internal static void ExportBlockBenchModel(string fileName, Model model, IEnumerable<NamedTexture> textures)
{
BlockBenchModel blockBenchModel = CreateBlockBenchModel(Path.GetFileNameWithoutExtension(fileName), model.TextureSize, textures.Select(nt => (Texture)nt));

List<Outline> outliners = new List<Outline>(5);
List<Element> elements = new List<Element>(model.Parts.Count);

Vector3 transformAxis = new Vector3(1, 1, 0);

foreach (ModelPart part in model.Parts.Values)
{
var outline = new Outline(part.Name);

Vector3 partTranslation = new Vector3(part.TranslationX, part.TranslationY, part.TranslationZ);
outline.Origin = TranslateToInternalPosition("", partTranslation, Vector3.Zero, transformAxis);

Vector3 rotation = new Vector3(part.UnknownFloat, part.TextureOffsetX, part.TextureOffsetY) + new Vector3(part.RotationX, part.RotationY, part.RotationZ);
outline.Rotation = rotation * TransformSpace(Vector3.One, Vector3.Zero, transformAxis);

foreach (ModelBox box in part.Boxes)
{
Element element = CreateElement(box, partTranslation, part.Name);
element.Origin = outline.Origin;
elements.Add(element);
outline.Children.Add(element.Uuid);
}
outliners.Add(outline);
}

blockBenchModel.Elements = elements.ToArray();
blockBenchModel.Outliner = JArray.FromObject(outliners);

string content = JsonConvert.SerializeObject(blockBenchModel);
File.WriteAllText(fileName, content);
}

private static BlockBenchModel CreateBlockBenchModel(string name, Size textureResolution, IEnumerable<Texture> textures)
{
return new BlockBenchModel()
{
Name = name,
Textures = textures.ToArray(),
TextureResolution = textureResolution,
ModelIdentifier = "",
Metadata = new Meta()
{
FormatVersion = "4.5",
ModelFormat = "free",
UseBoxUv = true,
}
};
}

private static Element CreateElement(SkinBOX box)
{
Element element = new Element
Vector3 transformPos = TranslateFromInternalPosistion(box, new Vector3(1, 1, 0));
Element element = CreateElement(box.UV, transformPos, box.Size, box.Scale, box.Mirror);
if (box.IsOverlayPart())
element.Inflate = box.Type == "HEADWEAR" ? 0.5f : 0.25f;
return element;
}

private static Element CreateElement(ModelBox box, Vector3 origin, string name)
{
Vector3 pos = new Vector3(box.PositionX, box.PositionY, box.PositionZ);
Vector3 size = new Vector3(box.Length, box.Height, box.Width);
Vector3 transformPos = TranslateToInternalPosition("", pos + origin, size, new Vector3(1, 1, 0));
return CreateElement(name, new Vector2(box.UvX, box.UvY), transformPos, size, box.Scale, box.Mirror);
}

private static Element CreateElement(Vector2 uvOffset, Vector3 pos, Vector3 size, float inflate, bool mirror)
{
return CreateElement("cube", uvOffset, pos, size, inflate, mirror);
}

private static Element CreateElement(string name, Vector2 uvOffset, Vector3 pos, Vector3 size, float inflate, bool mirror)
{
return new Element
{
Name = "cube",
Name = name,
UseBoxUv = true,
Locked = false,
Rescale = false,
Type = "cube",
Uuid = Guid.NewGuid(),
UvOffset = box.UV,
MirrorUv = box.Mirror
UvOffset = uvOffset,
MirrorUv = mirror,
Inflate = inflate,
From = pos,
To = pos + size
};
Vector3 transformPos = TranslateFromInternalPosistion(box, new Vector3(1, 1, 0));

element.From = transformPos;
element.To = transformPos + box.Size;
if (box.IsOverlayPart())
element.Inflate = box.Type == "HEADWEAR" ? 0.5f : 0.25f;
return element;
}

internal static SkinModelInfo ImportBedrockJson(string fileName)
Expand Down
17 changes: 17 additions & 0 deletions PCK-Studio/Internal/NamedTexture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Drawing;
namespace PckStudio.Internal
{
internal readonly struct NamedTexture
{
public readonly string Name;
public readonly Image Texture;

public NamedTexture(string name, Image texture)
{
Name = name;
Texture = texture;
}
}
}
Loading

0 comments on commit 293d4d2

Please sign in to comment.