private void GenerateVoronoi() { voronoiGraph = new Voronoi(minDistance); List <Vector2> sites = new List <Vector2>(); for (int i = 0; i < 50; i++) { bool tooClose = false; Vector2 newSite = new Vector2(Rand.Range(0.0f, size, false), Rand.Range(0.0f, size, false)); foreach (Vector2 site in sites) { if (Vector2.Distance(newSite, site) <= minDistance) { tooClose = true; break; } } if (!tooClose && !(newSite.X == 0 || newSite.X == size || newSite.Y == 0 || newSite.Y == size)) { sites.Add(newSite); } else { i--; } } graphEdges = voronoiGraph.MakeVoronoiGraph(sites, size, size); graphCells = CaveGenerator.GraphEdgesToCells(graphEdges, new Rectangle(0, 0, size, size), (float)size, out graphCellsGrid); sites.Clear(); }
private void Generate() { connections.Clear(); Locations.Clear(); GenerateNoiseMap(generationParams.NoiseOctaves, generationParams.NoisePersistence); List <Vector2> sites = new List <Vector2>(); float mapRadius = size / 2; Vector2 mapCenter = new Vector2(mapRadius, mapRadius); float locationRadius = mapRadius * generationParams.LocationRadius; for (float x = mapCenter.X - locationRadius; x < mapCenter.X + locationRadius; x += generationParams.VoronoiSiteInterval) { for (float y = mapCenter.Y - locationRadius; y < mapCenter.Y + locationRadius; y += generationParams.VoronoiSiteInterval) { float noiseVal = Noise[(int)(x / size * generationParams.NoiseResolution), (int)(y / size * generationParams.NoiseResolution)]; if (Rand.Range(generationParams.VoronoiSitePlacementMinVal, 1.0f, Rand.RandSync.Server) < noiseVal * generationParams.VoronoiSitePlacementProbability) { sites.Add(new Vector2(x, y)); } } } Voronoi voronoi = new Voronoi(0.5f); List <GraphEdge> edges = voronoi.MakeVoronoiGraph(sites, size, size); float zoneRadius = size / 2 / generationParams.DifficultyZones; sites.Clear(); foreach (GraphEdge edge in edges) { if (edge.Point1 == edge.Point2) { continue; } if (Vector2.DistanceSquared(edge.Point1, mapCenter) >= locationRadius * locationRadius || Vector2.DistanceSquared(edge.Point2, mapCenter) >= locationRadius * locationRadius) { continue; } Location[] newLocations = new Location[2]; newLocations[0] = Locations.Find(l => l.MapPosition == edge.Point1 || l.MapPosition == edge.Point2); newLocations[1] = Locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.Point1 || l.MapPosition == edge.Point2)); for (int i = 0; i < 2; i++) { if (newLocations[i] != null) { continue; } Vector2[] points = new Vector2[] { edge.Point1, edge.Point2 }; int positionIndex = Rand.Int(1, Rand.RandSync.Server); Vector2 position = points[positionIndex]; if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) { position = points[1 - positionIndex]; } int zone = MathHelper.Clamp(generationParams.DifficultyZones - (int)Math.Floor(Vector2.Distance(position, mapCenter) / zoneRadius), 1, generationParams.DifficultyZones); newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server)); Locations.Add(newLocations[i]); } var newConnection = new LocationConnection(newLocations[0], newLocations[1]); float centerDist = Vector2.Distance(newConnection.CenterPos, mapCenter); newConnection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 10.0f, Rand.RandSync.Server), 0, 100); connections.Add(newConnection); } //remove connections that are too short float minConnectionDistanceSqr = generationParams.MinConnectionDistance * generationParams.MinConnectionDistance; for (int i = connections.Count - 1; i >= 0; i--) { LocationConnection connection = connections[i]; if (Vector2.DistanceSquared(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minConnectionDistanceSqr) { continue; } //locations.Remove(connection.Locations[0]); connections.Remove(connection); foreach (LocationConnection connection2 in connections) { if (connection2.Locations[0] == connection.Locations[0]) { connection2.Locations[0] = connection.Locations[1]; } if (connection2.Locations[1] == connection.Locations[0]) { connection2.Locations[1] = connection.Locations[1]; } } } HashSet <Location> connectedLocations = new HashSet <Location>(); foreach (LocationConnection connection in connections) { connection.Locations[0].Connections.Add(connection); connection.Locations[1].Connections.Add(connection); connectedLocations.Add(connection.Locations[0]); connectedLocations.Add(connection.Locations[1]); } //remove orphans Locations.RemoveAll(c => !connectedLocations.Contains(c)); //remove locations that are too close to each other float minLocationDistanceSqr = generationParams.MinLocationDistance * generationParams.MinLocationDistance; for (int i = Locations.Count - 1; i >= 0; i--) { for (int j = Locations.Count - 1; j > i; j--) { float dist = Vector2.DistanceSquared(Locations[i].MapPosition, Locations[j].MapPosition); if (dist > minLocationDistanceSqr) { continue; } //move connections from Locations[j] to Locations[i] foreach (LocationConnection connection in Locations[j].Connections) { if (connection.Locations[0] == Locations[j]) { connection.Locations[0] = Locations[i]; } else { connection.Locations[1] = Locations[i]; } Locations[i].Connections.Add(connection); } Locations.RemoveAt(j); } } for (int i = connections.Count - 1; i >= 0; i--) { i = Math.Min(i, connections.Count - 1); LocationConnection connection = connections[i]; for (int n = Math.Min(i - 1, connections.Count - 1); n >= 0; n--) { if (connection.Locations.Contains(connections[n].Locations[0]) && connection.Locations.Contains(connections[n].Locations[1])) { connections.RemoveAt(n); } } } foreach (LocationConnection connection in connections) { float centerDist = Vector2.Distance(connection.CenterPos, mapCenter); connection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 10.0f, Rand.RandSync.Server), 0, 100); } AssignBiomes(); GenerateNoiseMapProjSpecific(); }
private void GenerateLocations() { Voronoi voronoi = new Voronoi(0.5f); List <Vector2> sites = new List <Vector2>(); for (int i = 0; i < 100; i++) { sites.Add(new Vector2(Rand.Range(0.0f, size, Rand.RandSync.Server), Rand.Range(0.0f, size, Rand.RandSync.Server))); } List <GraphEdge> edges = voronoi.MakeVoronoiGraph(sites, size, size); sites.Clear(); foreach (GraphEdge edge in edges) { if (edge.point1 == edge.point2) { continue; } //remove points from the edge of the map if (edge.point1.X == 0 || edge.point1.X == size) { continue; } if (edge.point1.Y == 0 || edge.point1.Y == size) { continue; } if (edge.point2.X == 0 || edge.point2.X == size) { continue; } if (edge.point2.Y == 0 || edge.point2.Y == size) { continue; } Location[] newLocations = new Location[2]; newLocations[0] = locations.Find(l => l.MapPosition == edge.point1 || l.MapPosition == edge.point2); newLocations[1] = locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.point1 || l.MapPosition == edge.point2)); for (int i = 0; i < 2; i++) { if (newLocations[i] != null) { continue; } Vector2[] points = new Vector2[] { edge.point1, edge.point2 }; int positionIndex = Rand.Int(1, Rand.RandSync.Server); Vector2 position = points[positionIndex]; if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) { position = points[1 - positionIndex]; } newLocations[i] = Location.CreateRandom(position); locations.Add(newLocations[i]); } //int seed = (newLocations[0].GetHashCode() | newLocations[1].GetHashCode()); connections.Add(new LocationConnection(newLocations[0], newLocations[1])); } //remove connections that are too short float minDistance = 50.0f; for (int i = connections.Count - 1; i >= 0; i--) { LocationConnection connection = connections[i]; if (Vector2.Distance(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minDistance) { continue; } locations.Remove(connection.Locations[0]); connections.Remove(connection); foreach (LocationConnection connection2 in connections) { if (connection2.Locations[0] == connection.Locations[0]) { connection2.Locations[0] = connection.Locations[1]; } if (connection2.Locations[1] == connection.Locations[0]) { connection2.Locations[1] = connection.Locations[1]; } } } foreach (LocationConnection connection in connections) { connection.Locations[0].Connections.Add(connection); connection.Locations[1].Connections.Add(connection); } for (int i = connections.Count - 1; i >= 0; i--) { i = Math.Min(i, connections.Count - 1); LocationConnection connection = connections[i]; for (int n = Math.Min(i - 1, connections.Count - 1); n >= 0; n--) { if (connection.Locations.Contains(connections[n].Locations[0]) && connection.Locations.Contains(connections[n].Locations[1])) { connections.RemoveAt(n); } } } foreach (LocationConnection connection in connections) { Vector2 start = connection.Locations[0].MapPosition; Vector2 end = connection.Locations[1].MapPosition; int generations = (int)(Math.Sqrt(Vector2.Distance(start, end) / 10.0f)); connection.CrackSegments = MathUtils.GenerateJaggedLine(start, end, generations, 5.0f); } AssignBiomes(); }
public void Generate(bool mirror = false) { if (backgroundSpriteManager == null) { var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.BackgroundSpritePrefabs); if (files.Count > 0) { backgroundSpriteManager = new BackgroundSpriteManager(files); } else { backgroundSpriteManager = new BackgroundSpriteManager("Content/BackgroundSprites/BackgroundSpritePrefabs.xml"); } } #if CLIENT if (backgroundCreatureManager == null) { var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.BackgroundCreaturePrefabs); if (files.Count > 0) { backgroundCreatureManager = new BackgroundCreatureManager(files); } else { backgroundCreatureManager = new BackgroundCreatureManager("Content/BackgroundSprites/BackgroundCreaturePrefabs.xml"); } } #endif Stopwatch sw = new Stopwatch(); sw.Start(); if (loaded != null) { loaded.Unload(); } loaded = this; positionsOfInterest = new List <InterestingPosition>(); Voronoi voronoi = new Voronoi(1.0); List <Vector2> sites = new List <Vector2>(); bodies = new List <Body>(); Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); #if CLIENT renderer = new LevelRenderer(this); backgroundColor = generationParams.BackgroundColor; float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3; GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f); #endif SeaFloorTopPos = generationParams.SeaFloorDepth + generationParams.MountainHeightMax + generationParams.SeaFloorVariance; float minWidth = 6500.0f; if (Submarine.MainSub != null) { Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders(); minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height)); } Rectangle pathBorders = borders; pathBorders.Inflate(-minWidth * 2, -minWidth * 2); startPosition = new Vector2( Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server)); endPosition = new Vector2( borders.Width - Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server)); //---------------------------------------------------------------------------------- //generate the initial nodes for the main path and smaller tunnels //---------------------------------------------------------------------------------- List <Vector2> pathNodes = new List <Vector2>(); pathNodes.Add(new Vector2(startPosition.X, borders.Height)); Vector2 nodeInterval = generationParams.MainPathNodeIntervalRange; for (float x = startPosition.X + nodeInterval.X; x < endPosition.X - nodeInterval.X; x += Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server)) { pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, Rand.RandSync.Server))); } pathNodes.Add(new Vector2(endPosition.X, borders.Height)); if (pathNodes.Count <= 2) { pathNodes.Insert(1, borders.Center.ToVector2()); } GenerateTunnels(pathNodes, minWidth); //---------------------------------------------------------------------------------- //generate voronoi sites //---------------------------------------------------------------------------------- Vector2 siteInterval = generationParams.VoronoiSiteInterval; Vector2 siteVariance = generationParams.VoronoiSiteVariance; for (float x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X) { for (float y = siteInterval.Y / 2; y < borders.Height; y += siteInterval.Y) { Vector2 site = new Vector2( x + Rand.Range(-siteVariance.X, siteVariance.X, Rand.RandSync.Server), y + Rand.Range(-siteVariance.Y, siteVariance.Y, Rand.RandSync.Server)); if (smallTunnels.Any(t => t.Any(node => Vector2.Distance(node, site) < siteInterval.Length()))) { //add some more sites around the small tunnels to generate more small voronoi cells if (x < borders.Width - siteInterval.X) { sites.Add(new Vector2(x, y) + Vector2.UnitX * siteInterval * 0.5f); } if (y < borders.Height - siteInterval.Y) { sites.Add(new Vector2(x, y) + Vector2.UnitY * siteInterval * 0.5f); } if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y) { sites.Add(new Vector2(x, y) + Vector2.One * siteInterval * 0.5f); } } if (mirror) { site.X = borders.Width - site.X; } sites.Add(site); } } //---------------------------------------------------------------------------------- // construct the voronoi graph and cells //---------------------------------------------------------------------------------- Stopwatch sw2 = new Stopwatch(); sw2.Start(); List <GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, borders.Width, borders.Height); Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); //construct voronoi cells based on the graph edges cells = CaveGenerator.GraphEdgesToCells(graphEdges, borders, GridCellSize, out cellGrid); Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); //---------------------------------------------------------------------------------- // generate a path through the initial path nodes //---------------------------------------------------------------------------------- List <VoronoiCell> mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize, new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, mirror); for (int i = 2; i < mainPath.Count; i += 3) { positionsOfInterest.Add(new InterestingPosition(mainPath[i].Center, PositionType.MainPath)); } List <VoronoiCell> pathCells = new List <VoronoiCell>(mainPath); //make sure the path is wide enough to pass through EnlargeMainPath(pathCells, minWidth); foreach (InterestingPosition positionOfInterest in positionsOfInterest) { WayPoint wayPoint = new WayPoint(positionOfInterest.Position, SpawnType.Enemy, null); wayPoint.MoveWithLevel = true; } startPosition.X = pathCells[0].Center.X; //---------------------------------------------------------------------------------- // tunnels through the tunnel nodes //---------------------------------------------------------------------------------- foreach (List <Vector2> tunnel in smallTunnels) { if (tunnel.Count < 2) { continue; } //find the cell which the path starts from int startCellIndex = CaveGenerator.FindCellIndex(tunnel[0], cells, cellGrid, GridCellSize, 1); if (startCellIndex < 0) { continue; } //if it wasn't one of the cells in the main path, don't create a tunnel if (cells[startCellIndex].CellType != CellType.Path) { continue; } var newPathCells = CaveGenerator.GeneratePath(tunnel, cells, cellGrid, GridCellSize, pathBorders); positionsOfInterest.Add(new InterestingPosition(tunnel.Last(), PositionType.Cave)); if (tunnel.Count > 4) { positionsOfInterest.Add(new InterestingPosition(tunnel[tunnel.Count / 2], PositionType.Cave)); } pathCells.AddRange(newPathCells); } Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); //---------------------------------------------------------------------------------- // remove unnecessary cells and create some holes at the bottom of the level //---------------------------------------------------------------------------------- cells = CleanCells(pathCells); pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle( (int)(borders.Width * 0.2f), 0, (int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f)))); foreach (VoronoiCell cell in cells) { if (cell.Center.Y < borders.Height / 2) { continue; } cell.edges.ForEach(e => e.OutsideLevel = true); } //---------------------------------------------------------------------------------- // initialize the cells that are still left and insert them into the cell grid //---------------------------------------------------------------------------------- foreach (VoronoiCell cell in pathCells) { cell.edges.ForEach(e => e.OutsideLevel = false); cell.CellType = CellType.Path; cells.Remove(cell); } for (int x = 0; x < cellGrid.GetLength(0); x++) { for (int y = 0; y < cellGrid.GetLength(1); y++) { cellGrid[x, y].Clear(); } } foreach (VoronoiCell cell in cells) { int x = (int)Math.Floor(cell.Center.X / GridCellSize); int y = (int)Math.Floor(cell.Center.Y / GridCellSize); if (x < 0 || y < 0 || x >= cellGrid.GetLength(0) || y >= cellGrid.GetLength(1)) { continue; } cellGrid[x, y].Add(cell); } //---------------------------------------------------------------------------------- // create some ruins //---------------------------------------------------------------------------------- ruins = new List <Ruin>(); for (int i = 0; i < generationParams.RuinCount; i++) { GenerateRuin(mainPath); } //---------------------------------------------------------------------------------- // generate the bodies and rendered triangles of the cells //---------------------------------------------------------------------------------- startPosition.Y = borders.Height; endPosition.Y = borders.Height; List <VoronoiCell> cellsWithBody = new List <VoronoiCell>(cells); List <Vector2[]> triangles; bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out triangles); #if CLIENT renderer.SetBodyVertices(CaveGenerator.GenerateRenderVerticeList(triangles).ToArray(), generationParams.WallColor); renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells), generationParams.WallColor); #endif TopBarrier = BodyFactory.CreateEdge(GameMain.World, ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)), ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0))); TopBarrier.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, borders.Height)), 0.0f); TopBarrier.BodyType = BodyType.Static; TopBarrier.CollisionCategories = Physics.CollisionLevel; bodies.Add(TopBarrier); GenerateSeaFloor(); backgroundSpriteManager.PlaceSprites(this, generationParams.BackgroundSpriteAmount); #if CLIENT backgroundCreatureManager.SpawnSprites(80); #endif foreach (VoronoiCell cell in cells) { foreach (GraphEdge edge in cell.edges) { edge.cell1 = null; edge.cell2 = null; edge.site1 = null; edge.site2 = null; } } //initialize MapEntities that aren't in any sub (e.g. items inside ruins) MapEntity.MapLoaded(null); Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); if (mirror) { Vector2 temp = startPosition; startPosition = endPosition; endPosition = temp; } Debug.WriteLine("**********************************************************************************"); Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms"); Debug.WriteLine("Seed: " + seed); Debug.WriteLine("**********************************************************************************"); if (GameSettings.VerboseLogging) { DebugConsole.NewMessage("Generated level with the seed " + seed + " (type: " + generationParams.Name + ")", Color.White); } }
private void Generate() { Connections.Clear(); Locations.Clear(); List <Vector2> voronoiSites = new List <Vector2>(); for (float x = 10.0f; x < Width - 10.0f; x += generationParams.VoronoiSiteInterval.X) { for (float y = 10.0f; y < Height - 10.0f; y += generationParams.VoronoiSiteInterval.Y) { voronoiSites.Add(new Vector2( x + generationParams.VoronoiSiteVariance.X * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server), y + generationParams.VoronoiSiteVariance.Y * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server))); } } Voronoi voronoi = new Voronoi(0.5f); List <GraphEdge> edges = voronoi.MakeVoronoiGraph(voronoiSites, Width, Height); float zoneWidth = Width / generationParams.DifficultyZones; Vector2 margin = new Vector2( Math.Min(10, Width * 0.1f), Math.Min(10, Height * 0.2f)); float startX = margin.X, endX = Width - margin.X; float startY = margin.Y, endY = Height - margin.Y; if (!edges.Any()) { throw new Exception($"Generating a campaign map failed (no edges in the voronoi graph). Width: {Width}, height: {Height}, margin: {margin}"); } voronoiSites.Clear(); foreach (GraphEdge edge in edges) { if (edge.Point1 == edge.Point2) { continue; } if (edge.Point1.X < margin.X || edge.Point1.X > Width - margin.X || edge.Point1.Y < startY || edge.Point1.Y > endY) { continue; } if (edge.Point2.X < margin.X || edge.Point2.X > Width - margin.X || edge.Point2.Y < startY || edge.Point2.Y > endY) { continue; } Location[] newLocations = new Location[2]; newLocations[0] = Locations.Find(l => l.MapPosition == edge.Point1 || l.MapPosition == edge.Point2); newLocations[1] = Locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.Point1 || l.MapPosition == edge.Point2)); for (int i = 0; i < 2; i++) { if (newLocations[i] != null) { continue; } Vector2[] points = new Vector2[] { edge.Point1, edge.Point2 }; int positionIndex = Rand.Int(1, Rand.RandSync.Server); Vector2 position = points[positionIndex]; if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) { position = points[1 - positionIndex]; } int zone = MathHelper.Clamp((int)Math.Floor(position.X / zoneWidth) + 1, 1, generationParams.DifficultyZones); newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server), requireOutpost: false, Locations); Locations.Add(newLocations[i]); } var newConnection = new LocationConnection(newLocations[0], newLocations[1]); Connections.Add(newConnection); } //remove connections that are too short float minConnectionDistanceSqr = generationParams.MinConnectionDistance * generationParams.MinConnectionDistance; for (int i = Connections.Count - 1; i >= 0; i--) { LocationConnection connection = Connections[i]; if (Vector2.DistanceSquared(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minConnectionDistanceSqr) { continue; } //locations.Remove(connection.Locations[0]); Connections.Remove(connection); foreach (LocationConnection connection2 in Connections) { if (connection2.Locations[0] == connection.Locations[0]) { connection2.Locations[0] = connection.Locations[1]; } if (connection2.Locations[1] == connection.Locations[0]) { connection2.Locations[1] = connection.Locations[1]; } } } HashSet <Location> connectedLocations = new HashSet <Location>(); foreach (LocationConnection connection in Connections) { connection.Locations[0].Connections.Add(connection); connection.Locations[1].Connections.Add(connection); connectedLocations.Add(connection.Locations[0]); connectedLocations.Add(connection.Locations[1]); } //remove orphans Locations.RemoveAll(c => !connectedLocations.Contains(c)); //remove locations that are too close to each other float minLocationDistanceSqr = generationParams.MinLocationDistance * generationParams.MinLocationDistance; for (int i = Locations.Count - 1; i >= 0; i--) { for (int j = Locations.Count - 1; j > i; j--) { float dist = Vector2.DistanceSquared(Locations[i].MapPosition, Locations[j].MapPosition); if (dist > minLocationDistanceSqr) { continue; } //move connections from Locations[j] to Locations[i] foreach (LocationConnection connection in Locations[j].Connections) { if (connection.Locations[0] == Locations[j]) { connection.Locations[0] = Locations[i]; } else { connection.Locations[1] = Locations[i]; } if (connection.Locations[0] != connection.Locations[1]) { Locations[i].Connections.Add(connection); } else { Connections.Remove(connection); } } Locations[i].Connections.RemoveAll(c => c.OtherLocation(Locations[i]) == Locations[j]); Locations.RemoveAt(j); } } for (int i = Connections.Count - 1; i >= 0; i--) { i = Math.Min(i, Connections.Count - 1); LocationConnection connection = Connections[i]; for (int n = Math.Min(i - 1, Connections.Count - 1); n >= 0; n--) { if (connection.Locations.Contains(Connections[n].Locations[0]) && connection.Locations.Contains(Connections[n].Locations[1])) { Connections.RemoveAt(n); } } } foreach (Location location in Locations) { for (int i = location.Connections.Count - 1; i >= 0; i--) { if (!Connections.Contains(location.Connections[i])) { location.Connections.RemoveAt(i); } } } foreach (LocationConnection connection in Connections) { connection.Difficulty = MathHelper.Clamp((connection.CenterPos.X / Width * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f); } AssignBiomes(); CreateEndLocation(); foreach (Location location in Locations) { location.LevelData = new LevelData(location); } foreach (LocationConnection connection in Connections) { connection.LevelData = new LevelData(connection); } }
public static List <VoronoiCell> CarveCave(List <VoronoiCell> cells, Vector2 startPoint, out List <VoronoiCell> newCells) { Voronoi voronoi = new Voronoi(1.0); List <Vector2> sites = new List <Vector2>(); float siteInterval = 400.0f; float siteVariance = siteInterval * 0.4f; Vector4 edges = new Vector4( cells.Min(x => x.edges.Min(e => e.point1.X)), cells.Min(x => x.edges.Min(e => e.point1.Y)), cells.Max(x => x.edges.Max(e => e.point1.X)), cells.Max(x => x.edges.Max(e => e.point1.Y))); edges.X -= siteInterval * 2; edges.Y -= siteInterval * 2; edges.Z += siteInterval * 2; edges.W += siteInterval * 2; Rectangle borders = new Rectangle((int)edges.X, (int)edges.Y, (int)(edges.Z - edges.X), (int)(edges.W - edges.Y)); for (float x = edges.X + siteInterval; x < edges.Z - siteInterval; x += siteInterval) { for (float y = edges.Y + siteInterval; y < edges.W - siteInterval; y += siteInterval) { if (Rand.Int(5, false) == 0) { continue; //skip some positions to make the cells more irregular } sites.Add(new Vector2(x, y) + Rand.Vector(siteVariance, false)); } } List <GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, edges.X, edges.Y, edges.Z, edges.W); List <VoronoiCell>[,] cellGrid; newCells = GraphEdgesToCells(graphEdges, borders, 1000, out cellGrid); foreach (VoronoiCell cell in newCells) { //if the cell is at the edge of the graph, remove it if (cell.edges.Any(e => e.point1.X == edges.X || e.point1.X == edges.Z || e.point1.Y == edges.Z || e.point1.Y == edges.W)) { cell.CellType = CellType.Removed; continue; } //remove cells that aren't inside any of the original "base cells" if (cells.Any(c => c.IsPointInside(cell.Center))) { continue; } foreach (GraphEdge edge in cell.edges) { //mark all the cells adjacent to the removed cell as edges of the cave var adjacent = edge.AdjacentCell(cell); if (adjacent != null && adjacent.CellType != CellType.Removed) { adjacent.CellType = CellType.Edge; } } cell.CellType = CellType.Removed; } newCells.RemoveAll(newCell => newCell.CellType == CellType.Removed); //start carving from the edge cell closest to the startPoint VoronoiCell startCell = null; float closestDist = 0.0f; foreach (VoronoiCell cell in newCells) { if (cell.CellType != CellType.Edge) { continue; } float dist = Vector2.Distance(startPoint, cell.Center); if (dist < closestDist || startCell == null) { startCell = cell; closestDist = dist; } } startCell.CellType = CellType.Path; List <VoronoiCell> path = new List <VoronoiCell>() { startCell }; VoronoiCell pathCell = startCell; for (int i = 0; i < newCells.Count / 2; i++) { var allowedNextCells = new List <VoronoiCell>(); foreach (GraphEdge edge in pathCell.edges) { var adjacent = edge.AdjacentCell(pathCell); if (adjacent == null || adjacent.CellType == CellType.Removed || adjacent.CellType == CellType.Edge) { continue; } allowedNextCells.Add(adjacent); } if (allowedNextCells.Count == 0) { if (i > 5) { break; } foreach (GraphEdge edge in pathCell.edges) { var adjacent = edge.AdjacentCell(pathCell); if (adjacent == null || adjacent.CellType == CellType.Removed) { continue; } allowedNextCells.Add(adjacent); } if (allowedNextCells.Count == 0) { break; } } //randomly pick one of the adjacent cells as the next cell pathCell = allowedNextCells[Rand.Int(allowedNextCells.Count, false)]; //randomly take steps further away from the startpoint to make the cave expand further if (Rand.Int(4, false) == 0) { float furthestDist = 0.0f; foreach (VoronoiCell nextCell in allowedNextCells) { float dist = Vector2.Distance(startCell.Center, nextCell.Center); if (dist > furthestDist || furthestDist == 0.0f) { furthestDist = dist; pathCell = nextCell; } } } pathCell.CellType = CellType.Path; path.Add(pathCell); } //make sure the tunnel is always wider than minPathWidth float minPathWidth = 100.0f; for (int i = 0; i < path.Count; i++) { var cell = path[i]; foreach (GraphEdge edge in cell.edges) { if (edge.point1 == edge.point2) { continue; } if (Vector2.Distance(edge.point1, edge.point2) > minPathWidth) { continue; } GraphEdge adjacentEdge = cell.edges.Find(e => e != edge && (e.point1 == edge.point1 || e.point2 == edge.point1)); var adjacentCell = adjacentEdge.AdjacentCell(cell); if (i > 0 && (adjacentCell.CellType == CellType.Path || adjacentCell.CellType == CellType.Edge)) { continue; } adjacentCell.CellType = CellType.Path; path.Add(adjacentCell); } } return(path); }
public void Generate(bool mirror = false) { Stopwatch sw = new Stopwatch(); sw.Start(); if (loaded != null) { loaded.Unload(); } loaded = this; positionsOfInterest = new List <InterestingPosition>(); renderer = new LevelRenderer(this); Voronoi voronoi = new Voronoi(1.0); List <Vector2> sites = new List <Vector2>(); bodies = new List <Body>(); Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); backgroundColor = generationParams.BackgroundColor; float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3; GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f); float minWidth = 6500.0f; if (Submarine.MainSub != null) { Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders(); minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height)); } startPosition = new Vector2( Rand.Range(minWidth, minWidth * 2, false), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, false)); endPosition = new Vector2( borders.Width - Rand.Range(minWidth, minWidth * 2, false), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, false)); List <Vector2> pathNodes = new List <Vector2>(); Rectangle pathBorders = borders;// new Rectangle((int)minWidth, (int)minWidth, borders.Width - (int)minWidth * 2, borders.Height - (int)minWidth); pathBorders.Inflate(-minWidth * 2, -minWidth * 2); pathNodes.Add(new Vector2(startPosition.X, borders.Height)); Vector2 nodeInterval = generationParams.MainPathNodeIntervalRange; for (float x = startPosition.X + Rand.Range(nodeInterval.X, nodeInterval.Y, false); x < endPosition.X - Rand.Range(nodeInterval.X, nodeInterval.Y, false); x += Rand.Range(nodeInterval.X, nodeInterval.Y, false)) { pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, false))); } pathNodes.Add(new Vector2(endPosition.X, borders.Height)); if (pathNodes.Count <= 2) { pathNodes.Add((startPosition + endPosition) / 2); } List <List <Vector2> > smallTunnels = new List <List <Vector2> >(); for (int i = 0; i < generationParams.SmallTunnelCount; i++) { var tunnelStartPos = pathNodes[Rand.Range(2, pathNodes.Count - 2, false)]; tunnelStartPos.X = MathHelper.Clamp(tunnelStartPos.X, pathBorders.X, pathBorders.Right); float tunnelLength = Rand.Range( generationParams.SmallTunnelLengthRange.X, generationParams.SmallTunnelLengthRange.Y, false); var tunnelNodes = MathUtils.GenerateJaggedLine( tunnelStartPos, new Vector2(tunnelStartPos.X, pathBorders.Bottom) + Rand.Vector(tunnelLength, false), 4, 1000.0f); List <Vector2> tunnel = new List <Vector2>(); foreach (Vector2[] tunnelNode in tunnelNodes) { if (!pathBorders.Contains(tunnelNode[0])) { continue; } tunnel.Add(tunnelNode[0]); } if (tunnel.Any()) { smallTunnels.Add(tunnel); } } Vector2 siteInterval = generationParams.VoronoiSiteInterval; Vector2 siteVariance = generationParams.VoronoiSiteVariance; for (float x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X) { for (float y = siteInterval.Y / 2; y < borders.Height; y += siteInterval.Y) { Vector2 site = new Vector2( x + Rand.Range(-siteVariance.X, siteVariance.X, false), y + Rand.Range(-siteVariance.Y, siteVariance.Y, false)); if (smallTunnels.Any(t => t.Any(node => Vector2.Distance(node, site) < siteInterval.Length()))) { //add some more sites around the small tunnels to generate more small voronoi cells if (x < borders.Width - siteInterval.X) { sites.Add(new Vector2(x, y) + Vector2.UnitX * siteInterval * 0.5f); } if (y < borders.Height - siteInterval.Y) { sites.Add(new Vector2(x, y) + Vector2.UnitY * siteInterval * 0.5f); } if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y) { sites.Add(new Vector2(x, y) + Vector2.One * siteInterval * 0.5f); } } if (mirror) { site.X = borders.Width - site.X; } sites.Add(site); } } Stopwatch sw2 = new Stopwatch(); sw2.Start(); List <GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, borders.Width, borders.Height); Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); //construct voronoi cells based on the graph edges cells = CaveGenerator.GraphEdgesToCells(graphEdges, borders, GridCellSize, out cellGrid); Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); List <VoronoiCell> mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize, new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, mirror); for (int i = 2; i < mainPath.Count; i += 3) { positionsOfInterest.Add(new InterestingPosition(mainPath[i].Center, PositionType.MainPath)); } List <VoronoiCell> pathCells = new List <VoronoiCell>(mainPath); EnlargeMainPath(pathCells, minWidth); foreach (InterestingPosition positionOfInterest in positionsOfInterest) { WayPoint wayPoint = new WayPoint(positionOfInterest.Position, SpawnType.Enemy, null); wayPoint.MoveWithLevel = true; } startPosition.X = pathCells[0].Center.X; foreach (List <Vector2> tunnel in smallTunnels) { if (tunnel.Count < 2) { continue; } //find the cell which the path starts from int startCellIndex = CaveGenerator.FindCellIndex(tunnel[0], cells, cellGrid, GridCellSize, 1); if (startCellIndex < 0) { continue; } //if it wasn't one of the cells in the main path, don't create a tunnel if (cells[startCellIndex].CellType != CellType.Path) { continue; } var newPathCells = CaveGenerator.GeneratePath(tunnel, cells, cellGrid, GridCellSize, pathBorders); positionsOfInterest.Add(new InterestingPosition(tunnel.Last(), PositionType.Cave)); if (tunnel.Count > 4) { positionsOfInterest.Add(new InterestingPosition(tunnel[tunnel.Count / 2], PositionType.Cave)); } pathCells.AddRange(newPathCells); } Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); cells = CleanCells(pathCells); pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle( (int)(borders.Width * 0.2f), 0, (int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f)))); foreach (VoronoiCell cell in cells) { if (cell.Center.Y < borders.Height / 2) { continue; } cell.edges.ForEach(e => e.OutsideLevel = true); } foreach (VoronoiCell cell in pathCells) { cell.edges.ForEach(e => e.OutsideLevel = false); cell.CellType = CellType.Path; cells.Remove(cell); } //generate some narrow caves int caveAmount = 0;// Rand.Int(3, false); List <VoronoiCell> usedCaveCells = new List <VoronoiCell>(); for (int i = 0; i < caveAmount; i++) { Vector2 startPoint = Vector2.Zero; VoronoiCell startCell = null; var caveCells = new List <VoronoiCell>(); int maxTries = 5, tries = 0; while (tries < maxTries) { startCell = cells[Rand.Int(cells.Count, false)]; //find an edge between the cell and the already carved path GraphEdge startEdge = startCell.edges.Find(e => pathCells.Contains(e.AdjacentCell(startCell))); if (startEdge != null) { startPoint = (startEdge.point1 + startEdge.point2) / 2.0f; startPoint += startPoint - startCell.Center; //get the cells in which the cave will be carved caveCells = GetCells(startCell.Center, 2); //remove cells that have already been "carved" out caveCells.RemoveAll(c => c.CellType == CellType.Path); //if any of the cells have already been used as a cave, continue and find some other cells if (usedCaveCells.Any(c => caveCells.Contains(c))) { continue; } break; } tries++; } //couldn't find a place for a cave -> abort if (tries >= maxTries) { break; } if (!caveCells.Any()) { continue; } usedCaveCells.AddRange(caveCells); List <VoronoiCell> caveSolidCells; var cavePathCells = CaveGenerator.CarveCave(caveCells, startPoint, out caveSolidCells); //remove the large cells used as a "base" for the cave (they've now been replaced with smaller ones) caveCells.ForEach(c => cells.Remove(c)); cells.AddRange(caveSolidCells); foreach (VoronoiCell cell in cavePathCells) { cells.Remove(cell); } pathCells.AddRange(cavePathCells); for (int j = cavePathCells.Count / 2; j < cavePathCells.Count; j += 10) { positionsOfInterest.Add(new InterestingPosition(cavePathCells[j].Center, PositionType.Cave)); } } for (int x = 0; x < cellGrid.GetLength(0); x++) { for (int y = 0; y < cellGrid.GetLength(1); y++) { cellGrid[x, y].Clear(); } } foreach (VoronoiCell cell in cells) { int x = (int)Math.Floor(cell.Center.X / GridCellSize); int y = (int)Math.Floor(cell.Center.Y / GridCellSize); if (x < 0 || y < 0 || x >= cellGrid.GetLength(0) || y >= cellGrid.GetLength(1)) { continue; } cellGrid[x, y].Add(cell); } ruins = new List <Ruin>(); for (int i = 0; i < generationParams.RuinCount; i++) { GenerateRuin(mainPath); } startPosition.Y = borders.Height; endPosition.Y = borders.Height; List <VoronoiCell> cellsWithBody = new List <VoronoiCell>(cells); List <VertexPositionTexture> bodyVertices; bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out bodyVertices); renderer.SetBodyVertices(bodyVertices.ToArray()); renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells)); renderer.PlaceSprites(generationParams.BackgroundSpriteAmount); ShaftBody = BodyFactory.CreateEdge(GameMain.World, ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)), ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0))); ShaftBody.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, borders.Height)), 0.0f); ShaftBody.BodyType = BodyType.Static; ShaftBody.CollisionCategories = Physics.CollisionLevel; bodies.Add(ShaftBody); foreach (VoronoiCell cell in cells) { foreach (GraphEdge edge in cell.edges) { edge.cell1 = null; edge.cell2 = null; edge.site1 = null; edge.site2 = null; } } //initialize MapEntities that aren't in any sub (e.g. items inside ruins) MapEntity.MapLoaded(null); Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); if (mirror) { Vector2 temp = startPosition; startPosition = endPosition; endPosition = temp; } Debug.WriteLine("**********************************************************************************"); Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms"); Debug.WriteLine("Seed: " + seed); Debug.WriteLine("**********************************************************************************"); }