3DTD/Assets/Scripts/Utilities/EditableArc.cs

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;
}
}