summaryrefslogtreecommitdiff
path: root/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs
diff options
context:
space:
mode:
authorchai <215380520@qq.com>2024-05-23 10:08:29 +0800
committerchai <215380520@qq.com>2024-05-23 10:08:29 +0800
commit8722a9920c1f6119bf6e769cba270e63097f8e25 (patch)
tree2eaf9865de7fb1404546de4a4296553d8f68cc3b /Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs
parent3ba4020b69e5971bb0df7ee08b31d10ea4d01937 (diff)
+ astar project
Diffstat (limited to 'Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs')
-rw-r--r--Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs141
1 files changed, 141 insertions, 0 deletions
diff --git a/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs
new file mode 100644
index 0000000..c6a58c6
--- /dev/null
+++ b/Other/AstarPathfindingDemo/Packages/com.arongranberg.astar/Behaviors/AIPathAlignedToSurface.cs
@@ -0,0 +1,141 @@
+using UnityEngine;
+using System.Collections.Generic;
+using UnityEngine.Profiling;
+
+namespace Pathfinding {
+ using Pathfinding.Util;
+ using Unity.Collections.LowLevel.Unsafe;
+
+ /// <summary>
+ /// Movement script for curved worlds.
+ /// This script inherits from AIPath, but adjusts its movement plane every frame using the ground normal.
+ /// </summary>
+ [HelpURL("https://arongranberg.com/astar/documentation/stable/aipathalignedtosurface.html")]
+ public class AIPathAlignedToSurface : AIPath {
+ /// <summary>Scratch dictionary used to avoid allocations every frame</summary>
+ static readonly Dictionary<Mesh, int> scratchDictionary = new Dictionary<Mesh, int>();
+
+ protected override void OnEnable () {
+ base.OnEnable();
+ movementPlane = new Util.SimpleMovementPlane(rotation);
+ }
+
+ protected override void ApplyGravity (float deltaTime) {
+ // Apply gravity
+ if (usingGravity) {
+ // Gravity is relative to the current surface.
+ // Only the normal direction is well defined however so x and z are ignored.
+ verticalVelocity += deltaTime * (float.IsNaN(gravity.x) ? Physics.gravity.y : gravity.y);
+ } else {
+ verticalVelocity = 0;
+ }
+ }
+
+ /// <summary>
+ /// Calculates smoothly interpolated normals for all raycast hits and uses that to set the movement planes of the agents.
+ ///
+ /// To support meshes that change at any time, we use Mesh.AcquireReadOnlyMeshData to get a read-only view of the mesh data.
+ /// This is only efficient if we batch all updates and make a single call to Mesh.AcquireReadOnlyMeshData.
+ ///
+ /// This method is quite convoluted due to having to read the raw vertex data streams from unity meshes to avoid allocations.
+ /// </summary>
+ public static void UpdateMovementPlanes (AIPathAlignedToSurface[] components, int count) {
+ Profiler.BeginSample("UpdateMovementPlanes");
+ var meshes = ListPool<Mesh>.Claim();
+ var componentsByMesh = new List<List<AIPathAlignedToSurface> >();
+ var meshToIndex = scratchDictionary;
+ for (int i = 0; i < count; i++) {
+ var c = components[i].lastRaycastHit.collider;
+ // triangleIndex can be -1 if the mesh collider is convex, and the raycast started inside it.
+ // This is not a documented behavior, but it seems to happen in practice.
+ if (c is MeshCollider mc && components[i].lastRaycastHit.triangleIndex != -1) {
+ var sharedMesh = mc.sharedMesh;
+ if (meshToIndex.TryGetValue(sharedMesh, out var meshIndex)) {
+ componentsByMesh[meshIndex].Add(components[i]);
+ } else if (sharedMesh != null && sharedMesh.isReadable) {
+ meshToIndex[sharedMesh] = meshes.Count;
+ meshes.Add(sharedMesh);
+ componentsByMesh.Add(ListPool<AIPathAlignedToSurface>.Claim());
+ componentsByMesh[meshes.Count-1].Add(components[i]);
+ } else {
+ // Unreadable mesh
+ components[i].SetInterpolatedNormal(components[i].lastRaycastHit.normal);
+ }
+ } else {
+ // Not a mesh collider, or the triangle index was -1
+ components[i].SetInterpolatedNormal(components[i].lastRaycastHit.normal);
+ }
+ }
+ var meshDatas = Mesh.AcquireReadOnlyMeshData(meshes);
+ for (int i = 0; i < meshes.Count; i++) {
+ var m = meshes[i];
+ var meshIndex = meshToIndex[m];
+ var meshData = meshDatas[meshIndex];
+ var componentsForMesh = componentsByMesh[meshIndex];
+
+ var stream = meshData.GetVertexAttributeStream(UnityEngine.Rendering.VertexAttribute.Normal);
+
+ if (stream == -1) {
+ // Mesh does not have normals
+ for (int j = 0; j < componentsForMesh.Count; j++) componentsForMesh[j].SetInterpolatedNormal(componentsForMesh[j].lastRaycastHit.normal);
+ continue;
+ }
+ var vertexData = meshData.GetVertexData<byte>(stream);
+ var stride = meshData.GetVertexBufferStride(stream);
+ var normalOffset = meshData.GetVertexAttributeOffset(UnityEngine.Rendering.VertexAttribute.Normal);
+ unsafe {
+ var normals = (byte*)vertexData.GetUnsafeReadOnlyPtr() + normalOffset;
+
+ for (int j = 0; j < componentsForMesh.Count; j++) {
+ var comp = componentsForMesh[j];
+ var hit = comp.lastRaycastHit;
+ int t0, t1, t2;
+
+ // Get the vertex indices corresponding to the triangle that was hit
+ if (meshData.indexFormat == UnityEngine.Rendering.IndexFormat.UInt16) {
+ var indices = meshData.GetIndexData<ushort>();
+ t0 = indices[hit.triangleIndex * 3 + 0];
+ t1 = indices[hit.triangleIndex * 3 + 1];
+ t2 = indices[hit.triangleIndex * 3 + 2];
+ } else {
+ var indices = meshData.GetIndexData<int>();
+ t0 = indices[hit.triangleIndex * 3 + 0];
+ t1 = indices[hit.triangleIndex * 3 + 1];
+ t2 = indices[hit.triangleIndex * 3 + 2];
+ }
+
+ // Get the normals corresponding to the 3 vertices
+ var n0 = *((Vector3*)(normals + t0 * stride));
+ var n1 = *((Vector3*)(normals + t1 * stride));
+ var n2 = *((Vector3*)(normals + t2 * stride));
+
+ // Interpolate the normal using the barycentric coordinates
+ Vector3 baryCenter = hit.barycentricCoordinate;
+ Vector3 interpolatedNormal = n0 * baryCenter.x + n1 * baryCenter.y + n2 * baryCenter.z;
+ interpolatedNormal = interpolatedNormal.normalized;
+ Transform hitTransform = hit.collider.transform;
+ interpolatedNormal = hitTransform.TransformDirection(interpolatedNormal);
+ comp.SetInterpolatedNormal(interpolatedNormal);
+ }
+ }
+ }
+ meshDatas.Dispose();
+ for (int i = 0; i < componentsByMesh.Count; i++) ListPool<AIPathAlignedToSurface>.Release(componentsByMesh[i]);
+ ListPool<Mesh>.Release(ref meshes);
+ scratchDictionary.Clear();
+ Profiler.EndSample();
+ }
+
+ void SetInterpolatedNormal (Vector3 normal) {
+ if (normal != Vector3.zero) {
+ var fwd = Vector3.Cross(movementPlane.rotation * Vector3.right, normal);
+ movementPlane = new Util.SimpleMovementPlane(Quaternion.LookRotation(fwd, normal));
+ }
+ if (rvoController != null) rvoController.movementPlane = movementPlane;
+ }
+
+ protected override void UpdateMovementPlane () {
+ // The UpdateMovementPlanes method will take care of this
+ }
+ }
+}