private float ShowMidpointControls(Ferr2DPath aPath, int i, PathEditorUtil.Selection aSelection, Matrix4x4 aTransform, SerializedProperty aPathProp, float aCurrDistance)
    {
        EventType eType = Event.current.type;

        // calculate distance data for finding the midpoint
        float segmentDistance = aPath.GetDistanceBetween(i, PathUtil.WrapIndex(i + 1, aPath.Count, aPath.Closed));
        float nextDistance    = aCurrDistance + segmentDistance;
        float midDist         = aCurrDistance + (segmentDistance) / 2;

        // if we have no segment on the control vert, skip this midpoint
        if (aPath.Count <= 1 || (i == aPath.Count - 1 && !aPath.Closed))
        {
            return(nextDistance);
        }

        // find the midpoint of this line
        Vector2 midPoint       = aPath.GetPointAtDistance(midDist);
        Vector2 midNormal      = aPath.GetNormalAtDistance(midDist);
        Vector3 midWorldPoint  = aTransform.MultiplyPoint(midPoint);
        Vector3 midWorldNormal = aTransform.MultiplyVector(midNormal);
        float   midSize        = eType == EventType.Layout || eType == EventType.Repaint ? HandleUtility.GetHandleSize(midWorldPoint) : 0;

        // edge override button
        int direction = aPath.GetData(i).directionOverride;

        if (terrain.EdgeMode != Ferr2D_SectionMode.None && (i == activeSegment || aSelection.IsSegmentSelected(i, aPath.Count, aPath.Closed) || direction != (int)Ferr2DT_TerrainDirection.None))
        {
            if (Event.current.alt)
            {
                if (direction != (int)Ferr2DT_TerrainDirection.None && Handles.Button(midWorldPoint + midWorldNormal * midSize * sizeLargeHandle * 2, Quaternion.identity, midSize * sizeSmallHandle, midSize * sizeSmallHandle, Ferr2DT_Caps.CapDotReset))
                {
                    aSelection.EachSegment(i, aPath.Count, aPath.Closed, id => {
                        SerializedProperty overrideProp = aPathProp.FindPropertyRelative("_data").GetArrayElementAtIndex(id).FindPropertyRelative("directionOverride");
                        overrideProp.intValue           = (int)Ferr2DT_TerrainDirection.None;
                    });
                }
            }
            else
            {
                if (Handles.Button(midWorldPoint + midWorldNormal * midSize * sizeLargeHandle * 2, Quaternion.identity, midSize * sizeSmallHandle, midSize * sizeSmallHandle, Ferr2DT_Caps.GetEdgeCap(direction)))
                {
                    CycleEdgeOverride(aPathProp, i);
                    SerializedProperty cycledOverride = aPathProp.FindPropertyRelative("_data").GetArrayElementAtIndex(i).FindPropertyRelative("directionOverride");
                    aSelection.EachSegment(i, aPath.Count, aPath.Closed, id => {
                        SerializedProperty overrideProp = aPathProp.FindPropertyRelative("_data").GetArrayElementAtIndex(id).FindPropertyRelative("directionOverride");
                        overrideProp.intValue           = cycledOverride.intValue;
                    });
                }
            }
        }

        // new point button
        if (!Event.current.alt && i == activeSegment && Handles.Button(midWorldPoint, Quaternion.identity, midSize * sizeLargeHandle, midSize * sizeLargeHandle, Ferr2DT_Caps.CapDotPlus))
        {
            PathEditorUtil.AddPoint(aPathProp, midPoint, i + 1);
            ProcessNewPoint(aPathProp, i + 1);
        }

        return(nextDistance);
    }
    private void  FindActiveControls(Ferr2DPath aPath, Matrix4x4 aTransform, Matrix4x4 aInvTransform)
    {
        if (Event.current.type == EventType.MouseMove)
        {
            Ray   r    = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
            Plane p    = new Plane(aTransform.MultiplyVector(Vector3.forward), aTransform.MultiplyPoint(Vector3.zero));
            float dist = 0;

            if (p.Raycast(r, out dist))
            {
                Vector2 mousePoint = aInvTransform.MultiplyPoint(r.GetPoint(dist));
                int     newSegment = aPath.GetClosestSegment(mousePoint);
                if (newSegment != activeSegment)
                {
                    activeSegment = newSegment;
                    SceneView.RepaintAll();
                }
                int newPoint = aPath.GetClosestControlPoint(mousePoint);
                if (newPoint != activePoint)
                {
                    activePoint = newPoint;
                    SceneView.RepaintAll();
                }
            }
        }
    }
    private void ShowPathGUI(Transform aTransform, Ferr2DPath aPath, SerializedProperty aPathProp)
    {
        EventType eType = Event.current.type;
        Matrix4x4 mat   = aTransform.localToWorldMatrix;

        // show the path and all its standard controls
        PathEditorUtil.OnSceneGUI(aTransform.localToWorldMatrix, aTransform.worldToLocalMatrix, aPathProp, aPath, true, ProcessNewPoint, ProcessRemovePoint, Path2D.Plane.XY, Ferr2DT_Menu.PathSnapMode, Ferr2DT_Menu.SmartSnap?Ferr2DT_Menu.SmartSnapDist:0, KeyCode.C, visuals);
        PathEditorUtil.Selection selection = PathEditorUtil.GetSelection(aPath);

        // find which segments and points are active
        FindActiveControls(aPath, mat, aTransform.worldToLocalMatrix);

        if (Ferr2DT_SceneOverlay.segmentLockMode)
        {
            ShowTexSegmentSelect(aPath, aPathProp, mat);
        }
        else
        {
            float currDistance = 0;
            for (int i = 0; i < aPath.Count; i++)
            {
                // show all the data at the control points
                ShowPointControls(aPath, i, selection, mat, aPathProp);

                // show midpoint controls
                currDistance = ShowMidpointControls(aPath, i, selection, mat, aPathProp, currDistance);
            }
        }

        // drag select has to happen last, otherwise it steals control ahead of the other handles
        ShowDragSelect(aPath, mat);
    }
    private void  ShowPointControls(Ferr2DPath aPath, int i, PathEditorUtil.Selection aSelection, Matrix4x4 aTransform, SerializedProperty aPathProp)
    {
        EventType eType = Event.current.type;

        Ferr2D_PointData pointData        = aPath.GetData(i);
        Vector2          point            = aPath[i];
        Vector2          pointNormal      = aPath.GetNormal(i);
        Vector3          pointWorld       = aTransform.MultiplyPoint(point);
        Vector3          pointWorldNormal = aTransform.MultiplyVector(pointNormal);
        float            pointSize        = eType == EventType.Layout || eType == EventType.Repaint ? HandleUtility.GetHandleSize(pointWorld) : 0;

        // point scale button
        if (terrain.EdgeMode != Ferr2D_SectionMode.None && (i == activePoint || aSelection.IsSelected(i) || pointData.scale != 1))
        {
            Vector3 scaleStart = pointWorld + pointWorldNormal * (visuals.sizeVertex + sizeSmallHandle) * pointSize;
            Vector3 scalePos   = scaleStart + ((pointData.scale - 0.5f) * 3f) * pointWorldNormal;
            if (Event.current.alt)
            {
                // reset scale
                if (pointData.scale != 1 && Handles.Button(scalePos, Quaternion.identity, pointSize * sizeSmallHandle, pointSize * sizeSmallHandle, Ferr2DT_Caps.CapDotReset))
                {
                    aSelection.Each(i, id => {
                        SerializedProperty scaleProp = aPathProp.FindPropertyRelative("_data").GetArrayElementAtIndex(id).FindPropertyRelative("scale");
                        scaleProp.floatValue         = 1;
                    });
                }
            }
            else
            {
                Vector2 screenNormal = HandleUtility.WorldToGUIPoint(pointWorld + pointWorldNormal) - HandleUtility.WorldToGUIPoint(pointWorld);
                screenNormal.Normalize();

                Vector3 move = Handles.FreeMoveHandle(scalePos, Quaternion.identity, pointSize * sizeSmallHandle, Vector3.zero, Ferr2DT_Caps.GetScaleCap(screenNormal));
                if (move != scalePos)
                {
                    move = PathUtil.GetClosetPointOnLine(scaleStart, scaleStart + pointWorldNormal * 3, move, true);
                    float finalScale = 0.5f + (scaleStart - move).magnitude / 3f;
                    float delta      = finalScale - pointData.scale;

                    aSelection.Each(i, id => {
                        SerializedProperty scaleProp = aPathProp.FindPropertyRelative("_data").GetArrayElementAtIndex(id).FindPropertyRelative("scale");
                        scaleProp.floatValue        += delta;
                    });
                }
            }
        }

        // show index numbers
        if (Ferr2DT_SceneOverlay.showIndices)
        {
            Vector2 pt = HandleUtility.WorldToGUIPoint(pointWorld - pointWorldNormal * pointSize * sizeLargeHandle * 2);
            Rect    r  = new Rect(pt.x - 15, pt.y - EditorGUIUtility.singleLineHeight * 0.5f, 30, EditorGUIUtility.singleLineHeight);
            Handles.BeginGUI();
            GUI.Label(r, i.ToString(), PathEditorUtil.CenteredShadowStyle);
            GUI.Label(r, i.ToString(), PathEditorUtil.CenteredLabelStyle);
            Handles.EndGUI();
        }
    }
    private void  ShowDragSelect(Ferr2DPath aPath, Matrix4x4 aTransform)
    {
        if (!Ferr2DT_Menu.DragSelect)
        {
            return;
        }

        // manually make a move widget, since we have to override Unity's widget
        Vector3 center = Vector3.zero;

        if (Tools.pivotMode == PivotMode.Center)
        {
            center = PathUtil.Average(aPath.GetPathRaw());
        }
        Vector3 newPos = Handles.DoPositionHandle(terrain.transform.position + center, Quaternion.identity) - center;

        if (newPos != terrain.transform.position)
        {
            Undo.RecordObject(terrain.transform, "Move Terrain Object");
            terrain.transform.position = newPos;
        }
        // execute the drag-select code
        PathEditorUtil.DoDragSelect(aTransform, aPath, new Rect(0, EditorGUIUtility.singleLineHeight, Screen.width, Screen.height - EditorGUIUtility.singleLineHeight), visuals);
    }
    public void LegacyUpgrade()
    {
        if (!IsLegacy)
        {
            return;
        }

                #if UNITY_EDITOR
        UnityEditor.Undo.RecordObject(gameObject, "Upgrade Ferr2D Terrain");
                #endif

        Ferr2D_Path oldPath = GetComponent <Ferr2D_Path>();
        MatchOverrides();

        // upgrade the path
        pathData        = new Ferr2DPath();
        pathData.Closed = oldPath.closed;
        for (int i = 0; i < oldPath.pathVerts.Count; i++)
        {
            int            next      = Ferr.PathUtil.WrapIndex(i - 1, Path.Count, Path.closed);
            Ferr.PointType pointType = Ferr.PointType.Sharp;
            if (smoothPath)
            {
                Ferr2DT_TerrainDirection prevSegmentDirection = Ferr2D_Path.GetDirection(Path.pathVerts, next, fill == Ferr2DT_FillMode.InvertedClosed, Path.closed, directionOverrides);
                Ferr2DT_TerrainDirection nextSegmentDirection = Ferr2D_Path.GetDirection(Path.pathVerts, i, fill == Ferr2DT_FillMode.InvertedClosed, Path.closed, directionOverrides);
                if (prevSegmentDirection == nextSegmentDirection)
                {
                    pointType = Ferr.PointType.Auto;
                }
            }

            Ferr2D_PointData data = new Ferr2D_PointData();
            data.scale             = vertScales[next];
            data.directionOverride = (int)directionOverrides[next];
            data.cutOverrides      = cutOverrides[next].data;
            pathData.Add(oldPath.pathVerts[i], data, pointType);
        }
        pathData.ReverseSelf();
        pathData.SetDirty();

        // remove old path values
        directionOverrides = null;
        cutOverrides       = null;
        vertScales         = null;

        // upgrade collider settings
        if (createCollider)
        {
            if (useEdgeCollider)
            {
                colliderMode = Ferr2D_ColliderMode.Edge2D;
            }
            else if (create3DCollider)
            {
                colliderMode = Ferr2D_ColliderMode.Mesh3D;
            }
            else
            {
                colliderMode = Ferr2D_ColliderMode.Polygon2D;
            }
        }
        else
        {
            colliderMode = Ferr2D_ColliderMode.None;
        }

        // upgrade the fill settings
        switch (fill)
        {
        case Ferr2DT_FillMode.None:
            edgeMode = Ferr2D_SectionMode.Normal;
            fillMode = Ferr2D_SectionMode.None; break;

        case Ferr2DT_FillMode.Closed:
            edgeMode = Ferr2D_SectionMode.Normal;
            fillMode = Ferr2D_SectionMode.Normal; break;

        case Ferr2DT_FillMode.InvertedClosed:
            edgeMode = Ferr2D_SectionMode.Invert;
            fillMode = Ferr2D_SectionMode.Invert; break;

        case Ferr2DT_FillMode.FillOnlyClosed:
            edgeMode = Ferr2D_SectionMode.None;
            fillMode = Ferr2D_SectionMode.Normal; break;

        case Ferr2DT_FillMode.Skirt:
            edgeMode = Ferr2D_SectionMode.Normal;
            fillMode = Ferr2D_SectionMode.Normal;
            useSkirt = true; break;

        case Ferr2DT_FillMode.FillOnlySkirt:
            edgeMode = Ferr2D_SectionMode.None;
            fillMode = Ferr2D_SectionMode.Normal;
            useSkirt = true; break;
        }

        isLegacy = false;

                #if UNITY_EDITOR
        UnityEditor.Undo.DestroyObjectImmediate(oldPath);
                #else
        Destroy(oldPath);
                #endif

        Build(true);

                #if UNITY_EDITOR
        UnityEditor.SceneView.RepaintAll();
                #endif
    }
    private void Start()
    {
        terrain    = GetComponent <Ferr2DT_PathTerrain>();
        ferr2DPath = terrain.PathData;
        List <Vector2> rawPath   = ferr2DPath.GetPathRaw();
        List <Vector2> finalPath = ferr2DPath.GetFinalPath();

        //List<FloorNode> floorNodeList = new List<FloorNode>(finalPath.Count);
        FloorNode[] floorNodeArray = new FloorNode[finalPath.Count];

        // Create Walk Nodes
        for (int i = 0; i < finalPath.Count; i++)
        {
            Ferr2DT_TerrainDirection dir = Ferr2DT_PathTerrain.GetSegmentDirection(ferr2DPath, i);
            if (dir == Ferr2DT_TerrainDirection.Top)
            {
                if (i == finalPath.Count - 1)
                {
                    floorNodeArray[i] = InstantiateFloorNode(finalPath[0], finalPath[i]);
                }
                else
                {
                    floorNodeArray[i] = InstantiateFloorNode(finalPath[i + 1], finalPath[i]);
                }

                floorNodeArray[i].owner = this;
                ownedFloorNodes.Add(floorNodeArray[i]);
            }
        }


        // Check if there are any consecutive walk nodes
        // if so, set them as neighbors
        for (int i = 1; i < floorNodeArray.Length; i++)
        {
            if (floorNodeArray[i] != null && floorNodeArray[i - 1] != null)
            {
                // found consecutive walk nodes
                // set their neighboringness with regards to their x-coordinate
                if (floorNodeArray[i - 1].gameObject.transform.position.x < floorNodeArray[i].gameObject.transform.position.x)
                {
                    floorNodeArray[i - 1].SetRightNeighbor(floorNodeArray[i]);
                    floorNodeArray[i].SetLeftNeighbor(floorNodeArray[i - 1]);
                }
                else
                {
                    floorNodeArray[i].SetRightNeighbor(floorNodeArray[i - 1]);
                    floorNodeArray[i - 1].SetLeftNeighbor(floorNodeArray[i]);
                }
            }
        }

        // the edge case
        if (floorNodeArray[0] != null && floorNodeArray[floorNodeArray.Length - 1] != null)
        {
            if (floorNodeArray[0].gameObject.transform.position.x < floorNodeArray[floorNodeArray.Length - 1].gameObject.transform.position.x)
            {
                floorNodeArray[0].SetRightNeighbor(floorNodeArray[floorNodeArray.Length - 1]);
                floorNodeArray[floorNodeArray.Length - 1].SetLeftNeighbor(floorNodeArray[0]);
            }
            else
            {
                floorNodeArray[floorNodeArray.Length - 1].SetRightNeighbor(floorNodeArray[0]);
                floorNodeArray[0].SetLeftNeighbor(floorNodeArray[floorNodeArray.Length - 1]);
            }
        }

        foreach (FloorNode w in floorNodeArray)
        {
            if (w != null)
            {
                w.CheckIntersections();
            }
        }
    }
    private void  ShowTexSegmentSelect(Ferr2DPath aPath, SerializedProperty aPathProp, Matrix4x4 aTransform)
    {
        // calculate segment information for displaying the handles correctly
        List <Ferr2DT_PathTerrain.EdgeSegment> segments = Ferr2DT_PathTerrain.EdgeSegment.CreateEdgeSegments(aPath, terrain.splitCorners);
        bool    invert = terrain.EdgeMode == Ferr2D_SectionMode.Invert;
        Vector2 upUV   = terrain.UnitsPerUV;

        for (int i = 0; i < segments.Count; i++)
        {
            // get our segment data, and see if we have any data to override with
            var edgeSegment = segments[i];
            edgeSegment.direction = invert ? Ferr2DT_PathTerrain.Invert(edgeSegment.direction) : edgeSegment.direction;
            var edgeData = terrain.TerrainMaterial.GetDescriptor(edgeSegment.direction);
            if (edgeData.BodyCount < 2)
            {
                continue;
            }

            bool  rightInner = edgeSegment.path.GetInteriorAngle(edgeSegment.start) > 180;
            float rightOff   = edgeData.GetRightCapOffset(invert? !rightInner:rightInner, upUV);

            // calculate all the texture segments present
            List <int> texSources  = new List <int>();
            float      scale       = 0;
            List <int> texSegments = terrain.CreateLineList(edgeSegment, out scale, texSources);

            // now go and add handles for all the texture segments
            for (int t = 0; t < texSegments.Count; t++)
            {
                // find handle location
                float   segDist = TexDistAtSegment(edgeSegment, edgeData, texSegments, t) * scale;
                Vector3 pt      = aTransform.MultiplyPoint(aPath.GetPointAtDistance(edgeSegment.startDistance - rightOff + segDist));
                float   size    = HandleUtility.GetHandleSize(pt);

                // figure out what our data says about this segment, if anything
                int cutPointId   = texSources[t * 2];
                int cutId        = texSources[t * 2 + 1];
                int cutValue     = 0;
                var cutOverrides = aPath.GetData(cutPointId).cutOverrides;
                if (cutOverrides != null && cutOverrides.Count > cutId)
                {
                    cutValue = cutOverrides[cutId];
                }

                // and execute the handles
                if (Event.current.alt)
                {
                    if (cutValue != 0 && Handles.Button(pt, Quaternion.identity, size * sizeSmallHandle, size * sizeSmallHandle, Ferr2DT_Caps.CapDotReset))
                    {
                        SerializedProperty cutProp        = aPathProp.FindPropertyRelative("_data").GetArrayElementAtIndex(cutPointId).FindPropertyRelative("cutOverrides");
                        SerializedProperty segCutProperty = cutProp.GetArrayElementAtIndex(cutId);
                        segCutProperty.intValue = 0;
                    }
                }
                else
                {
                    if (Handles.Button(pt, Quaternion.identity, size * sizeSmallHandle, size * sizeSmallHandle, Ferr2DT_Caps.GetNumberCap(cutValue)))
                    {
                        SerializedProperty cutProp = aPathProp.FindPropertyRelative("_data").GetArrayElementAtIndex(cutPointId).FindPropertyRelative("cutOverrides");
                        while (cutProp.arraySize <= cutId)
                        {
                            cutProp.arraySize += 1;
                            cutProp.GetArrayElementAtIndex(cutProp.arraySize - 1).intValue = 0;
                        }
                        while (cutProp.arraySize > texSegments.Count)
                        {
                            cutProp.arraySize--;
                        }
                        SerializedProperty segCutProperty = cutProp.GetArrayElementAtIndex(cutId);
                        segCutProperty.intValue = (segCutProperty.intValue + 1) % (edgeData.BodyCount + 1);
                    }
                }
            }
        }
    }