//==================================================================================== // // 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/ // //==================================================================================== namespace VRTK { using UnityEngine; 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 VRTK_CurveGenerator : MonoBehaviour { public enum BezierControlPointMode { Free, Aligned, Mirrored } protected Vector3[] points; protected GameObject[] items; protected BezierControlPointMode[] modes; protected bool loop; protected int frequency; protected bool customTracer; protected bool rescalePointerTracer; protected GameObject tracerLineRenderer; protected LineRenderer customLineRenderer; protected bool lineRendererAndItem; public virtual void Create(int setFrequency, float radius, GameObject tracer, bool rescaleTracer = false) { float circleSize = radius / 8; frequency = setFrequency; customLineRenderer = (tracer != null ? tracer.GetComponent() : null); lineRendererAndItem = (customLineRenderer != null && tracer.GetComponentInChildren()); if (customLineRenderer != null) { tracerLineRenderer = Instantiate(tracer); tracerLineRenderer.name = VRTK_SharedMethods.GenerateVRTKObjectName(true, name, "LineRenderer"); for (int i = 0; i < tracerLineRenderer.transform.childCount; i++) { Destroy(tracerLineRenderer.transform.GetChild(i).gameObject); } customLineRenderer = tracerLineRenderer.GetComponent(); #if UNITY_5_5 customLineRenderer.numPositions = frequency; #elif UNITY_5_6_OR_NEWER customLineRenderer.positionCount = frequency; #else customLineRenderer.SetVertexCount(frequency); #endif } if (customLineRenderer == null || lineRendererAndItem) { items = new GameObject[frequency]; for (int f = 0; f < items.Length; f++) { customTracer = true; items[f] = (tracer != null ? Instantiate(tracer) : CreateSphere()); items[f].transform.SetParent(transform); items[f].layer = LayerMask.NameToLayer("Ignore Raycast"); items[f].transform.localScale = new Vector3(circleSize, circleSize, circleSize); if (customLineRenderer != null) { Destroy(items[f].GetComponent()); } } } rescalePointerTracer = rescaleTracer; } public virtual void SetPoints(Vector3[] controlPoints, Material material, Color color) { PointsInit(controlPoints); SetObjects(material, color); } public virtual Vector3[] GetPoints(Vector3[] controlPoints) { PointsInit(controlPoints); Vector3[] calculatedPoints = new Vector3[frequency]; float stepSize = frequency * 1; if (Loop || stepSize == 1) { stepSize = VRTK_SharedMethods.DividerToMultiplier(stepSize); } else { stepSize = VRTK_SharedMethods.DividerToMultiplier((stepSize - 1)); } for (int f = 0; f < frequency; f++) { calculatedPoints[f] = GetPoint(f * stepSize); } return calculatedPoints; } public virtual void TogglePoints(bool state) { gameObject.SetActive(state); if (tracerLineRenderer != null) { tracerLineRenderer.SetActive(state); } } protected virtual void OnDisable() { if (tracerLineRenderer != null) { tracerLineRenderer.SetActive(false); } } protected virtual void PointsInit(Vector3[] controlPoints) { points = controlPoints; modes = new BezierControlPointMode[] { BezierControlPointMode.Free, BezierControlPointMode.Free }; } protected virtual GameObject CreateSphere() { customTracer = false; GameObject item = GameObject.CreatePrimitive(PrimitiveType.Sphere); item.name = VRTK_SharedMethods.GenerateVRTKObjectName(true, "Sphere"); Destroy(item.GetComponent()); item.GetComponent().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; item.GetComponent().receiveShadows = false; return item; } protected virtual bool Loop { get { return loop; } set { loop = value; if (value == true) { modes[modes.Length - 1] = modes[0]; SetControlPoint(0, points[0]); } } } protected virtual int ControlPointCount { get { return points.Length; } } protected virtual Vector3 GetControlPoint(int index) { return points[index]; } protected virtual 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); } protected virtual 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; } protected virtual int CurveCount { get { return (points.Length - 1) / 3; } } protected virtual 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)); } protected virtual void SetObjects(Material material, Color color) { float stepSize = frequency * 1; if (Loop || stepSize == 1) { stepSize = VRTK_SharedMethods.DividerToMultiplier(stepSize); } else { stepSize = VRTK_SharedMethods.DividerToMultiplier((stepSize - 1)); } SetPointData(material, color, stepSize); } protected virtual void SetPointData(Material material, Color color, float stepSize) { for (int f = 0; f < frequency; f++) { Vector3 position = GetPoint(f * stepSize); if (customLineRenderer != null) { customLineRenderer.SetPosition(f, position); SetMaterial(customLineRenderer.sharedMaterial, color); } if (customLineRenderer == null || lineRendererAndItem) { SetItemPosition(f, position, material, color, stepSize); } } } protected virtual void SetItemPosition(int currentIndex, Vector3 setPosition, Material material, Color color, float stepSize) { if (customTracer && (currentIndex == (frequency - 1))) { items[currentIndex].SetActive(false); return; } SetItemMaterial(items[currentIndex], material, color); items[currentIndex].transform.position = setPosition; Vector3 nextPosition = GetPoint((currentIndex + 1) * stepSize); Vector3 offset = nextPosition - setPosition; Vector3 lookPosition = offset.normalized; if (lookPosition != Vector3.zero) { items[currentIndex].transform.rotation = Quaternion.LookRotation(lookPosition); // rescale the custom tracer according to the length of the beam if (rescalePointerTracer) { Vector3 scl = items[currentIndex].transform.localScale; scl.z = offset.magnitude / 2f; // (assuming a center-based scaling) items[currentIndex].transform.localScale = scl; } } } protected virtual void SetItemMaterial(GameObject item, Material material, Color color) { Renderer[] itemRenderers = item.GetComponentsInChildren(); for (int i = 0; i < itemRenderers.Length; i++) { if (material != null) { itemRenderers[i].material = material; } SetMaterial(itemRenderers[i].material, color); } } protected virtual void SetMaterial(Material material, Color color) { if (material != null) { material.EnableKeyword("_EMISSION"); if (material.HasProperty("_Color")) { material.color = color; } if (material.HasProperty("_EmissionColor")) { material.SetColor("_EmissionColor", VRTK_SharedMethods.ColorDarken(color, 50)); } } } } }