Skip to content

Commit 3603bec

Browse files
authored
Merge pull request #26 from zrckr/gltf-exporter
Implement binary GLTF conversion from XNB and vice versa
2 parents 0f5d3c1 + a8abc32 commit 3603bec

20 files changed

Lines changed: 551 additions & 73 deletions

Core/Conversion/FormatConversion.cs

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public static class FormatConversion
1818
/// convert it and store in a <see cref="FileBundle"/>.
1919
/// </summary>
2020
/// <param name="data">A reference to object to convert</param>
21-
/// <param name="configuration">A configuration dictionary to alter converter behaviour</param>
21+
/// <param name="settings">A configuration to alter converter behaviour</param>
2222
/// <returns>
2323
/// A <see cref="FileBundle"/> containing file or files converted from given object.
2424
/// </returns>
@@ -28,7 +28,7 @@ public static class FormatConversion
2828
/// <exception cref="FormatConversionException">
2929
/// Thrown when a type of given object is not supported by Repacker
3030
/// </exception>
31-
public static FileBundle Convert(object? data, IDictionary<string, object>? configuration = null)
31+
public static FileBundle Convert(object? data, FormatConverterSettings? settings = null)
3232
{
3333
if(data == null)
3434
{
@@ -42,14 +42,7 @@ public static FileBundle Convert(object? data, IDictionary<string, object>? conf
4242
throw new FormatConversionException($"Type {data.GetType()} is not supported for conversion.");
4343
}
4444

45-
if (configuration != null)
46-
{
47-
foreach (var pair in configuration)
48-
{
49-
converter.Configuration.Add(pair);
50-
}
51-
}
52-
45+
converter.Settings = settings ?? new FormatConverterSettings();
5346
return converter.Convert(data);
5447
}
5548

@@ -58,14 +51,14 @@ public static FileBundle Convert(object? data, IDictionary<string, object>? conf
5851
/// then attempts to deconvert it back to an object with a type assigned to this converter.
5952
/// </summary>
6053
/// <param name="bundle">A <see cref="FileBundle"/> containing files to convert.</param>
61-
/// <param name="configuration">A configuration dictionary to alter converter behaviour</param>
54+
/// <param name="settings">A configuration to alter converter behaviour</param>
6255
/// <returns>
6356
/// An object deconverted from files contained in given <see cref="FileBundle"/>.
6457
/// </returns>
6558
/// <exception cref="FormatConversionException">
6659
/// Thrown when main extension of given <see cref="FileBundle"/> is not supported by Repacker
6760
/// </exception>
68-
public static object? Deconvert(FileBundle bundle, IDictionary<string, object>? configuration = null)
61+
public static object? Deconvert(FileBundle bundle, FormatConverterSettings? settings = null)
6962
{
7063
var converter = FormatConverters.FindForFileBundle(bundle);
7164

@@ -74,14 +67,7 @@ public static FileBundle Convert(object? data, IDictionary<string, object>? conf
7467
throw new FormatConversionException($"File bundle type {bundle.MainExtension} is not supported for conversion.");
7568
}
7669

77-
if (configuration != null)
78-
{
79-
foreach (var pair in configuration)
80-
{
81-
converter.Configuration.Add(pair);
82-
}
83-
}
84-
70+
converter.Settings = settings ?? new FormatConverterSettings();
8571
return converter.Deconvert(bundle);
8672
}
8773
}

Core/Conversion/FormatConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public abstract class FormatConverter
1717
public abstract FileBundle Convert(object? data);
1818
public abstract object? Deconvert(FileBundle bundle);
1919

20-
public IDictionary<string, object> Configuration { get; } = new Dictionary<string, object>();
20+
public FormatConverterSettings Settings { get; set; }
2121

2222
public FileBundle Convert<T>(T data)
2323
{
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using FEZRepacker.Core.Definitions.Game.ArtObject;
2+
using FEZRepacker.Core.Definitions.Game.TrileSet;
3+
4+
namespace FEZRepacker.Core.Conversion
5+
{
6+
/// <summary>
7+
/// Contains data for changing the behavior of the converter
8+
/// </summary>
9+
public struct FormatConverterSettings()
10+
{
11+
/// <summary>
12+
/// By default, the <see href="https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html">glTF</see>
13+
/// all-in-one format is used for transmitting and editing <see cref="ArtObject"/> properties.
14+
/// If the flag is true, the converter will use a legacy bundle with separate files.
15+
/// </summary>
16+
public bool UseLegacyArtObjectBundle = false;
17+
18+
/// <summary>
19+
/// By default, the <see href="https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html">glTF</see>
20+
/// all-in-one format is used for transmitting and editing <see cref="TrileSet"/> properties.
21+
/// If the flag is true, the converter will use a legacy bundle with separate files.
22+
/// </summary>
23+
public bool UseLegacyTrileSetBundle = false;
24+
}
25+
}

Core/Conversion/Formats/ArtObjectConverter.cs

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
using System.Text;
2+
using System.Text.Json.Nodes;
23

34
using FEZRepacker.Core.Definitions.Game.ArtObject;
5+
using FEZRepacker.Core.Definitions.Game.Graphics;
46
using FEZRepacker.Core.Definitions.Game.XNA;
57
using FEZRepacker.Core.FileSystem;
68
using FEZRepacker.Core.Helpers;
79
using FEZRepacker.Core.Helpers.Json;
810

11+
using SharpGLTF.Schema2;
12+
using SharpGLTF.Validation;
13+
914
using SixLabors.ImageSharp.Formats.Png;
1015

1116
namespace FEZRepacker.Core.Conversion.Formats
@@ -16,6 +21,11 @@ internal class ArtObjectConverter : FormatConverter<ArtObject>
1621

1722
public override FileBundle ConvertTyped(ArtObject data)
1823
{
24+
if (!Settings.UseLegacyArtObjectBundle)
25+
{
26+
return FileBundle.Single(GetTransmissionFormatStream(data), FileFormat, ".glb");
27+
}
28+
1929
var bundle = ConfiguredJsonSerializer.SerializeToFileBundle(FileFormat, data);
2030

2131
bundle.AddFile(GetTextureStream(data, TexturesUtil.CubemapPart.Albedo), ".png");
@@ -27,13 +37,20 @@ public override FileBundle ConvertTyped(ArtObject data)
2737

2838
public override ArtObject DeconvertTyped(FileBundle bundle)
2939
{
30-
var artObject = ConfiguredJsonSerializer.DeserializeFromFileBundle<ArtObject>(bundle);
31-
32-
AppendGeometryStream(ref artObject, bundle.RequireData(".obj"));
33-
LoadCubemap(ref artObject, bundle.GetData(".png"), bundle.GetData(".apng"));
40+
try
41+
{
42+
return LoadFromTransmissionFormat(bundle.RequireData(".glb"));
43+
}
44+
catch (FileNotFoundException)
45+
{
46+
Console.WriteLine(" The glTF bundle was not found! Using legacy art object bundle format...");
47+
var artObject = ConfiguredJsonSerializer.DeserializeFromFileBundle<ArtObject>(bundle);
3448

49+
AppendGeometryStream(ref artObject, bundle.RequireData(".obj"));
50+
LoadCubemap(ref artObject, bundle.GetData(".png"), bundle.GetData(".apng"));
3551

36-
return artObject;
52+
return artObject;
53+
}
3754
}
3855

3956
private static Stream GetTextureStream(ArtObject data, TexturesUtil.CubemapPart part)
@@ -47,20 +64,48 @@ private static Stream GetModelStream(ArtObject data)
4764
return new MemoryStream(Encoding.UTF8.GetBytes(data.Geometry.ToWavefrontObj()));
4865
}
4966

67+
private static Stream GetTransmissionFormatStream(ArtObject data)
68+
{
69+
using var albedo =
70+
TexturesUtil.ExtractCubemapPartFromTexture(data.Cubemap, TexturesUtil.CubemapPart.Albedo);
71+
using var emission =
72+
TexturesUtil.ExtractCubemapPartFromTexture(data.Cubemap, TexturesUtil.CubemapPart.Emission);
73+
var extras = ConfiguredJsonSerializer.SerializeToNode(data);
74+
var entry = new GltfEntry<Matrix>(data.Name, data.Geometry, extras);
75+
return GltfUtil.ToGltfModel(entry, albedo, emission).SaveAsGlb();
76+
}
77+
78+
private static ArtObject LoadFromTransmissionFormat(Stream modelStream)
79+
{
80+
var modelRoot = ModelRoot.ReadGLB(modelStream);
81+
var entries = GltfUtil.FromGltfModel<Matrix>(modelRoot);
82+
83+
if (entries.Count < 1)
84+
return new ArtObject();
85+
86+
var entry = entries.First();
87+
var artObject = ConfiguredJsonSerializer.DeserializeFromNode<ArtObject>(entry.Extras);
88+
artObject.Geometry = entry.Geometry;
89+
TrixelArtUtil.RecalculateCubemapTexCoords(artObject.Geometry, artObject.Size);
90+
91+
(Stream? albedo, Stream? emission) = GltfUtil.ExtractCubemapStreams(modelRoot);
92+
LoadCubemap(ref artObject, albedo, emission);
93+
94+
return artObject;
95+
}
96+
5097
private static void AppendGeometryStream(ref ArtObject data, Stream geometryStream)
5198
{
5299
var geometries = TrixelArtUtil.LoadGeometry<Matrix>(geometryStream);
53-
if (geometries.Count() > 0)
54-
{
55-
data.Geometry = geometries.First().Value;
56-
TrixelArtUtil.RecalculateCubemapTexCoords(data.Geometry, data.Size);
57-
}
100+
if (geometries.Count < 1) return;
101+
data.Geometry = geometries.First().Value;
102+
TrixelArtUtil.RecalculateCubemapTexCoords(data.Geometry, data.Size);
58103
}
59104

60-
private static void LoadCubemap(ref ArtObject data, Stream albedoStream, Stream emissionStream)
105+
private static void LoadCubemap(ref ArtObject data, Stream? albedoStream, Stream? emissionStream)
61106
{
62107
using var image = TexturesUtil.ConstructCubemap(albedoStream, emissionStream);
63108
data.Cubemap = TexturesUtil.ImageToTexture2D(image);
64109
}
65110
}
66-
}
111+
}

Core/Conversion/Formats/TrileSetConverter.cs

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text;
2+
using System.Text.Json.Nodes;
23

34
using FEZRepacker.Core.Definitions.Game.ArtObject;
45
using FEZRepacker.Core.Definitions.Game.Graphics;
@@ -8,6 +9,8 @@
89
using FEZRepacker.Core.Helpers;
910
using FEZRepacker.Core.Helpers.Json;
1011

12+
using SharpGLTF.Schema2;
13+
1114
using SixLabors.ImageSharp.Formats.Png;
1215

1316
namespace FEZRepacker.Core.Conversion.Formats
@@ -16,8 +19,15 @@ internal class TrileSetConverter : FormatConverter<TrileSet>
1619
{
1720
public override string FileFormat => ".fezts";
1821

22+
private const string TrileIdKey = "TrileId";
23+
1924
public override FileBundle ConvertTyped(TrileSet data)
2025
{
26+
if (!Settings.UseLegacyTrileSetBundle)
27+
{
28+
return FileBundle.Single(GetTransmissionFormatStream(data), FileFormat, ".glb");
29+
}
30+
2131
var bundle = ConfiguredJsonSerializer.SerializeToFileBundle(FileFormat, data);
2232

2333
bundle.AddFile(GetTextureStream(data, TexturesUtil.CubemapPart.Albedo), ".png");
@@ -29,12 +39,20 @@ public override FileBundle ConvertTyped(TrileSet data)
2939

3040
public override TrileSet DeconvertTyped(FileBundle bundle)
3141
{
32-
var trileSet = ConfiguredJsonSerializer.DeserializeFromFileBundle<TrileSet>(bundle);
33-
34-
AppendGeometryStream(ref trileSet, bundle.RequireData(".obj"));
35-
LoadCubemap(ref trileSet, bundle.GetData(".png"), bundle.GetData(".apng"));
36-
37-
return trileSet;
42+
try
43+
{
44+
return LoadFromTransmissionFormat(bundle.RequireData(".glb"));
45+
}
46+
catch (FileNotFoundException)
47+
{
48+
Console.WriteLine(" The glTF bundle was not found! Using legacy trile set bundle format...");
49+
var trileSet = ConfiguredJsonSerializer.DeserializeFromFileBundle<TrileSet>(bundle);
50+
51+
AppendGeometryStream(ref trileSet, bundle.RequireData(".obj"));
52+
LoadCubemap(ref trileSet, bundle.GetData(".png"), bundle.GetData(".apng"));
53+
54+
return trileSet;
55+
}
3856
}
3957

4058
private static Stream GetTextureStream(TrileSet data, TexturesUtil.CubemapPart part)
@@ -51,10 +69,51 @@ private static Stream GetModelStream(TrileSet data)
5169
{
5270
geometryDict[trileRecord.Key.ToString()] = trileRecord.Value.Geometry;
5371
}
72+
5473
var objString = WavefrontObjUtil.ToWavefrontObj(geometryDict);
5574
return new MemoryStream(Encoding.UTF8.GetBytes(objString));
5675
}
5776

77+
private static Stream GetTransmissionFormatStream(TrileSet data)
78+
{
79+
var entries = new List<GltfEntry<Vector4>>();
80+
foreach (var trileRecord in data.Triles)
81+
{
82+
var extras = ConfiguredJsonSerializer.SerializeToNode(trileRecord.Value);
83+
extras[TrileIdKey] = trileRecord.Key;
84+
entries.Add(new GltfEntry<Vector4>(trileRecord.Value.Name, trileRecord.Value.Geometry, extras));
85+
}
86+
87+
using var albedo =
88+
TexturesUtil.ExtractCubemapPartFromTexture(data.TextureAtlas, TexturesUtil.CubemapPart.Albedo);
89+
using var emission =
90+
TexturesUtil.ExtractCubemapPartFromTexture(data.TextureAtlas, TexturesUtil.CubemapPart.Emission);
91+
92+
return GltfUtil.ToGltfModel(data.Name, entries, albedo, emission).SaveAsGlb();
93+
}
94+
95+
private static TrileSet LoadFromTransmissionFormat(Stream modelStream)
96+
{
97+
var modelRoot = ModelRoot.ReadGLB(modelStream);
98+
var trileSet = new TrileSet { Name = modelRoot.DefaultScene.Name };
99+
100+
var entries = GltfUtil.FromGltfModel<Vector4>(modelRoot);
101+
foreach (var entry in entries)
102+
{
103+
var id = entry.Extras[TrileIdKey]!.GetValue<int>();
104+
if (!trileSet.Triles.ContainsKey(id))
105+
{
106+
trileSet.Triles[id] = ConfiguredJsonSerializer.DeserializeFromNode<Trile>(entry.Extras);
107+
}
108+
109+
trileSet.Triles[id].Geometry = entry.Geometry;
110+
}
111+
112+
(Stream? albedo, Stream? emission) = GltfUtil.ExtractCubemapStreams(modelRoot);
113+
LoadCubemap(ref trileSet, albedo, emission);
114+
115+
return trileSet;
116+
}
58117

59118
private static void AppendGeometryStream(ref TrileSet data, Stream geometryStream)
60119
{
@@ -67,14 +126,15 @@ private static void AppendGeometryStream(ref TrileSet data, Stream geometryStrea
67126
{
68127
data.Triles[id] = new Trile();
69128
}
129+
70130
data.Triles[id].Geometry = objRecord.Value;
71131
}
72132
}
73133

74-
private static void LoadCubemap(ref TrileSet data, Stream albedoStream, Stream emissionStream)
134+
private static void LoadCubemap(ref TrileSet data, Stream? albedoStream, Stream? emissionStream)
75135
{
76136
using var image = TexturesUtil.ConstructCubemap(albedoStream, emissionStream);
77137
data.TextureAtlas = TexturesUtil.ImageToTexture2D(image);
78138
}
79139
}
80-
}
140+
}

Core/Definitions/Game/XNA/Quaternion.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ public bool Equals(Quaternion other) =>
3737
this.X == other.X && this.Y == other.Y && this.Z == other.Z && this.W == other.W;
3838
public static bool operator ==(Quaternion left, Quaternion right) => left.Equals(right);
3939
public static bool operator !=(Quaternion left, Quaternion right) => !left.Equals(right);
40+
41+
public System.Numerics.Quaternion ToNumeric() => new(this.X, this.Y, this.Z, this.W);
42+
public static Quaternion FromNumeric(System.Numerics.Quaternion numeric) => new(numeric.X, numeric.Y, numeric.Z, numeric.W);
4043

4144
public override int GetHashCode()
4245
{

Core/Definitions/Game/XNA/Vector2.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ public bool Equals(Vector2 other) =>
2222
this.X == other.X && this.Y == other.Y;
2323
public static bool operator ==(Vector2 left, Vector2 right) => left.Equals(right);
2424
public static bool operator !=(Vector2 left, Vector2 right) => !left.Equals(right);
25+
26+
public System.Numerics.Vector2 ToNumeric() => new(this.X, this.Y);
27+
public static Vector2 FromNumeric(System.Numerics.Vector2 numeric) => new(numeric.X, numeric.Y);
2528

2629
public override int GetHashCode()
2730
{

Core/Definitions/Game/XNA/Vector3.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ public bool Equals(Vector3 other) =>
4444
public static bool operator ==(Vector3 left, Vector3 right) => left.Equals(right);
4545
public static bool operator !=(Vector3 left, Vector3 right) => !left.Equals(right);
4646

47+
public System.Numerics.Vector3 ToNumeric() => new(this.X, this.Y, this.Z);
48+
public static Vector3 FromNumeric(System.Numerics.Vector3 numeric) => new(numeric.X, numeric.Y, numeric.Z);
49+
4750
public override int GetHashCode()
4851
{
4952
return this.X.GetHashCode() ^ this.Y.GetHashCode() << 2 ^ this.Z.GetHashCode() >> 2;

0 commit comments

Comments
 (0)