154 lines
5.8 KiB
C#
154 lines
5.8 KiB
C#
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 Vector2 rotationMinMax = new Vector2(-30f, 30f);
|
|
private LineRenderer lineRenderer;
|
|
|
|
public Observer<float> 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<LineRenderer>();
|
|
|
|
Value.AddListener(UpdateArc);
|
|
Value.AddListener(UpdateKnobPosition);
|
|
|
|
// Set default rotation to middle if outside
|
|
if (Value.Value < rotationMinMax.x || Value.Value > rotationMinMax.y)
|
|
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
|
|
Vector2 mouseMovement = new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"));
|
|
|
|
Vector3 start = Quaternion.AngleAxis(rotationMinMax.x, normal) * tangent;
|
|
Vector3 end = Quaternion.AngleAxis(rotationMinMax.y, normal) * tangent;
|
|
|
|
// Use calculus to calculate differentialkvotient tangent at angle
|
|
Vector3 v1 = SamplePointOnArcAngle(start, end, visualRadius, Value.Value);
|
|
Vector3 v2 = SamplePointOnArcAngle(start, end, visualRadius, Value.Value - 1f);
|
|
Vector3 arcTangent3D = (v2 - v1).normalized;
|
|
Vector2 arcTangent = orientation == ArcOrientation.HORIZONTAL ? new Vector2(arcTangent3D.x, arcTangent3D.z) : new Vector2(arcTangent3D.x, arcTangent3D.y);
|
|
|
|
// i have no idea why but somehow this needs to be here idk
|
|
// if (orientation == ArcOrientation.VERTICAL)
|
|
// arcTangent.x *= -1f;
|
|
|
|
Debug.DrawRay(knob.transform.position, arcTangent3D, Color.yellow, 5f);
|
|
Vector3 screenRay = Camera.main.ScreenPointToRay(Input.mousePosition).direction;
|
|
Vector3 barreltToAim = Quaternion.AngleAxis(Value.Value, normal) * tangent;
|
|
float dot = Vector3.Dot(transform.forward, Camera.main.transform.forward) * -1f;
|
|
|
|
float sign = orientation == ArcOrientation.HORIZONTAL ? 1f : -1f;
|
|
mouseMovement.y *= sign * Mathf.Sin(dot);
|
|
float delta = Vector2.Dot(arcTangent, mouseMovement) * knobSensitiviy * Mathf.Sign(dot);
|
|
|
|
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 Vector3 SamplePointOnArcAngle(Vector3 startPoint, Vector3 endPoint, float radius, float angle)
|
|
{
|
|
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;
|
|
}
|
|
}
|