/******************************************************************************
* Copyright (C) Ultraleap, Inc. 2011-2021. *
* *
* Use subject to the terms of the Apache License 2.0 available at *
* http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
* between Ultraleap and you, your company or other organization. *
******************************************************************************/
using Leap.Unity.Infix;
using Leap.Unity.Query;
using System.Collections.Generic;
using UnityEngine;
namespace Leap.Unity.Geometry
{
[System.Serializable]
public struct Plane
{
public Vector3 center;
public Direction3 normal;
public Transform transform;
private Vector3? _cachedCenter;
private Direction3? _cachedNormal;
private Matrix4x4? _cachedMatrix;
private Matrix4x4? _cachedTransformMatrix;
///
/// The local to world matrix for this plane. The plane is always locally an
/// XY plane, with Z being height in front of or behind the plane.
///
/// This matrix is defined by the plane's center and normal, so it's not
/// directly settable.
///
public Matrix4x4 matrix
{
get
{
if (!checkIsCacheValid()) { rebuildCache(); }
return _cachedMatrix.Value;
}
}
private Pose? _cachedPose;
///
/// The local to world pose for this plane. See Plane.matrix.
///
public Pose pose
{
get
{
if (!checkIsCacheValid()) { rebuildCache(); }
return _cachedPose.Value;
}
}
private bool checkIsCacheValid()
{
return _cachedPose.HasValue &&
_cachedCenter.HasValue &&
_cachedNormal.HasValue &&
center == _cachedCenter.Value &&
Direction3.PointsInSameDirection(normal, _cachedNormal.Value) &&
((transform == null && !_cachedTransformMatrix.HasValue) ||
(_cachedTransformMatrix.HasValue &&
_cachedTransformMatrix.Value == transform.localToWorldMatrix));
}
private void rebuildCache()
{
_cachedMatrix = Matrix4x4.TRS(
center,
Quaternion.LookRotation(normal),
Vector3.one);
_cachedPose = new Pose(center, Quaternion.LookRotation(normal));
_cachedCenter = center;
_cachedNormal = normal;
if (transform == null)
{
_cachedTransformMatrix = null;
}
else
{
_cachedMatrix = transform.localToWorldMatrix * _cachedMatrix;
_cachedPose = transform.localToWorldMatrix.GetPose().mul(_cachedPose.Value);
_cachedTransformMatrix = transform.localToWorldMatrix;
}
}
public Plane(Vector3 center, Direction3 normal)
{
_cachedCenter = null;
_cachedNormal = null;
_cachedMatrix = null;
_cachedPose = null;
_cachedTransformMatrix = null;
this.center = center;
this.normal = normal;
this.transform = null;
}
public Plane(Vector3 center, Direction3 normal, Transform transform)
{
_cachedCenter = null;
_cachedNormal = null;
_cachedMatrix = null;
_cachedPose = null;
_cachedTransformMatrix = null;
this.center = center;
this.normal = normal;
this.transform = transform;
}
private static List s_backingUnityBodyCollidersCache;
private static List s_unityBodyCollidersCache
{
get
{
if (s_backingUnityBodyCollidersCache == null)
{
s_backingUnityBodyCollidersCache = new List();
}
return s_backingUnityBodyCollidersCache;
}
}
public bool CollidesWith(UnityEngine.Rigidbody unityBody,
bool includeBehindPlane = false)
{
s_unityBodyCollidersCache.Clear();
Leap.Unity.Utils.FindOwnedChildComponents(
unityBody,
s_unityBodyCollidersCache,
includeInactiveObjects: false
);
foreach (var collider in s_unityBodyCollidersCache)
{
if (this.CollidesWith(collider, includeBehindPlane))
{
return true;
}
}
return false;
}
///
/// Only guaranteed to be correct for convex colliders.
///
public bool CollidesWith(UnityEngine.Collider unityCollider,
bool includeBehindPlane = false)
{
var planePose = this.pose;
var planePoseInverse = this.pose.inverse();
var colliderCenter = getUnityColliderWorldCenter(unityCollider);
var colliderCenter_plane =
planePoseInverse.mul(colliderCenter).position;
var colliderCenterOnPlane_plane = colliderCenter_plane.WithZ(0f);
var colliderCenterOnPlane =
planePose.mul(colliderCenterOnPlane_plane).position;
var closestPoint = unityCollider.ClosestPoint(colliderCenterOnPlane);
var closestPoint_plane = planePoseInverse.mul(closestPoint).position;
// Concave shapes will break here; projection from their center is not
// valid. To support concavity, you'd want to project to the plane from
// the planeward-most point on the concave collider.
var planeToClosestPoint_plane =
closestPoint_plane - colliderCenterOnPlane_plane;
if (includeBehindPlane)
{
// Plane faces forward on Z.
return planeToClosestPoint_plane.z <= 0f;
}
else
{
return planeToClosestPoint_plane.z == 0f;
}
}
private Vector3 getUnityColliderWorldCenter(
UnityEngine.Collider unityCollider)
{
SphereCollider sphere = unityCollider as SphereCollider;
if (sphere != null)
{
return unityCollider.transform.localToWorldMatrix.MultiplyPoint3x4(
sphere.center
);
}
CapsuleCollider capsule = unityCollider as CapsuleCollider;
if (capsule != null)
{
Vector3 a, b;
capsule.GetCapsulePoints(out a, out b);
return unityCollider.transform.localToWorldMatrix.MultiplyPoint3x4(
(a + b) / 2f
);
}
BoxCollider box = unityCollider as BoxCollider;
if (box != null)
{
return unityCollider.transform.localToWorldMatrix.MultiplyPoint3x4(
box.center
);
}
MeshCollider mesh = unityCollider as MeshCollider;
if (mesh != null)
{
// Warning: Only valid for convex meshes.
return unityCollider.transform.localToWorldMatrix.MultiplyPoint3x4(
mesh.bounds.center
);
}
throw new System.Exception("(Plane Collision) Collider type not supported: " +
unityCollider.GetType().ToString());
}
}
}