implement world generator, frustum culling and rebuild queue
This commit is contained in:
@@ -12,7 +12,7 @@ window.GameWindow = class {
|
||||
|
||||
// Stats
|
||||
this.stats = new Stats()
|
||||
this.stats.showPanel(1);
|
||||
this.stats.showPanel(0);
|
||||
wrapper.appendChild(this.stats.dom);
|
||||
|
||||
// Add web renderer canvas to wrapper
|
||||
|
||||
@@ -11,6 +11,9 @@ window.Minecraft = class {
|
||||
this.frames = 0;
|
||||
this.lastTime = Date.now();
|
||||
|
||||
// Create all blocks
|
||||
Block.create();
|
||||
|
||||
// Create world
|
||||
this.world = new World();
|
||||
this.worldRenderer.scene.add(this.world.group);
|
||||
@@ -23,9 +26,6 @@ window.Minecraft = class {
|
||||
}
|
||||
|
||||
init() {
|
||||
// Create all blocks
|
||||
Block.create();
|
||||
|
||||
// Start render loop
|
||||
this.running = true;
|
||||
this.requestNextFrame();
|
||||
|
||||
@@ -47,7 +47,7 @@ window.Player = class {
|
||||
}
|
||||
|
||||
resetPos() {
|
||||
this.setPos(0, 25, 0);
|
||||
this.setPos(0, 80, 0);
|
||||
}
|
||||
|
||||
setPos(x, y, z) {
|
||||
@@ -182,7 +182,7 @@ window.Player = class {
|
||||
this.motionY = 0.42;
|
||||
|
||||
if (this.sprinting) {
|
||||
let radiansYaw = this.yaw * (Math.PI / 180) + Math.PI;
|
||||
let radiansYaw = MathHelper.toRadians(this.yaw + 180);
|
||||
this.motionX -= Math.sin(radiansYaw) * 0.2;
|
||||
this.motionZ += Math.cos(radiansYaw) * 0.2;
|
||||
}
|
||||
@@ -281,7 +281,7 @@ window.Player = class {
|
||||
up = up * distance;
|
||||
forward = forward * distance;
|
||||
|
||||
let yawRadians = this.yaw * (Math.PI / 180) + Math.PI;
|
||||
let yawRadians = MathHelper.toRadians(this.yaw + 180);
|
||||
let sin = Math.sin(yawRadians);
|
||||
let cos = Math.cos(yawRadians);
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ window.WorldRenderer = class {
|
||||
this.camera.rotation.order = 'ZYX';
|
||||
this.camera.up = new THREE.Vector3(0, 0, 1);
|
||||
|
||||
this.frustum = new THREE.Frustum();
|
||||
|
||||
// Create scene
|
||||
this.scene = new THREE.Scene();
|
||||
this.scene.matrixAutoUpdate = false;
|
||||
@@ -42,6 +44,8 @@ window.WorldRenderer = class {
|
||||
|
||||
// Block Renderer
|
||||
this.blockRenderer = new BlockRenderer(this);
|
||||
|
||||
this.chunkSectionUpdateQueue = [];
|
||||
}
|
||||
|
||||
render(partialTicks) {
|
||||
@@ -49,7 +53,10 @@ window.WorldRenderer = class {
|
||||
this.orientCamera(partialTicks);
|
||||
|
||||
// Render chunks
|
||||
this.renderChunks(this, partialTicks);
|
||||
let player = this.minecraft.player;
|
||||
let cameraChunkX = Math.floor(player.x >> 4);
|
||||
let cameraChunkZ = Math.floor(player.z >> 4);
|
||||
this.renderChunks(cameraChunkX, cameraChunkZ, EnumWorldBlockLayer.SOLID);
|
||||
|
||||
// Render window
|
||||
this.webRenderer.render(this.scene, this.camera);
|
||||
@@ -59,8 +66,8 @@ window.WorldRenderer = class {
|
||||
let player = this.minecraft.player;
|
||||
|
||||
// Rotation
|
||||
this.camera.rotation.y = -player.yaw * (Math.PI / 180) + Math.PI;
|
||||
this.camera.rotation.x = -player.pitch * (Math.PI / 180);
|
||||
this.camera.rotation.y = -MathHelper.toRadians(player.yaw + 180);
|
||||
this.camera.rotation.x = -MathHelper.toRadians(player.pitch);
|
||||
|
||||
// Position
|
||||
let x = player.prevX + (player.x - player.prevX) * partialTicks;
|
||||
@@ -68,6 +75,9 @@ window.WorldRenderer = class {
|
||||
let z = player.prevZ + (player.z - player.prevZ) * partialTicks;
|
||||
this.camera.position.set(x, y + player.getEyeHeight(), z);
|
||||
|
||||
// Update frustum
|
||||
this.frustum.setFromProjectionMatrix(new THREE.Matrix4().multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse));
|
||||
|
||||
// Update FOV
|
||||
this.camera.fov = 85 + player.getFOVModifier();
|
||||
this.camera.updateProjectionMatrix();
|
||||
@@ -88,17 +98,68 @@ window.WorldRenderer = class {
|
||||
}
|
||||
}
|
||||
|
||||
renderChunks(renderer, partialTicks) {
|
||||
renderChunks(cameraChunkX, cameraChunkZ, renderLayer) {
|
||||
let world = this.minecraft.world;
|
||||
|
||||
for(let i in world.chunks) {
|
||||
for (let i in world.chunks) {
|
||||
let chunk = world.chunks[i];
|
||||
|
||||
for (let y = 0; y < chunk.sections.length; y++) {
|
||||
let section = chunk.sections[y];
|
||||
let distanceX = Math.abs(cameraChunkX - chunk.x);
|
||||
let distanceZ = Math.abs(cameraChunkZ - chunk.z);
|
||||
|
||||
if (section.dirty) {
|
||||
section.rebuild(renderer);
|
||||
// Is in render distance check
|
||||
if (distanceX < WorldRenderer.RENDER_DISTANCE && distanceZ < WorldRenderer.RENDER_DISTANCE) {
|
||||
// Make chunk visible
|
||||
chunk.group.visible = true;
|
||||
|
||||
|
||||
// For all chunk sections
|
||||
for (let y in chunk.sections) {
|
||||
let chunkSection = chunk.sections[y];
|
||||
|
||||
// Is in camera view check
|
||||
if (this.frustum.intersectsBox(chunkSection.boundingBox)) {
|
||||
// Make section visible
|
||||
chunkSection.group.visible = true;
|
||||
|
||||
// Render chunk section
|
||||
chunkSection.render(renderLayer);
|
||||
|
||||
// Queue for rebuild
|
||||
if (chunkSection.isQueuedForRebuild() && !this.chunkSectionUpdateQueue.includes(chunkSection)) {
|
||||
this.chunkSectionUpdateQueue.push(chunkSection);
|
||||
}
|
||||
} else {
|
||||
// Hide section
|
||||
chunkSection.group.visible = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Hide chunk
|
||||
chunk.group.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort update queue, chunk sections that are closer to the camera get a higher priority
|
||||
this.chunkSectionUpdateQueue.sort((section1, section2) => {
|
||||
let distance1 = Math.floor(Math.pow(section1.x - cameraChunkX, 2) + Math.pow(section1.z - cameraChunkZ, 2));
|
||||
let distance2 = Math.floor(Math.pow(section2.x - cameraChunkX, 2) + Math.pow(section2.z - cameraChunkZ, 2));
|
||||
return distance1 - distance2;
|
||||
});
|
||||
|
||||
// Rebuild 16 chunk sections per frame (An entire chunk)
|
||||
for (let i = 0; i < 16; i++) {
|
||||
if (this.chunkSectionUpdateQueue.length !== 0) {
|
||||
let chunkSection = this.chunkSectionUpdateQueue.shift();
|
||||
if (chunkSection != null) {
|
||||
// Load chunk
|
||||
let chunk = chunkSection.chunk;
|
||||
if (!chunk.isLoaded()) {
|
||||
world.loadChunk(chunk);
|
||||
}
|
||||
|
||||
// Rebuild chunk
|
||||
chunkSection.rebuild(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,22 @@ window.Chunk = class {
|
||||
this.group = new THREE.Object3D();
|
||||
this.group.matrixAutoUpdate = false;
|
||||
|
||||
this.loaded = false;
|
||||
|
||||
// Initialize sections
|
||||
this.sections = [];
|
||||
for (let y = 0; y < 16; y++) {
|
||||
let section = new ChunkSection(world, x, y, z);
|
||||
let section = new ChunkSection(world, this, x, y, z);
|
||||
|
||||
this.sections[y] = section;
|
||||
this.group.add(section.group);
|
||||
}
|
||||
}
|
||||
|
||||
setBlockAt(x, y, z, typeId) {
|
||||
this.getSection(y >> 4).setBlockAt(x, y & 15, z, typeId);
|
||||
}
|
||||
|
||||
getSection(y) {
|
||||
return this.sections[y];
|
||||
}
|
||||
@@ -34,4 +40,12 @@ window.Chunk = class {
|
||||
}
|
||||
}
|
||||
|
||||
load() {
|
||||
this.loaded = true;
|
||||
}
|
||||
|
||||
isLoaded() {
|
||||
return this.loaded;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,15 +2,24 @@ window.ChunkSection = class {
|
||||
|
||||
static SIZE = 16;
|
||||
|
||||
constructor(world, x, y, z) {
|
||||
constructor(world, chunk, x, y, z) {
|
||||
this.world = world;
|
||||
this.chunk = chunk;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
|
||||
this.boundingBox = new THREE.Box3();
|
||||
this.boundingBox.min.x = x * ChunkSection.SIZE;
|
||||
this.boundingBox.min.y = y * ChunkSection.SIZE;
|
||||
this.boundingBox.min.z = z * ChunkSection.SIZE;
|
||||
this.boundingBox.max.x = x * ChunkSection.SIZE + ChunkSection.SIZE;
|
||||
this.boundingBox.max.y = y * ChunkSection.SIZE + ChunkSection.SIZE;
|
||||
this.boundingBox.max.z = z * ChunkSection.SIZE + ChunkSection.SIZE;
|
||||
|
||||
this.group = new THREE.Object3D();
|
||||
this.group.matrixAutoUpdate = false;
|
||||
this.dirty = true;
|
||||
this.queuedForRebuild = true;
|
||||
|
||||
this.blocks = [];
|
||||
for (let x = 0; x < ChunkSection.SIZE; x++) {
|
||||
@@ -22,8 +31,12 @@ window.ChunkSection = class {
|
||||
}
|
||||
}
|
||||
|
||||
render(renderLayer) {
|
||||
|
||||
}
|
||||
|
||||
rebuild(renderer) {
|
||||
this.dirty = false;
|
||||
this.queuedForRebuild = false;
|
||||
this.group.clear();
|
||||
|
||||
// Start drawing chunk section
|
||||
@@ -62,6 +75,10 @@ window.ChunkSection = class {
|
||||
}
|
||||
|
||||
queueForRebuild() {
|
||||
this.dirty = true;
|
||||
this.queuedForRebuild = true;
|
||||
}
|
||||
|
||||
isQueuedForRebuild() {
|
||||
return this.queuedForRebuild;
|
||||
}
|
||||
}
|
||||
@@ -7,15 +7,19 @@ window.World = class {
|
||||
this.group.matrixAutoUpdate = false;
|
||||
this.chunks = [];
|
||||
|
||||
// Debug world
|
||||
for (let x = -16; x < 16; x++) {
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let z = -16; z < 16; z++) {
|
||||
this.setBlockAt(x, y, z, y === 15 ? 2 : 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setBlockAt(0, 16, -2, 17);
|
||||
// Load world
|
||||
this.generator = new WorldGenerator(this, Date.now() % 100000);
|
||||
}
|
||||
|
||||
loadChunk(chunk) {
|
||||
// Load chunk
|
||||
chunk.load();
|
||||
|
||||
// Generate new chunk
|
||||
this.generator.generateChunk(chunk);
|
||||
|
||||
// Populate chunk
|
||||
this.generator.populateChunk(chunk.x, chunk.z);
|
||||
}
|
||||
|
||||
getChunkAtBlock(x, y, z) {
|
||||
@@ -56,10 +60,9 @@ window.World = class {
|
||||
chunkSection.setBlockAt(x & 15, y & 15, z & 15, type);
|
||||
}
|
||||
|
||||
this.blockChanged(x, y, z);
|
||||
this.onBlockChanged(x, y, z);
|
||||
}
|
||||
|
||||
|
||||
getBlockAt(x, y, z) {
|
||||
let chunkSection = this.getChunkAtBlock(x, y, z);
|
||||
return chunkSection == null ? 0 : chunkSection.getBlockAt(x & 15, y & 15, z & 15);
|
||||
@@ -79,11 +82,20 @@ window.World = class {
|
||||
return chunk;
|
||||
}
|
||||
|
||||
blockChanged(x, y, z) {
|
||||
this.setDirty(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1);
|
||||
getHighestBlockYAt(x, z) {
|
||||
for (let y = World.TOTAL_HEIGHT; y > 0; y--) {
|
||||
if (this.isSolidBlockAt(x, y, z)) {
|
||||
return y;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
setDirty(minX, minY, minZ, maxX, maxY, maxZ) {
|
||||
onBlockChanged(x, y, z) {
|
||||
this.queueForRebuildInRegion(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1);
|
||||
}
|
||||
|
||||
queueForRebuildInRegion(minX, minY, minZ, maxX, maxY, maxZ) {
|
||||
// To chunk coordinates
|
||||
minX = minX >> 4;
|
||||
maxX = maxX >> 4;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
window.NoiseGenerator = class {
|
||||
perlin(x, z) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
window.WorldGenerator = class {
|
||||
|
||||
constructor(world, seed) {
|
||||
this.world = world;
|
||||
this.random = new Random(seed);
|
||||
|
||||
this.waterLevel = 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)));
|
||||
|
||||
// Water noise
|
||||
this.sandInWaterNoise = new NoiseGeneratorOctaves(this.random, 8);
|
||||
|
||||
// Hole in hills and islands
|
||||
this.holeNoise = new NoiseGeneratorOctaves(this.random, 3);
|
||||
this.islandNoise = new NoiseGeneratorOctaves(this.random, 3);
|
||||
|
||||
// Caves
|
||||
this.caveNoise = new NoiseGeneratorOctaves(this.random, 8);
|
||||
|
||||
// Population
|
||||
this.forestNoise = 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++) {
|
||||
|
||||
// Absolute position of the block
|
||||
let x = chunk.x * ChunkSection.SIZE + relX;
|
||||
let z = chunk.z * ChunkSection.SIZE + relZ;
|
||||
|
||||
// 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);
|
||||
|
||||
// Calculate final height for this position
|
||||
let groundHeightY = Math.floor(heightValue / 10 + this.waterLevel + hillValue);
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
populateChunk(chunkX, chunkZ) {
|
||||
for (let index = 0; index < 10; index++) {
|
||||
let x = this.random.nextInt(ChunkSection.SIZE);
|
||||
let z = this.random.nextInt(ChunkSection.SIZE);
|
||||
|
||||
// Absolute position of the block
|
||||
let absoluteX = chunkX * ChunkSection.SIZE + x;
|
||||
let absoluteZ = chunkZ * ChunkSection.SIZE + z;
|
||||
|
||||
// Use noise for a forest pattern
|
||||
let perlin = this.forestNoise.perlin(absoluteX * 10, absoluteZ * 10);
|
||||
if (perlin > 0 && this.random.nextInt(2) === 0) {
|
||||
|
||||
// Get highest block at this position
|
||||
let highestY = this.world.getHighestBlockYAt(absoluteX, absoluteZ);
|
||||
|
||||
// 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;
|
||||
|
||||
// Create tree log
|
||||
for (let i = 0; i < treeHeight; i++) {
|
||||
this.world.setBlockAt(absoluteX, highestY + i + 1, absoluteZ, Block.LOG.getId());
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
window.NoiseGeneratorCombined = class extends NoiseGenerator {
|
||||
|
||||
constructor(firstGenerator, secondGenerator) {
|
||||
super();
|
||||
|
||||
this.firstGenerator = firstGenerator;
|
||||
this.secondGenerator = secondGenerator;
|
||||
}
|
||||
|
||||
perlin(x, z) {
|
||||
return this.firstGenerator.perlin(x + this.secondGenerator.perlin(x, z), z);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
window.NoiseGeneratorOctaves = class extends NoiseGenerator {
|
||||
|
||||
constructor(random, octaves) {
|
||||
super();
|
||||
|
||||
this.octaves = octaves;
|
||||
this.generatorCollection = [];
|
||||
|
||||
for (let i = 0; i < octaves; i++) {
|
||||
this.generatorCollection[i] = new NoiseGeneratorPerlin(random);
|
||||
}
|
||||
}
|
||||
|
||||
perlin(x, z) {
|
||||
let total = 0.0;
|
||||
let frequency = 1.0;
|
||||
for (let i = 0; i < this.octaves; i++) {
|
||||
total += this.generatorCollection[i].perlin(x / frequency, z / frequency) * frequency;
|
||||
frequency *= 2.0;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
window.NoiseGeneratorPerlin = class extends NoiseGenerator {
|
||||
|
||||
constructor(random) {
|
||||
super();
|
||||
|
||||
this.permutations = [];
|
||||
for (let i = 0; i < 256; i++) {
|
||||
this.permutations[i] = i;
|
||||
}
|
||||
for (let i = 0; i < 256; i++) {
|
||||
let n = random.nextInt(256 - i) + i;
|
||||
let n2 = this.permutations[i];
|
||||
this.permutations[i] = this.permutations[n];
|
||||
this.permutations[n] = n2;
|
||||
this.permutations[i + 256] = this.permutations[i];
|
||||
}
|
||||
}
|
||||
|
||||
fade(t) {
|
||||
// Fade function as defined by Ken Perlin. This eases coordinate values
|
||||
// so that they will "ease" towards integral values. This ends up smoothing
|
||||
// the final output.
|
||||
return t * t * t * (t * (t * 6 - 15) + 10); // 6t^5 - 15t^4 + 10t^3
|
||||
}
|
||||
|
||||
lerp(x, a, b) {
|
||||
return a + x * (b - a);
|
||||
}
|
||||
|
||||
grad(hash, x, y, z) {
|
||||
let h = hash & 15; // Take the hashed value and take the first 4 bits of it (15 == 0b1111)
|
||||
let u = h < 8 /* 0b1000 */ ? x : y; // If the most significant bit (MSB) of the hash is 0 then set u = x. Otherwise y.
|
||||
|
||||
let v; // In Ken Perlin's original implementation this was another conditional operator (?:). I
|
||||
// expanded it for readability.
|
||||
if (h < 4 /* 0b0100 */) // If the first and second significant bits are 0 set v = y
|
||||
v = y;
|
||||
else if (h === 12 /* 0b1100 */ || h === 14 /* 0b1110*/) // If the first and second significant bits are 1 set v = x
|
||||
v = x;
|
||||
else // If the first and second significant bits are not equal (0/1, 1/0) set v = z
|
||||
v = z;
|
||||
|
||||
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v); // Use the last 2 bits to decide if u and v are positive or negative. Then return their addition.
|
||||
}
|
||||
|
||||
perlin(x, z) {
|
||||
let y;
|
||||
|
||||
let xi = Math.floor(x) & 0xFF;
|
||||
let zi = Math.floor(z) & 0xFF;
|
||||
let yi = Math.floor(0.0) & 0xFF;
|
||||
|
||||
x -= Math.floor(x);
|
||||
z -= Math.floor(z);
|
||||
y = 0.0 - Math.floor(0.0);
|
||||
|
||||
let u = this.fade(x);
|
||||
let w = this.fade(z);
|
||||
let v = this.fade(y);
|
||||
|
||||
let xzi = this.permutations[xi] + zi;
|
||||
let xzyi = this.permutations[xzi] + yi;
|
||||
|
||||
xzi = this.permutations[xzi + 1] + yi;
|
||||
xi = this.permutations[xi + 1] + zi;
|
||||
zi = this.permutations[xi] + yi;
|
||||
xi = this.permutations[xi + 1] + yi;
|
||||
|
||||
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.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.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.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))));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
window.EnumWorldBlockLayer = class {
|
||||
static SOLID = 0;
|
||||
static CUTOUT = 0;
|
||||
}
|
||||
@@ -8,4 +8,12 @@ window.MathHelper = class {
|
||||
return value < i ? i - 1 : i;
|
||||
}
|
||||
|
||||
static toDegrees(angle) {
|
||||
return angle * (180 / Math.PI);
|
||||
}
|
||||
|
||||
static toRadians(degree) {
|
||||
return degree * (Math.PI / 180);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
window.Random = class {
|
||||
|
||||
constructor(seed) {
|
||||
this.mask = 0xffffffff;
|
||||
this.m_w = (123456789 + seed) & this.mask;
|
||||
this.m_z = (987654321 - seed) & this.mask;
|
||||
}
|
||||
|
||||
nextBoolean() {
|
||||
return this.nextFloat() > 0.5;
|
||||
}
|
||||
|
||||
nextInt(max) {
|
||||
return Math.floor(this.nextFloat() * (max + 1));
|
||||
}
|
||||
|
||||
nextFloat() {
|
||||
this.m_z = (36969 * (this.m_z & 65535) + (this.m_z >>> 16)) & this.mask;
|
||||
this.m_w = (18000 * (this.m_w & 65535) + (this.m_w >>> 16)) & this.mask;
|
||||
|
||||
let result = ((this.m_z << 16) + (this.m_w & 65535)) >>> 0;
|
||||
result /= 4294967296;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -50,7 +50,9 @@ loadScripts([
|
||||
// Minecraft Source
|
||||
"src/js/net/minecraft/util/EnumBlockFace.js",
|
||||
"src/js/net/minecraft/util/Timer.js",
|
||||
"src/js/net/minecraft/util/Random.js",
|
||||
"src/js/net/minecraft/util/Vector3.js",
|
||||
"src/js/net/minecraft/util/EnumWorldBlockLayer.js",
|
||||
"src/js/net/minecraft/util/MovingObjectPosition.js",
|
||||
"src/js/net/minecraft/util/MathHelper.js",
|
||||
"src/js/net/minecraft/util/BoundingBox.js",
|
||||
@@ -67,6 +69,11 @@ loadScripts([
|
||||
"src/js/net/minecraft/client/world/ChunkSection.js",
|
||||
"src/js/net/minecraft/client/world/Chunk.js",
|
||||
"src/js/net/minecraft/client/world/World.js",
|
||||
"src/js/net/minecraft/client/world/generator/NoiseGenerator.js",
|
||||
"src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorPerlin.js",
|
||||
"src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorOctaves.js",
|
||||
"src/js/net/minecraft/client/world/generator/noise/NoiseGeneratorCombined.js",
|
||||
"src/js/net/minecraft/client/world/generator/WorldGenerator.js",
|
||||
"src/js/net/minecraft/client/entity/Player.js",
|
||||
"src/js/net/minecraft/client/Minecraft.js",
|
||||
"src/js/net/minecraft/client/render/Tessellator.js",
|
||||
|
||||
Reference in New Issue
Block a user