/* * Copyright 2015 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.polyworld.raster; import java.util.Collection; import java.util.EnumSet; import org.terasology.commonworld.geom.BresenhamCollectorVisitor; import org.terasology.commonworld.geom.BresenhamLineIterator; import org.terasology.math.ChunkMath; import org.terasology.math.Region3i; import org.terasology.math.TeraMath; import org.terasology.math.geom.Vector2i; import org.terasology.math.geom.Vector3i; import org.terasology.polyworld.graph.Edge; import org.terasology.polyworld.graph.Graph; import org.terasology.polyworld.graph.GraphFacet; import org.terasology.polyworld.rivers.RiverModel; import org.terasology.polyworld.rivers.RiverModelFacet; import org.terasology.registry.CoreRegistry; import org.terasology.world.block.Block; import org.terasology.world.block.BlockManager; import org.terasology.world.chunks.ChunkConstants; import org.terasology.world.chunks.CoreChunk; import org.terasology.world.generation.Region; import org.terasology.world.generation.WorldRasterizer; import org.terasology.world.generation.facets.SeaLevelFacet; import org.terasology.world.generation.facets.SurfaceHeightFacet; /** * Rasterizer for the river model of PolyWorld. * * This rasterizer class turns the edges of the PolyWorld graph with a positive river value into actual in-game blocks. * The river width is determined by the river value associated with an edge s.t. the width is proportional to this value. */ public class RiverRasterizer implements WorldRasterizer { private Block water; private Block air; @Override public void initialize() { BlockManager blockManager = CoreRegistry.get(BlockManager.class); water = blockManager.getBlock("core:water"); air = blockManager.getBlock(BlockManager.AIR_ID); } @Override public void generateChunk(CoreChunk chunk, Region chunkRegion) { GraphFacet graphFacet = chunkRegion.getFacet(GraphFacet.class); SeaLevelFacet seaLevelFacet = chunkRegion.getFacet(SeaLevelFacet.class); RiverModelFacet riverModelFacet = chunkRegion.getFacet(RiverModelFacet.class); SurfaceHeightFacet surfaceHeightData = chunkRegion.getFacet(SurfaceHeightFacet.class); Region3i region = chunkRegion.getRegion(); int seaLevel = seaLevelFacet.getSeaLevel(); for (Graph graph : graphFacet.getAllGraphs()) { RiverModel riverModel = riverModelFacet.get(graph); for (Edge e : graph.getEdges()) { int riverValue = riverModel.getRiverValue(e); if (riverValue > 0) { int[][] structElem = getStructuringElement(riverValue); int x0 = TeraMath.floorToInt(e.getCorner0().getLocation().x()); int z0 = TeraMath.floorToInt(e.getCorner0().getLocation().y()); int x1 = TeraMath.floorToInt(e.getCorner1().getLocation().x()); int z1 = TeraMath.floorToInt(e.getCorner1().getLocation().y()); BresenhamCollectorVisitor bresenhamCollector = new BresenhamCollectorVisitor(); BresenhamLineIterator.iterateLine2D(x0, z0, x1, z1, bresenhamCollector, EnumSet.allOf(BresenhamLineIterator.Overlap.class)); Collection<Vector2i> line = bresenhamCollector.getLinePoints(); for (Vector2i p : line) { if (p.getX() >= region.minX() && p.getX() <= region.maxX() && p.getY() >= region.minZ() && p.getY() <= region.maxZ()) { int x = ChunkMath.calcBlockPosX(p.getX(), ChunkConstants.INNER_CHUNK_POS_FILTER.x); int z = ChunkMath.calcBlockPosZ(p.getY(), ChunkConstants.INNER_CHUNK_POS_FILTER.z); int y = TeraMath.floorToInt(surfaceHeightData.get(x, z)); Vector3i worldPos = new Vector3i(p.getX(), y, p.getY()); placeWaterBody(chunk, region, worldPos, structElem, seaLevel); } } } } } } /** * Returns a structuring element for the specified radius in form of a 2-dimensional disk/circle. * * For instance, the structuring element for a radius of 1 looks like follows: * 0|1|0 * 1|1|1 * 0|1|0 * * @param radius the radius of the structuring element * @return the matrix of the structuring element */ static int[][] getStructuringElement(int radius) { int[][] structElem = new int[2 * radius + 1][2 * radius + 1]; for (int y = -radius; y <= radius; y++) { for (int x = -radius; x <= radius; x++) { if (x * x + y * y <= radius * radius) { structElem[x + radius][y + radius] = 1; } } } return structElem; } /** * Places a 'disk' of water around the specified position. * * @param chunk the chunk to be edited * @param region chunk region being affected * @param worldPos the central position of the water body * @param structElem the structuring element for block placement * @param seaLevel */ private void placeWaterBody(CoreChunk chunk, Region3i region, Vector3i worldPos, int[][] structElem, int seaLevel) { int radius = (structElem.length - 1) / 2; Vector3i pos; for (int dx = -radius; dx <= radius; dx++) { for (int dz = -radius; dz <= radius; dz++) { if (structElem[dx + radius][dz + radius] != 0) { pos = new Vector3i(worldPos.add(dx, 0, dz)); // remove top layer (soil) if (region.encompasses(pos.x, pos.y, pos.z)) { chunk.setBlock(ChunkMath.calcBlockPos(pos.x, pos.y, pos.z), air); } // don't dig below the sea level if (pos.y > seaLevel) { pos.y -= 1; } if (region.encompasses(pos.x, pos.y, pos.z)) { chunk.setBlock(ChunkMath.calcBlockPos(pos.x, pos.y, pos.z), water); } } } } } }