Created
May 27, 2019 02:24
-
-
Save Kazetsukai/8b6d65a19ca369026c4a90f6b9413089 to your computer and use it in GitHub Desktop.
Attempt at tectonic plate based terrain generation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Numerics; | |
using System.Drawing; | |
using System.Drawing.Imaging; | |
using System.Linq; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace FlowTest | |
{ | |
class Program | |
{ | |
// Currently Z axis is in kilometers, X and Y are in pixels (arbitrary) | |
const int WIDTH = 200; | |
const int HEIGHT = 200; | |
const int NUMBER_OF_PLATES = 32; | |
const double MANTLE_DENSITY = 3.3; // kg/m^3 | |
const double OCEANIC_CRUST_START_THICKNESS = 6; // km | |
const double OCEANIC_CRUST_START_DENSITY = 3; // kg/m^3 | |
const double OCEANIC_CRUST_START_TOP = 1; // km | |
const double TOTAL_WATER_VOLUME = WIDTH * HEIGHT / 2; | |
const int EROSION_WALKS_PER_ITERATION = WIDTH * HEIGHT; | |
const double EROSION_AMOUNT_ROCK = 0.0049846; | |
const double EROSION_AMOUNT_SEDIMENT = 0.0103156; | |
const double EROSION_DEPOSIT_SEDIMENT = 0.00332105; | |
const double EROSION_DEPOSIT_SEDIMENT_UNDERWATER = 0.014216; | |
static Random rand = new Random(); | |
static List<Vector2> hotspots = new List<Vector2>(); | |
static Vector2[] plateForces = new Vector2[NUMBER_OF_PLATES]; | |
static Vector2[] oldPlateForces = new Vector2[NUMBER_OF_PLATES]; | |
static double[] plateMass = new double[NUMBER_OF_PLATES]; | |
static int frame = 0; | |
static double waterLevel = 3; | |
private static int CountNorth = 0; | |
private static int CountEast = 0; | |
private static int CountSouth = 0; | |
private static int CountWest = 0; | |
static void Main(string[] args) | |
{ | |
double[,] mantleHeat = new double[HEIGHT, WIDTH]; | |
double[,] mantleHeatVelocity = new double[HEIGHT, WIDTH]; | |
Vector2[,] tectonicForces = new Vector2[HEIGHT, WIDTH]; | |
Crust[,] crust = new Crust[HEIGHT, WIDTH]; | |
double[,] crustShifts = new double[HEIGHT, WIDTH]; | |
Bitmap b = new Bitmap(WIDTH, HEIGHT, PixelFormat.Format24bppRgb); | |
mantleHeat.Do((m, y, x) => m[y, x] = Math.Sin(x / 15.234) + Math.Cos(y / 13.645)); | |
mantleHeatVelocity.Do((m, y, x) => m[y, x] = 0); | |
// Mantle bootstrap | |
Console.WriteLine("Bootstrapping the mantle..."); | |
for (int i = 0; i < 200; i++) | |
{ | |
UpdateMantleHeat(ref mantleHeat, ref mantleHeatVelocity); | |
DrawMantleHeat(b, mantleHeat); | |
frame++; | |
} | |
// Form a crust | |
Console.WriteLine("Forming a crust..."); | |
crust.Do((c, y, x) => | |
{ | |
var averageHeat = GetAverageOfCross(mantleHeat, x, y); | |
c[y, x].Thickness = OCEANIC_CRUST_START_THICKNESS + 3 * averageHeat; | |
c[y, x].Density = OCEANIC_CRUST_START_DENSITY - averageHeat / 10; | |
}); | |
SplitCrustIntoPlates(crust); | |
for (int i = 0; i < 150; i++) | |
{ | |
UpdateMantleHeat(ref mantleHeat, ref mantleHeatVelocity); | |
DrawMantleHeat(b, mantleHeat); | |
if (frame < 320) | |
UpdateTectonicForces(tectonicForces, mantleHeat); | |
else | |
{ | |
tectonicForces.Do((t, y, x) => { t[y, x] = Vector2.Zero; }); | |
} | |
UpdateCrust(crust, tectonicForces, mantleHeat, crustShifts); | |
UpdateWeather(crust); | |
DrawCrust(b, crust); | |
DrawCrustForce(b, crust); | |
WriteStats(crust, tectonicForces, mantleHeat); | |
frame++; | |
} | |
DrawHeightmap(b, crust); | |
} | |
private static void WriteStats(Crust[,] crust, Vector2[,] tectonicForces, double[,] mantleHeat) | |
{ | |
var count = 0; | |
var minThickness = double.PositiveInfinity; | |
var sumThickness = 0.0; | |
var maxThickness = double.NegativeInfinity; | |
var minDensity = double.PositiveInfinity; | |
var sumDensity = 0.0; | |
var maxDensity = double.NegativeInfinity; | |
var minTop = double.PositiveInfinity; | |
var sumTop = 0.0; | |
var maxTop = double.NegativeInfinity; | |
crust.Do((c, y, x) => | |
{ | |
var cr = c[y, x]; | |
count++; | |
sumThickness += cr.Thickness; | |
sumDensity += cr.Density; | |
sumTop += cr.Top; | |
if (cr.Thickness > maxThickness) maxThickness = cr.Thickness; | |
if (cr.Thickness < minThickness) minThickness = cr.Thickness; | |
if (cr.Density > maxDensity) maxDensity = cr.Density; | |
if (cr.Density < minDensity) minDensity = cr.Density; | |
if (cr.Top > maxTop) maxTop = cr.Top; | |
if (cr.Top < minTop) minTop = cr.Top; | |
}); | |
Console.WriteLine($"Thickness: ({minThickness} | {sumThickness / count} | {maxThickness})"); | |
Console.WriteLine($"Density: ({minDensity} | {sumDensity / count} | {maxDensity})"); | |
Console.WriteLine($"Top: ({minTop} | {sumTop / count} | {maxTop})"); | |
Console.WriteLine($"Water level: {waterLevel}"); | |
Console.WriteLine($"{CountNorth} {CountEast} {CountSouth} {CountWest}"); | |
} | |
private static void SplitCrustIntoPlates(Crust[,] crust) | |
{ | |
Console.WriteLine("Splitting into plates..."); | |
List<Point> plateSeeds = new List<Point>(); | |
for (int i = 0; i < NUMBER_OF_PLATES; i++) | |
{ | |
plateSeeds.Add(new Point(rand.Next(HEIGHT), rand.Next(WIDTH))); | |
} | |
crust.Do((c, y, x) => | |
{ | |
var minDist = double.PositiveInfinity; | |
var id = 0; | |
for (int i = 0; i < NUMBER_OF_PLATES; i++) | |
{ | |
var dist = Math.Sqrt(Math.Pow(x - plateSeeds[i].X, 2) + Math.Pow(y - plateSeeds[i].Y, 2)); | |
if (dist < minDist) | |
{ | |
minDist = dist; | |
id = i; | |
} | |
} | |
crust[y, x].PlateId = id; | |
}); | |
} | |
private static void UpdateWeather(Crust[,] crust) | |
{ | |
// Recalculate water. Try to balance it out to a set volume by adjusting water level. | |
var totalVolume = 0.0; | |
crust.Do((c, y, x) => | |
{ | |
totalVolume += Math.Max(0, waterLevel - c[y, x].Top); | |
}); | |
if (totalVolume > TOTAL_WATER_VOLUME) | |
{ | |
waterLevel -= 0.0625; | |
} | |
if (totalVolume < TOTAL_WATER_VOLUME) | |
{ | |
waterLevel += 0.0625; | |
} | |
// TODO: Wind calculation | |
// Erosion and sediment redistribution | |
for (int i = 0; i < EROSION_WALKS_PER_ITERATION; i++) | |
{ | |
var x = rand.Next(WIDTH); | |
var y = rand.Next(HEIGHT); | |
ErosionWalk(crust, y, x); | |
} | |
} | |
private static void ErosionWalk(Crust[,] crust, int y, int x) | |
{ | |
var currentSediment = 0.0; | |
var maxWalk = 50; | |
while (maxWalk > 0) | |
{ | |
var aboveWater = crust[y, x].TotalTop() > waterLevel - 0.125; | |
// Only pick up sediment above water | |
if (aboveWater) | |
{ | |
// Pick up some sediment | |
var sedimentAmount = Math.Min(crust[y, x].Sediment, rand.NextDouble() * EROSION_AMOUNT_SEDIMENT); | |
crust[y, x].Sediment -= sedimentAmount; | |
currentSediment += sedimentAmount; | |
// Deposit some sediment | |
var depositAmount = Math.Min(currentSediment, rand.NextDouble() * EROSION_DEPOSIT_SEDIMENT); | |
currentSediment -= depositAmount; | |
// Erode some rock if it is on the surface | |
if (crust[y, x].Sediment < EROSION_AMOUNT_SEDIMENT && crust[y, x].Thickness > 1) | |
{ | |
var erodeAmount = rand.NextDouble() * EROSION_AMOUNT_ROCK; | |
crust[y, x].Thickness -= erodeAmount; | |
currentSediment += erodeAmount; | |
} | |
// Deposit after rock erosion. Give rock erosion a chance. | |
crust[y, x].Sediment += depositAmount; | |
} | |
else | |
{ | |
// Just deposit a bunch of sediment | |
var depositAmount = Math.Min(currentSediment, EROSION_DEPOSIT_SEDIMENT_UNDERWATER); | |
crust[y, x].Sediment += depositAmount; | |
currentSediment -= depositAmount; | |
// If we run out then we can stop | |
if (currentSediment <= 0) return; | |
} | |
var north = y > 0 && crust[y - 1, x].TotalTop() < crust[y, x].TotalTop(); | |
var east = x < WIDTH - 1 && crust[y, x + 1].TotalTop() < crust[y, x].TotalTop(); | |
var south = y < HEIGHT - 1 && crust[y + 1, x].TotalTop() < crust[y, x].TotalTop(); | |
var west = x > 0 && crust[y, x - 1].TotalTop() < crust[y, x].TotalTop(); | |
if (!(north || east || south || west)) | |
{ | |
crust[y, x].Sediment += currentSediment; | |
return; | |
} | |
var found = false; | |
while (!found) | |
{ | |
switch (rand.Next(4)) | |
{ | |
case 0: | |
if (north) { y -= 1; found = true; CountNorth++; } | |
break; | |
case 1: | |
if (east) { x += 1; found = true; CountEast++; } | |
break; | |
case 2: | |
if (south) { y += 1; found = true; CountSouth++; } | |
break; | |
case 3: | |
if (west) { x -= 1; found = true; CountWest++; } | |
break; | |
} | |
} | |
maxWalk--; | |
} | |
} | |
private static void UpdateCrust(Crust[,] crust, Vector2[,] tectonicForces, double[,] mantleHeat, double[,] crustShifts) | |
{ | |
// Undensify (totally a word) by fractional differentiation of heavier elements | |
// Also standardise the thickness | |
crust.Do((c, y, x) => | |
{ | |
var densityChange = (2.4 - c[y, x].Density) * (mantleHeat[y, x] / 50); | |
ChangeDensity(c, y, x, densityChange); | |
//c[y, x].Thickness -= (c[y, x].Thickness - 15 + rand.NextDouble() * 15) / 5000; | |
if (c[y, x].Top < 0.2) c[y, x].Top = 0.2; | |
}); | |
// Move crust based on isostasy | |
crust.Do((c, y, x) => | |
{ | |
var idealHeight = (1 - crust[y, x].Density / 3.3) * crust[y, x].Thickness; | |
crust[y, x].Force.Z += (float)(idealHeight - crust[y, x].Top) / 4; | |
}); | |
// Apply tectonic forces from convection (super hack) | |
crust.Do((c, y, x) => | |
{ | |
var v = tectonicForces[y, x]; | |
c[y, x].Force.X += v.X; | |
c[y, x].Force.Y += v.Y; | |
}); | |
// Distribute forces | |
for (int i = 0; i < 5; i++) | |
{ | |
crust.Do((c, y, x) => | |
{ | |
DistributeForce(c, y, x); | |
}); | |
} | |
var temp = oldPlateForces; | |
oldPlateForces = plateForces; | |
plateForces = temp; | |
for (int i = 0; i < NUMBER_OF_PLATES; i++) | |
{ | |
plateMass[i] = 0; | |
plateForces[i] = default(Vector2); | |
} | |
// Resolve forces | |
crust.Do((c, y, x) => | |
{ | |
var upForce = c[y, x].Force.Z; | |
c[y, x].Top += upForce; | |
c[y, x].Force.Z -= upForce; | |
Vector2 lateralForce = new Vector2(c[y, x].Force.X, c[y, x].Force.Y); | |
plateForces[c[y, x].PlateId] += lateralForce; | |
plateMass[c[y, x].PlateId] += 1; | |
}); | |
for (int i = 0; i < NUMBER_OF_PLATES; i++) | |
plateForces[i] = plateForces[i] / (float)plateMass[i] + oldPlateForces[i]; | |
bool doing = true; | |
while (doing) | |
{ | |
doing = false; | |
for (int i = 0; i < NUMBER_OF_PLATES; i++) | |
{ | |
if (plateForces[i].X >= 1) | |
{ | |
ShiftPlate(crust, i, 1, 0); | |
plateForces[i].X -= 1; | |
doing = true; | |
} | |
if (plateForces[i].X <= -1) | |
{ | |
ShiftPlate(crust, i, -1, 0); | |
plateForces[i].X += 1; | |
doing = true; | |
} | |
if (plateForces[i].Y >= 1) | |
{ | |
ShiftPlate(crust, i, 0, 1); | |
plateForces[i].Y -= 1; | |
doing = true; | |
} | |
if (plateForces[i].Y <= -1) | |
{ | |
ShiftPlate(crust, i, 0, -1); | |
plateForces[i].Y += 1; | |
doing = true; | |
} | |
} | |
} | |
// TODO: Calculate plate splits | |
// Smooth out the tops | |
crust.Do((c, y, x) => | |
{ | |
crustShifts[y, x] = crust[y, x].Top; | |
}); | |
crust.Do((c, y, x) => | |
{ | |
crust[y, x].Top += (GetAverageOfCross(crustShifts, x, y) - crust[y, x].Top) / 100; | |
}); | |
} | |
private static void ChangeDensity(Crust[,] c, int y, int x, double densityChange) | |
{ | |
c[y, x].Thickness = (c[y, x].Thickness * c[y, x].Density) / (c[y, x].Density + densityChange); | |
c[y, x].Density += densityChange; | |
} | |
private static void ShiftPlate(Crust[,] crust, int plateId, int xOff, int yOff) | |
{ | |
// Stops continents going "off screen" | |
var fuckoff = false; | |
crust.Do((c, y, x) => | |
{ | |
if (c[y, x].PlateId == plateId && ( | |
y + yOff < 0 || y + yOff >= WIDTH || | |
x + xOff < 0 || x + xOff >= HEIGHT) | |
) | |
{ | |
fuckoff = true; | |
} | |
}); | |
if (fuckoff) return; | |
crust.Do((c, y, x) => | |
{ | |
if (c[y, x].PlateId == plateId) | |
{ | |
var donorX = x - xOff; | |
var donorY = y - yOff; | |
if (donorX >= 0 && donorX < WIDTH && donorY >= 0 && donorY < HEIGHT && crust[donorY, donorX].PlateId == crust[y, x].PlateId) | |
{ | |
var toX = x + xOff; | |
var toY = y + yOff; | |
// Subduct the lower crust more often | |
var p = Math.Pow((Math.Tanh(c[toY, toX].Top - c[y, x].Top) + 1) / 2, 5); | |
if (rand.NextDouble() < p) | |
{ | |
Subduct(c, toY, toX, c[y, x]); | |
} | |
else | |
{ | |
var temp = c[toY, toX]; | |
c[toY, toX] = c[y, x]; | |
Subduct(c, toY, toX, temp); | |
} | |
crust[y, x] = crust[donorY, donorX]; | |
} | |
else | |
{ | |
// Spawn new oceanic crust | |
crust[y, x] = new Crust() | |
{ | |
Density = OCEANIC_CRUST_START_DENSITY, | |
Thickness = OCEANIC_CRUST_START_THICKNESS, | |
Top = OCEANIC_CRUST_START_TOP, | |
PlateId = crust[y, x].PlateId | |
}; | |
} | |
} | |
}, xOff > 0 || yOff > 0); // Go backwards if we are shifting x or y forwards | |
} | |
private static void Subduct(Crust[,] c, int y, int x, Crust crust) | |
{ | |
// Give the crust being subducted under a bit of a bump | |
c[y, x].Thickness += crust.Top / 100; | |
// And give it a bit of lower density magma | |
var densityChange = (2.4 - c[y, x].Density) / 50; | |
ChangeDensity(c, y, x, densityChange); | |
} | |
private static void DistributeForce(Crust[,] crust, int y, int x) | |
{ | |
var plateId = crust[y, x].PlateId; | |
var north = y > 0;// && crust[y - 1, x].PlateId == plateId; | |
var east = x < WIDTH - 1;// && crust[y, x + 1].PlateId == plateId; | |
var south = y < HEIGHT - 1;// && crust[y + 1, x].PlateId == plateId; | |
var west = x > 0;// && crust[y, x - 1].PlateId == plateId; | |
if (!(north || east || south || west)) | |
return; | |
// Let's distribute half the force because why not. | |
var excess = crust[y, x].Force / 2; | |
crust[y, x].Force -= excess; | |
var splitAmount = (excess / 4); | |
if (north) crust[y - 1, x].Force += splitAmount; | |
if (east) crust[y, x + 1].Force += splitAmount; | |
if (south) crust[y + 1, x].Force += splitAmount; | |
if (west) crust[y, x - 1].Force += splitAmount; | |
} | |
private static double GetAverageOfCross(double[,] m, int x, int y) | |
{ | |
return ((y > 0 ? m[y - 1, x] : m[y, x]) + | |
(y < HEIGHT - 1 ? m[y + 1, x] : m[y, x]) + | |
(x > 0 ? m[y, x - 1] : m[y, x]) + | |
(x < WIDTH - 1 ? m[y, x + 1] : m[y, x]) + | |
m[y, x]) / 5; | |
} | |
private static void UpdateTectonicForces(Vector2[,] tectonicForces, double[,] mantleHeat) | |
{ | |
mantleHeat.Map(tectonicForces, (m, y, x) => | |
{ | |
// Hot flows towards cold. | |
var xForce = (m[y, x] - (x > 0 ? m[y, x - 1] : m[y, x])) + | |
((x < WIDTH - 1 ? m[y, x + 1] : m[y, x]) - m[y, x]) * 100; | |
var yForce = (m[y, x] - (y > 0 ? m[y - 1, x] : m[y, x])) + | |
((y < HEIGHT - 1 ? m[y + 1, x] : m[y, x]) - m[y, x]) * 100; | |
return new Vector2((float)xForce, (float)yForce); | |
}); | |
} | |
static void UpdateMantleHeat(ref double[,] mantleHeat, ref double[,] velocity) | |
{ | |
var vel = velocity; | |
var mhe = mantleHeat; | |
// Randomly add and remove hotspots. These act kind of like mantle plumes. Which totally might not be a real thing. | |
if (rand.NextDouble() > 0.85 + 0.01 * hotspots.Count) | |
hotspots.Add(new Vector2(rand.Next(WIDTH), rand.Next(HEIGHT))); | |
if (rand.NextDouble() > 0.85 && hotspots.Count > 3) | |
hotspots.RemoveAt(0); | |
// What's that? Heat doesn't travel in waves? Screw you, if I say it travels in waves then it travels in waves! | |
foreach (Vector2 hotspot in hotspots) | |
{ | |
vel[(int)hotspot.Y, (int)hotspot.X] += 1; | |
} | |
mantleHeat.Do((m, y, x) => | |
{ | |
vel[y, x] += | |
(((y > 0 ? m[y - 1, x] : m[y, x]) + | |
(y < HEIGHT - 1 ? m[y + 1, x] : m[y, x]) + | |
(x > 0 ? m[y, x - 1] : m[y, x]) + | |
(x < WIDTH - 1 ? m[y, x + 1] : m[y, x])) / 4 | |
- m[y, x]); | |
vel[y, x] *= 0.9999; | |
}); | |
velocity.Do((v, y, x) => | |
{ | |
mhe[y, x] += v[y, x]; | |
}); | |
//Normalise the energy in the system to prevent it growing or shrinking | |
var totalEnergy = 0.0; | |
mhe.Do((m, y, x) => { totalEnergy += Math.Abs(m[y, x]); }); | |
var adjust = (WIDTH * HEIGHT / 4) / totalEnergy; | |
mhe.Do((m, y, x) => { m[y, x] = m[y, x] * adjust; }); | |
} | |
private static void DrawMantleHeat(Bitmap bitmap, double[,] mantleHeat) | |
{ | |
var data = bitmap.LockBits(Rectify(bitmap.Size), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); | |
unsafe | |
{ | |
byte* start = (byte*)data.Scan0; | |
mantleHeat.Do((m, y, x) => | |
{ | |
var val = m[y, x]; | |
byte r = 0; | |
byte g = 0; | |
byte b = 0; | |
if (val < 0) | |
{ | |
r = 0; | |
b = (byte)(-val * 100); | |
} | |
else | |
{ | |
b = 0; | |
r = (byte)(val * 100); | |
} | |
if (val > 3) g = 255; | |
*(start) = b; | |
*(start + 1) = g; | |
*(start + 2) = r; | |
start += 3; | |
}); | |
} | |
bitmap.UnlockBits(data); | |
bitmap.Save("mantle" + frame + ".png"); | |
} | |
private static void DrawHeightmap(Bitmap bitmap, Crust[,] crust) | |
{ | |
var data = bitmap.LockBits(Rectify(bitmap.Size), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); | |
var maxTop = 3.0; | |
crust.Do((c, y, x) => | |
{ | |
if (c[y,x].TotalTop() > maxTop) | |
maxTop = c[y,x].TotalTop(); | |
}); | |
unsafe | |
{ | |
byte* start = (byte*)data.Scan0; | |
crust.Do((m, y, x) => | |
{ | |
var c = m[y, x]; | |
byte r = 0; | |
byte g = 0; | |
byte b = 0; | |
r = b = g = (byte)((c.TotalTop() / maxTop) * 255); | |
*(start) = b; | |
*(start + 1) = g; | |
*(start + 2) = r; | |
start += 3; | |
}); | |
} | |
bitmap.UnlockBits(data); | |
bitmap.Save("crust" + frame + ".png"); | |
} | |
private static void DrawCrust(Bitmap bitmap, Crust[,] crust) | |
{ | |
var data = bitmap.LockBits(Rectify(bitmap.Size), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); | |
var maxTop = 3.0; | |
var maxDepth = 3.0; | |
var maxSediment = 1.0; | |
crust.Do((m, y, x) => | |
{ | |
var c = m[y, x]; | |
if (waterLevel - c.TotalTop() > maxDepth) | |
maxDepth = waterLevel - c.TotalTop(); | |
if (c.Top > maxTop) | |
maxTop = c.Top; | |
if (c.Sediment > maxSediment) | |
maxSediment = c.Sediment; | |
}); | |
unsafe | |
{ | |
byte* start = (byte*)data.Scan0; | |
crust.Do((m, y, x) => | |
{ | |
var c = m[y, x]; | |
byte r = 0; | |
byte g = 0; | |
byte b = 0; | |
//r = b = g = (byte)(50 + (c.TotalTop() / maxTop) * 120); | |
r = c.TotalTop() >= waterLevel ? (byte)(50 + (c.Top / maxTop) * 120) : (byte)0; | |
var water = waterLevel - c.TotalTop(); | |
b = water > 0 ? (byte)(128 - (water / maxDepth) * 120) : (byte)0; | |
g = (byte)((c.Sediment / maxSediment) * 250); | |
*(start) = b; | |
*(start + 1) = g; | |
*(start + 2) = r; | |
start += 3; | |
}); | |
} | |
bitmap.UnlockBits(data); | |
bitmap.Save("crust" + frame + ".png"); | |
} | |
private static void DrawCrustForce(Bitmap bitmap, Crust[,] crust) | |
{ | |
var data = bitmap.LockBits(Rectify(bitmap.Size), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); | |
var maxForce = 0.1f; | |
crust.Do((m, y, x) => | |
{ | |
var d = crust[y, x].Force.Length(); | |
if (d > maxForce) | |
maxForce = d; | |
}); | |
maxForce = Math.Max(maxForce, 1); | |
unsafe | |
{ | |
byte* start = (byte*)data.Scan0; | |
crust.Do((m, y, x) => | |
{ | |
var c = m[y, x]; | |
var force = c.Force.Length(); | |
*(start) = (byte)((force / maxForce) * 250); | |
*(start + 1) = (byte)((force / maxForce) * 250); | |
*(start + 2) = (byte)((force / maxForce) * 250); | |
start += 3; | |
}); | |
} | |
bitmap.UnlockBits(data); | |
bitmap.Save("crustForces" + frame + ".png"); | |
} | |
private static Rectangle Rectify(Size size) | |
{ | |
return new Rectangle(0, 0, size.Width, size.Height); | |
} | |
private static void Swap(ref double[,] mantleHeat, ref double[,] temp) | |
{ | |
var swap = temp; | |
temp = mantleHeat; | |
mantleHeat = swap; | |
} | |
static void Display(double[,] array) | |
{ | |
array.Do((m, y, x) => | |
{ | |
if (y < 20 && x < 20) | |
{ | |
Console.SetCursorPosition(x * 4, y); | |
if (m[y, x] > 0) Console.ForegroundColor = ConsoleColor.Red; | |
if (m[y, x] == 0) Console.ForegroundColor = ConsoleColor.White; | |
if (m[y, x] < 0) Console.ForegroundColor = ConsoleColor.Blue; | |
Console.Write((int)m[y, x] + (x == WIDTH - 1 ? "\n" : "")); | |
} | |
}); | |
} | |
} | |
internal class CrustPointComparer : IComparer<Point> | |
{ | |
private Crust[,] crust; | |
public CrustPointComparer(Crust[,] crust) | |
{ | |
this.crust = crust; | |
} | |
public int Compare(Point a, Point b) | |
{ | |
return crust[a.Y, a.X].CompareTo(crust[b.Y, b.X]); | |
} | |
} | |
internal struct Crust : IComparable<Crust> | |
{ | |
// Most of these are in kilometer units | |
public double Top; // Height of the top of solid matter of this block (without sediment) above mantle equilibrium point | |
public double Density; // Density of the solid matter in this | |
public double Thickness; // Height of solid matter in this block | |
public double Sediment; // The amount of sediment on top of this block | |
public Vector3 Force; // Direction and magnitude of force being applied to this section of crust | |
public int PlateId; | |
public override string ToString() | |
{ | |
return $"Density (g/cm^3): {Density} Thickness (km): {Thickness} PlateId: {PlateId}"; | |
} | |
public int CompareTo(Crust other) | |
{ | |
return (Top).CompareTo(other.Top); | |
} | |
public double TotalTop() | |
{ | |
return Top + Sediment; | |
} | |
} | |
static class Extensions | |
{ | |
public static void Map<T, TOut>(this T[,] array, TOut[,] outArray, Func<T[,], int, int, TOut> action) | |
{ | |
for (int y = 0; y < array.GetLength(0); y++) | |
{ | |
for (int x = 0; x < array.GetLength(1); x++) | |
{ | |
outArray[y, x] = action(array, y, x); | |
} | |
} | |
} | |
public static void Do<T>(this T[,] array, Action<T[,], int, int> action) | |
{ | |
Do(array, action, false); | |
} | |
public static void Do<T>(this T[,] array, Action<T[,], int, int> action, bool backwards) | |
{ | |
if (backwards) | |
{ | |
for (int y = array.GetLength(0) - 1; y >= 0; y--) | |
{ | |
for (int x = array.GetLength(1) - 1; x >= 0; x--) | |
{ | |
action(array, y, x); | |
} | |
} | |
} | |
else | |
{ | |
for (int y = 0; y < array.GetLength(0); y++) | |
{ | |
for (int x = 0; x < array.GetLength(1); x++) | |
{ | |
action(array, y, x); | |
} | |
} | |
} | |
} | |
public static double XYSum(this Vector3 me) | |
{ | |
return me.X + me.Y; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment