247 lines
6.5 KiB
JavaScript
247 lines
6.5 KiB
JavaScript
window.Chunk = class {
|
|
|
|
constructor(world, x, z) {
|
|
this.world = world;
|
|
this.x = x;
|
|
this.z = z;
|
|
|
|
this.group = new THREE.Object3D();
|
|
this.group.matrixAutoUpdate = false;
|
|
this.group.chunkX = x;
|
|
this.group.chunkZ = z;
|
|
|
|
this.loaded = false;
|
|
|
|
// Initialize sections
|
|
this.sections = [];
|
|
for (let y = 0; y < 16; y++) {
|
|
let section = new ChunkSection(world, this, x, y, z);
|
|
|
|
this.sections[y] = section;
|
|
this.group.add(section.group);
|
|
}
|
|
|
|
// Create height map
|
|
this.heightMap = [];
|
|
}
|
|
|
|
generateSkylightMap() {
|
|
// Calculate height map
|
|
for (let x = 0; x < 16; x++) {
|
|
for (let z = 0; z < 16; z++) {
|
|
this.setHeightAt(x, z, 0); // TODO set to 0 to calculate proper lightning
|
|
this.updateHeightMap(x, World.TOTAL_HEIGHT, z);
|
|
}
|
|
}
|
|
|
|
// Update light of neighbor blocks
|
|
for (let x = 0; x < 16; x++) {
|
|
for (let z = 0; z < 16; z++) {
|
|
this.notifyNeighbors(x, z);
|
|
}
|
|
}
|
|
|
|
// Rebuild all sections
|
|
this.setModifiedAllSections();
|
|
}
|
|
|
|
updateBlockLight() {
|
|
this.setModifiedAllSections();
|
|
}
|
|
|
|
notifyNeighbors(x, z) {
|
|
let height = this.getHeightAt(x, z);
|
|
let totalX = this.x * 16 + x;
|
|
let totalZ = this.z * 16 + z;
|
|
|
|
this.updateSkyLight(totalX - 1, totalZ, height);
|
|
this.updateSkyLight(totalX + 1, totalZ, height);
|
|
this.updateSkyLight(totalX, totalZ - 1, height);
|
|
this.updateSkyLight(totalX, totalZ + 1, height);
|
|
}
|
|
|
|
updateSkyLight(x, z, y) {
|
|
let height = this.world.getHeightAt(x, z);
|
|
if (height > y) {
|
|
this.world.updateLight(EnumSkyBlock.SKY, x, y, z, x, height, z);
|
|
} else if (height < y) {
|
|
this.world.updateLight(EnumSkyBlock.SKY, x, height, z, x, y, z);
|
|
}
|
|
this.setModifiedAllSections();
|
|
}
|
|
|
|
updateHeightMap(relX, y, relZ) {
|
|
let currentHighestY = this.getHeightAt(relX, relZ);
|
|
let highestY = currentHighestY;
|
|
if (y > currentHighestY) {
|
|
highestY = y;
|
|
}
|
|
|
|
highestY = this.calculateHeightAt(relX, relZ, highestY);
|
|
|
|
if (highestY === currentHighestY) {
|
|
return;
|
|
}
|
|
|
|
this.setHeightAt(relX, relZ, highestY);
|
|
|
|
let x = this.x * 16 + relX;
|
|
let z = this.z * 16 + relZ;
|
|
|
|
if (highestY < currentHighestY) {
|
|
for (let hy = highestY; hy < currentHighestY; hy++) {
|
|
this.setLightAt(EnumSkyBlock.SKY, relX, hy, relZ, 15);
|
|
}
|
|
} else {
|
|
this.world.updateLight(EnumSkyBlock.SKY, x, currentHighestY, z, x, highestY, z);
|
|
for (let hy = currentHighestY; hy < highestY; hy++) {
|
|
this.setLightAt(EnumSkyBlock.SKY, relX, hy, relZ, 0);
|
|
}
|
|
}
|
|
|
|
let lightLevel = 15;
|
|
let prevHeight = highestY;
|
|
while (highestY > 0 && lightLevel > 0) {
|
|
highestY--;
|
|
|
|
let typeId = this.getBlockID(relX, highestY, relZ);
|
|
let block = Block.getById(typeId);
|
|
|
|
let opacity = Math.floor(typeId === 0 ? 0 : block.getOpacity() * 255);
|
|
if (opacity === 0) {
|
|
opacity = 1;
|
|
}
|
|
lightLevel -= opacity;
|
|
if (lightLevel < 0) {
|
|
lightLevel = 0;
|
|
}
|
|
|
|
this.setLightAt(EnumSkyBlock.SKY, relX, highestY, relZ, lightLevel);
|
|
}
|
|
|
|
highestY = this.calculateHeightAt(relX, relZ, highestY);
|
|
|
|
if (highestY !== prevHeight) {
|
|
this.world.updateLight(EnumSkyBlock.SKY, x - 1, highestY, z - 1, x + 1, prevHeight, z + 1);
|
|
}
|
|
this.setModifiedAllSections();
|
|
}
|
|
|
|
calculateHeightAt(x, z, startY) {
|
|
let y = startY;
|
|
while (y > 0) {
|
|
let typeId = this.getBlockAt(x, y - 1, z);
|
|
let block = Block.getById(typeId);
|
|
let opacity = typeId === 0 ? 0 : block.getOpacity();
|
|
|
|
if (opacity !== 0) {
|
|
break;
|
|
}
|
|
y--;
|
|
}
|
|
return y;
|
|
}
|
|
|
|
updateHeightMapAt(x, z) {
|
|
let y = this.calculateHeightAt(x, z, World.TOTAL_HEIGHT);
|
|
this.setHeightAt(x, z, y);
|
|
}
|
|
|
|
setHeightAt(x, z, height) {
|
|
this.heightMap[z << 4 | x] = height;
|
|
}
|
|
|
|
/**
|
|
* Is the highest solid block or above
|
|
*/
|
|
isHighestBlock(x, y, z) {
|
|
return y >= this.getHighestBlockAt(x, z);
|
|
}
|
|
|
|
/**
|
|
* Is above the highest solid block
|
|
*/
|
|
isAboveGround(x, y, z) {
|
|
return y >= this.getHeightAt(x, z);
|
|
}
|
|
|
|
/**
|
|
* Get the first non-solid block
|
|
*/
|
|
getHeightAt(x, z) {
|
|
return this.heightMap[z << 4 | x];
|
|
}
|
|
|
|
/**
|
|
* Get the highest solid block
|
|
*/
|
|
getHighestBlockAt(x, z) {
|
|
return this.getHeightAt(x, z) - 1;
|
|
}
|
|
|
|
setLightAt(sourceType, x, y, z, level) {
|
|
let section = this.getSection(y >> 4);
|
|
section.setLightAt(sourceType, x, y & 15, z, level);
|
|
}
|
|
|
|
setBlockAt(x, y, z, typeId) {
|
|
let height = this.getHeightAt(x, z);
|
|
let prevTypeId = this.getBlockAt(x, y, z);
|
|
if (prevTypeId === typeId) {
|
|
return false;
|
|
}
|
|
|
|
this.getSection(y >> 4).setBlockAt(x, y & 15, z, typeId);
|
|
|
|
if (!this.loaded) {
|
|
return;
|
|
}
|
|
|
|
let block = Block.getById(typeId);
|
|
if (typeId !== 0 && block.isSolid()) {
|
|
if (y >= height) {
|
|
this.updateHeightMap(x, y + 1, z);
|
|
}
|
|
} else if (y === height - 1) {
|
|
this.updateHeightMap(x, y, z);
|
|
}
|
|
|
|
let totalX = this.x * 16 + x;
|
|
let totalZ = this.z * 16 + z;
|
|
|
|
this.world.updateLight(EnumSkyBlock.SKY, totalX, y, totalZ, totalX, y, totalZ);
|
|
this.world.updateLight(EnumSkyBlock.BLOCK, totalX, y, totalZ, totalX, y, totalZ);
|
|
|
|
this.notifyNeighbors(x, z);
|
|
this.setModifiedAllSections();
|
|
return true;
|
|
}
|
|
|
|
getBlockID(x, y, z) {
|
|
return this.getBlockAt(x, y, z);
|
|
}
|
|
|
|
getBlockAt(x, y, z) {
|
|
return this.getSection(y >> 4).getBlockAt(x, y & 15, z);
|
|
}
|
|
|
|
getSection(y) {
|
|
return this.sections[y];
|
|
}
|
|
|
|
rebuild(renderer) {
|
|
for (let y = 0; y < this.sections.length; y++) {
|
|
this.sections[y].rebuild(renderer);
|
|
}
|
|
}
|
|
|
|
isLoaded() {
|
|
return this.loaded;
|
|
}
|
|
|
|
setModifiedAllSections() {
|
|
for (let y = 0; y < this.sections.length; y++) {
|
|
this.sections[y].isModified = true;
|
|
}
|
|
}
|
|
} |