public static void SetContinentsInertia(float movement, float rotation) { TectonicMotion = new Vector3[World.Cells.Count()]; for (int i = 0; i <= World.Tectonics.Count(); i++) { TectonicPlate t = World.Tectonics[i]; t.Movement = t.LocalMatrix.MultiplyPoint3x4(new Vector3(UnityEngine.Random.Range(-movement, movement), UnityEngine.Random.Range(-movement, movement), 0)) - t.Location; foreach (uint u in t.PlateCellList) { Vector3 vec = rotation * t.LocalMatrix.inverse.MultiplyPoint3x4(World.Cells[u].Location); TectonicMotion[u] += t.LocalMatrix.MultiplyPoint3x4(vec) - World.Cells[u].Location; } } }
void TryFillPixel(MapPixel[,] map, ivec2 pos) { MapPixel p = map[pos.x, pos.y]; var plateVotes = new Dictionary <TectonicPlate, int>(); // Look for a neighbor pixel with a plate assigned foreach (ivec2 d in neighborMask) { int dx = pos.x + d.x; int dy = pos.y + d.y; if (dx >= 0 && dx < MapSize.x && dy >= 0 && dy < MapSize.y) { TectonicPlate votedPlate = null; if (map[dx, dy].Plate != null && map[dx, dy].Edges.Count == 0) { votedPlate = map[dx, dy].Plate; } if (votedPlate == null) { continue; } if (!plateVotes.ContainsKey(votedPlate)) { plateVotes[votedPlate] = 1; } else { plateVotes[votedPlate] += 1; } } } if (plateVotes.Count == 0) { return; } p.Plate = (from plateVote in plateVotes orderby plateVote.Value descending select plateVote.Key).First(); }
TectonicPlate SelectSide(ivec2 position, List <TectonicEdge> edges) { var plateVotes = new Dictionary <TectonicPlate, int>(); foreach (var edge in edges) { float angle = EdgeAngle(position, edge.A, edge.B); TectonicPlate votedPlate = angle > 0 ? edge.RightPlate : edge.LeftPlate; if (!plateVotes.ContainsKey(votedPlate)) { plateVotes[votedPlate] = 1; } else { plateVotes[votedPlate] += 1; } } return((from plateVote in plateVotes orderby plateVote.Value descending select plateVote.Key).First()); }
private void GenerateElevation(Dictionary <Site, float> initialSiteElevations, bool pointElevation = false) { float elevationCutoff = 0.01f; Queue <Site> updateSites = new Queue <Site>(); Queue <float> elevations = new Queue <float>(); Dictionary <Site, bool> queuedSites = new Dictionary <Site, bool>(); // Queue initial sites for processing. foreach (KeyValuePair <Site, float> kv in initialSiteElevations) { Site site = kv.Key; updateSites.Enqueue(site); elevations.Enqueue(initialSiteElevations[site]); queuedSites.Add(site, true); } // Propagate elevation. while (updateSites.Any()) { Site site = updateSites.Dequeue(); TectonicPlate sitePlate = this.plateMetadata[site]; float newElevation = elevations.Dequeue(); // Preserve any elevation that has been generated before. if (this.sectionMetadata[site].Elevation < newElevation) { this.sectionMetadata[site].Elevation = newElevation; } foreach (KeyValuePair <Site, Edge> kv in site.NeighborSiteEdges()) { Site neighborSite = kv.Key; TectonicPlate neighborPlate = this.plateMetadata[neighborSite]; // Don't queue any sites for processing that have already been queued. bool alreadyQueued = false; queuedSites.TryGetValue(neighborSite, out alreadyQueued); if (!alreadyQueued) { // Generate perturbed elevations based on parent elevation. float range = neighborPlate.Density; float perturbation; if (pointElevation) { // Constant elevation decay rate primarily for testing. perturbation = 0.95f; } else { perturbation = (float)randGen.NextDouble() * range + 1.1f - range; } float neighborHeight = newElevation * perturbation; if (newElevation > elevationCutoff) { updateSites.Enqueue(neighborSite); elevations.Enqueue(neighborHeight); queuedSites.Add(neighborSite, true); } } } } }
// Simulates tectonic uplift based on plate boundary stress conditions. private void CreateUplift() { Dictionary <Site, float> initialSites = new Dictionary <Site, float>(); foreach (KeyValuePair <Edge, Boundary> kv in this.plateBoundaries) { Edge edge = kv.Key; if (edge.LeftSite == null || edge.RightSite == null) { continue; } Boundary boundary = kv.Value; TectonicPlate leftPlate = this.plateMetadata[edge.LeftSite]; TectonicPlate rightPlate = this.plateMetadata[edge.RightSite]; float leftElevation; float rightElevation; if ((boundary.Orthogonal > boundary.Parallel) && !boundary.Divergent && (boundary.Stress.Magnitude > 0.1f)) { float minElevation = Mathf.Max(leftPlate.Elevation, rightPlate.Elevation); float maxElevation = Mathf.Max(leftPlate.MaxElevation, rightPlate.MaxElevation); leftElevation = boundary.Orthogonal / boundary.Stress.Magnitude * (maxElevation - minElevation) + minElevation; rightElevation = boundary.Orthogonal / boundary.Stress.Magnitude * (maxElevation - minElevation) + minElevation; } else if (((boundary.Parallel > boundary.Orthogonal) || boundary.Divergent) && (boundary.Stress.Magnitude > 0.1f)) { float minElevation = Mathf.Max(leftPlate.Elevation, rightPlate.Elevation); float maxElevation = Mathf.Max(leftPlate.MaxElevation, rightPlate.MaxElevation); leftElevation = boundary.Parallel / boundary.Stress.Magnitude * 0.25f * (maxElevation - minElevation) + minElevation; rightElevation = boundary.Parallel / boundary.Stress.Magnitude * 0.25f * (maxElevation - minElevation) + minElevation; } else { leftElevation = (leftPlate.Elevation + rightPlate.Elevation) * 0.5f; rightElevation = leftElevation; } try { // Preserve the highest elevation (generated by the highest stress). if (!initialSites.ContainsKey(edge.LeftSite) || (initialSites.ContainsKey(edge.LeftSite) && (leftElevation > initialSites[edge.LeftSite]))) { initialSites.Add(edge.LeftSite, leftElevation); } if (!initialSites.ContainsKey(edge.RightSite) || (initialSites.ContainsKey(edge.RightSite) && (rightElevation > initialSites[edge.RightSite]))) { initialSites.Add(edge.RightSite, rightElevation); } } catch (System.ArgumentException ex) { // Ignore. } } this.GenerateElevation(initialSites); }
// Generates tectonic plates by assigning sections to plates via a pseudo-simultaneous flood-fill algorithm. // Also generates a global list of plate boundaries. private void GenerateTectonicPlates(int numPlates) { Queue <Site> updateSites = new Queue <Site>(); Dictionary <Site, bool> queuedSites = new Dictionary <Site, bool>(); List <Vector2f> seeds = this.CreateRandomPoints(numPlates); int numOceanPlates = 0; // Counter for number of oceanic plates generated. foreach (Vector2f seed in seeds) { // Select a random section to be a plate spawn point. Site sectionSite = this.GetClosestSection(this.SectionDiagram, seed); // Select a different section if the one we picked is already in the list. while (this.plateMetadata.ContainsKey(sectionSite)) { sectionSite = this.GetClosestSection(this.SectionDiagram, this.CreateRandomPoints(1)[0]); } TectonicPlate plate = new TectonicPlate(); // Determine whether the plate should be oceanic or continental. if ((float)numOceanPlates / numPlates < 0.7f) { plate.Oceanic = true; plate.Density = 0.4f; // Set elevation parameters for oceanic plates. plate.Elevation = this.seafloor; plate.MaxElevation = this.sealevel + 0.21f; } else { plate.Oceanic = false; plate.Density = 0.125f; // Set elevation parameters for continental plates. plate.Elevation = this.sealevel; plate.MaxElevation = 1.0f; } numOceanPlates++; // Create bi-directional mappings between sections and plates. this.plateMetadata.Add(sectionSite, plate); plate.Sections.Add(sectionSite); this.plates.Add(plate); // Generate a random movement vector for the plate. float forceX = (float)this.randGen.NextDouble(); float forceY = (float)this.randGen.NextDouble(); plate.Force = new Vector2f(forceX, forceY); plate.Force.Normalize(); updateSites.Enqueue(sectionSite); queuedSites.Add(sectionSite, true); } // Assign sections to plates and build a list of boundary edges for each plate. while (updateSites.Any()) { Site sectionSite = updateSites.Dequeue(); TectonicPlate plateData = this.plateMetadata[sectionSite]; Dictionary <Site, Edge> sectionEdges = sectionSite.NeighborSiteEdges(); foreach (Site neighborSite in sectionSite.NeighborSites()) { // Don't add any sites for processing that have already been queued. bool alreadyQueued = false; queuedSites.TryGetValue(neighborSite, out alreadyQueued); if (!alreadyQueued) { // Assimilate any available neighboring sections. this.plateMetadata.Add(neighborSite, plateData); plateData.Sections.Add(neighborSite); updateSites.Enqueue(neighborSite); queuedSites.Add(neighborSite, true); } else { // If the neighbor doesn't share the same plate, the edge is a boundary. if (plateData != this.plateMetadata[neighborSite]) { // Get boundary edge. Edge edge; if (sectionEdges.TryGetValue(neighborSite, out edge)) { // Add boundary edge to list for current plate. try { this.plateBoundaries.Add(edge, new Boundary()); } catch (System.ArgumentException ex) { // Don't need to worry about failed attempts to add duplicates. } } } } } } }
private static void GenerateTectonics(Voronoi graph, int numberOfPlates, int seed) { var sites = graph.SitesIndexedByLocation.Select(s => s.Value).ToList(); graph.Plates = new List <TectonicPlate>(); // Always generate tectonic plates for the north and south pole var northPole = new TectonicPlate(-1); var southPole = new TectonicPlate(-2); foreach (var site in sites.Where(s => s.IsNorthPoleSite)) { northPole.AddSite(site); } foreach (var site in sites.Where(s => s.IsSouthPoleSite)) { southPole.AddSite(site); } graph.Plates.Add(northPole); graph.Plates.Add(southPole); // Randomly assign a site a tectonic number. // If the randomly selected plate is already assigned, retry. var rnger = 0; for (var i = 1; i <= numberOfPlates - 2; i++) { var tp = new TectonicPlate(i); var siteIndex = Rng.GetRandomNumber(0, sites.Count, rnger++, seed); if (sites[siteIndex].TectonicPlate != null) { i--; continue; } tp.AddSite(sites[siteIndex]); graph.Plates.Add(tp); } // Now we've got our plates, it's time to expand them. // This may be a slow process, let's find out. Dictionary <TectonicPlate, bool> noNeighbours = new Dictionary <TectonicPlate, bool>(); foreach (var p in graph.Plates) { noNeighbours.Add(p, false); } while (!noNeighbours.All(n => n.Value)) { foreach (var plate in graph.Plates.Where(p => noNeighbours[p] == false)) { var neighbours = plate.NeighbourSites.Where(n => n.TectonicPlate == null).ToArray(); if (!neighbours.Any()) { noNeighbours[plate] = true; continue; } //var newSite = neighbours.OrderBy(n => n.Value).Last(); foreach (var site in neighbours) { plate.AddSite(site); } } } foreach (var plate in graph.Plates) { plate.AssociatePlateToGlobe(); } }
// Create a basic set of public List <TectonicPlate> GeneratePlates() { // Spawn some random cell centers within a grid. // Add one row and column outside of the map so no cells inside the map are border cells. List <TectonicPlate> plates = new List <TectonicPlate>(); for (int left = -PlateSize; left < MapSize.x + PlateSize; left += PlateSize) { for (int bottom = -PlateSize; bottom < MapSize.y + PlateSize; bottom += PlateSize) { int right = left + PlateSize; int top = bottom + PlateSize; plates.Add(new TectonicPlate { Center = new ivec2(rand.Next(left, right), rand.Next(bottom, top)), AngularVelocity = (float)rand.NextDouble() * maxPlateAngluarVelocity, LinearVelocity = new vec2((float)rand.NextDouble() * maxPlateLinearVelocity, (float)rand.NextDouble() * maxPlateLinearVelocity), BaseHeight = (float)rand.NextDouble() + 1f }); } } // Compute voronoi triangulation for plate edges var plateVectors = new Dictionary <Vector, TectonicPlate>(); foreach (var tectonicPlate in plates) { var center = new Vector(tectonicPlate.Center.x, tectonicPlate.Center.y); plateVectors[center] = tectonicPlate; } VoronoiGraph graph = Fortune.ComputeVoronoiGraph(plateVectors.Keys); foreach (var edge in graph.Edges) { ivec2 a = new ivec2((int)edge.VVertexA[0], (int)edge.VVertexA[1]); ivec2 b = new ivec2((int)edge.VVertexB[0], (int)edge.VVertexB[1]); // Ignore edges into infinity. We generate cells outside of the map so we have only finite edges in the mep if (a.x == Int32.MaxValue || a.x == Int32.MinValue || a.y == Int32.MaxValue || a.y == Int32.MinValue || b.x == Int32.MaxValue || b.x == Int32.MinValue || b.y == Int32.MaxValue || b.y == Int32.MinValue) { continue; } a.x = Math.Min(Math.Max(-200, a.x), MapSize.x + 200); a.y = Math.Min(Math.Max(-200, a.y), MapSize.y + 200); b.x = Math.Min(Math.Max(-200, b.x), MapSize.x + 200); b.y = Math.Min(Math.Max(-200, b.y), MapSize.y + 200); // left and right cells of the edges given by the fortune voronoi implementation are incorrect, compute the correct cells again ivec2 middle = (a + b) / 2; // Find the two plate centers closest to the edge middle point List <TectonicPlate> neighborCells = new List <TectonicPlate>(); neighborCells.AddRange(plates.OrderBy(p => (p.Center - middle).Length).Take(2)); TectonicPlate left = neighborCells[0]; TectonicPlate right = neighborCells[1]; // left/right correct? if (EdgeAngle(neighborCells[0].Center, a, b) > 0) { right = neighborCells[0]; left = neighborCells[1]; } float mountainFactor = rand.NextFloat(-1f, 1f); var tectonicEdge = new TectonicEdge { A = a, B = b, LeftPlate = left, RightPlate = right, MountainFactor = mountainFactor }; left.Edges.Add(tectonicEdge); right.Edges.Add(tectonicEdge); } SavePlateImage(plates, "plates.svg"); return(plates); }