From 55cfcd83390d4c6219caf130b58f27dd0247ebb8 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 00:26:04 +0200 Subject: [PATCH 01/14] Added support for exporting animation frames to OBJ for Crash2 Signed-off-by: Alexis Maiquez Murcia --- .../Animation/AnimationEntryController.cs | 47 +++++ .../Controllers/Animation/FrameController.cs | 184 ++++++++++++++++++ CrashEdit/FileUtil.cs | 23 +++ 3 files changed, 254 insertions(+) diff --git a/CrashEdit/Controllers/Animation/AnimationEntryController.cs b/CrashEdit/Controllers/Animation/AnimationEntryController.cs index 9bd971fa..6681a149 100644 --- a/CrashEdit/Controllers/Animation/AnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/AnimationEntryController.cs @@ -1,3 +1,4 @@ +using System.IO; using Crash; using System.Windows.Forms; @@ -14,8 +15,54 @@ public AnimationEntryController(EntryChunkController entrychunkcontroller, Anima } InvalidateNode(); InvalidateNodeImage(); + AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); + AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); } + 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 (TreeNode node in Node.Nodes) + { + if (node.Tag 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 (TreeNode node in Node.Nodes) + { + if (node.Tag is not FrameController frame) + continue; + + string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; + File.WriteAllBytes (final, frame.ToGameOBJ ()); + id++; + } + } + public override void InvalidateNode() { Node.Text = string.Format(Crash.UI.Properties.Resources.AnimationEntryController_Text, AnimationEntry.EName); diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index d386f9ac..4d857167 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; using Crash; using System.Windows.Forms; @@ -11,8 +14,189 @@ public FrameController(AnimationEntryController animationentrycontroller, Frame Frame = frame; InvalidateNode(); InvalidateNodeImage(); + AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); + AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); } + 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 FrameVertex 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.NSF.GetEntry(Frame.ModelEID); + + obj.WriteLine("# Vertices"); + + var vertices = Frame.MakeVertices (this.AnimationEntryController.NSF); + 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; + FrameVertex 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.NSF.GetEntry(Frame.ModelEID); + + obj.WriteLine("# Vertices"); + + var vertices = Frame.MakeVertices (this.AnimationEntryController.NSF); + 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 (FrameVertex 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(); + } + } + public override void InvalidateNode() { Node.Text = Crash.UI.Properties.Resources.FrameController_Text; diff --git a/CrashEdit/FileUtil.cs b/CrashEdit/FileUtil.cs index 94cb7492..728e7bb1 100755 --- a/CrashEdit/FileUtil.cs +++ b/CrashEdit/FileUtil.cs @@ -50,6 +50,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) { if (data == null) From 1c13f79327e8c02b4bd3ad4251a86c3c77212edb Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 01:34:23 +0200 Subject: [PATCH 02/14] Applied same treatment to Crash 1's animations Signed-off-by: Alexis Maiquez Murcia --- .../Crash Formats/Animation/OldFrame.cs | 42 +--- .../ColoredAnimationEntryController.cs | 48 +++++ .../Animation/ColoredFrameController.cs | 186 ++++++++++++++++- .../Controllers/Animation/FrameController.cs | 2 +- .../Animation/OldAnimationEntryController.cs | 47 +++++ .../Animation/OldFrameController.cs | 192 +++++++++++++++++- 6 files changed, 461 insertions(+), 56 deletions(-) diff --git a/Crash/Formats/Crash Formats/Animation/OldFrame.cs b/Crash/Formats/Crash Formats/Animation/OldFrame.cs index 0e78ba4e..4b3ee70a 100755 --- a/Crash/Formats/Crash Formats/Animation/OldFrame.cs +++ b/Crash/Formats/Crash Formats/Animation/OldFrame.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Collections.Generic; +using System.Globalization; namespace Crash { @@ -168,46 +169,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 f9b85d4d..c6dbb8a3 100644 --- a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs @@ -1,3 +1,4 @@ +using System.IO; using Crash; using System.Windows.Forms; @@ -13,10 +14,57 @@ public ColoredAnimationEntryController(EntryChunkController entrychunkcontroller { AddNode(new ColoredFrameController(this, frame)); } + AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); + AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); + InvalidateNode(); InvalidateNodeImage(); } + 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 (TreeNode node in Node.Nodes) + { + if (node.Tag is not ColoredFrameController 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 (TreeNode node in Node.Nodes) + { + if (node.Tag is not ColoredFrameController frame) + continue; + + string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; + File.WriteAllBytes (final, frame.ToGameOBJ ()); + id++; + } + } + public override void InvalidateNode() { Node.Text = string.Format(Crash.UI.Properties.Resources.ColoredAnimationEntryController_Text, ColoredAnimationEntry.EName); diff --git a/CrashEdit/Controllers/Animation/ColoredFrameController.cs b/CrashEdit/Controllers/Animation/ColoredFrameController.cs index efeaea89..0562ecfd 100644 --- a/CrashEdit/Controllers/Animation/ColoredFrameController.cs +++ b/CrashEdit/Controllers/Animation/ColoredFrameController.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; using Crash; using System.Windows.Forms; @@ -9,7 +12,8 @@ public ColoredFrameController(ColoredAnimationEntryController coloranimationentr { ColorAnimationEntryController = coloranimationentrycontroller; 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); InvalidateNode(); InvalidateNodeImage(); } @@ -33,18 +37,188 @@ protected override Control CreateEditor() public ColoredAnimationEntryController ColorAnimationEntryController { get; } public OldFrame OldFrame { get; } - private void Menu_Export_OBJ() + private void Menu_Export_OBJ_Game() { - OldModelEntry modelentry = ColorAnimationEntryController.EntryChunkController.NSFController.NSF.GetEntry(OldFrame.ModelEID); - if (modelentry == null) + if (MessageBox.Show("Texture and color information will not be exported.\n\nContinue anyway?", "Export as OBJ", MessageBoxButtons.YesNo) != DialogResult.Yes) { - throw new GUIException("The linked model entry could not be found."); + return; } + 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(OldFrame.ToOBJ(modelentry), FileFilters.OBJ, FileFilters.Any); + 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 = this.ColorAnimationEntryController.NSF.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.Unknown & 0x7FFF]; + + // 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 = this.ColorAnimationEntryController.NSF.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.Unknown & 0x7FFF]; + + 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(); + } } } } diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index 4d857167..80644281 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -124,7 +124,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 dd1a94f1..ca2fb177 100755 --- a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs @@ -1,3 +1,4 @@ +using System.IO; using Crash; using System.Windows.Forms; @@ -14,8 +15,54 @@ public OldAnimationEntryController(EntryChunkController entrychunkcontroller, Ol } InvalidateNode(); InvalidateNodeImage(); + AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); + AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); } + 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 (TreeNode node in Node.Nodes) + { + if (node.Tag 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 (TreeNode node in Node.Nodes) + { + if (node.Tag is not OldFrameController frame) + continue; + + string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; + File.WriteAllBytes (final, frame.ToGameOBJ ()); + id++; + } + } + public override void InvalidateNode() { Node.Text = string.Format(Crash.UI.Properties.Resources.OldAnimationEntryController_Text, OldAnimationEntry.EName); diff --git a/CrashEdit/Controllers/Animation/OldFrameController.cs b/CrashEdit/Controllers/Animation/OldFrameController.cs index 28522996..49248630 100755 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -1,3 +1,6 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; using Crash; using System.Windows.Forms; @@ -10,7 +13,8 @@ public OldFrameController(ProtoAnimationEntryController protoanimationentrycontr ProtoAnimationEntryController = protoanimationentrycontroller; OldAnimationEntryController = null; 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); InvalidateNode(); InvalidateNodeImage(); } @@ -20,7 +24,8 @@ public OldFrameController(OldAnimationEntryController oldanimationentrycontrolle ProtoAnimationEntryController = null; OldAnimationEntryController = oldanimationentrycontroller; 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); InvalidateNode(); InvalidateNodeImage(); } @@ -65,19 +70,190 @@ protected override Control CreateEditor() public OldAnimationEntryController OldAnimationEntryController { get; } public OldFrame OldFrame { get; } - private void Menu_Export_OBJ() + private void Menu_Export_OBJ_Game() { - EntryController entry = OldAnimationEntryController != null ? OldAnimationEntryController : ProtoAnimationEntryController; - OldModelEntry modelentry = entry.EntryChunkController.NSFController.NSF.GetEntry(OldFrame.ModelEID); - if (modelentry == null) + if (MessageBox.Show("Texture and color information will not be exported.\n\nContinue anyway?", "Export as OBJ", MessageBoxButtons.YesNo) != DialogResult.Yes) { - throw new GUIException("The linked model entry could not be found."); + return; } + 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(OldFrame.ToOBJ(modelentry), FileFilters.OBJ, FileFilters.Any); + 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)) + { + EntryController controller = OldAnimationEntryController != null ? OldAnimationEntryController : ProtoAnimationEntryController; + var model = controller.NSF.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.Unknown & 0x7FFF]; + + // 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)) + { + EntryController controller = OldAnimationEntryController != null ? OldAnimationEntryController : ProtoAnimationEntryController; + var model = controller.NSF.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.Unknown & 0x7FFF]; + + 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 c812740c16d4fc38a8716077cca20eb431010937 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 18:41:47 +0200 Subject: [PATCH 03/14] Proper and close to final implementation of OBJ exporting frames Signed-off-by: Alexis Maiquez Murcia --- .../Animation/AnimationEntryController.cs | 28 +- .../ColoredAnimationEntryController.cs | 30 +- .../Animation/ColoredFrameController.cs | 252 +++++-------- .../Controllers/Animation/FrameController.cs | 296 ++++++++------- .../Animation/OldAnimationEntryController.cs | 30 +- .../Animation/OldFrameController.cs | 261 +++++-------- CrashEdit/CrashEdit.csproj | 3 +- CrashEdit/Exporters/OBJExporter.cs | 352 ++++++++++++++++++ CrashEdit/Exporters/TextureExporter.cs | 129 +++++++ 9 files changed, 829 insertions(+), 552 deletions(-) create mode 100644 CrashEdit/Exporters/OBJExporter.cs create mode 100644 CrashEdit/Exporters/TextureExporter.cs diff --git a/CrashEdit/Controllers/Animation/AnimationEntryController.cs b/CrashEdit/Controllers/Animation/AnimationEntryController.cs index 6681a149..229f8f4d 100644 --- a/CrashEdit/Controllers/Animation/AnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/AnimationEntryController.cs @@ -15,30 +15,7 @@ public AnimationEntryController(EntryChunkController entrychunkcontroller, Anima } InvalidateNode(); InvalidateNodeImage(); - AddMenu ("Export as OBJ (game geometry)", Menu_Export_OBJ_Game); - AddMenu ("Export as OBJ (processed geometry)", Menu_Export_OBJ_Processed); - } - - 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 (TreeNode node in Node.Nodes) - { - if (node.Tag is not FrameController frame) - continue; - - string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; - File.WriteAllBytes (final, frame.ToProcessedOBJ ()); - id++; - } + AddMenu ("Export as OBJ", Menu_Export_OBJ_Game); } private void Menu_Export_OBJ_Game () @@ -57,8 +34,7 @@ private void Menu_Export_OBJ_Game () if (node.Tag 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 c6dbb8a3..7e12e113 100644 --- a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs @@ -14,14 +14,13 @@ public ColoredAnimationEntryController(EntryChunkController entrychunkcontroller { AddNode(new ColoredFrameController(this, 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); InvalidateNode(); InvalidateNodeImage(); } - private void Menu_Export_OBJ_Processed() + private void Menu_Export_OBJ() { FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); @@ -37,34 +36,11 @@ private void Menu_Export_OBJ_Processed() if (node.Tag is not ColoredFrameController 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 (TreeNode node in Node.Nodes) - { - if (node.Tag is not ColoredFrameController frame) - continue; - - string final = path + Path.DirectorySeparatorChar + filename + id.ToString () + ext; - File.WriteAllBytes (final, frame.ToGameOBJ ()); - id++; - } - } - public override void InvalidateNode() { Node.Text = string.Format(Crash.UI.Properties.Resources.ColoredAnimationEntryController_Text, ColoredAnimationEntry.EName); diff --git a/CrashEdit/Controllers/Animation/ColoredFrameController.cs b/CrashEdit/Controllers/Animation/ColoredFrameController.cs index 0562ecfd..56d1fda0 100644 --- a/CrashEdit/Controllers/Animation/ColoredFrameController.cs +++ b/CrashEdit/Controllers/Animation/ColoredFrameController.cs @@ -1,8 +1,13 @@ +using System; using System.Collections.Generic; +using System.Drawing; using System.Globalization; using System.IO; +using System.Linq; using Crash; using System.Windows.Forms; +using CrashEdit.Exporters; +using OpenTK; namespace CrashEdit { @@ -12,8 +17,7 @@ public ColoredFrameController(ColoredAnimationEntryController coloranimationentr { ColorAnimationEntryController = coloranimationentrycontroller; 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); InvalidateNode(); InvalidateNodeImage(); } @@ -37,188 +41,108 @@ protected override Control CreateEditor() public ColoredAnimationEntryController ColorAnimationEntryController { get; } public OldFrame OldFrame { get; } - private void Menu_Export_OBJ_Game() + private void Menu_Export_OBJ() { - 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.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() + public void ToOBJ(string path, string modelname) { - if (MessageBox.Show("Texture and color information will not be exported.\n\nContinue anyway?", "Export as OBJ", MessageBoxButtons.YesNo) != DialogResult.Yes) + var exporter = new OBJExporter (); + var model = this.ColorAnimationEntryController.NSF.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) { - return; + if (str is not OldModelTexture tex) + continue; + + if (textureEIDs.ContainsKey (tex.EID)) + continue; + + textureEIDs [tex.EID] = textureEIDs.Count; } - 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 = this.ColorAnimationEntryController.NSF.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); + 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.Unknown & 0x7FFF]; + OldFrameVertex ov1 = OldFrame.Vertices [polygon.VertexA / 6]; + OldFrameVertex ov2 = OldFrame.Vertices [polygon.VertexB / 6]; + OldFrameVertex ov3 = OldFrame.Vertices [polygon.VertexC / 6]; - var list = new List (); + 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; - foreach (OldModelPolygon polygon in model.Polygons) - { - OldModelStruct str = model.Structs[polygon.Unknown & 0x7FFF]; - - // 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); - } - } + if (str is OldModelTexture t) + { + color = new Vector3 (t.R, t.G, t.B) / 255F; - 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) - ); - } + // 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; - foreach (ProcessedEntries vertex in list) + if (material is null) { - obj.WriteLine("vn {0} {1} {2}", - vertex.NX.ToString(CultureInfo.InvariantCulture), - vertex.NY.ToString(CultureInfo.InvariantCulture), - vertex.NZ.ToString(CultureInfo.InvariantCulture) + 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] ); - } - - 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 = this.ColorAnimationEntryController.NSF.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 }); + var tpag = this.ColorAnimationEntryController.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; } - - // update color information - foreach (OldModelPolygon polygon in model.Polygons) - { - OldModelStruct str = model.Structs[polygon.Unknown & 0x7FFF]; - 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) + 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); } - return stream.ToArray(); + 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); } } } diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index 80644281..7db2a11e 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -1,8 +1,14 @@ +using System; using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; using System.Globalization; using System.IO; +using System.Linq; using Crash; using System.Windows.Forms; +using CrashEdit.Exporters; +using OpenTK; namespace CrashEdit { @@ -14,187 +20,201 @@ public FrameController(AnimationEntryController animationentrycontroller, Frame Frame = frame; InvalidateNode(); InvalidateNodeImage(); - 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); } - 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 FrameVertex 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 = this.AnimationEntryController.NSF.GetEntry(Frame.ModelEID); + var vertices = Frame.MakeVertices (this.AnimationEntryController.NSF); + 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.NSF.GetEntry(Frame.ModelEID); - - obj.WriteLine("# Vertices"); + int tpag_eid = model.GetTPAG (i); - var vertices = Frame.MakeVertices (this.AnimationEntryController.NSF); - 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; - FrameVertex 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 = this.AnimationEntryController.NSF.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)) { - obj.WriteLine ("f {0} {1} {2}", i + 1, i + 2, i + 3); + 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 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]]; + FrameVertex fv1 = vertices [tri.Vertex [!flip ? 0 : 2] + Frame.SpecialVertexCount]; + FrameVertex fv2 = vertices [tri.Vertex [!flip ? 1 : 1] + Frame.SpecialVertexCount]; + FrameVertex 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.NSF.GetEntry(Frame.ModelEID); - - obj.WriteLine("# Vertices"); - - var vertices = Frame.MakeVertices (this.AnimationEntryController.NSF); - 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 (FrameVertex 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); } public override void InvalidateNode() diff --git a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs index ca2fb177..ffb49677 100755 --- a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs @@ -15,11 +15,10 @@ public OldAnimationEntryController(EntryChunkController entrychunkcontroller, Ol } InvalidateNode(); InvalidateNodeImage(); - 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); } - private void Menu_Export_OBJ_Processed() + private void Menu_Export_OBJ () { FileUtil.SelectSaveFile (out string output, FileFilters.OBJ, FileFilters.Any); @@ -35,30 +34,7 @@ private void Menu_Export_OBJ_Processed() if (node.Tag 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 (TreeNode node in Node.Nodes) - { - if (node.Tag 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 49248630..aff629c1 100755 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -1,8 +1,14 @@ +using System; using System.Collections.Generic; +using System.Drawing; using System.Globalization; using System.IO; +using System.Linq; using Crash; using System.Windows.Forms; +using CrashEdit.Exporters; +using OpenTK; +using OpenTK.Graphics.OpenGL; namespace CrashEdit { @@ -13,8 +19,7 @@ public OldFrameController(ProtoAnimationEntryController protoanimationentrycontr ProtoAnimationEntryController = protoanimationentrycontroller; OldAnimationEntryController = null; 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); InvalidateNode(); InvalidateNodeImage(); } @@ -24,8 +29,7 @@ public OldFrameController(OldAnimationEntryController oldanimationentrycontrolle ProtoAnimationEntryController = null; OldAnimationEntryController = oldanimationentrycontroller; 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 (game geometry)", Menu_Export_OBJ); InvalidateNode(); InvalidateNodeImage(); } @@ -70,190 +74,109 @@ protected override Control CreateEditor() public OldAnimationEntryController OldAnimationEntryController { get; } public OldFrame OldFrame { get; } - private void Menu_Export_OBJ_Game() + private void Menu_Export_OBJ() { - 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.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; + FileUtil.SelectSaveFile (out string filename, FileFilters.OBJ, FileFilters.Any); + ToOBJ (Path.GetDirectoryName (filename), Path.GetFileNameWithoutExtension (filename)); } - - public byte[] ToProcessedOBJ() - { - using (MemoryStream stream = new MemoryStream()) - { - using (StreamWriter obj = new StreamWriter(stream)) - { - EntryController controller = OldAnimationEntryController != null ? OldAnimationEntryController : ProtoAnimationEntryController; - var model = controller.NSF.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); + public void ToOBJ(string path, string modelname) + { + var exporter = new OBJExporter (); + EntryController controller = OldAnimationEntryController != null ? OldAnimationEntryController : ProtoAnimationEntryController; + var model = controller.NSF.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); - var list = new List (); + // detect how many textures are used an ther eids to prepare the image + Dictionary textureEIDs = new (); - foreach (OldModelPolygon polygon in model.Polygons) - { - OldModelStruct str = model.Structs[polygon.Unknown & 0x7FFF]; - - // add the three polygons to the list - int [] vertices = new [] {polygon.VertexA / 6, polygon.VertexB / 6, polygon.VertexC / 6}; + foreach (OldModelStruct str in model.Structs) + { + if (str is not OldModelTexture tex) + continue; - foreach (int vertex in vertices) - { - ProcessedEntries entry = new ProcessedEntries (); - - if (str is OldSceneryColor color) - entry.color = color; + if (textureEIDs.ContainsKey (tex.EID)) + continue; - 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(); + 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.Unknown & 0x7FFF]; + 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) { - EntryController controller = OldAnimationEntryController != null ? OldAnimationEntryController : ProtoAnimationEntryController; - var model = controller.NSF.GetEntry(OldFrame.ModelEID); + color = new Vector3 (t.R, t.G, t.B) / 255F; - // 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 }); - } + // 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.Unknown & 0x7FFF]; - - 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 = controller.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; } - - 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); } - return stream.ToArray(); + 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); } } } diff --git a/CrashEdit/CrashEdit.csproj b/CrashEdit/CrashEdit.csproj index 78c79d8e..0846f0eb 100755 --- a/CrashEdit/CrashEdit.csproj +++ b/CrashEdit/CrashEdit.csproj @@ -121,6 +121,8 @@ UserControl + + Form @@ -568,7 +570,6 @@ false - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 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 91fae00a6f64da3c8d8bb6ac0e7b719d0a01f27b Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 22:16:02 +0200 Subject: [PATCH 04/14] Added support for quads on exporter Added OBJ export to normal scenery entries Signed-off-by: Alexis Maiquez Murcia --- .../Controllers/Animation/FrameController.cs | 8 +- .../Animation/OldFrameController.cs | 1 - .../Scenery/SceneryEntryController.cs | 250 +++++++++++++++++- CrashEdit/Exporters/OBJExporter.cs | 214 +++++++++++++-- 4 files changed, 447 insertions(+), 26 deletions(-) diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index fe3e4be7..8029f6ad 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -42,7 +42,7 @@ public void ToOBJ (string path, string modelname) { var exporter = new OBJExporter (); var model = this.AnimationEntryController.NSF.GetEntry(Frame.ModelEID); - var vertices = Frame.MakeVertices (this.AnimationEntryController.NSF); + 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); @@ -136,9 +136,9 @@ public void ToOBJ (string path, string modelname) 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]]; - FrameVertex fv1 = vertices [tri.Vertex [!flip ? 0 : 2] + Frame.SpecialVertexCount]; - FrameVertex fv2 = vertices [tri.Vertex [!flip ? 1 : 1] + Frame.SpecialVertexCount]; - FrameVertex fv3 = vertices [tri.Vertex [!flip ? 2 : 0] + Frame.SpecialVertexCount]; + 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); diff --git a/CrashEdit/Controllers/Animation/OldFrameController.cs b/CrashEdit/Controllers/Animation/OldFrameController.cs index 3169af9c..5119612f 100644 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -100,7 +100,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 23f8d4c5..a02345a2 100755 --- a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs @@ -1,5 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; using Crash; using System.Windows.Forms; +using CrashEdit.Exporters; +using OpenTK; namespace CrashEdit { @@ -35,15 +42,246 @@ protected 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) @@ -61,7 +299,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 a2c2dee0f559e088eefd0fee15eda2604bc2cb25 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 22:20:29 +0200 Subject: [PATCH 05/14] 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 77dfc1e7..4db1eccd 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 (SceneryVertex 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 (SceneryVertex 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 a02345a2..c21307bd 100755 --- a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs @@ -17,7 +17,6 @@ public SceneryEntryController(EntryChunkController entrychunkcontroller, Scenery 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); InvalidateNode(); @@ -282,24 +281,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 b380f342bf734d3c48c567e5d0f3d8b223d63970 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 22:38:51 +0200 Subject: [PATCH 06/14] 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 | 264 ++++++++++++++++++++++ 3 files changed, 276 insertions(+), 1 deletion(-) diff --git a/Crash.UI/Properties/Resources.Designer.cs b/Crash.UI/Properties/Resources.Designer.cs index afe29e40..dd5a9284 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 6762236c..3d9e139e 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -1,7 +1,13 @@ using Crash; using System; using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; using System.Windows.Forms; +using CrashEdit.Exporters; +using DiscUtils; +using OpenTK; namespace CrashEdit { @@ -46,6 +52,8 @@ public NSFController(NSF nsf, GameVersion gameversion) AddMenuSeparator(); AddMenu(Crash.UI.Properties.Resources.NSFController_AcShowLevel, Menu_ShowLevelC2); AddMenu(Crash.UI.Properties.Resources.NSFController_AcShowLevelZones, Menu_ShowLevelZonesC2); + AddMenuSeparator (); + AddMenu (Crash.UI.Properties.Resources.NSFController_AcExportScenery, Menu_ExportSceneryOBJ); } InvalidateNode(); InvalidateNodeImage(); @@ -361,6 +369,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 fe7eb5f23b2b8ad512fe0b0a58c61f0d20f34916 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 23:42:45 +0200 Subject: [PATCH 07/14] Cleanup of duplicated code Signed-off-by: Alexis Maiquez Murcia --- .../Animation/AnimationEntryController.cs | 3 +- .../ColoredAnimationEntryController.cs | 3 +- .../Animation/ColoredFrameController.cs | 4 +- .../Controllers/Animation/FrameController.cs | 64 ++---------------- .../Animation/OldAnimationEntryController.cs | 3 +- .../Animation/OldFrameController.cs | 4 +- CrashEdit/Controllers/NSFController.cs | 67 ++----------------- .../Scenery/SceneryEntryController.cs | 66 ++---------------- CrashEdit/Controls/3D/AnimationEntryViewer.cs | 2 +- CrashEdit/Controls/3D/GLViewer.cs | 50 -------------- CrashEdit/Controls/3D/SceneryEntryViewer.cs | 4 +- CrashEdit/CrashEdit.csproj | 1 + CrashEdit/Utils/TextureUtils.cs | 58 ++++++++++++++++ 13 files changed, 89 insertions(+), 240 deletions(-) create mode 100644 CrashEdit/Utils/TextureUtils.cs diff --git a/CrashEdit/Controllers/Animation/AnimationEntryController.cs b/CrashEdit/Controllers/Animation/AnimationEntryController.cs index 64982202..c470dcad 100644 --- a/CrashEdit/Controllers/Animation/AnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/AnimationEntryController.cs @@ -20,7 +20,8 @@ public AnimationEntryController(EntryChunkController entrychunkcontroller, Anima private void Menu_Export_OBJ_Game () { - 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 7e12e113..cc822c9f 100644 --- a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs @@ -22,7 +22,8 @@ public ColoredAnimationEntryController(EntryChunkController entrychunkcontroller 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 index 56d1fda0..a739c363 100644 --- a/CrashEdit/Controllers/Animation/ColoredFrameController.cs +++ b/CrashEdit/Controllers/Animation/ColoredFrameController.cs @@ -43,7 +43,9 @@ protected 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)); } diff --git a/CrashEdit/Controllers/Animation/FrameController.cs b/CrashEdit/Controllers/Animation/FrameController.cs index 8029f6ad..1378accf 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -25,7 +25,9 @@ public FrameController(AnimationEntryController animationentrycontroller, Frame 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)); } @@ -73,7 +75,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; @@ -159,64 +161,6 @@ 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); - } - public override void InvalidateNode() { Node.Text = Crash.UI.Properties.Resources.FrameController_Text; diff --git a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs index 391cf6fc..c500eff1 100755 --- a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs @@ -20,7 +20,8 @@ public OldAnimationEntryController(EntryChunkController entrychunkcontroller, Ol 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 5119612f..d8e90736 100644 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -65,7 +65,9 @@ protected 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)); } diff --git a/CrashEdit/Controllers/NSFController.cs b/CrashEdit/Controllers/NSFController.cs index 3d9e139e..997a835e 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -371,7 +371,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)); } @@ -407,7 +409,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; @@ -487,7 +489,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; @@ -567,64 +569,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 c21307bd..f903dbcd 100755 --- a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs @@ -43,7 +43,9 @@ protected 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)); } @@ -70,7 +72,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; @@ -143,7 +145,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; @@ -223,64 +225,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/AnimationEntryViewer.cs b/CrashEdit/Controls/3D/AnimationEntryViewer.cs index ac47ab26..a299fad4 100644 --- a/CrashEdit/Controls/3D/AnimationEntryViewer.cs +++ b/CrashEdit/Controls/3D/AnimationEntryViewer.cs @@ -225,7 +225,7 @@ private void RenderFrame(Frame frame, int buf) } foreach (var tri in model.Triangles) { - var polygon_texture_info = ProcessTextureInfoC2(tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures); + var polygon_texture_info = TextureUtils.ProcessTextureInfoC2 (this.render.RealCurrentFrame, tri.Texture, tri.Animated, model.Textures, model.AnimatedTextures); if (!polygon_texture_info.Item1) continue; bool nocull = tri.Subtype == 0 || tri.Subtype == 2; diff --git a/CrashEdit/Controls/3D/GLViewer.cs b/CrashEdit/Controls/3D/GLViewer.cs index 6b7ae3d0..a1302aad 100644 --- a/CrashEdit/Controls/3D/GLViewer.cs +++ b/CrashEdit/Controls/3D/GLViewer.cs @@ -908,56 +908,6 @@ protected void SetupTPAGs(Dictionary tex_eids) } } - 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)((render.RealCurrentFrame / 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); - } - protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); diff --git a/CrashEdit/Controls/3D/SceneryEntryViewer.cs b/CrashEdit/Controls/3D/SceneryEntryViewer.cs index 03694252..a47ba11f 100644 --- a/CrashEdit/Controls/3D/SceneryEntryViewer.cs +++ b/CrashEdit/Controls/3D/SceneryEntryViewer.cs @@ -133,7 +133,7 @@ protected void RenderWorld(SceneryEntry world, Dictionary tex_eids) foreach (SceneryTriangle tri in world.Triangles) { if (tri.VertexA >= world.Vertices.Count || tri.VertexB >= world.Vertices.Count || tri.VertexC >= world.Vertices.Count) continue; - var polygon_texture_info = ProcessTextureInfoC2(tri.Texture, tri.Animated, world.Textures, world.AnimatedTextures); + var polygon_texture_info = TextureUtils.ProcessTextureInfoC2(render.RealCurrentFrame, tri.Texture, tri.Animated, world.Textures, world.AnimatedTextures); if (!polygon_texture_info.Item1) continue; int tex = 0; // completely untextured @@ -156,7 +156,7 @@ protected void RenderWorld(SceneryEntry world, Dictionary tex_eids) foreach (SceneryQuad quad in world.Quads) { if (quad.VertexA >= world.Vertices.Count || quad.VertexB >= world.Vertices.Count || quad.VertexC >= world.Vertices.Count || quad.VertexD >= world.Vertices.Count) continue; - var polygon_texture_info = ProcessTextureInfoC2(quad.Texture, quad.Animated, world.Textures, world.AnimatedTextures); + var polygon_texture_info = TextureUtils.ProcessTextureInfoC2(render.RealCurrentFrame, quad.Texture, quad.Animated, world.Textures, world.AnimatedTextures); if (!polygon_texture_info.Item1) continue; int tex = 0; // completely untextured diff --git a/CrashEdit/CrashEdit.csproj b/CrashEdit/CrashEdit.csproj index fb23b641..12cc0224 100755 --- a/CrashEdit/CrashEdit.csproj +++ b/CrashEdit/CrashEdit.csproj @@ -355,6 +355,7 @@ + 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 f9b75cc887119bb6b9dde8e0701d1ec0570f3ba6 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Mon, 10 Apr 2023 23:48:54 +0200 Subject: [PATCH 08/14] 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 1378accf..0ba91494 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -85,13 +85,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 @@ -99,10 +100,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 997a835e..c31480ba 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -390,8 +390,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); @@ -428,12 +427,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); @@ -496,13 +497,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 @@ -510,10 +512,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 f903dbcd..64a6e3b8 100755 --- a/CrashEdit/Controllers/Scenery/SceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/SceneryEntryController.cs @@ -91,12 +91,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); @@ -152,13 +154,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 @@ -166,10 +169,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 7a1b9f8d07ef64a12f7498b3fa296900b3e87064 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Tue, 11 Apr 2023 00:21:45 +0200 Subject: [PATCH 09/14] Added scenery export to Crash 1 Signed-off-by: Alexis Maiquez Murcia --- CrashEdit/Controllers/NSFController.cs | 115 +++++++++++++++++- .../Scenery/OldSceneryEntryController.cs | 10 -- 2 files changed, 111 insertions(+), 14 deletions(-) diff --git a/CrashEdit/Controllers/NSFController.cs b/CrashEdit/Controllers/NSFController.cs index c31480ba..70434028 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -40,6 +40,7 @@ public NSFController(NSF nsf, GameVersion gameversion) AddMenuSeparator(); AddMenu(Crash.UI.Properties.Resources.NSFController_AcShowLevel, Menu_ShowLevelC1); AddMenu(Crash.UI.Properties.Resources.NSFController_AcShowLevelZones, Menu_ShowLevelZonesC1); + AddMenu (Crash.UI.Properties.Resources.NSFController_AcExportScenery, Menu_ExportSceneryC1OBJ); } else if (GameVersion == GameVersion.Crash1Beta1995) { @@ -53,7 +54,7 @@ public NSFController(NSF nsf, GameVersion gameversion) AddMenu(Crash.UI.Properties.Resources.NSFController_AcShowLevel, Menu_ShowLevelC2); AddMenu(Crash.UI.Properties.Resources.NSFController_AcShowLevelZones, Menu_ShowLevelZonesC2); AddMenuSeparator (); - AddMenu (Crash.UI.Properties.Resources.NSFController_AcExportScenery, Menu_ExportSceneryOBJ); + AddMenu (Crash.UI.Properties.Resources.NSFController_AcExportScenery, Menu_ExportSceneryC2OBJ); } InvalidateNode(); InvalidateNodeImage(); @@ -369,15 +370,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 (); @@ -571,7 +580,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 178aefbb..3cbbf876 100755 --- a/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs +++ b/CrashEdit/Controllers/Scenery/OldSceneryEntryController.cs @@ -14,7 +14,6 @@ public OldSceneryEntryController(EntryChunkController entrychunkcontroller, OldS } AddMenuSeparator(); AddMenu("Export as OBJ", Menu_Export_OBJ); - AddMenu("Export as COLLADA", Menu_Export_COLLADA); InvalidateNode(); InvalidateNodeImage(); } @@ -45,14 +44,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 9000a956f176e78fd252847aa655704984e2d9fc Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Tue, 11 Apr 2023 00:43:27 +0200 Subject: [PATCH 10/14] 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/CrashEdit.csproj | 2 + CrashEdit/Exporters/FrameExtensions.cs | 219 ++++++++++++++ CrashEdit/Exporters/SceneryExtensions.cs | 283 ++++++++++++++++++ 6 files changed, 512 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 0ba91494..8fbcd099 100644 --- a/CrashEdit/Controllers/Animation/FrameController.cs +++ b/CrashEdit/Controllers/Animation/FrameController.cs @@ -42,123 +42,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 d8e90736..c4bdf326 100644 --- a/CrashEdit/Controllers/Animation/OldFrameController.cs +++ b/CrashEdit/Controllers/Animation/OldFrameController.cs @@ -73,98 +73,12 @@ private void Menu_Export_OBJ() public void ToOBJ(string path, string modelname) { - var exporter = new OBJExporter (); - var model = OldAnimationEntryController.NSF.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.Unknown & 0x7FFF]; - 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 = OldAnimationEntryController.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 - ); - } - + 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 70434028..14f1036b 100755 --- a/CrashEdit/Controllers/NSFController.cs +++ b/CrashEdit/Controllers/NSFController.cs @@ -397,185 +397,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); @@ -592,88 +414,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/CrashEdit.csproj b/CrashEdit/CrashEdit.csproj index 12cc0224..2f775de3 100755 --- a/CrashEdit/CrashEdit.csproj +++ b/CrashEdit/CrashEdit.csproj @@ -124,7 +124,9 @@ UserControl + + 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 76a84cfd18d274f6be4ce679cde9c4e9a6075532 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Tue, 11 Apr 2023 01:07:48 +0200 Subject: [PATCH 11/14] More cleanup and simplification of exporters Signed-off-by: Alexis Maiquez Murcia --- CrashEdit/CrashEdit.csproj | 1 + CrashEdit/Exporters/FrameExtensions.cs | 111 +-------- CrashEdit/Exporters/MaterialExtensions.cs | 270 ++++++++++++++++++++++ CrashEdit/Exporters/SceneryExtensions.cs | 143 +----------- 4 files changed, 289 insertions(+), 236 deletions(-) create mode 100644 CrashEdit/Exporters/MaterialExtensions.cs diff --git a/CrashEdit/CrashEdit.csproj b/CrashEdit/CrashEdit.csproj index 2f775de3..b752b226 100755 --- a/CrashEdit/CrashEdit.csproj +++ b/CrashEdit/CrashEdit.csproj @@ -125,6 +125,7 @@ + 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 ba5d5adcea0a872e2433720adc5e09c73775cb89 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Wed, 12 Apr 2023 21:59:28 +0200 Subject: [PATCH 12/14] 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 8482f374db92bcc40767e1d462412f31911e7ea5 Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Thu, 13 Apr 2023 03:00:14 +0200 Subject: [PATCH 13/14] 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 9c8760129426e80430aa7458d0671f88319b687e Mon Sep 17 00:00:00 2001 From: Alexis Maiquez Murcia Date: Sat, 15 Apr 2023 04:35:23 +0200 Subject: [PATCH 14/14] 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 c470dcad..75166654 100644 --- a/CrashEdit/Controllers/Animation/AnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/AnimationEntryController.cs @@ -29,13 +29,14 @@ private void Menu_Export_OBJ_Game () string path = Path.GetDirectoryName (output); int id = 0; + int count = Node.Nodes.Count.ToString().Length; foreach (TreeNode node in Node.Nodes) { if (node.Tag 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 cc822c9f..300090fc 100644 --- a/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/ColoredAnimationEntryController.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 (TreeNode node in Node.Nodes) { if (node.Tag is not ColoredFrameController 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 c500eff1..f72f2b10 100755 --- a/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs +++ b/CrashEdit/Controllers/Animation/OldAnimationEntryController.cs @@ -29,13 +29,14 @@ private void Menu_Export_OBJ () string path = Path.GetDirectoryName (output); int id = 0; + int count = Node.Nodes.Count.ToString().Length; foreach (TreeNode node in Node.Nodes) { if (node.Tag 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