/// <summary> /// Update the parameters if anything changes /// </summary> void Update() { // early out if no updating is requested if (!AutoUpdate) { return; } // create a new mesh if the divisions have changed if (m_PreviousDivisions != Divisions) { CreateMeshRibbon(); } // recompute vertex positions if helix has changed if (!Helix.AreEquivalent(m_PreviousHelix, Helix)) { RecalculateVertexPositions(); } // compute uvs if the scale has changed if (!AutoVScale && (m_PreviousVScale != VScale)) { RecalculateUVCoordinates(); } // store old values m_PreviousHelix = new Helix(m_PreviousHelix); m_PreviousDivisions = Divisions; m_PreviousVScale = VScale; }
/// <summary> /// Display a wire ribbon handle. /// </summary> /// <returns>A new helix with any modifications applied.</returns> /// <param name="baseId">Base identifier. Each handle has its own unique hash based off this value.</param> /// <param name="helix">Helix.</param> /// <param name="origin">Origin.</param> /// <param name="orientation">Orientation.</param> /// <param name="scale">Scale.</param> /// <param name="color">Color.</param> public static Helix WireRibbon( int baseId, Helix helix, Vector3 origin, Quaternion orientation, Vector3 scale, Color color ) { return(DoHelixHandle( baseId, helix, origin, orientation, scale, color, s_SmoothDivisionCount, FillMode.Wire, ShapeMode.Ribbon )); }
/// <summary> /// Display a solid helix handle. /// </summary> /// <returns>A new helix with any modifications applied.</returns> /// <param name="baseId">Base identifier. Each handle has its own unique hash based off this value.</param> /// <param name="helix">Helix.</param> /// <param name="origin">Origin.</param> /// <param name="orientation">Orientation.</param> /// <param name="scale">Scale.</param> /// <param name="color">Color.</param> public static Helix SolidHelix( int baseId, Helix helix, Vector3 origin, Quaternion orientation, Vector3 scale, Color color ) { return(DoHelixHandle( baseId, helix, origin, orientation, scale, color, s_SmoothDivisionCount, FillMode.Solid, ShapeMode.Helix )); }
/// <summary> /// Creates the mesh ribbon. /// </summary> private void CreateMeshRibbon() { // validate data Divisions = Mathf.Max(Divisions, 1); // compute vertex positions, normals, and uv coordinates Vector3[] vertices = new Vector3[2 * (Divisions + 1)]; Vector3[] normals = new Vector3[vertices.Length]; Vector2[] uv = new Vector2[vertices.Length]; // cache divisions float oneHalfLengthOverDivisions = 0.5f * Helix.Length / Divisions; float oneHalfActualUVScale = 0.5f / VScale / Divisions; for (int i = 0; i < vertices.Length; i += 2) { // compute vertex positions float parameter = (float)(i) * oneHalfLengthOverDivisions; vertices[i] = Helix.Evaluate(parameter); vertices[i + 1] = Helix.EvaluateOpposite(parameter); // compute uvs uv[i] = new Vector2(1f, (float)(i) * oneHalfActualUVScale); uv[i + 1] = new Vector2(0f, uv[i].y); } // compute triangles int[] triangles = new int[Divisions * 6]; bool isEvenTriangle = true; int stepCount = 3; for (int i = 0; i < triangles.Length; i += stepCount) { int start = i / stepCount; if (isEvenTriangle) { triangles[i] = start; triangles[i + 1] = start + 1; triangles[i + 2] = start + 2; } else { triangles[i] = start + 2; triangles[i + 1] = start + 1; triangles[i + 2] = start; } isEvenTriangle = !isEvenTriangle; } // create the mesh MeshFilter.mesh.Clear(); MeshFilter.mesh.vertices = vertices; MeshFilter.mesh.normals = normals; MeshFilter.mesh.triangles = triangles; MeshFilter.mesh.uv = uv; MeshFilter.mesh.RecalculateNormals(); MeshFilter.mesh.RecalculateBounds(); // scale uvs if required if (AutoVScale) { RecalculateUVCoordinates(); } }
/// <summary> /// Initializes a new instance of the <see cref="Helix"/> class. /// </summary> /// <param name="helix">Helix.</param> /// <exception cref='System.ArgumentNullException'> /// Is thrown when a <see langword="null" /> Helix is passed. /// </exception> public Helix(Helix helix) { if (helix == null) { throw new System.ArgumentNullException("helix"); } else { this.Length = helix.Length; this.AutoSmoothCurveTangents = helix.AutoSmoothCurveTangents; Twist = helix.Twist; Width = helix.Width; } }
/// <summary> /// Recompute vertex positions, as when the helix changes. /// </summary> private void RecalculateVertexPositions() { Vector3[] vertices = MeshFilter.mesh.vertices; float oneHalfLengthOverDivisions = 0.5f * Helix.Length / Divisions; for (int i = 0; i < vertices.Length; i += 2) { float parameter = (float)(i) * oneHalfLengthOverDivisions; vertices[i] = Helix.Evaluate(parameter); vertices[i + 1] = Helix.EvaluateOpposite(parameter); } MeshFilter.mesh.vertices = vertices; if (AutoVScale) { RecalculateUVCoordinates(); } }
/// <summary> /// Display a helix handle. /// </summary> /// <returns>A new helix with any modifications applied.</returns> /// <param name="baseId">Base identifier. Each handle has its own unique hash based off this value.</param> /// <param name="helix">Helix.</param> /// <param name="origin">Origin.</param> /// <param name="orientation">Orientation.</param> /// <param name="scale">Scale.</param> /// <param name="color">Color.</param> /// <param name="divisions">Divisions.</param> /// <param name="fillMode">Fill mode.</param> /// <param name="shapeMode">Shape mode.</param> private static Helix DoHelixHandle( int baseId, Helix helix, Vector3 origin, Quaternion orientation, Vector3 scale, Color color, int divisions, FillMode fillMode, ShapeMode shapeMode ) { // early out if the scale is too small if (scale.sqrMagnitude < s_RequiredMinHandleChange) { Debug.LogWarning("Scale vector for helix handle is too close to 0. Handle is disabled"); return(null); } // create copy of helix helix = new Helix(helix); // store the handle matrix Matrix4x4 oldMatrix = Handles.matrix; Handles.matrix *= Matrix4x4.TRS(origin, orientation, scale); // set the helix handle's color Color oldColor = Handles.color; Handles.color = color; // validate the number of divisions divisions = Mathf.Max(1, divisions); // create a handle to adjust length DoHelixLengthHandle(baseId, helix); // next, create handles for the twist curve DoHelixTwistHandles(baseId, helix); // next, create handles for the width curve DoHelixWidthHandles(baseId, helix); // draw lines to visualize the helix DrawHelix(helix, divisions, fillMode, shapeMode); // reset handle color Handles.color = oldColor; // reset handle matrix Handles.matrix = oldMatrix; // return result return(helix); }
/// <summary> /// Displays the helix twist curve handles. /// </summary> /// <param name="baseId">Base identifier.</param> /// <param name="helix">Helix.</param> private static void DoHelixTwistHandles(int baseId, Helix helix) { float v; AnimationCurve width = helix.Width; AnimationCurve newTwist = new AnimationCurve(helix.Twist.keys); Keyframe[] newKeys = newTwist.keys; for (int i = 0; i < newKeys.Length; ++i) { // compute the disc center Vector3 discCenter = Vector3.forward * newKeys[i].time * helix.Length; v = newKeys[i].value; // create an arc handle float helixWidth = width.Evaluate(newKeys[i].time); v = ArcHandles.SolidAngle( ObjectX.GenerateHashCode(baseId, s_TwistHandleHash, i, 1), v, discCenter, Quaternion.LookRotation(Vector3.right, Vector3.forward), helixWidth, string.Format("{0:0} Degrees", v) ); Handles.DrawWireDisc(discCenter, Vector3.forward, helixWidth); newKeys[i].value = v; // create time adjustment handles for intermediary keys if (i == 0 || i == newKeys.Length - 1) { continue; } v = newKeys[i].time * helix.Length; int id = ObjectX.GenerateHashCode(baseId, s_TwistHandleHash, i, 2); v = LinearHandles.Cone(id, v, Vector3.zero, Vector3.forward, capScale: s_TimeAdjustHandleSize); v = Mathf.Clamp( v / helix.Length, Mathf.Epsilon * 2f, 1f - Mathf.Epsilon * 2f ); if (Mathf.Abs(v - newKeys[i].time) > s_RequiredMinHandleChange) { newKeys[i].time = v; } // TODO: Fix problems with keys moving over each other } newTwist.keys = newKeys; helix.Twist = newTwist; }
/// <summary> /// Display a helix length handle. /// </summary> /// <param name="baseId">Base identifier.</param> /// <param name="helix">Helix.</param> private static void DoHelixLengthHandle(int baseId, Helix helix) { float length = helix.Length; int id = ObjectX.GenerateHashCode(baseId, s_LengthHandleHash); length = LinearHandles.Arrow( id, length, Vector3.zero, Vector3.forward, string.Format("Length: {0:0.###}", length), s_LengthHandleSize ); if (Mathf.Abs(length - helix.Length) < s_RequiredMinHandleChange) { length = helix.Length; } helix.Length = length; }
/// <summary> /// Displays helix width handles. /// </summary> /// <param name="baseId">Base identifier.</param> /// <param name="helix">Helix.</param> private static void DoHelixWidthHandles(int baseId, Helix helix) { float v; AnimationCurve twist = helix.Twist; AnimationCurve newWidth = helix.Width; Keyframe[] newKeys = newWidth.keys; for (int i = 0; i < newKeys.Length; ++i) { // compute the disc center Vector3 discCenter = Vector3.forward * newKeys[i].time * helix.Length; // compute the angle of the handle around the length Quaternion angle = Quaternion.AngleAxis(twist.Evaluate(newKeys[i].time), Vector3.forward); // create a cone handle with a slight offset to not overlap the arc handle v = newKeys[i].value + s_WidthHandleOffset; int id = ObjectX.GenerateHashCode(baseId, s_WidthHandleHash, i, 1); v = LinearHandles.Cone(id, v, discCenter, angle * Vector3.right, capScale: s_WidthHandleSize); newKeys[i].value = v - s_WidthHandleOffset; // create time adjustment handles for intermediary keys if (i == 0 || i == newKeys.Length - 1) { continue; } v = newKeys[i].time * helix.Length; id = ObjectX.GenerateHashCode(baseId, s_WidthHandleHash, i, 2); v = LinearHandles.Cone(id, v, Vector3.zero, Vector3.forward, capScale: s_TimeAdjustHandleSize); v = Mathf.Clamp( v / helix.Length, Mathf.Epsilon * 2f, 1f - Mathf.Epsilon * 2f ); if (Mathf.Abs(v - newKeys[i].time) > s_RequiredMinHandleChange) { newKeys[i].time = v; } // TODO: Fix problems with keys moving over each other } newWidth.keys = newKeys; helix.Width = newWidth; }
/// <summary> /// Initialize /// </summary> void Start() { // create the mesh if (MeshFilter == null) { MeshFilter = gameObject.AddComponent <MeshFilter>(); } if (MeshFilter.mesh == null) { MeshFilter.mesh = new Mesh(); MeshFilter.mesh.name = name + " Helix Mesh Ribbon"; } CreateMeshRibbon(); if (AutoVScale) { AutoComputeVScale(); } // store the old values m_PreviousHelix = Helix; m_PreviousDivisions = Divisions; m_PreviousVScale = VScale; }
/// <summary> /// Tests if two helices are equivalent. /// </summary> /// <returns> /// <see langword="true"/> if all field values are equivalent; otherwise, <see langword="false"/>. /// </returns> /// <param name="h1">Helix 1.</param> /// <param name="h2">Helix 2.</param> public static bool AreEquivalent(Helix h1, Helix h2) { if (h1 == null) { return(h2 == null); } else if (h2 == null) { return(false); } else if ( h1.AutoSmoothCurveTangents == h2.AutoSmoothCurveTangents && h1.Length == h2.Length && h1.Twist.IsValueEqualTo(h2.Twist) && h1.Width.IsValueEqualTo(h2.Width) ) { return(true); } else { return(false); } }
public static Helix WireRibbon(Helix helix, Vector3 origin, Quaternion orientation, Vector3 scale, Color color) { return(WireRibbon(0, helix, origin, orientation, scale, color)); }
public static void SolidHelix(Helix helix, Vector3 origin, Quaternion orientation, Vector3 scale, Color color) { SolidHelix(0, helix, origin, orientation, scale, color); }
/// <summary> /// Draws the helix. /// </summary> /// <param name="helix">Helix.</param> /// <param name="divisions">Divisions.</param> /// <param name="fillMode">Fill mode.</param> /// <param name="shapeMode">Shape mode.</param> private static void DrawHelix(Helix helix, int divisions, FillMode fillMode, ShapeMode shapeMode) { // early out if not repaint phase if (Event.current.type != EventType.Repaint) { return; } // initialize mesh properties Vector3[] vertices = new Vector3[divisions * 2 + 2]; Vector2[] uv = new Vector2[vertices.Length]; Color[] colors = new Color[vertices.Length]; int[] triangles = new int[divisions * 12]; // compute mesh data bool isRibbon = shapeMode == ShapeMode.Ribbon; bool isSolid = fillMode != FillMode.Wire; bool isMesh = fillMode == FillMode.Mesh; float oneHalfLengthOverDivisions = 0.5f * helix.Length / divisions; for (int i = 0; i < vertices.Length; i += 2) { // current parameter value float parameter = (float)(i) * oneHalfLengthOverDivisions; // compute vertex vertices[i] = helix.Evaluate(parameter); // draw line if (i > 0) { Handles.DrawLine(vertices[i - 2], vertices[i]); } // compute corresponding vertex if (isRibbon) { vertices[i + 1] = helix.EvaluateOpposite(parameter); // draw opposite line if (i > 0) { Handles.DrawLine(vertices[i - 1], vertices[i + 1]); } } else { vertices[i + 1].z = vertices[i].z; } // draw connecting lines if requested if (isMesh) { Handles.DrawLine(vertices[i], vertices[i + 1]); } // populate colors colors[i] = Handles.color; colors[i].a *= SceneGUI.FillAlphaScalar; colors[i + 1] = colors[i]; } // compute triangles int stepCount = 6; for (int i = 0; i < triangles.Length; i += stepCount) { int start = i / stepCount; triangles[i] = start; triangles[i + 1] = start + 1; triangles[i + 2] = start + 2; triangles[i + 3] = start + 2; triangles[i + 4] = start + 1; triangles[i + 5] = start; } // update mesh if (s_FillMesh == null) { s_FillMesh = new Mesh(); s_FillMesh.hideFlags = HideFlags.DontSave; } s_FillMesh.Clear(); s_FillMesh.vertices = vertices; s_FillMesh.colors = colors; s_FillMesh.triangles = triangles; s_FillMesh.uv = uv; s_FillMesh.RecalculateNormals(); // draw the mesh if (isSolid) { Graphics.DrawMeshNow(s_FillMesh, Handles.matrix); } }