Added splashpack
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Overlays;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Diagnostics;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TextCore.Text;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
@@ -12,6 +16,7 @@ namespace SplashEdit.RuntimeCode
|
||||
public class PSXSceneExporter : MonoBehaviour
|
||||
{
|
||||
private PSXObjectExporter[] _exporters;
|
||||
private TextureAtlas[] _atlases;
|
||||
|
||||
private PSXData _psxData;
|
||||
private readonly string _psxDataPath = "Assets/PSXData.asset";
|
||||
@@ -53,7 +58,7 @@ namespace SplashEdit.RuntimeCode
|
||||
VRAMPacker tp = new VRAMPacker(framebuffers, prohibitedAreas);
|
||||
var packed = tp.PackTexturesIntoVRAM(_exporters);
|
||||
_exporters = packed.processedObjects;
|
||||
vramPixels = packed._vramPixels;
|
||||
_atlases = packed.atlases;
|
||||
|
||||
}
|
||||
|
||||
@@ -61,77 +66,201 @@ namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
string path = EditorUtility.SaveFilePanel("Select Output File", "", "output", "bin");
|
||||
int totalFaces = 0;
|
||||
|
||||
// Lists for mesh data offsets.
|
||||
List<long> offsetPlaceholderPositions = new List<long>();
|
||||
List<long> meshDataOffsets = new List<long>();
|
||||
|
||||
// Lists for atlas data offsets.
|
||||
List<long> atlasOffsetPlaceholderPositions = new List<long>();
|
||||
List<long> atlasDataOffsets = new List<long>();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
// Header
|
||||
writer.Write('S');
|
||||
writer.Write('P');
|
||||
writer.Write((ushort)1);
|
||||
writer.Write((ushort)_exporters.Length);
|
||||
writer.Write((ushort)_atlases.Length);
|
||||
// Start of Metadata section
|
||||
|
||||
// GameObject section (exporters)
|
||||
foreach (PSXObjectExporter exporter in _exporters)
|
||||
{
|
||||
// Write object's position
|
||||
writer.Write((int)PSXTrig.ConvertCoordinateToPSX(transform.position.x));
|
||||
writer.Write((int)PSXTrig.ConvertCoordinateToPSX(transform.position.y));
|
||||
writer.Write((int)PSXTrig.ConvertCoordinateToPSX(transform.position.z));
|
||||
|
||||
int expander = 16 / ((int)exporter.Texture.BitDepth);
|
||||
int[,] rotationMatrix = PSXTrig.ConvertRotationToPSXMatrix(exporter.transform.rotation);
|
||||
writer.Write((int)rotationMatrix[0, 0]);
|
||||
writer.Write((int)rotationMatrix[0, 1]);
|
||||
writer.Write((int)rotationMatrix[0, 2]);
|
||||
writer.Write((int)rotationMatrix[1, 0]);
|
||||
writer.Write((int)rotationMatrix[1, 1]);
|
||||
writer.Write((int)rotationMatrix[1, 2]);
|
||||
writer.Write((int)rotationMatrix[2, 0]);
|
||||
writer.Write((int)rotationMatrix[2, 1]);
|
||||
writer.Write((int)rotationMatrix[2, 2]);
|
||||
|
||||
// Set up texture page attributes
|
||||
TPageAttr tpage = new TPageAttr();
|
||||
tpage.SetPageX(exporter.Texture.TexpageX);
|
||||
tpage.SetPageX(exporter.Texture.TexpageY);
|
||||
switch (exporter.Texture.BitDepth)
|
||||
{
|
||||
case PSXBPP.TEX_4BIT:
|
||||
tpage.Set(ColorMode.Mode4Bit);
|
||||
break;
|
||||
case PSXBPP.TEX_8BIT:
|
||||
tpage.Set(ColorMode.Mode8Bit);
|
||||
break;
|
||||
case PSXBPP.TEX_16BIT:
|
||||
tpage.Set(ColorMode.Mode16Bit);
|
||||
break;
|
||||
}
|
||||
tpage.SetDithering(true);
|
||||
writer.Write((ushort)tpage.info);
|
||||
writer.Write((ushort)exporter.Mesh.Triangles.Count);
|
||||
|
||||
// Write placeholder for mesh data offset and record its position.
|
||||
offsetPlaceholderPositions.Add(writer.BaseStream.Position);
|
||||
writer.Write((int)0); // 4-byte placeholder for mesh data offset.
|
||||
}
|
||||
|
||||
// Atlas metadata section
|
||||
foreach (TextureAtlas atlas in _atlases)
|
||||
{
|
||||
// Write placeholder for texture atlas raw data offset.
|
||||
atlasOffsetPlaceholderPositions.Add(writer.BaseStream.Position);
|
||||
writer.Write((int)0); // 4-byte placeholder for atlas data offset.
|
||||
|
||||
writer.Write((ushort)atlas.Width);
|
||||
writer.Write((ushort)TextureAtlas.Height);
|
||||
writer.Write((ushort)atlas.PositionX);
|
||||
writer.Write((ushort)atlas.PositionY);
|
||||
}
|
||||
|
||||
// Start of data section
|
||||
|
||||
// Mesh data section: Write mesh data for each exporter.
|
||||
foreach (PSXObjectExporter exporter in _exporters)
|
||||
{
|
||||
// Record the current offset for this exporter's mesh data.
|
||||
long meshDataOffset = writer.BaseStream.Position;
|
||||
meshDataOffsets.Add(meshDataOffset);
|
||||
|
||||
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);
|
||||
|
||||
int expander = 16 / ((int)exporter.Texture.BitDepth);
|
||||
foreach (Tri tri in exporter.Mesh.Triangles)
|
||||
{
|
||||
// Write vertices coordinates
|
||||
writer.Write((short)tri.v0.vx);
|
||||
writer.Write((short)tri.v0.vy);
|
||||
writer.Write((short)tri.v0.vz);
|
||||
writer.Write((short)tri.v0.nx);
|
||||
writer.Write((short)tri.v0.ny);
|
||||
writer.Write((short)tri.v0.nz);
|
||||
writer.Write((byte)(tri.v0.u + exporter.Texture.PackingX * expander));
|
||||
writer.Write((byte)(tri.v0.v + exporter.Texture.PackingY));
|
||||
writer.Write((byte) tri.v0.r);
|
||||
writer.Write((byte) tri.v0.g);
|
||||
writer.Write((byte) tri.v0.b);
|
||||
for(int i = 0; i < 7; i ++) writer.Write((byte) 0);
|
||||
|
||||
writer.Write((short)tri.v1.vx);
|
||||
writer.Write((short)tri.v1.vy);
|
||||
writer.Write((short)tri.v1.vz);
|
||||
writer.Write((short)tri.v1.nx);
|
||||
writer.Write((short)tri.v1.ny);
|
||||
writer.Write((short)tri.v1.nz);
|
||||
writer.Write((byte)(tri.v1.u + exporter.Texture.PackingX * expander));
|
||||
writer.Write((byte)(tri.v1.v + exporter.Texture.PackingY));
|
||||
writer.Write((byte) tri.v1.r);
|
||||
writer.Write((byte) tri.v1.g);
|
||||
writer.Write((byte) tri.v1.b);
|
||||
for(int i = 0; i < 7; i ++) writer.Write((byte) 0);
|
||||
|
||||
writer.Write((short)tri.v2.vx);
|
||||
writer.Write((short)tri.v2.vy);
|
||||
writer.Write((short)tri.v2.vz);
|
||||
writer.Write((short)tri.v2.nx);
|
||||
writer.Write((short)tri.v2.ny);
|
||||
writer.Write((short)tri.v2.nz);
|
||||
|
||||
// Write vertex normals for v0 only
|
||||
writer.Write((short)tri.v0.nx);
|
||||
writer.Write((short)tri.v0.ny);
|
||||
writer.Write((short)tri.v0.nz);
|
||||
|
||||
// Write UVs for each vertex, adjusting for texture packing
|
||||
writer.Write((byte)(tri.v0.u + exporter.Texture.PackingX * expander));
|
||||
writer.Write((byte)(tri.v0.v + exporter.Texture.PackingY));
|
||||
|
||||
writer.Write((byte)(tri.v1.u + exporter.Texture.PackingX * expander));
|
||||
writer.Write((byte)(tri.v1.v + exporter.Texture.PackingY));
|
||||
|
||||
writer.Write((byte)(tri.v2.u + exporter.Texture.PackingX * expander));
|
||||
writer.Write((byte)(tri.v2.v + exporter.Texture.PackingY));
|
||||
|
||||
writer.Write((ushort)0); // padding
|
||||
|
||||
// Write vertex colors with padding
|
||||
writer.Write((byte)tri.v0.r);
|
||||
writer.Write((byte)tri.v0.g);
|
||||
writer.Write((byte)tri.v0.b);
|
||||
writer.Write((byte)0); // padding
|
||||
|
||||
writer.Write((byte)tri.v1.r);
|
||||
writer.Write((byte)tri.v1.g);
|
||||
writer.Write((byte)tri.v1.b);
|
||||
writer.Write((byte)0); // padding
|
||||
|
||||
writer.Write((byte)tri.v2.r);
|
||||
writer.Write((byte)tri.v2.g);
|
||||
writer.Write((byte)tri.v2.b);
|
||||
for(int i = 0; i < 7; i ++) writer.Write((byte) 0);
|
||||
|
||||
writer.Write((byte)0); // padding
|
||||
}
|
||||
}
|
||||
|
||||
// Atlas data section: Write raw texture data for each atlas.
|
||||
foreach (TextureAtlas atlas in _atlases)
|
||||
{
|
||||
AlignToFourBytes(writer);
|
||||
// Record the current offset for this atlas's data.
|
||||
long atlasDataOffset = writer.BaseStream.Position;
|
||||
atlasDataOffsets.Add(atlasDataOffset);
|
||||
|
||||
// Write the atlas's raw texture data.
|
||||
for (int y = 0; y < atlas.vramPixels.GetLength(1); y++)
|
||||
{
|
||||
for (int x = 0; x < atlas.vramPixels.GetLength(0); x++)
|
||||
{
|
||||
writer.Write(atlas.vramPixels[x, y].Pack());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Backfill the mesh data offsets into the metadata section.
|
||||
if (offsetPlaceholderPositions.Count == meshDataOffsets.Count)
|
||||
{
|
||||
for (int i = 0; i < offsetPlaceholderPositions.Count; i++)
|
||||
{
|
||||
writer.Seek((int)offsetPlaceholderPositions[i], SeekOrigin.Begin);
|
||||
writer.Write((int)meshDataOffsets[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Mismatch between metadata mesh offset placeholders and mesh data blocks!");
|
||||
}
|
||||
|
||||
// Backfill the atlas data offsets into the metadata section.
|
||||
if (atlasOffsetPlaceholderPositions.Count == atlasDataOffsets.Count)
|
||||
{
|
||||
for (int i = 0; i < atlasOffsetPlaceholderPositions.Count; i++)
|
||||
{
|
||||
writer.Seek((int)atlasOffsetPlaceholderPositions[i], SeekOrigin.Begin);
|
||||
writer.Write((int)atlasDataOffsets[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Mismatch between atlas offset placeholders and atlas data blocks!");
|
||||
}
|
||||
}
|
||||
Debug.Log(totalFaces);
|
||||
}
|
||||
|
||||
void AlignToFourBytes(BinaryWriter writer)
|
||||
{
|
||||
long position = writer.BaseStream.Position;
|
||||
int padding = (int)(4 - (position % 4)) % 4; // Compute needed padding
|
||||
Debug.Log($"aligned {padding} bytes");
|
||||
writer.Write(new byte[padding]); // Write zero padding
|
||||
}
|
||||
|
||||
public void LoadData()
|
||||
{
|
||||
_psxData = AssetDatabase.LoadAssetAtPath<PSXData>(_psxDataPath);
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace SplashEdit.RuntimeCode
|
||||
public int PositionY; // Y position of the atlas in VRAM.
|
||||
public int Width; // Width of the atlas.
|
||||
public const int Height = 256; // Fixed height for all atlases.
|
||||
public VRAMPixel[,] vramPixels;
|
||||
public List<PSXTexture2D> ContainedTextures = new List<PSXTexture2D>(); // Textures packed in this atlas.
|
||||
}
|
||||
|
||||
@@ -62,7 +63,7 @@ namespace SplashEdit.RuntimeCode
|
||||
/// </summary>
|
||||
/// <param name="objects">Array of PSXObjectExporter objects to process.</param>
|
||||
/// <returns>Tuple containing processed objects and the VRAM pixel array.</returns>
|
||||
public (PSXObjectExporter[] processedObjects, VRAMPixel[,] _vramPixels) PackTexturesIntoVRAM(PSXObjectExporter[] objects)
|
||||
public (PSXObjectExporter[] processedObjects, TextureAtlas[] atlases, VRAMPixel[,] _vramPixels) PackTexturesIntoVRAM(PSXObjectExporter[] objects)
|
||||
{
|
||||
List<PSXTexture2D> uniqueTextures = new List<PSXTexture2D>();
|
||||
// Group objects by texture bit depth (high to low).
|
||||
@@ -87,11 +88,11 @@ namespace SplashEdit.RuntimeCode
|
||||
foreach (var obj in group.OrderByDescending(obj => obj.Texture.QuantizedWidth * obj.Texture.Height))
|
||||
{
|
||||
// Remove duplicate textures
|
||||
if (uniqueTextures.Any(tex => tex.OriginalTexture.GetInstanceID() == obj.Texture.OriginalTexture.GetInstanceID() && tex.BitDepth == obj.Texture.BitDepth))
|
||||
/*if (uniqueTextures.Any(tex => tex.OriginalTexture.GetInstanceID() == obj.Texture.OriginalTexture.GetInstanceID() && tex.BitDepth == obj.Texture.BitDepth))
|
||||
{
|
||||
obj.Texture = uniqueTextures.First(tex => tex.OriginalTexture.GetInstanceID() == obj.Texture.OriginalTexture.GetInstanceID());
|
||||
continue;
|
||||
}
|
||||
}*/
|
||||
|
||||
// Try to place the texture in the current atlas.
|
||||
if (!TryPlaceTextureInAtlas(atlas, obj.Texture))
|
||||
@@ -116,7 +117,7 @@ namespace SplashEdit.RuntimeCode
|
||||
|
||||
// Build the final VRAM pixel array from placed textures and CLUTs.
|
||||
BuildVram();
|
||||
return (objects, _vramPixels);
|
||||
return (objects, _finalizedAtlases.ToArray(), _vramPixels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -249,13 +250,17 @@ namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
foreach (TextureAtlas atlas in _finalizedAtlases)
|
||||
{
|
||||
atlas.vramPixels = new VRAMPixel[atlas.Width, TextureAtlas.Height];
|
||||
|
||||
foreach (PSXTexture2D texture in atlas.ContainedTextures)
|
||||
{
|
||||
|
||||
// Copy texture image data into VRAM using atlas and texture packing offsets.
|
||||
for (int y = 0; y < texture.Height; y++)
|
||||
{
|
||||
for (int x = 0; x < texture.QuantizedWidth; x++)
|
||||
{
|
||||
atlas.vramPixels[x + texture.PackingX, y + texture.PackingY] = texture.ImageData[x, y];
|
||||
_vramPixels[x + atlas.PositionX + texture.PackingX, y + atlas.PositionY + texture.PackingY] = texture.ImageData[x, y];
|
||||
}
|
||||
}
|
||||
|
||||
120
Runtime/Utils.cs
120
Runtime/Utils.cs
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
@@ -41,4 +42,123 @@ namespace SplashEdit.RuntimeCode
|
||||
return new Rect(X, Y, Width, Height);
|
||||
}
|
||||
}
|
||||
public static class PSXTrig
|
||||
{
|
||||
|
||||
public static short ConvertCoordinateToPSX(float value)
|
||||
{
|
||||
return (short)(Mathf.Clamp(value, -4f, 3.999f) * 4096);
|
||||
}
|
||||
|
||||
public static short ConvertRadiansToPSX(float value)
|
||||
{
|
||||
return (short)(Mathf.Clamp(value, -4f, 3.999f) * 4096f / Mathf.PI);
|
||||
}
|
||||
|
||||
public static int[,] ConvertRotationToPSXMatrix(Quaternion rotation)
|
||||
{
|
||||
float xx = rotation.x * rotation.x;
|
||||
float yy = rotation.y * rotation.y;
|
||||
float zz = rotation.z * rotation.z;
|
||||
float xy = rotation.x * rotation.y;
|
||||
float xz = rotation.x * rotation.z;
|
||||
float yz = rotation.y * rotation.z;
|
||||
float wx = rotation.w * rotation.x;
|
||||
float wy = rotation.w * rotation.y;
|
||||
float wz = rotation.w * rotation.z;
|
||||
|
||||
// Create the 3x3 rotation matrix
|
||||
int[,] psxMatrix = new int[3, 3]
|
||||
{
|
||||
{ ConvertToFixed12(1.0f - 2.0f * (yy + zz)), ConvertToFixed12(2.0f * (xy - wz)), ConvertToFixed12(2.0f * (xz + wy)) },
|
||||
{ ConvertToFixed12(2.0f * (xy + wz)), ConvertToFixed12(1.0f - 2.0f * (xx + zz)), ConvertToFixed12(2.0f * (yz - wx)) },
|
||||
{ ConvertToFixed12(2.0f * (xz - wy)), ConvertToFixed12(2.0f * (yz + wx)), ConvertToFixed12(1.0f - 2.0f * (xx + yy)) }
|
||||
};
|
||||
|
||||
return psxMatrix;
|
||||
}
|
||||
|
||||
private static int ConvertToFixed12(float value)
|
||||
{
|
||||
return (int)(value * 4096.0f); // 2^12 = 4096
|
||||
}
|
||||
}
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct TPageAttr
|
||||
{
|
||||
public ushort info;
|
||||
|
||||
public TPageAttr SetPageX(byte x)
|
||||
{
|
||||
info &= 0xFFF0; // Clear lower 4 bits
|
||||
x &= 0x0F; // Ensure only lower 4 bits are used
|
||||
info |= x;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TPageAttr SetPageY(byte y)
|
||||
{
|
||||
info &= 0xFFEF; // Clear bit 4
|
||||
y &= 0x01; // Ensure only lower 1 bit is used
|
||||
info |= (ushort)(y << 4);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TPageAttr Set(SemiTrans trans)
|
||||
{
|
||||
info &= 0xFF9F; // Clear bits 5 and 6
|
||||
uint t = (uint)trans;
|
||||
info |= (ushort)(t << 5);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TPageAttr Set(ColorMode mode)
|
||||
{
|
||||
info &= 0xFE7F; // Clear bits 7 and 8
|
||||
uint m = (uint)mode;
|
||||
info |= (ushort)(m << 7);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TPageAttr SetDithering(bool dithering)
|
||||
{
|
||||
if (dithering)
|
||||
info |= 0x0200;
|
||||
else
|
||||
info &= 0xFDFF;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TPageAttr DisableDisplayArea()
|
||||
{
|
||||
info &= 0xFBFF; // Clear bit 10
|
||||
return this;
|
||||
}
|
||||
|
||||
public TPageAttr EnableDisplayArea()
|
||||
{
|
||||
info |= 0x0400; // Set bit 10
|
||||
return this;
|
||||
}
|
||||
|
||||
public override string ToString() => $"Info: 0x{info:X4}";
|
||||
}
|
||||
|
||||
// Define the enums for SemiTrans and ColorMode (assuming their values)
|
||||
public enum SemiTrans : uint
|
||||
{
|
||||
None = 0,
|
||||
Type1 = 1,
|
||||
Type2 = 2,
|
||||
Type3 = 3
|
||||
}
|
||||
|
||||
public enum ColorMode : uint
|
||||
{
|
||||
Mode4Bit = 0,
|
||||
Mode8Bit = 1,
|
||||
Mode16Bit = 2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user