diff --git a/src/js/net/minecraft/client/GameWindow.js b/src/js/net/minecraft/client/GameWindow.js index 9d0a202..6b441b2 100644 --- a/src/js/net/minecraft/client/GameWindow.js +++ b/src/js/net/minecraft/client/GameWindow.js @@ -42,7 +42,7 @@ window.GameWindow = class { // Handle mouse click on screen if (!(minecraft.currentScreen === null)) { - minecraft.currentScreen.mouseClicked(event.x, event.y, event.code); + minecraft.currentScreen.mouseClicked(event.x / scope.scaleFactor, event.y / scope.scaleFactor, event.code); } }, false); @@ -71,14 +71,14 @@ window.GameWindow = class { setTimeout(function () { window.focus(); - scope.renderer.canvasElement.requestPointerLock(); + scope.renderer.canvas.requestPointerLock(); }, 0); } loadRenderer(renderer) { this.renderer = renderer; - let canvas = this.renderer.canvasElement; + let canvas = this.renderer.canvas; // Add web renderer canvas to wrapper this.wrapper.appendChild(canvas); @@ -96,8 +96,15 @@ window.GameWindow = class { } updateWindowSize() { + this.scaleFactor = 1; this.width = this.wrapper.offsetWidth; this.height = this.wrapper.offsetHeight; + + for (this.scaleFactor = 1; this.width / (this.scaleFactor + 1) >= 320 && this.height / (this.scaleFactor + 1) >= 240; this.scaleFactor++) { + } + + this.width = this.width / this.scaleFactor; + this.height = this.height / this.scaleFactor; } onResize() { @@ -106,7 +113,7 @@ window.GameWindow = class { // Adjust camera this.renderer.camera.aspect = this.width / this.height; this.renderer.camera.updateProjectionMatrix(); - this.renderer.webRenderer.setSize(this.width, this.height); + this.renderer.webRenderer.setSize(this.width * this.scaleFactor, this.height * this.scaleFactor); // Reinitialize gui this.renderer.initializeGui(); @@ -118,19 +125,15 @@ window.GameWindow = class { } onFocusChanged() { - this.mouseLocked = document.pointerLockElement === this.renderer.canvasElement; - - if (!this.mouseLocked && this.minecraft.currentScreen === null) { - this.minecraft.displayScreen(new GuiIngameMenu()); - } + this.mouseLocked = document.pointerLockElement === this.renderer.canvas; } onMouseMove(event) { this.mouseMotionX = event.movementX; this.mouseMotionY = -event.movementY; - this.mouseX = event.clientX; - this.mouseY = event.clientY; + this.mouseX = event.clientX / this.scaleFactor; + this.mouseY = event.clientY / this.scaleFactor; if (!this.mouseLocked && this.minecraft.currentScreen === null) { this.requestFocus(); diff --git a/src/js/net/minecraft/client/Minecraft.js b/src/js/net/minecraft/client/Minecraft.js index d5550bc..acb051e 100644 --- a/src/js/net/minecraft/client/Minecraft.js +++ b/src/js/net/minecraft/client/Minecraft.js @@ -13,7 +13,7 @@ window.Minecraft = class { this.timer = new Timer(20); // Create current screen and overlay - this.ingameOverlay = new IngameOverlay(this.window); + this.ingameOverlay = new IngameOverlay(this, this.window); // Display loading screen this.loadingScreen = new GuiLoadingScreen(); @@ -114,8 +114,10 @@ window.Minecraft = class { // Initialize new screen if (screen === null) { + this.worldRenderer.webRenderer.setPixelRatio(1); this.window.requestFocus(); } else { + this.worldRenderer.webRenderer.setPixelRatio(1 / this.window.scaleFactor); screen.setup(this, this.window.width, this.window.height); } } diff --git a/src/js/net/minecraft/client/gui/Gui.js b/src/js/net/minecraft/client/gui/Gui.js index 7d1afd3..3cc82b9 100644 --- a/src/js/net/minecraft/client/gui/Gui.js +++ b/src/js/net/minecraft/client/gui/Gui.js @@ -1,7 +1,5 @@ window.Gui = class { - static FONT = "normal 20px Minecraftia"; - drawRect(stack, left, top, right, bottom, color, alpha = 1) { stack.save(); stack.fillStyle = color; @@ -10,40 +8,27 @@ window.Gui = class { stack.restore(); } - drawCenteredString(stack, string, x, y, color = 'white') { - this._drawString(stack, string, x + this.getStringWidth(stack, string) / 2, y, color, 1); + drawCenteredString(stack, string, x, y, color = -1) { + FontRenderer.INSTANCE.drawString(stack, string, x - this.getStringWidth(stack, string) / 2, y, color); } - drawString(stack, string, x, y, color = 'white') { - this._drawString(stack, string, x, y, color, 0); - } - - _drawString(stack, string, x, y, color, alignment) { - stack.font = Gui.FONT; - stack.fillStyle = 'black'; - stack.textAlign = alignment === 0 ? "center" : alignment < 0 ? "left" : "right"; - stack.fillText(string, x + 2, y + 2); - stack.fillStyle = color; - stack.fillText(string, x, y); + drawString(stack, string, x, y, color = -1) { + FontRenderer.INSTANCE.drawString(stack, string, x, y, color); } getStringWidth(stack, string) { - stack.font = Gui.FONT; - return stack.measureText(string).width; + return FontRenderer.INSTANCE.getStringWidth(string); } drawTexture(stack, texture, x, y, width, height, alpha = 1.0) { - this.drawSprite(stack, texture, 0, 0, 256, 256, x, y, width, height, alpha); + Gui.drawSprite(stack, texture, 0, 0, 256, 256, x, y, width, height, alpha); } drawSprite(stack, texture, spriteX, spriteY, spriteWidth, spriteHeight, x, y, width, height, alpha = 1.0) { - stack.save(); - stack.globalAlpha = alpha; - stack.drawImage(texture, spriteX, spriteY, spriteWidth, spriteHeight, x, y, width, height); - stack.restore(); + Gui.drawSprite(stack, texture, spriteX, spriteY, spriteWidth, spriteHeight, x, y, width, height, alpha); } - drawBackground(stack, texture, width, height, scale = 8) { + drawBackground(stack, texture, width, height, scale = 2) { let pattern = stack.createPattern(texture, "repeat"); stack.save(); stack.filter = "brightness(50%)"; @@ -54,9 +39,17 @@ window.Gui = class { stack.restore(); } - static loadTexture(path) { + static drawSprite(stack, texture, spriteX, spriteY, spriteWidth, spriteHeight, x, y, width, height, alpha = 1.0) { + stack.save(); + stack.globalAlpha = alpha; + stack.drawImage(texture, spriteX, spriteY, spriteWidth, spriteHeight, x, y, width, height); + stack.restore(); + } + + static loadTexture(path, callback) { let img = new Image(); - img.src = path; + img.src = "src/resources/" + path; + img.onload = callback; return img; } diff --git a/src/js/net/minecraft/client/gui/IngameOverlay.js b/src/js/net/minecraft/client/gui/IngameOverlay.js index 723ef3a..f5271c9 100644 --- a/src/js/net/minecraft/client/gui/IngameOverlay.js +++ b/src/js/net/minecraft/client/gui/IngameOverlay.js @@ -1,18 +1,21 @@ window.IngameOverlay = class extends Gui { - constructor(window) { + constructor(minecraft, window) { super(); + this.minecraft = minecraft; this.window = window; this.textureCrosshair = Gui.loadTexture("icons.png"); } render(stack, mouseX, mouseY, partialTicks) { - this.renderCrosshair(stack, this.window.width / 2, this.window.height / 2) + if (this.minecraft.hasInGameFocus()) { + this.renderCrosshair(stack, this.window.width / 2, this.window.height / 2) + } } renderCrosshair(stack, x, y) { - let size = 15 * 4; + let size = 15; this.drawSprite(stack, this.textureCrosshair, 0, 0, 15, 15, x - size / 2, y - size / 2, size, size, 0.6); } diff --git a/src/js/net/minecraft/client/gui/screens/GuiIngameMenu.js b/src/js/net/minecraft/client/gui/screens/GuiIngameMenu.js index fdab5eb..6a70e34 100644 --- a/src/js/net/minecraft/client/gui/screens/GuiIngameMenu.js +++ b/src/js/net/minecraft/client/gui/screens/GuiIngameMenu.js @@ -8,7 +8,7 @@ window.GuiIngameMenu = class extends GuiScreen { super.init(); let scope = this; - this.buttonList.push(new GuiButton("Back to game", this.width / 2 - 100 * 3, this.height / 2 - 50 * 3, 200 * 3, 20 * 3, function () { + this.buttonList.push(new GuiButton("Back to game", this.width / 2 - 100, this.height / 2 - 20, 200, 20, function () { scope.minecraft.displayScreen(null); })); } @@ -18,7 +18,7 @@ window.GuiIngameMenu = class extends GuiScreen { this.drawRect(stack, 0, 0, this.width, this.height, 'black', 0.6); // Title - this.drawCenteredString(stack, "Game paused", this.width / 2, 100); + this.drawCenteredString(stack, "Game paused", this.width / 2, 50); super.drawScreen(stack, mouseX, mouseY, partialTicks); } diff --git a/src/js/net/minecraft/client/gui/screens/GuiLoadingScreen.js b/src/js/net/minecraft/client/gui/screens/GuiLoadingScreen.js index 5aae55f..c66ca5d 100644 --- a/src/js/net/minecraft/client/gui/screens/GuiLoadingScreen.js +++ b/src/js/net/minecraft/client/gui/screens/GuiLoadingScreen.js @@ -11,10 +11,10 @@ window.GuiLoadingScreen = class extends GuiScreen { this.drawBackground(stack, this.textureBackground, this.width, this.height); // Render title - this.drawString(stack, this.title, this.width / 2, this.height / 2 - 20, '#FFFFFF'); + this.drawCenteredString(stack, this.title, this.width / 2, this.height / 2 - 20); - let progressWidth = 300; - let progressHeight = 5; + let progressWidth = 100; + let progressHeight = 2; // Render background of progress this.drawRect( diff --git a/src/js/net/minecraft/client/gui/widgets/GuiButton.js b/src/js/net/minecraft/client/gui/widgets/GuiButton.js index 952e373..de84cb7 100644 --- a/src/js/net/minecraft/client/gui/widgets/GuiButton.js +++ b/src/js/net/minecraft/client/gui/widgets/GuiButton.js @@ -16,7 +16,7 @@ window.GuiButton = class extends Gui { render(stack, mouseX, mouseY, partialTicks) { let mouseOver = this.isMouseOver(mouseX, mouseY); this.drawSprite(stack, GuiButton.textureGui, 0, mouseOver ? 40 : 20, 200, 20, this.x, this.y, this.width, this.height); - this.drawCenteredString(stack, this.string, this.x + this.width / 2, this.y + this.height / 2 + 17); + this.drawCenteredString(stack, this.string, this.x + this.width / 2, this.y + this.height / 2 - 5); } mouseClicked(mouseX, mouseY, mouseButton) { diff --git a/src/js/net/minecraft/client/render/FontRenderer.js b/src/js/net/minecraft/client/render/FontRenderer.js new file mode 100644 index 0000000..c5bfa6a --- /dev/null +++ b/src/js/net/minecraft/client/render/FontRenderer.js @@ -0,0 +1,139 @@ +window.FontRenderer = class { + + static BITMAP_SIZE = 16; + static FIELD_SIZE = 8; + + static COLOR_CODE_INDEX_LOOKUP = "0123456789abcdef"; + + constructor() { + this.charWidths = []; + + let scope = this; + this.texture = Gui.loadTexture("font.png", function () { + let bitMap = scope.createBitMap(scope.texture); + + // Calculate character width + for (let i = 0; i < 128; i++) { + scope.charWidths[i] = scope.calculateCharacterWidthAt(bitMap, i % FontRenderer.BITMAP_SIZE, Math.floor(i / FontRenderer.BITMAP_SIZE)) + 2; + } + }); + } + + calculateCharacterWidthAt(bitMap, indexX, indexY) { + // We scan the bitmap field from right to left + for (let x = indexX * FontRenderer.FIELD_SIZE + FontRenderer.FIELD_SIZE - 1; x >= indexX * FontRenderer.FIELD_SIZE; x--) { + + // Scan this column from top to bottom + for (let y = indexY * FontRenderer.FIELD_SIZE; y < indexY * FontRenderer.FIELD_SIZE + FontRenderer.FIELD_SIZE; y++) { + + let i = (x + y * this.texture.width) * 4; + + let red = bitMap[i]; + let green = bitMap[i + 1]; + let blue = bitMap[i + 2]; + let alpha = bitMap[i + 3]; + + // Return width if there is a white pixel + if (red !== 0 || green !== 0 || blue !== 0 || alpha !== 0) { + return x - indexX * FontRenderer.FIELD_SIZE; + } + } + } + + // Empty field width (Could be a space character) + return 2; + } + + drawString(stack, string, x, y, color = -1) { + this.drawStringRaw(stack, string, x + 1, y + 1, (color & 0xFCFCFC) >> 2, true); + this.drawStringRaw(stack, string, x, y, color, false); + } + + drawStringRaw(stack, string, x, y, color = -1, isShadow = true) { + stack.save(); + + if (isShadow) { + stack.filter = "brightness(0%)"; + } + + // For each character + for (let i = 0; i < string.length; i++) { + let character = string[i]; + let code = string[i].charCodeAt(0); + + // Handle color codes if character is & + if (character === '&' && i !== string.length - 1) { + // Get the next character + let nextCharacter = string[i + 1]; + + // Change color of string + //this.setColor(this.getColorOfCharacter(nextCharacter), isShadow); + + // Skip the color code for rendering + i += 1; + continue; + } + + // Get character offset in bitmap + let textureOffsetX = code % FontRenderer.BITMAP_SIZE * FontRenderer.FIELD_SIZE; + let textureOffsetY = Math.floor(code / FontRenderer.BITMAP_SIZE) * FontRenderer.FIELD_SIZE; + + // Draw character + Gui.drawSprite( + stack, + this.texture, + textureOffsetX, textureOffsetY, + FontRenderer.FIELD_SIZE, FontRenderer.FIELD_SIZE, + x, y, + FontRenderer.FIELD_SIZE, FontRenderer.FIELD_SIZE + ); + + // Increase drawing cursor + x += this.charWidths[code]; + } + + stack.restore(); + } + + getColorOfCharacter(character) { + let index = FontRenderer.COLOR_CODE_INDEX_LOOKUP.indexOf(character); + let brightness = (index & 0x8) * 8; + + // Convert index to RGB + let b = (index & 0x1) * 191 + brightness; + let g = ((index & 0x2) >> 1) * 191 + brightness; + let r = ((index & 0x4) >> 2) * 191 + brightness; + + return r << 16 | g << 8 | b; + } + + getStringWidth(string) { + let length = 0; + + // For each character + for (let i = 0; i < string.length; i++) { + + // Check for color code + if (string[i] === '&') { + // Skip the next character + i++; + } else { + // Add the width of the character + let code = string[i].charCodeAt(0); + length += this.charWidths[code]; + } + } + return length; + } + + + createBitMap(img) { + let canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + canvas.getContext('2d').drawImage(img, 0, 0, img.width, img.height); + return canvas.getContext('2d').getImageData(0, 0, img.width, img.height).data; + } +} + +window.FontRenderer.INSTANCE = new FontRenderer(); diff --git a/src/js/net/minecraft/client/render/WorldRenderer.js b/src/js/net/minecraft/client/render/WorldRenderer.js index 7d70a09..900fa3d 100644 --- a/src/js/net/minecraft/client/render/WorldRenderer.js +++ b/src/js/net/minecraft/client/render/WorldRenderer.js @@ -7,10 +7,6 @@ window.WorldRenderer = class { this.window = window; this.chunkSectionUpdateQueue = []; - this.supportWebGL = !!WebGLRenderingContext - && (!!document.createElement('canvas').getContext('experimental-webgl') - || !!document.createElement('canvas').getContext('webgl')); - // Load terrain this.terrainTexture = new THREE.TextureLoader().load('src/resources/terrain.png'); this.terrainTexture.magFilter = THREE.NearestFilter; @@ -41,12 +37,9 @@ window.WorldRenderer = class { this.scene.matrixAutoUpdate = false; // Create web renderer - this.canvasElement = document.createElement('canvas') - this.webRenderer = this.supportWebGL ? new THREE.WebGLRenderer({ - canvas: this.canvasElement, - antialias: false - }) : new THREE.CanvasRenderer({ - canvas: this.canvasElement, + this.canvas = document.createElement('canvas') + this.webRenderer = new THREE.WebGLRenderer({ + canvas: this.canvas, antialias: false }); @@ -70,12 +63,15 @@ window.WorldRenderer = class { // Get context stack of 2d canvas this.stack2d = this.canvas2d.getContext('2d'); + this.stack2d.webkitImageSmoothingEnabled = false; + this.stack2d.mozImageSmoothingEnabled = false; this.stack2d.imageSmoothingEnabled = false; // Create texture from rendered graphics. this.frameBuffer = new THREE.Texture(this.canvas2d) this.frameBuffer.needsUpdate = true; this.frameBuffer.minFilter = THREE.LinearFilter; + this.frameBuffer.maxFilter = THREE.LinearFilter; // Create HUD material. let material = new THREE.MeshBasicMaterial({ diff --git a/src/js/net/minecraft/client/world/World.js b/src/js/net/minecraft/client/world/World.js index 4c64e1c..6540cd2 100644 --- a/src/js/net/minecraft/client/world/World.js +++ b/src/js/net/minecraft/client/world/World.js @@ -22,7 +22,7 @@ window.World = class { i--; scope.lightUpdateQueue.shift().updateBlockLightning(scope); } - }, 1); + }, 0); } onTick() { diff --git a/src/resources/font.png b/src/resources/font.png new file mode 100644 index 0000000..e4283d9 Binary files /dev/null and b/src/resources/font.png differ diff --git a/src/resources/gui.png b/src/resources/gui.png index 973dd0d..6c709ac 100644 Binary files a/src/resources/gui.png and b/src/resources/gui.png differ diff --git a/src/start.js b/src/start.js index 2454473..35b90c1 100644 --- a/src/start.js +++ b/src/start.js @@ -84,6 +84,7 @@ loadScripts([ "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/FontRenderer.js", "src/js/net/minecraft/client/render/Tessellator.js", "src/js/net/minecraft/client/render/WorldRenderer.js", "src/js/net/minecraft/client/render/BlockRenderer.js" diff --git a/style.css b/style.css index cba8926..9ffb9e3 100644 --- a/style.css +++ b/style.css @@ -7,8 +7,14 @@ body { margin: 0; } +canvas { + image-rendering: -moz-crisp-edges; /* Firefox */ + image-rendering: -webkit-crisp-edges; /* Webkit (Safari) */ + image-rendering: pixelated; /* Chrome */ +} + #background { - background-image: url(background.png); + background-image: url(src/resources/background.png); background-size: 128px 128px; image-rendering: pixelated; filter: brightness(0.5);