use threejs module js, remove unused libraries, increase world generation seed to 64 bit, implement random world spawn, improve start script

This commit is contained in:
LabyStudio
2022-05-12 02:57:23 +02:00
parent 51c07050e9
commit 24c0451031
28 changed files with 50508 additions and 578 deletions
+51
View File
@@ -0,0 +1,51 @@
import Minecraft from './net/minecraft/client/Minecraft.js';
class Start {
constructor(preStatusElementId) {
this.preStatusElement = document.getElementById(preStatusElementId);
}
loadTextures(textures) {
let resources = [];
let index = 0;
return textures.reduce((currentPromise, texturePath) => {
return currentPromise.then(() => {
return new Promise((resolve, reject) => {
// Load texture
let image = new Image();
image.src = "src/resources/" + texturePath;
image.onload = () => resolve();
resources[texturePath] = image;
index++;
});
});
}, Promise.resolve()).then(() => {
return resources;
});
}
launch(canvasWrapperId) {
this.loadTextures([
"misc/grasscolor.png",
"gui/font.png",
"gui/gui.png",
"gui/background.png",
"gui/icons.png",
"terrain/terrain.png",
"terrain/sun.png",
"terrain/moon.png",
"char.png"
]).then((resources) => {
this.preStatusElement.remove();
// Launch actual game on canvas
window.app = new Minecraft(canvasWrapperId, resources);
});
}
}
// Launch game
new Start("pre-status").launch("canvas-container");
-11
View File
@@ -29,17 +29,6 @@ export default class GameWindow {
this.canvasItems = document.createElement('canvas');
this.wrapper.appendChild(this.canvasItems);
// Stats
this.statsFps = new Stats()
this.statsFps.showPanel(0);
this.statsFps.domElement.style.cssText = 'position:absolute;top:0px;right:80px;float:right';
//this.wrapper.appendChild(this.statsFps.dom);
this.statsMs = new Stats()
this.statsMs.showPanel(1);
this.statsMs.domElement.style.cssText = 'position:absolute;top:0px;right:160px;float:right';
//this.wrapper.appendChild(this.statsMs.dom);
// On resize
let scope = this;
+5 -14
View File
@@ -84,17 +84,14 @@ export default class Minecraft {
}
init() {
// Load spawn chunk
for (let x = -WorldRenderer.RENDER_DISTANCE; x <= WorldRenderer.RENDER_DISTANCE; x++) {
for (let z = -WorldRenderer.RENDER_DISTANCE; z <= WorldRenderer.RENDER_DISTANCE; z++) {
this.world.getChunkAt(x, z);
}
}
this.player.respawn();
// Start render loop
this.running = true;
this.requestNextFrame();
// Load spawn chunks and respawn player
this.world.findSpawn();
this.world.loadSpawnChunks();
this.player.respawn();
}
hasInGameFocus() {
@@ -112,9 +109,6 @@ export default class Minecraft {
}
onLoop() {
this.window.statsFps.begin();
this.window.statsMs.begin();
// Update the timer
this.timer.advanceTime();
@@ -135,9 +129,6 @@ export default class Minecraft {
this.lastTime += 1000;
this.frames = 0;
}
this.window.statsFps.end();
this.window.statsMs.end();
}
onRender(partialTicks) {
@@ -47,8 +47,8 @@ export default class PlayerEntity extends EntityLiving {
}
respawn() {
let spawnY = this.world.getHeightAt(0, 0);
this.setPosition(0, spawnY + 8, 0);
let spawn = this.world.getSpawn();
this.setPosition(spawn.x, spawn.y, spawn.z);
}
setPosition(x, y, z) {
@@ -1,3 +1,5 @@
import * as THREE from "../../../../../../libraries/three.module.js";
export default class Tessellator {
constructor() {
@@ -6,6 +6,7 @@ import Tessellator from "./Tessellator.js";
import ChunkSection from "../world/ChunkSection.js";
import Random from "../../util/Random.js";
import Vector3 from "../../util/Vector3.js";
import * as THREE from "../../../../../../libraries/three.module.js";
export default class WorldRenderer {
@@ -105,8 +106,8 @@ export default class WorldRenderer {
// Render chunks
let player = this.minecraft.player;
let cameraChunkX = Math.floor(player.x >> 4);
let cameraChunkZ = Math.floor(player.z >> 4);
let cameraChunkX = Math.floor(player.x) >> 4;
let cameraChunkZ = Math.floor(player.z) >> 4;
this.renderChunks(cameraChunkX, cameraChunkZ);
// Render sky
@@ -611,6 +612,13 @@ export default class WorldRenderer {
}
}
}
// Update render order of chunks
world.group.children.sort((a, b) => {
let distance1 = Math.floor(Math.pow(a.chunkX - cameraChunkX, 2) + Math.pow(a.chunkZ - cameraChunkZ, 2));
let distance2 = Math.floor(Math.pow(b.chunkX - cameraChunkX, 2) + Math.pow(b.chunkZ - cameraChunkZ, 2));
return distance2 - distance1;
});
}
rebuildAll() {
@@ -1,5 +1,6 @@
import Tessellator from "../Tessellator.js";
import MathHelper from "../../../util/MathHelper.js";
import * as THREE from "../../../../../../../libraries/three.module.js";
export default class EntityRenderer {
@@ -1,6 +1,7 @@
import ModelPlayer from "../../model/model/ModelPlayer.js";
import EntityRenderer from "../EntityRenderer.js";
import Block from "../../../world/block/Block.js";
import * as THREE from "../../../../../../../../libraries/three.module.js";
export default class PlayerRenderer extends EntityRenderer {
@@ -1,3 +1,5 @@
import * as THREE from "../../../../../../../libraries/three.module.js";
export default class ItemRenderer {
constructor(minecraft, window) {
@@ -1,5 +1,6 @@
import Polygon from "./Polygon.js";
import Vertex from "./Vertex.js";
import * as THREE from "../../../../../../../../libraries/three.module.js";
export default class ModelRenderer {
@@ -1,4 +1,5 @@
import Block from "../world/block/Block.js";
import * as THREE from "../../../../../../libraries/three.module.js";
export default class SoundManager {
@@ -2,6 +2,7 @@ import EnumSkyBlock from "../../util/EnumSkyBlock.js";
import Block from "./block/Block.js";
import World from "./World.js";
import ChunkSection from "./ChunkSection.js";
import * as THREE from "../../../../../../libraries/three.module.js";
export default class Chunk {
@@ -1,5 +1,6 @@
import EnumSkyBlock from "../../util/EnumSkyBlock.js";
import Block from "./block/Block.js";
import * as THREE from "../../../../../../libraries/three.module.js";
export default class ChunkSection {
+55 -18
View File
@@ -8,12 +8,16 @@ import EnumBlockFace from "../../util/EnumBlockFace.js";
import Vector3 from "../../util/Vector3.js";
import Vector4 from "../../util/Vector4.js";
import MetadataChunkBlock from "../../util/MetadataChunkBlock.js";
import Long from "../../../../../../libraries/long.js";
import * as THREE from "../../../../../../libraries/three.module.js";
import WorldRenderer from "../render/WorldRenderer.js";
import Random from "../../util/Random.js";
export default class World {
static TOTAL_HEIGHT = ChunkSection.SIZE * 8 - 1; // ChunkSection.SIZE * 16 - 1;
constructor(minecraft) {
constructor(minecraft, seed = Long.fromInt(Date.now() % 100000)) {
this.minecraft = minecraft;
this.entities = [];
@@ -25,10 +29,9 @@ export default class World {
this.lightUpdateQueue = [];
this.time = 0;
this.spawn = new Vector3(0, 0, 0);
// Load world
this.seed = Date.now() % 100000;
this.generator = new WorldGenerator(this, this.seed);
this.setSeed(seed);
// Update lights async
let scope = this;
@@ -41,18 +44,17 @@ export default class World {
}, 0);
}
setSeed(seed) {
this.seed = seed;
this.generator = new WorldGenerator(this, seed);
this.random = new Random(seed);
}
getSeed() {
return this.seed;
}
onTick() {
let player = this.minecraft.player;
let cameraChunkX = Math.floor(player.x >> 4);
let cameraChunkZ = Math.floor(player.z >> 4);
// Update render order of chunks
this.group.children.sort((a, b) => {
let distance1 = Math.floor(Math.pow(a.chunkX - cameraChunkX, 2) + Math.pow(a.chunkZ - cameraChunkZ, 2));
let distance2 = Math.floor(Math.pow(b.chunkX - cameraChunkX, 2) + Math.pow(b.chunkZ - cameraChunkZ, 2));
return distance2 - distance1;
});
// Update skylight subtracted (To make the night dark)
let lightLevel = this.calculateSkylightSubtracted(1.0);
if (lightLevel !== this.skylightSubtracted) {
@@ -108,9 +110,6 @@ export default class World {
}
getChunkAtBlock(x, y, z) {
if (!this.blockExists(x, y, z)) {
return null;
}
return this.getChunkAt(x >> 4, z >> 4).getSection(y >> 4);
}
@@ -617,4 +616,42 @@ export default class World {
}
return null;
}
getSpawn() {
return this.spawn;
}
setSpawn(x, z) {
let y = this.getHeightAt(x, z);
this.spawn = new Vector3(x, y + 8, z);
}
findSpawn() {
if (this.spawn.y <= 0) {
this.spawn.y = 64;
}
while (this.getBlockAboveSeaLevel(this.spawn.x, this.spawn.z) === 0) {
this.spawn.x += this.random.nextInt(8) - this.random.nextInt(8);
this.spawn.z += this.random.nextInt(8) - this.random.nextInt(8);
}
}
getBlockAboveSeaLevel(x, z) {
let y = this.generator.seaLevel;
while (this.getBlockAt(x, y + 1, z) !== 0) {
y++;
}
return this.getBlockAt(x, y, z);
}
loadSpawnChunks() {
for (let x = -WorldRenderer.RENDER_DISTANCE; x <= WorldRenderer.RENDER_DISTANCE; x++) {
for (let z = -WorldRenderer.RENDER_DISTANCE; z <= WorldRenderer.RENDER_DISTANCE; z++) {
this.getChunkAt(x + this.spawn.x >> 4, z + this.spawn.z >> 4);
}
}
this.spawn.y = this.getHeightAt(this.spawn.x, this.spawn.z) + 8;
}
}
@@ -1,4 +1,5 @@
import Random from "../../../util/Random.js";
import Long from "../../../../../../../libraries/long.js";
export default class Generator {
@@ -15,4 +16,23 @@ export default class Generator {
generateAtBlock(x, y, z, primer) {
}
generateSeedOffset() {
this.random.setSeed(this.seed);
let seedX = this.random.nextLong().divide(2).multiply(2).add(1);
let seedZ = this.random.nextLong().divide(2).multiply(2).add(1);
return {seedX, seedZ};
}
setSeedOffset(chunkX, chunkZ, seedX, seedZ) {
let seed = Long.fromInt(chunkX).multiply(seedX).add(Long.fromInt(chunkZ).multiply(seedZ)).xor(this.seed);
this.random.setSeed(seed);
}
setChunkSeed(chunkX, chunkZ) {
let {seedX, seedZ} = this.generateSeedOffset();
this.setSeedOffset(chunkX, chunkZ, seedX, seedZ);
}
}
@@ -20,12 +20,13 @@ export default class WorldGenerator extends Generator {
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);
this.natureGenerator1 = new NoiseGeneratorOctaves(this.random, 4);
this.natureGenerator2 = new NoiseGeneratorOctaves(this.random, 4);
this.terrainGenerator1 = new NoiseGeneratorOctaves(this.random, 10);
this.terrainGenerator2 = new NoiseGeneratorOctaves(this.random, 16);
this.populationNoiseGenerator = new NoiseGeneratorOctaves(this.random, 8);
}
@@ -52,18 +53,13 @@ export default class WorldGenerator extends Generator {
}
populateChunk(chunkX, chunkZ) {
// Reset seed
this.random.setSeed(this.seed);
// 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);
this.setChunkSeed(chunkX, chunkZ);
// 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);
let amount = Math.floor((this.populationNoiseGenerator.perlin(absoluteX * 0.5, absoluteY * 0.5) / 8 + this.random.nextDouble() * 4 + 4) / 3);
if (amount < 0) {
amount = 0;
}
@@ -5,9 +5,9 @@ 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.offsetX = random.nextDouble() * 256;
this.offsetY = random.nextDouble() * 256;
this.offsetZ = random.nextDouble() * 256;
this.permutations = [];
for (let i = 0; i < 256; i++) {
@@ -21,7 +21,7 @@ export default class BigTreeGenerator extends Generator {
}
generateAtBlock(x, y, z) {
let seed = this.random.nextInt();
let seed = this.random.nextLong();
this.random.setSeed(seed);
this.coords[0] = x;
@@ -11,18 +11,14 @@ export default class CaveGenerator extends Generator {
}
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;
let {seedX, seedZ} = this.generateSeedOffset();
// 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);
this.setSeedOffset(chunkX, chunkZ, seedX, seedZ);
// Generate entire cave
this.generateCave(chunkX, chunkZ, originChunkX, originChunkZ, primer);
@@ -50,10 +46,10 @@ export default class CaveGenerator extends Generator {
}
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);
let rotation1 = this.random.nextFloat() * Math.PI * 2.0;
let rotation2 = ((this.random.nextFloat() - 0.5) * 2.0) / 8;
let amplitude = this.random.nextFloat() * 2.0 + this.random.nextFloat();
this.generateCaveAtBlock(originChunkX, originChunkZ, primer, x, y, z, amplitude, rotation1, rotation2, 0, 0, 1.0);
}
}
}
@@ -67,47 +63,47 @@ export default class CaveGenerator extends Generator {
);
}
generateCaveAtBlock(originChunkX, originChunkZ, primer, absoluteX, absoluteY, absoluteZ, amplitude, rotation2, rotation1, progress, distance, strength) {
generateCaveAtBlock(originChunkX, originChunkZ, primer, absoluteX, absoluteY, absoluteZ, amplitude, rotation1, rotation2, 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());
let random = new Random(this.random.nextLong());
if (distance <= 0) {
let i1 = this.chunkRange * 16 - 16;
distance = i1 - random.nextInt(i1 / 4);
let range = this.chunkRange * 16 - 16;
distance = range - random.nextInt(Math.floor(range / 4));
}
let isBeginning = false;
if (progress === -1) {
progress = distance / 2;
progress = Math.floor(distance / 2);
isBeginning = true;
}
let maxProgress = random.nextInt(distance / 2) + distance / 4;
let maxProgress = random.nextInt(Math.floor(distance / 2)) + Math.floor(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);
let cos = Math.cos(rotation2);
let sin = Math.sin(rotation2);
absoluteX += Math.cos(rotation2) * cos;
absoluteX += Math.cos(rotation1) * cos;
absoluteY += sin;
absoluteZ += Math.sin(rotation2) * cos;
absoluteZ += Math.sin(rotation1) * cos;
if (isStrong) {
rotation1 *= 0.92;
rotation2 *= 0.92;
} else {
rotation1 *= 0.7;
rotation2 *= 0.7;
}
rotation1 += motion1 * 0.1;
rotation2 += motion2 * 0.1;
rotation2 += motion1 * 0.1;
rotation1 += motion2 * 0.1;
motion1 *= 0.9;
motion2 *= 0.75;
@@ -119,12 +115,12 @@ export default class CaveGenerator extends Generator {
this.generateCaveAtBlock(
originChunkX, originChunkZ, primer,
absoluteX, absoluteY, absoluteZ,
random.nextFloat() * 0.5 + 0.5, rotation2 - 1.570796, rotation1 / 3, progress, distance, 1.0
random.nextFloat() * 0.5 + 0.5, rotation1 - 1.570796, rotation2 / 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
random.nextFloat() * 0.5 + 0.5, rotation1 + 1.570796, rotation2 / 3, progress, distance, 1.0
);
return;
}
+65 -19
View File
@@ -1,32 +1,78 @@
import Long from "../../../../../libraries/long.js";
export default class Random {
static instances = 0;
constructor(seed = Date.now() % 1000000000 ^ Random.instances++ * 1000) {
this.mask = 0xffffffff;
this.multiplier = Long.fromString("25214903917");
this.mask = Long.fromInt(1).shiftLeft(48).subtract(1);
this.addend = Long.fromInt(0xB);
this.doubleUnit = 1.1102230246251565E-16;
this.setSeed(seed);
}
nextBoolean() {
return this.nextFloat() > 0.5;
}
nextInt(max = 0x7fffffff) {
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;
return this.next(24) / (1 << 24);
}
setSeed(seed) {
this.seed = seed;
this.m_w = (123456789 + seed) & this.mask;
this.m_z = (987654321 - seed) & this.mask;
nextDouble() {
return Long.fromInt(this.next(26)).shiftLeft(27).add(Long.fromInt(this.next(27))).toNumber() * this.doubleUnit;
}
nextInt(max = -1) {
if (max === -1) {
return this.next(32);
}
let r = this.next(31);
let m = max - 1;
if ((max & m) === 0) // i.e., bound is a power of 2
r = Long.fromInt(max).multiply(Long.fromInt(r)).shiftRightUnsigned(31).toNumber();
else {
for (let u = r;
u - (r = u % max) + m < 0;
u = this.next(31))
;
}
return r;
}
nextLong() {
return Long.fromInt(this.next(32)).shiftLeft(32).add(Long.fromInt(this.next(32)));
}
next(bits) {
let oldSeed;
let nextSeed;
do {
oldSeed = this.seed;
nextSeed = oldSeed.multiply(this.multiplier).add(this.addend).and(this.mask);
} while (!this._compareAndSet(oldSeed, nextSeed));
return nextSeed.shiftRight(48 - bits).toNumber();
}
_compareAndSet(expect, update) {
if (!this.seed.equals(expect)) {
return false;
}
this.seed = update;
return true;
}
setSeed(n) {
let long;
if (typeof n === "number") {
long = Long.fromInt(n);
} else if (n instanceof Long) {
long = n;
} else {
long = Long.fromString(n);
}
this.seed = long.xor(this.multiplier).and(this.mask);
}
}
-82
View File
@@ -1,82 +0,0 @@
import Minecraft from './js/net/minecraft/client/Minecraft.js';
let resources = [];
// Script loader
function loadScripts(scripts) {
let total = scripts.length;
let index = 0;
return scripts.reduce((currentPromise, scriptUrl) => {
return currentPromise.then(() => {
return new Promise((resolve, reject) => {
// Update status message
updatePreStatus("Loading scripts... " + index + "/" + total);
// Load script
let script = document.createElement('script');
script.async = true;
script.src = scriptUrl;
script.onload = () => resolve();
document.getElementsByTagName('head')[0].appendChild(script);
index++;
});
});
}, Promise.resolve());
}
// Texture loader
function loadTexture(textures) {
let total = textures.length;
let index = 0;
return textures.reduce((currentPromise, texturePath) => {
return currentPromise.then(() => {
return new Promise((resolve, reject) => {
// Update status message
updatePreStatus("Loading texture... " + index + "/" + total);
// Load texture
let image = new Image();
image.src = "src/resources/" + texturePath;
image.onload = () => resolve();
resources[texturePath] = image;
index++;
});
});
}, Promise.resolve());
}
function updatePreStatus(message) {
document.getElementById("pre-status").innerText = message;
}
// Load textures
loadTexture([
"misc/grasscolor.png",
"gui/font.png",
"gui/gui.png",
"gui/background.png",
"gui/icons.png",
"terrain/terrain.png",
"terrain/sun.png",
"terrain/moon.png",
"char.png"
]).then(() => {
// Load scripts
loadScripts([
// Dependencies
"libraries/three.min.js",
"libraries/stats.min.js",
"libraries/context-filter-polyfill.min.js"
]).then(() => {
// Remove pre status
document.getElementById("pre-status").remove();
// Start Minecraft
window.app = new Minecraft("canvas-container", resources);
});
});