void UpdateMeshFromSplineGraph()
        {
            Mesh mesh = meshFilter.sharedMesh;

            if (mesh == null)
            {
                mesh = new Mesh();
            }

            if (radialEdgeCount <= 0)
            {
                // TODO: Cleanup case when radialEdgeCount == 0 due to user still editing into inspector field.
                // This should be handled with a delayed int field.
                return;
            }

            var splineGraph = (splineGraphComponent != null)
                ? splineGraphComponent.GetSplineGraph()
                : splineGraphManager.GetSplineGraph();

            // First, go through and compute the number of vertex ring subdivisions required to represent the spline graph based on curvature.
            Int16 vertexIsValidCount = splineGraph.ComputeVertexIsValidCount();
            Int16 edgeIsValidCount   = splineGraph.ComputeEdgeIsValidCount();

            int[] edgeSubdivisionIndex      = new int[splineGraph.edgePoolChildren.count];
            int[] edgeSubdivisionCount      = new int[splineGraph.edgePoolChildren.count];
            int   meshSubdivisionCountTotal = 0;
            int   meshRingCountTotal        = 0;


            for (Int16 edgeIndex = 0; edgeIndex < splineGraph.edgePoolChildren.count; ++edgeIndex)
            {
                DirectedEdge edge = splineGraph.edgePoolChildren.data[edgeIndex];
                if (edge.IsValid() == 0)
                {
                    continue;
                }

                float splineLength     = splineGraph.payload.edgeLengths.data[edgeIndex];
                int   subdivisionCount = math.max(1, (int)math.floor(subdivisionsPerMeter * splineLength + 0.5f));
                int   ringCount        = subdivisionCount + 1;

                edgeSubdivisionIndex[edgeIndex] = meshSubdivisionCountTotal;
                edgeSubdivisionCount[edgeIndex] = subdivisionCount;
                meshSubdivisionCountTotal      += subdivisionCount;
                meshRingCountTotal += ringCount;
            }

            // TODO: Calculate counts.
            int meshVertexCount = meshRingCountTotal * radialEdgeCount;

            Vector3[] vertices  = new Vector3[meshVertexCount];
            Vector2[] uvs       = new Vector2[meshVertexCount];
            Vector3[] normals   = new Vector3[meshVertexCount];
            Color[]   colors    = new Color[meshVertexCount];
            int[]     triangles = new int[meshVertexCount * 6];

            int meshVertexIndex   = 0;
            int meshTriangleIndex = 0;

            for (Int16 edgeIndex = 0; edgeIndex < splineGraph.edgePoolChildren.count; ++edgeIndex)
            {
                DirectedEdge edge = splineGraph.edgePoolChildren.data[edgeIndex];
                if (edge.IsValid() == 0)
                {
                    continue;
                }

                Int16 vertexIndexChild  = splineGraph.edgePoolChildren.data[edgeIndex].vertexIndex;
                Int16 vertexIndexParent = splineGraph.edgePoolParents.data[edgeIndex].vertexIndex;

                SplineMath.Spline spline       = splineGraph.payload.edgeParentToChildSplines.data[edgeIndex];
                float             splineLength = splineGraph.payload.edgeLengths.data[edgeIndex];

                quaternion rotationParent = splineGraph.payload.rotations.data[vertexIndexParent];
                quaternion rotationChild  = splineGraph.payload.rotations.data[vertexIndexChild];

                SplineMath.Spline splineLeash = splineGraph.payload.edgeParentToChildSplinesLeashes.data[edgeIndex];

                // Find neighboring edge (if it exists) for use in CSG style operation against current one to handle intersections in branching region.
                DirectedVertex vertexParent = splineGraph.vertices.data[vertexIndexParent];
                Debug.Assert(vertexParent.IsValid() == 1);

                DirectedVertex vertexChild = splineGraph.vertices.data[vertexIndexChild];
                Debug.Assert(vertexChild.IsValid() == 1);

                int edgeSubdivisionCurrentCount = edgeSubdivisionCount[edgeIndex];
                for (int s = 0; s <= edgeSubdivisionCurrentCount; ++s)
                {
                    float t = (float)s / (float)edgeSubdivisionCurrentCount;
                    // Debug.Log("s = " + s + ", sCount = " + edgeSubdivisionCurrentCount + ", t = " + t);

                    float vNormalized = t;
                    if (s > 0)
                    {
                        // Compute the relative distance we have traveled between our current edge ring and previous.
                        SplineMath.ComputeSplitAtT(out SplineMath.Spline s00, out SplineMath.Spline s01, spline, t);

                        vNormalized = SplineMath.ComputeLengthEstimate(s00, 1e-5f) / splineLength;
                    }

                    float3     positionOnSpline = SplineMath.EvaluatePositionFromT(spline, t);
                    quaternion rotationOnSpline = SplineMath.EvaluateRotationWithRollFromT(spline, rotationParent, rotationChild, t);
                    // float2 leashMaxOS = math.lerp(leashParent, leashChild, t);
                    float2 leashMaxOS = SplineMath.EvaluatePositionFromT(splineLeash, t).xy;

                    // Generate radial ring of triangles
                    if (s < edgeSubdivisionCurrentCount)
                    {
                        for (int v = 0, vLen = radialEdgeCount; v < vLen; ++v)
                        {
                            triangles[meshTriangleIndex + v * 6 + 0] = meshVertexIndex + ((v + 0) % radialEdgeCount) + (0 * radialEdgeCount);
                            triangles[meshTriangleIndex + v * 6 + 1] = meshVertexIndex + ((v + 0) % radialEdgeCount) + (1 * radialEdgeCount);
                            triangles[meshTriangleIndex + v * 6 + 2] = meshVertexIndex + ((v + 1) % radialEdgeCount) + (1 * radialEdgeCount);

                            triangles[meshTriangleIndex + v * 6 + 3] = meshVertexIndex + ((v + 1) % radialEdgeCount) + (1 * radialEdgeCount);
                            triangles[meshTriangleIndex + v * 6 + 4] = meshVertexIndex + ((v + 1) % radialEdgeCount) + (0 * radialEdgeCount);
                            triangles[meshTriangleIndex + v * 6 + 5] = meshVertexIndex + ((v + 0) % radialEdgeCount) + (0 * radialEdgeCount);
                        }

                        meshTriangleIndex += radialEdgeCount * 6;
                    }

                    // Generate radial ring of vertices.
                    // bool ringIntersectsSibling = false;
                    for (int v = 0; v < radialEdgeCount; ++v)
                    {
                        float  thetaNormalized = (float)v / (float)radialEdgeCount;
                        float  theta           = thetaNormalized * 2.0f * math.PI;
                        float2 vertexOffsetOS  = new float2(
                            math.cos(theta),
                            math.sin(theta)
                            ) * leashMaxOS;// * radius;

                        float3 vertexOffsetWS   = math.mul(rotationOnSpline, new float3(vertexOffsetOS, 0.0f));
                        float3 vertexPositionWS = positionOnSpline + vertexOffsetWS;

                        float vertexIntersectionSignedDistanceMin = float.MaxValue;
                        {
                            for (Int16 edgeIndexSibling = vertexParent.childHead; edgeIndexSibling != -1; edgeIndexSibling = splineGraph.edgePoolChildren.data[edgeIndexSibling].next)
                            {
                                DirectedEdge edgeSibling = splineGraph.edgePoolChildren.data[edgeIndexSibling];
                                Debug.Assert(edgeSibling.IsValid() == 1);

                                if (edgeIndexSibling == edgeIndex)
                                {
                                    // Ignore ourselves.
                                    continue;
                                }

                                // Found our sibling edge. Only use the first one, as we only currently support CSG against a single branch.
                                Int16 siblingVertexIndexChild = edgeSibling.vertexIndex;
                                Debug.Assert(siblingVertexIndexChild != -1);

                                Int16 siblingVertexIndexParent = splineGraph.edgePoolParents.data[edgeIndexSibling].vertexIndex;
                                Debug.Assert(siblingVertexIndexParent != -1);

                                SplineMath.Spline splineSibling         = splineGraph.payload.edgeParentToChildSplines.data[edgeIndexSibling];
                                SplineMath.Spline splineLeashSibling    = splineGraph.payload.edgeParentToChildSplinesLeashes.data[edgeIndexSibling];
                                quaternion        siblingRotationParent = splineGraph.payload.rotations.data[siblingVertexIndexParent];
                                quaternion        siblingRotationChild  = splineGraph.payload.rotations.data[siblingVertexIndexChild];

                                SplineMath.FindTFromClosestPointOnSpline(out float siblingClosestT, out float siblingClosestDistance, vertexPositionWS, splineSibling);

                                float3     siblingPositionOnSpline = SplineMath.EvaluatePositionFromT(splineSibling, siblingClosestT);
                                quaternion siblingRotationOnSpline = SplineMath.EvaluateRotationWithRollFromT(splineSibling, siblingRotationParent, siblingRotationChild, siblingClosestT);
                                float2     siblingLeashMaxOS       = SplineMath.EvaluatePositionFromT(splineLeashSibling, siblingClosestT).xy;

                                float vertexIntersectionSignedDistance = math.length(math.mul(math.inverse(siblingRotationOnSpline), vertexPositionWS - siblingPositionOnSpline).xy / siblingLeashMaxOS) - 1.0f;
                                vertexIntersectionSignedDistanceMin = math.min(vertexIntersectionSignedDistanceMin, vertexIntersectionSignedDistance);
                            }

                            for (Int16 edgeIndexSibling = vertexChild.parentHead; edgeIndexSibling != -1; edgeIndexSibling = splineGraph.edgePoolParents.data[edgeIndexSibling].next)
                            {
                                DirectedEdge edgeSibling = splineGraph.edgePoolChildren.data[edgeIndexSibling];
                                Debug.Assert(edgeSibling.IsValid() == 1);

                                if (edgeIndexSibling == edgeIndex)
                                {
                                    // Ignore ourselves.
                                    continue;
                                }

                                // Found our sibling edge. Only use the first one, as we only currently support CSG against a single branch.
                                Int16 siblingVertexIndexParent = edgeSibling.vertexIndex;
                                Debug.Assert(siblingVertexIndexParent != -1);

                                Int16 siblingVertexIndexChild = splineGraph.edgePoolChildren.data[edgeIndexSibling].vertexIndex;
                                Debug.Assert(siblingVertexIndexChild != -1);

                                SplineMath.Spline splineSibling         = splineGraph.payload.edgeParentToChildSplines.data[edgeIndexSibling];
                                SplineMath.Spline splineLeashSibling    = splineGraph.payload.edgeParentToChildSplinesLeashes.data[edgeIndexSibling];
                                quaternion        siblingRotationParent = splineGraph.payload.rotations.data[siblingVertexIndexParent];
                                quaternion        siblingRotationChild  = splineGraph.payload.rotations.data[siblingVertexIndexChild];

                                SplineMath.FindTFromClosestPointOnSpline(out float siblingClosestT, out float siblingClosestDistance, vertexPositionWS, splineSibling);

                                float3     siblingPositionOnSpline = SplineMath.EvaluatePositionFromT(splineSibling, siblingClosestT);
                                quaternion siblingRotationOnSpline = SplineMath.EvaluateRotationWithRollFromT(splineSibling, siblingRotationParent, siblingRotationChild, siblingClosestT);
                                float2     siblingLeashMaxOS       = SplineMath.EvaluatePositionFromT(splineLeashSibling, siblingClosestT).xy;

                                float vertexIntersectionSignedDistance = math.length(math.mul(math.inverse(siblingRotationOnSpline), vertexPositionWS - siblingPositionOnSpline).xy / siblingLeashMaxOS) - 1.0f;
                                vertexIntersectionSignedDistanceMin = math.min(vertexIntersectionSignedDistanceMin, vertexIntersectionSignedDistance);
                            }
                        }

                        vertices[meshVertexIndex] = vertexPositionWS;
                        uvs[meshVertexIndex]      = new float2(
                            thetaNormalized,
                            vNormalized * splineLength * uvScale // TODO: Gotta figure out propogation of UVs.
                            );
                        normals[meshVertexIndex] = math.normalize(vertexOffsetWS);

                        float vertexIntersectionDistanceFade = math.smoothstep(vertexIntersectionSignedDistanceFadeMin, vertexIntersectionSignedDistanceFadeMax, vertexIntersectionSignedDistanceMin);
                        colors[meshVertexIndex] = new Color(vertexIntersectionDistanceFade, vertexIntersectionDistanceFade, vertexIntersectionDistanceFade, vertexIntersectionDistanceFade);

                        ++meshVertexIndex;
                    }
                }
            }

            // Trim arrays to final size.
            Vector3[] verticesTrimmed  = new Vector3[meshVertexIndex];
            Vector2[] uvsTrimmed       = new Vector2[meshVertexIndex];
            Vector3[] normalsTrimmed   = new Vector3[meshVertexIndex];
            Color[]   colorsTrimmed    = new Color[meshVertexIndex];
            int[]     trianglesTrimmed = new int[meshTriangleIndex];

            Array.Copy(vertices, verticesTrimmed, meshVertexIndex);
            Array.Copy(uvs, uvsTrimmed, meshVertexIndex);
            Array.Copy(normals, normalsTrimmed, meshVertexIndex);
            Array.Copy(colors, colorsTrimmed, meshVertexIndex);
            Array.Copy(triangles, trianglesTrimmed, meshTriangleIndex);

            // Finally assign back data (causes GC allocs).
            mesh.Clear();
            mesh.vertices         = verticesTrimmed;
            mesh.uv               = uvsTrimmed;
            mesh.normals          = normalsTrimmed;
            mesh.colors           = colorsTrimmed;
            mesh.triangles        = trianglesTrimmed;
            meshFilter.sharedMesh = mesh;

            MeshCollider meshCollider = meshFilter.gameObject.GetComponent <MeshCollider>();

            if (meshCollider != null)
            {
                meshCollider.sharedMesh = mesh;
            }
        }
 public WeighedDirectedEdge(DirectedVertex <T> source, DirectedVertex <T> target, int weight = 1)
     : base(source, target)
 {
     Weight = weight;
 }
        public void VertexWeldAllRedundant()
        {
            Verify();

            for (Int16 v0 = 0, v0Count = (Int16)splineGraph.vertices.count; v0 < v0Count; ++v0)
            {
                DirectedVertex vertex0 = splineGraph.vertices.data[v0];
                if (vertex0.IsValid() == 0)
                {
                    continue;
                }

                // For now, just going to handle the easy, and probably majority case of a spline that looks like:
                // vertex0 -------------------> vertex0Child -------------------> vertex0ChildChild
                // where vertex0, and vertex0Child only have 1 child and vertex0 -> vertex0Child -> vertex0ChildChild describes a straight line.

                Int16 vertex0ChildCount = splineGraph.VertexComputeChildCount(v0);
                if (vertex0ChildCount != 1)
                {
                    continue;
                }

                Int16 vertex0ChildIndex = splineGraph.edgePoolChildren.data[vertex0.childHead].vertexIndex;
                Debug.Assert(vertex0ChildIndex >= 0 && vertex0ChildIndex < v0Count);
                DirectedVertex vertex0Child = splineGraph.vertices.data[vertex0ChildIndex];
                Debug.Assert(vertex0Child.IsValid() == 1);

                Int16 vertex0ChildChildCount = splineGraph.VertexComputeChildCount(vertex0ChildIndex);
                if (vertex0ChildChildCount != 1)
                {
                    continue;
                }
                Int16 vertex0ChildParentCount = splineGraph.VertexComputeParentCount(vertex0ChildIndex);
                if (vertex0ChildParentCount != 1)
                {
                    continue;
                }

                // We have now verified that vertex0 -> vertex0Child -> vertex0ChildChild describe a single path.
                // Now need to verify that this path is a straight line.
                Int16 vertex0ChildChildIndex = splineGraph.edgePoolChildren.data[vertex0Child.childHead].vertexIndex;
                Debug.Assert(vertex0ChildChildIndex >= 0 && vertex0ChildChildIndex < v0Count);
                DirectedVertex vertex0ChildChild = splineGraph.vertices.data[vertex0ChildChildIndex];
                Debug.Assert(vertex0ChildChild.IsValid() == 1);

                float3 vertex0Position           = splineGraph.payload.positions.data[v0];
                float3 vertex0ChildPosition      = splineGraph.payload.positions.data[vertex0ChildIndex];
                float3 vertex0ChildChildPosition = splineGraph.payload.positions.data[vertex0ChildChildIndex];

                float3 vertex0ChildFromVertex0Direction           = math.normalize(vertex0ChildPosition - vertex0Position);
                float3 vertex0ChildChildFromVertex0Direction      = math.normalize(vertex0ChildChildPosition - vertex0Position);
                float3 vertex0ChildChildFromVertex0ChildDirection = math.normalize(vertex0ChildChildPosition - vertex0ChildPosition);

                // Note: Mathematically, we only need to actually check the first of these two dot products.
                // For precision reasons with large distances between vertices, we check both to be extra conservative.
                if (math.dot(vertex0ChildFromVertex0Direction, vertex0ChildChildFromVertex0Direction) < 0.9999f ||
                    math.dot(vertex0ChildFromVertex0Direction, vertex0ChildChildFromVertex0ChildDirection) < 0.9999f)
                {
                    // Positions alone do not line up. Cannot be a linear path:
                    //
                    // vertex0 ------------> vertexChild0 -
                    //                                      \
                    //                                       \
                    //                                        \
                    //                                 vertex0ChildChild
                    continue;
                }

                quaternion vertex0Rotation           = splineGraph.payload.rotations.data[v0];
                quaternion vertex0ChildRotation      = splineGraph.payload.rotations.data[vertex0ChildIndex];
                quaternion vertex0ChildChildRotation = splineGraph.payload.rotations.data[vertex0ChildChildIndex];

                float3 vertex0Forward           = math.mul(vertex0Rotation, new float3(0.0f, 0.0f, 1.0f));
                float3 vertex0ChildForward      = math.mul(vertex0ChildRotation, new float3(0.0f, 0.0f, 1.0f));
                float3 vertex0ChildChildForward = math.mul(vertex0ChildChildRotation, new float3(0.0f, 0.0f, 1.0f));

                if (math.dot(vertex0Forward, vertex0ChildForward) < 0.999f)
                {
                    // Not pointing in the same direction. Cannot be a linear path.
                    continue;
                }

                if (math.dot(vertex0Forward, vertex0ChildChildForward) < 0.999f)
                {
                    // Not pointing in the same direction. Cannot be a linear path.
                    continue;
                }

                if (math.abs(math.dot(vertex0Forward, vertex0ChildFromVertex0Direction)) < 0.999f)
                {
                    // Vertices not pointing directly toward eachother. Cannot be a linear path.
                    continue;
                }

                float2 vertex0LeashOS      = splineGraph.payload.leashes.data[v0];
                float2 vertex0ChildLeashOS = splineGraph.payload.leashes.data[vertex0ChildIndex];
                float3 vertex0LeashWS      = math.mul(vertex0Rotation, new float3(vertex0LeashOS, 0.0f));
                float3 vertex0ChildLeashWS = math.mul(vertex0Rotation, new float3(vertex0ChildLeashOS, 0.0f));
                if (math.any(math.abs(vertex0LeashWS - vertex0ChildLeashWS) > 1e-3f))
                {
                    // Leash is not identical, cannot be a linear path.
                    continue;
                }

                float2 vertex0ChildChildLeashOS = splineGraph.payload.leashes.data[vertex0ChildChildIndex];
                float3 vertex0ChildChildLeashWS = math.mul(vertex0ChildChildRotation, new float3(vertex0ChildChildLeashOS, 0.0f));
                if (math.any(math.abs(vertex0ChildLeashWS - vertex0ChildChildLeashWS) > 1e-3f))
                {
                    // Leash is not identical, cannot be a linear path.
                    continue;
                }

                // Found a linear path!
                // Note, we do not actually care about a scale differences.
                // All we care about is making sure scaleIn at vertex0 is maintained, and scaleOut at vertex0ChildChildRotation is maintained.
                // This will automatically happen just by merging vertex0Child into vertex0.
                splineGraph.VertexMerge(v0, vertex0ChildIndex, Allocator.Persistent);

                // Need to set the dirty flag here because the call to Verify() above cleared any dirty flags that were possibly set by UndoRecord()
                // and we have just changed our runtime data representation via welding.
                isDirty = true;

                // Since we merged, we can stay at v0 and test again to see if there is a new merge case.
                --v0;
            }
        }
        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            var sgm = target as SplineGraphManager;

            sgm.Verify();

            EditorGUILayout.BeginVertical();

            bool isEditingEnabledNew = EditorGUILayout.Toggle("Is Editing Enabled", sgm.isEditingEnabled);

            if (isEditingEnabledNew != sgm.isEditingEnabled)
            {
                sgm.UndoRecord("Toggled Spline Graph Manager isEditingEnabled");
                sgm.isEditingEnabled = isEditingEnabledNew;
            }
            if (!sgm.isEditingEnabled)
            {
                EditorGUILayout.EndVertical();
                return;
            }

            bool isRenderingEnabledNew = EditorGUILayout.Toggle("Is Rendering Enabled", sgm.isRenderingEnabled);

            if (isRenderingEnabledNew != sgm.isRenderingEnabled)
            {
                sgm.UndoRecord("Toggled Spline Graph Manager isRenderingEnabled");
                sgm.isRenderingEnabled = isRenderingEnabledNew;
            }

            bool isAutoUpdateEnabledNew = EditorGUILayout.Toggle("Is Auto Update Enabled", sgm.isAutoUpdateEnabled);

            if (isAutoUpdateEnabledNew != sgm.isAutoUpdateEnabled)
            {
                sgm.UndoRecord("Toggled Spline Graph Manager isAutoUpdateEnabled");
                sgm.isAutoUpdateEnabled = isAutoUpdateEnabledNew;
            }

            // return; // TODO: Remove?

            sgm.debugIsSpawnEnabled = EditorGUILayout.Toggle("Debug Is Spawn Enabled", sgm.debugIsSpawnEnabled);
            sgm.debugPosition       = EditorGUILayout.Vector3Field("Debug Position", sgm.debugPosition);
            sgm.debugVelocity       = EditorGUILayout.FloatField("Debug Velocity", sgm.debugVelocity);
            sgm.debugIsReverse      = EditorGUILayout.Toggle("Debug Is Reverse", sgm.debugIsReverse);

            int typeNew = EditorGUILayout.DelayedIntField("Type", sgm.type);

            typeNew = math.clamp(typeNew, 0, int.MaxValue);
            if (typeNew != sgm.type)
            {
                sgm.UndoRecord("Edited Spline Graph Manager Type");
                sgm.type = typeNew;
            }

            if (GUILayout.Button("Build Graph From Instances"))
            {
                sgm.UndoRecord("Spline Graph Manager Build Graph From Instances");

                sgm.BuildGraphFromInstances();
            }

            if (!sgm.isRenderingEnabled)
            {
                EditorGUILayout.EndVertical();
                return;
            }

            for (Int16 v = 0, vCount = (Int16)sgm.splineGraph.vertices.count; v < vCount; ++v)
            {
                DirectedVertex vertex = sgm.splineGraph.vertices.data[v];
                if (vertex.IsValid() == 0)
                {
                    continue;
                }

                EditorGUILayout.BeginVertical();

                // TODO: Convert these input fields to read-only fields.
                float3 position = sgm.splineGraph.payload.positions.data[v];
                EditorGUILayout.Vector3Field("Position", position);

                quaternion rotation             = sgm.splineGraph.payload.rotations.data[v];
                float3     rotationEulerDegrees = ((Quaternion)rotation).eulerAngles;
                EditorGUILayout.Vector3Field("Rotation", rotationEulerDegrees);

                float2 scale = sgm.splineGraph.payload.scales.data[v];
                EditorGUILayout.Vector2Field("Scale", scale);

                float2 leash = sgm.splineGraph.payload.leashes.data[v];
                EditorGUILayout.Vector2Field("Leash", leash);

                EditorGUILayout.EndVertical();
            }

            EditorGUILayout.EndVertical();

            serializedObject.ApplyModifiedProperties();

            UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
        }
        public void VertexWeldAllWithinThreshold()
        {
            Verify();

            for (Int16 v0 = 0, v0Count = (Int16)splineGraph.vertices.count; v0 < v0Count; ++v0)
            {
                DirectedVertex vertex0 = splineGraph.vertices.data[v0];
                if (vertex0.IsValid() == 0)
                {
                    continue;
                }

                float3     positionParent = splineGraph.payload.positions.data[v0];
                quaternion rotationParent = splineGraph.payload.rotations.data[v0];
                float2     scaleParent    = splineGraph.payload.scales.data[v0];
                float2     leashParent    = splineGraph.payload.leashes.data[v0];

                for (Int16 v1 = (Int16)(v0 + 1); v1 < v0Count; ++v1)
                {
                    DirectedVertex vertex1 = splineGraph.vertices.data[v1];
                    if (vertex1.IsValid() == 0)
                    {
                        continue;
                    }

                    float3     positionChild = splineGraph.payload.positions.data[v1];
                    quaternion rotationChild = splineGraph.payload.rotations.data[v1];
                    float2     scaleChild    = splineGraph.payload.scales.data[v1];
                    float2     leashChild    = splineGraph.payload.leashes.data[v1];

                    float positionDelta2 = math.lengthsq(positionParent - positionChild);

                    float positionMagnitude = math.max(math.cmax(math.abs(positionParent)), math.cmax(math.abs(positionChild)));

                    float leashDelta2 = math.lengthsq(leashParent - leashChild);

                    float epsilon = 1e-2f;//(positionMagnitude < 2.0f) ? 1e-5f : (math.log2(positionMagnitude) * 1e-5f);

                    if (positionDelta2 > (epsilon * epsilon))
                    {
                        continue;
                    }
                    if (leashDelta2 > (epsilon * epsilon))
                    {
                        continue;
                    }
                    if (!(math.any(math.abs(rotationParent.value - rotationChild.value) < 1e-2f) ||
                          math.any(math.abs(rotationParent.value + rotationChild.value) < 1e-2f)))
                    {
                        // Quaternions have double coverage so need to compare component wise equivalence for positive and negative operand b.
                        Debug.Log("Failed to weld because of rotation: {" + rotationParent.value.x + ", " + rotationParent.value.y + ", " + rotationParent.value.z + ", " + rotationParent.value.w + "} and {" + rotationChild.value.x + ", " + rotationChild.value.y + ", " + rotationChild.value.z + ", " + rotationChild.value.w + "}");

                        float3 forwardParent = math.mul(rotationParent, new float3(0.0f, 0.0f, 1.0f));
                        float3 forwardChild  = math.mul(rotationChild, new float3(0.0f, 0.0f, 1.0f));
                        Debug.Log("Failed to weld forward: {" + forwardParent.x + ", " + forwardParent.y + ", " + forwardParent.z + "} and {" + forwardChild.x + ", " + forwardChild.y + ", " + forwardChild.z + "}");

                        float3 tangentParent = math.mul(rotationParent, new float3(1.0f, 0.0f, 0.0f));
                        float3 tangentChild  = math.mul(rotationChild, new float3(1.0f, 0.0f, 0.0f));
                        Debug.Log("Failed to weld tangent: {" + tangentParent.x + ", " + tangentParent.y + ", " + tangentParent.z + "} and {" + tangentChild.x + ", " + tangentChild.y + ", " + tangentChild.z + "}");

                        float3 bitangentParent = math.mul(rotationParent, new float3(0.0f, 1.0f, 0.0f));
                        float3 bitangentChild  = math.mul(rotationChild, new float3(0.0f, 1.0f, 0.0f));
                        Debug.Log("Failed to weld bitangent: {" + bitangentParent.x + ", " + bitangentParent.y + ", " + bitangentParent.z + "} and {" + bitangentChild.x + ", " + bitangentChild.y + ", " + bitangentChild.z + "}");

                        continue;
                    }

                    // Average vertex payload data:
                    splineGraph.payload.positions.data[v0] = (positionParent * 0.5f + positionChild * 0.5f);
                    splineGraph.payload.rotations.data[v0] = math.slerp(rotationParent, rotationChild, 0.5f);

                    // Weighted average vertex payload scale and leash data.
                    {
                        float v0ParentCount = (float)splineGraph.VertexComputeParentCount(v0);
                        float v0ChildCount  = (float)splineGraph.VertexComputeChildCount(v0);
                        float v1ParentCount = (float)splineGraph.VertexComputeParentCount(v1);
                        float v1ChildCount  = (float)splineGraph.VertexComputeChildCount(v1);

                        float scaleX = ((v0ParentCount + v1ParentCount) > 0.0f)
                            ? ((splineGraph.payload.scales.data[v0].x * v0ParentCount + splineGraph.payload.scales.data[v1].x * v1ParentCount) / (v0ParentCount + v1ParentCount))
                            : (splineGraph.payload.scales.data[v0].x);
                        float scaleY = ((v0ChildCount + v1ChildCount) > 0.0f)
                            ? ((splineGraph.payload.scales.data[v0].y * v0ChildCount + splineGraph.payload.scales.data[v1].y * v1ChildCount) / (v0ChildCount + v1ChildCount))
                            : (splineGraph.payload.scales.data[v0].y);
                        splineGraph.payload.scales.data[v0] = new float2(scaleX, scaleY);

                        float leashX = ((v0ParentCount + v1ParentCount) > 0.0f)
                            ? ((splineGraph.payload.leashes.data[v0].x * v0ParentCount + splineGraph.payload.leashes.data[v1].x * v1ParentCount) / (v0ParentCount + v1ParentCount))
                            : (splineGraph.payload.leashes.data[v0].x);
                        float leashY = ((v0ChildCount + v1ChildCount) > 0.0f)
                            ? ((splineGraph.payload.leashes.data[v0].y * v0ChildCount + splineGraph.payload.leashes.data[v1].y * v1ChildCount) / (v0ChildCount + v1ChildCount))
                            : (splineGraph.payload.leashes.data[v0].y);
                        splineGraph.payload.leashes.data[v0] = new float2(leashX, leashY);
                    }
                    splineGraph.VertexMerge(v0, v1, Allocator.Persistent);

                    // Need to set the dirty flag here because the call to Verify() above cleared any dirty flags that were possibly set by UndoRecord()
                    // and we have just changed our runtime data representation via welding.
                    isDirty = true;
                }
            }
        }