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

Java tutorial

Introduction

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

Source

/*
 * Copyright 2013 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.Maps;
import com.google.common.collect.Sets;
import org.terasology.math.Region3i;
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.Map;
import java.util.Set;

/**
 * Batch propagator that works on a set of changed blocks
 *
 * @author Immortius
 */
public class StandardBatchPropagator implements BatchPropagator {

    private static final byte NO_VALUE = 0;

    private PropagationRules rules;
    private PropagatorWorldView world;

    private Set<Vector3i>[] reduceQueues;
    private Set<Vector3i>[] increaseQueues;

    private Map<Side, Vector3i> chunkEdgeDeltas = Maps.newEnumMap(Side.class);

    public StandardBatchPropagator(PropagationRules rules, PropagatorWorldView world) {
        this.world = world;
        this.rules = rules;

        for (Side side : Side.values()) {
            Vector3i delta = new Vector3i(side.getVector3i());
            if (delta.x < 0) {
                delta.x += ChunkConstants.SIZE_X;
            } else if (delta.x > 0) {
                delta.x -= ChunkConstants.SIZE_X;
            }
            if (delta.y < 0) {
                delta.y += ChunkConstants.SIZE_Y;
            } else if (delta.y > 0) {
                delta.y -= ChunkConstants.SIZE_Y;
            }
            if (delta.z < 0) {
                delta.z += ChunkConstants.SIZE_Z;
            } else if (delta.z > 0) {
                delta.z -= ChunkConstants.SIZE_Z;
            }
            chunkEdgeDeltas.put(side, delta);
        }

        increaseQueues = new Set[rules.getMaxValue()];
        reduceQueues = new Set[rules.getMaxValue()];
        for (int i = 0; i < rules.getMaxValue(); ++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);
        }

        processReduction();
        processIncrease();
        cleanUp();
    }

    private void reviewChange(BlockChange blockChange) {
        byte newValue = rules.getFixedValue(blockChange.getTo(), blockChange.getPosition());
        byte existingValue = world.getValueAt(blockChange.getPosition());
        if (newValue > existingValue) {
            increase(blockChange.getPosition(), newValue);
        }

        byte oldValue = rules.getFixedValue(blockChange.getFrom(), blockChange.getPosition());
        if (newValue < oldValue) {
            reduce(blockChange.getPosition(), oldValue);
        }

        for (Side side : Side.values()) {
            PropagationComparison comparison = rules.comparePropagation(blockChange.getTo(), blockChange.getFrom(),
                    side);
            if (comparison.isRestricting() && existingValue > 0) {
                reduce(blockChange.getPosition(), existingValue);
                Vector3i adjPos = side.getAdjacentPos(blockChange.getPosition());
                byte adjValue = world.getValueAt(adjPos);
                if (adjValue == rules.propagateValue(existingValue, side, blockChange.getFrom())) {
                    reduce(adjPos, adjValue);
                }
            } else if (comparison.isPermitting()) {
                if (existingValue > 0) {
                    queueSpreadValue(blockChange.getPosition(), existingValue);
                }
                Vector3i adjPos = side.getAdjacentPos(blockChange.getPosition());
                byte adjValue = world.getValueAt(adjPos);
                if (adjValue != PropagatorWorldView.UNAVAILABLE) {
                    queueSpreadValue(adjPos, adjValue);
                }
            }
        }
    }

    private void processReduction() {
        int depth = 0;
        while (depth < rules.getMaxValue()) {
            byte oldValue = (byte) (rules.getMaxValue() - depth);
            Set<Vector3i> toProcess = reduceQueues[depth];
            if (!toProcess.isEmpty()) {
                reduceQueues[depth] = Sets.newLinkedHashSetWithExpectedSize(toProcess.size());

                for (Vector3i pos : toProcess) {
                    purge(pos, oldValue);
                }
                if (toProcess.isEmpty()) {
                    depth++;
                }
            } else {
                depth++;
            }
        }
    }

    private void purge(Vector3i pos, byte oldValue) {
        Block block = world.getBlockAt(pos);
        increaseQueues[rules.getMaxValue() - oldValue].remove(pos);
        byte fixedValue = rules.getFixedValue(block, pos);
        if (fixedValue > 0) {
            increase(pos, fixedValue);
        } else {
            world.setValueAt(pos, NO_VALUE);
        }

        for (Side side : Side.values()) {
            byte expectedValue = rules.propagateValue(oldValue, side, block);
            Vector3i adjPos = side.getAdjacentPos(pos);
            if (rules.canSpreadOutOf(block, side)) {
                byte adjValue = world.getValueAt(adjPos);
                if (adjValue == expectedValue) {
                    Block adjBlock = world.getBlockAt(adjPos);
                    if (rules.canSpreadInto(adjBlock, side.reverse())) {
                        reduce(adjPos, expectedValue);
                    }
                } else if (adjValue > 0) {
                    queueSpreadValue(adjPos, adjValue);
                }
            }
        }
    }

    private void processIncrease() {
        int depth = 0;
        while (depth < rules.getMaxValue() - 1) {
            byte value = (byte) (rules.getMaxValue() - depth);
            Set<Vector3i> toProcess = increaseQueues[depth];
            if (!toProcess.isEmpty()) {
                increaseQueues[depth] = Sets.newLinkedHashSetWithExpectedSize(toProcess.size());

                for (Vector3i pos : toProcess) {
                    push(pos, value);
                }
                if (increaseQueues[depth].isEmpty()) {
                    depth++;
                }
            } else {
                depth++;
            }
        }
    }

    private void push(Vector3i pos, byte value) {
        Block block = world.getBlockAt(pos);
        for (Side side : Side.values()) {
            byte spreadValue = rules.propagateValue(value, side, block);
            Vector3i adjPos = side.getAdjacentPos(pos);
            if (rules.canSpreadOutOf(block, side)) {
                byte adjValue = world.getValueAt(adjPos);
                if (adjValue < spreadValue && adjValue != PropagatorWorldView.UNAVAILABLE) {
                    Block adjBlock = world.getBlockAt(adjPos);
                    if (rules.canSpreadInto(adjBlock, side.reverse())) {
                        increase(adjPos, spreadValue);
                    }
                }
            }
        }
    }

    private void cleanUp() {
        for (Set<Vector3i> queue : increaseQueues) {
            queue.clear();
        }
    }

    private void increase(Vector3i position, byte value) {
        world.setValueAt(position, value);
        queueSpreadValue(position, value);
    }

    private void queueSpreadValue(Vector3i position, byte value) {
        if (value > 1) {
            increaseQueues[rules.getMaxValue() - value].add(position);
        }
    }

    private void reduce(Vector3i position, byte oldValue) {
        if (oldValue > 0) {
            reduceQueues[rules.getMaxValue() - oldValue].add(position);
        }
    }

    @Override
    public void propagateBetween(LitChunk chunk, LitChunk adjChunk, Side side, boolean propagateExternal) {
        IndexProvider indexProvider = createIndexProvider(side);

        Region3i edgeRegion = TeraMath
                .getEdgeRegion(Region3i.createFromMinAndSize(Vector3i.zero(), ChunkConstants.CHUNK_SIZE), side);

        int edgeSize = edgeRegion.size().x * edgeRegion.size().y * edgeRegion.size().z;
        int[] depth = new int[edgeSize];

        propagateSide(chunk, adjChunk, side, indexProvider, edgeRegion, depth);
        propagateDepth(adjChunk, side, propagateExternal, indexProvider, edgeRegion, depth);
    }

    private void propagateDepth(LitChunk adjChunk, Side side, boolean propagateExternal,
            IndexProvider indexProvider, Region3i edgeRegion, int[] depths) {
        Vector3i adjPos = new Vector3i();

        int[] adjDepth = new int[depths.length];
        int dimA = (side == Side.LEFT || side == Side.RIGHT) ? ChunkConstants.SIZE_Y : ChunkConstants.SIZE_X;
        int dimB = (side == Side.FRONT || side == Side.BACK) ? ChunkConstants.SIZE_Y : ChunkConstants.SIZE_Z;
        TeraMath.populateMinAdjacent2D(depths, adjDepth, dimA, dimB, !propagateExternal);

        if (propagateExternal) {
            for (int y = 0; y < dimB; ++y) {
                adjDepth[y * dimA] = 0;
                adjDepth[dimA - 1 + y * dimA] = 0;
            }
            for (int x = 0; x < dimA; ++x) {
                adjDepth[x] = 0;
                adjDepth[x + dimA * (dimB - 1)] = 0;
            }
        }

        for (Vector3i pos : edgeRegion) {
            int depthIndex = indexProvider.getIndexFor(pos);
            int adjacentDepth = adjDepth[depthIndex];
            for (int i = adjacentDepth; i < depths[depthIndex]; ++i) {
                adjPos.set(side.getVector3i());
                adjPos.mult(i + 1);
                adjPos.add(pos);
                adjPos.add(chunkEdgeDeltas.get(side));
                byte value = rules.getValue(adjChunk, adjPos);
                if (value > 1) {
                    queueSpreadValue(adjChunk.chunkToWorldPosition(adjPos), value);
                }
            }
        }
    }

    private void propagateSide(LitChunk chunk, LitChunk adjChunk, Side side, IndexProvider indexProvider,
            Region3i edgeRegion, int[] depths) {
        Vector3i adjPos = new Vector3i();
        for (int x = edgeRegion.minX(); x <= edgeRegion.maxX(); ++x) {
            for (int y = edgeRegion.minY(); y <= edgeRegion.maxY(); ++y) {
                for (int z = edgeRegion.minZ(); z <= edgeRegion.maxZ(); ++z) {

                    int depthIndex = indexProvider.getIndexFor(x, y, z);
                    adjPos.set(x, y, z);
                    adjPos.add(chunkEdgeDeltas.get(side));

                    byte expectedValue = (byte) (rules.getValue(chunk, x, y, z) - 1);
                    if (expectedValue < 1) {
                        continue;
                    }

                    int depth = 0;
                    Block lastBlock = chunk.getBlock(x, y, z);
                    byte adjValue = rules.getValue(adjChunk, adjPos);
                    while (expectedValue > adjValue && adjValue != PropagatorWorldView.UNAVAILABLE
                            && rules.canSpreadOutOf(lastBlock, side)) {
                        lastBlock = adjChunk.getBlock(adjPos);
                        if (rules.canSpreadInto(lastBlock, side.reverse())) {
                            rules.setValue(adjChunk, adjPos, expectedValue);
                            adjPos.add(side.getVector3i());
                            depth++;
                            expectedValue--;
                            adjValue = rules.getValue(adjChunk, adjPos);
                        } else {
                            break;
                        }
                    }
                    depths[depthIndex] = depth;
                }
            }
        }
    }

    private IndexProvider createIndexProvider(Side side) {
        IndexProvider indexProvider;
        switch (side) {
        case TOP:
        case BOTTOM:
            indexProvider = new IndexProvider() {
                @Override
                public int getIndexFor(Vector3i pos) {
                    return pos.x + ChunkConstants.SIZE_X * pos.z;
                }

                @Override
                public int getIndexFor(int x, int y, int z) {
                    return x + ChunkConstants.SIZE_X * z;
                }
            };
            break;
        case LEFT:
        case RIGHT:
            indexProvider = new IndexProvider() {
                @Override
                public int getIndexFor(Vector3i pos) {
                    return pos.y + ChunkConstants.SIZE_Y * pos.z;
                }

                @Override
                public int getIndexFor(int x, int y, int z) {
                    return y + ChunkConstants.SIZE_Y * z;
                }
            };
            break;
        default:
            indexProvider = new IndexProvider() {

                @Override
                public int getIndexFor(Vector3i pos) {
                    return pos.x + ChunkConstants.SIZE_X * pos.y;
                }

                @Override
                public int getIndexFor(int x, int y, int z) {
                    return x + ChunkConstants.SIZE_X * y;
                }
            };
            break;
        }
        return indexProvider;
    }

    @Override
    public void propagateFrom(Vector3i pos, Block block) {
        queueSpreadValue(pos, rules.getFixedValue(block, pos));
    }

    @Override
    public void propagateFrom(Vector3i pos, byte value) {
        queueSpreadValue(pos, value);
    }

    @Override
    public void regenerate(Vector3i pos, byte value) {
        reduce(pos, value);
    }

    private interface IndexProvider {
        int getIndexFor(Vector3i pos);

        int getIndexFor(int x, int y, int z);
    }
}