implement new terrain generator, cave generator, tree generator, implement big tree generator, improve light update performance

This commit is contained in:
LabyStudio
2022-05-04 23:35:53 +02:00
parent b52a1385b3
commit d846613ca9
21 changed files with 1380 additions and 196 deletions
@@ -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() {
@@ -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) {
@@ -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;
}
@@ -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);
}
}
}
+6 -1
View File
@@ -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;
@@ -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;
}
}
}
+36 -14
View File
@@ -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 = [];
}
}
@@ -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) {
@@ -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)
}
}
@@ -10,6 +10,10 @@ export default class BlockWater extends Block {
return 0.01;
}
getTransparency() {
return 0.2;
}
isSolid() {
return false;
}
@@ -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) {
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
}
}
}
@@ -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;
}
}
@@ -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;
}
}
}
}
@@ -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;
}
}
@@ -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;
+8 -3
View File
@@ -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;
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB