diff --git a/src/js/net/minecraft/client/entity/PlayerEntity.js b/src/js/net/minecraft/client/entity/PlayerEntity.js index 045ba11..644f39c 100644 --- a/src/js/net/minecraft/client/entity/PlayerEntity.js +++ b/src/js/net/minecraft/client/entity/PlayerEntity.js @@ -5,6 +5,7 @@ import Block from "../world/block/Block.js"; import MathHelper from "../../util/MathHelper.js"; import Keyboard from "../../util/Keyboard.js"; import Vector3 from "../../util/Vector3.js"; +import {BlockRegistry} from "../world/block/BlockRegistry.js"; export default class PlayerEntity extends EntityLiving { @@ -161,7 +162,7 @@ export default class PlayerEntity extends EntityLiving { } isInWater() { - return this.world.getBlockAt(this.getBlockPosX(), this.getBlockPosY(), this.getBlockPosZ()) === Block.WATER.getId(); + return this.world.getBlockAt(this.getBlockPosX(), this.getBlockPosY(), this.getBlockPosZ()) === BlockRegistry.WATER.getId(); } isHeadInWater() { @@ -170,7 +171,7 @@ export default class PlayerEntity extends EntityLiving { Math.floor(cameraPosition.x), Math.floor(cameraPosition.y + 0.12), Math.floor(cameraPosition.z) - ) === Block.WATER.getId() + ) === BlockRegistry.WATER.getId() } jump() { diff --git a/src/js/net/minecraft/client/render/BlockRenderer.js b/src/js/net/minecraft/client/render/BlockRenderer.js index f343c08..c3edea5 100644 --- a/src/js/net/minecraft/client/render/BlockRenderer.js +++ b/src/js/net/minecraft/client/render/BlockRenderer.js @@ -68,6 +68,9 @@ export default class BlockRenderer { this.tessellator.setColor(color, color, color); } + // Set opacity of block + this.tessellator.setAlpha(1 - block.getTransparency()); + // Add face to tessellator this.addFace(world, face, ambientOcclusion, minX, minY, minZ, maxX, maxY, maxZ, minU, minV, maxU, maxV); } @@ -129,7 +132,7 @@ export default class BlockRenderer { let color = brightness * face.getShading(); // Set color with shading - this.tessellator.setColor(color, color, color); + this.tessellator.setColorRGB(color, color, color); } getAverageLightLevelAt(world, x, y, z) { diff --git a/src/js/net/minecraft/client/render/Tessellator.js b/src/js/net/minecraft/client/render/Tessellator.js index 39724a1..f2b1997 100644 --- a/src/js/net/minecraft/client/render/Tessellator.js +++ b/src/js/net/minecraft/client/render/Tessellator.js @@ -25,10 +25,18 @@ export default class Tessellator { this.colors = []; } - setColor(red, green, blue, alpha = 1) { + setColorRGB(red, green, blue) { this.red = red; this.green = green; this.blue = blue; + } + + setColor(red, green, blue, alpha = 1) { + this.setColorRGB(red, green, blue); + this.setAlpha(alpha); + } + + setAlpha(alpha) { this.alpha = alpha; } diff --git a/src/js/net/minecraft/client/render/WorldRenderer.js b/src/js/net/minecraft/client/render/WorldRenderer.js index 638c2ea..f090c14 100644 --- a/src/js/net/minecraft/client/render/WorldRenderer.js +++ b/src/js/net/minecraft/client/render/WorldRenderer.js @@ -540,8 +540,8 @@ export default class WorldRenderer { let renderDistance = WorldRenderer.RENDER_DISTANCE; // Load chunks - for (let x = -renderDistance; x <= renderDistance; x++) { - for (let z = -renderDistance; z <= renderDistance; z++) { + for (let x = -renderDistance + 1; x < renderDistance; x++) { + for (let z = -renderDistance + 1; z < renderDistance; z++) { world.getChunkAt(cameraChunkX + x, cameraChunkZ + z); } } @@ -580,6 +580,16 @@ export default class WorldRenderer { } else { // Hide chunk chunk.group.visible = false; + + // Unload chunk + if (chunk.loaded) { + chunk.unload(); + + // TODO Implement chunk unloading + //let index = chunk.x + (chunk.z << 16); + //world.chunks.delete(index); + //world.group.add(chunk.group); + } } } diff --git a/src/js/net/minecraft/client/world/Chunk.js b/src/js/net/minecraft/client/world/Chunk.js index c22be53..4efd1c0 100644 --- a/src/js/net/minecraft/client/world/Chunk.js +++ b/src/js/net/minecraft/client/world/Chunk.js @@ -16,6 +16,7 @@ export default class Chunk { this.group.chunkZ = z; this.loaded = false; + this.isTerrainPopulated = false; // Initialize sections this.sections = []; @@ -75,7 +76,7 @@ export default class Chunk { let typeId = section.getBlockAt(x, y & 15, z); let block = Block.getById(typeId); - let opacity = block.getOpacity(); + let opacity = typeId === 0 ? 0 : block.getOpacity(); let blockLight = typeId === 0 ? 0 : block.getLightValue(); if (opacity === 0) { @@ -322,6 +323,10 @@ export default class Chunk { return this.loaded; } + unload() { + this.loaded = false; + } + setModifiedAllSections() { for (let y = 0; y < this.sections.length; y++) { this.sections[y].isModified = true; diff --git a/src/js/net/minecraft/client/world/ChunkSection.js b/src/js/net/minecraft/client/world/ChunkSection.js index cca9b12..13b40c0 100644 --- a/src/js/net/minecraft/client/world/ChunkSection.js +++ b/src/js/net/minecraft/client/world/ChunkSection.js @@ -38,7 +38,7 @@ export default class ChunkSection { this.blocks[index] = 0; this.blocksData[index] = 0; this.blockLight[index] = 0; - this.skyLight[index] = 0; + this.skyLight[index] = 14; } } } diff --git a/src/js/net/minecraft/client/world/World.js b/src/js/net/minecraft/client/world/World.js index f9ea4c6..4222e13 100644 --- a/src/js/net/minecraft/client/world/World.js +++ b/src/js/net/minecraft/client/world/World.js @@ -1,14 +1,13 @@ import ChunkSection from "./ChunkSection.js"; import WorldGenerator from "./generator/WorldGenerator.js"; -import Chunk from "./Chunk.js"; import MathHelper from "../../util/MathHelper.js"; import BoundingBox from "../../util/BoundingBox.js"; -import MetadataChunkBlock from "../../util/MetadataChunkBlock.js"; import EnumSkyBlock from "../../util/EnumSkyBlock.js"; import Block from "./block/Block.js"; import EnumBlockFace from "../../util/EnumBlockFace.js"; import Vector3 from "../../util/Vector3.js"; import Vector4 from "../../util/Vector4.js"; +import MetadataChunkBlock from "../../util/MetadataChunkBlock.js"; export default class World { @@ -28,7 +27,8 @@ export default class World { this.time = 0; // Load world - this.generator = new WorldGenerator(this, Date.now() % 100000); + this.seed = Date.now() % 100000; + this.generator = new WorldGenerator(this, this.seed); // Update lights async let scope = this; @@ -70,24 +70,41 @@ export default class World { let index = x + (z << 16); let chunk = this.chunks.get(index); if (typeof chunk === 'undefined') { - chunk = new Chunk(this, x, z); - // Generate new chunk - this.generator.generateChunk(chunk); + chunk = this.generator.newChunk(this, x, z); - // Init - chunk.generateSkylightMap(); - chunk.generateBlockLightMap(); - - // Register + // Register and mark as loaded chunk.loaded = true; this.chunks.set(index, chunk); + + // Populate the chunk + if (!chunk.isTerrainPopulated && this.chunkExists(x + 1, z + 1) && this.chunkExists(x, z + 1) && this.chunkExists(x + 1, z)) { + this.populate(x, z); + } + if (this.chunkExists(x - 1, z) && !this.getChunkAt(x - 1, z).isTerrainPopulated && this.chunkExists(x - 1, z + 1) && this.chunkExists(x, z + 1) && this.chunkExists(x - 1, z)) { + this.populate(x - 1, z); + } + if (this.chunkExists(x, z - 1) && !this.getChunkAt(x, z - 1).isTerrainPopulated && this.chunkExists(x + 1, z - 1) && this.chunkExists(x, z - 1) && this.chunkExists(x + 1, z)) { + this.populate(x, z - 1); + } + if (this.chunkExists(x - 1, z - 1) && !this.getChunkAt(x - 1, z - 1).isTerrainPopulated && this.chunkExists(x - 1, z - 1) && this.chunkExists(x, z - 1) && this.chunkExists(x - 1, z)) { + this.populate(x - 1, z - 1); + } + + // Register in three.js this.group.add(chunk.group); + } + return chunk; + } + + populate(x, z) { + let chunk = this.getChunkAt(x, z); + if (!chunk.isTerrainPopulated) { + chunk.isTerrainPopulated = true; // Populate chunk this.generator.populateChunk(chunk.x, chunk.z); } - return chunk; } getChunkAtBlock(x, y, z) { @@ -161,13 +178,18 @@ export default class World { } } + let centerChunk = this.getChunkAt(centerX >> 4, centerZ >> 4); + if (!centerChunk.loaded) { + return; + } + // Add light update region to queue - if (this.lightUpdateQueue.length < 10000) { + if (this.lightUpdateQueue.length < 9999) { this.lightUpdateQueue.push(new MetadataChunkBlock(sourceType, x1, y1, z1, x2, y2, z2)); } // Max light updates in queue - if (this.lightUpdateQueue.length > 100000) { + if (this.lightUpdateQueue.length > 10000) { this.lightUpdateQueue = []; } } diff --git a/src/js/net/minecraft/client/world/block/Block.js b/src/js/net/minecraft/client/world/block/Block.js index f0bc39c..4205206 100644 --- a/src/js/net/minecraft/client/world/block/Block.js +++ b/src/js/net/minecraft/client/world/block/Block.js @@ -35,8 +35,12 @@ export default class Block { return this.textureSlotId; } + getTransparency() { + return 0.0; + } + isTransparent() { - return this.getOpacity() < 1.0; + return this.getTransparency() > 0.0; } shouldRenderFace(world, x, y, z, face) { diff --git a/src/js/net/minecraft/client/world/block/BlockRegistry.js b/src/js/net/minecraft/client/world/block/BlockRegistry.js index 6cd4089..7617768 100644 --- a/src/js/net/minecraft/client/world/block/BlockRegistry.js +++ b/src/js/net/minecraft/client/world/block/BlockRegistry.js @@ -22,14 +22,14 @@ export class BlockRegistry { Block.sounds.sand = new Sound("sand", 1.0); // Blocks - Block.STONE = new BlockStone(1, 0); - Block.GRASS = new BlockGrass(2, 1); - Block.DIRT = new BlockDirt(3, 2); - Block.WOOD = new BlockWood(5, 10); - Block.LOG = new BlockLog(17, 4); - Block.LEAVE = new BlockLeave(18, 6); - Block.WATER = new BlockWater(9, 7); - Block.SAND = new BlockSand(12, 8) - Block.TORCH = new BlockTorch(50, 9) + BlockRegistry.STONE = new BlockStone(1, 0); + BlockRegistry.GRASS = new BlockGrass(2, 1); + BlockRegistry.DIRT = new BlockDirt(3, 2); + BlockRegistry.WOOD = new BlockWood(5, 10); + BlockRegistry.LOG = new BlockLog(17, 4); + BlockRegistry.LEAVE = new BlockLeave(18, 6); + BlockRegistry.WATER = new BlockWater(9, 7); + BlockRegistry.SAND = new BlockSand(12, 8) + BlockRegistry.TORCH = new BlockTorch(50, 9) } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/block/type/BlockWater.js b/src/js/net/minecraft/client/world/block/type/BlockWater.js index a7f27b9..57dabf9 100644 --- a/src/js/net/minecraft/client/world/block/type/BlockWater.js +++ b/src/js/net/minecraft/client/world/block/type/BlockWater.js @@ -10,6 +10,10 @@ export default class BlockWater extends Block { return 0.01; } + getTransparency() { + return 0.2; + } + isSolid() { return false; } diff --git a/src/js/net/minecraft/client/world/generator/Generator.js b/src/js/net/minecraft/client/world/generator/Generator.js new file mode 100644 index 0000000..305646c --- /dev/null +++ b/src/js/net/minecraft/client/world/generator/Generator.js @@ -0,0 +1,18 @@ +import Random from "../../../util/Random.js"; + +export default class Generator { + + constructor(world, seed) { + this.world = world; + this.seed = seed; + this.random = new Random(seed); + } + + generateInChunk(chunkX, chunkZ, primer) { + + } + + generateAtBlock(x, y, z, primer) { + + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/generator/Primer.js b/src/js/net/minecraft/client/world/generator/Primer.js new file mode 100644 index 0000000..266c6e2 --- /dev/null +++ b/src/js/net/minecraft/client/world/generator/Primer.js @@ -0,0 +1,18 @@ +export default class Primer { + + constructor(chunk) { + this.chunk = chunk; + } + + get(x, y, z) { + return this.chunk.getBlockAt(x, y, z); + } + + set(x, y, z, typeId) { + this.chunk.setBlockAt(x, y, z, typeId); + } + + setByIndex(index, typeId) { + this.set(index >> 12 & 15, index >> 8 & 15, index & 15, typeId); + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/generator/WorldGenerator.js b/src/js/net/minecraft/client/world/generator/WorldGenerator.js index e6891af..252263a 100644 --- a/src/js/net/minecraft/client/world/generator/WorldGenerator.js +++ b/src/js/net/minecraft/client/world/generator/WorldGenerator.js @@ -1,176 +1,389 @@ -import Random from "../../../util/Random.js"; -import NoiseGeneratorCombined from "./noise/NoiseGeneratorCombined.js"; import NoiseGeneratorOctaves from "./noise/NoiseGeneratorOctaves.js"; +import Chunk from "../Chunk.js"; +import Primer from "./Primer.js"; +import CaveGenerator from "./structure/CaveGenerator.js"; +import {BlockRegistry} from "../block/BlockRegistry.js"; +import TreeGenerator from "./structure/TreeGenerator.js"; +import BigTreeGenerator from "./structure/BigTreeGenerator.js"; +import Generator from "./Generator.js"; import ChunkSection from "../ChunkSection.js"; -import Block from "../block/Block.js"; -export default class WorldGenerator { +export default class WorldGenerator extends Generator { constructor(world, seed) { - this.world = world; - this.random = new Random(seed); + super(world, seed); - this.waterLevel = 64; + this.seaLevel = 64; - // Create noise for the ground height - this.groundHeightNoise = new NoiseGeneratorOctaves(this.random, 8); - this.hillNoise = new NoiseGeneratorCombined(new NoiseGeneratorOctaves(this.random, 4), - new NoiseGeneratorCombined(new NoiseGeneratorOctaves(this.random, 4), - new NoiseGeneratorOctaves(this.random, 4))); + this.caveGenerator = new CaveGenerator(world, seed); - // Water noise - this.sandInWaterNoise = new NoiseGeneratorOctaves(this.random, 8); + this.terrainGenerator4 = new NoiseGeneratorOctaves(this.random, 16); + this.terrainGenerator5 = new NoiseGeneratorOctaves(this.random, 16); + this.terrainGenerator3 = new NoiseGeneratorOctaves(this.random, 8); + this.terrainGenerator1 = new NoiseGeneratorOctaves(this.random, 10); + this.terrainGenerator2 = new NoiseGeneratorOctaves(this.random, 16); - // Hole in hills and islands - this.holeNoise = new NoiseGeneratorOctaves(this.random, 3); - this.islandNoise = new NoiseGeneratorOctaves(this.random, 3); + this.natureGenerator1 = new NoiseGeneratorOctaves(this.random, 4); + this.natureGenerator2 = new NoiseGeneratorOctaves(this.random, 4); - // Caves - this.caveNoise = new NoiseGeneratorOctaves(this.random, 8); - - // Population - this.forestNoise = new NoiseGeneratorOctaves(this.random, 8); + this.populationNoiseGenerator = new NoiseGeneratorOctaves(this.random, 8); } - generateChunk(chunk) { - // For each block in the chunk - for (let relX = 0; relX < ChunkSection.SIZE; relX++) { - for (let relZ = 0; relZ < ChunkSection.SIZE; relZ++) { + newChunk(world, chunkX, chunkZ) { + this.random.setSeed(chunkX * 0x4f9939f508 + chunkZ * 0x1ef1565bd5); - // Absolute position of the block - let x = chunk.x * ChunkSection.SIZE + relX + 10000; // TODO fix this 10000 offset - let z = chunk.z * ChunkSection.SIZE + relZ + 10000; + let chunk = new Chunk(world, chunkX, chunkZ); + let primer = new Primer(chunk); - // Extract height value of the noise - let heightValue = this.groundHeightNoise.perlin(x, z); - let hillValue = Math.max(0, this.hillNoise.perlin(x / 18, z / 18) * 6); + this.generateInChunk(chunkX, chunkZ, primer); - // Calculate final height for this position - let groundHeightY = Math.floor(heightValue / 10 + this.waterLevel + hillValue); + // Init skylight + chunk.generateSkylightMap(); + chunk.generateBlockLightMap(); - if (groundHeightY < this.waterLevel) { - // Generate water - for (let y = 0; y <= this.waterLevel; y++) { - // Use noise to place sand in water - let sandInWater = this.sandInWaterNoise.perlin(x, z) < 0; - let block = y > groundHeightY ? Block.WATER : groundHeightY - y < 3 && sandInWater ? Block.SAND : Block.STONE; + return chunk; + } - // Send water, sand and stone - chunk.setBlockAt(x & 15, y, z & 15, block.getId()); - } - } else { - // Generate height, the highest block is grass - for (let y = 0; y <= groundHeightY; y++) { - // Use the height map to determine the start of the water by shifting it - let isBeach = heightValue < 5 && y < this.waterLevel + 2; - let block = y === groundHeightY ? isBeach ? Block.SAND : Block.GRASS : groundHeightY - y < 3 ? Block.DIRT : Block.STONE; + generateInChunk(chunkX, chunkZ, primer) { + this.generateTerrain(chunkX, chunkZ, primer); + this.naturalize(chunkX, chunkZ, primer); - // Set sand, grass, dirt and stone - chunk.setBlockAt(x & 15, y, z & 15, block.getId()); - } - } - - /* - int holeY = (int) (this.holeNouse.perlin(-x / 20F, -z / 20F) * 3F + this.waterLevel + 10); - int holeHeight = (int) this.holeNouse.perlin(x / 4F, -z / 4F); - if (holeHeight > 0) { - for (int y = holeY - holeHeight; y <= holeY + holeHeight; y++) { - chunk.setBlockAt(x & 15, y, z & 15, 1); - } - } - */ - - // Random holes in hills - let holePositionY = Math.floor(this.holeNoise.perlin(-x / 20, -z / 20) * 3 + this.waterLevel + 10); - let holeHeight = Math.floor(this.holeNoise.perlin(x / 4, -z / 4)); - - if (holeHeight > 0) { - for (let y = holePositionY - holeHeight; y <= holePositionY + holeHeight; y++) { - if (y > this.waterLevel) { - chunk.setBlockAt(x & 15, y, z & 15, 0); - } - } - } - - // Floating islands - let islandPositionY = Math.floor(this.islandNoise.perlin(-x / 10, -z / 10) * 3 + this.waterLevel + 10); - let islandHeight = Math.floor(this.islandNoise.perlin(x / 4, -z / 4) * 4); - let islandRarity = Math.floor(this.islandNoise.perlin(x / 40, z / 40) * 4) - 10; - - if (islandHeight > 0 && islandRarity > 0) { - for (let y = islandPositionY - islandHeight; y <= islandPositionY + islandHeight; y++) { - let block = y === islandPositionY + islandHeight ? Block.GRASS : (islandPositionY + islandHeight) - y < 2 ? Block.DIRT : Block.STONE; - chunk.setBlockAt(x & 15, y, z & 15, block.getId()); - } - } - - // Caves - } - } + this.caveGenerator.generateInChunk(chunkX, chunkZ, primer); } populateChunk(chunkX, chunkZ) { - for (let index = 0; index < 10; index++) { - let x = this.random.nextInt(ChunkSection.SIZE); - let z = this.random.nextInt(ChunkSection.SIZE); + // Reset seed + this.random.setSeed(this.seed); - // Absolute position of the block - let absoluteX = chunkX * ChunkSection.SIZE + x; - let absoluteZ = chunkZ * ChunkSection.SIZE + z; + // Set seed for chunk + let seedX = (this.random.nextInt() / 2) * 2 + 1; + let seedZ = (this.random.nextInt() / 2) * 2 + 1; + this.random.setSeed(chunkX * seedX + chunkZ * seedZ ^ this.seed); - // Use noise for a forest pattern - let perlin = this.forestNoise.perlin(absoluteX * 10, absoluteZ * 10); - if (perlin > 0 && this.random.nextInt(2) === 0) { + // Access noise data for population + let absoluteX = chunkX * 16; + let absoluteY = chunkZ * 16; + let amount = Math.floor((this.populationNoiseGenerator.perlin(absoluteX * 0.5, absoluteY * 0.5) / 8 + this.random.nextFloat() * 4 + 4) / 3); + if (amount < 0) { + amount = 0; + } + if (this.random.nextInt(10) === 0) { + amount++; + } - // Get the highest block at this position - let highestY = this.world.getHighestBlockAt(absoluteX, absoluteZ); + // Tree generator + let bigTree = this.random.nextInt(10) === 0; + let treeSeed = this.random.seed; + let treeGenerator = bigTree ? new BigTreeGenerator(this.world, treeSeed) : new TreeGenerator(this.world, treeSeed); - // Don't place a tree if there is no grass - if (this.world.getBlockAt(absoluteX, highestY, absoluteZ) === Block.GRASS.getId() - && this.world.getBlockAt(absoluteX, highestY + 1, absoluteZ) === 0) { - let treeHeight = this.random.nextInt(2) + 5; + // Plant the trees in the chunk + for (let i = 0; i < amount; i++) { + let totalX = absoluteX + this.random.nextInt(16) + 8; + let totalZ = absoluteY + this.random.nextInt(16) + 8; + let totalY = this.world.getHeightAt(totalX, totalZ); - // Create tree log - for (let i = 0; i < treeHeight; i++) { - this.world.setBlockAt(absoluteX, highestY + i + 1, absoluteZ, Block.LOG.getId()); - } + // Generate tree at position + treeGenerator.generateAtBlock(totalX, totalY, totalZ); + } + } - // Create big leave ring - for (let tx = -2; tx <= 2; tx++) { - for (let ty = 0; ty < 2; ty++) { - for (let tz = -2; tz <= 2; tz++) { - let isCorner = Math.abs(tx) === 2 && Math.abs(tz) === 2; - if (isCorner && this.random.nextBoolean()) { - continue; + + generateTerrain(chunkX, chunkZ, primer) { + let range = 4; + let sizeX = range + 1; + let sizeZ = 17; + let factor = 1 / 4; + + // Generate terrain noise + let noise = this.generateTerrainNoise(chunkX * range, 0, chunkZ * range, sizeX, sizeZ, sizeX); + + let isSnowBiome = false; + + for (let indexX = 0; indexX < range; indexX++) { + for (let indexZ = 0; indexZ < range; indexZ++) { + for (let indexY = 0; indexY < 16; indexY++) { + let sec = 1 / 8; + + // Terrain base noise values + let noise1 = noise[(indexX * sizeX + indexZ) * sizeZ + indexY]; + let noise2 = noise[(indexX * sizeX + (indexZ + 1)) * sizeZ + indexY]; + + let noise3 = noise[((indexX + 1) * sizeX + indexZ) * sizeZ + indexY]; + let noise4 = noise[((indexX + 1) * sizeX + (indexZ + 1)) * sizeZ + indexY]; + + // Mutation noise values + let mut1 = (noise[(indexX * sizeX + indexZ) * sizeZ + (indexY + 1)] - noise1) * sec; + let mut2 = (noise[(indexX * sizeX + (indexZ + 1)) * sizeZ + (indexY + 1)] - noise2) * sec; + let mut3 = (noise[((indexX + 1) * sizeX + indexZ) * sizeZ + (indexY + 1)] - noise3) * sec; + let mut4 = (noise[((indexX + 1) * sizeX + (indexZ + 1)) * sizeZ + (indexY + 1)] - noise4) * sec; + + // For each y level of the section + for (let y = 0; y < 8; y++) { + // Take two noise values for the stone to rise + let stoneNoiseAtY1 = noise1; + let stoneNoiseAtY2 = noise2; + + // Calculate difference of the selected noise values and two other noise values + let diffNoiseY1 = (noise3 - noise1) * factor; + let diffNoiseY2 = (noise4 - noise2) * factor; + + // For each x and z coordinate of the section + for (let x = 0; x < 4; x++) { + let stoneNoise = stoneNoiseAtY1; + let diffNoiseX = (stoneNoiseAtY2 - stoneNoiseAtY1) * factor; + + for (let z = 0; z < 4; z++) { + let typeId = 0; + + // Set water if y level is below sea level + if (indexY * 8 + y < this.seaLevel) { + if (isSnowBiome && indexY * 8 + y >= this.seaLevel - 1) { + typeId = BlockRegistry.WATER.getId(); // TODO add ice block + } else { + typeId = BlockRegistry.WATER.getId(); + } } - // Place leave if there is no block yet - if (!this.world.isSolidBlockAt(absoluteX + tx, highestY + treeHeight + ty - 2, absoluteZ + tz)) { - this.world.setBlockAt(absoluteX + tx, highestY + treeHeight + ty - 2, absoluteZ + tz, Block.LEAVE.getId()); + // Let the terrain rise out of the water + if (stoneNoise > 0.0) { + typeId = BlockRegistry.STONE.getId(); } + + //Set target type id + primer.set(indexX * 4 + x, indexY * 8 + y, indexZ * 4 + z, typeId); + + // Increase noise by noise x difference + stoneNoise += diffNoiseX; } - } - } - // Create small leave ring on top - for (let tx = -1; tx <= 1; tx++) { - for (let ty = 0; ty < 2; ty++) { - for (let tz = -1; tz <= 1; tz++) { - let isCorner = Math.abs(tx) === 1 && Math.abs(tz) === 1; - if (isCorner && (ty === 1 || this.random.nextBoolean())) { - continue; - } - - // Place leave if there is no block yet - if (!this.world.isSolidBlockAt(absoluteX + tx, highestY + treeHeight + ty, absoluteZ + tz)) { - this.world.setBlockAt(absoluteX + tx, highestY + treeHeight + ty, absoluteZ + tz, Block.LEAVE.getId()); - } - } + // Increase noise by noise y differences + stoneNoiseAtY1 += diffNoiseY1; + stoneNoiseAtY2 += diffNoiseY2; } + + // Mutate noise values + noise1 += mut1; + noise2 += mut2; + noise3 += mut3; + noise4 += mut4; } } } } - } + + naturalize(chunkX, chunkZ, primer) { + let strength = 1 / 32; + let chunkSize = ChunkSection.SIZE; + + // Generate noise for nature painting + let natureNoise1 = this.natureGenerator1.generateNoiseOctaves( + chunkX * chunkSize, chunkZ * chunkSize, + 0.0, chunkSize, chunkSize, 1, + strength, strength, 1.0 + ); + let natureNoise2 = this.natureGenerator1.generateNoiseOctaves( + chunkZ * chunkSize, 109.0134, chunkX * chunkSize, + chunkSize, 1, chunkSize, + strength, 1.0, strength); + let natureNoise3 = this.natureGenerator2.generateNoiseOctaves( + chunkX * chunkSize, chunkZ * chunkSize, 0.0, + chunkSize, chunkSize, 1, + strength * 2, strength * 2, strength * 2 + ); + + // Paint entire chunk with nature blocks + for (let x = 0; x < 16; x++) { + for (let z = 0; z < 16; z++) { + // Pull noise values for patches + let sandPatchNoise = natureNoise1[x + z * 16] + this.random.nextFloat() * 0.2 > 0; + let gravelPatchNoise = natureNoise2[x + z * 16] + this.random.nextFloat() * 0.2 > 3; + let stonePatchNoise = (natureNoise3[x + z * 16] / 3 + 3 + this.random.nextFloat() * 0.25); + + let prevStonePatchNoise = -1; + + // Default layer type ids + let topLayerTypeId = BlockRegistry.GRASS.getId(); + let innerLayerTypeId = BlockRegistry.DIRT.getId(); + + // For the entire height of the chunk + for (let y = 127; y >= 0; y--) { + // Set bedrock on floor level + if (y <= (this.random.nextInt(6)) - 1) { + primer.set(x, y, z, BlockRegistry.STONE.getId()); // TODO add bedrock block + continue; + } + + // Get block type at current position + let typeIdAt = primer.get(x, y, z); + + // Ignore air block + if (typeIdAt === 0) { + prevStonePatchNoise = -1; + continue; + } + + // Check if it's a stone block + if (typeIdAt !== BlockRegistry.STONE.getId()) { + continue; + } + + // Check if previous iteration was an air block + if (prevStonePatchNoise === -1) { + if (stonePatchNoise <= 0) { + // Keep the stone + topLayerTypeId = 0; + innerLayerTypeId = BlockRegistry.STONE.getId(); + } else if (y >= this.seaLevel - 4 && y <= this.seaLevel + 1) { + // Fallback is grass and dirt + topLayerTypeId = BlockRegistry.GRASS.getId(); + innerLayerTypeId = BlockRegistry.DIRT.getId(); + + // Add gravel patches + if (gravelPatchNoise) { + topLayerTypeId = 0; + innerLayerTypeId = BlockRegistry.STONE.getId(); // TODO add gravel block + } + + // Add sand patches + if (sandPatchNoise) { + topLayerTypeId = BlockRegistry.SAND.getId(); + innerLayerTypeId = BlockRegistry.SAND.getId(); + } + } + + // Set water if it's below the sea level + if (y < this.seaLevel && topLayerTypeId === 0) { + topLayerTypeId = BlockRegistry.WATER.getId(); // TODO add water moving block + } + + // Set flag that we hit a block + prevStonePatchNoise = stonePatchNoise; + + // Set grass or dirt type depending on sea level height + if (y >= this.seaLevel - 1) { + primer.set(x, y, z, topLayerTypeId); + } else { + primer.set(x, y, z, innerLayerTypeId); + } + continue; + } + + // Set further inner layer blocks + if (prevStonePatchNoise > 0) { + prevStonePatchNoise--; + primer.set(x, y, z, innerLayerTypeId); + } + } + } + } + } + + generateTerrainNoise(noiseX, noiseY, noiseZ, width, height, depth) { + let strength = 684.412; + + // Generate terrain noise + let terrainNoise1 = this.terrainGenerator1.generateNoiseOctaves(noiseX, noiseY, noiseZ, width, 1, depth, 1.0, 0.0, 1.0); + let terrainNoise2 = this.terrainGenerator2.generateNoiseOctaves(noiseX, noiseY, noiseZ, width, 1, depth, 100, 0.0, 100); + let terrainNoise3 = this.terrainGenerator3.generateNoiseOctaves(noiseX, noiseY, noiseZ, width, height, depth, strength / 80, strength / 160, strength / 80); + let terrainNoise4 = this.terrainGenerator4.generateNoiseOctaves(noiseX, noiseY, noiseZ, width, height, depth, strength, strength, strength); + let terrainNoise5 = this.terrainGenerator5.generateNoiseOctaves(noiseX, noiseY, noiseZ, width, height, depth, strength, strength, strength); + + // Output noise + let output = []; + + let index = 0; + let id = 0; + + // For each x, z coordinate + for (let x = 0; x < width; x++) { + for (let z = 0; z < depth; z++) { + let out1 = (terrainNoise1[id] + 256) / 512; + if (out1 > 1.0) { + out1 = 1.0; + } + + let maxY = 0.0; + let out2 = terrainNoise2[id] / 8000; + + if (out2 < 0.0) { + out2 = -out2; + } + + out2 = out2 * 3 - 3; + + if (out2 < 0.0) { + out2 /= 2; + + if (out2 < -1) { + out2 = -1; + } + + out2 /= 1.4; + out2 /= 2; + out1 = 0.0; + } else { + if (out2 > 1.0) { + out2 = 1.0; + } + out2 /= 6; + } + + out1 += 0.5; + out2 = (out2 * height) / 16; + id++; + + let h = height / 2 + out2 * 4; + + // Y loop + for (let y = 0; y < height; y++) { + let noise = 0; + let value = ((y - h) * 12) / out1; + + if (value < 0.0) { + value *= 4; + } + + let out4 = terrainNoise4[index] / 512; + let out5 = terrainNoise5[index] / 512; + let out3 = (terrainNoise3[index] / 10 + 1.0) / 2; + + if (out3 < 0.0) { + noise = out4; + } else if (out3 > 1.0) { + noise = out5; + } else { + noise = out4 + (out5 - out4) * out3; + } + + noise -= value; + + if (y > height - 4) { + let diff = (y - (height - 4)) / 3; + noise = noise * (1.0 - diff) + -10 * diff; + } + + if (y < maxY) { + let diff = (maxY - y) / 4; + + if (diff < 0.0) { + diff = 0.0; + } + + if (diff > 1.0) { + diff = 1.0; + } + noise = noise * (1.0 - diff) + -10 * diff; + } + + // Add noise to array and increase index + output[index] = noise; + index++; + } + } + } + + return output; + } + } \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorOctaves.js b/src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorOctaves.js index 0948459..5299590 100644 --- a/src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorOctaves.js +++ b/src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorOctaves.js @@ -24,4 +24,28 @@ export default class NoiseGeneratorOctaves extends NoiseGenerator { return total; } + generateNoiseOctaves(x1, y1, z1, x2, y2, z2, strengthX, strengthY, strengthZ) { + let length = x2 * y2 * z2; + let noise = []; + for (let i = 0; i < length; i++) { + noise[i] = 0; + } + + let frequency = 1.0; + for (let i = 0; i < this.octaves; i++) { + this.generatorCollection[i].combined( + noise, + x1, y1, z1, + x2, y2, z2, + strengthX * frequency, + strengthY * frequency, + strengthZ * frequency, + frequency + ); + frequency /= 2; + } + + return noise; + } + } \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorPerlin.js b/src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorPerlin.js index c7b6043..2b728b2 100644 --- a/src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorPerlin.js +++ b/src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorPerlin.js @@ -5,6 +5,10 @@ export default class NoiseGeneratorPerlin extends NoiseGenerator { constructor(random) { super(); + this.offsetX = random.nextFloat() * 256; + this.offsetY = random.nextFloat() * 256; + this.offsetZ = random.nextFloat() * 256; + this.permutations = []; for (let i = 0; i < 256; i++) { this.permutations[i] = i; @@ -46,42 +50,150 @@ export default class NoiseGeneratorPerlin extends NoiseGenerator { } perlin(x, z) { - let y; + return this.perlinXYZ(x, z, 0); + } - let xi = Math.floor(x) & 0xFF; - let zi = Math.floor(z) & 0xFF; - let yi = Math.floor(0.0) & 0xFF; + perlinXYZ(x, y, z) { + let shiftX = x + this.offsetX; + let shiftY = y + this.offsetY; + let shiftZ = z + this.offsetZ; - x -= Math.floor(x); - z -= Math.floor(z); - y = 0.0 - Math.floor(0.0); + let floorX = Math.floor(shiftX); + let floorY = Math.floor(shiftY); + let floorZ = Math.floor(shiftZ); - let u = this.fade(x); - let w = this.fade(z); - let v = this.fade(y); + if (shiftX < floorX) { + floorX--; + } + if (shiftY < floorY) { + floorY--; + } + if (shiftZ < floorZ) { + floorZ--; + } - let xzi = this.permutations[xi] + zi; - let xzyi = this.permutations[xzi] + yi; + let x1 = floorX & 0xff; + let y1 = floorY & 0xff; + let z1 = floorZ & 0xff; - xzi = this.permutations[xzi + 1] + yi; - xi = this.permutations[xi + 1] + zi; - zi = this.permutations[xi] + yi; - xi = this.permutations[xi + 1] + yi; + shiftX -= floorX; + shiftY -= floorY; + shiftZ -= floorZ; + + let u = this.fade(shiftX); + let w = this.fade(shiftY); + let v = this.fade(shiftZ); + + let xy = this.permutations[x1] + y1; + let xyz = this.permutations[xy] + z1; + + let xy1z = this.permutations[xy + 1] + z1; + let xi = this.permutations[x1 + 1] + y1; + let yi = this.permutations[xi] + z1; + let zi = this.permutations[xi + 1] + z1; return this.lerp(v, this.lerp(w, this.lerp(u, - this.grad(this.permutations[xzyi], x, z, y), - this.grad(this.permutations[zi], x - 1.0, z, y)), + this.grad(this.permutations[xyz], shiftX, shiftY, shiftZ), + this.grad(this.permutations[yi], shiftX - 1.0, shiftY, shiftZ)), this.lerp(u, - this.grad(this.permutations[xzi], x, z - 1.0, y), - this.grad(this.permutations[xi], x - 1.0, z - 1.0, y))), + this.grad(this.permutations[xy1z], shiftX, shiftY - 1.0, shiftZ), + this.grad(this.permutations[zi], shiftX - 1.0, shiftY - 1.0, shiftZ))), this.lerp(w, this.lerp(u, - this.grad(this.permutations[xzyi + 1], x, z, y - 1.0), - this.grad(this.permutations[zi + 1], x - 1.0, z, y - 1.0)), + this.grad(this.permutations[xyz + 1], shiftX, shiftY, shiftZ - 1.0), + this.grad(this.permutations[yi + 1], shiftX - 1.0, shiftY, shiftZ - 1.0)), this.lerp(u, - this.grad(this.permutations[xzi + 1], x, z - 1.0, y - 1.0), - this.grad(this.permutations[xi + 1], x - 1.0, z - 1.0, y - 1.0)))); + this.grad(this.permutations[xy1z + 1], shiftX, shiftY - 1.0, shiftZ - 1.0), + this.grad(this.permutations[zi + 1], shiftX - 1.0, shiftY - 1.0, shiftZ - 1.0)))); + } + + combined(noise, x1, y1, z1, x2, y2, z2, strengthX, strengthY, strengthZ, frequency) { + let index = 0; + let invertFrequency = 1.0 / frequency; + let prevY3 = -1; + + // Output values + let output1 = 0; + let output2 = 0; + let output3 = 0; + let output4 = 0; + + // X loop + for (let x = 0; x < x2; x++) { + let shiftX = (x1 + x) * strengthX + this.offsetX; + let floorX = Math.floor(shiftX); + + if (shiftX < floorX) { + floorX--; + } + + let x3 = floorX & 0xff; + shiftX -= floorX; + + // Z loop + let u = this.fade(shiftX); + for (let z = 0; z < z2; z++) { + let shiftZ = (z1 + z) * strengthZ + this.offsetZ; + let floorZ = Math.floor(shiftZ); + + if (shiftZ < floorZ) { + floorZ--; + } + + let z3 = floorZ & 0xff; + shiftZ -= floorZ; + + // Y loop + let w = this.fade(shiftZ); + for (let y = 0; y < y2; y++) { + let shiftY = (y1 + y) * strengthY + this.offsetY; + let floorY = Math.floor(shiftY); + + if (shiftY < floorY) { + floorY--; + } + + let y3 = floorY & 0xff; + shiftY -= floorY; + + let v = this.fade(shiftY); + + // Check if y changed + if (y === 0 || y3 !== prevY3) { + prevY3 = y3; + + let xy = this.permutations[x3] + y3; + let xyz = this.permutations[xy] + z3; + + let xy1z = this.permutations[xy + 1] + z3; + let xi = this.permutations[x3 + 1] + y3; + let yi = this.permutations[xi] + z3; + let zi = this.permutations[xi + 1] + z3; + + output1 = this.lerp(u, + this.grad(this.permutations[xyz], shiftX, shiftY, shiftZ), + this.grad(this.permutations[yi], shiftX - 1.0, shiftY, shiftZ)); + output2 = this.lerp(u, + this.grad(this.permutations[xy1z], shiftX, shiftY - 1.0, shiftZ), + this.grad(this.permutations[zi], shiftX - 1.0, shiftY - 1.0, shiftZ)); + output3 = this.lerp(u, + this.grad(this.permutations[xyz + 1], shiftX, shiftY, shiftZ - 1.0), + this.grad(this.permutations[yi + 1], shiftX - 1.0, shiftY, shiftZ - 1.0)); + output4 = this.lerp(u, + this.grad(this.permutations[xy1z + 1], shiftX, shiftY - 1.0, shiftZ - 1.0), + this.grad(this.permutations[zi + 1], shiftX - 1.0, shiftY - 1.0, shiftZ - 1.0)); + } + + let output5 = this.lerp(v, output1, output2); + let output6 = this.lerp(v, output3, output4); + + // Add final output to noise array + let output = this.lerp(w, output5, output6); + noise[index++] += output * invertFrequency; + } + } + } } } \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/generator/structure/BigTreeGenerator.js b/src/js/net/minecraft/client/world/generator/structure/BigTreeGenerator.js new file mode 100644 index 0000000..76efd12 --- /dev/null +++ b/src/js/net/minecraft/client/world/generator/structure/BigTreeGenerator.js @@ -0,0 +1,391 @@ +import Generator from "../Generator.js"; +import {BlockRegistry} from "../../block/BlockRegistry.js"; + +export default class BigTreeGenerator extends Generator { + + constructor(world, seed) { + super(world, seed); + + this.heightLimit = 0; + this.heightAttenuation = 0.617; + this.branchSlope = 0.381; + this.scaleWidth = 1.0; + this.leafDensity = 1.0; + this.trunkSize = 1; + this.heightLimitLimit = 12; + this.offsetY = 4; + + this.coords = [] + this.types = [2, 0, 0, 1, 2, 1] + this.nodes = []; + } + + generateAtBlock(x, y, z) { + let seed = this.random.nextInt(); + this.random.setSeed(seed); + + this.coords[0] = x; + this.coords[1] = y; + this.coords[2] = z; + + if (this.heightLimit === 0) { + this.heightLimit = 5 + this.random.nextInt(this.heightLimitLimit); + } + + if (!this.validTreeLocation()) { + return false; + } else { + this.generateLeafNodeList(); + this.generateLeafNodes(); + this.generateTrunk(); + this.generateLeafNodeBases(); + return true; + } + } + + validTreeLocation() { + let minCoords = [this.coords[0], this.coords[1], this.coords[2]]; + let maxCoords = [this.coords[0], (this.coords[1] + this.heightLimit) - 1, this.coords[2]]; + + let typeId = this.world.getBlockAt(this.coords[0], this.coords[1] - 1, this.coords[2]); + if (typeId !== 2 && typeId !== 3) { + return false; + } + + let blockLine = this.checkBlockLine(minCoords, maxCoords); + if (blockLine === -1) { + return true; + } + + if (blockLine < 6) { + return false; + } else { + this.heightLimit = blockLine; + return true; + } + } + + checkBlockLine(minCoords, maxCoords) { + let dimension = [0, 0, 0] + let index = 0; + + // Find target index for dimension + let targetIndex = 0; + for (; index < 3; index++) { + dimension[index] = maxCoords[index] - minCoords[index]; + if (Math.abs(dimension[index]) > Math.abs(dimension[targetIndex])) { + targetIndex = index; + } + } + + // Invalid dimension + if (dimension[targetIndex] === 0) { + return -1; + } + + // Get dimension index by target index + let widthIndex = this.types[targetIndex]; + let heightIndex = this.types[targetIndex + 3]; + + // Determine offset + let offset; + if (dimension[targetIndex] > 0) { + offset = 1; + } else { + offset = -1; + } + + // Get width and height + let width = dimension[widthIndex] / dimension[targetIndex]; + let height = dimension[heightIndex] / dimension[targetIndex]; + + let indexBlock = 0; + let breakIndex = dimension[targetIndex] + offset; + + do { + if (indexBlock === breakIndex) { + break; + } + + let coords = [0, 0, 0] + coords[targetIndex] = minCoords[targetIndex] + indexBlock; + coords[widthIndex] = Math.floor(minCoords[widthIndex] + indexBlock * width); + coords[heightIndex] = Math.floor(minCoords[heightIndex] + indexBlock * height); + + // Check for collision with objects except leaves + let typeId = this.world.getBlockAt(coords[0], coords[1], coords[2]); + if (typeId !== 0 && typeId !== BlockRegistry.LEAVE.getId()) { + break; + } + + indexBlock += offset; + } while (true); + + if (indexBlock === breakIndex) { + return -1; + } else { + return Math.abs(indexBlock); + } + } + + generateLeafNodeList() { + this.height = Math.floor(this.heightLimit * this.heightAttenuation); + if (this.height >= this.heightLimit) { + this.height = this.heightLimit - 1; + } + + let spheres = Math.floor(1.38 + Math.pow((this.leafDensity * this.heightLimit) / 13, 2)); + if (spheres < 1) { + spheres = 1; + } + + // Create 2D array with max possible nodes + let array = []; + for (let i = 0; i < spheres * this.heightLimit; i++) { + array[i] = []; + } + + let minY = (this.coords[1] + this.heightLimit) - this.offsetY; + let maxY = this.coords[1] + this.height; + + // Start point + let y = minY - this.coords[1]; + + // Fill array + array[0][0] = this.coords[0]; // x + array[0][1] = minY; + array[0][2] = this.coords[2]; // z + array[0][3] = maxY; + + // Shift + minY--; + + let nodeIndex = 1; + while (y >= 0) { + let sphereIndex = 0; + let size = this.layerSize(y); + + if (size < 0.0) { + minY--; + y--; + } else { + let offsetXZ = 0.5; + + // Create multiple spheres (nodes) + for (; sphereIndex < spheres; sphereIndex++) { + let finalSize = this.scaleWidth * (size * (this.random.nextFloat() + 0.328)); + let rotation = this.random.nextFloat() * 2 * Math.PI; + + let x = Math.floor(finalSize * Math.sin(rotation) + this.coords[0] + offsetXZ); + let z = Math.floor(finalSize * Math.cos(rotation) + this.coords[2] + offsetXZ); + + let from = [x, minY, z]; + let to = [x, minY + this.offsetY, z]; + + // Check if node is in a valid spot + if (this.checkBlockLine(from, to) !== -1) { + continue; + } + + let coords = [this.coords[0], this.coords[1], this.coords[2]]; + let factor = Math.sqrt(Math.pow(Math.abs(this.coords[0] - from[0]), 2) + Math.pow(Math.abs(this.coords[2] - from[2]), 2)); + let slope = factor * this.branchSlope; + + if (from[1] - slope > maxY) { + coords[1] = maxY; + } else { + coords[1] = Math.floor(from[1] - slope); + } + + // Fill nodes + if (this.checkBlockLine(coords, from) === -1) { + array[nodeIndex][0] = x; + array[nodeIndex][1] = minY; + array[nodeIndex][2] = z; + array[nodeIndex][3] = coords[1]; + nodeIndex++; + } + } + + minY--; + y--; + } + } + + // Store nodes + this.nodes = []; + for (let i = 0; i < nodeIndex; i++) { + this.nodes[i] = array[i]; + } + } + + layerSize(y) { + if (y < this.heightLimit * 0.29) { + return -1.618; + } + + let halfY = this.heightLimit / 2.0; + let distance = this.heightLimit / 2.0 - y; + + let size; + if (distance === 0.0) { + size = halfY; + } else if (Math.abs(distance) >= halfY) { + size = 0.0; + } else { + size = Math.sqrt(Math.pow(Math.abs(halfY), 2) - Math.pow(Math.abs(distance), 2)); + } + + size *= 0.5; + return size; + } + + + generateLeafNode(x, y, z) { + for (let startY = y; startY < y + this.offsetY; startY++) { + let size = this.leafSize(startY - y); + this.generateNodeByType(x, startY, z, size, 1, BlockRegistry.LEAVE.getId()); + } + } + + leafSize(y) { + if (y < 0 || y >= this.offsetY) { + return -1; + } + return y !== 0 && y !== this.offsetY - 1 ? 3 : 2.0; + } + + generateNodeByType(x, y, z, size, type, typeId) { + let actualSize = Math.floor(size + 0.618); + let typeIndex1 = this.types[type]; + let typeIndex2 = this.types[type + 3]; + + let origin = [x, y, z]; + let coords = [0, 0, 0]; + coords[type] = origin[type]; + + for (let i = -actualSize; i <= actualSize; i++) { + coords[typeIndex1] = origin[typeIndex1] + i; + + for (let radius = -actualSize; radius <= actualSize;) { + let distance = Math.sqrt(Math.pow(Math.abs(i) + 0.5, 2) + Math.pow(Math.abs(radius) + 0.5, 2)); + + if (distance > size) { + radius++; + } else { + coords[typeIndex2] = origin[typeIndex2] + radius; + + let typeAt = this.world.getBlockAt(coords[0], coords[1], coords[2]); + if (typeAt !== 0 && typeAt !== BlockRegistry.LEAVE.getId()) { + radius++; + } else { + this.world.setBlockAt(coords[0], coords[1], coords[2], typeId); + radius++; + } + } + } + } + } + + generateLeafNodes() { + for (let i = 0; i < this.nodes.length; i++) { + let x = this.nodes[i][0]; + let y = this.nodes[i][1]; + let z = this.nodes[i][2]; + this.generateLeafNode(x, y, z); + } + } + + generateTrunk() { + let x = this.coords[0]; + let minY = this.coords[1]; + let maxY = this.coords[1] + this.height; + let z = this.coords[2]; + + let coordsMin = [x, minY, z]; + let coordsMax = [x, maxY, z]; + + // Place normal trunk blocks + let logTypeId = BlockRegistry.LOG.getId(); + this.setBlocks(coordsMin, coordsMax, logTypeId); + + // Increase trunk size + if (this.trunkSize === 2) { + coordsMin[0]++; + coordsMax[0]++; + this.setBlocks(coordsMin, coordsMax, logTypeId); + coordsMin[2]++; + coordsMax[2]++; + this.setBlocks(coordsMin, coordsMax, logTypeId); + coordsMin[0]--; + coordsMax[0]--; + this.setBlocks(coordsMin, coordsMax, logTypeId); + } + } + + setBlocks(minCoords, maxCoords, typeId) { + let coords = [0, 0, 0]; + let index = 0; + + // Find target index for dimension + let targetIndex = 0; + for (; index < 3; index++) { + coords[index] = maxCoords[index] - minCoords[index]; + if (Math.abs(coords[index]) > Math.abs(coords[targetIndex])) { + targetIndex = index; + } + } + + // Invalid dimension + if (coords[targetIndex] === 0) { + return; + } + + // Get dimension index by target index + let widthIndex = this.types[targetIndex]; + let heightIndex = this.types[targetIndex + 3]; + + let offset; + if (coords[targetIndex] > 0) { + offset = 1; + } else { + offset = -1; + } + + let width = coords[widthIndex] / coords[targetIndex]; + let height = coords[heightIndex] / coords[targetIndex]; + + // Fill area with blocks + for (let indexBlock = 0; indexBlock !== coords[targetIndex] + offset; indexBlock += offset) { + let coords2 = [0, 0, 0]; + coords2[targetIndex] = Math.floor((minCoords[targetIndex] + indexBlock) + 0.5); + coords2[widthIndex] = Math.floor(minCoords[widthIndex] + indexBlock * width + 0.5); + coords2[heightIndex] = Math.floor(minCoords[heightIndex] + indexBlock * height + 0.5); + this.world.setBlockAt(coords2[0], coords2[1], coords2[2], typeId); + } + } + + generateLeafNodeBases() { + let length = this.nodes.length; + let minCoords = [this.coords[0], this.coords[1], this.coords[2]]; + + for (let i = 0; i < length; i++) { + let node = this.nodes[i]; + let maxCoords = [node[0], node[1], node[2]]; + + // Override y coordinate + minCoords[1] = node[3]; + + // Set log in middle of sphere + let y = minCoords[1] - this.coords[1]; + if (this.leafNodeNeedsBase(y)) { + this.setBlocks(minCoords, maxCoords, BlockRegistry.LOG.getId()); + } + } + } + + leafNodeNeedsBase(y) { + return y >= this.heightLimit * 0.2; + } + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/generator/structure/CaveGenerator.js b/src/js/net/minecraft/client/world/generator/structure/CaveGenerator.js new file mode 100644 index 0000000..b20bba7 --- /dev/null +++ b/src/js/net/minecraft/client/world/generator/structure/CaveGenerator.js @@ -0,0 +1,252 @@ +import Random from "../../../../util/Random.js"; +import {BlockRegistry} from "../../block/BlockRegistry.js"; +import Generator from "../Generator.js"; + +export default class CaveGenerator extends Generator { + + constructor(world, seed) { + super(world, seed); + + this.chunkRange = 8; + } + + generateInChunk(originChunkX, originChunkZ, primer) { + // Reset seed + this.random.setSeed(this.seed); + + let offset = this.chunkRange; + let seedX = (this.random.nextInt() / 2) * 2 + 1; + let seedZ = (this.random.nextInt() / 2) * 2 + 1; + + // Generate entire cave over 16x16 chunk area + for (let chunkX = originChunkX - offset; chunkX <= originChunkX + offset; chunkX++) { + for (let chunkZ = originChunkZ - offset; chunkZ <= originChunkZ + offset; chunkZ++) { + // Set seed for position + this.random.setSeed(chunkX * seedX + chunkZ * seedZ ^ this.seed); + + // Generate entire cave + this.generateCave(chunkX, chunkZ, originChunkX, originChunkZ, primer); + } + } + } + + generateCave(chunkX, chunkZ, originChunkX, originChunkZ, primer) { + let num = this.random.nextInt(this.random.nextInt(this.random.nextInt(40) + 1) + 1); + if (this.random.nextInt(15) !== 0) { + num = 0; + } + + // Generate multiple caves in the given area + for (let i = 0; i < num; i++) { + let x = chunkX * 16 + this.random.nextInt(16); + let y = this.random.nextInt(this.random.nextInt(120) + 8); + let z = chunkZ * 16 + this.random.nextInt(16); + + let amount = 1; + + if (this.random.nextInt(4) === 0) { + this.generateBasicCaveAtBlock(originChunkX, originChunkZ, primer, x, y, z); + amount += this.random.nextInt(4); + } + + for (let j = 0; j < amount; j++) { + let num1 = this.random.nextFloat() * Math.PI * 2.0; + let num2 = ((this.random.nextFloat() - 0.5) * 2.0) / 8; + let num3 = this.random.nextFloat() * 2.0 + this.random.nextFloat(); + this.generateCaveAtBlock(originChunkX, originChunkZ, primer, x, y, z, num3, num1, num2, 0, 0, 1.0); + } + } + } + + generateBasicCaveAtBlock(originChunkX, originChunkZ, primer, x, y, z) { + this.generateCaveAtBlock(originChunkX, originChunkZ, primer, + x, y, z, + 1.0 + this.random.nextFloat() * 6, + 0.0, 0.0, + -1, -1, 0.5 + ); + } + + generateCaveAtBlock(originChunkX, originChunkZ, primer, absoluteX, absoluteY, absoluteZ, amplitude, rotation2, rotation1, progress, distance, strength) { + let centerX = originChunkX * 16 + 8; + let centerZ = originChunkZ * 16 + 8; + + let motion2 = 0; + let motion1 = 0; + + let random = new Random(this.random.nextInt()); + if (distance <= 0) { + let i1 = this.chunkRange * 16 - 16; + distance = i1 - random.nextInt(i1 / 4); + } + + let isBeginning = false; + if (progress === -1) { + progress = distance / 2; + isBeginning = true; + } + + let maxProgress = random.nextInt(distance / 2) + distance / 4; + let isStrong = random.nextInt(6) === 0; + + for (; progress < distance; progress++) { + let value = 1.5 + (Math.sin((progress * Math.PI) / distance) * amplitude); + let valueWithStrength = value * strength; + + let cos = Math.cos(rotation1); + let sin = Math.sin(rotation1); + + absoluteX += Math.cos(rotation2) * cos; + absoluteY += sin; + absoluteZ += Math.sin(rotation2) * cos; + + if (isStrong) { + rotation1 *= 0.92; + } else { + rotation1 *= 0.7; + } + + rotation1 += motion1 * 0.1; + rotation2 += motion2 * 0.1; + + motion1 *= 0.9; + motion2 *= 0.75; + + motion1 += (random.nextFloat() - random.nextFloat()) * random.nextFloat() * 2.0; + motion2 += (random.nextFloat() - random.nextFloat()) * random.nextFloat() * 4; + + if (!isBeginning && progress === maxProgress && amplitude > 1.0) { + this.generateCaveAtBlock( + originChunkX, originChunkZ, primer, + absoluteX, absoluteY, absoluteZ, + random.nextFloat() * 0.5 + 0.5, rotation2 - 1.570796, rotation1 / 3, progress, distance, 1.0 + ); + this.generateCaveAtBlock( + originChunkX, originChunkZ, primer, + absoluteX, absoluteY, absoluteZ, + random.nextFloat() * 0.5 + 0.5, rotation2 + 1.570796, rotation1 / 3, progress, distance, 1.0 + ); + return; + } + + if (!isBeginning && random.nextInt(4) === 0) { + continue; + } + + let distanceToCenterX = absoluteX - centerX; + let distanceToCenterY = absoluteZ - centerZ; + + let progressInvert = distance - progress; + let shiftedAmplitude = amplitude + 2.0 + 16; + + if ((distanceToCenterX * distanceToCenterX + distanceToCenterY * distanceToCenterY) + - progressInvert * progressInvert > shiftedAmplitude * shiftedAmplitude) { + return; + } + + if (absoluteX < centerX - 16 - value * 2 + || absoluteZ < centerZ - 16 - value * 2 + || absoluteX > centerX + 16 + value * 2 + || absoluteZ > centerZ + 16 + value * 2) { + continue; + } + + distanceToCenterX = Math.floor(absoluteX - value) - originChunkX * 16 - 1; + let layerX = (Math.floor(absoluteX + value) - originChunkX * 16) + 1; + + distanceToCenterY = Math.floor(absoluteY - valueWithStrength) - 1; + let layerY = Math.floor(absoluteY + valueWithStrength) + 1; + + progressInvert = Math.floor(absoluteZ - value) - originChunkZ * 16 - 1; + let layerZ = (Math.floor(absoluteZ + value) - originChunkZ * 16) + 1; + + distanceToCenterX = Math.max(distanceToCenterX, 0); + layerX = Math.min(layerX, 16); + distanceToCenterY = Math.max(distanceToCenterY, 1); + layerY = Math.min(layerY, 120); + progressInvert = Math.max(progressInvert, 0); + layerZ = Math.min(layerZ, 16); + + let isWaterCave = false; + + // Check for water + for (let x = Math.floor(distanceToCenterX); !isWaterCave && x < layerX; x++) { + for (let z = Math.floor(progressInvert); !isWaterCave && z < layerZ; z++) { + for (let y = layerY + 1; !isWaterCave && y >= distanceToCenterY - 1; y--) { + if (y < 0 || y >= 128) { + continue; + } + + // Get current block + let typeId = primer.get(x, y, z) + + // Check if we have water in here + if (typeId === BlockRegistry.WATER.getId() || typeId === BlockRegistry.WATER.getId()) { // TODO one of them WATER MOVING + isWaterCave = true; + } + + if (y !== distanceToCenterY - 1 && x !== distanceToCenterX && x !== layerX - 1 && z !== progressInvert && z !== layerZ - 1) { + y = Math.floor(distanceToCenterY); + } + } + } + } + if (isWaterCave) { + continue; + } + + // For each block on layer x + for (let x = Math.floor(distanceToCenterX); x < layerX; x++) { + let offsetX = (((x + originChunkX * 16) + 0.5) - absoluteX) / value; + + // For each block on layer z + for (let z = Math.floor(progressInvert); z < layerZ; z++) { + let offsetZ = (((z + originChunkZ * 16) + 0.5) - absoluteZ) / value; + + let totalY = layerY; + let hasGrass = false; + + // For each block on layer y + for (let y = layerY - 1; y >= distanceToCenterY; y--) { + let offsetY = ((y + 0.5) - absoluteY) / valueWithStrength; + + // Check if the offset vector length is below 1 + if (offsetY > -0.7 && offsetX * offsetX + offsetY * offsetY + offsetZ * offsetZ < 1.0) { + let typeId = primer.get(x, totalY, z) + + // We found grass + if (typeId === BlockRegistry.GRASS.getId()) { + hasGrass = true; + } + + // Check if we can create a cave here + if (typeId === BlockRegistry.STONE.getId() || typeId === BlockRegistry.DIRT.getId() || typeId === BlockRegistry.GRASS.getId()) { + + if (y < 10) { + // Lava cave + primer.set(x, totalY, z, BlockRegistry.WATER.getId()); // TODO LAVA STILL + } else { + // Normal cave + primer.set(x, totalY, z, 0); + + // Grow grass if we have dirt in here + if (hasGrass && primer.get(x, totalY - 1, z) === BlockRegistry.DIRT.getId()) { + primer.set(x, totalY - 1, z, BlockRegistry.GRASS.getId()); + } + } + } + } + + // Go further down + totalY--; + } + } + } + + if (isBeginning) { + break; + } + } + } + +} \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/generator/structure/TreeGenerator.js b/src/js/net/minecraft/client/world/generator/structure/TreeGenerator.js new file mode 100644 index 0000000..20600a5 --- /dev/null +++ b/src/js/net/minecraft/client/world/generator/structure/TreeGenerator.js @@ -0,0 +1,88 @@ +import {BlockRegistry} from "../../block/BlockRegistry.js"; +import Generator from "../Generator.js"; + +export default class TreeGenerator extends Generator { + + constructor(world, seed) { + super(world, seed); + + this.size = 8; + } + + generateAtBlock(x, y, z) { + // Generate random height + let height = this.random.nextInt(3) + 4; + if (y < 1 || y + height + 1 > 128) { + return false; + } + + // Check if we have enough space for the tree to grow + for (let totalY = y; totalY <= y + 1 + height; totalY++) { + let radius = 1; + + // Define radius of the tree depending on the height + if (totalY === y) { + radius = 0; + } + if (totalY >= (y + 1 + height) - 2) { + radius = 2; + } + + // Scan radius + for (let totalX = x - radius; totalX <= x + radius; totalX++) { + for (let totalZ = z - radius; totalZ <= z + radius; totalZ++) { + if (totalY >= 0 && totalY < 128) { + let typeId = this.world.getBlockAt(totalX, totalY, totalZ); + + // Check if we have just air or leaves in the way + if (typeId !== 0 && typeId !== BlockRegistry.LEAVE.getId()) { + return; + } + } else { + return; + } + } + } + } + + // Check if tree can grow here + let typeIdBelowTree = this.world.getBlockAt(x, y - 1, z); + if (typeIdBelowTree !== BlockRegistry.GRASS.getId() && typeIdBelowTree !== BlockRegistry.DIRT.getId() || y >= 128 - height - 1) { + return false; + } + + // Set dirt below the tree + this.world.setBlockAt(x, y - 1, z, BlockRegistry.DIRT.getId()); + + // Create leaves + for (let totalY = (y - 3) + height; totalY <= y + height; totalY++) { + let offsetY = totalY - (y + height); + + // Increase leave radius depending on height + let radius = Math.floor(1 - offsetY / 2); + + // Create leave ring + for (let totalX = x - radius; totalX <= x + radius; totalX++) { + let offsetX = totalX - x; + for (let totalZ = z - radius; totalZ <= z + radius; totalZ++) { + let offsetZ = totalZ - z; + + // Skip corners + if (Math.abs(offsetX) !== radius || Math.abs(offsetZ) !== radius || this.random.nextInt(2) !== 0 && offsetY !== 0) { + this.world.setBlockAt(totalX, totalY, totalZ, BlockRegistry.LEAVE.getId()); + } + } + } + } + + // Create tree trunk + for (let i = 0; i < height; i++) { + let typeId = this.world.getBlockAt(x, y + i, z); + if (typeId === 0 || typeId === BlockRegistry.LEAVE.getId()) { + this.world.setBlockAt(x, y + i, z, BlockRegistry.LOG.getId()); + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/js/net/minecraft/util/MetadataChunkBlock.js b/src/js/net/minecraft/util/MetadataChunkBlock.js index 4ef3a92..5b6ed7f 100644 --- a/src/js/net/minecraft/util/MetadataChunkBlock.js +++ b/src/js/net/minecraft/util/MetadataChunkBlock.js @@ -22,12 +22,18 @@ export default class MetadataChunkBlock { if (index > 32768) { return; } + for (let x = this.x1; x <= this.x2; x++) { for (let z = this.z1; z <= this.z2; z++) { if (!world.blockExists(x, 0, z)) { continue; } + let centerChunk = world.getChunkAt(x >> 4, z >> 4); + if (!centerChunk.loaded) { + return; + } + for (let y = this.y1; y <= this.y2; y++) { if (y < 0 || y >= World.TOTAL_HEIGHT) { continue; diff --git a/src/js/net/minecraft/util/Random.js b/src/js/net/minecraft/util/Random.js index 6714957..72df661 100644 --- a/src/js/net/minecraft/util/Random.js +++ b/src/js/net/minecraft/util/Random.js @@ -4,15 +4,14 @@ export default class Random { constructor(seed = Date.now() % 1000000000 ^ Random.instances++ * 1000) { this.mask = 0xffffffff; - this.m_w = (123456789 + seed) & this.mask; - this.m_z = (987654321 - seed) & this.mask; + this.setSeed(seed); } nextBoolean() { return this.nextFloat() > 0.5; } - nextInt(max) { + nextInt(max = 0x7fffffff) { return Math.floor(this.nextFloat() * (max + 1)); } @@ -24,4 +23,10 @@ export default class Random { result /= 4294967296; return result; } + + setSeed(seed) { + this.seed = seed; + this.m_w = (123456789 + seed) & this.mask; + this.m_z = (987654321 - seed) & this.mask; + } } \ No newline at end of file diff --git a/src/resources/terrain/terrain.png b/src/resources/terrain/terrain.png index f2aea4d..7b770f4 100644 Binary files a/src/resources/terrain/terrain.png and b/src/resources/terrain/terrain.png differ