845 lines
28 KiB
C#
845 lines
28 KiB
C#
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);
|
|
}
|
|
} |