mirror of
				https://github.com/ClassiCube/ClassiCube.git
				synced 2025-11-04 03:27:49 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			392 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			392 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
 | 
						|
// 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 {
 | 
						|
	
 | 
						|
	public sealed partial class NotchyGenerator : IMapGenerator {
 | 
						|
		
 | 
						|
		int width, height, length;
 | 
						|
		int waterLevel, oneY;
 | 
						|
		byte[] blocks;
 | 
						|
		short[] heightmap;
 | 
						|
		JavaRandom rnd;
 | 
						|
		
 | 
						|
		public override string GeneratorName { get { return "Vanilla classic"; } }
 | 
						|
		
 | 
						|
		public override byte[] Generate(int width, int height, int length, int seed) {
 | 
						|
			this.width = width;
 | 
						|
			this.height = height;
 | 
						|
			this.length = length;
 | 
						|
			oneY = width * length;
 | 
						|
			waterLevel = height / 2;
 | 
						|
			blocks = new byte[width * height * length];
 | 
						|
			rnd = new JavaRandom(seed);
 | 
						|
			
 | 
						|
			CreateHeightmap();			
 | 
						|
			CreateStrata();
 | 
						|
			CarveCaves();
 | 
						|
			CarveOreVeins(0.9f, "coal ore", Block.CoalOre);
 | 
						|
			CarveOreVeins(0.7f, "iron ore", Block.IronOre);
 | 
						|
			CarveOreVeins(0.5f, "gold ore", Block.GoldOre);
 | 
						|
			
 | 
						|
			FloodFillWaterBorders();
 | 
						|
			FloodFillWater();
 | 
						|
			FloodFillLava();
 | 
						|
 | 
						|
			CreateSurfaceLayer();
 | 
						|
			PlantFlowers();
 | 
						|
			PlantMushrooms();
 | 
						|
			PlantTrees();
 | 
						|
			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];
 | 
						|
			CurrentState = "Building heightmap";
 | 
						|
			
 | 
						|
			for (int z = 0; z < length; z++) {
 | 
						|
				CurrentProgress = (float)z / length;
 | 
						|
				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);
 | 
						|
					height *= 0.5;
 | 
						|
					if (height < 0) height *= 0.8f;
 | 
						|
					hMap[index++] = (short)(height + waterLevel);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			heightmap = hMap;
 | 
						|
		}
 | 
						|
		
 | 
						|
		void CreateStrata() {
 | 
						|
			Noise n = new OctaveNoise(8, rnd);
 | 
						|
			CurrentState = "Creating strata";
 | 
						|
			
 | 
						|
			int hMapIndex = 0;
 | 
						|
			for (int z = 0; z < length; z++) {
 | 
						|
				CurrentProgress = (float)z / length;
 | 
						|
				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] = Block.Lava;
 | 
						|
					mapIndex += oneY;
 | 
						|
					for (int y = 1; y < height; y++) {
 | 
						|
						byte block = 0;
 | 
						|
						if (y <= stoneHeight) block = Block.Stone;
 | 
						|
						else if (y <= dirtHeight) block = Block.Dirt;
 | 
						|
						
 | 
						|
						blocks[mapIndex] = block;
 | 
						|
						mapIndex += oneY;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		
 | 
						|
		void CarveCaves() {
 | 
						|
			int cavesCount = blocks.Length / 8192;
 | 
						|
			CurrentState = "Carving caves";
 | 
						|
			
 | 
						|
			for (int i = 0; i < cavesCount; i++) {
 | 
						|
				CurrentProgress = (float)i / cavesCount;
 | 
						|
				double caveX = rnd.Next(width);
 | 
						|
				double caveY = rnd.Next(height);
 | 
						|
				double caveZ = rnd.Next(length);
 | 
						|
				
 | 
						|
				int caveLen = (int)(rnd.NextFloat() * rnd.NextFloat() * 200);
 | 
						|
				double theta = rnd.NextFloat() * 2 * Math.PI, deltaTheta = 0;
 | 
						|
				double phi = rnd.NextFloat() * 2 * Math.PI, deltaPhi = 0;
 | 
						|
				double caveRadius = rnd.NextFloat() * rnd.NextFloat();
 | 
						|
				
 | 
						|
				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 = theta + deltaTheta * 0.2;
 | 
						|
					deltaTheta = deltaTheta * 0.9 + rnd.NextFloat() - rnd.NextFloat();
 | 
						|
					phi = phi / 2 + deltaPhi / 4;
 | 
						|
					deltaPhi = deltaPhi * 0.75 + rnd.NextFloat() - rnd.NextFloat();
 | 
						|
					if (rnd.NextFloat() < 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, (float)radius, Block.Air);
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		
 | 
						|
		void CarveOreVeins(float abundance, string blockName, byte block) {
 | 
						|
			int numVeins = (int)(blocks.Length * abundance / 16384);
 | 
						|
			CurrentState = "Carving " + blockName;
 | 
						|
			
 | 
						|
			for (int i = 0; i < numVeins; i++) {
 | 
						|
				CurrentProgress = (float)i / numVeins;
 | 
						|
				double veinX = rnd.Next(width);
 | 
						|
				double veinY = rnd.Next(height);
 | 
						|
				double veinZ = rnd.Next(length);
 | 
						|
				
 | 
						|
				int veinLen = (int)(rnd.NextFloat() * rnd.NextFloat() * 75 * abundance);
 | 
						|
				double theta = rnd.NextFloat() * 2 * Math.PI, deltaTheta = 0;
 | 
						|
				double phi = rnd.NextFloat() * 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.NextFloat() - rnd.NextFloat();
 | 
						|
					phi = phi / 2 + deltaPhi / 4;
 | 
						|
					deltaPhi = deltaPhi * 0.9 + rnd.NextFloat() - rnd.NextFloat();
 | 
						|
					
 | 
						|
					float radius = abundance * (float)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;
 | 
						|
			CurrentState = "Flooding edge water";
 | 
						|
			
 | 
						|
			for (int x = 0; x < width; x++) {
 | 
						|
				CurrentProgress = 0 + ((float)x / width) * 0.5f;
 | 
						|
				FloodFill(index1, Block.Water);
 | 
						|
				FloodFill(index2, Block.Water);
 | 
						|
				index1++; index2++;
 | 
						|
			}
 | 
						|
			
 | 
						|
			index1 = (waterY * length + 0) * width + 0;
 | 
						|
			index2 = (waterY * length + 0) * width + (width - 1);
 | 
						|
			for (int z = 0; z < length; z++) {
 | 
						|
				CurrentProgress = 0.5f + ((float)z / length) * 0.5f;
 | 
						|
				FloodFill(index1, Block.Water);
 | 
						|
				FloodFill(index2, Block.Water);
 | 
						|
				index1 += width; index2 += width;
 | 
						|
			}
 | 
						|
		}
 | 
						|
		
 | 
						|
		void FloodFillWater() {
 | 
						|
			int numSources = width * length / 800;
 | 
						|
			CurrentState = "Flooding water";
 | 
						|
			
 | 
						|
			for (int i = 0; i < numSources; i++) {
 | 
						|
				CurrentProgress = (float)i / numSources;
 | 
						|
				int x = rnd.Next(width), z = rnd.Next(length);
 | 
						|
				int y = waterLevel - rnd.Next(1, 3);
 | 
						|
				FloodFill((y * length + z) * width + x, Block.Water);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		
 | 
						|
		void FloodFillLava() {
 | 
						|
			int numSources = width * length / 20000;
 | 
						|
			CurrentState = "Flooding lava";
 | 
						|
			
 | 
						|
			for (int i = 0; i < numSources; i++) {
 | 
						|
				CurrentProgress = (float)i / numSources;
 | 
						|
				int x = rnd.Next(width), z = rnd.Next(length);
 | 
						|
				int y = (int)((waterLevel - 3) * rnd.NextFloat() * rnd.NextFloat());
 | 
						|
				FloodFill((y * length + z) * width + x, Block.Lava);
 | 
						|
			}
 | 
						|
		}
 | 
						|
		
 | 
						|
		void CreateSurfaceLayer() {
 | 
						|
			Noise n1 = new OctaveNoise(8, rnd), n2 = new OctaveNoise(8, rnd);
 | 
						|
			CurrentState = "Creating surface";
 | 
						|
			// TODO: update heightmap
 | 
						|
			
 | 
						|
			int hMapIndex = 0;
 | 
						|
			for (int z = 0; z < length; z++) {
 | 
						|
				CurrentProgress = (float)z / length;
 | 
						|
				for (int x = 0; x < width; x++) {
 | 
						|
					bool sand = n1.Compute(x, z) > 8;
 | 
						|
					bool gravel = n2.Compute(x, z) > 12;
 | 
						|
					int y = heightmap[hMapIndex++];
 | 
						|
					if (y >= height) continue;
 | 
						|
					
 | 
						|
					int index = (y * length + z) * width + x;
 | 
						|
					byte blockAbove = y >= (height - 1) ? Block.Air : blocks[index + oneY];
 | 
						|
					if (blockAbove == Block.Water && gravel) {
 | 
						|
						blocks[index] = Block.Gravel;
 | 
						|
					} else if (blockAbove == Block.Air) {
 | 
						|
						blocks[index] = (y <= waterLevel && sand) ? Block.Sand : Block.Grass;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		
 | 
						|
		void PlantFlowers() {
 | 
						|
			int numPatches = width * length / 3000;
 | 
						|
			CurrentState = "Planting flowers";
 | 
						|
			
 | 
						|
			for (int i = 0; i < numPatches; i++) {
 | 
						|
				CurrentProgress = (float)i / numPatches;
 | 
						|
				byte type = (byte)(Block.Dandelion + rnd.Next(2));
 | 
						|
				int patchX = rnd.Next(width), patchZ = rnd.Next(length);
 | 
						|
				for (int j = 0; j < 10; j++) {
 | 
						|
					int flowerX = patchX, flowerZ = patchZ;
 | 
						|
					for (int k = 0; k < 5; k++) {
 | 
						|
						flowerX += rnd.Next(6) - rnd.Next(6);
 | 
						|
						flowerZ += rnd.Next(6) - rnd.Next(6);
 | 
						|
						if (flowerX < 0 || flowerZ < 0 || flowerX >= width || flowerZ >= length)
 | 
						|
							continue;
 | 
						|
						
 | 
						|
						int flowerY = heightmap[flowerZ * width + flowerX] + 1;
 | 
						|
						int index = (flowerY * length + flowerZ) * width + flowerX;
 | 
						|
						if (blocks[index] == Block.Air && blocks[index - oneY] == Block.Grass)
 | 
						|
							blocks[index] = type;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		
 | 
						|
		void PlantMushrooms() {
 | 
						|
			int numPatches = width * length / 2000;
 | 
						|
			CurrentState = "Planting mushrooms";
 | 
						|
			
 | 
						|
			for (int i = 0; i < numPatches; i++) {
 | 
						|
				CurrentProgress = (float)i / numPatches;
 | 
						|
				byte type = (byte)(Block.BrownMushroom + rnd.Next(2));
 | 
						|
				int patchX = rnd.Next(width);
 | 
						|
				int patchY = rnd.Next(height);
 | 
						|
				int patchZ = rnd.Next(length);
 | 
						|
				
 | 
						|
				for (int j = 0; j < 20; j++) {
 | 
						|
					int mushX = patchX, mushY = patchY, mushZ = patchZ;
 | 
						|
					for (int k = 0; k < 5; k++) {
 | 
						|
						mushX += rnd.Next(6) - rnd.Next(6);
 | 
						|
						mushZ += rnd.Next(6) - rnd.Next(6);
 | 
						|
						if (mushX < 0 || mushZ < 0 || mushX >= width || mushZ >= length)
 | 
						|
							continue;
 | 
						|
						int solidHeight = heightmap[mushZ * width + mushX];
 | 
						|
						if (mushY >= (solidHeight - 1))
 | 
						|
							continue;
 | 
						|
						
 | 
						|
						int index = (mushY * length + mushZ) * width + mushX;
 | 
						|
						if (blocks[index] == Block.Air && blocks[index - oneY] == Block.Stone)
 | 
						|
							blocks[index] = type;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		
 | 
						|
		void PlantTrees() {
 | 
						|
			int numPatches = width * length / 4000;
 | 
						|
			CurrentState = "Planting tress";
 | 
						|
			
 | 
						|
			for (int i = 0; i < numPatches; i++) {
 | 
						|
				CurrentProgress = (float)i / numPatches;
 | 
						|
				int patchX = rnd.Next(width), patchZ = rnd.Next(length);
 | 
						|
				
 | 
						|
				for (int j = 0; j < 20; j++) {
 | 
						|
					int treeX = patchX, treeZ = patchZ;
 | 
						|
					for (int k = 0; k < 20; k++) {
 | 
						|
						treeX += rnd.Next(6) - rnd.Next(6);
 | 
						|
						treeZ += rnd.Next(6) - rnd.Next(6);
 | 
						|
						if (treeX < 0 || treeZ < 0 || treeX >= width ||
 | 
						|
						   treeZ >= length || rnd.NextFloat() >= 0.25)
 | 
						|
							continue;
 | 
						|
						
 | 
						|
						int treeY = heightmap[treeZ * width + treeX] + 1;
 | 
						|
						int treeHeight = 5 + rnd.Next(3);
 | 
						|
						if (CanGrowTree(treeX, treeY, treeZ, treeHeight)) {
 | 
						|
							GrowTree(treeX, treeY, treeZ, treeHeight);
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		
 | 
						|
		bool CanGrowTree(int treeX, int treeY, int treeZ, int treeHeight) {
 | 
						|
			// check tree base
 | 
						|
			int baseHeight = treeHeight - 4;
 | 
						|
			for (int y = treeY; y < treeY + baseHeight; y++)
 | 
						|
				for (int z = treeZ - 1; z <= treeZ + 1; z++)
 | 
						|
					for (int x = treeX - 1; x <= treeX + 1; x++)
 | 
						|
			{
 | 
						|
				if (x < 0 || y < 0 || z < 0 || x >= width || y >= height || z >= length)
 | 
						|
					return false;
 | 
						|
				int index = (y * length + z) * width + x;
 | 
						|
				if (blocks[index] != 0) return false;
 | 
						|
			}
 | 
						|
			
 | 
						|
			// and also check canopy
 | 
						|
			for (int y = treeY + baseHeight; y < treeY + treeHeight; y++)
 | 
						|
				for (int z = treeZ - 2; z <= treeZ + 2; z++)
 | 
						|
					for (int x = treeX - 2; x <= treeX + 2; x++)
 | 
						|
			{
 | 
						|
				if (x < 0 || y < 0 || z < 0 || x >= width || y >= height || z >= length)
 | 
						|
					return false;
 | 
						|
				int index = (y * length + z) * width + x;
 | 
						|
				if (blocks[index] != 0) return false;
 | 
						|
			}
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
		
 | 
						|
		void GrowTree(int treeX, int treeY, int treeZ, int height) {
 | 
						|
			int baseHeight = height - 4;
 | 
						|
			int index = 0;
 | 
						|
			
 | 
						|
			// leaves bottom layer
 | 
						|
			for (int y = treeY + baseHeight; y < treeY + baseHeight + 2; y++)
 | 
						|
				for (int zz = -2; zz <= 2; zz++)
 | 
						|
					for (int xx = -2; xx <= 2; xx++)
 | 
						|
			{
 | 
						|
				int x = xx + treeX, z = zz + treeZ;
 | 
						|
				index = (y * length + z) * width + x;
 | 
						|
				
 | 
						|
				if (Math.Abs(xx) == 2 && Math.Abs(zz) == 2) {
 | 
						|
					if (rnd.NextFloat() >= 0.5)
 | 
						|
						blocks[index] = Block.Leaves;
 | 
						|
				} else {
 | 
						|
					blocks[index] = Block.Leaves;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			
 | 
						|
			// leaves top layer
 | 
						|
			int bottomY = treeY + baseHeight + 2;
 | 
						|
			for (int y = treeY + baseHeight + 2; y < treeY + height; y++)
 | 
						|
				for (int zz = -1; zz <= 1; zz++)
 | 
						|
					for (int xx = -1; xx <= 1; xx++)
 | 
						|
			{
 | 
						|
				int x = xx + treeX, z = zz + treeZ;
 | 
						|
				index = (y * length + z) * width + x;
 | 
						|
 | 
						|
				if (xx == 0 || zz == 0) {
 | 
						|
					blocks[index] = Block.Leaves;
 | 
						|
				} else if (y == bottomY && rnd.NextFloat() >= 0.5) {
 | 
						|
					blocks[index] = Block.Leaves;
 | 
						|
				}
 | 
						|
			}
 | 
						|
			
 | 
						|
			// then place trunk
 | 
						|
			index = (treeY * length + treeZ) * width + treeX;
 | 
						|
			for (int y = 0; y < height - 1; y++) {
 | 
						|
				blocks[index] = Block.Log;
 | 
						|
				index += oneY;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
} |