Seed each noise instance, more work on singleplayer generator.

This commit is contained in:
UnknownShadow200 2015-12-05 23:14:48 +11:00
parent 0ab941c787
commit 3b90136e40
6 changed files with 248 additions and 53 deletions

View File

@ -4,7 +4,7 @@
<ProjectGuid>{BEB1C785-5CAD-48FF-A886-876BF0A318D4}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<OutputType>WinExe</OutputType>
<OutputType>Exe</OutputType>
<RootNamespace>ClassicalSharp</RootNamespace>
<AssemblyName>ClassicalSharp</AssemblyName>
<TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
@ -143,6 +143,7 @@
<Compile Include="Game\Game.Properties.cs" />
<Compile Include="Generator\Noise.cs" />
<Compile Include="Generator\NotchyGenerator.cs" />
<Compile Include="Generator\NotchyGenerator.Utils.cs" />
<Compile Include="Particles\CollidableParticle.cs" />
<Compile Include="Particles\Particle.cs" />
<Compile Include="Particles\ParticleManager.cs" />

View File

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

View File

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

View File

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

View File

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

View File

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