/*
* This file is part of the Distant Horizon mod (formerly the LOD Mod),
* licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
*
* Coordinate Standard:
* Coordinate called posX or posZ are relative LevelPos coordinates
* unless stated otherwise.
*
* @author Leonardo Amato
* @version 10-10-2021
*/
public class LodRegion
{
/**
* Number of detail level supported by a region
*/
private static final byte POSSIBLE_LOD = 10;
/**
* Holds the lowest (least detailed) detail level in this region
*/
private byte minDetailLevel;
/**
* This holds all data for this region
*/
private final LevelContainer[] dataContainer;
/**
* This chunk Pos has been generated
*/
//private final boolean[] preGeneratedChunkPos;
/**
* the generation mode for this region
*/
private DistanceGenerationMode generationMode;
/**
* the vertical quality of this region
*/
private VerticalQuality verticalQuality;
/**
* this region's x RegionPos
*/
public final int regionPosX;
/**
* this region's z RegionPos
*/
public final int regionPosZ;
public LodRegion(byte minDetailLevel, RegionPos regionPos, DistanceGenerationMode generationMode, VerticalQuality verticalQuality)
{
this.minDetailLevel = minDetailLevel;
this.regionPosX = regionPos.x;
this.regionPosZ = regionPos.z;
this.verticalQuality = verticalQuality;
this.generationMode = generationMode;
dataContainer = new LevelContainer[POSSIBLE_LOD];
// Initialize all the different matrices
for (byte lod = minDetailLevel; lod <= LodUtil.REGION_DETAIL_LEVEL; lod++)
{
dataContainer[lod] = new VerticalLevelContainer(lod);
}
boolean fileFound = false;
/*
preGeneratedChunkPos = new boolean[32 * 32];
if (MinecraftWrapper.INSTANCE.hasSinglePlayerServer() && LodConfig.CLIENT.worldGenerator.useExperimentalPreGenLoading.get())
{
File regionFileDirHead;
File regionFileDirParent;
// local world
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(MinecraftWrapper.INSTANCE.getCurrentDimension());
// provider needs a separate variable to prevent
// the compiler from complaining
StringBuilder string = new StringBuilder();
try
{
ServerChunkProvider provider = serverWorld.getChunkSource();
//System.out.println(provider.dataStorage.dataFolder);
regionFileDirHead = new File(provider.dataStorage.dataFolder.getCanonicalFile().getParentFile().toPath().toAbsolutePath().toString() + File.separatorChar + "region", "r." + regionPosZ + "." + regionPosX + ".mca");
if (regionFileDirHead.exists())
{
regionFileDirParent = regionFileDirHead.getParentFile();
//string.append(regionFileDirParent.toString());
string.append(regionFileDirHead);
RegionFile regionFile = new RegionFile(regionFileDirHead, regionFileDirParent, true);
for (int x = 0; x < 32; x++)
{
for (int z = 0; z < 32; z++)
{
preGeneratedChunkPos[x * 32 + z] = regionFile.doesChunkExist(new ChunkPos(regionPosX * 32 + x, regionPosZ * 32 + z));
}
}
string.append("region " + regionPosX + " " + regionPosZ + "\n");
for (int x = 0; x < 32; x++)
{
for (int z = 0; z < 32; z++)
{
//regionFile.doesChunkExist()
string.append(preGeneratedChunkPos[x * 32 + z] + "\t");
}
string.append("\n");
}
regionFile.close();
}
}
catch (Exception e)
{
e.printStackTrace();
}
System.out.println(string);
}*/
}
/**
* Return true if the chunk has been pregenerated in game
*/
//public boolean isChunkPreGenerated(int xChunkPos, int zChunkPos)
//{
// xChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, xChunkPos);
// zChunkPos = LevelPosUtil.getRegionModule(LodUtil.CHUNK_DETAIL_LEVEL, zChunkPos);
// return preGeneratedChunkPos[xChunkPos * 32 + zChunkPos];
//}
/**
* Inserts the data point into the region.
*
* TODO this will always return true unless it has * @return true if the data was added successfully */ public boolean addData(byte detailLevel, int posX, int posZ, int verticalIndex, long data) { posX = LevelPosUtil.getRegionModule(detailLevel, posX); posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); // The dataContainer could have null entries if the // detailLevel changes. if (this.dataContainer[detailLevel] == null) { this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel); } this.dataContainer[detailLevel].addData(data, posX, posZ, verticalIndex); return true; } /** * Inserts the vertical data into the region. *
* TODO this will always return true unless it has * @return true if the data was added successfully */ public boolean addVerticalData(byte detailLevel, int posX, int posZ, long[] data) { //position is already relative //posX = LevelPosUtil.getRegionModule(detailLevel, posX); //posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); // The dataContainer could have null entries if the // detailLevel changes. if (this.dataContainer[detailLevel] == null) this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel); return this.dataContainer[detailLevel].addVerticalData(data, posX, posZ); } /** * Get the dataPoint at the given relative position. * @return the data at the relative pos and detail level, * 0 if the data doesn't exist. */ public long getData(byte detailLevel, int posX, int posZ, int verticalIndex) { return dataContainer[detailLevel].getData(posX, posZ, verticalIndex); } /** * Get the dataPoint at the given relative position. * @return the data at the relative pos and detail level, * 0 if the data doesn't exist. */ public long getSingleData(byte detailLevel, int posX, int posZ) { return dataContainer[detailLevel].getSingleData(posX, posZ); } /** * Clears the datapoint at the given relative position */ public void clear(byte detailLevel, int posX, int posZ) { dataContainer[detailLevel].clear(posX, posZ); } /** * This method will fill the posToGenerate array with all levelPos that * are render-able. *
* TODO why don't we return the posToGenerate, it would make this easier to understand */ public void getPosToGenerate(PosToGenerateContainer posToGenerate, int playerBlockPosX, int playerBlockPosZ) { getPosToGenerate(posToGenerate, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerBlockPosX, playerBlockPosZ); } /** * A recursive method that fills the posToGenerate array with all levelPos that * need to be generated. *
* TODO why don't we return the posToGenerate, it would make this easier to understand */ private void getPosToGenerate(PosToGenerateContainer posToGenerate, byte detailLevel, int childOffsetPosX, int childOffsetPosZ, int playerPosX, int playerPosZ) { // equivalent to 2^(...) int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); // calculate what LevelPos are in range to generate int maxDistance = LevelPosUtil.maxDistance(detailLevel, childOffsetPosX, childOffsetPosZ, playerPosX, playerPosZ, regionPosX, regionPosZ); // determine this child's levelPos byte childDetailLevel = (byte) (detailLevel - 1); int childPosX = childOffsetPosX * 2; int childPosZ = childOffsetPosZ * 2; int childSize = 1 << (LodUtil.REGION_DETAIL_LEVEL - childDetailLevel); byte targetDetailLevel = DetailDistanceUtil.getLodGenDetail(DetailDistanceUtil.getGenerationDetailFromDistance(maxDistance)).detailLevel; if (targetDetailLevel <= detailLevel) { if (targetDetailLevel == detailLevel) { if (!doesDataExist(detailLevel, childOffsetPosX, childOffsetPosZ)) posToGenerate.addPosToGenerate(detailLevel, childOffsetPosX + regionPosX * size, childOffsetPosZ + regionPosZ * size); } else { // we want at max one request per chunk (since the world generator creates chunks). // So for lod smaller than a chunk, only recurse down // the top right child if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL) { int ungeneratedChildren = 0; // make sure all children are generated to this detailLevel for (int x = 0; x <= 1; x++) { for (int z = 0; z <= 1; z++) { if (!doesDataExist(childDetailLevel, childPosX + x, childPosZ + z)) { ungeneratedChildren++; posToGenerate.addPosToGenerate(childDetailLevel, childPosX + x + regionPosX * childSize, childPosZ + z + regionPosZ * childSize); } } } // only if all the children are correctly generated // should we go deeper if (ungeneratedChildren == 0) for (int x = 0; x <= 1; x++) for (int z = 0; z <= 1; z++) getPosToGenerate(posToGenerate, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ); } else { // The detail Level is smaller than a chunk. // Only recurse down the top right child. if (DetailDistanceUtil.getLodGenDetail(childDetailLevel).detailLevel <= (childDetailLevel)) { if (!doesDataExist(childDetailLevel, childPosX, childPosZ)) posToGenerate.addPosToGenerate(childDetailLevel, childPosX + regionPosX * childSize, childPosZ + regionPosZ * childSize); else getPosToGenerate(posToGenerate, childDetailLevel, childPosX, childPosZ, playerPosX, playerPosZ); } } } } // we have gone beyond the target Detail level // we can stop generating } /** * This method will fill the posToRender array with all levelPos that * are render-able. *
* TODO why don't we return the posToRender, it would make this easier to understand */ public void getPosToRender(PosToRenderContainer posToRender, int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel) { getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel); } /** * This method will fill the posToRender array with all levelPos that * are render-able. *
* TODO why don't we return the posToRender, it would make this easier to understand * TODO this needs some more comments, James was only able to figure out part of it */ private void getPosToRender(PosToRenderContainer posToRender, byte detailLevel, int posX, int posZ, int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel) { // equivalent to 2^(...) int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); byte desiredLevel; int maxDistance; int minDistance; int childLevel; // calculate the LevelPos that are in range maxDistance = LevelPosUtil.maxDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ); desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance)); minDistance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ, regionPosX, regionPosZ); childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance)); if (detailLevel == childLevel - 1) { posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size); } else //if (desiredLevel > detailLevel) //{ // we have gone beyond the target Detail level // we can stop generating //} else if (desiredLevel == detailLevel) { posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size); } else //case where (detailLevel > desiredLevel) { int childPosX = posX * 2; int childPosZ = posZ * 2; byte childDetailLevel = (byte) (detailLevel - 1); int childrenCount = 0; for (int x = 0; x <= 1; x++) { for (int z = 0; z <= 1; z++) { if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z)) { if (!requireCorrectDetailLevel) childrenCount++; else getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel); } } } if (!requireCorrectDetailLevel) { // If all the four children exist go deeper if (childrenCount == 4) { for (int x = 0; x <= 1; x++) for (int z = 0; z <= 1; z++) getPosToRender(posToRender, childDetailLevel, childPosX + x, childPosZ + z, playerPosX, playerPosZ, requireCorrectDetailLevel); } else { posToRender.addPosToRender(detailLevel, posX + regionPosX * size, posZ + regionPosZ * size); } } } } /** * Updates all children. *
* TODO could this be renamed mergeArea? */ public void updateArea(byte detailLevel, int posX, int posZ) { int width; int startX; int startZ; // TODO what are each of these loops updating? for (byte down = (byte) (minDetailLevel + 1); down <= detailLevel; down++) { startX = LevelPosUtil.convert(detailLevel, posX, down); startZ = LevelPosUtil.convert(detailLevel, posZ, down); width = 1 << (detailLevel - down); for (int x = 0; x < width; x++) for (int z = 0; z < width; z++) update(down, startX + x, startZ + z); } for (byte up = (byte) (detailLevel + 1); up <= LodUtil.REGION_DETAIL_LEVEL; up++) { update(up, LevelPosUtil.convert(detailLevel, posX, up), LevelPosUtil.convert(detailLevel, posZ, up)); } } /** * Update the child at the given relative Pos *
* TODO could this be renamed mergeChildData? */ private void update(byte detailLevel, int posX, int posZ) { dataContainer[detailLevel].updateData(dataContainer[detailLevel - 1], posX, posZ); } /** * Returns if data exists at the given relative Pos. */ public boolean doesDataExist(byte detailLevel, int posX, int posZ) { if (detailLevel < minDetailLevel) return false; posX = LevelPosUtil.getRegionModule(detailLevel, posX); posZ = LevelPosUtil.getRegionModule(detailLevel, posZ); if (dataContainer == null || dataContainer[detailLevel] == null) return false; return dataContainer[detailLevel].doesItExist(posX, posZ); } /** * Gets the generation mode for the data point at the given relative pos. */ public byte getGenerationMode(byte detailLevel, int posX, int posZ) { if (dataContainer[detailLevel].doesItExist(posX, posZ)) // We take the bottom information always // TODO what does that mean? bottom of what? return DataPointUtil.getGenerationMode(dataContainer[detailLevel].getSingleData(posX, posZ)); else return DistanceGenerationMode.NONE.complexity; } /** * Returns the lowest (least detailed) detail level in this region * TODO is that right? */ public byte getMinDetailLevel() { return minDetailLevel; } /** * Returns the LevelContainer for the detailLevel * @throws IllegalArgumentException if the detailLevel is less than minDetailLevel */ public LevelContainer getLevel(byte detailLevel) { if (detailLevel < minDetailLevel) throw new IllegalArgumentException("getLevel asked for a detail level that does not exist: minimum: [" + minDetailLevel + "] level requested: [" + detailLevel + "]"); return dataContainer[detailLevel]; } /** * Add the levelContainer to this Region, updating the minDetailLevel * if necessary. * @throws IllegalArgumentException if the LevelContainer's detailLevel * is 2 or more detail levels lower than the * minDetailLevel of this region. */ public void addLevelContainer(LevelContainer levelContainer) { if (levelContainer.getDetailLevel() < minDetailLevel - 1) { throw new IllegalArgumentException( "the LevelContainer's detailLevel was " + "[" + levelContainer.getDetailLevel() + "] but this region " + "only allows adding LevelContainers with a " + "detail level of [" + (minDetailLevel - 1) + "]"); } if (levelContainer.getDetailLevel() == minDetailLevel - 1) minDetailLevel = levelContainer.getDetailLevel(); dataContainer[levelContainer.getDetailLevel()] = levelContainer; } // TODO James thinks cutTree and growTree (which he renamed to match cutTree) // should have more descriptive names, to make sure the "Tree" portion isn't // confused with Minecraft trees (the plant). /** * Removes any dataContainers that are higher than * the given detailLevel */ public void cutTree(byte detailLevel) { if (detailLevel > minDetailLevel) { for (byte detailLevelIndex = 0; detailLevelIndex < detailLevel; detailLevelIndex++) dataContainer[detailLevelIndex] = null; minDetailLevel = detailLevel; } } /** * Make this region more detailed to the detailLevel given. * TODO is that correct? */ public void growTree(byte detailLevel) { if (detailLevel < minDetailLevel) { for (byte detailLevelIndex = (byte) (minDetailLevel - 1); detailLevelIndex >= detailLevel; detailLevelIndex--) { if (dataContainer[detailLevelIndex + 1] == null) dataContainer[detailLevelIndex + 1] = new VerticalLevelContainer((byte) (detailLevelIndex + 1)); dataContainer[detailLevelIndex] = dataContainer[detailLevelIndex + 1].expand(); } minDetailLevel = detailLevel; } } /** * return RegionPos of this lod region */ public RegionPos getRegionPos() { return new RegionPos(regionPosX, regionPosZ); } /** * Returns how many LODs are in this region */ public int getNumberOfLods() { int count = 0; for (LevelContainer container : dataContainer) count += container.getMaxNumberOfLods(); return count; } public VerticalQuality getVerticalQuality() { return verticalQuality; } public DistanceGenerationMode getGenerationMode() { return generationMode; } public int getMaxVerticalData(byte detailLevel) { return dataContainer[detailLevel].getMaxVerticalData(); } @Override public String toString() { return getLevel(LodUtil.REGION_DETAIL_LEVEL).toString(); } }