diff --git a/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java b/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java index 4ffb92f15..cf4fa755c 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/DHLevel.java @@ -30,7 +30,7 @@ public class DHLevel extends LodQuadTree { MC.getPlayerBlockPos().z); this.saveFolder = saveFolder; if (saveFolder != null) { - dataFileHandler = new DataFileHandler(saveFolder); + dataFileHandler = new DataFileHandler(saveFolder, this); } else { dataFileHandler = null; } @@ -67,4 +67,8 @@ public class DHLevel extends LodQuadTree { public void render(LodRenderProgram renderContext) { renderBufferHandler.render(renderContext); } + + public int getMinY() { + return level.getMinHeight(); + } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java index 4c8daf2fd..179c7cbfd 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java @@ -5,6 +5,7 @@ import com.seibel.lod.core.objects.a7.datatype.full.FullDatatype; import com.seibel.lod.core.objects.a7.pos.DhBlockPos2D; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; import com.seibel.lod.core.objects.a7.render.RenderDataSource; +import com.seibel.lod.core.objects.a7.render.RenderDataSourceLoader; import com.seibel.lod.core.util.DetailDistanceUtil; import com.seibel.lod.core.util.LodUtil; import com.seibel.lod.core.util.gridList.MovableGridRingList; @@ -38,19 +39,20 @@ public abstract class LodQuadTree { public final byte startingSectionLevel; private final MovableGridRingList[] ringLists; - static class ContainerTypeConfigEntry { - final Class containerType; - final byte levelOffset; - public ContainerTypeConfigEntry(Class containerType, byte levelOffset) { - this.containerType = containerType; - this.levelOffset = levelOffset; + static final ArrayList layerLoaderConfig = new ArrayList<>(); + + public static void registerLayerLoader(RenderDataSourceLoader loader, byte sectionLevel) { + while (layerLoaderConfig.size() <= sectionLevel) { + layerLoaderConfig.add(null); + } + if (layerLoaderConfig.set(sectionLevel, loader) != null) { + throw new RuntimeException("Layer loader for level " + sectionLevel + " has a registry conflict!"); } } - static final ArrayList containerTypeConfig = new ArrayList<>(); static { //TODO: Make this dynamic - Collections.addAll(containerTypeConfig, + Collections.addAll(layerLoaderConfig, null, null, //1 null, //2 @@ -75,10 +77,10 @@ public abstract class LodQuadTree { static void assertContainerTypeConfigCorrect() { boolean isInFront = true; - for (int i = 0; i < containerTypeConfig.size(); i++) { - if (containerTypeConfig.get(i) == null) continue; + for (int i = 0; i < layerLoaderConfig.size(); i++) { + if (layerLoaderConfig.get(i) == null) continue; isInFront = false; - ContainerTypeConfigEntry entry = containerTypeConfig.get(i); + ContainerTypeConfigEntry entry = layerLoaderConfig.get(i); if (i - entry.levelOffset < 0) { throw new RuntimeException("ContainerTypeConfigEntry " + i + " has a levelOffset of " + entry.levelOffset + " which makes the dataDetail be " + (i - entry.levelOffset) + "," + @@ -89,7 +91,7 @@ public abstract class LodQuadTree { + entry.levelOffset + " which is less than 0!"); } } - if (containerTypeConfig.get(containerTypeConfig.size()-1) == null) { + if (layerLoaderConfig.get(layerLoaderConfig.size()-1) == null) { throw new RuntimeException("The last ContainerTypeConfigEntry is null, which is invalid!"); } } @@ -110,14 +112,14 @@ public abstract class LodQuadTree { ContainerTypeConfigEntry finalEntry = null; byte topSectionLevel = 0; byte firstLevel = -1; - for (; topSectionLevel < containerTypeConfig.size(); topSectionLevel++) { - if (containerTypeConfig.get(topSectionLevel) == null) continue; - finalEntry = containerTypeConfig.get(topSectionLevel); + for (; topSectionLevel < layerLoaderConfig.size(); topSectionLevel++) { + if (layerLoaderConfig.get(topSectionLevel) == null) continue; + finalEntry = layerLoaderConfig.get(topSectionLevel); if (firstLevel == -1) firstLevel = topSectionLevel; if (topSectionLevel - finalEntry.levelOffset >= maxDetailLevel) break; } if (finalEntry == null) throw new RuntimeException("No container type found!"); - if (topSectionLevel == containerTypeConfig.size()) + if (topSectionLevel == layerLoaderConfig.size()) topSectionLevel = (byte) (maxDetailLevel - finalEntry.levelOffset); numbersOfSectionLevels = (byte) (topSectionLevel + 1); startingSectionLevel = firstLevel; @@ -131,21 +133,21 @@ public abstract class LodQuadTree { byte targetDataDetail; Class containerType; - if (i < containerTypeConfig.size()) { - if (containerTypeConfig.get(i) == null) { + if (i < layerLoaderConfig.size()) { + if (layerLoaderConfig.get(i) == null) { if (lastNonNullEntry == -1) continue; targetDataDetail = sectionDetailLayers[lastNonNullEntry].targetDataDetail; containerType = null; } else { lastNonNullEntry = i; - ContainerTypeConfigEntry entry = containerTypeConfig.get(i); + ContainerTypeConfigEntry entry = layerLoaderConfig.get(i); targetDataDetail = (byte) (i - entry.levelOffset); containerType = entry.containerType; } } else { - LodUtil.assertTrue(containerTypeConfig.get(containerTypeConfig.size() - 1) != null, + LodUtil.assertTrue(layerLoaderConfig.get(layerLoaderConfig.size() - 1) != null, "The last entry must not be null!"); - ContainerTypeConfigEntry entry = containerTypeConfig.get(containerTypeConfig.size() - 1); + ContainerTypeConfigEntry entry = layerLoaderConfig.get(layerLoaderConfig.size() - 1); targetDataDetail = (byte) (i - entry.levelOffset); containerType = entry.containerType; } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/RenderDataProvider.java b/src/main/java/com/seibel/lod/core/objects/a7/RenderDataProvider.java index 9a13392ed..35f040dda 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/RenderDataProvider.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/RenderDataProvider.java @@ -2,9 +2,10 @@ package com.seibel.lod.core.objects.a7; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; import com.seibel.lod.core.objects.a7.render.RenderDataSource; +import com.seibel.lod.core.objects.a7.render.RenderDataSourceLoader; import java.util.concurrent.CompletableFuture; public interface RenderDataProvider { - CompletableFuture createRenderData(RenderDataSource.RenderDataSourceLoader renderSourceLoader, DhSectionPos pos); + CompletableFuture createRenderData(RenderDataSourceLoader renderSourceLoader, DhSectionPos pos); } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFile.java b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFile.java index c47aae23a..e4bd9d642 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFile.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFile.java @@ -35,6 +35,7 @@ public class DataFile { public final File path; public final DhSectionPos pos; + public final byte dataLevel; public final LodDataSource.DataSourceLoader loader; public final Class dataType; @@ -47,11 +48,12 @@ public class DataFile { } } - public DataFile(File path, DhSectionPos pos, LodDataSource.DataSourceLoader loader, Class dataType) { + public DataFile(File path, DhSectionPos pos, LodDataSource.DataSourceLoader loader, Class dataType, byte dataLevel) { this.path = path; this.pos = pos; this.loader = loader; this.dataType = dataType; + this.dataLevel = dataLevel; } DataFile(File path, MappedByteBuffer meta) throws IOException { @@ -66,7 +68,7 @@ public class DataFile { int z = meta.getInt(); int checksum = meta.getInt(); byte detailLevel = meta.get(); - byte dataDetailLevel = meta.get(); + dataLevel = meta.get(); byte loaderVersion = meta.get(); byte unused = meta.get(); long dataTypeId = meta.getLong(); diff --git a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java index 5186d2016..865fa57b7 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/data/DataFileHandler.java @@ -4,15 +4,15 @@ import com.google.common.collect.HashMultimap; import com.seibel.lod.core.objects.a7.DHLevel; import com.seibel.lod.core.objects.a7.RenderDataProvider; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; -import com.seibel.lod.core.objects.a7.render.EmptyRenderContainer; import com.seibel.lod.core.objects.a7.render.RenderDataSource; +import com.seibel.lod.core.objects.a7.render.RenderDataSourceLoader; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; +import java.util.List; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; public class DataFileHandler implements RenderDataProvider { public static final String FILE_EXTENSION = ".lod"; @@ -72,16 +72,16 @@ public class DataFileHandler implements RenderDataProvider { } @Override - public CompletableFuture createRenderData(RenderDataSource.RenderDataSourceLoader renderSourceLoader, DhSectionPos pos) { + public CompletableFuture createRenderData(RenderDataSourceLoader renderSourceLoader, DhSectionPos pos) { return CompletableFuture.supplyAsync(() -> { Set files = renderSourceLoader.selectFiles(pos, level, unloadedDataFileCache.get(pos)); - LodDataSource[] dataSource = files.stream().map(f -> { + List dataSource = files.stream().map(f -> { try { return f.load(level); } catch (IOException e) { throw new RuntimeException(e); } - }).toArray(LodDataSource[]::new); + }).collect(Collectors.toList()); return renderSourceLoader.construct(dataSource, pos, level); }); } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnArrayView.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnArrayView.java new file mode 100644 index 000000000..b4b61b596 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnArrayView.java @@ -0,0 +1,115 @@ +package com.seibel.lod.core.objects.a7.datatype.column; + + +import java.util.Arrays; +import java.util.Iterator; + +public final class ColumnArrayView implements ColumnDataView { + private final long[] data; + private final int size; // size in longs + private final int offset; // offset in longs + private final int vertSize; // vertical size in longs + + public ColumnArrayView(long[] data, int size, int offset, int vertSize) { + this.data = data; + this.size = size; + this.offset = offset; + this.vertSize = vertSize; + } + @Override + public long get(int index) { + return data[index + offset]; + } + public void set(int index, long value) { + data[index + offset] = value; + } + @Override + public int size() { + return size; + } + + @Override + public int verticalSize() { + return vertSize; + } + @Override + public int dataCount() { + return size / vertSize; + } + + @Override + public ColumnArrayView subView(int dataIndexStart, int dataCount) { + return new ColumnArrayView(data, dataCount * vertSize, offset + dataIndexStart * vertSize, vertSize); + } + + public void fill(long value) { + Arrays.fill(data, offset, offset + size, value); + } + + public void copyFrom(ColumnDataView source, int outputDataIndexOffset) { + if (source.verticalSize() > vertSize) throw new IllegalArgumentException("source verticalSize must be <= self's verticalSize to copy"); + if (source.dataCount() + outputDataIndexOffset > dataCount()) throw new IllegalArgumentException("dataIndexStart + source.dataCount() must be <= self.dataCount() to copy"); + if (source.verticalSize() != vertSize) { + for (int i = 0; i < source.dataCount(); i++) { + int outputOffset = offset + outputDataIndexOffset * vertSize + i * vertSize; + source.subView(i, 1).copyTo(data, outputOffset); + Arrays.fill(data, outputOffset + source.verticalSize(), + outputOffset + vertSize, 0); + } + } else { + source.copyTo(data, offset + outputDataIndexOffset * vertSize); + } + } + public void copyFrom(ColumnDataView source) { + copyFrom(source, 0); + } + + @Override + public void copyTo(long[] target, int offset) { + System.arraycopy(data, this.offset, target, offset, size); + } + + public boolean mergeWith(ColumnArrayView source, boolean override) { + if (size != source.size) { + throw new IllegalArgumentException("Cannot merge views of different sizes"); + } + if (vertSize != source.vertSize) { + throw new IllegalArgumentException("Cannot merge views of different vertical sizes"); + } + boolean anyChange = false; + for (int o=0; o<(source.size()*vertSize); o+=vertSize) { + if (override) { + if (ColumnDataPoint.compareDatapointPriority(source.get(o), get(o)) >= 0) { + anyChange = true; + System.arraycopy(source.data, source.offset+o, data, offset+o, vertSize); + } + } else { + if (ColumnDataPoint.compareDatapointPriority(source.get(o), get(o)) > 0) { + anyChange = true; + System.arraycopy(source.data, source.offset+o, data, offset+o, vertSize); + } + } + } + return anyChange; + } + + public void changeVerticalSizeFrom(ColumnDataView source) { + if (dataCount() != source.dataCount()) { + throw new IllegalArgumentException("Cannot copy and resize to views with different dataCounts"); + } + if (vertSize >= source.verticalSize()) { + copyFrom(source); + } else { + for (int i=0; i. + */ + +package com.seibel.lod.core.objects.a7.datatype.column; + +import com.seibel.lod.core.logging.SpamReducedLogger; +import com.seibel.lod.core.util.ColorUtil; + +import java.util.Arrays; + + +/** + * + * @author Leonardo Amato + * @version ?? + */ +public class ColumnDataPoint +{ + /* + + |_ |g |g |g |a |a |a |a | + |r |r |r |r |r |r |r |r | + |g |g |g |g |g |g |g |g | + |b |b |b |b |b |b |b |b | + + |h |h |h |h |h |h |h |h | + |h |h |h |h |d |d |d |d | + |d |d |d |d |d |d |d |d | + |bl |bl |bl |bl |sl |sl |sl |sl | + + */ + + // Reminder: bytes have range of [-128, 127]. + // When converting to or from an int a 128 should be added or removed. + // If there is a bug with color then it's probably caused by this. + + public final static int EMPTY_DATA = 0; + public final static int MAX_WORLD_Y_SIZE = 4096; + + public final static int ALPHA_DOWNSIZE_SHIFT = 4; + + + public final static int GEN_TYPE_SHIFT = 60; + + public final static int COLOR_SHIFT = 32; + public final static int BLUE_SHIFT = COLOR_SHIFT; + public final static int GREEN_SHIFT = BLUE_SHIFT + 8; + public final static int RED_SHIFT = GREEN_SHIFT + 8; + public final static int ALPHA_SHIFT = RED_SHIFT + 8; + + public final static int HEIGHT_SHIFT = 20; + public final static int DEPTH_SHIFT = 8; + public final static int BLOCK_LIGHT_SHIFT = 4; + public final static int SKY_LIGHT_SHIFT = 0; + + public final static long ALPHA_MASK = 0xF; + public final static long RED_MASK = 0xFF; + public final static long GREEN_MASK = 0xFF; + public final static long BLUE_MASK = 0xFF; + public final static long COLOR_MASK = 0xFFFFFF; + public final static long HEIGHT_MASK = 0xFFF; + public final static long DEPTH_MASK = 0xFFF; + public final static long HEIGHT_DEPTH_MASK = 0xFFFFFF; + public final static long BLOCK_LIGHT_MASK = 0xF; + public final static long SKY_LIGHT_MASK = 0xF; + public final static long GEN_TYPE_MASK = 0b111; + public final static long COMPARE_SHIFT = GEN_TYPE_SHIFT; + + public final static long HEIGHT_SHIFTED_MASK = HEIGHT_MASK << HEIGHT_SHIFT; + public final static long DEPTH_SHIFTED_MASK = DEPTH_MASK << DEPTH_SHIFT; + + public final static long VOID_SETTER = HEIGHT_SHIFTED_MASK | DEPTH_SHIFTED_MASK; + + + public static long createVoidDataPoint(byte generationMode) + { + if (generationMode == 0) + throw new IllegalArgumentException("Trying to create void datapoint with genMode 0, which is NOT allowed in DataPoint version 10!"); + return (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT; + } + + public static long createDataPoint(int height, int depth, int color, int lightSky, int lightBlock, int generationMode) + { + return createDataPoint( + ColorUtil.getAlpha(color), + ColorUtil.getRed(color), + ColorUtil.getGreen(color), + ColorUtil.getBlue(color), + height, depth, lightSky, lightBlock, generationMode); + } + + public static long createDataPoint(int alpha, int red, int green, int blue, int height, int depth, int lightSky, int lightBlock, int generationMode) + { + if (generationMode == 0) + throw new IllegalArgumentException("Trying to create datapoint with genMode 0, which is NOT allowed in DataPoint version 10!"); + if (height < 0 || height > 4096) + throw new IllegalArgumentException("Height must be between 0 and 4096!"); + if (depth < 0 || depth > 4096) + throw new IllegalArgumentException("Depth must be between 0 and 4096!"); + if (lightSky < 0 || lightSky > 15) + throw new IllegalArgumentException("Sky light must be between 0 and 15!"); + if (lightBlock < 0 || lightBlock > 15) + throw new IllegalArgumentException("Block light must be between 0 and 15!"); + if (alpha < 0 || alpha > 255) + throw new IllegalArgumentException("Alpha must be between 0 and 255!"); + if (red < 0 || red > 255) + throw new IllegalArgumentException("Red must be between 0 and 255!"); + if (green < 0 || green > 255) + throw new IllegalArgumentException("Green must be between 0 and 255!"); + if (blue < 0 || blue > 255) + throw new IllegalArgumentException("Blue must be between 0 and 255!"); + if (generationMode < 0 || generationMode > 7) + throw new IllegalArgumentException("Generation mode must be between 0 and 7!"); + if (depth > height) + throw new IllegalArgumentException("Depth must be less than or equal to height!"); + + return (long) (alpha >>> ALPHA_DOWNSIZE_SHIFT) << ALPHA_SHIFT + | (red & RED_MASK) << RED_SHIFT + | (green & GREEN_MASK) << GREEN_SHIFT + | (blue & BLUE_MASK) << BLUE_SHIFT + | (height & HEIGHT_MASK) << HEIGHT_SHIFT + | (depth & DEPTH_MASK) << DEPTH_SHIFT + | (lightBlock & BLOCK_LIGHT_MASK) << BLOCK_LIGHT_SHIFT + | (lightSky & SKY_LIGHT_MASK) << SKY_LIGHT_SHIFT + | (generationMode & GEN_TYPE_MASK) << GEN_TYPE_SHIFT + ; + } + + public static long shiftHeightAndDepth(long dataPoint, short offset) { + long height = (dataPoint + ((long) offset << HEIGHT_SHIFT)) & HEIGHT_SHIFTED_MASK; + long depth = (dataPoint + (offset << DEPTH_SHIFT)) & DEPTH_SHIFTED_MASK; + return dataPoint & ~(HEIGHT_SHIFTED_MASK | DEPTH_SHIFTED_MASK) | height | depth; + } + + public static long version9Reorder(long dataPoint) { + /* + |a |a |a |a |r |r |r |r | + |r |r |r |r |g |g |g |g | + |g |g |g |g |b |b |b |b | + |b |b |b |b |h |h |h |h | + |h |h |h |h |h |h |d |d | + |d |d |d |d |d |d |d |d | + |bl |bl |bl |bl |sl |sl |sl |sl | + |l |l |f |g |g |g |v |e | + */ + if ((dataPoint & 1) == 0) return 0; + + long height = (dataPoint >>> 26) & 0x3FF; + long depth = (dataPoint >>> 16) & 0x3FF; + if (height == depth || (dataPoint & 0b10)==0b10) { + return createVoidDataPoint((byte) (((dataPoint >>> 2) & 0b111) + 1)); + } + return ((dataPoint >>> 60) & 0xF) << ALPHA_SHIFT + | ((dataPoint >>> 52) & 0xFF) << RED_SHIFT + | ((dataPoint >>> 44) & 0xFF) << GREEN_SHIFT + | ((dataPoint >>> 36) & 0xFF) << BLUE_SHIFT + | ((dataPoint >>> 26) & 0x3FF) << HEIGHT_SHIFT + | ((dataPoint >>> 16) & 0x3FF) << DEPTH_SHIFT + | ((dataPoint >>> 8) & 0xFF) << SKY_LIGHT_SHIFT + | (((dataPoint >>> 2) & 0xFF) + 1) << GEN_TYPE_SHIFT; + } + + public static short getHeight(long dataPoint) + { + return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK); + } + + public static short getDepth(long dataPoint) + { + return (short) ((dataPoint >>> DEPTH_SHIFT) & DEPTH_MASK); + } + + public static short getAlpha(long dataPoint) + { + return (short) ((((dataPoint >>> ALPHA_SHIFT) & ALPHA_MASK) << ALPHA_DOWNSIZE_SHIFT) | 0b1111); + } + + public static short getRed(long dataPoint) + { + return (short) ((dataPoint >>> RED_SHIFT) & RED_MASK); + } + + public static short getGreen(long dataPoint) + { + return (short) ((dataPoint >>> GREEN_SHIFT) & GREEN_MASK); + } + + public static short getBlue(long dataPoint) + { + return (short) ((dataPoint >>> BLUE_SHIFT) & BLUE_MASK); + } + + public static byte getLightSky(long dataPoint) + { + return (byte) ((dataPoint >>> SKY_LIGHT_SHIFT) & SKY_LIGHT_MASK); + } + + public static byte getLightBlock(long dataPoint) + { + return (byte) ((dataPoint >>> BLOCK_LIGHT_SHIFT) & BLOCK_LIGHT_MASK); + } + + private static final SpamReducedLogger warnLogger = new SpamReducedLogger(1); + + public static byte getGenerationMode(long dataPoint) + { + byte genMode = (byte) ((dataPoint >>> GEN_TYPE_SHIFT) & GEN_TYPE_MASK); + if (warnLogger.canMaybeLog() && doesItExist(dataPoint) && genMode==0) { + warnLogger.warnInc("Existing datapoint with genMode 0 detected! This is invalid in DataPoint version 10!" + + " This may be caused by old data that has not been updated correctly."); + return 1; + } + return genMode == 0 ? 1 : genMode; + } + + public static boolean isVoid(long dataPoint) + { + return (((dataPoint >>> DEPTH_SHIFT) & HEIGHT_DEPTH_MASK) == 0); + } + + public static boolean doesItExist(long dataPoint) + { + return dataPoint!=0; + } + + public static int getColor(long dataPoint) + { + long alpha = getAlpha(dataPoint); + return (int) (((dataPoint >>> COLOR_SHIFT) & COLOR_MASK) | (alpha << (ALPHA_SHIFT-COLOR_SHIFT))); + } + + /** This is used to convert a dataPoint to string (useful for the print function) */ + @SuppressWarnings("unused") + public static String toString(long dataPoint) + { + return getHeight(dataPoint) + " " + + getDepth(dataPoint) + " " + + getAlpha(dataPoint) + " " + + getRed(dataPoint) + " " + + getBlue(dataPoint) + " " + + getGreen(dataPoint) + " " + + getLightBlock(dataPoint) + " " + + getLightSky(dataPoint) + " " + + getGenerationMode(dataPoint) + " " + + isVoid(dataPoint) + " " + + doesItExist(dataPoint) + '\n'; + } + + + private static void shrinkArray(short[] array, int packetSize, int start, int length, int arraySize) + { + start *= packetSize; + length *= packetSize; + arraySize *= packetSize; + //remove comment to not leave garbage at the end + //array[start + packetSize + i] = 0; + if (arraySize - start >= 0) System.arraycopy(array, start + length, array, start, arraySize - start); + } + private static void extendArray(short[] array, int packetSize, int start, int length, int arraySize) + { + start *= packetSize; + length *= packetSize; + arraySize *= packetSize; + for (int i = arraySize - start - 1; i >= 0; i--) + { + array[start + length + i] = array[start + i]; + array[start + i] = 0; + } + } + + + /** Return (>0) if dataA should replace dataB, (0) if equal, (<0) if dataB should replace dataA */ + public static int compareDatapointPriority(long dataA, long dataB) { + return (int) ((dataA >> COMPARE_SHIFT) - (dataB >> COMPARE_SHIFT)); + } + private static final ThreadLocal tLocalHeightAndDepth = new ThreadLocal(); + private static final ThreadLocal tDataIndexCache = new ThreadLocal(); + /** + * This method merge column of multiple data together + * @param sourceData one or more columns of data + * @param output one column of space for the result to be written to + */ + public static void mergeMultiData(ColumnDataView sourceData, ColumnArrayView output) + { + if (output.dataCount() != 1) throw new IllegalArgumentException("output must be only reserved for one datapoint!"); + int inputVerticalSize = sourceData.verticalSize(); + int outputVerticalSize = output.verticalSize(); + output.fill(0); + + //dataCount indicate how many position we are merging in one position + int dataCount = sourceData.dataCount(); + + // We initialize the arrays that are going to be used + int heightAndDepthLength = (MAX_WORLD_Y_SIZE / 2 + 16) * 2; + short[] heightAndDepth = tLocalHeightAndDepth.get(); + if (heightAndDepth==null || heightAndDepth.length != heightAndDepthLength) { + heightAndDepth = new short[heightAndDepthLength]; + tLocalHeightAndDepth.set(heightAndDepth); + } + + byte genMode = getGenerationMode(sourceData.get(0)); + if (genMode == 0) genMode = 1; // FIXME: Hack to make the version 10 genMode never be 0. + boolean allEmpty = true; + boolean allVoid = true; + boolean limited = false; + boolean allDefault; + long singleData; + + short depth; + short height; + int count = 0; + int i; + int ii; + int dataIndex; + + //We collect the indexes of the data, ordered by the depth + for (int index = 0; index < dataCount; index++) + { + if (index == 0) + { + for (dataIndex = 0; dataIndex < inputVerticalSize; dataIndex++) + { + singleData = sourceData.get(dataIndex); + if (doesItExist(singleData)) + { + //genMode = Math.min(genMode, getGenerationMode(singleData)); + allEmpty = false; + if (!isVoid(singleData)) + { + allVoid = false; + count++; + heightAndDepth[dataIndex * 2] = getHeight(singleData); + heightAndDepth[dataIndex * 2 +1] = getDepth(singleData); + } + } + else + break; + } + } + else + { + for (dataIndex = 0; dataIndex < inputVerticalSize; dataIndex++) + { + singleData = sourceData.get(index * inputVerticalSize + dataIndex); + if (doesItExist(singleData)) + { + //genMode = Math.min(genMode, getGenerationMode(singleData)); + allEmpty = false; + if (!isVoid(singleData)) + { + allVoid = false; + depth = getDepth(singleData); + height = getHeight(singleData); + + int botPos = -1; + int topPos = -1; + //values fall in between and possibly require extension of array + boolean botExtend = false; + boolean topExtend = false; + for (i = 0; i < count; i++) + { + if (depth < heightAndDepth[i * 2] && depth >= heightAndDepth[i * 2 + 1]) + { + botPos = i; + break; + } + else if (depth < heightAndDepth[i * 2 + 1] && ((i + 1 < count && depth >= heightAndDepth[(i + 1) * 2]) || i + 1 == count)) + { + botPos = i; + botExtend = true; + break; + } + } + for (i = 0; i < count; i++) + { + if (height <= heightAndDepth[i * 2] && height > heightAndDepth[i * 2 + 1]) + { + topPos = i; + break; + } + else if (height <= heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count)) + { + topPos = i; + topExtend = true; + break; + } + } + if (topPos == -1) + { + if (botPos == -1) + { + //whole block falls above + extendArray(heightAndDepth, 2, 0, 1, count); + heightAndDepth[0] = height; + heightAndDepth[1] = depth; + count++; + } + else if (!botExtend) + { + //only top falls above extending it there, while bottom is inside existing + shrinkArray(heightAndDepth, 2, 0, botPos, count); + heightAndDepth[0] = height; + count -= botPos; + } + else + { + //top falls between some blocks, extending those as well + shrinkArray(heightAndDepth, 2, 0, botPos, count); + heightAndDepth[0] = height; + heightAndDepth[1] = depth; + count -= botPos; + } + } + else if (!topExtend) + { + if (!botExtend) + //both top and bottom are within some exiting blocks, possibly merging them + heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1]; + else + //top falls between some blocks, extending it there + heightAndDepth[topPos * 2 + 1] = depth; + shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); + count -= botPos - topPos; + } + else + { + if (!botExtend) + { + //only top is within some exiting block, extending it + topPos++; //to make it easier + heightAndDepth[topPos * 2] = height; + heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1]; + shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); + count -= botPos - topPos; + } + else + { + //both top and bottom are outside existing blocks + shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); + count -= botPos - topPos; + extendArray(heightAndDepth, 2, topPos + 1, 1, count); + count++; + heightAndDepth[topPos * 2 + 2] = height; + heightAndDepth[topPos * 2 + 3] = depth; + } + } + } + } + else + break; + } + } + } + + //We check if there is any data that's not empty or void + if (allEmpty) { + return; + } + if (allVoid) + { + output.set(0,createVoidDataPoint(genMode)); + return; + } + + //we limit the vertical portion to maxVerticalData + int j = 0; + while (count > outputVerticalSize) + { + limited = true; + ii = MAX_WORLD_Y_SIZE; + for (i = 0; i < count - 1; i++) + { + if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii) + { + ii = heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2]; + j = i; + } + } + heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1]; + for (i = j + 1; i < count - 1; i++) + { + heightAndDepth[i * 2] = heightAndDepth[(i + 1) * 2]; + heightAndDepth[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1]; + } + //System.arraycopy(heightAndDepth, j + 1, heightAndDepth, j, count - j - 1); + count--; + } + //As standard the vertical lods are ordered from top to bottom + + if (!limited && dataCount == 1) // This mean source vertSize < output vertSize AND both dataCount == 1 + { + output.copyFrom(sourceData); + } + else + { + + //We want to efficiently memorize indexes + int[] dataIndexesCache = tDataIndexCache.get(); + if (dataIndexesCache==null || dataIndexesCache.length != dataCount) { + dataIndexesCache = new int[dataCount]; + tDataIndexCache.set(dataIndexesCache); + } + Arrays.fill(dataIndexesCache,0); + + //For each lod height-depth value we have found we now want to generate the rest of the data + //by merging all lods at lower level that are contained inside the new ones + for (j = 0; j < count; j++) + { + //We firstly collect height and depth data + //this will be added to each realtive long DataPoint + height = heightAndDepth[j * 2]; + depth = heightAndDepth[j * 2 + 1]; + + //if both height and depth are at 0 then we finished + if ((depth == 0 && height == 0) || j >= heightAndDepth.length / 2) + break; + + //We initialize data useful for the merge + int numberOfChildren = 0; + allEmpty = true; + allVoid = true; + + //We initialize all the new values that we are going to put in the dataPoint + int tempAlpha = 0; + int tempRed = 0; + int tempGreen = 0; + int tempBlue = 0; + int tempLightBlock = 0; + int tempLightSky = 0; + long data = 0; + + //For each position that we want to merge + for (int index = 0; index < dataCount; index++) + { + //we scan the lods in the position from top to bottom + while(dataIndexesCache[index] < inputVerticalSize) + { + singleData = sourceData.get(index * inputVerticalSize + dataIndexesCache[index]); + if (doesItExist(singleData) && !isVoid(singleData)) + { + dataIndexesCache[index]++; + if ((depth <= getDepth(singleData) && getDepth(singleData) < height) + || (depth < getHeight(singleData) && getHeight(singleData) <= height)) + { + data = singleData; + break; + } + } + else + break; + } + if (!doesItExist(data)) + { + data = createVoidDataPoint(genMode); + } + + if (doesItExist(data)) + { + allEmpty = false; + if (!isVoid(data)) + { + numberOfChildren++; + allVoid = false; + tempAlpha = Math.max(getAlpha(data),tempAlpha); + tempRed += getRed(data) * getRed(data); + tempGreen += getGreen(data) * getGreen(data); + tempBlue += getBlue(data) * getBlue(data); + tempLightBlock += getLightBlock(data); + tempLightSky += getLightSky(data); + } + } + } + + if (allEmpty) + //no child has been initialized + output.set(j, EMPTY_DATA); + else if (allVoid) + //all the children are void + output.set(j, createVoidDataPoint(genMode)); + else + { + //we have at least 1 child + if (dataCount != 1) + { + tempRed = tempRed / numberOfChildren; + tempGreen = tempGreen / numberOfChildren; + tempBlue = tempBlue / numberOfChildren; + tempLightBlock = tempLightBlock / numberOfChildren; + tempLightSky = tempLightSky / numberOfChildren; + } + //data = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault); + //if (j > 0 && getColor(data) == getColor(dataPoint[j])) + //{ + // add simplification at the end due to color + //} + output.set(j, createDataPoint((int) Math.sqrt(tempAlpha), (int) Math.sqrt(tempRed), (int) Math.sqrt(tempGreen), (int) Math.sqrt(tempBlue), height, depth, tempLightSky, tempLightBlock, genMode)); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDataView.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDataView.java new file mode 100644 index 000000000..baba3c272 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDataView.java @@ -0,0 +1,33 @@ +package com.seibel.lod.core.objects.a7.datatype.column; + +import java.util.Iterator; + +public interface ColumnDataView { + long get(int index); + + int size(); + + default Iterator iterator() { + return new Iterator() { + private int index = 0; + private final int size = size(); + @Override + public boolean hasNext() { + return index < size; + } + + @Override + public Long next() { + return get(index++); + } + }; + } + + int verticalSize(); + + int dataCount(); + + ColumnDataView subView(int dataIndexStart, int dataCount); + + void copyTo(long[] target, int offset); +} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java index 55b3a03dc..b883c193e 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java @@ -2,9 +2,15 @@ package com.seibel.lod.core.objects.a7.datatype.column; import com.seibel.lod.core.objects.LodDataView; import com.seibel.lod.core.objects.a7.DHLevel; +import com.seibel.lod.core.objects.a7.LodQuadTree; +import com.seibel.lod.core.objects.a7.data.DataFile; import com.seibel.lod.core.objects.a7.data.LodDataSource; +import com.seibel.lod.core.objects.a7.datatype.full.FullDatatype; +import com.seibel.lod.core.objects.a7.pos.DhLodPos; +import com.seibel.lod.core.objects.a7.pos.DhLodUnit; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; import com.seibel.lod.core.objects.a7.render.RenderDataSource; +import com.seibel.lod.core.objects.a7.render.RenderDataSourceLoader; import com.seibel.lod.core.objects.opengl.RenderBuffer; import com.seibel.lod.core.util.DataPointUtil; import com.seibel.lod.core.util.DetailDistanceUtil; @@ -16,7 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.Arrays; +import java.util.*; import java.util.concurrent.atomic.AtomicReference; public class ColumnDatatype implements LodDataSource, RenderDataSource { @@ -24,10 +30,13 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { public static final byte SECTION_SIZE_OFFSET = 6; public static final int SECTION_SIZE = 1 << SECTION_SIZE_OFFSET; public static final int LATEST_VERSION = 9; + + public static final long DATA_TYPE_ID = "ColumnDatatype".hashCode(); public final int AIR_LODS_SIZE = 16; public final int AIR_SECTION_SIZE = SECTION_SIZE/AIR_LODS_SIZE; public final int verticalSize; public final DhSectionPos sectionPos; + public final int yOffset; public final long[] dataContainer; public final int[] airDataContainer; @@ -36,16 +45,18 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { * Constructor of the ColumnDataType * @param maxVerticalSize the maximum vertical size of the container */ - public ColumnDatatype(DhSectionPos sectionPos, int maxVerticalSize) { + public ColumnDatatype(DhSectionPos sectionPos, int maxVerticalSize, int yOffset) { verticalSize = maxVerticalSize; dataContainer = new long[SECTION_SIZE * SECTION_SIZE * verticalSize]; airDataContainer = new int[AIR_SECTION_SIZE * AIR_SECTION_SIZE * verticalSize]; this.sectionPos = sectionPos; + this.yOffset = yOffset; } // Load from data stream with maxVerticalSize loaded from the data stream - public ColumnDatatype(DhSectionPos sectionPos, DataInputStream inputData, int version) throws IOException { + public ColumnDatatype(DhSectionPos sectionPos, DataInputStream inputData, int version, DHLevel level) throws IOException { this.sectionPos = sectionPos; + this.yOffset = level.getMinY(); byte detailLevel = inputData.readByte(); if (sectionPos.sectionDetail - SECTION_SIZE_OFFSET != detailLevel) { throw new IOException("Invalid data: detail level does not match"); @@ -53,16 +64,16 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { verticalSize = inputData.readByte() & 0b01111111; switch (version) { case 6: - dataContainer = readDataVersion6(inputData, verticalSize, sectionPos.yOffset); + dataContainer = readDataVersion6(inputData, verticalSize); break; case 7: - dataContainer = readDataVersion7(inputData, verticalSize, sectionPos.yOffset); + dataContainer = readDataVersion7(inputData, verticalSize); break; case 8: - dataContainer = readDataVersion8(inputData, verticalSize, sectionPos.yOffset); + dataContainer = readDataVersion8(inputData, verticalSize); break; case 9: - dataContainer = readDataVersion9(inputData, verticalSize, sectionPos.yOffset); + dataContainer = readDataVersion9(inputData, verticalSize); break; default: throw new IOException("Invalid Data: The version of the data is not supported"); @@ -71,8 +82,9 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { } // Load from data stream with new maxVerticalSize - public ColumnDatatype(DhSectionPos sectionPos, DataInputStream inputData, int version, int maxVerticalSize) throws IOException { + public ColumnDatatype(DhSectionPos sectionPos, DataInputStream inputData, int version, DHLevel level, int maxVerticalSize) throws IOException { verticalSize = maxVerticalSize; + this.yOffset = level.getMinY(); this.sectionPos = sectionPos; byte detailLevel = inputData.readByte(); if (sectionPos.sectionDetail - SECTION_SIZE_OFFSET != detailLevel) { @@ -82,16 +94,16 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { long[] fileDataContainer = null; switch (version) { case 6: - fileDataContainer = readDataVersion6(inputData, fileMaxVerticalSize, sectionPos.yOffset); + fileDataContainer = readDataVersion6(inputData, fileMaxVerticalSize); break; case 7: - fileDataContainer = readDataVersion7(inputData, fileMaxVerticalSize, sectionPos.yOffset); + fileDataContainer = readDataVersion7(inputData, fileMaxVerticalSize); break; case 8: - fileDataContainer = readDataVersion8(inputData, fileMaxVerticalSize, sectionPos.yOffset); + fileDataContainer = readDataVersion8(inputData, fileMaxVerticalSize); break; case 9: - fileDataContainer = readDataVersion9(inputData, fileMaxVerticalSize, sectionPos.yOffset); + fileDataContainer = readDataVersion9(inputData, fileMaxVerticalSize); break; default: throw new IOException("Invalid Data: The version of the data is not supported"); @@ -100,30 +112,6 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { airDataContainer = new int[AIR_SECTION_SIZE * AIR_SECTION_SIZE * verticalSize]; } - // Copy constructor - public ColumnDatatype(DhSectionPos sectionPos, LodDataSource dataSource, int maxVerticalData) { - verticalSize = maxVerticalData; - this.sectionPos = sectionPos; - dataContainer = new long[SECTION_SIZE * SECTION_SIZE * verticalSize]; - airDataContainer = new int[AIR_SECTION_SIZE * AIR_SECTION_SIZE * verticalSize]; - DhSectionPos sourcePos = dataSource.getSectionPos(); - if (!sourcePos.overlaps(sectionPos)) { - throw new IllegalArgumentException("The source section does not overlap with new target position"); - } - if (sourcePos.dataDetail > sectionPos.dataDetail) { - throw new IllegalArgumentException("The source section has higher detail than new target detail"); - } - if (sourcePos.yOffset != sectionPos.yOffset) { - throw new IllegalArgumentException("Different yOffset is not yet supported"); // TODO: is this needed? - } - - if (sourcePos.equals(sectionPos)) { - //TODO: Simple full copy. - } else { - //TODO: Downsample copy. - } - } - /** * This method will clear all data at relative section position * @param posX @@ -256,8 +244,16 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { return result; } - public LodDataView getVerticalDataView(int posX, int posZ) { - return new LodDataView(dataContainer, verticalSize, posX * SECTION_SIZE * verticalSize + posZ * verticalSize); + public ColumnArrayView getVerticalDataView(int posX, int posZ) { + return new ColumnArrayView(dataContainer, verticalSize, + posX * SECTION_SIZE * verticalSize + posZ * verticalSize, verticalSize); + } + + public ColumnQuadView getDataInQuad(int quadX, int quadZ, int quadXSize, int quadZSize) { + return new ColumnQuadView(dataContainer, SECTION_SIZE, verticalSize, quadX, quadZ, quadXSize, quadZSize); + } + public ColumnQuadView getFullQuad() { + return new ColumnQuadView(dataContainer, SECTION_SIZE, verticalSize, 0, 0, SECTION_SIZE, SECTION_SIZE); } public int getVerticalSize() @@ -270,7 +266,7 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { return DataPointUtil.doesItExist(getSingleData(posX, posZ)); } - private long[] readDataVersion6(DataInputStream inputData, int tempMaxVerticalData, int yOffset) throws IOException { + private long[] readDataVersion6(DataInputStream inputData, int tempMaxVerticalData) throws IOException { int x = SECTION_SIZE * SECTION_SIZE * tempMaxVerticalData; byte[] data = new byte[x * Long.BYTES]; ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); @@ -281,7 +277,7 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { patchHeightAndDepth(result,-yOffset); return result; } - private long[] readDataVersion7(DataInputStream inputData, int tempMaxVerticalData, int yOffset) throws IOException { + private long[] readDataVersion7(DataInputStream inputData, int tempMaxVerticalData) throws IOException { int x = SECTION_SIZE * SECTION_SIZE * tempMaxVerticalData; byte[] data = new byte[x * Long.BYTES]; ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); @@ -293,7 +289,7 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { return result; } - private long[] readDataVersion8(DataInputStream inputData, int tempMaxVerticalData, int yOffset) throws IOException { + private long[] readDataVersion8(DataInputStream inputData, int tempMaxVerticalData) throws IOException { int x = SECTION_SIZE * SECTION_SIZE * tempMaxVerticalData; byte[] data = new byte[x * Long.BYTES]; short tempMinHeight = Short.reverseBytes(inputData.readShort()); @@ -308,7 +304,7 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { return result; } - private long[] readDataVersion9(DataInputStream inputData, int tempMaxVerticalData, int yOffset) throws IOException { + private long[] readDataVersion9(DataInputStream inputData, int tempMaxVerticalData) throws IOException { int x = SECTION_SIZE * SECTION_SIZE * tempMaxVerticalData; byte[] data = new byte[x * Long.BYTES]; short tempMinHeight = Short.reverseBytes(inputData.readShort()); @@ -339,60 +335,19 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { return new long[LodUtil.DETAIL_OPTIONS - 1][]; }); - public void updateData(ColumnDatatype lowerDataContainer, int posX, int posZ) + public void generateData(ColumnDatatype lowerDataContainer, int posX, int posZ) { - //We reset the array - long[][] verticalUpdateArrays = tLocalVerticalUpdateArrays.get(); - long[] dataToMerge = verticalUpdateArrays[sectionPos.dataDetail -1]; - int arrayLength = DetailDistanceUtil.getMaxVerticalData(sectionPos.dataDetail -1) * 4; - if (dataToMerge == null || dataToMerge.length != arrayLength) { - dataToMerge = new long[arrayLength]; - verticalUpdateArrays[sectionPos.dataDetail -1] = dataToMerge; - } else Arrays.fill(dataToMerge, 0); - - //int lowerMaxVertical = dataToMerge.length / 4; - int lowerSectionSize = lowerDataContainer.getSECTION_SIZE(); - int childPosStartX = Math.floorMod(2 * posX, lowerSectionSize); - int childPosEndX = Math.floorMod(2 * posX + 1, lowerSectionSize); - int childPosStartZ = Math.floorMod(2 * posZ, lowerSectionSize); - int childPosEndZ = Math.floorMod(2 * posZ + 1, lowerSectionSize); - - long[] data; - boolean anyDataExist = false; - - mergeAndAddDataFromOtherContainer(posX, posZ, lowerDataContainer, childPosStartX, childPosEndX, childPosStartZ, childPosEndZ); - /* - TODO remove this old code when we are sure that this works - for (int x = 0; x <= 1; x++) - { - for (int z = 0; z <= 1; z++) - { - childPosX = 2 * posX + x; - childPosZ = 2 * posZ + z; - if (lowerLevelContainer.doesItExist(childPosX, childPosZ)) anyDataExist = true; - for (int verticalIndex = 0; verticalIndex < lowerMaxVertical; verticalIndex++) - dataToMerge[(z * 2 + x) * lowerMaxVertical + verticalIndex] = lowerLevelContainer.getData(childPosX, childPosZ, verticalIndex); - } - } - if (!anyDataExist) - throw new RuntimeException("Update data called but no child datapoint exist!"); - - if ((!DataPointUtil.doesItExist(data[0])) && anyDataExist) - throw new RuntimeException("Update data called but higher level datapoint doesn't exist even though child data does exist!"); - - //FIXME: Disabled check if genMode for old data is already invalid due to having genMode 0. - if (DataPointUtil.getGenerationMode(data[0]) != DataPointUtil.getGenerationMode(lowerLevelContainer.getSingleData(posX*2, posZ*2))) - throw new RuntimeException("Update data called but higher level datapoint does not have the same GenerationMode as the top left corner child datapoint!"); - - forceWriteVerticalData(data, posX, posZ);*/ + ColumnQuadView quadView = lowerDataContainer.getDataInQuad(posX*2, posZ*2, 2,2); + ColumnArrayView outputView = getVerticalDataView(posX, posZ); + outputView.mergeMultiDataFrom(quadView); } public boolean writeData(DataOutputStream output) throws IOException { - output.writeByte(sectionPos.dataDetail); + output.writeByte(getDataDetail()); output.writeByte((byte) verticalSize); // FIXME: yOffset is a int, but we only are writing a short. - output.writeByte((byte) (sectionPos.yOffset & 0xFF)); - output.writeByte((byte) ((sectionPos.yOffset >> 8) & 0xFF)); + output.writeByte((byte) (yOffset & 0xFF)); + output.writeByte((byte) ((yOffset >> 8) & 0xFF)); boolean allGenerated = true; int x = SECTION_SIZE * SECTION_SIZE; for (int i = 0; i < x; i++) @@ -443,401 +398,186 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { return (long) dataContainer.length * Long.BYTES; } - - - private static final ThreadLocal tLocalHeightAndDepth = new ThreadLocal(); - private static final ThreadLocal tDataIndexCache = new ThreadLocal(); - /** - * - * This method merge column of multiple data together - */ - // TODO: Make this operate on a out param array, to allow skipping copy array on use - public void mergeAndAddDataFromOtherContainer(int mergeInX, int mergeInZ, ColumnDatatype lowerDataContainer, int mergeFromX, int mergeToX, int mergeFromZ, int mergeToZ) - { - int outBaseIndex = mergeInX * SECTION_SIZE * verticalSize + mergeInZ*verticalSize; - int inputVerticalSize = lowerDataContainer.verticalSize; - int inputSectionSize = lowerDataContainer.SECTION_SIZE; - int mergeFromToX; - int xSize = (mergeFromX - mergeToX + 1); - int zSize = (mergeFromZ - mergeToZ + 1); - //size indicate how many position we are merging in one position - - // We initialize the arrays that are going to be used - int heightAndDepthLength = (DataPointUtil.MAX_WORLD_Y_SIZE / 2 + 16) * 2; - short[] heightAndDepth = tLocalHeightAndDepth.get(); - if (heightAndDepth==null || heightAndDepth.length != heightAndDepthLength) { - heightAndDepth = new short[heightAndDepthLength]; - tLocalHeightAndDepth.set(heightAndDepth); - } - int dataPointLength = verticalSize; - - int firstIndex = mergeFromX*SECTION_SIZE*inputVerticalSize + mergeFromZ * inputVerticalSize; - byte genMode = DataPointUtil.getGenerationMode(lowerDataContainer.dataContainer[firstIndex]); - if (genMode == 0) genMode = 1; // FIXME: Hack to make the version 10 genMode never be 0. - boolean allEmpty = true; - boolean allVoid = true; - boolean limited = false; - boolean allDefault; - long singleData; - - - short depth; - short height; - int count = 0; - int i; - int ii; - - //We collect the indexes of the data, ordered by the depth - int dataIndex = 0; - int x; - int z; - int y; - for (x = mergeFromX; x <= mergeToX; x++) - { - for (z = mergeFromZ; z <= mergeToZ; z++) - { - if (x == mergeFromX && z == mergeFromZ) - { - for (y = 0; y < inputVerticalSize; y++) - { - dataIndex = x * inputSectionSize * inputVerticalSize + z * inputVerticalSize + y; - singleData = lowerDataContainer.dataContainer[dataIndex]; - if (DataPointUtil.doesItExist(singleData)) - { - //genMode = Math.min(genMode, getGenerationMode(singleData)); - allEmpty = false; - if (!DataPointUtil.isVoid(singleData)) - { - allVoid = false; - count++; - heightAndDepth[dataIndex * 2] = DataPointUtil.getHeight(singleData); - heightAndDepth[dataIndex * 2 + 1] = DataPointUtil.getDepth(singleData); - } - } - else - break; - } - } - else - { - for (y = 0; y < inputVerticalSize; y++) - { - dataIndex = x * inputSectionSize * inputVerticalSize + z * inputVerticalSize + y; - singleData = lowerDataContainer.dataContainer[dataIndex]; - if (DataPointUtil.doesItExist(singleData)) - { - //genMode = Math.min(genMode, getGenerationMode(singleData)); - allEmpty = false; - if (!DataPointUtil.isVoid(singleData)) - { - allVoid = false; - depth = DataPointUtil.getDepth(singleData); - height = DataPointUtil.getHeight(singleData); - - int botPos = -1; - int topPos = -1; - //values fall in between and possibly require extension of array - boolean botExtend = false; - boolean topExtend = false; - for (i = 0; i < count; i++) - { - if (depth < heightAndDepth[i * 2] && depth >= heightAndDepth[i * 2 + 1]) - { - botPos = i; - break; - } - else if (depth < heightAndDepth[i * 2 + 1] && ((i + 1 < count && depth >= heightAndDepth[(i + 1) * 2]) || i + 1 == count)) - { - botPos = i; - botExtend = true; - break; - } - } - for (i = 0; i < count; i++) - { - if (height <= heightAndDepth[i * 2] && height > heightAndDepth[i * 2 + 1]) - { - topPos = i; - break; - } - else if (height <= heightAndDepth[i * 2 + 1] && ((i + 1 < count && height > heightAndDepth[(i + 1) * 2]) || i + 1 == count)) - { - topPos = i; - topExtend = true; - break; - } - } - if (topPos == -1) - { - if (botPos == -1) - { - //whole block falls above - DataPointUtil.extendArray(heightAndDepth, 2, 0, 1, count); - heightAndDepth[0] = height; - heightAndDepth[1] = depth; - count++; - } - else if (!botExtend) - { - //only top falls above extending it there, while bottom is inside existing - DataPointUtil.shrinkArray(heightAndDepth, 2, 0, botPos, count); - heightAndDepth[0] = height; - count -= botPos; - } - else - { - //top falls between some blocks, extending those as well - DataPointUtil.shrinkArray(heightAndDepth, 2, 0, botPos, count); - heightAndDepth[0] = height; - heightAndDepth[1] = depth; - count -= botPos; - } - } - else if (!topExtend) - { - if (!botExtend) - //both top and bottom are within some exiting blocks, possibly merging them - heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1]; - else - //top falls between some blocks, extending it there - heightAndDepth[topPos * 2 + 1] = depth; - DataPointUtil.shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); - count -= botPos - topPos; - } - else - { - if (!botExtend) - { - //only top is within some exiting block, extending it - topPos++; //to make it easier - heightAndDepth[topPos * 2] = height; - heightAndDepth[topPos * 2 + 1] = heightAndDepth[botPos * 2 + 1]; - DataPointUtil.shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); - count -= botPos - topPos; - } - else - { - //both top and bottom are outside existing blocks - DataPointUtil.shrinkArray(heightAndDepth, 2, topPos + 1, botPos - topPos, count); - count -= botPos - topPos; - DataPointUtil.extendArray(heightAndDepth, 2, topPos + 1, 1, count); - count++; - heightAndDepth[topPos * 2 + 2] = height; - heightAndDepth[topPos * 2 + 3] = depth; - } - } - } - } - else - break; - } - } - } - } - //We check if there is any data that's not empty or void - if (allEmpty) - return; - if (allVoid) - { - dataContainer[outBaseIndex] = DataPointUtil.createVoidDataPoint(genMode); - return; - } - - //we limit the vertical portion to maxVerticalData - int j = 0; - while (count > verticalSize) - { - limited = true; - ii = DataPointUtil.MAX_WORLD_Y_SIZE; - for (i = 0; i < count - 1; i++) - { - if (heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2] <= ii) - { - ii = heightAndDepth[i * 2 + 1] - heightAndDepth[(i + 1) * 2]; - j = i; - } - } - heightAndDepth[j * 2 + 1] = heightAndDepth[(j + 1) * 2 + 1]; - for (i = j + 1; i < count - 1; i++) - { - heightAndDepth[i * 2] = heightAndDepth[(i + 1) * 2]; - heightAndDepth[i * 2 + 1] = heightAndDepth[(i + 1) * 2 + 1]; - } - //System.arraycopy(heightAndDepth, j + 1, heightAndDepth, j, count - j - 1); - count--; - } - int yOut; - //As standard the vertical lods are ordered from top to bottom - if (!limited && xSize*zSize == 1) - { - for (yOut = 0; yOut < count; yOut++) - dataIndex = mergeFromX*inputSectionSize*inputVerticalSize + mergeFromZ*inputVerticalSize + yOut; - dataContainer[outBaseIndex + yOut] = lowerDataContainer.dataContainer[dataIndex]; - } - else - { - - //We want to efficiently memorize indexes - int[] dataIndexesCache = tDataIndexCache.get(); - if (dataIndexesCache==null || dataIndexesCache.length != xSize*zSize) { - dataIndexesCache = new int[xSize*zSize]; - tDataIndexCache.set(dataIndexesCache); - } - Arrays.fill(dataIndexesCache,0); - - //For each lod height-depth value we have found we now want to generate the rest of the data - //by merging all lods at lower level that are contained inside the new ones - for (yOut = 0; yOut < count; yOut++) - { - //We firstly collect height and depth data - //this will be added to each realtive long DataPoint - height = heightAndDepth[yOut * 2]; - depth = heightAndDepth[yOut * 2 + 1]; - - //if both height and depth are at 0 then we finished - if ((depth == 0 && height == 0) || yOut >= heightAndDepth.length / 2) - break; - - //We initialize data useful for the merge - int numberOfChildren = 0; - allEmpty = true; - allVoid = true; - - //We initialize all the new values that we are going to put in the dataPoint - int tempAlpha = 0; - int tempRed = 0; - int tempGreen = 0; - int tempBlue = 0; - int tempLightBlock = 0; - int tempLightSky = 0; - long data = 0; - - int index; - - //For each position that we want to merge - for(x = mergeFromX; x <= mergeToX; x++) - { - for (z = mergeFromZ; z <= mergeToZ; z++) - { - index = x * xSize + z; - //we scan the lods in the position from top to bottom - while (dataIndexesCache[index] < inputVerticalSize) - { - y = dataIndexesCache[index]; - dataIndex = x * inputSectionSize * inputVerticalSize + z * inputVerticalSize + y; - - singleData = lowerDataContainer.dataContainer[dataIndex]; - if (DataPointUtil.doesItExist(singleData) && !DataPointUtil.isVoid(singleData)) - { - dataIndexesCache[index]++; - if ((depth <= DataPointUtil.getDepth(singleData) && DataPointUtil.getDepth(singleData) < height) - || (depth < DataPointUtil.getHeight(singleData) && DataPointUtil.getHeight(singleData) <= height)) - { - data = singleData; - break; - } - } - else - break; - } - if (!DataPointUtil.doesItExist(data)) - { - data = DataPointUtil.createVoidDataPoint(genMode); - } - - if (DataPointUtil.doesItExist(data)) - { - allEmpty = false; - if (!DataPointUtil.isVoid(data)) - { - numberOfChildren++; - allVoid = false; - tempAlpha = Math.max(DataPointUtil.getAlpha(data), tempAlpha); - tempRed += DataPointUtil.getRed(data) * DataPointUtil.getRed(data); - tempGreen += DataPointUtil.getGreen(data) * DataPointUtil.getGreen(data); - tempBlue += DataPointUtil.getBlue(data) * DataPointUtil.getBlue(data); - tempLightBlock += DataPointUtil.getLightBlock(data); - tempLightSky += DataPointUtil.getLightSky(data); - } - } - } - } - - if (allEmpty) - //no child has been initialized - dataContainer[outBaseIndex + yOut] = DataPointUtil.EMPTY_DATA; - else if (allVoid) - //all the children are void - dataContainer[outBaseIndex + yOut] = DataPointUtil.createVoidDataPoint(genMode); - else - { - //we have at least 1 child - if (xSize*zSize != 1) - { - tempRed = tempRed / numberOfChildren; - tempGreen = tempGreen / numberOfChildren; - tempBlue = tempBlue / numberOfChildren; - tempLightBlock = tempLightBlock / numberOfChildren; - tempLightSky = tempLightSky / numberOfChildren; - } - //data = createDataPoint(tempAlpha, tempRed, tempGreen, tempBlue, height, depth, tempLightSky, tempLightBlock, tempGenMode, allDefault); - //if (j > 0 && getColor(data) == getColor(dataPoint[j])) - //{ - // add simplification at the end due to color - //} - dataContainer[outBaseIndex + yOut] = DataPointUtil.createDataPoint((int) Math.sqrt(tempAlpha), (int) Math.sqrt(tempRed), (int) Math.sqrt(tempGreen), (int) Math.sqrt(tempBlue), height, depth, tempLightSky, tempLightBlock, genMode); - } - } - } - } - - public static LodDataSource load(DhSectionPos pos, InputStream is, int version) { + public static LodDataSource loadFile(DHLevel level, DhSectionPos pos, InputStream is, int version) { try (DataInputStream dis = new DataInputStream(is)) { - return new ColumnDatatype(pos, dis, version); + return new ColumnDatatype(pos, dis, version, level); } catch (IOException e) { //FIXME: Log error return null; } } - public static class ColumnRenderSourceLoader extends RenderDataSourceLoader { - @Override - public RenderDataSource construct(LodDataSource[] dataSources, DhSectionPos sectionPos, DHLevel level) { - // Select the direct one first - for (LodDataSource dataSource : dataSources) { - if (dataSource instanceof ColumnDatatype) { - return (RenderDataSource) dataSource; + public static RenderDataSourceLoader COLUMN_LAYER_LOADER = new RenderDataSourceLoader(4) { + @Override + public RenderDataSource construct(List dataSources, DhSectionPos sectionPos, DHLevel level) { + if (dataSources.size() == 0) return null; + + // Check for direct casting + if (dataSources.size() == 1 && dataSources.get(0) instanceof ColumnDatatype + && dataSources.get(0).getSectionPos().equals(sectionPos) + && dataSources.get(0).getDataDetail() == sectionPos.sectionDetail-SECTION_SIZE_OFFSET) { + // Directly using the data source as the render data source is possible. + return (ColumnDatatype) dataSources.get(0); } + + // Otherwise, we need to create a new render data source, and copy the data from the data sources. + ColumnDatatype renderDataSource = new ColumnDatatype(sectionPos, + DetailDistanceUtil.getMaxVerticalData(sectionPos.sectionDetail-SECTION_SIZE_OFFSET), + level.getMinY()); + boolean completeCopy = dataSources.get(0).getSectionPos().getWidth().toBlock() >= sectionPos.getWidth().toBlock(); + + if (completeCopy) { + // If there is only one data source, we need to insure on copy, we don't copy out of bounds as we + // may just need to copy partial section of the data source. + LodUtil.assertTrue(dataSources.size() == 1, "Expected only one data source for complete copy"); + byte targetDataLevel = (byte) (sectionPos.sectionDetail-SECTION_SIZE_OFFSET); + byte sourceDataLevel = dataSources.get(0).getDataDetail(); + LodUtil.assertTrue(targetDataLevel >= sourceDataLevel); + if (dataSources.get(0) instanceof ColumnDatatype) { + ColumnDatatype dataSource = (ColumnDatatype) dataSources.get(0); + DhSectionPos srcPos = dataSource.getSectionPos(); + + // Note that in here, the source data level will be always < target section level + int trgX = sectionPos.getCorner().getX().toBlock(); + int trgZ = sectionPos.getCorner().getZ().toBlock(); + int trgMaxX = trgX + sectionPos.getWidth().toBlock() - 1; + int trgMaxZ = trgZ + sectionPos.getWidth().toBlock() - 1; + int trgXSizeInSrc = (trgX >> sourceDataLevel) - (trgMaxX >> sourceDataLevel) + 1; + int trgZSizeInSrc = (trgZ >> sourceDataLevel) - (trgMaxZ >> sourceDataLevel) + 1; + int trgXInSrc = (trgX >> sourceDataLevel) % srcPos.getWidth(sourceDataLevel).value; + int trgZInSrc = (trgZ >> sourceDataLevel) % srcPos.getWidth(sourceDataLevel).value; + + ColumnQuadView srcView = dataSource.getDataInQuad(trgXInSrc, trgZInSrc, trgXSizeInSrc, trgZSizeInSrc); + ColumnQuadView trgView = renderDataSource.getFullQuad(); + trgView.mergeMultiColumnFrom(srcView); + } else { + if (!(dataSources.get(0) instanceof FullDatatype)) + throw new IllegalArgumentException("Unsupported data source type: " + dataSources.get(0).getClass().getName()); + FullDatatype dataSource = (FullDatatype) dataSources.get(0); + DhSectionPos srcPos = dataSource.getSectionPos(); + //TODO: Impl this + LodUtil.assertTrue(false,"Not implemented yet"); + } + } else { + // If there are multiple data sources, we need to merge them into the target data source + for (LodDataSource dataSource : dataSources) { + byte targetDataLevel = (byte) (sectionPos.sectionDetail-SECTION_SIZE_OFFSET); + byte sourceDataLevel = dataSource.getDataDetail(); + DhSectionPos srcPos = dataSource.getSectionPos(); + + if (dataSource instanceof ColumnDatatype) { + ColumnDatatype clDataSource = (ColumnDatatype) dataSource; + + // Note that targetDataLevel can be > source section level + int srcX = srcPos.getCorner().getX().toBlock(); + int srcZ = srcPos.getCorner().getZ().toBlock(); + int srcMaxX = srcX + srcPos.getWidth().toBlock() - 1; + int srcMaxZ = srcZ + srcPos.getWidth().toBlock() - 1; + int srcXSizeInTrg = (srcX >> targetDataLevel) - (srcMaxX >> targetDataLevel) + 1; + int srcZSizeInTrg = (srcZ >> targetDataLevel) - (srcMaxZ >> targetDataLevel) + 1; + int srcXInTrg = (srcX >> targetDataLevel) % SECTION_SIZE; + int srcZInTrg = (srcZ >> targetDataLevel) % SECTION_SIZE; + + ColumnQuadView srcView = clDataSource.getFullQuad(); + ColumnQuadView trgView = renderDataSource.getDataInQuad(srcXInTrg, srcZInTrg, srcXSizeInTrg, srcZSizeInTrg); + trgView.mergeMultiColumnFrom(srcView); + } else { + if (!(dataSource instanceof FullDatatype)) + throw new IllegalArgumentException("Unsupported data source type: " + dataSource.getClass().getName()); + FullDatatype flDataSource = (FullDatatype) dataSource; + //TODO: Impl this + LodUtil.assertTrue(false,"Not implemented yet"); + } + } + } + + return renderDataSource; } + @Override + public List selectFiles(DhSectionPos sectionPos, DHLevel level, List[] availableFiles) { + byte targetDataLevel = (byte) (sectionPos.sectionDetail - SECTION_SIZE_OFFSET); + //No support for loading higher than the target level yet. + byte maxDataLevel = LodUtil.min((byte) (availableFiles.length-1), targetDataLevel); + byte topValidDataLevel = Byte.MIN_VALUE; + List selectedFiles = new LinkedList<>(); - // Select the one that is from lower level - } + for (int detail = maxDataLevel; detail >= 0; detail--) { + if (availableFiles[detail] == null) continue; + if (topValidDataLevel == Byte.MIN_VALUE) { + for (DataFile dataFile : availableFiles[detail]) { + if (dataFile.dataLevel > targetDataLevel) continue; + if (dataFile.dataType == ColumnDatatype.class || dataFile.dataType == FullDatatype.class) { + topValidDataLevel = LodUtil.max(topValidDataLevel, dataFile.dataLevel); + break; + } + } + } + if (topValidDataLevel == Byte.MIN_VALUE) continue; + + + DataFile singleCoveringColumnFile = null; + DataFile singleCoveringFullFile = null; + + for (DataFile dataFile : availableFiles[detail]) { + if (dataFile.pos.getWidth().toBlock() == sectionPos.getWidth().toBlock()) { + if (dataFile.dataType == ColumnDatatype.class) { + singleCoveringColumnFile = dataFile; + break; + } + else if (dataFile.dataType == FullDatatype.class) { + singleCoveringFullFile = dataFile; + // Don't break as there may be a column file later. + } + } else if (dataFile.pos.getWidth().toBlock() > sectionPos.getWidth().toBlock()) { + if (dataFile.dataType == ColumnDatatype.class && singleCoveringColumnFile == null) + singleCoveringColumnFile = dataFile; + else if (dataFile.dataType == FullDatatype.class && singleCoveringFullFile == null) + singleCoveringFullFile = dataFile; + } + } + + // First, try select single file that has enough width to cover the section + if (singleCoveringColumnFile != null) return Collections.singletonList(singleCoveringColumnFile); + if (singleCoveringFullFile != null) return Collections.singletonList(singleCoveringFullFile); + + // If no single file covers the section, try to select all files without any duplicates + for (DataFile dataFile : availableFiles[detail]) { + boolean isDuplicate = false; + boolean isSet = false; + for (int i = 0; i < selectedFiles.size(); i++) { + DataFile selectedFile = selectedFiles.get(i); + if (selectedFile == null) continue; + if (selectedFile.pos.overlaps(dataFile.pos)) { + // Now, the already selected file muct have same or higher data level + // so, we just select the file with a position that covers the most area. + // Therefore, we choose the file with the higher section level. + if (selectedFile.pos.sectionDetail < dataFile.pos.sectionDetail) { + if (isSet) selectedFiles.set(i, null); + else selectedFiles.set(i, dataFile); + isSet = true; + } else { + LodUtil.assertTrue(!isSet); // We should not have encountered a smaller section level. + // This mean its completely covered by the selected file, so we can skip it. + isDuplicate = true; + break; + } + } + } + if (!isDuplicate && !isSet) selectedFiles.add(dataFile); + } + } + if (topValidDataLevel == Byte.MIN_VALUE) return Collections.emptyList(); + selectedFiles.removeIf(Objects::isNull); + return selectedFiles; + } + }; + static { + LodQuadTree.registerLayerLoader(COLUMN_LAYER_LOADER, (byte) 7); // 7 or above } - - - - public static RenderDataSource loadByCasting(LodDataSource dataSource, DhSectionPos sectionPos) { - if (dataSource instanceof ColumnDatatype) { - return (RenderDataSource) dataSource; - } - return null; - } -// public static RenderDataSource loadByCopying(LodDataSource dataSource, DhSectionPos sectionPos) { -// -// ColumnDatatype columns = new ColumnDatatype(sectionPos, dataSource, -// DetailDistanceUtil.getMaxVerticalData(dataDetail)); -// //TODO -// -// return null; -// } -// static { -// RenderDataSource.registorLoader(ColumnDatatype::loadByCasting, 100); -// } - @Override public DataSourceLoader getLatestLoader() { - return (DhSectionPos sectionPos, InputStream is) -> load(sectionPos, is, LATEST_VERSION); + return (DHLevel level, DhSectionPos sectionPos, InputStream data) -> loadFile(level, sectionPos, data, LATEST_VERSION); } @Override diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnQuadView.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnQuadView.java new file mode 100644 index 000000000..e4e8d21c4 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnQuadView.java @@ -0,0 +1,127 @@ +package com.seibel.lod.core.objects.a7.datatype.column; + +import java.util.Iterator; + +public class ColumnQuadView implements ColumnDataView { + private final long[] data; + private final int perRowOffset; // per row offset in longs + private final int xSize; // x size in datapoints + private final int zSize; // x size in datapoints + private final int offset; // offset in longs + private final int vertSize; // vertical size in longs + + public ColumnQuadView(long[] data, int dataZWidth, int dataVertSize, int viewXOffset, int viewZOffset, int xSize, int zSize) { + if (viewXOffset + xSize > (data.length / (dataZWidth* dataVertSize)) || viewZOffset + zSize > dataZWidth) + throw new IllegalArgumentException("View is out of bounds"); + this.data = data; + this.xSize = xSize; + this.zSize = zSize; + this.vertSize = dataVertSize; + this.perRowOffset = dataZWidth * dataVertSize; + this.offset = (viewXOffset * perRowOffset + viewZOffset) * dataVertSize; + } + private ColumnQuadView(long[] data, int perRowOffset, int offset, int vertSize, int xSize, int zSize) { + this.data = data; + this.perRowOffset = perRowOffset; + this.offset = offset; + this.vertSize = vertSize; + this.xSize = xSize; + this.zSize = zSize; + } + + @Override + public long get(int index) { + int x = index % (xSize * vertSize); + int z = index / (xSize * vertSize); + int v = index % vertSize; + return get(x, z, v); + } + + public long get(int x, int z, int v) { + return data[offset + x * perRowOffset + z * vertSize + v]; + } + + public long set(int x, int z, int v, long value) { + return data[offset + x * perRowOffset + z * vertSize + v] = value; + } + + public ColumnArrayView get(int x, int z) { + return new ColumnArrayView(data, vertSize, offset + x * perRowOffset + z * vertSize, vertSize); + } + + public ColumnArrayView getRow(int x) { + return new ColumnArrayView(data, vertSize, offset + x * perRowOffset, zSize * vertSize); + } + + public void set(int x, int z, ColumnDataView singleColumn) { + if (singleColumn.verticalSize() != vertSize) throw new IllegalArgumentException("Vertical size of singleColumn must be equal to vertSize"); + if (singleColumn.dataCount() != 1) throw new IllegalArgumentException("SingleColumn must contain exactly one data point"); + singleColumn.copyTo(data, x * perRowOffset + z * vertSize); + } + + @Override + public int size() { + return xSize * zSize * vertSize; + } + + @Override + public int verticalSize() { + return vertSize; + } + + @Override + public int dataCount() { + return xSize * zSize; + } + + @Override + public ColumnDataView subView(int dataIndexStart, int dataCount) { + if (dataCount != 1) throw new UnsupportedOperationException("Fixme: subView for QUadView only support one data point!"); + int x = dataIndexStart % xSize; + int z = dataIndexStart / xSize; + return new ColumnArrayView(data, vertSize, offset + x * perRowOffset + z * vertSize, vertSize); + } + + public ColumnQuadView subView(int xOffset, int zOffset, int xSize, int zSize) { + if (xOffset + xSize > this.xSize || zOffset + zSize > this.zSize) throw new IllegalArgumentException("SubView is out of bounds"); + return new ColumnQuadView(data, perRowOffset, offset + xOffset * perRowOffset + zOffset * vertSize, vertSize, xSize, zSize); + } + + @Override + public void copyTo(long[] target, int offset) { + for (int x = 0; x < xSize; x++) { + System.arraycopy(data, this.offset + x * perRowOffset, target, offset + x * xSize * vertSize, zSize * vertSize); + } + } + + public void copyTo(ColumnQuadView target) { + if (target.xSize != xSize || target.zSize != zSize) + throw new IllegalArgumentException("Target view must have same size as this view"); + + for (int x = 0; x < xSize; x++) { + target.getRow(x).changeVerticalSizeFrom(getRow(x)); + } + } + + public void mergeMultiColumnFrom(ColumnQuadView source) { + if (source.xSize == xSize && source.zSize == zSize) + { + source.copyTo(this); + return; + } + if (source.xSize < xSize || source.zSize < zSize) + throw new IllegalArgumentException("Source view must have same or larger size as this view"); + + int srcXPerTrgX = source.xSize / xSize; + int srcZPerTrgZ = source.zSize / zSize; + if (source.xSize % xSize != 0 || source.zSize % zSize != 0) + throw new IllegalArgumentException("Source view's size must be a multiple of this view's size"); + + for (int x = 0; x < xSize; x++) { + for (int z = 0; z < zSize; z++) { + ColumnQuadView srcBlock = source.subView(x * srcXPerTrgX, z * srcZPerTrgZ, srcXPerTrgX, srcZPerTrgZ); + get(x, z).mergeMultiDataFrom(srcBlock); + } + } + } +} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/full/FullDatatype.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/full/FullDatatype.java index 9e1eb0a2c..b89f5ecb4 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/datatype/full/FullDatatype.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/full/FullDatatype.java @@ -1,4 +1,26 @@ package com.seibel.lod.core.objects.a7.datatype.full; -public class FullDatatype { +import com.seibel.lod.core.objects.a7.data.LodDataSource; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; + +public class FullDatatype implements LodDataSource { + @Override + public DataSourceLoader getLatestLoader() { + return null; + } + + @Override + public T[] getData() { + return null; + } + + @Override + public DhSectionPos getSectionPos() { + return null; + } + + @Override + public byte getDataDetail() { + return 0; + } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodPos.java b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodPos.java index 971a48c89..3f32be619 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodPos.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodPos.java @@ -69,4 +69,9 @@ public class DhLodPos { return this.equals(other.convertUpwardsTo(this.detail)); } } + + public DhLodPos add(DhLodUnit width) { + if (width.detail < detail) throw new IllegalArgumentException("add called with width.detail < pos detail"); + return new DhLodPos(detail, x + width.convertTo(detail).value, z + width.convertTo(detail).value); + } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodUnit.java b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodUnit.java index f3b880e5a..1d6b335e9 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodUnit.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodUnit.java @@ -22,8 +22,8 @@ public class DhLodUnit { return this; } if (detail > targetDetail) { //TODO check if this is correct - return new DhLodUnit(targetDetail, value >> (detail - targetDetail)); + return new DhLodUnit(targetDetail, value << (detail - targetDetail)); } - return new DhLodUnit(targetDetail, value << (targetDetail - detail)); + return new DhLodUnit(targetDetail, value >> (targetDetail - detail)); } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java index f61f8a1a8..1596b06e5 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSource.java @@ -1,9 +1,5 @@ package com.seibel.lod.core.objects.a7.render; -import com.seibel.lod.core.objects.a7.DHLevel; -import com.seibel.lod.core.objects.a7.data.LodDataSource; -import com.seibel.lod.core.objects.a7.data.DataFile; -import com.seibel.lod.core.objects.a7.pos.DhSectionPos; import com.seibel.lod.core.objects.opengl.RenderBuffer; import java.util.*; @@ -47,15 +43,6 @@ public interface RenderDataSource { // return null; // } - abstract class RenderDataSourceLoader { - public abstract RenderDataSource construct(LodDataSource[] dataSources, DhSectionPos sectionPos, DHLevel level); - - public Set selectFiles(DhSectionPos sectionPos, DHLevel level, Set availableFiles) { - return Collections.singleton(availableFiles.iterator().next()); - } - - } - void enableRender(); void disableRender(); boolean isRenderReady(); diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSourceLoader.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSourceLoader.java new file mode 100644 index 000000000..6f5f5ab18 --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderDataSourceLoader.java @@ -0,0 +1,23 @@ +package com.seibel.lod.core.objects.a7.render; + +import com.seibel.lod.core.objects.a7.DHLevel; +import com.seibel.lod.core.objects.a7.data.DataFile; +import com.seibel.lod.core.objects.a7.data.LodDataSource; +import com.seibel.lod.core.objects.a7.pos.DhSectionPos; + +import java.util.*; +import java.util.stream.Collectors; + +public abstract class RenderDataSourceLoader { + public final int detailOffset; + public RenderDataSourceLoader(int detailOffset) { + this.detailOffset = detailOffset; + } + + public abstract RenderDataSource construct(List dataSources, DhSectionPos sectionPos, DHLevel level); + + public List selectFiles(DhSectionPos sectionPos, DHLevel level, List[] availableFiles) { + return Arrays.stream(availableFiles).flatMap(Collection::stream).collect(Collectors.toList()); + } + +}