ClassiCube/ClassicalSharp/Generator/NotchyGenerator.cs

192 lines
6.5 KiB
C#

// Based on:
// https://github.com/UnknownShadow200/ClassicalSharp/wiki/Minecraft-Classic-map-generation-algorithm
// Thanks to Jerralish for originally reverse engineering classic's algorithm, then preparing a high level overview of the algorithm.
// I believe this process adheres to clean room reverse engineering.
using System;
using System.Collections.Generic;
namespace ClassicalSharp.Generator {
// TODO: figure out how noise functions differ, probably based on rnd.
public sealed partial class NotchyGenerator {
int width, height, length;
int waterLevel, oneY;
byte[] blocks;
short[] heightmap;
Random rnd;
public byte[] GenerateMap( int width, int height, int length ) {
this.width = width;
this.height = height;
this.length = length;
oneY = width * length;
waterLevel = height / 2;
blocks = new byte[width * height * length];
rnd = new Random( 0x5553200 );
CreateHeightmap();
CreateStrata();
CarveCaves();
CarveOreVeins( 0.9f, (byte)Block.CoalOre );
CarveOreVeins( 0.7f, (byte)Block.IronOre );
CarveOreVeins( 0.5f, (byte)Block.GoldOre );
FloodFillWaterBorders();
FloodFillWater();
FloodFillLava();
return blocks;
}
void CreateHeightmap() {
Noise n1 = new CombinedNoise(
new OctaveNoise( 8, rnd ), new OctaveNoise( 8, rnd ) );
Noise n2 = new CombinedNoise(
new OctaveNoise( 8, rnd ), new OctaveNoise( 8, rnd ) );
Noise n3 = new OctaveNoise( 6, rnd );
int index = 0;
short[] hMap = new short[width * length];
for( int z = 0; z < length; z++ ) {
for( int x = 0; x < width; x++ ) {
double hLow = n1.Compute( x * 1.3f, z * 1.3f ) / 6 - 4;
double hHigh = n2.Compute( x * 1.3f, z * 1.3f ) / 5 + 6;
double height = n3.Compute( x, z ) > 0 ? hLow : Math.Max( hLow, hHigh );
if( height < 0 ) height *= 0.8f;
hMap[index++] = (short)(height + waterLevel);
}
}
heightmap = hMap;
}
void CreateStrata() {
Noise n = new OctaveNoise( 8, rnd );
//Noise n = new ImprovedNoise( rnd );
int hMapIndex = 0;
for( int z = 0; z < length; z++ ) {
for( int x = 0; x < width; x++ ) {
int dirtThickness = (int)(n.Compute( x, z ) / 24 - 4);
int dirtHeight = heightmap[hMapIndex++];
int stoneHeight = dirtHeight + dirtThickness;
int mapIndex = z * width + x;
blocks[mapIndex] = (byte)Block.Lava;
mapIndex += oneY;
for( int y = 1; y < height; y++ ) {
byte type = 0;
if( y <= stoneHeight ) type = (byte)Block.Stone;
else if( y <= dirtHeight ) type = (byte)Block.Dirt;
blocks[mapIndex] = type;
mapIndex += oneY;
}
}
}
}
void CarveCaves() {
int cavesCount = blocks.Length / 8192;
for( int i = 0; i < cavesCount; i++ ) {
double caveX = rnd.Next( width );
double caveY = rnd.Next( height );
double caveZ = rnd.Next( length );
int caveLen = (int)(rnd.NextDouble() * rnd.NextDouble() * 200);
double theta = rnd.NextDouble() * 2 * Math.PI, deltaTheta = 0;
double phi = rnd.NextDouble() * 2 * Math.PI, deltaPhi = 0;
double caveRadius = rnd.NextDouble() * rnd.NextDouble();
for( int j = 0; j < caveLen; j++ ) {
caveX += Math.Sin( theta ) * Math.Cos( phi );
caveY += Math.Cos( theta ) * Math.Cos( phi );
caveZ += Math.Sin( phi );
theta = deltaTheta * 0.2;
deltaTheta = deltaTheta * 0.9 + rnd.NextDouble() - rnd.NextDouble();
phi = phi / 2 + deltaPhi / 4;
deltaPhi = deltaPhi * 0.75 + rnd.NextDouble() - rnd.NextDouble();
if( rnd.NextDouble() < 0.25 ) continue;
int cenX = (int)(caveX + (rnd.Next( 4 ) - 2) * 0.2);
int cenY = (int)(caveY + (rnd.Next( 4 ) - 2) * 0.2);
int cenZ = (int)(caveZ + (rnd.Next( 4 ) - 2) * 0.2);
double radius = (height - cenY) / (double)height;
radius = 1.2 + (radius * 3.5 + 1) * caveRadius;
radius = radius + Math.Sin( j * Math.PI / caveLen );
FillOblateSpheroid( cenX, cenY, cenZ, (int)radius, (byte)Block.Air );
}
}
}
void CarveOreVeins( float abundance, byte block ) {
int numVeins = (int)(blocks.Length * abundance / 16384);
for( int i = 0; i < numVeins; i++ ) {
double veinX = rnd.Next( width );
double veinY = rnd.Next( height );
double veinZ = rnd.Next( length );
int veinLen = (int)(rnd.NextDouble() * rnd.NextDouble() * 75 * abundance);
double theta = rnd.NextDouble() * 2 * Math.PI, deltaTheta = 0;
double phi = rnd.NextDouble() * 2 * Math.PI, deltaPhi = 0;
for( int j = 0; j < veinLen; j++ ) {
veinX += Math.Sin( theta ) * Math.Cos( phi );
veinY += Math.Cos( theta ) * Math.Cos( phi );
veinZ += Math.Sin( phi );
theta = deltaTheta * 0.2;
deltaTheta = deltaTheta * 0.9 + rnd.NextDouble() - rnd.NextDouble();
phi = phi / 2 + deltaPhi / 4;
deltaPhi = deltaPhi * 0.9 + rnd.NextDouble() - rnd.NextDouble();
int radius = (int)(abundance * Math.Sin( j * Math.PI / veinLen ) + 1);
FillOblateSpheroid( (int)veinX, (int)veinY, (int)veinZ, radius, block );
}
}
}
void FloodFillWaterBorders() {
int waterY = waterLevel - 1;
int index1 = (waterY * length + 0) * width + 0;
int index2 = (waterY * length + (length - 1)) * width + 0;
for( int x = 0; x < width; x++ ) {
FloodFill( index1, (byte)Block.Water );
FloodFill( index2, (byte)Block.Water );
index1++; index2++;
}
index1 = (waterY * length + 0) * width + 0;
index2 = (waterY * length + 0) * width + (width - 1);
for( int z = 0; z < length; z++ ) {
FloodFill( index1, (byte)Block.Water );
FloodFill( index2, (byte)Block.Water );
index1 += width; index2 += width;
}
}
void FloodFillWater() {
int numSources = width * length / 800;
for( int i = 0; i < numSources; i++ ) {
int x = rnd.Next( width ), z = rnd.Next( length );
int y = waterLevel - rnd.Next( 1, 3 );
FloodFill( (y * length + z) * width + x, (byte)Block.Water );
}
}
void FloodFillLava() {
int numSources = width * length / 20000;
for( int i = 0; i < numSources; i++ ) {
int x = rnd.Next( width ), z = rnd.Next( length );
int y = (int)((waterLevel - 3) * rnd.NextDouble() * rnd.NextDouble());
FloodFill( (y * length + z) * width + x, (byte)Block.Lava );
}
}
void CreateSurfaceLayer() {
Noise n1 = new OctaveNoise( 8, rnd ), n2 = new OctaveNoise( 8, rnd );
}
}
}