/// <summary> /// Convert the tilemap into a better data structure /// </summary> private void analyzeRooms() { var rooms = new List <Map.Room>(); // pass 1 - find all rooms for (int r = 0; r < structure.Height; r++) { for (int c = 0; c < structure.Width; c++) { var tile = structure.GetTile(c, r); if (tile == null) { continue; } Map.TileOri ori(TmxLayerTile t) { return(analyzeWallTile((Map.TileKind)(t.Gid - worldTileset.FirstGid), tileDirection(t))); } var tileOri = ori(tile); if (tileOri == Map.TileOri.UpLeft) { var topEdge = r; var leftEdge = c; var scanFirst = default(Point); var scanOpen = 0; var openings = new List <Map.Door>(); // line-scan setup void updateScan(TmxLayerTile t, Point p, Direction dir) { if (t != null) { if (scanOpen > 0) { openings.Add(new Map.Door(scanFirst, p, dir)); scanFirst = default; scanOpen = 0; } } else { if (scanOpen == 0) { // start the count scanFirst = p; } scanOpen++; } } // scan for an UpRight var ulTile = tile; var urTile = default(TmxLayerTile); var rightEdge = -1; for (int sx = leftEdge; sx < structure.Width; sx++) { // pass left-to-right along top var scTile = structure.GetTile(sx, topEdge); updateScan(scTile, new Point(sx, topEdge), Direction.Up); if (scTile == null) { continue; } if (ori(scTile) == Map.TileOri.UpRight) { urTile = scTile; rightEdge = sx; break; } } if (urTile == null) { break; } var drTile = default(TmxLayerTile); var downEdge = -1; for (int sy = topEdge; sy < structure.Height; sy++) { // pass top-to-bottom along right var scTile = structure.GetTile(rightEdge, sy); updateScan(scTile, new Point(rightEdge, sy), Direction.Right); if (scTile == null) { continue; } if (ori(scTile) == Map.TileOri.DownRight) { drTile = scTile; downEdge = sy; break; } } if (drTile == null) { break; } var dlTile = default(TmxLayerTile); for (int sx = rightEdge; sx >= 0; sx--) { // pass right-to left along down var scTile = structure.GetTile(sx, downEdge); updateScan(scTile, new Point(sx, downEdge), Direction.Down); if (scTile == null) { continue; } if (ori(scTile) == Map.TileOri.DownLeft) { dlTile = scTile; break; } } if (dlTile == null) { break; } // finally, check the left side for (int sy = downEdge; sy >= 0; sy--) { // pass down-to-top along left var scTile = structure.GetTile(leftEdge, sy); updateScan(scTile, new Point(leftEdge, sy), Direction.Left); if (scTile == null) { continue; } if (ori(scTile) == Map.TileOri.UpLeft) { // we found her again break; } } // all 4 corners have been found, create a room var room = new Map.Room(new Point(leftEdge, topEdge), new Point(rightEdge, downEdge)); room.doors = openings; foreach (var door in openings) { // set local room of all doors door.roomLocal = room; } rooms.Add(room); Global.log.trace($"room ul:{room.ul}, dr{room.dr}, doors:{room.doors.Count})"); } } } // pass 2 - determine room links foreach (var room in rooms) { foreach (var door in room.doors) { // average the door pos var inPos = door.doorCenter; var(dx, dy) = DirectionStepper.stepIn(door.dir); // now scan in direction var distScanned = 0; // set initial pos var ix = inPos.X; var iy = inPos.Y; // set scan pos var sx = ix; var sy = iy; while (distScanned < ROOM_LINK_DIST) { // update scan vars distScanned = Math.Abs(ix - sx) + Math.Abs(iy - sy); sx += dx; sy += dy; // check if we're inside another room var sPt = new Point(sx, sy); // TODO: optimize this checking // check if we're in any other room var otherRoom = default(Map.Room); foreach (var testRoom in rooms) { if (testRoom.inRoom(sPt)) { otherRoom = testRoom; break; } } if (otherRoom != null) { // set up the connection door.roomOther = otherRoom; room.links.Add(otherRoom); Global.log.trace( $"room link [dist: {distScanned}] from Room[@{room.center}] to Room[@{otherRoom.center}]"); break; } } } } // set up room graph mapRepr.roomGraph = new RoomGraph(rooms); }
public void analyze() { // 1. build mapping from room to center nodes foreach (var room in roomGraph.rooms) { // create unconnected center nodes sngNodes[room] = new DelayedNode(new StructuralNavigationGraph.Node(room.center) { room = room // store link to room }); } // 2. create nodes of indirection foreach (var nodePair in sngNodes) { var room = nodePair.Key; var centerNode = nodePair.Value; foreach (var door in room.doors) { // calculate positions of inner and outer door nodes var doorCenter = door.doorCenter; var(dx, dy) = DirectionStepper.stepIn(door.dir); var innerDoor = doorCenter + new Point(-dx * StructuralNavigationGraph.DOOR_NODE_DIST, -dy * StructuralNavigationGraph.DOOR_NODE_DIST); var outerDoor = doorCenter + new Point(dx * StructuralNavigationGraph.DOOR_NODE_DIST, dy * StructuralNavigationGraph.DOOR_NODE_DIST); var innerDoorNode = new DelayedNode(new StructuralNavigationGraph.Node(innerDoor)); var outerDoorNode = new DelayedNode(new StructuralNavigationGraph.Node(outerDoor) { edge = new StructuralNavigationGraph.RoomLink(door.roomLocal, door.roomOther) }); centerNode.addPendingLink(innerDoorNode); // attach CENTER - INNER innerDoorNode.addPendingLink(outerDoorNode); // attach INNER - OUTER // when collapsed, we should get CENTER - INNER - OUTER (a "spike") } centerNode.collapse(); } var centralNodes = sngNodes.Values.Select(x => x.centerNode).ToList(); allNodes.AddRange(centralNodes); // now, we have all our nodes of the form // CENTER with [INNER - OUTER] spikes for each door // 3. next, we need to merge these spikes by door link // and merge all these delayed nodes into a graph var spikeNodes = new List <StructuralNavigationGraph.Node>(); foreach (var nodePair in sngNodes) { var room = nodePair.Key; var centerNode = nodePair.Value; var gn = centerNode.centerNode; // do a BFS on the node. we are going to make a list of spike nodes. var visited = new Dictionary <StructuralNavigationGraph.Node, bool>(); var queue = new Queue <StructuralNavigationGraph.Node>(); // mark the starting node as visited, and queue it visited[gn] = true; queue.Enqueue(gn); while (queue.Count > 0) { // process the current vertex var vertex = queue.Dequeue(); // add to node list if (!allNodes.Contains(vertex)) { allNodes.Add(vertex); } if (vertex.edge != null) { spikeNodes.Add(vertex); } // get all adjacent, and enqueue any unvisited foreach (var adjacent in vertex.links) { if (!visited.ContainsKey(adjacent) || !visited[adjacent]) // not visited { visited[adjacent] = true; // mark visited queue.Enqueue(adjacent); // enqueue } } } } // then, we will x2 loop through all spike nodes and match spike to spike via link equiv foreach (var spk1 in spikeNodes) { foreach (var spk2 in spikeNodes) { if (spk1 == spk2) { continue; } // now, match spike-to-spike if (spk1.edge.equiv(spk2.edge)) { // we have a match! spk1.links.Add(spk2); spk2.links.Add(spk1); } } } // this should give us a graph!e }