From cd1c12be12e86c4bc79674c60864654747933878 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sun, 13 Nov 2022 21:49:26 -0600 Subject: [PATCH] Implement DhApiTerrainDataPointRepo get methods --- .../data/IDhApiTerrainDataRepo.java | 56 +++-- .../objects/data/DhApiTerrainDataPoint.java | 45 ++-- .../methods/data/DhApiTerrainDataRepo.java | 212 ++++++++++++++++-- .../com/seibel/lod/core/pos/DhLodPos.java | 2 + 4 files changed, 257 insertions(+), 58 deletions(-) diff --git a/api/src/main/java/com/seibel/lod/api/interfaces/data/IDhApiTerrainDataRepo.java b/api/src/main/java/com/seibel/lod/api/interfaces/data/IDhApiTerrainDataRepo.java index 31b10b6de..bf1e0bf87 100644 --- a/api/src/main/java/com/seibel/lod/api/interfaces/data/IDhApiTerrainDataRepo.java +++ b/api/src/main/java/com/seibel/lod/api/interfaces/data/IDhApiTerrainDataRepo.java @@ -1,48 +1,46 @@ package com.seibel.lod.api.interfaces.data; +import com.seibel.lod.api.interfaces.world.IDhApiLevelWrapper; import com.seibel.lod.api.objects.DhApiResult; import com.seibel.lod.api.objects.data.DhApiTerrainDataPoint; /** * @author James Seibel - * @version 2022-9-16 + * @version 2022-11-13 */ public interface IDhApiTerrainDataRepo { - /** - * Returns the terrain data at the given block position. - * Null if the position hasn't been generated. - */ - DhApiTerrainDataPoint getDataAtBlockPos(int blockPosX, int blockPosY, int blockPosZ); - /** Sets the terrain data at the given block position. */ - DhApiResult setDataAtBlockPos(int blockPosX, int blockPosY, int blockPosZ, DhApiTerrainDataPoint newData); + /** Returns the terrain datapoint at the given block position, at or containing the given Y position. */ + DhApiResult getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ); + /** Returns every datapoint in the column located at the given block X and Z position. */ + DhApiResult getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ); +// /** Sets the terrain data at the given block position. */ +// DhApiResult setDataAtBlockPos(int blockPosX, int blockPosY, int blockPosZ, DhApiTerrainDataPoint newData); - /** - * Returns the average color for the chunk at the given chunk position. - * Returns null if the position hasn't been generated. - */ - DhApiTerrainDataPoint getDataAtChunkPos(int chunkPosX, int chunkPosZ); - /** Sets the terrain data at the given chunk position. */ - DhApiResult setDataAtChunkPos(int chunkPosX, int chunkPosZ, DhApiTerrainDataPoint newData); - /** - * Returns the average color for the chunk at the given chunk position. - * May return inaccurate data if the whole region hasn't been generated yet. - * Returns null if the position hasn't been generated. - */ - DhApiTerrainDataPoint getDataAtRegionPos(int regionPosX, int regionPosZ); - /** Sets the terrain data at the given chunk position. */ - DhApiResult setDataAtRegionPos(int regionPosX, int regionPosZ, DhApiTerrainDataPoint newData); + /** Returns every datapoint in the column located at the given chunk X and Z position. */ + DhApiResult getColumnDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ); +// /** Sets the terrain data at the given chunk position. */ +// DhApiResult setDataAtChunkPos(int chunkPosX, int chunkPosZ, DhApiTerrainDataPoint newData); + + + /** Returns every datapoint in the column located at the given region X and Z position. */ + DhApiResult getColumnDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ); +// /** Sets the terrain data at the given chunk position. */ +// DhApiResult setDataAtRegionPos(int regionPosX, int regionPosZ, DhApiTerrainDataPoint newData); /** - * Returns the average color for the chunk at the given chunk position. - * May return inaccurate data if the whole region hasn't been generated yet. - * Returns null if the position hasn't been generated. + * Returns every datapoint in the column located at the given detail level and X/Z position.
+ * This can be used to return terrain data for non-standard sizes (IE 2x2 blocks or 2x2 chunks). + * + * @param detailLevel a positive byte defining the detail level of the returned data.
+ * Every increase doubles the width of the returned area.
+ * Example values: 0 = block, 1 = 2x2 blocks, 2 = 4x4 blocks, ... 4 = chunk (16x16 blocks), ... 9 = region (512x512 blocks) */ - DhApiTerrainDataPoint getDataAtDetailLevelAndPos(short detailLevel, int relativePosX, int relativePosY, int relativePosZ); - /** Sets the terrain data at the given chunk position. */ - DhApiResult setDataAtRegionPos(short detailLevel, int relativePosX, int relativePosY, int relativePosZ, DhApiTerrainDataPoint newData); + DhApiResult getColumnDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ); +// /** Sets the terrain data at the given chunk position. */ +// DhApiResult setDataAtRegionPos(short detailLevel, int relativePosX, int relativePosY, int relativePosZ, DhApiTerrainDataPoint newData); } diff --git a/api/src/main/java/com/seibel/lod/api/objects/data/DhApiTerrainDataPoint.java b/api/src/main/java/com/seibel/lod/api/objects/data/DhApiTerrainDataPoint.java index ba9a816ce..bc246fda6 100644 --- a/api/src/main/java/com/seibel/lod/api/objects/data/DhApiTerrainDataPoint.java +++ b/api/src/main/java/com/seibel/lod/api/objects/data/DhApiTerrainDataPoint.java @@ -1,29 +1,44 @@ package com.seibel.lod.api.objects.data; -import java.awt.Color; +import com.seibel.lod.api.interfaces.block.IDhApiBiomeWrapper; +import com.seibel.lod.api.interfaces.block.IDhApiBlockStateWrapper; /** * Holds a single datapoint of terrain data. - * - * // TODO what additional data should this hold? - * + * * @author James Seibel - * @version 2022-7-12 + * @version 2022-11-13 */ public class DhApiTerrainDataPoint { /** - * The average color for the given data point. - * Invisible if the position is air. - * Null if the position is invalid. + * 0 = block
+ * 1 = 2x2 blocks
+ * 2 = 4x4 blocks
+ * 4 = chunk (16x16 blocks)
+ * 9 = region (512x512 blocks)
*/ - public Color color; + public final byte detailLevel; - /** - * TODO is this data type correct? - * TODO create an API enum that contains useful values (block, chunk, region, etc.) - * 0 = block - */ - public short detailLevel; + public final int lightLevel; + public final int topYBlockPos; + public final int bottomYBlockPos; + + public final IDhApiBlockStateWrapper blockStateWrapper; + public final IDhApiBiomeWrapper biomeWrapper; + + + + public DhApiTerrainDataPoint(byte detailLevel, int lightLevel, int topYBlockPos, int bottomYBlockPos, IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper) + { + this.detailLevel = detailLevel; + + this.lightLevel = lightLevel; + this.topYBlockPos = topYBlockPos; + this.bottomYBlockPos = bottomYBlockPos; + + this.blockStateWrapper = blockStateWrapper; + this.biomeWrapper = biomeWrapper; + } } diff --git a/core/src/main/java/com/seibel/lod/core/api/external/methods/data/DhApiTerrainDataRepo.java b/core/src/main/java/com/seibel/lod/core/api/external/methods/data/DhApiTerrainDataRepo.java index 29b97d2b2..1b6325250 100644 --- a/core/src/main/java/com/seibel/lod/core/api/external/methods/data/DhApiTerrainDataRepo.java +++ b/core/src/main/java/com/seibel/lod/core/api/external/methods/data/DhApiTerrainDataRepo.java @@ -1,5 +1,6 @@ package com.seibel.lod.core.api.external.methods.data; +import com.seibel.lod.api.interfaces.world.IDhApiLevelWrapper; import com.seibel.lod.api.objects.DhApiResult; import com.seibel.lod.api.objects.data.DhApiTerrainDataPoint; import com.seibel.lod.api.interfaces.data.IDhApiTerrainDataRepo; @@ -26,15 +27,13 @@ import org.apache.logging.log4j.Logger; import java.awt.*; import java.util.concurrent.ExecutionException; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; /** * Allows getting and setting any terrain data Distant Horizons has stored. * * @author James Seibel - * @version 2022-11-12 + * @version 2022-11-13 */ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo { @@ -57,25 +56,183 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo @Override - public DhApiTerrainDataPoint getDataAtBlockPos(int blockPosX, int blockPosY, int blockPosZ) { throw new UnsupportedOperationException(); } + public DhApiResult getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ) + { + return getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY); + } @Override - public DhApiResult setDataAtBlockPos(int blockPosX, int blockPosY, int blockPosZ, DhApiTerrainDataPoint newData) { throw new UnsupportedOperationException(); } + public DhApiResult getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ) + { + return getTerrainDataArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null); + } +// @Override +// public DhApiResult setDataAtBlockPos(int blockPosX, int blockPosY, int blockPosZ, DhApiTerrainDataPoint newData) { throw new UnsupportedOperationException(); } @Override - public DhApiTerrainDataPoint getDataAtChunkPos(int chunkPosX, int chunkPosZ) { throw new UnsupportedOperationException(); } - @Override - public DhApiResult setDataAtChunkPos(int chunkPosX, int chunkPosZ, DhApiTerrainDataPoint newData) { throw new UnsupportedOperationException(); } + public DhApiResult getColumnDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ) + { + return getTerrainDataArray(levelWrapper, new DhLodPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ), null); + } +// @Override +// public DhApiResult setDataAtChunkPos(int chunkPosX, int chunkPosZ, DhApiTerrainDataPoint newData) { throw new UnsupportedOperationException(); } @Override - public DhApiTerrainDataPoint getDataAtRegionPos(int regionPosX, int regionPosZ) { throw new UnsupportedOperationException(); } - @Override - public DhApiResult setDataAtRegionPos(int regionPosX, int regionPosZ, DhApiTerrainDataPoint newData) { throw new UnsupportedOperationException(); } + public DhApiResult getColumnDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ) + { + return getTerrainDataArray(levelWrapper, new DhLodPos(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ), null); + } +// @Override +// public DhApiResult setDataAtRegionPos(int regionPosX, int regionPosZ, DhApiTerrainDataPoint newData) { throw new UnsupportedOperationException(); } @Override - public DhApiTerrainDataPoint getDataAtDetailLevelAndPos(short detailLevel, int relativePosX, int relativePosY, int relativePosZ) { throw new UnsupportedOperationException(); } - @Override - public DhApiResult setDataAtRegionPos(short detailLevel, int relativePosX, int relativePosY, int relativePosZ, DhApiTerrainDataPoint newData) { throw new UnsupportedOperationException(); } + public DhApiResult getColumnDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ) + { + return getTerrainDataArray(levelWrapper, new DhLodPos(detailLevel, posX, posZ), null); + } +// @Override +// public DhApiResult setDataAtRegionPos(short detailLevel, int relativePosX, int relativePosY, int relativePosZ, DhApiTerrainDataPoint newData) { throw new UnsupportedOperationException(); } + + + + //================// + // Getter Methods // + //================// + + /** Returns a single API terrain datapoint that contains the given Y block position */ + private static DhApiResult getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer blockYPos) + { + DhApiResult result = getTerrainDataArray(levelWrapper, requestedColumnPos, blockYPos); + if (result.success && result.payload.length > 0) + { + return DhApiResult.createSuccess(result.errorMessage, result.payload[0]); + } + else + { + return DhApiResult.createFail(result.errorMessage); + } + } + + /** + * If nullableBlockYPos is null: returns every datapoint in the column defined by the DhLodPos.
+ * If nullableBlockYPos is NOT null: returns a single datapoint in the column defined by the DhLodPos which contains the block Y position.

+ * + * Returns an empty array if no data could be returned. + */ + private static DhApiResult getTerrainDataArray(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer nullableBlockYPos) + { + if (SharedApi.currentWorld == null) + { + return DhApiResult.createFail("Unable to get terrain data before the world has loaded."); + } + if (!(levelWrapper instanceof ILevelWrapper coreLevelWrapper)) + { + // custom level wrappers aren't supported, + // the API user must get a level wrapper from our code somewhere + return DhApiResult.createFail("Unsupported [" + IDhApiLevelWrapper.class.getSimpleName() + "] implementation, only the core class [" + IDhLevel.class.getSimpleName() + "] is a valid parameter."); + } + + IDhLevel level = SharedApi.currentWorld.getLevel(coreLevelWrapper); + if (level == null) + { + return DhApiResult.createFail("Unable to get terrain data before the world has loaded."); + } +// DhClientServerLevel serverLevel = (DhClientServerLevel) level; +// IDhLevel serverLevel = (IDhLevel) level; + + // get the detail levels for this request + byte requestedDetailLevel = requestedColumnPos.detailLevel; + byte sectionDetailLevel = (byte) (requestedDetailLevel + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); + + // get the positions for this request + DhSectionPos sectionPos = requestedColumnPos.getSectionPosWithSectionDetailLevel(sectionDetailLevel); + DhLodPos relativePos = requestedColumnPos.getDhSectionRelativePositionForDetailLevel(); + + + try + { + // attempt to get/generate the data source for this section + ILodDataSource dataSource = level.getFileHandler().read(sectionPos).get(); + if (dataSource == null) + { + return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + sectionPos + "]."); + } + else + { + // attempt to get the LOD data from the data source + FullDataPointIdMap mapping = dataSource.getMapping(); + SingleFullArrayView dataColumn = dataSource.tryGet(relativePos.x, relativePos.z); + if (dataColumn != null) + { + int dataColumnIndexCount = dataColumn.getSingleLength(); + DhApiTerrainDataPoint[] returnArray = new DhApiTerrainDataPoint[dataColumnIndexCount]; + long dataPoint; + + boolean getSpecificYCoordinate = nullableBlockYPos != null; + int levelMinimumHeight = levelWrapper.getMinHeight(); + + + // search for a datapoint that contains the block y position + for (int i = 0; i < dataColumnIndexCount; i++) + { + dataPoint = dataColumn.getSingle(i); + + if (!getSpecificYCoordinate) + { + // if we aren't look for a specific datapoint, add each datapoint to the return array + returnArray[i] = generateApiDatapoint(mapping, requestedDetailLevel, dataPoint); + } + else + { + // we are looking for a specific datapoint, + // don't look at null ones + if (dataPoint != 0) + { + int requestedY = nullableBlockYPos; + int bottomY = FullDataPoint.getY(dataPoint) + levelMinimumHeight; // TODO rename getY to getBottomY + int height = FullDataPoint.getDepth(dataPoint); // TODO rename to getHeight + int topY = bottomY + height; + + // does this datapoint contain the requested Y position? + if (bottomY < requestedY && requestedY <= topY) + { + // this datapoint contains the requested block position, return it + DhApiTerrainDataPoint apiTerrainData = generateApiDatapoint(mapping, requestedDetailLevel, dataPoint); + return DhApiResult.createSuccess(new DhApiTerrainDataPoint[]{apiTerrainData}); + } + } + } + } + + // return all collected data + return DhApiResult.createSuccess(returnArray); + } + + // the requested data wasn't present in this column (and/or the column wasn't able to be accessed/generated) + return DhApiResult.createSuccess(new DhApiTerrainDataPoint[0]); + } + } + catch (InterruptedException | ExecutionException e) + { + // shouldn't normally happen, but just in case + e.printStackTrace(); + return DhApiResult.createFail("Unexpected exception: [" + e.getMessage() + "]."); + } + } + + private static DhApiTerrainDataPoint generateApiDatapoint(FullDataPointIdMap mapping, byte detailLevel, long dataPoint) + { + IBlockStateWrapper blockState = mapping.getBlockStateWrapper(FullDataPoint.getId(dataPoint)); + IBiomeWrapper biomeWrapper = mapping.getBiomeWrapper(FullDataPoint.getId(dataPoint)); + + int topY = FullDataPoint.getY(dataPoint); + int depth = FullDataPoint.getDepth(dataPoint); + int bottomY = topY - depth; + + return new DhApiTerrainDataPoint(detailLevel, + FullDataPoint.getLight(dataPoint), topY, bottomY, + blockState, biomeWrapper); + } @@ -83,6 +240,32 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo // debug methods // //===============// + /** debug methods need to be async because pausing the main thread to debug and hot swapping will crash the program */ + public static void asyncDebugMethod(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ) + { + if (!debugThreadRunning) + { + debugThreadRunning = true; + Thread thread = new Thread(() -> { + try + { + DhApiResult x = getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY); + DhApiResult y = getTerrainDataArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null); + } + catch (Exception e) + { + e.printStackTrace(); + } + finally + { + debugThreadRunning = false; + } + }); + thread.start(); + } + } + + /** * Shouldn't be visible to API users, is only valid * for use on singleplayer worlds, and should only be @@ -114,6 +297,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo DhLodPos relativePos = inputPos.getDhSectionRelativePositionForDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL); + // attempt to get the data source for this section ILodDataSource dataSource = serverLevel.dataFileHandler.read(sectionPos).get(); if (dataSource != null) diff --git a/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java b/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java index b7327cb13..df523d064 100644 --- a/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java +++ b/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java @@ -85,6 +85,8 @@ public class DhLodPos implements Comparable /** Returns this position's child index in its parent */ public int getChildIndexOfParent() { return (this.x & 1) + BitShiftUtil.square(this.z & 1); } + /** @see DhLodPos#getSectionPosWithSectionDetailLevel(byte) */ + public DhLodPos getDhSectionRelativePositionForDetailLevel() throws IllegalArgumentException { return this.getDhSectionRelativePositionForDetailLevel(this.detailLevel); } /** * Returns a DhLodPos with the given detail level and an X/Z position somewhere between (0,0) and (63,63). * This is done to access specific sections from a {@link ILodDataSource} where LOD columns are stored