package com.jmrapp.terralegion.game.utils; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; import com.badlogic.gdx.utils.Array; import com.jmrapp.terralegion.game.world.block.BlockManager; import com.jmrapp.terralegion.game.world.block.BlockProperties; import com.jmrapp.terralegion.game.world.chunk.Chunk; import com.jmrapp.terralegion.game.world.chunk.ChunkManager; public class LightUtils { /** The darkest a tile can be */ public static float MIN_LIGHT_VALUE = 0f, FLOAT_LOSS_PRECISION = 0.001f; private static final HashMap<String, QueuedLight> lightQueue = new HashMap<String, QueuedLight>(); private static final Array<QueuedLight> recycledLights = new Array<QueuedLight>(); public static void calculateChunkLight(ChunkManager manager, int lightX, int lightY, float lightValue, boolean lightSource) { if (lightValue > MIN_LIGHT_VALUE) { applyLight(manager, lightX, lightY, lightValue, lightSource); } } private static void applyLight(ChunkManager manager, int currentX, int currentY, float lightValue, boolean lightSource) { if (currentX < 0 || currentX >= (ChunkManager.CHUNKS_X * Chunk.CHUNK_SIZE) || currentY < 0 || currentY >= (ChunkManager.CHUNKS_Y * Chunk.CHUNK_SIZE)) { return; } Chunk posChunk = manager.getChunkFromTilePos(currentX, currentY); int posChunkX = currentX - (posChunk.getStartX() * Chunk.CHUNK_SIZE); int posChunkY = currentY - (posChunk.getStartY() * Chunk.CHUNK_SIZE); lightValue -= BlockManager.getBlock(posChunk.getBlock(posChunkX, posChunkY)).getLightBlockingAmount(); if (lightValue <= posChunk.getLightValue(posChunkX, posChunkY)) return; posChunk.setLightValue(lightValue, posChunkX, posChunkY); if (lightSource) posChunk.getBlockProperties(posChunkX, posChunkY).setFlag(BlockProperties.LIT_BY_LIGHT_SOURCE); if (lightValue <= LightUtils.MIN_LIGHT_VALUE) return; applyLight(manager, currentX + 1, currentY, lightValue, lightSource); applyLight(manager, currentX, currentY + 1, lightValue, lightSource); applyLight(manager, currentX - 1, currentY, lightValue, lightSource); applyLight(manager, currentX, currentY - 1, lightValue, lightSource); } public static void calculateChunkLightDown(ChunkManager manager, int lightX, int lightY, float lightValue) { if (lightValue > MIN_LIGHT_VALUE) { applyLightDown(manager, lightX, lightY, lightValue); } } /** Applies light only downwards and doesn't extend out. Used for the lighting from the sun. * * @param manager The chunk manager for the world * @param currentX The current tile X we're calculating * @param currentY The current tile Y we're calculating * @param lightValue The decreasing lightValue to set */ private static void applyLightDown(ChunkManager manager, int currentX, int currentY, float lightValue) { if (currentX < 0 || currentX >= (ChunkManager.CHUNKS_X * Chunk.CHUNK_SIZE) || currentY < 0 || currentY >= (ChunkManager.CHUNKS_Y * Chunk.CHUNK_SIZE)) { return; } Chunk posChunk = manager.getChunkFromTilePos(currentX, currentY); int posChunkX = currentX - (posChunk.getStartX() * Chunk.CHUNK_SIZE); int posChunkY = currentY - (posChunk.getStartY() * Chunk.CHUNK_SIZE); lightValue -= BlockManager.getBlock(posChunk.getBlock(posChunkX, posChunkY)).getLightBlockingAmount(); float totalLight = lightValue; if (totalLight <= posChunk.getLightValue(posChunkX, posChunkY)) return; posChunk.setLightValue(totalLight, posChunkX, posChunkY); if (lightValue <= LightUtils.MIN_LIGHT_VALUE) return; applyLightDown(manager, currentX, currentY - 1, lightValue); } /** Reverses the process of lighting the world and removes the light that surround the selected tile. * * @param manager The chunk manager instance * @param lightX The light X position * @param lightY The light Y position * @param lightValue The light value to start extinguishing at */ public static void extinguishLight(ChunkManager manager, int lightX, int lightY, float lightValue, boolean lightSource) { if (lightValue > MIN_LIGHT_VALUE) { applyExtinguishingLight(manager, lightX, lightY, lightValue, false); Iterator<Entry<String, QueuedLight>> it = lightQueue.entrySet().iterator(); while (it.hasNext()) { QueuedLight light = it.next().getValue(); float currentLight = light.getChunk().getLightValue(light.getX(), light.getY()); if (currentLight > MIN_LIGHT_VALUE) { //make sure another iteration didnt change the light value after it was added float lightBlockage = BlockManager.getBlock(light.getChunk().getBlock(light.getX(), light.getY())).getLightBlockingAmount(); light.getChunk().setLightValue(MIN_LIGHT_VALUE, light.getX(), light.getY()); applyLight(manager, light.getX() + (light.getChunk().getStartX() * Chunk.CHUNK_SIZE), light.getY() + (light.getChunk().getStartY() * Chunk.CHUNK_SIZE), currentLight + lightBlockage, lightSource); } recycledLights.add(light); it.remove(); } } } public static void extinguishLightAmount(ChunkManager manager, int lightX, int lightY, float originalLightValue, float subtractedValue, boolean lightSource) { if (originalLightValue > MIN_LIGHT_VALUE) { applyExtinguishingLightAmount(manager, lightX, lightY, originalLightValue, subtractedValue, false); Iterator<Entry<String, QueuedLight>> it = lightQueue.entrySet().iterator(); while (it.hasNext()) { QueuedLight light = it.next().getValue(); float currentLight = light.getChunk().getLightValue(light.getX(), light.getY()); if (currentLight > MIN_LIGHT_VALUE) { //make sure another iteration didnt change the light value after it was added float lightBlockage = BlockManager.getBlock(light.getChunk().getBlock(light.getX(), light.getY())).getLightBlockingAmount(); //light.getChunk().setLightValue(MIN_LIGHT_VALUE, light.getX(), light.getY()); //applyLight(manager, light.getX() + (light.getChunk().getStartX() * Chunk.CHUNK_SIZE), light.getY() + (light.getChunk().getStartY() * Chunk.CHUNK_SIZE), currentLight + lightBlockage, lightSource); } recycledLights.add(light); it.remove(); } } } private static void applyExtinguishingLight(ChunkManager manager, int currentX, int currentY, float lightValue, boolean checkEdges) { if (currentX < 0 || currentX >= (ChunkManager.CHUNKS_X * Chunk.CHUNK_SIZE) || currentY < 0 || currentY >= (ChunkManager.CHUNKS_Y * Chunk.CHUNK_SIZE)) { return; } Chunk posChunk = manager.getChunkFromTilePos(currentX, currentY); int posChunkX = currentX - (posChunk.getStartX() * Chunk.CHUNK_SIZE); int posChunkY = currentY - (posChunk.getStartY() * Chunk.CHUNK_SIZE); lightValue -= BlockManager.getBlock(posChunk.getBlock(posChunkX, posChunkY)).getLightBlockingAmount(); boolean edgeFlag = false; //Tells to set checkEdges at the end of the algorithm float currentLight = posChunk.getLightValue(posChunkX, posChunkY); if (currentLight <= LightUtils.MIN_LIGHT_VALUE) { return; } //Assume that the precision loss amount is about .001f if (currentLight - lightValue > FLOAT_LOSS_PRECISION) { //If the value of the light is greater than what it should be, then queue it. lightQueue.put(posChunk.getStartX() + " " + posChunk.getStartY() + " " + posChunkX + " " + posChunkY, getQueuedLight(posChunk, posChunkX, posChunkY));//Queue this tile to calculate light again edgeFlag = true; } else if (lightValue - currentLight > FLOAT_LOSS_PRECISION) { //if less than the light it should be then something else will handle it return; } if (checkEdges) //See if we were only checking edges for higher values to recalculate return; if (!edgeFlag) { posChunk.setLightValue(MIN_LIGHT_VALUE, posChunkX, posChunkY); //extinguish the light posChunk.getBlockProperties(posChunkX, posChunkY).removeFlag(BlockProperties.LIT_BY_LIGHT_SOURCE); lightQueue.remove(posChunk.getStartX() + " " + posChunk.getStartY() + " " + posChunkX + " " + posChunkY);//Remove if in queue } if (edgeFlag || lightValue <= MIN_LIGHT_VALUE) //Set the checkEdges if we flagged it to do so or if we have the smallest amount of light checkEdges = true; applyExtinguishingLight(manager, currentX + 1, currentY, lightValue, checkEdges); applyExtinguishingLight(manager, currentX, currentY + 1, lightValue, checkEdges); applyExtinguishingLight(manager, currentX - 1, currentY, lightValue, checkEdges); applyExtinguishingLight(manager, currentX, currentY - 1, lightValue, checkEdges); } private static void applyExtinguishingLightAmount(ChunkManager manager, int currentX, int currentY, float lightValue, float subtractedValue, boolean checkEdges) { if (currentX < 0 || currentX >= (ChunkManager.CHUNKS_X * Chunk.CHUNK_SIZE) || currentY < 0 || currentY >= (ChunkManager.CHUNKS_Y * Chunk.CHUNK_SIZE)) { return; } Chunk posChunk = manager.getChunkFromTilePos(currentX, currentY); int posChunkX = currentX - (posChunk.getStartX() * Chunk.CHUNK_SIZE); int posChunkY = currentY - (posChunk.getStartY() * Chunk.CHUNK_SIZE); boolean edgeFlag = false; //Tells to set checkEdges at the end of the algorithm float currentLight = posChunk.getLightValue(posChunkX, posChunkY); if (currentLight <= LightUtils.MIN_LIGHT_VALUE) { return; } //Assume that the precision loss amount is about .001f if (currentLight - lightValue > FLOAT_LOSS_PRECISION || lightValue - currentLight > FLOAT_LOSS_PRECISION) { //If the value of the light is greater than what it should be, then queue it. //lightQueue.put(posChunk.getStartX() + " " + posChunk.getStartY() + " " + posChunkX + " " + posChunkY, getQueuedLight(posChunk, posChunkX, posChunkY));//Queue this tile to calculate light again edgeFlag = true; } else if (lightValue - currentLight > FLOAT_LOSS_PRECISION) { //if less than the light it should be then something else will handle it return; } if (checkEdges) //See if we were only checking edges for higher values to recalculate return; if (!edgeFlag) { //if (currentLight - lightValue >= 0 && currentLight - lightValue <= FLOAT_LOSS_PRECISION) { posChunk.setLightValue(currentLight - subtractedValue, posChunkX, posChunkY); posChunk.getBlockProperties(posChunkX, posChunkY).removeFlag(BlockProperties.LIT_BY_LIGHT_SOURCE); //lightQueue.remove(posChunk.getStartX() + " " + posChunk.getStartY() + " " + posChunkX + " " + posChunkY);//Remove if in queue //} } if (edgeFlag || lightValue <= MIN_LIGHT_VALUE) //Set the checkEdges if we flagged it to do so or if we have the smallest amount of light checkEdges = true; float blockingValue = BlockManager.getBlock(posChunk.getBlock(posChunkX, posChunkY)).getLightBlockingAmount(); applyExtinguishingLightAmount(manager, currentX + 1, currentY, lightValue - blockingValue, subtractedValue, checkEdges); applyExtinguishingLightAmount(manager, currentX, currentY + 1, lightValue - blockingValue, subtractedValue, checkEdges); applyExtinguishingLightAmount(manager, currentX - 1, currentY, lightValue - blockingValue, subtractedValue, checkEdges); applyExtinguishingLightAmount(manager, currentX, currentY - 1, lightValue - blockingValue, subtractedValue, checkEdges); } private static QueuedLight getQueuedLight(Chunk chunk, int x, int y) { if (recycledLights.size > 0) { QueuedLight light = recycledLights.removeIndex(0); light.set(chunk, x, y); return light; } return new QueuedLight(chunk, x, y); } } class QueuedLight { private Chunk chunk; private int x, y; public QueuedLight(Chunk chunk, int x, int y) { this.chunk = chunk; this.x = x; this.y = y; } public void set(Chunk chunk, int x, int y) { this.chunk = chunk; this.x = x; this.y = y; } public Chunk getChunk() { return chunk; } public int getX() { return x; } public int getY() { return y; } }