mirror of
https://github.com/NotAKidoS/NAK_CVR_Mods.git
synced 2025-09-02 22:39:22 +00:00
Move many mods to Deprecated folder, fix spelling
This commit is contained in:
parent
5e822cec8d
commit
0042590aa6
539 changed files with 7475 additions and 3120 deletions
42
.Deprecated/SuperAwesomeMod/Interaction/CVRPlayerHand.cs
Normal file
42
.Deprecated/SuperAwesomeMod/Interaction/CVRPlayerHand.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using ABI_RC.Core.InteractionSystem;
|
||||
using ABI_RC.Core.InteractionSystem.Base;
|
||||
using ABI_RC.Systems.InputManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction
|
||||
{
|
||||
public class CVRPlayerHand : MonoBehaviour
|
||||
{
|
||||
#region Fields
|
||||
|
||||
[SerializeField]
|
||||
private CVRHand _hand;
|
||||
|
||||
// Pickup rig
|
||||
[SerializeField] private Transform rayDirection;
|
||||
[SerializeField] private Transform _attachmentPoint;
|
||||
[SerializeField] private Transform _pivotPoint;
|
||||
[SerializeField] private VelocityTracker _velocityTracker;
|
||||
|
||||
// Pickup state
|
||||
private bool _isHoldingObject;
|
||||
private Pickupable _heldPickupable;
|
||||
private Pickupable _proximityPickupable;
|
||||
|
||||
#endregion Fields
|
||||
|
||||
#region Unity Events
|
||||
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Private Methods
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
#region Public Methods
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
using ABI_RC.Core.Player.Interaction.RaycastImpl;
|
||||
using ABI_RC.Core.Savior;
|
||||
using ABI_RC.Systems.InputManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction
|
||||
{
|
||||
public class CVRPlayerInteractionManager : MonoBehaviour
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
public static CVRPlayerInteractionManager Instance { get; private set; }
|
||||
|
||||
#endregion Singleton
|
||||
|
||||
#region Serialized Fields
|
||||
|
||||
[Header("Hand Components")]
|
||||
[SerializeField] private CVRPlayerHand handVrLeft;
|
||||
[SerializeField] private CVRPlayerHand handVrRight;
|
||||
[SerializeField] private CVRPlayerHand handDesktopRight; // Desktop does not have a left hand
|
||||
|
||||
[Header("Raycast Transforms")]
|
||||
[SerializeField] private Transform raycastTransformVrRight;
|
||||
[SerializeField] private Transform raycastTransformVrLeft;
|
||||
[SerializeField] private Transform raycastTransformDesktopRight;
|
||||
|
||||
[Header("Settings")]
|
||||
[SerializeField] private bool interactionEnabled = true;
|
||||
[SerializeField] private LayerMask interactionLayerMask = -1; // Default to all layers, will be filtered
|
||||
|
||||
#endregion Serialized Fields
|
||||
|
||||
#region Properties
|
||||
|
||||
private CVRPlayerHand _rightHand;
|
||||
private CVRPlayerHand _leftHand;
|
||||
|
||||
private CVRPlayerRaycaster _rightRaycaster;
|
||||
private CVRPlayerRaycaster _leftRaycaster;
|
||||
|
||||
private CVRRaycastResult _rightRaycastResult;
|
||||
private CVRRaycastResult _leftRaycastResult;
|
||||
|
||||
// Input handler
|
||||
private CVRPlayerInputHandler _inputHandler;
|
||||
|
||||
// Interaction flags
|
||||
public bool InteractionEnabled
|
||||
{
|
||||
get => interactionEnabled;
|
||||
set => interactionEnabled = value;
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance != null && Instance != this)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
Instance = this;
|
||||
|
||||
// Create the input handler
|
||||
_inputHandler = gameObject.AddComponent<CVRPlayerInputHandler>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// Setup interaction for current device mode
|
||||
SetupInteractionForDeviceMode();
|
||||
|
||||
// Listen for VR mode changes
|
||||
MetaPort.Instance.onVRModeSwitch.AddListener(SetupInteractionForDeviceMode);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!interactionEnabled)
|
||||
return;
|
||||
|
||||
// Process right hand
|
||||
if (_rightRaycaster != null)
|
||||
{
|
||||
// Determine raycast flags based on current mode
|
||||
CVRPlayerRaycaster.RaycastFlags flags = DetermineRaycastFlags(_rightHand);
|
||||
|
||||
// Get raycast results
|
||||
_rightRaycastResult = _rightRaycaster.GetRaycastResults(flags);
|
||||
|
||||
// Process input based on raycast results
|
||||
_inputHandler.ProcessInput(CVRHand.Right, _rightRaycastResult);
|
||||
}
|
||||
|
||||
// Process left hand (if available)
|
||||
if (_leftRaycaster != null)
|
||||
{
|
||||
// Determine raycast flags based on current mode
|
||||
CVRPlayerRaycaster.RaycastFlags flags = DetermineRaycastFlags(_leftHand);
|
||||
|
||||
// Get raycast results
|
||||
_leftRaycastResult = _leftRaycaster.GetRaycastResults(flags);
|
||||
|
||||
// Process input based on raycast results
|
||||
_inputHandler.ProcessInput(CVRHand.Left, _leftRaycastResult);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Clean up event listener
|
||||
if (MetaPort.Instance != null)
|
||||
MetaPort.Instance.onVRModeSwitch.RemoveListener(SetupInteractionForDeviceMode);
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Register a custom tool mode
|
||||
/// </summary>
|
||||
public void RegisterCustomToolMode(System.Action<CVRHand, CVRRaycastResult, InputState> callback)
|
||||
{
|
||||
_inputHandler.RegisterCustomTool(callback);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unregister the current custom tool mode
|
||||
/// </summary>
|
||||
public void UnregisterCustomToolMode()
|
||||
{
|
||||
_inputHandler.UnregisterCustomTool();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the interaction mode
|
||||
/// </summary>
|
||||
public void SetInteractionMode(CVRPlayerInputHandler.InteractionMode mode)
|
||||
{
|
||||
_inputHandler.SetInteractionMode(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the raycast result for a specific hand
|
||||
/// </summary>
|
||||
public CVRRaycastResult GetRaycastResult(CVRHand hand)
|
||||
{
|
||||
return hand == CVRHand.Left ? _leftRaycastResult : _rightRaycastResult;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void SetupInteractionForDeviceMode()
|
||||
{
|
||||
bool isVr = MetaPort.Instance.isUsingVr;
|
||||
|
||||
if (isVr)
|
||||
{
|
||||
// VR mode
|
||||
_rightHand = handVrRight;
|
||||
_leftHand = handVrLeft;
|
||||
|
||||
// VR uses the controller transform for raycasting
|
||||
_rightRaycaster = new CVRPlayerRaycasterTransform(raycastTransformVrRight);
|
||||
_leftRaycaster = new CVRPlayerRaycasterTransform(raycastTransformVrLeft);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Desktop mode
|
||||
_rightHand = handDesktopRight;
|
||||
_leftHand = null;
|
||||
|
||||
// Desktop uses the mouse position for raycasting when unlocked
|
||||
Camera desktopCamera = PlayerSetup.Instance.desktopCam;
|
||||
_rightRaycaster = new CVRPlayerRaycasterMouse(raycastTransformDesktopRight, desktopCamera);
|
||||
_leftRaycaster = null;
|
||||
}
|
||||
|
||||
// Set the layer mask for raycasters
|
||||
if (_rightRaycaster != null)
|
||||
_rightRaycaster.SetLayerMask(interactionLayerMask);
|
||||
|
||||
if (_leftRaycaster != null)
|
||||
_leftRaycaster.SetLayerMask(interactionLayerMask);
|
||||
}
|
||||
|
||||
private static CVRPlayerRaycaster.RaycastFlags DetermineRaycastFlags(CVRPlayerHand hand)
|
||||
{
|
||||
// Default to all flags
|
||||
CVRPlayerRaycaster.RaycastFlags flags = CVRPlayerRaycaster.RaycastFlags.All;
|
||||
|
||||
// Check if hand is holding a pickup
|
||||
if (hand != null && hand.IsHoldingObject)
|
||||
{
|
||||
// When holding an object, only check for COHTML interaction
|
||||
flags = CVRPlayerRaycaster.RaycastFlags.CohtmlInteract;
|
||||
}
|
||||
|
||||
// Could add more conditional flag adjustments here based on the current mode
|
||||
// For example, in a teleport tool mode, you might only want world hits
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
using ABI_RC.Core.Base;
|
||||
using ABI_RC.Core.Player;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace NAK.SuperAwesomeMod.Components
|
||||
{
|
||||
public class CVRCanvasWrapper : MonoBehaviour
|
||||
{
|
||||
public bool IsInteractable = true;
|
||||
public float MaxInteractDistance = 10f;
|
||||
|
||||
private Canvas _canvas;
|
||||
private GraphicRaycaster _graphicsRaycaster;
|
||||
private static readonly List<RaycastResult> _raycastResults = new();
|
||||
private static readonly PointerEventData _pointerEventData = new(EventSystem.current);
|
||||
|
||||
private static Selectable _workingSelectable;
|
||||
private Camera _camera;
|
||||
private RectTransform _rectTransform;
|
||||
|
||||
#region Unity Events
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (!TryGetComponent(out _canvas)
|
||||
|| _canvas.renderMode != RenderMode.WorldSpace)
|
||||
{
|
||||
IsInteractable = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_rectTransform = _canvas.GetComponent<RectTransform>();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_graphicsRaycaster = _canvas.gameObject.AddComponent<GraphicRaycaster>();
|
||||
_camera = PlayerSetup.Instance.activeCam;
|
||||
_canvas.worldCamera = _camera;
|
||||
}
|
||||
|
||||
#endregion Unity Events
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public bool GetGraphicsHit(Ray worldRay, out RaycastResult result)
|
||||
{
|
||||
result = default;
|
||||
|
||||
if (!IsInteractable || _camera == null) return false;
|
||||
|
||||
// Get the plane of the canvas
|
||||
Plane canvasPlane = new(transform.forward, transform.position);
|
||||
|
||||
// Find where the ray intersects the canvas plane
|
||||
if (!canvasPlane.Raycast(worldRay, out float distance))
|
||||
return false;
|
||||
|
||||
// Get the world point of intersection
|
||||
Vector3 worldHitPoint = worldRay.origin + worldRay.direction * distance;
|
||||
|
||||
// Check if hit point is within max interaction distance
|
||||
if (Vector3.Distance(worldRay.origin, worldHitPoint) > MaxInteractDistance)
|
||||
return false;
|
||||
|
||||
// Check if hit point is within canvas bounds
|
||||
Vector3 localHitPoint = transform.InverseTransformPoint(worldHitPoint);
|
||||
Rect canvasRect = _rectTransform.rect;
|
||||
if (!canvasRect.Contains(new Vector2(localHitPoint.x, localHitPoint.y)))
|
||||
return false;
|
||||
|
||||
// Convert world hit point to screen space
|
||||
Vector2 screenPoint = _camera.WorldToScreenPoint(worldHitPoint);
|
||||
|
||||
// Update pointer event data
|
||||
_pointerEventData.position = screenPoint;
|
||||
_pointerEventData.delta = Vector2.zero;
|
||||
|
||||
// Clear previous results and perform raycast
|
||||
_raycastResults.Clear();
|
||||
_graphicsRaycaster.Raycast(_pointerEventData, _raycastResults);
|
||||
|
||||
// Early out if no hits
|
||||
if (_raycastResults.Count == 0)
|
||||
{
|
||||
//Debug.Log($"No hits on canvas {_canvas.name}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find first valid interactive UI element
|
||||
foreach (RaycastResult hit in _raycastResults)
|
||||
{
|
||||
if (!hit.isValid)
|
||||
{
|
||||
//Debug.Log($"Invalid hit on canvas {_canvas.name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the hit object has a Selectable component and is interactable
|
||||
GameObject hitObject = hit.gameObject;
|
||||
if (!hitObject.TryGetComponent(out _workingSelectable)
|
||||
|| !_workingSelectable.interactable)
|
||||
{
|
||||
//Debug.Log($"Non-interactable hit on canvas {_canvas.name} - {hitObject.name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
//Debug.Log($"Hit on canvas {_canvas.name} with {hitObject.name}");
|
||||
|
||||
result = hit;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
}
|
||||
}
|
|
@ -0,0 +1,385 @@
|
|||
using ABI_RC.Core.InteractionSystem.Base;
|
||||
using ABI_RC.Core.UI;
|
||||
using ABI.CCK.Components;
|
||||
using NAK.SuperAwesomeMod.Components;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
|
||||
{
|
||||
public abstract class CVRPlayerRaycaster
|
||||
{
|
||||
#region Enums
|
||||
|
||||
[Flags]
|
||||
public enum RaycastFlags
|
||||
{
|
||||
None = 0,
|
||||
TelepathicCandidate = 1 << 0,
|
||||
ProximityInteract = 1 << 1,
|
||||
RayInteract = 1 << 2,
|
||||
CohtmlInteract = 1 << 3,
|
||||
All = ~0
|
||||
}
|
||||
|
||||
#endregion Enums
|
||||
|
||||
#region Constants
|
||||
|
||||
private const float MAX_RAYCAST_DISTANCE = 100f; // Max distance you can raycast
|
||||
private const float RAYCAST_SPHERE_RADIUS = 0.1f; // Radius of the proximity sphere
|
||||
private const float TELEPATHIC_SPHERE_RADIUS = 0.3f; // Radius of the telepathic sphere
|
||||
private const float MAX_TELEPATHIC_DISTANCE = 20f; // Max distance for telepathic grab
|
||||
private const int MAX_RAYCAST_HITS = 100; // Hit buffer size, high due to triggers, which we use lots in CCK
|
||||
|
||||
// Global setting is Collide, but better to be explicit about what we need
|
||||
private const QueryTriggerInteraction _triggerInteraction = QueryTriggerInteraction.Collide;
|
||||
|
||||
// Layers that are reserved for other purposes or illegal to interact with
|
||||
private const int RESERVED_OR_ILLEGAL_LAYERS = (1 << CVRLayers.IgnoreRaycast)
|
||||
| (1 << CVRLayers.MirrorReflection)
|
||||
| (1 << CVRLayers.PlayerLocal);
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Static Fields
|
||||
|
||||
private static readonly RaycastHit[] _hits = new RaycastHit[MAX_RAYCAST_HITS];
|
||||
private static readonly Comparer<RaycastHit> _hitsComparer = Comparer<RaycastHit>.Create((hit1, hit2) =>
|
||||
{
|
||||
bool isUI1 = hit1.collider.gameObject.layer == CVRLayers.UIInternal;
|
||||
bool isUI2 = hit2.collider.gameObject.layer == CVRLayers.UIInternal;
|
||||
|
||||
// Prioritize UIInternal hits
|
||||
if (isUI1 && !isUI2) return -1; // UIInternal comes first
|
||||
if (!isUI1 && isUI2) return 1; // Non-UIInternal comes after
|
||||
|
||||
// If both are UIInternal or both are not, sort by distance
|
||||
return hit1.distance.CompareTo(hit2.distance);
|
||||
});
|
||||
|
||||
private static readonly LayerMask _telepathicLayerMask = 1 << CVRLayers.MirrorReflection;
|
||||
|
||||
// Working variables to avoid repeated allocations
|
||||
private static Collider _workingCollider;
|
||||
private static GameObject _workingGameObject;
|
||||
private static Pickupable _workingPickupable;
|
||||
private static Interactable _workingInteractable;
|
||||
private static Selectable _workingSelectable;
|
||||
private static ICanvasElement _workingCanvasElement;
|
||||
|
||||
#endregion Static Fields
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private LayerMask _layerMask; // Default to no layers so we know if we fucked up
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Constructor
|
||||
|
||||
protected CVRPlayerRaycaster(Transform rayOrigin) => _rayOrigin = rayOrigin;
|
||||
protected readonly Transform _rayOrigin;
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void SetLayerMask(LayerMask layerMask)
|
||||
{
|
||||
layerMask &= ~RESERVED_OR_ILLEGAL_LAYERS;
|
||||
_layerMask = layerMask;
|
||||
}
|
||||
|
||||
public CVRRaycastResult GetRaycastResults(RaycastFlags flags = RaycastFlags.All)
|
||||
{
|
||||
// Early out if we don't want to do anything
|
||||
if (flags == RaycastFlags.None) return default;
|
||||
|
||||
Ray ray = GetRayFromImpl();
|
||||
CVRRaycastResult result = new();
|
||||
|
||||
// Always check COHTML first
|
||||
if ((flags & RaycastFlags.CohtmlInteract) != 0
|
||||
&& TryProcessCohtmlHit(ray, ref result))
|
||||
return result;
|
||||
|
||||
// Check if there are pickups or interactables in immediate proximity
|
||||
if ((flags & RaycastFlags.ProximityInteract) != 0)
|
||||
{
|
||||
ProcessProximityHits(ray, ref result); // TODO: Offset origin to center of palm based on hand type
|
||||
if (result.isProximityHit)
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for regular raycast hits
|
||||
if ((flags & RaycastFlags.RayInteract) != 0)
|
||||
ProcessRaycastHits(ray, ref result);
|
||||
|
||||
// If we hit something, check for telepathic grab candidates at the hit point
|
||||
if ((flags & RaycastFlags.TelepathicCandidate) != 0 && result.hit.collider)
|
||||
ProcessTelepathicGrabCandidate(result.hit.point, ref result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static bool TryProcessCohtmlHit(Ray ray, ref CVRRaycastResult result)
|
||||
{
|
||||
CohtmlControlledView hitView = CohtmlViewInputHandler.Instance.RayToView(ray,
|
||||
out float _, out Vector2 hitCoords);
|
||||
if (hitView == null) return false;
|
||||
|
||||
result.hitCohtml = true;
|
||||
result.hitCohtmlView = hitView;
|
||||
result.hitCohtmlCoords = hitCoords;
|
||||
|
||||
// Manually check for pickups & interactables on the hit view (future-proofing for menu grabbing)
|
||||
if (hitView.TryGetComponent(out _workingInteractable)) result.hitInteractable = _workingInteractable;
|
||||
if (hitView.TryGetComponent(out _workingPickupable)) result.hitPickupable = _workingPickupable;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ProcessProximityHits(Ray ray, ref CVRRaycastResult result)
|
||||
{
|
||||
int proximityHits = Physics.SphereCastNonAlloc(
|
||||
ray.origin,
|
||||
RAYCAST_SPHERE_RADIUS,
|
||||
Vector3.up,
|
||||
_hits,
|
||||
0.001f,
|
||||
_layerMask,
|
||||
_triggerInteraction
|
||||
);
|
||||
|
||||
if (proximityHits <= 0) return;
|
||||
|
||||
Array.Sort(_hits, 0, proximityHits, _hitsComparer);
|
||||
|
||||
for (int i = 0; i < proximityHits; i++)
|
||||
{
|
||||
RaycastHit hit = _hits[i];
|
||||
_workingCollider = hit.collider;
|
||||
_workingGameObject = _workingCollider.gameObject;
|
||||
|
||||
// Skip things behind the ray origin
|
||||
if (Vector3.Dot(ray.direction, hit.point - ray.origin) < 0)
|
||||
continue;
|
||||
|
||||
// Check for interactables & pickupables in proximity
|
||||
if (!TryProcessInteractables(hit, ref result))
|
||||
continue;
|
||||
|
||||
result.isProximityHit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessRaycastHits(Ray ray, ref CVRRaycastResult result)
|
||||
{
|
||||
// Get all hits including triggers, sorted by UI Internal layer & distance
|
||||
int hitCount = Physics.RaycastNonAlloc(ray,
|
||||
_hits,
|
||||
MAX_RAYCAST_DISTANCE,
|
||||
_layerMask,
|
||||
_triggerInteraction);
|
||||
|
||||
if (hitCount <= 0) return;
|
||||
|
||||
Array.Sort(_hits, 0, hitCount, _hitsComparer);
|
||||
|
||||
for (int i = 0; i < hitCount; i++)
|
||||
{
|
||||
RaycastHit hit = _hits[i];
|
||||
_workingCollider = hit.collider;
|
||||
_workingGameObject = _workingCollider.gameObject;
|
||||
|
||||
// Special case where we only get the closest water hit position.
|
||||
// As the array is sorted by distance, we only need to check if we didn't hit water yet.
|
||||
if (!result.hitWater) TryProcessFluidVolume(hit, ref result);
|
||||
|
||||
// Check for hits in order of priority
|
||||
|
||||
if (TryProcessSelectable(hit, ref result))
|
||||
break; // Hit a Unity UI Selectable (Button, Slider, etc.)
|
||||
|
||||
if (TryProcessCanvasElement(hit, ref result))
|
||||
break; // Hit a Unity UI Canvas Element (ScrollRect, idk what else yet)
|
||||
|
||||
if (TryProcessInteractables(hit, ref result))
|
||||
break; // Hit an in-range Interactable or Pickup
|
||||
|
||||
if (TryProcessWorldHit(hit, ref result))
|
||||
break; // Hit a non-trigger collider (world, end of ray)
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessTelepathicGrabCandidate(Vector3 hitPoint, ref CVRRaycastResult result)
|
||||
{
|
||||
// If we already hit a pickupable, we don't need to check for telepathic grab candidates
|
||||
if (result.hitPickupable)
|
||||
{
|
||||
result.hasTelepathicGrabCandidate = true;
|
||||
result.telepathicPickupable = result.hitPickupable;
|
||||
result.telepathicGrabPoint = hitPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the hit distance is too far, don't bother checking for telepathic grab candidates
|
||||
if (Vector3.Distance(hitPoint, _rayOrigin.position) > MAX_TELEPATHIC_DISTANCE)
|
||||
return;
|
||||
|
||||
// Check for mirror reflection triggers in a sphere around the hit point
|
||||
int telepathicHits = Physics.SphereCastNonAlloc(
|
||||
hitPoint,
|
||||
TELEPATHIC_SPHERE_RADIUS,
|
||||
Vector3.up,
|
||||
_hits,
|
||||
0.001f,
|
||||
_telepathicLayerMask,
|
||||
QueryTriggerInteraction.Collide
|
||||
);
|
||||
|
||||
if (telepathicHits <= 0) return;
|
||||
|
||||
// Look for pickupable objects near our hit point
|
||||
var nearestDistance = float.MaxValue;
|
||||
for (int i = 0; i < telepathicHits; i++)
|
||||
{
|
||||
RaycastHit hit = _hits[i];
|
||||
_workingCollider = hit.collider;
|
||||
// _workingGameObject = _workingCollider.gameObject;
|
||||
|
||||
Transform parentTransform = _workingCollider.transform.parent;
|
||||
if (!parentTransform
|
||||
|| !parentTransform.TryGetComponent(out _workingPickupable)
|
||||
|| !_workingPickupable.CanPickup)
|
||||
continue;
|
||||
|
||||
var distance = Vector3.Distance(hitPoint, hit.point);
|
||||
if (!(distance < nearestDistance))
|
||||
continue;
|
||||
|
||||
result.hasTelepathicGrabCandidate = true;
|
||||
result.telepathicPickupable = _workingPickupable;
|
||||
result.telepathicGrabPoint = hitPoint;
|
||||
nearestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryProcessSelectable(RaycastHit hit, ref CVRRaycastResult result)
|
||||
{
|
||||
if (!_workingGameObject.TryGetComponent(out _workingSelectable))
|
||||
return false;
|
||||
|
||||
result.hitUnityUi = true;
|
||||
result.hitSelectable = _workingSelectable;
|
||||
result.hit = hit;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryProcessCanvasElement(RaycastHit hit, ref CVRRaycastResult result)
|
||||
{
|
||||
if (!_workingGameObject.TryGetComponent(out _workingCanvasElement))
|
||||
return false;
|
||||
|
||||
result.hitUnityUi = true;
|
||||
result.hitCanvasElement = _workingCanvasElement;
|
||||
result.hit = hit;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void TryProcessFluidVolume(RaycastHit hit, ref CVRRaycastResult result)
|
||||
{
|
||||
if (_workingGameObject.layer != CVRLayers.Water) return;
|
||||
|
||||
result.hitWater = true;
|
||||
result.waterHit = hit;
|
||||
}
|
||||
|
||||
private static bool TryProcessInteractables(RaycastHit hit, ref CVRRaycastResult result)
|
||||
{
|
||||
bool hitValidComponent = false;
|
||||
|
||||
if (_workingGameObject.TryGetComponent(out _workingInteractable)
|
||||
&& _workingInteractable.CanInteract
|
||||
&& IsCVRInteractableWithinRange(_workingInteractable, hit))
|
||||
{
|
||||
result.hitInteractable = _workingInteractable;
|
||||
hitValidComponent = true;
|
||||
}
|
||||
if (_workingGameObject.TryGetComponent(out _workingPickupable)
|
||||
&& _workingPickupable.CanPickup
|
||||
&& IsCVRPickupableWithinRange(_workingPickupable, hit))
|
||||
{
|
||||
result.hitPickupable = _workingPickupable;
|
||||
hitValidComponent = true;
|
||||
}
|
||||
|
||||
if (!hitValidComponent)
|
||||
return false;
|
||||
|
||||
result.hit = hit;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryProcessWorldHit(RaycastHit hit, ref CVRRaycastResult result)
|
||||
{
|
||||
if (_workingCollider.isTrigger)
|
||||
return false;
|
||||
|
||||
result.hitWorld = true;
|
||||
result.hit = hit;
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected abstract Ray GetRayFromImpl();
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Utility Because Original Methods Are Broken
|
||||
|
||||
private static bool IsCVRInteractableWithinRange(Interactable interactable, RaycastHit hit)
|
||||
{
|
||||
if (interactable is not CVRInteractable cvrInteractable)
|
||||
return true;
|
||||
|
||||
foreach (CVRInteractableAction action in cvrInteractable.actions)
|
||||
{
|
||||
if (action.actionType
|
||||
is not (CVRInteractableAction.ActionRegister.OnInteractDown
|
||||
or CVRInteractableAction.ActionRegister.OnInteractUp
|
||||
or CVRInteractableAction.ActionRegister.OnInputDown
|
||||
or CVRInteractableAction.ActionRegister.OnInputUp))
|
||||
continue;
|
||||
|
||||
float maxDistance = action.floatVal;
|
||||
if (Mathf.Approximately(maxDistance, 0f)
|
||||
|| hit.distance <= maxDistance)
|
||||
return true; // Interactable is within range
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsCVRPickupableWithinRange(Pickupable pickupable, RaycastHit hit)
|
||||
{
|
||||
return hit.distance <= pickupable.MaxGrabDistance;
|
||||
}
|
||||
|
||||
private static bool IsCVRCanvasWrapperWithinRange(CVRCanvasWrapper canvasWrapper, RaycastHit hit)
|
||||
{
|
||||
return hit.distance <= canvasWrapper.MaxInteractDistance;
|
||||
}
|
||||
|
||||
#endregion Utility Because Original Methods Are Broken
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
|
||||
{
|
||||
public class CVRPlayerRaycasterMouse : CVRPlayerRaycaster
|
||||
{
|
||||
private readonly Camera _camera;
|
||||
public CVRPlayerRaycasterMouse(Transform rayOrigin, Camera camera) : base(rayOrigin) { _camera = camera; }
|
||||
protected override Ray GetRayFromImpl() => Cursor.lockState == CursorLockMode.Locked
|
||||
? new Ray(_camera.transform.position, _camera.transform.forward)
|
||||
: _camera.ScreenPointToRay(Input.mousePosition);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
|
||||
{
|
||||
public class CVRPlayerRaycasterTransform : CVRPlayerRaycaster
|
||||
{
|
||||
public CVRPlayerRaycasterTransform(Transform rayOrigin) : base(rayOrigin) { }
|
||||
protected override Ray GetRayFromImpl() => new(_rayOrigin.position, _rayOrigin.forward);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
using ABI_RC.Core.InteractionSystem.Base;
|
||||
using ABI_RC.Core.UI;
|
||||
using NAK.SuperAwesomeMod.Components;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
|
||||
{
|
||||
public struct CVRRaycastResult
|
||||
{
|
||||
// Hit flags
|
||||
public bool hitWorld; // Any non-specific collision
|
||||
public bool hitWater; // Hit a fluid volume
|
||||
public bool hitCohtml; // Specifically hit a COHTML view (Main/Quick Menu)
|
||||
public bool isProximityHit; // Hit was from proximity sphere check
|
||||
public bool hitUnityUi; // Hit a canvas
|
||||
|
||||
// Main raycast hit info
|
||||
public RaycastHit hit;
|
||||
public RaycastHit? waterHit; // Only valid if hitWater is true
|
||||
|
||||
// Specific hit components
|
||||
public Pickupable hitPickupable;
|
||||
public Interactable hitInteractable;
|
||||
public Selectable hitSelectable;
|
||||
public ICanvasElement hitCanvasElement;
|
||||
|
||||
// COHTML specific results
|
||||
public CohtmlControlledView hitCohtmlView;
|
||||
public Vector2 hitCohtmlCoords;
|
||||
|
||||
// Telepathic pickup
|
||||
public bool hasTelepathicGrabCandidate;
|
||||
public Pickupable telepathicPickupable;
|
||||
public Vector3 telepathicGrabPoint;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace ABI_RC.Core.Player.Interaction.RaycastImpl
|
||||
{
|
||||
public class CVRRaycastDebugManager : MonoBehaviour
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
private static CVRRaycastDebugManager _instance;
|
||||
public static CVRRaycastDebugManager Instance => _instance;
|
||||
|
||||
public static void Initialize(Camera camera)
|
||||
{
|
||||
if (_instance != null) return;
|
||||
|
||||
var go = new GameObject("RaycastDebugManager");
|
||||
_instance = go.AddComponent<CVRRaycastDebugManager>();
|
||||
DontDestroyOnLoad(go);
|
||||
|
||||
_instance.Setup(camera);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
|
||||
private CVRPlayerRaycasterMouse _raycaster;
|
||||
private CVRRaycastResult _lastResult;
|
||||
private System.Diagnostics.Stopwatch _stopwatch;
|
||||
|
||||
// Performance tracking
|
||||
private const int ROLLING_AVERAGE_SAMPLES = 60; // 1 second at 60fps
|
||||
private readonly float[] _timeHistory = new float[ROLLING_AVERAGE_SAMPLES];
|
||||
private int _currentSampleIndex;
|
||||
private float _lastRaycastTime;
|
||||
private float _minRaycastTime = float.MaxValue;
|
||||
private float _maxRaycastTime;
|
||||
private float _rollingAverageTime;
|
||||
private bool _historyFilled;
|
||||
|
||||
private const int DEBUG_PANEL_WIDTH = 300;
|
||||
private const int DEBUG_PANEL_MARGIN = 10;
|
||||
private const float MOUSE_CURSOR_SIZE = 24f;
|
||||
private const float CURSOR_OFFSET = MOUSE_CURSOR_SIZE / 2f;
|
||||
|
||||
private GUIStyle _labelStyle;
|
||||
private GUIStyle _headerStyle;
|
||||
private GUIStyle _boxStyle;
|
||||
|
||||
private static readonly Color32 TIMING_COLOR = new(255, 255, 150, 255); // Yellow
|
||||
private static readonly Color32 COHTML_COLOR = new(150, 255, 150, 255); // Green
|
||||
private static readonly Color32 UI_COLOR = new(150, 150, 255, 255); // Blue
|
||||
private static readonly Color32 UNITY_UI_COLOR = new(255, 200, 150, 255); // Orange
|
||||
private static readonly Color32 INTERACT_COLOR = new(255, 150, 150, 255); // Red
|
||||
private static readonly Color32 WATER_COLOR = new(150, 255, 255, 255); // Cyan
|
||||
private static readonly Color32 TELEPATHIC_COLOR = new(255, 150, 255, 255);// Purple
|
||||
private static readonly Color32 SELECTABLE_COLOR = new(200, 150, 255, 255);// Light Purple
|
||||
|
||||
#endregion
|
||||
|
||||
#region Setup
|
||||
|
||||
private void Setup(Camera camera)
|
||||
{
|
||||
_raycaster = new CVRPlayerRaycasterMouse(transform, camera);
|
||||
_raycaster.SetLayerMask(Physics.DefaultRaycastLayers);
|
||||
_stopwatch = new System.Diagnostics.Stopwatch();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MonoBehaviour
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_stopwatch.Restart();
|
||||
_lastResult = _raycaster.GetRaycastResults();
|
||||
_stopwatch.Stop();
|
||||
|
||||
UpdatePerformanceMetrics();
|
||||
}
|
||||
|
||||
private void UpdatePerformanceMetrics()
|
||||
{
|
||||
// Calculate current frame time
|
||||
_lastRaycastTime = _stopwatch.ElapsedTicks / (float)System.TimeSpan.TicksPerMillisecond;
|
||||
|
||||
// Update min/max
|
||||
_minRaycastTime = Mathf.Min(_minRaycastTime, _lastRaycastTime);
|
||||
_maxRaycastTime = Mathf.Max(_maxRaycastTime, _lastRaycastTime);
|
||||
|
||||
// Update rolling average
|
||||
_timeHistory[_currentSampleIndex] = _lastRaycastTime;
|
||||
|
||||
// Calculate rolling average based on filled samples
|
||||
float sum = 0f;
|
||||
int sampleCount = _historyFilled ? ROLLING_AVERAGE_SAMPLES : _currentSampleIndex + 1;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
sum += _timeHistory[i];
|
||||
|
||||
_rollingAverageTime = sum / sampleCount;
|
||||
|
||||
// Update index for next frame
|
||||
_currentSampleIndex = (_currentSampleIndex + 1) % ROLLING_AVERAGE_SAMPLES;
|
||||
if (_currentSampleIndex == 0)
|
||||
_historyFilled = true;
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
InitializeStyles();
|
||||
DrawDebugPanel();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Drawing Methods
|
||||
|
||||
private void InitializeStyles()
|
||||
{
|
||||
if (_labelStyle != null) return;
|
||||
|
||||
_labelStyle = new GUIStyle
|
||||
{
|
||||
normal = { textColor = Color.white },
|
||||
fontSize = 12,
|
||||
padding = new RectOffset(5, 5, 2, 2),
|
||||
margin = new RectOffset(5, 5, 0, 0)
|
||||
};
|
||||
|
||||
_headerStyle = new GUIStyle
|
||||
{
|
||||
normal = { textColor = Color.white },
|
||||
fontSize = 14,
|
||||
fontStyle = FontStyle.Bold,
|
||||
padding = new RectOffset(5, 5, 5, 5),
|
||||
margin = new RectOffset(5, 5, 5, 5)
|
||||
};
|
||||
|
||||
_boxStyle = new GUIStyle(GUI.skin.box)
|
||||
{
|
||||
padding = new RectOffset(10, 10, 5, 5),
|
||||
margin = new RectOffset(5, 5, 5, 5)
|
||||
};
|
||||
}
|
||||
|
||||
private void DrawDebugPanel()
|
||||
{
|
||||
var rect = new Rect(
|
||||
Screen.width - DEBUG_PANEL_WIDTH - DEBUG_PANEL_MARGIN,
|
||||
DEBUG_PANEL_MARGIN,
|
||||
DEBUG_PANEL_WIDTH,
|
||||
Screen.height - (DEBUG_PANEL_MARGIN * 2)
|
||||
);
|
||||
|
||||
GUI.Box(rect, "");
|
||||
GUILayout.BeginArea(rect);
|
||||
|
||||
GUI.backgroundColor = Color.black;
|
||||
GUILayout.Label("Raycast Debug Info", _headerStyle);
|
||||
|
||||
DrawPerformanceSection();
|
||||
DrawCohtmlSection();
|
||||
DrawUnityUISection();
|
||||
DrawSelectableSection();
|
||||
DrawInteractionSection();
|
||||
DrawWaterSection();
|
||||
DrawTelepathicSection();
|
||||
DrawWorldHitSection();
|
||||
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
|
||||
private void DrawPerformanceSection()
|
||||
{
|
||||
GUI.backgroundColor = TIMING_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("Performance", _headerStyle);
|
||||
DrawLabel("Last Raycast", $"{_lastRaycastTime:F3} ms");
|
||||
DrawLabel("Average (1s)", $"{_rollingAverageTime:F3} ms");
|
||||
DrawLabel("Min", $"{_minRaycastTime:F3} ms");
|
||||
DrawLabel("Max", $"{_maxRaycastTime:F3} ms");
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawCohtmlSection()
|
||||
{
|
||||
if (!_lastResult.hitCohtml) return;
|
||||
|
||||
GUI.backgroundColor = COHTML_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("COHTML Hit", _headerStyle);
|
||||
DrawLabel("View", _lastResult.hitCohtmlView.name);
|
||||
DrawLabel("Coords", _lastResult.hitCohtmlCoords.ToString());
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawUnityUISection()
|
||||
{
|
||||
if (!_lastResult.hitUnityUi || _lastResult.hitCanvasElement == null) return;
|
||||
|
||||
GUI.backgroundColor = UNITY_UI_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("Unity UI Hit", _headerStyle);
|
||||
|
||||
var canvasElement = _lastResult.hitCanvasElement;
|
||||
var gameObject = canvasElement as MonoBehaviour;
|
||||
|
||||
DrawLabel("Canvas Element", gameObject != null ? gameObject.name : "Unknown");
|
||||
DrawLabel("Element Type", canvasElement.GetType().Name);
|
||||
|
||||
if (gameObject != null)
|
||||
{
|
||||
DrawLabel("GameObject", gameObject.gameObject.name);
|
||||
|
||||
if (gameObject.transform.parent != null)
|
||||
DrawLabel("Parent", gameObject.transform.parent.name);
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawSelectableSection()
|
||||
{
|
||||
if (_lastResult.hitSelectable == null) return;
|
||||
|
||||
GUI.backgroundColor = SELECTABLE_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("UI Selectable", _headerStyle);
|
||||
DrawLabel("Selectable", _lastResult.hitSelectable.name);
|
||||
DrawLabel("Selectable Type", _lastResult.hitSelectable.GetType().Name);
|
||||
DrawLabel("Is Interactable", _lastResult.hitSelectable.interactable.ToString());
|
||||
DrawLabel("Navigation Mode", _lastResult.hitSelectable.navigation.mode.ToString());
|
||||
|
||||
if (_lastResult.hitSelectable is Toggle toggle)
|
||||
DrawLabel("Toggle State", toggle.isOn.ToString());
|
||||
else if (_lastResult.hitSelectable is Slider slider)
|
||||
DrawLabel("Slider Value", slider.value.ToString("F2"));
|
||||
else if (_lastResult.hitSelectable is Scrollbar scrollbar)
|
||||
DrawLabel("Scrollbar Value", scrollbar.value.ToString("F2"));
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawInteractionSection()
|
||||
{
|
||||
if (!_lastResult.hitPickupable && !_lastResult.hitInteractable) return;
|
||||
|
||||
GUI.backgroundColor = INTERACT_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("Interaction", _headerStyle);
|
||||
if (_lastResult.hitPickupable)
|
||||
DrawLabel("Pickupable", _lastResult.hitPickupable.name);
|
||||
if (_lastResult.hitInteractable)
|
||||
DrawLabel("Interactable", _lastResult.hitInteractable.name);
|
||||
DrawLabel("Is Proximity", _lastResult.isProximityHit.ToString());
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawWaterSection()
|
||||
{
|
||||
if (!_lastResult.hitWater || !_lastResult.waterHit.HasValue) return;
|
||||
|
||||
GUI.backgroundColor = WATER_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("Water Surface", _headerStyle);
|
||||
DrawLabel("Hit Point", _lastResult.waterHit.Value.point.ToString("F2"));
|
||||
DrawLabel("Surface Normal", _lastResult.waterHit.Value.normal.ToString("F2"));
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawTelepathicSection()
|
||||
{
|
||||
if (!_lastResult.hasTelepathicGrabCandidate) return;
|
||||
|
||||
GUI.backgroundColor = TELEPATHIC_COLOR;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("Telepathic Grab", _headerStyle);
|
||||
DrawLabel("Target", _lastResult.telepathicPickupable.name);
|
||||
DrawLabel("Grab Point", _lastResult.telepathicGrabPoint.ToString("F2"));
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawWorldHitSection()
|
||||
{
|
||||
if (_lastResult.hitCohtml ||
|
||||
_lastResult.hitPickupable ||
|
||||
_lastResult.hitInteractable ||
|
||||
_lastResult.hitUnityUi ||
|
||||
!_lastResult.hitWorld ||
|
||||
_lastResult.hit.collider == null) return;
|
||||
|
||||
GUI.backgroundColor = Color.grey;
|
||||
GUILayout.BeginVertical(_boxStyle);
|
||||
GUILayout.Label("World Hit", _headerStyle);
|
||||
DrawLabel("Object", _lastResult.hit.collider.name);
|
||||
DrawLabel("Distance", _lastResult.hit.distance.ToString("F2"));
|
||||
DrawLabel("Point", _lastResult.hit.point.ToString("F2"));
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void DrawLabel(string label, string value)
|
||||
{
|
||||
GUILayout.Label($"{label}: {value}", _labelStyle);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue