From a0e6d00bf53dfb8260e61590d172ea75ce3614a2 Mon Sep 17 00:00:00 2001 From: LabyStudio Date: Tue, 1 Feb 2022 12:45:25 +0100 Subject: [PATCH] implement lightning and smooth lightning --- src/js/net/minecraft/client/Minecraft.js | 3 + .../minecraft/client/render/BlockRenderer.js | 52 ++++++- .../minecraft/client/world/ChunkSection.js | 39 ++++- src/js/net/minecraft/client/world/World.js | 133 ++++++++++++++++++ 4 files changed, 220 insertions(+), 7 deletions(-) diff --git a/src/js/net/minecraft/client/Minecraft.js b/src/js/net/minecraft/client/Minecraft.js index e39e82b..b77b144 100644 --- a/src/js/net/minecraft/client/Minecraft.js +++ b/src/js/net/minecraft/client/Minecraft.js @@ -84,6 +84,9 @@ window.Minecraft = class { } onTick() { + // Tick world + this.world.onTick(); + // Tick the player this.player.onTick(); } diff --git a/src/js/net/minecraft/client/render/BlockRenderer.js b/src/js/net/minecraft/client/render/BlockRenderer.js index 837c708..3ecdc3c 100644 --- a/src/js/net/minecraft/client/render/BlockRenderer.js +++ b/src/js/net/minecraft/client/render/BlockRenderer.js @@ -1,5 +1,7 @@ window.BlockRenderer = class { + static CLASSIC_LIGHTNING = false; + constructor(worldRenderer) { this.worldRenderer = worldRenderer; this.tessellator = new Tessellator(); @@ -44,9 +46,11 @@ window.BlockRenderer = class { maxV = 1 - maxV; // Classic lightning - let brightness = 0.9 / 15.0 * 15 + 0.1; - let color = brightness * face.getShading(); - this.tessellator.setColor(color, color, color); + if (BlockRenderer.CLASSIC_LIGHTNING) { + let brightness = 0.9 / 15.0 * 15 + 0.1; + let color = brightness * face.getShading(); + this.tessellator.setColor(color, color, color); + } if (face === EnumBlockFace.BOTTOM) { this.addBlockCorner(world, face, minX, minY, maxZ, minU, maxV); @@ -87,7 +91,49 @@ window.BlockRenderer = class { } addBlockCorner(world, face, x, y, z, u, v) { + // Smooth lightning + if (!BlockRenderer.CLASSIC_LIGHTNING) { + this.setAverageColor(world, face, x, y, z); + } + this.tessellator.addVertexWithUV(x, y, z, u, v); } + setAverageColor(world, face, x, y, z) { + // Get the average light level of all 4 blocks at this corner + let lightLevelAtThisCorner = this.getAverageLightLevelAt(world, x, y, z); + + // Convert light level from [0 - 15] to [0.1 - 1.0] + let brightness = 0.9 / 15.0 * lightLevelAtThisCorner + 0.1; + let color = brightness * face.getShading(); + + // Set color with shading + this.tessellator.setColor(color, color, color); + } + + getAverageLightLevelAt(world, x, y, z) { + let totalLightLevel = 0; + let totalBlocks = 0; + + // For all blocks around this corner + for (let offsetX = -1; offsetX <= 0; offsetX++) { + for (let offsetY = -1; offsetY <= 0; offsetY++) { + for (let offsetZ = -1; offsetZ <= 0; offsetZ++) { + let typeId = world.getBlockAt(x + offsetX, y + offsetY, z + offsetZ); + + // Does it contain air? + if (typeId === 0 || Block.getById(typeId).isTransparent()) { + + // Sum up the light levels + totalLightLevel += world.getLightAt(x + offsetX, y + offsetY, z + offsetZ); + totalBlocks++; + } + } + } + } + + // Calculate the average light level of all surrounding blocks + return totalBlocks === 0 ? 0 : totalLightLevel / totalBlocks; + } + } \ No newline at end of file diff --git a/src/js/net/minecraft/client/world/ChunkSection.js b/src/js/net/minecraft/client/world/ChunkSection.js index ce3e407..abe4e75 100644 --- a/src/js/net/minecraft/client/world/ChunkSection.js +++ b/src/js/net/minecraft/client/world/ChunkSection.js @@ -5,6 +5,7 @@ window.ChunkSection = class { constructor(world, chunk, x, y, z) { this.world = world; this.chunk = chunk; + this.x = x; this.y = y; this.z = z; @@ -22,10 +23,16 @@ window.ChunkSection = class { this.queuedForRebuild = 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); + this.blockLight = []; + + // Fill chunk with air and light + for (let lightX = 0; lightX < ChunkSection.SIZE; lightX++) { + for (let lightY = 0; lightY < ChunkSection.SIZE; lightY++) { + for (let lightZ = 0; lightZ < ChunkSection.SIZE; lightZ++) { + let index = lightY << 8 | lightZ << 4 | lightX; + + this.blocks[index] = 0; + this.blockLight[index] = 15; } } } @@ -74,6 +81,30 @@ window.ChunkSection = class { this.blocks[index] = typeId; } + setLightAt(x, y, z, lightLevel) { + let index = y << 8 | z << 4 | x; + this.blockLight[index] = lightLevel; + } + + getLightAt(x, y, z) { + let index = y << 8 | z << 4 | x; + return this.blockLight[index]; + } + + isEmpty() { + 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 index = y << 8 | z << 4 | x; + if (this.blocks[index] !== 0) { + return false; + } + } + } + } + return true; + } + queueForRebuild() { this.queuedForRebuild = true; } diff --git a/src/js/net/minecraft/client/world/World.js b/src/js/net/minecraft/client/world/World.js index 35e8a6a..4e56469 100644 --- a/src/js/net/minecraft/client/world/World.js +++ b/src/js/net/minecraft/client/world/World.js @@ -9,6 +9,28 @@ window.World = class { // Load world this.generator = new WorldGenerator(this, Date.now() % 100000); + + this.lightUpdateQueue = []; + } + + onTick() { + // Handle 128 light updates per tick + for (let i = 0; i < 128; i++) { + + // Light updates + if (this.lightUpdateQueue.length !== 0) { + + // Get next position to update + let positionIndex = this.lightUpdateQueue.shift(); + if (positionIndex != null) { + let z = positionIndex >> 16; + let x = positionIndex - (z << 16); + this.updateBlockLightsAtXZ(x, z); + } else { + break; + } + } + } } loadChunk(chunk) { @@ -54,10 +76,17 @@ window.World = class { return typeId !== 0 && Block.getById(typeId).isSolid(); } + isTransparentBlockAt(x, y, z) { + let typeId = this.getBlockAt(x, y, z); + return typeId === 0 || Block.getById(typeId).isTransparent(); + } + 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.updateBlockLightAt(x, y, z); } this.onBlockChanged(x, y, z); @@ -95,6 +124,110 @@ window.World = class { return 0; } + updateBlockLightAt(x, y, z) { + // Calculate brightness for target block + let lightLevel = this.isHighestBlockAt(x, y, z) ? 15 : this.calculateLightAt(x, y, z); + + // Update target block light + this.getChunkAtBlock(x, y, z).setLightAt(x & 15, y & 15, z & 15, lightLevel); + + // Update block lights below the target block and the surrounding blocks + for (let offsetX = -1; offsetX <= 1; offsetX++) { + for (let offsetZ = -1; offsetZ <= 1; offsetZ++) { + this.updateBlockLightsAtXZ(x + offsetX, z + offsetZ); + } + } + } + + updateBlockLightsAtXZ(x, z) { + let lightChanged = false; + let skyLevel = 15; + + // Scan from the top to the bottom + for (let y = World.TOTAL_HEIGHT; y >= 0; y--) { + if (!this.isTransparentBlockAt(x, y, z)) { + // Sun is blocked because of solid block + skyLevel = 0; + } else { + // Get opacity of this block + let typeId = this.getBlockAt(x, y, z); + let translucence = typeId === 0 ? 1.0 : 1.0 - Block.getById(typeId).getOpacity(); + + // Decrease strength of the skylight by the opacity of the block + skyLevel *= translucence; + + // Get previous block light + let prevBlockLight = this.getLightAt(x, y, z); + + // Combine skylight with the calculated block light and decrease strength by the opacity of the block + let blockLight = Math.floor(Math.max(skyLevel, this.calculateLightAt(x, y, z)) * translucence); + + // Did one of the light change inside of the range? + if (prevBlockLight !== blockLight) { + lightChanged = true; + } + + // Apply the new light to the block + this.setLightAt(x, y, z, blockLight); + } + } + + // Chain reaction, update next affected blocks + if (lightChanged && this.lightUpdateQueue.length < 512) { + for (let offsetX = -1; offsetX <= 1; offsetX++) { + for (let offsetZ = -1; offsetZ <= 1; offsetZ++) { + let positionIndex = (x + offsetX) + ((z + offsetZ) << 16); + + // Add block range to update queue + if (!this.lightUpdateQueue.includes(positionIndex)) { + this.lightUpdateQueue.push(positionIndex); + } + } + } + } + } + + setLightAt(x, y, z, light) { + let chunkSection = this.getChunkAtBlock(x, y, z); + if (chunkSection != null) { + chunkSection.setLightAt(x & 15, y & 15, z & 15, light); + chunkSection.queueForRebuild(); + } + } + + getLightAt(x, y, z) { + let chunkSection = this.getChunkAtBlock(x, y, z); + return chunkSection == null ? 15 : chunkSection.getLightAt(x & 15, y & 15, z & 15); + } + + isHighestBlockAt(x, y, z) { + for (let i = y + 1; i < World.TOTAL_HEIGHT; i++) { + if (this.isSolidBlockAt(x, i, z)) { + return false; + } + } + return true; + } + + calculateLightAt(x, y, z) { + let maxBrightness = 0; + + // Get maximal brightness of surround blocks + let values = EnumBlockFace.values(); + for (let i in values) { + let face = values[i]; + + if (this.isTransparentBlockAt(x + face.x, y + face.y, z + face.z)) { + let brightness = this.getLightAt(x + face.x, y + face.y, z + face.z); + + maxBrightness = Math.max(maxBrightness, brightness); + } + } + + // Decrease maximum brightness by 6% + return Math.max(0, maxBrightness - 1); + } + onBlockChanged(x, y, z) { this.queueForRebuildInRegion(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1); }