239 lines
10 KiB
Java
239 lines
10 KiB
Java
/*
|
|
*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2020 TerraForged
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
package com.terraforged.mod.feature;
|
|
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.terraforged.n2d.util.NoiseUtil;
|
|
import it.unimi.dsi.fastutil.longs.LongIterator;
|
|
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
|
import it.unimi.dsi.fastutil.objects.ObjectList;
|
|
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
|
|
import net.minecraft.block.Blocks;
|
|
import net.minecraft.util.math.BlockPos;
|
|
import net.minecraft.util.math.ChunkPos;
|
|
import net.minecraft.util.math.MutableBoundingBox;
|
|
import net.minecraft.world.IWorld;
|
|
import net.minecraft.world.chunk.IChunk;
|
|
import net.minecraft.world.gen.Heightmap;
|
|
import net.minecraft.world.gen.feature.jigsaw.JigsawPattern;
|
|
import net.minecraft.world.gen.feature.structure.AbstractVillagePiece;
|
|
import net.minecraft.world.gen.feature.structure.IStructurePieceType;
|
|
import net.minecraft.world.gen.feature.structure.Structure;
|
|
import net.minecraft.world.gen.feature.structure.StructurePiece;
|
|
import net.minecraft.world.gen.feature.structure.StructureStart;
|
|
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
public class TerrainHelper {
|
|
|
|
private static final Set<IStructurePieceType> SURFACE_STRUCTURES = ImmutableSet.of(
|
|
IStructurePieceType.PCP, // outpost
|
|
IStructurePieceType.NVI, // village
|
|
IStructurePieceType.TEJP, // jungle temple
|
|
IStructurePieceType.IGLU, // igloo
|
|
IStructurePieceType.TEDP // desert pyramid
|
|
);
|
|
|
|
private final float radius;
|
|
private final float overhang;
|
|
private final float overhang2;
|
|
|
|
// base - the size of the base built up around a piece as a percentage of its bounding box size
|
|
// overhang - the amount of overhead overhang to be cut out
|
|
public TerrainHelper(float base, float cutout) {
|
|
this.radius = base;
|
|
this.overhang = cutout;
|
|
this.overhang2 = cutout * cutout;
|
|
}
|
|
|
|
public void flatten(IWorld world, IChunk chunk) {
|
|
ObjectList<StructurePiece> pieces = new ObjectArrayList<>(10);
|
|
collectPieces(world, chunk, pieces);
|
|
buildBases(chunk, pieces);
|
|
}
|
|
|
|
// see NoiseChunkGenerator
|
|
private void collectPieces(IWorld world, IChunk chunk, ObjectList<StructurePiece> pieces) {
|
|
ChunkPos pos = chunk.getPos();
|
|
for (Structure<?> structure : Structure.ILLAGER_STRUCTURES) {
|
|
String name = structure.getStructureName();
|
|
LongIterator structureIds = chunk.getStructureReferences(name).iterator();
|
|
|
|
while (structureIds.hasNext()) {
|
|
long id = structureIds.nextLong();
|
|
ChunkPos structurePos = new ChunkPos(id);
|
|
IChunk neighbourChunk = world.getChunk(structurePos.asBlockPos());
|
|
StructureStart structurestart = neighbourChunk.getStructureStart(name);
|
|
if (structurestart != null && structurestart.isValid()) {
|
|
for (StructurePiece structurepiece : structurestart.getComponents()) {
|
|
// collect if piece is within radius of the chunk
|
|
if (structurepiece.func_214810_a(pos, 12)) {
|
|
collectPiece(structurepiece, pieces);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// lowers or raises the terrain matcher the base height of each structure piece
|
|
private void buildBases(IChunk chunk, ObjectList<StructurePiece> pieces) {
|
|
int chunkStartX = chunk.getPos().getXStart();
|
|
int chunkStartZ = chunk.getPos().getZStart();
|
|
BlockPos.Mutable pos = new BlockPos.Mutable();
|
|
ObjectListIterator<StructurePiece> iterator = pieces.iterator();
|
|
MutableBoundingBox chunkBounds = new MutableBoundingBox(chunkStartX, chunkStartZ, chunkStartX + 15, chunkStartZ + 15);
|
|
|
|
for (int dz = 0; dz < 16; dz++) {
|
|
for (int dx = 0; dx < 16; dx++) {
|
|
int x = chunkStartX + dx;
|
|
int z = chunkStartZ + dz;
|
|
int surface = chunk.getTopBlockY(Heightmap.Type.OCEAN_FLOOR_WG, dx, dz);
|
|
float y = surface;
|
|
|
|
int highestOffset = 0;
|
|
StructurePiece highest = null;
|
|
while (iterator.hasNext()) {
|
|
StructurePiece piece = iterator.next();
|
|
MutableBoundingBox pieceBounds = piece.getBoundingBox();
|
|
int length = Math.min(pieceBounds.maxX - pieceBounds.minX, pieceBounds.maxZ - pieceBounds.minZ);
|
|
int borderRadius = Math.min(5, Math.max(10, NoiseUtil.round(length * radius)));
|
|
MutableBoundingBox expanded = expand(pieceBounds, borderRadius);
|
|
if (!expanded.intersectsWith(chunkBounds)) {
|
|
continue;
|
|
}
|
|
|
|
int offset = getGroundLevelDelta(piece);
|
|
int level = pieceBounds.minY + offset;
|
|
if (level > y) {
|
|
y = raise(pieceBounds, pos.setPos(x, surface, z), level, y, borderRadius);
|
|
}
|
|
|
|
if (x > pieceBounds.minX && x < pieceBounds.maxX && z > pieceBounds.minZ && z < pieceBounds.maxZ) {
|
|
if (highest == null || pieceBounds.minY > highest.getBoundingBox().minY) {
|
|
highest = piece;
|
|
highestOffset = offset;
|
|
}
|
|
}
|
|
}
|
|
|
|
// reset iterator for next pass
|
|
iterator.back(pieces.size());
|
|
|
|
if (y > surface) {
|
|
int delta = (int) y - surface;
|
|
for (int dy = 0; dy < delta; dy++) {
|
|
pos.setPos(dx, surface + dy, dz);
|
|
chunk.setBlockState(pos, Blocks.STONE.getDefaultState(), false);
|
|
}
|
|
}
|
|
|
|
if (highest != null) {
|
|
MutableBoundingBox bounds = highest.getBoundingBox();
|
|
int minY = bounds.minY + highestOffset;
|
|
int maxY = minY + bounds.getYSize();
|
|
|
|
if (maxY <= surface) {
|
|
// gets weaker the further from the center of the piece
|
|
float dist = getCenterDistance2(x, z, bounds);
|
|
float distAlpha = 1F - NoiseUtil.clamp(dist / overhang2, 0, 1);
|
|
|
|
// gets weaker the more material is overhead creating the inverse cutout (ie overhang)
|
|
float depth = surface - maxY;
|
|
float depthAlpha = 1F - NoiseUtil.clamp(depth / overhang, 0, 1);
|
|
|
|
maxY += NoiseUtil.round(depthAlpha * distAlpha * overhang);
|
|
}
|
|
|
|
for (int dy = minY; dy <= maxY; dy++) {
|
|
pos.setPos(dx, dy, dz);
|
|
chunk.setBlockState(pos, Blocks.AIR.getDefaultState(), false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private float raise(MutableBoundingBox bounds, BlockPos.Mutable pos, float level, float surface, int borderRadius) {
|
|
float radius2 = Math.max(1, borderRadius * borderRadius);
|
|
float alpha = 1 - getDistAlpha(pos.getX(), pos.getZ(), bounds, radius2);
|
|
alpha = (float) Math.pow(alpha, 2F - alpha);
|
|
return NoiseUtil.lerp(surface, level, alpha);
|
|
}
|
|
|
|
private float getCenterDistance2(int x, int z, MutableBoundingBox bounds) {
|
|
float cx = bounds.minX + (bounds.getXSize() / 2F);
|
|
float cz = bounds.minZ + (bounds.getZSize() / 2F);
|
|
float dx = cx - x;
|
|
float dz = cz - z;
|
|
return dx * dx + dz * dz;
|
|
}
|
|
|
|
private static void collectPiece(StructurePiece structurepiece, List<StructurePiece> list) {
|
|
if (structurepiece instanceof AbstractVillagePiece) {
|
|
AbstractVillagePiece piece = (AbstractVillagePiece) structurepiece;
|
|
JigsawPattern.PlacementBehaviour placement = piece.getJigsawPiece().getPlacementBehaviour();
|
|
if (placement == JigsawPattern.PlacementBehaviour.RIGID) {
|
|
list.add(piece);
|
|
}
|
|
} else if (SURFACE_STRUCTURES.contains(structurepiece.getStructurePieceType())) {
|
|
list.add(structurepiece);
|
|
}
|
|
}
|
|
|
|
private static int getGroundLevelDelta(StructurePiece piece) {
|
|
if (piece instanceof AbstractVillagePiece) {
|
|
return ((AbstractVillagePiece) piece).getGroundLevelDelta();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private static MutableBoundingBox expand(MutableBoundingBox box, int radius) {
|
|
return new MutableBoundingBox(
|
|
box.minX - radius,
|
|
box.minY,
|
|
box.minZ - radius,
|
|
box.maxX + radius,
|
|
box.maxY,
|
|
box.maxZ + radius
|
|
);
|
|
}
|
|
|
|
private static float getDistAlpha(int x, int z, MutableBoundingBox box, float radius2) {
|
|
int dx = x < box.minX ? box.minX - x : x > box.maxX ? x - box.maxX : 0;
|
|
int dz = z < box.minZ ? box.minZ - z : z > box.maxZ ? z - box.maxZ : 0;
|
|
int d2 = dx * dx + dz * dz;
|
|
if (d2 == 0) {
|
|
return 0;
|
|
}
|
|
if (d2 > radius2) {
|
|
return 1F;
|
|
}
|
|
return d2 / radius2;
|
|
}
|
|
}
|