implement lightning and smooth lightning

This commit is contained in:
LabyStudio
2022-02-01 12:45:25 +01:00
parent bda4e01807
commit a0e6d00bf5
4 changed files with 220 additions and 7 deletions
+3
View File
@@ -84,6 +84,9 @@ window.Minecraft = class {
}
onTick() {
// Tick world
this.world.onTick();
// Tick the player
this.player.onTick();
}
@@ -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;
}
}
@@ -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;
}
+133
View File
@@ -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);
}