using System.Collections.Generic; using SplashEdit.RuntimeCode; using UnityEngine; using UnityEngine.Serialization; namespace SplashEdit.RuntimeCode { /// /// Collision type for PS1 runtime /// public enum PSXCollisionType { None = 0, // No collision Solid = 1, // Solid collision - blocks movement Trigger = 2, // Trigger - fires events but doesn't block Platform = 3 // Platform - solid from above, passable from below } /// /// Object behavior flags for PS1 runtime /// [System.Flags] public enum PSXObjectFlags { None = 0, Static = 1 << 0, // Object never moves (can be optimized) Dynamic = 1 << 1, // Object can move Visible = 1 << 2, // Object is rendered CastsShadow = 1 << 3, // Object casts shadows (future) ReceivesShadow = 1 << 4, // Object receives shadows (future) Interactable = 1 << 5, // Player can interact with this AlwaysRender = 1 << 6, // Skip frustum culling for this object } [RequireComponent(typeof(Renderer))] public class PSXObjectExporter : MonoBehaviour, IPSXExportable { public LuaFile LuaFile => luaFile; [FormerlySerializedAs("IsActive")] [SerializeField] private bool isActive = true; public bool IsActive => isActive; public List Textures { get; set; } = new List(); public PSXMesh Mesh { get; protected set; } [Header("Export Settings")] [FormerlySerializedAs("BitDepth")] [SerializeField] private PSXBPP bitDepth = PSXBPP.TEX_8BIT; [SerializeField] private LuaFile luaFile; [Header("Object Flags")] [SerializeField] private PSXObjectFlags objectFlags = PSXObjectFlags.Static | PSXObjectFlags.Visible; [Header("Collision Settings")] [SerializeField] private PSXCollisionType collisionType = PSXCollisionType.None; [SerializeField] private bool exportCollisionMesh = false; [SerializeField] private Mesh customCollisionMesh; // Optional simplified collision mesh [Tooltip("Layer mask for collision detection (1-8)")] [Range(1, 8)] [SerializeField] private int collisionLayer = 1; [Header("Navigation")] [Tooltip("Include this object's walkable surfaces in nav region generation")] [SerializeField] private bool generateNavigation = false; [Header("Gizmo Settings")] [FormerlySerializedAs("PreviewNormals")] [SerializeField] private bool previewNormals = false; [SerializeField] private float normalPreviewLength = 0.5f; [SerializeField] private bool showCollisionBounds = true; // Public accessors for editor and export public PSXBPP BitDepth => bitDepth; public PSXCollisionType CollisionType => collisionType; public bool ExportCollisionMesh => exportCollisionMesh; public Mesh CustomCollisionMesh => customCollisionMesh; public int CollisionLayer => collisionLayer; public PSXObjectFlags ObjectFlags => objectFlags; public bool GenerateNavigation => generateNavigation; // For assigning texture from editor public Texture2D texture; private readonly Dictionary<(int, PSXBPP), PSXTexture2D> cache = new(); private void OnDrawGizmos() { if (previewNormals) { MeshFilter filter = GetComponent(); if (filter != null) { Mesh mesh = filter.sharedMesh; Vector3[] vertices = mesh.vertices; Vector3[] normals = mesh.normals; Gizmos.color = Color.green; for (int i = 0; i < vertices.Length; i++) { Vector3 worldVertex = transform.TransformPoint(vertices[i]); Vector3 worldNormal = transform.TransformDirection(normals[i]); Gizmos.DrawLine(worldVertex, worldVertex + worldNormal * normalPreviewLength); } } } } private void OnDrawGizmosSelected() { // Draw collision bounds when object is selected if (showCollisionBounds && collisionType != PSXCollisionType.None) { MeshFilter filter = GetComponent(); Mesh collisionMesh = customCollisionMesh != null ? customCollisionMesh : (filter?.sharedMesh); if (collisionMesh != null) { Bounds bounds = collisionMesh.bounds; // Choose color based on collision type switch (collisionType) { case PSXCollisionType.Solid: Gizmos.color = new Color(1f, 0.3f, 0.3f, 0.5f); // Red break; case PSXCollisionType.Trigger: Gizmos.color = new Color(0.3f, 1f, 0.3f, 0.5f); // Green break; case PSXCollisionType.Platform: Gizmos.color = new Color(0.3f, 0.3f, 1f, 0.5f); // Blue break; } // Draw AABB Matrix4x4 oldMatrix = Gizmos.matrix; Gizmos.matrix = transform.localToWorldMatrix; Gizmos.DrawWireCube(bounds.center, bounds.size); // Draw filled with lower alpha Color fillColor = Gizmos.color; fillColor.a = 0.1f; Gizmos.color = fillColor; Gizmos.DrawCube(bounds.center, bounds.size); Gizmos.matrix = oldMatrix; } } } public void CreatePSXTextures2D() { Renderer renderer = GetComponent(); Textures.Clear(); if (renderer != null) { // If an override texture is set, use it for all submeshes if (texture != null) { PSXTexture2D tex; if (cache.ContainsKey((texture.GetInstanceID(), bitDepth))) { tex = cache[(texture.GetInstanceID(), bitDepth)]; } else { tex = PSXTexture2D.CreateFromTexture2D(texture, bitDepth); tex.OriginalTexture = texture; cache.Add((texture.GetInstanceID(), bitDepth), tex); } Textures.Add(tex); return; } Material[] materials = renderer.sharedMaterials; foreach (Material mat in materials) { if (mat != null && mat.mainTexture != null) { Texture mainTexture = mat.mainTexture; Texture2D tex2D = null; if (mainTexture is Texture2D existingTex2D) { tex2D = existingTex2D; } else { tex2D = ConvertToTexture2D(mainTexture); } if (tex2D != null) { PSXTexture2D tex; if (cache.ContainsKey((tex2D.GetInstanceID(), bitDepth))) { tex = cache[(tex2D.GetInstanceID(), bitDepth)]; } else { tex = PSXTexture2D.CreateFromTexture2D(tex2D, bitDepth); tex.OriginalTexture = tex2D; cache.Add((tex2D.GetInstanceID(), bitDepth), tex); } Textures.Add(tex); } } } } } private Texture2D ConvertToTexture2D(Texture texture) { Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false); RenderTexture currentActiveRT = RenderTexture.active; RenderTexture.active = texture as RenderTexture; texture2D.ReadPixels(new Rect(0, 0, texture.width, texture.height), 0, 0); texture2D.Apply(); RenderTexture.active = currentActiveRT; return texture2D; } public PSXTexture2D GetTexture(int index) { if (index >= 0 && index < Textures.Count) { return Textures[index]; } return null; } public void CreatePSXMesh(float GTEScaling) { Renderer renderer = GetComponent(); if (renderer != null) { Mesh = PSXMesh.CreateFromUnityRenderer(renderer, GTEScaling, transform, Textures); } } } }