refactor project structure, implement tessellator

This commit is contained in:
LabyStudio
2022-01-31 20:15:27 +01:00
parent 192417f626
commit 293a6d9553
15 changed files with 160 additions and 36 deletions
+57
View File
@@ -0,0 +1,57 @@
window.GameWindow = class {
constructor(renderer, canvasWrapperId) {
this.renderer = renderer;
this.canvasWrapperId = canvasWrapperId;
this.mouseMotionX = 0;
this.mouseMotionY = 0;
this.mouseLocked = false;
// Add web renderer canvas to wrapper
let canvas = renderer.canvasElement;
document.getElementById(this.canvasWrapperId).appendChild(canvas);
// Init
this.onResize();
// On resize
let scope = this;
window.addEventListener('resize', _ => scope.onResize(), false);
// Request focus
canvas.onclick = function () {
canvas.requestPointerLock();
}
// Focus listener
document.addEventListener('pointerlockchange', _ => this.onFocusChanged(), false);
// Mouse motion
document.addEventListener('mousemove', event => this.onMouseMove(event), false);
// Create keyboard
Keyboard.create();
}
onResize() {
// Get canvas size
let canvasElement = document.getElementById(this.canvasWrapperId);
this.canvasWidth = canvasElement.offsetWidth;
this.canvasHeight = canvasElement.offsetHeight;
// Adjust camera
this.renderer.camera.aspect = this.canvasWidth / this.canvasHeight;
this.renderer.camera.updateProjectionMatrix();
this.renderer.webRenderer.setSize(this.canvasWidth, this.canvasHeight);
}
onFocusChanged() {
this.mouseLocked = document.pointerLockElement === this.renderer.canvasElement;
}
onMouseMove(event) {
this.mouseMotionX = -event.movementX;
this.mouseMotionY = event.movementY;
}
}
+80
View File
@@ -0,0 +1,80 @@
window.Minecraft = class {
/**
* Create Minecraft instance and render it on a canvas
*/
constructor(canvasWrapperId) {
this.worldRenderer = new WorldRenderer(this);
this.window = new GameWindow(this.worldRenderer, canvasWrapperId);
this.timer = new Timer(20);
this.frames = 0;
this.lastTime = Date.now();
// Create world
this.world = new World();
this.worldRenderer.scene.add(this.world.group);
// Create player
this.player = new Player(this.world);
// Initialize
this.init();
}
init() {
this.running = true;
this.requestNextFrame();
}
requestNextFrame() {
let scope = this;
requestAnimationFrame(function () {
if (scope.running) {
scope.requestNextFrame();
scope.onLoop();
}
});
}
onLoop() {
// Update the timer
this.timer.advanceTime();
// Call the tick to reach updates 20 per seconds
for (let i = 0; i < this.timer.ticks; i++) {
this.onTick();
}
// Render the game
this.onRender(this.timer.partialTicks);
// Increase rendered frame
this.frames++;
// Loop if a second passed
while (Date.now() >= this.lastTime + 1000) {
//console.log(this.frames + " fps");
this.lastTime += 1000;
this.frames = 0;
}
}
onRender(partialTicks) {
if (this.window.mouseLocked) {
this.player.turn(this.window.mouseMotionX, this.window.mouseMotionY);
this.window.mouseMotionX = 0;
this.window.mouseMotionY = 0;
}
// Render the game
this.worldRenderer.render(partialTicks);
}
onTick() {
this.player.onTick();
}
}
@@ -0,0 +1,477 @@
window.Player = class {
constructor(world) {
this.world = world;
this.prevX = 0;
this.prevY = 0;
this.prevZ = 0;
this.x = 0;
this.y = 0;
this.z = 0;
this.motionX = 0;
this.motionY = 0;
this.motionZ = 0;
this.yaw = 0;
this.pitch = 0;
this.onGround = false;
this.collision = false;
this.jumpMovementFactor = 0.02;
this.speedInAir = 0.02;
this.flySpeed = 0.05;
this.stepHeight = 0.5;
this.moveForward = 0.0;
this.moveStrafing = 0.0;
this.jumpTicks = 0;
this.flyToggleTimer = 0;
this.sprintToggleTimer = 0;
this.jumping = false;
this.sprinting = false;
this.sneaking = false;
this.flying = false;
this.prevFovModifier = 0;
this.fovModifier = 0;
this.timeFovChanged = 0;
this.resetPos();
}
resetPos() {
this.setPos(0, 2, 0);
this.pitch = -90;
}
setPos(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
let w = 0.3;
let h = 0.9;
this.boundingBox = new BoundingBox(x - w, y - h, z - w, x + w, y + h, z + w);
}
turn(motionX, motionY) {
this.yaw = this.yaw + motionX * 0.15;
this.pitch = this.pitch - motionY * 0.15;
if (this.pitch < -90.0) {
this.pitch = -90.0;
}
if (this.pitch > 90.0) {
this.pitch = 90.0;
}
}
onTick() {
let prevMoveForward = this.moveForward;
let prevJumping = this.jumping;
this.updateKeyboardInput();
// Toggle jumping
if (!prevJumping && this.jumping) {
if (this.flyToggleTimer === 0) {
this.flyToggleTimer = 7;
} else {
this.flying = !this.flying;
this.flyToggleTimer = 0;
this.updateFOVModifier();
}
}
// Toggle sprint
if (prevMoveForward === 0 && this.moveForward > 0) {
if (this.sprintToggleTimer === 0) {
this.sprintToggleTimer = 7;
} else {
this.sprinting = true;
this.sprintToggleTimer = 0;
this.updateFOVModifier();
}
}
if (this.jumpTicks > 0) {
--this.jumpTicks;
}
if (this.flyToggleTimer > 0) {
--this.flyToggleTimer;
}
if (this.sprintToggleTimer > 0) {
--this.sprintToggleTimer;
}
this.prevX = this.x;
this.prevY = this.y;
this.prevZ = this.z;
// Stop if too slow
if (Math.abs(this.motionX) < 0.003) {
this.motionX = 0.0;
}
if (Math.abs(this.motionY) < 0.003) {
this.motionY = 0.0;
}
if (Math.abs(this.motionZ) < 0.003) {
this.motionZ = 0.0;
}
// Jump
if (this.jumping) {
if (this.isInWater()) {
this.motionY += 0.04;
} else if (this.onGround && this.jumpTicks === 0) {
this.jump();
this.jumpTicks = 10;
}
} else {
this.jumpTicks = 0;
}
this.moveStrafing *= 0.98;
this.moveForward *= 0.98;
if (this.flying) {
this.travelFlying(this.moveForward, 0, this.moveStrafing);
} else {
if (this.isInWater()) {
// Is inside of water
this.travelInWater(this.moveForward, 0, this.moveStrafing);
} else {
// Is on land
this.travel(this.moveForward, 0, this.moveStrafing);
}
}
this.jumpMovementFactor = this.speedInAir;
if (this.sprinting) {
this.jumpMovementFactor = this.jumpMovementFactor + this.speedInAir * 0.3;
if (this.moveForward <= 0 || this.collision || this.sneaking) {
this.sprinting = false;
this.updateFOVModifier();
}
}
}
isInWater() {
return false;
}
isHeadInWater() {
return false;
}
jump() {
this.motionY = 0.42;
if (this.sprinting) {
let radiansYaw = -this.yaw * (Math.PI / 180);
this.motionX -= Math.sin(radiansYaw) * 0.2;
this.motionZ += Math.cos(radiansYaw) * 0.2;
}
}
travelFlying(forward, vertical, strafe) {
// Fly move up and down
if (this.sneaking) {
this.moveStrafing = strafe / 0.3;
this.moveForward = forward / 0.3;
this.motionY -= this.flySpeed * 3.0;
}
if (this.jumping) {
this.motionY += this.flySpeed * 3.0;
}
let prevMotionY = this.motionY;
let prevJumpMovementFactor = this.jumpMovementFactor;
this.jumpMovementFactor = this.flySpeed * (this.sprinting ? 2 : 1);
this.travel(forward, vertical, strafe);
this.motionY = prevMotionY * 0.6;
this.jumpMovementFactor = prevJumpMovementFactor;
if (this.onGround) {
this.flying = false;
}
}
travelInWater(forward, vertical, strafe) {
let slipperiness = 0.8;
let friction = 0.02;
this.moveRelative(forward, vertical, strafe, friction);
this.collision = this.moveCollide(-this.motionX, this.motionY, -this.motionZ);
this.motionX *= slipperiness;
this.motionY *= 0.8;
this.motionZ *= slipperiness;
this.motionY -= 0.02;
}
travel(forward, vertical, strafe) {
let prevSlipperiness = this.getBlockSlipperiness() * 0.91;
let value = 0.16277136 / (prevSlipperiness * prevSlipperiness * prevSlipperiness);
let friction;
if (this.onGround) {
friction = this.getAIMoveSpeed() * value;
} else {
friction = this.jumpMovementFactor;
}
this.moveRelative(forward, vertical, strafe, friction);
// Get new speed
let slipperiness = this.getBlockSlipperiness() * 0.91;
// Move
this.collision = this.moveCollide(-this.motionX, this.motionY, -this.motionZ);
// Gravity
if (!this.flying) {
this.motionY -= 0.08;
}
// Decrease motion
this.motionX *= slipperiness;
this.motionY *= 0.98;
this.motionZ *= slipperiness;
}
getBlockSlipperiness() {
return this.onGround ? 0.6 : 1.0;
}
getAIMoveSpeed() {
return this.sprinting ? 0.13 : 0.1;
}
moveRelative(forward, up, strafe, friction) {
let distance = strafe * strafe + up * up + forward * forward;
if (distance >= 0.0001) {
distance = Math.sqrt(distance);
if (distance < 1.0) {
distance = 1.0;
}
distance = friction / distance;
strafe = strafe * distance;
up = up * distance;
forward = forward * distance;
let yawRadians = -this.yaw * (Math.PI / 180);
let sin = Math.sin(yawRadians);
let cos = Math.cos(yawRadians);
this.motionX += strafe * cos - forward * sin;
this.motionY += up;
this.motionZ += forward * cos + strafe * sin;
}
}
updateKeyboardInput() {
let moveForward = 0.0;
let moveStrafe = 0.0;
let jumping = false;
let sneaking = false;
if (Keyboard.isKeyDown("KeyR")) { // R
this.resetPos();
}
if (Keyboard.isKeyDown("KeyW")) { // W
moveForward++;
}
if (Keyboard.isKeyDown("KeyS")) { // S
moveForward--;
}
if (Keyboard.isKeyDown("KeyA")) { // A
moveStrafe++;
}
if (Keyboard.isKeyDown("KeyD")) { // D
moveStrafe--;
}
if (Keyboard.isKeyDown("Space")) { // Space
jumping = true;
}
if (Keyboard.isKeyDown("ShiftLeft")) { // Shift
if (this.moveForward > 0 && !this.sneaking && !this.sprinting && this.motionX !== 0 && this.motionZ !== 0) {
this.sprinting = true;
this.updateFOVModifier();
}
}
if (Keyboard.isKeyDown("KeyQ")) { // Q
sneaking = true;
}
if (sneaking) {
moveStrafe = moveStrafe * 0.3;
moveForward = moveForward * 0.3;
}
this.moveForward = moveForward;
this.moveStrafing = moveStrafe;
this.jumping = jumping;
this.sneaking = sneaking;
}
moveCollide(targetX, targetY, targetZ) {
// Target position
let originalTargetX = targetX;
let originalTargetY = targetY;
let originalTargetZ = targetZ;
if (this.onGround && this.sneaking) {
for (; targetX !== 0.0 && this.world.getCollisionBoxes(this.boundingBox.offset(targetX, -this.stepHeight, 0.0)).length === 0; originalTargetX = targetX) {
if (targetX < 0.05 && targetX >= -0.05) {
targetX = 0.0;
} else if (targetX > 0.0) {
targetX -= 0.05;
} else {
targetX += 0.05;
}
}
for (; targetZ !== 0.0 && this.world.getCollisionBoxes(this.boundingBox.offset(0.0, -this.stepHeight, targetZ)).length === 0; originalTargetZ = targetZ) {
if (targetZ < 0.05 && targetZ >= -0.05) {
targetZ = 0.0;
} else if (targetZ > 0.0) {
targetZ -= 0.05;
} else {
targetZ += 0.05;
}
}
for (; targetX !== 0.0 && targetZ !== 0.0 && this.world.getCollisionBoxes(this.boundingBox.offset(targetX, -this.stepHeight, targetZ)).length === 0; originalTargetZ = targetZ) {
if (targetX < 0.05 && targetX >= -0.05) {
targetX = 0.0;
} else if (targetX > 0.0) {
targetX -= 0.05;
} else {
targetX += 0.05;
}
originalTargetX = targetX;
if (targetZ < 0.05 && targetZ >= -0.05) {
targetZ = 0.0;
} else if (targetZ > 0.0) {
targetZ -= 0.05;
} else {
targetZ += 0.05;
}
}
}
// Get level tiles as bounding boxes
let boundingBoxList = this.world.getCollisionBoxes(this.boundingBox.expand(targetX, targetY, targetZ));
// Move bounding box
for (let aABB in boundingBoxList) {
targetY = boundingBoxList[aABB].clipYCollide(this.boundingBox, targetY);
}
this.boundingBox.move(0.0, targetY, 0.0);
for (let aABB in boundingBoxList) {
targetX = boundingBoxList[aABB].clipXCollide(this.boundingBox, targetX);
}
this.boundingBox.move(targetX, 0.0, 0.0);
for (let aABB in boundingBoxList) {
targetZ = boundingBoxList[aABB].clipZCollide(this.boundingBox, targetZ);
}
this.boundingBox.move(0.0, 0.0, targetZ);
this.onGround = originalTargetY !== targetY && originalTargetY < 0.0;
// Stop motion on collision
if (originalTargetX !== targetX) {
this.motionX = 0.0;
}
if (originalTargetY !== targetY) {
this.motionY = 0.0;
}
if (originalTargetZ !== targetZ) {
this.motionZ = 0.0;
}
// Update position
this.x = (this.boundingBox.minX + this.boundingBox.maxX) / 2.0;
this.y = this.boundingBox.minY;
this.z = (this.boundingBox.minZ + this.boundingBox.maxZ) / 2.0;
// Horizontal collision?
return originalTargetX !== targetX || originalTargetZ !== targetZ;
}
getEyeHeight() {
return this.sneaking ? 1.50 : 1.62;
}
updateFOVModifier() {
let value = 1.0;
if (this.sprinting) {
value += 1;
}
if (this.flying) {
value *= 1.1;
}
this.setFOVModifier((value - 1.0) * 10);
}
setFOVModifier(fov) {
this.prevFovModifier = this.fovModifier;
this.fovModifier = fov;
this.timeFovChanged = Date.now();
}
getFOVModifier() {
let timePassed = Date.now() - this.timeFovChanged;
let distance = this.prevFovModifier - this.fovModifier;
let duration = 100;
let progress = distance / duration * timePassed;
return timePassed > duration ? this.fovModifier : this.prevFovModifier - progress;
}
getBlockPosX() {
return this.x - (this.x < 0 ? 1 : 0);
}
getBlockPosY() {
return this.y - (this.y < 0 ? 1 : 0);
}
getBlockPosZ() {
return this.z - (this.z < 0 ? 1 : 0);
}
}
@@ -0,0 +1,81 @@
window.BlockRenderer = class {
constructor(worldRenderer) {
this.worldRenderer = worldRenderer;
this.tessellator = new Tessellator();
this.tessellator.bindTexture(worldRenderer.terrainTexture);
}
renderBlock(world, group, typeId, x, y, z) {
let boundingBox = new BoundingBox(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
for (let face = 0; face < 6; face++) {
this.tessellator.startDrawing();
this.renderFace(world, typeId, boundingBox, face, x, y, z);
this.tessellator.draw(group);
}
}
renderFace(world, typeId, boundingBox, face, x, y, z) {
// Vertex mappings
let minX = x + boundingBox.minX;
let minY = y + boundingBox.minY;
let minZ = z + boundingBox.minZ;
let maxX = x + boundingBox.maxX;
let maxY = y + boundingBox.maxY;
let maxZ = z + boundingBox.maxZ;
// UV Mapping
let textureIndex = typeId;
let minU = (textureIndex % 16) / 16.0;
let maxU = minU + (16 / 256);
let minV = parseInt(textureIndex / 16); // TODO Math.round
let maxV = minV + (16 / 256);
// Flip V
minV = 1 - minV;
maxV = 1 - maxV;
if (face === 0) {
this.addBlockCorner(world, face, minX, minY, maxZ, minU, maxV);
this.addBlockCorner(world, face, minX, minY, minZ, minU, minV);
this.addBlockCorner(world, face, maxX, minY, minZ, maxU, minV);
this.addBlockCorner(world, face, maxX, minY, maxZ, maxU, maxV);
}
if (face === 1) {
this.addBlockCorner(world, face, maxX, maxY, maxZ, maxU, maxV);
this.addBlockCorner(world, face, maxX, maxY, minZ, maxU, minV);
this.addBlockCorner(world, face, minX, maxY, minZ, minU, minV);
this.addBlockCorner(world, face, minX, maxY, maxZ, minU, maxV);
}
if (face === 2) {
this.addBlockCorner(world, face, minX, maxY, minZ, maxU, minV);
this.addBlockCorner(world, face, maxX, maxY, minZ, minU, minV);
this.addBlockCorner(world, face, maxX, minY, minZ, minU, maxV);
this.addBlockCorner(world, face, minX, minY, minZ, maxU, maxV);
}
if (face === 3) {
this.addBlockCorner(world, face, minX, maxY, maxZ, minU, minV);
this.addBlockCorner(world, face, minX, minY, maxZ, minU, maxV);
this.addBlockCorner(world, face, maxX, minY, maxZ, maxU, maxV);
this.addBlockCorner(world, face, maxX, maxY, maxZ, maxU, minV);
}
if (face === 4) {
this.addBlockCorner(world, face, minX, maxY, maxZ, maxU, minV);
this.addBlockCorner(world, face, minX, maxY, minZ, minU, minV);
this.addBlockCorner(world, face, minX, minY, minZ, minU, maxV);
this.addBlockCorner(world, face, minX, minY, maxZ, maxU, maxV);
}
if (face === 5) {
this.addBlockCorner(world, face, maxX, minY, maxZ, minU, maxV);
this.addBlockCorner(world, face, maxX, minY, minZ, maxU, maxV);
this.addBlockCorner(world, face, maxX, maxY, minZ, maxU, minV);
this.addBlockCorner(world, face, maxX, maxY, maxZ, minU, minV);
}
}
addBlockCorner(world, face, x, y, z, u, v) {
this.tessellator.addVertexWithUV(x, y, z, u, v);
}
}
@@ -0,0 +1,38 @@
window.Tessellator = class {
constructor() {
this.material = new THREE.MeshBasicMaterial({
color: 0xffffff,
side: THREE.BackSide
});
}
bindTexture(texture) {
this.material.map = texture;
}
startDrawing() {
this.verticies = [];
this.uv = [];
}
addVertexWithUV(x, y, z, u, v) {
this.verticies.push(x);
this.verticies.push(y);
this.verticies.push(z);
this.uv.push(u);
this.uv.push(v);
}
draw(group) {
let geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(this.verticies), 3));
geometry.setAttribute('uv', new THREE.BufferAttribute(new Float32Array(this.uv), 2));
geometry.setIndex(new THREE.BufferAttribute(new Uint32Array([0, 2, 1, 0, 3, 2]), 1));
let mesh = new THREE.Mesh(geometry, this.material);
group.add(mesh);
}
}
@@ -0,0 +1,99 @@
window.WorldRenderer = class {
constructor(minecraft) {
this.minecraft = minecraft;
this.supportWebGL = !!WebGLRenderingContext
&& (!!document.createElement('canvas').getContext('experimental-webgl')
|| !!document.createElement('canvas').getContext('webgl'));
// Create cameras
this.camera = new THREE.PerspectiveCamera(0, 1, 0.001, 10000);
this.camera.rotation.order = 'ZYX';
this.camera.up = new THREE.Vector3(0, 0, 1);
// Create scene
this.scene = new THREE.Scene();
// Create web renderer
this.canvasElement = document.createElement('canvas')
this.webRenderer = this.supportWebGL ? new THREE.WebGLRenderer({
canvas: this.canvasElement,
antialias: true
}) : new THREE.CanvasRenderer({
canvas: this.canvasElement,
antialias: true
});
// Settings
this.webRenderer.shadowMap.enabled = true;
this.webRenderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap
this.webRenderer.autoClear = false;
this.webRenderer.setClearColor(0x000000, 0);
this.webRenderer.clear();
// Add lights
const nightLight = new THREE.AmbientLight(0x888888, 1.0);
this.scene.add(nightLight);
// Load terrain
this.terrainTexture = new THREE.TextureLoader().load( 'src/resources/terrain.png' );
this.terrainTexture.magFilter = THREE.NearestFilter;
this.terrainTexture.minFilter = THREE.LinearFilter;
// Block Renderer
this.blockRenderer = new BlockRenderer(this);
}
render(partialTicks) {
// Setup camera
this.orientCamera(partialTicks);
// Render chunks
this.renderChunks(this, partialTicks);
// Render window
this.webRenderer.render(this.scene, this.camera);
}
orientCamera(partialTicks) {
let player = this.minecraft.player;
// Rotation
this.camera.rotation.y = player.yaw * (Math.PI / 180);
this.camera.rotation.x = player.pitch * (Math.PI / 180);
// Position
let x = player.prevX + (player.x - player.prevX) * partialTicks;
let y = player.prevY + (player.y - player.prevY) * partialTicks;
let z = player.prevZ + (player.z - player.prevZ) * partialTicks;
this.camera.position.set(x, y + player.getEyeHeight(), z);
// Update FOV
this.camera.fov = 85 + player.getFOVModifier();
this.camera.updateProjectionMatrix();
}
renderChunks(renderer, partialTicks) {
let world = this.minecraft.world;
const xKeys = Object.keys(world.chunks)
for (let x = 0; x < xKeys.length; x++) {
let zArray = world.chunks[xKeys[x]];
const zKeys = Object.keys(zArray)
for (let z = 0; z < zKeys.length; z++) {
let chunk = zArray[zKeys[z]];
for (let y = 0; y < chunk.sections.length; y++) {
let section = chunk.sections[y];
if (section.dirty) {
section.rebuild(renderer);
}
}
}
}
}
}
@@ -0,0 +1,36 @@
window.Chunk = class {
constructor(world, x, z) {
this.world = world;
this.x = x;
this.z = z;
this.group = new THREE.Object3D();
// Initialize sections
this.sections = [];
for (let y = 0; y < 16; y++) {
let section = new ChunkSection(world, x, y, z);
this.sections[y] = section;
this.group.add(section.group);
}
}
getSection(y) {
return this.sections[y];
}
rebuild(renderer) {
for (let y = 0; y < this.sections.length; y++) {
this.sections[y].rebuild(renderer);
}
}
queueForRebuild() {
for (let y = 0; y < this.sections.length; y++) {
this.sections[y].queueForRebuild();
}
}
}
@@ -0,0 +1,61 @@
window.ChunkSection = class {
static get SIZE() {
return 16;
}
constructor(world, x, y, z) {
this.world = world;
this.x = x;
this.y = y;
this.z = z;
this.group = new THREE.Object3D();
this.dirty = true;
this.blocks = [];
for (let x = 0; x < ChunkSection.SIZE; x++) {
for (let y = 0; y < ChunkSection.SIZE; y++) {
for (let z = 0; z < ChunkSection.SIZE; z++) {
this.setBlockAt(x, y, z, 0);
}
}
}
}
rebuild(renderer) {
this.dirty = false;
this.group.clear();
for (let x = 0; x < ChunkSection.SIZE; x++) {
for (let y = 0; y < ChunkSection.SIZE; y++) {
for (let z = 0; z < ChunkSection.SIZE; z++) {
let typeId = this.getBlockAt(x, y, z);
if (typeId !== 0) {
let absoluteX = this.x * ChunkSection.SIZE + x;
let absoluteY = this.y * ChunkSection.SIZE + y;
let absoluteZ = this.z * ChunkSection.SIZE + z;
renderer.blockRenderer.renderBlock(this.world, this.group, typeId, absoluteX, absoluteY, absoluteZ);
}
}
}
}
}
getBlockAt(x, y, z) {
let index = y << 8 | z << 4 | x;
return this.blocks[index];
}
setBlockAt(x, y, z, typeId) {
let index = y << 8 | z << 4 | x;
this.blocks[index] = typeId;
}
queueForRebuild() {
this.dirty = true;
}
}
+106
View File
@@ -0,0 +1,106 @@
window.World = class {
static get TOTAL_HEIGHT() {
return ChunkSection.SIZE * 16 - 1;
}
constructor() {
this.group = new THREE.Object3D();
this.chunks = [];
for (let x = -16; x < 16; x++) {
for (let z = -16; z < 16; z++) {
this.setBlockAt(x, 0, z, 1);
}
}
}
getChunkAtBlock(x, y, z) {
let chunk = this.getChunkAt(x >> 4, z >> 4);
return y < 0 || y > World.TOTAL_HEIGHT ? null : chunk.getSection(y >> 4);
}
getCollisionBoxes(aabb) {
let boundingBoxList = [];
let minX = MathHelper.floor_double(aabb.minX);
let maxX = MathHelper.floor_double(aabb.maxX + 1.0);
let minY = MathHelper.floor_double(aabb.minY);
let maxY = MathHelper.floor_double(aabb.maxY + 1.0);
let minZ = MathHelper.floor_double(aabb.minZ);
let maxZ = MathHelper.floor_double(aabb.maxZ + 1.0);
for (let x = minX; x < maxX; x++) {
for (let y = minY; y < maxY; y++) {
for (let z = minZ; z < maxZ; z++) {
if (this.isSolidBlockAt(x, y, z)) {
boundingBoxList.push(new BoundingBox(x, y, z, x + 1, y + 1, z + 1));
}
}
}
}
return boundingBoxList;
}
isSolidBlockAt(x, y, z) {
let typeId = this.getBlockAt(x, y, z);
return typeId !== 0; /* && Block.getById(typeId).isSolid();*/
}
setBlockAt(x, y, z, type) {
let chunkSection = this.getChunkAtBlock(x, y, z);
if (chunkSection != null) {
chunkSection.setBlockAt(x & 15, y & 15, z & 15, type);
}
this.blockChanged(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);
}
getChunkAt(x, z) {
let zArray = this.chunks[x];
if (typeof zArray === 'undefined') {
zArray = this.chunks[x] = [];
}
let chunk = zArray[z];
if (typeof chunk === 'undefined') {
chunk = new Chunk(this, x, z);
this.chunks[x][z] = chunk;
this.group.add(chunk.group);
}
return chunk;
}
blockChanged(x, y, z) {
this.setDirty(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1);
}
setDirty(minX, minY, minZ, maxX, maxY, maxZ) {
// To chunk coordinates
minX = minX >> 4;
maxX = maxX >> 4;
minY = minY >> 4;
maxY = maxY >> 4;
minZ = minZ >> 4;
maxZ = maxZ >> 4;
// Minimum and maximum y
minY = Math.max(0, minY);
maxY = Math.min(15, maxY);
for (let x = minX; x <= maxX; x++) {
for (let y = minY; y <= maxY; y++) {
for (let z = minZ; z <= maxZ; z++) {
this.getChunkAt(x, y, z).queueForRebuild();
}
}
}
}
}
+265
View File
@@ -0,0 +1,265 @@
window.BoundingBox = class {
/**
* Bounding box
*
* @param minX Minimum x side
* @param minY Minimum y side
* @param minZ Minimum z side
* @param maxX Maximum x side
* @param maxY Maximum y side
* @param maxZ Maximum z side
*/
constructor(minX, minY, minZ, maxX, maxY, maxZ) {
this.epsilon = 0.0;
this.minX = minX;
this.minY = minY;
this.minZ = minZ;
this.maxX = maxX;
this.maxY = maxY;
this.maxZ = maxZ;
}
/**
* Copy the current bounding box object
*
* @return Clone of the bounding box
*/
clone() {
return new BoundingBox(this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ);
}
/**
* Expand the bounding box. Positive and negative numbers controls which side of the box should grow.
*
* @param x Amount to expand the minX or maxX
* @param y Amount to expand the minY or maxY
* @param z Amount to expand the minZ or maxZ
* @return The expanded bounding box
*/
expand(x, y, z) {
let minX = this.minX;
let minY = this.minY;
let minZ = this.minZ;
let maxX = this.maxX;
let maxY = this.maxY;
let maxZ = this.maxZ;
// Handle expanding of min/max x
if (x < 0.0) {
minX += x;
} else {
maxX += x;
}
// Handle expanding of min/max y
if (y < 0.0) {
minY += y;
} else {
maxY += y;
}
// Handle expanding of min/max z
if (z < 0.0) {
minZ += z;
} else {
maxZ += z;
}
// Create new bounding box
return new BoundingBox(minX, minY, minZ, maxX, maxY, maxZ);
}
/**
* Expand the bounding box on both sides.
* The center is always fixed when using grow.
*
* @param x
* @param y
* @param z
* @return
*/
grow(x, y, z) {
return new BoundingBox(
this.minX - x,
this.minY - y,
this.minZ - z,
this.maxX + x,
this.maxY + y,
this.maxZ + z
);
}
/**
* Check for collision on the X axis
*
* @param otherBoundingBox The other bounding box that is colliding with the this one.
* @param x Position on the X axis that is colliding
* @return Returns the corrected x position that collided.
*/
clipXCollide(otherBoundingBox, x) {
// Check if the boxes are colliding on the Y axis
if (otherBoundingBox.maxY <= this.minY || otherBoundingBox.minY >= this.maxY) {
return x;
}
// Check if the boxes are colliding on the Z axis
if (otherBoundingBox.maxZ <= this.minZ || otherBoundingBox.minZ >= this.maxZ) {
return x;
}
// Check for collision if the X axis of the current box is bigger
if (x > 0.0 && otherBoundingBox.maxX <= this.minX) {
let max = this.minX - otherBoundingBox.maxX - this.epsilon;
if (max < x) {
x = max;
}
}
// Check for collision if the X axis of the current box is smaller
if (x < 0.0 && otherBoundingBox.minX >= this.maxX) {
let max = this.maxX - otherBoundingBox.minX + this.epsilon;
if (max > x) {
x = max;
}
}
return x;
}
/**
* Check for collision on the Y axis
*
* @param otherBoundingBox The other bounding box that is colliding with the this one.
* @param y Position on the X axis that is colliding
* @return Returns the corrected x position that collided.
*/
clipYCollide(otherBoundingBox, y) {
// Check if the boxes are colliding on the X axis
if (otherBoundingBox.maxX <= this.minX || otherBoundingBox.minX >= this.maxX) {
return y;
}
// Check if the boxes are colliding on the Z axis
if (otherBoundingBox.maxZ <= this.minZ || otherBoundingBox.minZ >= this.maxZ) {
return y;
}
// Check for collision if the Y axis of the current box is bigger
if (y > 0.0 && otherBoundingBox.maxY <= this.minY) {
let max = this.minY - otherBoundingBox.maxY - this.epsilon;
if (max < y) {
y = max;
}
}
// Check for collision if the Y axis of the current box is bigger
if (y < 0.0 && otherBoundingBox.minY >= this.maxY) {
let max = this.maxY - otherBoundingBox.minY + this.epsilon;
if (max > y) {
y = max;
}
}
return y;
}
/**
* Check for collision on the Y axis
*
* @param otherBoundingBox The other bounding box that is colliding with the this one.
* @param z Position on the X axis that is colliding
* @return Returns the corrected x position that collided.
*/
clipZCollide(otherBoundingBox, z) {
// Check if the boxes are colliding on the X axis
if (otherBoundingBox.maxX <= this.minX || otherBoundingBox.minX >= this.maxX) {
return z;
}
// Check if the boxes are colliding on the Y axis
if (otherBoundingBox.maxY <= this.minY || otherBoundingBox.minY >= this.maxY) {
return z;
}
// Check for collision if the Z axis of the current box is bigger
if (z > 0.0 && otherBoundingBox.maxZ <= this.minZ) {
let max = this.minZ - otherBoundingBox.maxZ - this.epsilon;
if (max < z) {
z = max;
}
}
// Check for collision if the Z axis of the current box is bigger
if (z < 0.0 && otherBoundingBox.minZ >= this.maxZ) {
let max = this.maxZ - otherBoundingBox.minZ + this.epsilon;
if (max > z) {
z = max;
}
}
return z;
}
/**
* Check if the two boxes are intersecting/overlapping
*
* @param otherBoundingBox The other bounding box that could intersect
* @return The two boxes are overlapping
*/
intersects(otherBoundingBox) {
// Check on X axis
if (otherBoundingBox.maxX <= this.minX || otherBoundingBox.minX >= this.maxX) {
return false;
}
// Check on Y axis
if (otherBoundingBox.maxY <= this.minY || otherBoundingBox.minY >= this.maxY) {
return false;
}
// Check on Z axis
return (!(otherBoundingBox.maxZ <= this.minZ)) && (!(otherBoundingBox.minZ >= this.maxZ));
}
/**
* Move the bounding box relative.
*
* @param x Relative offset x
* @param y Relative offset y
* @param z Relative offset z
*/
move(x, y, z) {
this.minX += x;
this.minY += y;
this.minZ += z;
this.maxX += x;
this.maxY += y;
this.maxZ += z;
}
/**
* Create a new bounding box with the given offset
*
* @param x Relative offset x
* @param y Relative offset x
* @param z Relative offset x
* @return New bounding box with the given offset relative to this bounding box
*/
offset(x, y, z) {
return new BoundingBox(
this.minX + x,
this.minY + y,
this.minZ + z,
this.maxX + x,
this.maxY + y,
this.maxZ + z
);
}
}
+20
View File
@@ -0,0 +1,20 @@
window.Keyboard = class {
static state = {};
static create() {
window.addEventListener('keydown', function (event) {
Keyboard.state[event.code] = true;
//console.log("Key " + event.code + " down");
});
window.addEventListener('keyup', function (event) {
delete Keyboard.state[event.code];
//console.log("Key " + event.code + " up");
});
};
static isKeyDown(key) {
return Keyboard.state[key];
}
}
+11
View File
@@ -0,0 +1,11 @@
window.MathHelper = class {
/**
* Returns the greatest integer less than or equal to the double argument
*/
static floor_double(value) {
let i = parseInt(value);
return value < i ? i - 1 : i;
}
}
+84
View File
@@ -0,0 +1,84 @@
window.Timer = class {
/**
* Timer to control the tick speed independently of the framerate
*
* @param ticksPerSecond Amount of ticks per second
*/
constructor(ticksPerSecond) {
this.MS_PER_SECOND = 1000;
this.MAX_MS_PER_UPDATE = 1000;
this.MAX_TICKS_PER_UPDATE = 100;
/**
* Amount of ticks per second
*/
this.ticksPerSecond = ticksPerSecond;
/**
* Last time updated in nano seconds
*/
this.lastTime = this._nanoTime();
/**
* Scale the tick speed
*/
this.timeScale = 1.0;
/**
* Framerate of the advanceTime update
*/
this.fps = 0.0;
/**
* Passed time since last game update
*/
this.passedTime = 0.0;
/**
* The amount of ticks for the current game update.
* It's the passed time as an integer
*/
this.ticks = 0;
/**
* The overflow of the current tick, caused by casting the passed time to an integer
*/
this.partialTicks = 0;
}
/**
* This function calculates the amount of ticks required to reach the ticksPerSecond.
* Call this function in the main render loop of the game
*/
advanceTime() {
let now = this._nanoTime();
let passedMs = now - this.lastTime;
// Store nano time of this update
this.lastTime = now;
// Maximum and minimum
passedMs = Math.max(0, passedMs);
passedMs = Math.min(this.MAX_MS_PER_UPDATE, passedMs);
// Calculate fps
this.fps = this.MS_PER_SECOND / passedMs;
// Calculate passed time and ticks
this.passedTime += passedMs * this.timeScale * this.ticksPerSecond / this.MS_PER_SECOND;
this.ticks = parseInt(this.passedTime);
// Maximum ticks per update
this.ticks = Math.min(this.MAX_TICKS_PER_UPDATE, this.ticks);
// Calculate the overflow of the current tick
this.passedTime -= this.ticks;
this.partialTicks = this.passedTime;
}
_nanoTime() {
return Date.now();
}
}