From 0cf779936e2f3f2637aba4165038215c8ed57804 Mon Sep 17 00:00:00 2001 From: LabyStudio Date: Fri, 4 Feb 2022 10:56:24 +0100 Subject: [PATCH] implement font renderer and gui scaling --- src/js/net/minecraft/client/GameWindow.js | 25 ++-- src/js/net/minecraft/client/Minecraft.js | 4 +- src/js/net/minecraft/client/gui/Gui.js | 43 +++--- .../net/minecraft/client/gui/IngameOverlay.js | 9 +- .../client/gui/screens/GuiIngameMenu.js | 4 +- .../client/gui/screens/GuiLoadingScreen.js | 6 +- .../minecraft/client/gui/widgets/GuiButton.js | 2 +- .../minecraft/client/render/FontRenderer.js | 139 ++++++++++++++++++ .../minecraft/client/render/WorldRenderer.js | 16 +- src/js/net/minecraft/client/world/World.js | 2 +- src/resources/font.png | Bin 0 -> 1329 bytes src/resources/gui.png | Bin 8645 -> 17475 bytes src/start.js | 1 + style.css | 8 +- 14 files changed, 201 insertions(+), 58 deletions(-) create mode 100644 src/js/net/minecraft/client/render/FontRenderer.js create mode 100644 src/resources/font.png 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 0000000000000000000000000000000000000000..e4283d94974670682a2aa77248dae5295f5eca39 GIT binary patch literal 1329 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H3?#oinD`S&F%}28J29*~C-V}>VM%xNb!1@J z*w6hZkrl}25AX?b&9<+@#Ws`4P9NkvfmHSxa_A$+Ey}>!s%PZpMJVEeonE7qYos$GqR=*;iiF5P)`!^15 zVwceyJmq8d>F-;HBK5lEKN&1K-FaKjRMgz96xo)~+;U3A zE!$(4&7(Eb?D7({bMn|v2v4b=6jIcGA^6#@Yuhseo-=e;M5^wYmKEneEi-f0wO?BA z-pyx_{;AUXYtpaGrc1jr-Y!n7{>>7!+czs1WBc?Vb+~S~j zv+T*wI^N08w!Tk@{be5eOM^!!siES$>zN**9QpDSwt-hlD>aVHefjYoQ^=&J!W!-j z$L_eF`@H1r`Y-ezvJTu5jn1%f#OdzXT3?TQr@O>;(J4S&b6ggtHQ6>>mRYvAD%l6~O!_tf>b zHcPQT(0P$QS2p}-@tcL$u4#YV_2X%&+H{i$sf$5-We+@!eEa**)5k5Ui2;V5$EIoB zV`95DOXA`6ir9Zor*d{CzrA+%hVV_{07=y!r=NPdoi1QEWXQ@+?RmDTI#~8VUS+Il z)HWW0zcUZ}{zg0Ip4*GtJ{j55JEzd4F zu3ZtX;wRVc7yKu-M2>IAHoM=xZ{B}fx!m6+?3i(~a>3i<+gt^=Pu!?)xA~HO=fXPv z4-MO0_I$nA$6~K@QT*2FlfPbaE;%S->fYa*Za;aptiJkdqXWA>I)tmd|Nj3EOGW#f z7@b~EpYM6QD-h>yWdeu{JP4~(;D!fpJA-Yv<66beSDbXL(c74%3U9q@t`OD*s zj{;R}4!4TTYg3%mvq)8N_T0+EISR zt=#E0i`pwL{KNl9rG80VZ20y4 zske7bkl(oLpYH8rt)CQc+-JHHzd1K%#dZ7NVh@V*!14j>>%MV*I4gTe~DWM4fwZw`` literal 0 HcmV?d00001 diff --git a/src/resources/gui.png b/src/resources/gui.png index 973dd0d0298898a87cf10b4172136594beca878f..6c709ac94d6448f44663f937d4ea0ab8d5b8e62b 100644 GIT binary patch literal 17475 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 E0BYfJ(EtDd literal 8645 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 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);