feature: Added psxsplash installer, added basic BSP implementation (non-functional)
This commit is contained in:
845
Runtime/BSP.cs
Normal file
845
Runtime/BSP.cs
Normal file
@@ -0,0 +1,845 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Diagnostics;
|
||||
using SplashEdit.RuntimeCode;
|
||||
|
||||
public class BSP
|
||||
{
|
||||
private List<PSXObjectExporter> _objects;
|
||||
private Node root;
|
||||
private const float EPSILON = 1e-6f;
|
||||
private const int MAX_TRIANGLES_PER_LEAF = 256;
|
||||
private const int MAX_TREE_DEPTH = 50;
|
||||
private const int CANDIDATE_PLANE_COUNT = 15;
|
||||
|
||||
// Statistics
|
||||
private int totalTrianglesProcessed;
|
||||
private int totalSplits;
|
||||
private int treeDepth;
|
||||
private Stopwatch buildTimer;
|
||||
|
||||
public bool verboseLogging = false;
|
||||
|
||||
// Store the triangle that was used for the split plane for debugging
|
||||
private Dictionary<Node, Triangle> splitPlaneTriangles = new Dictionary<Node, Triangle>();
|
||||
|
||||
private struct Triangle
|
||||
{
|
||||
public Vector3 v0;
|
||||
public Vector3 v1;
|
||||
public Vector3 v2;
|
||||
public Vector3 n0;
|
||||
public Vector3 n1;
|
||||
public Vector3 n2;
|
||||
public Vector2 uv0;
|
||||
public Vector2 uv1;
|
||||
public Vector2 uv2;
|
||||
public Plane plane;
|
||||
public Bounds bounds;
|
||||
public PSXObjectExporter sourceExporter;
|
||||
public int materialIndex; // Store material index instead of submesh index
|
||||
|
||||
public Triangle(Vector3 a, Vector3 b, Vector3 c, Vector3 na, Vector3 nb, Vector3 nc,
|
||||
Vector2 uva, Vector2 uvb, Vector2 uvc, PSXObjectExporter exporter, int matIndex)
|
||||
{
|
||||
v0 = a;
|
||||
v1 = b;
|
||||
v2 = c;
|
||||
n0 = na;
|
||||
n1 = nb;
|
||||
n2 = nc;
|
||||
uv0 = uva;
|
||||
uv1 = uvb;
|
||||
uv2 = uvc;
|
||||
sourceExporter = exporter;
|
||||
materialIndex = matIndex;
|
||||
|
||||
// Calculate plane
|
||||
Vector3 edge1 = v1 - v0;
|
||||
Vector3 edge2 = v2 - v0;
|
||||
Vector3 normal = Vector3.Cross(edge1, edge2);
|
||||
|
||||
if (normal.sqrMagnitude < 1e-4f)
|
||||
{
|
||||
plane = new Plane(Vector3.up, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
normal.Normalize();
|
||||
plane = new Plane(normal, v0);
|
||||
}
|
||||
|
||||
// Calculate bounds
|
||||
bounds = new Bounds(v0, Vector3.zero);
|
||||
bounds.Encapsulate(v1);
|
||||
bounds.Encapsulate(v2);
|
||||
}
|
||||
|
||||
public void Transform(Matrix4x4 matrix)
|
||||
{
|
||||
v0 = matrix.MultiplyPoint3x4(v0);
|
||||
v1 = matrix.MultiplyPoint3x4(v1);
|
||||
v2 = matrix.MultiplyPoint3x4(v2);
|
||||
|
||||
// Transform normals (using inverse transpose for correct scaling)
|
||||
Matrix4x4 invTranspose = matrix.inverse.transpose;
|
||||
n0 = invTranspose.MultiplyVector(n0).normalized;
|
||||
n1 = invTranspose.MultiplyVector(n1).normalized;
|
||||
n2 = invTranspose.MultiplyVector(n2).normalized;
|
||||
|
||||
// Recalculate plane and bounds after transformation
|
||||
Vector3 edge1 = v1 - v0;
|
||||
Vector3 edge2 = v2 - v0;
|
||||
Vector3 normal = Vector3.Cross(edge1, edge2);
|
||||
|
||||
if (normal.sqrMagnitude < 1e-4f)
|
||||
{
|
||||
plane = new Plane(Vector3.up, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
normal.Normalize();
|
||||
plane = new Plane(normal, v0);
|
||||
}
|
||||
|
||||
bounds = new Bounds(v0, Vector3.zero);
|
||||
bounds.Encapsulate(v1);
|
||||
bounds.Encapsulate(v2);
|
||||
}
|
||||
}
|
||||
|
||||
private class Node
|
||||
{
|
||||
public Plane plane;
|
||||
public Node front;
|
||||
public Node back;
|
||||
public List<Triangle> triangles;
|
||||
public bool isLeaf = false;
|
||||
public Bounds bounds;
|
||||
public int depth;
|
||||
public int triangleSourceIndex = -1;
|
||||
}
|
||||
|
||||
public BSP(List<PSXObjectExporter> objects)
|
||||
{
|
||||
_objects = objects;
|
||||
buildTimer = new Stopwatch();
|
||||
}
|
||||
|
||||
public void Build()
|
||||
{
|
||||
buildTimer.Start();
|
||||
|
||||
List<Triangle> triangles = ExtractTrianglesFromMeshes();
|
||||
totalTrianglesProcessed = triangles.Count;
|
||||
|
||||
if (verboseLogging)
|
||||
UnityEngine.Debug.Log($"Starting BSP build with {totalTrianglesProcessed} triangles");
|
||||
|
||||
if (triangles.Count == 0)
|
||||
{
|
||||
root = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate overall bounds
|
||||
Bounds overallBounds = CalculateBounds(triangles);
|
||||
|
||||
// Build tree recursively with depth tracking
|
||||
root = BuildNode(triangles, overallBounds, 0);
|
||||
|
||||
// Create modified meshes for all exporters
|
||||
CreateModifiedMeshes();
|
||||
|
||||
buildTimer.Stop();
|
||||
|
||||
if (verboseLogging)
|
||||
{
|
||||
UnityEngine.Debug.Log($"BSP build completed in {buildTimer.Elapsed.TotalMilliseconds}ms");
|
||||
UnityEngine.Debug.Log($"Total splits: {totalSplits}, Max depth: {treeDepth}");
|
||||
}
|
||||
}
|
||||
|
||||
private List<Triangle> ExtractTrianglesFromMeshes()
|
||||
{
|
||||
List<Triangle> triangles = new List<Triangle>();
|
||||
|
||||
foreach (var meshObj in _objects)
|
||||
{
|
||||
if (!meshObj.IsActive) continue;
|
||||
|
||||
MeshFilter mf = meshObj.GetComponent<MeshFilter>();
|
||||
Renderer renderer = meshObj.GetComponent<Renderer>();
|
||||
if (mf == null || mf.sharedMesh == null || renderer == null) continue;
|
||||
|
||||
Mesh mesh = mf.sharedMesh;
|
||||
Vector3[] vertices = mesh.vertices;
|
||||
Vector3[] normals = mesh.normals.Length > 0 ? mesh.normals : new Vector3[vertices.Length];
|
||||
Vector2[] uvs = mesh.uv.Length > 0 ? mesh.uv : new Vector2[vertices.Length];
|
||||
Matrix4x4 matrix = meshObj.transform.localToWorldMatrix;
|
||||
|
||||
// Handle case where normals are missing
|
||||
if (mesh.normals.Length == 0)
|
||||
{
|
||||
for (int i = 0; i < normals.Length; i++)
|
||||
{
|
||||
normals[i] = Vector3.up;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle case where UVs are missing
|
||||
if (mesh.uv.Length == 0)
|
||||
{
|
||||
for (int i = 0; i < uvs.Length; i++)
|
||||
{
|
||||
uvs[i] = Vector2.zero;
|
||||
}
|
||||
}
|
||||
|
||||
// Process each submesh and track material index
|
||||
for (int submesh = 0; submesh < mesh.subMeshCount; submesh++)
|
||||
{
|
||||
int materialIndex = Mathf.Min(submesh, renderer.sharedMaterials.Length - 1);
|
||||
int[] indices = mesh.GetTriangles(submesh);
|
||||
|
||||
for (int i = 0; i < indices.Length; i += 3)
|
||||
{
|
||||
int idx0 = indices[i];
|
||||
int idx1 = indices[i + 1];
|
||||
int idx2 = indices[i + 2];
|
||||
|
||||
Vector3 v0 = vertices[idx0];
|
||||
Vector3 v1 = vertices[idx1];
|
||||
Vector3 v2 = vertices[idx2];
|
||||
|
||||
// Skip degenerate triangles
|
||||
if (Vector3.Cross(v1 - v0, v2 - v0).sqrMagnitude < 1e-4f)
|
||||
continue;
|
||||
|
||||
Vector3 n0 = normals[idx0];
|
||||
Vector3 n1 = normals[idx1];
|
||||
Vector3 n2 = normals[idx2];
|
||||
|
||||
Vector2 uv0 = uvs[idx0];
|
||||
Vector2 uv1 = uvs[idx1];
|
||||
Vector2 uv2 = uvs[idx2];
|
||||
|
||||
Triangle tri = new Triangle(v0, v1, v2, n0, n1, n2, uv0, uv1, uv2, meshObj, materialIndex);
|
||||
tri.Transform(matrix);
|
||||
triangles.Add(tri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return triangles;
|
||||
}
|
||||
|
||||
private Node BuildNode(List<Triangle> triangles, Bounds bounds, int depth)
|
||||
{
|
||||
if (triangles == null || triangles.Count == 0)
|
||||
return null;
|
||||
|
||||
Node node = new Node
|
||||
{
|
||||
triangles = new List<Triangle>(),
|
||||
bounds = bounds,
|
||||
depth = depth
|
||||
};
|
||||
|
||||
treeDepth = Mathf.Max(treeDepth, depth);
|
||||
|
||||
// Create leaf node if conditions are met
|
||||
if (triangles.Count <= MAX_TRIANGLES_PER_LEAF || depth >= MAX_TREE_DEPTH)
|
||||
{
|
||||
node.isLeaf = true;
|
||||
node.triangles = triangles;
|
||||
|
||||
if (verboseLogging && depth >= MAX_TREE_DEPTH)
|
||||
UnityEngine.Debug.LogWarning($"Max tree depth reached at depth {depth} with {triangles.Count} triangles");
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// Select the best splitting plane using multiple strategies
|
||||
Triangle? splitTriangle = null;
|
||||
if (!SelectBestSplittingPlane(triangles, bounds, out node.plane, out splitTriangle))
|
||||
{
|
||||
// Fallback: create leaf if no good split found
|
||||
node.isLeaf = true;
|
||||
node.triangles = triangles;
|
||||
|
||||
if (verboseLogging)
|
||||
UnityEngine.Debug.Log($"Created leaf node with {triangles.Count} triangles (no good split found)");
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// Store the triangle that provided the split plane for debugging
|
||||
if (splitTriangle.HasValue)
|
||||
{
|
||||
splitPlaneTriangles[node] = splitTriangle.Value;
|
||||
}
|
||||
|
||||
List<Triangle> frontList = new List<Triangle>();
|
||||
List<Triangle> backList = new List<Triangle>();
|
||||
List<Triangle> coplanarList = new List<Triangle>();
|
||||
|
||||
// Classify all triangles
|
||||
foreach (var tri in triangles)
|
||||
{
|
||||
ClassifyTriangle(tri, node.plane, coplanarList, frontList, backList);
|
||||
}
|
||||
|
||||
// Handle cases where splitting doesn't provide benefit
|
||||
if (frontList.Count == 0 || backList.Count == 0)
|
||||
{
|
||||
// If split doesn't separate geometry, create a leaf
|
||||
node.isLeaf = true;
|
||||
node.triangles = triangles;
|
||||
|
||||
if (verboseLogging)
|
||||
UnityEngine.Debug.Log($"Created leaf node with {triangles.Count} triangles (ineffective split)");
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
// Distribute coplanar triangles to the side with fewer triangles
|
||||
if (coplanarList.Count > 0)
|
||||
{
|
||||
if (frontList.Count <= backList.Count)
|
||||
{
|
||||
frontList.AddRange(coplanarList);
|
||||
}
|
||||
else
|
||||
{
|
||||
backList.AddRange(coplanarList);
|
||||
}
|
||||
}
|
||||
|
||||
if (verboseLogging)
|
||||
UnityEngine.Debug.Log($"Node at depth {depth}: {triangles.Count} triangles -> {frontList.Count} front, {backList.Count} back");
|
||||
|
||||
// Calculate bounds for children
|
||||
Bounds frontBounds = CalculateBounds(frontList);
|
||||
Bounds backBounds = CalculateBounds(backList);
|
||||
|
||||
// Recursively build child nodes
|
||||
node.front = BuildNode(frontList, frontBounds, depth + 1);
|
||||
node.back = BuildNode(backList, backBounds, depth + 1);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private bool SelectBestSplittingPlane(List<Triangle> triangles, Bounds bounds, out Plane bestPlane, out Triangle? splitTriangle)
|
||||
{
|
||||
bestPlane = new Plane();
|
||||
splitTriangle = null;
|
||||
int bestScore = int.MaxValue;
|
||||
bool foundValidPlane = false;
|
||||
|
||||
// Strategy 1: Try planes from triangle centroids
|
||||
int candidatesToTry = Mathf.Min(CANDIDATE_PLANE_COUNT, triangles.Count);
|
||||
for (int i = 0; i < candidatesToTry; i++)
|
||||
{
|
||||
Triangle tri = triangles[i];
|
||||
Plane candidate = tri.plane;
|
||||
|
||||
int score = EvaluateSplitPlane(triangles, candidate);
|
||||
if (score < bestScore && score >= 0)
|
||||
{
|
||||
bestScore = score;
|
||||
bestPlane = candidate;
|
||||
splitTriangle = tri;
|
||||
foundValidPlane = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: Try axis-aligned planes through bounds center
|
||||
if (!foundValidPlane || bestScore > triangles.Count * 3)
|
||||
{
|
||||
Vector3[] axes = { Vector3.right, Vector3.up, Vector3.forward };
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
Plane candidate = new Plane(axes[i], bounds.center);
|
||||
int score = EvaluateSplitPlane(triangles, candidate);
|
||||
if (score < bestScore && score >= 0)
|
||||
{
|
||||
bestScore = score;
|
||||
bestPlane = candidate;
|
||||
splitTriangle = null;
|
||||
foundValidPlane = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: Try planes based on bounds extents
|
||||
if (!foundValidPlane)
|
||||
{
|
||||
Vector3 extents = bounds.extents;
|
||||
if (extents.x >= extents.y && extents.x >= extents.z)
|
||||
bestPlane = new Plane(Vector3.right, bounds.center);
|
||||
else if (extents.y >= extents.x && extents.y >= extents.z)
|
||||
bestPlane = new Plane(Vector3.up, bounds.center);
|
||||
else
|
||||
bestPlane = new Plane(Vector3.forward, bounds.center);
|
||||
|
||||
splitTriangle = null;
|
||||
foundValidPlane = true;
|
||||
}
|
||||
|
||||
return foundValidPlane;
|
||||
}
|
||||
|
||||
private int EvaluateSplitPlane(List<Triangle> triangles, Plane plane)
|
||||
{
|
||||
int frontCount = 0;
|
||||
int backCount = 0;
|
||||
int splitCount = 0;
|
||||
int coplanarCount = 0;
|
||||
|
||||
foreach (var tri in triangles)
|
||||
{
|
||||
float d0 = plane.GetDistanceToPoint(tri.v0);
|
||||
float d1 = plane.GetDistanceToPoint(tri.v1);
|
||||
float d2 = plane.GetDistanceToPoint(tri.v2);
|
||||
|
||||
// Check for NaN/infinity
|
||||
if (float.IsNaN(d0) || float.IsNaN(d1) || float.IsNaN(d2) ||
|
||||
float.IsInfinity(d0) || float.IsInfinity(d1) || float.IsInfinity(d2))
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
|
||||
bool front = d0 > EPSILON || d1 > EPSILON || d2 > EPSILON;
|
||||
bool back = d0 < -EPSILON || d1 < -EPSILON || d2 < -EPSILON;
|
||||
|
||||
if (front && back)
|
||||
splitCount++;
|
||||
else if (front)
|
||||
frontCount++;
|
||||
else if (back)
|
||||
backCount++;
|
||||
else
|
||||
coplanarCount++;
|
||||
}
|
||||
|
||||
// Reject planes that would cause too many splits or imbalanced trees
|
||||
if (splitCount > triangles.Count / 2)
|
||||
return int.MaxValue;
|
||||
|
||||
// Score based on balance and split count
|
||||
return Mathf.Abs(frontCount - backCount) + splitCount * 2;
|
||||
}
|
||||
|
||||
private void ClassifyTriangle(Triangle tri, Plane plane, List<Triangle> coplanar, List<Triangle> front, List<Triangle> back)
|
||||
{
|
||||
float d0 = plane.GetDistanceToPoint(tri.v0);
|
||||
float d1 = plane.GetDistanceToPoint(tri.v1);
|
||||
float d2 = plane.GetDistanceToPoint(tri.v2);
|
||||
|
||||
// Check for numerical issues
|
||||
if (float.IsNaN(d0) || float.IsNaN(d1) || float.IsNaN(d2) ||
|
||||
float.IsInfinity(d0) || float.IsInfinity(d1) || float.IsInfinity(d2))
|
||||
{
|
||||
coplanar.Add(tri);
|
||||
return;
|
||||
}
|
||||
|
||||
bool front0 = d0 > EPSILON;
|
||||
bool front1 = d1 > EPSILON;
|
||||
bool front2 = d2 > EPSILON;
|
||||
|
||||
bool back0 = d0 < -EPSILON;
|
||||
bool back1 = d1 < -EPSILON;
|
||||
bool back2 = d2 < -EPSILON;
|
||||
|
||||
int fCount = (front0 ? 1 : 0) + (front1 ? 1 : 0) + (front2 ? 1 : 0);
|
||||
int bCount = (back0 ? 1 : 0) + (back1 ? 1 : 0) + (back2 ? 1 : 0);
|
||||
|
||||
if (fCount == 3)
|
||||
{
|
||||
front.Add(tri);
|
||||
}
|
||||
else if (bCount == 3)
|
||||
{
|
||||
back.Add(tri);
|
||||
}
|
||||
else if (fCount == 0 && bCount == 0)
|
||||
{
|
||||
coplanar.Add(tri);
|
||||
}
|
||||
else
|
||||
{
|
||||
totalSplits++;
|
||||
SplitTriangle(tri, plane, front, back);
|
||||
}
|
||||
}
|
||||
|
||||
private void SplitTriangle(Triangle tri, Plane plane, List<Triangle> front, List<Triangle> back)
|
||||
{
|
||||
// Get distances
|
||||
float d0 = plane.GetDistanceToPoint(tri.v0);
|
||||
float d1 = plane.GetDistanceToPoint(tri.v1);
|
||||
float d2 = plane.GetDistanceToPoint(tri.v2);
|
||||
|
||||
// Classify points
|
||||
bool[] frontSide = { d0 > EPSILON, d1 > EPSILON, d2 > EPSILON };
|
||||
bool[] backSide = { d0 < -EPSILON, d1 < -EPSILON, d2 < -EPSILON };
|
||||
|
||||
// Count how many points are on each side
|
||||
int frontCount = (frontSide[0] ? 1 : 0) + (frontSide[1] ? 1 : 0) + (frontSide[2] ? 1 : 0);
|
||||
int backCount = (backSide[0] ? 1 : 0) + (backSide[1] ? 1 : 0) + (backSide[2] ? 1 : 0);
|
||||
|
||||
// 2 points on one side, 1 on the other
|
||||
if (frontCount == 2 && backCount == 1)
|
||||
{
|
||||
int loneIndex = backSide[0] ? 0 : (backSide[1] ? 1 : 2);
|
||||
SplitTriangle2To1(tri, plane, loneIndex, true, front, back);
|
||||
}
|
||||
else if (backCount == 2 && frontCount == 1)
|
||||
{
|
||||
int loneIndex = frontSide[0] ? 0 : (frontSide[1] ? 1 : 2);
|
||||
SplitTriangle2To1(tri, plane, loneIndex, false, front, back);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Complex case - add to both sides (should be rare)
|
||||
front.Add(tri);
|
||||
back.Add(tri);
|
||||
}
|
||||
}
|
||||
|
||||
private void SplitTriangle2To1(Triangle tri, Plane plane, int loneIndex, bool loneIsBack,
|
||||
List<Triangle> front, List<Triangle> back)
|
||||
{
|
||||
Vector3[] v = { tri.v0, tri.v1, tri.v2 };
|
||||
Vector3[] n = { tri.n0, tri.n1, tri.n2 };
|
||||
Vector2[] uv = { tri.uv0, tri.uv1, tri.uv2 };
|
||||
|
||||
Vector3 loneVertex = v[loneIndex];
|
||||
Vector3 loneNormal = n[loneIndex];
|
||||
Vector2 loneUV = uv[loneIndex];
|
||||
|
||||
Vector3 v1 = v[(loneIndex + 1) % 3];
|
||||
Vector3 v2 = v[(loneIndex + 2) % 3];
|
||||
Vector3 n1 = n[(loneIndex + 1) % 3];
|
||||
Vector3 n2 = n[(loneIndex + 2) % 3];
|
||||
Vector2 uv1 = uv[(loneIndex + 1) % 3];
|
||||
Vector2 uv2 = uv[(loneIndex + 2) % 3];
|
||||
|
||||
Vector3 i1 = PlaneIntersection(plane, loneVertex, v1);
|
||||
float t1 = CalculateInterpolationFactor(plane, loneVertex, v1);
|
||||
Vector3 n_i1 = Vector3.Lerp(loneNormal, n1, t1).normalized;
|
||||
Vector2 uv_i1 = Vector2.Lerp(loneUV, uv1, t1);
|
||||
|
||||
Vector3 i2 = PlaneIntersection(plane, loneVertex, v2);
|
||||
float t2 = CalculateInterpolationFactor(plane, loneVertex, v2);
|
||||
Vector3 n_i2 = Vector3.Lerp(loneNormal, n2, t2).normalized;
|
||||
Vector2 uv_i2 = Vector2.Lerp(loneUV, uv2, t2);
|
||||
|
||||
// Desired normal: prefer triangle's plane normal, fallback to geometric normal
|
||||
Vector3 desired = tri.plane.normal;
|
||||
if (desired.sqrMagnitude < 1e-4f)
|
||||
desired = Vector3.Cross(tri.v1 - tri.v0, tri.v2 - tri.v0).normalized;
|
||||
if (desired.sqrMagnitude < 1e-4f)
|
||||
desired = Vector3.up;
|
||||
|
||||
// Helper: decide and swap b/c if necessary, then add triangle
|
||||
void AddTriClockwise(List<Triangle> list,
|
||||
Vector3 a, Vector3 b, Vector3 c,
|
||||
Vector3 na, Vector3 nb, Vector3 nc,
|
||||
Vector2 ua, Vector2 ub, Vector2 uc)
|
||||
{
|
||||
Vector3 cross = Vector3.Cross(b - a, c - a);
|
||||
if (cross.z > 0f) // <-- assumes you're working in PS1 screen space (z forward)
|
||||
{
|
||||
// swap b <-> c
|
||||
var tmpV = b; b = c; c = tmpV;
|
||||
var tmpN = nb; nb = nc; nc = tmpN;
|
||||
var tmpUv = ub; ub = uc; uc = tmpUv;
|
||||
}
|
||||
|
||||
list.Add(new Triangle(a, b, c, na, nb, nc, ua, ub, uc, tri.sourceExporter, tri.materialIndex));
|
||||
}
|
||||
|
||||
if (loneIsBack)
|
||||
{
|
||||
// back: (lone, i1, i2)
|
||||
AddTriClockwise(back, loneVertex, i1, i2, loneNormal, n_i1, n_i2, loneUV, uv_i1, uv_i2);
|
||||
|
||||
// front: (v1, i1, i2) and (v1, i2, v2)
|
||||
AddTriClockwise(front, v1, i1, i2, n1, n_i1, n_i2, uv1, uv_i1, uv_i2);
|
||||
AddTriClockwise(front, v1, i2, v2, n1, n_i2, n2, uv1, uv_i2, uv2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// front: (lone, i1, i2)
|
||||
AddTriClockwise(front, loneVertex, i1, i2, loneNormal, n_i1, n_i2, loneUV, uv_i1, uv_i2);
|
||||
|
||||
// back: (v1, i1, i2) and (v1, i2, v2)
|
||||
AddTriClockwise(back, v1, i1, i2, n1, n_i1, n_i2, uv1, uv_i1, uv_i2);
|
||||
AddTriClockwise(back, v1, i2, v2, n1, n_i2, n2, uv1, uv_i2, uv2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Vector3 PlaneIntersection(Plane plane, Vector3 a, Vector3 b)
|
||||
{
|
||||
Vector3 ba = b - a;
|
||||
float denominator = Vector3.Dot(plane.normal, ba);
|
||||
|
||||
// Check for parallel line (shouldn't happen in our case)
|
||||
if (Mathf.Abs(denominator) < 1e-4f)
|
||||
return a;
|
||||
|
||||
float t = (-plane.distance - Vector3.Dot(plane.normal, a)) / denominator;
|
||||
return a + ba * Mathf.Clamp01(t);
|
||||
}
|
||||
|
||||
private float CalculateInterpolationFactor(Plane plane, Vector3 a, Vector3 b)
|
||||
{
|
||||
Vector3 ba = b - a;
|
||||
float denominator = Vector3.Dot(plane.normal, ba);
|
||||
|
||||
if (Mathf.Abs(denominator) < 1e-4f)
|
||||
return 0.5f;
|
||||
|
||||
float t = (-plane.distance - Vector3.Dot(plane.normal, a)) / denominator;
|
||||
return Mathf.Clamp01(t);
|
||||
}
|
||||
|
||||
private Bounds CalculateBounds(List<Triangle> triangles)
|
||||
{
|
||||
if (triangles == null || triangles.Count == 0)
|
||||
return new Bounds();
|
||||
|
||||
Bounds bounds = triangles[0].bounds;
|
||||
for (int i = 1; i < triangles.Count; i++)
|
||||
{
|
||||
bounds.Encapsulate(triangles[i].bounds);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
// Add a method to create modified meshes after BSP construction
|
||||
// Add a method to create modified meshes after BSP construction
|
||||
private void CreateModifiedMeshes()
|
||||
{
|
||||
if (root == null) return;
|
||||
|
||||
// Collect all triangles from the BSP tree
|
||||
List<Triangle> allTriangles = new List<Triangle>();
|
||||
CollectTrianglesFromNode(root, allTriangles);
|
||||
|
||||
// Group triangles by their source exporter and material index
|
||||
Dictionary<PSXObjectExporter, Dictionary<int, List<Triangle>>> exporterTriangles =
|
||||
new Dictionary<PSXObjectExporter, Dictionary<int, List<Triangle>>>();
|
||||
|
||||
foreach (var tri in allTriangles)
|
||||
{
|
||||
if (!exporterTriangles.ContainsKey(tri.sourceExporter))
|
||||
{
|
||||
exporterTriangles[tri.sourceExporter] = new Dictionary<int, List<Triangle>>();
|
||||
}
|
||||
|
||||
var materialDict = exporterTriangles[tri.sourceExporter];
|
||||
if (!materialDict.ContainsKey(tri.materialIndex))
|
||||
{
|
||||
materialDict[tri.materialIndex] = new List<Triangle>();
|
||||
}
|
||||
|
||||
materialDict[tri.materialIndex].Add(tri);
|
||||
}
|
||||
|
||||
// Create modified meshes for each exporter
|
||||
foreach (var kvp in exporterTriangles)
|
||||
{
|
||||
PSXObjectExporter exporter = kvp.Key;
|
||||
Dictionary<int, List<Triangle>> materialTriangles = kvp.Value;
|
||||
|
||||
Mesh originalMesh = exporter.GetComponent<MeshFilter>().sharedMesh;
|
||||
Renderer renderer = exporter.GetComponent<Renderer>();
|
||||
|
||||
Mesh modifiedMesh = new Mesh();
|
||||
modifiedMesh.name = originalMesh.name + "_BSP";
|
||||
|
||||
List<Vector3> vertices = new List<Vector3>();
|
||||
List<Vector3> normals = new List<Vector3>();
|
||||
List<Vector2> uvs = new List<Vector2>();
|
||||
List<Vector4> tangents = new List<Vector4>();
|
||||
List<Color> colors = new List<Color>();
|
||||
|
||||
// Create a list for each material's triangles
|
||||
List<List<int>> materialIndices = new List<List<int>>();
|
||||
for (int i = 0; i < renderer.sharedMaterials.Length; i++)
|
||||
{
|
||||
materialIndices.Add(new List<int>());
|
||||
}
|
||||
|
||||
// Get the inverse transform to convert from world space back to object space
|
||||
Matrix4x4 worldToLocal = exporter.transform.worldToLocalMatrix;
|
||||
|
||||
// Process each material
|
||||
foreach (var materialKvp in materialTriangles)
|
||||
{
|
||||
int materialIndex = materialKvp.Key;
|
||||
List<Triangle> triangles = materialKvp.Value;
|
||||
|
||||
// Add vertices, normals, and uvs for this material
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
Triangle tri = triangles[i];
|
||||
|
||||
// Transform vertices from world space back to object space
|
||||
Vector3 v0 = worldToLocal.MultiplyPoint3x4(tri.v0);
|
||||
Vector3 v1 = worldToLocal.MultiplyPoint3x4(tri.v1);
|
||||
Vector3 v2 = worldToLocal.MultiplyPoint3x4(tri.v2);
|
||||
|
||||
int vertexIndex = vertices.Count;
|
||||
vertices.Add(v0);
|
||||
vertices.Add(v1);
|
||||
vertices.Add(v2);
|
||||
|
||||
// Transform normals from world space back to object space
|
||||
Vector3 n0 = worldToLocal.MultiplyVector(tri.n0).normalized;
|
||||
Vector3 n1 = worldToLocal.MultiplyVector(tri.n1).normalized;
|
||||
Vector3 n2 = worldToLocal.MultiplyVector(tri.n2).normalized;
|
||||
|
||||
normals.Add(n0);
|
||||
normals.Add(n1);
|
||||
normals.Add(n2);
|
||||
|
||||
uvs.Add(tri.uv0);
|
||||
uvs.Add(tri.uv1);
|
||||
uvs.Add(tri.uv2);
|
||||
|
||||
// Add default tangents and colors (will be recalculated later)
|
||||
tangents.Add(new Vector4(1, 0, 0, 1));
|
||||
tangents.Add(new Vector4(1, 0, 0, 1));
|
||||
tangents.Add(new Vector4(1, 0, 0, 1));
|
||||
|
||||
colors.Add(Color.white);
|
||||
colors.Add(Color.white);
|
||||
colors.Add(Color.white);
|
||||
|
||||
// Add indices for this material
|
||||
materialIndices[materialIndex].Add(vertexIndex);
|
||||
materialIndices[materialIndex].Add(vertexIndex + 1);
|
||||
materialIndices[materialIndex].Add(vertexIndex + 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Assign data to the mesh
|
||||
modifiedMesh.vertices = vertices.ToArray();
|
||||
modifiedMesh.normals = normals.ToArray();
|
||||
modifiedMesh.uv = uvs.ToArray();
|
||||
modifiedMesh.tangents = tangents.ToArray();
|
||||
modifiedMesh.colors = colors.ToArray();
|
||||
|
||||
// Set up submeshes based on materials
|
||||
modifiedMesh.subMeshCount = materialIndices.Count;
|
||||
for (int i = 0; i < materialIndices.Count; i++)
|
||||
{
|
||||
modifiedMesh.SetTriangles(materialIndices[i].ToArray(), i);
|
||||
}
|
||||
|
||||
// Recalculate important mesh properties
|
||||
modifiedMesh.RecalculateBounds();
|
||||
modifiedMesh.RecalculateTangents();
|
||||
|
||||
// Assign the modified mesh to the exporter
|
||||
exporter.ModifiedMesh = modifiedMesh;
|
||||
}
|
||||
}
|
||||
// Helper method to collect all triangles from the BSP tree
|
||||
private void CollectTrianglesFromNode(Node node, List<Triangle> triangles)
|
||||
{
|
||||
if (node == null) return;
|
||||
|
||||
if (node.isLeaf)
|
||||
{
|
||||
triangles.AddRange(node.triangles);
|
||||
}
|
||||
else
|
||||
{
|
||||
CollectTrianglesFromNode(node.front, triangles);
|
||||
CollectTrianglesFromNode(node.back, triangles);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawGizmos(int maxDepth)
|
||||
{
|
||||
if (root == null) return;
|
||||
|
||||
DrawNodeGizmos(root, 0, maxDepth);
|
||||
}
|
||||
|
||||
private void DrawNodeGizmos(Node node, int depth, int maxDepth)
|
||||
{
|
||||
if (node == null) return;
|
||||
if (depth > maxDepth) return;
|
||||
|
||||
Color nodeColor = Color.HSVToRGB((depth * 0.1f) % 1f, 0.8f, 0.8f);
|
||||
Gizmos.color = nodeColor;
|
||||
|
||||
if (node.isLeaf)
|
||||
{
|
||||
foreach (var tri in node.triangles)
|
||||
{
|
||||
DrawTriangleGizmo(tri);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawPlaneGizmo(node.plane, node.bounds);
|
||||
|
||||
// Draw the triangle that was used for the split plane if available
|
||||
if (splitPlaneTriangles.ContainsKey(node))
|
||||
{
|
||||
Gizmos.color = Color.magenta;
|
||||
DrawTriangleGizmo(splitPlaneTriangles[node]);
|
||||
Gizmos.color = nodeColor;
|
||||
}
|
||||
|
||||
DrawNodeGizmos(node.front, depth + 1, maxDepth);
|
||||
DrawNodeGizmos(node.back, depth + 1, maxDepth);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTriangleGizmo(Triangle tri)
|
||||
{
|
||||
Gizmos.DrawLine(tri.v0, tri.v1);
|
||||
Gizmos.DrawLine(tri.v1, tri.v2);
|
||||
Gizmos.DrawLine(tri.v2, tri.v0);
|
||||
}
|
||||
|
||||
private void DrawPlaneGizmo(Plane plane, Bounds bounds)
|
||||
{
|
||||
Vector3 center = bounds.center;
|
||||
Vector3 normal = plane.normal;
|
||||
|
||||
Vector3 tangent = Vector3.Cross(normal, Vector3.up);
|
||||
if (tangent.magnitude < 0.1f) tangent = Vector3.Cross(normal, Vector3.right);
|
||||
tangent = tangent.normalized;
|
||||
|
||||
Vector3 bitangent = Vector3.Cross(normal, tangent).normalized;
|
||||
|
||||
float size = Mathf.Max(bounds.size.x, bounds.size.y, bounds.size.z) * 0.5f;
|
||||
tangent *= size;
|
||||
bitangent *= size;
|
||||
|
||||
Vector3 p0 = center - tangent - bitangent;
|
||||
Vector3 p1 = center + tangent - bitangent;
|
||||
Vector3 p2 = center + tangent + bitangent;
|
||||
Vector3 p3 = center - tangent + bitangent;
|
||||
|
||||
Gizmos.DrawLine(p0, p1);
|
||||
Gizmos.DrawLine(p1, p2);
|
||||
Gizmos.DrawLine(p2, p3);
|
||||
Gizmos.DrawLine(p3, p0);
|
||||
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawLine(center, center + normal * size * 0.5f);
|
||||
}
|
||||
}
|
||||
2
Runtime/BSP.cs.meta
Normal file
2
Runtime/BSP.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15144e67b42b92447a546346e594155b
|
||||
@@ -3,14 +3,36 @@ using UnityEngine;
|
||||
|
||||
namespace SplashEdit.RuntimeCode
|
||||
{
|
||||
|
||||
public enum PSXConnectionType
|
||||
{
|
||||
REAL_HARDWARE, // Unirom
|
||||
EMULATOR // PCSX-Redux
|
||||
}
|
||||
|
||||
[CreateAssetMenu(fileName = "PSXData", menuName = "Scriptable Objects/PSXData")]
|
||||
public class PSXData : ScriptableObject
|
||||
{
|
||||
|
||||
// Texture packing settings
|
||||
public Vector2 OutputResolution = new Vector2(320, 240);
|
||||
public bool DualBuffering = true;
|
||||
public bool VerticalBuffering = true;
|
||||
public List<ProhibitedArea> ProhibitedAreas = new List<ProhibitedArea>();
|
||||
|
||||
|
||||
// Connection settings
|
||||
public PSXConnectionType ConnectionType = PSXConnectionType.REAL_HARDWARE;
|
||||
|
||||
// Real hardware settings
|
||||
public string PortName = "COM3";
|
||||
public int BaudRate = 0;
|
||||
|
||||
// Emulator settings
|
||||
public string PCSXReduxPath = "";
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ namespace SplashEdit.RuntimeCode
|
||||
public PSXVertex v1;
|
||||
public PSXVertex v2;
|
||||
|
||||
public int TextureIndex;
|
||||
public int TextureIndex;
|
||||
public readonly PSXVertex[] Vertexes => new PSXVertex[] { v0, v1, v2 };
|
||||
}
|
||||
|
||||
@@ -82,77 +82,176 @@ namespace SplashEdit.RuntimeCode
|
||||
/// <param name="textureHeight">Height of the texture (default is 256).</param>
|
||||
/// <param name="transform">Optional transform to convert vertices to world space.</param>
|
||||
/// <returns>A new PSXMesh containing the converted triangles.</returns>
|
||||
public static PSXMesh CreateFromUnityRenderer(Renderer renderer, float GTEScaling, Transform transform, List<PSXTexture2D> textures)
|
||||
{
|
||||
PSXMesh psxMesh = new PSXMesh { Triangles = new List<Tri>() };
|
||||
Material[] materials = renderer.sharedMaterials;
|
||||
Mesh mesh = renderer.GetComponent<MeshFilter>().sharedMesh;
|
||||
|
||||
for (int submeshIndex = 0; submeshIndex < materials.Length; submeshIndex++)
|
||||
{
|
||||
int[] submeshTriangles = mesh.GetTriangles(submeshIndex);
|
||||
Material material = materials[submeshIndex];
|
||||
Texture2D texture = material.mainTexture as Texture2D;
|
||||
|
||||
// Find texture index instead of the texture itself
|
||||
int textureIndex = -1;
|
||||
if (texture != null)
|
||||
public static PSXMesh CreateFromUnityRenderer(Renderer renderer, float GTEScaling, Transform transform, List<PSXTexture2D> textures)
|
||||
{
|
||||
for (int i = 0; i < textures.Count; i++)
|
||||
PSXMesh psxMesh = new PSXMesh { Triangles = new List<Tri>() };
|
||||
Material[] materials = renderer.sharedMaterials;
|
||||
Mesh mesh = renderer.GetComponent<MeshFilter>().sharedMesh;
|
||||
|
||||
for (int submeshIndex = 0; submeshIndex < materials.Length; submeshIndex++)
|
||||
{
|
||||
if (textures[i].OriginalTexture == texture)
|
||||
int[] submeshTriangles = mesh.GetTriangles(submeshIndex);
|
||||
Material material = materials[submeshIndex];
|
||||
Texture2D texture = material.mainTexture as Texture2D;
|
||||
|
||||
// Find texture index instead of the texture itself
|
||||
int textureIndex = -1;
|
||||
if (texture != null)
|
||||
{
|
||||
textureIndex = i;
|
||||
break;
|
||||
for (int i = 0; i < textures.Count; i++)
|
||||
{
|
||||
if (textures[i].OriginalTexture == texture)
|
||||
{
|
||||
textureIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (textureIndex == -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get mesh data arrays
|
||||
mesh.RecalculateNormals();
|
||||
Vector3[] vertices = mesh.vertices;
|
||||
Vector3[] normals = mesh.normals;
|
||||
Vector3[] smoothNormals = RecalculateSmoothNormals(mesh);
|
||||
Vector2[] uv = mesh.uv;
|
||||
|
||||
PSXVertex convertData(int index)
|
||||
{
|
||||
Vector3 v = Vector3.Scale(vertices[index], transform.lossyScale);
|
||||
Vector3 wv = transform.TransformPoint(vertices[index]);
|
||||
Vector3 wn = transform.TransformDirection(smoothNormals[index]).normalized;
|
||||
Color c = PSXLightingBaker.ComputeLighting(wv, wn);
|
||||
return ConvertToPSXVertex(v, GTEScaling, normals[index], uv[index], textures[textureIndex]?.Width, textures[textureIndex]?.Height, c);
|
||||
}
|
||||
|
||||
for (int i = 0; i < submeshTriangles.Length; i += 3)
|
||||
{
|
||||
int vid0 = submeshTriangles[i];
|
||||
int vid1 = submeshTriangles[i + 1];
|
||||
int vid2 = submeshTriangles[i + 2];
|
||||
|
||||
Vector3 faceNormal = Vector3.Cross(vertices[vid1] - vertices[vid0], vertices[vid2] - vertices[vid0]).normalized;
|
||||
|
||||
if (Vector3.Dot(faceNormal, normals[vid0]) < 0)
|
||||
{
|
||||
(vid1, vid2) = (vid2, vid1);
|
||||
}
|
||||
|
||||
psxMesh.Triangles.Add(new Tri
|
||||
{
|
||||
v0 = convertData(vid0),
|
||||
v1 = convertData(vid1),
|
||||
v2 = convertData(vid2),
|
||||
TextureIndex = textureIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return psxMesh;
|
||||
}
|
||||
|
||||
if (textureIndex == -1)
|
||||
public static PSXMesh CreateFromUnityMesh(Mesh mesh, Renderer renderer, float GTEScaling, Transform transform, List<PSXTexture2D> textures)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
PSXMesh psxMesh = new PSXMesh { Triangles = new List<Tri>() };
|
||||
Material[] materials = renderer.sharedMaterials;
|
||||
|
||||
// Get mesh data arrays
|
||||
mesh.RecalculateNormals();
|
||||
Vector3[] vertices = mesh.vertices;
|
||||
Vector3[] normals = mesh.normals;
|
||||
Vector3[] smoothNormals = RecalculateSmoothNormals(mesh);
|
||||
Vector2[] uv = mesh.uv;
|
||||
|
||||
PSXVertex convertData(int index)
|
||||
{
|
||||
Vector3 v = Vector3.Scale(vertices[index], transform.lossyScale);
|
||||
Vector3 wv = transform.TransformPoint(vertices[index]);
|
||||
Vector3 wn = transform.TransformDirection(smoothNormals[index]).normalized;
|
||||
Color c = PSXLightingBaker.ComputeLighting(wv, wn);
|
||||
return ConvertToPSXVertex(v, GTEScaling, normals[index], uv[index], textures[textureIndex]?.Width, textures[textureIndex]?.Height, c);
|
||||
}
|
||||
|
||||
for (int i = 0; i < submeshTriangles.Length; i += 3)
|
||||
{
|
||||
int vid0 = submeshTriangles[i];
|
||||
int vid1 = submeshTriangles[i + 1];
|
||||
int vid2 = submeshTriangles[i + 2];
|
||||
|
||||
Vector3 faceNormal = Vector3.Cross(vertices[vid1] - vertices[vid0], vertices[vid2] - vertices[vid0]).normalized;
|
||||
|
||||
if (Vector3.Dot(faceNormal, normals[vid0]) < 0)
|
||||
// Ensure mesh has required data
|
||||
if (mesh.normals == null || mesh.normals.Length == 0)
|
||||
{
|
||||
(vid1, vid2) = (vid2, vid1);
|
||||
mesh.RecalculateNormals();
|
||||
}
|
||||
|
||||
psxMesh.Triangles.Add(new Tri {
|
||||
v0 = convertData(vid0),
|
||||
v1 = convertData(vid1),
|
||||
v2 = convertData(vid2),
|
||||
TextureIndex = textureIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
if (mesh.uv == null || mesh.uv.Length == 0)
|
||||
{
|
||||
Vector2[] uvs = new Vector2[mesh.vertices.Length];
|
||||
mesh.uv = uvs;
|
||||
}
|
||||
|
||||
return psxMesh;
|
||||
}
|
||||
// Precompute smooth normals for the entire mesh
|
||||
Vector3[] smoothNormals = RecalculateSmoothNormals(mesh);
|
||||
|
||||
// Precompute world positions and normals for all vertices
|
||||
Vector3[] worldVertices = new Vector3[mesh.vertices.Length];
|
||||
Vector3[] worldNormals = new Vector3[mesh.normals.Length];
|
||||
|
||||
for (int i = 0; i < mesh.vertices.Length; i++)
|
||||
{
|
||||
worldVertices[i] = transform.TransformPoint(mesh.vertices[i]);
|
||||
worldNormals[i] = transform.TransformDirection(mesh.normals[i]).normalized;
|
||||
}
|
||||
|
||||
for (int submeshIndex = 0; submeshIndex < mesh.subMeshCount; submeshIndex++)
|
||||
{
|
||||
int materialIndex = Mathf.Min(submeshIndex, materials.Length - 1);
|
||||
Material material = materials[materialIndex];
|
||||
Texture2D texture = material.mainTexture as Texture2D;
|
||||
|
||||
// Find texture index
|
||||
int textureIndex = -1;
|
||||
if (texture != null)
|
||||
{
|
||||
for (int i = 0; i < textures.Count; i++)
|
||||
{
|
||||
if (textures[i].OriginalTexture == texture)
|
||||
{
|
||||
textureIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int[] submeshTriangles = mesh.GetTriangles(submeshIndex);
|
||||
|
||||
// Get mesh data arrays
|
||||
Vector3[] vertices = mesh.vertices;
|
||||
Vector3[] normals = mesh.normals;
|
||||
Vector2[] uv = mesh.uv;
|
||||
|
||||
PSXVertex convertData(int index)
|
||||
{
|
||||
Vector3 v = Vector3.Scale(vertices[index], transform.lossyScale);
|
||||
|
||||
// Use precomputed world position and normal for consistent lighting
|
||||
Vector3 wv = worldVertices[index];
|
||||
Vector3 wn = worldNormals[index];
|
||||
|
||||
// For split triangles, use the original vertex's lighting if possible
|
||||
Color c = PSXLightingBaker.ComputeLighting(wv, wn);
|
||||
|
||||
return ConvertToPSXVertex(v, GTEScaling, normals[index], uv[index],
|
||||
textures[textureIndex]?.Width, textures[textureIndex]?.Height, c);
|
||||
}
|
||||
|
||||
for (int i = 0; i < submeshTriangles.Length; i += 3)
|
||||
{
|
||||
int vid0 = submeshTriangles[i];
|
||||
int vid1 = submeshTriangles[i + 1];
|
||||
int vid2 = submeshTriangles[i + 2];
|
||||
|
||||
Vector3 faceNormal = Vector3.Cross(vertices[vid1] - vertices[vid0], vertices[vid2] - vertices[vid0]).normalized;
|
||||
|
||||
if (Vector3.Dot(faceNormal, normals[vid0]) < 0)
|
||||
{
|
||||
(vid1, vid2) = (vid2, vid1);
|
||||
}
|
||||
|
||||
psxMesh.Triangles.Add(new Tri
|
||||
{
|
||||
v0 = convertData(vid0),
|
||||
v1 = convertData(vid1),
|
||||
v2 = convertData(vid2),
|
||||
TextureIndex = textureIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return psxMesh;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Unity vertex into a PSXVertex by applying fixed-point conversion, shading, and UV mapping.
|
||||
|
||||
@@ -12,19 +12,30 @@ namespace SplashEdit.RuntimeCode
|
||||
|
||||
public bool IsActive = true;
|
||||
|
||||
public List<PSXTexture2D> Textures { get; set; } = new List<PSXTexture2D>(); // Stores the converted PlayStation-style texture
|
||||
public PSXMesh Mesh { get; protected set; } // Stores the converted PlayStation-style mesh
|
||||
public List<PSXTexture2D> Textures { get; set; } = new List<PSXTexture2D>();
|
||||
public PSXMesh Mesh { get; protected set; }
|
||||
|
||||
[Header("Export Settings")]
|
||||
[FormerlySerializedAs("BitDepth")]
|
||||
[SerializeField] private PSXBPP bitDepth = PSXBPP.TEX_8BIT; // Defines the bit depth of the texture (e.g., 4BPP, 8BPP)
|
||||
[SerializeField] private PSXBPP bitDepth = PSXBPP.TEX_8BIT;
|
||||
[SerializeField] private LuaFile luaFile;
|
||||
|
||||
[Header("BSP Settings")]
|
||||
[SerializeField] private Mesh _modifiedMesh; // Mesh after BSP processing
|
||||
|
||||
[Header("Gizmo Settings")]
|
||||
[FormerlySerializedAs("PreviewNormals")]
|
||||
[SerializeField] private bool previewNormals = false;
|
||||
[SerializeField] private float normalPreviewLength = 0.5f; // Length of the normal lines
|
||||
[SerializeField] private float normalPreviewLength = 0.5f;
|
||||
|
||||
private readonly Dictionary<(int, PSXBPP), PSXTexture2D> cache = new();
|
||||
|
||||
public Mesh ModifiedMesh
|
||||
{
|
||||
get => _modifiedMesh;
|
||||
set => _modifiedMesh = value;
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (previewNormals)
|
||||
@@ -37,25 +48,19 @@ namespace SplashEdit.RuntimeCode
|
||||
Vector3[] vertices = mesh.vertices;
|
||||
Vector3[] normals = mesh.normals;
|
||||
|
||||
Gizmos.color = Color.green; // Normal color
|
||||
Gizmos.color = Color.green;
|
||||
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
Vector3 worldVertex = transform.TransformPoint(vertices[i]); // Convert to world space
|
||||
Vector3 worldNormal = transform.TransformDirection(normals[i]); // Transform normal to world space
|
||||
Vector3 worldVertex = transform.TransformPoint(vertices[i]);
|
||||
Vector3 worldNormal = transform.TransformDirection(normals[i]);
|
||||
|
||||
Gizmos.DrawLine(worldVertex, worldVertex + worldNormal * normalPreviewLength);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Converts the object's material texture into a PlayStation-compatible texture.
|
||||
/// </summary>
|
||||
///
|
||||
public void CreatePSXTextures2D()
|
||||
{
|
||||
Renderer renderer = GetComponent<Renderer>();
|
||||
@@ -71,14 +76,12 @@ namespace SplashEdit.RuntimeCode
|
||||
Texture mainTexture = mat.mainTexture;
|
||||
Texture2D tex2D = null;
|
||||
|
||||
// Check if it's already a Texture2D
|
||||
if (mainTexture is Texture2D existingTex2D)
|
||||
{
|
||||
tex2D = existingTex2D;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not a Texture2D, try to convert
|
||||
tex2D = ConvertToTexture2D(mainTexture);
|
||||
}
|
||||
|
||||
@@ -92,7 +95,7 @@ namespace SplashEdit.RuntimeCode
|
||||
else
|
||||
{
|
||||
tex = PSXTexture2D.CreateFromTexture2D(tex2D, bitDepth);
|
||||
tex.OriginalTexture = tex2D; // Store reference to the original texture
|
||||
tex.OriginalTexture = tex2D;
|
||||
cache.Add((tex2D.GetInstanceID(), bitDepth), tex);
|
||||
}
|
||||
Textures.Add(tex);
|
||||
@@ -104,10 +107,8 @@ namespace SplashEdit.RuntimeCode
|
||||
|
||||
private Texture2D ConvertToTexture2D(Texture texture)
|
||||
{
|
||||
// Create a new Texture2D with the same dimensions and format
|
||||
Texture2D texture2D = new Texture2D(texture.width, texture.height, TextureFormat.RGBA32, false);
|
||||
|
||||
// Read the texture pixels
|
||||
RenderTexture currentActiveRT = RenderTexture.active;
|
||||
RenderTexture.active = texture as RenderTexture;
|
||||
|
||||
@@ -128,16 +129,35 @@ namespace SplashEdit.RuntimeCode
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the object's mesh into a PlayStation-compatible mesh.
|
||||
/// </summary>
|
||||
public void CreatePSXMesh(float GTEScaling)
|
||||
public void CreatePSXMesh(float GTEScaling, bool useBSP = false)
|
||||
{
|
||||
Renderer renderer = GetComponent<Renderer>();
|
||||
if (renderer != null)
|
||||
{
|
||||
Mesh = PSXMesh.CreateFromUnityRenderer(renderer, GTEScaling, transform, Textures);
|
||||
if (useBSP && _modifiedMesh != null)
|
||||
{
|
||||
// Create a temporary GameObject with the modified mesh but same materials
|
||||
GameObject tempGO = new GameObject("TempBSPMesh");
|
||||
tempGO.transform.position = transform.position;
|
||||
tempGO.transform.rotation = transform.rotation;
|
||||
tempGO.transform.localScale = transform.localScale;
|
||||
|
||||
MeshFilter tempMF = tempGO.AddComponent<MeshFilter>();
|
||||
tempMF.sharedMesh = _modifiedMesh;
|
||||
|
||||
MeshRenderer tempMR = tempGO.AddComponent<MeshRenderer>();
|
||||
tempMR.sharedMaterials = renderer.sharedMaterials;
|
||||
|
||||
Mesh = PSXMesh.CreateFromUnityRenderer(tempMR, GTEScaling, transform, Textures);
|
||||
|
||||
// Clean up
|
||||
GameObject.DestroyImmediate(tempGO);
|
||||
}
|
||||
else
|
||||
{
|
||||
Mesh = PSXMesh.CreateFromUnityRenderer(renderer, GTEScaling, transform, Textures);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,11 @@ namespace SplashEdit.RuntimeCode
|
||||
private Quaternion _playerRot;
|
||||
private float _playerHeight;
|
||||
|
||||
private BSP _bsp;
|
||||
|
||||
public bool PreviewBSP = true;
|
||||
public int BSPPreviewDepth = 9999;
|
||||
|
||||
public void Export()
|
||||
{
|
||||
_psxData = DataStorage.LoadData(out selectedResolution, out dualBuffering, out verticalLayout, out prohibitedAreas);
|
||||
@@ -42,7 +47,7 @@ namespace SplashEdit.RuntimeCode
|
||||
PSXObjectExporter exp = _exporters[i];
|
||||
EditorUtility.DisplayProgressBar($"{nameof(PSXSceneExporter)}", $"Export {nameof(PSXObjectExporter)}", ((float)i) / _exporters.Length);
|
||||
exp.CreatePSXTextures2D();
|
||||
exp.CreatePSXMesh(GTEScaling);
|
||||
exp.CreatePSXMesh(GTEScaling, true);
|
||||
}
|
||||
|
||||
_navmeshes = FindObjectsByType<PSXNavMesh>(FindObjectsSortMode.None);
|
||||
@@ -66,6 +71,9 @@ namespace SplashEdit.RuntimeCode
|
||||
_playerRot = player.transform.rotation;
|
||||
}
|
||||
|
||||
_bsp = new BSP(_exporters.ToList());
|
||||
_bsp.Build();
|
||||
|
||||
ExportFile();
|
||||
}
|
||||
|
||||
@@ -450,6 +458,10 @@ namespace SplashEdit.RuntimeCode
|
||||
Vector3 cubeSize = new Vector3(8.0f * GTEScaling, 8.0f * GTEScaling, 8.0f * GTEScaling);
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireCube(sceneOrigin, cubeSize);
|
||||
|
||||
if (_bsp == null || !PreviewBSP) return;
|
||||
_bsp.DrawGizmos(BSPPreviewDepth);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user