From 762fa842c4585d248b3b5f0477d59eadb8f31a6e Mon Sep 17 00:00:00 2001 From: SmylerMC Date: Fri, 13 Aug 2021 08:52:15 +0200 Subject: [PATCH 1/6] Support for patterns instead of simple blocks states in CachedChunkData --- .../dataset/vector/draw/Block.java | 16 +++-- .../generator/CachedChunkData.java | 64 +++++++++++++------ .../generator/ChunkBiomesBuilder.java | 7 +- .../generator/EarthGenerator.java | 44 ++++++++----- .../generator/IEarthAsyncPipelineStep.java | 18 +++--- .../generator/TerrainPreview.java | 58 +++++++++++------ .../surface/BakedSurfacePattern.java | 19 ++++++ .../surface/BlockSurfacePattern.java | 27 ++++++++ .../generator/surface/ISurfacePattern.java | 14 ++++ .../util/ImmutableCompactArray.java | 15 ++--- 10 files changed, 199 insertions(+), 83 deletions(-) create mode 100644 src/main/java/net/buildtheearth/terraplusplus/generator/surface/BakedSurfacePattern.java create mode 100644 src/main/java/net/buildtheearth/terraplusplus/generator/surface/BlockSurfacePattern.java create mode 100644 src/main/java/net/buildtheearth/terraplusplus/generator/surface/ISurfacePattern.java diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java index f38c5c5a..e2f70076 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java @@ -1,30 +1,36 @@ package net.buildtheearth.terraplusplus.dataset.vector.draw; +import java.io.IOException; + import com.google.gson.annotations.JsonAdapter; import com.google.gson.stream.JsonReader; + import lombok.NonNull; -import lombok.RequiredArgsConstructor; import net.buildtheearth.terraplusplus.TerraConstants; import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; import net.buildtheearth.terraplusplus.generator.CachedChunkData; +import net.buildtheearth.terraplusplus.generator.surface.BlockSurfacePattern; import net.minecraft.block.state.IBlockState; -import java.io.IOException; - /** * {@link DrawFunction} which sets the surface block to a fixed block state. * * @author DaPorkchop_ */ @JsonAdapter(Block.Parser.class) -@RequiredArgsConstructor public final class Block implements DrawFunction { @NonNull protected final IBlockState state; + protected transient final BlockSurfacePattern pattern; + + public Block(IBlockState state) { + this.state = state; + this.pattern = new BlockSurfacePattern(state); + } @Override public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - data.surfaceBlocks()[x * 16 + z] = this.state; + data.surfacePatterns()[x * 16 + z] = this.pattern; } static class Parser extends JsonParser { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java b/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java index 3450539f..d0cb8937 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java @@ -1,26 +1,33 @@ package net.buildtheearth.terraplusplus.generator; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static net.daporkchop.lib.common.math.PMath.clamp; +import static net.daporkchop.lib.common.math.PMath.floorI; +import static net.daporkchop.lib.common.math.PMath.lerp; + +import java.util.Arrays; +import java.util.Map; +import java.util.Random; + import com.google.common.collect.ImmutableMap; + import io.github.opencubicchunks.cubicchunks.api.util.Coords; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import lombok.Getter; import lombok.NonNull; import lombok.Setter; +import net.buildtheearth.terraplusplus.generator.surface.BakedSurfacePattern; +import net.buildtheearth.terraplusplus.generator.surface.ISurfacePattern; import net.buildtheearth.terraplusplus.util.CustomAttributeContainer; import net.buildtheearth.terraplusplus.util.ImmutableCompactArray; import net.daporkchop.lib.common.ref.Ref; import net.daporkchop.lib.common.ref.ThreadRef; import net.daporkchop.lib.common.util.PorkUtil; -import net.minecraft.block.state.IBlockState; import net.minecraft.init.Biomes; +import net.minecraft.util.math.ChunkPos; import net.minecraft.world.biome.Biome; -import java.util.Arrays; -import java.util.Map; - -import static java.lang.Math.*; -import static net.daporkchop.lib.common.math.PMath.*; - /** * A collection of data cached per-column by {@link EarthGenerator}. * @@ -38,8 +45,8 @@ public class CachedChunkData extends CustomAttributeContainer { private static final Ref BUILDER_CACHE = ThreadRef.soft(Builder::new); - public static Builder builder() { - return BUILDER_CACHE.get().reset(); + public static Builder builder(ChunkPos pos) { + return BUILDER_CACHE.get().reset(pos); } private static int extractActualDepth(int waterDepth) { @@ -53,7 +60,7 @@ private static int extractActualDepth(int waterDepth) { @Getter private final byte[] biomes; - private final ImmutableCompactArray surfaceBlocks; + private final ImmutableCompactArray surfacePatterns; private final int surfaceMinCube; private final int surfaceMaxCube; @@ -99,13 +106,27 @@ private CachedChunkData(@NonNull Builder builder, @NonNull Map c this.biomes[i] = (byte) Biome.getIdForBiome(PorkUtil.fallbackIfNull(builder.biomes[i], Biomes.DEEP_OCEAN)); } - this.surfaceBlocks = new ImmutableCompactArray<>(builder.surfaceBlocks); + Random random = new Random(ChunkPos.asLong(builder.pos.x, builder.pos.z)); + BakedSurfacePattern[] patterns = new BakedSurfacePattern[16 * 16]; + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int i = x*16 + z; + int wx = builder.pos.x * 16 + x; + int wy = this.groundHeight[i]; + int wz = builder.pos.z * 16 + z; + ISurfacePattern pattern = builder.surfacePatterns()[i]; + patterns[i] = pattern != null ? pattern.bake(wx, wy, wz, random): null; + } + } + this.surfacePatterns = new ImmutableCompactArray<>(patterns); //TODO Not so efficient anymore as it is reference based int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; for (int i = 0; i < 16 * 16; i++) { - min = min(min, min(this.groundHeight[i], this.surfaceHeight[i])); - max = max(max, max(this.groundHeight[i], this.surfaceHeight[i])); + BakedSurfacePattern pattern = this.surfacePatterns.get(i); + if(pattern == null) continue; + min = min(min, min(this.groundHeight[i], this.surfaceHeight[i]) - pattern.offset()); + max = max(max, max(this.groundHeight[i], this.surfaceHeight[i]) + pattern.pattern().length - pattern.offset() - 1); } this.surfaceMinCube = Coords.blockToCube(min) - 1; this.surfaceMaxCube = Coords.blockToCube(max) + 1; @@ -139,8 +160,8 @@ public int waterHeight(int x, int z) { return this.surfaceHeight(x, z) - 1; } - public IBlockState surfaceBlock(int x, int z) { - return this.surfaceBlocks.get(x * 16 + z); + public BakedSurfacePattern surfacePattern(int x, int z) { + return this.surfacePatterns.get(x * 16 + z); } public int biome(int x, int z) { @@ -155,20 +176,22 @@ public int biome(int x, int z) { @Getter @Setter public static final class Builder extends CustomAttributeContainer implements IEarthAsyncDataBuilder { + private ChunkPos pos; + private final int[] surfaceHeight = new int[16 * 16]; private final byte[] waterDepth = new byte[16 * 16]; private final Biome[] biomes = new Biome[16 * 16]; - protected final IBlockState[] surfaceBlocks = new IBlockState[16 * 16]; + protected final ISurfacePattern[] surfacePatterns = new ISurfacePattern[16 * 16]; /** - * @deprecated use {@link #builder()} unless you have a specific reason to invoke this constructor directly + * @deprecated use {@link #builder(ChunkPos)} unless you have a specific reason to invoke this constructor directly */ @Deprecated public Builder() { super(new Object2ObjectOpenHashMap<>()); - this.reset(); + this.reset(new ChunkPos(0, 0)); } public Builder surfaceHeight(int x, int z, int value) { @@ -200,10 +223,11 @@ public void putCustom(@NonNull String key, @NonNull Object value) { this.custom.put(key, value); } - public Builder reset() { + public Builder reset(ChunkPos pos) { Arrays.fill(this.surfaceHeight, BLANK_HEIGHT); Arrays.fill(this.waterDepth, (byte) WATERDEPTH_DEFAULT); - Arrays.fill(this.surfaceBlocks, null); + Arrays.fill(this.surfacePatterns, null); + this.pos = pos; this.custom.clear(); return this; } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java b/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java index 1208999d..12f9ed3e 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java @@ -1,13 +1,14 @@ package net.buildtheearth.terraplusplus.generator; +import java.util.Arrays; + import lombok.Getter; import net.buildtheearth.terraplusplus.util.ImmutableCompactArray; import net.daporkchop.lib.common.ref.Ref; import net.daporkchop.lib.common.ref.ThreadRef; +import net.minecraft.util.math.ChunkPos; import net.minecraft.world.biome.Biome; -import java.util.Arrays; - /** * Builds a 16x16 area of chunks. * @@ -17,7 +18,7 @@ public class ChunkBiomesBuilder implements IEarthAsyncDataBuilder> { private static final Ref BUILDER_CACHE = ThreadRef.soft(ChunkBiomesBuilder::new); - public static ChunkBiomesBuilder get() { + public static ChunkBiomesBuilder get(ChunkPos pos) { return BUILDER_CACHE.get().reset(); } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java index 7a99b2b7..6ebd1f46 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java @@ -1,8 +1,21 @@ package net.buildtheearth.terraplusplus.generator; +import static java.lang.Math.max; +import static java.lang.Math.min; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; + import io.github.opencubicchunks.cubicchunks.api.util.Coords; import io.github.opencubicchunks.cubicchunks.api.util.CubePos; import io.github.opencubicchunks.cubicchunks.api.world.ICube; @@ -31,6 +44,7 @@ import net.buildtheearth.terraplusplus.TerraMod; import net.buildtheearth.terraplusplus.generator.data.IEarthDataBaker; import net.buildtheearth.terraplusplus.generator.populate.IEarthPopulator; +import net.buildtheearth.terraplusplus.generator.surface.BakedSurfacePattern; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.minecraft.block.state.IBlockState; @@ -51,17 +65,6 @@ import net.minecraftforge.fml.common.gameevent.PlayerEvent; import net.minecraftforge.fml.common.registry.ForgeRegistries; -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -import static java.lang.Math.*; - public class EarthGenerator extends BasicCubeGenerator { public static final int WATER_DEPTH_OFFSET = 1; @@ -263,11 +266,18 @@ protected void generateCube(int cubeX, int cubeY, int cubeZ, CubePrimer primer, if (data.intersectsSurface(cubeY)) { //render surface blocks onto cube surface for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { - int y = data.surfaceHeight(x, z) - Coords.cubeToMinBlock(cubeY); - IBlockState state; - if ((y & 0xF) == y //don't set surface blocks outside of this cube - && (state = data.surfaceBlock(x, z)) != null) { - primer.setBlockState(x, y, z, state); + int surface = data.surfaceHeight(x, z) - Coords.cubeToMinBlock(cubeY); + BakedSurfacePattern pattern = data.surfacePattern(x, z); + if (pattern == null) continue; + IBlockState[] blocks = pattern.pattern(); + int offset = pattern.offset(); + for(int i = 0; i < blocks.length; i++) { + int y = surface - offset + i; + IBlockState state = blocks[i]; + if ((y & 0xF) == y //don't set surface blocks outside of this cube + && state != null) { + primer.setBlockState(x, y, z, state); + } } } } @@ -371,7 +381,7 @@ public void populate(ICube cube) { Random random = Coords.coordsSeedRandom(this.world.getSeed(), cube.getX(), cube.getY(), cube.getZ()); Biome biome = cube.getBiome(Coords.getCubeCenter(cube)); - this.cubiccfg.expectedBaseHeight = (float) datas[0].groundHeight(15, 15); + this.cubiccfg.expectedBaseHeight = datas[0].groundHeight(15, 15); for (IEarthPopulator populator : this.populators) { populator.populate(this.world, random, cube.getCoords(), biome, datas); diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java b/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java index 2811ff64..25e89b5b 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java @@ -1,5 +1,12 @@ package net.buildtheearth.terraplusplus.generator; +import static net.daporkchop.lib.common.util.PorkUtil.uncheckedCast; + +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + import io.github.opencubicchunks.cubicchunks.api.util.Coords; import net.buildtheearth.terraplusplus.TerraMod; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; @@ -7,18 +14,11 @@ import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; import net.minecraft.util.math.ChunkPos; -import java.util.Arrays; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.function.Supplier; - -import static net.daporkchop.lib.common.util.PorkUtil.*; - /** * @author DaPorkchop_ */ public interface IEarthAsyncPipelineStep> { - static > CompletableFuture getFuture(ChunkPos pos, GeneratorDatasets datasets, IEarthAsyncPipelineStep[] steps, Supplier builderFactory) { + static > CompletableFuture getFuture(ChunkPos pos, GeneratorDatasets datasets, IEarthAsyncPipelineStep[] steps, Function builderFactory) { int baseX = Coords.cubeToMinBlock(pos.x); int baseZ = Coords.cubeToMinBlock(pos.z); @@ -43,7 +43,7 @@ static > CompletableFuture getFuture(C CompletableFuture future = (nonNullFutures.length != 0 ? CompletableFuture.allOf(nonNullFutures) : CompletableFuture.completedFuture(null)) .thenApply(unused -> { - B builder = builderFactory.get(); + B builder = builderFactory.apply(pos); for (int i = 0; i < steps.length; i++) { CompletableFuture stepFuture = futures[i]; diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java b/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java index 2462ebfa..0a8347c6 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java @@ -1,22 +1,11 @@ package net.buildtheearth.terraplusplus.generator; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.generator.data.TreeCoverBaker; -import net.buildtheearth.terraplusplus.projection.GeographicProjection; -import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; -import net.buildtheearth.terraplusplus.util.EmptyWorld; -import net.buildtheearth.terraplusplus.util.TilePos; -import net.buildtheearth.terraplusplus.util.http.Http; -import net.minecraft.block.state.IBlockState; -import net.minecraft.init.Bootstrap; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.ChunkPos; -import net.minecraftforge.client.model.pipeline.LightUtil; +import static java.lang.Math.max; +import static net.daporkchop.lib.common.math.PMath.clamp; +import static net.daporkchop.lib.common.math.PMath.floorI; +import static net.daporkchop.lib.common.math.PMath.lerpI; +import static net.daporkchop.lib.common.util.PorkUtil.uncheckedCast; -import javax.swing.JFrame; import java.awt.Canvas; import java.awt.Graphics; import java.awt.Graphics2D; @@ -31,9 +20,26 @@ import java.util.Collections; import java.util.concurrent.CompletableFuture; -import static java.lang.Math.*; -import static net.daporkchop.lib.common.math.PMath.*; -import static net.daporkchop.lib.common.util.PorkUtil.*; +import javax.swing.JFrame; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.data.TreeCoverBaker; +import net.buildtheearth.terraplusplus.generator.surface.BakedSurfacePattern; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.EmptyWorld; +import net.buildtheearth.terraplusplus.util.TilePos; +import net.buildtheearth.terraplusplus.util.http.Http; +import net.minecraft.block.material.MapColor; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Bootstrap; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraftforge.client.model.pipeline.LightUtil; /** * @author DaPorkchop_ @@ -214,7 +220,7 @@ public void dispose() { //proj = state.projection.fromGeo(12.610463237424899, 37.673937184583636); //somewhere in sicily //proj = state.projection.fromGeo(9.6726, 45.6699); //lombardia, italy //proj = state.projection.fromGeo(8.93058, 44.40804); //genova, italy - //proj = state.projection.fromGeo(16.5922, 38.9069); //catanzaro, italy + proj = state.projection.fromGeo(16.5922, 38.9069); //catanzaro, italy state.setView(floorI(proj[0]) >> 4, floorI(proj[1]) >> 4, 0); state.update(); @@ -283,7 +289,17 @@ protected CompletableFuture baseZoomTile(int x, int z) { for (int cz = 0; cz < 16; cz++) { int c; - IBlockState state = data.surfaceBlock(cx, cz); + IBlockState state = null; + BakedSurfacePattern pattern = data.surfacePattern(cx, cz); + if(pattern != null) { + IBlockState[] states = pattern.pattern(); + for(int i = states.length - 1; i >= 0; i--) { + if(states[i] != null && states[i].getMapColor(EmptyWorld.INSTANCE, BlockPos.ORIGIN) != MapColor.AIR) { + state = states[i]; + break; + } + } + } if (state != null) { c = state.getMapColor(EmptyWorld.INSTANCE, BlockPos.ORIGIN).colorValue; } else { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BakedSurfacePattern.java b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BakedSurfacePattern.java new file mode 100644 index 00000000..042e3870 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BakedSurfacePattern.java @@ -0,0 +1,19 @@ +package net.buildtheearth.terraplusplus.generator.surface; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.minecraft.block.state.IBlockState; + +/** + * A surface pattern ready to be generated + */ +@RequiredArgsConstructor +@Getter +@EqualsAndHashCode +public final class BakedSurfacePattern { + + private final IBlockState[] pattern; + private final int offset; + +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BlockSurfacePattern.java b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BlockSurfacePattern.java new file mode 100644 index 00000000..42638766 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BlockSurfacePattern.java @@ -0,0 +1,27 @@ +package net.buildtheearth.terraplusplus.generator.surface; + +import java.util.Random; + +import net.minecraft.block.state.IBlockState; + +/** + * @author SmylerMC + */ +public class BlockSurfacePattern implements ISurfacePattern { + + private final IBlockState[] blocks; + + public BlockSurfacePattern(IBlockState block) { + this.blocks = new IBlockState[] { block }; + } + + @Override + public BakedSurfacePattern bake(int x, int surfaceY, int z, Random random) { + return new BakedSurfacePattern(this.blocks, 0); + } + + public IBlockState block() { + return this.blocks[0]; + } + +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/ISurfacePattern.java b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/ISurfacePattern.java new file mode 100644 index 00000000..6666a600 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/ISurfacePattern.java @@ -0,0 +1,14 @@ +package net.buildtheearth.terraplusplus.generator.surface; + +import java.util.Random; + +/** + * A vertical pattern that should be drawn at the surface of the ground + * + * @author SmylerMC + */ +public interface ISurfacePattern { + + BakedSurfacePattern bake(int x, int surfaceY, int z, Random random); + +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/ImmutableCompactArray.java b/src/main/java/net/buildtheearth/terraplusplus/util/ImmutableCompactArray.java index c01070b1..dd2b8fe4 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/ImmutableCompactArray.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/ImmutableCompactArray.java @@ -1,20 +1,19 @@ package net.buildtheearth.terraplusplus.util; -import it.unimi.dsi.fastutil.objects.Reference2IntMap; -import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -import lombok.NonNull; -import net.daporkchop.lib.binary.bit.BitArray; -import net.daporkchop.lib.binary.bit.padded.PaddedBitArray; -import net.minecraft.block.state.IBlockState; +import static java.lang.Math.max; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static java.lang.Math.*; +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import lombok.NonNull; +import net.daporkchop.lib.binary.bit.BitArray; +import net.daporkchop.lib.binary.bit.padded.PaddedBitArray; /** - * A compact, immutable array of {@link IBlockState}s. + * A compact, immutable array. * * @author DaPorkchop_ */ From ac4b16e136230baa6cbc544df5550a7cdb9b0825 Mon Sep 17 00:00:00 2001 From: SmylerMC Date: Fri, 13 Aug 2021 14:37:15 +0200 Subject: [PATCH 2/6] MultiBlock surface pattern implementation --- .../vector/draw/DrawFunctionParser.java | 5 +- .../dataset/vector/draw/MultiBlock.java | 71 +++++++++++++++++++ .../surface/MultiBlockSurfacePattern.java | 25 +++++++ 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/MultiBlock.java create mode 100644 src/main/java/net/buildtheearth/terraplusplus/generator/surface/MultiBlockSurfacePattern.java diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionParser.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionParser.java index 7fe48920..28f40f58 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionParser.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/DrawFunctionParser.java @@ -1,10 +1,10 @@ package net.buildtheearth.terraplusplus.dataset.vector.draw; +import java.util.Map; + import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; -import java.util.Map; - /** * @author DaPorkchop_ */ @@ -20,6 +20,7 @@ public class DrawFunctionParser extends JsonParser.Typed { TYPES.put("weight_less_than", WeightLessThan.class); TYPES.put("block", Block.class); + TYPES.put("blocks", MultiBlock.class); TYPES.put("no_trees", NoTrees.class); TYPES.put("ocean", Ocean.class); TYPES.put("water", Water.class); diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/MultiBlock.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/MultiBlock.java new file mode 100644 index 00000000..fef35471 --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/MultiBlock.java @@ -0,0 +1,71 @@ +package net.buildtheearth.terraplusplus.dataset.vector.draw; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; + +import lombok.NonNull; +import net.buildtheearth.terraplusplus.TerraConstants; +import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; +import net.buildtheearth.terraplusplus.generator.CachedChunkData; +import net.buildtheearth.terraplusplus.generator.surface.MultiBlockSurfacePattern; +import net.daporkchop.lib.common.util.PValidation; +import net.minecraft.block.state.IBlockState; + +/** + * {@link DrawFunction} which sets the surface to a fixed surface pattern. + * + * @author SmylerMC + */ +@JsonAdapter(MultiBlock.Parser.class) +public class MultiBlock implements DrawFunction { + + @NonNull + protected final MultiBlockSurfacePattern pattern; + + public MultiBlock(int offset, IBlockState... states) { + this.pattern = new MultiBlockSurfacePattern(offset, states); + } + + @Override + public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { + data.surfacePatterns()[x * 16 + z] = this.pattern; + } + + static class Parser extends JsonParser { + @Override + public MultiBlock read(JsonReader in) throws IOException { + in.beginObject(); + int offset = 0; + List states = new ArrayList<>(); + while (in.peek() != JsonToken.END_OBJECT) { + String name = in.nextName(); + switch (name) { + case "offset": + offset = in.nextInt(); + break; + case "blocks": + in.beginArray(); + while (in.peek() != JsonToken.END_ARRAY) { + states.add(TerraConstants.GSON.fromJson(in, IBlockState.class)); + } + in.endArray(); + break; + default: + throw new IllegalStateException("invalid property: " + name); + } + } + in.endObject(); + PValidation.checkState(states.size() > 0, "Illegal block state array: at least one required"); + Collections.reverse(states); + IBlockState[] blocks = states.toArray(new IBlockState[0]); + return new MultiBlock(offset, blocks); + } + } + +} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/MultiBlockSurfacePattern.java b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/MultiBlockSurfacePattern.java new file mode 100644 index 00000000..b8f3034b --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/MultiBlockSurfacePattern.java @@ -0,0 +1,25 @@ +package net.buildtheearth.terraplusplus.generator.surface; + +import java.util.Random; + +import net.minecraft.block.state.IBlockState; + +/** + * A basic surface pattern with blocks on top of each others + * + * @author SmylerMC + */ +public class MultiBlockSurfacePattern implements ISurfacePattern { + + private final BakedSurfacePattern baked; + + public MultiBlockSurfacePattern(int offset, IBlockState... states) { + this.baked = new BakedSurfacePattern(states, offset); + } + + @Override + public BakedSurfacePattern bake(int x, int surfaceY, int z, Random random) { + return this.baked; + } + +} From 6d3eca9c2c4df9fb9146ed250988fc5bf665d581 Mon Sep 17 00:00:00 2001 From: SmylerMC Date: Fri, 13 Aug 2021 14:51:21 +0200 Subject: [PATCH 3/6] Fixed surface intersection calculation --- .../terraplusplus/generator/CachedChunkData.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java b/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java index d0cb8937..3e448028 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java @@ -124,9 +124,17 @@ private CachedChunkData(@NonNull Builder builder, @NonNull Map c int max = Integer.MIN_VALUE; for (int i = 0; i < 16 * 16; i++) { BakedSurfacePattern pattern = this.surfacePatterns.get(i); - if(pattern == null) continue; - min = min(min, min(this.groundHeight[i], this.surfaceHeight[i]) - pattern.offset()); - max = max(max, max(this.groundHeight[i], this.surfaceHeight[i]) + pattern.pattern().length - pattern.offset() - 1); + int patternSize; + int patternOffset; + if(pattern != null) { + patternSize = pattern.pattern().length; + patternOffset = pattern.offset(); + } else { + patternSize = 1; + patternOffset = 0; + } + min = min(min, min(this.groundHeight[i] - patternOffset, this.surfaceHeight[i])); + max = max(max, max(this.groundHeight[i] + patternSize - patternOffset - 1, this.surfaceHeight[i])); } this.surfaceMinCube = Coords.blockToCube(min) - 1; this.surfaceMaxCube = Coords.blockToCube(max) + 1; From 0de86efb99de9b8674d012f8af0e8e0dbd0c5f63 Mon Sep 17 00:00:00 2001 From: SmylerMC Date: Sun, 15 Aug 2021 18:31:31 +0200 Subject: [PATCH 4/6] StabbingTree implementation --- .../terraplusplus/util/StabbingTree.java | 233 +++++++++++++++++ .../terraplusplus/util/StabbingTreeTest.java | 236 ++++++++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 src/main/java/net/buildtheearth/terraplusplus/util/StabbingTree.java create mode 100644 src/test/java/net/buildtheearth/terraplusplus/util/StabbingTreeTest.java diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/StabbingTree.java b/src/main/java/net/buildtheearth/terraplusplus/util/StabbingTree.java new file mode 100644 index 00000000..d37a836b --- /dev/null +++ b/src/main/java/net/buildtheearth/terraplusplus/util/StabbingTree.java @@ -0,0 +1,233 @@ +package net.buildtheearth.terraplusplus.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; + +import com.google.common.base.Objects; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import net.daporkchop.lib.common.util.PValidation; + +/** + * A data structure to keep track of sections to which values have been assigned. + * + * @param - section key type + * @param - section value type + * + * @author SmylerMC + */ +public class StabbingTree, V> { + + private List nodes = new ArrayList<>(); + private final V defaultValue; + + /** + * Creates a new {@link StabbingTree}. + * + * @param defaultValue - the value to return when requesting a value at a point that isn't in any section + */ + public StabbingTree(V defaultValue) { + this.defaultValue = defaultValue; + } + + /** + * Creates a new {@link StabbingTree} that returns null when requesting a value at a point that isn't in any section. + */ + public StabbingTree() { + this.defaultValue = null; + } + + /** + * Creates a new section with a given value. + * + * @param start - starting point of the section (inclusive) + * @param end - ending point of the section (exclusive) + * @param value - the value for this section + * + * @throws NullPointerException if either start or end is null + * @throws IllegalArgumentException if start is grater or equal to end + */ + public void setBetween(K start, K end, V value) { + this.validateInterval(start, end); + int nodeCount = this.nodes.size(); + if (nodeCount == 0) { + this.nodes.add(new Node(start, value)); + this.nodes.add(new Node(end, this.defaultValue)); + } else { + int lower = this.getInternalIndexFor(start); + int upper = this.getInternalIndexFor(end); + V continueVal; + if (upper < nodeCount && this.nodes.get(upper).start.equals(end)){ + // The start next of the previous section is just where ours end, do not look at the previous one + continueVal = this.nodes.get(upper).value; + } else if (upper == 0) { + // We are before the first node + continueVal = this.defaultValue; + } else { + // Once we end, we can continue the section that was there before us + continueVal = this.nodes.get(--upper).value; + } + // Remove the sections we are overwriting + for(; upper >= lower; upper--) { + this.nodes.remove(upper); + } + if (!Objects.equal(value, continueVal)) { + // We do not need to add a node if the section that follows has the same value, in that case we just merge + this.nodes.add(lower, new Node(end, continueVal)); + } + if (lower == 0 || !Objects.equal(value, this.nodes.get(lower - 1).value)) { + // If the section that precedes has the same value, we just need to extend it, no need for a starting node + this.nodes.add(lower, new Node(start, value)); + } + } + } + + /** + * Gets the value at the given point. + * If there is no section there, the default value is returned. + * + * @param index - where to get he value + * + * @return the value at the given point + */ + public V get(K index) { + int nodeCount = this.nodes.size(); + if (nodeCount >= 2) { + int i = this.getInternalIndexFor(index); + if (i < nodeCount && this.nodes.get(i).start.equals(index)) { + return this.nodes.get(i).value; + } else if (i == 0) { + // We are before the first node + return this.defaultValue; + } else { + Node node = this.nodes.get(i - 1); + return node.value; + } + } else { + return this.defaultValue; + } + } + + /** + * @return the number of section + */ + public int sections() { + int c = this.nodes.size(); + return c == 0 ? c : c - 1; + } + + /** + * Clears all sections, resets this {@link StabbingTree} as if if was just created + */ + public void clear() { + this.nodes.clear(); + } + + /** + * Calls a consumer for all sections in the tree. + * The consumer will take the start node and the end node of each section as inputs. + * The start node holds the value for the section. + * The end node holds the value for the section that follows. + * + * @param consumer - the method to call + */ + public void forEachSection(BiConsumer consumer) { + int nodeCount = this.nodes.size(); + if (nodeCount > 1) { + Node previousNode = this.nodes.get(0); + for (int i = 1; i < nodeCount; i++) { + Node node = this.nodes.get(i); + consumer.accept(previousNode, node); + previousNode = node; + } + } + } + + /** + * Calls a consumer for all sections in the tree intersecting with the given interval. + * The consumer will take the start node and the end node of each section as inputs. + * The start node holds the value for the section. + * The end node holds the value for the section that follows. + * + * @param consumer - the method to call + * + * @throws NullPointerException if either start or end is null + * @throws IllegalArgumentException if start is grater or equal to end + */ + public void forEachSection(K start, K end, BiConsumer consumer) { + this.validateInterval(start, end); + int si = this.getInternalIndexFor(start); + int ei = this.getInternalIndexFor(end); + int nodeCount = this.nodes.size(); + if (si != ei || (si != 0 && si != nodeCount)) { + // We do intersect + Node previousNode = this.nodes.get(si); + if (!previousNode.start.equals(start) && si > 0) { + // We only keep a portion of the previous section + previousNode = new Node(start, this.nodes.get(si - 1).value); + } else { + si++; + } + for (; si < ei; si++) { + Node node = this.nodes.get(si); + consumer.accept(previousNode, node); + previousNode = node; + } + Node endNode; + if (ei < nodeCount) { + endNode = this.nodes.get(ei); + if(!endNode.start.equals(end)) { + // What follows still has the same value + endNode = new Node(end, previousNode.value); + } + consumer.accept(previousNode, endNode); + } + } + } + + /** + * Finds the index at which a node would have to be inserted to keep the list sorted + * + * @param key + * + * @return the proper index for the given key + */ + private int getInternalIndexFor(K key) { + int lower = -1, upper = this.nodes.size(); + while (upper - lower > 1) { + int mid = (upper + lower) / 2; + K midKey = this.nodes.get(mid).start; + int comparison = key.compareTo(midKey); + if (comparison < 0) { + upper = mid; + } else if (comparison > 0) { + lower = mid; + } else { + return mid; + } + } + return lower + 1; + } + + private void validateInterval(@NonNull K start, @NonNull K end) { + PValidation.checkArg(start.compareTo(end) < 0, "Start of section cannot greater than or equal to the end of the section"); + } + + /** + * Represents the boundaries of sections in a {@link StabbingTree}. + * A {@link Node} is the end of a first section, in which it is excluded, + * and the beginning of the section that follows, in which it is included, + * and for which it holds the value stored in the section. + */ + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + @Getter + public class Node { + private final K start; + private final V value; + } + +} diff --git a/src/test/java/net/buildtheearth/terraplusplus/util/StabbingTreeTest.java b/src/test/java/net/buildtheearth/terraplusplus/util/StabbingTreeTest.java new file mode 100644 index 00000000..3a6165f6 --- /dev/null +++ b/src/test/java/net/buildtheearth/terraplusplus/util/StabbingTreeTest.java @@ -0,0 +1,236 @@ +package net.buildtheearth.terraplusplus.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +public class StabbingTreeTest { + + @Test + public void testSetAndGet() { + StabbingTree tree = new StabbingTree<>(); + assertNull(tree.get(0)); + + tree.setBetween(0, 100, 1); + + assertEquals(1, tree.sections()); + assertNull(tree.get(-1)); + for (int i = 0; i < 100; i++) assertEquals(1, tree.get(i).intValue()); + assertNull(tree.get(100)); + + tree.setBetween(-50, 50, 2); + + assertEquals(2, tree.sections()); + assertNull(tree.get(-51)); + for (int i = -50; i < 50; i++) assertEquals(2, tree.get(i).intValue()); + for (int i = 50; i < 100; i++) assertEquals(1, tree.get(i).intValue()); + assertNull(tree.get(100)); + + tree.setBetween(40, 60, 3); + + assertEquals(3, tree.sections()); + assertNull(tree.get(-51)); + for (int i = -50; i < 40; i++) assertEquals(2, tree.get(i).intValue()); + for (int i = 40; i < 60; i++) assertEquals(3, tree.get(i).intValue()); + for (int i = 60; i < 100; i++) assertEquals(1, tree.get(i).intValue()); + assertNull(tree.get(100)); + + tree.setBetween(100, 150, 4); + + assertEquals(4, tree.sections()); + assertNull(tree.get(-51)); + for (int i = -50; i < 40; i++) assertEquals(2, tree.get(i).intValue()); + for (int i = 40; i < 60; i++) assertEquals(3, tree.get(i).intValue()); + for (int i = 60; i < 100; i++) assertEquals(1, tree.get(i).intValue()); + for (int i = 100; i < 150; i++) assertEquals(4, tree.get(i).intValue()); + assertNull(tree.get(150)); + + tree.setBetween(-100, -50, 5); + + assertEquals(5, tree.sections()); + assertNull(tree.get(-101)); + for (int i = -100; i < -50; i++) assertEquals(5, tree.get(i).intValue()); + for (int i = -50; i < 40; i++) assertEquals(2, tree.get(i).intValue()); + for (int i = 40; i < 60; i++) assertEquals(3, tree.get(i).intValue()); + for (int i = 60; i < 100; i++) assertEquals(1, tree.get(i).intValue()); + for (int i = 100; i < 150; i++) assertEquals(4, tree.get(i).intValue()); + assertNull(tree.get(150)); + + tree.setBetween(-10, 120, 6); + + assertEquals(4, tree.sections()); + assertNull(tree.get(-101)); + for (int i = -100; i < -50; i++) assertEquals(5, tree.get(i).intValue()); + for (int i = -50; i < -10; i++) assertEquals(2, tree.get(i).intValue()); + for (int i = -10; i < 120; i++) assertEquals(6, tree.get(i).intValue()); + for (int i = 120; i < 150; i++) assertEquals(4, tree.get(i).intValue()); + assertNull(tree.get(150)); + + tree.setBetween(20, 80, 7); + + assertEquals(6, tree.sections()); + assertNull(tree.get(-101)); + for (int i = -100; i < -50; i++) assertEquals(5, tree.get(i).intValue()); + for (int i = -50; i < -10; i++) assertEquals(2, tree.get(i).intValue()); + for (int i = -10; i < 20; i++) assertEquals(6, tree.get(i).intValue()); + for (int i = 20; i < 80; i++) assertEquals(7, tree.get(i).intValue()); + for (int i = 80; i < 120; i++) assertEquals(6, tree.get(i).intValue()); + for (int i = 120; i < 150; i++) assertEquals(4, tree.get(i).intValue()); + assertNull(tree.get(150)); + + tree.setBetween(80, 100, 8); + + assertEquals(7, tree.sections()); + assertNull(tree.get(-101)); + for (int i = -100; i < -50; i++) assertEquals(5, tree.get(i).intValue()); + for (int i = -50; i < -10; i++) assertEquals(2, tree.get(i).intValue()); + for (int i = -10; i < 20; i++) assertEquals(6, tree.get(i).intValue()); + for (int i = 20; i < 80; i++) assertEquals(7, tree.get(i).intValue()); + for (int i = 80; i < 100; i++) assertEquals(8, tree.get(i).intValue()); + for (int i = 100; i < 120; i++) assertEquals(6, tree.get(i).intValue()); + for (int i = 120; i < 150; i++) assertEquals(4, tree.get(i).intValue()); + assertNull(tree.get(150)); + + tree.setBetween(0, 20, 9); + + assertEquals(8, tree.sections()); + assertNull(tree.get(-101)); + for (int i = -100; i < -50; i++) assertEquals(5, tree.get(i).intValue()); + for (int i = -50; i < -10; i++) assertEquals(2, tree.get(i).intValue()); + for (int i = -10; i < 0; i++) assertEquals(6, tree.get(i).intValue()); + for (int i = 0; i < 20; i++) assertEquals(9, tree.get(i).intValue()); + for (int i = 20; i < 80; i++) assertEquals(7, tree.get(i).intValue()); + for (int i = 80; i < 100; i++) assertEquals(8, tree.get(i).intValue()); + for (int i = 100; i < 120; i++) assertEquals(6, tree.get(i).intValue()); + for (int i = 120; i < 150; i++) assertEquals(4, tree.get(i).intValue()); + assertNull(tree.get(150)); + + tree.setBetween(60, 80, 8); + + assertEquals(8, tree.sections()); + assertNull(tree.get(-101)); + for (int i = -100; i < -50; i++) assertEquals(5, tree.get(i).intValue()); + for (int i = -50; i < -10; i++) assertEquals(2, tree.get(i).intValue()); + for (int i = -10; i < 0; i++) assertEquals(6, tree.get(i).intValue()); + for (int i = 0; i < 20; i++) assertEquals(9, tree.get(i).intValue()); + for (int i = 20; i < 60; i++) assertEquals(7, tree.get(i).intValue()); + for (int i = 60; i < 100; i++) assertEquals(8, tree.get(i).intValue()); + for (int i = 100; i < 120; i++) assertEquals(6, tree.get(i).intValue()); + for (int i = 120; i < 150; i++) assertEquals(4, tree.get(i).intValue()); + assertNull(tree.get(150)); + + tree.setBetween(100, 130, 8); + + assertEquals(7, tree.sections()); + assertNull(tree.get(-101)); + for (int i = -100; i < -50; i++) assertEquals(5, tree.get(i).intValue()); + for (int i = -50; i < -10; i++) assertEquals(2, tree.get(i).intValue()); + for (int i = -10; i < 0; i++) assertEquals(6, tree.get(i).intValue()); + for (int i = 0; i < 20; i++) assertEquals(9, tree.get(i).intValue()); + for (int i = 20; i < 60; i++) assertEquals(7, tree.get(i).intValue()); + for (int i = 60; i < 130; i++) assertEquals(8, tree.get(i).intValue()); + for (int i = 130; i < 150; i++) assertEquals(4, tree.get(i).intValue()); + assertNull(tree.get(150)); + } + + @Test + public void testForEach() { + StabbingTree tree = new StabbingTree<>(); + this.testForEach(tree); + + tree.setBetween(0, 100, 1); + + this.testForEach(tree, + 0, 1, 100, null); + + tree.setBetween(-50, 50, 2); + + this.testForEach(tree, + -50, 2, 50, 1, + 50, 1, 100, null); + + tree.setBetween(40, 60, 3); + + this.testForEach(tree, + -50, 2, 40, 3, + 40, 3, 60, 1, + 60, 1, 100, null); + + tree.setBetween(100, 150, 4); + tree.setBetween(-100, -50, 5); + tree.setBetween(-10, 120, 6); + tree.setBetween(20, 80, 7); + tree.setBetween(80, 100, 8); + tree.setBetween(0, 20, 9); + tree.setBetween(60, 80, 8); + + this.testForEach(tree, + -100, 5, -50, 2, + -50, 2, -10, 6, + -10, 6, 0, 9, + 0, 9, 20, 7, + 20, 7, 60, 8, + 60, 8, 100, 6, + 100, 6, 120, 4, + 120, 4, 150, null); + this.testForEach(-210, -200, tree); + this.testForEach(500, 510, tree); + this.testForEach(-50, 60, tree, + -50, 2, -10, 6, + -10, 6, 0, 9, + 0, 9, 20, 7, + 20, 7, 60, 8); + this.testForEach(50, 130, tree, + 50, 7, 60, 8, + 60, 8, 100, 6, + 100, 6, 120, 4, + 120, 4, 130, 4); + this.testForEach(80, 160, tree, + 80, 8, 100, 6, + 100, 6, 120, 4, + 120, 4, 150, null); + this.testForEach(-200, 10, tree, + -100, 5, -50, 2, + -50, 2, -10, 6, + -10, 6, 0, 9, + 0, 9, 10, 9); + this.testForEach(30, 40, tree, + 30, 7, 40, 7); + + } + + private void testForEach(StabbingTree tree, Integer... values) { + List list = new ArrayList<>(); + tree.forEachSection((startNode, endNode) -> { + list.add(startNode.start()); + list.add(startNode.value()); + list.add(endNode.start()); + list.add(endNode.value()); + }); + assertEquals(values.length, list.size()); + for (int i = 0; i < values.length; i++) { + assertEquals(values[i], list.get(i)); + } + } + + private void testForEach(int start, int end, StabbingTree tree, Integer... values) { + List list = new ArrayList<>(); + tree.forEachSection(start, end, (startNode, endNode) -> { + System.out.println(startNode.start() + " " + endNode.start()); + list.add(startNode.start()); + list.add(startNode.value()); + list.add(endNode.start()); + list.add(endNode.value()); + }); + System.out.println(); + assertEquals(values.length, list.size()); + for (int i = 0; i < values.length; i++) { + assertEquals(values[i], list.get(i)); + } + } + +} From a5e70b44a01f0926f7bac38ff84daeb087cd259e Mon Sep 17 00:00:00 2001 From: SmylerMC Date: Sun, 15 Aug 2021 18:38:40 +0200 Subject: [PATCH 5/6] Reverted surface pattern commit --- .../dataset/vector/draw/Block.java | 16 ++--- .../generator/CachedChunkData.java | 56 +++++------------- .../generator/ChunkBiomesBuilder.java | 7 +-- .../generator/EarthGenerator.java | 44 ++++++-------- .../generator/IEarthAsyncPipelineStep.java | 18 +++--- .../generator/TerrainPreview.java | 58 +++++++------------ .../surface/BakedSurfacePattern.java | 19 ------ .../surface/BlockSurfacePattern.java | 27 --------- .../generator/surface/ISurfacePattern.java | 14 ----- .../util/ImmutableCompactArray.java | 15 ++--- .../terraplusplus/util/StabbingTreeTest.java | 2 - 11 files changed, 77 insertions(+), 199 deletions(-) delete mode 100644 src/main/java/net/buildtheearth/terraplusplus/generator/surface/BakedSurfacePattern.java delete mode 100644 src/main/java/net/buildtheearth/terraplusplus/generator/surface/BlockSurfacePattern.java delete mode 100644 src/main/java/net/buildtheearth/terraplusplus/generator/surface/ISurfacePattern.java diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java index e2f70076..f38c5c5a 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java @@ -1,36 +1,30 @@ package net.buildtheearth.terraplusplus.dataset.vector.draw; -import java.io.IOException; - import com.google.gson.annotations.JsonAdapter; import com.google.gson.stream.JsonReader; - import lombok.NonNull; +import lombok.RequiredArgsConstructor; import net.buildtheearth.terraplusplus.TerraConstants; import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; import net.buildtheearth.terraplusplus.generator.CachedChunkData; -import net.buildtheearth.terraplusplus.generator.surface.BlockSurfacePattern; import net.minecraft.block.state.IBlockState; +import java.io.IOException; + /** * {@link DrawFunction} which sets the surface block to a fixed block state. * * @author DaPorkchop_ */ @JsonAdapter(Block.Parser.class) +@RequiredArgsConstructor public final class Block implements DrawFunction { @NonNull protected final IBlockState state; - protected transient final BlockSurfacePattern pattern; - - public Block(IBlockState state) { - this.state = state; - this.pattern = new BlockSurfacePattern(state); - } @Override public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - data.surfacePatterns()[x * 16 + z] = this.pattern; + data.surfaceBlocks()[x * 16 + z] = this.state; } static class Parser extends JsonParser { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java b/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java index 3e448028..c255bd17 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java @@ -8,7 +8,6 @@ import java.util.Arrays; import java.util.Map; -import java.util.Random; import com.google.common.collect.ImmutableMap; @@ -17,15 +16,13 @@ import lombok.Getter; import lombok.NonNull; import lombok.Setter; -import net.buildtheearth.terraplusplus.generator.surface.BakedSurfacePattern; -import net.buildtheearth.terraplusplus.generator.surface.ISurfacePattern; import net.buildtheearth.terraplusplus.util.CustomAttributeContainer; import net.buildtheearth.terraplusplus.util.ImmutableCompactArray; import net.daporkchop.lib.common.ref.Ref; import net.daporkchop.lib.common.ref.ThreadRef; import net.daporkchop.lib.common.util.PorkUtil; +import net.minecraft.block.state.IBlockState; import net.minecraft.init.Biomes; -import net.minecraft.util.math.ChunkPos; import net.minecraft.world.biome.Biome; /** @@ -45,8 +42,8 @@ public class CachedChunkData extends CustomAttributeContainer { private static final Ref BUILDER_CACHE = ThreadRef.soft(Builder::new); - public static Builder builder(ChunkPos pos) { - return BUILDER_CACHE.get().reset(pos); + public static Builder builder() { + return BUILDER_CACHE.get().reset(); } private static int extractActualDepth(int waterDepth) { @@ -60,7 +57,7 @@ private static int extractActualDepth(int waterDepth) { @Getter private final byte[] biomes; - private final ImmutableCompactArray surfacePatterns; + private final ImmutableCompactArray surfaceBlocks; private final int surfaceMinCube; private final int surfaceMaxCube; @@ -106,35 +103,13 @@ private CachedChunkData(@NonNull Builder builder, @NonNull Map c this.biomes[i] = (byte) Biome.getIdForBiome(PorkUtil.fallbackIfNull(builder.biomes[i], Biomes.DEEP_OCEAN)); } - Random random = new Random(ChunkPos.asLong(builder.pos.x, builder.pos.z)); - BakedSurfacePattern[] patterns = new BakedSurfacePattern[16 * 16]; - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - int i = x*16 + z; - int wx = builder.pos.x * 16 + x; - int wy = this.groundHeight[i]; - int wz = builder.pos.z * 16 + z; - ISurfacePattern pattern = builder.surfacePatterns()[i]; - patterns[i] = pattern != null ? pattern.bake(wx, wy, wz, random): null; - } - } - this.surfacePatterns = new ImmutableCompactArray<>(patterns); //TODO Not so efficient anymore as it is reference based + this.surfaceBlocks = new ImmutableCompactArray<>(builder.surfaceBlocks); int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; for (int i = 0; i < 16 * 16; i++) { - BakedSurfacePattern pattern = this.surfacePatterns.get(i); - int patternSize; - int patternOffset; - if(pattern != null) { - patternSize = pattern.pattern().length; - patternOffset = pattern.offset(); - } else { - patternSize = 1; - patternOffset = 0; - } - min = min(min, min(this.groundHeight[i] - patternOffset, this.surfaceHeight[i])); - max = max(max, max(this.groundHeight[i] + patternSize - patternOffset - 1, this.surfaceHeight[i])); + min = min(min, min(this.groundHeight[i], this.surfaceHeight[i])); + max = max(max, max(this.groundHeight[i], this.surfaceHeight[i])); } this.surfaceMinCube = Coords.blockToCube(min) - 1; this.surfaceMaxCube = Coords.blockToCube(max) + 1; @@ -168,8 +143,8 @@ public int waterHeight(int x, int z) { return this.surfaceHeight(x, z) - 1; } - public BakedSurfacePattern surfacePattern(int x, int z) { - return this.surfacePatterns.get(x * 16 + z); + public IBlockState surfaceBlock(int x, int z) { + return this.surfaceBlocks.get(x * 16 + z); } public int biome(int x, int z) { @@ -184,22 +159,20 @@ public int biome(int x, int z) { @Getter @Setter public static final class Builder extends CustomAttributeContainer implements IEarthAsyncDataBuilder { - private ChunkPos pos; - private final int[] surfaceHeight = new int[16 * 16]; private final byte[] waterDepth = new byte[16 * 16]; private final Biome[] biomes = new Biome[16 * 16]; - protected final ISurfacePattern[] surfacePatterns = new ISurfacePattern[16 * 16]; + protected final IBlockState[] surfaceBlocks = new IBlockState[16 * 16]; /** - * @deprecated use {@link #builder(ChunkPos)} unless you have a specific reason to invoke this constructor directly + * @deprecated use {@link #builder()} unless you have a specific reason to invoke this constructor directly */ @Deprecated public Builder() { super(new Object2ObjectOpenHashMap<>()); - this.reset(new ChunkPos(0, 0)); + this.reset(); } public Builder surfaceHeight(int x, int z, int value) { @@ -231,11 +204,10 @@ public void putCustom(@NonNull String key, @NonNull Object value) { this.custom.put(key, value); } - public Builder reset(ChunkPos pos) { + public Builder reset() { Arrays.fill(this.surfaceHeight, BLANK_HEIGHT); Arrays.fill(this.waterDepth, (byte) WATERDEPTH_DEFAULT); - Arrays.fill(this.surfacePatterns, null); - this.pos = pos; + Arrays.fill(this.surfaceBlocks, null); this.custom.clear(); return this; } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java b/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java index 12f9ed3e..1208999d 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/ChunkBiomesBuilder.java @@ -1,14 +1,13 @@ package net.buildtheearth.terraplusplus.generator; -import java.util.Arrays; - import lombok.Getter; import net.buildtheearth.terraplusplus.util.ImmutableCompactArray; import net.daporkchop.lib.common.ref.Ref; import net.daporkchop.lib.common.ref.ThreadRef; -import net.minecraft.util.math.ChunkPos; import net.minecraft.world.biome.Biome; +import java.util.Arrays; + /** * Builds a 16x16 area of chunks. * @@ -18,7 +17,7 @@ public class ChunkBiomesBuilder implements IEarthAsyncDataBuilder> { private static final Ref BUILDER_CACHE = ThreadRef.soft(ChunkBiomesBuilder::new); - public static ChunkBiomesBuilder get(ChunkPos pos) { + public static ChunkBiomesBuilder get() { return BUILDER_CACHE.get().reset(); } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java index 6ebd1f46..7a99b2b7 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java @@ -1,21 +1,8 @@ package net.buildtheearth.terraplusplus.generator; -import static java.lang.Math.max; -import static java.lang.Math.min; - -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; - import io.github.opencubicchunks.cubicchunks.api.util.Coords; import io.github.opencubicchunks.cubicchunks.api.util.CubePos; import io.github.opencubicchunks.cubicchunks.api.world.ICube; @@ -44,7 +31,6 @@ import net.buildtheearth.terraplusplus.TerraMod; import net.buildtheearth.terraplusplus.generator.data.IEarthDataBaker; import net.buildtheearth.terraplusplus.generator.populate.IEarthPopulator; -import net.buildtheearth.terraplusplus.generator.surface.BakedSurfacePattern; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.minecraft.block.state.IBlockState; @@ -65,6 +51,17 @@ import net.minecraftforge.fml.common.gameevent.PlayerEvent; import net.minecraftforge.fml.common.registry.ForgeRegistries; +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import static java.lang.Math.*; + public class EarthGenerator extends BasicCubeGenerator { public static final int WATER_DEPTH_OFFSET = 1; @@ -266,18 +263,11 @@ protected void generateCube(int cubeX, int cubeY, int cubeZ, CubePrimer primer, if (data.intersectsSurface(cubeY)) { //render surface blocks onto cube surface for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { - int surface = data.surfaceHeight(x, z) - Coords.cubeToMinBlock(cubeY); - BakedSurfacePattern pattern = data.surfacePattern(x, z); - if (pattern == null) continue; - IBlockState[] blocks = pattern.pattern(); - int offset = pattern.offset(); - for(int i = 0; i < blocks.length; i++) { - int y = surface - offset + i; - IBlockState state = blocks[i]; - if ((y & 0xF) == y //don't set surface blocks outside of this cube - && state != null) { - primer.setBlockState(x, y, z, state); - } + int y = data.surfaceHeight(x, z) - Coords.cubeToMinBlock(cubeY); + IBlockState state; + if ((y & 0xF) == y //don't set surface blocks outside of this cube + && (state = data.surfaceBlock(x, z)) != null) { + primer.setBlockState(x, y, z, state); } } } @@ -381,7 +371,7 @@ public void populate(ICube cube) { Random random = Coords.coordsSeedRandom(this.world.getSeed(), cube.getX(), cube.getY(), cube.getZ()); Biome biome = cube.getBiome(Coords.getCubeCenter(cube)); - this.cubiccfg.expectedBaseHeight = datas[0].groundHeight(15, 15); + this.cubiccfg.expectedBaseHeight = (float) datas[0].groundHeight(15, 15); for (IEarthPopulator populator : this.populators) { populator.populate(this.world, random, cube.getCoords(), biome, datas); diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java b/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java index 25e89b5b..2811ff64 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/IEarthAsyncPipelineStep.java @@ -1,12 +1,5 @@ package net.buildtheearth.terraplusplus.generator; -import static net.daporkchop.lib.common.util.PorkUtil.uncheckedCast; - -import java.util.Arrays; -import java.util.Objects; -import java.util.concurrent.CompletableFuture; -import java.util.function.Function; - import io.github.opencubicchunks.cubicchunks.api.util.Coords; import net.buildtheearth.terraplusplus.TerraMod; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; @@ -14,11 +7,18 @@ import net.buildtheearth.terraplusplus.util.bvh.Bounds2d; import net.minecraft.util.math.ChunkPos; +import java.util.Arrays; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +import static net.daporkchop.lib.common.util.PorkUtil.*; + /** * @author DaPorkchop_ */ public interface IEarthAsyncPipelineStep> { - static > CompletableFuture getFuture(ChunkPos pos, GeneratorDatasets datasets, IEarthAsyncPipelineStep[] steps, Function builderFactory) { + static > CompletableFuture getFuture(ChunkPos pos, GeneratorDatasets datasets, IEarthAsyncPipelineStep[] steps, Supplier builderFactory) { int baseX = Coords.cubeToMinBlock(pos.x); int baseZ = Coords.cubeToMinBlock(pos.z); @@ -43,7 +43,7 @@ static > CompletableFuture getFuture(C CompletableFuture future = (nonNullFutures.length != 0 ? CompletableFuture.allOf(nonNullFutures) : CompletableFuture.completedFuture(null)) .thenApply(unused -> { - B builder = builderFactory.apply(pos); + B builder = builderFactory.get(); for (int i = 0; i < steps.length; i++) { CompletableFuture stepFuture = futures[i]; diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java b/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java index 0a8347c6..2462ebfa 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java @@ -1,46 +1,40 @@ package net.buildtheearth.terraplusplus.generator; -import static java.lang.Math.max; -import static net.daporkchop.lib.common.math.PMath.clamp; -import static net.daporkchop.lib.common.math.PMath.floorI; -import static net.daporkchop.lib.common.math.PMath.lerpI; -import static net.daporkchop.lib.common.util.PorkUtil.uncheckedCast; - -import java.awt.Canvas; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.KeyEventDispatcher; -import java.awt.KeyboardFocusManager; -import java.awt.RenderingHints; -import java.awt.event.KeyEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.image.BufferedImage; -import java.util.Arrays; -import java.util.Collections; -import java.util.concurrent.CompletableFuture; - -import javax.swing.JFrame; - import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; - import lombok.NonNull; import net.buildtheearth.terraplusplus.generator.data.TreeCoverBaker; -import net.buildtheearth.terraplusplus.generator.surface.BakedSurfacePattern; import net.buildtheearth.terraplusplus.projection.GeographicProjection; import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; import net.buildtheearth.terraplusplus.util.EmptyWorld; import net.buildtheearth.terraplusplus.util.TilePos; import net.buildtheearth.terraplusplus.util.http.Http; -import net.minecraft.block.material.MapColor; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Bootstrap; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.ChunkPos; import net.minecraftforge.client.model.pipeline.LightUtil; +import javax.swing.JFrame; +import java.awt.Canvas; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.KeyEventDispatcher; +import java.awt.KeyboardFocusManager; +import java.awt.RenderingHints; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.image.BufferedImage; +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.CompletableFuture; + +import static java.lang.Math.*; +import static net.daporkchop.lib.common.math.PMath.*; +import static net.daporkchop.lib.common.util.PorkUtil.*; + /** * @author DaPorkchop_ */ @@ -220,7 +214,7 @@ public void dispose() { //proj = state.projection.fromGeo(12.610463237424899, 37.673937184583636); //somewhere in sicily //proj = state.projection.fromGeo(9.6726, 45.6699); //lombardia, italy //proj = state.projection.fromGeo(8.93058, 44.40804); //genova, italy - proj = state.projection.fromGeo(16.5922, 38.9069); //catanzaro, italy + //proj = state.projection.fromGeo(16.5922, 38.9069); //catanzaro, italy state.setView(floorI(proj[0]) >> 4, floorI(proj[1]) >> 4, 0); state.update(); @@ -289,17 +283,7 @@ protected CompletableFuture baseZoomTile(int x, int z) { for (int cz = 0; cz < 16; cz++) { int c; - IBlockState state = null; - BakedSurfacePattern pattern = data.surfacePattern(cx, cz); - if(pattern != null) { - IBlockState[] states = pattern.pattern(); - for(int i = states.length - 1; i >= 0; i--) { - if(states[i] != null && states[i].getMapColor(EmptyWorld.INSTANCE, BlockPos.ORIGIN) != MapColor.AIR) { - state = states[i]; - break; - } - } - } + IBlockState state = data.surfaceBlock(cx, cz); if (state != null) { c = state.getMapColor(EmptyWorld.INSTANCE, BlockPos.ORIGIN).colorValue; } else { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BakedSurfacePattern.java b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BakedSurfacePattern.java deleted file mode 100644 index 042e3870..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BakedSurfacePattern.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.buildtheearth.terraplusplus.generator.surface; - -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import net.minecraft.block.state.IBlockState; - -/** - * A surface pattern ready to be generated - */ -@RequiredArgsConstructor -@Getter -@EqualsAndHashCode -public final class BakedSurfacePattern { - - private final IBlockState[] pattern; - private final int offset; - -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BlockSurfacePattern.java b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BlockSurfacePattern.java deleted file mode 100644 index 42638766..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/BlockSurfacePattern.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.buildtheearth.terraplusplus.generator.surface; - -import java.util.Random; - -import net.minecraft.block.state.IBlockState; - -/** - * @author SmylerMC - */ -public class BlockSurfacePattern implements ISurfacePattern { - - private final IBlockState[] blocks; - - public BlockSurfacePattern(IBlockState block) { - this.blocks = new IBlockState[] { block }; - } - - @Override - public BakedSurfacePattern bake(int x, int surfaceY, int z, Random random) { - return new BakedSurfacePattern(this.blocks, 0); - } - - public IBlockState block() { - return this.blocks[0]; - } - -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/ISurfacePattern.java b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/ISurfacePattern.java deleted file mode 100644 index 6666a600..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/ISurfacePattern.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.buildtheearth.terraplusplus.generator.surface; - -import java.util.Random; - -/** - * A vertical pattern that should be drawn at the surface of the ground - * - * @author SmylerMC - */ -public interface ISurfacePattern { - - BakedSurfacePattern bake(int x, int surfaceY, int z, Random random); - -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/ImmutableCompactArray.java b/src/main/java/net/buildtheearth/terraplusplus/util/ImmutableCompactArray.java index dd2b8fe4..c01070b1 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/ImmutableCompactArray.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/ImmutableCompactArray.java @@ -1,19 +1,20 @@ package net.buildtheearth.terraplusplus.util; -import static java.lang.Math.max; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import it.unimi.dsi.fastutil.objects.Reference2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; import lombok.NonNull; import net.daporkchop.lib.binary.bit.BitArray; import net.daporkchop.lib.binary.bit.padded.PaddedBitArray; +import net.minecraft.block.state.IBlockState; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static java.lang.Math.*; /** - * A compact, immutable array. + * A compact, immutable array of {@link IBlockState}s. * * @author DaPorkchop_ */ diff --git a/src/test/java/net/buildtheearth/terraplusplus/util/StabbingTreeTest.java b/src/test/java/net/buildtheearth/terraplusplus/util/StabbingTreeTest.java index 3a6165f6..66f96a5c 100644 --- a/src/test/java/net/buildtheearth/terraplusplus/util/StabbingTreeTest.java +++ b/src/test/java/net/buildtheearth/terraplusplus/util/StabbingTreeTest.java @@ -220,13 +220,11 @@ private void testForEach(StabbingTree tree, Integer... values) private void testForEach(int start, int end, StabbingTree tree, Integer... values) { List list = new ArrayList<>(); tree.forEachSection(start, end, (startNode, endNode) -> { - System.out.println(startNode.start() + " " + endNode.start()); list.add(startNode.start()); list.add(startNode.value()); list.add(endNode.start()); list.add(endNode.value()); }); - System.out.println(); assertEquals(values.length, list.size()); for (int i = 0; i < values.length; i++) { assertEquals(values[i], list.get(i)); From 62427793327ae710081694bbe150a029a8095069 Mon Sep 17 00:00:00 2001 From: SmylerMC Date: Sun, 15 Aug 2021 21:38:40 +0200 Subject: [PATCH 6/6] Replaced surface IBlockState array in CachedChunkData with SurfaceColumn --- .../dataset/vector/draw/Block.java | 7 +-- .../dataset/vector/draw/MultiBlock.java | 14 ++++-- .../generator/CachedChunkData.java | 49 +++++++++++++++---- .../generator/EarthGenerator.java | 46 ++++++++++------- .../generator/TerrainPreview.java | 46 +++++++++-------- .../surface/MultiBlockSurfacePattern.java | 25 ---------- .../terraplusplus/util/StabbingTree.java | 4 +- 7 files changed, 110 insertions(+), 81 deletions(-) delete mode 100644 src/main/java/net/buildtheearth/terraplusplus/generator/surface/MultiBlockSurfacePattern.java diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java index f38c5c5a..6d3881bb 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/Block.java @@ -1,7 +1,10 @@ package net.buildtheearth.terraplusplus.dataset.vector.draw; +import java.io.IOException; + import com.google.gson.annotations.JsonAdapter; import com.google.gson.stream.JsonReader; + import lombok.NonNull; import lombok.RequiredArgsConstructor; import net.buildtheearth.terraplusplus.TerraConstants; @@ -9,8 +12,6 @@ import net.buildtheearth.terraplusplus.generator.CachedChunkData; import net.minecraft.block.state.IBlockState; -import java.io.IOException; - /** * {@link DrawFunction} which sets the surface block to a fixed block state. * @@ -24,7 +25,7 @@ public final class Block implements DrawFunction { @Override public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - data.surfaceBlocks()[x * 16 + z] = this.state; + data.surfaceBlocks()[x * 16 + z].setBetween(0, 1, this.state); } static class Parser extends JsonParser { diff --git a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/MultiBlock.java b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/MultiBlock.java index fef35471..a0520534 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/MultiBlock.java +++ b/src/main/java/net/buildtheearth/terraplusplus/dataset/vector/draw/MultiBlock.java @@ -13,7 +13,7 @@ import net.buildtheearth.terraplusplus.TerraConstants; import net.buildtheearth.terraplusplus.dataset.osm.JsonParser; import net.buildtheearth.terraplusplus.generator.CachedChunkData; -import net.buildtheearth.terraplusplus.generator.surface.MultiBlockSurfacePattern; +import net.buildtheearth.terraplusplus.generator.CachedChunkData.SurfaceColumn; import net.daporkchop.lib.common.util.PValidation; import net.minecraft.block.state.IBlockState; @@ -25,16 +25,20 @@ @JsonAdapter(MultiBlock.Parser.class) public class MultiBlock implements DrawFunction { - @NonNull - protected final MultiBlockSurfacePattern pattern; + private int offset; + private IBlockState[] states; public MultiBlock(int offset, IBlockState... states) { - this.pattern = new MultiBlockSurfacePattern(offset, states); + this.offset = offset; + this.states = states; } @Override public void drawOnto(@NonNull CachedChunkData.Builder data, int x, int z, int weight) { - data.surfacePatterns()[x * 16 + z] = this.pattern; + SurfaceColumn column = data.surfaceBlocks()[x * 16 + z]; + for (int i = 0; i < states.length; i++) { + column.setBetween(i + this.offset, i + this.offset + 1, this.states[i]); + } } static class Parser extends JsonParser { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java b/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java index c255bd17..98faf2c3 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/CachedChunkData.java @@ -17,7 +17,7 @@ import lombok.NonNull; import lombok.Setter; import net.buildtheearth.terraplusplus.util.CustomAttributeContainer; -import net.buildtheearth.terraplusplus.util.ImmutableCompactArray; +import net.buildtheearth.terraplusplus.util.StabbingTree; import net.daporkchop.lib.common.ref.Ref; import net.daporkchop.lib.common.ref.ThreadRef; import net.daporkchop.lib.common.util.PorkUtil; @@ -57,7 +57,7 @@ private static int extractActualDepth(int waterDepth) { @Getter private final byte[] biomes; - private final ImmutableCompactArray surfaceBlocks; + private final SurfaceColumn[] surfaceBlocks; private final int surfaceMinCube; private final int surfaceMaxCube; @@ -103,13 +103,16 @@ private CachedChunkData(@NonNull Builder builder, @NonNull Map c this.biomes[i] = (byte) Biome.getIdForBiome(PorkUtil.fallbackIfNull(builder.biomes[i], Biomes.DEEP_OCEAN)); } - this.surfaceBlocks = new ImmutableCompactArray<>(builder.surfaceBlocks); + this.surfaceBlocks = new SurfaceColumn[16 * 16]; + for (int i = 0; i < 16 * 16; i++) { + this.surfaceBlocks[i] = builder.surfaceBlocks[i].copy(); + } int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; for (int i = 0; i < 16 * 16; i++) { - min = min(min, min(this.groundHeight[i], this.surfaceHeight[i])); - max = max(max, max(this.groundHeight[i], this.surfaceHeight[i])); + min = min(min, min(this.groundHeight[i], this.surfaceHeight[i]) - this.surfaceBlocks[i].lowestBlock()); + max = max(max, max(this.groundHeight[i], this.surfaceHeight[i]) + this.surfaceBlocks[i].highestBlock()); } this.surfaceMinCube = Coords.blockToCube(min) - 1; this.surfaceMaxCube = Coords.blockToCube(max) + 1; @@ -143,8 +146,8 @@ public int waterHeight(int x, int z) { return this.surfaceHeight(x, z) - 1; } - public IBlockState surfaceBlock(int x, int z) { - return this.surfaceBlocks.get(x * 16 + z); + public SurfaceColumn surfaceBlock(int x, int z) { + return this.surfaceBlocks[x * 16 + z]; } public int biome(int x, int z) { @@ -164,7 +167,7 @@ public static final class Builder extends CustomAttributeContainer implements IE private final Biome[] biomes = new Biome[16 * 16]; - protected final IBlockState[] surfaceBlocks = new IBlockState[16 * 16]; + protected final SurfaceColumn[] surfaceBlocks = new SurfaceColumn[16 * 16]; /** * @deprecated use {@link #builder()} unless you have a specific reason to invoke this constructor directly @@ -172,6 +175,9 @@ public static final class Builder extends CustomAttributeContainer implements IE @Deprecated public Builder() { super(new Object2ObjectOpenHashMap<>()); + for (int i = 0; i< this.surfaceBlocks.length; i++) { + this.surfaceBlocks[i] = new SurfaceColumn(); + } this.reset(); } @@ -207,7 +213,7 @@ public void putCustom(@NonNull String key, @NonNull Object value) { public Builder reset() { Arrays.fill(this.surfaceHeight, BLANK_HEIGHT); Arrays.fill(this.waterDepth, (byte) WATERDEPTH_DEFAULT); - Arrays.fill(this.surfaceBlocks, null); + Arrays.stream(this.surfaceBlocks).forEach(SurfaceColumn::clear); this.custom.clear(); return this; } @@ -219,4 +225,29 @@ public CachedChunkData build() { return new CachedChunkData(this, custom); } } + + /** + * This is mostly to avoid generic type shenanigans + */ + public static class SurfaceColumn extends StabbingTree { + + public SurfaceColumn copy() { + SurfaceColumn other = new SurfaceColumn(); + for (Node node: this.nodes) { + other.nodes.add(node); + } + return other; + } + + public int highestBlock() { + int size = this.nodes.size(); + return size > 0 ? this.nodes.get(size - 1).start() - 1 : 0; + } + + public int lowestBlock() { + int size = this.nodes.size(); + return size > 0 ? this.nodes.get(0).start() : 0; + } + } + } diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java index 7a99b2b7..629f4398 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/EarthGenerator.java @@ -1,8 +1,21 @@ package net.buildtheearth.terraplusplus.generator; +import static java.lang.Math.max; +import static java.lang.Math.min; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; + import io.github.opencubicchunks.cubicchunks.api.util.Coords; import io.github.opencubicchunks.cubicchunks.api.util.CubePos; import io.github.opencubicchunks.cubicchunks.api.world.ICube; @@ -29,6 +42,7 @@ import lombok.NonNull; import net.buildtheearth.terraplusplus.TerraConstants; import net.buildtheearth.terraplusplus.TerraMod; +import net.buildtheearth.terraplusplus.generator.CachedChunkData.SurfaceColumn; import net.buildtheearth.terraplusplus.generator.data.IEarthDataBaker; import net.buildtheearth.terraplusplus.generator.populate.IEarthPopulator; import net.buildtheearth.terraplusplus.projection.GeographicProjection; @@ -51,17 +65,6 @@ import net.minecraftforge.fml.common.gameevent.PlayerEvent; import net.minecraftforge.fml.common.registry.ForgeRegistries; -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -import static java.lang.Math.*; - public class EarthGenerator extends BasicCubeGenerator { public static final int WATER_DEPTH_OFFSET = 1; @@ -263,11 +266,20 @@ protected void generateCube(int cubeX, int cubeY, int cubeZ, CubePrimer primer, if (data.intersectsSurface(cubeY)) { //render surface blocks onto cube surface for (int x = 0; x < 16; x++) { for (int z = 0; z < 16; z++) { - int y = data.surfaceHeight(x, z) - Coords.cubeToMinBlock(cubeY); - IBlockState state; - if ((y & 0xF) == y //don't set surface blocks outside of this cube - && (state = data.surfaceBlock(x, z)) != null) { - primer.setBlockState(x, y, z, state); + SurfaceColumn column = data.surfaceBlock(x, z); + if (data != null) { + int cx = x; + int surfaceY = data.surfaceHeight(x, z) - Coords.cubeToMinBlock(cubeY); + int cz = z; + column.forEachSection(-surfaceY, -surfaceY + 16, (startNode, endNode) -> { + IBlockState state = startNode.value(); + if (state == null) return; + int start = startNode.start(); + int end = endNode.start(); + for (int y = start; y < end; y++) { + primer.setBlockState(cx, y + surfaceY, cz, state); + } + }); } } } @@ -371,7 +383,7 @@ public void populate(ICube cube) { Random random = Coords.coordsSeedRandom(this.world.getSeed(), cube.getX(), cube.getY(), cube.getZ()); Biome biome = cube.getBiome(Coords.getCubeCenter(cube)); - this.cubiccfg.expectedBaseHeight = (float) datas[0].groundHeight(15, 15); + this.cubiccfg.expectedBaseHeight = datas[0].groundHeight(15, 15); for (IEarthPopulator populator : this.populators) { populator.populate(this.world, random, cube.getCoords(), biome, datas); diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java b/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java index 2462ebfa..b99479ed 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java +++ b/src/main/java/net/buildtheearth/terraplusplus/generator/TerrainPreview.java @@ -1,22 +1,11 @@ package net.buildtheearth.terraplusplus.generator; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; -import lombok.NonNull; -import net.buildtheearth.terraplusplus.generator.data.TreeCoverBaker; -import net.buildtheearth.terraplusplus.projection.GeographicProjection; -import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; -import net.buildtheearth.terraplusplus.util.EmptyWorld; -import net.buildtheearth.terraplusplus.util.TilePos; -import net.buildtheearth.terraplusplus.util.http.Http; -import net.minecraft.block.state.IBlockState; -import net.minecraft.init.Bootstrap; -import net.minecraft.util.math.BlockPos; -import net.minecraft.util.math.ChunkPos; -import net.minecraftforge.client.model.pipeline.LightUtil; +import static java.lang.Math.max; +import static net.daporkchop.lib.common.math.PMath.clamp; +import static net.daporkchop.lib.common.math.PMath.floorI; +import static net.daporkchop.lib.common.math.PMath.lerpI; +import static net.daporkchop.lib.common.util.PorkUtil.uncheckedCast; -import javax.swing.JFrame; import java.awt.Canvas; import java.awt.Graphics; import java.awt.Graphics2D; @@ -31,9 +20,25 @@ import java.util.Collections; import java.util.concurrent.CompletableFuture; -import static java.lang.Math.*; -import static net.daporkchop.lib.common.math.PMath.*; -import static net.daporkchop.lib.common.util.PorkUtil.*; +import javax.swing.JFrame; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import lombok.NonNull; +import net.buildtheearth.terraplusplus.generator.CachedChunkData.SurfaceColumn; +import net.buildtheearth.terraplusplus.generator.data.TreeCoverBaker; +import net.buildtheearth.terraplusplus.projection.GeographicProjection; +import net.buildtheearth.terraplusplus.projection.OutOfProjectionBoundsException; +import net.buildtheearth.terraplusplus.util.EmptyWorld; +import net.buildtheearth.terraplusplus.util.TilePos; +import net.buildtheearth.terraplusplus.util.http.Http; +import net.minecraft.block.state.IBlockState; +import net.minecraft.init.Bootstrap; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.ChunkPos; +import net.minecraftforge.client.model.pipeline.LightUtil; /** * @author DaPorkchop_ @@ -283,7 +288,8 @@ protected CompletableFuture baseZoomTile(int x, int z) { for (int cz = 0; cz < 16; cz++) { int c; - IBlockState state = data.surfaceBlock(cx, cz); + SurfaceColumn column = data.surfaceBlock(cx, cz); + IBlockState state = column.get(column.highestBlock()); if (state != null) { c = state.getMapColor(EmptyWorld.INSTANCE, BlockPos.ORIGIN).colorValue; } else { diff --git a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/MultiBlockSurfacePattern.java b/src/main/java/net/buildtheearth/terraplusplus/generator/surface/MultiBlockSurfacePattern.java deleted file mode 100644 index b8f3034b..00000000 --- a/src/main/java/net/buildtheearth/terraplusplus/generator/surface/MultiBlockSurfacePattern.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.buildtheearth.terraplusplus.generator.surface; - -import java.util.Random; - -import net.minecraft.block.state.IBlockState; - -/** - * A basic surface pattern with blocks on top of each others - * - * @author SmylerMC - */ -public class MultiBlockSurfacePattern implements ISurfacePattern { - - private final BakedSurfacePattern baked; - - public MultiBlockSurfacePattern(int offset, IBlockState... states) { - this.baked = new BakedSurfacePattern(states, offset); - } - - @Override - public BakedSurfacePattern bake(int x, int surfaceY, int z, Random random) { - return this.baked; - } - -} diff --git a/src/main/java/net/buildtheearth/terraplusplus/util/StabbingTree.java b/src/main/java/net/buildtheearth/terraplusplus/util/StabbingTree.java index d37a836b..f9d0c548 100644 --- a/src/main/java/net/buildtheearth/terraplusplus/util/StabbingTree.java +++ b/src/main/java/net/buildtheearth/terraplusplus/util/StabbingTree.java @@ -22,8 +22,8 @@ */ public class StabbingTree, V> { - private List nodes = new ArrayList<>(); - private final V defaultValue; + protected List nodes = new ArrayList<>(); + @Getter private final V defaultValue; /** * Creates a new {@link StabbingTree}.