using UnityEngine; using System; using System.Collections; using System.Collections.Generic; namespace uc.Spline { [Serializable] public class BezierSplinePoint : BaseSplinePoint { public BezierSplinePoint(Vector3 p, Vector3 c1, Vector3 c2, SplinePointType t) : base(p) { m_prevctrl = c1; m_nextctrl = c2; m_type = t; } public BezierSplinePoint(Vector3 p) : base(p) { m_prevctrl = Vector3.zero; m_nextctrl = Vector3.zero; m_type = SplinePointType.Corner; } public Vector3 m_prevctrl, m_nextctrl; public SplinePointType m_type; } [Serializable] public class BezierSpline : BaseSpline { public class EditHelper : BaseEditHelper { protected new BezierSpline m_spline; internal EditHelper(BezierSpline spline) : base(spline) { this.m_spline = spline; } public override void AppendPoint() { Vector3 newPos = Vector3.zero; if (m_spline.PointCount == 1) { newPos = m_spline.GetPointPosition(m_spline.PointCount - 1) + Vector3.right; } else if(m_spline.PointCount > 1) { newPos = m_spline.GetPointPosition(m_spline.PointCount - 1) * 2 - m_spline.GetPointPosition(m_spline.PointCount - 2); } BezierSplinePoint p = new BezierSplinePoint(newPos); m_spline.AppendPoint(p); m_selidx = m_spline.PointCount - 1; } public override void InsertBefore() { if (m_spline.PointCount == 1) { BezierSplinePoint p = new BezierSplinePoint(m_spline.GetPointPosition(m_spline.PointCount - 1) + Vector3.left); m_spline.InsertPoint(0, p); } else { int previdx = m_selidx; m_selidx = (int)Mathf.Repeat(m_selidx - 1, m_spline.PointCount - 1); BezierSplinePoint p = new BezierSplinePoint((m_spline.GetPointPosition(m_selidx) + m_spline.GetPointPosition(previdx)) * 0.5f); m_spline.InsertPoint(previdx, p); m_selidx = previdx; } } public override void InsertAfter() { if (m_spline.m_points.Count == 1) { AppendPoint(); } else { int previdx = m_selidx; m_selidx = (int)Mathf.Repeat(m_selidx + 1, m_spline.PointCount - 1); BezierSplinePoint p = new BezierSplinePoint((m_spline.GetPointPosition(m_selidx) + m_spline.GetPointPosition(previdx)) * 0.5f); m_spline.InsertPoint(m_selidx, p); } } public override void Remove() { m_spline.RemovePoint(m_selidx); m_selidx = (int)Mathf.Repeat(m_selidx, m_spline.PointCount - 1); } private int m_selcpidx = -1; public override bool Selected { get { return base.Selected; } set { base.Selected = value; m_selcpidx = -1; } } public override BaseSplinePoint Point { get { return m_spline.m_points[m_idx]; } set { m_spline.m_points[m_idx] = value as BezierSplinePoint; } } public override BaseSplinePoint SelectedPoint { get { return m_spline.m_points[m_selidx]; } set { m_spline.m_points[m_selidx] = value as BezierSplinePoint; } } public bool ControlPoint1Selected { get { return m_selcpidx == 0; } set { if (value) { m_selcpidx = 0; } else { m_selcpidx = -1; } } } public bool ControlPoint2Selected { get { return m_selcpidx == 1; } set { if (value) { m_selcpidx = 1; } else { m_selcpidx = -1; } } } public bool ControlPointsVisible { get { if (m_selidx == -1) return false; BezierSplinePoint p = m_spline.m_points[m_selidx] as BezierSplinePoint; return (p.m_type == SplinePointType.Bezier || p.m_type == SplinePointType.BezierCorner); } } public override void RemoveLastPoint() { m_spline.m_points.RemoveAt(m_spline.PointCount - 1); } public override void RemoveAllPoints() { m_spline.m_points.Clear(); } } [Serializable] public class BezierSplineSegment : BaseSplineSegment { public SplineSegmentType m_type; internal override Vector3 GetPosition(float t) { switch (m_type) { case SplineSegmentType.Linear: return m_startpos + (m_endpos - m_startpos) * t; case SplineSegmentType.Curve: return uc.Math.Spline.Bezier.Position(m_startctrl, m_startpos, m_endpos, m_endctrl, t); default: return Vector3.zero; } } internal override Vector3 GetTangent(float t) { switch (m_type) { case SplineSegmentType.Linear: return (m_endpos - m_startpos); case SplineSegmentType.Curve: return uc.Math.Spline.Bezier.Tangent(m_startctrl, m_startpos, m_endpos, m_endctrl, t); default: return Vector3.zero; } } internal override Vector3 GetNormal(float t) { switch (m_type) { case SplineSegmentType.Linear: return Vector3.zero; case SplineSegmentType.Curve: return uc.Math.Spline.Bezier.Normal(m_startctrl, m_startpos, m_endpos, m_endctrl, t); default: return Vector3.zero; } } } [SerializeField] private List m_points = new List(); [SerializeField] private BezierSplineSegment[] m_segments = null; public override int PointCount { get { return m_points.Count; } } public void ReversePoints() { m_points.Reverse(); Vector3 swp; for (int i = 0; i < m_points.Count; ++i) { BezierSplinePoint p = m_points[i] as BezierSplinePoint; swp = p.m_nextctrl; p.m_nextctrl = p.m_prevctrl; p.m_prevctrl = swp; } } public override void Build() { int idx, count; BezierSplinePoint pp, sp, ep, np; // if (m_points.Count < 2) { m_segments = null; m_length = 0; return; } if (m_wrapmode == SplineWrapMode.Loop) { count = m_points.Count; } else { count = m_points.Count - 1; } // m_segments = new BezierSplineSegment[count]; m_length = 0; idx = 0; if (m_wrapmode == SplineWrapMode.Loop) { while (idx < count) { pp = m_points[(int)Mathf.Repeat(idx - 1, m_points.Count)] as BezierSplinePoint; sp = m_points[(int)Mathf.Repeat(idx, m_points.Count)] as BezierSplinePoint; ep = m_points[(int)Mathf.Repeat(idx + 1, m_points.Count)] as BezierSplinePoint; np = m_points[(int)Mathf.Repeat(idx + 2, m_points.Count)] as BezierSplinePoint; m_segments[idx] = new BezierSplineSegment(); BuildSegment(m_segments[idx] as BezierSplineSegment, pp, sp, ep, np); ++idx; } } else { while (idx < count) { pp = m_points[Mathf.Clamp(idx - 1, 0, m_points.Count - 1)] as BezierSplinePoint; sp = m_points[Mathf.Clamp(idx, 0, m_points.Count - 1)] as BezierSplinePoint; ep = m_points[Mathf.Clamp(idx + 1, 0, m_points.Count - 1)] as BezierSplinePoint; np = m_points[Mathf.Clamp(idx + 2, 0, m_points.Count - 1)] as BezierSplinePoint; m_segments[idx] = new BezierSplineSegment(); BuildSegment(m_segments[idx] as BezierSplineSegment, pp, sp, ep, np); ++idx; } } ++m_buildnum; } private void BuildSegment(BezierSplineSegment ss, BezierSplinePoint pp, BezierSplinePoint sp, BezierSplinePoint ep, BezierSplinePoint np) { PreparePoint(pp, sp, ep); PreparePoint(sp, ep, np); ss.m_startpos = sp.m_point; ss.m_endpos = ep.m_point; ss.m_startctrl = ss.m_startpos + sp.m_nextctrl; ss.m_endctrl = ss.m_endpos + ep.m_prevctrl; if (sp.m_type == SplinePointType.Corner && ep.m_type == SplinePointType.Corner) { ss.m_type = SplineSegmentType.Linear; } else { ss.m_type = SplineSegmentType.Curve; } ss.m_startlen = m_length; float seglen = GetLength(ss); m_length += seglen; ss.m_length = seglen; ss.m_endlen = m_length; switch (m_reparam) { case SplineReparamType.None: ss.m_params = null; ss.m_precomps = null; break; case SplineReparamType.Simple: { m_precompdiv = 1 / (float)m_stepcount; float param = 0, length = 0; Vector3 prev, next; ss.m_params = new float[m_stepcount + 1]; ss.m_precomps = new float[m_stepcount + 1]; for (int i = 1; i < m_stepcount + 1; ++i) { prev = ss.GetPosition(param); param += m_precompdiv; next = ss.GetPosition(param); length += (next - prev).magnitude; ss.m_precomps[i] = length / seglen; ss.m_params[i] = param; } ss.m_params[0] = 0; ss.m_params[m_stepcount] = 1; ss.m_precomps[0] = 0; ss.m_precomps[m_stepcount] = 1; m_precompdiv = 1 / (float)m_stepcount; } break; case SplineReparamType.RungeKutta: float dlen = seglen / (float)m_stepcount, lparam = 0; ss.m_params = new float[m_stepcount + 1]; ss.m_precomps = new float[m_stepcount + 1]; for (int i = 0; i < m_stepcount + 1; ++i) { ss.m_params[i] = GetReparamRungeKutta(ss, lparam); ss.m_precomps[i] = lparam / seglen; lparam += dlen; } ss.m_params[0] = 0; ss.m_params[m_stepcount] = 1; ss.m_precomps[0] = 0; ss.m_precomps[m_stepcount] = 1; m_precompdiv = 1 / (float)m_stepcount; break; } } private void PreparePoint(BezierSplinePoint pp, BezierSplinePoint pt, BezierSplinePoint np) { switch (pt.m_type) { case SplinePointType.Bezier: pt.m_nextctrl = -pt.m_prevctrl; break; case SplinePointType.Smooth: pt.m_prevctrl = -0.25f * (np.m_point - pp.m_point); pt.m_nextctrl = -0.25f * (pp.m_point - np.m_point); break; case SplinePointType.Corner: pt.m_prevctrl = Vector3.zero; pt.m_nextctrl = Vector3.zero; break; case SplinePointType.BezierCorner: break; } } private float GetLength(BezierSplineSegment ss) { switch (ss.m_type) { case SplineSegmentType.Linear: return (ss.m_endpos - ss.m_startpos).magnitude; case SplineSegmentType.Curve: { float len = 0; Vector3 start, end; float t = 0, dt = 1 / (float)m_stepcount; int idx = 0; start = ss.m_startpos; while (idx < m_stepcount) { t += dt; end = ss.GetPosition(t); len += (end - start).magnitude; start = end; ++idx; } return len; } default: return 0; } } public override int GetPointCount() { return m_points.Count; } public override int GetSegmentCount() { if (m_segments != null) { return m_segments.Length; } return 0; } protected override float GetSegmentLength(int segidx) { return m_segments[segidx].m_length; } protected override float GetSegmentStartLength(int segidx) { return m_segments[segidx].m_startlen; } protected override float GetSegmentEndLength(int segidx) { return m_segments[segidx].m_endlen; } protected override int FindSegment(float offset) { for (int i = 0; i < m_segments.Length; ++i) { if (m_segments[i].m_startlen <= offset && m_segments[i].m_endlen > offset) { return i; } } return m_segments.Length - 1; } protected override Vector3 GetDrawPosition(int segidx, float segpos) { return m_segments[segidx].GetPosition(segpos); } protected override Vector3 GetPosition(int segidx, float segpos) { BaseSplineSegment ss = m_segments[segidx]; if (m_reparam == SplineReparamType.None) { return ss.GetPosition(segpos / ss.m_length); } else { return ss.GetPosition(GetReparam(ss, segpos / ss.m_length)); } } protected override Vector3 GetTangent(int segidx, float segpos) { BaseSplineSegment ss = m_segments[segidx]; if (m_reparam == SplineReparamType.None) { return ss.GetTangent(segpos / ss.m_length); } else { return ss.GetTangent(GetReparam(ss, segpos / ss.m_length)); } } protected override Vector3 GetNormal(int segidx, float segpos) { BaseSplineSegment ss = m_segments[segidx]; if (m_reparam == SplineReparamType.None) { return ss.GetNormal(segpos / ss.m_length); } else { return ss.GetNormal(GetReparam(ss, segpos / ss.m_length)); } } internal override void AppendPoint(BaseSplinePoint p) { m_points.Add(p as BezierSplinePoint); } internal override void InsertPoint(int idx, BaseSplinePoint p) { if (idx < 0 || idx > m_points.Count) { throw (new IndexOutOfRangeException()); } m_points.Insert(idx, p as BezierSplinePoint); } internal override void RemovePoint(int idx) { if (idx < 0 || idx > m_points.Count) { throw (new IndexOutOfRangeException()); } m_points.RemoveAt(idx); } internal override Vector3 GetPointPosition(int idx) { if (idx < 0 || idx > m_points.Count) { throw (new IndexOutOfRangeException()); } return m_points[idx].m_point; } public override BaseEditHelper GetEditHelper() { return new EditHelper(this); } } public class BezierSplineComponent : MonoBehaviour { [SerializeField] private BezierSpline m_spline = new BezierSpline(); private int m_buildnum = -1; private Vector3[] m_drawcache; public BezierSpline Spline { get { return m_spline; } } void OnDrawGizmosSelected() { DrawGizmos(Color.red); } void OnDrawGizmos() { DrawGizmos(Color.white); } void DrawGizmos(Color color) { int curbuild = m_spline.BuildNum; if (m_spline.GetSegmentCount() > 0) { if (m_buildnum != curbuild) { m_drawcache = m_spline.GenerateDrawPoints(16); m_buildnum = curbuild; } if (m_drawcache != null) { Gizmos.matrix = transform.localToWorldMatrix; Gizmos.color = color; Vector3 startpos = Vector3.zero, endpos = Vector3.zero; for (int i = 0; i < m_drawcache.Length; ++i) { endpos = m_drawcache[i]; if (i != 0) { Gizmos.DrawLine(startpos, endpos); } startpos = endpos; } } } } } }