implement lightning and smooth lightning
This commit is contained in:
@@ -84,6 +84,9 @@ window.Minecraft = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTick() {
|
onTick() {
|
||||||
|
// Tick world
|
||||||
|
this.world.onTick();
|
||||||
|
|
||||||
// Tick the player
|
// Tick the player
|
||||||
this.player.onTick();
|
this.player.onTick();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
window.BlockRenderer = class {
|
window.BlockRenderer = class {
|
||||||
|
|
||||||
|
static CLASSIC_LIGHTNING = false;
|
||||||
|
|
||||||
constructor(worldRenderer) {
|
constructor(worldRenderer) {
|
||||||
this.worldRenderer = worldRenderer;
|
this.worldRenderer = worldRenderer;
|
||||||
this.tessellator = new Tessellator();
|
this.tessellator = new Tessellator();
|
||||||
@@ -44,9 +46,11 @@ window.BlockRenderer = class {
|
|||||||
maxV = 1 - maxV;
|
maxV = 1 - maxV;
|
||||||
|
|
||||||
// Classic lightning
|
// Classic lightning
|
||||||
let brightness = 0.9 / 15.0 * 15 + 0.1;
|
if (BlockRenderer.CLASSIC_LIGHTNING) {
|
||||||
let color = brightness * face.getShading();
|
let brightness = 0.9 / 15.0 * 15 + 0.1;
|
||||||
this.tessellator.setColor(color, color, color);
|
let color = brightness * face.getShading();
|
||||||
|
this.tessellator.setColor(color, color, color);
|
||||||
|
}
|
||||||
|
|
||||||
if (face === EnumBlockFace.BOTTOM) {
|
if (face === EnumBlockFace.BOTTOM) {
|
||||||
this.addBlockCorner(world, face, minX, minY, maxZ, minU, maxV);
|
this.addBlockCorner(world, face, minX, minY, maxZ, minU, maxV);
|
||||||
@@ -87,7 +91,49 @@ window.BlockRenderer = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addBlockCorner(world, face, x, y, z, u, v) {
|
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);
|
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) {
|
constructor(world, chunk, x, y, z) {
|
||||||
this.world = world;
|
this.world = world;
|
||||||
this.chunk = chunk;
|
this.chunk = chunk;
|
||||||
|
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.z = z;
|
this.z = z;
|
||||||
@@ -22,10 +23,16 @@ window.ChunkSection = class {
|
|||||||
this.queuedForRebuild = true;
|
this.queuedForRebuild = true;
|
||||||
|
|
||||||
this.blocks = [];
|
this.blocks = [];
|
||||||
for (let x = 0; x < ChunkSection.SIZE; x++) {
|
this.blockLight = [];
|
||||||
for (let y = 0; y < ChunkSection.SIZE; y++) {
|
|
||||||
for (let z = 0; z < ChunkSection.SIZE; z++) {
|
// Fill chunk with air and light
|
||||||
this.setBlockAt(x, y, z, 0);
|
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;
|
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() {
|
queueForRebuild() {
|
||||||
this.queuedForRebuild = true;
|
this.queuedForRebuild = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,28 @@ window.World = class {
|
|||||||
|
|
||||||
// Load world
|
// Load world
|
||||||
this.generator = new WorldGenerator(this, Date.now() % 100000);
|
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) {
|
loadChunk(chunk) {
|
||||||
@@ -54,10 +76,17 @@ window.World = class {
|
|||||||
return typeId !== 0 && Block.getById(typeId).isSolid();
|
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) {
|
setBlockAt(x, y, z, type) {
|
||||||
let chunkSection = this.getChunkAtBlock(x, y, z);
|
let chunkSection = this.getChunkAtBlock(x, y, z);
|
||||||
if (chunkSection != null) {
|
if (chunkSection != null) {
|
||||||
chunkSection.setBlockAt(x & 15, y & 15, z & 15, type);
|
chunkSection.setBlockAt(x & 15, y & 15, z & 15, type);
|
||||||
|
|
||||||
|
this.updateBlockLightAt(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onBlockChanged(x, y, z);
|
this.onBlockChanged(x, y, z);
|
||||||
@@ -95,6 +124,110 @@ window.World = class {
|
|||||||
return 0;
|
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) {
|
onBlockChanged(x, y, z) {
|
||||||
this.queueForRebuildInRegion(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1);
|
this.queueForRebuildInRegion(x - 1, y - 1, z - 1, x + 1, y + 1, z + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user