org.terasology.world.propagation.SunlightRegenBatchPropagator.java Source code

Java tutorial

Introduction

Here is the source code for org.terasology.world.propagation.SunlightRegenBatchPropagator.java

Source

/*
 * 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.Side;
import org.terasology.math.TeraMath;
import org.terasology.math.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
 *
 * @author Immortius
 */
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];

            for (Vector3i pos : toProcess) {
                purge(pos);
            }
            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];
            TeraMath.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];
            TeraMath.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();
    }

}