From fa5fc198f88bbfc100b8a1c3c6a1d7966ab732b7 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 00:26:04 +0200 Subject: [PATCH 01/17] Added support for exporting animation frames to OBJ for Crash2 --- .../Animation/AnimationEntryController.cs | 46 +++++ .../Controllers/Animation/FrameController.cs | 184 ++++++++++++++++++ CrashEdit/FileUtil.cs | 23 +++ 3 files changed, 253 insertions(+) diff --git a/CrashEdit/Controllers/Animation/AnimationEntryController.cs b/CrashEdit/Controllers/Animation/AnimationEntryController.cs index 14dc1aa7..935f0778 100644 --- a/CrashEdit/Controllers/Animation/AnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/AnimationEntryController.cs @@ -8,6 +8,8 @@ public sealed class AnimationEntryController : EntryController public AnimationEntryController(AnimationEntry animationentry, SubcontrollerGroup parentGroup) : base(animationentry, parentGroup) { AnimationEntry = animationentry; + AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); + AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); } public override bool EditorAvailable => true; @@ -18,5 +20,49 @@ public override Control CreateEditor() } public AnimationEntry AnimationEntry { get; } + + private void Menu_Export_OBJ_Processed() + { + FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); + + // modify the path to add a number before the extension + string ext = Path.GetExtension (output); + string filename = Path.GetFileNameWithoutExtension (output); + string path = Path.GetDirectoryName (output); + + int id = 0; + + foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) + { + if (node.Legacy is not FrameController frame) + continue; + + string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; + File.WriteAllBytes (final, frame.ToProcessedOBJ ()); + id++; + } + } + + private void Menu_Export_OBJ_Game () + { + FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); + + // modify the path to add a number before the extension + string ext = Path.GetExtension (output); + string filename = Path.GetFileNameWithoutExtension (output); + string path = Path.GetDirectoryName (output); + + int id = 0; + + foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) + { + if (node.Legacy is not FrameController frame) + continue; + + string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; + File.WriteAllBytes (final, frame.ToGameOBJ ()); + id++; + } + } } } diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index 1b430a25..6f4dc881 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -1,3 +1,4 @@ +using System.Globalization; using CrashEdit.Crash; namespace CrashEdit.CE @@ -8,6 +9,8 @@ public sealed class FrameController : LegacyController public FrameController(Frame frame, SubcontrollerGroup parentGroup) : base(parentGroup, frame) { Frame = frame; + AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); + AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); } public override bool EditorAvailable => true; @@ -19,5 +22,186 @@ public override Control CreateEditor() public AnimationEntryController AnimationEntryController => (AnimationEntryController)Modern.Parent.Legacy; public Frame Frame { get; } + + + private void Menu_Export_OBJ_Processed() + { + FileUtil.SaveFile(this.ToProcessedOBJ(), FileFilters.OBJ, FileFilters.Any); + } + + private void Menu_Export_OBJ_Game () + { + FileUtil.SaveFile(this.ToGameOBJ(), FileFilters.OBJ, FileFilters.Any); + } + + class ProcessedEntries + { + public float VX; + public float VY; + public float VZ; + public SceneryColor color; + } + + class GameEntries + { + public Position vertex; + public SceneryColor color; + } + + /// + /// Generates a OBJ file with the model's information ready to be displayed in any other 3D software. + /// It includes extra geometry to ensure vertex colors do look properly + /// + /// This function resides here because access to GameScales is required, and the Frame object does not have access to it + /// a good improvement would be to move this there + /// + /// + public byte[] ToProcessedOBJ() + { + using (MemoryStream stream = new MemoryStream()) + { + using (StreamWriter obj = new StreamWriter(stream)) + { + var model = this.AnimationEntryController.GetNSF ().GetEntry(Frame.ModelEID); + + obj.WriteLine("# Vertices"); + + var vertices = Frame.MakeVertices (model); + var list = new List (); + + // build scale + float scaleX = model.ScaleX / (GameScales.ModelC1 * GameScales.AnimC1); + float scaleY = model.ScaleY / (GameScales.ModelC1 * GameScales.AnimC1); + float scaleZ = model.ScaleZ / (GameScales.ModelC1 * GameScales.AnimC1); + + foreach (ModelTransformedTriangle tri in model.Triangles) + { + bool nocull = tri.Subtype == 0 || tri.Subtype == 2; + bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; + + for (int i = 0; i < 3; ++i) + { + var v_n = !flip ? i : 2 - i; + Position vert = vertices [tri.Vertex [v_n] + Frame.SpecialVertexCount]; + SceneryColor color = model.Colors [tri.Color [v_n]]; + + // calculate positions + list.Add (new ProcessedEntries + { + VX = (vert.X + (Frame.XOffset / 4F)) * scaleX, + VY = (vert.Z + (Frame.YOffset / 4F)) * scaleY, + VZ = (vert.Y + (Frame.ZOffset / 4F)) * scaleZ, + color = color + }); + } + } + + foreach (ProcessedEntries entry in list) + { + obj.WriteLine( + "v {0} {1} {2} {3} {4} {5}", + entry.VX.ToString(CultureInfo.InvariantCulture), + entry.VY.ToString(CultureInfo.InvariantCulture), + entry.VZ.ToString(CultureInfo.InvariantCulture), + (entry.color.Red / 255F).ToString(CultureInfo.InvariantCulture), + (entry.color.Green / 255F).ToString(CultureInfo.InvariantCulture), + (entry.color.Blue / 255F).ToString(CultureInfo.InvariantCulture)); + } + + obj.WriteLine (""); + obj.WriteLine ("# Triangles"); + + for (int i = 0; i < list.Count; i+=3) + { + obj.WriteLine ("f {0} {1} {2}", i + 1, i + 2, i + 3); + } + } + + return stream.ToArray(); + } + } + + /// + /// Generates a OBJ file with the model's information as close as to what the original frame is + /// + /// Due to OBJ format we cannot include all the required colors for each face + /// so it looks way out of place. + /// + /// TODO: MAYBE IMPLEMENT AN FBX EXPORT OR SOMETHING ELSE THAT IS A BIT MORE FLEXIBLE? + /// + /// This function resides here because access to GameScales is required, and the Frame object does not have access to it + /// a good improvement would be to move this there + /// + /// + public byte[] ToGameOBJ() + { + using (MemoryStream stream = new MemoryStream()) + { + using (StreamWriter obj = new StreamWriter(stream)) + { + var model = this.AnimationEntryController.GetNSF ().GetEntry(Frame.ModelEID); + + obj.WriteLine("# Vertices"); + + var vertices = Frame.MakeVertices (model); + var list = new List (); + + // build scale + float scaleX = model.ScaleX / (GameScales.ModelC1 * GameScales.AnimC1); + float scaleY = model.ScaleY / (GameScales.ModelC1 * GameScales.AnimC1); + float scaleZ = model.ScaleZ / (GameScales.ModelC1 * GameScales.AnimC1); + + foreach (Position vertex in vertices) + { + list.Add (new GameEntries { vertex = vertex }); + } + + foreach (ModelTransformedTriangle tri in model.Triangles) + { + bool nocull = tri.Subtype == 0 || tri.Subtype == 2; + bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; + + for (int i = 0; i < 3; ++i) + { + var v_n = !flip ? i : 2 - i; + list [tri.Vertex [v_n] + Frame.SpecialVertexCount].color = model.Colors [tri.Color [v_n]]; + } + } + + foreach (GameEntries entry in list) + { + obj.WriteLine( + "v {0} {1} {2} {3} {4} {5}", + ((entry.vertex.X + (Frame.XOffset / 4F)) * scaleX).ToString(CultureInfo.InvariantCulture), + ((entry.vertex.Z + (Frame.YOffset / 4F)) * scaleY).ToString(CultureInfo.InvariantCulture), + ((entry.vertex.Y + (Frame.ZOffset / 4F)) * scaleZ).ToString(CultureInfo.InvariantCulture), + (entry.color.Red / 255F).ToString(CultureInfo.InvariantCulture), + (entry.color.Green / 255F).ToString(CultureInfo.InvariantCulture), + (entry.color.Blue / 255F).ToString(CultureInfo.InvariantCulture)); + } + + obj.WriteLine(); + obj.WriteLine("# Triangles"); + + foreach (ModelTransformedTriangle tri in model.Triangles) + { + bool nocull = tri.Subtype == 0 || tri.Subtype == 2; + bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; + int [] verts = new int[3]; + + for (int i = 0; i < 3; ++i) + { + var v_n = !flip ? i : 2 - i; + verts [i] = tri.Vertex [v_n] + Frame.SpecialVertexCount; + } + + obj.WriteLine("f {0} {1} {2}", verts[0] + 1, verts[1] + 1, verts[2] + 1); + } + } + + return stream.ToArray(); + } + } + } } diff --git a/CrashEdit/FileUtil.cs b/CrashEdit/FileUtil.cs index 82588a9f..6fba6437 100755 --- a/CrashEdit/FileUtil.cs +++ b/CrashEdit/FileUtil.cs @@ -46,6 +46,29 @@ public static byte[][] OpenFiles(params string[] filters) } } + /// + /// Allows the user to select a path to save a file but leaves the actual file saving to the caller + /// + /// Useful for batch exports of OBJ files + /// + /// The filename to write as + /// + /// If the user selected a file to save + public static bool SelectSaveFile (out string filename, params string [] filters) + { + savefiledlg.Filter = string.Join("|", filters); + if (savefiledlg.ShowDialog(Owner) == DialogResult.OK) + { + filename = savefiledlg.FileName; + return true; + } + else + { + filename = null; + return false; + } + } + public static bool SaveFile(byte[] data, params string[] filters) { ArgumentNullException.ThrowIfNull(data); From 9ed63334b185147ed0831ceca9a21c6e643a0e7f Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 01:34:23 +0200 Subject: [PATCH 02/17] Applied same treatment to Crash 1's animations --- .../Crash Formats/Animation/OldFrame.cs | 41 ---- .../ColoredAnimationEntryController.cs | 47 +++++ .../Controllers/Animation/FrameController.cs | 2 +- .../Animation/OldAnimationEntryController.cs | 47 +++++ .../Animation/OldFrameController.cs | 184 +++++++++++++++++- 5 files changed, 276 insertions(+), 45 deletions(-) diff --git a/Crash/Formats/Crash Formats/Animation/OldFrame.cs b/Crash/Formats/Crash Formats/Animation/OldFrame.cs index 68fe29a9..0c297980 100755 --- a/Crash/Formats/Crash Formats/Animation/OldFrame.cs +++ b/Crash/Formats/Crash Formats/Animation/OldFrame.cs @@ -128,46 +128,5 @@ public byte[] Save() } return data; } - - public byte[] ToOBJ(OldModelEntry model) - { - long xorigin = 0; - long yorigin = 0; - long zorigin = 0; - //foreach (OldFrameVertex vertex in vertices) - //{ - // xorigin += vertex.X; - // yorigin += vertex.Y; - // zorigin += vertex.Z; - //} - //xorigin /= vertices.Count; - //yorigin /= vertices.Count; - //zorigin /= vertices.Count; - xorigin -= XOffset; - yorigin -= YOffset; - zorigin -= ZOffset; - using (MemoryStream stream = new MemoryStream()) - { - using (StreamWriter obj = new StreamWriter(stream)) - { - obj.WriteLine("# Vertices"); - foreach (OldFrameVertex vertex in vertices) - { - obj.WriteLine("v {0} {1} {2}", vertex.X - xorigin, vertex.Y - yorigin, vertex.Z - zorigin); - } - foreach (OldFrameVertex vertex in vertices) - { - obj.WriteLine("vn {0} {1} {2}", vertex.NormalX / 127.0, vertex.NormalY / 127.0, vertex.NormalZ / 127.0); - } - obj.WriteLine(); - obj.WriteLine("# Polygons"); - foreach (OldModelPolygon polygon in model.Polygons) - { - obj.WriteLine("f {0}//{0} {1}//{1} {2}//{2}", polygon.VertexA / 6 + 1, polygon.VertexB / 6 + 1, polygon.VertexC / 6 + 1); - } - } - return stream.ToArray(); - } - } } } diff --git a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs index 276ad1f2..4aa43691 100644 --- a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs @@ -9,6 +9,8 @@ public ColoredAnimationEntryController(ColoredAnimationEntry coloredanimationent : base(coloredanimationentry, parentGroup) { ColoredAnimationEntry = coloredanimationentry; + AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); + AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); } public override bool EditorAvailable => true; @@ -19,6 +21,51 @@ public override Control CreateEditor() } public ColoredAnimationEntry ColoredAnimationEntry { get; } + + private void Menu_Export_OBJ_Processed() + { + FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); + + // modify the path to add a number before the extension + string ext = Path.GetExtension (output); + string filename = Path.GetFileNameWithoutExtension (output); + string path = Path.GetDirectoryName (output); + + int id = 0; + + foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) + { + if (node.Legacy is not FrameController frame) + continue; + + string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; + File.WriteAllBytes (final, frame.ToProcessedOBJ ()); + id++; + } + } + + private void Menu_Export_OBJ_Game () + { + FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); + + // modify the path to add a number before the extension + string ext = Path.GetExtension (output); + string filename = Path.GetFileNameWithoutExtension (output); + string path = Path.GetDirectoryName (output); + + int id = 0; + + foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) + { + if (node.Legacy is not FrameController frame) + continue; + + string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; + File.WriteAllBytes (final, frame.ToGameOBJ ()); + id++; + } + } + } } diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index 6f4dc881..61b09a0c 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -130,7 +130,7 @@ public byte[] ToProcessedOBJ() /// TODO: MAYBE IMPLEMENT AN FBX EXPORT OR SOMETHING ELSE THAT IS A BIT MORE FLEXIBLE? /// /// This function resides here because access to GameScales is required, and the Frame object does not have access to it - /// a good improvement would be to move this there + /// a good improvement might be to move this there /// /// public byte[] ToGameOBJ() diff --git a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs index 3231fe75..45bb7f38 100755 --- a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs @@ -8,6 +8,8 @@ public sealed class OldAnimationEntryController : EntryController public OldAnimationEntryController(OldAnimationEntry oldanimationentry, SubcontrollerGroup parentGroup) : base(oldanimationentry, parentGroup) { OldAnimationEntry = oldanimationentry; + AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); + AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); } public override bool EditorAvailable => true; @@ -18,5 +20,50 @@ public override Control CreateEditor() } public OldAnimationEntry OldAnimationEntry { get; } + + + private void Menu_Export_OBJ_Processed() + { + FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); + + // modify the path to add a number before the extension + string ext = Path.GetExtension (output); + string filename = Path.GetFileNameWithoutExtension (output); + string path = Path.GetDirectoryName (output); + + int id = 0; + + foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) + { + if (node.Legacy is not OldFrameController frame) + continue; + + string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; + File.WriteAllBytes (final, frame.ToProcessedOBJ ()); + id++; + } + } + + private void Menu_Export_OBJ_Game () + { + FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); + + // modify the path to add a number before the extension + string ext = Path.GetExtension (output); + string filename = Path.GetFileNameWithoutExtension (output); + string path = Path.GetDirectoryName (output); + + int id = 0; + + foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) + { + if (node.Legacy is not OldFrameController frame) + continue; + + string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; + File.WriteAllBytes (final, frame.ToGameOBJ ()); + id++; + } + } } } diff --git a/CrashEdit/Controllers/Animation/OldFrameController.cs b/CrashEdit/Controllers/Animation/OldFrameController.cs index 363f6604..977cf756 100755 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -1,3 +1,4 @@ +using System.Globalization; using CrashEdit.Crash; namespace CrashEdit.CE @@ -8,7 +9,8 @@ public sealed class OldFrameController : LegacyController public OldFrameController(OldFrame oldframe, SubcontrollerGroup parentGroup) : base(parentGroup, oldframe) { OldFrame = oldframe; - AddMenu("Export as OBJ", Menu_Export_OBJ); + AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); + AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); } public override bool EditorAvailable => true; @@ -45,7 +47,7 @@ public override Control CreateEditor() public ColoredAnimationEntryController ColoredAnimationEntryController => Modern.Parent.Legacy as ColoredAnimationEntryController; public OldFrame OldFrame { get; } - private void Menu_Export_OBJ() + private void Menu_Export_OBJ_Game() { OldModelEntry modelentry = GetEntry(OldFrame.ModelEID); if (modelentry == null) @@ -56,7 +58,183 @@ private void Menu_Export_OBJ() { return; } - FileUtil.SaveFile(OldFrame.ToOBJ(modelentry), FileFilters.OBJ, FileFilters.Any); + FileUtil.SaveFile(this.ToGameOBJ(), FileFilters.OBJ, FileFilters.Any); + } + + private void Menu_Export_OBJ_Processed() + { + if (MessageBox.Show("Texture and color information will not be exported.\n\nContinue anyway?", "Export as OBJ", MessageBoxButtons.YesNo) != DialogResult.Yes) + { + return; + } + FileUtil.SaveFile(this.ToProcessedOBJ(), FileFilters.OBJ, FileFilters.Any); + } + + class ProcessedEntries + { + public float VX; + public float VY; + public float VZ; + public float NX; + public float NY; + public float NZ; + public OldSceneryColor color; + } + + class GameEntries + { + public OldFrameVertex vertex; + public OldSceneryColor color; + } + + public byte[] ToProcessedOBJ() + { + using (MemoryStream stream = new MemoryStream()) + { + using (StreamWriter obj = new StreamWriter(stream)) + { + var model = GetNSF ().GetEntry(OldFrame.ModelEID); + + // build scale + float scaleX = model.ScaleX / (GameScales.ModelC1 * GameScales.AnimC1); + float scaleY = model.ScaleY / (GameScales.ModelC1 * GameScales.AnimC1); + float scaleZ = model.ScaleZ / (GameScales.ModelC1 * GameScales.AnimC1); + + var list = new List (); + + foreach (OldModelPolygon polygon in model.Polygons) + { + OldModelStruct str = model.Structs[polygon.TexInfo]; + + // add the three polygons to the list + int [] vertices = new [] {polygon.VertexA / 6, polygon.VertexB / 6, polygon.VertexC / 6}; + + foreach (int vertex in vertices) + { + ProcessedEntries entry = new ProcessedEntries (); + + if (str is OldSceneryColor color) + entry.color = color; + + entry.VX = (OldFrame.Vertices [vertex].X - OldFrame.XOffset) * scaleX; + entry.VY = (OldFrame.Vertices [vertex].Y - OldFrame.YOffset) * scaleY; + entry.VZ = (OldFrame.Vertices [vertex].Z - OldFrame.ZOffset) * scaleZ; + entry.NX = OldFrame.Vertices [vertex].NormalX / 127F; + entry.NY = OldFrame.Vertices [vertex].NormalY / 127F; + entry.NZ = OldFrame.Vertices [vertex].NormalZ / 127F; + + list.Add (entry); + } + } + + obj.WriteLine("# Vertices"); + foreach (ProcessedEntries entry in list) + { + obj.WriteLine("v {0} {1} {2} {3} {4} {5}", + entry.VX.ToString(CultureInfo.InvariantCulture), + entry.VY.ToString(CultureInfo.InvariantCulture), + entry.VZ.ToString(CultureInfo.InvariantCulture), + (entry.color.R / 255F).ToString(CultureInfo.InvariantCulture), + (entry.color.G / 255F).ToString(CultureInfo.InvariantCulture), + (entry.color.B / 255F).ToString(CultureInfo.InvariantCulture) + ); + } + + foreach (ProcessedEntries vertex in list) + { + obj.WriteLine("vn {0} {1} {2}", + vertex.NX.ToString(CultureInfo.InvariantCulture), + vertex.NY.ToString(CultureInfo.InvariantCulture), + vertex.NZ.ToString(CultureInfo.InvariantCulture) + ); + } + + obj.WriteLine(); + obj.WriteLine("# Triangles"); + + for (int i = 0; i < list.Count; i += 3) + { + obj.WriteLine("f {0}//{0} {1}//{1} {2}//{2}", i + 1, i + 2, i + 3); + } + } + return stream.ToArray(); + } + } + /// + /// Generates a OBJ file with the model's information as close as to what the original frame is + /// + /// Due to OBJ format we cannot include all the required colors for each face + /// so it looks way out of place. + /// + /// TODO: MAYBE IMPLEMENT AN FBX EXPORT OR SOMETHING ELSE THAT IS A BIT MORE FLEXIBLE? + /// + /// This function resides here because access to GameScales is required, and the Frame object does not have access to it + /// a good improvement might be to move this there + /// + /// + public byte[] ToGameOBJ() + { + using (MemoryStream stream = new MemoryStream()) + { + using (StreamWriter obj = new StreamWriter(stream)) + { + var model = GetNSF ().GetEntry(OldFrame.ModelEID); + + // build scale + float scaleX = model.ScaleX / (GameScales.ModelC1 * GameScales.AnimC1); + float scaleY = model.ScaleY / (GameScales.ModelC1 * GameScales.AnimC1); + float scaleZ = model.ScaleZ / (GameScales.ModelC1 * GameScales.AnimC1); + + var list = new List (); + + foreach (OldFrameVertex vertex in OldFrame.Vertices) + { + list.Add (new GameEntries { vertex = vertex }); + } + + // update color information + foreach (OldModelPolygon polygon in model.Polygons) + { + OldModelStruct str = model.Structs[polygon.TexInfo]; + + if (str is not OldSceneryColor color) + continue; + + list [polygon.VertexA / 6].color = color; + list [polygon.VertexB / 6].color = color; + list [polygon.VertexC / 6].color = color; + } + + obj.WriteLine("# Vertices"); + foreach (GameEntries entry in list) + { + obj.WriteLine("v {0} {1} {2} {3} {4} {5}", + ((entry.vertex.X - OldFrame.XOffset) * scaleX).ToString(CultureInfo.InvariantCulture), + ((entry.vertex.Y - OldFrame.YOffset) * scaleY).ToString(CultureInfo.InvariantCulture), + ((entry.vertex.Z - OldFrame.ZOffset) * scaleZ).ToString(CultureInfo.InvariantCulture), + (entry.color.R / 255F).ToString(CultureInfo.InvariantCulture), + (entry.color.G / 255F).ToString(CultureInfo.InvariantCulture), + (entry.color.B / 255F).ToString(CultureInfo.InvariantCulture) + ); + } + + foreach (OldFrameVertex vertex in OldFrame.Vertices) + { + obj.WriteLine("vn {0} {1} {2}", + (vertex.NormalX/127F).ToString(CultureInfo.InvariantCulture), + (vertex.NormalY/127F).ToString(CultureInfo.InvariantCulture), + (vertex.NormalZ/127F).ToString(CultureInfo.InvariantCulture) + ); + } + obj.WriteLine(); + obj.WriteLine("# Triangles"); + foreach (OldModelPolygon polygon in model.Polygons) + { + obj.WriteLine("f {0}//{0} {1}//{1} {2}//{2}", polygon.VertexA / 6 + 1,polygon.VertexB / 6 + 1,polygon.VertexC / 6 + 1); + } + } + return stream.ToArray(); + } } } } From 624d8378384f8092bb9e1385355d6ab74360901e Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 18:41:47 +0200 Subject: [PATCH 03/17] Proper and close to final implementation of OBJ exporting frames Signed-off-by: Alexis Maiquez Murcia --- CrashEdit.Main.csproj | 2 + .../Animation/AnimationEntryController.cs | 30 +- .../ColoredAnimationEntryController.cs | 31 +- .../Controllers/Animation/FrameController.cs | 293 ++++++++------- .../Animation/OldAnimationEntryController.cs | 31 +- .../Animation/OldFrameController.cs | 250 +++++-------- CrashEdit/Exporters/OBJExporter.cs | 352 ++++++++++++++++++ CrashEdit/Exporters/TextureExporter.cs | 129 +++++++ 8 files changed, 734 insertions(+), 384 deletions(-) create mode 100644 CrashEdit/Exporters/OBJExporter.cs create mode 100644 CrashEdit/Exporters/TextureExporter.cs diff --git a/CrashEdit.Main.csproj b/CrashEdit.Main.csproj index ea0c32eb..411a423f 100644 --- a/CrashEdit.Main.csproj +++ b/CrashEdit.Main.csproj @@ -4,6 +4,8 @@ net8.0-windows enable enable + false + false diff --git a/CrashEdit/Controllers/Animation/AnimationEntryController.cs b/CrashEdit/Controllers/Animation/AnimationEntryController.cs index 935f0778..27607b7c 100644 --- a/CrashEdit/Controllers/Animation/AnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/AnimationEntryController.cs @@ -8,8 +8,7 @@ public sealed class AnimationEntryController : EntryController public AnimationEntryController(AnimationEntry animationentry, SubcontrollerGroup parentGroup) : base(animationentry, parentGroup) { AnimationEntry = animationentry; - AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); - AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); + AddMenu ("Export as OBJ", Menu_Export_OBJ); } public override bool EditorAvailable => true; @@ -21,29 +20,7 @@ public override Control CreateEditor() public AnimationEntry AnimationEntry { get; } - private void Menu_Export_OBJ_Processed() - { - FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); - - // modify the path to add a number before the extension - string ext = Path.GetExtension (output); - string filename = Path.GetFileNameWithoutExtension (output); - string path = Path.GetDirectoryName (output); - - int id = 0; - - foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) - { - if (node.Legacy is not FrameController frame) - continue; - - string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; - File.WriteAllBytes (final, frame.ToProcessedOBJ ()); - id++; - } - } - - private void Menu_Export_OBJ_Game () + private void Menu_Export_OBJ () { FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); @@ -59,8 +36,7 @@ private void Menu_Export_OBJ_Game () if (node.Legacy is not FrameController frame) continue; - string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; - File.WriteAllBytes (final, frame.ToGameOBJ ()); + frame.ToOBJ (path, filename + id.ToString()); id++; } } diff --git a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs index 4aa43691..205ba21d 100644 --- a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs @@ -9,8 +9,7 @@ public ColoredAnimationEntryController(ColoredAnimationEntry coloredanimationent : base(coloredanimationentry, parentGroup) { ColoredAnimationEntry = coloredanimationentry; - AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); - AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); + AddMenu ("Export as OBJ", Menu_Export_OBJ); } public override bool EditorAvailable => true; @@ -22,7 +21,7 @@ public override Control CreateEditor() public ColoredAnimationEntry ColoredAnimationEntry { get; } - private void Menu_Export_OBJ_Processed() + private void Menu_Export_OBJ() { FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); @@ -38,34 +37,10 @@ private void Menu_Export_OBJ_Processed() if (node.Legacy is not FrameController frame) continue; - string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; - File.WriteAllBytes (final, frame.ToProcessedOBJ ()); + frame.ToOBJ (path, filename + id.ToString()); id++; } } - - private void Menu_Export_OBJ_Game () - { - FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); - - // modify the path to add a number before the extension - string ext = Path.GetExtension (output); - string filename = Path.GetFileNameWithoutExtension (output); - string path = Path.GetDirectoryName (output); - - int id = 0; - - foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) - { - if (node.Legacy is not FrameController frame) - continue; - - string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; - File.WriteAllBytes (final, frame.ToGameOBJ ()); - id++; - } - } - } } diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index 61b09a0c..0b01cd92 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -1,5 +1,7 @@ using System.Globalization; using CrashEdit.Crash; +using CrashEdit.Exporters; +using OpenTK.Mathematics; namespace CrashEdit.CE { @@ -9,8 +11,7 @@ public sealed class FrameController : LegacyController public FrameController(Frame frame, SubcontrollerGroup parentGroup) : base(parentGroup, frame) { Frame = frame; - AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); - AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); + AddMenu ("Export as OBJ", Menu_Export_OBJ); } public override bool EditorAvailable => true; @@ -24,184 +25,200 @@ public override Control CreateEditor() public Frame Frame { get; } - private void Menu_Export_OBJ_Processed() + private void Menu_Export_OBJ () { - FileUtil.SaveFile(this.ToProcessedOBJ(), FileFilters.OBJ, FileFilters.Any); - } - - private void Menu_Export_OBJ_Game () - { - FileUtil.SaveFile(this.ToGameOBJ(), FileFilters.OBJ, FileFilters.Any); - } - - class ProcessedEntries - { - public float VX; - public float VY; - public float VZ; - public SceneryColor color; - } - - class GameEntries - { - public Position vertex; - public SceneryColor color; + FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any); + ToOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); } /// - /// Generates a OBJ file with the model's information ready to be displayed in any other 3D software. - /// It includes extra geometry to ensure vertex colors do look properly + /// Exports the model to the OBJ file format ready to be used with other software + /// + /// TODO: MAYBE IMPLEMENT AN FBX EXPORT OR SOMETHING ELSE THAT IS A BIT MORE FLEXIBLE? /// /// This function resides here because access to GameScales is required, and the Frame object does not have access to it - /// a good improvement would be to move this there + /// a good improvement might be to move this there /// /// - public byte[] ToProcessedOBJ() + public void ToOBJ (string path, string modelname) { - using (MemoryStream stream = new MemoryStream()) + var exporter = new OBJExporter (); + var model = GetNSF ().GetEntry(Frame.ModelEID); + var vertices = Frame.MakeVertices (model); + var offset = new Vector3 (Frame.XOffset, Frame.YOffset, Frame.ZOffset) / 4F; + var scale = new Vector3 (model.ScaleX, model.ScaleY, model.ScaleZ) / (GameScales.ModelC1 * GameScales.AnimC1); + + // detect how many textures are used and their eids to prepare the image + Dictionary textureEIDs = new (); + + for (int i = 0; i < model.TPAGCount; i++) { - using (StreamWriter obj = new StreamWriter(stream)) - { - var model = this.AnimationEntryController.GetNSF ().GetEntry(Frame.ModelEID); - - obj.WriteLine("# Vertices"); + int tpag_eid = model.GetTPAG (i); - var vertices = Frame.MakeVertices (model); - var list = new List (); + if (textureEIDs.ContainsKey (tpag_eid)) + continue; - // build scale - float scaleX = model.ScaleX / (GameScales.ModelC1 * GameScales.AnimC1); - float scaleY = model.ScaleY / (GameScales.ModelC1 * GameScales.AnimC1); - float scaleZ = model.ScaleZ / (GameScales.ModelC1 * GameScales.AnimC1); + textureEIDs.Add (tpag_eid, textureEIDs.Count); + } + + // once we have the textureEIDs we know what has to be loaded where + // these textures are 128 pixels of height + // and the game loads every texture used and keeps a lookup table of the texture "index" + + // depending on where in the model it's going to be drawn, the texture is treated differently + // (4 bits per pixel, 8 bits per pixel, 16 bits per pixel) + // that information is inside each triangle of the frame + // so the same texture can be treated differently + // try to build an atlas of sorts with all the information + Dictionary objTranslate = new Dictionary (); + + // iterate all the triangles, get the texture modes and build information about those + foreach (var tri in model.Triangles) + { + var info = ProcessTextureInfoC2 (tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures); + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null; + bool nocull = tri.Subtype == 0 || tri.Subtype == 2; + bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; + + // parse the texture and add it to the exporter + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; - foreach (ModelTransformedTriangle tri in model.Triangles) + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == textureEIDs [model.GetTPAG (value.Page)] + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) { - bool nocull = tri.Subtype == 0 || tri.Subtype == 2; - bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [model.GetTPAG (value.Page)] + ); - for (int i = 0; i < 3; ++i) - { - var v_n = !flip ? i : 2 - i; - Position vert = vertices [tri.Vertex [v_n] + Frame.SpecialVertexCount]; - SceneryColor color = model.Colors [tri.Color [v_n]]; - - // calculate positions - list.Add (new ProcessedEntries - { - VX = (vert.X + (Frame.XOffset / 4F)) * scaleX, - VY = (vert.Z + (Frame.YOffset / 4F)) * scaleY, - VZ = (vert.Y + (Frame.ZOffset / 4F)) * scaleZ, - color = color - }); - } - } + var tpag = GetNSF ().GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); - foreach (ProcessedEntries entry in list) - { - obj.WriteLine( - "v {0} {1} {2} {3} {4} {5}", - entry.VX.ToString(CultureInfo.InvariantCulture), - entry.VY.ToString(CultureInfo.InvariantCulture), - entry.VZ.ToString(CultureInfo.InvariantCulture), - (entry.color.Red / 255F).ToString(CultureInfo.InvariantCulture), - (entry.color.Green / 255F).ToString(CultureInfo.InvariantCulture), - (entry.color.Blue / 255F).ToString(CultureInfo.InvariantCulture)); + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; } - obj.WriteLine (""); - obj.WriteLine ("# Triangles"); + Vector2 texsize = value.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; - for (int i = 0; i < list.Count; i+=3) + uv2 = new Vector2 (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + + if ((tri.Type != 2 && !flip) || (tri.Type == 2 && tri.Subtype == 1)) + { + uv1 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + uv3 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + } + else { - obj.WriteLine ("f {0} {1} {2}", i + 1, i + 2, i + 3); + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + uv1 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); } } - return stream.ToArray(); + // add the face + SceneryColor fc1 = model.Colors [tri.Color [!flip ? 0 : 2]]; + SceneryColor fc2 = model.Colors [tri.Color [!flip ? 1 : 1]]; + SceneryColor fc3 = model.Colors [tri.Color [!flip ? 2 : 0]]; + Position fv1 = vertices [tri.Vertex [!flip ? 0 : 2] + Frame.SpecialVertexCount]; + Position fv2 = vertices [tri.Vertex [!flip ? 1 : 1] + Frame.SpecialVertexCount]; + Position fv3 = vertices [tri.Vertex [!flip ? 2 : 0] + Frame.SpecialVertexCount]; + Vector3 v1 = new Vector3 (fv1.X, fv1.Z, fv1.Y); + Vector3 v2 = new Vector3 (fv2.X, fv2.Z, fv2.Y); + Vector3 v3 = new Vector3 (fv3.X, fv3.Z, fv3.Y); + Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; + Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; + Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; + + exporter.AddFace ( + (v1 + offset) * scale, + (v2 + offset) * scale, + (v3 + offset) * scale, + c1, c2, c3, + material, + uv1, uv2, uv3 + ); } + + exporter.Export (path, modelname); } - + /// - /// Generates a OBJ file with the model's information as close as to what the original frame is - /// - /// Due to OBJ format we cannot include all the required colors for each face - /// so it looks way out of place. - /// - /// TODO: MAYBE IMPLEMENT AN FBX EXPORT OR SOMETHING ELSE THAT IS A BIT MORE FLEXIBLE? - /// - /// This function resides here because access to GameScales is required, and the Frame object does not have access to it - /// a good improvement might be to move this there + /// TODO: THIS IS A DUPLICATED OF GLViewer:899, MOVE IT SOMEWHERE ELSE WHERE BOTH HAVE ACCESS TO IT /// + /// + /// + /// + /// /// - public byte[] ToGameOBJ() + protected Tuple ProcessTextureInfoC2(int in_tex_id, bool animated, IList textures, IList animated_textures) { - using (MemoryStream stream = new MemoryStream()) + if (in_tex_id != 0 || animated) { - using (StreamWriter obj = new StreamWriter(stream)) + ModelTexture? info_temp = null; + int tex_id = in_tex_id - 1; + if (animated) { - var model = this.AnimationEntryController.GetNSF ().GetEntry(Frame.ModelEID); - - obj.WriteLine("# Vertices"); - - var vertices = Frame.MakeVertices (model); - var list = new List (); - - // build scale - float scaleX = model.ScaleX / (GameScales.ModelC1 * GameScales.AnimC1); - float scaleY = model.ScaleY / (GameScales.ModelC1 * GameScales.AnimC1); - float scaleZ = model.ScaleZ / (GameScales.ModelC1 * GameScales.AnimC1); - - foreach (Position vertex in vertices) + if (++tex_id >= animated_textures.Count) { - list.Add (new GameEntries { vertex = vertex }); + return new(false, null); } - - foreach (ModelTransformedTriangle tri in model.Triangles) + var anim = animated_textures[tex_id]; + // check if it's an untextured polygon + if (anim.Offset != 0) { - bool nocull = tri.Subtype == 0 || tri.Subtype == 2; - bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; - - for (int i = 0; i < 3; ++i) + tex_id = anim.Offset - 1; + if (anim.IsLOD) { - var v_n = !flip ? i : 2 - i; - list [tri.Vertex [v_n] + Frame.SpecialVertexCount].color = model.Colors [tri.Color [v_n]]; + tex_id += anim.LOD0; // we only render closest LOD for now } - } - - foreach (GameEntries entry in list) - { - obj.WriteLine( - "v {0} {1} {2} {3} {4} {5}", - ((entry.vertex.X + (Frame.XOffset / 4F)) * scaleX).ToString(CultureInfo.InvariantCulture), - ((entry.vertex.Z + (Frame.YOffset / 4F)) * scaleY).ToString(CultureInfo.InvariantCulture), - ((entry.vertex.Y + (Frame.ZOffset / 4F)) * scaleZ).ToString(CultureInfo.InvariantCulture), - (entry.color.Red / 255F).ToString(CultureInfo.InvariantCulture), - (entry.color.Green / 255F).ToString(CultureInfo.InvariantCulture), - (entry.color.Blue / 255F).ToString(CultureInfo.InvariantCulture)); - } - - obj.WriteLine(); - obj.WriteLine("# Triangles"); - - foreach (ModelTransformedTriangle tri in model.Triangles) - { - bool nocull = tri.Subtype == 0 || tri.Subtype == 2; - bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; - int [] verts = new int[3]; - - for (int i = 0; i < 3; ++i) + else { - var v_n = !flip ? i : 2 - i; - verts [i] = tri.Vertex [v_n] + Frame.SpecialVertexCount; + tex_id += (int)((0 / 2 / (1 + anim.Latency) + anim.Delay) & anim.Mask); + if (anim.Leap) + { + anim = animated_textures[++tex_id]; + tex_id = anim.Offset - 1 + anim.LOD0; + } } - - obj.WriteLine("f {0} {1} {2}", verts[0] + 1, verts[1] + 1, verts[2] + 1); + if (tex_id >= textures.Count) + { + return new(false, null); + } + info_temp = textures[tex_id]; } } - - return stream.ToArray(); + else + { + if (tex_id >= textures.Count) + { + return new(false, null); + } + info_temp = textures[tex_id]; + } + return new(true, info_temp); } + return new(true, null); } + } } diff --git a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs index 45bb7f38..5348f6ca 100755 --- a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs @@ -8,8 +8,7 @@ public sealed class OldAnimationEntryController : EntryController public OldAnimationEntryController(OldAnimationEntry oldanimationentry, SubcontrollerGroup parentGroup) : base(oldanimationentry, parentGroup) { OldAnimationEntry = oldanimationentry; - AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); - AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); + AddMenu ("Export as OBJ", Menu_Export_OBJ); } public override bool EditorAvailable => true; @@ -21,30 +20,7 @@ public override Control CreateEditor() public OldAnimationEntry OldAnimationEntry { get; } - - private void Menu_Export_OBJ_Processed() - { - FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); - - // modify the path to add a number before the extension - string ext = Path.GetExtension (output); - string filename = Path.GetFileNameWithoutExtension (output); - string path = Path.GetDirectoryName (output); - - int id = 0; - - foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) - { - if (node.Legacy is not OldFrameController frame) - continue; - - string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; - File.WriteAllBytes (final, frame.ToProcessedOBJ ()); - id++; - } - } - - private void Menu_Export_OBJ_Game () + private void Menu_Export_OBJ () { FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); @@ -60,8 +36,7 @@ private void Menu_Export_OBJ_Game () if (node.Legacy is not OldFrameController frame) continue; - string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; - File.WriteAllBytes (final, frame.ToGameOBJ ()); + frame.ToOBJ (path, filename + id.ToString()); id++; } } diff --git a/CrashEdit/Controllers/Animation/OldFrameController.cs b/CrashEdit/Controllers/Animation/OldFrameController.cs index 977cf756..cabb281f 100755 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -1,5 +1,7 @@ using System.Globalization; using CrashEdit.Crash; +using CrashEdit.Exporters; +using OpenTK.Mathematics; namespace CrashEdit.CE { @@ -9,8 +11,7 @@ public sealed class OldFrameController : LegacyController public OldFrameController(OldFrame oldframe, SubcontrollerGroup parentGroup) : base(parentGroup, oldframe) { OldFrame = oldframe; - AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); - AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); + AddMenu ("Export as OBJ", Menu_Export_OBJ); } public override bool EditorAvailable => true; @@ -47,7 +48,7 @@ public override Control CreateEditor() public ColoredAnimationEntryController ColoredAnimationEntryController => Modern.Parent.Legacy as ColoredAnimationEntryController; public OldFrame OldFrame { get; } - private void Menu_Export_OBJ_Game() + private void Menu_Export_OBJ() { OldModelEntry modelentry = GetEntry(OldFrame.ModelEID); if (modelentry == null) @@ -58,183 +59,106 @@ private void Menu_Export_OBJ_Game() { return; } - FileUtil.SaveFile(this.ToGameOBJ(), FileFilters.OBJ, FileFilters.Any); + FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any); + ToOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); } - - private void Menu_Export_OBJ_Processed() - { - if (MessageBox.Show("Texture and color information will not be exported.\n\nContinue anyway?", "Export as OBJ", MessageBoxButtons.YesNo) != DialogResult.Yes) - { - return; - } - FileUtil.SaveFile(this.ToProcessedOBJ(), FileFilters.OBJ, FileFilters.Any); - } - - class ProcessedEntries - { - public float VX; - public float VY; - public float VZ; - public float NX; - public float NY; - public float NZ; - public OldSceneryColor color; - } - - class GameEntries - { - public OldFrameVertex vertex; - public OldSceneryColor color; - } - - public byte[] ToProcessedOBJ() + + public void ToOBJ(string path, string modelname) { - using (MemoryStream stream = new MemoryStream()) - { - using (StreamWriter obj = new StreamWriter(stream)) - { - var model = GetNSF ().GetEntry(OldFrame.ModelEID); + var exporter = new OBJExporter (); + var model = GetNSF ().GetEntry(OldFrame.ModelEID); + var offset = new Vector3 (OldFrame.XOffset, OldFrame.YOffset, OldFrame.ZOffset); + var scale = new Vector3 (model.ScaleX, model.ScaleY, model.ScaleZ) / (GameScales.ModelC1 * GameScales.AnimC1); - // build scale - float scaleX = model.ScaleX / (GameScales.ModelC1 * GameScales.AnimC1); - float scaleY = model.ScaleY / (GameScales.ModelC1 * GameScales.AnimC1); - float scaleZ = model.ScaleZ / (GameScales.ModelC1 * GameScales.AnimC1); + // detect how many textures are used an ther eids to prepare the image + Dictionary textureEIDs = new (); - var list = new List (); + foreach (OldModelStruct str in model.Structs) + { + if (str is not OldModelTexture tex) + continue; - foreach (OldModelPolygon polygon in model.Polygons) - { - OldModelStruct str = model.Structs[polygon.TexInfo]; - - // add the three polygons to the list - int [] vertices = new [] {polygon.VertexA / 6, polygon.VertexB / 6, polygon.VertexC / 6}; - - foreach (int vertex in vertices) - { - ProcessedEntries entry = new ProcessedEntries (); - - if (str is OldSceneryColor color) - entry.color = color; - - entry.VX = (OldFrame.Vertices [vertex].X - OldFrame.XOffset) * scaleX; - entry.VY = (OldFrame.Vertices [vertex].Y - OldFrame.YOffset) * scaleY; - entry.VZ = (OldFrame.Vertices [vertex].Z - OldFrame.ZOffset) * scaleZ; - entry.NX = OldFrame.Vertices [vertex].NormalX / 127F; - entry.NY = OldFrame.Vertices [vertex].NormalY / 127F; - entry.NZ = OldFrame.Vertices [vertex].NormalZ / 127F; - - list.Add (entry); - } - } - - obj.WriteLine("# Vertices"); - foreach (ProcessedEntries entry in list) - { - obj.WriteLine("v {0} {1} {2} {3} {4} {5}", - entry.VX.ToString(CultureInfo.InvariantCulture), - entry.VY.ToString(CultureInfo.InvariantCulture), - entry.VZ.ToString(CultureInfo.InvariantCulture), - (entry.color.R / 255F).ToString(CultureInfo.InvariantCulture), - (entry.color.G / 255F).ToString(CultureInfo.InvariantCulture), - (entry.color.B / 255F).ToString(CultureInfo.InvariantCulture) - ); - } - - foreach (ProcessedEntries vertex in list) - { - obj.WriteLine("vn {0} {1} {2}", - vertex.NX.ToString(CultureInfo.InvariantCulture), - vertex.NY.ToString(CultureInfo.InvariantCulture), - vertex.NZ.ToString(CultureInfo.InvariantCulture) - ); - } - - obj.WriteLine(); - obj.WriteLine("# Triangles"); - - for (int i = 0; i < list.Count; i += 3) - { - obj.WriteLine("f {0}//{0} {1}//{1} {2}//{2}", i + 1, i + 2, i + 3); - } - } - return stream.ToArray(); + if (textureEIDs.ContainsKey (tex.EID)) + continue; + + textureEIDs [tex.EID] = textureEIDs.Count; } - } - /// - /// Generates a OBJ file with the model's information as close as to what the original frame is - /// - /// Due to OBJ format we cannot include all the required colors for each face - /// so it looks way out of place. - /// - /// TODO: MAYBE IMPLEMENT AN FBX EXPORT OR SOMETHING ELSE THAT IS A BIT MORE FLEXIBLE? - /// - /// This function resides here because access to GameScales is required, and the Frame object does not have access to it - /// a good improvement might be to move this there - /// - /// - public byte[] ToGameOBJ() - { - using (MemoryStream stream = new MemoryStream()) + + Dictionary objTranslate = new Dictionary (); + + foreach (OldModelPolygon polygon in model.Polygons) { - using (StreamWriter obj = new StreamWriter(stream)) + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null; + OldModelStruct str = model.Structs[polygon.TexInfo]; + OldFrameVertex ov1 = OldFrame.Vertices [polygon.VertexA / 6]; + OldFrameVertex ov2 = OldFrame.Vertices [polygon.VertexB / 6]; + OldFrameVertex ov3 = OldFrame.Vertices [polygon.VertexC / 6]; + + Vector3 v1 = new Vector3 (ov1.X, ov1.Y, ov1.Z); + Vector3 v2 = new Vector3 (ov2.X, ov2.Y, ov2.Z); + Vector3 v3 = new Vector3 (ov3.X, ov3.Y, ov3.Z); + Vector3 color = Vector3.Zero; + + if (str is OldModelTexture t) { - var model = GetNSF ().GetEntry(OldFrame.ModelEID); - - // build scale - float scaleX = model.ScaleX / (GameScales.ModelC1 * GameScales.AnimC1); - float scaleY = model.ScaleY / (GameScales.ModelC1 * GameScales.AnimC1); - float scaleZ = model.ScaleZ / (GameScales.ModelC1 * GameScales.AnimC1); - - var list = new List (); + color = new Vector3 (t.R, t.G, t.B) / 255F; - foreach (OldFrameVertex vertex in OldFrame.Vertices) - { - list.Add (new GameEntries { vertex = vertex }); - } + // add the texture to the list too + material = objTranslate.FirstOrDefault (x => + x.Value.color == t.ColorMode && + x.Value.blend == t.BlendMode && + x.Value.clutx == t.ClutX && + x.Value.cluty == t.ClutY && + x.Value.page == textureEIDs [t.EID] + ).Key; - // update color information - foreach (OldModelPolygon polygon in model.Polygons) + if (material is null) { - OldModelStruct str = model.Structs[polygon.TexInfo]; - - if (str is not OldSceneryColor color) - continue; + var texinfo = new TexInfoUnpacked( + true, color: t.ColorMode, blend: t.BlendMode, + clutx: t.ClutX, cluty: t.ClutY, + face: Convert.ToInt32(t.N), + page: textureEIDs[t.EID] + ); - list [polygon.VertexA / 6].color = color; - list [polygon.VertexB / 6].color = color; - list [polygon.VertexC / 6].color = color; + var tpag = GetNSF ().GetEntry (t.EID); + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; } - - obj.WriteLine("# Vertices"); - foreach (GameEntries entry in list) + + Vector2 texsize = t.ColorMode switch { - obj.WriteLine("v {0} {1} {2} {3} {4} {5}", - ((entry.vertex.X - OldFrame.XOffset) * scaleX).ToString(CultureInfo.InvariantCulture), - ((entry.vertex.Y - OldFrame.YOffset) * scaleY).ToString(CultureInfo.InvariantCulture), - ((entry.vertex.Z - OldFrame.ZOffset) * scaleZ).ToString(CultureInfo.InvariantCulture), - (entry.color.R / 255F).ToString(CultureInfo.InvariantCulture), - (entry.color.G / 255F).ToString(CultureInfo.InvariantCulture), - (entry.color.B / 255F).ToString(CultureInfo.InvariantCulture) - ); - } + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; - foreach (OldFrameVertex vertex in OldFrame.Vertices) - { - obj.WriteLine("vn {0} {1} {2}", - (vertex.NormalX/127F).ToString(CultureInfo.InvariantCulture), - (vertex.NormalY/127F).ToString(CultureInfo.InvariantCulture), - (vertex.NormalZ/127F).ToString(CultureInfo.InvariantCulture) - ); - } - obj.WriteLine(); - obj.WriteLine("# Triangles"); - foreach (OldModelPolygon polygon in model.Polygons) - { - obj.WriteLine("f {0}//{0} {1}//{1} {2}//{2}", polygon.VertexA / 6 + 1,polygon.VertexB / 6 + 1,polygon.VertexC / 6 + 1); - } + uv3 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); + uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); + uv1 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); + } + else if(str is OldSceneryColor c) + { + color = new Vector3 (c.R, c.G, c.B) / 255F; } - return stream.ToArray(); + + exporter.AddFace ( + (v1 + offset) * scale, + (v2 + offset) * scale, + (v3 + offset) * scale, + color, color, color, + material, + uv1, uv2, uv3 + ); } + + exporter.Export (path, modelname); } } } diff --git a/CrashEdit/Exporters/OBJExporter.cs b/CrashEdit/Exporters/OBJExporter.cs new file mode 100644 index 00000000..49b6c040 --- /dev/null +++ b/CrashEdit/Exporters/OBJExporter.cs @@ -0,0 +1,352 @@ +using System.Collections.Generic; +using System.Drawing; +using System.Globalization; +using System.IO; +using System.Linq; +using OpenTK; + +namespace CrashEdit.Exporters; + +public class OBJExporter +{ + private const string DEFAULT_MATERIAL = "default"; + + class Material + { + public Vector3 ambient; + public Vector3 diffuse; + public Vector3 specular; + public float highlight; + public Bitmap texture; + } + + class Face + { + public int V1; + public int V2; + public int V3; + public string material; + public int? UV1; + public int? UV2; + public int? UV3; + } + + class Vertex + { + public Vector3 position; + public Vector3 color; + } + + private Dictionary materials = new Dictionary (); + private List vertices = new List (); + private List faces = new List (); + private List uvs = new List (); + + public OBJExporter () + { + // create a default material for everything that is not textured + this.materials[DEFAULT_MATERIAL] = new Material + { + ambient = Vector3.One, + diffuse = Vector3.One, + highlight = 0.0f, + specular = Vector3.Zero, + texture = null + }; + } + + /// + /// Adds a texture with the given name to the obj + /// + /// + /// Texture data + /// The identifier for the texture in the obj export + public string AddTexture (string name, Bitmap texture) + { + string identifier = $"tex{name}"; + + this.materials [identifier] = new Material + { + ambient = Vector3.One, + diffuse = Vector3.One, + highlight = 0.0f, + specular = Vector3.Zero, + texture = texture + }; + + return identifier; + } + + /// + /// Adds a new vertex to the output + /// + /// + /// + public void AddVertex (Vector3 position, Vector3 color) + { + this.vertices.Add ( + new Vertex + { + position = position, + color = color + } + ); + } + + /// + /// Adds a simple face using the given vertices + /// + /// + /// + /// + /// + /// + /// + /// + public void AddFace (int v1, int v2, int v3, string material = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null) + { + // add uv coordinates to the lists first + int? uv1id = null; + int? uv2id = null; + int? uv3id = null; + + if (uv1 != uv2 || uv1 != uv3) + throw new InvalidDataException ("UVs must all be null or all have values"); + + if (uv1 is not null) + { + uv1id = this.uvs.Count; + uv2id = this.uvs.Count + 1; + uv3id = this.uvs.Count + 2; + + this.uvs.Add (uv1.Value); + this.uvs.Add (uv2.Value); + this.uvs.Add (uv3.Value); + } + + this.faces.Add ( + new Face + { + material = material ?? DEFAULT_MATERIAL, + V1 = v1, + V2 = v2, + V3 = v3, + UV1 = uv1id, + UV2 = uv2id, + UV3 = uv3id + } + ); + } + + /// + /// Creates a new face with it's own vertices and uv coordinates + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 c1, Vector3 c2, Vector3 c3, string material = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null) + { + int v1id = this.vertices.Count; + int v2id = this.vertices.Count + 1; + int v3id = this.vertices.Count + 2; + int? uv1id = null; + int? uv2id = null; + int? uv3id = null; + + if ( + (uv1 is null && (uv2 is not null || uv3 is not null)) || + (uv2 is null && (uv1 is not null || uv3 is not null)) || + (uv3 is null && (uv1 is not null || uv2 is not null)) + ) + throw new InvalidDataException ("UVs must all be null or all have values"); + + if (uv1 is not null) + { + uv1id = this.uvs.Count; + uv2id = this.uvs.Count + 1; + uv3id = this.uvs.Count + 2; + + this.uvs.Add (uv1.Value); + this.uvs.Add (uv2.Value); + this.uvs.Add (uv3.Value); + } + + this.vertices.Add ( + new Vertex + { + position = v1, + color = c1 + } + ); + this.vertices.Add ( + new Vertex + { + position = v2, + color = c2 + } + ); + this.vertices.Add ( + new Vertex + { + position = v3, + color = c3 + } + ); + + this.faces.Add ( + new Face + { + material = material, + V1 = v1id, + V2 = v2id, + V3 = v3id, + UV1 = uv1id, + UV2 = uv2id, + UV3 = uv3id + } + ); + } + + private void ExportMaterials (string path, string modelname) + { + // first write all the textures to disk + // then write the mtl file + using MemoryStream stream = new MemoryStream (); + using StreamWriter writer = new StreamWriter (stream); + + writer.WriteLine ("# CrashEdit exported material"); + + // write all the materials + foreach (KeyValuePair material in this.materials) + { + writer.WriteLine ("newmtl {0}", material.Key); + writer.WriteLine ( + "Ka {0} {1} {2}", + material.Value.ambient.X.ToString(CultureInfo.InvariantCulture), + material.Value.ambient.Y.ToString(CultureInfo.InvariantCulture), + material.Value.ambient.Z.ToString(CultureInfo.InvariantCulture) + ); + writer.WriteLine ( + "Kd {0} {1} {2}", + material.Value.diffuse.X.ToString(CultureInfo.InvariantCulture), + material.Value.diffuse.Y.ToString(CultureInfo.InvariantCulture), + material.Value.diffuse.Z.ToString(CultureInfo.InvariantCulture) + ); + writer.WriteLine ( + "Ks {0} {1} {2}", + material.Value.specular.X.ToString(CultureInfo.InvariantCulture), + material.Value.specular.Y.ToString(CultureInfo.InvariantCulture), + material.Value.specular.Z.ToString(CultureInfo.InvariantCulture) + ); + writer.WriteLine ( + "Ns {0}", + material.Value.highlight.ToString(CultureInfo.InvariantCulture) + ); + + if (material.Value.texture is null) + continue; + + writer.WriteLine( + "map_Kd {0}.bmp", + material.Key + ); + + // write the bitmap to a file too + material.Value.texture.Save (path + Path.DirectorySeparatorChar + material.Key + ".bmp"); + } + + writer.Flush (); + + // material file finally written, save it to disk too + File.WriteAllBytes (path + Path.DirectorySeparatorChar + modelname + ".mtl", stream.ToArray ()); + } + + public void Export (string path, string modelname) + { + // first write the material file + ExportMaterials (path, modelname); + + using MemoryStream stream = new MemoryStream(); + using StreamWriter writer = new StreamWriter (stream); + + writer.WriteLine ("# CrashEdit exported model"); + writer.WriteLine ("mtllib {0}.mtl", modelname); + writer.WriteLine ("# Vertices"); + + foreach (Vertex vertex in vertices) + { + writer.WriteLine ( + "v {0} {1} {2} {3} {4} {5}", + vertex.position.X.ToString(CultureInfo.InvariantCulture), + vertex.position.Y.ToString(CultureInfo.InvariantCulture), + vertex.position.Z.ToString(CultureInfo.InvariantCulture), + vertex.color.X.ToString(CultureInfo.InvariantCulture), + vertex.color.Y.ToString(CultureInfo.InvariantCulture), + vertex.color.Z.ToString(CultureInfo.InvariantCulture) + ); + } + + // write any uvs we have + writer.WriteLine (); + writer.WriteLine ("# UVs"); + + foreach (Vector2 uv in uvs) + { + writer.WriteLine( + "vt {0} {1}", + uv.X.ToString(CultureInfo.InvariantCulture), uv.Y.ToString(CultureInfo.InvariantCulture) + ); + } + + // finally write the faces + writer.WriteLine (); + writer.WriteLine ("# Faces with textures"); + + string lastmaterial = null; + + // by default use the default material + writer.WriteLine ("usemtl {0}", DEFAULT_MATERIAL); + + foreach (Face face in faces.OrderBy (x => x.material)) + { + if (lastmaterial != face.material) + { + writer.WriteLine ("usemtl {0}", face.material); + + lastmaterial = face.material; + } + + // write face information, UVs must all be null or have value + // at the same time, so this check is safe + if (face.UV1 is null) + writer.WriteLine ( + "f {0} {1} {2}", + face.V1 + 1, + face.V2 + 1, + face.V3 + 1 + ); + else + writer.WriteLine ( + "f {0}/{3} {1}/{4} {2}/{5}", + face.V1 + 1, + face.V2 + 1, + face.V3 + 1, + face.UV1 + 1, + face.UV2 + 1, + face.UV3 + 1 + ); + } + + writer.Flush (); + + // obj file ready, write to the destination + File.WriteAllBytes (path + Path.DirectorySeparatorChar + modelname + ".obj", stream.ToArray ()); + } +} \ No newline at end of file diff --git a/CrashEdit/Exporters/TextureExporter.cs b/CrashEdit/Exporters/TextureExporter.cs new file mode 100644 index 00000000..27d6c3a6 --- /dev/null +++ b/CrashEdit/Exporters/TextureExporter.cs @@ -0,0 +1,129 @@ +using System.Drawing; + +namespace CrashEdit.Exporters; + +public class TextureExporter +{ + public static Bitmap Create4bpp (byte [] data, TexInfoUnpacked info) + { + Bitmap bmp = new Bitmap (1024, 128); + + // clutx really contains the count of palettes available + // so multiplying by 16 gives the right amount of info for it + int clutx = info.clutx * 16; + + // write the texture somewhere for now, testing + for (int x = 0; x < 1024; x++) + { + for (int y = info.cluty; y < 128; y++) + { + byte entry = data [y * 512 + (x / 2)]; + + if ((x % 2) == 0) + entry = (byte) (entry & 0xF); + else + entry = (byte) ((entry >> 4) & 0xF); + + // now read the color from the palette + ushort color = (ushort) ( + data [info.cluty * 512 + ((clutx + entry) * 2)] | + (data [info.cluty * 512 + ((clutx + entry) * 2 + 1)] << 8) + ); + + // colors are in rgb15 format + Color c = Color.FromArgb ( + ((color >> 15) & 0x1) * 255, + (int) (((color & 0x1F) / 31F) * 255), + (int) ((((color >> 5) & 0x1F) / 31F) * 255), + (int) ((((color >> 10) & 0x1F) / 31F) * 255) + ); + + bmp.SetPixel (x, y, c); + } + } + + return bmp; + } + + public static Bitmap Create8bpp (byte [] data, TexInfoUnpacked info) + { + Bitmap bmp = new Bitmap (512, 128); + + // clutx really contains the count of palettes available + // so multiplying by 16 gives the right amount of info for it + int clutx = info.clutx * 16; + + // write the texture somewhere for now, testing + for (int x = 0; x < 512; x++) + { + for (int y = info.cluty; y < 128; y++) + { + byte entry = data [y * 512 + x]; + + // now read the color from the palette + ushort color = (ushort) ( + data [info.cluty * 512 + ((clutx + entry) * 2)] | + (data [info.cluty * 512 + ((clutx + entry) * 2 + 1)] << 8) + ); + + // colors are in rgb15 format + Color c = Color.FromArgb ( + ((color >> 15) & 0x1) * 255, + (int) (((color & 0x1F) / 31F) * 255), + (int) ((((color >> 5) & 0x1F) / 31F) * 255), + (int) ((((color >> 10) & 0x1F) / 31F) * 255) + ); + + bmp.SetPixel (x, y, c); + } + } + + return bmp; + } + + public static Bitmap Create16bpp (byte [] data, TexInfoUnpacked info) + { + Bitmap bmp = new Bitmap (256, 128); + + // clutx really contains the count of palettes available + // so multiplying by 16 gives the right amount of info for it + int clutx = info.clutx * 16; + + // write the texture somewhere for now, testing + for (int x = 0; x < 256; x++) + { + for (int y = info.cluty; y < 128; y++) + { + // now read the color from the palette + ushort color = (ushort) ( + data [(info.cluty + y) * 512 + ((clutx + x) * 2)] | + (data [(info.cluty + y) * 512 + ((clutx + x) * 2 + 1)] << 8) + ); + + // this is not 100% precise, but we can fix these manually + // as they're usually not that big + // colors are in rgb15 format + Color c = Color.FromArgb ( + ((color >> 15) & 0x1) * 255, + (int) (((color & 0x1F) / 31F) * 255), + (int) ((((color >> 5) & 0x1F) / 31F) * 255), + (int) ((((color >> 10) & 0x1F) / 31F) * 255) + ); + + bmp.SetPixel (x, y, c); + } + } + + return bmp; + } + + public static Bitmap CreateTexture (byte [] data, TexInfoUnpacked info) + { + return info.color switch + { + 0 => TextureExporter.Create4bpp (data, info), + 1 => TextureExporter.Create8bpp (data, info), + _ => TextureExporter.Create16bpp (data, info) + }; + } +} \ No newline at end of file From 3ec0ded1df8f5806cb31b55f633137baeedd99f3 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 22:16:02 +0200 Subject: [PATCH 04/17] Added support for quads on exporter Added OBJ export to normal scenery entries Signed-off-by: Alexis Maiquez Murcia --- .../Controllers/Animation/FrameController.cs | 4 +- .../Animation/OldFrameController.cs | 1 - .../Scenery/SceneryEntryController.cs | 243 +++++++++++++++++- CrashEdit/Exporters/OBJExporter.cs | 214 +++++++++++++-- 4 files changed, 438 insertions(+), 24 deletions(-) diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index 0b01cd92..9afc0760 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -43,7 +43,7 @@ private void Menu_Export_OBJ () public void ToOBJ (string path, string modelname) { var exporter = new OBJExporter (); - var model = GetNSF ().GetEntry(Frame.ModelEID); + var model = this.AnimationEntryController.NSF.GetEntry(Frame.ModelEID); var vertices = Frame.MakeVertices (model); var offset = new Vector3 (Frame.XOffset, Frame.YOffset, Frame.ZOffset) / 4F; var scale = new Vector3 (model.ScaleX, model.ScaleY, model.ScaleZ) / (GameScales.ModelC1 * GameScales.AnimC1); @@ -102,7 +102,7 @@ public void ToOBJ (string path, string modelname) page: textureEIDs [model.GetTPAG (value.Page)] ); - var tpag = GetNSF ().GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); + var tpag = this.AnimationEntryController.NSF.GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); diff --git a/CrashEdit/Controllers/Animation/OldFrameController.cs b/CrashEdit/Controllers/Animation/OldFrameController.cs index cabb281f..0db21770 100755 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -94,7 +94,6 @@ public void ToOBJ(string path, string modelname) OldFrameVertex ov1 = OldFrame.Vertices [polygon.VertexA / 6]; OldFrameVertex ov2 = OldFrame.Vertices [polygon.VertexB / 6]; OldFrameVertex ov3 = OldFrame.Vertices [polygon.VertexC / 6]; - Vector3 v1 = new Vector3 (ov1.X, ov1.Y, ov1.Z); Vector3 v2 = new Vector3 (ov2.X, ov2.Y, ov2.Z); Vector3 v3 = new Vector3 (ov3.X, ov3.Y, ov3.Z); diff --git a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs index 79e8be6c..7389edc9 100755 --- a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs @@ -24,15 +24,246 @@ public override Control CreateEditor() public SceneryEntry SceneryEntry { get; } - private void Menu_Export_OBJ() + private void Menu_Export_OBJ () { - if (MessageBox.Show("Exporting to Wavefront OBJ (.obj) is experimental.\nTexture and color information will not be exported.\n\nContinue anyway?", "Export as OBJ", MessageBoxButtons.YesNo) != DialogResult.Yes) + FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any); + ToOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); + } + + private void ToOBJ (string path, string modelname) + { + var exporter = new OBJExporter (); + var offset = new Vector3 (SceneryEntry.XOffset, SceneryEntry.YOffset, SceneryEntry.ZOffset); + var scale = new Vector3 (1 / GameScales.WorldC1); + + // detect how many textures are used and their eids to prepare the image + Dictionary textureEIDs = new (); + + for (int i = 0; i < SceneryEntry.TPAGCount; i++) { - return; + int tpag_eid = SceneryEntry.GetTPAG (i); + + if (textureEIDs.ContainsKey (tpag_eid)) + continue; + + textureEIDs.Add (tpag_eid, textureEIDs.Count); } - FileUtil.SaveFile(SceneryEntry.ToOBJ(), FileFilters.OBJ, FileFilters.Any); - } + + Dictionary objTranslate = new Dictionary (); + + foreach (var tri in SceneryEntry.Triangles) + { + var info = ProcessTextureInfoC2(tri.Texture, tri.Animated, SceneryEntry.Textures, SceneryEntry.AnimatedTextures); + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null; + + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; + + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == textureEIDs [SceneryEntry.GetTPAG (value.Page)] + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [SceneryEntry.GetTPAG (value.Page)] + ); + + var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = value.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + + uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + uv2 = new Vector2 (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + } + + // add the face + SceneryVertex fv1 = SceneryEntry.Vertices[tri.VertexA]; + SceneryVertex fv2 = SceneryEntry.Vertices[tri.VertexB]; + SceneryVertex fv3 = SceneryEntry.Vertices[tri.VertexC]; + SceneryColor fc1 = SceneryEntry.Colors [fv1.Color]; + SceneryColor fc2 = SceneryEntry.Colors [fv2.Color]; + SceneryColor fc3 = SceneryEntry.Colors [fv3.Color]; + Vector3 v1 = new Vector3 (fv1.X, fv1.Y, fv1.Z); + Vector3 v2 = new Vector3 (fv2.X, fv2.Y, fv2.Z); + Vector3 v3 = new Vector3 (fv3.X, fv3.Y, fv3.Z); + Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; + Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; + Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; + + exporter.AddFace ( + (v1 + offset / 16) * scale, + (v2 + offset / 16) * scale, + (v3 + offset / 16) * scale, + c1, c2, c3, + material, + uv1, uv2, uv3 + ); + } + + foreach (var quad in SceneryEntry.Quads) + { + var info = ProcessTextureInfoC2(quad.Texture, quad.Animated, SceneryEntry.Textures, SceneryEntry.AnimatedTextures); + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null, uv4 = null; + + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; + + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == textureEIDs [SceneryEntry.GetTPAG (value.Page)] + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [SceneryEntry.GetTPAG (value.Page)] + ); + + var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + Vector2 texsize = value.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + + uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + uv2 = new Vector2 (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + uv4 = new (value.X4 / texsize.X, (128 - value.Y4) / texsize.Y); + } + + // add the face + SceneryVertex fv1 = SceneryEntry.Vertices[quad.VertexA]; + SceneryVertex fv2 = SceneryEntry.Vertices[quad.VertexB]; + SceneryVertex fv3 = SceneryEntry.Vertices[quad.VertexC]; + SceneryVertex fv4 = SceneryEntry.Vertices[quad.VertexD]; + SceneryColor fc1 = SceneryEntry.Colors [fv1.Color]; + SceneryColor fc2 = SceneryEntry.Colors [fv2.Color]; + SceneryColor fc3 = SceneryEntry.Colors [fv3.Color]; + SceneryColor fc4 = SceneryEntry.Colors [fv4.Color]; + Vector3 v1 = new Vector3 (fv1.X, fv1.Y, fv1.Z); + Vector3 v2 = new Vector3 (fv2.X, fv2.Y, fv2.Z); + Vector3 v3 = new Vector3 (fv3.X, fv3.Y, fv3.Z); + Vector3 v4 = new Vector3 (fv4.X, fv4.Y, fv4.Z); + Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; + Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; + Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; + Vector3 c4 = new Vector3 (fc4.Red, fc4.Green, fc4.Blue) / 255f; + + exporter.AddFace ( + (v1 + offset / 16) * scale, + (v2 + offset / 16) * scale, + (v3 + offset / 16) * scale, + (v4 + offset / 16) * scale, + c1, c2, c3, c4, + material, + uv1, uv2, uv3, uv4 + ); + } + + exporter.Export (path, modelname); + } + + /// + /// TODO: THIS IS A DUPLICATED OF GLViewer:899, MOVE IT SOMEWHERE ELSE WHERE BOTH HAVE ACCESS TO IT + /// + /// + /// + /// + /// + /// + protected Tuple ProcessTextureInfoC2(int in_tex_id, bool animated, IList textures, IList animated_textures) + { + if (in_tex_id != 0 || animated) + { + ModelTexture? info_temp = null; + int tex_id = in_tex_id - 1; + if (animated) + { + if (++tex_id >= animated_textures.Count) + { + return new(false, null); + } + var anim = animated_textures[tex_id]; + // check if it's an untextured polygon + if (anim.Offset != 0) + { + tex_id = anim.Offset - 1; + if (anim.IsLOD) + { + tex_id += anim.LOD0; // we only render closest LOD for now + } + else + { + tex_id += (int)((0 / 2 / (1 + anim.Latency) + anim.Delay) & anim.Mask); + if (anim.Leap) + { + anim = animated_textures[++tex_id]; + tex_id = anim.Offset - 1 + anim.LOD0; + } + } + if (tex_id >= textures.Count) + { + return new(false, null); + } + info_temp = textures[tex_id]; + } + } + else + { + if (tex_id >= textures.Count) + { + return new(false, null); + } + info_temp = textures[tex_id]; + } + return new(true, info_temp); + } + return new(true, null); + } + private void Menu_Export_PLY() { if (MessageBox.Show("Exporting to Stanford PLY (.ply) is experimental.\nTexture information will not be exported.\n\nContinue anyway?", "Export as PLY", MessageBoxButtons.YesNo) != DialogResult.Yes) @@ -50,7 +281,7 @@ private void Menu_Export_PLY() } FileUtil.SaveFile(sceneryentry.ToCOLLADA(), FileFilters.COLLADA, FileFilters.Any); }*/ - + private void Menu_Fix_WGEOv3() { for (int i = 0; i < SceneryEntry.Vertices.Count; i++) diff --git a/CrashEdit/Exporters/OBJExporter.cs b/CrashEdit/Exporters/OBJExporter.cs index 49b6c040..0408cce1 100644 --- a/CrashEdit/Exporters/OBJExporter.cs +++ b/CrashEdit/Exporters/OBJExporter.cs @@ -25,10 +25,12 @@ class Face public int V1; public int V2; public int V3; + public int? V4; public string material; public int? UV1; public int? UV2; public int? UV3; + public int? UV4; } class Vertex @@ -138,6 +140,61 @@ public void AddFace (int v1, int v2, int v3, string material = null, Vector2? uv ); } + /// + /// Adds a simple face using the given vertices + /// + /// + /// + /// + /// + /// + /// + /// + public void AddFace (int v1, int v2, int v3, int v4, string material = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null, Vector2? uv4 = null) + { + // add uv coordinates to the lists first + int? uv1id = null; + int? uv2id = null; + int? uv3id = null; + int? uv4id = null; + + if ( + (uv1 is null && (uv2 is not null || uv3 is not null || uv4 is not null)) || + (uv2 is null && (uv1 is not null || uv3 is not null || uv4 is not null)) || + (uv3 is null && (uv1 is not null || uv2 is not null || uv4 is not null)) || + (uv4 is null && (uv1 is not null || uv2 is not null || uv3 is not null)) + ) + throw new InvalidDataException ("UVs must all be null or all have values"); + + if (uv1 is not null) + { + uv1id = this.uvs.Count; + uv2id = this.uvs.Count + 1; + uv3id = this.uvs.Count + 2; + uv4id = this.uvs.Count + 3; + + this.uvs.Add (uv1.Value); + this.uvs.Add (uv2.Value); + this.uvs.Add (uv3.Value); + this.uvs.Add (uv4.Value); + } + + this.faces.Add ( + new Face + { + material = material ?? DEFAULT_MATERIAL, + V1 = v1, + V2 = v2, + V3 = v3, + V4 = v4, + UV1 = uv1id, + UV2 = uv2id, + UV3 = uv3id, + UV4 = uv4id + } + ); + } + /// /// Creates a new face with it's own vertices and uv coordinates /// @@ -214,6 +271,99 @@ public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 c1, Vector3 c2, ); } + /// + /// Creates a new face with it's own vertices and uv coordinates + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Vector3 c1, Vector3 c2, Vector3 c3, Vector3 c4, string material = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null, Vector2? uv4 = null) + { + int v1id = this.vertices.Count; + int v2id = this.vertices.Count + 1; + int v3id = this.vertices.Count + 2; + int v4id = this.vertices.Count + 3; + int? uv1id = null; + int? uv2id = null; + int? uv3id = null; + int? uv4id = null; + + if ( + (uv1 is null && (uv2 is not null || uv3 is not null || uv4 is not null)) || + (uv2 is null && (uv1 is not null || uv3 is not null || uv4 is not null)) || + (uv3 is null && (uv1 is not null || uv2 is not null || uv4 is not null)) || + (uv4 is null && (uv1 is not null || uv2 is not null || uv3 is not null)) + ) + throw new InvalidDataException ("UVs must all be null or all have values"); + + if (uv1 is not null) + { + uv1id = this.uvs.Count; + uv2id = this.uvs.Count + 1; + uv3id = this.uvs.Count + 2; + uv4id = this.uvs.Count + 3; + + this.uvs.Add (uv1.Value); + this.uvs.Add (uv2.Value); + this.uvs.Add (uv3.Value); + this.uvs.Add (uv4.Value); + } + + this.vertices.Add ( + new Vertex + { + position = v1, + color = c1 + } + ); + this.vertices.Add ( + new Vertex + { + position = v2, + color = c2 + } + ); + this.vertices.Add ( + new Vertex + { + position = v3, + color = c3 + } + ); + this.vertices.Add ( + new Vertex + { + position = v4, + color = c4 + } + ); + + this.faces.Add ( + new Face + { + material = material, + V1 = v1id, + V2 = v2id, + V3 = v3id, + V4 = v4id, + UV1 = uv1id, + UV2 = uv2id, + UV3 = uv3id, + UV4 = uv4id + } + ); + } + private void ExportMaterials (string path, string modelname) { // first write all the textures to disk @@ -326,22 +476,56 @@ public void Export (string path, string modelname) // write face information, UVs must all be null or have value // at the same time, so this check is safe if (face.UV1 is null) - writer.WriteLine ( - "f {0} {1} {2}", - face.V1 + 1, - face.V2 + 1, - face.V3 + 1 - ); + { + if (face.V4 is null) + { + writer.WriteLine ( + "f {0} {1} {2}", + face.V1 + 1, + face.V2 + 1, + face.V3 + 1 + ); + } + else + { + writer.WriteLine ( + "f {0} {1} {2} {3}", + face.V1 + 1, + face.V2 + 1, + face.V3 + 1, + face.V4 + 1 + ); + } + } else - writer.WriteLine ( - "f {0}/{3} {1}/{4} {2}/{5}", - face.V1 + 1, - face.V2 + 1, - face.V3 + 1, - face.UV1 + 1, - face.UV2 + 1, - face.UV3 + 1 - ); + { + if (face.V4 is null) + { + writer.WriteLine ( + "f {0}/{3} {1}/{4} {2}/{5}", + face.V1 + 1, + face.V2 + 1, + face.V3 + 1, + face.UV1 + 1, + face.UV2 + 1, + face.UV3 + 1 + ); + } + else + { + writer.WriteLine ( + "f {0}/{4} {1}/{5} {2}/{6} {3}/{7}", + face.V1 + 1, + face.V2 + 1, + face.V3 + 1, + face.V4 + 1, + face.UV1 + 1, + face.UV2 + 1, + face.UV3 + 1, + face.UV4 + 1 + ); + } + } } writer.Flush (); From 13bdbaee3fb795d65fea40ce8bf2c350a51d7006 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 22:20:29 +0200 Subject: [PATCH 05/17] Removed old exporter code for scenery Signed-off-by: Alexis Maiquez Murcia --- .../Crash Formats/Scenery/SceneryEntry.cs | 237 ------------------ .../Scenery/SceneryEntryController.cs | 19 -- 2 files changed, 256 deletions(-) diff --git a/Crash/Formats/Crash Formats/Scenery/SceneryEntry.cs b/Crash/Formats/Crash Formats/Scenery/SceneryEntry.cs index 2cd0b068..c71f48e1 100755 --- a/Crash/Formats/Crash Formats/Scenery/SceneryEntry.cs +++ b/Crash/Formats/Crash Formats/Scenery/SceneryEntry.cs @@ -104,242 +104,5 @@ public override UnprocessedEntry Unprocess() } return new UnprocessedEntry(items, EID, Type); } - - public byte[] ToOBJ() - { - using (MemoryStream stream = new MemoryStream()) - { - using (StreamWriter obj = new StreamWriter(stream)) - { - obj.WriteLine("# Vertices"); - foreach (SceneryVertex vertex in vertices) - { - if (vertex.Color < Colors.Count) - { - SceneryColor color = Colors[vertex.Color]; - obj.WriteLine("v {0} {1} {2} {3} {4} {5}", vertex.X + XOffset / 16, vertex.Y + YOffset / 16, vertex.Z + ZOffset / 16, color.Red / 255.0, color.Green / 255.0, color.Blue / 255.0); - } - else - { - obj.WriteLine("v {0} {1} {2}", vertex.X + XOffset / 16, vertex.Y + YOffset / 16, vertex.Z + ZOffset / 16); - } - } - obj.WriteLine(); - obj.WriteLine("# Triangles"); - foreach (SceneryTriangle triangle in triangles) - { - obj.WriteLine("f {0} {1} {2}", triangle.VertexA + 1, triangle.VertexB + 1, triangle.VertexC + 1); - } - obj.WriteLine(); - obj.WriteLine("# Quads"); - foreach (SceneryQuad quad in quads) - { - obj.WriteLine("f {0} {1} {2} {3}", quad.VertexA + 1, quad.VertexB + 1, quad.VertexC + 1, quad.VertexD + 1); - } - } - return stream.ToArray(); - } - } - - public byte[] ToPLY() - { - using (MemoryStream stream = new MemoryStream()) - { - using (StreamWriter ply = new StreamWriter(stream)) - { - int polycount = 0; - foreach (SceneryTriangle triangle in triangles) - { - if (triangle.VertexA < vertices.Count - 1 && triangle.VertexB < vertices.Count - 1 && triangle.VertexC < vertices.Count - 1) - { - ++polycount; - } - } - foreach (SceneryQuad quad in quads) - { - if (quad.VertexA < vertices.Count - 1 && quad.VertexB < vertices.Count - 1 && quad.VertexC < vertices.Count - 1 && quad.VertexD < vertices.Count - 1) - { - ++polycount; - } - } - ply.WriteLine("ply"); - ply.WriteLine("format ascii 1.0"); - ply.WriteLine("element vertex {0}", Vertices.Count); - ply.WriteLine("property int x"); - ply.WriteLine("property int y"); - ply.WriteLine("property int z"); - ply.WriteLine("property uchar red"); - ply.WriteLine("property uchar green"); - ply.WriteLine("property uchar blue"); - ply.WriteLine("element face {0}", polycount); - ply.WriteLine("property list uchar int vertex_index"); - ply.WriteLine("end_header"); - foreach (SceneryVertex vertex in vertices) - { - if (vertex.Color < Colors.Count) - { - SceneryColor color = Colors[vertex.Color]; - ply.WriteLine("{0} {1} {2} {3} {4} {5}", vertex.X + XOffset / 16, vertex.Y + YOffset / 16, vertex.Z + ZOffset / 16, color.Red, color.Green, color.Blue); - } - else - { - ply.WriteLine("{0} {1} {2} 255 0 255", vertex.X + XOffset / 16, vertex.Y + YOffset / 16, vertex.Z + ZOffset / 16); - } - } - foreach (SceneryTriangle triangle in triangles) - { - if (triangle.VertexA < vertices.Count - 1 && triangle.VertexB < vertices.Count - 1 && triangle.VertexC < vertices.Count - 1) - { - ply.WriteLine("3 {0} {1} {2}", triangle.VertexA, triangle.VertexB, triangle.VertexC); - } - } - foreach (SceneryQuad quad in quads) - { - if (quad.VertexA < vertices.Count - 1 && quad.VertexB < vertices.Count - 1 && quad.VertexC < vertices.Count - 1 && quad.VertexD < vertices.Count - 1) - { - ply.WriteLine("4 {0} {1} {2} {3}", quad.VertexA, quad.VertexB, quad.VertexC, quad.VertexD); - } - } - } - return stream.ToArray(); - } - } - - /*public byte[] ToCOLLADA() - { - using (MemoryStream stream = new MemoryStream()) - { - using (StreamWriter textwriter = new StreamWriter(stream)) - using (XmlTextWriter xmlwriter = new XmlTextWriter(textwriter)) - { - xmlwriter.WriteStartDocument(); - xmlwriter.WriteStartElement("COLLADA"); - xmlwriter.WriteAttributeString("xmlns", "http://www.collada.org/2005/11/COLLADASchema"); - xmlwriter.WriteAttributeString("version", "1.4.1"); - xmlwriter.WriteStartElement("library_geometries"); - xmlwriter.WriteStartElement("geometry"); - xmlwriter.WriteStartElement("mesh"); - xmlwriter.WriteStartElement("source"); - xmlwriter.WriteAttributeString("id", "positions"); - xmlwriter.WriteStartElement("float_array"); - xmlwriter.WriteAttributeString("id", "positions-array"); - xmlwriter.WriteAttributeString("count", (vertices.Count * 3).ToString()); - foreach (NewSceneryVertex vertex in vertices) - { - xmlwriter.WriteValue(vertex.X + XOffset / 4); - xmlwriter.WriteWhitespace(" "); - xmlwriter.WriteValue(vertex.Y + YOffset / 4); - xmlwriter.WriteWhitespace(" "); - xmlwriter.WriteValue(vertex.Z + ZOffset / 4); - xmlwriter.WriteWhitespace(" "); - } - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("technique_common"); - xmlwriter.WriteStartElement("accessor"); - xmlwriter.WriteAttributeString("source", "#positions-array"); - xmlwriter.WriteAttributeString("count", vertices.Count.ToString()); - xmlwriter.WriteAttributeString("stride", "3"); - xmlwriter.WriteStartElement("param"); - xmlwriter.WriteAttributeString("name", "X"); - xmlwriter.WriteAttributeString("type", "float"); - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("param"); - xmlwriter.WriteAttributeString("name", "Y"); - xmlwriter.WriteAttributeString("type", "float"); - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("param"); - xmlwriter.WriteAttributeString("name", "Y"); - xmlwriter.WriteAttributeString("type", "float"); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("source"); - xmlwriter.WriteAttributeString("id", "colors"); - xmlwriter.WriteStartElement("float_array"); - xmlwriter.WriteAttributeString("id", "colors-array"); - xmlwriter.WriteAttributeString("count", (vertices.Count * 3).ToString()); - foreach (NewSceneryVertex vertex in vertices) - { - if (vertex.Color < Colors.Count) - { - SceneryColor color = Colors[vertex.Color]; - xmlwriter.WriteValue(color.Red / 256.0); - xmlwriter.WriteWhitespace(" "); - xmlwriter.WriteValue(color.Green / 256.0); - xmlwriter.WriteWhitespace(" "); - xmlwriter.WriteValue(color.Blue / 256.0); - xmlwriter.WriteWhitespace(" "); - } - else - { - xmlwriter.WriteValue(256.0 / 256.0); - xmlwriter.WriteWhitespace(" "); - xmlwriter.WriteValue(0 / 256.0); - xmlwriter.WriteWhitespace(" "); - xmlwriter.WriteValue(256.0 / 256.0); - xmlwriter.WriteWhitespace(" "); - } - } - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("technique_common"); - xmlwriter.WriteStartElement("accessor"); - xmlwriter.WriteAttributeString("source", "#colors-array"); - xmlwriter.WriteAttributeString("count", vertices.Count.ToString()); - xmlwriter.WriteAttributeString("stride", "3"); - xmlwriter.WriteStartElement("param"); - xmlwriter.WriteAttributeString("name", "R"); - xmlwriter.WriteAttributeString("type", "float"); - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("param"); - xmlwriter.WriteAttributeString("name", "G"); - xmlwriter.WriteAttributeString("type", "float"); - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("param"); - xmlwriter.WriteAttributeString("name", "B"); - xmlwriter.WriteAttributeString("type", "float"); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("vertices"); - xmlwriter.WriteAttributeString("id", "vertices"); - xmlwriter.WriteStartElement("input"); - xmlwriter.WriteAttributeString("semantic", "POSITION"); - xmlwriter.WriteAttributeString("source", "positions"); - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("input"); - xmlwriter.WriteAttributeString("semantic", "COLOR"); - xmlwriter.WriteAttributeString("source", "colors"); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("triangles"); - xmlwriter.WriteAttributeString("count", triangles.Count.ToString()); - xmlwriter.WriteStartElement("input"); - xmlwriter.WriteAttributeString("semantic", "VERTEX"); - xmlwriter.WriteAttributeString("source", "vertices"); - xmlwriter.WriteAttributeString("offset", "0"); - xmlwriter.WriteEndElement(); - xmlwriter.WriteStartElement("p"); - foreach (SceneryTriangle triangle in triangles) - { - xmlwriter.WriteValue(triangle.VertexA); - xmlwriter.WriteWhitespace(" "); - xmlwriter.WriteValue(triangle.VertexB); - xmlwriter.WriteWhitespace(" "); - xmlwriter.WriteValue(triangle.VertexC); - xmlwriter.WriteWhitespace(" "); - } - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndElement(); - xmlwriter.WriteEndDocument(); - } - return stream.ToArray(); - } - }*/ } } diff --git a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs index 7389edc9..f47e3217 100755 --- a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs @@ -10,7 +10,6 @@ public SceneryEntryController(SceneryEntry sceneryentry, SubcontrollerGroup pare SceneryEntry = sceneryentry; AddMenuSeparator(); AddMenu("Export as Wavefront OBJ", Menu_Export_OBJ); - AddMenu("Export as Stanford PLY", Menu_Export_PLY); //AddMenu("Export as COLLADA",Menu_Export_COLLADA); AddMenu("Fix coords imported from Crash 3", Menu_Fix_WGEOv3); } @@ -264,24 +263,6 @@ private void ToOBJ (string path, string modelname) return new(true, null); } - private void Menu_Export_PLY() - { - if (MessageBox.Show("Exporting to Stanford PLY (.ply) is experimental.\nTexture information will not be exported.\n\nContinue anyway?", "Export as PLY", MessageBoxButtons.YesNo) != DialogResult.Yes) - { - return; - } - FileUtil.SaveFile(SceneryEntry.ToPLY(), FileFilters.PLY, FileFilters.Any); - } - - /*private void Menu_Export_COLLADA() - { - if (MessageBox.Show("Exporting to COLLADA (.dae) is experimental.\nTexture and quad information will not be exported.\n\nContinue anyway?", "Export as OBJ", MessageBoxButtons.YesNo) != DialogResult.Yes) - { - return; - } - FileUtil.SaveFile(sceneryentry.ToCOLLADA(), FileFilters.COLLADA, FileFilters.Any); - }*/ - private void Menu_Fix_WGEOv3() { for (int i = 0; i < SceneryEntry.Vertices.Count; i++) From 2bc70884a4ad4b8e2346d05f8f1f2fcaeddab622 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 22:38:51 +0200 Subject: [PATCH 06/17] Added support to export all sceneries inside NSF Signed-off-by: Alexis Maiquez Murcia --- Crash.UI/Properties/Resources.Designer.cs | 10 +- Crash.UI/Properties/Resources.resx | 3 + CrashEdit/Controllers/NSFController.cs | 259 ++++++++++++++++++++++ 3 files changed, 271 insertions(+), 1 deletion(-) diff --git a/Crash.UI/Properties/Resources.Designer.cs b/Crash.UI/Properties/Resources.Designer.cs index 1ece3293..717c190d 100755 --- a/Crash.UI/Properties/Resources.Designer.cs +++ b/Crash.UI/Properties/Resources.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -393,6 +392,15 @@ public static string NSFController_AcAddWavebankChunk { } } + /// + /// Looks up a localized string similar to Export all scenery to OBJ. + /// + public static string NSFController_AcExportScenery { + get { + return ResourceManager.GetString("NSFController_AcExportScenery", resourceCulture); + } + } + /// /// Looks up a localized string similar to Fix Box Count. /// diff --git a/Crash.UI/Properties/Resources.resx b/Crash.UI/Properties/Resources.resx index fe32bd03..17f04671 100755 --- a/Crash.UI/Properties/Resources.resx +++ b/Crash.UI/Properties/Resources.resx @@ -357,4 +357,7 @@ Show All Level Zones + + Export all scenery to OBJ + \ No newline at end of file diff --git a/CrashEdit/Controllers/NSFController.cs b/CrashEdit/Controllers/NSFController.cs index 044b39b7..91c93a0c 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -34,8 +34,11 @@ public NSFController(NSF nsf, SubcontrollerGroup parentGroup) : base(parentGroup } else if (GameVersion == GameVersion.Crash2 || GameVersion == GameVersion.Crash3) { + AddMenuSeparator(); AddMenu(CrashUI.Properties.Resources.NSFController_AcShowLevel, Menu_ShowLevelC2); AddMenu(CrashUI.Properties.Resources.NSFController_AcShowLevelZones, Menu_ShowLevelZonesC2); + AddMenuSeparator (); + AddMenu (CrashUI.Properties.Resources.NSFController_AcExportScenery, Menu_ExportSceneryOBJ); } } @@ -308,6 +311,262 @@ private void Menu_ShowLevelZonesC2() }; } + private void Menu_ExportSceneryOBJ () + { + FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any); + ExportSceneryOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); + } + + private void ExportSceneryOBJ (string path, string modelname) + { + var exporter = new OBJExporter (); + + // detect how many textures are used and their eids to prepare the image + Dictionary textureEIDs = new (); + Dictionary objTranslate = new Dictionary (); + + // find all the scenery insde chunks + foreach (SceneryEntry scenery in NSF.GetEntries ()) + { + var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); + var scale = new Vector3 (1 / GameScales.WorldC1); + + + for (int i = 0; i < scenery.TPAGCount; i++) + { + int tpag_eid = scenery.GetTPAG (i); + + if (textureEIDs.ContainsKey (tpag_eid)) + continue; + + textureEIDs.Add (tpag_eid, textureEIDs.Count); + } + + foreach (var tri in scenery.Triangles) + { + if (tri.VertexA > scenery.Vertices.Count || + tri.VertexB > scenery.Vertices.Count || + tri.VertexC > scenery.Vertices.Count) + continue; + + var info = ProcessTextureInfoC2 (tri.Texture, tri.Animated, scenery.Textures, scenery.AnimatedTextures); + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null; + + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; + + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == textureEIDs [scenery.GetTPAG (value.Page)] + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [scenery.GetTPAG (value.Page)] + ); + + var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = value.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + + uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + uv2 = new Vector2 (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + } + + // add the face + SceneryVertex fv1 = scenery.Vertices [tri.VertexA]; + SceneryVertex fv2 = scenery.Vertices [tri.VertexB]; + SceneryVertex fv3 = scenery.Vertices [tri.VertexC]; + SceneryColor fc1 = scenery.Colors [fv1.Color]; + SceneryColor fc2 = scenery.Colors [fv2.Color]; + SceneryColor fc3 = scenery.Colors [fv3.Color]; + Vector3 v1 = new Vector3 (fv1.X, fv1.Y, fv1.Z); + Vector3 v2 = new Vector3 (fv2.X, fv2.Y, fv2.Z); + Vector3 v3 = new Vector3 (fv3.X, fv3.Y, fv3.Z); + Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; + Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; + Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; + + exporter.AddFace ( + (v1 + offset / 16) * scale, + (v2 + offset / 16) * scale, + (v3 + offset / 16) * scale, + c1, c2, c3, + material, + uv1, uv2, uv3 + ); + } + + foreach (var quad in scenery.Quads) + { + // ignore quads that are out of limits + if (quad.VertexA > scenery.Vertices.Count || + quad.VertexB > scenery.Vertices.Count || + quad.VertexC > scenery.Vertices.Count || + quad.VertexD > scenery.Vertices.Count) + continue; + + var info = ProcessTextureInfoC2 (quad.Texture, quad.Animated, scenery.Textures, scenery.AnimatedTextures); + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null, uv4 = null; + + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; + + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == textureEIDs [scenery.GetTPAG (value.Page)] + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [scenery.GetTPAG (value.Page)] + ); + + var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = value.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + + uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + uv2 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + uv4 = new (value.X4 / texsize.X, (128 - value.Y4) / texsize.Y); + } + + // add the face + SceneryVertex fv1 = scenery.Vertices [quad.VertexA]; + SceneryVertex fv2 = scenery.Vertices [quad.VertexB]; + SceneryVertex fv3 = scenery.Vertices [quad.VertexC]; + SceneryVertex fv4 = scenery.Vertices [quad.VertexD]; + SceneryColor fc1 = scenery.Colors [fv1.Color]; + SceneryColor fc2 = scenery.Colors [fv2.Color]; + SceneryColor fc3 = scenery.Colors [fv3.Color]; + SceneryColor fc4 = scenery.Colors [fv4.Color]; + Vector3 v1 = new Vector3 (fv1.X, fv1.Y, fv1.Z); + Vector3 v2 = new Vector3 (fv2.X, fv2.Y, fv2.Z); + Vector3 v3 = new Vector3 (fv3.X, fv3.Y, fv3.Z); + Vector3 v4 = new Vector3 (fv4.X, fv4.Y, fv4.Z); + Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; + Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; + Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; + Vector3 c4 = new Vector3 (fc4.Red, fc4.Green, fc4.Blue) / 255f; + + exporter.AddFace ( + (v1 + offset / 16) * scale, + (v2 + offset / 16) * scale, + (v3 + offset / 16) * scale, + (v4 + offset / 16) * scale, + c1, c2, c3, c4, + material, + uv1, uv2, uv3, uv4 + ); + } + } + + exporter.Export (path, modelname); + } + /// + /// TODO: THIS IS A DUPLICATED OF GLViewer:899, MOVE IT SOMEWHERE ELSE WHERE BOTH HAVE ACCESS TO IT + /// + /// + /// + /// + /// + /// + protected Tuple ProcessTextureInfoC2(int in_tex_id, bool animated, IList textures, IList animated_textures) + { + if (in_tex_id != 0 || animated) + { + ModelTexture? info_temp = null; + int tex_id = in_tex_id - 1; + if (animated) + { + if (++tex_id >= animated_textures.Count) + { + return new(false, null); + } + var anim = animated_textures[tex_id]; + // check if it's an untextured polygon + if (anim.Offset != 0) + { + tex_id = anim.Offset - 1; + if (anim.IsLOD) + { + tex_id += anim.LOD0; // we only render closest LOD for now + } + else + { + tex_id += (int)((0 / 2 / (1 + anim.Latency) + anim.Delay) & anim.Mask); + if (anim.Leap) + { + anim = animated_textures[++tex_id]; + tex_id = anim.Offset - 1 + anim.LOD0; + } + } + if (tex_id >= textures.Count) + { + return new(false, null); + } + info_temp = textures[tex_id]; + } + } + else + { + if (tex_id >= textures.Count) + { + return new(false, null); + } + info_temp = textures[tex_id]; + } + return new(true, info_temp); + } + return new(true, null); + } + private void Menu_Import_Chunk() { byte[][] datas = FileUtil.OpenFiles(FileFilters.Any); From 5f28bf31199818aef29a81703fe575bd751c6633 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 23:42:45 +0200 Subject: [PATCH 07/17] Cleanup of duplicated code Signed-off-by: Alexis Maiquez Murcia --- .../Animation/AnimationEntryController.cs | 3 +- .../ColoredAnimationEntryController.cs | 3 +- .../Animation/ColoredFrameController.cs | 0 .../Controllers/Animation/FrameController.cs | 66 ++---------------- .../Animation/OldAnimationEntryController.cs | 3 +- .../Animation/OldFrameController.cs | 6 +- CrashEdit/Controllers/NSFController.cs | 67 ++----------------- .../Scenery/SceneryEntryController.cs | 66 ++---------------- CrashEdit/Controls/3D/GLViewer.cs | 61 +---------------- CrashEdit/Utils/TextureUtils.cs | 58 ++++++++++++++++ 10 files changed, 82 insertions(+), 251 deletions(-) create mode 100644 CrashEdit/Controllers/Animation/ColoredFrameController.cs create mode 100644 CrashEdit/Utils/TextureUtils.cs diff --git a/CrashEdit/Controllers/Animation/AnimationEntryController.cs b/CrashEdit/Controllers/Animation/AnimationEntryController.cs index 27607b7c..5ddda16f 100644 --- a/CrashEdit/Controllers/Animation/AnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/AnimationEntryController.cs @@ -22,7 +22,8 @@ public override Control CreateEditor() private void Menu_Export_OBJ () { - FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); + if (!FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any)) + return; // modify the path to add a number before the extension string ext = Path.GetExtension (output); diff --git a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs index 205ba21d..9610fb1c 100644 --- a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs @@ -23,7 +23,8 @@ public override Control CreateEditor() private void Menu_Export_OBJ() { - FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); + if (!FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any)) + return; // modify the path to add a number before the extension string ext = Path.GetExtension (output); diff --git a/CrashEdit/Controllers/Animation/ColoredFrameController.cs b/CrashEdit/Controllers/Animation/ColoredFrameController.cs new file mode 100644 index 00000000..e69de29b diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index 9afc0760..ed9801af 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -27,7 +27,9 @@ public override Control CreateEditor() private void Menu_Export_OBJ () { - FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any); + if (!FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any)) + return; + ToOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); } @@ -75,7 +77,7 @@ public void ToOBJ (string path, string modelname) // iterate all the triangles, get the texture modes and build information about those foreach (var tri in model.Triangles) { - var info = ProcessTextureInfoC2 (tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures); + var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures); string material = null; Vector2? uv1 = null, uv2 = null, uv3 = null; bool nocull = tri.Subtype == 0 || tri.Subtype == 2; @@ -160,65 +162,5 @@ public void ToOBJ (string path, string modelname) exporter.Export (path, modelname); } - - /// - /// TODO: THIS IS A DUPLICATED OF GLViewer:899, MOVE IT SOMEWHERE ELSE WHERE BOTH HAVE ACCESS TO IT - /// - /// - /// - /// - /// - /// - protected Tuple ProcessTextureInfoC2(int in_tex_id, bool animated, IList textures, IList animated_textures) - { - if (in_tex_id != 0 || animated) - { - ModelTexture? info_temp = null; - int tex_id = in_tex_id - 1; - if (animated) - { - if (++tex_id >= animated_textures.Count) - { - return new(false, null); - } - var anim = animated_textures[tex_id]; - // check if it's an untextured polygon - if (anim.Offset != 0) - { - tex_id = anim.Offset - 1; - if (anim.IsLOD) - { - tex_id += anim.LOD0; // we only render closest LOD for now - } - else - { - tex_id += (int)((0 / 2 / (1 + anim.Latency) + anim.Delay) & anim.Mask); - if (anim.Leap) - { - anim = animated_textures[++tex_id]; - tex_id = anim.Offset - 1 + anim.LOD0; - } - } - if (tex_id >= textures.Count) - { - return new(false, null); - } - info_temp = textures[tex_id]; - } - } - else - { - if (tex_id >= textures.Count) - { - return new(false, null); - } - info_temp = textures[tex_id]; - } - return new(true, info_temp); - } - return new(true, null); - } - - } } diff --git a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs index 5348f6ca..7fd646f3 100755 --- a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs @@ -22,7 +22,8 @@ public override Control CreateEditor() private void Menu_Export_OBJ () { - FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); + if (!FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any)) + return; // modify the path to add a number before the extension string ext = Path.GetExtension (output); diff --git a/CrashEdit/Controllers/Animation/OldFrameController.cs b/CrashEdit/Controllers/Animation/OldFrameController.cs index 0db21770..72f35847 100755 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -55,11 +55,9 @@ private void Menu_Export_OBJ() { throw new GUIException("The linked model entry could not be found."); } - if (MessageBox.Show("Texture and color information will not be exported.\n\nContinue anyway?", "Export as OBJ", MessageBoxButtons.YesNo) != DialogResult.Yes) - { + if (!FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any)) return; - } - FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any); + ToOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); } diff --git a/CrashEdit/Controllers/NSFController.cs b/CrashEdit/Controllers/NSFController.cs index 91c93a0c..cf513a2f 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -313,7 +313,9 @@ private void Menu_ShowLevelZonesC2() private void Menu_ExportSceneryOBJ () { - FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any); + if (!FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any)) + return; + ExportSceneryOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); } @@ -349,7 +351,7 @@ private void ExportSceneryOBJ (string path, string modelname) tri.VertexC > scenery.Vertices.Count) continue; - var info = ProcessTextureInfoC2 (tri.Texture, tri.Animated, scenery.Textures, scenery.AnimatedTextures); + var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, scenery.Textures, scenery.AnimatedTextures); string material = null; Vector2? uv1 = null, uv2 = null, uv3 = null; @@ -429,7 +431,7 @@ private void ExportSceneryOBJ (string path, string modelname) quad.VertexD > scenery.Vertices.Count) continue; - var info = ProcessTextureInfoC2 (quad.Texture, quad.Animated, scenery.Textures, scenery.AnimatedTextures); + var info = TextureUtils.ProcessTextureInfoC2 (0, quad.Texture, quad.Animated, scenery.Textures, scenery.AnimatedTextures); string material = null; Vector2? uv1 = null, uv2 = null, uv3 = null, uv4 = null; @@ -509,64 +511,7 @@ private void ExportSceneryOBJ (string path, string modelname) exporter.Export (path, modelname); } - /// - /// TODO: THIS IS A DUPLICATED OF GLViewer:899, MOVE IT SOMEWHERE ELSE WHERE BOTH HAVE ACCESS TO IT - /// - /// - /// - /// - /// - /// - protected Tuple ProcessTextureInfoC2(int in_tex_id, bool animated, IList textures, IList animated_textures) - { - if (in_tex_id != 0 || animated) - { - ModelTexture? info_temp = null; - int tex_id = in_tex_id - 1; - if (animated) - { - if (++tex_id >= animated_textures.Count) - { - return new(false, null); - } - var anim = animated_textures[tex_id]; - // check if it's an untextured polygon - if (anim.Offset != 0) - { - tex_id = anim.Offset - 1; - if (anim.IsLOD) - { - tex_id += anim.LOD0; // we only render closest LOD for now - } - else - { - tex_id += (int)((0 / 2 / (1 + anim.Latency) + anim.Delay) & anim.Mask); - if (anim.Leap) - { - anim = animated_textures[++tex_id]; - tex_id = anim.Offset - 1 + anim.LOD0; - } - } - if (tex_id >= textures.Count) - { - return new(false, null); - } - info_temp = textures[tex_id]; - } - } - else - { - if (tex_id >= textures.Count) - { - return new(false, null); - } - info_temp = textures[tex_id]; - } - return new(true, info_temp); - } - return new(true, null); - } - + private void Menu_Import_Chunk() { byte[][] datas = FileUtil.OpenFiles(FileFilters.Any); diff --git a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs index f47e3217..2556b288 100755 --- a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs @@ -25,7 +25,9 @@ public override Control CreateEditor() private void Menu_Export_OBJ () { - FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any); + if (!FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any)) + return; + ToOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); } @@ -52,7 +54,7 @@ private void ToOBJ (string path, string modelname) foreach (var tri in SceneryEntry.Triangles) { - var info = ProcessTextureInfoC2(tri.Texture, tri.Animated, SceneryEntry.Textures, SceneryEntry.AnimatedTextures); + var info = TextureUtils.ProcessTextureInfoC2(0, tri.Texture, tri.Animated, SceneryEntry.Textures, SceneryEntry.AnimatedTextures); string material = null; Vector2? uv1 = null, uv2 = null, uv3 = null; @@ -125,7 +127,7 @@ private void ToOBJ (string path, string modelname) foreach (var quad in SceneryEntry.Quads) { - var info = ProcessTextureInfoC2(quad.Texture, quad.Animated, SceneryEntry.Textures, SceneryEntry.AnimatedTextures); + var info = TextureUtils.ProcessTextureInfoC2(0, quad.Texture, quad.Animated, SceneryEntry.Textures, SceneryEntry.AnimatedTextures); string material = null; Vector2? uv1 = null, uv2 = null, uv3 = null, uv4 = null; @@ -205,64 +207,6 @@ private void ToOBJ (string path, string modelname) exporter.Export (path, modelname); } - /// - /// TODO: THIS IS A DUPLICATED OF GLViewer:899, MOVE IT SOMEWHERE ELSE WHERE BOTH HAVE ACCESS TO IT - /// - /// - /// - /// - /// - /// - protected Tuple ProcessTextureInfoC2(int in_tex_id, bool animated, IList textures, IList animated_textures) - { - if (in_tex_id != 0 || animated) - { - ModelTexture? info_temp = null; - int tex_id = in_tex_id - 1; - if (animated) - { - if (++tex_id >= animated_textures.Count) - { - return new(false, null); - } - var anim = animated_textures[tex_id]; - // check if it's an untextured polygon - if (anim.Offset != 0) - { - tex_id = anim.Offset - 1; - if (anim.IsLOD) - { - tex_id += anim.LOD0; // we only render closest LOD for now - } - else - { - tex_id += (int)((0 / 2 / (1 + anim.Latency) + anim.Delay) & anim.Mask); - if (anim.Leap) - { - anim = animated_textures[++tex_id]; - tex_id = anim.Offset - 1 + anim.LOD0; - } - } - if (tex_id >= textures.Count) - { - return new(false, null); - } - info_temp = textures[tex_id]; - } - } - else - { - if (tex_id >= textures.Count) - { - return new(false, null); - } - info_temp = textures[tex_id]; - } - return new(true, info_temp); - } - return new(true, null); - } - private void Menu_Fix_WGEOv3() { for (int i = 0; i < SceneryEntry.Vertices.Count; i++) diff --git a/CrashEdit/Controls/3D/GLViewer.cs b/CrashEdit/Controls/3D/GLViewer.cs index b577b369..f3010795 100644 --- a/CrashEdit/Controls/3D/GLViewer.cs +++ b/CrashEdit/Controls/3D/GLViewer.cs @@ -1096,66 +1096,7 @@ protected void UploadTPAGs() } } } - - protected bool ProcessTextureInfoC2(int in_tex_id, bool animated, IList textures, IList animated_textures, out ModelTexture tex) => ProcessTextureInfoC2(render.RealCurrentFrame / 2, in_tex_id, animated, textures, animated_textures, out tex); - - public static bool ProcessTextureInfoC2(long texture_frame, int in_tex_id, bool animated, IList textures, IList animated_textures, out ModelTexture tex) - { - if (in_tex_id != 0 || animated) - { - int tex_id = in_tex_id - 1; - if (animated) - { - if (++tex_id >= animated_textures.Count) - { - tex = default; - return false; - } - var anim = animated_textures[tex_id]; - // check if it's an untextured polygon - if (anim.Offset != 0) - { - tex_id = anim.Offset - 1; - if (anim.IsLOD) - { - tex_id += anim.LOD0; // we only render closest LOD for now - } - else - { - tex_id += (int)((texture_frame / (1 + anim.Latency) + anim.Delay) & anim.Mask); - if (anim.Leap) - { - anim = animated_textures[++tex_id]; - tex_id = anim.Offset - 1 + anim.LOD0; - } - } - if (tex_id >= textures.Count) - { - tex = default; - return false; - } - tex = textures[tex_id]; - } - else - { - tex = default; - } - } - else - { - if (tex_id >= textures.Count) - { - tex = default; - return false; - } - tex = textures[tex_id]; - } - return true; - } - tex = default; - return true; - } - + protected override void OnMouseWheel(MouseEventArgs e) { base.OnMouseWheel(e); diff --git a/CrashEdit/Utils/TextureUtils.cs b/CrashEdit/Utils/TextureUtils.cs new file mode 100644 index 00000000..2ead55ed --- /dev/null +++ b/CrashEdit/Utils/TextureUtils.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using Crash; + +namespace CrashEdit; + +public class TextureUtils +{ + public static Tuple ProcessTextureInfoC2(long currentFrame, int in_tex_id, bool animated, IList textures, IList animated_textures) + { + if (in_tex_id != 0 || animated) + { + ModelTexture? info_temp = null; + int tex_id = in_tex_id - 1; + if (animated) + { + if (++tex_id >= animated_textures.Count) + { + return new(false, null); + } + var anim = animated_textures[tex_id]; + // check if it's an untextured polygon + if (anim.Offset != 0) + { + tex_id = anim.Offset - 1; + if (anim.IsLOD) + { + tex_id += anim.LOD0; // we only render closest LOD for now + } + else + { + tex_id += (int)((currentFrame / 2 / (1 + anim.Latency) + anim.Delay) & anim.Mask); + if (anim.Leap) + { + anim = animated_textures[++tex_id]; + tex_id = anim.Offset - 1 + anim.LOD0; + } + } + if (tex_id >= textures.Count) + { + return new(false, null); + } + info_temp = textures[tex_id]; + } + } + else + { + if (tex_id >= textures.Count) + { + return new(false, null); + } + info_temp = textures[tex_id]; + } + return new(true, info_temp); + } + return new(true, null); + } +} \ No newline at end of file From c7e93384f175270b5e2b11dafff2f19a46549fc1 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 23:48:54 +0200 Subject: [PATCH 08/17] Fix texture cache using the wrong texture on export Signed-off-by: Alexis Maiquez Murcia --- .../Controllers/Animation/FrameController.cs | 9 +++++---- CrashEdit/Controllers/NSFController.cs | 16 +++++++++------- .../Scenery/SceneryEntryController.cs | 13 ++++++++----- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index ed9801af..fefbebba 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -87,13 +87,14 @@ public void ToOBJ (string path, string modelname) if (info.Item1 && info.Item2 is not null) { var value = info.Item2.Value; - + int textureEID = model.GetTPAG (value.Page); + material = objTranslate.FirstOrDefault (x => x.Value.color == value.ColorMode && x.Value.blend == value.BlendMode && x.Value.clutx == value.ClutX && x.Value.cluty == value.ClutY && - x.Value.page == textureEIDs [model.GetTPAG (value.Page)] + x.Value.page == textureEIDs [textureEID] ).Key; // ignore the texinfo if there's already a texture with the exact same settings stored @@ -101,10 +102,10 @@ public void ToOBJ (string path, string modelname) { var texinfo = new TexInfoUnpacked ( true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [model.GetTPAG (value.Page)] + page: textureEIDs [textureEID] ); - var tpag = this.AnimationEntryController.NSF.GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); + var tpag = this.AnimationEntryController.NSF.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); diff --git a/CrashEdit/Controllers/NSFController.cs b/CrashEdit/Controllers/NSFController.cs index cf513a2f..8c467d7a 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -332,8 +332,7 @@ private void ExportSceneryOBJ (string path, string modelname) { var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); var scale = new Vector3 (1 / GameScales.WorldC1); - - + for (int i = 0; i < scenery.TPAGCount; i++) { int tpag_eid = scenery.GetTPAG (i); @@ -370,12 +369,14 @@ private void ExportSceneryOBJ (string path, string modelname) // ignore the texinfo if there's already a texture with the exact same settings stored if (material is null) { + int textureEID = scenery.GetTPAG (value.Page); + var texinfo = new TexInfoUnpacked ( true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [scenery.GetTPAG (value.Page)] + page: textureEIDs [textureEID] ); - var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); + var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); @@ -438,13 +439,14 @@ private void ExportSceneryOBJ (string path, string modelname) if (info.Item1 && info.Item2 is not null) { var value = info.Item2.Value; + int textureEID = scenery.GetTPAG (value.Page); material = objTranslate.FirstOrDefault (x => x.Value.color == value.ColorMode && x.Value.blend == value.BlendMode && x.Value.clutx == value.ClutX && x.Value.cluty == value.ClutY && - x.Value.page == textureEIDs [scenery.GetTPAG (value.Page)] + x.Value.page == textureEIDs [textureEID] ).Key; // ignore the texinfo if there's already a texture with the exact same settings stored @@ -452,10 +454,10 @@ private void ExportSceneryOBJ (string path, string modelname) { var texinfo = new TexInfoUnpacked ( true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [scenery.GetTPAG (value.Page)] + page: textureEIDs [textureEID] ); - var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); + var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); diff --git a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs index 2556b288..7439bf65 100755 --- a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs @@ -73,12 +73,14 @@ private void ToOBJ (string path, string modelname) // ignore the texinfo if there's already a texture with the exact same settings stored if (material is null) { + int textureEID = SceneryEntry.GetTPAG (value.Page); + var texinfo = new TexInfoUnpacked ( true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [SceneryEntry.GetTPAG (value.Page)] + page: textureEIDs [textureEID] ); - var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); + var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); @@ -134,13 +136,14 @@ private void ToOBJ (string path, string modelname) if (info.Item1 && info.Item2 is not null) { var value = info.Item2.Value; + int textureEID = SceneryEntry.GetTPAG (value.Page); material = objTranslate.FirstOrDefault (x => x.Value.color == value.ColorMode && x.Value.blend == value.BlendMode && x.Value.clutx == value.ClutX && x.Value.cluty == value.ClutY && - x.Value.page == textureEIDs [SceneryEntry.GetTPAG (value.Page)] + x.Value.page == textureEIDs [textureEID] ).Key; // ignore the texinfo if there's already a texture with the exact same settings stored @@ -148,10 +151,10 @@ private void ToOBJ (string path, string modelname) { var texinfo = new TexInfoUnpacked ( true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [SceneryEntry.GetTPAG (value.Page)] + page: textureEIDs [textureEID] ); - var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Value == texinfo.page).Key); + var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); From 30bac41038d6586082be03cb7b7fe52759a1980b Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Tue, 11 Apr 2023 00:21:45 +0200 Subject: [PATCH 09/17] Added scenery export to Crash 1 Signed-off-by: Alexis Maiquez Murcia --- CrashEdit/Controllers/NSFController.cs | 115 +++++++++++++++++- .../Scenery/OldSceneryEntryController.cs | 12 +- 2 files changed, 113 insertions(+), 14 deletions(-) diff --git a/CrashEdit/Controllers/NSFController.cs b/CrashEdit/Controllers/NSFController.cs index 8c467d7a..1ced6568 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -26,6 +26,7 @@ public NSFController(NSF nsf, SubcontrollerGroup parentGroup) : base(parentGroup { AddMenu(CrashUI.Properties.Resources.NSFController_AcShowLevel, Menu_ShowLevelC1); AddMenu(CrashUI.Properties.Resources.NSFController_AcShowLevelZones, Menu_ShowLevelZonesC1); + AddMenu(CrashUI.Properties.Resources.NSFController_AcExportScenery, Menu_ExportSceneryC1OBJ); } else if (GameVersion == GameVersion.Crash1Beta1995) { @@ -38,7 +39,7 @@ public NSFController(NSF nsf, SubcontrollerGroup parentGroup) : base(parentGroup AddMenu(CrashUI.Properties.Resources.NSFController_AcShowLevel, Menu_ShowLevelC2); AddMenu(CrashUI.Properties.Resources.NSFController_AcShowLevelZones, Menu_ShowLevelZonesC2); AddMenuSeparator (); - AddMenu (CrashUI.Properties.Resources.NSFController_AcExportScenery, Menu_ExportSceneryOBJ); + AddMenu(CrashUI.Properties.Resources.NSFController_AcExportScenery, Menu_ExportSceneryC2OBJ); } } @@ -311,15 +312,23 @@ private void Menu_ShowLevelZonesC2() }; } - private void Menu_ExportSceneryOBJ () + private void Menu_ExportSceneryC1OBJ () { if (!FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any)) return; - ExportSceneryOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); + ExportSceneryC1OBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); } - private void ExportSceneryOBJ (string path, string modelname) + private void Menu_ExportSceneryC2OBJ () + { + if (!FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any)) + return; + + ExportSceneryC2OBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); + } + + private void ExportSceneryC2OBJ (string path, string modelname) { var exporter = new OBJExporter (); @@ -513,7 +522,105 @@ private void ExportSceneryOBJ (string path, string modelname) exporter.Export (path, modelname); } + + private void ExportSceneryC1OBJ (string path, string modelname) + { + var exporter = new OBJExporter (); + + // detect how many textures are used and their eids to prepare the image + Dictionary textureEIDs = new (); + Dictionary objTranslate = new Dictionary (); + + // find all the scenery insde chunks + foreach (OldSceneryEntry scenery in NSF.GetEntries ()) + { + var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); + var scale = new Vector3 (1 / GameScales.WorldC1); + + for (int i = 0; i < scenery.TPAGCount; i++) + { + int tpag_eid = scenery.GetTPAG (i); + + if (textureEIDs.ContainsKey (tpag_eid)) + continue; + textureEIDs.Add (tpag_eid, textureEIDs.Count); + } + + foreach (var polygon in scenery.Polygons) + { + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null; + OldModelStruct str = scenery.Structs[polygon.ModelStruct]; + OldSceneryVertex ov1 = scenery.Vertices [polygon.VertexA]; + OldSceneryVertex ov2 = scenery.Vertices [polygon.VertexB]; + OldSceneryVertex ov3 = scenery.Vertices [polygon.VertexC]; + Vector3 v1 = new Vector3 (ov1.X, ov1.Y, ov1.Z); + Vector3 v2 = new Vector3 (ov2.X, ov2.Y, ov2.Z); + Vector3 v3 = new Vector3 (ov3.X, ov3.Y, ov3.Z); + Vector3 color = Vector3.Zero; + + if (str is OldSceneryTexture t) + { + color = new Vector3 (t.R, t.G, t.B) / 255F; + + // add the texture to the list too + material = objTranslate.FirstOrDefault (x => + x.Value.color == t.ColorMode && + x.Value.blend == t.BlendMode && + x.Value.clutx == t.ClutX && + x.Value.cluty == t.ClutY && + x.Value.page == textureEIDs [scenery.GetTPAG (polygon.Page)] + ).Key; + + if (material is null) + { + var texinfo = new TexInfoUnpacked( + true, color: t.ColorMode, blend: t.BlendMode, + clutx: t.ClutX, cluty: t.ClutY, + page: textureEIDs[scenery.GetTPAG (polygon.Page)] + ); + + var tpag = this.NSF.GetEntry (scenery.GetTPAG (polygon.Page)); + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = t.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + + uv3 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); + uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); + uv1 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); + } + else if(str is OldSceneryColor c) + { + color = new Vector3 (c.R, c.G, c.B) / 255F; + } + + exporter.AddFace ( + (v1 + offset) * scale, + (v2 + offset) * scale, + (v3 + offset) * scale, + color, color, color, + material, + uv1, uv2, uv3 + ); + } + } + + exporter.Export (path, modelname); + } + private void Menu_Import_Chunk() { byte[][] datas = FileUtil.OpenFiles(FileFilters.Any); diff --git a/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs b/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs index 2a98f694..bb284a6b 100755 --- a/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs @@ -10,7 +10,8 @@ public OldSceneryEntryController(OldSceneryEntry oldsceneryentry, SubcontrollerG OldSceneryEntry = oldsceneryentry; AddMenuSeparator(); AddMenu("Export as OBJ", Menu_Export_OBJ); - AddMenu("Export as COLLADA", Menu_Export_COLLADA); + InvalidateNode(); + InvalidateNodeImage(); } public override bool EditorAvailable => true; @@ -30,14 +31,5 @@ private void Menu_Export_OBJ() } FileUtil.SaveFile(OldSceneryEntry.ToOBJ(), FileFilters.OBJ, FileFilters.Any); } - - private void Menu_Export_COLLADA() - { - if (MessageBox.Show("Exporting to COLLADA is experimental.\nTexture information will not be exported.\n\nContinue anyway?", "Export as COLLADA", MessageBoxButtons.YesNo) != DialogResult.Yes) - { - return; - } - FileUtil.SaveFile(OldSceneryEntry.ToCOLLADA(), FileFilters.COLLADA, FileFilters.Any); - } } } From 8cc53798911824a5307e649d7eabc2d7f21e3217 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Tue, 11 Apr 2023 00:43:27 +0200 Subject: [PATCH 10/17] Deduplicated code related to export Signed-off-by: Alexis Maiquez Murcia --- .../Controllers/Animation/FrameController.cs | 115 +------ .../Animation/OldFrameController.cs | 94 +----- CrashEdit/Controllers/NSFController.cs | 263 +--------------- CrashEdit/Exporters/FrameExtensions.cs | 219 ++++++++++++++ CrashEdit/Exporters/SceneryExtensions.cs | 283 ++++++++++++++++++ 5 files changed, 510 insertions(+), 464 deletions(-) create mode 100644 CrashEdit/Exporters/FrameExtensions.cs create mode 100644 CrashEdit/Exporters/SceneryExtensions.cs diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index fefbebba..829d7c42 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -44,123 +44,12 @@ private void Menu_Export_OBJ () /// public void ToOBJ (string path, string modelname) { - var exporter = new OBJExporter (); - var model = this.AnimationEntryController.NSF.GetEntry(Frame.ModelEID); - var vertices = Frame.MakeVertices (model); - var offset = new Vector3 (Frame.XOffset, Frame.YOffset, Frame.ZOffset) / 4F; - var scale = new Vector3 (model.ScaleX, model.ScaleY, model.ScaleZ) / (GameScales.ModelC1 * GameScales.AnimC1); - - // detect how many textures are used and their eids to prepare the image Dictionary textureEIDs = new (); - - for (int i = 0; i < model.TPAGCount; i++) - { - int tpag_eid = model.GetTPAG (i); - - if (textureEIDs.ContainsKey (tpag_eid)) - continue; - - textureEIDs.Add (tpag_eid, textureEIDs.Count); - } - - // once we have the textureEIDs we know what has to be loaded where - // these textures are 128 pixels of height - // and the game loads every texture used and keeps a lookup table of the texture "index" - - // depending on where in the model it's going to be drawn, the texture is treated differently - // (4 bits per pixel, 8 bits per pixel, 16 bits per pixel) - // that information is inside each triangle of the frame - // so the same texture can be treated differently - // try to build an atlas of sorts with all the information Dictionary objTranslate = new Dictionary (); - // iterate all the triangles, get the texture modes and build information about those - foreach (var tri in model.Triangles) - { - var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures); - string material = null; - Vector2? uv1 = null, uv2 = null, uv3 = null; - bool nocull = tri.Subtype == 0 || tri.Subtype == 2; - bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; - - // parse the texture and add it to the exporter - if (info.Item1 && info.Item2 is not null) - { - var value = info.Item2.Value; - int textureEID = model.GetTPAG (value.Page); - - material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == textureEIDs [textureEID] - ).Key; - - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] - ); - - var tpag = this.AnimationEntryController.NSF.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); - - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = value.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv2 = new Vector2 (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); - - if ((tri.Type != 2 && !flip) || (tri.Type == 2 && tri.Subtype == 1)) - { - uv1 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); - uv3 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); - } - else - { - uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); - uv1 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); - } - } - - // add the face - SceneryColor fc1 = model.Colors [tri.Color [!flip ? 0 : 2]]; - SceneryColor fc2 = model.Colors [tri.Color [!flip ? 1 : 1]]; - SceneryColor fc3 = model.Colors [tri.Color [!flip ? 2 : 0]]; - Position fv1 = vertices [tri.Vertex [!flip ? 0 : 2] + Frame.SpecialVertexCount]; - Position fv2 = vertices [tri.Vertex [!flip ? 1 : 1] + Frame.SpecialVertexCount]; - Position fv3 = vertices [tri.Vertex [!flip ? 2 : 0] + Frame.SpecialVertexCount]; - Vector3 v1 = new Vector3 (fv1.X, fv1.Z, fv1.Y); - Vector3 v2 = new Vector3 (fv2.X, fv2.Z, fv2.Y); - Vector3 v3 = new Vector3 (fv3.X, fv3.Z, fv3.Y); - Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; - Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; - Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; - - exporter.AddFace ( - (v1 + offset) * scale, - (v2 + offset) * scale, - (v3 + offset) * scale, - c1, c2, c3, - material, - uv1, uv2, uv3 - ); - } + var exporter = new OBJExporter (); + exporter.AddFrame (AnimationEntryController.NSF, Frame, ref textureEIDs, ref objTranslate); exporter.Export (path, modelname); } } diff --git a/CrashEdit/Controllers/Animation/OldFrameController.cs b/CrashEdit/Controllers/Animation/OldFrameController.cs index 72f35847..3b020350 100755 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -63,98 +63,12 @@ private void Menu_Export_OBJ() public void ToOBJ(string path, string modelname) { - var exporter = new OBJExporter (); - var model = GetNSF ().GetEntry(OldFrame.ModelEID); - var offset = new Vector3 (OldFrame.XOffset, OldFrame.YOffset, OldFrame.ZOffset); - var scale = new Vector3 (model.ScaleX, model.ScaleY, model.ScaleZ) / (GameScales.ModelC1 * GameScales.AnimC1); - - // detect how many textures are used an ther eids to prepare the image - Dictionary textureEIDs = new (); - - foreach (OldModelStruct str in model.Structs) - { - if (str is not OldModelTexture tex) - continue; - - if (textureEIDs.ContainsKey (tex.EID)) - continue; - - textureEIDs [tex.EID] = textureEIDs.Count; - } - + Dictionary textureEIDs = new Dictionary (); Dictionary objTranslate = new Dictionary (); - foreach (OldModelPolygon polygon in model.Polygons) - { - string material = null; - Vector2? uv1 = null, uv2 = null, uv3 = null; - OldModelStruct str = model.Structs[polygon.TexInfo]; - OldFrameVertex ov1 = OldFrame.Vertices [polygon.VertexA / 6]; - OldFrameVertex ov2 = OldFrame.Vertices [polygon.VertexB / 6]; - OldFrameVertex ov3 = OldFrame.Vertices [polygon.VertexC / 6]; - Vector3 v1 = new Vector3 (ov1.X, ov1.Y, ov1.Z); - Vector3 v2 = new Vector3 (ov2.X, ov2.Y, ov2.Z); - Vector3 v3 = new Vector3 (ov3.X, ov3.Y, ov3.Z); - Vector3 color = Vector3.Zero; - - if (str is OldModelTexture t) - { - color = new Vector3 (t.R, t.G, t.B) / 255F; - - // add the texture to the list too - material = objTranslate.FirstOrDefault (x => - x.Value.color == t.ColorMode && - x.Value.blend == t.BlendMode && - x.Value.clutx == t.ClutX && - x.Value.cluty == t.ClutY && - x.Value.page == textureEIDs [t.EID] - ).Key; - - if (material is null) - { - var texinfo = new TexInfoUnpacked( - true, color: t.ColorMode, blend: t.BlendMode, - clutx: t.ClutX, cluty: t.ClutY, - face: Convert.ToInt32(t.N), - page: textureEIDs[t.EID] - ); - - var tpag = GetNSF ().GetEntry (t.EID); - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = t.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv3 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); - uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); - uv1 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); - } - else if(str is OldSceneryColor c) - { - color = new Vector3 (c.R, c.G, c.B) / 255F; - } - - exporter.AddFace ( - (v1 + offset) * scale, - (v2 + offset) * scale, - (v3 + offset) * scale, - color, color, color, - material, - uv1, uv2, uv3 - ); - } - + var exporter = new OBJExporter (); + + exporter.AddFrame (OldAnimationEntryController.NSF, OldFrame, ref textureEIDs, ref objTranslate); exporter.Export (path, modelname); } } diff --git a/CrashEdit/Controllers/NSFController.cs b/CrashEdit/Controllers/NSFController.cs index 1ced6568..d6117b3d 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -339,185 +339,7 @@ private void ExportSceneryC2OBJ (string path, string modelname) // find all the scenery insde chunks foreach (SceneryEntry scenery in NSF.GetEntries ()) { - var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); - var scale = new Vector3 (1 / GameScales.WorldC1); - - for (int i = 0; i < scenery.TPAGCount; i++) - { - int tpag_eid = scenery.GetTPAG (i); - - if (textureEIDs.ContainsKey (tpag_eid)) - continue; - - textureEIDs.Add (tpag_eid, textureEIDs.Count); - } - - foreach (var tri in scenery.Triangles) - { - if (tri.VertexA > scenery.Vertices.Count || - tri.VertexB > scenery.Vertices.Count || - tri.VertexC > scenery.Vertices.Count) - continue; - - var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, scenery.Textures, scenery.AnimatedTextures); - string material = null; - Vector2? uv1 = null, uv2 = null, uv3 = null; - - if (info.Item1 && info.Item2 is not null) - { - var value = info.Item2.Value; - - material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == textureEIDs [scenery.GetTPAG (value.Page)] - ).Key; - - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - int textureEID = scenery.GetTPAG (value.Page); - - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] - ); - - var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); - - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = value.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); - uv2 = new Vector2 (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); - uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); - } - - // add the face - SceneryVertex fv1 = scenery.Vertices [tri.VertexA]; - SceneryVertex fv2 = scenery.Vertices [tri.VertexB]; - SceneryVertex fv3 = scenery.Vertices [tri.VertexC]; - SceneryColor fc1 = scenery.Colors [fv1.Color]; - SceneryColor fc2 = scenery.Colors [fv2.Color]; - SceneryColor fc3 = scenery.Colors [fv3.Color]; - Vector3 v1 = new Vector3 (fv1.X, fv1.Y, fv1.Z); - Vector3 v2 = new Vector3 (fv2.X, fv2.Y, fv2.Z); - Vector3 v3 = new Vector3 (fv3.X, fv3.Y, fv3.Z); - Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; - Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; - Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; - - exporter.AddFace ( - (v1 + offset / 16) * scale, - (v2 + offset / 16) * scale, - (v3 + offset / 16) * scale, - c1, c2, c3, - material, - uv1, uv2, uv3 - ); - } - - foreach (var quad in scenery.Quads) - { - // ignore quads that are out of limits - if (quad.VertexA > scenery.Vertices.Count || - quad.VertexB > scenery.Vertices.Count || - quad.VertexC > scenery.Vertices.Count || - quad.VertexD > scenery.Vertices.Count) - continue; - - var info = TextureUtils.ProcessTextureInfoC2 (0, quad.Texture, quad.Animated, scenery.Textures, scenery.AnimatedTextures); - string material = null; - Vector2? uv1 = null, uv2 = null, uv3 = null, uv4 = null; - - if (info.Item1 && info.Item2 is not null) - { - var value = info.Item2.Value; - int textureEID = scenery.GetTPAG (value.Page); - - material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == textureEIDs [textureEID] - ).Key; - - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] - ); - - var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); - - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = value.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); - uv2 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); - uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); - uv4 = new (value.X4 / texsize.X, (128 - value.Y4) / texsize.Y); - } - - // add the face - SceneryVertex fv1 = scenery.Vertices [quad.VertexA]; - SceneryVertex fv2 = scenery.Vertices [quad.VertexB]; - SceneryVertex fv3 = scenery.Vertices [quad.VertexC]; - SceneryVertex fv4 = scenery.Vertices [quad.VertexD]; - SceneryColor fc1 = scenery.Colors [fv1.Color]; - SceneryColor fc2 = scenery.Colors [fv2.Color]; - SceneryColor fc3 = scenery.Colors [fv3.Color]; - SceneryColor fc4 = scenery.Colors [fv4.Color]; - Vector3 v1 = new Vector3 (fv1.X, fv1.Y, fv1.Z); - Vector3 v2 = new Vector3 (fv2.X, fv2.Y, fv2.Z); - Vector3 v3 = new Vector3 (fv3.X, fv3.Y, fv3.Z); - Vector3 v4 = new Vector3 (fv4.X, fv4.Y, fv4.Z); - Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; - Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; - Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; - Vector3 c4 = new Vector3 (fc4.Red, fc4.Green, fc4.Blue) / 255f; - - exporter.AddFace ( - (v1 + offset / 16) * scale, - (v2 + offset / 16) * scale, - (v3 + offset / 16) * scale, - (v4 + offset / 16) * scale, - c1, c2, c3, c4, - material, - uv1, uv2, uv3, uv4 - ); - } + exporter.AddScenery (NSF, scenery, ref textureEIDs, ref objTranslate); } exporter.Export (path, modelname); @@ -534,88 +356,7 @@ private void ExportSceneryC1OBJ (string path, string modelname) // find all the scenery insde chunks foreach (OldSceneryEntry scenery in NSF.GetEntries ()) { - var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); - var scale = new Vector3 (1 / GameScales.WorldC1); - - for (int i = 0; i < scenery.TPAGCount; i++) - { - int tpag_eid = scenery.GetTPAG (i); - - if (textureEIDs.ContainsKey (tpag_eid)) - continue; - - textureEIDs.Add (tpag_eid, textureEIDs.Count); - } - - foreach (var polygon in scenery.Polygons) - { - string material = null; - Vector2? uv1 = null, uv2 = null, uv3 = null; - OldModelStruct str = scenery.Structs[polygon.ModelStruct]; - OldSceneryVertex ov1 = scenery.Vertices [polygon.VertexA]; - OldSceneryVertex ov2 = scenery.Vertices [polygon.VertexB]; - OldSceneryVertex ov3 = scenery.Vertices [polygon.VertexC]; - Vector3 v1 = new Vector3 (ov1.X, ov1.Y, ov1.Z); - Vector3 v2 = new Vector3 (ov2.X, ov2.Y, ov2.Z); - Vector3 v3 = new Vector3 (ov3.X, ov3.Y, ov3.Z); - Vector3 color = Vector3.Zero; - - if (str is OldSceneryTexture t) - { - color = new Vector3 (t.R, t.G, t.B) / 255F; - - // add the texture to the list too - material = objTranslate.FirstOrDefault (x => - x.Value.color == t.ColorMode && - x.Value.blend == t.BlendMode && - x.Value.clutx == t.ClutX && - x.Value.cluty == t.ClutY && - x.Value.page == textureEIDs [scenery.GetTPAG (polygon.Page)] - ).Key; - - if (material is null) - { - var texinfo = new TexInfoUnpacked( - true, color: t.ColorMode, blend: t.BlendMode, - clutx: t.ClutX, cluty: t.ClutY, - page: textureEIDs[scenery.GetTPAG (polygon.Page)] - ); - - var tpag = this.NSF.GetEntry (scenery.GetTPAG (polygon.Page)); - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = t.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv3 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); - uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); - uv1 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); - } - else if(str is OldSceneryColor c) - { - color = new Vector3 (c.R, c.G, c.B) / 255F; - } - - exporter.AddFace ( - (v1 + offset) * scale, - (v2 + offset) * scale, - (v3 + offset) * scale, - color, color, color, - material, - uv1, uv2, uv3 - ); - } + exporter.AddScenery (NSF, scenery, ref textureEIDs, ref objTranslate); } exporter.Export (path, modelname); diff --git a/CrashEdit/Exporters/FrameExtensions.cs b/CrashEdit/Exporters/FrameExtensions.cs new file mode 100644 index 00000000..6908d06f --- /dev/null +++ b/CrashEdit/Exporters/FrameExtensions.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using Crash; +using OpenTK; + +namespace CrashEdit.Exporters; + +public static class FrameExtensions +{ + public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) + { + var model = nsf.GetEntry(frame.ModelEID); + var offset = new Vector3 (frame.XOffset, frame.YOffset, frame.ZOffset); + var scale = new Vector3 (model.ScaleX, model.ScaleY, model.ScaleZ) / (GameScales.ModelC1 * GameScales.AnimC1); + + foreach (OldModelStruct str in model.Structs) + { + if (str is not OldModelTexture tex) + continue; + + if (textureEIDs.ContainsKey (tex.EID)) + continue; + + textureEIDs [tex.EID] = textureEIDs.Count; + } + + foreach (OldModelPolygon polygon in model.Polygons) + { + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null; + OldModelStruct str = model.Structs [polygon.Unknown & 0x7FFF]; + OldFrameVertex ov1 = frame.Vertices [polygon.VertexA / 6]; + OldFrameVertex ov2 = frame.Vertices [polygon.VertexB / 6]; + OldFrameVertex ov3 = frame.Vertices [polygon.VertexC / 6]; + Vector3 v1 = new Vector3 (ov1.X, ov1.Y, ov1.Z); + Vector3 v2 = new Vector3 (ov2.X, ov2.Y, ov2.Z); + Vector3 v3 = new Vector3 (ov3.X, ov3.Y, ov3.Z); + Vector3 color = Vector3.Zero; + + if (str is OldModelTexture t) + { + int page = textureEIDs [t.EID]; + color = new Vector3 (t.R, t.G, t.B) / 255F; + + // add the texture to the list too + material = objTranslate.FirstOrDefault (x => + x.Value.color == t.ColorMode && + x.Value.blend == t.BlendMode && + x.Value.clutx == t.ClutX && + x.Value.cluty == t.ClutY && + x.Value.page == page + ).Key; + + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: t.ColorMode, blend: t.BlendMode, + clutx: t.ClutX, cluty: t.ClutY, + face: Convert.ToInt32 (t.N), + page: textureEIDs [t.EID] + ); + + var tpag = nsf.GetEntry (t.EID); + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = t.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + + uv3 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); + uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); + uv1 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); + } + else if (str is OldSceneryColor c) + { + color = new Vector3 (c.R, c.G, c.B) / 255F; + } + + exporter.AddFace ( + (v1 + offset) * scale, + (v2 + offset) * scale, + (v3 + offset) * scale, + color, color, color, + material, + uv1, uv2, uv3 + ); + } + } + + public static void AddFrame (this OBJExporter exporter, NSF nsf, Frame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) + { + var model = nsf.GetEntry(frame.ModelEID); + var vertices = frame.MakeVertices (model); + var offset = new Vector3 (frame.XOffset, frame.YOffset, frame.ZOffset) / 4F; + var scale = new Vector3 (model.ScaleX, model.ScaleY, model.ScaleZ) / (GameScales.ModelC1 * GameScales.AnimC1); + + // detect how many textures are used and their eids to prepare the image + + for (int i = 0; i < model.TPAGCount; i++) + { + int tpag_eid = model.GetTPAG (i); + + if (textureEIDs.ContainsKey (tpag_eid)) + continue; + + textureEIDs.Add (tpag_eid, textureEIDs.Count); + } + + // once we have the textureEIDs we know what has to be loaded where + // these textures are 128 pixels of height + // and the game loads every texture used and keeps a lookup table of the texture "index" + + // depending on where in the model it's going to be drawn, the texture is treated differently + // (4 bits per pixel, 8 bits per pixel, 16 bits per pixel) + // that information is inside each triangle of the frame + // so the same texture can be treated differently + // try to build an atlas of sorts with all the information + + // iterate all the triangles, get the texture modes and build information about those + foreach (var tri in model.Triangles) + { + var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures); + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null; + bool nocull = tri.Subtype == 0 || tri.Subtype == 2; + bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; + + // parse the texture and add it to the exporter + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; + int textureEID = model.GetTPAG (value.Page); + int page = textureEIDs [textureEID]; + + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == page + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [textureEID] + ); + + var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = value.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + + uv2 = new Vector2 (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + + if ((tri.Type != 2 && !flip) || (tri.Type == 2 && tri.Subtype == 1)) + { + uv1 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + uv3 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + } + else + { + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + uv1 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + } + } + + // add the face + SceneryColor fc1 = model.Colors [tri.Color [!flip ? 0 : 2]]; + SceneryColor fc2 = model.Colors [tri.Color [!flip ? 1 : 1]]; + SceneryColor fc3 = model.Colors [tri.Color [!flip ? 2 : 0]]; + Position fv1 = vertices [tri.Vertex [!flip ? 0 : 2] + frame.SpecialVertexCount]; + Position fv2 = vertices [tri.Vertex [!flip ? 1 : 1] + frame.SpecialVertexCount]; + Position fv3 = vertices [tri.Vertex [!flip ? 2 : 0] + frame.SpecialVertexCount]; + Vector3 v1 = new Vector3 (fv1.X, fv1.Z, fv1.Y); + Vector3 v2 = new Vector3 (fv2.X, fv2.Z, fv2.Y); + Vector3 v3 = new Vector3 (fv3.X, fv3.Z, fv3.Y); + Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; + Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; + Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; + + exporter.AddFace ( + (v1 + offset) * scale, + (v2 + offset) * scale, + (v3 + offset) * scale, + c1, c2, c3, + material, + uv1, uv2, uv3 + ); + } + } +} \ No newline at end of file diff --git a/CrashEdit/Exporters/SceneryExtensions.cs b/CrashEdit/Exporters/SceneryExtensions.cs new file mode 100644 index 00000000..de83a8cd --- /dev/null +++ b/CrashEdit/Exporters/SceneryExtensions.cs @@ -0,0 +1,283 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using Crash; +using OpenTK; + +namespace CrashEdit.Exporters; + +public static class SceneryExtensions +{ + public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) + { + var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); + var scale = new Vector3 (1 / GameScales.WorldC1); + + for (int i = 0; i < scenery.TPAGCount; i++) + { + int tpag_eid = scenery.GetTPAG (i); + + if (textureEIDs.ContainsKey (tpag_eid)) + continue; + + textureEIDs.Add (tpag_eid, textureEIDs.Count); + } + + foreach (var polygon in scenery.Polygons) + { + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null; + OldModelStruct str = scenery.Structs[polygon.ModelStruct]; + OldSceneryVertex ov1 = scenery.Vertices [polygon.VertexA]; + OldSceneryVertex ov2 = scenery.Vertices [polygon.VertexB]; + OldSceneryVertex ov3 = scenery.Vertices [polygon.VertexC]; + Vector3 v1 = new Vector3 (ov1.X, ov1.Y, ov1.Z); + Vector3 v2 = new Vector3 (ov2.X, ov2.Y, ov2.Z); + Vector3 v3 = new Vector3 (ov3.X, ov3.Y, ov3.Z); + Vector3 color = Vector3.Zero; + + if (str is OldSceneryTexture t) + { + int page = textureEIDs [scenery.GetTPAG (polygon.Page)]; + color = new Vector3 (t.R, t.G, t.B) / 255F; + + // add the texture to the list too + material = objTranslate.FirstOrDefault (x => + x.Value.color == t.ColorMode && + x.Value.blend == t.BlendMode && + x.Value.clutx == t.ClutX && + x.Value.cluty == t.ClutY && + x.Value.page == page + ).Key; + + if (material is null) + { + var texinfo = new TexInfoUnpacked( + true, color: t.ColorMode, blend: t.BlendMode, + clutx: t.ClutX, cluty: t.ClutY, + page: textureEIDs[scenery.GetTPAG (polygon.Page)] + ); + + var tpag = nsf.GetEntry (scenery.GetTPAG (polygon.Page)); + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = t.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + + uv3 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); + uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); + uv1 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); + } + else if(str is OldSceneryColor c) + { + color = new Vector3 (c.R, c.G, c.B) / 255F; + } + + exporter.AddFace ( + (v1 + offset) * scale, + (v2 + offset) * scale, + (v3 + offset) * scale, + color, color, color, + material, + uv1, uv2, uv3 + ); + } + } + + public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) + { + var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); + var scale = new Vector3 (1 / GameScales.WorldC1); + + for (int i = 0; i < scenery.TPAGCount; i++) + { + int tpag_eid = scenery.GetTPAG (i); + + if (textureEIDs.ContainsKey (tpag_eid)) + continue; + + textureEIDs.Add (tpag_eid, textureEIDs.Count); + } + + foreach (var tri in scenery.Triangles) + { + if (tri.VertexA > scenery.Vertices.Count || + tri.VertexB > scenery.Vertices.Count || + tri.VertexC > scenery.Vertices.Count) + continue; + + var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, scenery.Textures, scenery.AnimatedTextures); + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null; + + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; + int textureEID = scenery.GetTPAG (value.Page); + int page = textureEIDs [textureEID]; + + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == page + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) + { + + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [textureEID] + ); + + var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = value.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + + uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + uv2 = new Vector2 (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + } + + // add the face + SceneryVertex fv1 = scenery.Vertices [tri.VertexA]; + SceneryVertex fv2 = scenery.Vertices [tri.VertexB]; + SceneryVertex fv3 = scenery.Vertices [tri.VertexC]; + SceneryColor fc1 = scenery.Colors [fv1.Color]; + SceneryColor fc2 = scenery.Colors [fv2.Color]; + SceneryColor fc3 = scenery.Colors [fv3.Color]; + Vector3 v1 = new Vector3 (fv1.X, fv1.Y, fv1.Z); + Vector3 v2 = new Vector3 (fv2.X, fv2.Y, fv2.Z); + Vector3 v3 = new Vector3 (fv3.X, fv3.Y, fv3.Z); + Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; + Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; + Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; + + exporter.AddFace ( + (v1 + offset / 16) * scale, + (v2 + offset / 16) * scale, + (v3 + offset / 16) * scale, + c1, c2, c3, + material, + uv1, uv2, uv3 + ); + } + + foreach (var quad in scenery.Quads) + { + // ignore quads that are out of limits + if (quad.VertexA > scenery.Vertices.Count || + quad.VertexB > scenery.Vertices.Count || + quad.VertexC > scenery.Vertices.Count || + quad.VertexD > scenery.Vertices.Count) + continue; + + var info = TextureUtils.ProcessTextureInfoC2 (0, quad.Texture, quad.Animated, scenery.Textures, scenery.AnimatedTextures); + string material = null; + Vector2? uv1 = null, uv2 = null, uv3 = null, uv4 = null; + + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; + int textureEID = scenery.GetTPAG (value.Page); + int page = textureEIDs [textureEID]; + + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == page + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [textureEID] + ); + + var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = value.ColorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + + uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + uv2 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + uv4 = new (value.X4 / texsize.X, (128 - value.Y4) / texsize.Y); + } + + // add the face + SceneryVertex fv1 = scenery.Vertices [quad.VertexA]; + SceneryVertex fv2 = scenery.Vertices [quad.VertexB]; + SceneryVertex fv3 = scenery.Vertices [quad.VertexC]; + SceneryVertex fv4 = scenery.Vertices [quad.VertexD]; + SceneryColor fc1 = scenery.Colors [fv1.Color]; + SceneryColor fc2 = scenery.Colors [fv2.Color]; + SceneryColor fc3 = scenery.Colors [fv3.Color]; + SceneryColor fc4 = scenery.Colors [fv4.Color]; + Vector3 v1 = new Vector3 (fv1.X, fv1.Y, fv1.Z); + Vector3 v2 = new Vector3 (fv2.X, fv2.Y, fv2.Z); + Vector3 v3 = new Vector3 (fv3.X, fv3.Y, fv3.Z); + Vector3 v4 = new Vector3 (fv4.X, fv4.Y, fv4.Z); + Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; + Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; + Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; + Vector3 c4 = new Vector3 (fc4.Red, fc4.Green, fc4.Blue) / 255f; + + exporter.AddFace ( + (v1 + offset / 16) * scale, + (v2 + offset / 16) * scale, + (v3 + offset / 16) * scale, + (v4 + offset / 16) * scale, + c1, c2, c3, c4, + material, + uv1, uv2, uv3, uv4 + ); + } + } +} \ No newline at end of file From 5a84b38718e5b2672c2f154ad601e06b1aed6260 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Tue, 11 Apr 2023 01:07:48 +0200 Subject: [PATCH 11/17] More cleanup and simplification of exporters Signed-off-by: Alexis Maiquez Murcia --- CrashEdit/Exporters/FrameExtensions.cs | 111 +-------- CrashEdit/Exporters/MaterialExtensions.cs | 270 ++++++++++++++++++++++ CrashEdit/Exporters/SceneryExtensions.cs | 143 +----------- 3 files changed, 288 insertions(+), 236 deletions(-) create mode 100644 CrashEdit/Exporters/MaterialExtensions.cs diff --git a/CrashEdit/Exporters/FrameExtensions.cs b/CrashEdit/Exporters/FrameExtensions.cs index 6908d06f..45c01996 100644 --- a/CrashEdit/Exporters/FrameExtensions.cs +++ b/CrashEdit/Exporters/FrameExtensions.cs @@ -7,6 +7,10 @@ namespace CrashEdit.Exporters; +// TODO: ALL FRAMES CLASSES/STRUCTS SHOULD HAVE A BASE INTERFACE WITH DATA IN COMMON +// TODO: THAT WOULD MAKE WORKING WITH THEM EASIER FOR THINGS LIKE THESE WHERE YOU ONLY NEED THE DATA +// TODO: THEY HAVE IN COMMON, BUT CHANGING THAT IS OUT OF THE SCOPE OF THESE COMMITS +// TODO: BUT THAT WOULD CUT DOWN THE METHODS HERE TO JUST ONE OR TWO public static class FrameExtensions { public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) @@ -41,47 +45,7 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, if (str is OldModelTexture t) { - int page = textureEIDs [t.EID]; - color = new Vector3 (t.R, t.G, t.B) / 255F; - - // add the texture to the list too - material = objTranslate.FirstOrDefault (x => - x.Value.color == t.ColorMode && - x.Value.blend == t.BlendMode && - x.Value.clutx == t.ClutX && - x.Value.cluty == t.ClutY && - x.Value.page == page - ).Key; - - if (material is null) - { - var texinfo = new TexInfoUnpacked ( - true, color: t.ColorMode, blend: t.BlendMode, - clutx: t.ClutX, cluty: t.ClutY, - face: Convert.ToInt32 (t.N), - page: textureEIDs [t.EID] - ); - - var tpag = nsf.GetEntry (t.EID); - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = t.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv3 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); - uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); - uv1 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); + material = exporter.AddTexture (nsf, t, ref textureEIDs, ref objTranslate, out color, out uv1, out uv2, out uv3); } else if (str is OldSceneryColor c) { @@ -131,66 +95,11 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, Frame frame, re // iterate all the triangles, get the texture modes and build information about those foreach (var tri in model.Triangles) { - var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures); - string material = null; - Vector2? uv1 = null, uv2 = null, uv3 = null; - bool nocull = tri.Subtype == 0 || tri.Subtype == 2; - bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; - - // parse the texture and add it to the exporter - if (info.Item1 && info.Item2 is not null) - { - var value = info.Item2.Value; - int textureEID = model.GetTPAG (value.Page); - int page = textureEIDs [textureEID]; - - material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == page - ).Key; - - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] - ); - - var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); - - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = value.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv2 = new Vector2 (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); - - if ((tri.Type != 2 && !flip) || (tri.Type == 2 && tri.Subtype == 1)) - { - uv1 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); - uv3 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); - } - else - { - uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); - uv1 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); - } - } + string material = exporter.AddTexture ( + nsf, tri, model, ref textureEIDs, ref objTranslate, + out var uv1, out var uv2, out var uv3, + out bool flip + ); // add the face SceneryColor fc1 = model.Colors [tri.Color [!flip ? 0 : 2]]; diff --git a/CrashEdit/Exporters/MaterialExtensions.cs b/CrashEdit/Exporters/MaterialExtensions.cs new file mode 100644 index 00000000..475aea1a --- /dev/null +++ b/CrashEdit/Exporters/MaterialExtensions.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using Crash; +using OpenTK; + +namespace CrashEdit.Exporters; + +// TODO: ALL VERTEX/QUAD CLASSES/STRUCTS SHOULD HAVE A BASE INTERFACE WITH DATA IN COMMON +// TODO: THAT WOULD MAKE WORKING WITH THEM EASIER FOR THINGS LIKE THESE WHERE YOU ONLY NEED THE DATA +// TODO: THEY HAVE IN COMMON, BUT CHANGING THAT IS OUT OF THE SCOPE OF THESE COMMITS +// TODO: BUT THAT WOULD CUT DOWN THE METHODS HERE TO JUST ONE OR TWO +public static class MaterialExtensions +{ + private static Vector2 TexSize (int colorMode) + { + return colorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + } + + public static string AddTexture (this OBJExporter exporter, NSF nsf, ModelTransformedTriangle tri, ModelEntry model, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3, out bool flip) + { + var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures); + string material = null; + uv1 = uv2 = uv3 = null; + bool nocull = tri.Subtype == 0 || tri.Subtype == 2; + flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; + + // parse the texture and add it to the exporter + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; + int textureEID = model.GetTPAG (value.Page); + int page = textureEIDs [textureEID]; + + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == page + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [textureEID] + ); + + var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = TexSize (value.ColorMode); + + uv2 = new Vector2 (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + + if ((tri.Type != 2 && !flip) || (tri.Type == 2 && tri.Subtype == 1)) + { + uv1 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + uv3 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + } + else + { + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + uv1 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + } + } + + return material; + } + + public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryTriangle tri, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) + { + var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, scenery.Textures, scenery.AnimatedTextures); + string material = null; + uv1 = uv2 = uv3 = null; + + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; + int textureEID = scenery.GetTPAG (value.Page); + int page = textureEIDs [textureEID]; + + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == page + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [textureEID] + ); + + var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = TexSize (value.ColorMode); + + uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + uv2 = new Vector2 (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + } + + return material; + } + + public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryQuad quad, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3, out Vector2? uv4) + { + var info = TextureUtils.ProcessTextureInfoC2 (0, quad.Texture, quad.Animated, scenery.Textures, scenery.AnimatedTextures); + string material = null; + uv1 = uv2 = uv3 = uv4 = null; + + if (info.Item1 && info.Item2 is not null) + { + var value = info.Item2.Value; + int textureEID = scenery.GetTPAG (value.Page); + int page = textureEIDs [textureEID]; + + material = objTranslate.FirstOrDefault (x => + x.Value.color == value.ColorMode && + x.Value.blend == value.BlendMode && + x.Value.clutx == value.ClutX && + x.Value.cluty == value.ClutY && + x.Value.page == page + ).Key; + + // ignore the texinfo if there's already a texture with the exact same settings stored + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: textureEIDs [textureEID] + ); + + var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = TexSize (value.ColorMode); + + uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); + uv2 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); + uv4 = new (value.X4 / texsize.X, (128 - value.Y4) / texsize.Y); + } + + return material; + } + + public static string AddTexture (this OBJExporter exporter, NSF nsf, OldSceneryTexture t, int textureEID, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector3 color, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) + { + string material = null; + int page = textureEIDs [textureEID]; + color = new Vector3 (t.R, t.G, t.B) / 255F; + + // add the texture to the list too + material = objTranslate.FirstOrDefault (x => + x.Value.color == t.ColorMode && + x.Value.blend == t.BlendMode && + x.Value.clutx == t.ClutX && + x.Value.cluty == t.ClutY && + x.Value.page == page + ).Key; + + if (material is null) + { + var texinfo = new TexInfoUnpacked( + true, color: t.ColorMode, blend: t.BlendMode, + clutx: t.ClutX, cluty: t.ClutY, + page: textureEIDs[textureEID] + ); + + var tpag = nsf.GetEntry (textureEID); + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = TexSize (t.ColorMode); + + uv3 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); + uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); + uv1 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); + + return material; + } + + public static string AddTexture (this OBJExporter exporter, NSF nsf, OldModelTexture t, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector3 color, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) + { + string material = null; + int page = textureEIDs [t.EID]; + color = new Vector3 (t.R, t.G, t.B) / 255F; + + // add the texture to the list too + material = objTranslate.FirstOrDefault (x => + x.Value.color == t.ColorMode && + x.Value.blend == t.BlendMode && + x.Value.clutx == t.ClutX && + x.Value.cluty == t.ClutY && + x.Value.page == page + ).Key; + + if (material is null) + { + var texinfo = new TexInfoUnpacked ( + true, color: t.ColorMode, blend: t.BlendMode, + clutx: t.ClutX, cluty: t.ClutY, + face: Convert.ToInt32 (t.N), + page: textureEIDs [t.EID] + ); + + var tpag = nsf.GetEntry (t.EID); + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); + + // the material name changes + material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + + // add it to the lookup table too + objTranslate [material] = texinfo; + } + + Vector2 texsize = TexSize (t.ColorMode); + + uv3 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); + uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); + uv1 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); + + return material; + } +} \ No newline at end of file diff --git a/CrashEdit/Exporters/SceneryExtensions.cs b/CrashEdit/Exporters/SceneryExtensions.cs index de83a8cd..23a50f8a 100644 --- a/CrashEdit/Exporters/SceneryExtensions.cs +++ b/CrashEdit/Exporters/SceneryExtensions.cs @@ -7,6 +7,10 @@ namespace CrashEdit.Exporters; +// TODO: ALL SCENERY CLASSES/STRUCTS SHOULD HAVE A BASE INTERFACE WITH DATA IN COMMON +// TODO: THAT WOULD MAKE WORKING WITH THEM EASIER FOR THINGS LIKE THESE WHERE YOU ONLY NEED THE DATA +// TODO: THEY HAVE IN COMMON, BUT CHANGING THAT IS OUT OF THE SCOPE OF THESE COMMITS +// TODO: BUT THAT WOULD CUT DOWN THE METHODS HERE TO JUST ONE OR TWO public static class SceneryExtensions { public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) @@ -39,46 +43,8 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEnt if (str is OldSceneryTexture t) { - int page = textureEIDs [scenery.GetTPAG (polygon.Page)]; - color = new Vector3 (t.R, t.G, t.B) / 255F; - - // add the texture to the list too - material = objTranslate.FirstOrDefault (x => - x.Value.color == t.ColorMode && - x.Value.blend == t.BlendMode && - x.Value.clutx == t.ClutX && - x.Value.cluty == t.ClutY && - x.Value.page == page - ).Key; - - if (material is null) - { - var texinfo = new TexInfoUnpacked( - true, color: t.ColorMode, blend: t.BlendMode, - clutx: t.ClutX, cluty: t.ClutY, - page: textureEIDs[scenery.GetTPAG (polygon.Page)] - ); - - var tpag = nsf.GetEntry (scenery.GetTPAG (polygon.Page)); - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = t.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv3 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); - uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); - uv1 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); + int textureEID = scenery.GetTPAG (polygon.Page); + material = exporter.AddTexture (nsf, t, textureEID, ref textureEIDs, ref objTranslate, out color, out uv1, out uv2, out uv3); } else if(str is OldSceneryColor c) { @@ -118,55 +84,9 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry tri.VertexC > scenery.Vertices.Count) continue; - var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, scenery.Textures, scenery.AnimatedTextures); - string material = null; Vector2? uv1 = null, uv2 = null, uv3 = null; - if (info.Item1 && info.Item2 is not null) - { - var value = info.Item2.Value; - int textureEID = scenery.GetTPAG (value.Page); - int page = textureEIDs [textureEID]; - - material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == page - ).Key; - - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] - ); - - var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); - - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = value.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); - uv2 = new Vector2 (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); - uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); - } + string material = exporter.AddTexture (nsf, tri, scenery, ref textureEIDs, ref objTranslate, out uv1, out uv2, out uv3); // add the face SceneryVertex fv1 = scenery.Vertices [tri.VertexA]; @@ -201,55 +121,8 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry quad.VertexD > scenery.Vertices.Count) continue; - var info = TextureUtils.ProcessTextureInfoC2 (0, quad.Texture, quad.Animated, scenery.Textures, scenery.AnimatedTextures); - string material = null; Vector2? uv1 = null, uv2 = null, uv3 = null, uv4 = null; - - if (info.Item1 && info.Item2 is not null) - { - var value = info.Item2.Value; - int textureEID = scenery.GetTPAG (value.Page); - int page = textureEIDs [textureEID]; - - material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == page - ).Key; - - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] - ); - - var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); - - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = value.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); - uv2 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); - uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); - uv4 = new (value.X4 / texsize.X, (128 - value.Y4) / texsize.Y); - } + string material = exporter.AddTexture (nsf, quad, scenery, ref textureEIDs, ref objTranslate, out uv1, out uv2, out uv3, out uv4); // add the face SceneryVertex fv1 = scenery.Vertices [quad.VertexA]; From 10d567382c7d06a4ff7e0717e0905632075cd271 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Wed, 12 Apr 2023 21:59:28 +0200 Subject: [PATCH 12/17] Fix alpha channels on textures Signed-off-by: Alexis Maiquez Murcia --- CrashEdit/Exporters/TextureExporter.cs | 60 ++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/CrashEdit/Exporters/TextureExporter.cs b/CrashEdit/Exporters/TextureExporter.cs index 27d6c3a6..51b73a22 100644 --- a/CrashEdit/Exporters/TextureExporter.cs +++ b/CrashEdit/Exporters/TextureExporter.cs @@ -30,12 +30,34 @@ public static Bitmap Create4bpp (byte [] data, TexInfoUnpacked info) (data [info.cluty * 512 + ((clutx + entry) * 2 + 1)] << 8) ); + int red = (int) (((color & 0x1F) / 31F) * 255); + int green = (int) ((((color >> 5) & 0x1F) / 31F) * 255); + int blue = (int) ((((color >> 10) & 0x1F) / 31F) * 255); + int a = ((color >> 15) & 0x1); + int alpha = 255; + + if (info.blend == 0 && (info.color == 0 || info.color == 1)) + { + if (a == 1) + alpha = 127; + else + alpha = 255; + + if (red == 0 && green == 0 && blue == 0 && a == 0) + alpha = 0; + } + else if (info.blend == 1 && (info.color == 0 || info.color == 1)) + { + if (red == 0 && green == 0 && blue == 0 && a == 0) + alpha = 0; + } + // colors are in rgb15 format Color c = Color.FromArgb ( - ((color >> 15) & 0x1) * 255, - (int) (((color & 0x1F) / 31F) * 255), - (int) ((((color >> 5) & 0x1F) / 31F) * 255), - (int) ((((color >> 10) & 0x1F) / 31F) * 255) + alpha, + red, + green, + blue ); bmp.SetPixel (x, y, c); @@ -66,12 +88,34 @@ public static Bitmap Create8bpp (byte [] data, TexInfoUnpacked info) (data [info.cluty * 512 + ((clutx + entry) * 2 + 1)] << 8) ); + int red = (int) (((color & 0x1F) / 31F) * 255); + int green = (int) ((((color >> 5) & 0x1F) / 31F) * 255); + int blue = (int) ((((color >> 10) & 0x1F) / 31F) * 255); + int a = ((color >> 15) & 0x1); + int alpha = 255; + + if (info.blend == 0 && (info.color == 0 || info.color == 1)) + { + if (a == 1) + alpha = 127; + else + alpha = 255; + + if (red == 0 && green == 0 && blue == 0 && a == 0) + alpha = 0; + } + else if (info.blend == 1 && (info.color == 0 || info.color == 1)) + { + if (red == 0 && green == 0 && blue == 0 && a == 0) + alpha = 0; + } + // colors are in rgb15 format Color c = Color.FromArgb ( - ((color >> 15) & 0x1) * 255, - (int) (((color & 0x1F) / 31F) * 255), - (int) ((((color >> 5) & 0x1F) / 31F) * 255), - (int) ((((color >> 10) & 0x1F) / 31F) * 255) + alpha, + red, + green, + blue ); bmp.SetPixel (x, y, c); From bcb612f07f5b01354d7f7668595923e3653bf545 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Thu, 13 Apr 2023 03:00:14 +0200 Subject: [PATCH 13/17] Include color mode and blending mode in the export This enabled 3D software with scripting capabilities (like blender) to display textures properly Signed-off-by: Alexis Maiquez Murcia --- CrashEdit/Exporters/MaterialExtensions.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CrashEdit/Exporters/MaterialExtensions.cs b/CrashEdit/Exporters/MaterialExtensions.cs index 475aea1a..9f577d16 100644 --- a/CrashEdit/Exporters/MaterialExtensions.cs +++ b/CrashEdit/Exporters/MaterialExtensions.cs @@ -59,7 +59,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, ModelTransf Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + material = exporter.AddTexture ($"{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -117,7 +117,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryTria Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + material = exporter.AddTexture ($"{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -166,7 +166,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryQuad Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + material = exporter.AddTexture ($"{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -210,7 +210,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, OldSceneryT Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + material = exporter.AddTexture ($"{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -253,7 +253,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, OldModelTex Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); + material = exporter.AddTexture ($"{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; From 58dfa6d05431475a49e3056b5ff35f96694411b2 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Sat, 15 Apr 2023 04:35:23 +0200 Subject: [PATCH 14/17] Properly apply scales on export Frame export should keep filename length the same regardless of the amount of frames Added eid to texture export to prevent textures being overriden when they were indeed different Signed-off-by: Alexis Maiquez Murcia --- .../Animation/AnimationEntryController.cs | 3 ++- .../Animation/ColoredAnimationEntryController.cs | 3 ++- .../Animation/OldAnimationEntryController.cs | 3 ++- CrashEdit/Exporters/FrameExtensions.cs | 4 +++- CrashEdit/Exporters/MaterialExtensions.cs | 10 +++++----- CrashEdit/Exporters/SceneryExtensions.cs | 16 ++++++++-------- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/CrashEdit/Controllers/Animation/AnimationEntryController.cs b/CrashEdit/Controllers/Animation/AnimationEntryController.cs index 5ddda16f..c4840477 100644 --- a/CrashEdit/Controllers/Animation/AnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/AnimationEntryController.cs @@ -31,13 +31,14 @@ private void Menu_Export_OBJ () string path = Path.GetDirectoryName (output); int id = 0; + int count = Node.Nodes.Count.ToString().Length; foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) { if (node.Legacy is not FrameController frame) continue; - frame.ToOBJ (path, filename + id.ToString()); + frame.ToOBJ (path, filename + id.ToString().PadLeft (count, '0')); id++; } } diff --git a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs index 9610fb1c..3cd73f85 100644 --- a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs @@ -32,13 +32,14 @@ private void Menu_Export_OBJ() string path = Path.GetDirectoryName (output); int id = 0; + int count = Node.Nodes.Count.ToString().Length; foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) { if (node.Legacy is not FrameController frame) continue; - frame.ToOBJ (path, filename + id.ToString()); + frame.ToOBJ (path, filename + id.ToString().PadLeft (count, '0')); id++; } } diff --git a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs index 7fd646f3..6e210ef1 100755 --- a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs @@ -31,13 +31,14 @@ private void Menu_Export_OBJ () string path = Path.GetDirectoryName (output); int id = 0; + int count = Node.Nodes.Count.ToString().Length; foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) { if (node.Legacy is not OldFrameController frame) continue; - frame.ToOBJ (path, filename + id.ToString()); + frame.ToOBJ (path, filename + id.ToString().PadLeft (count, '0')); id++; } } diff --git a/CrashEdit/Exporters/FrameExtensions.cs b/CrashEdit/Exporters/FrameExtensions.cs index 45c01996..e379b22e 100644 --- a/CrashEdit/Exporters/FrameExtensions.cs +++ b/CrashEdit/Exporters/FrameExtensions.cs @@ -65,10 +65,12 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, public static void AddFrame (this OBJExporter exporter, NSF nsf, Frame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) { + // TODO: SUPPORT CRASH2 AND CRASH3 PROPER SCALING + // offset correction is 4f in Crash2, 32f in Crash3 var model = nsf.GetEntry(frame.ModelEID); var vertices = frame.MakeVertices (model); var offset = new Vector3 (frame.XOffset, frame.YOffset, frame.ZOffset) / 4F; - var scale = new Vector3 (model.ScaleX, model.ScaleY, model.ScaleZ) / (GameScales.ModelC1 * GameScales.AnimC1); + var scale = new Vector3 (model.ScaleX, model.ScaleY, model.ScaleZ) / GameScales.ModelC1 / GameScales.AnimC1; // detect how many textures are used and their eids to prepare the image diff --git a/CrashEdit/Exporters/MaterialExtensions.cs b/CrashEdit/Exporters/MaterialExtensions.cs index 9f577d16..d8d76283 100644 --- a/CrashEdit/Exporters/MaterialExtensions.cs +++ b/CrashEdit/Exporters/MaterialExtensions.cs @@ -59,7 +59,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, ModelTransf Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture ($"{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); + material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -117,7 +117,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryTria Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture ($"{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); + material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -166,7 +166,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryQuad Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture ($"{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); + material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -210,7 +210,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, OldSceneryT Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture ($"{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); + material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -253,7 +253,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, OldModelTex Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture ($"{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); + material = exporter.AddTexture ($"{t.EID}-{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; diff --git a/CrashEdit/Exporters/SceneryExtensions.cs b/CrashEdit/Exporters/SceneryExtensions.cs index 23a50f8a..d8b486ac 100644 --- a/CrashEdit/Exporters/SceneryExtensions.cs +++ b/CrashEdit/Exporters/SceneryExtensions.cs @@ -65,7 +65,7 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEnt public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) { var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); - var scale = new Vector3 (1 / GameScales.WorldC1); + //var scale = new Vector3 (1 / GameScales.WorldC1); for (int i = 0; i < scenery.TPAGCount; i++) { @@ -103,9 +103,9 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; exporter.AddFace ( - (v1 + offset / 16) * scale, - (v2 + offset / 16) * scale, - (v3 + offset / 16) * scale, + (v1 * 16 + offset) / GameScales.WorldC1, + (v2 * 16 + offset) / GameScales.WorldC1, + (v3 * 16 + offset) / GameScales.WorldC1, c1, c2, c3, material, uv1, uv2, uv3 @@ -143,10 +143,10 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry Vector3 c4 = new Vector3 (fc4.Red, fc4.Green, fc4.Blue) / 255f; exporter.AddFace ( - (v1 + offset / 16) * scale, - (v2 + offset / 16) * scale, - (v3 + offset / 16) * scale, - (v4 + offset / 16) * scale, + (v1 * 16 + offset) / GameScales.WorldC1, + (v2 * 16 + offset) / GameScales.WorldC1, + (v3 * 16 + offset) / GameScales.WorldC1, + (v4 * 16 + offset) / GameScales.WorldC1, c1, c2, c3, c4, material, uv1, uv2, uv3, uv4 From 2f10783d7d80e1dfea4792b58cacb9fadd02d12d Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Sun, 9 Feb 2025 20:13:52 +0100 Subject: [PATCH 15/17] Fix all compilation issues of cherry picking changes from d/new-load --- .../Animation/AnimationEntryController.cs | 2 +- .../ColoredAnimationEntryController.cs | 2 +- .../Controllers/Animation/FrameController.cs | 4 +- .../Animation/OldAnimationEntryController.cs | 2 +- .../Animation/OldFrameController.cs | 4 +- CrashEdit/Controllers/NSFController.cs | 5 +- .../Scenery/OldSceneryEntryController.cs | 20 +- .../Scenery/SceneryEntryController.cs | 175 +----------------- CrashEdit/Controls/3D/SceneryEntryViewer.cs | 4 +- CrashEdit/Exporters/FrameExtensions.cs | 17 +- CrashEdit/Exporters/MaterialExtensions.cs | 133 ++++++------- CrashEdit/Exporters/OBJExporter.cs | 8 +- CrashEdit/Exporters/SceneryExtensions.cs | 15 +- CrashEdit/Exporters/TextureExporter.cs | 62 ++++--- .../OpenTK Integration/AnimationRenderer.cs | 2 +- CrashEdit/Utils/TextureUtils.cs | 31 ++-- 16 files changed, 155 insertions(+), 331 deletions(-) diff --git a/CrashEdit/Controllers/Animation/AnimationEntryController.cs b/CrashEdit/Controllers/Animation/AnimationEntryController.cs index c4840477..af6e1085 100644 --- a/CrashEdit/Controllers/Animation/AnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/AnimationEntryController.cs @@ -31,7 +31,7 @@ private void Menu_Export_OBJ () string path = Path.GetDirectoryName (output); int id = 0; - int count = Node.Nodes.Count.ToString().Length; + int count = Modern.SubcontrollerGroups.SelectMany(x => x.Members).Count().ToString().Length; foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) { diff --git a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs index 3cd73f85..5a533eea 100644 --- a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs @@ -32,7 +32,7 @@ private void Menu_Export_OBJ() string path = Path.GetDirectoryName (output); int id = 0; - int count = Node.Nodes.Count.ToString().Length; + int count = Modern.SubcontrollerGroups.SelectMany(x => x.Members).Count().ToString().Length; foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) { diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index 829d7c42..b15a2c8f 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -45,11 +45,11 @@ private void Menu_Export_OBJ () public void ToOBJ (string path, string modelname) { Dictionary textureEIDs = new (); - Dictionary objTranslate = new Dictionary (); + Dictionary objTranslate = new Dictionary (); var exporter = new OBJExporter (); - exporter.AddFrame (AnimationEntryController.NSF, Frame, ref textureEIDs, ref objTranslate); + exporter.AddFrame (this.GetNSF (), Frame, ref textureEIDs, ref objTranslate); exporter.Export (path, modelname); } } diff --git a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs index 6e210ef1..2fbfe5d3 100755 --- a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs @@ -31,7 +31,7 @@ private void Menu_Export_OBJ () string path = Path.GetDirectoryName (output); int id = 0; - int count = Node.Nodes.Count.ToString().Length; + int count = Modern.SubcontrollerGroups.SelectMany(x => x.Members).Count().ToString().Length; foreach (Controller node in Modern.SubcontrollerGroups.SelectMany (x => x.Members)) { diff --git a/CrashEdit/Controllers/Animation/OldFrameController.cs b/CrashEdit/Controllers/Animation/OldFrameController.cs index 3b020350..56ce4aa3 100755 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -64,11 +64,11 @@ private void Menu_Export_OBJ() public void ToOBJ(string path, string modelname) { Dictionary textureEIDs = new Dictionary (); - Dictionary objTranslate = new Dictionary (); + Dictionary objTranslate = new Dictionary (); var exporter = new OBJExporter (); - exporter.AddFrame (OldAnimationEntryController.NSF, OldFrame, ref textureEIDs, ref objTranslate); + exporter.AddFrame (this.GetNSF (), OldFrame, ref textureEIDs, ref objTranslate); exporter.Export (path, modelname); } } diff --git a/CrashEdit/Controllers/NSFController.cs b/CrashEdit/Controllers/NSFController.cs index d6117b3d..aad48a13 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -1,4 +1,5 @@ using CrashEdit.Crash; +using CrashEdit.Exporters; namespace CrashEdit.CE { @@ -334,7 +335,7 @@ private void ExportSceneryC2OBJ (string path, string modelname) // detect how many textures are used and their eids to prepare the image Dictionary textureEIDs = new (); - Dictionary objTranslate = new Dictionary (); + Dictionary objTranslate = new Dictionary (); // find all the scenery insde chunks foreach (SceneryEntry scenery in NSF.GetEntries ()) @@ -351,7 +352,7 @@ private void ExportSceneryC1OBJ (string path, string modelname) // detect how many textures are used and their eids to prepare the image Dictionary textureEIDs = new (); - Dictionary objTranslate = new Dictionary (); + Dictionary objTranslate = new Dictionary (); // find all the scenery insde chunks foreach (OldSceneryEntry scenery in NSF.GetEntries ()) diff --git a/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs b/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs index bb284a6b..9b1ac73c 100755 --- a/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs @@ -1,4 +1,5 @@ using CrashEdit.Crash; +using CrashEdit.Exporters; namespace CrashEdit.CE { @@ -10,8 +11,6 @@ public OldSceneryEntryController(OldSceneryEntry oldsceneryentry, SubcontrollerG OldSceneryEntry = oldsceneryentry; AddMenuSeparator(); AddMenu("Export as OBJ", Menu_Export_OBJ); - InvalidateNode(); - InvalidateNodeImage(); } public override bool EditorAvailable => true; @@ -25,11 +24,20 @@ public override Control CreateEditor() private void Menu_Export_OBJ() { - if (MessageBox.Show("Exporting to OBJ is experimental.\nTexture and color information will not be exported.\n\nContinue anyway?", "Export as OBJ", MessageBoxButtons.YesNo) != DialogResult.Yes) - { + if (!FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any)) return; - } - FileUtil.SaveFile(OldSceneryEntry.ToOBJ(), FileFilters.OBJ, FileFilters.Any); + + ToOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); + } + + private void ToOBJ (string path, string modelname) + { + var exporter = new OBJExporter (); + Dictionary textureEIDs = new (); + Dictionary objTranslate = new Dictionary (); + + exporter.AddScenery (this.GetNSF (), OldSceneryEntry, ref textureEIDs, ref objTranslate); + exporter.Export (path, modelname); } } } diff --git a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs index 7439bf65..c21b89a5 100755 --- a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs @@ -1,4 +1,6 @@ using CrashEdit.Crash; +using CrashEdit.Exporters; +using OpenTK.Mathematics; namespace CrashEdit.CE { @@ -34,179 +36,10 @@ private void Menu_Export_OBJ () private void ToOBJ (string path, string modelname) { var exporter = new OBJExporter (); - var offset = new Vector3 (SceneryEntry.XOffset, SceneryEntry.YOffset, SceneryEntry.ZOffset); - var scale = new Vector3 (1 / GameScales.WorldC1); - - // detect how many textures are used and their eids to prepare the image Dictionary textureEIDs = new (); - - for (int i = 0; i < SceneryEntry.TPAGCount; i++) - { - int tpag_eid = SceneryEntry.GetTPAG (i); - - if (textureEIDs.ContainsKey (tpag_eid)) - continue; - - textureEIDs.Add (tpag_eid, textureEIDs.Count); - } - - Dictionary objTranslate = new Dictionary (); - - foreach (var tri in SceneryEntry.Triangles) - { - var info = TextureUtils.ProcessTextureInfoC2(0, tri.Texture, tri.Animated, SceneryEntry.Textures, SceneryEntry.AnimatedTextures); - string material = null; - Vector2? uv1 = null, uv2 = null, uv3 = null; - - if (info.Item1 && info.Item2 is not null) - { - var value = info.Item2.Value; - - material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == textureEIDs [SceneryEntry.GetTPAG (value.Page)] - ).Key; - - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - int textureEID = SceneryEntry.GetTPAG (value.Page); - - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] - ); - - var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); - - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = value.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); - uv2 = new Vector2 (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); - uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); - } - - // add the face - SceneryVertex fv1 = SceneryEntry.Vertices[tri.VertexA]; - SceneryVertex fv2 = SceneryEntry.Vertices[tri.VertexB]; - SceneryVertex fv3 = SceneryEntry.Vertices[tri.VertexC]; - SceneryColor fc1 = SceneryEntry.Colors [fv1.Color]; - SceneryColor fc2 = SceneryEntry.Colors [fv2.Color]; - SceneryColor fc3 = SceneryEntry.Colors [fv3.Color]; - Vector3 v1 = new Vector3 (fv1.X, fv1.Y, fv1.Z); - Vector3 v2 = new Vector3 (fv2.X, fv2.Y, fv2.Z); - Vector3 v3 = new Vector3 (fv3.X, fv3.Y, fv3.Z); - Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; - Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; - Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; - - exporter.AddFace ( - (v1 + offset / 16) * scale, - (v2 + offset / 16) * scale, - (v3 + offset / 16) * scale, - c1, c2, c3, - material, - uv1, uv2, uv3 - ); - } - - foreach (var quad in SceneryEntry.Quads) - { - var info = TextureUtils.ProcessTextureInfoC2(0, quad.Texture, quad.Animated, SceneryEntry.Textures, SceneryEntry.AnimatedTextures); - string material = null; - Vector2? uv1 = null, uv2 = null, uv3 = null, uv4 = null; - - if (info.Item1 && info.Item2 is not null) - { - var value = info.Item2.Value; - int textureEID = SceneryEntry.GetTPAG (value.Page); - - material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == textureEIDs [textureEID] - ).Key; - - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] - ); - - var tpag = this.NSF.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); - - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture (((int) texinfo).ToString ("X8"), texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = value.ColorMode switch - { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; - - uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); - uv2 = new Vector2 (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); - uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); - uv4 = new (value.X4 / texsize.X, (128 - value.Y4) / texsize.Y); - } - - // add the face - SceneryVertex fv1 = SceneryEntry.Vertices[quad.VertexA]; - SceneryVertex fv2 = SceneryEntry.Vertices[quad.VertexB]; - SceneryVertex fv3 = SceneryEntry.Vertices[quad.VertexC]; - SceneryVertex fv4 = SceneryEntry.Vertices[quad.VertexD]; - SceneryColor fc1 = SceneryEntry.Colors [fv1.Color]; - SceneryColor fc2 = SceneryEntry.Colors [fv2.Color]; - SceneryColor fc3 = SceneryEntry.Colors [fv3.Color]; - SceneryColor fc4 = SceneryEntry.Colors [fv4.Color]; - Vector3 v1 = new Vector3 (fv1.X, fv1.Y, fv1.Z); - Vector3 v2 = new Vector3 (fv2.X, fv2.Y, fv2.Z); - Vector3 v3 = new Vector3 (fv3.X, fv3.Y, fv3.Z); - Vector3 v4 = new Vector3 (fv4.X, fv4.Y, fv4.Z); - Vector3 c1 = new Vector3 (fc1.Red, fc1.Green, fc1.Blue) / 255f; - Vector3 c2 = new Vector3 (fc2.Red, fc2.Green, fc2.Blue) / 255f; - Vector3 c3 = new Vector3 (fc3.Red, fc3.Green, fc3.Blue) / 255f; - Vector3 c4 = new Vector3 (fc4.Red, fc4.Green, fc4.Blue) / 255f; - - exporter.AddFace ( - (v1 + offset / 16) * scale, - (v2 + offset / 16) * scale, - (v3 + offset / 16) * scale, - (v4 + offset / 16) * scale, - c1, c2, c3, c4, - material, - uv1, uv2, uv3, uv4 - ); - } + Dictionary objTranslate = new Dictionary (); + exporter.AddScenery (this.GetNSF (), SceneryEntry, ref textureEIDs, ref objTranslate); exporter.Export (path, modelname); } diff --git a/CrashEdit/Controls/3D/SceneryEntryViewer.cs b/CrashEdit/Controls/3D/SceneryEntryViewer.cs index 135b503a..17ac439b 100644 --- a/CrashEdit/Controls/3D/SceneryEntryViewer.cs +++ b/CrashEdit/Controls/3D/SceneryEntryViewer.cs @@ -123,7 +123,7 @@ private void RenderTriangle(SceneryEntry world, int index) var tri = world.Triangles[index]; if (tri.VertexA >= world.Vertices.Count || tri.VertexB >= world.Vertices.Count || tri.VertexC >= world.Vertices.Count) return; - if (!ProcessTextureInfoC2(tri.Texture, tri.Animated, world.Textures, world.AnimatedTextures, out var polygon_texture_info)) + if (!TextureUtils.ProcessTextureInfoC2(this.render.CurrentFrame / 2, tri.Texture, tri.Animated, world.Textures, world.AnimatedTextures, out var polygon_texture_info)) return; ref var a = ref _vao.Verts[_vao.CurVert + 0]; ref var b = ref _vao.Verts[_vao.CurVert + 1]; @@ -153,7 +153,7 @@ private void RenderQuad(SceneryEntry world, int index, int state) var quad = world.Quads[index]; if (quad.VertexA >= world.Vertices.Count || quad.VertexB >= world.Vertices.Count || quad.VertexC >= world.Vertices.Count || quad.VertexD >= world.Vertices.Count) return; - if (!ProcessTextureInfoC2(quad.Texture, quad.Animated, world.Textures, world.AnimatedTextures, out var polygon_texture_info)) + if (!TextureUtils.ProcessTextureInfoC2(this.render.CurrentFrame / 2, quad.Texture, quad.Animated, world.Textures, world.AnimatedTextures, out var polygon_texture_info)) return; ref var a = ref _vao.Verts[_vao.CurVert + 0]; ref var b = ref _vao.Verts[_vao.CurVert + 1]; diff --git a/CrashEdit/Exporters/FrameExtensions.cs b/CrashEdit/Exporters/FrameExtensions.cs index e379b22e..7bb06bfc 100644 --- a/CrashEdit/Exporters/FrameExtensions.cs +++ b/CrashEdit/Exporters/FrameExtensions.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using Crash; -using OpenTK; +using CrashEdit.CE; +using CrashEdit.Crash; +using OpenTK.Mathematics; namespace CrashEdit.Exporters; @@ -13,7 +10,7 @@ namespace CrashEdit.Exporters; // TODO: BUT THAT WOULD CUT DOWN THE METHODS HERE TO JUST ONE OR TWO public static class FrameExtensions { - public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) + public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) { var model = nsf.GetEntry(frame.ModelEID); var offset = new Vector3 (frame.XOffset, frame.YOffset, frame.ZOffset); @@ -34,7 +31,7 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, { string material = null; Vector2? uv1 = null, uv2 = null, uv3 = null; - OldModelStruct str = model.Structs [polygon.Unknown & 0x7FFF]; + OldModelStruct str = model.Structs [polygon.TexInfo]; OldFrameVertex ov1 = frame.Vertices [polygon.VertexA / 6]; OldFrameVertex ov2 = frame.Vertices [polygon.VertexB / 6]; OldFrameVertex ov3 = frame.Vertices [polygon.VertexC / 6]; @@ -45,7 +42,7 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, if (str is OldModelTexture t) { - material = exporter.AddTexture (nsf, t, ref textureEIDs, ref objTranslate, out color, out uv1, out uv2, out uv3); + material = exporter.AddTexture (nsf, t, ref objTranslate, out color, out uv1, out uv2, out uv3); } else if (str is OldSceneryColor c) { @@ -63,7 +60,7 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, } } - public static void AddFrame (this OBJExporter exporter, NSF nsf, Frame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) + public static void AddFrame (this OBJExporter exporter, NSF nsf, Frame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) { // TODO: SUPPORT CRASH2 AND CRASH3 PROPER SCALING // offset correction is 4f in Crash2, 32f in Crash3 diff --git a/CrashEdit/Exporters/MaterialExtensions.cs b/CrashEdit/Exporters/MaterialExtensions.cs index d8d76283..8b6f40c5 100644 --- a/CrashEdit/Exporters/MaterialExtensions.cs +++ b/CrashEdit/Exporters/MaterialExtensions.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using Crash; -using OpenTK; +using CrashEdit.CE; +using CrashEdit.Crash; +using OpenTK.Mathematics; namespace CrashEdit.Exporters; @@ -23,35 +20,32 @@ private static Vector2 TexSize (int colorMode) }; } - public static string AddTexture (this OBJExporter exporter, NSF nsf, ModelTransformedTriangle tri, ModelEntry model, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3, out bool flip) + public static string AddTexture (this OBJExporter exporter, NSF nsf, ModelTransformedTriangle tri, ModelEntry model, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3, out bool flip) { - var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures); string material = null; uv1 = uv2 = uv3 = null; bool nocull = tri.Subtype == 0 || tri.Subtype == 2; flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; // parse the texture and add it to the exporter - if (info.Item1 && info.Item2 is not null) + if (TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures, out ModelTexture value) && value is not null) { - var value = info.Item2.Value; int textureEID = model.GetTPAG (value.Page); - int page = textureEIDs [textureEID]; material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == page + x.Value.Color == value.ColorMode && + x.Value.Blend == value.BlendMode && + x.Value.ClutX == value.ClutX && + x.Value.ClutY == value.ClutY && + x.Value.Page == value.Page ).Key; // ignore the texinfo if there's already a texture with the exact same settings stored if (material is null) { - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] + var texinfo = new VertexTexInfo ( + color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: value.Page ); var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); @@ -59,7 +53,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, ModelTransf Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); + material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.Color}b{texinfo.Blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -84,32 +78,29 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, ModelTransf return material; } - public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryTriangle tri, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) + public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryTriangle tri, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) { - var info = TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, scenery.Textures, scenery.AnimatedTextures); string material = null; uv1 = uv2 = uv3 = null; - if (info.Item1 && info.Item2 is not null) + if (TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, scenery.Textures, scenery.AnimatedTextures, out ModelTexture value) && value is not null) { - var value = info.Item2.Value; int textureEID = scenery.GetTPAG (value.Page); - int page = textureEIDs [textureEID]; material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == page + x.Value.Color == value.ColorMode && + x.Value.Blend == value.BlendMode && + x.Value.ClutX == value.ClutX && + x.Value.ClutY == value.ClutY && + x.Value.Page == value.Page ).Key; // ignore the texinfo if there's already a texture with the exact same settings stored if (material is null) { - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] + var texinfo = new VertexTexInfo ( + color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: value.Page ); var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); @@ -117,7 +108,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryTria Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); + material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.Color}b{texinfo.Blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -133,32 +124,29 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryTria return material; } - public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryQuad quad, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3, out Vector2? uv4) + public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryQuad quad, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3, out Vector2? uv4) { - var info = TextureUtils.ProcessTextureInfoC2 (0, quad.Texture, quad.Animated, scenery.Textures, scenery.AnimatedTextures); string material = null; uv1 = uv2 = uv3 = uv4 = null; - if (info.Item1 && info.Item2 is not null) + if (TextureUtils.ProcessTextureInfoC2 (0, quad.Texture, quad.Animated, scenery.Textures, scenery.AnimatedTextures, out ModelTexture value) && value is not null) { - var value = info.Item2.Value; int textureEID = scenery.GetTPAG (value.Page); - int page = textureEIDs [textureEID]; material = objTranslate.FirstOrDefault (x => - x.Value.color == value.ColorMode && - x.Value.blend == value.BlendMode && - x.Value.clutx == value.ClutX && - x.Value.cluty == value.ClutY && - x.Value.page == page + x.Value.Color == value.ColorMode && + x.Value.Blend == value.BlendMode && + x.Value.ClutX == value.ClutX && + x.Value.ClutY == value.ClutY && + x.Value.Page == value.Page ).Key; // ignore the texinfo if there's already a texture with the exact same settings stored if (material is null) { - var texinfo = new TexInfoUnpacked ( - true, color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: textureEIDs [textureEID] + var texinfo = new VertexTexInfo ( + color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, + page: value.Page ); var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); @@ -166,7 +154,7 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryQuad Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); + material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.Color}b{texinfo.Blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -183,34 +171,32 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryQuad return material; } - public static string AddTexture (this OBJExporter exporter, NSF nsf, OldSceneryTexture t, int textureEID, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector3 color, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) + public static string AddTexture (this OBJExporter exporter, NSF nsf, OldSceneryTexture t, int textureEID, ref Dictionary objTranslate, out Vector3 color, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) { - string material = null; - int page = textureEIDs [textureEID]; color = new Vector3 (t.R, t.G, t.B) / 255F; // add the texture to the list too - material = objTranslate.FirstOrDefault (x => - x.Value.color == t.ColorMode && - x.Value.blend == t.BlendMode && - x.Value.clutx == t.ClutX && - x.Value.cluty == t.ClutY && - x.Value.page == page + string material = objTranslate.FirstOrDefault (x => + x.Value.Color == t.ColorMode && + x.Value.Blend == t.BlendMode && + x.Value.ClutX == t.ClutX && + x.Value.ClutY == t.ClutY && + x.Value.Page == t.UVIndex ).Key; if (material is null) { - var texinfo = new TexInfoUnpacked( - true, color: t.ColorMode, blend: t.BlendMode, + var texinfo = new VertexTexInfo( + color: t.ColorMode, blend: t.BlendMode, clutx: t.ClutX, cluty: t.ClutY, - page: textureEIDs[textureEID] + page: (short) t.UVIndex ); var tpag = nsf.GetEntry (textureEID); Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); + material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.Color}b{texinfo.Blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; @@ -225,35 +211,34 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, OldSceneryT return material; } - public static string AddTexture (this OBJExporter exporter, NSF nsf, OldModelTexture t, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector3 color, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) + public static string AddTexture (this OBJExporter exporter, NSF nsf, OldModelTexture t, ref Dictionary objTranslate, out Vector3 color, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) { - string material = null; - int page = textureEIDs [t.EID]; color = new Vector3 (t.R, t.G, t.B) / 255F; // add the texture to the list too - material = objTranslate.FirstOrDefault (x => - x.Value.color == t.ColorMode && - x.Value.blend == t.BlendMode && - x.Value.clutx == t.ClutX && - x.Value.cluty == t.ClutY && - x.Value.page == page + string material = objTranslate.FirstOrDefault (x => + x.Value.Color == t.ColorMode && + x.Value.Blend == t.BlendMode && + x.Value.ClutX == t.ClutX && + x.Value.ClutY == t.ClutY && + x.Value.Page == t.UVIndex && + x.Value.Face == Convert.ToInt32 (t.N) ).Key; if (material is null) { - var texinfo = new TexInfoUnpacked ( - true, color: t.ColorMode, blend: t.BlendMode, + var texinfo = new VertexTexInfo ( + color: t.ColorMode, blend: t.BlendMode, clutx: t.ClutX, cluty: t.ClutY, face: Convert.ToInt32 (t.N), - page: textureEIDs [t.EID] + page: (short)t.UVIndex ); var tpag = nsf.GetEntry (t.EID); Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); // the material name changes - material = exporter.AddTexture ($"{t.EID}-{((int)texinfo).ToString("X8")}c{texinfo.color}b{texinfo.blend}", texture); + material = exporter.AddTexture ($"{t.EID}-{((int)texinfo).ToString("X8")}c{texinfo.Color}b{texinfo.Blend}", texture); // add it to the lookup table too objTranslate [material] = texinfo; diff --git a/CrashEdit/Exporters/OBJExporter.cs b/CrashEdit/Exporters/OBJExporter.cs index 0408cce1..38806833 100644 --- a/CrashEdit/Exporters/OBJExporter.cs +++ b/CrashEdit/Exporters/OBJExporter.cs @@ -1,9 +1,5 @@ -using System.Collections.Generic; -using System.Drawing; -using System.Globalization; -using System.IO; -using System.Linq; -using OpenTK; +using System.Globalization; +using OpenTK.Mathematics; namespace CrashEdit.Exporters; diff --git a/CrashEdit/Exporters/SceneryExtensions.cs b/CrashEdit/Exporters/SceneryExtensions.cs index d8b486ac..ee3f685b 100644 --- a/CrashEdit/Exporters/SceneryExtensions.cs +++ b/CrashEdit/Exporters/SceneryExtensions.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using Crash; -using OpenTK; +using CrashEdit.CE; +using CrashEdit.Crash; +using OpenTK.Mathematics; namespace CrashEdit.Exporters; @@ -13,7 +10,7 @@ namespace CrashEdit.Exporters; // TODO: BUT THAT WOULD CUT DOWN THE METHODS HERE TO JUST ONE OR TWO public static class SceneryExtensions { - public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) + public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) { var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); var scale = new Vector3 (1 / GameScales.WorldC1); @@ -44,7 +41,7 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEnt if (str is OldSceneryTexture t) { int textureEID = scenery.GetTPAG (polygon.Page); - material = exporter.AddTexture (nsf, t, textureEID, ref textureEIDs, ref objTranslate, out color, out uv1, out uv2, out uv3); + material = exporter.AddTexture (nsf, t, textureEID, ref objTranslate, out color, out uv1, out uv2, out uv3); } else if(str is OldSceneryColor c) { @@ -62,7 +59,7 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEnt } } - public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) + public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) { var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); //var scale = new Vector3 (1 / GameScales.WorldC1); diff --git a/CrashEdit/Exporters/TextureExporter.cs b/CrashEdit/Exporters/TextureExporter.cs index 51b73a22..6ac49be4 100644 --- a/CrashEdit/Exporters/TextureExporter.cs +++ b/CrashEdit/Exporters/TextureExporter.cs @@ -1,21 +1,23 @@ using System.Drawing; +using System.Drawing.Imaging; +using CrashEdit.CE; namespace CrashEdit.Exporters; public class TextureExporter { - public static Bitmap Create4bpp (byte [] data, TexInfoUnpacked info) + public static Bitmap Create4bpp (byte [] data, VertexTexInfo info) { Bitmap bmp = new Bitmap (1024, 128); - // clutx really contains the count of palettes available + // ClutX really contains the count of palettes available // so multiplying by 16 gives the right amount of info for it - int clutx = info.clutx * 16; + int clutx = info.ClutX * 16; // write the texture somewhere for now, testing for (int x = 0; x < 1024; x++) { - for (int y = info.cluty; y < 128; y++) + for (int y = info.ClutY; y < 128; y++) { byte entry = data [y * 512 + (x / 2)]; @@ -24,10 +26,10 @@ public static Bitmap Create4bpp (byte [] data, TexInfoUnpacked info) else entry = (byte) ((entry >> 4) & 0xF); - // now read the color from the palette + // now read the Color from the palette ushort color = (ushort) ( - data [info.cluty * 512 + ((clutx + entry) * 2)] | - (data [info.cluty * 512 + ((clutx + entry) * 2 + 1)] << 8) + data [info.ClutY * 512 + ((clutx + entry) * 2)] | + (data [info.ClutY * 512 + ((clutx + entry) * 2 + 1)] << 8) ); int red = (int) (((color & 0x1F) / 31F) * 255); @@ -36,7 +38,7 @@ public static Bitmap Create4bpp (byte [] data, TexInfoUnpacked info) int a = ((color >> 15) & 0x1); int alpha = 255; - if (info.blend == 0 && (info.color == 0 || info.color == 1)) + if (info.Blend == 0 && (info.Color == 0 || info.Color == 1)) { if (a == 1) alpha = 127; @@ -46,13 +48,13 @@ public static Bitmap Create4bpp (byte [] data, TexInfoUnpacked info) if (red == 0 && green == 0 && blue == 0 && a == 0) alpha = 0; } - else if (info.blend == 1 && (info.color == 0 || info.color == 1)) + else if (info.Blend == 1 && (info.Color == 0 || info.Color == 1)) { if (red == 0 && green == 0 && blue == 0 && a == 0) alpha = 0; } - // colors are in rgb15 format + // Colors are in rgb15 format Color c = Color.FromArgb ( alpha, red, @@ -67,25 +69,25 @@ public static Bitmap Create4bpp (byte [] data, TexInfoUnpacked info) return bmp; } - public static Bitmap Create8bpp (byte [] data, TexInfoUnpacked info) + public static Bitmap Create8bpp (byte [] data, VertexTexInfo info) { Bitmap bmp = new Bitmap (512, 128); - // clutx really contains the count of palettes available + // ClutX really contains the count of palettes available // so multiplying by 16 gives the right amount of info for it - int clutx = info.clutx * 16; + int clutx = info.ClutX * 16; // write the texture somewhere for now, testing for (int x = 0; x < 512; x++) { - for (int y = info.cluty; y < 128; y++) + for (int y = info.ClutY; y < 128; y++) { byte entry = data [y * 512 + x]; - // now read the color from the palette + // now read the Color from the palette ushort color = (ushort) ( - data [info.cluty * 512 + ((clutx + entry) * 2)] | - (data [info.cluty * 512 + ((clutx + entry) * 2 + 1)] << 8) + data [info.ClutY * 512 + ((clutx + entry) * 2)] | + (data [info.ClutY * 512 + ((clutx + entry) * 2 + 1)] << 8) ); int red = (int) (((color & 0x1F) / 31F) * 255); @@ -94,7 +96,7 @@ public static Bitmap Create8bpp (byte [] data, TexInfoUnpacked info) int a = ((color >> 15) & 0x1); int alpha = 255; - if (info.blend == 0 && (info.color == 0 || info.color == 1)) + if (info.Blend == 0 && (info.Color == 0 || info.Color == 1)) { if (a == 1) alpha = 127; @@ -104,13 +106,13 @@ public static Bitmap Create8bpp (byte [] data, TexInfoUnpacked info) if (red == 0 && green == 0 && blue == 0 && a == 0) alpha = 0; } - else if (info.blend == 1 && (info.color == 0 || info.color == 1)) + else if (info.Blend == 1 && (info.Color == 0 || info.Color == 1)) { if (red == 0 && green == 0 && blue == 0 && a == 0) alpha = 0; } - // colors are in rgb15 format + // Colors are in rgb15 format Color c = Color.FromArgb ( alpha, red, @@ -125,28 +127,28 @@ public static Bitmap Create8bpp (byte [] data, TexInfoUnpacked info) return bmp; } - public static Bitmap Create16bpp (byte [] data, TexInfoUnpacked info) + public static Bitmap Create16bpp (byte [] data, VertexTexInfo info) { Bitmap bmp = new Bitmap (256, 128); - // clutx really contains the count of palettes available + // ClutX really contains the count of palettes available // so multiplying by 16 gives the right amount of info for it - int clutx = info.clutx * 16; + int clutx = info.ClutX * 16; // write the texture somewhere for now, testing for (int x = 0; x < 256; x++) { - for (int y = info.cluty; y < 128; y++) + for (int y = info.ClutY; y < 128; y++) { - // now read the color from the palette + // now read the Color from the palette ushort color = (ushort) ( - data [(info.cluty + y) * 512 + ((clutx + x) * 2)] | - (data [(info.cluty + y) * 512 + ((clutx + x) * 2 + 1)] << 8) + data [(info.ClutY + y) * 512 + ((clutx + x) * 2)] | + (data [(info.ClutY + y) * 512 + ((clutx + x) * 2 + 1)] << 8) ); // this is not 100% precise, but we can fix these manually // as they're usually not that big - // colors are in rgb15 format + // Colors are in rgb15 format Color c = Color.FromArgb ( ((color >> 15) & 0x1) * 255, (int) (((color & 0x1F) / 31F) * 255), @@ -161,9 +163,9 @@ data [(info.cluty + y) * 512 + ((clutx + x) * 2)] | return bmp; } - public static Bitmap CreateTexture (byte [] data, TexInfoUnpacked info) + public static Bitmap CreateTexture (byte [] data, VertexTexInfo info) { - return info.color switch + return info.Color switch { 0 => TextureExporter.Create4bpp (data, info), 1 => TextureExporter.Create8bpp (data, info), diff --git a/CrashEdit/Utils/OpenTK Integration/AnimationRenderer.cs b/CrashEdit/Utils/OpenTK Integration/AnimationRenderer.cs index 061610bc..26f0d02a 100644 --- a/CrashEdit/Utils/OpenTK Integration/AnimationRenderer.cs +++ b/CrashEdit/Utils/OpenTK Integration/AnimationRenderer.cs @@ -147,7 +147,7 @@ private bool RenderFrame(VAO[] vaos, Frame frame, int buf) } foreach (var tri in model.Triangles) { - if (!ProcessTextureInfoC2(Render.RealCurrentFrame / 2, tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures, out var polygon_texture_info)) + if (!TextureUtils.ProcessTextureInfoC2(Render.RealCurrentFrame / 2, tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures, out var polygon_texture_info)) continue; bool nocull = tri.Subtype == 0 || tri.Subtype == 2; bool flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; diff --git a/CrashEdit/Utils/TextureUtils.cs b/CrashEdit/Utils/TextureUtils.cs index 2ead55ed..5fa0b116 100644 --- a/CrashEdit/Utils/TextureUtils.cs +++ b/CrashEdit/Utils/TextureUtils.cs @@ -1,22 +1,20 @@ -using System; -using System.Collections.Generic; -using Crash; +using CrashEdit.Crash; namespace CrashEdit; public class TextureUtils { - public static Tuple ProcessTextureInfoC2(long currentFrame, int in_tex_id, bool animated, IList textures, IList animated_textures) + public static bool ProcessTextureInfoC2(long texture_frame, int in_tex_id, bool animated, IList textures, IList animated_textures, out ModelTexture tex) { if (in_tex_id != 0 || animated) { - ModelTexture? info_temp = null; int tex_id = in_tex_id - 1; if (animated) { if (++tex_id >= animated_textures.Count) { - return new(false, null); + tex = default; + return false; } var anim = animated_textures[tex_id]; // check if it's an untextured polygon @@ -29,7 +27,7 @@ public class TextureUtils } else { - tex_id += (int)((currentFrame / 2 / (1 + anim.Latency) + anim.Delay) & anim.Mask); + tex_id += (int)((texture_frame / (1 + anim.Latency) + anim.Delay) & anim.Mask); if (anim.Leap) { anim = animated_textures[++tex_id]; @@ -38,21 +36,28 @@ public class TextureUtils } if (tex_id >= textures.Count) { - return new(false, null); + tex = default; + return false; } - info_temp = textures[tex_id]; + tex = textures[tex_id]; + } + else + { + tex = default; } } else { if (tex_id >= textures.Count) { - return new(false, null); + tex = default; + return false; } - info_temp = textures[tex_id]; + tex = textures[tex_id]; } - return new(true, info_temp); + return true; } - return new(true, null); + tex = default; + return true; } } \ No newline at end of file From 4085a060493f57e075870b8d8a3ecaf64ef4e977 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Fri, 21 Feb 2025 05:57:15 +0100 Subject: [PATCH 16/17] Added basic texture atlas and fixed colors for crash1 scenery --- .../Controllers/Animation/FrameController.cs | 2 +- .../Animation/OldFrameController.cs | 2 +- CrashEdit/Controllers/NSFController.cs | 4 +- .../Scenery/OldSceneryEntryController.cs | 2 +- .../Scenery/SceneryEntryController.cs | 2 +- CrashEdit/Exporters/FrameExtensions.cs | 10 +- CrashEdit/Exporters/MaterialExtensions.cs | 224 +++++------------- CrashEdit/Exporters/OBJExporter.cs | 208 ++++++++++------ CrashEdit/Exporters/SceneryExtensions.cs | 25 +- CrashEdit/Exporters/TextureAtlas.cs | 104 ++++++++ CrashEdit/Utils/TextureUtils.cs | 11 + 11 files changed, 335 insertions(+), 259 deletions(-) create mode 100644 CrashEdit/Exporters/TextureAtlas.cs diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index b15a2c8f..166c83fd 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -45,7 +45,7 @@ private void Menu_Export_OBJ () public void ToOBJ (string path, string modelname) { Dictionary textureEIDs = new (); - Dictionary objTranslate = new Dictionary (); + Dictionary objTranslate = new Dictionary (); var exporter = new OBJExporter (); diff --git a/CrashEdit/Controllers/Animation/OldFrameController.cs b/CrashEdit/Controllers/Animation/OldFrameController.cs index 56ce4aa3..71ac4d1c 100755 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -64,7 +64,7 @@ private void Menu_Export_OBJ() public void ToOBJ(string path, string modelname) { Dictionary textureEIDs = new Dictionary (); - Dictionary objTranslate = new Dictionary (); + Dictionary objTranslate = new Dictionary (); var exporter = new OBJExporter (); diff --git a/CrashEdit/Controllers/NSFController.cs b/CrashEdit/Controllers/NSFController.cs index aad48a13..4b2fff3f 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -335,7 +335,7 @@ private void ExportSceneryC2OBJ (string path, string modelname) // detect how many textures are used and their eids to prepare the image Dictionary textureEIDs = new (); - Dictionary objTranslate = new Dictionary (); + Dictionary objTranslate = new Dictionary (); // find all the scenery insde chunks foreach (SceneryEntry scenery in NSF.GetEntries ()) @@ -352,7 +352,7 @@ private void ExportSceneryC1OBJ (string path, string modelname) // detect how many textures are used and their eids to prepare the image Dictionary textureEIDs = new (); - Dictionary objTranslate = new Dictionary (); + Dictionary objTranslate = new Dictionary (); // find all the scenery insde chunks foreach (OldSceneryEntry scenery in NSF.GetEntries ()) diff --git a/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs b/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs index 9b1ac73c..3d37547f 100755 --- a/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs @@ -34,7 +34,7 @@ private void ToOBJ (string path, string modelname) { var exporter = new OBJExporter (); Dictionary textureEIDs = new (); - Dictionary objTranslate = new Dictionary (); + Dictionary objTranslate = new Dictionary (); exporter.AddScenery (this.GetNSF (), OldSceneryEntry, ref textureEIDs, ref objTranslate); exporter.Export (path, modelname); diff --git a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs index c21b89a5..5db83967 100755 --- a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs @@ -37,7 +37,7 @@ private void ToOBJ (string path, string modelname) { var exporter = new OBJExporter (); Dictionary textureEIDs = new (); - Dictionary objTranslate = new Dictionary (); + Dictionary objTranslate = new Dictionary (); exporter.AddScenery (this.GetNSF (), SceneryEntry, ref textureEIDs, ref objTranslate); exporter.Export (path, modelname); diff --git a/CrashEdit/Exporters/FrameExtensions.cs b/CrashEdit/Exporters/FrameExtensions.cs index 7bb06bfc..fbd2d89e 100644 --- a/CrashEdit/Exporters/FrameExtensions.cs +++ b/CrashEdit/Exporters/FrameExtensions.cs @@ -10,7 +10,7 @@ namespace CrashEdit.Exporters; // TODO: BUT THAT WOULD CUT DOWN THE METHODS HERE TO JUST ONE OR TWO public static class FrameExtensions { - public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) + public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) { var model = nsf.GetEntry(frame.ModelEID); var offset = new Vector3 (frame.XOffset, frame.YOffset, frame.ZOffset); @@ -29,7 +29,7 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, foreach (OldModelPolygon polygon in model.Polygons) { - string material = null; + VertexTexInfo? material = null; Vector2? uv1 = null, uv2 = null, uv3 = null; OldModelStruct str = model.Structs [polygon.TexInfo]; OldFrameVertex ov1 = frame.Vertices [polygon.VertexA / 6]; @@ -42,7 +42,7 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, if (str is OldModelTexture t) { - material = exporter.AddTexture (nsf, t, ref objTranslate, out color, out uv1, out uv2, out uv3); + material = exporter.AddTexture (nsf, t, t.EID, ref textureEIDs, ref objTranslate, out color, out uv1, out uv2, out uv3); } else if (str is OldSceneryColor c) { @@ -60,7 +60,7 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, } } - public static void AddFrame (this OBJExporter exporter, NSF nsf, Frame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) + public static void AddFrame (this OBJExporter exporter, NSF nsf, Frame frame, ref Dictionary textureEIDs, ref Dictionary objTranslate) { // TODO: SUPPORT CRASH2 AND CRASH3 PROPER SCALING // offset correction is 4f in Crash2, 32f in Crash3 @@ -94,7 +94,7 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, Frame frame, re // iterate all the triangles, get the texture modes and build information about those foreach (var tri in model.Triangles) { - string material = exporter.AddTexture ( + VertexTexInfo? material = exporter.AddTexture ( nsf, tri, model, ref textureEIDs, ref objTranslate, out var uv1, out var uv2, out var uv3, out bool flip diff --git a/CrashEdit/Exporters/MaterialExtensions.cs b/CrashEdit/Exporters/MaterialExtensions.cs index 8b6f40c5..e5919dab 100644 --- a/CrashEdit/Exporters/MaterialExtensions.cs +++ b/CrashEdit/Exporters/MaterialExtensions.cs @@ -1,4 +1,5 @@ -using CrashEdit.CE; +using System.Runtime.CompilerServices; +using CrashEdit.CE; using CrashEdit.Crash; using OpenTK.Mathematics; @@ -10,19 +11,47 @@ namespace CrashEdit.Exporters; // TODO: BUT THAT WOULD CUT DOWN THE METHODS HERE TO JUST ONE OR TWO public static class MaterialExtensions { - private static Vector2 TexSize (int colorMode) + private static VertexTexInfo? FindOrAddTexture + (this OBJExporter exporter, NSF nsf, int colorMode, int blendMode, int clutx, int cluty, short page, int face, int textureEID, ref Dictionary textureEIDs, ref Dictionary objTranslate) { - return colorMode switch + VertexTexInfo? material = null; + + try + { + // ignore the texinfo if there's already a texture with the exact same settings stored + material = objTranslate.First (x => + x.Key.Color == colorMode && + x.Key.Blend == blendMode && + x.Key.ClutX == clutx && + x.Key.ClutY == cluty && + x.Key.Page == page && + x.Key.Face == face + ).Key; + } + catch (Exception e) { - 0 => new Vector2 (1024, 128), - 1 => new Vector2 (512, 128), - _ => new Vector2 (256, 128) - }; + // first throw an exception if there's no match, thus here we create it + material = new VertexTexInfo ( + color: colorMode, blend: blendMode, clutx: clutx, cluty: cluty, page: page, face: face + ); + + var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); + + Bitmap texture = TextureExporter.CreateTexture (tpag.Data, material.Value); + + // the material name changes + exporter.AddTexture (material.Value, texture); + + // add it to the lookup table too + objTranslate.Add (material.Value, material.Value); + } + + return material; } - public static string AddTexture (this OBJExporter exporter, NSF nsf, ModelTransformedTriangle tri, ModelEntry model, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3, out bool flip) + public static VertexTexInfo? AddTexture (this OBJExporter exporter, NSF nsf, ModelTransformedTriangle tri, ModelEntry model, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3, out bool flip) { - string material = null; + VertexTexInfo? material = null; uv1 = uv2 = uv3 = null; bool nocull = tri.Subtype == 0 || tri.Subtype == 2; flip = (tri.Type == 2 ^ tri.Subtype == 3) && !nocull; @@ -32,34 +61,9 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, ModelTransf { int textureEID = model.GetTPAG (value.Page); - material = objTranslate.FirstOrDefault (x => - x.Value.Color == value.ColorMode && - x.Value.Blend == value.BlendMode && - x.Value.ClutX == value.ClutX && - x.Value.ClutY == value.ClutY && - x.Value.Page == value.Page - ).Key; + material = exporter.FindOrAddTexture (nsf, value.ColorMode, value.BlendMode, value.ClutX, value.ClutY, value.Page, 0, textureEID, ref textureEIDs, ref objTranslate); - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - var texinfo = new VertexTexInfo ( - color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: value.Page - ); - - var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); - - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.Color}b{texinfo.Blend}", texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = TexSize (value.ColorMode); + Vector2 texsize = TextureUtils.TextureSize (value.ColorMode); uv2 = new Vector2 (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); @@ -78,89 +82,39 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, ModelTransf return material; } - public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryTriangle tri, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) + public static VertexTexInfo? AddTexture (this OBJExporter exporter, NSF nsf, SceneryTriangle tri, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) { - string material = null; + VertexTexInfo? material = null; uv1 = uv2 = uv3 = null; if (TextureUtils.ProcessTextureInfoC2 (0, tri.Texture, tri.Animated, scenery.Textures, scenery.AnimatedTextures, out ModelTexture value) && value is not null) { int textureEID = scenery.GetTPAG (value.Page); - material = objTranslate.FirstOrDefault (x => - x.Value.Color == value.ColorMode && - x.Value.Blend == value.BlendMode && - x.Value.ClutX == value.ClutX && - x.Value.ClutY == value.ClutY && - x.Value.Page == value.Page - ).Key; - - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - var texinfo = new VertexTexInfo ( - color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: value.Page - ); - - var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); - - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.Color}b{texinfo.Blend}", texture); + material = exporter.FindOrAddTexture (nsf, value.ColorMode, value.BlendMode, value.ClutX, value.ClutY, value.Page, 0, textureEID, ref textureEIDs, ref objTranslate); - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = TexSize (value.ColorMode); + Vector2 texsize = TextureUtils.TextureSize (value.ColorMode); uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); - uv2 = new Vector2 (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); + uv2 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); uv3 = new (value.X3 / texsize.X, (128 - value.Y3) / texsize.Y); } return material; } - public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryQuad quad, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3, out Vector2? uv4) + public static VertexTexInfo? AddTexture (this OBJExporter exporter, NSF nsf, SceneryQuad quad, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3, out Vector2? uv4) { - string material = null; + VertexTexInfo? material = null; uv1 = uv2 = uv3 = uv4 = null; if (TextureUtils.ProcessTextureInfoC2 (0, quad.Texture, quad.Animated, scenery.Textures, scenery.AnimatedTextures, out ModelTexture value) && value is not null) { int textureEID = scenery.GetTPAG (value.Page); - material = objTranslate.FirstOrDefault (x => - x.Value.Color == value.ColorMode && - x.Value.Blend == value.BlendMode && - x.Value.ClutX == value.ClutX && - x.Value.ClutY == value.ClutY && - x.Value.Page == value.Page - ).Key; - - // ignore the texinfo if there's already a texture with the exact same settings stored - if (material is null) - { - var texinfo = new VertexTexInfo ( - color: value.ColorMode, blend: value.BlendMode, clutx: value.ClutX, cluty: value.ClutY, - page: value.Page - ); - - var tpag = nsf.GetEntry (textureEIDs.First (x => x.Key == textureEID).Key); + material = exporter.FindOrAddTexture (nsf, value.ColorMode, value.BlendMode, value.ClutX, value.ClutY, value.Page, 0, textureEID, ref textureEIDs, ref objTranslate); - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.Color}b{texinfo.Blend}", texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = TexSize (value.ColorMode); + Vector2 texsize = TextureUtils.TextureSize (value.ColorMode); uv1 = new (value.X2 / texsize.X, (128 - value.Y2) / texsize.Y); uv2 = new (value.X1 / texsize.X, (128 - value.Y1) / texsize.Y); @@ -171,84 +125,28 @@ public static string AddTexture (this OBJExporter exporter, NSF nsf, SceneryQuad return material; } - public static string AddTexture (this OBJExporter exporter, NSF nsf, OldSceneryTexture t, int textureEID, ref Dictionary objTranslate, out Vector3 color, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) + public static VertexTexInfo? AddTexture (this OBJExporter exporter, NSF nsf, OldSceneryTexture t, int textureEID, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) { - color = new Vector3 (t.R, t.G, t.B) / 255F; - - // add the texture to the list too - string material = objTranslate.FirstOrDefault (x => - x.Value.Color == t.ColorMode && - x.Value.Blend == t.BlendMode && - x.Value.ClutX == t.ClutX && - x.Value.ClutY == t.ClutY && - x.Value.Page == t.UVIndex - ).Key; + VertexTexInfo? material = exporter.FindOrAddTexture (nsf, t.ColorMode, t.BlendMode, t.ClutX, t.ClutY, (short) t.UVIndex, 0, textureEID, ref textureEIDs, ref objTranslate); + Vector2 texsize = TextureUtils.TextureSize (t.ColorMode); - if (material is null) - { - var texinfo = new VertexTexInfo( - color: t.ColorMode, blend: t.BlendMode, - clutx: t.ClutX, cluty: t.ClutY, - page: (short) t.UVIndex - ); - - var tpag = nsf.GetEntry (textureEID); - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture ($"{textureEID}-{((int)texinfo).ToString("X8")}c{texinfo.Color}b{texinfo.Blend}", texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = TexSize (t.ColorMode); - - uv3 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); - uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); - uv1 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); + uv3 = new (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); + uv2 = new (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); + uv1 = new (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); return material; } - public static string AddTexture (this OBJExporter exporter, NSF nsf, OldModelTexture t, ref Dictionary objTranslate, out Vector3 color, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) + public static VertexTexInfo? AddTexture (this OBJExporter exporter, NSF nsf, OldModelTexture t, int textureEID, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector3 color, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) { color = new Vector3 (t.R, t.G, t.B) / 255F; - // add the texture to the list too - string material = objTranslate.FirstOrDefault (x => - x.Value.Color == t.ColorMode && - x.Value.Blend == t.BlendMode && - x.Value.ClutX == t.ClutX && - x.Value.ClutY == t.ClutY && - x.Value.Page == t.UVIndex && - x.Value.Face == Convert.ToInt32 (t.N) - ).Key; - - if (material is null) - { - var texinfo = new VertexTexInfo ( - color: t.ColorMode, blend: t.BlendMode, - clutx: t.ClutX, cluty: t.ClutY, - face: Convert.ToInt32 (t.N), - page: (short)t.UVIndex - ); - - var tpag = nsf.GetEntry (t.EID); - Bitmap texture = TextureExporter.CreateTexture (tpag.Data, texinfo); - - // the material name changes - material = exporter.AddTexture ($"{t.EID}-{((int)texinfo).ToString("X8")}c{texinfo.Color}b{texinfo.Blend}", texture); - - // add it to the lookup table too - objTranslate [material] = texinfo; - } - - Vector2 texsize = TexSize (t.ColorMode); + VertexTexInfo? material = exporter.FindOrAddTexture (nsf, t.ColorMode, t.BlendMode, t.ClutX, t.ClutY, (short) t.UVIndex, Convert.ToInt32 (t.N), textureEID, ref textureEIDs, ref objTranslate); + Vector2 texsize = TextureUtils.TextureSize (t.ColorMode); - uv3 = new Vector2 (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); - uv2 = new Vector2 (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); - uv1 = new Vector2 (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); + uv3 = new (t.U3 / texsize.X, (128 - t.V3) / texsize.Y); + uv2 = new (t.U2 / texsize.X, (128 - t.V2) / texsize.Y); + uv1 = new (t.U1 / texsize.X, (128 - t.V1) / texsize.Y); return material; } diff --git a/CrashEdit/Exporters/OBJExporter.cs b/CrashEdit/Exporters/OBJExporter.cs index 38806833..2346ec41 100644 --- a/CrashEdit/Exporters/OBJExporter.cs +++ b/CrashEdit/Exporters/OBJExporter.cs @@ -1,4 +1,5 @@ using System.Globalization; +using CrashEdit.CE; using OpenTK.Mathematics; namespace CrashEdit.Exporters; @@ -13,7 +14,6 @@ class Material public Vector3 diffuse; public Vector3 specular; public float highlight; - public Bitmap texture; } class Face @@ -22,7 +22,7 @@ class Face public int V2; public int V3; public int? V4; - public string material; + public VertexTexInfo? texture; public int? UV1; public int? UV2; public int? UV3; @@ -34,11 +34,20 @@ class Vertex public Vector3 position; public Vector3 color; } + + class UV + { + public Vector2 position; + public VertexTexInfo texture; + } private Dictionary materials = new Dictionary (); private List vertices = new List (); private List faces = new List (); - private List uvs = new List (); + private List uvs = new List (); + private Dictionary atlas = new Dictionary (); + + private static string BuildMaterialName (int color, int blending) => $"{DEFAULT_MATERIAL}c{color}b{blending}"; public OBJExporter () { @@ -49,30 +58,40 @@ public OBJExporter () diffuse = Vector3.One, highlight = 0.0f, specular = Vector3.Zero, - texture = null }; + + // create the rest of the atlas materials for proper rendering + for (int color = 0; color < 4; color++) + { + for (int blending = 0; blending < 4; blending++) + { + this.materials [BuildMaterialName (color, blending)] = new Material + { + ambient = Vector3.One, + diffuse = Vector3.One, + highlight = 0.0f, + specular = Vector3.Zero, + }; + } + } } /// /// Adds a texture with the given name to the obj /// - /// + /// Texture info /// Texture data /// The identifier for the texture in the obj export - public string AddTexture (string name, Bitmap texture) + public void AddTexture (VertexTexInfo texinfo, Bitmap texture) { - string identifier = $"tex{name}"; + string name = BuildMaterialName (texinfo.Color, texinfo.Blend); - this.materials [identifier] = new Material + if (!this.atlas.TryGetValue (name, out TextureAtlas atlas)) { - ambient = Vector3.One, - diffuse = Vector3.One, - highlight = 0.0f, - specular = Vector3.Zero, - texture = texture - }; - - return identifier; + this.atlas[name] = atlas = new TextureAtlas (); + } + + atlas.AddTexture (texinfo, texture); } /// @@ -97,35 +116,40 @@ public void AddVertex (Vector3 position, Vector3 color) /// /// /// - /// + /// /// /// /// - public void AddFace (int v1, int v2, int v3, string material = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null) + public void AddFace (int v1, int v2, int v3, VertexTexInfo? texture = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null) { // add uv coordinates to the lists first int? uv1id = null; int? uv2id = null; int? uv3id = null; - if (uv1 != uv2 || uv1 != uv3) - throw new InvalidDataException ("UVs must all be null or all have values"); + if( + (uv1 is null && (uv2 is not null || uv3 is not null || texture is not null)) || + (uv2 is null && (uv1 is not null || uv3 is not null || texture is not null)) || + (uv3 is null && (uv1 is not null || uv2 is not null || texture is not null)) || + (texture is null && (uv1 is not null || uv2 is not null || uv3 is not null)) + ) + throw new InvalidDataException ("UVs and texture must all be null or all have values"); - if (uv1 is not null) + if (uv1 is not null && uv2 is not null && uv3 is not null && texture is not null) { uv1id = this.uvs.Count; uv2id = this.uvs.Count + 1; uv3id = this.uvs.Count + 2; - this.uvs.Add (uv1.Value); - this.uvs.Add (uv2.Value); - this.uvs.Add (uv3.Value); + this.uvs.Add (new UV { position = uv1.Value, texture = texture.Value}); + this.uvs.Add (new UV { position = uv2.Value, texture = texture.Value}); + this.uvs.Add (new UV { position = uv3.Value, texture = texture.Value}); } this.faces.Add ( new Face { - material = material ?? DEFAULT_MATERIAL, + texture = texture, V1 = v1, V2 = v2, V3 = v3, @@ -142,11 +166,11 @@ public void AddFace (int v1, int v2, int v3, string material = null, Vector2? uv /// /// /// - /// + /// /// /// /// - public void AddFace (int v1, int v2, int v3, int v4, string material = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null, Vector2? uv4 = null) + public void AddFace (int v1, int v2, int v3, int v4, VertexTexInfo? texture = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null, Vector2? uv4 = null) { // add uv coordinates to the lists first int? uv1id = null; @@ -155,30 +179,31 @@ public void AddFace (int v1, int v2, int v3, int v4, string material = null, Vec int? uv4id = null; if ( - (uv1 is null && (uv2 is not null || uv3 is not null || uv4 is not null)) || - (uv2 is null && (uv1 is not null || uv3 is not null || uv4 is not null)) || - (uv3 is null && (uv1 is not null || uv2 is not null || uv4 is not null)) || - (uv4 is null && (uv1 is not null || uv2 is not null || uv3 is not null)) + (uv1 is null && (uv2 is not null || uv3 is not null || uv4 is not null || texture is not null)) || + (uv2 is null && (uv1 is not null || uv3 is not null || uv4 is not null || texture is not null)) || + (uv3 is null && (uv1 is not null || uv2 is not null || uv4 is not null || texture is not null)) || + (uv4 is null && (uv1 is not null || uv2 is not null || uv3 is not null || texture is not null)) || + (texture is null && (uv1 is not null || uv2 is not null || uv3 is not null || uv4 is not null)) ) - throw new InvalidDataException ("UVs must all be null or all have values"); + throw new InvalidDataException ("UVs and texture must all be null or all have values"); - if (uv1 is not null) + if (uv1 is not null && uv2 is not null && uv3 is not null && uv4 is not null && texture is not null) { uv1id = this.uvs.Count; uv2id = this.uvs.Count + 1; uv3id = this.uvs.Count + 2; uv4id = this.uvs.Count + 3; - - this.uvs.Add (uv1.Value); - this.uvs.Add (uv2.Value); - this.uvs.Add (uv3.Value); - this.uvs.Add (uv4.Value); + + this.uvs.Add (new UV { position = uv1.Value, texture = texture.Value}); + this.uvs.Add (new UV { position = uv2.Value, texture = texture.Value}); + this.uvs.Add (new UV { position = uv3.Value, texture = texture.Value}); + this.uvs.Add (new UV { position = uv4.Value, texture = texture.Value}); } this.faces.Add ( new Face { - material = material ?? DEFAULT_MATERIAL, + texture = texture, V1 = v1, V2 = v2, V3 = v3, @@ -200,11 +225,11 @@ public void AddFace (int v1, int v2, int v3, int v4, string material = null, Vec /// /// /// - /// + /// /// /// /// - public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 c1, Vector3 c2, Vector3 c3, string material = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null) + public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 c1, Vector3 c2, Vector3 c3, VertexTexInfo? texture = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null) { int v1id = this.vertices.Count; int v2id = this.vertices.Count + 1; @@ -214,21 +239,22 @@ public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 c1, Vector3 c2, int? uv3id = null; if ( - (uv1 is null && (uv2 is not null || uv3 is not null)) || - (uv2 is null && (uv1 is not null || uv3 is not null)) || - (uv3 is null && (uv1 is not null || uv2 is not null)) + (uv1 is null && (uv2 is not null || uv3 is not null || texture is not null)) || + (uv2 is null && (uv1 is not null || uv3 is not null || texture is not null)) || + (uv3 is null && (uv1 is not null || uv2 is not null || texture is not null)) || + (texture is null && (uv1 is not null || uv2 is not null || uv3 is not null)) ) - throw new InvalidDataException ("UVs must all be null or all have values"); + throw new InvalidDataException ("UVs and texture must all be null or all have values"); - if (uv1 is not null) + if (uv1 is not null && uv2 is not null && uv3 is not null && texture is not null) { uv1id = this.uvs.Count; uv2id = this.uvs.Count + 1; uv3id = this.uvs.Count + 2; - this.uvs.Add (uv1.Value); - this.uvs.Add (uv2.Value); - this.uvs.Add (uv3.Value); + this.uvs.Add (new UV { position = uv1.Value, texture = texture.Value}); + this.uvs.Add (new UV { position = uv2.Value, texture = texture.Value}); + this.uvs.Add (new UV { position = uv3.Value, texture = texture.Value}); } this.vertices.Add ( @@ -256,7 +282,7 @@ public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 c1, Vector3 c2, this.faces.Add ( new Face { - material = material, + texture = texture, V1 = v1id, V2 = v2id, V3 = v3id, @@ -278,12 +304,12 @@ public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 c1, Vector3 c2, /// /// /// - /// + /// /// /// /// /// - public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Vector3 c1, Vector3 c2, Vector3 c3, Vector3 c4, string material = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null, Vector2? uv4 = null) + public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Vector3 c1, Vector3 c2, Vector3 c3, Vector3 c4, VertexTexInfo? texture = null, Vector2? uv1 = null, Vector2? uv2 = null, Vector2? uv3 = null, Vector2? uv4 = null) { int v1id = this.vertices.Count; int v2id = this.vertices.Count + 1; @@ -295,24 +321,25 @@ public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Vector3 c1, int? uv4id = null; if ( - (uv1 is null && (uv2 is not null || uv3 is not null || uv4 is not null)) || - (uv2 is null && (uv1 is not null || uv3 is not null || uv4 is not null)) || - (uv3 is null && (uv1 is not null || uv2 is not null || uv4 is not null)) || - (uv4 is null && (uv1 is not null || uv2 is not null || uv3 is not null)) + (uv1 is null && (uv2 is not null || uv3 is not null || uv4 is not null || texture is not null)) || + (uv2 is null && (uv1 is not null || uv3 is not null || uv4 is not null || texture is not null)) || + (uv3 is null && (uv1 is not null || uv2 is not null || uv4 is not null || texture is not null)) || + (uv4 is null && (uv1 is not null || uv2 is not null || uv3 is not null || texture is not null)) || + (texture is null && (uv1 is not null || uv2 is not null || uv3 is not null || uv4 is not null)) ) throw new InvalidDataException ("UVs must all be null or all have values"); - if (uv1 is not null) + if (uv1 is not null && uv2 is not null && uv3 is not null && uv4 is not null && texture is not null) { uv1id = this.uvs.Count; uv2id = this.uvs.Count + 1; uv3id = this.uvs.Count + 2; uv4id = this.uvs.Count + 3; - this.uvs.Add (uv1.Value); - this.uvs.Add (uv2.Value); - this.uvs.Add (uv3.Value); - this.uvs.Add (uv4.Value); + this.uvs.Add (new UV { position = uv1.Value, texture = texture.Value}); + this.uvs.Add (new UV { position = uv2.Value, texture = texture.Value}); + this.uvs.Add (new UV { position = uv3.Value, texture = texture.Value}); + this.uvs.Add (new UV { position = uv4.Value, texture = texture.Value}); } this.vertices.Add ( @@ -347,7 +374,7 @@ public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Vector3 c1, this.faces.Add ( new Face { - material = material, + texture = texture, V1 = v1id, V2 = v2id, V3 = v3id, @@ -359,13 +386,20 @@ public void AddFace (Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Vector3 c1, } ); } + + struct AtlasInfo + { + public Vector2 size; + public Dictionary pieces; + } - private void ExportMaterials (string path, string modelname) + private void ExportMaterials (string path, string modelname, out Dictionary topleft) { // first write all the textures to disk // then write the mtl file using MemoryStream stream = new MemoryStream (); using StreamWriter writer = new StreamWriter (stream); + topleft = new Dictionary (); writer.WriteLine ("# CrashEdit exported material"); @@ -396,16 +430,24 @@ private void ExportMaterials (string path, string modelname) material.Value.highlight.ToString(CultureInfo.InvariantCulture) ); - if (material.Value.texture is null) + if (!this.atlas.TryGetValue (material.Key, out TextureAtlas atlas)) continue; + + string filename = $"{modelname}_{material.Key}"; writer.WriteLine( "map_Kd {0}.bmp", - material.Key + filename ); + + Dictionary topleftCurrent = new Dictionary (); + + // generate the bitmap based on the atlas + // TODO: ADD MODEL SAVENAME TO DIFFERENTIATE THE ATLAS? + atlas.BuildAtlas (out int width, out int height, out topleftCurrent).Save(path + Path.DirectorySeparatorChar + filename + ".bmp"); - // write the bitmap to a file too - material.Value.texture.Save (path + Path.DirectorySeparatorChar + material.Key + ".bmp"); + // add the textures to the information + topleft.Add (material.Key, new AtlasInfo () { size = new Vector2 (width, height), pieces = topleftCurrent}); } writer.Flush (); @@ -417,7 +459,7 @@ private void ExportMaterials (string path, string modelname) public void Export (string path, string modelname) { // first write the material file - ExportMaterials (path, modelname); + ExportMaterials (path, modelname, out Dictionary materialsTopleft); using MemoryStream stream = new MemoryStream(); using StreamWriter writer = new StreamWriter (stream); @@ -443,11 +485,26 @@ public void Export (string path, string modelname) writer.WriteLine (); writer.WriteLine ("# UVs"); - foreach (Vector2 uv in uvs) + foreach (UV uv in uvs) { + var topleft = materialsTopleft [BuildMaterialName (uv.texture.Color, uv.texture.Blend)]; + var piece = topleft.pieces.First (x => x.Key.Texinfo == uv.texture); + + // calculate new uv value + // these work from 0 to 1 + // 0,0 being the leftmost, top side, and 1,1 being the rightmost, bottom side + // as we've now got multiple textures in one + // these have to be recalculated from their slice texture to the global space + // for the model to export properly + Vector2 pieceOffset = piece.Value; + Vector2 actualPosition = new Vector2(uv.position.X, 1 - uv.position.Y) * new Vector2 (piece.Key.Data.Width, piece.Key.Data.Height) + pieceOffset; + Vector2 position = actualPosition / topleft.size; + position.Y = 1 - position.Y; + writer.WriteLine( "vt {0} {1}", - uv.X.ToString(CultureInfo.InvariantCulture), uv.Y.ToString(CultureInfo.InvariantCulture) + position.X.ToString(CultureInfo.InvariantCulture), + position.Y.ToString(CultureInfo.InvariantCulture) ); } @@ -455,18 +512,19 @@ public void Export (string path, string modelname) writer.WriteLine (); writer.WriteLine ("# Faces with textures"); - string lastmaterial = null; + VertexTexInfo? lastmaterial = null; // by default use the default material writer.WriteLine ("usemtl {0}", DEFAULT_MATERIAL); - foreach (Face face in faces.OrderBy (x => x.material)) + foreach (Face face in faces.OrderBy (x => !x.texture.HasValue ? DEFAULT_MATERIAL : BuildMaterialName (x.texture.Value.Color, x.texture.Value.Blend))) { - if (lastmaterial != face.material) + if ((!lastmaterial.HasValue && face.texture.HasValue) || + (lastmaterial.HasValue && face.texture.HasValue && (face.texture.Value.Color != lastmaterial.Value.Color || face.texture.Value.Blend != lastmaterial.Value.Blend))) { - writer.WriteLine ("usemtl {0}", face.material); + writer.WriteLine ("usemtl {0}", BuildMaterialName (face.texture.Value.Color, face.texture.Value.Blend)); - lastmaterial = face.material; + lastmaterial = face.texture; } // write face information, UVs must all be null or have value diff --git a/CrashEdit/Exporters/SceneryExtensions.cs b/CrashEdit/Exporters/SceneryExtensions.cs index ee3f685b..ba2bc1e0 100644 --- a/CrashEdit/Exporters/SceneryExtensions.cs +++ b/CrashEdit/Exporters/SceneryExtensions.cs @@ -10,7 +10,7 @@ namespace CrashEdit.Exporters; // TODO: BUT THAT WOULD CUT DOWN THE METHODS HERE TO JUST ONE OR TWO public static class SceneryExtensions { - public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) + public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) { var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); var scale = new Vector3 (1 / GameScales.WorldC1); @@ -27,7 +27,7 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEnt foreach (var polygon in scenery.Polygons) { - string material = null; + VertexTexInfo? material = null; Vector2? uv1 = null, uv2 = null, uv3 = null; OldModelStruct str = scenery.Structs[polygon.ModelStruct]; OldSceneryVertex ov1 = scenery.Vertices [polygon.VertexA]; @@ -36,30 +36,35 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, OldSceneryEnt Vector3 v1 = new Vector3 (ov1.X, ov1.Y, ov1.Z); Vector3 v2 = new Vector3 (ov2.X, ov2.Y, ov2.Z); Vector3 v3 = new Vector3 (ov3.X, ov3.Y, ov3.Z); - Vector3 color = Vector3.Zero; + Vector3 c1 = Vector3.Zero; + Vector3 c2 = Vector3.Zero; + Vector3 c3 = Vector3.Zero; if (str is OldSceneryTexture t) { int textureEID = scenery.GetTPAG (polygon.Page); - material = exporter.AddTexture (nsf, t, textureEID, ref objTranslate, out color, out uv1, out uv2, out uv3); + material = exporter.AddTexture (nsf, t, textureEID, ref textureEIDs, ref objTranslate, out uv1, out uv2, out uv3); + c1 = new Vector3 (ov1.Red, ov1.Green, ov1.Blue) / 255F; + c2 = new Vector3 (ov2.Red, ov2.Green, ov2.Blue) / 255F; + c3 = new Vector3 (ov3.Red, ov3.Green, ov3.Blue) / 255F; } else if(str is OldSceneryColor c) { - color = new Vector3 (c.R, c.G, c.B) / 255F; + c1 = c2 = c3 = new Vector3 (c.R, c.G, c.B) / 255F; } exporter.AddFace ( (v1 + offset) * scale, (v2 + offset) * scale, (v3 + offset) * scale, - color, color, color, + c1, c2, c3, material, uv1, uv2, uv3 ); } } - public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) + public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry scenery, ref Dictionary textureEIDs, ref Dictionary objTranslate) { var offset = new Vector3 (scenery.XOffset, scenery.YOffset, scenery.ZOffset); //var scale = new Vector3 (1 / GameScales.WorldC1); @@ -83,7 +88,7 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry Vector2? uv1 = null, uv2 = null, uv3 = null; - string material = exporter.AddTexture (nsf, tri, scenery, ref textureEIDs, ref objTranslate, out uv1, out uv2, out uv3); + VertexTexInfo? texture = exporter.AddTexture (nsf, tri, scenery, ref textureEIDs, ref objTranslate, out uv1, out uv2, out uv3); // add the face SceneryVertex fv1 = scenery.Vertices [tri.VertexA]; @@ -104,7 +109,7 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry (v2 * 16 + offset) / GameScales.WorldC1, (v3 * 16 + offset) / GameScales.WorldC1, c1, c2, c3, - material, + texture, uv1, uv2, uv3 ); } @@ -119,7 +124,7 @@ public static void AddScenery (this OBJExporter exporter, NSF nsf, SceneryEntry continue; Vector2? uv1 = null, uv2 = null, uv3 = null, uv4 = null; - string material = exporter.AddTexture (nsf, quad, scenery, ref textureEIDs, ref objTranslate, out uv1, out uv2, out uv3, out uv4); + VertexTexInfo? material = exporter.AddTexture (nsf, quad, scenery, ref textureEIDs, ref objTranslate, out uv1, out uv2, out uv3, out uv4); // add the face SceneryVertex fv1 = scenery.Vertices [quad.VertexA]; diff --git a/CrashEdit/Exporters/TextureAtlas.cs b/CrashEdit/Exporters/TextureAtlas.cs new file mode 100644 index 00000000..4fd127ce --- /dev/null +++ b/CrashEdit/Exporters/TextureAtlas.cs @@ -0,0 +1,104 @@ +using CrashEdit.CE; +using OpenTK.Mathematics; + +namespace CrashEdit.Exporters; + +/// +/// Helper class that simplifies the creation of texture atlas +/// optimized for export +/// +public class TextureAtlas +{ + public class TextureInfo + { + public Bitmap Data; + public VertexTexInfo Texinfo; + } + // TODO: VertexTexInfo MIGHT NOT BE THE BEST TO DISCERN DIFFERENT TEXTURES + // TODO: MAINLY BECAUSE IT HAS MORE INFORMATION THAN JUST COLOR AND BLEND MODES + // TODO: AN OPTIMIZATION MIGHT BE ADJUSTING THIS TO TAKE THAT INTO ACCOUNT + // TODO: AND THUS REDUCING THE SIZE OF THE OUTPUT + + private Dictionary originals = new Dictionary (); + + /// + /// Registers the info of a texture for the atlas to be used + /// + /// + /// + public void AddTexture (VertexTexInfo info, Bitmap data) + { + this.originals [info] = new TextureInfo {Data = data, Texinfo = info}; + } + + /// + /// The meat of the class, takes all the UV information and builds an image with all the required information + /// + public Bitmap BuildAtlas (out int width, out int height, out Dictionary topleft) + { + topleft = new Dictionary (); + width = 1024 * 4; + height = 128; + int currentX = 0; + + // width of the atlas will be a multiple of 1024 to fit multiple 1024 textures horizontally + foreach (KeyValuePair pair in originals) + { + Vector2 textureSize = TextureUtils.TextureSize (pair.Key.Color); + topleft.Add (pair.Value, new Vector2 (currentX, height - 128)); + + int nextX = currentX + (int) textureSize.X; + + if (nextX >= width) + { + currentX = 0; + height += 128; + } + else + { + currentX = nextX; + } + } + + // the last texture added filled a row, do not create an empty row + if (currentX == 0) + { + height -= 128; + } + + Bitmap result = new Bitmap (width, height); + + currentX = 0; + int currentY = 0; + + // now apply the same algorithm but copying the data out + foreach (KeyValuePair pair in originals) + { + Vector2 textureSize = TextureUtils.TextureSize (pair.Key.Color); + + // copy pixels manually + for (int x = 0; x < textureSize.X; x++) + { + for (int y = 0; y < textureSize.Y; y++) + { + result.SetPixel (x + currentX, y + currentY, pair.Value.Data.GetPixel (x, y)); + } + } + + // determine next point to get pixels from + int nextX = currentX + (int) textureSize.X; + + if (nextX >= width) + { + currentX = 0; + currentY += 128; + } + else + { + currentX = nextX; + } + } + + return result; + } +} \ No newline at end of file diff --git a/CrashEdit/Utils/TextureUtils.cs b/CrashEdit/Utils/TextureUtils.cs index 5fa0b116..0306989d 100644 --- a/CrashEdit/Utils/TextureUtils.cs +++ b/CrashEdit/Utils/TextureUtils.cs @@ -1,4 +1,5 @@ using CrashEdit.Crash; +using OpenTK.Mathematics; namespace CrashEdit; @@ -60,4 +61,14 @@ public static bool ProcessTextureInfoC2(long texture_frame, int in_tex_id, bool tex = default; return true; } + + public static Vector2 TextureSize (int colorMode) + { + return colorMode switch + { + 0 => new Vector2 (1024, 128), + 1 => new Vector2 (512, 128), + _ => new Vector2 (256, 128) + }; + } } \ No newline at end of file From e22ab63e40c260fc6f39c755d5b6c51613a82365 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Fri, 21 Feb 2025 06:46:49 +0100 Subject: [PATCH 17/17] chore: frame colors should not be detected in addtexture --- CrashEdit/Exporters/FrameExtensions.cs | 3 ++- CrashEdit/Exporters/MaterialExtensions.cs | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CrashEdit/Exporters/FrameExtensions.cs b/CrashEdit/Exporters/FrameExtensions.cs index fbd2d89e..0b993c84 100644 --- a/CrashEdit/Exporters/FrameExtensions.cs +++ b/CrashEdit/Exporters/FrameExtensions.cs @@ -42,7 +42,8 @@ public static void AddFrame (this OBJExporter exporter, NSF nsf, OldFrame frame, if (str is OldModelTexture t) { - material = exporter.AddTexture (nsf, t, t.EID, ref textureEIDs, ref objTranslate, out color, out uv1, out uv2, out uv3); + material = exporter.AddTexture (nsf, t, t.EID, ref textureEIDs, ref objTranslate, out uv1, out uv2, out uv3); + color = new Vector3(t.R, t.G, t.B) / 255F; } else if (str is OldSceneryColor c) { diff --git a/CrashEdit/Exporters/MaterialExtensions.cs b/CrashEdit/Exporters/MaterialExtensions.cs index e5919dab..35eaf26f 100644 --- a/CrashEdit/Exporters/MaterialExtensions.cs +++ b/CrashEdit/Exporters/MaterialExtensions.cs @@ -137,10 +137,8 @@ private static VertexTexInfo? FindOrAddTexture return material; } - public static VertexTexInfo? AddTexture (this OBJExporter exporter, NSF nsf, OldModelTexture t, int textureEID, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector3 color, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) + public static VertexTexInfo? AddTexture (this OBJExporter exporter, NSF nsf, OldModelTexture t, int textureEID, ref Dictionary textureEIDs, ref Dictionary objTranslate, out Vector2? uv1, out Vector2? uv2, out Vector2? uv3) { - color = new Vector3 (t.R, t.G, t.B) / 255F; - VertexTexInfo? material = exporter.FindOrAddTexture (nsf, t.ColorMode, t.BlendMode, t.ClutX, t.ClutY, (short) t.UVIndex, Convert.ToInt32 (t.N), textureEID, ref textureEIDs, ref objTranslate); Vector2 texsize = TextureUtils.TextureSize (t.ColorMode);