ui基本完毕,修了一大把的bug

This commit is contained in:
2026-07-13 02:28:39 +08:00
parent 1e20d73e90
commit fd22501f71
958 changed files with 378289 additions and 41038 deletions
@@ -0,0 +1,96 @@
/// <summary>
/// Defines an interface for an object that is sorted by bin number
/// </summary>
public interface IBinSortable
{
int bin { get; set; }
}
/// <summary>
/// Methods for sorting objects on an ordered grid by bin number.
///
/// The grid ordering is shown by example below. Even rows (row 0 = bottom row) are ordered
/// right-to-left while odd rows are ordered left-to-right.
/// _____ _____ _____
/// | | | |
/// | 6 | 7 | 8 |
/// |_____|_____|_____|
/// | | | |
/// | 5 | 4 | 3 |
/// |_____|_____|_____|
/// | | | |
/// | 0 | 1 | 2 |
/// |_____|_____|_____|
///
/// </summary>
public class BinSort
{
/// <summary>
/// Computes the bin number for the set of grid coordinates
/// </summary>
/// <param name="i">Grid row</param>
/// <param name="j">Grid column</param>
/// <param name="n">Grid size</param>
/// <returns></returns>
internal static int GetBinNumber(int i, int j, int n)
{
return (i % 2 == 0) ? (i * n) + j : (i + 1) * n - j - 1;
}
/// <summary>
/// Performs a counting sort of the input points based on their bin number. Only
/// sorts the elements in the index range [0, count]. If binCount is <= 1, no sorting
/// is performed. If lastIndex > input.Length, the entire input array is sorted.
/// </summary>
/// <param name="input">The input array to sort</param>
/// <param name="lastIndex">The index of the last element in `input` to sort. Only the
/// elements [0, lastIndex) are sorted.</param>
/// <param name="binCount">Number of bins</param>
internal static T[] Sort<T>(T[] input, int lastIndex, int binCount) where T: IBinSortable
{
int[] count = new int[binCount];
T[] output = new T[input.Length];
#region Validation
// Need at least two bins to sort
if (binCount <= 1)
{
return input;
}
// If lastIndex is out of range, default to sorting the entire input array
if (lastIndex > input.Length)
{
lastIndex = input.Length;
}
#endregion
// Only sort the first [0, count] points, don't want to sort super-triangle vertices
for (int i = 0; i < lastIndex; i++)
{
int j = input[i].bin;
count[j] += 1;
}
for (int i = 1; i < binCount; i++)
{
count[i] += count[i - 1];
}
for (int i = lastIndex - 1; i >= 0; i--)
{
int j = input[i].bin;
count[j] -= 1;
output[count[j]] = input[i];
}
// Copy over the rest of the un-sorted points
for (int i = lastIndex; i < output.Length; i++)
{
output[i] = input[i];
}
return output;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fa6520366fb0c0d418e02cb5b731ad3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,128 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class MathUtils
{
/// <summary>
/// Returns true if the quad specified by the two diagonals a1->a2 and b1->b2 is convex
/// Quad is convex if a1->a2 and b1->b2 intersect each other
/// </summary>
/// <param name="a1">Start point of diagonal A</param>
/// <param name="a2">End point of diagonal A</param>
/// <param name="b1">Start point of diagonal B</param>
/// <param name="b2">End point of diagonal B</param>
/// <returns></returns>
public static bool IsQuadConvex(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2)
{
return LinesIntersectInternal(a1, a2, b1, b2, true);
}
/// <summary>
/// Returns true lines a1->a2 and b1->b2 is intersect
/// </summary>
/// <param name="a1">Start point of line A</param>
/// <param name="a2">End point of line A</param>
/// <param name="b1">Start point of line B</param>
/// <param name="b2">End point of line B</param>
/// <returns></returns>
public static bool LinesIntersect(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2)
{
return LinesIntersectInternal(a1, a2, b1, b2, false);
}
/// <summary>
/// Returns true lines a1->a2 and b1->b2 is intersect
/// </summary>
/// <param name="a1">Start point of line A</param>
/// <param name="a2">End point of line A</param>
/// <param name="b1">Start point of line B</param>
/// <param name="b2">End point of line B</param>
/// <returns></returns>
private static bool LinesIntersectInternal(Vector2 a1, Vector2 a2, Vector2 b1, Vector2 b2, bool includeSharedEndpoints)
{
Vector2 a12 = new Vector2(a2.x - a1.x, a2.y - a1.y);
Vector2 b12 = new Vector2(b2.x - b1.x, b2.y - b1.y);
// If any of the vertices are shared between the two diagonals,
// the quad collapses into a triangle and is convex by default.
if (a1 == b1 || a1 == b2 || a2 == b1 || a2 == b2)
{
return includeSharedEndpoints;
}
else
{
// Compute cross product between each point and the opposite diagonal
// Look at sign of the Z component to see which side of line point is on
float a1xb = (a1.x - b1.x) * b12.y - (a1.y - b1.y) * b12.x;
float a2xb = (a2.x - b1.x) * b12.y - (a2.y - b1.y) * b12.x;
float b1xa = (b1.x - a1.x) * a12.y - (b1.y - a1.y) * a12.x;
float b2xa = (b2.x - a1.x) * a12.y - (b2.y - a1.y) * a12.x;
// Check that the points for each diagonal lie on opposite sides of the other
// diagonal. Quad is also convex if a1/a2 lie on b1->b2 (and vice versa) since
// the shape collapses into a triangle (hence >= instead of >)
return ((a1xb >= 0 && a2xb <= 0) || (a1xb <= 0 && a2xb >= 0)) &&
((b1xa >= 0 && b2xa <= 0) || (b1xa <= 0 && b2xa >= 0));
}
}
/// <summary>
/// Determines the intersection between the line segment a->b and the plane defined by the specified normal and origin point. If an intersection point exists, it is returned via the out parameter `intersection`. The parameter `s` is defined below and is used to properly interpolate normals/uvs for intersection vertices.
/// </summary>
/// <param name="a">Start point of line</param>
/// <param name="b">End point of line</param>
/// <param name="n">Plane normal</param>
/// <param name="p0">Plane origin</param>
/// <param name="x">If intersection exists, intersection point return as out parameter.</param>
/// <param name="s">Returns the parameterization of the intersection where x = a + (b - a) * s</param>
/// <returns></returns>
public static bool LinePlaneIntersection(Vector3 a,
Vector3 b,
Vector3 n,
Vector3 p0,
out Vector3 x,
out float s)
{
// Initialize out params
s = 0;
x = Vector3.zero;
// Handle degenerate cases
if (a == b)
{
return false;
}
else if (n == Vector3.zero)
{
return false;
}
// `s` is the parameter for the line segment a -> b where 0.0 <= s <= 1.0
s = Vector3.Dot(p0 - a, n) / Vector3.Dot(b - a, n);
if (s >= 0 && s <= 1)
{
x = a + (b - a) * s;
return true;
}
return false;
}
/// <summary>
/// Returns true of the point `p` is on the left side of the directed line segment `i` -> `j`
/// Use for checking if a point is inside of a triangle. Since triangle vertices oriented
/// CCW, a point on the left side of a triangle edge is "inside" that edge of the triangle.
/// </summary>
/// <param name="p">Index of test point in `points` array</param>
/// <param name="i">Index of first vertex of the edge in the `points` array</param>
/// /// <param name="j">Index of second vertex of the edge in the `points` array</param>
/// <returns>True if the point `p` is on the left side of the line `i`->`j`</returns>
public static bool IsPointOnRightSideOfLine(Vector2 a, Vector2 b, Vector2 c)
{
// The <= is essential; if it is <, the whole thing falls apart
return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) <= 0;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2d37a4c4b4b021b4dafc4db72c4a6fc2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,239 @@
using System.Collections.Generic;
using UnityEngine;
using Unity.Collections;
using UnityEngine.Rendering;
public static class MeshUtils
{
// Description of vertex attributes for the island mesh
private static VertexAttributeDescriptor[] layout = new[]
{
new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3),
new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2),
};
/// <summary>
/// Identifies all disconnected sets of geometry contained within the mesh.
/// Each set of geometry is split into a separate meshes.
/// </summary>
/// <param name="mesh">The mesh to search</param>
/// <returns>Returns an array of all disconnected meshes found.</returns>
public static Mesh[] FindDisconnectedMeshes(Mesh mesh)
{
// Each disconnected set of geometry is referred to as an "island"
List<Mesh> islands = new List<Mesh>();
#region Preliminaries
// Extract mesh data
var vertices = mesh.vertices;
var triangles = mesh.triangles;
var normals = mesh.normals;
var uvs = mesh.uv;
// For each triangle, find the corresponding sub-mesh index. (Mesh.triangles contains
// the triangles for all sub-meshes)
int[] triangleSubMesh = new int[triangles.Length / 3];
int subMeshIndex = 0;
int subMeshSize = mesh.GetTriangles(subMeshIndex).Length / 3;
for (int i = 0; i < triangles.Length / 3; i++)
{
if (i >= subMeshSize)
{
subMeshIndex++;
subMeshSize += mesh.GetTriangles(subMeshIndex).Length / 3;
}
triangleSubMesh[i] = subMeshIndex;
}
// Identify coincident vertices
List<int>[] coincidentVertices = new List<int>[vertices.Length];
for(int i = 0; i < vertices.Length; i++)
{
coincidentVertices[i] = new List<int>();
}
for(int i = 0; i < vertices.Length; i++)
{
Vector3 v_i = vertices[i];
for (int k = i + 1; k < vertices.Length; k++)
{
Vector3 v_k = vertices[k];
if (v_i == v_k)
{
coincidentVertices[k].Add(i);
coincidentVertices[i].Add(k);
}
}
}
// Find the triangles the each vertex belongs to. Need to do this for each submesh
List<int>[] vertexTriangles = new List<int>[vertices.Length];
for (int i = 0; i < vertices.Length; i++)
{
vertexTriangles[i] = new List<int>();
}
int v1, v2, v3;
for (int i = 0; i < triangles.Length; i += 3)
{
// Index of the triangle
int t = i / 3;
v1 = triangles[i];
v2 = triangles[i + 1];
v3 = triangles[i + 2];
vertexTriangles[v1].Add(t);
vertexTriangles[v2].Add(t);
vertexTriangles[v3].Add(t);
}
#endregion
// Search the mesh geometry and identify all islands
// 1) Start by finding a vertex that has not yet been visited
// 2) Insert the vertex into a queue, begin a breadth-first search
// 3) Dequeue the next vertex 'v'
// 4) Find all triangles that 'v' is connected to. Add each triangle to a list
// 5) Enqueue the vertices for each connected triangle if they haven't been visited yet
// 6) Enqueue all vertices coincident with 'v' if they haven't been visited yet
// 7) Repeat Steps 3-6 until the queue is empty
// 8) Take the list of triangles and use the existing mesh data to create a new island mesh
// 9) Go back to Step 1, continue until all vertices have been visited.
bool[] visitedVertices = new bool[vertices.Length];
bool[] visitedTriangles = new bool[triangles.Length];
Queue<int> frontier = new Queue<int>();
// Vertex data for the island mesh. Only initialize once and keep track of pointer to last element to minimize GC
NativeArray<MeshVertex> islandVertices = new NativeArray<MeshVertex>(vertices.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
// Array containing triangle data for the island mesh. Need to keep track of triangles for each sub-mesh separately
int[][] islandTriangles = new int[mesh.subMeshCount][];
for (int i = 0; i < mesh.subMeshCount; i++)
{
islandTriangles[i] = new int[triangles.Length];
}
// Counters to keep track of how many vertices
int vertexCount = 0;
int totalIndexCount = 0;
int[] subMeshIndexCounts = new int[mesh.subMeshCount];
for (int i = 0; i < vertices.Length; i++)
{
if (visitedVertices[i]) continue;
// Reset the vertex/triangle counts
vertexCount = 0;
totalIndexCount = 0;
for(int j = 0; j < mesh.subMeshCount; j++)
{
subMeshIndexCounts[j] = 0;
}
// Search the mesh geometry starting at vertex 'i'. Search is performed by looking up
// the triangles that contain each vertex, adding their vertices, etc. until all
// triangles have been visited.
frontier.Enqueue(i);
// Index map between source mesh vertex array and the sub mesh vertex arrays
int[] vertexMap = new int[vertices.Length];
// Initialize map to '-1' to serve as "unmapped" value
for(int j = 0; j < vertices.Length; j++)
{
vertexMap[j] = -1;
}
while (frontier.Count > 0)
{
int k = frontier.Dequeue();
// Ignore vertex if we've already visited it
if (visitedVertices[k])
{
continue;
}
else
{
visitedVertices[k] = true;
}
// Add this vertex array for the island mesh
// Map between the original vertex index to the vertex's new index in the island
// mesh vertex array. This will be used to update the indices for the triangles later
vertexMap[k] = vertexCount;
islandVertices[vertexCount++] = new MeshVertex(vertices[k], normals[k], uvs[k]);
// Get the list of all triangles that this vertex is a part of
foreach(int t in vertexTriangles[k])
{
// If triangle is already included, skip it
if (!visitedTriangles[t])
{
visitedTriangles[t] = true;
// Loop through each vertex of the triangle and add the non-visited ones
// to the search frontier
for (int m = t * 3; m < t * 3 + 3; m++)
{
int v = triangles[m];
subMeshIndex = triangleSubMesh[t];
islandTriangles[subMeshIndex][subMeshIndexCounts[subMeshIndex]++] = v;
totalIndexCount++;
frontier.Enqueue(v);
// If this vertex is coincident with other vertices, add those to the search frontier
foreach(int cv in coincidentVertices[v])
{
frontier.Enqueue(cv);
}
}
}
}
}
// If the island contains at least one triangle, create a new mesh
if (vertexCount > 0)
{
Mesh island = new Mesh();
island.SetIndexBufferParams(totalIndexCount, IndexFormat.UInt32);
island.SetVertexBufferParams(vertexCount, layout);
island.SetVertexBufferData(islandVertices, 0, 0, vertexCount);
// Set the triangles for each submesh
island.subMeshCount = mesh.subMeshCount;
int indexStart = 0;
for (subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++)
{
var subMeshIndexBuffer = islandTriangles[subMeshIndex];
var subMeshIndexCount = subMeshIndexCounts[subMeshIndex];
// Map vertex indexes from the original mesh to the island mesh
for(int k = 0; k < subMeshIndexCount; k++)
{
int originalIndex = subMeshIndexBuffer[k];
subMeshIndexBuffer[k] = vertexMap[originalIndex];
}
// Set the index data for this sub mesh
island.SetIndexBufferData(subMeshIndexBuffer, 0, indexStart, (int)subMeshIndexCount);
island.SetSubMesh(subMeshIndex, new SubMeshDescriptor(indexStart, subMeshIndexCount));
indexStart += subMeshIndexCount;
}
island.RecalculateBounds();
islands.Add(island);
}
}
// Loop through rest of triangles
return islands.ToArray();
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fe28a9177c0175f479eb9155cc4aa3a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
using System;
using UnityEngine;
public static class Vector3Extensions
{
//
// that the normal is pointing to
// - p: The point being checked
// - n: The normal of the plane
// - o: The origin of the plane
/// <summary>
/// Returns true if the point is either on or above the plane. "Above" is the side of the place in the direction of the normal.
/// </summary>
/// <param name="p">The test point</param>
/// <param name="n">The plane normal</param>
/// <param name="o">The plane origin</param>
/// <returns></returns>
public static bool IsAbovePlane(this Vector3 p, Vector3 n, Vector3 o)
{
return (n.x * (p.x - o.x) + n.y * (p.y - o.y) + n.z * (p.z - o.z)) >= 0;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d0cb4a40ec03ec24485fca5a7cd3ad16
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: