/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.pepsoft.worldpainter.exporting; import org.pepsoft.minecraft.Chunk; import org.pepsoft.util.Box; import java.util.Arrays; import static org.pepsoft.minecraft.Block.BLOCK_TRANSPARENCY; import static org.pepsoft.minecraft.Block.LIGHT_SOURCES; import static org.pepsoft.minecraft.Constants.HIGHEST_KNOWN_BLOCK_ID; /** * A lighting calculator for MinecraftWorlds. * * <p>The process consists of two passes. In the first pass, all chunks which * have previously been marked as having "primary light dirty" are visited and * have their primary light recalculated. "Primary light" means full daylight, * and block light at the site of any luminous blocks such as torches. * * @author pepijn */ public class LightingCalculator { public LightingCalculator(MinecraftWorld world) { this.world = world; maxHeight = world.getMaxHeight(); } public Box getDirtyArea() { return dirtyArea; } public void setDirtyArea(Box dirtyArea) { this.dirtyArea = dirtyArea; } /** * For the selected chunk and the chunks around it, calculate the secondary * light, if necessary. The dirty area is constricted to the blocks that * were actually changed, and <code>false</code> is returned if no changes * were made at all (indicating that lighting is complete). */ public boolean calculateSecondaryLight() { int lowestY = Integer.MAX_VALUE, highestY = Integer.MIN_VALUE; boolean changed = false; for (int x = dirtyArea.getX1(); x <= dirtyArea.getX2(); x++) { for (int z = dirtyArea.getZ1(); z <= dirtyArea.getZ2(); z++) { Chunk chunk = world.getChunk(x >> 4, z >> 4); if (chunk == null) { continue; } for (int y = dirtyArea.getY1(); y <= dirtyArea.getY2(); y++) { int blockType = world.getBlockTypeAt(x, z, y); int currentSkylightLevel = world.getSkyLightLevel(x, z, y); int currentBlockLightLevel = world.getBlockLightLevel(x, z, y); int newSkyLightLevel; int newBlockLightLevel; if ((blockType <= HIGHEST_KNOWN_BLOCK_ID) && (BLOCK_TRANSPARENCY[blockType] == 15)) { // Opaque block newSkyLightLevel = 0; newBlockLightLevel = (LIGHT_SOURCES[blockType] > 0) ? currentBlockLightLevel : 0; } else { // Transparent block, or unknown block. We err on the // side of transparency for unknown blocks to try and // cause less visible lighting bugs newSkyLightLevel = (currentSkylightLevel < 15) ? calculateSkyLightLevel(x, y, z) : 15; newBlockLightLevel = ((blockType <= HIGHEST_KNOWN_BLOCK_ID) && (LIGHT_SOURCES[blockType] > 0)) ? currentBlockLightLevel : calculateBlockLightLevel(x, y, z); } if ((newSkyLightLevel != currentSkylightLevel) || (newBlockLightLevel != currentBlockLightLevel)) { if (newSkyLightLevel != currentSkylightLevel) { world.setSkyLightLevel(x, z, y, newSkyLightLevel); } if (newBlockLightLevel != currentBlockLightLevel) { world.setBlockLightLevel(x, z, y, newBlockLightLevel); } changed = true; if (y - 1 < lowestY) { lowestY = y - 1; } if (y + 1 > highestY) { highestY = y + 1; } } } } } if (changed) { dirtyArea.setY1(Math.max(lowestY, 0)); dirtyArea.setY2(Math.min(highestY, maxHeight - 1)); } return changed; } public int[] calculatePrimaryLight(Chunk chunk) { for (int x = 0; x < 16; x++) { Arrays.fill(DAYLIGHT[x], true); Arrays.fill(HEIGHT[x], maxHeight - 1); } // The point above which there are only transparent, non light source // blocks int lightingVolumeHighMark = 0; // The point below which there are only non-transparent, non light // source blocks int lightingVolumeLowMark = maxHeight - 1; for (int y = maxHeight - 1; y >= 0; y--) { for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { int blockType = chunk.getBlockType(x, y, z); int blockLightLevel = chunk.getBlockLightLevel(x, y, z); int skyLightLevel = chunk.getSkyLightLevel(x, y, z); int newBlockLightLevel, newSkyLightLevel; if ((blockType > HIGHEST_KNOWN_BLOCK_ID) || (BLOCK_TRANSPARENCY[blockType] < 15)) { // Transparent block, or unknown block. We err on the // side of transparency for unknown blocks to try and // cause less visible lighting bugs if (y < lightingVolumeLowMark) { lightingVolumeLowMark = y; } int transparency = (blockType > HIGHEST_KNOWN_BLOCK_ID) ? 0 : BLOCK_TRANSPARENCY[blockType]; if ((transparency == 0) && (DAYLIGHT[x][z])) { // Propagate daylight down newSkyLightLevel = 15; HEIGHT[x][z] = y; } else { if ((transparency > 0) && (y > lightingVolumeHighMark)) { lightingVolumeHighMark = y; } newSkyLightLevel = 0; DAYLIGHT[x][z] = false; } } else { if (y > lightingVolumeHighMark) { lightingVolumeHighMark = y; } newSkyLightLevel = 0; DAYLIGHT[x][z] = false; } if ((blockType <= HIGHEST_KNOWN_BLOCK_ID) && (LIGHT_SOURCES[blockType] > 0)) { if (y > lightingVolumeHighMark) { lightingVolumeHighMark = y; } if (y < lightingVolumeLowMark) { lightingVolumeLowMark = y; } newBlockLightLevel = LIGHT_SOURCES[blockType]; } else { newBlockLightLevel = 0; } if (newBlockLightLevel != blockLightLevel) { chunk.setBlockLightLevel(x, y, z, newBlockLightLevel); } if (newSkyLightLevel != skyLightLevel) { chunk.setSkyLightLevel(x, y, z, newSkyLightLevel); } } } } for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { if (chunk.getHeight(x, z) != HEIGHT[x][z]) { chunk.setHeight(x, z, HEIGHT[x][z]); } } } // System.out.println("Lighting volume low mark: " + lightingVolumeLowMark + ", high mark: " + lightingVolumeHighMark); return new int[] {lightingVolumeLowMark, lightingVolumeHighMark}; } public void recalculatePrimaryLight() { for (int x = dirtyArea.getX1(); x <= dirtyArea.getX2(); x++) { for (int z = dirtyArea.getZ1(); z <= dirtyArea.getZ2(); z++) { Chunk chunk = world.getChunkForEditing(x >> 4, z >> 4); if (chunk == null) { continue; } boolean daylight = (dirtyArea.getY2() >= (world.getMaxHeight() - 1)) ? true : (world.getSkyLightLevel(x, z, dirtyArea.getY2() + 1) == 15); for (int y = dirtyArea.getY2(); y >= dirtyArea.getY1(); y--) { int blockType = chunk.getBlockType(x & 0xf, y, z & 0xf); int blockLightLevel = chunk.getBlockLightLevel(x & 0xf, y, z & 0xf); int skyLightLevel = chunk.getSkyLightLevel(x & 0xf, y, z & 0xf); int newBlockLightLevel, newSkyLightLevel; if ((blockType > HIGHEST_KNOWN_BLOCK_ID) || (BLOCK_TRANSPARENCY[blockType] < 15)) { // Transparent block, or unknown block. We err on the // side of transparency for unknown blocks to try and // cause less visible lighting bugs int transparency = (blockType > HIGHEST_KNOWN_BLOCK_ID) ? 0 : BLOCK_TRANSPARENCY[blockType]; if ((transparency == 0) && daylight) { // Propagate daylight down newSkyLightLevel = 15; } else { newSkyLightLevel = 0; daylight = false; } } else { newSkyLightLevel = 0; daylight = false; } if ((blockType <= HIGHEST_KNOWN_BLOCK_ID) && (LIGHT_SOURCES[blockType] > 0)) { newBlockLightLevel = LIGHT_SOURCES[blockType]; } else { newBlockLightLevel = 0; } if (newBlockLightLevel != blockLightLevel) { chunk.setBlockLightLevel(x & 0xf, y, z & 0xf, newBlockLightLevel); } if (newSkyLightLevel != skyLightLevel) { chunk.setSkyLightLevel(x & 0xf, y, z & 0xf, newSkyLightLevel); } } } } } private int calculateSkyLightLevel(int x, int y, int z) { int blockType = world.getBlockTypeAt(x, z, y); int skyLightLevel = getSkyLightLevelAt(x, y + 1, z); int highestSurroundingSkyLight = skyLightLevel; if (highestSurroundingSkyLight < 15) { skyLightLevel = getSkyLightLevelAt(x - 1, y, z); if (skyLightLevel > highestSurroundingSkyLight) { highestSurroundingSkyLight = skyLightLevel; } if (highestSurroundingSkyLight < 15) { skyLightLevel = getSkyLightLevelAt(x + 1, y, z); if (skyLightLevel > highestSurroundingSkyLight) { highestSurroundingSkyLight = skyLightLevel; } if (highestSurroundingSkyLight < 15) { skyLightLevel = getSkyLightLevelAt(x, y, z - 1); if (skyLightLevel > highestSurroundingSkyLight) { highestSurroundingSkyLight = skyLightLevel; } if (highestSurroundingSkyLight < 15) { skyLightLevel = getSkyLightLevelAt(x, y, z + 1); if (skyLightLevel > highestSurroundingSkyLight) { highestSurroundingSkyLight = skyLightLevel; } if (highestSurroundingSkyLight < 15) { skyLightLevel = getSkyLightLevelAt(x, y - 1, z); if (skyLightLevel > highestSurroundingSkyLight) { highestSurroundingSkyLight = skyLightLevel; } } } } } } return Math.max(highestSurroundingSkyLight - Math.max((blockType <= HIGHEST_KNOWN_BLOCK_ID) ? BLOCK_TRANSPARENCY[blockType] : 0, 1), 0); } private int calculateBlockLightLevel(int x, int y, int z) { int blockType = world.getBlockTypeAt(x, z, y); int blockLightLevel = getBlockLightLevelAt(x, y + 1, z); int highestSurroundingBlockLight = blockLightLevel; blockLightLevel = getBlockLightLevelAt(x - 1, y, z); if (blockLightLevel > highestSurroundingBlockLight) { highestSurroundingBlockLight = blockLightLevel; } blockLightLevel = getBlockLightLevelAt(x + 1, y, z); if (blockLightLevel > highestSurroundingBlockLight) { highestSurroundingBlockLight = blockLightLevel; } blockLightLevel = getBlockLightLevelAt(x, y, z - 1); if (blockLightLevel > highestSurroundingBlockLight) { highestSurroundingBlockLight = blockLightLevel; } blockLightLevel = getBlockLightLevelAt(x, y, z + 1); if (blockLightLevel > highestSurroundingBlockLight) { highestSurroundingBlockLight = blockLightLevel; } blockLightLevel = getBlockLightLevelAt(x, y - 1, z); if (blockLightLevel > highestSurroundingBlockLight) { highestSurroundingBlockLight = blockLightLevel; } return Math.max(highestSurroundingBlockLight - Math.max((blockType <= HIGHEST_KNOWN_BLOCK_ID) ? BLOCK_TRANSPARENCY[blockType] : 0, 1), 0); } private int getSkyLightLevelAt(int x, int y, int z) { if (y < 0) { return 0; } else if (y >= maxHeight) { return 15; } else { return world.getSkyLightLevel(x, z, y); } } private int getBlockLightLevelAt(int x, int y, int z) { if ((y < 0) || (y >= maxHeight)) { return 0; } else { return world.getBlockLightLevel(x, z, y); } } private final MinecraftWorld world; private final int maxHeight; private Box dirtyArea; private final boolean[][] DAYLIGHT = new boolean[16][16]; private final int[][] HEIGHT = new int[16][16]; }