Skip to content

Instantly share code, notes, and snippets.

@Epicguru
Created October 9, 2020 17:52
Show Gist options
  • Save Epicguru/aef4ef03f5880c9fa47a4b9d5f36e4f1 to your computer and use it in GitHub Desktop.
Save Epicguru/aef4ef03f5880c9fa47a4b9d5f36e4f1 to your computer and use it in GitHub Desktop.
Unity physics advanced Raycast helper.
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Contains a dual linecast method, which is designed to calculate hits on both front and back faces
/// of colliders. Useful for testing depth of objects (such as checking how thick a wall is) or doing advanced
/// ballistic simulation (such as a projectile penetrating a surface and deflecting based on how far it traveled through the object).
///
/// Also useful to work around the problem of raycasts not detecting hits when started inside a collider.
/// In that situation, the back face will be detected by this method.
///
/// However, this method is limited. Currently, it does support concave mesh colliders where the ray will intersect the object
/// more than once (imagine a doughnut, with the ray going through one side, out into the middle, and back into the other side).
/// However, convex mesh colliders and all other 3D collider types are fully supported.
/// </summary>
public static class RaycastUtils
{
public const int MAX_RAY_HITS = 512;
private static readonly RaycastHit[] cacheA = new RaycastHit[MAX_RAY_HITS], cacheB = new RaycastHit[MAX_RAY_HITS];
private static readonly Dictionary<Collider, int> colliderToIndex = new Dictionary<Collider, int>();
public static int DualLinecastAll(DualRaycastHit[] hits, Vector3 start, Vector3 end, LayerMask mask, QueryTriggerInteraction triggerInteraction)
{
if (hits == null)
return 0;
const bool SKIP_ERROR_CHECKS = false;
Vector3 direction = (end - start);
float distance = direction.magnitude;
int forwardsHitCount = Physics.RaycastNonAlloc(start, direction, cacheA, distance, mask, triggerInteraction);
int backwardsHitCount = Physics.RaycastNonAlloc(end, -direction, cacheB, distance, mask, triggerInteraction);
colliderToIndex.Clear();
int dualCount = 0;
for (int i = 0; i < forwardsHitCount; i++)
{
var hit = cacheA[i];
if (!SKIP_ERROR_CHECKS && colliderToIndex.ContainsKey(hit.collider))
{
Debug.LogWarning($"Forward raycast hit collider '{hit.collider.gameObject.name}' more than once. Concave meshes are not yet supported in DualLinecast.", hit.collider);
continue;
}
colliderToIndex.Add(hit.collider, i);
if (dualCount >= hits.Length)
{
dualCount++;
break;
}
var obj = new DualRaycastHit();
obj.FrontHit = hit;
hits[dualCount] = obj;
dualCount++;
}
for (int i = 0; i < backwardsHitCount; i++)
{
var hit = cacheB[i];
if (colliderToIndex.TryGetValue(hit.collider, out int index))
{
// Found back side!
var obj = hits[index];
if (!SKIP_ERROR_CHECKS && obj.HasBackHit)
Debug.LogWarning($"Multiple back facing hits on collider '{hit.collider.gameObject.name}'. Concave meshes are not yet supported in DualLinecast.", hit.collider);
obj.BackHit = hit;
hits[index] = obj;
}
else
{
if (dualCount >= hits.Length)
{
dualCount++;
break;
}
// Hit only on the way back. This can happen if the start position is inside a collider.
var obj = new DualRaycastHit();
obj.BackHit = hit;
hits[dualCount] = obj;
dualCount++;
}
}
return dualCount;
}
}
public struct DualRaycastHit
{
public bool HasFrontHit
{
get
{
return FrontHit.collider != null;
}
}
public bool HasBackHit
{
get
{
return BackHit.collider != null;
}
}
public bool IsValid
{
get
{
return HasFrontHit || HasBackHit;
}
}
public RaycastHit FrontHit;
public RaycastHit BackHit;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment