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); }