571 lines
20 KiB
C#
571 lines
20 KiB
C#
![]() |
using UnityEngine;
|
||
|
using System.Collections.Generic;
|
||
|
|
||
|
namespace Pathfinding.Recast {
|
||
|
using System;
|
||
|
using Pathfinding;
|
||
|
using Pathfinding.Voxels;
|
||
|
|
||
|
internal class RecastMeshGatherer {
|
||
|
readonly int terrainSampleSize;
|
||
|
readonly LayerMask mask;
|
||
|
readonly List<string> tagMask;
|
||
|
readonly float colliderRasterizeDetail;
|
||
|
readonly Bounds bounds;
|
||
|
|
||
|
public RecastMeshGatherer (Bounds bounds, int terrainSampleSize, LayerMask mask, List<string> tagMask, float colliderRasterizeDetail) {
|
||
|
// Clamp to at least 1 since that's the resolution of the heightmap
|
||
|
terrainSampleSize = Math.Max(terrainSampleSize, 1);
|
||
|
|
||
|
this.bounds = bounds;
|
||
|
this.terrainSampleSize = terrainSampleSize;
|
||
|
this.mask = mask;
|
||
|
this.tagMask = tagMask ?? new List<string>();
|
||
|
this.colliderRasterizeDetail = colliderRasterizeDetail;
|
||
|
}
|
||
|
|
||
|
static List<MeshFilter> FilterMeshes (MeshFilter[] meshFilters, List<string> tagMask, LayerMask layerMask) {
|
||
|
var filtered = new List<MeshFilter>(meshFilters.Length / 3);
|
||
|
|
||
|
for (int i = 0; i < meshFilters.Length; i++) {
|
||
|
MeshFilter filter = meshFilters[i];
|
||
|
Renderer rend = filter.GetComponent<Renderer>();
|
||
|
|
||
|
if (rend != null && filter.sharedMesh != null && rend.enabled && (((1 << filter.gameObject.layer) & layerMask) != 0 || tagMask.Contains(filter.tag))) {
|
||
|
if (filter.GetComponent<RecastMeshObj>() == null) {
|
||
|
filtered.Add(filter);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return filtered;
|
||
|
}
|
||
|
|
||
|
public void CollectSceneMeshes (List<RasterizationMesh> meshes) {
|
||
|
if (tagMask.Count > 0 || mask != 0) {
|
||
|
// This is unfortunately the fastest way to find all mesh filters.. and it is not particularly fast.
|
||
|
var meshFilters = GameObject.FindObjectsOfType<MeshFilter>();
|
||
|
var filteredMeshes = FilterMeshes(meshFilters, tagMask, mask);
|
||
|
|
||
|
var cachedVertices = new Dictionary<Mesh, Vector3[]>();
|
||
|
var cachedTris = new Dictionary<Mesh, int[]>();
|
||
|
|
||
|
bool containedStatic = false;
|
||
|
|
||
|
for (int i = 0; i < filteredMeshes.Count; i++) {
|
||
|
MeshFilter filter = filteredMeshes[i];
|
||
|
|
||
|
// Note, guaranteed to have a renderer
|
||
|
Renderer rend = filter.GetComponent<Renderer>();
|
||
|
|
||
|
if (rend.isPartOfStaticBatch) {
|
||
|
// Statically batched meshes cannot be used due to Unity limitations
|
||
|
// log a warning about this
|
||
|
containedStatic = true;
|
||
|
} else {
|
||
|
// Only include it if it intersects with the graph
|
||
|
if (rend.bounds.Intersects(bounds)) {
|
||
|
Mesh mesh = filter.sharedMesh;
|
||
|
RasterizationMesh smesh;
|
||
|
|
||
|
// Check the cache to avoid allocating
|
||
|
// a new array unless necessary
|
||
|
if (cachedVertices.ContainsKey(mesh)) {
|
||
|
smesh = new RasterizationMesh(cachedVertices[mesh], cachedTris[mesh], rend.bounds);
|
||
|
} else {
|
||
|
smesh = new RasterizationMesh(mesh.vertices, mesh.triangles, rend.bounds);
|
||
|
cachedVertices[mesh] = smesh.vertices;
|
||
|
cachedTris[mesh] = smesh.triangles;
|
||
|
}
|
||
|
|
||
|
smesh.matrix = rend.localToWorldMatrix;
|
||
|
smesh.original = filter;
|
||
|
meshes.Add(smesh);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (containedStatic)
|
||
|
Debug.LogWarning("Some meshes were statically batched. These meshes can not be used for navmesh calculation" +
|
||
|
" due to technical constraints.\nDuring runtime scripts cannot access the data of meshes which have been statically batched.\n" +
|
||
|
"One way to solve this problem is to use cached startup (Save & Load tab in the inspector) to only calculate the graph when the game is not playing.");
|
||
|
}
|
||
|
|
||
|
#if ASTARDEBUG
|
||
|
int y = 0;
|
||
|
foreach (RasterizationMesh smesh in meshes) {
|
||
|
y++;
|
||
|
Vector3[] vecs = smesh.vertices;
|
||
|
int[] tris = smesh.triangles;
|
||
|
|
||
|
for (int i = 0; i < tris.Length; i += 3) {
|
||
|
Vector3 p1 = smesh.matrix.MultiplyPoint3x4(vecs[tris[i+0]]);
|
||
|
Vector3 p2 = smesh.matrix.MultiplyPoint3x4(vecs[tris[i+1]]);
|
||
|
Vector3 p3 = smesh.matrix.MultiplyPoint3x4(vecs[tris[i+2]]);
|
||
|
|
||
|
Debug.DrawLine(p1, p2, Color.red, 1);
|
||
|
Debug.DrawLine(p2, p3, Color.red, 1);
|
||
|
Debug.DrawLine(p3, p1, Color.red, 1);
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Find all relevant RecastMeshObj components and create ExtraMeshes for them</summary>
|
||
|
public void CollectRecastMeshObjs (List<RasterizationMesh> buffer) {
|
||
|
var buffer2 = Util.ListPool<RecastMeshObj>.Claim();
|
||
|
|
||
|
// Get all recast mesh objects inside the bounds
|
||
|
RecastMeshObj.GetAllInBounds(buffer2, bounds);
|
||
|
|
||
|
var cachedVertices = new Dictionary<Mesh, Vector3[]>();
|
||
|
var cachedTris = new Dictionary<Mesh, int[]>();
|
||
|
|
||
|
// Create an RasterizationMesh object
|
||
|
// for each RecastMeshObj
|
||
|
for (int i = 0; i < buffer2.Count; i++) {
|
||
|
MeshFilter filter = buffer2[i].GetMeshFilter();
|
||
|
Renderer rend = filter != null? filter.GetComponent<Renderer>() : null;
|
||
|
|
||
|
if (filter != null && rend != null && filter.sharedMesh != null) {
|
||
|
Mesh mesh = filter.sharedMesh;
|
||
|
RasterizationMesh smesh;
|
||
|
|
||
|
// Don't read the vertices and triangles from the
|
||
|
// mesh if we have seen the same mesh previously
|
||
|
if (cachedVertices.ContainsKey(mesh)) {
|
||
|
smesh = new RasterizationMesh(cachedVertices[mesh], cachedTris[mesh], rend.bounds);
|
||
|
} else {
|
||
|
smesh = new RasterizationMesh(mesh.vertices, mesh.triangles, rend.bounds);
|
||
|
cachedVertices[mesh] = smesh.vertices;
|
||
|
cachedTris[mesh] = smesh.triangles;
|
||
|
}
|
||
|
|
||
|
smesh.matrix = rend.localToWorldMatrix;
|
||
|
smesh.original = filter;
|
||
|
smesh.area = buffer2[i].area;
|
||
|
buffer.Add(smesh);
|
||
|
} else {
|
||
|
Collider coll = buffer2[i].GetCollider();
|
||
|
|
||
|
if (coll == null) {
|
||
|
Debug.LogError("RecastMeshObject ("+buffer2[i].gameObject.name +") didn't have a collider or MeshFilter+Renderer attached", buffer2[i].gameObject);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
RasterizationMesh smesh = RasterizeCollider(coll);
|
||
|
|
||
|
// Make sure a valid RasterizationMesh was returned
|
||
|
if (smesh != null) {
|
||
|
smesh.area = buffer2[i].area;
|
||
|
buffer.Add(smesh);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Clear cache to avoid memory leak
|
||
|
capsuleCache.Clear();
|
||
|
|
||
|
Util.ListPool<RecastMeshObj>.Release(ref buffer2);
|
||
|
}
|
||
|
|
||
|
public void CollectTerrainMeshes (bool rasterizeTrees, float desiredChunkSize, List<RasterizationMesh> result) {
|
||
|
// Find all terrains in the scene
|
||
|
var terrains = Terrain.activeTerrains;
|
||
|
|
||
|
if (terrains.Length > 0) {
|
||
|
// Loop through all terrains in the scene
|
||
|
for (int j = 0; j < terrains.Length; j++) {
|
||
|
if (terrains[j].terrainData == null) continue;
|
||
|
|
||
|
GenerateTerrainChunks(terrains[j], bounds, desiredChunkSize, result);
|
||
|
|
||
|
if (rasterizeTrees) {
|
||
|
// Rasterize all tree colliders on this terrain object
|
||
|
CollectTreeMeshes(terrains[j], result);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void GenerateTerrainChunks (Terrain terrain, Bounds bounds, float desiredChunkSize, List<RasterizationMesh> result) {
|
||
|
var terrainData = terrain.terrainData;
|
||
|
|
||
|
if (terrainData == null)
|
||
|
throw new System.ArgumentException("Terrain contains no terrain data");
|
||
|
|
||
|
Vector3 offset = terrain.GetPosition();
|
||
|
Vector3 center = offset + terrainData.size * 0.5F;
|
||
|
|
||
|
// Figure out the bounds of the terrain in world space
|
||
|
var terrainBounds = new Bounds(center, terrainData.size);
|
||
|
|
||
|
// Only include terrains which intersects the graph
|
||
|
if (!terrainBounds.Intersects(bounds))
|
||
|
return;
|
||
|
|
||
|
// Original heightmap size
|
||
|
#if UNITY_2019_3_OR_NEWER
|
||
|
int heightmapWidth = terrainData.heightmapResolution;
|
||
|
int heightmapDepth = terrainData.heightmapResolution;
|
||
|
#else
|
||
|
int heightmapWidth = terrainData.heightmapWidth;
|
||
|
int heightmapDepth = terrainData.heightmapHeight;
|
||
|
#endif
|
||
|
|
||
|
|
||
|
// Sample the terrain heightmap
|
||
|
float[, ] heights = terrainData.GetHeights(0, 0, heightmapWidth, heightmapDepth);
|
||
|
|
||
|
Vector3 sampleSize = terrainData.heightmapScale;
|
||
|
sampleSize.y = terrainData.size.y;
|
||
|
|
||
|
// Make chunks at least 12 quads wide
|
||
|
// since too small chunks just decreases performance due
|
||
|
// to the overhead of checking for bounds and similar things
|
||
|
const int MinChunkSize = 12;
|
||
|
|
||
|
// Find the number of samples along each edge that corresponds to a world size of desiredChunkSize
|
||
|
// Then round up to the nearest multiple of terrainSampleSize
|
||
|
var chunkSizeAlongX = Mathf.CeilToInt(Mathf.Max(desiredChunkSize / (sampleSize.x * terrainSampleSize), MinChunkSize)) * terrainSampleSize;
|
||
|
var chunkSizeAlongZ = Mathf.CeilToInt(Mathf.Max(desiredChunkSize / (sampleSize.z * terrainSampleSize), MinChunkSize)) * terrainSampleSize;
|
||
|
|
||
|
for (int z = 0; z < heightmapDepth; z += chunkSizeAlongZ) {
|
||
|
for (int x = 0; x < heightmapWidth; x += chunkSizeAlongX) {
|
||
|
var width = Mathf.Min(chunkSizeAlongX, heightmapWidth - x);
|
||
|
var depth = Mathf.Min(chunkSizeAlongZ, heightmapDepth - z);
|
||
|
var chunkMin = offset + new Vector3(z * sampleSize.x, 0, x * sampleSize.z);
|
||
|
var chunkMax = offset + new Vector3((z + depth) * sampleSize.x, sampleSize.y, (x + width) * sampleSize.z);
|
||
|
var chunkBounds = new Bounds();
|
||
|
chunkBounds.SetMinMax(chunkMin, chunkMax);
|
||
|
|
||
|
// Skip chunks that are not inside the desired bounds
|
||
|
if (chunkBounds.Intersects(bounds)) {
|
||
|
var chunk = GenerateHeightmapChunk(heights, sampleSize, offset, x, z, width, depth, terrainSampleSize);
|
||
|
result.Add(chunk);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>Returns ceil(lhs/rhs), i.e lhs/rhs rounded up</summary>
|
||
|
static int CeilDivision (int lhs, int rhs) {
|
||
|
return (lhs + rhs - 1)/rhs;
|
||
|
}
|
||
|
|
||
|
/// <summary>Generates a terrain chunk mesh</summary>
|
||
|
RasterizationMesh GenerateHeightmapChunk (float[, ] heights, Vector3 sampleSize, Vector3 offset, int x0, int z0, int width, int depth, int stride) {
|
||
|
// Downsample to a smaller mesh (full resolution will take a long time to rasterize)
|
||
|
// Round up the width to the nearest multiple of terrainSampleSize and then add 1
|
||
|
// (off by one because there are vertices at the edge of the mesh)
|
||
|
int resultWidth = CeilDivision(width, terrainSampleSize) + 1;
|
||
|
int resultDepth = CeilDivision(depth, terrainSampleSize) + 1;
|
||
|
|
||
|
var heightmapWidth = heights.GetLength(0);
|
||
|
var heightmapDepth = heights.GetLength(1);
|
||
|
|
||
|
// Create a mesh from the heightmap
|
||
|
var numVerts = resultWidth * resultDepth;
|
||
|
var terrainVertices = Util.ArrayPool<Vector3>.Claim(numVerts);
|
||
|
|
||
|
// Create lots of vertices
|
||
|
for (int z = 0; z < resultDepth; z++) {
|
||
|
for (int x = 0; x < resultWidth; x++) {
|
||
|
int sampleX = Math.Min(x0 + x*stride, heightmapWidth-1);
|
||
|
int sampleZ = Math.Min(z0 + z*stride, heightmapDepth-1);
|
||
|
|
||
|
terrainVertices[z*resultWidth + x] = new Vector3(sampleZ * sampleSize.x, heights[sampleX, sampleZ]*sampleSize.y, sampleX * sampleSize.z) + offset;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Create the mesh by creating triangles in a grid like pattern
|
||
|
int numTris = (resultWidth-1)*(resultDepth-1)*2*3;
|
||
|
var tris = Util.ArrayPool<int>.Claim(numTris);
|
||
|
int triangleIndex = 0;
|
||
|
for (int z = 0; z < resultDepth-1; z++) {
|
||
|
for (int x = 0; x < resultWidth-1; x++) {
|
||
|
tris[triangleIndex] = z*resultWidth + x;
|
||
|
tris[triangleIndex+1] = z*resultWidth + x+1;
|
||
|
tris[triangleIndex+2] = (z+1)*resultWidth + x+1;
|
||
|
triangleIndex += 3;
|
||
|
tris[triangleIndex] = z*resultWidth + x;
|
||
|
tris[triangleIndex+1] = (z+1)*resultWidth + x+1;
|
||
|
tris[triangleIndex+2] = (z+1)*resultWidth + x;
|
||
|
triangleIndex += 3;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if ASTARDEBUG
|
||
|
var color = AstarMath.IntToColor(x0 + 7 * z0, 0.7f);
|
||
|
for (int i = 0; i < numTris; i += 3) {
|
||
|
Debug.DrawLine(terrainVertices[tris[i]], terrainVertices[tris[i+1]], color, 40);
|
||
|
Debug.DrawLine(terrainVertices[tris[i+1]], terrainVertices[tris[i+2]], color, 40);
|
||
|
Debug.DrawLine(terrainVertices[tris[i+2]], terrainVertices[tris[i]], color, 40);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
var mesh = new RasterizationMesh(terrainVertices, tris, new Bounds());
|
||
|
mesh.numVertices = numVerts;
|
||
|
mesh.numTriangles = numTris;
|
||
|
mesh.pool = true;
|
||
|
// Could probably calculate these bounds in a faster way
|
||
|
mesh.RecalculateBounds();
|
||
|
return mesh;
|
||
|
}
|
||
|
|
||
|
void CollectTreeMeshes (Terrain terrain, List<RasterizationMesh> result) {
|
||
|
TerrainData data = terrain.terrainData;
|
||
|
|
||
|
for (int i = 0; i < data.treeInstances.Length; i++) {
|
||
|
TreeInstance instance = data.treeInstances[i];
|
||
|
TreePrototype prot = data.treePrototypes[instance.prototypeIndex];
|
||
|
|
||
|
// Make sure that the tree prefab exists
|
||
|
if (prot.prefab == null) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
var collider = prot.prefab.GetComponent<Collider>();
|
||
|
var treePosition = terrain.transform.position + Vector3.Scale(instance.position, data.size);
|
||
|
var scale = new Vector3(instance.widthScale, instance.heightScale, instance.widthScale);
|
||
|
scale = Vector3.Scale(scale, prot.prefab.transform.localScale);
|
||
|
|
||
|
if (collider == null) {
|
||
|
var instanceBounds = new Bounds(terrain.transform.position + Vector3.Scale(instance.position, data.size), new Vector3(instance.widthScale, instance.heightScale, instance.widthScale));
|
||
|
|
||
|
Matrix4x4 matrix = Matrix4x4.TRS(treePosition, Quaternion.identity, scale*0.5f);
|
||
|
|
||
|
var mesh = new RasterizationMesh(BoxColliderVerts, BoxColliderTris, instanceBounds, matrix);
|
||
|
|
||
|
result.Add(mesh);
|
||
|
} else {
|
||
|
// The prefab has a collider, use that instead
|
||
|
|
||
|
// Generate a mesh from the collider
|
||
|
RasterizationMesh mesh = RasterizeCollider(collider, Matrix4x4.TRS(treePosition, Quaternion.identity, scale));
|
||
|
|
||
|
// Make sure a valid mesh was generated
|
||
|
if (mesh != null) {
|
||
|
// The bounds are incorrectly based on collider.bounds.
|
||
|
// It is incorrect because the collider is on the prefab, not on the tree instance
|
||
|
// so we need to recalculate the bounds based on the actual vertex positions
|
||
|
mesh.RecalculateBounds();
|
||
|
result.Add(mesh);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void CollectColliderMeshes (List<RasterizationMesh> result) {
|
||
|
/// <summary>TODO: Use Physics.OverlapBox on newer Unity versions</summary>
|
||
|
// Find all colliders that could possibly be inside the bounds
|
||
|
var colls = Physics.OverlapSphere(bounds.center, bounds.size.magnitude, -1, QueryTriggerInteraction.Ignore);
|
||
|
|
||
|
if (tagMask.Count > 0 || mask != 0) {
|
||
|
for (int i = 0; i < colls.Length; i++) {
|
||
|
Collider collider = colls[i];
|
||
|
|
||
|
if ((((mask >> collider.gameObject.layer) & 1) != 0 || tagMask.Contains(collider.tag)) && collider.enabled && !collider.isTrigger && collider.bounds.Intersects(bounds) && collider.GetComponent<RecastMeshObj>() == null) {
|
||
|
RasterizationMesh emesh = RasterizeCollider(collider);
|
||
|
//Make sure a valid RasterizationMesh was returned
|
||
|
if (emesh != null)
|
||
|
result.Add(emesh);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Clear cache to avoid memory leak
|
||
|
capsuleCache.Clear();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Box Collider triangle indices can be reused for multiple instances.
|
||
|
/// Warning: This array should never be changed
|
||
|
/// </summary>
|
||
|
private readonly static int[] BoxColliderTris = {
|
||
|
0, 1, 2,
|
||
|
0, 2, 3,
|
||
|
|
||
|
6, 5, 4,
|
||
|
7, 6, 4,
|
||
|
|
||
|
0, 5, 1,
|
||
|
0, 4, 5,
|
||
|
|
||
|
1, 6, 2,
|
||
|
1, 5, 6,
|
||
|
|
||
|
2, 7, 3,
|
||
|
2, 6, 7,
|
||
|
|
||
|
3, 4, 0,
|
||
|
3, 7, 4
|
||
|
};
|
||
|
|
||
|
/// <summary>
|
||
|
/// Box Collider vertices can be reused for multiple instances.
|
||
|
/// Warning: This array should never be changed
|
||
|
/// </summary>
|
||
|
private readonly static Vector3[] BoxColliderVerts = {
|
||
|
new Vector3(-1, -1, -1),
|
||
|
new Vector3(1, -1, -1),
|
||
|
new Vector3(1, -1, 1),
|
||
|
new Vector3(-1, -1, 1),
|
||
|
|
||
|
new Vector3(-1, 1, -1),
|
||
|
new Vector3(1, 1, -1),
|
||
|
new Vector3(1, 1, 1),
|
||
|
new Vector3(-1, 1, 1),
|
||
|
};
|
||
|
|
||
|
/// <summary>Holds meshes for capsules to avoid generating duplicate capsule meshes for identical capsules</summary>
|
||
|
private List<CapsuleCache> capsuleCache = new List<CapsuleCache>();
|
||
|
|
||
|
class CapsuleCache {
|
||
|
public int rows;
|
||
|
public float height;
|
||
|
public Vector3[] verts;
|
||
|
public int[] tris;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Rasterizes a collider to a mesh.
|
||
|
/// This will pass the col.transform.localToWorldMatrix to the other overload of this function.
|
||
|
/// </summary>
|
||
|
RasterizationMesh RasterizeCollider (Collider col) {
|
||
|
return RasterizeCollider(col, col.transform.localToWorldMatrix);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Rasterizes a collider to a mesh assuming it's vertices should be multiplied with the matrix.
|
||
|
/// Note that the bounds of the returned RasterizationMesh is based on collider.bounds. So you might want to
|
||
|
/// call myExtraMesh.RecalculateBounds on the returned mesh to recalculate it if the collider.bounds would
|
||
|
/// not give the correct value.
|
||
|
/// </summary>
|
||
|
RasterizationMesh RasterizeCollider (Collider col, Matrix4x4 localToWorldMatrix) {
|
||
|
RasterizationMesh result = null;
|
||
|
|
||
|
if (col is BoxCollider) {
|
||
|
result = RasterizeBoxCollider(col as BoxCollider, localToWorldMatrix);
|
||
|
} else if (col is SphereCollider || col is CapsuleCollider) {
|
||
|
var scollider = col as SphereCollider;
|
||
|
var ccollider = col as CapsuleCollider;
|
||
|
|
||
|
float radius = (scollider != null ? scollider.radius : ccollider.radius);
|
||
|
float height = scollider != null ? 0 : (ccollider.height*0.5f/radius) - 1;
|
||
|
Quaternion rot = Quaternion.identity;
|
||
|
// Capsule colliders can be aligned along the X, Y or Z axis
|
||
|
if (ccollider != null) rot = Quaternion.Euler(ccollider.direction == 2 ? 90 : 0, 0, ccollider.direction == 0 ? 90 : 0);
|
||
|
Matrix4x4 matrix = Matrix4x4.TRS(scollider != null ? scollider.center : ccollider.center, rot, Vector3.one*radius);
|
||
|
|
||
|
matrix = localToWorldMatrix * matrix;
|
||
|
|
||
|
result = RasterizeCapsuleCollider(radius, height, col.bounds, matrix);
|
||
|
} else if (col is MeshCollider) {
|
||
|
var collider = col as MeshCollider;
|
||
|
|
||
|
if (collider.sharedMesh != null) {
|
||
|
result = new RasterizationMesh(collider.sharedMesh.vertices, collider.sharedMesh.triangles, collider.bounds, localToWorldMatrix);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if ASTARDEBUG
|
||
|
for (int i = 0; i < result.triangles.Length; i += 3) {
|
||
|
Debug.DrawLine(result.matrix.MultiplyPoint3x4(result.vertices[result.triangles[i]]), result.matrix.MultiplyPoint3x4(result.vertices[result.triangles[i+1]]), Color.yellow);
|
||
|
Debug.DrawLine(result.matrix.MultiplyPoint3x4(result.vertices[result.triangles[i+2]]), result.matrix.MultiplyPoint3x4(result.vertices[result.triangles[i+1]]), Color.yellow);
|
||
|
Debug.DrawLine(result.matrix.MultiplyPoint3x4(result.vertices[result.triangles[i]]), result.matrix.MultiplyPoint3x4(result.vertices[result.triangles[i+2]]), Color.yellow);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
RasterizationMesh RasterizeBoxCollider (BoxCollider collider, Matrix4x4 localToWorldMatrix) {
|
||
|
Matrix4x4 matrix = Matrix4x4.TRS(collider.center, Quaternion.identity, collider.size*0.5f);
|
||
|
|
||
|
matrix = localToWorldMatrix * matrix;
|
||
|
|
||
|
return new RasterizationMesh(BoxColliderVerts, BoxColliderTris, collider.bounds, matrix);
|
||
|
}
|
||
|
|
||
|
RasterizationMesh RasterizeCapsuleCollider (float radius, float height, Bounds bounds, Matrix4x4 localToWorldMatrix) {
|
||
|
// Calculate the number of rows to use
|
||
|
// grows as sqrt(x) to the radius of the sphere/capsule which I have found works quite well
|
||
|
int rows = Mathf.Max(4, Mathf.RoundToInt(colliderRasterizeDetail*Mathf.Sqrt(localToWorldMatrix.MultiplyVector(Vector3.one).magnitude)));
|
||
|
|
||
|
if (rows > 100) {
|
||
|
Debug.LogWarning("Very large detail for some collider meshes. Consider decreasing Collider Rasterize Detail (RecastGraph)");
|
||
|
}
|
||
|
|
||
|
int cols = rows;
|
||
|
|
||
|
Vector3[] verts;
|
||
|
int[] trisArr;
|
||
|
|
||
|
// Check if we have already calculated a similar capsule
|
||
|
CapsuleCache cached = null;
|
||
|
for (int i = 0; i < capsuleCache.Count; i++) {
|
||
|
CapsuleCache c = capsuleCache[i];
|
||
|
if (c.rows == rows && Mathf.Approximately(c.height, height)) {
|
||
|
cached = c;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (cached == null) {
|
||
|
// Generate a sphere/capsule mesh
|
||
|
|
||
|
verts = new Vector3[(rows)*cols + 2];
|
||
|
|
||
|
var tris = new List<int>();
|
||
|
verts[verts.Length-1] = Vector3.up;
|
||
|
|
||
|
for (int r = 0; r < rows; r++) {
|
||
|
for (int c = 0; c < cols; c++) {
|
||
|
verts[c + r*cols] = new Vector3(Mathf.Cos(c*Mathf.PI*2/cols)*Mathf.Sin((r*Mathf.PI/(rows-1))), Mathf.Cos((r*Mathf.PI/(rows-1))) + (r < rows/2 ? height : -height), Mathf.Sin(c*Mathf.PI*2/cols)*Mathf.Sin((r*Mathf.PI/(rows-1))));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
verts[verts.Length-2] = Vector3.down;
|
||
|
|
||
|
for (int i = 0, j = cols-1; i < cols; j = i++) {
|
||
|
tris.Add(verts.Length-1);
|
||
|
tris.Add(0*cols + j);
|
||
|
tris.Add(0*cols + i);
|
||
|
}
|
||
|
|
||
|
for (int r = 1; r < rows; r++) {
|
||
|
for (int i = 0, j = cols-1; i < cols; j = i++) {
|
||
|
tris.Add(r*cols + i);
|
||
|
tris.Add(r*cols + j);
|
||
|
tris.Add((r-1)*cols + i);
|
||
|
|
||
|
tris.Add((r-1)*cols + j);
|
||
|
tris.Add((r-1)*cols + i);
|
||
|
tris.Add(r*cols + j);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (int i = 0, j = cols-1; i < cols; j = i++) {
|
||
|
tris.Add(verts.Length-2);
|
||
|
tris.Add((rows-1)*cols + j);
|
||
|
tris.Add((rows-1)*cols + i);
|
||
|
}
|
||
|
|
||
|
// Add calculated mesh to the cache
|
||
|
cached = new CapsuleCache();
|
||
|
cached.rows = rows;
|
||
|
cached.height = height;
|
||
|
cached.verts = verts;
|
||
|
cached.tris = tris.ToArray();
|
||
|
capsuleCache.Add(cached);
|
||
|
}
|
||
|
|
||
|
// Read from cache
|
||
|
verts = cached.verts;
|
||
|
trisArr = cached.tris;
|
||
|
|
||
|
return new RasterizationMesh(verts, trisArr, bounds, localToWorldMatrix);
|
||
|
}
|
||
|
}
|
||
|
}
|