2025-12-11 13:14:43 +01:00

217 lines
6.6 KiB
C#

// ThrottleLever.cs
// Zweck: XR-Hebel entlang definierter Rail bewegen, Output 0..1 als throttle. Stabil gegen XRI/Physik.
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Interactables;
using UnityEngine.XR.Interaction.Toolkit.Interactors;
[DefaultExecutionOrder(1000)] // nach den meisten Systemen laufen
[RequireComponent(typeof(XRGrabInteractable))]
public class ThrottleLever : MonoBehaviour
{
[SerializeField] private bool forceStartAtZero = true;
[Header("References")]
public Transform handle; // visueller Hebel
public Transform startPoint; // Rail-Anfang
public Transform endPoint; // Rail-Ende
public Transform space; // Referenzrahmen der Rail
[SerializeField] private Transform shipRoot;
[Header("Output")]
[Range(0f, 1f)] public float throttle = 0f;
[Header("Diagnostics")]
public bool selfTestMode = false;
public float selfTestSpeed = 0.5f;
private XRGrabInteractable grab;
private bool isGrabbed;
private Transform follow; // Attach des Interactors
private float cachedT;
private void Awake()
{
grab = GetComponent<XRGrabInteractable>();
if (!handle || !startPoint || !endPoint)
{
Debug.LogError("[ThrottleLever] handle/start/end zuweisen.");
enabled = false; return;
}
if (!space)
space = startPoint && startPoint.parent ? startPoint.parent : handle.parent;
// sicherstellen, dass space mit dem Schiff mitfährt
if (shipRoot && space && !space.IsChildOf(shipRoot))
{
Vector3 wp = space.position;
Quaternion wq = space.rotation;
space.SetParent(shipRoot, worldPositionStays: true);
space.position = wp;
space.rotation = wq;
Debug.LogWarning("[ThrottleLever] 'space' unter shipRoot angehängt (fährt nun mit).");
}
// Rail-Validierung
var a = space.InverseTransformPoint(startPoint.position);
var b = space.InverseTransformPoint(endPoint.position);
if ((b - a).sqrMagnitude < 1e-6f)
{
Debug.LogError("[ThrottleLever] startPoint und endPoint liegen aufeinander.");
enabled = false; return;
}
// Physik am Handle (statisch, wir bewegen ihn selbst)
var rb = handle.GetComponent<Rigidbody>() ?? handle.gameObject.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.isKinematic = true;
if (!handle.GetComponent<Collider>()) handle.gameObject.AddComponent<BoxCollider>();
// XRI soll unsere Pose NICHT direkt treiben
grab.trackPosition = false;
grab.trackRotation = false;
grab.throwOnDetach = false;
grab.selectEntered.AddListener(OnGrab);
grab.selectExited.AddListener(OnRelease);
Application.onBeforeRender += OnBeforeRenderPose;
}
private void OnDestroy()
{
if (grab)
{
grab.selectEntered.RemoveListener(OnGrab);
grab.selectExited.RemoveListener(OnRelease);
}
Application.onBeforeRender -= OnBeforeRenderPose;
}
private void Start()
{
if (forceStartAtZero)
{
cachedT = 0f; throttle = 0f;
SetHandleByT(cachedT);
}
else
{
ProjectHandleOntoRailFromCurrent();
}
}
private void Update()
{
if (!space) return;
Vector3 a = space.InverseTransformPoint(startPoint.position);
Vector3 b = space.InverseTransformPoint(endPoint.position);
Vector3 ab = b - a;
float abLen2 = ab.sqrMagnitude;
if (abLen2 < 1e-6f) return;
if (selfTestMode)
{
cachedT = 0.5f + 0.5f * Mathf.Sin(Time.time * selfTestSpeed * Mathf.PI * 2f);
}
else if (isGrabbed && follow)
{
Vector3 pLocal = space.InverseTransformPoint(follow.position);
cachedT = Mathf.Clamp01(Vector3.Dot(pLocal - a, ab) / abLen2);
}
else
{
Vector3 hLocal = space.InverseTransformPoint(handle.position);
cachedT = Mathf.Clamp01(Vector3.Dot(hLocal - a, ab) / abLen2);
}
throttle = cachedT;
// Vorläufig schreiben…
SetHandleByT(cachedT);
// …und später noch einmal absichern
}
private void LateUpdate() => SetHandleByT(cachedT);
private void OnBeforeRenderPose() => SetHandleByT(cachedT);
private void SetHandleByT(float t)
{
Vector3 a = space.InverseTransformPoint(startPoint.position);
Vector3 b = space.InverseTransformPoint(endPoint.position);
Vector3 targetLocal = Vector3.Lerp(a, b, Mathf.Clamp01(t));
handle.position = space.TransformPoint(targetLocal);
}
public void SetNormalized(float t)
{
t = Mathf.Clamp01(t);
cachedT = throttle = t;
SetHandleByT(t);
}
public void ResetToZero() => SetNormalized(0f);
private void OnGrab(SelectEnterEventArgs args)
{
isGrabbed = true;
if (args.interactorObject is XRBaseInteractor xrInteractor)
follow = xrInteractor.attachTransform;
else
follow = args.interactorObject.GetAttachTransform(args.interactableObject);
if (!follow)
Debug.LogWarning("[ThrottleLever] attachTransform nicht gefunden.");
if (space && handle.parent != space)
handle.SetParent(space, true);
grab.trackPosition = false;
grab.trackRotation = false;
}
private void OnRelease(SelectExitEventArgs args)
{
isGrabbed = false;
follow = null;
ProjectHandleOntoRailFromCurrent();
if (space && handle.parent != space)
handle.SetParent(space, true);
}
private void ProjectHandleOntoRailFromCurrent()
{
Vector3 a = space.InverseTransformPoint(startPoint.position);
Vector3 b = space.InverseTransformPoint(endPoint.position);
Vector3 ab = b - a; float abLen2 = ab.sqrMagnitude;
if (abLen2 < 1e-6f) return;
Vector3 hLocal = space.InverseTransformPoint(handle.position);
float t = Mathf.Clamp01(Vector3.Dot(hLocal - a, ab) / abLen2);
throttle = cachedT = t;
SetHandleByT(t);
}
public void SetGrabEnabled(bool enabled)
{
if (!grab) grab = GetComponent<XRGrabInteractable>();
if (grab) grab.enabled = enabled;
}
#if UNITY_EDITOR
private void OnDrawGizmosSelected()
{
if (!startPoint || !endPoint) return;
Gizmos.color = Color.yellow;
Gizmos.DrawLine(startPoint.position, endPoint.position);
}
#endif
}