- use fast-util long2obj maps

- reorganised river stuff
- improvements to TerrainHelper - fixes buildings spawning with terrain inside
This commit is contained in:
dags- 2020-03-20 09:36:13 +00:00
parent 04a85e8ea0
commit de1f845b71
26 changed files with 224 additions and 278 deletions

View File

@ -8,12 +8,12 @@ repositories {
dependencies {
compile "org.processing:core:3.3.7"
compile "it.unimi.dsi:fastutil:8.2.1"
compile project(":TerraForgedCore")
}
jar {
manifest { attributes "Main-Class": "com.terraforged.app.Main" }
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}

View File

@ -1,7 +1,12 @@
apply plugin: "maven-publish"
repositories {
mavenCentral()
}
dependencies {
compile project(":Noise2D")
compile "it.unimi.dsi:fastutil:8.2.1"
}
publishing {

View File

@ -28,14 +28,11 @@ 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.ChunkGenTask;
import com.terraforged.core.region.chunk.ChunkReader;
import com.terraforged.core.region.chunk.ChunkWriter;
import com.terraforged.core.region.chunk.ChunkZoomTask;
import com.terraforged.core.util.concurrent.batcher.Batcher;
import com.terraforged.core.world.decorator.Decorator;
import com.terraforged.core.world.heightmap.Heightmap;
import com.terraforged.core.world.river.RiverRegionList;
import com.terraforged.core.world.rivermap.RiverRegionList;
import com.terraforged.core.world.terrain.Terrain;
import java.util.Collection;
@ -173,47 +170,22 @@ public class Region implements Extent {
}
}
public void generate(Heightmap heightmap, Batcher batcher) {
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);
Runnable task = new ChunkGenTask(chunk, heightmap);
batcher.submit(task);
}
}
}
public void generateZoom(Heightmap heightmap, float offsetX, float offsetZ, float zoom, Batcher batcher) {
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);
Runnable task = new ChunkZoomTask(chunk, heightmap, translateX, translateZ, zoom);
batcher.submit(task);
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<Terrain> cell = chunk.genCell(dx, dz);
heightmap.apply(cell, x, z);
}
}
}
public void check() {
for (int dz = 0; dz < chunkSize.total; dz++) {
for (int dx = 0; dx < chunkSize.total; dx++) {
int index = chunkSize.indexOf(dx, dz);
if (chunks[index] == null) {
throw new NullPointerException("Null chunk " + dx + ":" + dz);
}
}
}
for (int dz = 0; dz < blockSize.total; dz++) {
for (int dx = 0; dx < blockSize.total; dx++) {
int index = blockSize.indexOf(dx, dz);
if (blocks[index] == null) {
throw new NullPointerException("Null block " + dx + ":" + dz);
}
}
}
}

View File

@ -38,7 +38,7 @@ public class RegionCache implements RegionExtent {
private final boolean queuing;
private final RegionGenerator renderer;
private final Cache<Long, CacheEntry<Region>> cache;
private final Cache<CacheEntry<Region>> cache;
public RegionCache(boolean queueNeighbours, RegionGenerator renderer) {
this.renderer = renderer;

View File

@ -31,7 +31,7 @@ import com.terraforged.core.util.concurrent.cache.CacheEntry;
import com.terraforged.core.world.WorldGenerator;
import com.terraforged.core.world.WorldGeneratorFactory;
import com.terraforged.core.world.heightmap.RegionExtent;
import com.terraforged.core.world.river.RiverRegionList;
import com.terraforged.core.world.rivermap.RiverRegionList;
import java.util.concurrent.CompletableFuture;
@ -89,7 +89,7 @@ public class RegionGenerator implements RegionExtent {
public Region generateRegion(int regionX, int regionZ) {
WorldGenerator generator = genPool.get();
Region region = regions.create(regionX, regionZ, factor, border);
RiverRegionList rivers = generator.getHeightmap().getRiverManager().getRivers(region);
RiverRegionList rivers = generator.getHeightmap().getRiverMap().getRivers(region);
region.generateBase(generator.getHeightmap());
region.generateRivers(generator.getHeightmap(), rivers);
postProcess(region, generator);
@ -104,15 +104,7 @@ public class RegionGenerator implements RegionExtent {
public Region generateRegion(float centerX, float centerZ, float zoom, boolean filter) {
WorldGenerator generator = genPool.get();
Region region = regions.create(0, 0, factor, border);
float translateX = centerX - ((region.getBlockSize().size * zoom) / 2F);
float translateZ = centerZ - ((region.getBlockSize().size * zoom) / 2F);
region.generate(chunk -> {
chunk.generate((cell, dx, dz) -> {
float x = ((chunk.getBlockX() + dx) * zoom) + translateX;
float z = ((chunk.getBlockZ() + dz) * zoom) + translateZ;
generator.getHeightmap().apply(cell, x, z);
});
});
region.generateZoom(generator.getHeightmap(), centerX, centerZ, zoom);
postProcess(region, generator, centerX, centerZ, zoom, filter);
return region;
}

View File

@ -2,31 +2,30 @@ package com.terraforged.core.util.concurrent.cache;
import com.terraforged.core.util.concurrent.ThreadPool;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.LongFunction;
import java.util.function.Supplier;
public class Cache<K, V extends ExpiringEntry> implements Runnable {
public class Cache<V extends ExpiringEntry> implements Runnable {
private final long expireMS;
private final long intervalMS;
private final Map<K, V> map = new ConcurrentHashMap<>();
private final SynchronizedLongMap<V> map;
private volatile long timestamp = 0L;
public Cache(long expireTime, long interval, TimeUnit unit) {
this.expireMS = unit.toMillis(expireTime);
this.intervalMS = unit.toMillis(interval);
this.map = new SynchronizedLongMap<>(100);
}
public V computeIfAbsent(K k, Supplier<V> supplier) {
return computeIfAbsent(k, o -> supplier.get());
public V computeIfAbsent(long key, Supplier<V> supplier) {
return computeIfAbsent(key, o -> supplier.get());
}
public V computeIfAbsent(K k, Function<K, V> func) {
V v = map.computeIfAbsent(k, func);
public V computeIfAbsent(long key, LongFunction<V> func) {
V v = map.computeIfAbsent(key, func);
queueUpdate();
return v;
}
@ -42,10 +41,6 @@ public class Cache<K, V extends ExpiringEntry> implements Runnable {
@Override
public void run() {
final long now = timestamp;
map.forEach((key, val) -> {
if (now - val.getTimestamp() > expireMS) {
map.remove(key);
}
});
map.removeIf(val -> now - val.getTimestamp() > expireMS);
}
}

View File

@ -1,16 +1,17 @@
package com.terraforged.core.util.concurrent.cache;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionException;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.function.Supplier;
public class CacheEntry<T> extends FutureTask<T> implements ExpiringEntry {
public class CacheEntry<T> implements Runnable, ExpiringEntry {
private volatile T result;
private volatile long timestamp;
private CacheEntry(Callable<T> supplier) {
super(supplier);
private final Supplier<T> supplier;
private CacheEntry(Supplier<T> supplier) {
this.supplier = supplier;
this.timestamp = System.currentTimeMillis();
}
@ -20,16 +21,30 @@ public class CacheEntry<T> extends FutureTask<T> implements ExpiringEntry {
}
@Override
public T get() {
try {
public void run() {
this.result = supplier.get();
this.timestamp = System.currentTimeMillis();
return super.get();
} catch (Throwable t) {
throw new CompletionException(t);
}
}
public static <T> CacheEntry<T> supplyAsync(Callable<T> supplier, Executor executor) {
public boolean isDone() {
return result != null;
}
public T get() {
T value = result;
if (value == null) {
value = getNow();
}
return value;
}
private T getNow() {
T result = supplier.get();
this.result = result;
return result;
}
public static <T> CacheEntry<T> supplyAsync(Supplier<T> supplier, Executor executor) {
CacheEntry<T> entry = new CacheEntry<>(supplier);
executor.execute(entry);
return entry;

View File

@ -0,0 +1,54 @@
package com.terraforged.core.util.concurrent.cache;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import java.util.function.LongFunction;
import java.util.function.Predicate;
public class SynchronizedLongMap<V> {
private final Object lock;
private final Long2ObjectOpenHashMap<V> map;
public SynchronizedLongMap(int size) {
this.map = new Long2ObjectOpenHashMap<>(size);
this.lock = this;
}
public void remove(long key) {
synchronized (lock) {
map.remove(key);
}
}
public void put(long key, V v) {
synchronized (lock) {
map.put(key, v);
}
}
public V get(long key) {
synchronized (lock) {
return map.get(key);
}
}
public V computeIfAbsent(long key, LongFunction<V> func) {
synchronized (lock) {
return map.computeIfAbsent(key, func);
}
}
public void removeIf(Predicate<V> predicate) {
synchronized (lock) {
ObjectIterator<Long2ObjectMap.Entry<V>> iterator = map.long2ObjectEntrySet().fastIterator();
while (iterator.hasNext()) {
if (predicate.test(iterator.next().getValue())) {
iterator.remove();
}
}
}
}
}

View File

@ -31,15 +31,15 @@ import com.terraforged.core.cell.Populator;
import com.terraforged.core.region.Size;
import com.terraforged.core.util.concurrent.ObjectPool;
import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.river.RiverManager;
import com.terraforged.core.world.river.RiverRegionList;
import com.terraforged.core.world.rivermap.RiverMap;
import com.terraforged.core.world.rivermap.RiverRegionList;
import com.terraforged.core.world.terrain.Terrain;
public interface Heightmap extends Populator, Extent {
Climate getClimate();
RiverManager getRiverManager();
RiverMap getRiverMap();
void visit(Cell<Terrain> cell, float x, float z);

View File

@ -36,8 +36,8 @@ import com.terraforged.core.world.climate.Climate;
import com.terraforged.core.world.continent.ContinentLerper2;
import com.terraforged.core.world.continent.ContinentLerper3;
import com.terraforged.core.world.continent.ContinentModule;
import com.terraforged.core.world.river.RiverManager;
import com.terraforged.core.world.river.RiverRegionList;
import com.terraforged.core.world.rivermap.RiverMap;
import com.terraforged.core.world.rivermap.RiverRegionList;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.TerrainPopulator;
import com.terraforged.core.world.terrain.Terrains;
@ -67,7 +67,7 @@ public class WorldHeightmap implements Heightmap {
private final Climate climate;
private final Populator root;
private final RiverManager riverManager;
private final RiverMap riverMap;
private final TerrainProvider terrainProvider;
public WorldHeightmap(GeneratorContext context) {
@ -154,7 +154,7 @@ public class WorldHeightmap implements Heightmap {
COAST_VALUE - 0.05F
);
riverManager = new RiverManager(this, context);
riverMap = new RiverMap(this, context);
}
@Override
@ -167,7 +167,7 @@ public class WorldHeightmap implements Heightmap {
@Override
public void apply(Cell<Terrain> cell, float x, float z) {
applyBase(cell, x, z);
applyRivers(cell, x, z, riverManager.getRivers((int) x, (int) z));
applyRivers(cell, x, z, riverMap.getRivers((int) x, (int) z));
applyClimate(cell, x, z);
}
@ -219,8 +219,8 @@ public class WorldHeightmap implements Heightmap {
}
@Override
public RiverManager getRiverManager() {
return riverManager;
public RiverMap getRiverMap() {
return riverMap;
}
public Populator getPopulator(Terrain terrain) {

View File

@ -23,10 +23,11 @@
* SOFTWARE.
*/
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.heightmap.Heightmap;
import com.terraforged.core.world.rivermap.river.RiverNode;
import com.terraforged.core.world.terrain.Terrain;
import me.dags.noise.domain.Domain;
import me.dags.noise.util.Vec2i;

View File

@ -23,7 +23,7 @@
* SOFTWARE.
*/
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.region.Region;
@ -32,21 +32,24 @@ import com.terraforged.core.util.concurrent.cache.Cache;
import com.terraforged.core.util.concurrent.cache.CacheEntry;
import com.terraforged.core.world.GeneratorContext;
import com.terraforged.core.world.heightmap.Heightmap;
import com.terraforged.core.world.rivermap.lake.LakeConfig;
import com.terraforged.core.world.rivermap.river.RiverConfig;
import com.terraforged.core.world.rivermap.river.RiverRegion;
import com.terraforged.core.world.terrain.Terrain;
import me.dags.noise.util.NoiseUtil;
import java.util.concurrent.TimeUnit;
public class RiverManager {
public class RiverMap {
private static final int QUAD_SIZE = (1 << RiverRegion.SCALE) / 2;
private final Heightmap heightmap;
private final GeneratorContext context;
private final RiverContext riverContext;
private final Cache<Long, CacheEntry<RiverRegion>> cache = new Cache<>(120, 60, TimeUnit.SECONDS);
private final RiverMapConfig riverMapConfig;
private final Cache<CacheEntry<RiverRegion>> cache;
public RiverManager(Heightmap heightmap, GeneratorContext context) {
public RiverMap(Heightmap heightmap, GeneratorContext context) {
RiverConfig primary = RiverConfig.builder(context.levels)
.bankHeight(context.settings.rivers.primaryRivers.minBankHeight, context.settings.rivers.primaryRivers.maxBankHeight)
.bankWidth(context.settings.rivers.primaryRivers.bankWidth)
@ -76,7 +79,8 @@ public class RiverManager {
this.heightmap = heightmap;
this.context = context;
this.riverContext = new RiverContext(context.settings.rivers.riverFrequency, primary, secondary, tertiary, lakes);
this.riverMapConfig = new RiverMapConfig(context.settings.rivers.riverFrequency, primary, secondary, tertiary, lakes);
this.cache = new Cache<>(120, 60, TimeUnit.SECONDS);
}
public RiverRegionList getRivers(Region region) {
@ -100,8 +104,7 @@ public class RiverManager {
RiverRegionList list = new RiverRegionList();
for (int dz = minZ; dz <= maxZ; dz++) {
for (int dx = minX; dx <= maxX; dx++) {
CacheEntry<RiverRegion> entry = getRegion(rx + dx, rz + dz);
list.add(entry);
list.add(getRegion(rx + dx, rz + dz));
}
}
@ -148,6 +151,6 @@ public class RiverManager {
}
private CacheEntry<RiverRegion> generateRegion(int rx, int rz) {
return CacheEntry.supplyAsync(() -> new RiverRegion(rx, rz, heightmap, context, riverContext), ThreadPool.getDefaultPool());
return CacheEntry.supplyAsync(() -> new RiverRegion(rx, rz, heightmap, context, riverMapConfig), ThreadPool.getDefaultPool());
}
}

View File

@ -1,6 +1,9 @@
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap;
public class RiverContext {
import com.terraforged.core.world.rivermap.lake.LakeConfig;
import com.terraforged.core.world.rivermap.river.RiverConfig;
public class RiverMapConfig {
public final float frequency;
public final RiverConfig primary;
@ -8,7 +11,7 @@ public class RiverContext {
public final RiverConfig tertiary;
public final LakeConfig lakes;
public RiverContext(float frequency, RiverConfig primary, RiverConfig secondary, RiverConfig tertiary, LakeConfig lakes) {
public RiverMapConfig(float frequency, RiverConfig primary, RiverConfig secondary, RiverConfig tertiary, LakeConfig lakes) {
this.frequency = frequency;
this.primary = primary;
this.secondary = secondary;

View File

@ -1,7 +1,8 @@
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.util.concurrent.cache.CacheEntry;
import com.terraforged.core.world.rivermap.river.RiverRegion;
import com.terraforged.core.world.terrain.Terrain;
public class RiverRegionList {
@ -10,7 +11,10 @@ public class RiverRegionList {
private final CacheEntry<RiverRegion>[] regions = new CacheEntry[4];
protected void add(CacheEntry<RiverRegion> entry) {
regions[index++] = entry;
if (index < regions.length) {
regions[index] = entry;
index++;
}
}
public void apply(Cell<Terrain> cell, float x, float z) {

View File

@ -23,9 +23,10 @@
* SOFTWARE.
*/
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap.lake;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.rivermap.river.River;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.TerrainPopulator;
import com.terraforged.core.world.terrain.Terrains;

View File

@ -23,7 +23,7 @@
* SOFTWARE.
*/
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap.lake;
import com.terraforged.core.settings.RiverSettings;
import com.terraforged.core.world.heightmap.Levels;

View File

@ -23,7 +23,7 @@
* SOFTWARE.
*/
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap.river;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.world.terrain.Terrain;

View File

@ -23,7 +23,7 @@
* SOFTWARE.
*/
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap.river;
import me.dags.noise.util.Vec2f;

View File

@ -23,7 +23,7 @@
* SOFTWARE.
*/
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap.river;
import com.terraforged.core.world.heightmap.Levels;
import me.dags.noise.util.NoiseUtil;

View File

@ -23,7 +23,7 @@
* SOFTWARE.
*/
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap.river;
public class RiverNode {

View File

@ -23,13 +23,17 @@
* SOFTWARE.
*/
package com.terraforged.core.world.river;
package com.terraforged.core.world.rivermap.river;
import com.terraforged.core.cell.Cell;
import com.terraforged.core.util.concurrent.ObjectPool;
import com.terraforged.core.util.concurrent.cache.ExpiringEntry;
import com.terraforged.core.world.GeneratorContext;
import com.terraforged.core.world.heightmap.Heightmap;
import com.terraforged.core.world.rivermap.PosGenerator;
import com.terraforged.core.world.rivermap.RiverMapConfig;
import com.terraforged.core.world.rivermap.lake.Lake;
import com.terraforged.core.world.rivermap.lake.LakeConfig;
import com.terraforged.core.world.terrain.Terrain;
import com.terraforged.core.world.terrain.Terrains;
import me.dags.noise.domain.Domain;
@ -73,27 +77,27 @@ public class RiverRegion implements ExpiringEntry {
private final long timestamp = System.currentTimeMillis() + EXPIRE_TIME;
public RiverRegion(int regionX, int regionZ, Heightmap heightmap, GeneratorContext context, RiverContext riverContext) {
public RiverRegion(int regionX, int regionZ, Heightmap heightmap, GeneratorContext context, RiverMapConfig riverMapConfig) {
int seed = new Random(NoiseUtil.seed(regionX, regionZ)).nextInt();
this.lake = riverContext.lakes;
this.primary = riverContext.primary;
this.secondary = riverContext.secondary;
this.tertiary = riverContext.tertiary;
this.lake = riverMapConfig.lakes;
this.primary = riverMapConfig.primary;
this.secondary = riverMapConfig.secondary;
this.tertiary = riverMapConfig.tertiary;
this.terrains = context.terrain;
this.domain = Domain.warp(seed, 400, 1, 400)
.add(Domain.warp(seed + 1, 80, 1, 35));
this.primaryLimit = NoiseUtil.round(10 * riverContext.frequency);
this.secondaryLimit = NoiseUtil.round(20 * riverContext.frequency);
this.secondaryRelaxedLimit = NoiseUtil.round(30 * riverContext.frequency);
this.forkLimit = NoiseUtil.round(40 * riverContext.frequency);
this.tertiaryLimit = NoiseUtil.round(50 * riverContext.frequency);
this.primaryLimit = NoiseUtil.round(10 * riverMapConfig.frequency);
this.secondaryLimit = NoiseUtil.round(20 * riverMapConfig.frequency);
this.secondaryRelaxedLimit = NoiseUtil.round(30 * riverMapConfig.frequency);
this.forkLimit = NoiseUtil.round(40 * riverMapConfig.frequency);
this.tertiaryLimit = NoiseUtil.round(50 * riverMapConfig.frequency);
this.primaryAttempts = NoiseUtil.round(100 * riverContext.frequency);
this.secondaryAttempts = NoiseUtil.round(100 * riverContext.frequency);
this.secondaryRelaxedAttempts = NoiseUtil.round(50 * riverContext.frequency);
this.forkAttempts = NoiseUtil.round(75 * riverContext.frequency);
this.tertiaryAttempts = NoiseUtil.round(50 * riverContext.frequency);
this.primaryAttempts = NoiseUtil.round(100 * riverMapConfig.frequency);
this.secondaryAttempts = NoiseUtil.round(100 * riverMapConfig.frequency);
this.secondaryRelaxedAttempts = NoiseUtil.round(50 * riverMapConfig.frequency);
this.forkAttempts = NoiseUtil.round(75 * riverMapConfig.frequency);
this.tertiaryAttempts = NoiseUtil.round(50 * riverMapConfig.frequency);
try (ObjectPool.Item<Cell<Terrain>> cell = Cell.pooled()) {
PosGenerator pos = new PosGenerator(heightmap, domain, cell.getValue(),1 << SCALE, River.VALLEY_WIDTH);

View File

@ -24,15 +24,9 @@ repositories {
dependencies {
minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}"
compile project(":Noise2D")
compile (project(":TerraForgedCore")) {
transitive false
}
compile (project(":FeatureManager")) {
transitive false
}
compile (project(":TerraForgedAPI")) {
transitive false
}
compile (project(":TerraForgedCore")) { transitive false }
compile (project(":FeatureManager")) { transitive false }
compile (project(":TerraForgedAPI")) { transitive false }
}
minecraft {
@ -81,7 +75,6 @@ task collectResources(type: Copy) {
processResources {
dependsOn(collectResources)
filesMatching("**/mods.toml") {
expand(
"version": "${mod_version}${getClassifier()}",

View File

@ -25,6 +25,7 @@
package com.terraforged.mod.feature;
import com.terraforged.api.material.state.States;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectList;
@ -89,7 +90,7 @@ public class TerrainHelper {
}
}
// try to fill in type air beneath village pieces with the biomes default filler block
// lowers or raises the terrain matcher the base height of each structure piece
private void buildBases(IChunk chunk, ObjectList<AbstractVillagePiece> pieces, int chunkStartX, int chunkStartZ) {
BlockPos.Mutable pos = new BlockPos.Mutable();
MutableBoundingBox chunkBounds = new MutableBoundingBox(chunkStartX, chunkStartZ, chunkStartX + 15, chunkStartZ + 15);
@ -110,6 +111,9 @@ public class TerrainHelper {
int endX = Math.min(chunkStartX + 15, expanded.maxX);
int endZ = Math.min(chunkStartZ + 15, expanded.maxZ);
// y position of the structure piece
int level = pieceBounds.minY + piece.getGroundLevelDelta();
// iterate the intersecting area
for (int z = startZ; z <= endZ; z++) {
for (int x = startX; x <= endX; x++) {
@ -117,40 +121,56 @@ public class TerrainHelper {
int dx = x & 15;
int dz = z & 15;
// the paste position of the village piece
BlockPos position = piece.getPos();
int offset = piece.getGroundLevelDelta();
int level = position.getY() + (offset - 1);
int surface = chunk.getTopBlockY(Heightmap.Type.OCEAN_FLOOR_WG, dx, dz) - 1;
int height = level - surface;
if (height <= 0) {
int surface = chunk.getTopBlockY(Heightmap.Type.OCEAN_FLOOR_WG, dx, dz);
if (surface == level) {
continue;
}
float radius2 = Math.max(1, borderRadius * borderRadius * noise.getValue(x, z));
float alpha = getAlpha(pieceBounds, radius2, x, z);
if (surface > level) {
// world-surface is higher than the piece's base .: flatten terrain
flatten(chunk, pieceBounds, pos.setPos(x, surface, z), dx, dz, level, surface, borderRadius);
} else {
// piece is higher than world-surface .: raise ground to form a base
raise(chunk, pieceBounds, pos.setPos(x, surface, z), dx, dz, level, surface, borderRadius);
}
}
}
}
}
private void flatten(IChunk chunk, MutableBoundingBox bounds, BlockPos.Mutable pos, int dx, int dz, int level, int surface, int borderRadius) {
// only flatten terrain within the footprint of the structure piece
if (pos.getX() >= bounds.minX && pos.getX() <= bounds.maxX && pos.getZ() >= bounds.minZ && pos.getZ() <= bounds.maxZ) {
for (int dy = level + 1; dy <= surface; dy++) {
chunk.setBlockState(pos.setPos(dx, dy, dz), Blocks.AIR.getDefaultState(), false);
}
}
}
private void raise(IChunk chunk, MutableBoundingBox bounds, BlockPos.Mutable pos, int dx, int dz, int level, int surface, int borderRadius) {
float radius2 = Math.max(1, borderRadius * borderRadius * noise.getValue(pos.getX(), pos.getZ()));
float alpha = getAlpha(bounds, radius2, pos.getX(), pos.getZ());
if (alpha == 0F) {
continue;
// outside of the raise-able radius
return;
}
int heightDelta = level - surface - 1;
if (alpha < 1F) {
// sharper fall-off
alpha = alpha * alpha;
height = NoiseUtil.round(alpha * height);
heightDelta = NoiseUtil.round(alpha * heightDelta);
}
BlockState state = Blocks.STONE.getDefaultState();
for (int dy = surface + height; dy >= surface; dy--) {
BlockState state = States.STONE.getDefaultState();
for (int dy = surface + heightDelta; dy >= surface; dy--) {
pos.setPos(dx, dy, dz);
if (chunk.getBlockState(pos).isSolid()) {
break;
return;
}
chunk.setBlockState(pos.setPos(dx, dy, dz), state, false);
}
}
}
}
}
private static MutableBoundingBox expand(MutableBoundingBox box, int radius) {
return new MutableBoundingBox(

View File

@ -1,32 +0,0 @@
package com.terraforged.mod.feature.placement;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IWorld;
import net.minecraft.world.gen.ChunkGenerator;
import net.minecraft.world.gen.Heightmap;
import net.minecraft.world.gen.placement.HeightWithChanceConfig;
import net.minecraft.world.gen.placement.Placement;
import java.util.Random;
import java.util.stream.Stream;
public class BetterAtSurfaceWithChanceMultiple extends Placement<HeightWithChanceConfig> {
public BetterAtSurfaceWithChanceMultiple() {
super(HeightWithChanceConfig::deserialize);
}
@Override
public Stream<BlockPos> getPositions(IWorld world, ChunkGenerator<?> generator, Random random, HeightWithChanceConfig config, BlockPos pos) {
return PosStream.of(config.count, next -> {
if (random.nextFloat() < config.chance) {
int x = random.nextInt(16) + pos.getX();
int z = random.nextInt(16) + pos.getZ();
int y = world.getHeight(Heightmap.Type.MOTION_BLOCKING, x, z);
next.setPos(x, y, z);
return true;
}
return false;
});
}
}

View File

@ -1,8 +0,0 @@
package com.terraforged.mod.feature.placement;
import net.minecraft.util.math.BlockPos;
public interface PosGenerator {
boolean generate(BlockPos.Mutable next);
}

View File

@ -1,76 +0,0 @@
package com.terraforged.mod.feature.placement;
import net.minecraft.util.math.BlockPos;
import java.util.Spliterator;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public final class PosStream {
private static final ThreadLocal<BlockPos.Mutable> MUTABLE_THREAD_LOCAL = ThreadLocal.withInitial(BlockPos.Mutable::new);
private PosStream() {
}
public static Stream<BlockPos> of(int count, Populator populator) {
return StreamSupport.stream(new PosSpliterator(count, populator), false);
}
@FunctionalInterface
public interface Populator {
/**
* Populates the BlockPos.Mutable with the next x,y,z coordinates
*
* @param next the BlockPos.Mutable to populate
* @return true if the populated BlockPos.Mutable should be used next in the stream
*/
boolean populate(BlockPos.Mutable next);
}
private static class PosSpliterator implements Spliterator<BlockPos> {
private final int attempts;
private final Populator populator;
private final BlockPos.Mutable pos = MUTABLE_THREAD_LOCAL.get();
private int counter = -1;
private PosSpliterator(int attempts, Populator populator) {
this.attempts = attempts;
this.populator = populator;
}
@Override
public boolean tryAdvance(Consumer<? super BlockPos> action) {
if (counter < attempts) {
counter++;
if (populator.populate(pos)) {
action.accept(pos);
}
return true;
}
return false;
}
@Override
public Spliterator<BlockPos> trySplit() {
// cannot split
return null;
}
@Override
public long estimateSize() {
return attempts;
}
@Override
public int characteristics() {
// assume that positions are generated from a seeded random .: ordered
return Spliterator.ORDERED;
}
}
}