using System; using Unity.Collections; using Unity.Mathematics; namespace UnityEngine.U2D { /// /// SpriteShape Object placement Modes. /// Auto : Allows editing the transform of the Object while keeping it on the surface of the spline. /// Manual : Allows mo0vement strictly with the Ratio and Start, End points. /// public enum SpriteShapeObjectPlacementMode { Auto, Manual }; /// /// SpriteShapeObjectPlacement helps placing a game Object along the Spline. /// [ExecuteInEditMode] [ExecuteAlways] public class SpriteShapeObjectPlacement : MonoBehaviour { [SerializeField] SpriteShapeController m_SpriteShapeController; [SerializeField] bool m_SetNormal = true; [SerializeField] SpriteShapeObjectPlacementMode m_Mode; [SerializeField] [Min(0)] int m_StartPoint = 0; [SerializeField] [Min(0)] int m_EndPoint = 1; [SerializeField] float m_Ratio = 0.5f; /// Internal Hash Code. int m_ActiveHashCode = 0; static readonly float kMaxDistance = 10000.0f; private static readonly int kMaxIteration = 128; /// /// Dictates whether the object needs to be rotated to the normal along the spline. /// public bool setNormal { get { return m_SetNormal; } set { m_SetNormal = value; } } /// /// Set SpriteShape Object placement mode. /// public SpriteShapeObjectPlacementMode mode { get { return m_Mode; } set { m_Mode = value; } } /// /// Ratio of the distance between the startPoint and the endPoint. Must be between 0 and 1 /// public float ratio { get { return m_Ratio; } set { m_Ratio = value; } } /// /// Source SpriteShapeController gameObject that contains the Spline to which this Object is placed along. /// public SpriteShapeController spriteShapeController { get { return m_SpriteShapeController; } set { m_SpriteShapeController = value; } } /// /// Start point of the pair of points between which the object is placed. Must be between 0 and points in Spline. /// public int startPoint { get { return m_StartPoint; } set { m_StartPoint = value; } } /// /// End point of the pair of points between which the object is placed. Must be between 0 and points in Spline and larger than StartPoint. /// public int endPoint { get { return m_EndPoint; } set { m_EndPoint = value; } } bool PlaceObjectOnHashChange() { if (null == spriteShapeController) return false; unchecked { var count = 0; int spriteShapeHashCode = (int) 2166136261 ^ spriteShapeController.splineHashCode; spriteShapeHashCode = spriteShapeHashCode * 16777619 ^ spriteShapeController.spriteShapeHashCode; var ssTransform = spriteShapeController.gameObject.transform; var pos = gameObject.transform.position; var rot = gameObject.transform.rotation; spriteShapeHashCode = spriteShapeHashCode * 16777619 ^ (setNormal ? 1 : 0); spriteShapeHashCode = spriteShapeHashCode * 16777619 ^ (startPoint); spriteShapeHashCode = spriteShapeHashCode * 16777619 ^ (endPoint); spriteShapeHashCode = spriteShapeHashCode * 16777619 ^ ssTransform.position.GetHashCode(); spriteShapeHashCode = spriteShapeHashCode * 16777619 ^ ssTransform.rotation.GetHashCode(); do { // SpriteShape. // Local Stuff. int hashCode = spriteShapeHashCode * 16777619 ^ Math.Round(pos.x * 1000.0f).GetHashCode(); hashCode = hashCode * 16777619 ^ Math.Round(pos.y * 1000.0f).GetHashCode(); hashCode = hashCode * 16777619 ^ Math.Round(pos.z * 1000.0f).GetHashCode(); hashCode = hashCode * 16777619 ^ Math.Round(ratio * 1000.0f).GetHashCode(); hashCode = hashCode * 16777619 ^ Math.Round(rot.x * 1000.0f).GetHashCode(); hashCode = hashCode * 16777619 ^ Math.Round(rot.y * 1000.0f).GetHashCode(); hashCode = hashCode * 16777619 ^ Math.Round(rot.z * 1000.0f).GetHashCode(); hashCode = hashCode * 16777619 ^ Math.Round(rot.w * 1000.0f).GetHashCode(); if (m_ActiveHashCode != hashCode) { var run = Place(); m_ActiveHashCode = hashCode; if (!run) break; } else { break; } } while (count++ < kMaxIteration); } return false; } static float Angle(Vector3 a, Vector3 b) { float dot = Vector3.Dot(a, b); float det = (a.x * b.y) - (b.x * a.y); return Mathf.Atan2(det, dot) * Mathf.Rad2Deg; } float GetDistance(float dist, int spoint, int epoint, ref int start, ref int end, ref float r, NativeArray shapePoints) { start = -1; var detail = spriteShapeController.splineDetail; var distance = 0.0f; var division = (float)(detail - 1); var pointCount = shapePoints.Length; for (int i = spoint; i < epoint; ++i) { var j = i + 1; if (j == pointCount) j = 0; var cp = shapePoints[i]; var pp = shapePoints[j]; var p0 = cp.position; var p1 = pp.position; var sp = p0; var rt = p0 + cp.rightTangent; var lt = p1 + pp.leftTangent; var ld = 0.0f; var pd = 0.0f; var st = false; if (dist != 0 && dist > distance) { start = i; end = (i + 1 == pointCount) ? 0 : (i + 1); pd = distance; st = true; } for (int n = 1; n < detail; ++n) { var t = (float)n / division; var bp = BezierUtility.BezierPoint(rt, p0, p1, lt, t); var d = math.distance(bp, sp); ld += d; distance += d; } if (st) { var diff = dist - pd; r = diff / ld; } } return distance; } Vector3 PlaceObjectInternal(int sp, int ep, float t, NativeArray shapePoints) { ep = ep % shapePoints.Length; var p0 = shapePoints[sp].position; var p1 = shapePoints[ep].position; var rt = p0 + shapePoints[sp].rightTangent; var lt = p1 + shapePoints[ep].leftTangent; var bp = BezierUtility.BezierPoint(rt, p0, p1, lt, t); var position = new Vector3(bp.x, bp.y, 0); var srcTransform = spriteShapeController.transform.localToWorldMatrix; var dstTransform = gameObject.transform; var p = srcTransform.MultiplyPoint3x4(position); var d = dstTransform.position; if (m_Mode == SpriteShapeObjectPlacementMode.Auto) d.y = p.y; else d = p; dstTransform.position = d; if (setNormal) { var _r = math.clamp(t, 0.002f, 0.998f); var pp = BezierUtility.BezierPoint(rt, p0, p1, lt, _r - 0.001f); bp = BezierUtility.BezierPoint(rt, p0, p1, lt, _r); var np = BezierUtility.BezierPoint(rt, p0, p1, lt, _r + 0.001f); var _lt = Vector3.Normalize(new Vector3(pp.x, pp.y, 0) - new Vector3(bp.x, bp.y, 0)); var _rt = Vector3.Normalize(new Vector3(np.x, np.y, 0) - new Vector3(bp.x, bp.y, 0)); var a = Angle(Vector3.up, _lt); var b = Angle(_lt, _rt); var c = a + (b * 0.5f); if (b > 0) c = (180 + c); var rotation = Quaternion.Euler(0, 0, c); dstTransform.rotation = srcTransform.rotation * rotation; } return d; } Vector3 PlaceObject(Spline spline, int sp, int ep, ref bool run) { var shapePoints = spriteShapeController.GetShapeControlPoints(); if (sp > shapePoints.Length || ep > shapePoints.Length) { run = false; return Vector3.zero; } var t = math.clamp(ratio, 0.0001f, 0.9999f); if (ep - sp == 1) { run = true; return PlaceObjectInternal(sp, ep, t, shapePoints); } else { var s = 0; var e = 0; var d = 0.0f; var r = 0.0f; d = GetDistance(d, sp, ep, ref s, ref e, ref r, shapePoints) * t; GetDistance(d, sp, ep, ref s, ref e, ref r, shapePoints); if (s >= 0) { run = true; return PlaceObjectInternal(s, e, r, shapePoints); } } run = false; return Vector3.zero; } int GetSplinePointCount() { var spline = spriteShapeController.spline; var pointCount = spline.GetPointCount(); pointCount = spline.isOpenEnded ? pointCount - 1 : pointCount; return pointCount; } bool Place() { var pointCount = GetSplinePointCount(); var run = false; if (m_Mode == SpriteShapeObjectPlacementMode.Manual) { var sp = math.clamp(startPoint, 0, pointCount); var ep = math.clamp(endPoint, 0, pointCount); if (sp >= ep) { endPoint = pointCount; Debug.LogWarning("Invalid End point and it has been clamped", transform); } PlaceObject(spriteShapeController.spline, sp, ep, ref run); return run; } var distance = kMaxDistance; var position = transform.position; var closestPoint = Vector3.zero; { int tp = 0, np = 0; float et = 100, dist = kMaxDistance; var spline = spriteShapeController.spline; var matrix = spriteShapeController.transform.localToWorldMatrix; var splinePointCount = spline.GetPointCount(); for (int i = 0; i < pointCount; ++i) { var ni = (i + 1) % spline.GetPointCount(); var thisposition = matrix.MultiplyPoint3x4(spline.GetPosition(i)); var nextPosition = matrix.MultiplyPoint3x4(spline.GetPosition(ni)); var rightTangent = spline.GetRightTangent(i) + thisposition; var leftTangent = spline.GetLeftTangent(ni) + nextPosition; float t; closestPoint = BezierUtility.ClosestPointOnCurve( position, thisposition, nextPosition, rightTangent, leftTangent, 0.0001f, out t); float _d = (closestPoint - position).magnitude; if (_d < dist) { tp = i; np = ni; et = t; dist = _d; } } if (tp >= 0 && tp < splinePointCount && np >= 0 && np < splinePointCount) { startPoint = tp; endPoint = np == 0 ? tp + 1 : np; ratio = et; position = PlaceObject(spline, startPoint, endPoint, ref run); } } return run; } void Start () { PlaceObjectOnHashChange(); } void Update () { PlaceObjectOnHashChange(); } } }