You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
564 lines
22 KiB
564 lines
22 KiB
using System;
|
|
using System.Linq;
|
|
|
|
using UnityEngine;
|
|
using UnityEngine.VFX;
|
|
|
|
using UnityEditor.VFX.UI;
|
|
using Type = System.Type;
|
|
|
|
namespace UnityEditor.VFX
|
|
{
|
|
class VFXGizmoAttribute : System.Attribute
|
|
{
|
|
public VFXGizmoAttribute(Type type)
|
|
{
|
|
this.type = type;
|
|
}
|
|
|
|
public readonly Type type;
|
|
}
|
|
|
|
abstract class VFXGizmo
|
|
{
|
|
public interface IProperty<T>
|
|
{
|
|
bool isEditable { get; }
|
|
|
|
void SetValue(T value);
|
|
}
|
|
|
|
public interface IContext
|
|
{
|
|
IProperty<T> RegisterProperty<T>(string memberPath);
|
|
}
|
|
public abstract void RegisterEditableMembers(IContext context);
|
|
public abstract void CallDrawGizmo(object value);
|
|
|
|
public abstract Bounds CallGetGizmoBounds(object obj);
|
|
|
|
protected const float handleSize = 0.1f;
|
|
protected const float arcHandleSizeMultiplier = 1.25f;
|
|
|
|
public VFXSpace currentSpace { get; set; }
|
|
public VisualEffect component { get; set; }
|
|
public int currentHashCode { get; set; }
|
|
|
|
private static readonly int s_HandleColorID = Shader.PropertyToID("_HandleColor");
|
|
private static readonly int s_HandleSizeID = Shader.PropertyToID("_HandleSize");
|
|
private static readonly int s_HandleZTestID = Shader.PropertyToID("_HandleZTest");
|
|
private static readonly int s_ObjectToWorldID = Shader.PropertyToID("_ObjectToWorld");
|
|
|
|
static Matrix4x4 StartCapDrawRevertingScale(Vector3 position, Quaternion rotation, float size)
|
|
{
|
|
//See : https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Handles.cs#L956
|
|
|
|
var lossyScale = Handles.matrix.lossyScale;
|
|
var invLossyScale = new Vector3(1.0f / lossyScale.x, 1.0f / lossyScale.y, 1.0f / lossyScale.z);
|
|
|
|
var mat = Handles.matrix;
|
|
//Remove scale from the current global matrix
|
|
mat *= Matrix4x4.TRS(Vector3.zero, Quaternion.identity, invLossyScale);
|
|
//Correct position according to previous scale
|
|
var correctPosition = new Vector3(position.x * lossyScale.x, position.y * lossyScale.y, position.z * lossyScale.z);
|
|
mat *= Matrix4x4.TRS(correctPosition, rotation, Vector3.one);
|
|
|
|
Shader.SetGlobalMatrix(s_ObjectToWorldID, mat);
|
|
Shader.SetGlobalColor(s_HandleColorID, Handles.color);
|
|
Shader.SetGlobalFloat(s_HandleSizeID, size);
|
|
|
|
HandleUtility.handleMaterial.SetFloat(s_HandleZTestID, (float)Handles.zTest);
|
|
HandleUtility.handleMaterial.SetPass(0);
|
|
|
|
return mat;
|
|
}
|
|
|
|
protected static void CustomCubeHandleCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
|
|
{
|
|
if (!IsValidTRSMatrix(Handles.matrix))
|
|
return;
|
|
|
|
switch (eventType)
|
|
{
|
|
case EventType.Layout:
|
|
case EventType.MouseMove:
|
|
HandleUtility.AddControl(controlID, HandleUtility.DistanceToCube(position, rotation, size));
|
|
break;
|
|
case EventType.Repaint:
|
|
Graphics.DrawMeshNow(Handles.cubeMesh, StartCapDrawRevertingScale(position, rotation, size));
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected static void CustomConeHandleCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
|
|
{
|
|
if (!IsValidTRSMatrix(Handles.matrix))
|
|
return;
|
|
|
|
switch (eventType)
|
|
{
|
|
case EventType.Layout:
|
|
case EventType.MouseMove:
|
|
HandleUtility.AddControl(controlID, HandleUtility.DistanceToCone(position, rotation, size));
|
|
break;
|
|
case EventType.Repaint:
|
|
Graphics.DrawMeshNow(Handles.coneMesh, StartCapDrawRevertingScale(position, rotation, size));
|
|
break;
|
|
}
|
|
}
|
|
|
|
protected static void CustomAngleHandleCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
|
|
{
|
|
if (!IsValidTRSMatrix(Handles.matrix))
|
|
return;
|
|
|
|
switch (eventType)
|
|
{
|
|
case EventType.Layout:
|
|
case EventType.MouseMove:
|
|
HandleUtility.AddControl(controlID, HandleUtility.DistanceToCube(position, rotation, size * arcHandleSizeMultiplier));
|
|
break;
|
|
case (EventType.Repaint):
|
|
{
|
|
var worldPosition = Handles.matrix.MultiplyPoint3x4(position);
|
|
var normal = worldPosition - Handles.matrix.GetPosition();
|
|
var tangent = Handles.matrix.MultiplyVector(Quaternion.AngleAxis(90f, Vector3.up) * position);
|
|
|
|
var crossLength = Vector3.Cross(normal, tangent).sqrMagnitude;
|
|
if (!float.IsFinite(crossLength) || crossLength < 1e-5f)
|
|
break;
|
|
|
|
rotation = Quaternion.LookRotation(tangent, normal);
|
|
var matrix = Matrix4x4.TRS(worldPosition, rotation, (Vector3.one + Vector3.forward * arcHandleSizeMultiplier));
|
|
|
|
using (new Handles.DrawingScope(matrix))
|
|
{
|
|
Handles.CylinderHandleCap(controlID, Vector3.zero, Quaternion.identity, size, eventType);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static bool IsValidSlider(Vector3 position, Vector3 direction, float size)
|
|
{
|
|
if (!float.IsFinite(position.sqrMagnitude))
|
|
return false;
|
|
if (!float.IsFinite(direction.sqrMagnitude))
|
|
return false;
|
|
if (!float.IsFinite(size))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
protected static Vector3 CustomSlider(int controlID, Vector3 position, Vector3 direction, float size)
|
|
{
|
|
return IsValidSlider(position, direction, size)
|
|
? Handles.Slider(controlID, position, direction, size, CustomCubeHandleCap, 0)
|
|
: position;
|
|
}
|
|
|
|
protected static Vector3 CustomSlider(Vector3 position, Vector3 direction, float size)
|
|
{
|
|
var controlID = GUIUtility.GetControlID(Handles.s_SliderHash, FocusType.Passive);
|
|
return CustomSlider(controlID, position, direction, size);
|
|
}
|
|
|
|
private static bool IsValidTRSMatrix(Matrix4x4 matrix)
|
|
{
|
|
if (!matrix.ValidTRS())
|
|
return false;
|
|
|
|
for (int i = 0; i < 16; ++i)
|
|
if (!float.IsFinite(matrix[i]))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private Quaternion GetHandleRotation(Quaternion localRotation)
|
|
{
|
|
if (Tools.pivotRotation == PivotRotation.Local)
|
|
return localRotation;
|
|
return Handles.matrix.inverse.rotation;
|
|
}
|
|
|
|
[Flags]
|
|
enum ForceTransformGizmo
|
|
{
|
|
None,
|
|
Position,
|
|
Orientation,
|
|
Scale
|
|
}
|
|
|
|
public bool RotationOnlyGizmo(Vector3 center, Vector3 angles, IProperty<Vector3> anglesProperty)
|
|
{
|
|
return TransformGizmo(center, angles, Vector3.one, null, anglesProperty, null, ForceTransformGizmo.Position);
|
|
}
|
|
|
|
public bool PositionOnlyGizmo(Vector3 center, IProperty<Vector3> centerProperty)
|
|
{
|
|
return TransformGizmo(center, Vector3.zero, Vector3.one, new PropertyWrapperSimple(centerProperty), null, null, ForceTransformGizmo.Position);
|
|
}
|
|
|
|
public bool PositionOnlyGizmo(Position center, IProperty<Position> centerProperty)
|
|
{
|
|
return TransformGizmo(center, Vector3.zero, Vector3.one, new PropertyWrapperPosition(centerProperty), null, null, ForceTransformGizmo.Position);
|
|
}
|
|
|
|
public bool TransformGizmo(Vector3 center, Vector3 angles, Vector3 scale, IProperty<Vector3> centerProperty, IProperty<Vector3> anglesProperty, IProperty<Vector3> scaleProperty)
|
|
{
|
|
return TransformGizmo(center, angles, scale, new PropertyWrapperSimple(centerProperty), anglesProperty, scaleProperty, ForceTransformGizmo.None);
|
|
}
|
|
|
|
interface PropertyWrapperVector3
|
|
{
|
|
bool isEditable { get; }
|
|
void SetValue(Vector3 value);
|
|
}
|
|
|
|
readonly struct PropertyWrapperSimple : PropertyWrapperVector3
|
|
{
|
|
private readonly IProperty<Vector3> m_Property;
|
|
|
|
public PropertyWrapperSimple(IProperty<Vector3> property)
|
|
{
|
|
m_Property = property;
|
|
}
|
|
|
|
public bool isEditable => m_Property.isEditable;
|
|
|
|
public void SetValue(Vector3 value)
|
|
{
|
|
m_Property.SetValue(value);
|
|
}
|
|
}
|
|
|
|
readonly struct PropertyWrapperPosition : PropertyWrapperVector3
|
|
{
|
|
private readonly IProperty<Position> m_Property;
|
|
|
|
public PropertyWrapperPosition(IProperty<Position> property)
|
|
{
|
|
m_Property = property;
|
|
}
|
|
|
|
public bool isEditable => m_Property.isEditable;
|
|
|
|
public void SetValue(Vector3 value)
|
|
{
|
|
m_Property.SetValue(value);
|
|
}
|
|
}
|
|
|
|
|
|
private bool TransformGizmo(Vector3 center, Vector3 angles, Vector3 scale, PropertyWrapperVector3 centerProperty, IProperty<Vector3> anglesProperty, IProperty<Vector3> scaleProperty, ForceTransformGizmo forceTransformGizmo)
|
|
{
|
|
if (!float.IsFinite(center.sqrMagnitude)
|
|
|| !float.IsFinite(scale.sqrMagnitude)
|
|
|| !float.IsFinite(angles.sqrMagnitude))
|
|
return false;
|
|
|
|
var parentTransform = Handles.matrix;
|
|
|
|
var rotation = Quaternion.Euler(angles);
|
|
var currentTransform = Matrix4x4.TRS(center, rotation, scale);
|
|
var worldTransform = parentTransform * currentTransform;
|
|
|
|
center = worldTransform.GetPosition();
|
|
rotation = worldTransform.rotation;
|
|
|
|
using (new Handles.DrawingScope(Matrix4x4.identity))
|
|
{
|
|
if (centerProperty is { isEditable: true } && PositionGizmo(ref center, rotation, forceTransformGizmo.HasFlag(ForceTransformGizmo.Position)))
|
|
{
|
|
var inverse = parentTransform.inverse;
|
|
center = inverse.MultiplyPoint(center);
|
|
centerProperty.SetValue(center);
|
|
return true;
|
|
}
|
|
|
|
if (anglesProperty is { isEditable: true } && RotationGizmo(center, ref rotation, forceTransformGizmo.HasFlag(ForceTransformGizmo.Orientation)))
|
|
{
|
|
var inverse = parentTransform.inverse;
|
|
rotation = inverse.rotation * rotation;
|
|
angles = rotation.eulerAngles;
|
|
anglesProperty.SetValue(angles);
|
|
return true;
|
|
}
|
|
|
|
if (scaleProperty is { isEditable: true } && ScaleGizmo(center, rotation, ref scale, forceTransformGizmo.HasFlag(ForceTransformGizmo.Scale)))
|
|
{
|
|
scaleProperty.SetValue(scale);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Vector3 m_InitialNormal;
|
|
public bool NormalGizmo(Vector3 position, ref Vector3 normal, bool always)
|
|
{
|
|
if (Event.current.type == EventType.MouseDown)
|
|
{
|
|
m_InitialNormal = normal;
|
|
}
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
|
|
var parentTransform = Handles.matrix;
|
|
var currentTransform = Matrix4x4.TRS(position, Quaternion.identity, Vector3.one);
|
|
var worldTransform = parentTransform * currentTransform;
|
|
|
|
using (new Handles.DrawingScope(Matrix4x4.identity))
|
|
{
|
|
var delta = worldTransform.rotation;
|
|
RotationGizmo(worldTransform.GetPosition(), ref delta, always);
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
var inverse = parentTransform.inverse;
|
|
delta = inverse.rotation * delta;
|
|
normal = delta * m_InitialNormal;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool PositionGizmo(ref Vector3 position, Quaternion rotation, bool always)
|
|
{
|
|
if (always || Tools.current == Tool.Move || Tools.current == Tool.Transform || Tools.current == Tool.None)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
position = Handles.PositionHandle(position, GetHandleRotation(rotation));
|
|
return EditorGUI.EndChangeCheck();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static Color ToActiveColorSpace(Color color)
|
|
{
|
|
return (QualitySettings.activeColorSpace == ColorSpace.Linear) ? color.linear : color;
|
|
}
|
|
|
|
static readonly Color[] s_AxisColor = new Color[] { Handles.xAxisColor, Handles.yAxisColor, Handles.zAxisColor, Handles.centerColor };
|
|
static Vector3[] s_AxisVector = { Vector3.right, Vector3.up, Vector3.forward, Vector3.zero };
|
|
static int[] s_AxisId = { "VFX_RotateAxis_X".GetHashCode(), "VFX_RotateAxis_Y".GetHashCode(), "VFX_RotateAxis_Z".GetHashCode(), "VFX_RotateAxis_Camera".GetHashCode() };
|
|
static Color s_DisabledHandleColor = new Color(0.5f, 0.5f, 0.5f, 0.5f);
|
|
private Quaternion CustomRotationHandle(Quaternion rotation, Vector3 position, bool onlyCameraAxis = false)
|
|
{
|
|
//Equivalent of Rotation Handle but with explicit id & *without* free rotate.
|
|
var evt = Event.current;
|
|
var isRepaint = evt.type == EventType.Repaint;
|
|
var camForward = Handles.inverseMatrix.MultiplyVector(Camera.current != null ? Camera.current.transform.forward : Vector3.forward);
|
|
var size = HandleUtility.GetHandleSize(position);
|
|
var isHot = s_AxisId.Any(id => GetCombinedHashCode(id) == GUIUtility.hotControl);
|
|
|
|
var previousColor = Handles.color;
|
|
for (var i = onlyCameraAxis ? 3 : 0; i < 4; ++i)
|
|
{
|
|
Handles.color = ToActiveColorSpace(s_AxisColor[i]);
|
|
var axisDir = i == 3 ? camForward : rotation * s_AxisVector[i];
|
|
rotation = Handles.Disc(GetCombinedHashCode(s_AxisId[i]), rotation, position, axisDir, size, true, EditorSnapSettings.rotate);
|
|
}
|
|
|
|
if (isHot && evt.type == EventType.Repaint)
|
|
{
|
|
Handles.color = ToActiveColorSpace(s_DisabledHandleColor);
|
|
Handles.DrawWireDisc(position, camForward, size, Handles.lineThickness);
|
|
}
|
|
|
|
Handles.color = previousColor;
|
|
return rotation;
|
|
}
|
|
|
|
static int s_FreeRotationID = "VFX_FreeRotation_Id".GetHashCode();
|
|
private Quaternion CustomFreeRotationHandle(Quaternion rotation, Vector3 position)
|
|
{
|
|
var previousColor = Handles.color;
|
|
Handles.color = ToActiveColorSpace(s_DisabledHandleColor);
|
|
var newRotation = Handles.FreeRotateHandle(GetCombinedHashCode(s_FreeRotationID), rotation, position, HandleUtility.GetHandleSize(position));
|
|
Handles.color = previousColor;
|
|
return newRotation;
|
|
}
|
|
|
|
bool m_IsRotating = false;
|
|
Quaternion m_StartRotation = Quaternion.identity;
|
|
int m_HotControlRotation = -1;
|
|
|
|
private bool RotationGizmo(Vector3 position, ref Quaternion rotation, bool always)
|
|
{
|
|
if (always || Tools.current == Tool.Rotate || Tools.current == Tool.Transform || Tools.current == Tool.None)
|
|
{
|
|
bool usingFreeRotation = GUIUtility.hotControl == GetCombinedHashCode(s_FreeRotationID);
|
|
var handleRotation = GetHandleRotation(rotation);
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
var rotationFromFreeHandle = CustomFreeRotationHandle(rotation, position);
|
|
var rotationFromAxis = CustomRotationHandle(handleRotation, position);
|
|
var newRotation = usingFreeRotation ? rotationFromFreeHandle : rotationFromAxis;
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
if (!m_IsRotating)
|
|
{
|
|
//Save first rotation state to avoid rotation accumulation while dragging in global space.
|
|
m_StartRotation = rotation;
|
|
m_HotControlRotation = GUIUtility.hotControl;
|
|
}
|
|
|
|
if (!usingFreeRotation /* Free rotation are always in local */ && Tools.pivotRotation == PivotRotation.Global)
|
|
rotation = newRotation * Handles.matrix.rotation * m_StartRotation;
|
|
else
|
|
rotation = newRotation;
|
|
|
|
m_IsRotating = true;
|
|
return true;
|
|
}
|
|
|
|
if (GUIUtility.hotControl != m_HotControlRotation)
|
|
{
|
|
//If hotControl has changed, the dragging has been terminated.
|
|
m_StartRotation = Quaternion.identity;
|
|
m_IsRotating = false;
|
|
m_HotControlRotation = -1;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private bool ScaleGizmo(Vector3 position, Quaternion rotation, ref Vector3 scale, bool always)
|
|
{
|
|
if (always || Tools.current == Tool.Scale || Tools.current == Tool.Transform || Tools.current == Tool.None)
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
scale = Handles.ScaleHandle(scale, position, rotation);
|
|
return EditorGUI.EndChangeCheck();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private static readonly int s_ArcGizmoName = "VFX_ArcGizmo".GetHashCode();
|
|
|
|
public void ArcGizmo(Vector3 center, float radius, float degArc, IProperty<float> arcProperty, Quaternion rotation)
|
|
{
|
|
// Arc handle control
|
|
if (arcProperty.isEditable)
|
|
{
|
|
using (new Handles.DrawingScope(Handles.matrix * Matrix4x4.Translate(center) * Matrix4x4.Rotate(rotation)))
|
|
{
|
|
EditorGUI.BeginChangeCheck();
|
|
Vector3 arcHandlePosition = Quaternion.AngleAxis(degArc, Vector3.up) * Vector3.forward * radius;
|
|
|
|
if (Mathf.Abs(radius) > 1e-5f && float.IsFinite(arcHandlePosition.sqrMagnitude))
|
|
{
|
|
arcHandlePosition = Handles.Slider2D(
|
|
GetCombinedHashCode(s_ArcGizmoName),
|
|
arcHandlePosition,
|
|
Vector3.up,
|
|
Vector3.forward,
|
|
Vector3.right,
|
|
handleSize * arcHandleSizeMultiplier * HandleUtility.GetHandleSize(arcHandlePosition),
|
|
CustomAngleHandleCap,
|
|
Vector2.zero
|
|
);
|
|
}
|
|
|
|
if (EditorGUI.EndChangeCheck())
|
|
{
|
|
float newArc = Vector3.Angle(Vector3.forward, arcHandlePosition) * Mathf.Sign(Vector3.Dot(Vector3.right, arcHandlePosition));
|
|
degArc += Mathf.DeltaAngle(degArc, newArc);
|
|
degArc = Mathf.Repeat(degArc, 360.0f);
|
|
arcProperty.SetValue(degArc * Mathf.Deg2Rad);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual GizmoError error => GizmoError.None;
|
|
|
|
public int GetCombinedHashCode(int hashCode) => HashCode.Combine(currentHashCode, hashCode);
|
|
}
|
|
|
|
abstract class VFXGizmo<T> : VFXGizmo
|
|
{
|
|
public override void CallDrawGizmo(object value)
|
|
{
|
|
if (value is T)
|
|
OnDrawGizmo((T)value);
|
|
}
|
|
|
|
public override Bounds CallGetGizmoBounds(object value)
|
|
{
|
|
if (value is T)
|
|
{
|
|
return OnGetGizmoBounds((T)value);
|
|
}
|
|
|
|
return new Bounds();
|
|
}
|
|
|
|
public abstract void OnDrawGizmo(T value);
|
|
|
|
public abstract Bounds OnGetGizmoBounds(T value);
|
|
}
|
|
abstract class VFXSpaceableGizmo<T> : VFXGizmo<T>
|
|
{
|
|
public override void OnDrawGizmo(T value)
|
|
{
|
|
if (error != GizmoError.None)
|
|
return;
|
|
|
|
var oldMatrix = Handles.matrix;
|
|
if (currentSpace == VFXSpace.Local)
|
|
{
|
|
Handles.matrix = component.transform.localToWorldMatrix;
|
|
}
|
|
else
|
|
{
|
|
Handles.matrix = Matrix4x4.identity;
|
|
}
|
|
|
|
OnDrawSpacedGizmo(value);
|
|
Handles.matrix = oldMatrix;
|
|
}
|
|
|
|
public override Bounds OnGetGizmoBounds(T value)
|
|
{
|
|
Bounds bounds = OnGetSpacedGizmoBounds(value);
|
|
if (currentSpace == VFXSpace.Local)
|
|
{
|
|
if (component == null)
|
|
return new Bounds();
|
|
|
|
return UnityEditorInternal.InternalEditorUtility.TransformBounds(bounds, component.transform);
|
|
}
|
|
|
|
return bounds;
|
|
}
|
|
|
|
public override GizmoError error
|
|
{
|
|
get
|
|
{
|
|
var currentError = base.error;
|
|
var needsComponent = currentSpace == VFXSpace.Local;
|
|
|
|
if (needsComponent && component == null)
|
|
currentError |= GizmoError.NeedComponent;
|
|
|
|
if (currentSpace == VFXSpace.None)
|
|
currentError |= GizmoError.NeedExplicitSpace;
|
|
|
|
return currentError;
|
|
}
|
|
}
|
|
|
|
public abstract void OnDrawSpacedGizmo(T value);
|
|
|
|
public abstract Bounds OnGetSpacedGizmoBounds(T value);
|
|
}
|
|
}
|