implement lightning and smooth lightning
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user