float DistanceToEdgeSquared(TectonicEdge edge, ivec2 position) { vec2 v = new vec2(edge.A.x, edge.A.y); vec2 w = new vec2(edge.B.x, edge.B.y); vec2 p = new vec2(position.x, position.y); // Return minimum distance between line segment vw and point p float l2 = (v - w).LengthSqr; // i.e. |w-v|^2 - avoid a sqrt if (l2 == 0f) { return((p - v).LengthSqr); // v == w case } // Consider the line extending the segment, parameterized as v + t (w - v). // We find projection of point p onto the line. // It falls where t = [(p-v) . (w-v)] / |w-v|^2 float t = glm.Dot(p - v, w - v) / l2; if (t < 0) { return((p - v).LengthSqr); // Beyond the 'v' end of the segment } else if (t > 1) { return((p - w).LengthSqr); // Beyond the 'w' end of the segment } vec2 projection = v + t * (w - v); // Projection falls on the segment return((p - projection).LengthSqr); }
void drawLine(TectonicEdge edge, MapPixel[,] map) { int x0 = edge.A.x; int y0 = edge.A.y; int x1 = edge.B.x; int y1 = edge.B.y; int dx = Math.Abs(x1 - x0), sx = x0 < x1 ? 1 : -1; int dy = -Math.Abs(y1 - y0), sy = y0 < y1 ? 1 : -1; int err = dx + dy, e2; // error value e_xy if (dx > map.GetLength(0) * 4 || dy > map.GetLength(1) * 4) { //TODO: Investigate broken edge coordinates return; } for (; ;) { if (x0 >= 0 && x0 < MapSize.x && y0 >= 0 && y0 < MapSize.y) { map[x0, y0].Edges.Add(edge); map[x0, y0].Plate = edge.LeftPlate; } if (x0 == x1 && y0 == y1) { break; } e2 = 2 * err; // horizontal step? if (e2 > dy) { err += dy; x0 += sx; } else if (e2 < dx) { err += dx; y0 += sy; } } }
// 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); }
MapPixel[,] GenerateHeightMap(List <TectonicPlate> plates, float progressMin, float progressMax) { var map = new MapPixel[MapSize.x, MapSize.y]; UpdateGenerationInfo("Initialize with random heightmap"); for (int x = 0; x < MapSize.x; x++) { for (int y = 0; y < MapSize.y; y++) { map[x, y] = new MapPixel(); map[x, y].Height = 0.5f + 0.5f * SimplexNoise.RecursiveNoise.Noise2D(x / 200f, y / 200f, 8); } } var drawnEdges = new HashSet <TectonicEdge>(); UpdateGenerationInfo("Initializing tectonic edges"); // Initialize the edges and plate centers foreach (var tectonicPlate in plates) { foreach (var edge in tectonicPlate.Edges) { if (drawnEdges.Contains(edge)) { continue; } drawLine(edge, map); drawnEdges.Add(edge); } int x = tectonicPlate.Center.x; int y = tectonicPlate.Center.y; if (x >= 0 && x < MapSize.x && y >= 0 && y < MapSize.y) { map[x, y].Plate = tectonicPlate; } } UpdateGenerationInfo("Computing tectonic surface"); // Flood fill from the plate centers bool done = false; for (int i = 1; !done; i++) { done = true; for (int x = 0; x < MapSize.x; x++) { for (int y = 0; y < MapSize.y; y++) { // Found a pixel without a Plate assigned? MapPixel p = map[x, y]; if (p.Plate == null) { TryFillPixel(map, new ivec2(x, y)); if (p.Plate != null) { done = false; } } } } for (int x = MapSize.x - 1; x > 0; x--) { for (int y = MapSize.y - 1; y > 0; y--) { // Found a pixel without a Plate assigned? MapPixel p = map[x, y]; if (p.Plate == null) { TryFillPixel(map, new ivec2(x, y)); if (p.Plate != null) { done = false; } } } } } UpdateProgress(progressMin + (progressMax - progressMin) * 0.5f); // Find missing plates UpdateGenerationInfo("Find unused tectonic plates"); List <TectonicPlate> missingPlates = FindUnusedPlates(map, plates); // Set height values depending on the base heights of the plates and the UpdateGenerationInfo("Set base height of terrain"); Parallel.For(0, MapSize.x, x => { for (int y = 0; y < MapSize.y; y++) { var p = map[x, y]; TectonicEdge bestEdge = null; float bestDistance = Math.Max(MapSize.x, MapSize.y); float baseHeight = -1f; if (p.Plate != null) { baseHeight = p.Plate.BaseHeight; foreach (var edge in p.Plate.Edges) { if (!missingPlates.Contains(edge.LeftPlate) && !missingPlates.Contains(edge.RightPlate)) { float dist = DistanceToEdgeSquared(edge, new ivec2(x, y)); if (dist < bestDistance) { bestDistance = dist; bestEdge = edge; } } } } float tectonicHeight = 0f; if (bestEdge != null) { tectonicHeight = bestEdge.MountainFactor * 3f * (1f / ((float)Math.Pow(1.2f + 0.02f * bestDistance, 2))) * (1f + SimplexNoise.RecursiveNoise.Noise2D(250f - x / 200f, 250f - y / 200f, 10)); } float noise = SimplexNoise.RecursiveNoise.Noise2D(x / 100f, y / 100f, 8); p.Height = baseHeight + tectonicHeight + noise; } }); UpdateProgress(progressMax); return(map); }
List <TectonicEdge> SplitTectonicEdge(TectonicEdge edge, TectonicEdge originalEdge, int depth) { var subEdges = new List <TectonicEdge>(); vec2 a = new vec2(edge.A.x, edge.A.y); vec2 b = new vec2(edge.B.x, edge.B.y); // Randomly move on an axis perpendicular to the original voronoi edge // vector of original edge line vec2 edgeVector = new vec2(originalEdge.A.x - originalEdge.B.x, originalEdge.A.y - originalEdge.B.y); // vectors between left/right plate centers and original.A vec2 leftCenterToA = new vec2(edge.A.x - edge.LeftPlate.Center.x, edge.A.y - edge.LeftPlate.Center.y); vec2 rightCenterToA = new vec2(edge.A.x - edge.RightPlate.Center.x, edge.A.y - edge.RightPlate.Center.y); // project centers to origVector float projectedCenterLeft = (glm.Dot(edgeVector, leftCenterToA) / leftCenterToA.LengthSqr); float projectedCenterRight = (glm.Dot(edgeVector, rightCenterToA) / rightCenterToA.LengthSqr); vec2 movementAxis = new vec2(-edgeVector.y, edgeVector.x); movementAxis = movementAxis.Normalized; float shift = rand.NextFloat(0.1f, 0.9f); vec2 vMid = shift * a + (1f - shift) * b; vMid += movementAxis * rand.NextFloat(0f, depth * depth); ivec2 middle = new ivec2((int)vMid.x, (int)vMid.y); TectonicEdge subEdge1 = new TectonicEdge { A = edge.A, B = middle, LeftPlate = edge.LeftPlate, RightPlate = edge.RightPlate, MountainFactor = edge.MountainFactor }; TectonicEdge subEdge2 = new TectonicEdge { A = middle, B = edge.B, LeftPlate = edge.LeftPlate, RightPlate = edge.RightPlate, MountainFactor = edge.MountainFactor }; if (depth <= 0) { subEdges.Add(subEdge1); subEdges.Add(subEdge2); } else { subEdges.AddRange(SplitTectonicEdge(subEdge1, originalEdge, depth - 1)); subEdges.AddRange(SplitTectonicEdge(subEdge2, originalEdge, depth - 1)); } return(subEdges); }