/* * * MIT License * * Copyright (c) 2020 TerraForged * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.terraforged.core.region; import com.terraforged.core.cell.Cell; import com.terraforged.core.cell.Extent; import com.terraforged.core.filter.Filterable; import com.terraforged.core.region.chunk.ChunkReader; import com.terraforged.core.region.chunk.ChunkWriter; import com.terraforged.core.util.concurrent.Disposable; import com.terraforged.core.world.decorator.Decorator; import com.terraforged.core.world.heightmap.Heightmap; import com.terraforged.core.world.rivermap.RiverRegionList; import com.terraforged.core.world.terrain.Terrain; import me.dags.noise.util.NoiseUtil; import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; public class Region implements Extent, Disposable { private final int regionX; private final int regionZ; private final int chunkX; private final int chunkZ; private final int blockX; private final int blockZ; private final int border; private final Size blockSize; private final Size chunkSize; private final GenCell[] blocks; private final GenChunk[] chunks; private final int disposableChunks; private final Disposable.Listener disposalListener; private final AtomicInteger disposedChunks = new AtomicInteger(); public Region(int regionX, int regionZ, int size, int borderChunks) { this(regionX, regionZ, size, borderChunks, region -> {}); } public Region(int regionX, int regionZ, int size, int borderChunks, Disposable.Listener disposalListener) { this.regionX = regionX; this.regionZ = regionZ; this.chunkX = regionX << size; this.chunkZ = regionZ << size; this.blockX = Size.chunkToBlock(chunkX); this.blockZ = Size.chunkToBlock(chunkZ); this.border = borderChunks; this.chunkSize = Size.chunks(size, borderChunks); this.blockSize = Size.blocks(size, borderChunks); this.disposalListener = disposalListener; this.disposableChunks = chunkSize.size * chunkSize.size; this.blocks = new GenCell[blockSize.total * blockSize.total]; this.chunks = new GenChunk[chunkSize.total * chunkSize.total]; } @Override public void dispose() { int disposed = disposedChunks.incrementAndGet(); if (disposed < disposableChunks) { return; } disposalListener.onDispose(this); } public long getRegionId() { return NoiseUtil.seed(getRegionX(), getRegionZ()); } public int getRegionX() { return regionX; } public int getRegionZ() { return regionZ; } public int getBlockX() { return blockX; } public int getBlockZ() { return blockZ; } public int getOffsetChunks() { return border; } public int getChunkCount() { return chunks.length; } public int getBlockCount() { return blocks.length; } public Size getChunkSize() { return chunkSize; } public Size getBlockSize() { return blockSize; } public Filterable filterable() { return new FilterRegion(); } public Cell getCell(int blockX, int blockZ) { int relBlockX = blockSize.border + blockSize.mask(blockX); int relBlockZ = blockSize.border + blockSize.mask(blockZ); int index = blockSize.indexOf(relBlockX, relBlockZ); return blocks[index]; } public Cell getRawCell(int blockX, int blockZ) { int index = blockSize.indexOf(blockX, blockZ); return blocks[index]; } public ChunkReader getChunk(int chunkX, int chunkZ) { int relChunkX = chunkSize.border + chunkSize.mask(chunkX); int relChunkZ = chunkSize.border + chunkSize.mask(chunkZ); int index = chunkSize.indexOf(relChunkX, relChunkZ); return chunks[index]; } public void generate(Consumer consumer) { for (int cz = 0; cz < chunkSize.total; cz++) { for (int cx = 0; cx < chunkSize.total; cx++) { int index = chunkSize.indexOf(cx, cz); GenChunk chunk = computeChunk(index, cx, cz); consumer.accept(chunk); } } } public void generateBase(Heightmap heightmap) { for (int cz = 0; cz < chunkSize.total; cz++) { for (int cx = 0; cx < chunkSize.total; cx++) { int index = chunkSize.indexOf(cx, cz); GenChunk chunk = computeChunk(index, cx, cz); for (int dz = 0; dz < 16; dz++) { for (int dx = 0; dx < 16; dx++) { float x = chunk.getBlockX() + dx; float z = chunk.getBlockZ() + dz; Cell cell = chunk.genCell(dx, dz); heightmap.applyBase(cell, x, z); } } } } } public void generateRivers(Heightmap heightmap, RiverRegionList rivers) { for (int cz = 0; cz < chunkSize.total; cz++) { for (int cx = 0; cx < chunkSize.total; cx++) { int index = chunkSize.indexOf(cx, cz); GenChunk chunk = computeChunk(index, cx, cz); for (int dz = 0; dz < 16; dz++) { for (int dx = 0; dx < 16; dx++) { float x = chunk.getBlockX() + dx; float z = chunk.getBlockZ() + dz; Cell cell = chunk.genCell(dx, dz); heightmap.applyRivers(cell, x, z, rivers); heightmap.applyClimate(cell, x, z); } } } } } public void generateZoom(Heightmap heightmap, float offsetX, float offsetZ, float zoom) { float translateX = offsetX - ((blockSize.size * zoom) / 2F); float translateZ = offsetZ - ((blockSize.size * zoom) / 2F); for (int cz = 0; cz < chunkSize.total; cz++) { for (int cx = 0; cx < chunkSize.total; cx++) { int index = chunkSize.indexOf(cx, cz); GenChunk chunk = computeChunk(index, cx, cz); for (int dz = 0; dz < 16; dz++) { for (int dx = 0; dx < 16; dx++) { float x = ((chunk.getBlockX() + dx) * zoom) + translateX; float z = ((chunk.getBlockZ() + dz) * zoom) + translateZ; Cell cell = chunk.genCell(dx, dz); heightmap.apply(cell, x, z); } } } } } public void decorate(Collection decorators) { for (int dz = 0; dz < blockSize.total; dz++) { for (int dx = 0; dx < blockSize.total; dx++) { int index = blockSize.indexOf(dx, dz); GenCell cell = blocks[index]; for (Decorator decorator : decorators) { if (decorator.apply(cell, getBlockX() + dx, getBlockZ() + dz)) { break; } } } } } public void decorateZoom(Collection decorators, float offsetX, float offsetZ, float zoom) { float translateX = offsetX - ((blockSize.size * zoom) / 2F); float translateZ = offsetZ - ((blockSize.size * zoom) / 2F); for (int cz = 0; cz < chunkSize.total; cz++) { for (int cx = 0; cx < chunkSize.total; cx++) { int index = chunkSize.indexOf(cx, cz); GenChunk chunk = computeChunk(index, cx, cz); chunk.iterate((cell, dx, dz) -> { float x = ((chunk.getBlockX() + dx) * zoom) + translateX; float z = ((chunk.getBlockZ() + dz) * zoom) + translateZ; for (Decorator decorator : decorators) { decorator.apply(cell, x, z); } }); } } } public void iterate(Consumer consumer) { for (int cz = 0; cz < chunkSize.size; cz++) { int chunkZ = chunkSize.border + cz; for (int cx = 0; cx < chunkSize.size; cx++) { int chunkX = chunkSize.border + cx; int index = chunkSize.indexOf(chunkX, chunkZ); GenChunk chunk = chunks[index]; consumer.accept(chunk); } } } public void iterate(Cell.Visitor visitor) { for (int dz = 0; dz < blockSize.size; dz++) { int z = blockSize.border + dz; for (int dx = 0; dx < blockSize.size; dx++) { int x = blockSize.border + dx; int index = blockSize.indexOf(x, z); GenCell cell = blocks[index]; visitor.visit(cell, dx, dz); } } } @Override public void visit(int minX, int minZ, int maxX, int maxZ, Cell.Visitor visitor) { int regionMinX = getBlockX(); int regionMinZ = getBlockZ(); if (maxX < regionMinX || maxZ < regionMinZ) { return; } int regionMaxX = getBlockX() + getBlockSize().size - 1; int regionMaxZ = getBlockZ() + getBlockSize().size - 1; if (minX > regionMaxX || maxZ > regionMaxZ) { return; } minX = Math.max(minX, regionMinX); minZ = Math.max(minZ, regionMinZ); maxX = Math.min(maxX, regionMaxX); maxZ = Math.min(maxZ, regionMaxZ); for (int z = minZ; z <= maxX; z++) { for (int x = minX; x <= maxZ; x++) { visitor.visit(getCell(x, z), x, z); } } } private GenChunk computeChunk(int index, int chunkX, int chunkZ) { GenChunk chunk = chunks[index]; if (chunk == null) { chunk = new GenChunk(chunkX, chunkZ); chunks[index] = chunk; } return chunk; } private GenCell computeCell(int index) { GenCell cell = blocks[index]; if (cell == null) { cell = new GenCell(); blocks[index] = cell; } return cell; } private static class GenCell extends Cell {} private class GenChunk implements ChunkReader, ChunkWriter { private final int chunkX; private final int chunkZ; private final int blockX; private final int blockZ; private final int regionBlockX; private final int regionBlockZ; // the coordinate of the chunk within this region (relative to 0,0) private GenChunk(int regionChunkX, int regionChunkZ) { // the block coordinate of this chunk within this region (relative 0,0) this.regionBlockX = regionChunkX << 4; this.regionBlockZ = regionChunkZ << 4; // the real coordinate of this chunk within the world this.chunkX = Region.this.chunkX + regionChunkX - getOffsetChunks(); this.chunkZ = Region.this.chunkZ + regionChunkZ - getOffsetChunks(); // the real block coordinate of this chunk within the world this.blockX = chunkX << 4; this.blockZ = chunkZ << 4; } @Override public int getChunkX() { return chunkX; } @Override public int getChunkZ() { return chunkZ; } @Override public int getBlockX() { return blockX; } @Override public int getBlockZ() { return blockZ; } @Override public void dispose() { Region.this.dispose(); } @Override public Cell getCell(int blockX, int blockZ) { int relX = regionBlockX + (blockX & 15); int relZ = regionBlockZ + (blockZ & 15); int index = blockSize.indexOf(relX, relZ); return blocks[index]; } @Override public Cell genCell(int blockX, int blockZ) { int relX = regionBlockX + (blockX & 15); int relZ = regionBlockZ + (blockZ & 15); int index = blockSize.indexOf(relX, relZ); return computeCell(index); } } private class FilterRegion implements Filterable { @Override public Size getSize() { return blockSize; } @Override public Cell[] getBacking() { return blocks; } @Override public Cell getCellRaw(int x, int z) { int index = blockSize.indexOf(x, z); if (index < 0 || index >= blocks.length) { return Cell.empty(); } return blocks[index]; } } }