Skip to content

Instantly share code, notes, and snippets.

@Epicguru
Created October 9, 2020 17:52
Show Gist options
  • Select an option

  • Save Epicguru/aef4ef03f5880c9fa47a4b9d5f36e4f1 to your computer and use it in GitHub Desktop.

Select an option

Save Epicguru/aef4ef03f5880c9fa47a4b9d5f36e4f1 to your computer and use it in GitHub Desktop.

Revisions

  1. Epicguru created this gist Oct 9, 2020.
    117 changes: 117 additions & 0 deletions RaycastUtils.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,117 @@
    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;
    }