Java tutorial
/* * 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); } }