Skip to content

Commit 3fcb788

Browse files
committed
handle trixel art winding issue
meshes in fez are clockwise-front-facing, which needs converting
1 parent a525976 commit 3fcb788

5 files changed

Lines changed: 56 additions & 40 deletions

File tree

Core/Conversion/Formats/ArtObjectConverter.cs

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

43
using FEZRepacker.Core.Definitions.Game.ArtObject;
5-
using FEZRepacker.Core.Definitions.Game.Graphics;
64
using FEZRepacker.Core.Definitions.Game.XNA;
75
using FEZRepacker.Core.FileSystem;
86
using FEZRepacker.Core.Helpers;
97
using FEZRepacker.Core.Helpers.Json;
108

119
using SharpGLTF.Schema2;
12-
using SharpGLTF.Validation;
13-
1410
using SixLabors.ImageSharp.Formats.Png;
1511

1612
namespace FEZRepacker.Core.Conversion.Formats
@@ -61,7 +57,8 @@ private static Stream GetTextureStream(ArtObject data, TexturesUtil.CubemapPart
6157

6258
private static Stream GetModelStream(ArtObject data)
6359
{
64-
return new MemoryStream(Encoding.UTF8.GetBytes(data.Geometry.ToWavefrontObj()));
60+
var geometry = data.Geometry.WithReversedWindingIndices();
61+
return new MemoryStream(Encoding.UTF8.GetBytes(geometry.ToWavefrontObj()));
6562
}
6663

6764
private static Stream GetTransmissionFormatStream(ArtObject data)
@@ -71,7 +68,7 @@ private static Stream GetTransmissionFormatStream(ArtObject data)
7168
using var emission =
7269
TexturesUtil.ExtractCubemapPartFromTexture(data.Cubemap, TexturesUtil.CubemapPart.Emission);
7370
var extras = ConfiguredJsonSerializer.SerializeToNode(data);
74-
var entry = new GltfEntry<Matrix>(data.Name, data.Geometry, extras);
71+
var entry = new GltfEntry<Matrix>(data.Name, data.Geometry.WithReversedWindingIndices(), extras);
7572
return GltfUtil.ToGltfModel(entry, albedo, emission).SaveAsGlb();
7673
}
7774

@@ -85,8 +82,8 @@ private static ArtObject LoadFromTransmissionFormat(Stream modelStream)
8582

8683
var entry = entries.First();
8784
var artObject = ConfiguredJsonSerializer.DeserializeFromNode<ArtObject>(entry.Extras) ?? new ArtObject();
88-
artObject.Geometry = entry.Geometry;
89-
TrixelArtUtil.RecalculateCubemapTexCoords(artObject.Geometry, artObject.Size);
85+
artObject.Geometry = entry.Geometry.WithReversedWindingIndices();
86+
FezGeometryUtil.RecalculateCubemapTexCoords(artObject.Geometry, artObject.Size);
9087

9188
(Stream? albedo, Stream? emission) = GltfUtil.ExtractCubemapStreams(modelRoot);
9289
LoadCubemap(ref artObject, albedo, emission);
@@ -96,10 +93,10 @@ private static ArtObject LoadFromTransmissionFormat(Stream modelStream)
9693

9794
private static void AppendGeometryStream(ref ArtObject data, Stream geometryStream)
9895
{
99-
var geometries = TrixelArtUtil.LoadGeometry<Matrix>(geometryStream);
96+
var geometries = WavefrontObjUtil.FromWavefrontObjStream<Matrix>(geometryStream);
10097
if (geometries.Count < 1) return;
101-
data.Geometry = geometries.First().Value;
102-
TrixelArtUtil.RecalculateCubemapTexCoords(data.Geometry, data.Size);
98+
data.Geometry = geometries.First().Value.WithReversedWindingIndices();
99+
FezGeometryUtil.RecalculateCubemapTexCoords(data.Geometry, data.Size);
103100
}
104101

105102
private static void LoadCubemap(ref ArtObject data, Stream? albedoStream, Stream? emissionStream)

Core/Conversion/Formats/TrileSetConverter.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private static Stream GetModelStream(TrileSet data)
6767

6868
foreach (var trileRecord in data.Triles)
6969
{
70-
geometryDict[trileRecord.Key.ToString()] = trileRecord.Value.Geometry;
70+
geometryDict[trileRecord.Key.ToString()] = trileRecord.Value.Geometry.WithReversedWindingIndices();
7171
}
7272

7373
var objString = WavefrontObjUtil.ToWavefrontObj(geometryDict);
@@ -81,7 +81,8 @@ private static Stream GetTransmissionFormatStream(TrileSet data)
8181
{
8282
var extras = ConfiguredJsonSerializer.SerializeToNode(trileRecord.Value) ?? new JsonObject();
8383
extras[TrileIdKey] = trileRecord.Key;
84-
entries.Add(new GltfEntry<Vector4>(trileRecord.Value.Name, trileRecord.Value.Geometry, extras));
84+
var geometry = trileRecord.Value.Geometry.WithReversedWindingIndices();
85+
entries.Add(new GltfEntry<Vector4>(trileRecord.Value.Name, geometry, extras));
8586
}
8687

8788
using var albedo =
@@ -111,7 +112,7 @@ private static TrileSet LoadFromTransmissionFormat(Stream modelStream)
111112
trileSet.Triles[id] = ConfiguredJsonSerializer.DeserializeFromNode<Trile>(entry.Extras) ?? new Trile();
112113
}
113114

114-
trileSet.Triles[id].Geometry = entry.Geometry;
115+
trileSet.Triles[id].Geometry = entry.Geometry.WithReversedWindingIndices();
115116
}
116117

117118
(Stream? albedo, Stream? emission) = GltfUtil.ExtractCubemapStreams(modelRoot);
@@ -122,7 +123,7 @@ private static TrileSet LoadFromTransmissionFormat(Stream modelStream)
122123

123124
private static void AppendGeometryStream(ref TrileSet data, Stream geometryStream)
124125
{
125-
var geometries = TrixelArtUtil.LoadGeometry<Vector4>(geometryStream);
126+
var geometries = WavefrontObjUtil.FromWavefrontObjStream<Vector4>(geometryStream);
126127
foreach (var objRecord in geometries)
127128
{
128129
var id = int.Parse(objRecord.Key);
@@ -132,7 +133,7 @@ private static void AppendGeometryStream(ref TrileSet data, Stream geometryStrea
132133
data.Triles[id] = new Trile();
133134
}
134135

135-
data.Triles[id].Geometry = objRecord.Value;
136+
data.Triles[id].Geometry = objRecord.Value.WithReversedWindingIndices();
136137
}
137138
}
138139

Original file line numberDiff line numberDiff line change
@@ -1,27 +1,15 @@
1-
using System.Text;
2-
3-
using FEZRepacker.Core.Definitions.Game.ArtObject;
1+
using FEZRepacker.Core.Definitions.Game.ArtObject;
42
using FEZRepacker.Core.Definitions.Game.Graphics;
53
using FEZRepacker.Core.Definitions.Game.XNA;
64

75
namespace FEZRepacker.Core.Helpers
86
{
9-
internal static class TrixelArtUtil
7+
internal static class FezGeometryUtil
108
{
11-
public static Dictionary<string, IndexedPrimitives<VertexInstance, VertexType>> LoadGeometry<VertexType>(Stream geometryStream)
12-
{
13-
using var geometryReader = new BinaryReader(geometryStream, Encoding.UTF8, true);
14-
string geometryString = new string(geometryReader.ReadChars((int)geometryStream.Length));
15-
return WavefrontObjUtil.FromWavefrontObj<VertexType>(geometryString);
16-
}
17-
18-
/// <summary>
199
/// Recalculates texture coordinates to match what game does with them during loading process.
20-
/// </summary>
21-
/// <typeparam name="VertexType">Type of modified geometry</typeparam>
22-
/// <param name="geometry">Geometry to modify</param>
23-
/// <param name="geometryBounds">Bounds of the geometry to calculate texture coordinates with.</param>
24-
public static void RecalculateCubemapTexCoords<VertexType>(IndexedPrimitives<VertexInstance, VertexType> geometry, Vector3 geometryBounds) {
10+
public static void RecalculateCubemapTexCoords<VertexType>(
11+
IndexedPrimitives<VertexInstance, VertexType> geometry, Vector3 geometryBounds
12+
) {
2513
foreach (var vertex in geometry.Vertices)
2614
{
2715
(int textureOffset, Vector3 xAxis, Vector3 yAxis) = vertex.NormalByte switch
@@ -42,5 +30,31 @@ public static void RecalculateCubemapTexCoords<VertexType>(IndexedPrimitives<Ver
4230
);
4331
}
4432
}
33+
34+
// FEZ meshes are counter-clockwise culled, which is the opposite of what most 3D software uses for front faces.
35+
public static IndexedPrimitives<VertexInstance, VertexType> WithReversedWindingIndices<VertexType>(
36+
this IndexedPrimitives<VertexInstance, VertexType> geometry
37+
)
38+
{
39+
var newGeometry = new IndexedPrimitives<VertexInstance, VertexType>()
40+
{
41+
PrimitiveType = geometry.PrimitiveType,
42+
Vertices = geometry.Vertices,
43+
Indices = new ushort[geometry.Indices.Length]
44+
};
45+
46+
for (int i = 0; i < geometry.Indices.Length; i++)
47+
{
48+
var index = geometry.PrimitiveType switch
49+
{
50+
PrimitiveType.TriangleList => i + (((i + 1) % 3) - 1),
51+
PrimitiveType.TriangleStrip => geometry.Indices.Length - (i + 1),
52+
_ => i
53+
};
54+
newGeometry.Indices[i] = geometry.Indices[index];
55+
}
56+
57+
return newGeometry;
58+
}
4559
}
4660
}

Core/Helpers/GltfUtil.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,6 @@ private static Material CreateMaterial(
172172
ImageBuilder emissionImage = emission.SaveAsMemoryStream(new PngEncoder()).ToArray();
173173

174174
var materialBuilder = new MaterialBuilder()
175-
.WithDoubleSide(true)
176175
.WithMetallicRoughnessShader();
177176

178177
materialBuilder

Core/Helpers/WavefrontObjUtil.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,23 @@ public static string ToWavefrontObj<T>(this IndexedPrimitives<VertexInstance,T>
7474
{
7575
for (int i = 2; i < indices.Count() - indices.Count() % 3; i += (isList ? 3 : 1))
7676
{
77-
// revert orders of indices
78-
int i3 = indices[i - 2] + iOffset;
77+
int i1 = indices[i - 2] + iOffset;
7978
int i2 = indices[i - 1] + iOffset;
80-
int i1 = indices[i] + iOffset;
79+
int i3 = indices[i] + iOffset;
8180

8281
objBuilder.AppendLine($"f {i1}/{i1}/{i1} {i2}/{i2}/{i2} {i3}/{i3}/{i3}");
8382
}
8483
}
8584

8685
return objBuilder.ToString();
8786
}
87+
88+
public static Dictionary<string, IndexedPrimitives<VertexInstance, T>> FromWavefrontObjStream<T>(Stream objStream)
89+
{
90+
using var geometryReader = new BinaryReader(objStream, Encoding.UTF8, true);
91+
string geometryString = new string(geometryReader.ReadChars((int)objStream.Length));
92+
return FromWavefrontObj<T>(geometryString);
93+
}
8894

8995
public static Dictionary<string, IndexedPrimitives<VertexInstance, T>> FromWavefrontObj<T>(string obj)
9096
{
@@ -148,10 +154,9 @@ public static Dictionary<string, IndexedPrimitives<VertexInstance, T>> FromWavef
148154

149155
else if(tokens[0] == "f" && tokens.Length >= 4)
150156
{
151-
// revert orders of indices
152-
indicesGroup[indicesGroup.Count - 1].Add(tokens[3]);
153-
indicesGroup[indicesGroup.Count - 1].Add(tokens[2]);
154157
indicesGroup[indicesGroup.Count - 1].Add(tokens[1]);
158+
indicesGroup[indicesGroup.Count - 1].Add(tokens[2]);
159+
indicesGroup[indicesGroup.Count - 1].Add(tokens[3]);
155160
}
156161
}
157162

0 commit comments

Comments
 (0)