Skip to content

Commit

Permalink
feat: determine connected components
Browse files Browse the repository at this point in the history
This commit allows determining the connected components, and visualises them in the ConnectedComponents scene by coloring the tiles
  • Loading branch information
SilasPeters committed Apr 14, 2024
1 parent 52b102a commit b921321
Show file tree
Hide file tree
Showing 9 changed files with 617 additions and 40 deletions.
432 changes: 432 additions & 0 deletions aplib.net-demo/Assets/Scenes/ConnectedComponents.unity

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions aplib.net-demo/Assets/Scenes/ConnectedComponents.unity.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions aplib.net-demo/Assets/Scripts/Tiles/Corner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ namespace Assets.Scripts.Tiles
/// <summary>
/// Represents a corner tile.
/// ___ ___
/// | | |_|
/// | |____
/// |_| | |
/// ____| |
/// |_____|
/// </summary>
public class Corner : Tile
{
/// <summary>
/// Initializes a new instance of the <see cref="Corner"/> class.
/// The default is a top-right corner.
/// The default is a top-left corner.
/// </summary>
/// <param name="rotate">The amount of times to rotate the tile.</param>
public Corner(int rotate = 0)
{
Rotation = rotate;
AllowedDirections = new List<bool> { false, false, false, false };

int index = rotate % 4;
int index = (rotate + 3) % 4;
int nextIndex = (index + 1) % 4;

AllowedDirections[index] = true;
Expand Down
1 change: 1 addition & 0 deletions aplib.net-demo/Assets/Scripts/Tiles/Straight.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class Straight : Tile
/// <param name="rotate">The amount of times to rotate the tile.</param>
public Straight(int rotate = 0)
{
rotate %= 4;
Rotation = rotate;
bool isVertical = rotate % 2 == 0;

Expand Down
8 changes: 4 additions & 4 deletions aplib.net-demo/Assets/Scripts/Tiles/TSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@ namespace Assets.Scripts.Tiles
{
/// <summary>
/// Represents a T-section tile.
/// _______
/// |_____|
/// ___ ___
/// |_| |_|
/// _______
/// |_____|
/// </summary>
public class TSection : Tile
{
/// <summary>
/// Initializes a new instance of the <see cref="TSection"/> class.
/// The default is a T-section with the top side closed.
/// The default is a T-section with the top side opened.
/// </summary>
/// <param name="rotate">The amount of times to rotate the tile.</param>
public TSection(int rotate = 0)
{
Rotation = rotate;
AllowedDirections = new List<bool> { true, true, true, true };

int index = rotate % 4;
int index = (rotate + 2) % 4;

AllowedDirections[index] = false;
}
Expand Down
3 changes: 3 additions & 0 deletions aplib.net-demo/Assets/Scripts/Tiles/Tile.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using UnityEngine;

namespace Assets.Scripts.Tiles
{
Expand All @@ -7,6 +8,8 @@ namespace Assets.Scripts.Tiles
/// </summary>
public abstract class Tile
{
public GameObject GameObject { get; set; }

/// <summary>
/// The rotation of the tile. 0 = 0 degrees, 1 = 90 degrees, 2 = 180 degrees, 3 = 270 degrees.
/// </summary>
Expand Down
16 changes: 13 additions & 3 deletions aplib.net-demo/Assets/Scripts/WFC/Cell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,18 @@ public class Cell
/// </summary>
public List<Tile> Candidates { get; set; }

public int X { get; }

public int Y { get; }

/// <summary>
/// Initializes a new instance of the <see cref="Cell"/> class.
/// </summary>
public Cell()
/// </summary> // TODO comment posX/Y on both constructors
public Cell(int posX, int posY)
{
X = posX;
Y = posY;

Tile = new Empty();
Candidates = new List<Tile>()
{
Expand All @@ -49,8 +56,11 @@ public Cell()
/// Initializes a new instance of the <see cref="Cell"/> class.
/// </summary>
/// <param name="tiles">The possible tiles that can be placed in this cell.</param>
public Cell(List<Tile> tiles)
public Cell(int posX, int posY, List<Tile> tiles)
{
X = posX;
Y = posY;

Tile = new Empty();
Candidates = tiles;
}
Expand Down
105 changes: 102 additions & 3 deletions aplib.net-demo/Assets/Scripts/WFC/Grid.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using Assets.Scripts.Tiles;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

namespace Assets.Scripts.WFC
{
Expand Down Expand Up @@ -42,8 +45,8 @@ public Grid(int width, int height)
/// <param name="y">The y-coordinate of the cell.</param>
public Cell this[int x, int y]
{
get => _cells[(y * Width) + x];
set => _cells[(y * Width) + x] = value;
get => _cells[CoordinatesToIndex(x, y)];
set => _cells[CoordinatesToIndex(x, y)] = value;
}

/// <summary>
Expand All @@ -62,7 +65,18 @@ public Cell this[int index]
public void Init()
{
for (int i = 0; i < Width * Height; i++)
_cells.Add(new Cell());
{
(int x, int y) = IndexToCoordinates(i);
_cells.Add(new Cell(x, y));
}
}

protected (int x, int y) IndexToCoordinates(int index) => (index % Width, index / Width);

protected int CoordinatesToIndex(int x, int y)
{
if (x >= Width || y >= Height) throw new IndexOutOfRangeException("Coordinates specified are out of range.");
return y * Width + x;
}

/// <summary>
Expand All @@ -76,5 +90,90 @@ public void PlaceRoom(int x, int y, Room room)
this[x, y].Tile = room;
this[x, y].Candidates = new List<Tile>();
}

public ICollection<Cell> Get4NeighbouringCells(Cell cell)
{
ICollection<Cell> neighbours = new Collection<Cell>();
if (cell.X > 0) neighbours.Add(this[cell.X - 1, cell.Y]);
if (cell.X < Width - 1) neighbours.Add(this[cell.X + 1, cell.Y]);
if (cell.Y > 0) neighbours.Add(this[cell.X, cell.Y - 1]);
if (cell.Y < Height - 1) neighbours.Add(this[cell.X, cell.Y + 1]);
return neighbours;
}

public ICollection<Cell> Get8NeighbouringCells(Cell cell)
{
ICollection<Cell> neighbours = new Collection<Cell>();
for (int i = -1; i < 2; i++)
{
if (cell.X + i < 0 || cell.X + i >= Width) continue; // No out of range
for (int j = -1; j < 2; j++)
{
if (i == j && i == 0) continue; // Skip the cell itself
if (cell.Y + j < 0 || cell.Y + j >= Height) continue; // No out of range
neighbours.Add(this[cell.X + i, cell.Y + j]);
}
}
return neighbours;
}

/// <summary>
///
/// </summary>
/// <param name="cell"></param>
/// <returns></returns>
/// <remarks>Assumes that the cells are assigned a tile</remarks>
public ICollection<Cell> GetConnectedNeighbours(Cell cell)
{
ICollection<Cell> connectedNeighbours = new Collection<Cell>();
ICollection<Cell> neighbours = Get4NeighbouringCells(cell); // Note: no diagonal neighbours
foreach (Cell neighbour in neighbours)
{
if (cell.Tile.CanConnectInDirection(1) && neighbour.X > cell.X && neighbour.Tile.CanConnectInDirection(3))
connectedNeighbours.Add(neighbour);
else if (cell.Tile.CanConnectInDirection(3) && neighbour.X < cell.X && neighbour.Tile.CanConnectInDirection(1))
connectedNeighbours.Add(neighbour);
else if (cell.Tile.CanConnectInDirection(0) && neighbour.Y > cell.Y && neighbour.Tile.CanConnectInDirection(2))
connectedNeighbours.Add(neighbour);
else if (cell.Tile.CanConnectInDirection(2) && neighbour.Y < cell.Y && neighbour.Tile.CanConnectInDirection(0))
connectedNeighbours.Add(neighbour);
}

return connectedNeighbours;
}

public IList<ISet<Cell>> DetermineConnectedComponents()
{
ISet<Cell> unvisitedCells = new HashSet<Cell>(_cells.Where(cell => cell.Tile is not Empty)); // Deep copy
IList<ISet<Cell>> connectedComponents = new List<ISet<Cell>>();

while (unvisitedCells.Any())
{
ISet<Cell> connectedComponent = new HashSet<Cell>();
connectedComponents.Add(connectedComponent);

// Determine connected component, which updates unvisitedCells and connectedComponent
DetermineSingleConnectedComponent(unvisitedCells, connectedComponent, unvisitedCells.First());
}

return connectedComponents;
}

public void DetermineSingleConnectedComponent(in ISet<Cell> unvisitedCells, in ISet<Cell> connectedComponent, Cell cell)
{
connectedComponent.Add(cell);
unvisitedCells.Remove(cell);

ICollection<Cell> connectedNeighbours = GetConnectedNeighbours(cell);
foreach (Cell connectedNeighbour in connectedNeighbours)
{
if (!unvisitedCells.Contains(connectedNeighbour)) continue; // Already visited

connectedComponent.Add(connectedNeighbour);
unvisitedCells.Remove(connectedNeighbour);

DetermineSingleConnectedComponent(unvisitedCells, connectedComponent, connectedNeighbour);
}
}
}
}
77 changes: 51 additions & 26 deletions aplib.net-demo/Assets/Scripts/WFC/GridPlacer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Assets.Scripts.Tiles;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Assets.Scripts.WFC
Expand Down Expand Up @@ -35,7 +36,7 @@ public class GridPlacer : MonoBehaviour
public RoomObjects RoomObjects;

/// <summary>
/// Awake is called when the script instance is being loaded.
/// This contains the whole 'pipeline' of level generation, including initialising the grid and placing teleporters.
/// </summary>
public void Awake()
{
Expand All @@ -52,29 +53,31 @@ public void Awake()
PlaceTile(x, y, _grid[x, y].Tile);
}
}

JoinConnectedComponentsWithTeleporters();
}

/// <summary>
/// A temporary function to fill the grid with rooms.
/// </summary>
public void TempFillFunction()
{
_grid.PlaceRoom(2, 1, new Room(new List<bool> { false, true, true, false }));

_grid.PlaceRoom(4, 4, new Room(new List<bool> { false, true, true, false }));

// Road 1
_grid[2, 2].Tile = new Straight();
_grid[2, 3].Tile = new Straight();
_grid[2, 4].Tile = new Corner(2);
_grid[3, 4].Tile = new Straight(1);

// Road 2
_grid[3, 1].Tile = new Straight(1);
_grid[4, 1].Tile = new TSection(3);
_grid[4, 2].Tile = new Straight();
_grid[4, 3].Tile = new Straight();
_grid[4, 0].Tile = new DeadEnd();
_grid[0, 0].Tile = new TSection(3);
_grid[0, 1].Tile = new Crossing();
_grid[0, 2].Tile = new DeadEnd(2);
_grid[0, 3].Tile = new Straight(1);
_grid[1, 0].Tile = new TSection();
_grid[1, 1].Tile = new Straight();
_grid[1, 2].Tile = new Corner(2);
_grid[1, 3].Tile = new Crossing();
_grid[2, 0].Tile = new Corner();
_grid.PlaceRoom(2, 1, new Room(new List<bool> { true, true, true, true }));

_grid.PlaceRoom(3, 3, new Room(new List<bool> { true, true, true, true }));
_grid[3, 2].Tile = new Corner(1);
_grid[4, 2].Tile = new TSection(3);
_grid[4, 1].Tile = new Straight();
_grid.PlaceRoom(4, 0, new Room(new List<bool> { true, true, true, true }));
}

/// <summary>
Expand All @@ -87,18 +90,40 @@ public void PlaceTile(int x, int y, Tile tile)
{
GameObject prefab = tile switch
{
Corner _ => RoomObjects.Corner,
Crossing _ => RoomObjects.Crossing,
DeadEnd _ => RoomObjects.DeadEnd,
Empty _ => RoomObjects.Empty,
Room _ => RoomObjects.Room,
Straight _ => RoomObjects.Straight,
TSection _ => RoomObjects.TSection,
_ => null
Corner => RoomObjects.Corner,
Crossing => RoomObjects.Crossing,
DeadEnd => RoomObjects.DeadEnd,
Empty => RoomObjects.Empty,
Room => RoomObjects.Room,
Straight => RoomObjects.Straight,
TSection => RoomObjects.TSection,
_ => null
};

if (prefab != null)
_ = Instantiate(prefab, new Vector3(x * _tileSizeX, 0, y * _tileSizeY), Quaternion.Euler(0, tile.Rotation * _tileRotation, 0), transform);
{
tile.GameObject = Instantiate(prefab,
new Vector3(x * _tileSizeX, 0, y * _tileSizeY),
Quaternion.Euler(0, tile.Rotation * _tileRotation, 0),
transform);
}
}

private void JoinConnectedComponentsWithTeleporters()
{
IList<ISet<Cell>> connectedComponents = _grid.DetermineConnectedComponents();

// We draw all the connected components individually
foreach (ISet<Cell> connectedComponent in connectedComponents)
{
Color color = GetUnusedColor();
foreach (Cell cell in connectedComponent)
cell.Tile.GameObject.GetComponent<MeshRenderer>().material.color = color;
}
}

private static Color[] _colors = { Color.red, Color.blue, Color.green, Color.yellow, Color.magenta, Color.cyan };
private static int _colorIndex = -1;
private static Color GetUnusedColor() => _colors[_colorIndex = (_colorIndex + 1) % _colors.Length];
}
}

0 comments on commit b921321

Please sign in to comment.