diff --git a/src/net/minecraft/client/GameWindow.js b/src/net/minecraft/client/GameWindow.js index adfcd7e..83bc970 100644 --- a/src/net/minecraft/client/GameWindow.js +++ b/src/net/minecraft/client/GameWindow.js @@ -4,8 +4,13 @@ window.GameWindow = class { this.renderer = renderer; this.canvasWrapperId = canvasWrapperId; + this.mouseMotionX = 0; + this.mouseMotionY = 0; + this.mouseLocked = false; + // Add web renderer canvas to wrapper - document.getElementById(this.canvasWrapperId).appendChild(renderer.canvasElement); + let canvas = renderer.canvasElement; + document.getElementById(this.canvasWrapperId).appendChild(canvas); // Init this.initialize(); @@ -13,10 +18,23 @@ window.GameWindow = class { // On resize let scope = this; window.addEventListener('resize', _ => scope.initialize(), 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); } - initialize() { + // Create keyboard + Keyboard.create(); + // Get canvas size let canvasElement = document.getElementById(this.canvasWrapperId); this.canvasWidth = canvasElement.offsetWidth; @@ -27,4 +45,13 @@ window.GameWindow = class { 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; + } } \ No newline at end of file diff --git a/src/net/minecraft/client/Minecraft.js b/src/net/minecraft/client/Minecraft.js index 7352601..1e48b0d 100644 --- a/src/net/minecraft/client/Minecraft.js +++ b/src/net/minecraft/client/Minecraft.js @@ -11,9 +11,14 @@ window.Minecraft = class { 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(); } @@ -27,12 +32,12 @@ window.Minecraft = class { requestAnimationFrame(function () { if (scope.running) { scope.requestNextFrame(); - scope.onRender(); + scope.onLoop(); } }); } - onRender() { + onLoop() { // Update the timer this.timer.advanceTime(); @@ -42,22 +47,34 @@ window.Minecraft = class { } // Render the game - this.worldRenderer.render(this.timer.partialTicks); + 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"); + //console.log(this.frames + " fps"); this.lastTime += 1000; this.frames = 0; } } - onTick() { + 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(); } } \ No newline at end of file diff --git a/src/net/minecraft/client/entity/Player.js b/src/net/minecraft/client/entity/Player.js new file mode 100644 index 0000000..e05a57f --- /dev/null +++ b/src/net/minecraft/client/entity/Player.js @@ -0,0 +1,476 @@ +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, 15, 0); + } + + 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); + } + +} \ No newline at end of file diff --git a/src/net/minecraft/client/render/WorldRenderer.js b/src/net/minecraft/client/render/WorldRenderer.js index 6784b2c..e070946 100644 --- a/src/net/minecraft/client/render/WorldRenderer.js +++ b/src/net/minecraft/client/render/WorldRenderer.js @@ -8,8 +8,8 @@ window.WorldRenderer = class { || !!document.createElement('canvas').getContext('webgl')); // Create cameras - this.camera = new THREE.PerspectiveCamera(85, 1, 1, 10000); - this.camera.position.set(0, 3, 0); + this.camera = new THREE.PerspectiveCamera(85, 1, 0.001, 10000); + this.camera.rotation.order = 'ZYX'; this.camera.up = new THREE.Vector3(0, 0, 1); // Create scene @@ -36,6 +36,31 @@ window.WorldRenderer = class { } render(partialTicks) { + // Setup camera + this.orientCamera(partialTicks); + + // Render chunks + this.renderChunks(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); + } + + renderChunks(partialTicks) { let world = this.minecraft.world; const xKeys = Object.keys(world.chunks) @@ -56,10 +81,5 @@ window.WorldRenderer = class { } } } - - // Render window - this.webRenderer.render(this.scene, this.camera); } - - } \ No newline at end of file diff --git a/src/net/minecraft/client/world/ChunkSection.js b/src/net/minecraft/client/world/ChunkSection.js index 403d8ce..aabb776 100644 --- a/src/net/minecraft/client/world/ChunkSection.js +++ b/src/net/minecraft/client/world/ChunkSection.js @@ -45,7 +45,7 @@ window.ChunkSection = class { }); let cube = new THREE.Mesh(geometry, material); - cube.position.set(absoluteX - 0.5, absoluteY - 0.5, absoluteZ - 0.5); + cube.position.set(absoluteX + 0.5, absoluteY + 0.5, absoluteZ + 0.5); this.group.add(cube); } diff --git a/src/net/minecraft/client/world/World.js b/src/net/minecraft/client/world/World.js index b7d7db9..0236cfb 100644 --- a/src/net/minecraft/client/world/World.js +++ b/src/net/minecraft/client/world/World.js @@ -15,6 +15,38 @@ window.World = class { } } + 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 = Math.floor(aabb.minX) - 1; + let maxX = Math.ceil(aabb.maxX) + 1; + let minY = Math.floor(aabb.minY) - 1; + let maxY = Math.ceil(aabb.maxY) + 1; + let minZ = Math.floor(aabb.minZ) - 1; + let maxZ = Math.ceil(aabb.maxZ) + 1; + + 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) { @@ -24,6 +56,12 @@ window.World = class { 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') { @@ -65,10 +103,4 @@ window.World = class { } } - getChunkAtBlock(x, y, z) { - let chunk = this.getChunkAt(x >> 4, z >> 4); - return y < 0 || y > World.TOTAL_HEIGHT ? null : chunk.getSection(y >> 4); - } - - } \ No newline at end of file diff --git a/src/net/minecraft/util/BoundingBox.js b/src/net/minecraft/util/BoundingBox.js new file mode 100644 index 0000000..b491c83 --- /dev/null +++ b/src/net/minecraft/util/BoundingBox.js @@ -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 + ); + } +} \ No newline at end of file diff --git a/src/net/minecraft/util/Keyboard.js b/src/net/minecraft/util/Keyboard.js new file mode 100644 index 0000000..1651882 --- /dev/null +++ b/src/net/minecraft/util/Keyboard.js @@ -0,0 +1,14 @@ +window.Keyboard = class { + + static state = {}; + + static create() { + window.addEventListener('keyup', (e) => Keyboard.state[e.code] = false); + window.addEventListener('keydown', (e) => Keyboard.state[e.code] = true); + }; + + static isKeyDown(key) { + return Keyboard.state[key]; + } + +} \ No newline at end of file diff --git a/src/start.js b/src/start.js index b1b6f86..02c90e7 100644 --- a/src/start.js +++ b/src/start.js @@ -48,10 +48,13 @@ loadScripts([ // Minecraft Source "src/net/minecraft/util/Timer.js", + "src/net/minecraft/util/BoundingBox.js", + "src/net/minecraft/util/Keyboard.js", "src/net/minecraft/client/GameWindow.js", "src/net/minecraft/client/world/ChunkSection.js", "src/net/minecraft/client/world/Chunk.js", "src/net/minecraft/client/world/World.js", + "src/net/minecraft/client/entity/Player.js", "src/net/minecraft/client/Minecraft.js", "src/net/minecraft/client/render/WorldRenderer.js" ]).then(() => {