hellbound/Assets/Sources/Lean/Touch/Extras/LeanScreenDepth.cs

415 lines
13 KiB
C#

using UnityEngine;
using Lean.Common;
namespace Lean.Touch
{
/// <summary>This struct handles the conversion between screen coordinates, and world coordinates.
/// This conversion is required for many touch interactions, and there are numerous ways it can be performed.</summary>
[System.Serializable]
public struct LeanScreenDepth
{
public enum ConversionType
{
FixedDistance,
DepthIntercept,
PhysicsRaycast,
PlaneIntercept,
PathClosest,
AutoDistance,
HeightIntercept
}
/// <summary>The method used to convert between screen coordinates, and world coordinates.
/// FixedDistance = A point will be projected out from the camera.
/// DepthIntercept = A point will be intercepted out from the camera on a surface lying flat on the XY plane.
/// PhysicsRaycast = A ray will be cast from the camera.
/// PathClosest = A point will be intercepted out from the camera to the closest point on the specified path.
/// AutoDistance = A point will be projected out from the camera based on the current Transform depth.
/// HeightIntercept = A point will be intercepted out from the camera on a surface lying flat on the XZ plane.</summary>
public ConversionType Conversion;
/// <summary>The camera the depth calculations will be done using.
/// None = MainCamera.</summary>
public Camera Camera;
/// <summary>The plane/path/etc that will be intercepted.</summary>
public Object Object;
/// <summary>The layers used in the raycast.</summary>
public LayerMask Layers;
/// <summary>Tooltips are modified at runtime based on Conversion setting.</summary>
public float Distance;
/// <summary>When performing a ScreenDepth conversion, the converted point can have a normal associated with it. This stores that.</summary>
public static Vector3 LastWorldNormal = Vector3.forward;
private static readonly RaycastHit[] hits = new RaycastHit[128];
public LeanScreenDepth(ConversionType newConversion, int newLayers = Physics.DefaultRaycastLayers, float newDistance = 0.0f)
{
Conversion = newConversion;
Camera = null;
Object = null;
Layers = newLayers;
Distance = newDistance;
}
// This will do the actual conversion
public Vector3 Convert(Vector2 screenPoint, GameObject gameObject = null, Transform ignore = null)
{
var position = default(Vector3);
TryConvert(ref position, screenPoint, gameObject, ignore);
return position;
}
// This will return the delta between two converted screenPoints
public Vector3 ConvertDelta(Vector2 lastScreenPoint, Vector2 screenPoint, GameObject gameObject = null, Transform ignore = null)
{
var lastWorldPoint = Convert(lastScreenPoint, gameObject, ignore);
var worldPoint = Convert( screenPoint, gameObject, ignore);
return worldPoint - lastWorldPoint;
}
// This will do the actual conversion
public bool TryConvert(ref Vector3 position, Vector2 screenPoint, GameObject gameObject = null, Transform ignore = null)
{
var camera = LeanHelper.GetCamera(Camera, gameObject);
if (camera != null)
{
switch (Conversion)
{
case ConversionType.FixedDistance:
{
var screenPoint3 = new Vector3(screenPoint.x, screenPoint.y, Distance);
position = camera.ScreenToWorldPoint(screenPoint3);
LastWorldNormal = -camera.transform.forward;
return true;
}
case ConversionType.DepthIntercept:
{
var ray = camera.ScreenPointToRay(screenPoint);
var slope = -ray.direction.z;
if (slope != 0.0f)
{
var scale = (ray.origin.z - Distance) / slope;
position = ray.GetPoint(scale);
LastWorldNormal = Vector3.back;
return true;
}
}
break;
case ConversionType.PhysicsRaycast:
{
var ray = camera.ScreenPointToRay(screenPoint);
var hitCount = Physics.RaycastNonAlloc(ray, hits, float.PositiveInfinity, Layers);
var bestPoint = default(Vector3);
var bestDist = float.PositiveInfinity;
for (var i = hitCount - 1; i >= 0; i--)
{
var hit = hits[i];
var hitDistance = hit.distance;
if (hitDistance < bestDist && IsChildOf(hit.transform, ignore) == false)
{
bestPoint = hit.point + hit.normal * Distance;
bestDist = hitDistance;
LastWorldNormal = hit.normal;
}
}
if (bestDist < float.PositiveInfinity)
{
position = bestPoint;
return true;
}
}
break;
case ConversionType.PlaneIntercept:
{
var plane = default(LeanPlane);
if (Exists(gameObject, ref plane) == true)
{
var ray = camera.ScreenPointToRay(screenPoint);
var hit = default(Vector3);
if (plane.TryRaycast(ray, ref hit, Distance) == true)
{
position = hit;
LastWorldNormal = plane.transform.forward;
return true;
}
}
}
break;
case ConversionType.PathClosest:
{
var path = default(LeanPath);
if (Exists(gameObject, ref path) == true)
{
var ray = camera.ScreenPointToRay(screenPoint);
if (path.TryGetClosest(ray, ref position, -1, Distance * Time.deltaTime) == true)
{
LastWorldNormal = LeanPath.LastWorldNormal;
return true;
}
}
}
break;
case ConversionType.AutoDistance:
{
if (gameObject != null)
{
var depth = camera.WorldToScreenPoint(gameObject.transform.position).z;
var screenPoint3 = new Vector3(screenPoint.x, screenPoint.y, depth + Distance);
position = camera.ScreenToWorldPoint(screenPoint3);
LastWorldNormal = -camera.transform.forward;
return true;
}
}
break;
case ConversionType.HeightIntercept:
{
var ray = camera.ScreenPointToRay(screenPoint);
var slope = -ray.direction.y;
if (slope != 0.0f)
{
var scale = (ray.origin.y - Distance) / slope;
position = ray.GetPoint(scale);
LastWorldNormal = Vector3.down;
return true;
}
}
break;
}
}
else
{
Debug.LogError("Failed to find camera. Either tag your cameras MainCamera, or set one in this component.", gameObject);
}
return false;
}
// If the specified object doesn't exist, try and find it in the scene
private bool Exists<T>(GameObject gameObject, ref T instance)
where T : Object
{
instance = Object as T;
// Already exists?
if (instance != null)
{
return true;
}
// Exists in ancestor?
Object = instance = gameObject.GetComponentInParent<T>();
if (instance != null)
{
return true;
}
// Exists in scene?
Object = instance = Object.FindObjectOfType<T>();
if (instance != null)
{
return true;
}
// Doesn't exist
return false;
}
// This will return true if current or one of its parents matches the specified gameObject's Transform (current must be non-null)
private static bool IsChildOf(Transform current, Transform target)
{
if (target != null)
{
while (true)
{
if (current == target)
{
return true;
}
current = current.parent;
if (current == null)
{
break;
}
}
}
return false;
}
}
}
#if UNITY_EDITOR
namespace Lean.Touch.Editor
{
using UnityEditor;
[CustomPropertyDrawer(typeof(LeanScreenDepth))]
public class LeanScreenDepth_Drawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var conversion = (LeanScreenDepth.ConversionType)property.FindPropertyRelative("Conversion").enumValueIndex;
var height = base.GetPropertyHeight(property, label);
var step = height + 2;
switch (conversion)
{
case LeanScreenDepth.ConversionType.FixedDistance: height += step * 2; break;
case LeanScreenDepth.ConversionType.DepthIntercept: height += step * 2; break;
case LeanScreenDepth.ConversionType.PhysicsRaycast: height += step * 3; break;
case LeanScreenDepth.ConversionType.PlaneIntercept: height += step * 3; break;
case LeanScreenDepth.ConversionType.PathClosest: height += step * 3; break;
case LeanScreenDepth.ConversionType.AutoDistance: height += step * 2; break;
case LeanScreenDepth.ConversionType.HeightIntercept: height += step * 2; break;
}
return height;
}
public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
{
var conversion = (LeanScreenDepth.ConversionType)property.FindPropertyRelative("Conversion").enumValueIndex;
var height = base.GetPropertyHeight(property, label);
rect.height = height;
DrawProperty(ref rect, property, label, "Conversion", label.text, "The method used to convert between screen coordinates, and world coordinates.\n\nFixedDistance = A point will be projected out from the camera.\n\nDepthIntercept = A point will be intercepted out from the camera on a surface lying flat on the XY plane.\n\nPhysicsRaycast = A ray will be cast from the camera.\n\nPathClosest = A point will be intercepted out from the camera to the closest point on the specified path.\n\nAutoDistance = A point will be projected out from the camera based on the current Transform depth.\n\nHeightIntercept = A point will be intercepted out from the camera on a surface lying flat on the XZ plane.");
EditorGUI.indentLevel++;
{
DrawProperty(ref rect, property, label, "Camera", null, "The camera the depth calculations will be done using.\n\nNone = MainCamera.");
switch (conversion)
{
case LeanScreenDepth.ConversionType.FixedDistance:
{
LeanEditor.BeginError(property.FindPropertyRelative("Distance").floatValue == 0.0f);
DrawProperty(ref rect, property, label, "Distance", "Distance", "The world space distance from the camera the point will be placed. This should be greater than 0.");
LeanEditor.EndError();
}
break;
case LeanScreenDepth.ConversionType.DepthIntercept:
{
DrawProperty(ref rect, property, label, "Distance", "Z =", "The world space point along the Z axis the plane will be placed. For normal 2D scenes this should be 0.");
}
break;
case LeanScreenDepth.ConversionType.PhysicsRaycast:
{
LeanEditor.BeginError(property.FindPropertyRelative("Layers").intValue == 0);
DrawProperty(ref rect, property, label, "Layers", "The layers used in the raycast.");
LeanEditor.EndError();
DrawProperty(ref rect, property, label, "Distance", "Offset", "The world space offset from the raycast hit point.");
}
break;
case LeanScreenDepth.ConversionType.PlaneIntercept:
{
DrawObjectProperty<LeanPlane>(ref rect, property, "Plane", "The plane that will be intercepted.");
DrawProperty(ref rect, property, label, "Distance", "Offset", "The world space offset from the intercept hit point.");
}
break;
case LeanScreenDepth.ConversionType.PathClosest:
{
DrawObjectProperty<LeanPath>(ref rect, property, "Path", "The path that will be intercepted.");
DrawProperty(ref rect, property, label, "Distance", "Max Delta", "The maximum amount of segments that can be moved between.");
}
break;
case LeanScreenDepth.ConversionType.AutoDistance:
{
DrawProperty(ref rect, property, label, "Distance", "Offset", "The depth offset from the calculated point.");
}
break;
case LeanScreenDepth.ConversionType.HeightIntercept:
{
DrawProperty(ref rect, property, label, "Distance", "Y =", "The world space point along the Y axis the plane will be placed. For normal top down scenes this should be 0.");
}
break;
}
}
EditorGUI.indentLevel--;
}
private void DrawObjectProperty<T>(ref Rect rect, SerializedProperty property, string title, string tooltip)
where T : Object
{
var propertyObject = property.FindPropertyRelative("Object");
var oldValue = propertyObject.objectReferenceValue as T;
var color = GUI.color; if (oldValue == null) GUI.color = Color.red;
var mixed = EditorGUI.showMixedValue; EditorGUI.showMixedValue = propertyObject.hasMultipleDifferentValues;
var newValue = EditorGUI.ObjectField(rect, new GUIContent(title, tooltip), oldValue, typeof(T), true);
EditorGUI.showMixedValue = mixed;
GUI.color = color;
if (oldValue != newValue)
{
propertyObject.objectReferenceValue = newValue;
}
rect.y += rect.height;
}
private void DrawProperty(ref Rect rect, SerializedProperty property, GUIContent label, string childName, string overrideName = null, string overrideTooltip = null)
{
var childProperty = property.FindPropertyRelative(childName);
label.text = string.IsNullOrEmpty(overrideName) == false ? overrideName : childProperty.displayName;
label.tooltip = string.IsNullOrEmpty(overrideTooltip) == false ? overrideTooltip : childProperty.tooltip;
EditorGUI.PropertyField(rect, childProperty, label);
rect.y += rect.height + 2;
}
}
}
#endif