Revamped collision system
This commit is contained in:
@@ -15,7 +15,6 @@ namespace SplashEdit.RuntimeCode
|
||||
Solid = 0x01,
|
||||
Slope = 0x02,
|
||||
Stairs = 0x04,
|
||||
Trigger = 0x08,
|
||||
NoWalk = 0x10,
|
||||
}
|
||||
|
||||
@@ -84,8 +83,8 @@ namespace SplashEdit.RuntimeCode
|
||||
|
||||
foreach (var exporter in exporters)
|
||||
{
|
||||
// Dynamic objects are handled by the runtime collision system, skip them
|
||||
if (!exporter.StaticCollider && exporter.CollisionType != PSXCollisionType.None)
|
||||
// Dynamic objects use runtime AABB colliders, skip them
|
||||
if (exporter.CollisionType == PSXCollisionType.Dynamic)
|
||||
continue;
|
||||
|
||||
PSXCollisionType effectiveType = exporter.CollisionType;
|
||||
@@ -94,7 +93,7 @@ namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
if (autoIncludeSolid)
|
||||
{
|
||||
effectiveType = PSXCollisionType.Solid;
|
||||
effectiveType = PSXCollisionType.Static;
|
||||
autoIncluded++;
|
||||
}
|
||||
else
|
||||
@@ -103,11 +102,8 @@ namespace SplashEdit.RuntimeCode
|
||||
}
|
||||
}
|
||||
|
||||
// Get the collision mesh (custom or render mesh)
|
||||
MeshFilter mf = exporter.GetComponent<MeshFilter>();
|
||||
Mesh collisionMesh = exporter.CustomCollisionMesh != null
|
||||
? exporter.CustomCollisionMesh
|
||||
: mf?.sharedMesh;
|
||||
Mesh collisionMesh = mf?.sharedMesh;
|
||||
|
||||
if (collisionMesh == null)
|
||||
continue;
|
||||
@@ -133,37 +129,25 @@ namespace SplashEdit.RuntimeCode
|
||||
// Determine surface flags
|
||||
byte flags = 0;
|
||||
|
||||
if (effectiveType == PSXCollisionType.Trigger)
|
||||
// Floor-like: normal.y > cosWalkable
|
||||
float dotUp = normal.y;
|
||||
|
||||
if (dotUp > cosWalkable)
|
||||
{
|
||||
flags = (byte)PSXSurfaceFlag.Trigger;
|
||||
flags = (byte)PSXSurfaceFlag.Solid;
|
||||
|
||||
if (dotUp < 0.95f && dotUp > cosWalkable)
|
||||
{
|
||||
flags |= (byte)PSXSurfaceFlag.Stairs;
|
||||
}
|
||||
}
|
||||
else if (dotUp > 0.0f)
|
||||
{
|
||||
flags = (byte)(PSXSurfaceFlag.Solid | PSXSurfaceFlag.Slope);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Floor-like: normal.y > cosWalkable
|
||||
// Note: Unity Y is up; PS1 Y is down. We export in Unity space
|
||||
// and convert to PS1 space during WriteToBinary.
|
||||
float dotUp = normal.y;
|
||||
|
||||
if (dotUp > cosWalkable)
|
||||
{
|
||||
flags = (byte)PSXSurfaceFlag.Solid;
|
||||
|
||||
// Check if stairs (tagged on exporter or steep-ish)
|
||||
if (dotUp < 0.95f && dotUp > cosWalkable)
|
||||
{
|
||||
flags |= (byte)PSXSurfaceFlag.Stairs;
|
||||
}
|
||||
}
|
||||
else if (dotUp > 0.0f)
|
||||
{
|
||||
// Slope too steep to walk on
|
||||
flags = (byte)(PSXSurfaceFlag.Solid | PSXSurfaceFlag.Slope);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wall or ceiling
|
||||
flags = (byte)PSXSurfaceFlag.Solid;
|
||||
}
|
||||
flags = (byte)PSXSurfaceFlag.Solid;
|
||||
}
|
||||
|
||||
_allTriangles.Add(new CollisionTriExport
|
||||
|
||||
@@ -187,23 +187,48 @@ namespace SplashEdit.RuntimeCode
|
||||
}
|
||||
}
|
||||
|
||||
// ── Font pixel data (written BEFORE the UI table, alongside atlas/CLUT data) ──
|
||||
// The C++ parser expects canvas descriptors immediately after font descriptors
|
||||
// (font pixel data is at absolute offsets, not inline). Write pixel data here
|
||||
// so it doesn't sit between font descriptors and canvas descriptors.
|
||||
List<long> fontDataOffsetPositions = new List<long>();
|
||||
List<long> fontPixelDataPositions = new List<long>();
|
||||
if (fonts != null)
|
||||
{
|
||||
for (int fi = 0; fi < fonts.Length; fi++)
|
||||
{
|
||||
var font = fonts[fi];
|
||||
if (font.PixelData == null || font.PixelData.Length == 0)
|
||||
{
|
||||
fontPixelDataPositions.Add(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
AlignToFourBytes(writer);
|
||||
long dataPos = writer.BaseStream.Position;
|
||||
writer.Write(font.PixelData);
|
||||
fontPixelDataPositions.Add(dataPos);
|
||||
}
|
||||
}
|
||||
|
||||
// ── UI table (same format as splashpack UI section) ──
|
||||
AlignToFourBytes(writer);
|
||||
long uiTableStart = writer.BaseStream.Position;
|
||||
|
||||
// ── Font descriptors (112 bytes each) ──
|
||||
List<long> fontDataOffsetPositions = new List<long>();
|
||||
if (fonts != null)
|
||||
{
|
||||
foreach (var font in fonts)
|
||||
for (int fi = 0; fi < fonts.Length; fi++)
|
||||
{
|
||||
var font = fonts[fi];
|
||||
writer.Write(font.GlyphWidth); // [0]
|
||||
writer.Write(font.GlyphHeight); // [1]
|
||||
writer.Write(font.VramX); // [2-3]
|
||||
writer.Write(font.VramY); // [4-5]
|
||||
writer.Write(font.TextureHeight); // [6-7]
|
||||
fontDataOffsetPositions.Add(writer.BaseStream.Position);
|
||||
writer.Write((uint)0); // [8-11] dataOffset placeholder
|
||||
// dataOffset: use the pre-written pixel data position
|
||||
long pixPos = fontPixelDataPositions[fi];
|
||||
writer.Write((uint)pixPos); // [8-11] dataOffset (0 if no data)
|
||||
writer.Write((uint)(font.PixelData?.Length ?? 0)); // [12-15] dataSize
|
||||
if (font.AdvanceWidths != null && font.AdvanceWidths.Length >= 96)
|
||||
writer.Write(font.AdvanceWidths, 0, 96);
|
||||
@@ -212,26 +237,13 @@ namespace SplashEdit.RuntimeCode
|
||||
}
|
||||
}
|
||||
|
||||
// ── Font pixel data ──
|
||||
if (fonts != null)
|
||||
{
|
||||
for (int fi = 0; fi < fonts.Length; fi++)
|
||||
{
|
||||
var font = fonts[fi];
|
||||
if (font.PixelData == null || font.PixelData.Length == 0) continue;
|
||||
|
||||
AlignToFourBytes(writer);
|
||||
long dataPos = writer.BaseStream.Position;
|
||||
writer.Write(font.PixelData);
|
||||
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)fontDataOffsetPositions[fi], SeekOrigin.Begin);
|
||||
writer.Write((uint)dataPos);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
// Canvas descriptors now follow immediately after font descriptors
|
||||
// (no font pixel data in between — it was written above).
|
||||
|
||||
// ── Canvas descriptor (12 bytes) ──
|
||||
// Must align here: the C++ parser aligns fontDataEnd to 4 bytes
|
||||
// when skipping past font pixel data to find the canvas descriptor.
|
||||
AlignToFourBytes(writer);
|
||||
var elements = canvas.Elements ?? new PSXUIElementData[0];
|
||||
string cvName = canvas.Name ?? "loading";
|
||||
if (cvName.Length > 24) cvName = cvName.Substring(0, 24);
|
||||
|
||||
@@ -312,6 +312,8 @@ namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
foreach (var exporter in exporters)
|
||||
{
|
||||
if (exporter.CollisionType == PSXCollisionType.Dynamic)
|
||||
continue;
|
||||
|
||||
MeshFilter mf = exporter.GetComponent<MeshFilter>();
|
||||
Mesh mesh = mf?.sharedMesh;
|
||||
|
||||
@@ -8,9 +8,8 @@ namespace SplashEdit.RuntimeCode
|
||||
public enum PSXCollisionType
|
||||
{
|
||||
None = 0,
|
||||
Solid = 1,
|
||||
Trigger = 2,
|
||||
Platform = 3
|
||||
Static = 1,
|
||||
Dynamic = 2
|
||||
}
|
||||
|
||||
[RequireComponent(typeof(Renderer))]
|
||||
@@ -29,22 +28,11 @@ namespace SplashEdit.RuntimeCode
|
||||
[SerializeField] private PSXBPP bitDepth = PSXBPP.TEX_8BIT;
|
||||
[SerializeField] private LuaFile luaFile;
|
||||
|
||||
[FormerlySerializedAs("collisionType")]
|
||||
[SerializeField] private PSXCollisionType collisionType = PSXCollisionType.None;
|
||||
[SerializeField] private bool staticCollider = true;
|
||||
[SerializeField] private bool exportCollisionMesh = false;
|
||||
[SerializeField] private Mesh customCollisionMesh;
|
||||
[Range(1, 8)]
|
||||
[SerializeField] private int collisionLayer = 1;
|
||||
|
||||
[SerializeField] private bool generateNavigation = false;
|
||||
|
||||
public PSXBPP BitDepth => bitDepth;
|
||||
public PSXCollisionType CollisionType => collisionType;
|
||||
public bool StaticCollider => staticCollider;
|
||||
public bool ExportCollisionMesh => exportCollisionMesh;
|
||||
public Mesh CustomCollisionMesh => customCollisionMesh;
|
||||
public int CollisionLayer => collisionLayer;
|
||||
public bool GenerateNavigation => generateNavigation;
|
||||
|
||||
private readonly Dictionary<(int, PSXBPP), PSXTexture2D> cache = new();
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ namespace SplashEdit.RuntimeCode
|
||||
// Component arrays
|
||||
private PSXInteractable[] _interactables;
|
||||
private PSXAudioSource[] _audioSources;
|
||||
private PSXTriggerBox[] _triggerBoxes;
|
||||
|
||||
// Phase 3+4: World collision and nav regions
|
||||
private PSXCollisionExporter _collisionExporter;
|
||||
@@ -120,6 +121,7 @@ namespace SplashEdit.RuntimeCode
|
||||
// Collect components
|
||||
_interactables = FindObjectsByType<PSXInteractable>(FindObjectsSortMode.None);
|
||||
_audioSources = FindObjectsByType<PSXAudioSource>(FindObjectsSortMode.None);
|
||||
_triggerBoxes = FindObjectsByType<PSXTriggerBox>(FindObjectsSortMode.None);
|
||||
|
||||
// Collect UI image textures for VRAM packing alongside 3D textures
|
||||
PSXUIImage[] uiImages = FindObjectsByType<PSXUIImage>(FindObjectsSortMode.None);
|
||||
@@ -164,7 +166,7 @@ namespace SplashEdit.RuntimeCode
|
||||
_collisionExporter = new PSXCollisionExporter();
|
||||
_collisionExporter.Build(_exporters, GTEScaling);
|
||||
if (_collisionExporter.MeshCount == 0)
|
||||
Debug.LogWarning("No collision meshes! Set CollisionType=Solid on your floor/wall objects.");
|
||||
Debug.LogWarning("No collision meshes! Set CollisionType=Static on your floor/wall objects.");
|
||||
|
||||
// Phase 4+5: Room volumes are needed by BOTH the nav region builder
|
||||
// (for spatial room assignment) and the room builder (for triangle assignment).
|
||||
@@ -295,6 +297,7 @@ namespace SplashEdit.RuntimeCode
|
||||
audioSources = _audioSources,
|
||||
canvases = _canvases,
|
||||
fonts = _fonts,
|
||||
triggerBoxes = _triggerBoxes,
|
||||
};
|
||||
|
||||
PSXSceneWriter.Write(path, in scene, (msg, type) =>
|
||||
|
||||
@@ -39,6 +39,9 @@ namespace SplashEdit.RuntimeCode
|
||||
// Custom fonts (v13, embedded in UI block)
|
||||
public PSXFontData[] fonts;
|
||||
|
||||
// Trigger boxes (v16)
|
||||
public PSXTriggerBox[] triggerBoxes;
|
||||
|
||||
// Player
|
||||
public Vector3 playerPos;
|
||||
public Quaternion playerRot;
|
||||
@@ -105,32 +108,40 @@ namespace SplashEdit.RuntimeCode
|
||||
}
|
||||
if (scene.sceneLuaFile != null && !luaFiles.Contains(scene.sceneLuaFile))
|
||||
luaFiles.Add(scene.sceneLuaFile);
|
||||
// Trigger box Lua files
|
||||
if (scene.triggerBoxes != null)
|
||||
{
|
||||
foreach (var tb in scene.triggerBoxes)
|
||||
{
|
||||
if (tb.LuaFile != null && !luaFiles.Contains(tb.LuaFile))
|
||||
luaFiles.Add(tb.LuaFile);
|
||||
}
|
||||
}
|
||||
|
||||
using (BinaryWriter writer = new BinaryWriter(File.Open(path, FileMode.Create)))
|
||||
{
|
||||
int colliderCount = 0;
|
||||
foreach (var e in scene.exporters)
|
||||
{
|
||||
if (e.CollisionType == PSXCollisionType.None || e.StaticCollider)
|
||||
continue;
|
||||
Mesh cm = e.CustomCollisionMesh != null
|
||||
? e.CustomCollisionMesh
|
||||
: e.GetComponent<MeshFilter>()?.sharedMesh;
|
||||
if (cm != null)
|
||||
if (e.CollisionType != PSXCollisionType.Dynamic) continue;
|
||||
MeshFilter mf = e.GetComponent<MeshFilter>();
|
||||
if (mf?.sharedMesh != null)
|
||||
colliderCount++;
|
||||
}
|
||||
|
||||
int triggerBoxCount = scene.triggerBoxes?.Length ?? 0;
|
||||
|
||||
// Build exporter index lookup for components
|
||||
Dictionary<PSXObjectExporter, int> exporterIndex = new Dictionary<PSXObjectExporter, int>();
|
||||
for (int i = 0; i < scene.exporters.Length; i++)
|
||||
exporterIndex[scene.exporters[i]] = i;
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
// Header (104 bytes — splashpack v15)
|
||||
// Header (104 bytes — splashpack v16)
|
||||
// ──────────────────────────────────────────────────────
|
||||
writer.Write('S');
|
||||
writer.Write('P');
|
||||
writer.Write((ushort)15);
|
||||
writer.Write((ushort)16);
|
||||
writer.Write((ushort)luaFiles.Count);
|
||||
writer.Write((ushort)scene.exporters.Length);
|
||||
writer.Write((ushort)scene.atlases.Length);
|
||||
@@ -156,7 +167,7 @@ namespace SplashEdit.RuntimeCode
|
||||
writer.Write((ushort)scene.bvh.TriangleRefCount);
|
||||
|
||||
writer.Write((ushort)scene.sceneType);
|
||||
writer.Write((ushort)0); // pad0
|
||||
writer.Write((ushort)triggerBoxCount); // was pad0
|
||||
|
||||
writer.Write((ushort)scene.collisionExporter.MeshCount);
|
||||
writer.Write((ushort)scene.collisionExporter.TriangleCount);
|
||||
@@ -278,29 +289,50 @@ namespace SplashEdit.RuntimeCode
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
// Collider metadata (32 bytes each)
|
||||
// Collider metadata (32 bytes each) — Dynamic objects only
|
||||
// ──────────────────────────────────────────────────────
|
||||
for (int exporterIdx = 0; exporterIdx < scene.exporters.Length; exporterIdx++)
|
||||
{
|
||||
PSXObjectExporter exporter = scene.exporters[exporterIdx];
|
||||
if (exporter.CollisionType == PSXCollisionType.None || exporter.StaticCollider)
|
||||
continue;
|
||||
if (exporter.CollisionType != PSXCollisionType.Dynamic) continue;
|
||||
|
||||
MeshFilter meshFilter = exporter.GetComponent<MeshFilter>();
|
||||
Mesh collisionMesh = exporter.CustomCollisionMesh != null
|
||||
? exporter.CustomCollisionMesh
|
||||
: meshFilter?.sharedMesh;
|
||||
Mesh renderMesh = meshFilter?.sharedMesh;
|
||||
if (renderMesh == null) continue;
|
||||
|
||||
if (collisionMesh == null)
|
||||
continue;
|
||||
WriteWorldAABB(writer, exporter, renderMesh.bounds, gte);
|
||||
|
||||
WriteWorldAABB(writer, exporter, collisionMesh.bounds, gte);
|
||||
|
||||
// Collision metadata (8 bytes)
|
||||
writer.Write((byte)exporter.CollisionType);
|
||||
writer.Write((byte)(1 << (exporter.CollisionLayer - 1)));
|
||||
writer.Write((byte)1); // CollisionType::Solid on C++ side
|
||||
writer.Write((byte)0xFF); // layerMask (all layers)
|
||||
writer.Write((ushort)exporterIdx);
|
||||
writer.Write((uint)0); // padding
|
||||
writer.Write((uint)0);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
// Trigger box metadata (32 bytes each)
|
||||
// ──────────────────────────────────────────────────────
|
||||
if (scene.triggerBoxes != null)
|
||||
{
|
||||
foreach (var tb in scene.triggerBoxes)
|
||||
{
|
||||
Bounds wb = tb.GetWorldBounds();
|
||||
Vector3 wMin = wb.min;
|
||||
Vector3 wMax = wb.max;
|
||||
|
||||
writer.Write(PSXTrig.ConvertWorldToFixed12(wMin.x / gte));
|
||||
writer.Write(PSXTrig.ConvertWorldToFixed12(-wMax.y / gte));
|
||||
writer.Write(PSXTrig.ConvertWorldToFixed12(wMin.z / gte));
|
||||
writer.Write(PSXTrig.ConvertWorldToFixed12(wMax.x / gte));
|
||||
writer.Write(PSXTrig.ConvertWorldToFixed12(-wMin.y / gte));
|
||||
writer.Write(PSXTrig.ConvertWorldToFixed12(wMax.z / gte));
|
||||
|
||||
if (tb.LuaFile != null)
|
||||
writer.Write((short)luaFiles.IndexOf(tb.LuaFile));
|
||||
else
|
||||
writer.Write((short)-1);
|
||||
writer.Write((ushort)0); // padding
|
||||
writer.Write((uint)0); // padding
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
|
||||
37
Runtime/PSXTriggerBox.cs
Normal file
37
Runtime/PSXTriggerBox.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
public class PSXTriggerBox : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private Vector3 size = Vector3.one;
|
||||
[SerializeField] private LuaFile luaFile;
|
||||
|
||||
public Vector3 Size => size;
|
||||
public LuaFile LuaFile => luaFile;
|
||||
|
||||
public Bounds GetWorldBounds()
|
||||
{
|
||||
Vector3 halfSize = size * 0.5f;
|
||||
Vector3 worldCenter = transform.position;
|
||||
Vector3 worldMin = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
|
||||
Vector3 worldMax = new Vector3(float.MinValue, float.MinValue, float.MinValue);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
Vector3 corner = new Vector3(
|
||||
(i & 1) != 0 ? halfSize.x : -halfSize.x,
|
||||
(i & 2) != 0 ? halfSize.y : -halfSize.y,
|
||||
(i & 4) != 0 ? halfSize.z : -halfSize.z
|
||||
);
|
||||
Vector3 world = transform.TransformPoint(corner);
|
||||
worldMin = Vector3.Min(worldMin, world);
|
||||
worldMax = Vector3.Max(worldMax, world);
|
||||
}
|
||||
|
||||
Bounds b = new Bounds();
|
||||
b.SetMinMax(worldMin, worldMax);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Runtime/PSXTriggerBox.cs.meta
Normal file
2
Runtime/PSXTriggerBox.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 72b9d8d2e8eafba46a30ba345beb9692
|
||||
@@ -161,10 +161,23 @@ namespace SplashEdit.RuntimeCode
|
||||
|
||||
List<PSXUIElementData> elements = new List<PSXUIElementData>();
|
||||
|
||||
Debug.Log($"[UIExporter] Canvas '{canvas.CanvasName}' on '{canvas.gameObject.name}' " +
|
||||
$"canvasW={canvasW} canvasH={canvasH} childCount={canvas.transform.childCount}");
|
||||
|
||||
// Log what each collector finds
|
||||
int prevCount = elements.Count;
|
||||
CollectImages(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements);
|
||||
Debug.Log($"[UIExporter] Images: {elements.Count - prevCount}");
|
||||
prevCount = elements.Count;
|
||||
CollectBoxes(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements);
|
||||
Debug.Log($"[UIExporter] Boxes: {elements.Count - prevCount}");
|
||||
prevCount = elements.Count;
|
||||
CollectTexts(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements, uniqueFonts);
|
||||
Debug.Log($"[UIExporter] Texts: {elements.Count - prevCount}");
|
||||
prevCount = elements.Count;
|
||||
CollectProgressBars(canvas.transform, canvasRect, scaleX, scaleY, resolution, elements);
|
||||
Debug.Log($"[UIExporter] ProgressBars: {elements.Count - prevCount}");
|
||||
Debug.Log($"[UIExporter] TOTAL elements: {elements.Count}");
|
||||
|
||||
string name = canvas.CanvasName ?? "canvas";
|
||||
if (name.Length > 24) name = name.Substring(0, 24);
|
||||
|
||||
Reference in New Issue
Block a user