//==================================================================================== // // Purpose: To generate a bezier curve between at least 4 points in space and draw // a number of spheres across the generated curve // // This script is heavily based on the tutorial at: // http://catlikecoding.com/unity/tutorials/curves-and-splines/ // //==================================================================================== using UnityEngine; using System.Collections; using System; public static class Bezier { public static Vector3 GetPoint(Vector3 p0, Vector3 p1, Vector3 p2, float t) { t = Mathf.Clamp01(t); float oneMinusT = 1f - t; return oneMinusT * oneMinusT * p0 + 2f * oneMinusT * t * p1 + t * t * p2; } public static Vector3 GetFirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, float t) { return 2f * (1f - t) * (p1 - p0) + 2f * t * (p2 - p1); } public static Vector3 GetPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { t = Mathf.Clamp01(t); float OneMinusT = 1f - t; return OneMinusT * OneMinusT * OneMinusT * p0 + 3f * OneMinusT * OneMinusT * t * p1 + 3f * OneMinusT * t * t * p2 + t * t * t * p3; } public static Vector3 GetFirstDerivative(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) { t = Mathf.Clamp01(t); float oneMinusT = 1f - t; return 3f * oneMinusT * oneMinusT * (p1 - p0) + 6f * oneMinusT * t * (p2 - p1) + 3f * t * t * (p3 - p2); } } public class CurveGenerator : MonoBehaviour { private enum BezierControlPointMode { Free, Aligned, Mirrored } private Vector3[] points; private GameObject[] items; private BezierControlPointMode[] modes; private bool loop; private int frequency; private bool customTracer; public void Create(int setFrequency, float radius, GameObject tracer) { float circleSize = radius / 8; frequency = setFrequency; items = new GameObject[frequency]; for (int f = 0; f < items.Length; f++) { customTracer = true; items[f] = (tracer ? Instantiate(tracer) : CreateSphere()); items[f].transform.parent = this.transform; items[f].layer = 2; items[f].transform.localScale = new Vector3(circleSize, circleSize, circleSize); } } public void SetPoints(Vector3[] controlPoints, Material material) { points = controlPoints; modes = new BezierControlPointMode[] { BezierControlPointMode.Free, BezierControlPointMode.Free }; SetObjects(material); } public void TogglePoints(bool state) { this.gameObject.SetActive(state); } private GameObject CreateSphere() { customTracer = false; GameObject item = GameObject.CreatePrimitive(PrimitiveType.Sphere); Destroy(item.GetComponent()); item.GetComponent().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; item.GetComponent().receiveShadows = false; return item; } private bool Loop { get { return loop; } set { loop = value; if (value == true) { modes[modes.Length - 1] = modes[0]; SetControlPoint(0, points[0]); } } } private int ControlPointCount { get { return points.Length; } } private Vector3 GetControlPoint(int index) { return points[index]; } private void SetControlPoint(int index, Vector3 point) { if (index % 3 == 0) { Vector3 delta = point - points[index]; if (loop) { if (index == 0) { points[1] += delta; points[points.Length - 2] += delta; points[points.Length - 1] = point; } else if (index == points.Length - 1) { points[0] = point; points[1] += delta; points[index - 1] += delta; } else { points[index - 1] += delta; points[index + 1] += delta; } } else { if (index > 0) { points[index - 1] += delta; } if (index + 1 < points.Length) { points[index + 1] += delta; } } } points[index] = point; EnforceMode(index); } private void EnforceMode(int index) { int modeIndex = (index + 1) / 3; BezierControlPointMode mode = modes[modeIndex]; if (mode == BezierControlPointMode.Free || !loop && (modeIndex == 0 || modeIndex == modes.Length - 1)) { return; } int middleIndex = modeIndex * 3; int fixedIndex, enforcedIndex; if (index <= middleIndex) { fixedIndex = middleIndex - 1; if (fixedIndex < 0) { fixedIndex = points.Length - 2; } enforcedIndex = middleIndex + 1; if (enforcedIndex >= points.Length) { enforcedIndex = 1; } } else { fixedIndex = middleIndex + 1; if (fixedIndex >= points.Length) { fixedIndex = 1; } enforcedIndex = middleIndex - 1; if (enforcedIndex < 0) { enforcedIndex = points.Length - 2; } } Vector3 middle = points[middleIndex]; Vector3 enforcedTangent = middle - points[fixedIndex]; if (mode == BezierControlPointMode.Aligned) { enforcedTangent = enforcedTangent.normalized * Vector3.Distance(middle, points[enforcedIndex]); } points[enforcedIndex] = middle + enforcedTangent; } private int CurveCount { get { return (points.Length - 1) / 3; } } private Vector3 GetPoint(float t) { int i; if (t >= 1f) { t = 1f; i = points.Length - 4; } else { t = Mathf.Clamp01(t) * CurveCount; i = (int)t; t -= i; i *= 3; } return transform.TransformPoint(Bezier.GetPoint(points[i], points[i + 1], points[i + 2], points[i + 3], t)); } private void SetObjects(Material material) { float stepSize = frequency * 1; if (this.Loop || stepSize == 1) { stepSize = 1f / stepSize; } else { stepSize = 1f / (stepSize - 1); } for (int f = 0; f < frequency; f++) { if (customTracer && (f == 0 || f == (frequency - 1))) { items[f].SetActive(false); continue; } setMeshMaterial(items[f], material); setSkinnedMeshMaterial(items[f], material); Vector3 position = this.GetPoint(f * stepSize); items[f].transform.position = position; Vector3 nextPosition = this.GetPoint((f + 1) * stepSize); Vector3 lookPosition = (nextPosition - position).normalized; if (lookPosition != Vector3.zero) { items[f].transform.rotation = Quaternion.LookRotation(lookPosition); } } } private void setMeshMaterial(GameObject item, Material material) { if (item.GetComponent()) { item.GetComponent().material = material; } foreach (MeshRenderer mr in item.GetComponentsInChildren()) { mr.material = material; } } private void setSkinnedMeshMaterial(GameObject item, Material material) { if (item.GetComponent()) { item.GetComponent().material = material; } foreach (SkinnedMeshRenderer mr in item.GetComponentsInChildren()) { mr.material = material; } } }