using System; using UnityEngine; using UnityEngine.Assertions; public enum ArcOrientation { HORIZONTAL, VERTICAL, } [RequireComponent(typeof(LineRenderer))] public class EditableArc : MonoBehaviour { [SerializeField] private ArcOrientation orientation = ArcOrientation.HORIZONTAL; [SerializeField, Range(5, 50)] private int samples = 15; [SerializeField] private float visualRadius = 1f; [SerializeField] private SliderKnob knob; [SerializeField] private float knobSensitiviy = 1f; [SerializeField] private string moveKnobAxisName = "Mouse X"; [SerializeField] private Vector2 rotationMinMax = new Vector2(-30f, 30f); private LineRenderer lineRenderer; public Observer Value { get; private set; } = new(0); public Vector2 RotationMinMax => rotationMinMax; public Vector3 normal => orientation == ArcOrientation.HORIZONTAL ? transform.up : transform.forward; public Vector3 tangent => orientation == ArcOrientation.HORIZONTAL ? transform.forward : transform.up; public Vector3 ToKnobVector => Quaternion.AngleAxis(Value.Value, normal) * tangent; private void Awake() { lineRenderer = GetComponent(); Value.AddListener(UpdateArc); Value.AddListener(UpdateKnobPosition); // Set default rotation to average between min max // Value.Value = (rotationMinMax.x + rotationMinMax.y) / 2f; Assert.IsNotNull(knob, $"No knob on {this}"); knob.OnDrag += PointerDraggedOnKnob; // Initial UpdateKnobPosition(Value); } // Moves the knob to the right position based on the angle on the arc private void UpdateKnobPosition(float angle) { Vector3 dir = Quaternion.AngleAxis(angle, normal) * tangent; Vector3 knobPos = transform.position + dir; knob.transform.position = knobPos; } private void OnDestroy() { Value.RemoveListener(UpdateArc); Value.RemoveListener(UpdateKnobPosition); knob.OnDrag -= PointerDraggedOnKnob; } private void PointerDraggedOnKnob(SliderKnob knob) { // Amount mouse have moved since last frame - ie. mouse velocity float mouseMovement = Input.GetAxis(moveKnobAxisName); // TODO: figure out this based on camera orientation float sign = -1f; float delta = mouseMovement * knobSensitiviy * sign; float newAngle = ClampAngle(Value.Value + delta, rotationMinMax.x, rotationMinMax.y); Value.Value = newAngle; } private void Update() { UpdateArc(Value.Value); } private void UpdateArc(float rotation) { float angle = rotationMinMax.y - rotationMinMax.x; float v = (Mathf.PI - Value * Mathf.Deg2Rad) / 2f; Vector3 start = Quaternion.AngleAxis(rotationMinMax.x, normal) * tangent; Vector3 end = Quaternion.AngleAxis(rotationMinMax.y, normal) * tangent; // Sample LineRenderer points lineRenderer.positionCount = samples + 1; Vector3[] positions = new Vector3[samples + 1]; float stepSize = 1f / samples; float t = 0; for (int i = 0; i <= samples; i++) { positions[i] = SamplePointOnArc(start, end, visualRadius, t); // Debug.Log($"t: = {t}, pos: {positions[i]}"); t += stepSize; } lineRenderer.SetPositions(positions); // Set looop if (angle >= 360) lineRenderer.loop = true; else lineRenderer.loop = false; } public Vector3 SamplePointOnArc(Vector3 startPoint, Vector3 endPoint, float radius, float t) { float angle = Mathf.Lerp(rotationMinMax.x, rotationMinMax.y, t); Vector3 dir = Quaternion.AngleAxis(angle, normal) * tangent; return transform.position + dir.normalized * radius; } public static float ClampAngle(float current, float min, float max) { float dtAngle = Mathf.Abs(((min - max) + 180) % 360 - 180); float hdtAngle = dtAngle * 0.5f; float midAngle = min + hdtAngle; float offset = Mathf.Abs(Mathf.DeltaAngle(current, midAngle)) - hdtAngle; if (offset > 0) current = Mathf.MoveTowardsAngle(current, midAngle, offset); return current; } }