diff --git a/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java b/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java index cb8abb78f..c0137843f 100644 --- a/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java +++ b/src/main/java/com/seibel/lod/builders/LodNodeBufferBuilder.java @@ -1,5 +1,6 @@ package com.seibel.lod.builders; +import com.seibel.lod.builders.worldGeneration.LodChunkGenWorker; import com.seibel.lod.handlers.LodConfig; import com.seibel.lod.objects.LodChunk; import com.seibel.lod.objects.LodDimension; @@ -134,16 +135,18 @@ public class LodNodeBufferBuilder buildableFarBuffer.begin(GL11.GL_QUADS, LodRenderer.LOD_VERTEX_FORMAT); - List lodList = new ArrayList<>(); - lodList.addAll(lodDim.getNodeToRender((int) playerX,(int)playerZ,(byte) 9, 100000,8000)); - lodList.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 8, 8000,4000)); - lodList.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 7, 4000,2000)); - lodList.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 6, 2000,1000)); - lodList.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 5, 1000,500)); - lodList.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 4, 500,250)); - lodList.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 3, 250,0)); - - for(LodNodeData data : lodList){ + List lodToRender = new ArrayList<>(); + lodToRender.addAll(lodDim.getNodeToRender((int) playerX,(int)playerZ,(byte) 0, 100000,0)); + /* + lodToRender.addAll(lodDim.getNodeToRender((int) playerX,(int)playerZ,(byte) 9, 100000,8000)); + lodToRender.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 8, 8000,4000)); + lodToRender.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 7, 4000,2000)); + lodToRender.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 6, 2000,1000)); + lodToRender.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 5, 1000,500)); + lodToRender.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 4, 500,250)); + lodToRender.addAll(lodDim.getNodeToRender((int)playerX,(int)playerZ,(byte) 3, 250,0)); +*/ + for(LodNodeData data : lodToRender){ BufferBuilder currentBuffer = null; /* if (isCoordinateInNearFogArea(i, j, numbChunksWide / 2)) @@ -156,6 +159,8 @@ public class LodNodeBufferBuilder // get the desired LodTemplate and // add this LOD to the buffer } + + // x axis // finish the buffer building buildableNearBuffer.end(); diff --git a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/AbstractLodNodeTemplate.java b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/AbstractLodNodeTemplate.java new file mode 100644 index 000000000..1c7645b4b --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/AbstractLodNodeTemplate.java @@ -0,0 +1,38 @@ +package com.seibel.lod.builders.lodNodeTemplates; + +import com.seibel.lod.enums.LodDetail; +import com.seibel.lod.objects.LodChunk; +import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.quadTree.LodNodeData; +import com.seibel.lod.objects.quadTree.LodQuadTreeDimension; +import net.minecraft.client.renderer.BufferBuilder; + +/** + * This is the abstract class used to create different + * BufferBuilders. + * + * @author James Seibel + * @version 06-16-2021 + */ +public abstract class AbstractLodNodeTemplate +{ + public abstract void addLodToBuffer(BufferBuilder buffer, + LodQuadTreeDimension lodDim, LodNodeData lod, + double xOffset, double yOffset, double zOffset, + boolean debugging); + + /** add the given position and color to the buffer */ + protected void addPosAndColor(BufferBuilder buffer, + double x, double y, double z, + int red, int green, int blue, int alpha) + { + buffer.vertex(x, y, z).color(red, green, blue, alpha).endVertex(); + } + + /** Returns in bytes how much buffer memory is required + * for one LOD object */ + public abstract int getBufferMemoryForSingleLod(); + + + +} diff --git a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/CubicLodNodeTemplate.java b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/CubicLodNodeTemplate.java new file mode 100644 index 000000000..e9f820464 --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/CubicLodNodeTemplate.java @@ -0,0 +1,155 @@ +package com.seibel.lod.builders.lodNodeTemplates; + +import com.seibel.lod.enums.ColorDirection; +import com.seibel.lod.enums.LodDetail; +import com.seibel.lod.handlers.LodConfig; +import com.seibel.lod.objects.LodChunk; +import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.quadTree.LodNodeData; +import com.seibel.lod.objects.quadTree.LodQuadTreeDimension; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.util.math.AxisAlignedBB; + +import java.awt.*; + +/** + * Builds LODs as rectangular prisms. + * + * @author James Seibel + * @version 06-16-2021 + */ +public class CubicLodNodeTemplate extends AbstractLodNodeTemplate { + + public CubicLodNodeTemplate() { + + } + + + @Override + public void addLodToBuffer(BufferBuilder buffer, + LodQuadTreeDimension lodDim, LodNodeData lod, + double xOffset, double yOffset, double zOffset, + boolean debugging) { + AxisAlignedBB bbox; + + // Add this LOD to the BufferBuilder + // using the quality setting set by the config + LodDetail detail = LodConfig.CLIENT.lodDetail.get(); + + int halfWidth = lod.width / 2; + int startX = lod.startX; + int startZ = lod.startZ; + int endX = lod.startX + lod.width; + int endZ = lod.startZ + lod.width; + + // returns null if the lod is empty at the given location + bbox = generateBoundingBox( + lod.height, + lod.height, + detail.dataPointWidth, + xOffset - (halfWidth / 2) + startX, + yOffset, + zOffset - (halfWidth / 2) + startZ); + + if (bbox != null) { + addBoundingBoxToBuffer(buffer, bbox, lod.color); + } + } + + + private AxisAlignedBB generateBoundingBox(int height, int depth, int width, double xOffset, double yOffset, double zOffset) { + // don't add an LOD if it is empty + if (height == -1 && depth == -1) + return null; + + if (depth == height) { + // if the top and bottom points are at the same height + // render this LOD as 1 block thick + height++; + } + + return new AxisAlignedBB(0, depth, 0, width, height, width).move(xOffset, yOffset, zOffset); + } + + + private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, Color c) { + // top (facing up) + addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + // bottom (facing down) + addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + + // south (facing -Z) + addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + // north (facing +Z) + addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + + // west (facing -X) + addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + // east (facing +X) + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha()); + } + + + @SuppressWarnings("unused") + private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, Color[] c) { + // top (facing up) + addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.TOP.value].getRed(), c[ColorDirection.TOP.value].getGreen(), c[ColorDirection.TOP.value].getBlue(), c[ColorDirection.TOP.value].getAlpha()); + // bottom (facing down) + addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha()); + addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha()); + addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.BOTTOM.value].getRed(), c[ColorDirection.BOTTOM.value].getGreen(), c[ColorDirection.BOTTOM.value].getBlue(), c[ColorDirection.BOTTOM.value].getAlpha()); + + // south (facing -Z) + addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha()); + addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.SOUTH.value].getRed(), c[ColorDirection.SOUTH.value].getGreen(), c[ColorDirection.SOUTH.value].getBlue(), c[ColorDirection.SOUTH.value].getAlpha()); + // north (facing +Z) + addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.NORTH.value].getRed(), c[ColorDirection.NORTH.value].getGreen(), c[ColorDirection.NORTH.value].getBlue(), c[ColorDirection.NORTH.value].getAlpha()); + + // west (facing -X) + addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha()); + addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha()); + addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, c[ColorDirection.WEST.value].getRed(), c[ColorDirection.WEST.value].getGreen(), c[ColorDirection.WEST.value].getBlue(), c[ColorDirection.WEST.value].getAlpha()); + // east (facing +X) + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha()); + addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, c[ColorDirection.EAST.value].getRed(), c[ColorDirection.EAST.value].getGreen(), c[ColorDirection.EAST.value].getBlue(), c[ColorDirection.EAST.value].getAlpha()); + } + + + @Override + public int getBufferMemoryForSingleLod() { + // (sidesOnACube * pointsInASquare * (positionPoints + colorPoints))) * howManyPointsPerLodChunk + return (6 * 4 * (3 + 4)); + } + + +} diff --git a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/DynamicLodNodeTemplate.java b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/DynamicLodNodeTemplate.java new file mode 100644 index 000000000..a5e1f078f --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/DynamicLodNodeTemplate.java @@ -0,0 +1,35 @@ +package com.seibel.lod.builders.lodNodeTemplates; + +import com.seibel.lod.enums.LodDetail; +import com.seibel.lod.objects.LodChunk; +import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.quadTree.LodNodeData; +import com.seibel.lod.objects.quadTree.LodQuadTreeDimension; +import net.minecraft.client.renderer.BufferBuilder; + +/** + * TODO DynamicLodTemplate + * Chunks smoothly transition between + * each other, unless a neighboring chunk + * is at a significantly different height. + * + * @author James Seibel + * @version 06-16-2021 + */ +public class DynamicLodNodeTemplate extends AbstractLodNodeTemplate +{ + @Override + public void addLodToBuffer(BufferBuilder buffer, + LodQuadTreeDimension lodDim, LodNodeData lod, + double xOffset, double yOffset, double zOffset, + boolean debugging) + { + System.err.println("DynamicLodTemplate not implemented!"); + } + + @Override + public int getBufferMemoryForSingleLod() { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/src/main/java/com/seibel/lod/builders/lodNodeTemplates/TriangularLodNodeTemplate.java b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/TriangularLodNodeTemplate.java new file mode 100644 index 000000000..2a4d5612d --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/lodNodeTemplates/TriangularLodNodeTemplate.java @@ -0,0 +1,33 @@ +package com.seibel.lod.builders.lodNodeTemplates; + +import com.seibel.lod.enums.LodDetail; +import com.seibel.lod.objects.LodChunk; +import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.quadTree.LodNodeData; +import com.seibel.lod.objects.quadTree.LodQuadTreeDimension; +import net.minecraft.client.renderer.BufferBuilder; + +/** + * TODO #21 TriangularLodTemplate + * Builds each LOD chunk as a singular rectangular prism. + * + * @author James Seibel + * @version 06-16-2021 + */ +public class TriangularLodNodeTemplate extends AbstractLodNodeTemplate +{ + @Override + public void addLodToBuffer(BufferBuilder buffer, + LodQuadTreeDimension lodDim, LodNodeData lod, + double xOffset, double yOffset, double zOffset, + boolean debugging) + { + System.err.println("DynamicLodTemplate not implemented!"); + } + + @Override + public int getBufferMemoryForSingleLod() { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/src/main/java/com/seibel/lod/builders/worldGeneration/LodNodeGenWorker.java b/src/main/java/com/seibel/lod/builders/worldGeneration/LodNodeGenWorker.java new file mode 100644 index 000000000..f91817e71 --- /dev/null +++ b/src/main/java/com/seibel/lod/builders/worldGeneration/LodNodeGenWorker.java @@ -0,0 +1,574 @@ +package com.seibel.lod.builders.worldGeneration; + +import com.seibel.lod.builders.*; +import com.seibel.lod.enums.DistanceGenerationMode; +import com.seibel.lod.handlers.LodConfig; +import com.seibel.lod.objects.LodChunk; +import com.seibel.lod.objects.LodDimension; +import com.seibel.lod.objects.LodRegion; +import com.seibel.lod.objects.quadTree.LodNodeData; +import com.seibel.lod.objects.quadTree.LodQuadTreeDimension; +import com.seibel.lod.proxy.ClientProxy; +import com.seibel.lod.render.LodNodeRenderer; +import com.seibel.lod.render.LodRenderer; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.util.WeightedList.Entry; +import net.minecraft.util.math.ChunkPos; +import net.minecraft.util.palette.UpgradeData; +import net.minecraft.world.biome.Biome; +import net.minecraft.world.biome.BiomeContainer; +import net.minecraft.world.chunk.ChunkPrimer; +import net.minecraft.world.chunk.ChunkStatus; +import net.minecraft.world.chunk.IChunk; +import net.minecraft.world.gen.ChunkGenerator; +import net.minecraft.world.gen.Heightmap; +import net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider; +import net.minecraft.world.gen.feature.*; +import net.minecraft.world.gen.placement.ConfiguredPlacement; +import net.minecraft.world.gen.placement.DecoratedPlacementConfig; +import net.minecraft.world.gen.placement.IPlacementConfig; +import net.minecraft.world.gen.placement.NoiseDependant; +import net.minecraft.world.server.ServerChunkProvider; +import net.minecraft.world.server.ServerWorld; +import net.minecraft.world.server.ServerWorldLightManager; +import net.minecraftforge.common.WorldWorkerManager.IWorker; + +import java.util.ConcurrentModificationException; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Supplier; + +/** + * This is used to generate a LodChunk at a given ChunkPos. + * + * @author James Seibel + * @version 7-4-2021 + */ +public class LodNodeGenWorker implements IWorker +{ + public static final ExecutorService genThreads = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + + private boolean threadStarted = false; + private LodChunkGenThread thread; + + /** If a configured feature fails for whatever reason, + * add it to this list, this is to hopefully remove any + * features that could cause issues down the line. */ + private static ConcurrentHashMap> configuredFeaturesToAvoid = new ConcurrentHashMap<>(); + + + + public LodNodeGenWorker(ChunkPos newPos, LodNodeRenderer newLodRenderer, + LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder, + LodQuadTreeDimension newLodDimension, ServerWorld newServerWorld, + BiomeContainer newBiomeContainer) + { + if (newServerWorld == null) + throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ServerWorld"); + + thread = new LodChunkGenThread(newPos, newLodRenderer, + newLodBuilder, newLodBufferBuilder, + newLodDimension, newServerWorld); + } + + @Override + public boolean doWork() + { + if (!threadStarted) + { + // make sure we don't generate this chunk again + thread.lodDim.addNode(new LodNodeData(LodNodeData.CHUNK_LEVEL,thread.pos.x, thread.pos.z)); + + thread.lodBufferBuilder.numberOfChunksWaitingToGenerate--; + + if (LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.SERVER) + { + // if we are using SERVER generation that has to be done + // synchronously to prevent crashing and harmful + // interactions with the normal world generator + thread.run(); + } + else + { + // Every other method can + // be done asynchronously + genThreads.execute(thread); + } + + threadStarted = true; + + // useful for debugging +// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods()); + } + + return false; + } + + @Override + public boolean hasWork() + { + return !threadStarted; + } + + + + + private class LodChunkGenThread implements Runnable + { + public final ServerWorld serverWorld; + public final LodQuadTreeDimension lodDim; + public final LodNodeBuilder lodChunkBuilder; + public final LodNodeRenderer lodRenderer; + private LodNodeBufferBuilder lodBufferBuilder; + + private ChunkPos pos; + + public LodChunkGenThread(ChunkPos newPos, LodNodeRenderer newLodRenderer, + LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder, + LodQuadTreeDimension newLodDimension, ServerWorld newServerWorld) + { + pos = newPos; + lodRenderer = newLodRenderer; + lodChunkBuilder = newLodBuilder; + lodBufferBuilder = newLodBufferBuilder; + lodDim = newLodDimension; + serverWorld = newServerWorld; + } + + @Override + public void run() + { + // only generate LodChunks if they can + // be added to the current LodDimension + if (lodDim.regionIsInRange(pos.x / LodRegion.SIZE, pos.z / LodRegion.SIZE)) + { +// long startTime = System.currentTimeMillis(); + + switch(LodConfig.CLIENT.distanceGenerationMode.get()) + { + case BIOME_ONLY: + case BIOME_ONLY_SIMULATE_HEIGHT: + // fastest + generateUsingBiomesOnly(); + break; + case SURFACE: + // faster + generateUsingSurface(); + break; + case FEATURES: + // fast + generateUsingFeatures(); + break; + case SERVER: + // very slow + generateWithServer(); + break; + } + + lodRenderer.regenerateLODsNextFrame(); + + +// if (lodDim.getLodFromCoordinates(pos.x, pos.z) != null) +// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!"); +// else +// ClientProxy.LOGGER.info(pos.x + " " + pos.z); + +// long endTime = System.currentTimeMillis(); +// System.out.println(endTime - startTime); + + }// if in range + + }// run + + + + /** + * takes about 2-5 ms + */ + private void generateUsingBiomesOnly() + { + List chunkList = new LinkedList<>(); + ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY); + chunkList.add(chunk); + + ServerChunkProvider chunkSource = serverWorld.getChunkSource(); + ChunkGenerator chunkGen = chunkSource.generator; + + + // generate the terrain (this is thread safe) + ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + // override the chunk status so we can run the next generator stage + chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES); + ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES); + + + // generate fake height data for this LOD + int seaLevel = serverWorld.getSeaLevel(); + + boolean simulateHeight = LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT; + boolean inTheEnd = false; + + // add fake heightmap data so our LODs aren't at height 0 + Heightmap heightmap = new Heightmap(chunk, LodChunk.DEFAULT_HEIGHTMAP); + for(int x = 0; x < LodChunk.WIDTH && !inTheEnd; x++) + { + for(int z = 0; z < LodChunk.WIDTH && !inTheEnd; z++) + { + if (simulateHeight) + { + // TODO use the biomes around each block to smooth out the transition + + // these heights are of course aren't super accurate, + // they are just to simulate height data where there isn't any + switch(chunk.getBiomes().getNoiseBiome(x, seaLevel, z).getBiomeCategory()) + { + case NETHER: + heightmap.setHeight(x, z, serverWorld.getHeight() / 2); + break; + + case EXTREME_HILLS: + heightmap.setHeight(x, z, seaLevel + 30); + break; + case MESA: + heightmap.setHeight(x, z, seaLevel + 20); + break; + case JUNGLE: + heightmap.setHeight(x, z, seaLevel + 20); + break; + case BEACH: + heightmap.setHeight(x, z, seaLevel + 5); + break; + case NONE: + heightmap.setHeight(x, z, 0); + break; + + case OCEAN: + case RIVER: + heightmap.setHeight(x, z, seaLevel); + break; + + case THEEND: + inTheEnd = true; + break; + + // DESERT + // FOREST + // ICY + // MUSHROOM + // SAVANNA + // SWAMP + // TAIGA + // PLAINS + default: + heightmap.setHeight(x, z, seaLevel + 10); + break; + }// heightmap switch + } + else + { + // we aren't simulating height + // always use sea level + heightmap.setHeight(x, z, seaLevel); + } + }// z + }// x + + chunk.setHeightmap(LodChunk.DEFAULT_HEIGHTMAP, heightmap.getRawData()); + + + LodNodeData lod; + if (!inTheEnd) + { + lod = lodChunkBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false)); + } + else + { + // if we are in the end, don't generate any chunks. + // Since we don't know where the islands are, everything + // generates the same and it looks really bad. + lod = new LodNodeData(LodNodeData.CHUNK_LEVEL,chunk.getPos().x, chunk.getPos().z); + } + lodDim.addNode(lod); + } + + + /** + * takes about 10 - 20 ms + */ + private void generateUsingSurface() + { + List chunkList = new LinkedList<>(); + ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY); + chunkList.add(chunk); + LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk); + + ServerChunkProvider chunkSource = serverWorld.getChunkSource(); + ChunkGenerator chunkGen = chunkSource.generator; + + + // generate the terrain (this is thread safe) + ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + // override the chunk status so we can run the next generator stage + chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES); + ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + + // this feature has proved to be thread safe + // so we will add it + IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.CODEC); + snowFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition(), null); + + LodNodeData lod = lodChunkBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false)); + lodDim.addNode(lod); + } + + + /** + * takes about 15 - 20 ms + * + * Causes concurrentModification Exceptions, + * which could cause instability or world generation bugs + */ + private void generateUsingFeatures() + { + List chunkList = new LinkedList<>(); + ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY); + chunkList.add(chunk); + LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk); + + ServerChunkProvider chunkSource = serverWorld.getChunkSource(); + ChunkGenerator chunkGen = chunkSource.generator; + + + // generate the terrain (this is thread safe) + ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + // override the chunk status so we can run the next generator stage + chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES); + ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList); + + + // get all the biomes in the chunk + HashSet biomes = new HashSet<>(); + for (int x = 0; x < LodChunk.WIDTH; x++) + { + for (int z = 0; z < LodChunk.WIDTH; z++) + { + Biome biome = chunk.getBiomes().getNoiseBiome(x, serverWorld.getSeaLevel(), z); + + // Issue #35 + // For some reason Jungle biomes cause incredible lag + // the features here must be interacting with each other + // in unpredictable ways (specifically tree feature generation). + // When generating Features my CPU usage generally hovers around 30 - 40% + // when generating Jungles it spikes to 100%. + if (biome.getBiomeCategory() != Biome.Category.JUNGLE) + { + // should probably use the heightmap here instead of seaLevel, + // but this seems to get the job done well enough + biomes.add(biome); + } + } + } + + + // generate all the features related to this chunk. + // this may or may not be thread safe + for (Biome biome : biomes) + { + List>>> featuresForState = biome.generationSettings.features(); + + for(int featureStateToGenerate = 0; featureStateToGenerate < featuresForState.size(); featureStateToGenerate++) + { + for(Supplier> featureSupplier : featuresForState.get(featureStateToGenerate)) + { + ConfiguredFeature configuredFeature = featureSupplier.get(); + + if (configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode())) + continue; + + + try + { + configuredFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition()); + } + catch(ConcurrentModificationException e) + { + // This will happen. I'm not sure what to do about it + // except pray that it doesn't effect the normal world generation + // in any harmful way + + // Issue #35 + // I tried cloning the config for each feature, but that + // path was blocked since I can't clone lambda methods. + // I tried using a deep cloning library and discovered + // the problem there. + // ( https://github.com/kostaskougios/cloning ) + + configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature); +// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount()); + } + catch(UnsupportedOperationException e) + { + // This will happen when the LodServerWorld + // isn't able to return something that a feature + // generator needs + + configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature); +// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount()); + } + catch(Exception e) + { + // I'm not sure what happened, print to the log + + System.out.println(); + System.out.println(); + e.printStackTrace(); + System.out.println(); + //ClientProxy.LOGGER.error("error class: \"" + configuredfeature.config.getClass() + "\""); + System.out.println(); + + configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature); +// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount()); + } + } + } + } + + // generate a Lod like normal + + LodNodeData lod = lodChunkBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false)); + lodDim.addNode(lod); + } + + + /** + * on pre generated chunks 0 - 1 ms + * on un generated chunks 0 - 50 ms + * with the median seeming to hover around 15 - 30 ms + * and outliers in the 100 - 200 ms range + * + * Note this should not be multithreaded and does cause server/simulation lag + * (Higher lag for generating than loading) + */ + private void generateWithServer() { + //lodChunkBuilder.generateLodNodeAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld); + } + + + + + + + //================// + // Unused methods // + //================// + + // Sadly I wasn't able to get these to work, + // they are here for documentation purposes + + @SuppressWarnings({ "rawtypes", "unchecked", "unused" }) + private DecoratedFeatureConfig cloneDecoratedFeatureConfig(DecoratedFeatureConfig config) + { + IPlacementConfig placementConfig = null; + + Class oldConfigClass = config.decorator.config().getClass(); + + if (oldConfigClass == FeatureSpreadConfig.class) + { + FeatureSpreadConfig oldPlacementConfig = (FeatureSpreadConfig) config.decorator.config(); + FeatureSpread oldSpread = oldPlacementConfig.count(); + + placementConfig = new FeatureSpreadConfig(oldSpread); + } + else if(oldConfigClass == DecoratedPlacementConfig.class) + { + DecoratedPlacementConfig oldPlacementConfig = (DecoratedPlacementConfig) config.decorator.config(); + placementConfig = new DecoratedPlacementConfig(oldPlacementConfig.inner(), oldPlacementConfig.outer()); + } + else if(oldConfigClass == NoiseDependant.class) + { + NoiseDependant oldPlacementConfig = (NoiseDependant) config.decorator.config(); + placementConfig = new NoiseDependant(oldPlacementConfig.noiseLevel, oldPlacementConfig.belowNoise, oldPlacementConfig.aboveNoise); + } + else + { +// ClientProxy.LOGGER.debug("unkown decorated placement config: \"" + config.decorator.config().getClass() + "\""); + return config; + } + + + ConfiguredPlacement newPlacement = new ConfiguredPlacement(config.decorator.decorator, placementConfig); + return new DecoratedFeatureConfig(config.feature, newPlacement); + } + + + @SuppressWarnings("unused") + private BlockClusterFeatureConfig cloneBlockClusterFeatureConfig(BlockClusterFeatureConfig config) + { + WeightedBlockStateProvider provider = new WeightedBlockStateProvider(); + for(Entry state : ((WeightedBlockStateProvider) config.stateProvider).weightedList.entries) + provider.weightedList.entries.add(state); + + HashSet whitelist = new HashSet<>(); + for(Block block : config.whitelist) + whitelist.add(block); + + HashSet blacklist = new HashSet<>(); + for(BlockState state : config.blacklist) + blacklist.add(state); + + + BlockClusterFeatureConfig.Builder builder = new BlockClusterFeatureConfig.Builder(provider, config.blockPlacer); + builder.whitelist(whitelist); + builder.blacklist(blacklist); + builder.xspread(config.xspread); + builder.yspread(config.yspread); + builder.zspread(config.zspread); + if(config.canReplace) { builder.canReplace(); } + if(config.needWater) { builder.needWater(); } + if(config.project) { builder.noProjection(); } + builder.tries(config.tries); + + + return builder.build(); + } + + } + + + /* + * performance/generation tests related to + * serverWorld.getChunk(x, z, ChunkStatus. *** ) + + true/false is whether they generated blocks or not + the time is how long it took to generate + + ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P) + ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks) + ChunkStatus.BIOMES 1 - 10 ms false (no height) + ChunkStatus.NOISE 4 - 15 ms true (all blocks are stone) + ChunkStatus.LIQUID_CARVERS 6 - 12 ms true (no snow/trees, just grass) + ChunkStatus.SURFACE 5 - 15 ms true (no snow/trees, just grass) + ChunkStatus.CARVERS 5 - 30 ms true (no snow/trees, just grass) + ChunkStatus.FEATURES 7 - 25 ms true + ChunkStatus.HEIGHTMAPS 20 - 40 ms true + ChunkStatus.LIGHT 20 - 40 ms true + ChunkStatus.FULL 30 - 50 ms true + ChunkStatus.SPAWN 50 - 80 ms true + + + At this point I would suggest using FEATURES, as it generates snow and trees + (and any other object that is needed to make biomes distinct) + + Otherwise if snow/trees aren't necessary SURFACE is the next fastest (although not by much) + */ +} diff --git a/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTree.java b/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTree.java index ae27f69da..8edcf81e8 100644 --- a/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTree.java +++ b/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTree.java @@ -133,11 +133,11 @@ public class LodQuadTree { byte targetLevel = newLodNodeData.level; byte currentLevel = lodNodeData.level; if (targetLevel < currentLevel) { - int posX = newLodNodeData.posX; - int posZ = newLodNodeData.posZ; + int posX = Math.abs(newLodNodeData.posX); + int posZ = Math.abs(newLodNodeData.posZ); short widthRatio = (short) (lodNodeData.width / (2 * newLodNodeData.width)); - int NS = Math.abs((posX / widthRatio) % 2); - int WE = Math.abs((posZ / widthRatio) % 2); + int WE = Math.abs((posX / widthRatio) % 2); + int NS = Math.abs((posZ / widthRatio) % 2); if (getChild(NS, WE) == null) { setChild(NS, WE); } @@ -171,8 +171,8 @@ public class LodQuadTree { return lodNodeData; } else if (targetLevel < currentLevel) { short widthRatio = (short) (lodNodeData.width / Math.pow(2, level)); - int NS = Math.abs((posX / widthRatio) % lodNodeData.posX); - int WE = Math.abs((posZ / widthRatio) % lodNodeData.posZ); + int WE = Math.abs((posX / widthRatio) % lodNodeData.posX); + int NS = Math.abs((posZ / widthRatio) % lodNodeData.posZ); if (getChild(NS, WE) == null) { return null; } @@ -209,8 +209,8 @@ public class LodQuadTree { */ public void setChild(LodNodeData newLodNodeData) { if (newLodNodeData.level == lodNodeData.level - 1) { - int NS = newLodNodeData.posX % lodNodeData.posX; - int WE = newLodNodeData.posZ % lodNodeData.posZ; + int WE = newLodNodeData.posX % lodNodeData.posX; + int NS = newLodNodeData.posZ % lodNodeData.posZ; children[NS][WE] = new LodQuadTree(this, lodNodeData); } } diff --git a/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTreeDimension.java b/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTreeDimension.java index cbdef1caa..474aa7b3f 100644 --- a/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTreeDimension.java +++ b/src/main/java/com/seibel/lod/objects/quadTree/LodQuadTreeDimension.java @@ -200,7 +200,7 @@ public class LodQuadTreeDimension { regions[xIndex][zIndex] = getRegionFromFile(regionX, regionZ); if (regions[xIndex][zIndex] == null) { - regions[xIndex][zIndex] = new LodQuadTree(regionZ, regionX); + regions[xIndex][zIndex] = new LodQuadTree(regionX, regionZ); } } @@ -240,7 +240,7 @@ public class LodQuadTreeDimension { if (region == null) { // if no region exists, create it - region = new LodQuadTree(zIndex, xIndex); + region = new LodQuadTree(xIndex, zIndex); setRegion(region); } } @@ -271,7 +271,7 @@ public class LodQuadTreeDimension { if (region == null) { // if no region exists, create it - region = new LodQuadTree(pos.z, pos.x); + region = new LodQuadTree(pos.x, pos.z); setRegion(region); } boolean coorectlyAdded = region.setNodeAtLowerLevel(lodNodeData, true); @@ -344,7 +344,7 @@ public class LodQuadTreeDimension { zIndex = (zRegion + centerZ) - halfWidth; region = getRegion(xIndex,zIndex); if (region == null){ - region = new LodQuadTree(zIndex, xIndex); + region = new LodQuadTree(xIndex, zIndex); setRegion(region); } listOfQuadTree.addAll(region.getLevelToGenerate(x,z,level,maxDistance,minDistance)); diff --git a/src/main/java/com/seibel/lod/objects/quadTree/QuadTreeImage.java b/src/main/java/com/seibel/lod/objects/quadTree/QuadTreeImage.java index 118f67827..bf09f9f17 100644 --- a/src/main/java/com/seibel/lod/objects/quadTree/QuadTreeImage.java +++ b/src/main/java/com/seibel/lod/objects/quadTree/QuadTreeImage.java @@ -75,9 +75,9 @@ public class QuadTreeImage extends JPanel { } private static void createAndShowGui() { - int playerX = 32*512; - int playerZ = (32*512); - LodQuadTreeDimension dim = new LodQuadTreeDimension(null, null, 64); + int playerX =-0*511; + int playerZ =-0*511; + LodQuadTreeDimension dim = new LodQuadTreeDimension(null, null, 16); System.out.println(dim.getRegion(0, 0)); dim.move(playerX/512,playerZ/512); System.out.println(dim.getCenterX()); @@ -105,6 +105,7 @@ public class QuadTreeImage extends JPanel { dist = 32; } List levelToGenerate = dim.getNodeToGenerate(playerX, playerZ, (byte) (9 - i), distances[i], 0); + System.out.println(levelToGenerate); for (LodQuadTree level : levelToGenerate) { Color color; int startX = level.getLodNodeData().startX; @@ -131,8 +132,9 @@ public class QuadTreeImage extends JPanel { for (Integer posXI : posXs) { for (Integer posZI : posZs) { - int posZ = posXI.intValue(); - int posX = posZI.intValue(); + int posX = posXI.intValue(); + int posZ = posZI.intValue(); + //System.out.println(posX + " " + posZ); color = BiomeColorsUtils.getColorFromBiomeManual(biomeSource.getBiome(posZ, 0, posX)); //color = BiomeColorsUtils.getColorFromIdCB(biomeSource.getBiome(posZ, 0, posX).getId()); LodNodeData node = new LodNodeData(otherLevel, posX, posZ, 0, 0, color, true); @@ -159,6 +161,7 @@ public class QuadTreeImage extends JPanel { } + System.out.println(listOfList); int timerDelay = 0; System.out.println("STARTING"); System.out.println(dim.getWidth()); @@ -177,10 +180,9 @@ public class QuadTreeImage extends JPanel { } else { if(drawCount==0) quadTreeImage.clearAll(); final List myDrawables = new ArrayList<>(); - double amp = 0.025; + double amp = 0.1; Collection lodList = listOfList.get(drawCount); for (LodNodeData data : lodList) { - System.out.println(); myDrawables.add(new MyDrawable(new Rectangle2D.Double( ((data.startX - xOffset ) * amp), ((data.startZ - zOffset) * amp),