diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj
index 95fab372e..05076232e 100644
--- a/ClassicalSharp/ClassicalSharp.csproj
+++ b/ClassicalSharp/ClassicalSharp.csproj
@@ -4,7 +4,7 @@
{BEB1C785-5CAD-48FF-A886-876BF0A318D4}
Debug
AnyCPU
- WinExe
+ Exe
ClassicalSharp
ClassicalSharp
v2.0
@@ -143,6 +143,7 @@
+
diff --git a/ClassicalSharp/Generator/Noise.cs b/ClassicalSharp/Generator/Noise.cs
index 602ede782..d55b2c767 100644
--- a/ClassicalSharp/Generator/Noise.cs
+++ b/ClassicalSharp/Generator/Noise.cs
@@ -6,7 +6,6 @@ using System;
namespace ClassicalSharp.Generator {
- // TODO:L calculate based on seed
public abstract class Noise {
public abstract double Compute( double x, double y );
@@ -14,13 +13,20 @@ namespace ClassicalSharp.Generator {
public sealed class ImprovedNoise : Noise {
+ public ImprovedNoise( Random rnd ) {
+ // make a random initial permutation based on seed,
+ // instead of using fixed permutation table in original code.
+ for( int i = 0; i < 256; i++ )
+ p[i + 256] = p[i] = rnd.Next( 256 );
+ }
+
+ // TODO: need to half this maybe?
public override double Compute( double x, double y ) {
+ int xFloor = Utils.Floor( x ), yFloor = Utils.Floor( y );
// Find unit rectangle that contains point
- int X = (int)Math.Floor( x ) & 255;
- int Y = (int)Math.Floor( y ) & 255;
+ int X = xFloor & 0xFF, Y = yFloor & 0xFF;
// Find relative x, y of each point in rectangle.
- x -= Math.Floor( x );
- y -= Math.Floor( y );
+ x -= Math.Floor( x ); y -= Math.Floor( y );
// Compute fade curves for each of x, y.
double u = Fade( x ), v = Fade( y );
@@ -54,43 +60,26 @@ namespace ClassicalSharp.Generator {
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
- static int[] p = new int[512];
- static int[] permutation = { 151,160,137,91,90,15,
- 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
- 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
- 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
- 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
- 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
- 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
- 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
- 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
- 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
- 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
- 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
- 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
- };
-
- static ImprovedNoise() {
- for( int i = 0; i < 256; i++ )
- p[256+i] = p[i] = permutation[i];
- }
+ int[] p = new int[512];
}
public sealed class OctaveNoise : Noise {
readonly int octaves;
- readonly ImprovedNoise baseNoise;
- public OctaveNoise( int octaves ) {
+ readonly ImprovedNoise[] baseNoise;
+ public OctaveNoise( int octaves, Random rnd ) {
this.octaves = octaves;
- baseNoise = new ImprovedNoise();
+ baseNoise = new ImprovedNoise[octaves];
+ for( int i = 0; i < octaves; i++ )
+ baseNoise[i] = new ImprovedNoise( rnd );
}
public override double Compute( double x, double y ) {
double amplitude = 1, frequency = 1;
double sum = 0;
- for( int i = 0; i < octaves; i++ ) {
- sum += baseNoise.Compute( x * frequency, y * frequency ) * amplitude;
+ for( int i = 0; i < baseNoise.Length; i++ ) {
+ sum += baseNoise[i].Compute( x * frequency, y * frequency ) * amplitude;
amplitude *= 2;
frequency /= 2;
}
diff --git a/ClassicalSharp/Generator/NotchyGenerator.Utils.cs b/ClassicalSharp/Generator/NotchyGenerator.Utils.cs
new file mode 100644
index 000000000..b7773f126
--- /dev/null
+++ b/ClassicalSharp/Generator/NotchyGenerator.Utils.cs
@@ -0,0 +1,75 @@
+// 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 {
+
+ void FillOblateSpheroid( int x, int y, int z, int radius, byte block ) {
+ int xStart = Math.Max( x - radius, 0 ), xEnd = Math.Min( x + radius, width - 1 );
+ int yStart = Math.Max( y - radius, 0 ), yEnd = Math.Min( y + radius, height - 1 );
+ int zStart = Math.Max( z - radius, 0 ), zEnd = Math.Min( z + radius, length - 1 );
+ int radiusSq = radius * radius;
+
+ for( int yy = yStart; yy <= yEnd; yy++ )
+ for( int zz = zStart; zz <= zEnd; zz++ )
+ for( int xx = xStart; xx <= xEnd; xx++ )
+ {
+ int dx = xx - x, dy = yy - y, dz = zz - z;
+ if( (dx * dx + 2 * dy * dy + dz * dz) < radiusSq ) {
+ int index = (yy * length + zz) * width + xx;
+ if( blocks[index] == (byte)Block.Stone )
+ blocks[index] = block;
+ }
+ }
+ }
+
+ void FloodFill( int startIndex, byte block ) {
+ FastIntStack stack = new FastIntStack( 4 );
+ stack.Push( startIndex );
+ while( stack.Size > 0 ) {
+ int index = stack.Pop();
+ if( blocks[index] == 0 ) {
+ blocks[index] = block;
+
+ int x = index % width;
+ int y = index / oneY;
+ int z = (index / width) % length;
+ if( x > 0 ) stack.Push( index - 1 );
+ if( x < width - 1 ) stack.Push( index + 1 );
+ if( z > 0 ) stack.Push( index - width );
+ if( z < length - 1 ) stack.Push( index + width );
+ if( y > 0 ) stack.Push( index - oneY );
+ }
+ }
+ }
+
+ sealed class FastIntStack {
+ public int[] Values;
+ public int Size;
+
+ public FastIntStack( int capacity ) {
+ Values = new int[capacity];
+ Size = 0;
+ }
+
+ public int Pop() {
+ return Values[--Size];
+ }
+
+ public void Push( int item ) {
+ if( Size == Values.Length ) {
+ int[] array = new int[Values.Length * 2];
+ Buffer.BlockCopy( Values, 0, array, 0, Size * sizeof(int) );
+ Values = array;
+ }
+ Values[Size++] = item;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ClassicalSharp/Generator/NotchyGenerator.cs b/ClassicalSharp/Generator/NotchyGenerator.cs
index 462e6aaf4..b60ddc90e 100644
--- a/ClassicalSharp/Generator/NotchyGenerator.cs
+++ b/ClassicalSharp/Generator/NotchyGenerator.cs
@@ -3,37 +3,49 @@
// 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 class NotchyGenerator {
+ public sealed partial class NotchyGenerator {
int width, height, length;
- int waterLevel;
+ 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();
- return null;
+ 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 ), new OctaveNoise( 8 ) );
+ new OctaveNoise( 8, rnd ), new OctaveNoise( 8, rnd ) );
Noise n2 = new CombinedNoise(
- new OctaveNoise( 8 ), new OctaveNoise( 8 ) );
- Noise n3 = new OctaveNoise( 6 );
+ new OctaveNoise( 8, rnd ), new OctaveNoise( 8, rnd ) );
+ Noise n3 = new OctaveNoise( 6, rnd );
int index = 0;
- short[] map = new short[width * length];
+ short[] hMap = new short[width * length];
for( int z = 0; z < length; z++ ) {
for( int x = 0; x < width; x++ ) {
@@ -42,28 +54,139 @@ namespace ClassicalSharp.Generator {
double height = n3.Compute( x, z ) > 0 ? hLow : Math.Max( hLow, hHigh );
if( height < 0 ) height *= 0.8f;
- map[index++] = (short)(height + waterLevel);
+ hMap[index++] = (short)(height + waterLevel);
}
}
- heightmap = map;
+ heightmap = hMap;
}
void CreateStrata() {
- Noise n = new OctaveNoise( 8 );
+ Noise n = new OctaveNoise( 8, rnd );
+ //Noise n = new ImprovedNoise( rnd );
- int mapIndex = 0;
+ 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[mapIndex];
+ int dirtHeight = heightmap[hMapIndex++];
+
int stoneHeight = dirtHeight + dirtThickness;
+ int mapIndex = z * width + x;
- for( int y = 0; y < height; y++ ) {
-
+ 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;
}
- mapIndex++;
}
}
}
+
+ 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 );
+
+ }
}
}
\ No newline at end of file
diff --git a/ClassicalSharp/Singleplayer/Server.cs b/ClassicalSharp/Singleplayer/Server.cs
index b09384c70..8023fdc98 100644
--- a/ClassicalSharp/Singleplayer/Server.cs
+++ b/ClassicalSharp/Singleplayer/Server.cs
@@ -27,7 +27,8 @@ namespace ClassicalSharp.Singleplayer {
game.Events.RaiseBlockPermissionsChanged();
NewMap();
- MakeMap( 128, 128, 128 );
+ //MakeMap( 128, 128, 128 );
+ MakeMap( 128, 64, 128 );
game.CommandManager.RegisterCommand( new GenerateCommand() );
}
@@ -68,12 +69,14 @@ namespace ClassicalSharp.Singleplayer {
}
internal unsafe void MakeMap( int width, int height, int length ) {
- byte[] map = new byte[width * height * length];
- var sw = System.Diagnostics.Stopwatch.StartNew();
- fixed( byte* ptr = map ) {
- MapSet( width, length, ptr, 0, height / 2 - 2, (byte)Block.Dirt );
- MapSet( width, length, ptr, height / 2 - 1, height / 2 - 1, (byte)Block.Grass );
- }
+ //byte[] map = new byte[width * height * length];
+ //var sw = System.Diagnostics.Stopwatch.StartNew();
+ //fixed( byte* ptr = map ) {
+ // MapSet( width, length, ptr, 0, height / 2 - 2, (byte)Block.Dirt );
+ // MapSet( width, length, ptr, height / 2 - 1, height / 2 - 1, (byte)Block.Grass );
+ //}
+ byte[] map = new ClassicalSharp.Generator.NotchyGenerator()
+ .GenerateMap( width, height, length );
game.Map.SetData( map, width, height, length );
game.Events.RaiseOnNewMapLoaded();
game.SetNewScreen( null );
diff --git a/ClassicalSharp/Utils/Utils.cs b/ClassicalSharp/Utils/Utils.cs
index dc43f1d54..4e4750b43 100644
--- a/ClassicalSharp/Utils/Utils.cs
+++ b/ClassicalSharp/Utils/Utils.cs
@@ -242,6 +242,10 @@ namespace ClassicalSharp {
return value >= 0 ? (int)value : (int)value - 1;
}
+ public static int Floor( double value ) {
+ return value >= 0 ? (int)value : (int)value - 1;
+ }
+
public static int AdjViewDist( int value ) {
return (int)(1.4142135 * value);
}