2026-05-07 10:14:44 +02:00

428 lines
12 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using ToriaCommon.Datas.Types;
using UnityEngine;
using Color = UnityEngine.Color;
using Material = UnityEngine.Material;
namespace Wing.Gizmos
{
/*
* NOTE:
* This code comes from an old Gizmos package, it has been modified a long time ago (legacy code).
* For improve the system and clean the code I have to modified it again.
* Now it's possible to specify around which axis rotate.
*
* With all these changes, this script has become dependent on ToriaCommon. It is no longer a package script.
*/
public class GizmosRotate : MonoBehaviour
{
public delegate void RotateEventHandler(float dx, float dy, float dz);
public event RotateEventHandler OnRotate;
public event Action OnRotateEnd;
const float interactionAngle = 20; //angle on each side of an arc where it is interactible to track it
public Camera RenderCamera;
public float RotateSpeed = 1;
[SerializeField] float radius = .1f; //scale of the sphere
public bool EnableFreeRotate = false;
[Header("Colors")]
[SerializeField] Color xColor = new(1, 0, 0, 0.5f);
[SerializeField] Color yColor = new(0, 1, 0, 0.5f);
[SerializeField] Color zColor = new(0, 0, 1, 0.5f);
[SerializeField] Color selectColor = new(1, 0.92f, 0.016f, 0.5f);
private Vector3 mTotal = Vector3.zero;
readonly private List<int> verticesCount = new();
private float m_fTheta = 0.1f; //arc precision (mesh generation)
private GameObject m_pSphere = null;
private GameObject m_pMeshGo = null;
private Mesh m_pMesh;
private int m_iLayer = 0;
private Matrix4x4[] m_arrMats = null;
private AxisXYZ trackedAxis = AxisXYZ.None;
private AxisXYZ allowedAxis = AxisXYZ.All;
private bool rotationChanged = false;
private Vector3 lastMousePosition = Vector3.zero;
void Awake()
{
if (RenderCamera == null)
{
RenderCamera = Camera.main;
}
m_iLayer = LayerMask.NameToLayer(Settings.GizmosLayerName);
m_arrMats = new Matrix4x4[]
{
Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(0, 0, 90), Vector3.one),
Matrix4x4.identity,
Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(90, 0, 0), Vector3.one)
};
CreateSphere();
CreateXYZCircles();
}
#region Init
public void Initialize(float radius, Quaternion rotation, AxisXYZ allowedAxis, bool enableFreeRotation = false)
{
//Destroy previous XYZ circles
Destroy(m_pSphere.transform.GetChild(0).gameObject);
m_pSphere.transform.rotation = rotation;
EnableFreeRotate = enableFreeRotation;
this.radius = radius;
this.allowedAxis = allowedAxis;
CreateXYZCircles();
}
private void CreateSphere()
{
m_pSphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
m_pSphere.transform.SetParent(transform);
m_pSphere.transform.localPosition = Vector3.zero;
RefreshScale();
m_pSphere.GetComponent<SphereCollider>().isTrigger = true;
m_pSphere.layer = m_iLayer;
MeshRenderer render = m_pSphere.GetComponent<MeshRenderer>();
render.material = new Material(Shader.Find("TransparentShader"));
render.material.color = new Color(0.5f, 0.5f, 0.5f, 0.2f);
//m_pSphere.GetComponent<MeshRenderer>().enabled = false;
}
private void CreateXYZCircles()
{
m_pMesh = new Mesh();
m_pMesh.MarkDynamic();
int verticesCount;
//x
if (IsAxisAllowed(AxisXYZ.X))
{
verticesCount = CreateArc(xColor, m_pMesh, m_arrMats[0]);
this.verticesCount.Add(verticesCount);
}
//y
if (IsAxisAllowed(AxisXYZ.Y))
{
verticesCount = CreateArc(yColor, m_pMesh, m_arrMats[1]);
this.verticesCount.Add(verticesCount);
}
//z
if (IsAxisAllowed(AxisXYZ.Z))
{
verticesCount = CreateArc(zColor, m_pMesh, m_arrMats[2]);
this.verticesCount.Add(verticesCount);
}
//Mesh Generation
m_pMesh.subMeshCount = 1;
m_pMesh.SetIndices(SequentialTriangles(m_pMesh.vertices.Length), MeshTopology.Lines, 0);
m_pMeshGo = new GameObject("Rotate Axis");
m_pMeshGo.transform.SetParent(m_pSphere.transform, false);
m_pMeshGo.layer = m_iLayer;
m_pMeshGo.AddComponent<MeshFilter>().mesh = m_pMesh;
m_pMeshGo.AddComponent<MeshRenderer>().material = new Material(Shader.Find("RotateShader"));
//reset circles mesh transform
m_pMeshGo.transform.localPosition = Vector3.zero;
m_pMeshGo.transform.localRotation = Quaternion.identity;
m_pMeshGo.transform.localScale = Vector3.one / 2;
}
private int CreateArc(Color color, Mesh mesh, Matrix4x4 mat, float angle = 2 * Mathf.PI, bool close = false)
{
m_fTheta = Mathf.Max(0.0001f, m_fTheta);
int count = mesh.vertexCount;
List<Vector3> vertices = new List<Vector3>(mesh.vertices);
List<Color> colors = new List<Color>(mesh.colors);
// draw circle
Vector3 beginPoint = mat * Vector3.zero;
Vector3 firstPoint = mat * Vector3.zero;
for (float theta = 0; theta < angle; theta += m_fTheta)
{
float x = Mathf.Cos(theta);
float z = Mathf.Sin(theta);
Vector3 endPoint = mat * new Vector3(x, 0, z);
if (theta == 0 && !close)
{
firstPoint = endPoint;
}
else
{
vertices.Add(beginPoint);
vertices.Add(endPoint);
colors.Add(color);
colors.Add(color);
}
beginPoint = endPoint;
}
// draw last line
vertices.Add(firstPoint);
vertices.Add(beginPoint);
colors.Add(color);
colors.Add(color);
mesh.SetVertices(vertices);
mesh.SetColors(colors);
return vertices.Count - count;
}
private static int[] SequentialTriangles(int len)
{
int[] array = new int[len];
for (int i = 0; i < len; i++)
{
array[i] = i;
}
return array;
}
#endregion
public Quaternion GetRotation()
{
return m_pSphere.transform.rotation;
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
StartTracking();
if (Input.GetMouseButtonUp(0))
StopTracking();
RotateAroundTrackedAxis();
RefreshScale();
}
private void StartTracking()
{
lastMousePosition = Input.mousePosition;
Ray ray = RenderCamera.ScreenPointToRay(lastMousePosition);
//Try select closest axis and start tracking it
if (Physics.Raycast(ray, out RaycastHit hit, 10000, 1 << m_iLayer, QueryTriggerInteraction.Collide))
{
AxisXYZ axis = FindClosetAxis(hit.point);
SelectAxis(axis);
rotationChanged = false;
}
}
private void StopTracking()
{
SelectAxis(AxisXYZ.None);
if (rotationChanged)
{
StartCoroutine(DelayRelease());
rotationChanged = false;
}
}
private void RefreshScale()
{
m_pSphere.transform.localScale = (Vector3.Distance(RenderCamera.transform.position, m_pSphere.transform.position) / 4) * radius * Vector3.one;
}
private void RotateAroundTrackedAxis()
{
if (!IsAxisAllowed(trackedAxis))
return;
Vector3 delta = TrackBall(Input.mousePosition);
//Snap rotation
//NOTE: the snap isn't perfect at the moment, so let it disable.
//It snap axis by axis so when you touch all the axis, the final result isn't correctly snapped.
//To test the snap, you must activate it beforehand with this line: SnapFactory.Instance.Get(ESnapType.Rotate).Enable = true;
Vector3 total = Vector3.zero;
ISnap snap = SnapFactory.Instance.Get(ESnapType.Rotate);
if (snap.Snap(mTotal + delta, ref total))
{
delta = total - mTotal;
}
mTotal += delta;
mTotal.x %= 360;
mTotal.y %= 360;
mTotal.z %= 360;
//////////////
if (delta == Vector3.zero)
return;
m_pSphere.transform.Rotate(delta);
rotationChanged = true;
OnRotate?.Invoke(delta.x, delta.y, delta.z);
}
private IEnumerator DelayRelease()
{
yield return new WaitForEndOfFrame();
// OnRotateEnd(m_eCurrentAxis, GetRotate());
OnRotateEnd?.Invoke();
}
private Vector3 ComputeRotateDir(Vector3 offset, AxisXYZ type)
{
if (offset.x + offset.y == 0)
{
return Vector3.zero;
}
Vector3 startPos = m_pSphere.transform.position;
Vector3 start = Camera.main.WorldToScreenPoint(startPos);
Vector3 axis;
Vector3 end;
if (type == AxisXYZ.X)
{
end = Camera.main.WorldToScreenPoint(startPos + m_pSphere.transform.right * 2);
axis = Vector3.right;
}
else if (type == AxisXYZ.Y)
{
end = Camera.main.WorldToScreenPoint(startPos + m_pSphere.transform.up * 2);
axis = Vector3.up;
}
else
{
end = Camera.main.WorldToScreenPoint(startPos + m_pSphere.transform.forward * 2);
axis = Vector3.forward;
}
Vector3 axisDir = end - start;
axisDir.Normalize();
axisDir = Vector3.Cross(Vector3.forward, axisDir);
float moveLen = axisDir.x * offset.x + axisDir.y * offset.y;
float mx = moveLen * axis.x;
float my = moveLen * axis.y;
float mz = moveLen * axis.z;
return new Vector3(mx, my, mz);
}
private Vector3 TrackBall(Vector3 screenPosition)
{
Vector3 offset = (screenPosition - lastMousePosition) * RotateSpeed;
lastMousePosition = screenPosition;
Vector3 angle = Vector3.zero;
if (trackedAxis == AxisXYZ.All)
{
Quaternion q = Quaternion.AngleAxis(offset.y, m_pSphere.transform.InverseTransformVector(RenderCamera.transform.right));
q *= Quaternion.AngleAxis(-offset.x, m_pSphere.transform.InverseTransformVector(RenderCamera.transform.up));
angle += q.eulerAngles;
}
else
{
angle += ComputeRotateDir(offset, trackedAxis);
}
return angle;
}
private AxisXYZ FindClosetAxis(Vector3 hitPoint)
{
GetDeltaAngles(hitPoint, out float xAngle, out float yAngle, out float zAngle);
(AxisXYZ axis, float angle) closest = (AxisXYZ.None, 0);
TrySetClosest(AxisXYZ.X, xAngle);
TrySetClosest(AxisXYZ.Y, yAngle);
TrySetClosest(AxisXYZ.Z, zAngle);
if (EnableFreeRotate && closest.axis == AxisXYZ.None && IsAxisAllowed(AxisXYZ.All))
closest.axis = AxisXYZ.All;
return closest.axis;
void TrySetClosest(AxisXYZ axis, float angle)
{
if (IsAxisAllowed(axis) && angle < interactionAngle && (closest.axis == AxisXYZ.None || angle < closest.angle))
closest = (axis, angle);
}
}
private void GetDeltaAngles(Vector3 position, out float xAngle, out float yAngle, out float zAngle)
{
Vector3 dir = m_pSphere.transform.InverseTransformPoint(position).normalized;
xAngle = Mathf.Acos(dir.x) * Mathf.Rad2Deg;
yAngle = Mathf.Asin(dir.y) * Mathf.Rad2Deg;
zAngle = Mathf.Acos(dir.z) * Mathf.Rad2Deg;
xAngle = GetDelta(xAngle, true);
yAngle = GetDelta(yAngle, false);
zAngle = GetDelta(zAngle, true);
}
private float GetDelta(float angle, bool verticalAxis)
{
//0° is the angle for a horizontal axis
//90° is the angle for a vertical axis
//See trigonometry circle
return Mathf.Abs(angle - (verticalAxis ? 90 : 0));
}
private void SelectAxis(AxisXYZ toSelect)
{
if (trackedAxis == toSelect)
return;
trackedAxis = toSelect;
List<Color> colors = new();
int index = 0;
SetArcColor(AxisXYZ.X, xColor);
SetArcColor(AxisXYZ.Y, yColor);
SetArcColor(AxisXYZ.Z, zColor);
m_pMesh.SetColors(colors);
void SetArcColor(AxisXYZ axis, Color axisColor)
{
if (!IsAxisAllowed(axis))
return;
int count = verticesCount[index++];
for (int i = 0; i < count; i++)
{
colors.Add(trackedAxis == axis || trackedAxis == AxisXYZ.All ? selectColor : axisColor);
}
}
}
bool IsAxisAllowed(AxisXYZ axis) => axis != AxisXYZ.None && (allowedAxis == AxisXYZ.All || allowedAxis == axis);
}
}