Exemplo n.º 1
0
 void _PickSector(SECTR_Portal myPortal)
 {
     HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
     if (Event.current.type == EventType.MouseMove)
     {
         _ComputeCursorVert();
     }
     else if (Event.current.type == EventType.MouseUp && Event.current.button == 0 && !Event.current.alt && !Event.current.control)
     {
         SECTR_Sector sector = _GetSectorFromSelection();
         if (sector)
         {
             SECTR_Undo.Record(myPortal, "Assign Sector to Portal.");
             if (pickBack)
             {
                 myPortal.BackSector = sector;
             }
             else
             {
                 myPortal.FrontSector = sector;
             }
             EditorUtility.SetDirty(myPortal);
             pickFront = false;
             pickBack  = false;
             _EndSelection();
         }
     }
     else if (Event.current.type == EventType.KeyUp && Event.current.keyCode == KeyCode.Escape)
     {
         pickBack  = false;
         pickFront = false;
         _EndSelection();
     }
 }
Exemplo n.º 2
0
    protected static GameObject CreateTriggerFromPortal(SECTR_Portal portal, string newName)
    {
        GameObject newGameObject = null;

        if (portal)
        {
            newGameObject = new GameObject(newName);
            BoxCollider newCollider = newGameObject.AddComponent <BoxCollider>();
            newCollider.isTrigger = true;

            newGameObject.transform.position = portal.transform.position;
            newGameObject.transform.rotation = portal.transform.rotation;
            Vector3 newSize = Vector3.Scale(portal.HullMesh ? portal.HullMesh.bounds.size : Vector3.one, portal.transform.lossyScale);
            float   maxSize = Mathf.Max(newSize.x, Mathf.Max(newSize.y, newSize.z));
            if (Mathf.Abs(newSize.x) < 0.001f)
            {
                newSize.x = maxSize;
            }
            else if (Mathf.Abs(newSize.y) < 0.001f)
            {
                newSize.y = maxSize;
            }
            else
            {
                newSize.z = maxSize;
            }
            newCollider.size = newSize;
        }
        return(newGameObject);
    }
    public static void CreateTriggerLoader()
    {
        string       newObjectName = "SECTR Trigger Loader";
        string       undoName      = "Created " + newObjectName;
        GameObject   newGameObject;
        SECTR_Portal selectedPortal = Selection.activeGameObject ? Selection.activeGameObject.GetComponent <SECTR_Portal>() : null;

        if (selectedPortal)
        {
            newGameObject = CreateTriggerFromPortal(selectedPortal, newObjectName);
            SECTR_TriggerLoader newLoader = newGameObject.AddComponent <SECTR_TriggerLoader>();
            if (selectedPortal.FrontSector)
            {
                newLoader.Sectors.Add(selectedPortal.FrontSector);
            }
            if (selectedPortal.BackSector)
            {
                newLoader.Sectors.Add(selectedPortal.BackSector);
            }
        }
        else
        {
            newGameObject = CreateGameObject(newObjectName);
            BoxCollider newCollider = newGameObject.AddComponent <BoxCollider>();
            newCollider.isTrigger = true;
            newGameObject.AddComponent <SECTR_TriggerLoader>();
        }

        SECTR_Undo.Created(newGameObject, undoName);
        Selection.activeGameObject = newGameObject;
    }
 /// Informs the Sector that a Portal is connected into it.
 /// Should only be called by SECTR_Portal.
 public void Register(SECTR_Portal portal)
 {
     // Should this be an assert? Probably?
     if (!portals.Contains(portal))
     {
         portals.Add(portal);
     }
 }
Exemplo n.º 5
0
    void _DrawViewportGUI(SECTR_Portal myPortal)
    {
        Handles.BeginGUI();

        float width = 400;

        if (pickBack || pickFront)
        {
            float height = 100;
            GUI.Box(new Rect((Screen.width * 0.5f) - (width * 0.5f), Screen.height - height, width, height),
                    "Selecting " + (pickBack ? "back" : "front") + " Sector of " + myPortal.name + ".\n" +
                    (_GetSectorFromSelection() != null ? "Left Click to select." : "") + "\nEsc to cancel.", boxStyle);
        }
        else if (createHull || myPortal.ForceEditHull)
        {
            float  height     = 100;
            string returnText = "";
            if (newHullVerts.Count >= 3)
            {
                returnText = "Return to complete.";
            }
            else if (newHullVerts.Count == 0 && myPortal.ForceEditHull)
            {
                returnText = "Return to create empty portal.";
            }
            GUI.Box(new Rect((Screen.width * 0.5f) - (width * 0.5f), Screen.height - height, width, height),
                    "Drawing geometry for " + myPortal.name + ".\n" +
                    (closesetVertIsValid ? "Left Click to add vert. " : "") + returnText + "\nEsc to cancel.",
                    boxStyle);
        }
        else if (Selection.gameObjects.Length == 1)
        {
            float height = 100;
            GUILayout.BeginArea(new Rect((Screen.width * 0.5f) - (width * 0.5f), Screen.height - height, width, height));
            if (GUILayout.Button(new GUIContent(myPortal.HullMesh ? "Redraw Portal" : "Draw Portal", "Lets you (re)create the geometry for this Portal"), buttonStyle))
            {
                createHull = true;
            }

            GUILayout.BeginHorizontal();
            if (GUILayout.Button(new GUIContent("Pick Front Sector", "Provides an in-viewport interface for picking the front Sector."), buttonStyle))
            {
                pickFront = true;
            }
            if (GUILayout.Button(new GUIContent("Swap Sectors", "Swaps the front and back Sectors, in case they are backwards."), buttonStyle))
            {
                _SwapSectors(myPortal);
            }
            if (GUILayout.Button(new GUIContent("Pick Back Sector", "Provides an in-viewport interface for picking the front Sector."), buttonStyle))
            {
                pickBack = true;
            }
            GUILayout.EndHorizontal();

            GUILayout.EndArea();
        }
        Handles.EndGUI();
    }
Exemplo n.º 6
0
    void _SwapSectors(SECTR_Portal myPortal)
    {
        SECTR_Undo.Record(myPortal, "Swap Portal Sectors");
        SECTR_Sector oldFront = myPortal.FrontSector;
        SECTR_Sector oldBack  = myPortal.BackSector;

        myPortal.FrontSector = null;
        myPortal.BackSector  = null;
        myPortal.FrontSector = oldBack;
        myPortal.BackSector  = oldFront;
        EditorUtility.SetDirty(myPortal);
    }
Exemplo n.º 7
0
    public static void CreatePortal()
    {
        string       newName       = "SECTR Portal";
        string       undoName      = "Create " + newName;
        GameObject   newGameObject = CreateGameObject(newName);
        SECTR_Portal newPortal     = newGameObject.AddComponent <SECTR_Portal>();

        newPortal.ForceEditHull = true;
        newPortal.CenterOnEdit  = true;
        SECTR_Undo.Created(newGameObject, undoName);
        Selection.activeGameObject = newGameObject;
    }
Exemplo n.º 8
0
    void _DrawSectorLinks(SECTR_Portal myPortal)
    {
        nullStyle.normal.textColor = SECTR_Portal.FrontAnchorColor;
        Handles.Label(myPortal.FrontAnchorPosition, "F", nullStyle);
        nullStyle.normal.textColor = SECTR_Portal.BackAnchorColor;
        Handles.Label(myPortal.BackAnchorPosition, "B", nullStyle);
        if (myPortal.FrontSector != null)
        {
            Handles.color = SECTR_Portal.FrontAnchorColor;
            Handles.DrawLine(myPortal.FrontAnchorPosition, myPortal.FrontSector.TotalBounds.center);
        }

        if (myPortal.BackSector != null)
        {
            Handles.color = SECTR_Portal.BackAnchorColor;
            Handles.DrawLine(myPortal.BackAnchorPosition, myPortal.BackSector.TotalBounds.center);
        }
    }
Exemplo n.º 9
0
    public override void OnInspectorGUI()
    {
        SECTR_Portal myPortal = (SECTR_Portal)target;
        SECTR_Sector newFront = ObjectField <SECTR_Sector>("Front Sector", "Reference to the Sector on the front side of this Portal", myPortal.FrontSector, true);
        SECTR_Sector newBack  = ObjectField <SECTR_Sector>("Back Sector", "Reference to the Sector on the back side of this Portal", myPortal.BackSector, true);

        // Only apply changes if things are actually different.
        // Note that the code below duplicates some functionality from SECTR_Portal's
        // accessors, but I can'f figure out any other way to get the SerializedProperty
        // multi-select compatable Undo to work...
        if (myPortal.FrontSector != newFront || myPortal.BackSector != newBack)
        {
            serializedObject.Update();
            if (myPortal.FrontSector != newFront)
            {
                if (myPortal.FrontSector)
                {
                    myPortal.FrontSector.Deregister(myPortal);
                }
                frontProp.objectReferenceValue = newFront;
                if (myPortal.FrontSector)
                {
                    myPortal.FrontSector.Register(myPortal);
                }
            }
            if (myPortal.BackSector != newBack)
            {
                if (myPortal.BackSector)
                {
                    myPortal.BackSector.Deregister(myPortal);
                }
                backProp.objectReferenceValue = newBack;
                if (myPortal.BackSector)
                {
                    myPortal.BackSector.Register(myPortal);
                }
            }
            serializedObject.ApplyModifiedProperties();
        }

        base.OnInspectorGUI();
    }
    protected override void OnDrawGizmosSelected()
    {
        Bounds bounds = TotalBounds;

        Gizmos.color = SectorColor;
        Gizmos.DrawWireCube(bounds.center, bounds.size);

        if (enabled)
        {
            // Render links to neighbor Sectors.
            Gizmos.color = SECTR_Portal.ActivePortalColor;
            int numNeighbors = portals.Count;
            for (int neighborIndex = 0; neighborIndex < numNeighbors; ++neighborIndex)
            {
                SECTR_Portal portal = portals[neighborIndex];
                Gizmos.DrawLine(TotalBounds.center, portal.Center);
            }

            Gizmos.color = Color.red;
            List <SECTR_Member.Child> sharedChildren = GetSharedChildren();
            int numSharedChildren = sharedChildren.Count;
            for (int childIndex = 0; childIndex < numSharedChildren; ++childIndex)
            {
                SECTR_Member.Child child            = sharedChildren[childIndex];
                Bounds             totalChildBounds = new Bounds(child.gameObject.transform.position, Vector3.zero);
                if (child.renderer)
                {
                    totalChildBounds.Encapsulate(child.rendererBounds);
                }
                if (child.light)
                {
                    totalChildBounds.Encapsulate(child.lightBounds);
                }
                if (child.terrain)
                {
                    totalChildBounds.Encapsulate(child.terrainBounds);
                }
                Gizmos.DrawWireCube(totalChildBounds.center, totalChildBounds.size);
            }
        }
    }
Exemplo n.º 11
0
    protected static GameObject CreateDoor <T>(string newName) where T : SECTR_Door
    {
        string       undoName = "Create " + newName;
        GameObject   newGameObject;
        SECTR_Portal selectedPortal = Selection.activeGameObject ? Selection.activeGameObject.GetComponent <SECTR_Portal>() : null;

        if (selectedPortal)
        {
            newGameObject = CreateTriggerFromPortal(selectedPortal, newName);
            T newDoor = newGameObject.AddComponent <T>();
            newDoor.Portal = selectedPortal;
        }
        else
        {
            newGameObject = CreateGameObject(newName);
            BoxCollider newCollider = newGameObject.AddComponent <BoxCollider>();
            newCollider.isTrigger = true;
            newGameObject.AddComponent <T>();
        }

        SECTR_Undo.Created(newGameObject, undoName);
        Selection.activeGameObject = newGameObject;
        return(newGameObject);
    }
Exemplo n.º 12
0
    public void OnSceneGUI()
    {
        SECTR_Portal myPortal = (SECTR_Portal)target;

        if (boxStyle == null)
        {
            boxStyle           = new GUIStyle(GUI.skin.box);
            boxStyle.alignment = TextAnchor.UpperCenter;
            boxStyle.fontSize  = 15;
        }

        if (buttonStyle == null)
        {
            buttonStyle           = new GUIStyle(GUI.skin.button);
            buttonStyle.alignment = TextAnchor.UpperCenter;
            buttonStyle.fontSize  = 12;
        }

        if (nullStyle == null)
        {
            nullStyle = new GUIStyle();
        }

        // Viewport GUI Drawing
        _DrawViewportGUI(myPortal);

        // Input
        if (pickBack || pickFront)
        {
            _PickSector(myPortal);
        }
        else if ((createHull || myPortal.ForceEditHull) && !Application.isPlaying)
        {
            _EditHull(myPortal);
        }

        // Input may destroy this object.
        if (target == null)
        {
            return;
        }

        // Viewport 3D drawing
        if (!createHull && !myPortal.ForceEditHull)
        {
            _DrawSectorLinks(myPortal);
        }

        if (createHull || myPortal.ForceEditHull)
        {
            _DrawHullEditor(myPortal);
        }
        else if (pickBack || pickFront)
        {
            Handles.color = pickFront ? SECTR_Portal.FrontAnchorColor : SECTR_Portal.BackAnchorColor;
            Handles.DrawSolidDisc(closestVert, lastHit.normal, .1f);
            SECTR_Sector sector = _GetSectorFromSelection();
            if (sector != null)
            {
                Handles.Label(closestVert, sector.name);
            }
        }
    }
Exemplo n.º 13
0
	/// Informs the Sector that a Portal is no longer connected into it.
	/// Should only be called by SECTR_Portal.
	public void Deregister(SECTR_Portal portal)
	{
		// Assert that we're contained?
		portals.Remove(portal);
	}
Exemplo n.º 14
0
	/// Informs the Sector that a Portal is connected into it.
	/// Should only be called by SECTR_Portal.
	public void Register(SECTR_Portal portal)
	{
		// Should this be an assert? Probably?
		if(!portals.Contains(portal))
		{
			portals.Add(portal);
		}
	}
 /// Informs the Sector that a Portal is no longer connected into it.
 /// Should only be called by SECTR_Portal.
 public void Deregister(SECTR_Portal portal)
 {
     // Assert that we're contained?
     portals.Remove(portal);
 }
Exemplo n.º 16
0
    /// Finds the shortest path through the portal graph between two points.
    /// The start and end points must currently be within Sector in the graph.
    /// <param name="path">List into which search results will be written.</param>
    /// <param name="start">The world space position at which to start the search.</param>
    /// <param name="goal">The world space goal of the search.</param>
    /// <param name="stopFlags">Flag set to test at each SECTR_Portal. A failed test will stop the traversal.</param>
    /// <returns>A list of nodes from the Start to the Goal. Empty if there is no path.</returns>
    public static void FindShortestPath(ref List <Node> path, Vector3 start, Vector3 goal, SECTR_Portal.PortalFlags stopFlags)
    {
        // This is an implementation of a basic A* search.
        // Implementation is optimized through use of a priority queue open list
        // and a dictionary for the closed list.
        path.Clear();
        openSet.Clear();
        closedSet.Clear();

        // Get the list of starting portals, all of which will be pushed on to the open set.
        // There may be multiple candidate Sectors becuase the bounding boxes may overlap.
        SECTR_Sector.GetContaining(ref initialSectors, start);
        SECTR_Sector.GetContaining(ref goalSectors, goal);

        int numInitialSectors = initialSectors.Count;

        for (int initialSectorIndex = 0; initialSectorIndex < numInitialSectors; ++initialSectorIndex)
        {
            SECTR_Sector sector = initialSectors[initialSectorIndex];
            if (goalSectors.Contains(sector))
            {
                Node newElement = new Node();
                newElement.Sector = sector;
                path.Add(newElement);
                return;
            }

            int numPortals = sector.Portals.Count;
            for (int portalIndex = 0; portalIndex < numPortals; ++portalIndex)
            {
                SECTR_Portal portal = sector.Portals[portalIndex];
                if ((portal.Flags & stopFlags) == 0)
                {
                    Node newElement = new Node();
                    newElement.Portal           = portal;
                    newElement.Sector           = sector;
                    newElement.ForwardTraversal = sector == portal.FrontSector;
                    newElement.Cost             = Vector3.Magnitude(start - portal.transform.position);
                    float estimate = Vector3.Magnitude(goal - portal.transform.position);
                    newElement.CostPlusEstimate = newElement.Cost + estimate;
                    openSet.Enqueue(newElement);
                }
            }
        }

        // Time to do some A*...
        while (openSet.Count > 0)
        {
            Node         current = openSet.Dequeue();
            SECTR_Sector sector  = current.ForwardTraversal ? current.Portal.BackSector : current.Portal.FrontSector;
            if (!sector)
            {
                continue;
            }

            // If the current element Sector contains the goal point, we're done.
            // NOTE: I *think* it's correct to end here but we should prove that
            // this is correct even strange connections of concave Sector.
            if (goalSectors.Contains(sector))
            {
                Node.ReconstructPath(path, current);
                break;
            }

            int numPortals = sector.Portals.Count;
            for (int portalIndex = 0; portalIndex < numPortals; ++portalIndex)
            {
                SECTR_Portal portal = sector.Portals[portalIndex];
                if (portal != current.Portal && (portal.Flags & stopFlags) == 0)
                {
                    // Create a new SearchElement for this neighbor.
                    Node neighborElement = new Node();
                    neighborElement.Parent           = current;
                    neighborElement.Portal           = portal;
                    neighborElement.Sector           = sector;
                    neighborElement.ForwardTraversal = sector == portal.FrontSector;
                    neighborElement.Cost             = current.Cost + Vector3.Magnitude(neighborElement.Portal.transform.position - current.Portal.transform.position);
                    float estimate = Vector3.Magnitude(goal - neighborElement.Portal.transform.position);
                    neighborElement.CostPlusEstimate = neighborElement.Cost + estimate;

                    // If the closed list already contains this portal,
                    // and that version is closer than us, we'll skip this node.
                    Node closedElement = null;
                    closedSet.TryGetValue(neighborElement.Portal, out closedElement);
                    if (closedElement != null && closedElement.CostPlusEstimate < neighborElement.CostPlusEstimate)
                    {
                        continue;
                    }

                    // Check to see if the neighbor is already on the open list.
                    Node openElement = null;
                    for (int i = 0; i < openSet.Count; ++i)
                    {
                        if (openSet[i].Portal == neighborElement.Portal)
                        {
                            openElement = openSet[i];
                            break;
                        }
                    }
                    // Skip this neighbor if the open neighbor is better than us.
                    if (openElement != null && openElement.CostPlusEstimate < neighborElement.CostPlusEstimate)
                    {
                        continue;
                    }

                    // Add this neighbor to the open list.
                    openSet.Enqueue(neighborElement);
                }
            }

            // Once all neighbors are considered, put this node onto the close list.
            if (!closedSet.ContainsKey(current.Portal))
            {
                closedSet.Add(current.Portal, current);
            }
        }
    }
Exemplo n.º 17
0
 void _SwapSectors(SECTR_Portal myPortal)
 {
     SECTR_Undo.Record(myPortal, "Swap Portal Sectors");
     SECTR_Sector oldFront = myPortal.FrontSector;
     SECTR_Sector oldBack = myPortal.BackSector;
     myPortal.FrontSector = null;
     myPortal.BackSector = null;
     myPortal.FrontSector = oldBack;
     myPortal.BackSector = oldFront;
     EditorUtility.SetDirty(myPortal);
 }
Exemplo n.º 18
0
 void _PickSector(SECTR_Portal myPortal)
 {
     HandleUtility.AddDefaultControl(GUIUtility.GetControlID(FocusType.Passive));
     if(Event.current.type == EventType.mouseMove)
     {
         _ComputeCursorVert();
     }
     else if(Event.current.type == EventType.mouseUp && Event.current.button == 0 && !Event.current.alt && !Event.current.control)
     {
         SECTR_Sector sector = _GetSectorFromSelection();
         if(sector)
         {
             SECTR_Undo.Record(myPortal, "Assign Sector to Portal.");
             if(pickBack)
             {
                 myPortal.BackSector = sector;
             }
             else
             {
                 myPortal.FrontSector = sector;
             }
             EditorUtility.SetDirty(myPortal);
             pickFront = false;
             pickBack = false;
             _EndSelection();
         }
     }
     else if(Event.current.type == EventType.keyUp && Event.current.keyCode == KeyCode.Escape)
     {
         pickBack = false;
         pickFront = false;
         _EndSelection();
     }
 }
Exemplo n.º 19
0
    void _DrawViewportGUI(SECTR_Portal myPortal)
    {
        Handles.BeginGUI();

        float width = 400;
        if(pickBack || pickFront)
        {
            float height = 100;
            GUI.Box(new Rect((Screen.width * 0.5f) - (width * 0.5f), Screen.height - height, width, height),
                    "Selecting " + (pickBack ? "back" : "front") + " Sector of " + myPortal.name + ".\n" +
                    (_GetSectorFromSelection() != null ? "Left Click to select." : "") + "\nEsc to cancel.", boxStyle);
        }
        else if(createHull || myPortal.ForceEditHull)
        {
            float height = 100;
            string returnText = "";
            if(newHullVerts.Count >= 3)
            {
                returnText = "Return to complete.";
            }
            else if(newHullVerts.Count == 0 && myPortal.ForceEditHull)
            {
                returnText = "Return to create empty portal.";
            }
            GUI.Box(new Rect((Screen.width * 0.5f) - (width * 0.5f), Screen.height - height, width, height),
                    "Drawing geometry for " + myPortal.name + ".\n" +
                    (closesetVertIsValid ? "Left Click to add vert. " : "") + returnText + "\nEsc to cancel.",
                    boxStyle);
        }
        else if(Selection.gameObjects.Length == 1)
        {
            float height = 100;
            GUILayout.BeginArea(new Rect((Screen.width * 0.5f) - (width * 0.5f), Screen.height - height, width, height));
            if(GUILayout.Button(new GUIContent(myPortal.HullMesh ? "Redraw Portal" : "Draw Portal", "Lets you (re)create the geometry for this Portal"), buttonStyle))
            {
                createHull = true;
            }

            GUILayout.BeginHorizontal();
            if(GUILayout.Button(new GUIContent("Pick Front Sector", "Provides an in-viewport interface for picking the front Sector."), buttonStyle))
            {
                pickFront = true;
            }
            if(GUILayout.Button(new GUIContent("Swap Sectors", "Swaps the front and back Sectors, in case they are backwards."), buttonStyle))
            {
                _SwapSectors(myPortal);
            }
            if(GUILayout.Button(new GUIContent("Pick Back Sector", "Provides an in-viewport interface for picking the front Sector."), buttonStyle))
            {
                pickBack = true;
            }
            GUILayout.EndHorizontal();

            GUILayout.EndArea();
        }
        Handles.EndGUI();
    }
Exemplo n.º 20
0
    void _DrawSectorLinks(SECTR_Portal myPortal)
    {
        nullStyle.normal.textColor = SECTR_Portal.FrontAnchorColor;
        Handles.Label(myPortal.FrontAnchorPosition, "F", nullStyle);
        nullStyle.normal.textColor = SECTR_Portal.BackAnchorColor;
        Handles.Label(myPortal.BackAnchorPosition, "B", nullStyle);
        if(myPortal.FrontSector != null)
        {
            Handles.color = SECTR_Portal.FrontAnchorColor;
            Handles.DrawLine(myPortal.FrontAnchorPosition, myPortal.FrontSector.TotalBounds.center);
        }

        if(myPortal.BackSector != null)
        {
            Handles.color = SECTR_Portal.BackAnchorColor;
            Handles.DrawLine(myPortal.BackAnchorPosition, myPortal.BackSector.TotalBounds.center);
        }
    }
Exemplo n.º 21
0
    /// Generates a List of nodes that is a depth-first traversal of
    /// walk of sector graph from the specified root.
    /// <param name="nodes">List into which walk results will be written.</param>
    /// <param name="root">The Sector at which to start the traversal.</param>
    /// <param name="stopFlags">Flag set to test at each SECTR_Portal. A failed test will stop the traversal.</param>
    /// <param name="maxDepth">The depth into the graph at which to end the traversal. -1 means no limit.</param>
    /// <returns>A List of Nodes in depth-first traveral order.</returns>
    public static void DepthWalk(ref List <Node> nodes, SECTR_Sector root, SECTR_Portal.PortalFlags stopFlags, int maxDepth)
    {
        nodes.Clear();
        if (root == null)
        {
            return;
        }
        else if (maxDepth == 0)
        {
            Node node = new Node();
            node.Sector = root;
            nodes.Add(node);
            return;
        }

        // We only want to visit each Sector once, so we'll mark them
        // in order to avoid cycles.
        int numSectors = SECTR_Sector.All.Count;

        for (int sectorIndex = 0; sectorIndex < numSectors; ++sectorIndex)
        {
            SECTR_Sector.All[sectorIndex].Visited = false;
        }

        // Use a stack for the search, to keep implementation similar
        // to the breadth first search above.
        Stack <Node> nodeStack = new Stack <Node>(numSectors);
        Node         rootNode  = new Node();

        rootNode.Sector = root;
        rootNode.Depth  = 1;
        nodeStack.Push(rootNode);
        root.Visited = true;
        int exploredNodes = 0;

        while (nodeStack.Count > 0)
        {
            Node nextNode = nodeStack.Pop();
            nodes.Add(nextNode);
            ++exploredNodes;

            if (maxDepth < 0 || nextNode.Depth <= maxDepth)
            {
                int numPortals = nextNode.Sector.Portals.Count;
                for (int portalIndex = 0; portalIndex < numPortals; ++portalIndex)
                {
                    SECTR_Portal portal = nextNode.Sector.Portals[portalIndex];
                    if (portal && (portal.Flags & stopFlags) == 0)
                    {
                        SECTR_Sector neighborSector = portal.FrontSector == nextNode.Sector ? portal.BackSector : portal.FrontSector;
                        if (neighborSector && !neighborSector.Visited)
                        {
                            Node neighborNode = new Node();
                            neighborNode.Parent = nextNode;
                            neighborNode.Sector = neighborSector;
                            neighborNode.Portal = portal;
                            neighborNode.Depth  = nextNode.Depth + 1;
                            nodeStack.Push(neighborNode);
                            neighborSector.Visited = true;
                        }
                    }
                }
            }
        }
    }