implement new terrain generator, cave generator, tree generator, implement big tree generator, improve light update performance
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 |
Reference in New Issue
Block a user