diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IFullDataSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IFullDataSource.java
index 28e04c995..f30c616a2 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IFullDataSource.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/interfaces/IFullDataSource.java
@@ -93,172 +93,6 @@ public interface IFullDataSource
FullDataPointIdMap getMapping();
- /**
- * @param generatorDetailLevel the detail level that the un-generated positions should be split into.
- * @param onlyReturnPositionsTheGeneratorCanAccept
- * If true this will only return positions with the same detail level as generatorDetailLevel.
- * If false this will return the lowest detail level possible for totally empty sections.
- * As of 2023-9-28 both have been tested and confirmed working with the Batch world generator, the only difference is that
- * the task list will be deceptively small if this value is false.
- *
- * @return the list of {@link DhSectionPos} that aren't generated in this data source.
- */
- default ArrayList getUngeneratedPosList(byte generatorDetailLevel, boolean onlyReturnPositionsTheGeneratorCanAccept)
- {
- ArrayList posArray = this.getUngeneratedPosListForQuadrant(this.getSectionPos(), generatorDetailLevel);
-
- if (onlyReturnPositionsTheGeneratorCanAccept)
- {
- LinkedList posList = new LinkedList<>(posArray);
-
- // subdivide positions until they match the generatorDetailLevel
- ArrayList cleanedPosArray = new ArrayList<>();
- while (posList.size() > 0)
- {
- DhSectionPos pos = posList.remove();
- if (pos.getDetailLevel() > generatorDetailLevel)
- {
- pos.forEachChild((childPos) -> posList.push(childPos));
- }
- else
- {
- cleanedPosArray.add(pos);
- }
- }
-
- return cleanedPosArray;
- }
- else
- {
- return posArray;
- }
- }
- default ArrayList getUngeneratedPosListForQuadrant(DhSectionPos quadrantPos, byte generatorDetailLevel)
- {
- ArrayList ungeneratedPosList = new ArrayList<>();
-
- int sourceRelWidthInDataPoints = this.getWidthInDataPoints();
-
-
- if (quadrantPos.getDetailLevel() == generatorDetailLevel)
- {
- // we are at the highest detail level the world generator can accept,
- // we either need to generate this whole section, or not at all
-
- ESectionPopulationState populationState = this.getPopulationStateForPos(quadrantPos, sourceRelWidthInDataPoints);
- if (populationState != ESectionPopulationState.COMPLETE)
- {
- // at least 1 data point is missing,
- // this whole section must be generated
- ungeneratedPosList.add(quadrantPos);
- }
- }
- else if (quadrantPos.getDetailLevel() > generatorDetailLevel)
- {
- // detail level too low for world generator, check child positions
- for (int i = 0; i < 4; i++)
- {
- DhSectionPos inputPos = quadrantPos.getChildByIndex(i);
-
- ESectionPopulationState populationState = this.getPopulationStateForPos(inputPos, sourceRelWidthInDataPoints);
- if (populationState == ESectionPopulationState.COMPLETE)
- {
- // no generation necessary
- continue;
- }
- else if (populationState == ESectionPopulationState.EMPTY)
- {
- // nothing exists for this sub quadrant, add this sub-quadrant's position
- ungeneratedPosList.add(inputPos);
- }
- else if (populationState == ESectionPopulationState.PARTIAL)
- {
- // some data exists in this quadrant, but not all that we need
- // recurse down to determine which sub-quadrant positions will need generation
- ungeneratedPosList.addAll(this.getUngeneratedPosListForQuadrant(inputPos, generatorDetailLevel));
- }
- }
- }
- else
- {
- throw new IllegalArgumentException("detail level lower than world generator can accept.");
- }
-
- return ungeneratedPosList;
- }
- default ESectionPopulationState getPopulationStateForPos(DhSectionPos inputPos, int sourceRelWidthInDataPoints)
- {
- // TODO comment
-
- byte childDetailLevel = inputPos.getDetailLevel();
-
- int quadrantDetailLevelDiff = this.getSectionPos().getDetailLevel() - childDetailLevel;
- int widthInSecPos = BitShiftUtil.powerOfTwo(quadrantDetailLevelDiff);
- int relWidthForSecPos = sourceRelWidthInDataPoints / widthInSecPos;
-
- DhSectionPos minSecPos = this.getSectionPos().convertNewToDetailLevel(childDetailLevel);
-
-
-
- int minRelX = inputPos.getX() - minSecPos.getX();
- int maxRelX = minRelX + 1;
-
- int minRelZ = inputPos.getZ() - minSecPos.getZ();
- int maxRelZ = minRelZ + 1;
-
- minRelX = minRelX * relWidthForSecPos;
- maxRelX = maxRelX * relWidthForSecPos;
-
- minRelZ = minRelZ * relWidthForSecPos;
- maxRelZ = maxRelZ * relWidthForSecPos;
-
-
-
- boolean quadrantFullyGenerated = true;
- boolean quadrantEmpty = true;
- for (int relX = minRelX; relX < maxRelX; relX++)
- {
- for (int relZ = minRelZ; relZ < maxRelZ; relZ++)
- {
- SingleColumnFullDataAccessor column = this.tryGet(relX, relZ);
- if (column == null || !column.doesColumnExist())
- {
- // no data for this relative position
- quadrantFullyGenerated = false;
- }
- else
- {
- // data exists for this pos
- quadrantEmpty = false;
- }
- }
- }
-
-
- if (quadrantFullyGenerated)
- {
- // no generation necessary
- return ESectionPopulationState.COMPLETE;
- }
- else if (quadrantEmpty)
- {
- // nothing exists for this sub quadrant, add this sub-quadrant's position
- return ESectionPopulationState.EMPTY;
- }
- else
- {
- // some data exists in this quadrant, but not all that we need
- // recurse down to determine which sub-quadrant positions will need generation
- return ESectionPopulationState.PARTIAL;
- }
- }
- enum ESectionPopulationState
- {
- COMPLETE,
- EMPTY,
- PARTIAL
- }
-
//=======================//
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataFileHandler.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataFileHandler.java
index 65127c8ab..476ea1ffb 100644
--- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataFileHandler.java
+++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataFileHandler.java
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.CompleteFull
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IIncompleteFullDataSource;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
+import com.seibel.distanthorizons.core.generation.MissingWorldGenPositionFinder;
import com.seibel.distanthorizons.core.generation.IWorldGenerationQueue;
import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker;
import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult;
@@ -344,7 +345,7 @@ public class GeneratedFullDataFileHandler extends FullDataFileHandler
metaFile.genQueueChecked = true;
byte minGeneratorSectionDetailLevel = (byte) (worldGenQueue.smallestDataDetail() + DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
- ArrayList genPosList = dataSource.getUngeneratedPosList(minGeneratorSectionDetailLevel, true);
+ ArrayList genPosList = MissingWorldGenPositionFinder.getUngeneratedPosList(dataSource, minGeneratorSectionDetailLevel, true);
for (DhSectionPos genPos : genPosList)
{
diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/MissingWorldGenPositionFinder.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/MissingWorldGenPositionFinder.java
new file mode 100644
index 000000000..f6e554dd8
--- /dev/null
+++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/MissingWorldGenPositionFinder.java
@@ -0,0 +1,213 @@
+/*
+ * This file is part of the Distant Horizons mod
+ * licensed under the GNU LGPL v3 License.
+ *
+ * Copyright (C) 2020-2023 James Seibel
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program. If not, see .
+ */
+
+package com.seibel.distanthorizons.core.generation;
+
+import com.seibel.distanthorizons.core.dataObjects.fullData.accessor.SingleColumnFullDataAccessor;
+import com.seibel.distanthorizons.core.dataObjects.fullData.sources.interfaces.IFullDataSource;
+import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
+import com.seibel.distanthorizons.core.pos.DhSectionPos;
+import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
+import org.apache.logging.log4j.Logger;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * Handles finding any positions in a given {@link IFullDataSource} that
+ * aren't generated.
+ */
+public class MissingWorldGenPositionFinder
+{
+ private static final Logger LOGGER = DhLoggerBuilder.getLogger();
+
+
+ /**
+ * @param generatorDetailLevel the detail level that the un-generated positions should be split into.
+ * @param onlyReturnPositionsTheGeneratorCanAccept
+ * If true this will only return positions with the same detail level as generatorDetailLevel.
+ * If false this will return the lowest detail level possible for totally empty sections.
+ * As of 2023-9-28 both have been tested and confirmed working with the Batch world generator, the only difference is that
+ * the task list will be deceptively small if this value is false.
+ *
+ * @return the list of {@link DhSectionPos} that aren't generated in this data source.
+ */
+ public static ArrayList getUngeneratedPosList(IFullDataSource dataSource, byte generatorDetailLevel, boolean onlyReturnPositionsTheGeneratorCanAccept)
+ {
+ ArrayList posArray = getUngeneratedPosListForQuadrant(dataSource, dataSource.getSectionPos(), generatorDetailLevel);
+
+ if (onlyReturnPositionsTheGeneratorCanAccept)
+ {
+ LinkedList posList = new LinkedList<>(posArray);
+
+ // subdivide positions until they match the generatorDetailLevel
+ ArrayList cleanedPosArray = new ArrayList<>();
+ while (posList.size() > 0)
+ {
+ DhSectionPos pos = posList.remove();
+ if (pos.getDetailLevel() > generatorDetailLevel)
+ {
+ pos.forEachChild((childPos) -> posList.push(childPos));
+ }
+ else
+ {
+ cleanedPosArray.add(pos);
+ }
+ }
+
+ return cleanedPosArray;
+ }
+ else
+ {
+ return posArray;
+ }
+ }
+ private static ArrayList getUngeneratedPosListForQuadrant(IFullDataSource dataSource, DhSectionPos quadrantPos, byte generatorDetailLevel)
+ {
+ ArrayList ungeneratedPosList = new ArrayList<>();
+
+ int sourceRelWidthInDataPoints = dataSource.getWidthInDataPoints();
+
+
+ if (quadrantPos.getDetailLevel() == generatorDetailLevel)
+ {
+ // we are at the highest detail level the world generator can accept,
+ // we either need to generate this whole section, or not at all
+
+ ESectionPopulationState populationState = getPopulationStateForPos(dataSource, quadrantPos, sourceRelWidthInDataPoints);
+ if (populationState != ESectionPopulationState.COMPLETE)
+ {
+ // at least 1 data point is missing,
+ // this whole section must be generated
+ ungeneratedPosList.add(quadrantPos);
+ }
+ }
+ else if (quadrantPos.getDetailLevel() > generatorDetailLevel)
+ {
+ // detail level too low for world generator, check child positions
+ for (int i = 0; i < 4; i++)
+ {
+ DhSectionPos inputPos = quadrantPos.getChildByIndex(i);
+
+ ESectionPopulationState populationState = getPopulationStateForPos(dataSource, inputPos, sourceRelWidthInDataPoints);
+ if (populationState == ESectionPopulationState.COMPLETE)
+ {
+ // no generation necessary
+ continue;
+ }
+ else if (populationState == ESectionPopulationState.EMPTY)
+ {
+ // nothing exists for this sub quadrant, add this sub-quadrant's position
+ ungeneratedPosList.add(inputPos);
+ }
+ else if (populationState == ESectionPopulationState.PARTIAL)
+ {
+ // some data exists in this quadrant, but not all that we need
+ // recurse down to determine which sub-quadrant positions will need generation
+ ungeneratedPosList.addAll(getUngeneratedPosListForQuadrant(dataSource, inputPos, generatorDetailLevel));
+ }
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException("detail level lower than world generator can accept.");
+ }
+
+ return ungeneratedPosList;
+ }
+ private static ESectionPopulationState getPopulationStateForPos(IFullDataSource dataSource, DhSectionPos inputPos, int sourceRelWidthInDataPoints)
+ {
+ // TODO comment
+
+ byte childDetailLevel = inputPos.getDetailLevel();
+
+ int quadrantDetailLevelDiff = dataSource.getSectionPos().getDetailLevel() - childDetailLevel;
+ int widthInSecPos = BitShiftUtil.powerOfTwo(quadrantDetailLevelDiff);
+ int relWidthForSecPos = sourceRelWidthInDataPoints / widthInSecPos;
+
+ DhSectionPos minSecPos = dataSource.getSectionPos().convertNewToDetailLevel(childDetailLevel);
+
+
+
+ int minRelX = inputPos.getX() - minSecPos.getX();
+ int maxRelX = minRelX + 1;
+
+ int minRelZ = inputPos.getZ() - minSecPos.getZ();
+ int maxRelZ = minRelZ + 1;
+
+ minRelX = minRelX * relWidthForSecPos;
+ maxRelX = maxRelX * relWidthForSecPos;
+
+ minRelZ = minRelZ * relWidthForSecPos;
+ maxRelZ = maxRelZ * relWidthForSecPos;
+
+
+
+ boolean quadrantFullyGenerated = true;
+ boolean quadrantEmpty = true;
+ for (int relX = minRelX; relX < maxRelX; relX++)
+ {
+ for (int relZ = minRelZ; relZ < maxRelZ; relZ++)
+ {
+ SingleColumnFullDataAccessor column = dataSource.tryGet(relX, relZ);
+ if (column == null || !column.doesColumnExist())
+ {
+ // no data for this relative position
+ quadrantFullyGenerated = false;
+ }
+ else
+ {
+ // data exists for this pos
+ quadrantEmpty = false;
+ }
+ }
+ }
+
+
+ if (quadrantFullyGenerated)
+ {
+ // no generation necessary
+ return ESectionPopulationState.COMPLETE;
+ }
+ else if (quadrantEmpty)
+ {
+ // nothing exists for this sub quadrant, add this sub-quadrant's position
+ return ESectionPopulationState.EMPTY;
+ }
+ else
+ {
+ // some data exists in this quadrant, but not all that we need
+ // recurse down to determine which sub-quadrant positions will need generation
+ return ESectionPopulationState.PARTIAL;
+ }
+ }
+
+
+
+ //================//
+ // helper classes //
+ //================//
+
+ private enum ESectionPopulationState
+ {
+ COMPLETE,
+ EMPTY,
+ PARTIAL
+ }
+}