private void AssignOceanCoastAndLand() { Queue <CellCenter> queue = new Queue <CellCenter>(); int numWater = 0; //Set the cell to water / ocean / border foreach (var center in cells) { numWater = 0; foreach (var corner in center.cellCorners) { if (corner.isBorder) //If a corner connected to this cell is at the map border, then this cell is a ocean and the corner itself is water { center.isBorder = true; center.isOcean = true; corner.isWater = true; queue.Enqueue(center); } if (corner.isWater) { numWater += 1; } } //If the amount of corners on this cell is grater than the defined threshold, this cell is water center.isWater = center.isOcean || numWater >= center.cellCorners.Count * LAKE_THRESHOLD; } //Every cell around a border must be a ocean too, and we loop thought the neighbors until can't find more water (at which case, the queue would be empty) while (queue.Count > 0) { CellCenter c = queue.Dequeue(); foreach (var n in c.neighborCells) { if (n.isWater && !n.isOcean) { n.isOcean = true; queue.Enqueue(n); //If this neighbor is a ocean, we add it to the queue so wwe can check its neighbors } } } //Set the Coast cells based on the neighbors. If a cell has at least one ocean and one land neighbor, then the cell is a coast foreach (var cell in cells) { int numOcean = 0; int numLand = 0; foreach (var n in cell.neighborCells) { numOcean += n.isOcean ? 1 : 0; numLand += !n.isWater ? 1 : 0; } cell.isCoast = numOcean > 0 && numLand > 0; } }
private void DrawElevation() { Color low = Color.black; Color high = Color.white; Color water = Color.blue * 0.5f; water.a = 1; Color waterDeep = Color.blue * 0.1f; waterDeep.a = 1; for (int x = 0; x < resolution.x; x++) { for (int y = 0; y < resolution.y; y++) { CellCenter c = generator.cells[cellIDs[x + y * resolution.y]]; if (c.elevation < 0) { texColors[x, y] = Color.Lerp(waterDeep, water, 1 + c.elevation); } else { texColors[x, y] = Color.Lerp(low, high, c.elevation); } } } }
private void DrawMapBorders() { for (int x = 0; x < resolution.x; x++) { for (int y = 0; y < resolution.y; y++) { CellCenter c = generator.cells[cellIDs[x + y * resolution.y]]; if (c.isBorder) { texColors[x, y] = Color.red; } } } Color borderCorner = new Color(1, .5f, .5f); foreach (var corner in generator.corners) { if (corner.isBorder) { DrawGraphPoint(corner, borderCorner, POINT_SIZE * 2); } } }
private void DrawIslands() { Dictionary <int, Color> islandColors = new Dictionary <int, Color>(); for (int i = 0; i < generator.islands.Count; i++) { islandColors.Add(generator.islands[i][0].islandID, Color.HSVToRGB((i * (360f / generator.islands.Count)) / 360f, Random.Range(.5f, 1), 1)); } for (int x = 0; x < resolution.x; x++) { for (int y = 0; y < resolution.y; y++) { int currentCellID = cellIDs[x + y * resolution.y]; CellCenter c = generator.cells[currentCellID]; if (c.islandID < 0) { texColors[x, y] = Color.black; } else { texColors[x, y] = islandColors[c.islandID]; } } } }
private int[] GetClosestCenterForPixels() { //Send the data to the compute shader so the GPU can do the hard work CellData[] cellData = new CellData[generator.cells.Count]; ComputeBuffer cellDataBuffer = new ComputeBuffer(generator.cells.Count, sizeof(int) * 2); ComputeBuffer cellIdByPixeldBuffer = new ComputeBuffer(resolution.x * resolution.y, sizeof(int)); //This is the buffer we're gonna read from for (int i = 0; i < cellData.Length; i++) { CellCenter c = generator.cells[i]; cellData[i] = new CellData() { position = MapGraphCoordToTextureCoords(c.position.x, c.position.y) }; } cellDataBuffer.SetData(cellData); cellIdByPixeldBuffer.SetData(new int[resolution.x * resolution.y]); //we pass an empty array, since we just want to retrieve this data computeShader.SetBuffer(findClosestCellKernelIndex, "_CellData", cellDataBuffer); computeShader.SetBuffer(findClosestCellKernelIndex, "_CellIDByPixel", cellIdByPixeldBuffer); computeShader.SetInt("_Resolution", resolution.x); computeShader.Dispatch(findClosestCellKernelIndex, resolution.x / 8, resolution.y / 8, 1); //Get the result data back from the GPU int[] centersIDs = new int[resolution.x * resolution.y]; cellIdByPixeldBuffer.GetData(centersIDs); cellDataBuffer?.Dispose(); cellIdByPixeldBuffer?.Dispose(); return(centersIDs); }
private CellCenter GetClosestCenterFromPoint(Vector2 point) { float smallestDistance = float.MaxValue; CellCenter c = null; foreach (var center in generator.cells) { float d = Vector2.Distance(point, center.position); if (d < smallestDistance) { smallestDistance = d; c = center; } } return(c); }
private void DrawBiomes() { for (int x = 0; x < resolution.x; x++) { for (int y = 0; y < resolution.y; y++) { int currentCellID = cellIDs[x + y * resolution.y]; CellCenter c = generator.cells[currentCellID]; BiomeColor biome = biomes.FirstOrDefault(b => b.biome == c.biome); if (biome != null) { texColors[x, y] = biome.color; } else { texColors[x, y] = Color.black; } } } }
private void DrawWaterAndLand() { Color ocean = new Color(.1f, .1f, .5f); Color water = new Color(.5f, .5f, .7f); Color land = new Color(.7f, .7f, .5f); Color coast = new Color(.5f, .5f, .3f); for (int x = 0; x < resolution.x; x++) { for (int y = 0; y < resolution.y; y++) { int currentCellID = cellIDs[x + y * resolution.y]; CellCenter c = generator.cells[currentCellID]; if (c.isWater) { if (c.isOcean) { texColors[x, y] = ocean; } else { texColors[x, y] = water; } } else { if (c.isCoast) { texColors[x, y] = coast; } else { texColors[x, y] = land; } } } } }
private void DrawSelected() { CellCenter c = generator.cells[selectedID]; DrawGraphPoint(c, Color.green, POINT_SIZE * 3, 2); for (int i = 0; i < c.neighborCells.Count; i++) { DrawGraphPoint(c.neighborCells[i], Color.magenta, POINT_SIZE * 2, 2); } for (int i = 0; i < c.cellCorners.Count; i++) { DrawGraphPoint(c.cellCorners[i], Color.yellow, POINT_SIZE * 2, 2); } for (int i = 0; i < c.borderEdges.Count; i++) { DrawGraphEdge(c.borderEdges[i], new Color(0, .5f, .5f), 1, false); DrawGraphEdge(c.borderEdges[i], Color.cyan, 1, true); } }
private string GetInfoFromCell() { CellCenter c = generator.cells.First(x => x.index == selectedID); string info = $"CELL:\n"; info += "\n- " + string.Join("\n- ", $"id: {c.index}", $"elevation: {c.elevation}", $"moisture: {c.moisture}", $"isCoast: {c.isCoast}", $"isWater: {c.isWater}", $"isOcean: {c.isOcean}", $"isBorder: {c.isBorder}" ); info += "\n\nEDGES:\n"; foreach (var edge in c.borderEdges) { info += "\n- " + string.Join("\n", $"id: {edge.index}" ); } info += "\n\nCORNERS:\n"; foreach (var corner in c.cellCorners) { info += "\n- " + string.Join("\n", $"id: {corner.index}", $"elevation: {corner.elevation}" ); } return(info); }
private void DrawMoisture() { Color water = new Color(.1f, .1f, .5f); Color wet = new Color(0.25f, .39f, .2f); Color dry = new Color(.8f, .7f, .5f); for (int x = 0; x < resolution.x; x++) { for (int y = 0; y < resolution.y; y++) { int currentCellID = cellIDs[x + y * resolution.y]; CellCenter c = generator.cells[currentCellID]; if (c.isWater) { texColors[x, y] = water; } else { texColors[x, y] = Color.Lerp(dry, wet, c.moisture); } } } }
private void AssignMoisture() { HashSet <CellCenter> moistureSeeds = new HashSet <CellCenter>(); //we use a HashSet to guarantee we won't have duplicate values foreach (var edge in edges) { //Find all riverbanks (regions adjacent to rivers) if (edge.waterVolume > 0) { moistureSeeds.Add(edge.d0); moistureSeeds.Add(edge.d1); } //Find all lakeshores (regions adjacent to lakes) if ((edge.d0.isWater && !edge.d0.isOcean) || (edge.d1.isWater && !edge.d1.isOcean)) { moistureSeeds.Add(edge.d0); moistureSeeds.Add(edge.d1); } } //Copy the hashset values to a queue Queue <CellCenter> queue = new Queue <CellCenter>(moistureSeeds); Dictionary <int, float> waterDistance = new Dictionary <int, float>(); //Set the distance of each cell in the queue to 0, since they're the closest to a water source. Any other cell gets -1 foreach (var cell in cells) { if (queue.Contains(cell)) { waterDistance.Add(cell.index, 0); } else { waterDistance.Add(cell.index, -1); } } float maxDistance = 1; while (queue.Count > 0) { CellCenter currentCell = queue.Dequeue(); foreach (var neighbor in currentCell.neighborCells) { if (!neighbor.isWater && waterDistance[neighbor.index] < 0) { float newDistance = waterDistance[currentCell.index] + 1; waterDistance[neighbor.index] = newDistance; if (newDistance > maxDistance) { maxDistance = newDistance; } queue.Enqueue(neighbor); } } } //Normalize the moisture values foreach (var cell in cells) { cell.moisture = cell.isWater ? 1 : 1 - (waterDistance[cell.index] / maxDistance); } ////Redistribute moisture values evenly //List<CellCenter> land = cells.Where(x => !x.isWater).ToList(); //land.Sort((x, y) => //{ // if (x.moisture < y.moisture) // return -1; // if (x.moisture > y.moisture) // return 1; // return 0; //}); //float minMoisture = 0; //float maxMoisture = 1; //for (int i = 0; i < land.Count; i++) //{ // land[i].moisture = minMoisture + (maxMoisture - minMoisture) * i / (land.Count - 1); //} }
private void DetectIslands() { List <CellCenter> land = new List <CellCenter>(); foreach (var cell in cells) { if (cell.isOcean) { cell.islandID = -1; //Ocean tiles doesn't have a island } else { land.Add(cell); } } islands = new List <List <CellCenter> >(); for (int i = 0; i < land.Count; i++) { CellCenter currentCell = land[i]; //Is the current cell in any island already? if (!islands.Any(x => x.Contains(currentCell))) { //If not, create a new island for it and add the current cell List <CellCenter> currentIsland = new List <CellCenter>(); islands.Add(currentIsland); currentIsland.Add(currentCell); currentCell.islandID = islands.Count - 1; //Create a queue with the current cell. We check its neighbors to see if they are not an ocean tile. If not, check if it was already added to the current island. Queue <CellCenter> islandQueue = new Queue <CellCenter>(); islandQueue.Enqueue(currentCell); while (islandQueue.Count > 0) { currentCell = islandQueue.Dequeue(); foreach (var neighbor in currentCell.neighborCells) { if (!neighbor.isOcean && !currentIsland.Contains(neighbor)) { islandQueue.Enqueue(neighbor); //Add the neighbor to the queue so we can then check its neighbors, until we can't find any neightbor that is either a ocean or was not added to the current island currentIsland.Add(neighbor); neighbor.islandID = islands.Count - 1; } } } } } if (singleIsland) { minIslandSize = islands.Max(x => x.Count); } //Remove all islands that have a lower cell count than the minimum size for (int i = 0; i < islands.Count; i++) { List <CellCenter> currentIsland = islands[i]; if (currentIsland.Count < minIslandSize) { foreach (var cell in currentIsland) { //We transform all cells in the islands we want to discard into ocean cells cell.isWater = true; cell.isOcean = true; cell.islandID = -1; } //Remove the current island islands.RemoveAt(i); i--; } } }
private void GenerateGraphs(List <Vector2> points) { //Generate the Voronoi Rectf bounds = new Rectf(0, 0, size.x, size.y); Voronoi voronoi = new Voronoi(points, bounds, relaxation); //Cell centers foreach (var site in voronoi.SitesIndexedByLocation) { CellCenter c = new CellCenter(); c.index = cells.Count; c.position = site.Key; cells.Add(c); } //Cell Corners foreach (var edge in voronoi.Edges) { //If the edge doesn't have clipped ends, it was not withing bounds if (edge.ClippedEnds == null) { continue; } if (!corners.Any(x => x.position == edge.ClippedEnds[LR.LEFT])) { CellCorner c = new CellCorner(); c.index = corners.Count; c.position = edge.ClippedEnds[LR.LEFT]; c.isBorder = c.position.x == 0 || c.position.x == size.x || c.position.y == 0 || c.position.y == size.y; corners.Add(c); } if (!corners.Any(x => x.position == edge.ClippedEnds[LR.RIGHT])) { CellCorner c = new CellCorner(); c.index = corners.Count; c.position = edge.ClippedEnds[LR.RIGHT]; c.isBorder = c.position.x == 0 || c.position.x == size.x || c.position.y == 0 || c.position.y == size.y; corners.Add(c); } } //Define some local helper functions to help with the loop below void AddPointToPointList <T>(List <T> list, T point) where T : MapPoint { if (!list.Contains(point)) { list.Add(point); } } //Voronoi and Delaunay edges. Each edge point to two cells and two corners, so we can store both the sites and corners into a single edge object (thus making two edges into one object) foreach (var voronoiEdge in voronoi.Edges) { if (voronoiEdge.ClippedEnds == null) { continue; } CellEdge edge = new CellEdge(); edge.index = edges.Count; //Set the voronoi edge edge.v0 = corners.First(x => x.position == voronoiEdge.ClippedEnds[LR.LEFT]); edge.v1 = corners.First(x => x.position == voronoiEdge.ClippedEnds[LR.RIGHT]); //Set the Delaunay edge edge.d0 = cells.First(x => x.position == voronoiEdge.LeftSite.Coord); edge.d1 = cells.First(x => x.position == voronoiEdge.RightSite.Coord); edges.Add(edge); /*Set the relationships*/ //Set the relationship between this edge and the connected cells centers/corners edge.d0.borderEdges.Add(edge); edge.d1.borderEdges.Add(edge); edge.v0.connectedEdges.Add(edge); edge.v1.connectedEdges.Add(edge); //Set the relationship between the CELL CENTERS connected to this edge AddPointToPointList(edge.d0.neighborCells, edge.d1); AddPointToPointList(edge.d1.neighborCells, edge.d0); //Set the relationship between the CORNERS connected to this edge AddPointToPointList(edge.v0.neighborCorners, edge.v1); AddPointToPointList(edge.v1.neighborCorners, edge.v0); //Set the relationship of the CORNERS connected to this edge and the CELL CENTERS connected to this edge AddPointToPointList(edge.d0.cellCorners, edge.v0); AddPointToPointList(edge.d0.cellCorners, edge.v1); AddPointToPointList(edge.d1.cellCorners, edge.v0); AddPointToPointList(edge.d1.cellCorners, edge.v1); //Same as above, but the other way around AddPointToPointList(edge.v0.touchingCells, edge.d0); AddPointToPointList(edge.v0.touchingCells, edge.d1); AddPointToPointList(edge.v1.touchingCells, edge.d0); AddPointToPointList(edge.v1.touchingCells, edge.d1); } }