using System; using Unity.Mathematics; using UnityEngine.Splines; namespace UnityEditor.Splines { /// /// An interface that represents a selectable spline element. A selectable spline element can be a knot or a tangent. /// `ISelectableElement` is used by the selection to get information about the spline, the knot, and the positions of the spline elements. /// public interface ISelectableElement : IEquatable { /// /// The that describes the spline. /// SplineInfo SplineInfo { get; } /// /// The index of the knot in the spline. If the spline element is a tangent, this is the index of the knot /// that the tangent is attached to. /// int KnotIndex { get; } /// /// The position of the spline element in local space. /// float3 LocalPosition { get; set; } /// /// The position of the spline element in world space. /// float3 Position { get; set; } /// /// Checks if the element is valid. For example, checks if the spline is not null and the index is valid. /// /// Returns true if all fields from the element have valid values. bool IsValid(); } /// /// Implements the interface. SelectableKnot is used by the /// spline selection and handles to use tools and handles to manipulate spline elements. /// public struct SelectableKnot : ISelectableElement, IEquatable { /// public SplineInfo SplineInfo { get; } /// public int KnotIndex { get; } /// /// Transforms a knot from local space to world space (Read Only). /// internal float4x4 LocalToWorld => SplineInfo.LocalToWorld; /// public float3 Position { get => math.transform(LocalToWorld, LocalPosition); set => LocalPosition = math.transform(math.inverse(LocalToWorld), value); } /// public float3 LocalPosition { get => SplineInfo.Spline[KnotIndex].Position; set { var knot = SplineInfo.Spline[KnotIndex]; knot.Position = value; SplineInfo.Spline[KnotIndex] = knot; } } /// public bool IsValid() { return SplineInfo.Spline != null && KnotIndex >= 0 && KnotIndex < SplineInfo.Spline.Count; } /// /// The rotation of the spline element in world space. /// public quaternion Rotation { get => math.mul(new quaternion(LocalToWorld), LocalRotation); set => LocalRotation = math.mul(math.inverse(new quaternion(LocalToWorld)), value); } /// /// The rotation of the spline element in local space. /// public quaternion LocalRotation { get => SplineInfo.Spline[KnotIndex].Rotation; set { var knot = SplineInfo.Spline[KnotIndex]; knot.Rotation = math.normalize(value); SplineInfo.Spline[KnotIndex] = knot; } } /// /// The associated with a knot. /// public TangentMode Mode { get => SplineInfo.Spline.GetTangentMode(KnotIndex); set { SplineInfo.Spline.SetTangentMode(KnotIndex, value); SplineSelectionUtility.ValidateTangentSelection(this); } } /// /// The tension associated with a knot. `Tension` is only used if the tangent mode is Auto Smooth. /// public float Tension { get => SplineInfo.Spline.GetAutoSmoothTension(KnotIndex); set => SplineInfo.Spline.SetAutoSmoothTension(KnotIndex, value); } /// /// Sets the tangent mode of the knot. /// /// The to apply to the knot. /// The tangent to use as the main tangent when the tangent is set to the Mirrored or Continuous tangent mode. /// The main tangent is not modified, but the other tangent attached to that knot is modified to adopt the new tangent mode. public void SetTangentMode(TangentMode mode, BezierTangent main) { var spline = SplineInfo.Spline; spline.SetTangentMode(KnotIndex, mode, main); SplineSelectionUtility.ValidateTangentSelection(this); } /// /// The In tangent associated with the knot. The In tangent defines the curvature of the segment that enters the knot. /// public SelectableTangent TangentIn => new SelectableTangent(SplineInfo, KnotIndex, BezierTangent.In); /// /// The Out tangent associated with the knot. The Out tangent defines the curvature of the segment that exits the knot. /// public SelectableTangent TangentOut => new SelectableTangent(SplineInfo, KnotIndex, BezierTangent.Out); /// /// Creates a from a SplineInfo and a knot index. /// /// The associated with the tangent. /// The index of the knot. public SelectableKnot(SplineInfo info, int index) { this.SplineInfo = info; this.KnotIndex = index; } /// /// Creates the BezierKnot representation associated with a SelectableKnot. /// /// Set to true for the BezierKnot to be in world space, or set to false for the Bezierknot to be in local space. /// The associated with the knot. public BezierKnot GetBezierKnot(bool worldSpace) { return worldSpace ? SplineInfo.Spline[KnotIndex].Transform(LocalToWorld) : SplineInfo.Spline[KnotIndex]; } /// /// Checks if two instances of `SplineElement` are equal. /// /// The to compare against. /// /// Returns true when is a and the values of each instance are identical. /// public bool Equals(ISelectableElement other) { if (other is SelectableKnot knot) return Equals(knot); return false; } /// /// Checks if two instances of SelectableKnot are equal. /// /// The to compare against. /// /// Returns true if the values of each instance of `SelectableKnot` are identical. /// public bool Equals(SelectableKnot other) { return Equals(SplineInfo.Spline, other.SplineInfo.Spline) && KnotIndex == other.KnotIndex; } /// /// Checks if two instances of an object are equal. /// /// The object to compare against. /// /// Returns true if is a and its values are identical to the original instance. /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is SelectableKnot other && Equals(other); } /// /// Gets a hash code for this knot. /// /// /// A hash code for the . /// public override int GetHashCode() { return HashCode.Combine(SplineInfo.Spline, KnotIndex); } } /// /// Represents a struct that implements the interface. Spline selection uses /// `SelectableTangent` and handles to easily manipulate spline elements with tools and handles. /// public struct SelectableTangent : ISelectableElement, IEquatable { /// public SplineInfo SplineInfo { get; } /// public int KnotIndex { get; } /// /// The index of the tangent. A value of 0 represents an In tangent. A value of 1 represents an Out tangent. /// public int TangentIndex { get; } /// /// The knot associated with this tangent. /// public SelectableKnot Owner => new SelectableKnot(SplineInfo, KnotIndex); /// /// The opposite tangent on the knot. If this tangent is the In tangent, then the opposite tangent is the Out tangent. If this tangent is the Out tangent, then the opposite tangent is the In tangent. /// public SelectableTangent OppositeTangent => new SelectableTangent(SplineInfo, KnotIndex, 1 - TangentIndex); /// public bool IsValid() { return SplineInfo.Spline != null && KnotIndex >= 0 && KnotIndex < SplineInfo.Spline.Count && TangentIndex >= 0 && TangentIndex < 2 && Owner.Mode != TangentMode.Linear && Owner.Mode != TangentMode.AutoSmooth; } /// /// The direction of the tangent in world space. /// public float3 Direction { get => MathUtility.MultiplyVector(LocalToWorld, LocalDirection); set => LocalDirection = MathUtility.MultiplyVector(math.inverse(LocalToWorld), value); } /// /// The direction of the tangent in local space. /// public float3 LocalDirection { get => TangentIndex == (int)BezierTangent.In ? SplineInfo.Spline[KnotIndex].TangentIn : SplineInfo.Spline[KnotIndex].TangentOut; set { var spline = SplineInfo.Spline; var knot = spline[KnotIndex]; switch (TangentIndex) { case (int)BezierTangent.In: knot.TangentIn = value; break; case (int)BezierTangent.Out: knot.TangentOut = value; break; } spline.SetKnot(KnotIndex, knot, (BezierTangent)TangentIndex); } } /// /// Matrix that transforms a tangent point from local space into world space using its associated knot (Read Only). /// internal float4x4 LocalToWorld => math.mul(SplineInfo.LocalToWorld, new float4x4(Owner.LocalRotation, Owner.LocalPosition)); /// public float3 Position { get => math.transform(LocalToWorld, LocalPosition); set => LocalPosition = math.transform(math.inverse(LocalToWorld), value); } /// public float3 LocalPosition { get => LocalDirection; set => LocalDirection = value; } /// /// Creates a new object. /// /// The associated with the tangent. /// The index of the knot that the tangent is attached to. /// The that represents this tangent. public SelectableTangent(SplineInfo info, int knotIndex, BezierTangent tangent) : this(info, knotIndex, (int)tangent) { } /// /// Creates a new object. /// /// The associated with the tangent. /// The index of the knot that the tangent is attached to. /// The index of the tangent. A value of 0 represents an In tangent. A value of 1 represents an Out tangent. public SelectableTangent(SplineInfo info, int knotIndex, int tangentIndex) { SplineInfo = info; KnotIndex = knotIndex; TangentIndex = tangentIndex; } /// /// Checks if two instances of a `SplineElement` are equal. /// /// The to compare against. /// /// Returns true when is a and the values of each instance are identical. /// public bool Equals(ISelectableElement other) { if (other is SelectableTangent tangent) return Equals(tangent); return false; } /// /// Checks if two instances of `SelectableTangent` are equal. /// /// The to compare against. /// /// Returns true if the values of each instance are identical. /// public bool Equals(SelectableTangent other) { return Equals(SplineInfo.Spline, other.SplineInfo.Spline) && KnotIndex == other.KnotIndex && TangentIndex == other.TangentIndex; } /// /// Checks if two objects are equal. /// /// The object to compare against. /// /// Returns true when is a and the values of each instance are identical. /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; return obj is SelectableTangent other && Equals(other); } /// /// Gets a hash code for this tangent. /// /// /// A hash code for the . /// public override int GetHashCode() { return HashCode.Combine(SplineInfo.Spline, KnotIndex, TangentIndex); } } }