diff --git a/Editor/VramEditorWindow.cs b/Editor/VramEditorWindow.cs index bb57fb3..0865eee 100644 --- a/Editor/VramEditorWindow.cs +++ b/Editor/VramEditorWindow.cs @@ -8,13 +8,6 @@ using UnityEngine.Rendering; public class VRAMEditorWindow : EditorWindow { - class ProhibitedArea - { - public int X; - public int Y; - public int Width; - public int Height; - } private const int VramWidth = 1024; private const int VramHeight = 512; @@ -28,6 +21,9 @@ public class VRAMEditorWindow : EditorWindow private Color bufferColor2 = new Color(0, 1, 0, 0.5f); private Color prohibitedColor = new Color(1, 0, 0, 0.3f); + private static string _psxDataPath = "Assets/PSXData.asset"; + private PSXData _psxData; + private static readonly Vector2[] resolutions = { new Vector2(256, 240), new Vector2(256, 480), @@ -50,6 +46,8 @@ public class VRAMEditorWindow : EditorWindow vramImage.SetPixelData(blackPixels, 0); vramImage.Apply(); blackPixels.Dispose(); + + LoadData(); } public static void PasteTexture(Texture2D baseTexture, Texture2D overlayTexture, int posX, int posY) @@ -73,7 +71,7 @@ public class VRAMEditorWindow : EditorWindow for (int x = 0; x < overlayWidth; x++) { int baseX = posX + x; - int baseY = posY + y; + int baseY = posY + y; if (baseX >= 0 && baseX < baseWidth && baseY >= 0 && baseY < baseHeight) { int baseIndex = baseY * baseWidth + baseX; @@ -102,26 +100,21 @@ public class VRAMEditorWindow : EditorWindow exp.CreatePSXTexture2D(); } - List dontPackAreas = new List(); - foreach (ProhibitedArea area in prohibitedAreas) - { - dontPackAreas.Add(new Rect(area.X, area.Y, area.Width, area.Height)); - } - Rect buffer1 = new Rect(0, 0, selectedResolution.x, selectedResolution.y); Rect buffer2 = verticalLayout ? new Rect(0, 256, selectedResolution.x, selectedResolution.y) : new Rect(selectedResolution.x, 0, selectedResolution.x, selectedResolution.y); - dontPackAreas.Add(buffer1); + List framebuffers = new List { buffer1 }; if (dualBuffering) { - dontPackAreas.Add(buffer2); + framebuffers.Add(buffer2); } - VRAMPacker tp = new VRAMPacker(dontPackAreas); + VRAMPacker tp = new VRAMPacker(framebuffers, prohibitedAreas); var packed = tp.PackTexturesIntoVRAM(objects); + for (int y = 0; y < VramHeight; y++) { for (int x = 0; x < VramWidth; x++) @@ -190,10 +183,10 @@ public class VRAMEditorWindow : EditorWindow if (GUILayout.Button("Remove")) { prohibitedAreas.RemoveAt(i); - break; // Avoid out-of-bounds errors after removal + break; } - prohibitedAreas[i] = area; // Update the list with edited values + prohibitedAreas[i] = area; GUILayout.Space(10); } @@ -204,7 +197,6 @@ public class VRAMEditorWindow : EditorWindow prohibitedAreas.Add(new ProhibitedArea()); } - // New "Pack Textures" Button if (GUILayout.Button("Pack Textures")) { PackTextures(); @@ -235,5 +227,38 @@ public class VRAMEditorWindow : EditorWindow } GUILayout.EndHorizontal(); + StoreData(); + } + + private void LoadData() + { + _psxData = AssetDatabase.LoadAssetAtPath(_psxDataPath); + + if (!_psxData) + { + _psxData = CreateInstance(); + AssetDatabase.CreateAsset(_psxData, _psxDataPath); + AssetDatabase.SaveAssets(); + } + + selectedResolution = _psxData.OutputResolution; + dualBuffering = _psxData.DualBuffering; + verticalLayout = _psxData.VerticalBuffering; + prohibitedAreas = _psxData.ProhibitedAreas; + } + + private void StoreData() + { + if (_psxData != null) + { + _psxData.OutputResolution = selectedResolution; + _psxData.DualBuffering = dualBuffering; + _psxData.VerticalBuffering = verticalLayout; + _psxData.ProhibitedAreas = prohibitedAreas; + + EditorUtility.SetDirty(_psxData); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } } } \ No newline at end of file diff --git a/Runtime/PSXData.cs b/Runtime/PSXData.cs new file mode 100644 index 0000000..b23df5d --- /dev/null +++ b/Runtime/PSXData.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using PSXSplash.RuntimeCode; +using UnityEngine; + +[CreateAssetMenu(fileName = "PSXData", menuName = "Scriptable Objects/PSXData")] +public class PSXData : ScriptableObject +{ + public Vector2 OutputResolution = new Vector2(320, 240); + public bool DualBuffering = true; + public bool VerticalBuffering = true; + public List ProhibitedAreas = new List(); +} diff --git a/Runtime/PSXData.cs.meta b/Runtime/PSXData.cs.meta new file mode 100644 index 0000000..a1b124a --- /dev/null +++ b/Runtime/PSXData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cbd8c66199e036896848ce1569567dd6 \ No newline at end of file diff --git a/Runtime/PSXMesh.cs b/Runtime/PSXMesh.cs index bb234f3..809f762 100644 --- a/Runtime/PSXMesh.cs +++ b/Runtime/PSXMesh.cs @@ -3,7 +3,6 @@ using UnityEngine; namespace PSXSplash.RuntimeCode { - public struct PSXVertex { public short vx, vy, vz; @@ -22,7 +21,7 @@ namespace PSXSplash.RuntimeCode { public List Triangles; - public static PSXMesh CreateFromUnityMesh(Mesh mesh, int textureWidth, int textureHeight) + public static PSXMesh CreateFromUnityMesh(Mesh mesh, int textureWidth = 256, int textureHeight = 256, Transform transform = null) { PSXMesh psxMesh = new PSXMesh { Triangles = new List() }; @@ -36,11 +35,16 @@ namespace PSXSplash.RuntimeCode int vid1 = indices[i + 1]; int vid2 = indices[i + 2]; - PSXVertex v0 = ConvertToPSXVertex(vertices[vid0], uv[vid0], textureWidth, textureHeight); - PSXVertex v1 = ConvertToPSXVertex(vertices[vid1], uv[vid1], textureWidth, textureHeight); - PSXVertex v2 = ConvertToPSXVertex(vertices[vid2], uv[vid2], textureWidth, textureHeight); + // Convert to world space only if a transform is provided + Vector3 v0 = transform ? transform.TransformPoint(vertices[vid0]) : vertices[vid0]; + Vector3 v1 = transform ? transform.TransformPoint(vertices[vid1]) : vertices[vid1]; + Vector3 v2 = transform ? transform.TransformPoint(vertices[vid2]) : vertices[vid2]; - psxMesh.Triangles.Add(new Tri { v0 = v0, v1 = v1, v2 = v2 }); + PSXVertex psxV0 = ConvertToPSXVertex(v0, uv[vid0], textureWidth, textureHeight); + PSXVertex psxV1 = ConvertToPSXVertex(v1, uv[vid1], textureWidth, textureHeight); + PSXVertex psxV2 = ConvertToPSXVertex(v2, uv[vid2], textureWidth, textureHeight); + + psxMesh.Triangles.Add(new Tri { v0 = psxV0, v1 = psxV1, v2 = psxV2 }); } return psxMesh; @@ -51,12 +55,12 @@ namespace PSXSplash.RuntimeCode PSXVertex psxVertex = new PSXVertex { vx = (short)(Mathf.Clamp(vertex.x, -4f, 3.999f) * 4096), - vy = (short)(Mathf.Clamp(vertex.y, -4f, 3.999f) * 4096), + vy = (short)(Mathf.Clamp(-vertex.y, -4f, 3.999f) * 4096), vz = (short)(Mathf.Clamp(vertex.z, -4f, 3.999f) * 4096), - u = (byte)(Mathf.Clamp(uv.x * textureWidth, 0, 255)), - v = (byte)(Mathf.Clamp((1.0f - uv.y) * textureHeight, 0, 255)) + u = (byte)(Mathf.Clamp((uv.x * (textureWidth-1)), 0, 255)), + v = (byte)(Mathf.Clamp(((1.0f - uv.y) * (textureHeight-1)), 0, 255)) }; return psxVertex; } } -} \ No newline at end of file +} diff --git a/Runtime/PSXObjectExporter.cs b/Runtime/PSXObjectExporter.cs index 53d8099..db959b5 100644 --- a/Runtime/PSXObjectExporter.cs +++ b/Runtime/PSXObjectExporter.cs @@ -5,6 +5,7 @@ namespace PSXSplash.RuntimeCode public class PSXObjectExporter : MonoBehaviour { public PSXBPP BitDepth; + public bool MeshIsStatic = true; [HideInInspector] public PSXTexture2D Texture; @@ -27,7 +28,12 @@ namespace PSXSplash.RuntimeCode MeshFilter meshFilter = gameObject.GetComponent(); if (meshFilter != null) { - Mesh = PSXMesh.CreateFromUnityMesh(meshFilter.mesh, Texture.Width, Texture.Height); + if(MeshIsStatic) { + Mesh = PSXMesh.CreateFromUnityMesh(meshFilter.sharedMesh, Texture.Width, Texture.Height, transform); + } + else { + Mesh = PSXMesh.CreateFromUnityMesh(meshFilter.sharedMesh, Texture.Width, Texture.Height); + } } } } diff --git a/Runtime/PSXSceneExporter.cs b/Runtime/PSXSceneExporter.cs index 66cfdd2..597ae68 100644 --- a/Runtime/PSXSceneExporter.cs +++ b/Runtime/PSXSceneExporter.cs @@ -1,34 +1,128 @@ +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEditor.Overlays; using UnityEngine; using UnityEngine.SceneManagement; namespace PSXSplash.RuntimeCode { + + [ExecuteInEditMode] public class PSXSceneExporter : MonoBehaviour { + private PSXObjectExporter[] _exporters; + + private PSXData _psxData; + private readonly string _psxDataPath = "Assets/PSXData.asset"; + + private Vector2 selectedResolution; + private bool dualBuffering; + private bool verticalLayout; + private List prohibitedAreas; + private VRAMPixel[,] vramPixels; + + + public void Export() { - Debug.Log($"Exporting scene: {SceneManager.GetActiveScene().name}"); - - Scene activeScene = SceneManager.GetActiveScene(); - foreach (GameObject obj in activeScene.GetRootGameObjects()) + LoadData(); + _exporters = FindObjectsByType(FindObjectsSortMode.None); + foreach (PSXObjectExporter exp in _exporters) { - ExportAllPSXExporters(obj.transform); + exp.CreatePSXTexture2D(); + exp.CreatePSXMesh(); } + PackTextures(); + ExportFile(); } - void ExportAllPSXExporters(Transform parentTransform) + void PackTextures() { - PSXObjectExporter exporter = parentTransform.GetComponent(); - if (exporter != null) + Rect buffer1 = new Rect(0, 0, selectedResolution.x, selectedResolution.y); + Rect buffer2 = verticalLayout ? new Rect(0, 256, selectedResolution.x, selectedResolution.y) + : new Rect(selectedResolution.x, 0, selectedResolution.x, selectedResolution.y); + + List framebuffers = new List { buffer1 }; + if (dualBuffering) { - //exporter.Export(); + framebuffers.Add(buffer2); } - foreach (Transform child in parentTransform) + VRAMPacker tp = new VRAMPacker(framebuffers, prohibitedAreas); + var packed = tp.PackTexturesIntoVRAM(_exporters); + _exporters = packed.processedObjects; + vramPixels = packed._vramPixels; + + } + + void ExportFile() { + string path = EditorUtility.SaveFilePanel("Select Output File", "", "output", "bin"); + int totalFaces = 0; + using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create))) + { + // VramPixels are always 1MB + for (int y = 0; y < vramPixels.GetLength(1); y++) + { + for (int x = 0; x < vramPixels.GetLength(0); x++) + { + writer.Write(vramPixels[x, y].Pack()); + } + } + writer.Write((ushort) _exporters.Length); + foreach(PSXObjectExporter exporter in _exporters) { + + int expander = 16 / ((int) exporter.Texture.BitDepth); + + totalFaces += exporter.Mesh.Triangles.Count; + writer.Write((ushort) exporter.Mesh.Triangles.Count); + writer.Write((byte) exporter.Texture.BitDepth); + writer.Write((byte)exporter.Texture.TexpageX); + writer.Write((byte)exporter.Texture.TexpageY); + writer.Write((ushort)exporter.Texture.ClutPackingX); + writer.Write((ushort)exporter.Texture.ClutPackingY); + writer.Write((byte) 0); + foreach(Tri tri in exporter.Mesh.Triangles) { + writer.Write((short)tri.v0.vx); + writer.Write((short)tri.v0.vy); + writer.Write((short)tri.v0.vz); + writer.Write((byte)(tri.v0.u + exporter.Texture.PackingX * expander)); + writer.Write((byte)(tri.v0.v + exporter.Texture.PackingY)); + + writer.Write((short)tri.v1.vx); + writer.Write((short)tri.v1.vy); + writer.Write((short)tri.v1.vz); + writer.Write((byte)(tri.v1.u + exporter.Texture.PackingX * expander)); + writer.Write((byte)(tri.v1.v + exporter.Texture.PackingY)); + + writer.Write((short)tri.v2.vx); + writer.Write((short)tri.v2.vy); + writer.Write((short)tri.v2.vz); + writer.Write((byte)(tri.v2.u + exporter.Texture.PackingX * expander)); + writer.Write((byte)(tri.v2.v + exporter.Texture.PackingY)); + } + } + } + Debug.Log(totalFaces); + } + + public void LoadData() + { + _psxData = AssetDatabase.LoadAssetAtPath(_psxDataPath); + + if (!_psxData) { - ExportAllPSXExporters(child); + _psxData = ScriptableObject.CreateInstance(); + AssetDatabase.CreateAsset(_psxData, _psxDataPath); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); } + + selectedResolution = _psxData.OutputResolution; + dualBuffering = _psxData.DualBuffering; + verticalLayout = _psxData.VerticalBuffering; + prohibitedAreas = _psxData.ProhibitedAreas; } void OnDrawGizmos() diff --git a/Runtime/PSXTexture2D.cs b/Runtime/PSXTexture2D.cs index ab356ab..49614e4 100644 --- a/Runtime/PSXTexture2D.cs +++ b/Runtime/PSXTexture2D.cs @@ -102,14 +102,15 @@ namespace PSXSplash.RuntimeCode public Texture2D OriginalTexture; // Within supertexture - public int PackingX; - public int PackingY; + public byte PackingX; + public byte PackingY; - public int TexpageNum; + public byte TexpageX; + public byte TexpageY; // Absolute positioning - public int ClutPackingX; - public int ClutPackingY; + public ushort ClutPackingX; + public ushort ClutPackingY; private int _maxColors; @@ -263,7 +264,6 @@ namespace PSXSplash.RuntimeCode Texture2D vramTexture = new Texture2D(QuantizedWidth, Height); - List colors = new List(); for (int y = 0; y < Height; y++) { for (int x = 0; x < QuantizedWidth; x++) diff --git a/Runtime/TexturePacker.cs b/Runtime/TexturePacker.cs index bf3ce8d..f06819a 100644 --- a/Runtime/TexturePacker.cs +++ b/Runtime/TexturePacker.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using NUnit.Framework; using UnityEngine; @@ -29,9 +30,16 @@ namespace PSXSplash.RuntimeCode private VRAMPixel[,] _vramPixels; - public VRAMPacker(List reservedAreas) + public VRAMPacker(List framebuffers, List reservedAreas) { - _reservedAreas = reservedAreas; + List areasConvertedToRect = new List(); + foreach (ProhibitedArea area in reservedAreas) + { + areasConvertedToRect.Add(new Rect(area.X, area.Y, area.Width, area.Height)); + } + _reservedAreas = areasConvertedToRect; + _reservedAreas.Add(framebuffers[0]); + _reservedAreas.Add(framebuffers[1]); _vramPixels = new VRAMPixel[VRAM_WIDTH, VRAM_HEIGHT]; } @@ -77,15 +85,16 @@ namespace PSXSplash.RuntimeCode ArrangeAtlasesInVRAM(); AllocateCLUTs(); + BuildVram(); return (objects, _vramPixels); } private bool TryPlaceTextureInAtlas(TextureAtlas atlas, PSXTexture2D texture) { - for (int y = 0; y <= TextureAtlas.Height - texture.Height; y++) + for (byte y = 0; y <= TextureAtlas.Height - texture.Height; y++) { - for (int x = 0; x <= atlas.Width - texture.QuantizedWidth; x++) + for (byte x = 0; x <= atlas.Width - texture.QuantizedWidth; x++) { var candidateRect = new Rect(x, y, texture.QuantizedWidth, texture.Height); if (!atlas.ContainedTextures.Any(tex => new Rect(tex.PackingX, tex.PackingY, tex.QuantizedWidth, tex.Height).Overlaps(candidateRect))) @@ -129,7 +138,11 @@ namespace PSXSplash.RuntimeCode { foreach (PSXTexture2D texture in atlas.ContainedTextures) { - texture.TexpageNum = CalculateTexpage(atlas.PositionX, atlas.PositionY); + int colIndex = atlas.PositionX / 64; + int rowIndex = atlas.PositionY / 256; + + texture.TexpageX = (byte)colIndex; + texture.TexpageY = (byte)rowIndex; } break; } @@ -153,9 +166,9 @@ namespace PSXSplash.RuntimeCode int clutHeight = 1; bool placed = false; - for (int x = 0; x < VRAM_WIDTH; x+=16) + for (ushort x = 0; x < VRAM_WIDTH; x += 16) { - for (int y = 0; y <= VRAM_HEIGHT; y++) + for (ushort y = 0; y <= VRAM_HEIGHT; y++) { var candidate = new Rect(x, y, clutWidth, clutHeight); if (IsPlacementValid(candidate)) diff --git a/Runtime/Utils.cs b/Runtime/Utils.cs new file mode 100644 index 0000000..acc42db --- /dev/null +++ b/Runtime/Utils.cs @@ -0,0 +1,28 @@ +using UnityEngine; + +namespace PSXSplash.RuntimeCode +{ + public class ProhibitedArea + { + public int X; + public int Y; + public int Width; + public int Height; + + public static ProhibitedArea FromUnityRect(Rect rect) + { + return new ProhibitedArea + { + X = Mathf.RoundToInt(rect.x), + Y = Mathf.RoundToInt(rect.y), + Width = Mathf.RoundToInt(rect.width), + Height = Mathf.RoundToInt(rect.height) + }; + } + + public Rect ToUnityRect() + { + return new Rect(X, Y, Width, Height); + } + } +} \ No newline at end of file diff --git a/Runtime/Utils.cs.meta b/Runtime/Utils.cs.meta new file mode 100644 index 0000000..ed98df9 --- /dev/null +++ b/Runtime/Utils.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8c9b0581c1e4eeb6296f4c162359043f \ No newline at end of file