/// <summary>
        /// Finds a path between the start and end nodes.  Interally, we don't need to use the path for anything, but we just need to make sure a path exsists, otherwise the graph is not solveable
        /// This is used for keys and locked doors and stuff like that
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public bool HasPath(GraphNode start, GraphNode end)
        {
            //iterate through the graph to zero it out
            for (int i = 0; i < DungeonGraph.Count; i++)
            {
                DungeonGraph[i].pathfindingWeight    = -1f; //-1f means "not processed"
                DungeonGraph[i].pathfindingProcessed = false;
            }

            List <GraphNode> nodesToProcess = new List <GraphNode>();

            nodesToProcess.Add(start);
            start.pathfindingWeight    = 0f;
            start.pathfindingProcessed = true;


            //do a flood fill starting from start room, increasing the depth every step
            //for(int i = 0; i < nodesToProcess.Count; i++) {
            //ColorChildren(start.data.gameObject.transform, Color.cyan);
            //this is infinite looping
            while (nodesToProcess.Count > 0)
            {
                GraphNode nodeToProcess = nodesToProcess[0];
                nodesToProcess.RemoveAt(0);
                nodeToProcess.pathfindingProcessed = true;


                for (int j = 0; j < nodeToProcess.connections.Count; j++)
                {
                    if (nodeToProcess.connections[j].open)
                    {
                        GraphConnection c = nodeToProcess.connections[j];
                        if (!c.a.pathfindingProcessed)
                        {
                            //a has not been processed, so add it to the "rooms to process" and set it's weight accordingly
                            c.a.pathfindingWeight = nodeToProcess.pathfindingWeight + 1f;
                            //c.a.pathfindingProcessed = true;
                            nodesToProcess.Add(c.a);
                            //ColorChildren(c.a.data.transform, Color.red);
                        }

                        if (!c.b.pathfindingProcessed)
                        {
                            //a has not been processed, so add it to the "rooms to process" and set it's weight accordingly
                            c.b.pathfindingWeight = nodeToProcess.pathfindingWeight + 1f;
                            //c.a.pathfindingProcessed = true;
                            nodesToProcess.Add(c.b);
                            //ColorChildren(c.b.data.transform, Color.green);
                        }
                    }
                    else
                    {
                        //can't add this one as the way is closed!
                    }
                }
            }

            bool foundPath = false;

            if (end.pathfindingWeight != -1)
            {
                foundPath = true;
            }
            return(foundPath);
        }
        private void OnDrawGizmos()
        {
            Gizmos.color = Color.blue;
            for (int i = 0; i < openSet.Count; i++)
            {
                Vector3 v = GetVoxelWorldPos((openSet[i].position + openSet[i].direction), openSet[i].parent.rotation) + openSet[i].parent.transform.position;
                Gizmos.DrawWireCube(v, Vector3.one);
            }

            Gizmos.color = Color.green;
            if (drawGlobalVoxels)
            {
                Gizmos.color = globalVoxelColor;
                foreach (var i in GlobalVoxelGrid)
                {
                    Gizmos.DrawWireCube(i.Key, Vector3.one);
                }
            }

            if (drawAllDoors)
            {
                Gizmos.color = Color.cyan;
                for (int i = 0; i < AllDoorsData.Count; i++)
                {
                    Gizmos.DrawWireCube(AllDoorsData[i].position, Vector3.one);
                }
            }

#if UNITY_EDITOR
            if (drawDepthLabels)
            {
                GUIStyle style = new GUIStyle();
                style.fontSize         = 20;
                style.normal.textColor = Color.red;

                for (int i = 0; i < DungeonGraph.Count; i++)
                {
                    Vector3 offset = new Vector3(0f, 0f, 0f);
                    Vector3 pos    = DungeonGraph[i].data.transform.position + offset;

                    string label = DungeonGraph[i].depth.ToString();
                    //label = DungeonGraph[i].pathfindingWeight.ToString();

                    UnityEditor.Handles.Label(pos + new Vector3(0f, 0.1f, 0f), label, style);
                }
            }
#endif

            if (drawGraph)
            {
                for (int i = 0; i < DungeonGraph.Count; i++)
                {
                    Vector3 offset = new Vector3(0f, 0f, 0f);
                    Vector3 pos    = DungeonGraph[i].data.transform.position + offset;
                    float   s      = 0.4f;
                    if (i == 0)
                    {
                        Gizmos.color = Color.blue;
                        s            = 1f;
                    }
                    else
                    {
                        Gizmos.color = Color.green;
                        s            = 0.4f;
                    }

                    //Gizmos.DrawWireSphere(pos, s / 2f);

                    Color roomCol = debugGradient.Evaluate(((float)DungeonGraph[i].depth) / highestDepth);
                    if (i == 0)
                    {
                        roomCol = Color.blue;
                    }
                    if (colourRoomsWithDepth)
                    {
                        ColorChildren(DungeonGraph[i].data.gameObject.transform, roomCol);
                    }

                    if (drawKeyLocksLabels)
                    {
                        if (DungeonGraph[i].keyIDs.Count != 0)
                        {
                            string keyLabel = "keys: ";
                            for (int k = 0; k < DungeonGraph[i].keyIDs.Count; k++)
                            {
                                keyLabel += DungeonGraph[i].keyIDs[k].ToString() + ", ";
                            }
                            GUIStyle style = new GUIStyle();
                            style.fontSize         = 25;
                            style.normal.textColor = Color.red;
                            UnityEditor.Handles.Label(pos + new Vector3(0f, 0.4f, 0f), keyLabel, style);
                        }
                    }

                    s = 0.4f;
                    //since we have cyclic references, this will be drawn twice...
                    for (int j = 0; j < DungeonGraph[i].connections.Count; j++)
                    {
                        GraphConnection c = DungeonGraph[i].connections[j];
                        if (c.open)
                        {
                            Gizmos.color = Color.green;
                        }
                        else
                        {
                            Gizmos.color = Color.red;
                        }


                        if (colourLockedDoors)
                        {
                            ColorChildren(c.doorRef.spawnedDoor.transform, GetKeyColor(c.keyID));
                        }
                        Door    doorRef = DungeonGraph[i].connections[j].doorRef;
                        Vector3 dPos    = doorRef.spawnedDoor.transform.position + offset;

                        if (!c.open)
                        {
                            Gizmos.DrawWireCube(dPos, new Vector3(s, s, s));
                            if (drawKeyLocksLabels)
                            {
                                GUIStyle style = new GUIStyle();
                                style.fontSize         = 25;
                                style.normal.textColor = Color.black;
                                if (c.keyID != -1)
                                {
                                    UnityEditor.Handles.Label(dPos + new Vector3(0f, 0.4f, 0f), "Lock: " + c.keyID.ToString(), style);
                                }
                            }
                        }

                        if (c.b != null)
                        {
                            Vector3 posStart = c.a.data.transform.position;
                            Vector3 posEnd   = c.b.data.transform.position;

                            float l   = ((float)DungeonGraph[i].depth) / highestDepth;
                            Color col = debugGradient.Evaluate(l);
                            col          = graphConnectionColor;
                            Gizmos.color = col;
                            for (int thicc = 0; thicc < 5; thicc++)
                            {
                                float   thic      = 0.01f * thicc;
                                Vector3 lineThicc = new Vector3(thic, thic, thic);
                                Gizmos.DrawLine(posStart + offset + lineThicc, dPos + lineThicc);
                                Gizmos.DrawLine(posEnd + offset + lineThicc, dPos + lineThicc);
                            }
                        }
                    }
                }
            }
        }
        public void GenerateNextRoom()
        {
            Door targetDoor = openSet[0]; //grab the first door in the openset to process

            openSet.RemoveAt(0);

            Vector3 targetVoxel        = targetDoor.position + targetDoor.direction;                                                       //offset one voxel in door dir so we work on the unoccupied voxel the door leads to
            Vector3 targetWorldVoxPos  = GetVoxelWorldPos(targetVoxel, targetDoor.parent.rotation) + targetDoor.parent.transform.position; //need this for offset
            Vector3 targetWorldDoorDir = GetVoxelWorldDir(targetDoor.direction, targetDoor.parent.rotation);                               //the target voxel we're going to align to

            Door doorForProcessing = new Door(targetWorldVoxPos, targetWorldDoorDir, targetDoor.parent);                                   //why do I do this instead of using targetDoor directly...?
            //AllDoorsData.Add(doorForProcessing);

            List <GameObject> roomsToTry = new List <GameObject>(generatorSettings.possibleRooms);

            //create a copy of the "all possible rooms list" so we can pick and remove from this list as we try different rooms
            //this ensures we don't try the same room over and over, and so we know when we have exhausted all the possiblities and just have to cap it off with a 1x1x1 vox room

            for (int i = 0; i < roomsToTry.Count; i++)                                                          //find the room template of the room we are trying to connect to, and remove that room template from the list of possible rooms to spawn
            {
                if (roomsToTry[i].GetComponent <RoomData>().roomTemplateID == targetDoor.parent.roomTemplateID) //this is to make it so we are less likely to spawn the same room type twice in a row
                {
                    roomsToTry.RemoveAt(i); break;
                }
            }
            roomsToTry.Shuffle(rand);                                       //shuffle this list so we dont always try the rooms in the same order.

            for (int i = 0; i < generatorSettings.possibleRooms.Count; i++) //add back the room type we removed to the end of the (now shuffled) list, so that we try every other room first and only use this room as a last choice
            {
                if (generatorSettings.possibleRooms[i].GetComponent <RoomData>().roomTemplateID == targetDoor.parent.roomTemplateID)
                {
                    roomsToTry.Add(generatorSettings.possibleRooms[i]);
                }
            }

            RoomData instantiatedNewRoom = null;

            if (generatorSettings.useDeadendRooms)
            {
                if (AllRooms.Count + openSet.Count >= targetRooms)
                {
                    roomsToTry.Clear();
                    //Debug.Log("Ending Gen, Target rooms hit");
                }
            }
            else
            {
                if (AllRooms.Count >= targetRooms)
                {
                    roomsToTry.Clear();
                }
            }

            if (generatorSettings.useDeadendRooms)                           //append this to the end of the roomsToTryList
            {
                int ri = rand.Next(0, generatorSettings.deadendRooms.Count); //get a random deadend room
                roomsToTry.Add(generatorSettings.deadendRooms[ri]);          //append the "singleVoxelRoom" as a last resort, this room will fit in ALL cases
            }

            //roomsToTry list MIGHT be empty, if so we just don't spawn a room, spawn a door and a halfempty connection
            bool makeRoomDeadend = false;

            if (roomsToTry.Count == 0)
            {
                makeRoomDeadend = true;
            }


            if (!makeRoomDeadend) //spawn rooms like normal
            {
                //data we will have once we find a room, used for spawning the room into the world
                RoomData newRoom              = null;
                Vector3  computedRoomOffset   = Vector3.zero;
                int      doorIndex            = 0;
                float    computedRoomRotation = 0f;
                bool     anyOverlaps          = false;

                //start
                do
                {
                    //it's possible that we try every room and none fit, especially if we don't have any deaded rooms that _should_ fit in any situation.
                    //so if the list is empty, just break out
                    if (roomsToTry.Count == 0)
                    {
                        makeRoomDeadend = true;
                        break;
                    }

                    newRoom = roomsToTry[0].GetComponent <RoomData>();
                    roomsToTry.RemoveAt(0);

                    List <int> doorsToTry = new List <int>();
                    for (int i = 0; i < newRoom.Doors.Count; i++)
                    {
                        doorsToTry.Add(i);
                    }
                    doorsToTry.Shuffle(rand);      //same thing here with the doorors as with the rooms. Copy the list so we can exaust our options, shuffle it so we never try in the same order

                    do                             //try all the different doors in different orientations
                    {
                        doorIndex = doorsToTry[0]; //get first doorIndex (has been shuffled)
                        doorsToTry.RemoveAt(0);

                        Door    newDoor       = newRoom.Doors[doorIndex];
                        Vector3 targetDoorDir = targetWorldDoorDir;
                        computedRoomRotation = GetRoomRotation(targetDoorDir, newDoor); //computes the rotation of the room so that the door we've selected to try lines up properly to the door we are wanting to connect to
                        Vector3 sDLocalWithRotation = GetVoxelWorldPos(newDoor.position, computedRoomRotation);
                        computedRoomOffset = targetWorldVoxPos - sDLocalWithRotation;   //the computed offset we need to apply to the room gameobject so that the doors align


                        List <Vector3> worldVoxels = new List <Vector3>(); //check for overlaps with all of these. MUST BE Mathf.RoundToInt so that the vectors are not like 0.999999999 due to precision issues
                        for (int i = 0; i < newRoom.LocalVoxels.Count; i++)
                        {
                            Vector3 v = GetVoxelWorldPos(newRoom.LocalVoxels[i].position, computedRoomRotation) + computedRoomOffset; //all the room voxels
                            worldVoxels.Add(v);
                        }

                        for (int i = 0; i < newRoom.Doors.Count; i++)
                        {
                            if (i != doorIndex)  //all the door voxels (except the one we're currently working on/linking up to another room).
                                                 //We need to do this to so that we don't PLACE this room in a spot where the doors of this room have no space for at least a 1x1x1 room (eg, opening a door directly into a wall)
                            {
                                Vector3 v = GetVoxelWorldPos((newRoom.Doors[i].position + newRoom.Doors[i].direction), computedRoomRotation) + computedRoomOffset;
                                worldVoxels.Add(v);
                            }
                        }

                        //all room voxels addd. Get all open door voxels now.. as we don't want to block the exits to any doors not yet connected on both sides.
                        List <Vector3> doorNeighbours = new List <Vector3>();
                        for (int i = 0; i < openSet.Count; i++)
                        {
                            Vector3 v = GetVoxelWorldPos((openSet[i].position + openSet[i].direction), openSet[i].parent.rotation) + openSet[i].parent.transform.position;
                            doorNeighbours.Add(v);
                        }

                        anyOverlaps = false;
                        for (int i = 0; i < worldVoxels.Count; i++)
                        {
                            Vector3 iV     = new Vector3(Mathf.RoundToInt(worldVoxels[i].x), Mathf.RoundToInt(worldVoxels[i].y), Mathf.RoundToInt(worldVoxels[i].z));
                            bool    result = IsVoxelOccupied(iV); //check this rooms volume (including the voxels this rooms doors lead into) against all occupied voxels to check for overlaps
                            if (result)
                            {
                                anyOverlaps = true;
                                break;
                            }
                            else
                            {
                                for (int j = 0; j < doorNeighbours.Count; j++)  //also check this rooms volume against all the voxels openSet.doors lead into, prevents opening a door into a wall
                                {
                                    Vector3 iD = new Vector3(Mathf.RoundToInt(doorNeighbours[j].x), Mathf.RoundToInt(doorNeighbours[j].y), Mathf.RoundToInt(doorNeighbours[j].z));
                                    if (iD == iV)
                                    {
                                        anyOverlaps = true;
                                        break;
                                    }
                                }
                            }
                        }
                    } while(doorsToTry.Count > 0 && anyOverlaps);
                } while(roomsToTry.Count > 0 && anyOverlaps);

                if (anyOverlaps)  //if we made it here, we either have a newRoom assigned and ready to spawn, or we have no rooms left to as they all didn't fit
                {
                    makeRoomDeadend = true;
                }

                //Instantiate
                if (!makeRoomDeadend)
                {
                    instantiatedNewRoom = AddRoom(newRoom, computedRoomOffset, computedRoomRotation);
                    for (int i = 0; i < instantiatedNewRoom.Doors.Count; i++)
                    {
                        if (i != doorIndex)
                        {
                            openSet.Add(instantiatedNewRoom.Doors[i]);
                        }
                    }
                }
            }


            //spawn in door geometry
            int        di          = 0;
            GameObject doorToSpawn = null;

            if (!makeRoomDeadend)
            {
                di          = rand.Next(0, generatorSettings.doors.Count); //get a random door from the list
                doorToSpawn = generatorSettings.doors[di];
            }
            else
            {
                di          = rand.Next(0, generatorSettings.deadendDoors.Count); //get a random door from the list
                doorToSpawn = generatorSettings.deadendDoors[di];
            }
            Vector3    doorOffset  = new Vector3(0f, 0.5f, 0f); //to offset it so the gameobject pivot is on the bottom edge of the voxel
            GameObject spawnedDoor = GameObject.Instantiate(doorToSpawn, doorForProcessing.position - (doorForProcessing.direction * 0.5f) - doorOffset, Quaternion.LookRotation(doorForProcessing.direction), this.transform);

            doorForProcessing.spawnedDoor = spawnedDoor;

            //need to link up the doors to the roomData's too?
            //AllDoors.Add(spawnedDoor);

            //instantiatedNewRoom.Doors[doorIndex].spawnedDoor = spawnedDoor;


            //build graph.. we know...
            //instantiatedNewRoom and doorForProcessing.parent are the only two rooms that share the connection we just made so...
            //we also know instantiedNewRoom is brand new and has no other connections, we we can use that directly,
            //however we need to search for doorForProcessing.parent in the roomsList first as it could have more connections already, if it does, we need to add the connection
            //betwen it and
            if (!makeRoomDeadend)
            {
                GraphNode newNode = new GraphNode();
                newNode.data             = instantiatedNewRoom; //connect it both ways, so we access the data from the node, and the node from the data...
                instantiatedNewRoom.node = newNode;
                //grab the node of the room we are connecting to
                GraphNode lastNode = doorForProcessing.parent.node;
                newNode.depth = lastNode.depth + 1;
                if (newNode.depth > highestDepth)
                {
                    highestDepth = newNode.depth;                              //store the highest depth, used for debugging
                }
                //make a connection for the two of them
                GraphConnection con = new GraphConnection();
                con.a       = lastNode; //store the connections to the rooms
                con.b       = newNode;
                con.open    = true;
                con.doorRef = doorForProcessing; //this needs a reference to the door geometry, as we need to be able to...unlock it visually? We could hook it up to a data ref, which then has a ref to geometry too but that seems painfully overcomplicated

                lastNode.connections.Add(con);   //store the connections both ways
                newNode.connections.Add(con);
                spawnedDoor.GetComponent <GeneratorDoor>().data = con;
                DungeonGraph.Add(newNode);
            }
            else
            {
                //want to add just a new door as we did not spawn a room!

                //GraphNode newNode = new GraphNode();
                //newNode.data = instantiatedNewRoom; //connect it both ways, so we access the data from the node, and the node from the data...
                //instantiatedNewRoom.node = newNode;
                //grab the node of the room we are connecting to
                GraphNode lastNode = doorForProcessing.parent.node;
                //newNode.depth = lastNode.depth + 1;
                //if(newNode.depth > highestDepth) highestDepth = newNode.depth; //store the highest depth, used for debugging

                //make a connection for the two of them
                GraphConnection con = new GraphConnection();
                con.a       = lastNode; //store the connections to the rooms
                con.b       = null;
                con.open    = false;
                con.keyID   = -1;
                con.doorRef = doorForProcessing; //this needs a reference to the door geometry, as we need to be able to...unlock it visually? We could hook it up to a data ref, which then has a ref to geometry too but that seems painfully overcomplicated

                lastNode.connections.Add(con);   //store the connections both ways
                //newNode.connections.Add(con);
                spawnedDoor.GetComponent <GeneratorDoor>().data = con;
                //DungeonGraph.Add(newNode);
            }
        }