using UnityEngine;
using System.Collections.Generic;
using Unity.Collections;
namespace Pathfinding.Graphs.Grid {
using Pathfinding.Util;
using Pathfinding.Graphs.Grid.Jobs;
using Pathfinding.Jobs;
///
/// Handles collision checking for graphs.
/// Mostly used by grid based graphs
///
[System.Serializable]
public class GraphCollision {
///
/// Collision shape to use.
/// See:
///
public ColliderType type = ColliderType.Capsule;
///
/// Diameter of capsule or sphere when checking for collision.
/// When checking for collisions the system will check if any colliders
/// overlap a specific shape at the node's position. The shape is determined
/// by the field.
///
/// A diameter of 1 means that the shape has a diameter equal to the node's width,
/// or in other words it is equal to .
///
/// If is set to Ray, this does not affect anything.
///
/// [Open online documentation to see images]
///
public float diameter = 1F;
///
/// Height of capsule or length of ray when checking for collision.
/// If is set to Sphere, this does not affect anything.
///
/// [Open online documentation to see images]
///
/// Warning: In contrast to Unity's capsule collider and character controller this height does not include the end spheres of the capsule, but only the cylinder part.
/// This is mostly for historical reasons.
///
public float height = 2F;
///
/// Height above the ground that collision checks should be done.
/// For example, if the ground was found at y=0, collisionOffset = 2
/// type = Capsule and height = 3 then the physics system
/// will be queried to see if there are any colliders in a capsule
/// for which the bottom sphere that is made up of is centered at y=2
/// and the top sphere has its center at y=2+3=5.
///
/// If type = Sphere then the sphere's center would be at y=2 in this case.
///
public float collisionOffset;
///
/// Direction of the ray when checking for collision.
/// If is not Ray, this does not affect anything
///
/// Deprecated: Only the Both mode is supported now.
///
[System.Obsolete("Only the Both mode is supported now")]
public RayDirection rayDirection = RayDirection.Both;
/// Layers to be treated as obstacles.
public LayerMask mask;
/// Layers to be included in the height check.
public LayerMask heightMask = -1;
///
/// The height to check from when checking height ('ray length' in the inspector).
///
/// As the image below visualizes, different ray lengths can make the ray hit different things.
/// The distance is measured up from the graph plane.
///
/// [Open online documentation to see images]
///
public float fromHeight = 100;
///
/// Toggles thick raycast.
/// See: https://docs.unity3d.com/ScriptReference/Physics.SphereCast.html
///
public bool thickRaycast;
///
/// Diameter of the thick raycast in nodes.
/// 1 equals
///
public float thickRaycastDiameter = 1;
/// Make nodes unwalkable when no ground was found with the height raycast. If height raycast is turned off, this doesn't affect anything.
public bool unwalkableWhenNoGround = true;
///
/// Use Unity 2D Physics API.
///
/// If enabled, the 2D Physics API will be used, and if disabled, the 3D Physics API will be used.
///
/// This changes the collider types (see from 3D versions to their corresponding 2D versions. For example the sphere shape becomes a circle.
///
/// The setting will be ignored when 2D physics is used.
///
/// See: http://docs.unity3d.com/ScriptReference/Physics2D.html
///
public bool use2D;
/// Toggle collision check
public bool collisionCheck = true;
///
/// Toggle height check. If false, the grid will be flat.
///
/// This setting will be ignored when 2D physics is used.
///
public bool heightCheck = true;
///
/// Direction to use as UP.
/// See: Initialize
///
public Vector3 up;
///
/// * .
/// See: Initialize
///
private Vector3 upheight;
/// Used for 2D collision queries
private ContactFilter2D contactFilter;
///
/// Just so that the Physics2D.OverlapPoint method has some buffer to store things in.
/// We never actually read from this array, so we don't even care if this is thread safe.
///
private static Collider2D[] dummyArray = new Collider2D[1];
///
/// * scale * 0.5.
/// Where scale usually is
/// See: Initialize
///
private float finalRadius;
///
/// * scale * 0.5.
/// Where scale usually is See: Initialize
///
private float finalRaycastRadius;
/// Offset to apply after each raycast to make sure we don't hit the same point again in CheckHeightAll
public const float RaycastErrorMargin = 0.005F;
///
/// Sets up several variables using the specified matrix and scale.
/// See: GraphCollision.up
/// See: GraphCollision.upheight
/// See: GraphCollision.finalRadius
/// See: GraphCollision.finalRaycastRadius
///
public void Initialize (GraphTransform transform, float scale) {
up = (transform.Transform(Vector3.up) - transform.Transform(Vector3.zero)).normalized;
upheight = up*height;
finalRadius = diameter*scale*0.5F;
finalRaycastRadius = thickRaycastDiameter*scale*0.5F;
contactFilter = new ContactFilter2D { layerMask = mask, useDepth = false, useLayerMask = true, useNormalAngle = false, useTriggers = false };
}
///
/// Returns true if the position is not obstructed.
/// If is false, this will always return true.
///
public bool Check (Vector3 position) {
if (!collisionCheck) {
return true;
}
if (use2D) {
switch (type) {
case ColliderType.Capsule:
case ColliderType.Sphere:
return Physics2D.OverlapCircle(position, finalRadius, contactFilter, dummyArray) == 0;
default:
return Physics2D.OverlapPoint(position, contactFilter, dummyArray) == 0;
}
}
position += up*collisionOffset;
switch (type) {
case ColliderType.Capsule:
return !Physics.CheckCapsule(position, position+upheight, finalRadius, mask, QueryTriggerInteraction.Ignore);
case ColliderType.Sphere:
return !Physics.CheckSphere(position, finalRadius, mask, QueryTriggerInteraction.Ignore);
default:
return !Physics.Raycast(position, up, height, mask, QueryTriggerInteraction.Ignore) && !Physics.Raycast(position+upheight, -up, height, mask, QueryTriggerInteraction.Ignore);
}
}
///
/// Returns the position with the correct height.
/// If is false, this will return position.
///
public Vector3 CheckHeight (Vector3 position) {
RaycastHit hit;
bool walkable;
return CheckHeight(position, out hit, out walkable);
}
///
/// Returns the position with the correct height.
/// If is false, this will return position.
/// walkable will be set to false if nothing was hit.
/// The ray will check a tiny bit further than to the grids base to avoid floating point errors when the ground is exactly at the base of the grid
///
public Vector3 CheckHeight (Vector3 position, out RaycastHit hit, out bool walkable) {
walkable = true;
if (!heightCheck || use2D) {
hit = new RaycastHit();
return position;
}
if (thickRaycast) {
var ray = new Ray(position+up*fromHeight, -up);
if (Physics.SphereCast(ray, finalRaycastRadius, out hit, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore)) {
return VectorMath.ClosestPointOnLine(ray.origin, ray.origin+ray.direction, hit.point);
}
walkable &= !unwalkableWhenNoGround;
} else {
// Cast a ray from above downwards to try to find the ground
if (Physics.Raycast(position+up*fromHeight, -up, out hit, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore)) {
return hit.point;
}
walkable &= !unwalkableWhenNoGround;
}
return position;
}
/// Internal buffer used by
RaycastHit[] hitBuffer = new RaycastHit[8];
///
/// Returns all hits when checking height for position.
/// Warning: Does not work well with thick raycast, will only return an object a single time
///
/// Warning: The returned array is ephermal. It will be invalidated when this method is called again.
/// If you need persistent results you should copy it.
///
/// The returned array may be larger than the actual number of hits, the numHits out parameter indicates how many hits there actually were.
///
public RaycastHit[] CheckHeightAll (Vector3 position, out int numHits) {
if (!heightCheck || use2D) {
hitBuffer[0] = new RaycastHit {
point = position,
distance = 0,
};
numHits = 1;
return hitBuffer;
}
// Cast a ray from above downwards to try to find the ground
numHits = Physics.RaycastNonAlloc(position+up*fromHeight, -up, hitBuffer, fromHeight+0.005F, heightMask, QueryTriggerInteraction.Ignore);
if (numHits == hitBuffer.Length) {
// Try again with a larger buffer
hitBuffer = new RaycastHit[hitBuffer.Length*2];
return CheckHeightAll(position, out numHits);
}
return hitBuffer;
}
///
/// Returns if the position is obstructed for all nodes using the Ray collision checking method.
/// collisionCheckResult[i] = true if there were no obstructions, false otherwise
///
public void JobCollisionRay (NativeArray nodePositions, NativeArray collisionCheckResult, Vector3 up, Allocator allocationMethod, JobDependencyTracker dependencyTracker) {
var collisionRaycastCommands1 = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
var collisionRaycastCommands2 = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
var collisionHits1 = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
var collisionHits2 = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
// Fire rays from above down to the nodes' positions
new JobPrepareRaycasts {
origins = nodePositions,
originOffset = up * (height + collisionOffset),
direction = -up,
distance = height,
mask = mask,
physicsScene = Physics.defaultPhysicsScene,
raycastCommands = collisionRaycastCommands1,
}.Schedule(dependencyTracker);
// Fire rays from the node up towards the sky
new JobPrepareRaycasts {
origins = nodePositions,
originOffset = up * collisionOffset,
direction = up,
distance = height,
mask = mask,
physicsScene = Physics.defaultPhysicsScene,
raycastCommands = collisionRaycastCommands2,
}.Schedule(dependencyTracker);
dependencyTracker.ScheduleBatch(collisionRaycastCommands1, collisionHits1, 2048);
dependencyTracker.ScheduleBatch(collisionRaycastCommands2, collisionHits2, 2048);
new JobMergeRaycastCollisionHits {
hit1 = collisionHits1,
hit2 = collisionHits2,
result = collisionCheckResult,
}.Schedule(dependencyTracker);
}
#if UNITY_2022_2_OR_NEWER
public void JobCollisionCapsule (NativeArray nodePositions, NativeArray collisionCheckResult, Vector3 up, Allocator allocationMethod, JobDependencyTracker dependencyTracker) {
var commands = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
var collisionHits = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
new JobPrepareCapsuleCommands {
origins = nodePositions,
originOffset = up * collisionOffset,
direction = up * height,
radius = finalRadius,
mask = mask,
commands = commands,
physicsScene = Physics.defaultPhysicsScene,
}.Schedule(dependencyTracker);
dependencyTracker.ScheduleBatch(commands, collisionHits, 2048);
new JobColliderHitsToBooleans {
hits = collisionHits,
result = collisionCheckResult,
}.Schedule(dependencyTracker);
}
public void JobCollisionSphere (NativeArray nodePositions, NativeArray collisionCheckResult, Vector3 up, Allocator allocationMethod, JobDependencyTracker dependencyTracker) {
var commands = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
var collisionHits = dependencyTracker.NewNativeArray(nodePositions.Length, allocationMethod);
new JobPrepareSphereCommands {
origins = nodePositions,
originOffset = up * collisionOffset,
radius = finalRadius,
mask = mask,
commands = commands,
physicsScene = Physics.defaultPhysicsScene,
}.Schedule(dependencyTracker);
dependencyTracker.ScheduleBatch(commands, collisionHits, 2048);
new JobColliderHitsToBooleans {
hits = collisionHits,
result = collisionCheckResult,
}.Schedule(dependencyTracker);
}
#endif
}
///
/// Determines collision check shape.
/// See:
///
public enum ColliderType {
/// Uses a Sphere, Physics.CheckSphere. In 2D this is a circle instead.
Sphere,
/// Uses a Capsule, Physics.CheckCapsule. This will behave identically to the Sphere mode in 2D.
Capsule,
/// Uses a Ray, Physics.Linecast. In 2D this is a single point instead.
Ray
}
/// Determines collision check ray direction
public enum RayDirection {
Up, /// < Casts the ray from the bottom upwards
Down, /// < Casts the ray from the top downwards
Both /// < Casts two rays in both directions
}
}