/*
* Copyright 2014 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.world.propagation;
import com.google.common.collect.Sets;
import org.terasology.math.ChunkMath;
import org.terasology.math.Side;
import org.terasology.math.geom.Vector3i;
import org.terasology.world.block.Block;
import org.terasology.world.chunks.ChunkConstants;
import org.terasology.world.chunks.LitChunk;
import java.util.Arrays;
import java.util.Set;
/**
* Batch propagator that works on a set of changed blocks
*
*/
public class SunlightRegenBatchPropagator implements BatchPropagator {
private PropagationRules regenRules;
private PropagatorWorldView regenWorld;
private PropagatorWorldView sunlightWorld;
private BatchPropagator sunlightPropagator;
private Set<Vector3i>[] reduceQueues;
private Set<Vector3i>[] increaseQueues;
public SunlightRegenBatchPropagator(PropagationRules regenRules, PropagatorWorldView regenWorld, BatchPropagator sunlightPropagator, PropagatorWorldView sunlightWorld) {
this.regenRules = regenRules;
this.regenWorld = regenWorld;
this.sunlightPropagator = sunlightPropagator;
this.sunlightWorld = sunlightWorld;
increaseQueues = new Set[regenRules.getMaxValue() + 1];
reduceQueues = new Set[regenRules.getMaxValue() + 1];
for (int i = 0; i < regenRules.getMaxValue() + 1; ++i) {
increaseQueues[i] = Sets.newLinkedHashSet();
reduceQueues[i] = Sets.newLinkedHashSet();
}
}
@Override
public void process(BlockChange... changes) {
process(Arrays.asList(changes));
}
@Override
public void process(Iterable<BlockChange> blockChanges) {
for (BlockChange blockChange : blockChanges) {
reviewChange(blockChange);
}
processRegenReduction();
processRegenIncrease();
cleanUp();
}
private void reviewChange(BlockChange blockChange) {
reviewChangeToTop(blockChange);
reviewChangeToBottom(blockChange);
}
private void reviewChangeToBottom(BlockChange blockChange) {
PropagationComparison comparison = regenRules.comparePropagation(blockChange.getTo(), blockChange.getFrom(), Side.BOTTOM);
if (comparison.isPermitting()) {
byte existingValue = regenWorld.getValueAt(blockChange.getPosition());
queueSpreadRegen(blockChange.getPosition(), existingValue);
} else if (comparison.isRestricting()) {
Vector3i adjPos = Side.BOTTOM.getAdjacentPos(blockChange.getPosition());
byte existingValue = regenWorld.getValueAt(adjPos);
reduce(adjPos, existingValue);
}
}
private void reviewChangeToTop(BlockChange blockChange) {
PropagationComparison comparison = regenRules.comparePropagation(blockChange.getTo(), blockChange.getFrom(), Side.TOP);
if (comparison.isPermitting()) {
Vector3i adjPos = Side.TOP.getAdjacentPos(blockChange.getPosition());
byte adjValue = regenWorld.getValueAt(adjPos);
if (adjValue != PropagatorWorldView.UNAVAILABLE) {
queueSpreadRegen(adjPos, adjValue);
}
} else if (comparison.isRestricting()) {
byte existingValue = regenWorld.getValueAt(blockChange.getPosition());
reduce(blockChange.getPosition(), existingValue);
}
}
private void queueSpreadRegen(Vector3i position, byte value) {
increaseQueues[value].add(position);
}
private void processRegenReduction() {
for (byte depth = 0; depth <= regenRules.getMaxValue(); depth++) {
Set<Vector3i> toProcess = reduceQueues[depth];
toProcess.forEach(this::purge);
toProcess.clear();
}
}
private void purge(Vector3i pos) {
int expectedValue = regenWorld.getValueAt(pos);
if (expectedValue != 0) {
Vector3i position = new Vector3i(pos);
for (byte i = 0; i <= ChunkConstants.MAX_SUNLIGHT_REGEN; ++i) {
if (regenWorld.getValueAt(position) == expectedValue) {
regenWorld.setValueAt(position, i);
if (expectedValue - ChunkConstants.SUNLIGHT_REGEN_THRESHOLD > 0) {
sunlightPropagator.regenerate(new Vector3i(position), (byte) (expectedValue - ChunkConstants.SUNLIGHT_REGEN_THRESHOLD));
}
} else {
break;
}
position.y--;
if (expectedValue < ChunkConstants.MAX_SUNLIGHT_REGEN) {
expectedValue++;
}
}
}
}
private void processRegenIncrease() {
for (byte depth = regenRules.getMaxValue(); depth >= 0; depth--) {
Set<Vector3i> toProcess = increaseQueues[depth];
for (Vector3i pos : toProcess) {
push(pos, depth);
}
toProcess.clear();
}
}
private void push(Vector3i pos, byte value) {
byte regenValue = value;
Block block = regenWorld.getBlockAt(pos);
Vector3i position = new Vector3i(pos);
while (regenRules.canSpreadOutOf(block, Side.BOTTOM)) {
regenValue = regenRules.propagateValue(regenValue, Side.BOTTOM, block);
position.y -= 1;
byte adjValue = regenWorld.getValueAt(position);
if (adjValue < regenValue && adjValue != PropagatorWorldView.UNAVAILABLE) {
block = regenWorld.getBlockAt(position);
if (regenRules.canSpreadInto(block, Side.TOP)) {
regenWorld.setValueAt(position, regenValue);
reduceQueues[adjValue].remove(position);
byte sunlightValue = (byte) (regenValue - ChunkConstants.SUNLIGHT_REGEN_THRESHOLD);
if (sunlightValue > 0) {
byte prevValue = sunlightWorld.getValueAt(position);
if (prevValue < sunlightValue) {
sunlightWorld.setValueAt(position, sunlightValue);
sunlightPropagator.propagateFrom(new Vector3i(position), sunlightValue);
}
}
} else {
break;
}
} else {
break;
}
}
}
private void cleanUp() {
}
private void reduce(Vector3i position, byte oldValue) {
if (oldValue > 0) {
reduceQueues[oldValue].add(position);
}
}
@Override
public void propagateBetween(LitChunk chunk, LitChunk adjChunk, Side side, boolean propagateExternal) {
if (side == Side.BOTTOM) {
int[] depth = new int[ChunkConstants.SIZE_X * ChunkConstants.SIZE_Z];
int[] startingRegen = new int[depth.length];
propagateSweep(chunk, adjChunk, depth, startingRegen);
int[] adjDepths = new int[depth.length];
ChunkMath.populateMinAdjacent2D(depth, adjDepths, ChunkConstants.SIZE_X, ChunkConstants.SIZE_Z, !propagateExternal);
if (propagateExternal) {
for (int z = 0; z < ChunkConstants.SIZE_Z; ++z) {
adjDepths[z * ChunkConstants.SIZE_X] = 0;
adjDepths[ChunkConstants.SIZE_X - 1 + z * ChunkConstants.SIZE_X] = 0;
}
for (int x = 0; x < ChunkConstants.SIZE_X; ++x) {
adjDepths[x] = 0;
adjDepths[x + ChunkConstants.SIZE_X * (ChunkConstants.SIZE_Z - 1)] = 0;
}
}
int[] adjStartingRegen = new int[depth.length];
ChunkMath.populateMinAdjacent2D(startingRegen, adjStartingRegen, ChunkConstants.SIZE_X, ChunkConstants.SIZE_Z, true);
markForPropagation(adjChunk, depth, startingRegen, adjDepths, adjStartingRegen);
}
}
private void markForPropagation(LitChunk toChunk, int[] depth, int[] startingRegen, int[] adjDepths, int[] adjStartingRegen) {
Vector3i pos = new Vector3i();
for (int z = 0; z < ChunkConstants.SIZE_Z; ++z) {
for (int x = 0; x < ChunkConstants.SIZE_X; ++x) {
int depthIndex = x + ChunkConstants.SIZE_X * z;
int start = startingRegen[depthIndex];
int adjStart = adjStartingRegen[depthIndex];
if (start - adjStart > 1) {
int initialDepth = Math.max(ChunkConstants.SUNLIGHT_REGEN_THRESHOLD - start, 0);
int finalDepth = depth[depthIndex];
int strength = Math.min(start + initialDepth - ChunkConstants.SUNLIGHT_REGEN_THRESHOLD + 1, ChunkConstants.MAX_SUNLIGHT);
for (int i = initialDepth; i <= finalDepth; ++i) {
sunlightPropagator.propagateFrom(toChunk.chunkToWorldPosition(x, ChunkConstants.SIZE_Y - i - 1, z),
(byte) (strength));
if (strength < ChunkConstants.MAX_SUNLIGHT) {
strength++;
}
}
} else {
int initialDepth = Math.max(adjDepths[depthIndex], ChunkConstants.SUNLIGHT_REGEN_THRESHOLD - start);
byte strength = (byte) Math.min(ChunkConstants.MAX_SUNLIGHT, start + initialDepth - ChunkConstants.SUNLIGHT_REGEN_THRESHOLD + 1);
for (int i = initialDepth; i <= depth[depthIndex]; ++i) {
sunlightPropagator.propagateFrom(toChunk.chunkToWorldPosition(x, ChunkConstants.SIZE_Y - i - 1, z), strength);
if (strength < ChunkConstants.MAX_SUNLIGHT) {
strength++;
}
pos.y--;
}
}
}
}
}
private void propagateSweep(LitChunk fromChunk, LitChunk toChunk, int[] depth, int[] startingRegen) {
Vector3i pos = new Vector3i();
for (int z = 0; z < ChunkConstants.SIZE_Z; ++z) {
for (int x = 0; x < ChunkConstants.SIZE_X; ++x) {
int depthIndex = x + ChunkConstants.SIZE_X * z;
startingRegen[depthIndex] = regenRules.getValue(fromChunk, new Vector3i(x, 0, z));
byte expectedValue = (byte) Math.min(startingRegen[depthIndex] + 1, ChunkConstants.MAX_SUNLIGHT_REGEN);
Block fromBlock = fromChunk.getBlock(x, 0, z);
Block toBlock = toChunk.getBlock(x, ChunkConstants.SIZE_Y - 1, z);
if (!(regenRules.canSpreadOutOf(fromBlock, Side.BOTTOM) && regenRules.canSpreadInto(toBlock, Side.TOP))) {
continue;
}
byte predictedValue = 0;
pos.set(x, ChunkConstants.SIZE_Y - 1, z);
int currentValue = regenRules.getValue(toChunk, pos);
while (currentValue == predictedValue && expectedValue > currentValue) {
regenRules.setValue(toChunk, pos, expectedValue);
depth[depthIndex]++;
byte sunlight = (byte) (expectedValue - ChunkConstants.SUNLIGHT_REGEN_THRESHOLD);
if (sunlight > 0 && sunlight > toChunk.getSunlight(pos)) {
toChunk.setSunlight(pos, sunlight);
}
if (expectedValue < ChunkConstants.MAX_SUNLIGHT_REGEN) {
expectedValue++;
}
predictedValue++;
pos.y--;
currentValue = regenRules.getValue(toChunk, pos);
}
}
}
}
@Override
public void propagateFrom(Vector3i pos, Block block) {
throw new UnsupportedOperationException();
}
@Override
public void propagateFrom(Vector3i pos, byte value) {
throw new UnsupportedOperationException();
}
@Override
public void regenerate(Vector3i pos, byte value) {
throw new UnsupportedOperationException();
}
}