Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b066327a8 | |||
| 43d0a971f7 | |||
| 9e60c698de | |||
| bf2affa6d1 | |||
| 98f6cea86a | |||
| 9ae01dc1f8 | |||
| 40efc5cbf3 | |||
| 66bba1c80a | |||
| d9f3b31cc5 | |||
| e465ef5325 | |||
| 225385a43f | |||
| 7d7d07416b | |||
| 5ef308cbee | |||
| d61b601c14 | |||
| 246c679a97 | |||
| 4b317a8e00 | |||
| 1debd4b875 | |||
| 5dcda31990 | |||
| ae16ed2341 | |||
| 2c266d2495 | |||
| 7e40546bc5 | |||
| 5d391c83ea | |||
| 0895bf53e3 | |||
| a7203f8f33 | |||
| 22efbb211a | |||
| 95c4459d8a | |||
| 0ef11caaf2 | |||
| 2e3dfab6c3 | |||
| 42be139e94 | |||
| f866e7f8e3 |
+39
-19
@@ -23,43 +23,42 @@ dependencies {
|
|||||||
testImplementation "junit:junit:4.13"
|
testImplementation "junit:junit:4.13"
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
java {
|
||||||
// required for basic shadowJar setup
|
withSourcesJar()
|
||||||
configurations = [project.configurations.shadow]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
task addSourcesToCompiledJar(type: ShadowJar) {
|
task createReleaseApiJar(type: ShadowJar) {
|
||||||
|
|
||||||
|
mustRunAfter sourcesJar
|
||||||
|
dependsOn shadowJar
|
||||||
|
|
||||||
def sourceJarPath = "build/libs/DistantHorizons-api-${rootProject.versionStr}-sources.jar"
|
// the compiled "-all" jar is used since it's available at the time this task is run
|
||||||
def secondJarFile = file(sourceJarPath)
|
def compiledJarPath = "build/libs/DistantHorizonsApi-${rootProject.api_version}-all.jar"
|
||||||
|
def compiledJarFile = file(compiledJarPath)
|
||||||
|
|
||||||
// doFirst is so these only run when the task is actually executed
|
// doFirst is so these only run when the task is actually executed
|
||||||
doFirst {
|
doFirst {
|
||||||
System.out.println("Adding source files from: \n" +
|
System.out.println("Adding class files from: \n" +
|
||||||
"[" + sourceJarPath + "] to compiled API jar: \n" +
|
"[" + compiledJarPath + "] to source API jar: \n" +
|
||||||
"[" + shadowJar.archiveFile.get().asFile + "]")
|
"[" + shadowJar.archiveFile.get().asFile + "]")
|
||||||
|
|
||||||
// Validate the input JAR file
|
// Validate the input JAR file
|
||||||
if (!secondJarFile.exists()) {
|
if (!compiledJarFile.exists()) {
|
||||||
throw new GradleException("Second JAR file not found: [${secondJarFile}]")
|
throw new GradleException("Compiled JAR file not found: [${compiledJarFile}]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the name of the combined JAR file
|
archiveFileName.set("DistantHorizonsApi-${rootProject.api_version}-combined.jar") // jar name
|
||||||
archiveFileName.set("DistantHorizonsApi-${rootProject.api_version}.jar")
|
destinationDirectory = file('build/libs/') // jar location
|
||||||
|
|
||||||
// Set the destination directory for the combined JAR file
|
|
||||||
destinationDirectory = file('build/libs/merged/')
|
|
||||||
|
|
||||||
// Set the input JAR files to be combined
|
// Set the input JAR files to be combined
|
||||||
from sourceSets.main.allJava
|
from sourceSets.main.allJava
|
||||||
from {
|
from {
|
||||||
configurations.shadow.collect { it.isDirectory() ? it : zipTree(it) }
|
project.configurations.shadow.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the jars to merge
|
// add the class files
|
||||||
from shadowJar.archiveFile.get().asFile
|
from zipTree(compiledJarFile)
|
||||||
from secondJarFile
|
|
||||||
|
|
||||||
// alternative method to Include the source files in the combined JAR
|
// alternative method to Include the source files in the combined JAR
|
||||||
// and/or see which files are being included
|
// and/or see which files are being included
|
||||||
@@ -93,6 +92,13 @@ task addSourcesToCompiledJar(type: ShadowJar) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
// always create a combined jar for easy deployment
|
||||||
|
assemble.dependsOn(createReleaseApiJar)
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
// required for basic shadowJar setup
|
||||||
|
configurations = [project.configurations.shadow]
|
||||||
|
}
|
||||||
|
|
||||||
javadoc {
|
javadoc {
|
||||||
options {
|
options {
|
||||||
@@ -103,3 +109,17 @@ javadoc {
|
|||||||
addStringOption('Xdoclint:all,-missing', '-quiet')
|
addStringOption('Xdoclint:all,-missing', '-quiet')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set the jar name
|
||||||
|
def configureJar = { task ->
|
||||||
|
// outputs in the format:
|
||||||
|
// "DistantHorizonsApi-6.0.0.jar"
|
||||||
|
// "DistantHorizonsApi-6.0.0-sources.jar"
|
||||||
|
task.archiveBaseName = rootProject.api_name
|
||||||
|
task.archiveVersion = rootProject.api_version
|
||||||
|
}
|
||||||
|
configureJar(tasks.named("jar").get())
|
||||||
|
configureJar(tasks.named("sourcesJar").get())
|
||||||
|
configureJar(tasks.named("shadowJar").get())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+6
@@ -42,6 +42,12 @@ public interface IDhApiBlockStateWrapper extends IDhApiUnsafeWrapper
|
|||||||
/** @since API 1.0.0 */
|
/** @since API 1.0.0 */
|
||||||
boolean isLiquid();
|
boolean isLiquid();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a value between 0 (fully transparent) and 16 (fully opaque).
|
||||||
|
* @since 6.1.0
|
||||||
|
*/
|
||||||
|
int getOpacity();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the full serialized form of the given block
|
* Returns the full serialized form of the given block
|
||||||
* as defined by DH's serialization methods.
|
* as defined by DH's serialization methods.
|
||||||
|
|||||||
+10
-2
@@ -107,16 +107,24 @@ public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiB
|
|||||||
public IDhApiLevelWrapper getLevelWrapper() { return levelWrapper; }
|
public IDhApiLevelWrapper getLevelWrapper() { return levelWrapper; }
|
||||||
|
|
||||||
public int getColorAsInt() { return this.colorAsInt; }
|
public int getColorAsInt() { return this.colorAsInt; }
|
||||||
|
public int getAlpha() { return ColorUtil.getAlpha(this.colorAsInt); }
|
||||||
public int getRed() { return ColorUtil.getRed(this.colorAsInt); }
|
public int getRed() { return ColorUtil.getRed(this.colorAsInt); }
|
||||||
public int getGreen() { return ColorUtil.getGreen(this.colorAsInt); }
|
public int getGreen() { return ColorUtil.getGreen(this.colorAsInt); }
|
||||||
public int getBlue() { return ColorUtil.getBlue(this.colorAsInt); }
|
public int getBlue() { return ColorUtil.getBlue(this.colorAsInt); }
|
||||||
public void setColor(int red, int green, int blue) throws IllegalArgumentException
|
public void setColor(int red, int green, int blue) throws IllegalArgumentException { setColor(255, red, green, blue); }
|
||||||
|
/**
|
||||||
|
* Note: when if you set a partially transparent alpha channel the underlying {@link IDhApiBlockStateWrapper#getOpacity()}
|
||||||
|
* method should also return a non-opaque value.
|
||||||
|
* Otherwise LODs may behave incorrectly.
|
||||||
|
*/
|
||||||
|
public void setColor(int alpha, int red, int green, int blue) throws IllegalArgumentException
|
||||||
{
|
{
|
||||||
|
ColorUtil.throwIfColorValueOutOfIntRange("alpha", alpha);
|
||||||
ColorUtil.throwIfColorValueOutOfIntRange("red", red);
|
ColorUtil.throwIfColorValueOutOfIntRange("red", red);
|
||||||
ColorUtil.throwIfColorValueOutOfIntRange("green", green);
|
ColorUtil.throwIfColorValueOutOfIntRange("green", green);
|
||||||
ColorUtil.throwIfColorValueOutOfIntRange("blue", blue);
|
ColorUtil.throwIfColorValueOutOfIntRange("blue", blue);
|
||||||
|
|
||||||
this.colorAsInt = ColorUtil.rgbToInt(red, green, blue);
|
this.colorAsInt = ColorUtil.argbToInt(alpha, red, green, blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return the block's X value in the world */
|
/** @return the block's X value in the world */
|
||||||
|
|||||||
@@ -33,21 +33,24 @@ public final class ModInfo
|
|||||||
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
|
/** Incremented every time any packets are added, changed or removed, with a few exceptions. */
|
||||||
public static final int PROTOCOL_VERSION = 13;
|
public static final int PROTOCOL_VERSION = 13;
|
||||||
|
|
||||||
/** The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH) must be 20 characters or fewer for compatibility with <1.13. */
|
/**
|
||||||
|
* The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH)
|
||||||
|
* must be 20 characters or fewer for compatibility with MC 1.13 and older.
|
||||||
|
*/
|
||||||
public static final String WRAPPER_PACKET_PATH = "msg";
|
public static final String WRAPPER_PACKET_PATH = "msg";
|
||||||
|
|
||||||
/** The internal mod name */
|
/** The internal mod name */
|
||||||
public static final String NAME = "DistantHorizons";
|
public static final String NAME = "DistantHorizons";
|
||||||
/** Human-readable version of NAME */
|
/** Human-readable version of NAME */
|
||||||
public static final String READABLE_NAME = "Distant Horizons";
|
public static final String READABLE_NAME = "Distant Horizons";
|
||||||
public static final String VERSION = "3.0.0-b";
|
public static final String VERSION = "3.0.2-b";
|
||||||
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
/** Returns true if the current build is an unstable developer build, false otherwise. */
|
||||||
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
public static final boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
|
||||||
|
|
||||||
/** This version should only be updated when breaking changes are introduced to the DH API */
|
/** This version should only be updated when breaking changes are introduced to the DH API */
|
||||||
public static final int API_MAJOR_VERSION = 6;
|
public static final int API_MAJOR_VERSION = 6;
|
||||||
/** This version should be updated whenever new methods are added to the DH API */
|
/** This version should be updated whenever new methods are added to the DH API */
|
||||||
public static final int API_MINOR_VERSION = 0;
|
public static final int API_MINOR_VERSION = 1;
|
||||||
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
/** This version should be updated whenever non-breaking fixes are added to the DH API */
|
||||||
public static final int API_PATCH_VERSION = 0;
|
public static final int API_PATCH_VERSION = 0;
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ dependencies {
|
|||||||
compileOnly("io.netty:netty-buffer:${rootProject.netty_version}")
|
compileOnly("io.netty:netty-buffer:${rootProject.netty_version}")
|
||||||
compileOnly("org.jetbrains:annotations:16.0.2")
|
compileOnly("org.jetbrains:annotations:16.0.2")
|
||||||
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
|
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
|
||||||
compileOnly("com.google.common:google-collect:0.5")
|
|
||||||
compileOnly("com.google.guava:guava:31.1-jre")
|
compileOnly("com.google.guava:guava:31.1-jre")
|
||||||
|
|
||||||
// DH's bundled libraries (shadowed + relocated in loader jars)
|
// DH's bundled libraries (shadowed + relocated in loader jars)
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class Initializer
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void init()
|
public static void preConfigInit()
|
||||||
{
|
{
|
||||||
//============================//
|
//============================//
|
||||||
// check referenced libraries //
|
// check referenced libraries //
|
||||||
@@ -177,6 +177,11 @@ public class Initializer
|
|||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** fired after DH's config has been set up */
|
||||||
|
public static void postConfigInit()
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
//==============================//
|
//==============================//
|
||||||
@@ -238,8 +243,7 @@ public class Initializer
|
|||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ public class ServerApi
|
|||||||
|
|
||||||
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
|
public void serverPlayerJoinEvent(IServerPlayerWrapper player)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@ public class ServerApi
|
|||||||
}
|
}
|
||||||
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
|
public void serverPlayerDisconnectEvent(IServerPlayerWrapper player)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -150,7 +150,7 @@ public class ServerApi
|
|||||||
}
|
}
|
||||||
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
public void serverPlayerLevelChangeEvent(IServerPlayerWrapper player, IServerLevelWrapper originLevel, IServerLevelWrapper destinationLevel)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -170,7 +170,7 @@ public class ServerApi
|
|||||||
*/
|
*/
|
||||||
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
|
public void pluginMessageReceived(IServerPlayerWrapper player, @NotNull AbstractNetworkMessage message)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded() && DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,6 @@ public class SharedApi
|
|||||||
//region
|
//region
|
||||||
|
|
||||||
private SharedApi() { }
|
private SharedApi() { }
|
||||||
public static void init() { Initializer.init(); }
|
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -212,7 +211,7 @@ public class SharedApi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ignore updates if the world is read-only
|
// ignore updates if the world is read-only
|
||||||
if (DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
package com.seibel.distanthorizons.core.config.eventHandlers;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
|
import com.seibel.distanthorizons.core.util.TimerUtil;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
public abstract class AbstractDelayedConfigEventHandler implements IConfigListener
|
||||||
|
{
|
||||||
|
public static final long DEFAULT_TIMEOUT_IN_MS = 2_000L;
|
||||||
|
|
||||||
|
/** how long to wait in milliseconds before applying the config changes */
|
||||||
|
private final long timeoutInMs;
|
||||||
|
private Timer timer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//=============//
|
||||||
|
// constructor //
|
||||||
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public AbstractDelayedConfigEventHandler(long timeoutInMs) { this.timeoutInMs = timeoutInMs; }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//==================//
|
||||||
|
// abstract methods //
|
||||||
|
//==================//
|
||||||
|
//region
|
||||||
|
|
||||||
|
public abstract void onConfigTimeout();
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//========//
|
||||||
|
// events //
|
||||||
|
//========//
|
||||||
|
//region
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConfigValueSet()
|
||||||
|
{
|
||||||
|
if (this.timeoutInMs > 0)
|
||||||
|
{
|
||||||
|
this.refreshRenderDataAfterTimeout();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.onConfigTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calling this method multiple times will reset the timer */
|
||||||
|
private synchronized void refreshRenderDataAfterTimeout() // synchronized to prevent potential threading issues when adding/removing the timer
|
||||||
|
{
|
||||||
|
// stop the previous timer if one exists
|
||||||
|
if (this.timer != null)
|
||||||
|
{
|
||||||
|
this.timer.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new timer task
|
||||||
|
TimerTask timerTask = new TimerTask()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
AbstractDelayedConfigEventHandler.this.onConfigTimeout();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.timer = TimerUtil.CreateTimer("AbstractDelayedConfigTimer");
|
||||||
|
this.timer.schedule(timerTask, this.timeoutInMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+11
-44
@@ -27,72 +27,36 @@ import com.seibel.distanthorizons.core.util.TimerUtil;
|
|||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
|
||||||
public class ReloadLodsConfigEventHandler implements IConfigListener
|
public class ReloadLodsConfigEventHandler extends AbstractDelayedConfigEventHandler
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* should be used for user facing UI options
|
* should be used for user facing UI options
|
||||||
* this allows the user a second to click through options before they're applied
|
* this allows the user a second to click through options before they're applied
|
||||||
*/
|
*/
|
||||||
public static ReloadLodsConfigEventHandler DELAYED_INSTANCE = new ReloadLodsConfigEventHandler(2_000L);
|
public static ReloadLodsConfigEventHandler DELAYED_INSTANCE = new ReloadLodsConfigEventHandler(AbstractDelayedConfigEventHandler.DEFAULT_TIMEOUT_IN_MS);
|
||||||
/** should be used for debug options so their change can be seen instantly */
|
/** should be used for debug options so their change can be seen instantly */
|
||||||
public static ReloadLodsConfigEventHandler INSTANT_INSTANCE = new ReloadLodsConfigEventHandler(0);
|
public static ReloadLodsConfigEventHandler INSTANT_INSTANCE = new ReloadLodsConfigEventHandler(0);
|
||||||
|
|
||||||
/** how long to wait in milliseconds before applying the config changes */
|
|
||||||
private final long timeoutInMs;
|
|
||||||
private Timer cacheClearingTimer;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
public ReloadLodsConfigEventHandler(long timeoutInMs)
|
public ReloadLodsConfigEventHandler(long timeoutInMs) { super(timeoutInMs); }
|
||||||
{
|
|
||||||
this.timeoutInMs = timeoutInMs;
|
//endregion
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//========//
|
//========//
|
||||||
// events //
|
// events //
|
||||||
//========//
|
//========//
|
||||||
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigValueSet()
|
public void onConfigTimeout()
|
||||||
{
|
|
||||||
if (this.timeoutInMs > 0)
|
|
||||||
{
|
|
||||||
this.refreshRenderDataAfterTimeout();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
clearRenderDataCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Calling this method multiple times will reset the timer */
|
|
||||||
private synchronized void refreshRenderDataAfterTimeout() // synchronized to prevent potential threading issues when adding/removing the timer
|
|
||||||
{
|
|
||||||
// stop the previous timer if one exists
|
|
||||||
if (this.cacheClearingTimer != null)
|
|
||||||
{
|
|
||||||
this.cacheClearingTimer.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a new timer task
|
|
||||||
TimerTask timerTask = new TimerTask()
|
|
||||||
{
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
clearRenderDataCache();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this.cacheClearingTimer = TimerUtil.CreateTimer("RenderCacheClearConfigTimer");
|
|
||||||
this.cacheClearingTimer.schedule(timerTask, this.timeoutInMs);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void clearRenderDataCache()
|
|
||||||
{
|
{
|
||||||
IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy;
|
IDhApiRenderProxy renderProxy = DhApi.Delayed.renderProxy;
|
||||||
if (renderProxy != null)
|
if (renderProxy != null)
|
||||||
@@ -101,5 +65,8 @@ public class ReloadLodsConfigEventHandler implements IConfigListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-3
@@ -32,7 +32,9 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|||||||
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
|
||||||
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
import com.seibel.distanthorizons.coreapi.util.StringUtil;
|
||||||
|
|
||||||
public class RenderBlockCacheCsvHandler implements IConfigListener
|
import java.util.Timer;
|
||||||
|
|
||||||
|
public class RenderBlockCacheCsvHandler extends AbstractDelayedConfigEventHandler
|
||||||
{
|
{
|
||||||
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
|
||||||
|
|
||||||
@@ -43,18 +45,22 @@ public class RenderBlockCacheCsvHandler implements IConfigListener
|
|||||||
//=============//
|
//=============//
|
||||||
// constructor //
|
// constructor //
|
||||||
//=============//
|
//=============//
|
||||||
|
//region
|
||||||
|
|
||||||
/** private since we only ever need one handler at a time */
|
/** private since we only ever need one handler at a time */
|
||||||
private RenderBlockCacheCsvHandler() { }
|
private RenderBlockCacheCsvHandler() { super(AbstractDelayedConfigEventHandler.DEFAULT_TIMEOUT_IN_MS); }
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=================//
|
//=================//
|
||||||
// config handling //
|
// config handling //
|
||||||
//=================//
|
//=================//
|
||||||
|
//region
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConfigValueSet()
|
public void onConfigTimeout()
|
||||||
{
|
{
|
||||||
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
|
||||||
if (wrapperFactory != null)
|
if (wrapperFactory != null)
|
||||||
@@ -64,6 +70,8 @@ public class RenderBlockCacheCsvHandler implements IConfigListener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-3
@@ -91,10 +91,27 @@ public class LodRequestModule implements Closeable
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// Initial wait is to prevent an issue
|
||||||
|
// where this starts before the child object's constructor finishes,
|
||||||
|
// causing null pointers on final non-null references.
|
||||||
|
// The try-catch in the while loop should also handle this
|
||||||
|
// but this way we shouldn't have error logs.
|
||||||
|
Thread.sleep(500);
|
||||||
|
|
||||||
|
// run until the threadpool is shut down
|
||||||
while (!Thread.interrupted())
|
while (!Thread.interrupted())
|
||||||
{
|
{
|
||||||
Thread.sleep(20);
|
try
|
||||||
this.tick();
|
{
|
||||||
|
Thread.sleep(20);
|
||||||
|
|
||||||
|
this.tick();
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) { throw e; }
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
LOGGER.error("Unexpected error in [" + LodRequestModule.class.getSimpleName() + "] tick loop, error: [" + e.getMessage() + "].", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InterruptedException ignore) { }
|
catch (InterruptedException ignore) { }
|
||||||
@@ -104,7 +121,7 @@ public class LodRequestModule implements Closeable
|
|||||||
boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
|
boolean shouldDoWorldGen = this.onWorldGenCompleteListener.shouldDoWorldGen();
|
||||||
|
|
||||||
// if the world is read only don't generate anything
|
// if the world is read only don't generate anything
|
||||||
shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.getReadOnly();
|
shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.tryGetReadOnly();
|
||||||
|
|
||||||
// don't generate chunks for client levels that aren't being rendered
|
// don't generate chunks for client levels that aren't being rendered
|
||||||
// (this can happen when moving between dimensions)
|
// (this can happen when moving between dimensions)
|
||||||
|
|||||||
+1
-1
@@ -202,7 +202,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
|
|||||||
private synchronized void tryQueueNewWorldGenRequestsAsync()
|
private synchronized void tryQueueNewWorldGenRequestsAsync()
|
||||||
{
|
{
|
||||||
if (!DhApiWorldProxy.INSTANCE.worldLoaded()
|
if (!DhApiWorldProxy.INSTANCE.worldLoaded()
|
||||||
|| DhApiWorldProxy.INSTANCE.getReadOnly())
|
|| DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-2
@@ -151,8 +151,7 @@ public abstract class AbstractFullDataNetworkRequestQueue implements IDebugRende
|
|||||||
|
|
||||||
public synchronized boolean tick(DhBlockPos2D targetPos)
|
public synchronized boolean tick(DhBlockPos2D targetPos)
|
||||||
{
|
{
|
||||||
if (DhApiWorldProxy.INSTANCE.worldLoaded()
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
&& DhApiWorldProxy.INSTANCE.getReadOnly())
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -7,10 +7,10 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|||||||
import com.seibel.distanthorizons.core.network.INetworkObject;
|
import com.seibel.distanthorizons.core.network.INetworkObject;
|
||||||
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
|
||||||
import io.netty.buffer.CompositeByteBuf;
|
import io.netty.buffer.CompositeByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
|
private final ConcurrentMap<Integer, CompositeByteBuf> buffersById = CacheBuilder.newBuilder()
|
||||||
.expireAfterAccess(10, TimeUnit.SECONDS)
|
.expireAfterAccess(30, TimeUnit.SECONDS)
|
||||||
.<Integer, CompositeByteBuf>build().asMap();
|
.<Integer, CompositeByteBuf>build().asMap();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,7 +56,7 @@ public class FullDataPayloadReceiver implements AutoCloseable
|
|||||||
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
public FullDataSourceV2DTO decodeDataSource(FullDataPayload payload)
|
||||||
{
|
{
|
||||||
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
|
CompositeByteBuf compositeByteBuffer = this.buffersById.get(payload.dtoBufferId);
|
||||||
LodUtil.assertTrue(compositeByteBuffer != null, "decoded data source missing byte buffer");
|
Objects.requireNonNull(compositeByteBuffer, "Unable to get a complete buffer for a received payload. Ignore this if it doesn't spam similar errors");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
+172
-77
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.render.QuadTree;
|
|||||||
|
|
||||||
import com.seibel.distanthorizons.core.config.Config;
|
import com.seibel.distanthorizons.core.config.Config;
|
||||||
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
import com.seibel.distanthorizons.core.config.listeners.IConfigListener;
|
||||||
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
|
||||||
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
import com.seibel.distanthorizons.core.enums.EDhDirection;
|
||||||
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
import com.seibel.distanthorizons.core.file.fullDatafile.V2.FullDataSourceProviderV2;
|
||||||
@@ -148,7 +149,8 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
int initialPlayerBlockX, int initialPlayerBlockZ,
|
int initialPlayerBlockX, int initialPlayerBlockZ,
|
||||||
FullDataSourceProviderV2 fullDataSourceProvider)
|
FullDataSourceProviderV2 fullDataSourceProvider)
|
||||||
{
|
{
|
||||||
super(viewDiameterInBlocks, new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
super(viewDiameterInBlocks, FullDataSourceV2.WIDTH,
|
||||||
|
new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL);
|
||||||
|
|
||||||
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
|
||||||
|
|
||||||
@@ -245,17 +247,38 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//===================//
|
//===================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
this.setCenterBlockPos(playerPos, (renderSection) ->
|
// remove out of bound sections
|
||||||
{
|
this.setCenterBlockPos(playerPos,
|
||||||
// removing out of bounds sections
|
// remove completely out of bound nodes
|
||||||
if (renderSection != null)
|
// (the root node is no longer in bounds)
|
||||||
|
(renderSection) ->
|
||||||
{
|
{
|
||||||
this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
if (renderSection != null)
|
||||||
this.missingGenerationPosSet.remove(renderSection.pos);
|
{
|
||||||
this.queuedGenerationPosSet.remove(renderSection.pos);
|
this.fullDataSourceProvider.removeRetrievalRequestIf((long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||||
renderSection.close();
|
|
||||||
|
// unfortunately we have to fully go through each set
|
||||||
|
// since a removed position may be larger than the multiple generated positions
|
||||||
|
// it contains
|
||||||
|
this.missingGenerationPosSet.removeIf((Long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||||
|
this.queuedGenerationPosSet.removeIf((Long genPos) -> DhSectionPos.contains(renderSection.pos, genPos));
|
||||||
|
|
||||||
|
renderSection.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// mutate partially out of bound nodes
|
||||||
|
// (the root node is still in bounds, but this individual child node isn't)
|
||||||
|
(renderSection) ->
|
||||||
|
{
|
||||||
|
if (renderSection != null)
|
||||||
|
{
|
||||||
|
// when this node comes back into render distance
|
||||||
|
// we'll need to re-load it since the full data
|
||||||
|
// may have been modified while it was out of bounds
|
||||||
|
renderSection.renderDataDirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@@ -304,7 +327,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
node.value.retreivedMissingSectionsForRetreival = false;
|
node.value.queuedMissingSectionsForRetrieval = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,8 +395,6 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//=========================//
|
//=========================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
// also handles disabling beacons
|
|
||||||
|
|
||||||
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getDisableNodes())
|
for (QuadNode<LodRenderSection> node : this.tickNodeHolder.getDisableNodes())
|
||||||
{
|
{
|
||||||
if (node == null || node.value == null) { continue; }
|
if (node == null || node.value == null) { continue; }
|
||||||
@@ -449,9 +470,9 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
|
|
||||||
// since this section wants to render
|
// since this section wants to render
|
||||||
// check if it needs any generation to do so
|
// check if it needs any generation to do so
|
||||||
if (!node.value.retreivedMissingSectionsForRetreival)
|
if (!node.value.queuedMissingSectionsForRetrieval)
|
||||||
{
|
{
|
||||||
node.value.retreivedMissingSectionsForRetreival = true;
|
node.value.queuedMissingSectionsForRetrieval = true;
|
||||||
this.tryQueuePosForRetrieval(node.value.pos); // can be quite slow
|
this.tryQueuePosForRetrieval(node.value.pos); // can be quite slow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,9 +497,10 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
//=========================//
|
//=========================//
|
||||||
// tick - recursive update //
|
// tick - recursive update //
|
||||||
//=========================//
|
//=========================//
|
||||||
///region
|
//region
|
||||||
|
|
||||||
private void recursivelyUpdateRenderSectionNode(
|
/** @return true if the node at this position has uploaded its render data */
|
||||||
|
private boolean recursivelyUpdateRenderSectionNode(
|
||||||
@NotNull DhBlockPos2D playerPos,
|
@NotNull DhBlockPos2D playerPos,
|
||||||
@NotNull QuadNode<LodRenderSection> rootNode,
|
@NotNull QuadNode<LodRenderSection> rootNode,
|
||||||
@Nullable QuadNode<LodRenderSection> parentNode,
|
@Nullable QuadNode<LodRenderSection> parentNode,
|
||||||
@@ -490,19 +512,22 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
// get/create the node //
|
// get/create the node //
|
||||||
// and render section //
|
// and render section //
|
||||||
//=====================//
|
//=====================//
|
||||||
///region
|
//region
|
||||||
|
|
||||||
// create the node
|
quadNode = this.tryAddNodeToTree(rootNode, quadNode, sectionPos);
|
||||||
if (quadNode == null)
|
|
||||||
{
|
|
||||||
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider));
|
// Skip sections that are out-of-bounds.
|
||||||
quadNode = rootNode.getNode(sectionPos);
|
// If not done some sections will appear and/or generate
|
||||||
}
|
// outside the desired render distance
|
||||||
if (quadNode == null)
|
if (!this.isSectionPosInBounds(quadNode.sectionPos))
|
||||||
{
|
{
|
||||||
LodUtil.assertNotReach("Unable to add node with pos ["+DhSectionPos.toString(sectionPos)+"] to tree root ["+rootNode+"].");
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
this.recursivelyDisableChildNodes(quadNode);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// make sure the render section is created (shouldn't be necessary, but just in case)
|
// make sure the render section is created (shouldn't be necessary, but just in case)
|
||||||
LodRenderSection renderSection = quadNode.value;
|
LodRenderSection renderSection = quadNode.value;
|
||||||
if (renderSection == null)
|
if (renderSection == null)
|
||||||
@@ -511,7 +536,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
quadNode.setValue(sectionPos, renderSection);
|
quadNode.setValue(sectionPos, renderSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -519,7 +544,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
// handle enabling, loading, //
|
// handle enabling, loading, //
|
||||||
// and disabling render sections //
|
// and disabling render sections //
|
||||||
//===============================//
|
//===============================//
|
||||||
///region
|
//region
|
||||||
|
|
||||||
// load every node for rendering
|
// load every node for rendering
|
||||||
if (!renderSection.gpuUploadInProgress()
|
if (!renderSection.gpuUploadInProgress()
|
||||||
@@ -535,94 +560,153 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
|
|
||||||
if (DhSectionPos.getDetailLevel(quadNode.sectionPos) > expectedDetailLevel)
|
if (DhSectionPos.getDetailLevel(quadNode.sectionPos) > expectedDetailLevel)
|
||||||
{
|
{
|
||||||
this.onDetailLevelTooHigh(playerPos, rootNode, quadNode);
|
return this.onDetailLevelTooLow(playerPos, rootNode, quadNode);
|
||||||
}
|
}
|
||||||
// the (expectedDetailLevel-1) fixes corners being cut out due to distance calculations using the LOD center
|
// the (expectedDetailLevel-1) fixes corners being cut out due to distance calculations using the LOD center
|
||||||
else if (DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel
|
else if (DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel
|
||||||
|| DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel - 1)
|
|| DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel - 1)
|
||||||
{
|
{
|
||||||
this.onDesiredDetailLevel(quadNode, parentNode);
|
return this.onDesiredDetailLevel(quadNode, parentNode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new IllegalStateException("LodQuadTree shouldn't be updating renderSections below the expected detail level: [" + expectedDetailLevel + "].");
|
throw new IllegalStateException("LodQuadTree shouldn't be updating renderSections below the expected detail level: [" + expectedDetailLevel + "].");
|
||||||
}
|
}
|
||||||
|
|
||||||
///endregion
|
//endregion
|
||||||
}
|
}
|
||||||
private void onDetailLevelTooHigh(
|
/** @return true if the node at this position has uploaded its render data */
|
||||||
|
private boolean onDetailLevelTooLow(
|
||||||
@NotNull DhBlockPos2D playerPos,
|
@NotNull DhBlockPos2D playerPos,
|
||||||
@NotNull QuadNode<LodRenderSection> rootNode, @NotNull QuadNode<LodRenderSection> quadNode)
|
@NotNull QuadNode<LodRenderSection> rootNode,
|
||||||
|
@NotNull QuadNode<LodRenderSection> quadNode)
|
||||||
{
|
{
|
||||||
// recursively update each child node
|
// recursively update each child node
|
||||||
boolean allChildNodesCanRender = true;
|
int childNodeRenderCount = 0;
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
|
||||||
long childPos = DhSectionPos.getChildByIndex(quadNode.sectionPos, i);
|
long childPos = DhSectionPos.getChildByIndex(quadNode.sectionPos, i);
|
||||||
this.recursivelyUpdateRenderSectionNode(
|
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||||
playerPos,
|
|
||||||
rootNode, quadNode, childNode, childPos);
|
|
||||||
childNode = quadNode.getChildByIndex(i); // needs to be gotten again in case a new node was added to the tree (this will often happen when moving into new areas where the children were deleted)
|
|
||||||
|
|
||||||
// nodes shouldn't be null, but just in case
|
boolean childCanRender = this.recursivelyUpdateRenderSectionNode(
|
||||||
if (childNode != null
|
playerPos,
|
||||||
&& childNode.value != null
|
rootNode, quadNode, childNode, childPos);
|
||||||
&& !childNode.value.gpuUploadComplete())
|
if (childCanRender)
|
||||||
{
|
{
|
||||||
// the node is present but not uploaded yet
|
// node can be rendered
|
||||||
allChildNodesCanRender = false;
|
childNodeRenderCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isRootNode = (quadNode == rootNode);
|
||||||
if (allChildNodesCanRender)
|
if (isRootNode)
|
||||||
{
|
{
|
||||||
// all child nodes can render, this node isn't needed
|
// Never render the root node.
|
||||||
|
// This is done to prevent flashing when moving across root node
|
||||||
|
// boundaries.
|
||||||
|
// Otherwise, when moving, new empty nodes will be added at the edge of the tree
|
||||||
|
// which will require the root node to render to cover the "empty" area.
|
||||||
this.tickNodeHolder.addDisableNode(quadNode);
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (childNodeRenderCount >= 4)
|
||||||
|
{
|
||||||
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
|
||||||
|
// all children can render,
|
||||||
|
// the area will be filled when rendering
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// not all child positions are loaded yet, this one should be rendered instead
|
boolean nodeCanRender = quadNode.value != null
|
||||||
this.tickNodeHolder.addEnableNode(quadNode);
|
&& quadNode.value.canRender();
|
||||||
|
if (nodeCanRender)
|
||||||
|
{
|
||||||
|
// not all child positions are loaded yet, this one should be rendered instead
|
||||||
|
this.tickNodeHolder.addEnableNode(quadNode);
|
||||||
|
this.recursivelyDisableChildNodes(quadNode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeCanRender;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private void onDesiredDetailLevel(
|
|
||||||
@NotNull QuadNode<LodRenderSection> quadNode, @Nullable QuadNode<LodRenderSection> parentNode)
|
/** @return true if the node at this position has uploaded its render data */
|
||||||
|
private boolean onDesiredDetailLevel(
|
||||||
|
@NotNull QuadNode<LodRenderSection> quadNode,
|
||||||
|
@Nullable QuadNode<LodRenderSection> parentNode)
|
||||||
{
|
{
|
||||||
boolean allAdjNodesCanRender = true;
|
// Skip sections that are out-of-bounds.
|
||||||
|
// If not done some sections will appear and/or generate
|
||||||
// if the parent node is null, that means we're at the root node
|
// outside the desired render distance
|
||||||
// and we should always render
|
if (!this.isSectionPosInBounds(quadNode.sectionPos))
|
||||||
if (parentNode != null)
|
|
||||||
{
|
{
|
||||||
// check if all adjacent nodes are ready to render
|
return true;
|
||||||
// this check is done to prevent some overlapping due to the parent node
|
}
|
||||||
// still being active
|
|
||||||
for (int i = 0; i < 4; i++)
|
if (quadNode.value != null
|
||||||
|
&& quadNode.value.canRender())
|
||||||
|
{
|
||||||
|
if (!this.tickNodeHolder.getEnabledNodes().contains(parentNode))
|
||||||
{
|
{
|
||||||
QuadNode<LodRenderSection> adjNode = parentNode.getChildByIndex(i);
|
this.tickNodeHolder.addEnableDeleteChildrenNode(quadNode);
|
||||||
// nodes shouldn't be null, but just in case there's an issue
|
return true;
|
||||||
if (adjNode != null
|
}
|
||||||
&& adjNode.value != null
|
else
|
||||||
&& !adjNode.value.gpuUploadComplete())
|
{
|
||||||
{
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
// the node is present but not uploaded yet
|
return false;
|
||||||
allAdjNodesCanRender = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (allAdjNodesCanRender
|
|
||||||
&& quadNode.value != null
|
|
||||||
&& quadNode.value.gpuUploadComplete())
|
|
||||||
{
|
{
|
||||||
this.tickNodeHolder.addEnableDeleteChildrenNode(quadNode);
|
this.tickNodeHolder.addDisableNode(quadNode);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///endregion
|
@NotNull
|
||||||
|
private QuadNode<LodRenderSection> tryAddNodeToTree(
|
||||||
|
@NotNull QuadNode<LodRenderSection> rootNode,
|
||||||
|
@Nullable QuadNode<LodRenderSection> quadNode,
|
||||||
|
long sectionPos // section pos is needed here since the quad node may be null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// create the node
|
||||||
|
if (quadNode == null)
|
||||||
|
{
|
||||||
|
rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider));
|
||||||
|
quadNode = rootNode.getNode(sectionPos);
|
||||||
|
}
|
||||||
|
if (quadNode == null)
|
||||||
|
{
|
||||||
|
LodUtil.assertNotReach("Unable to add node with pos ["+DhSectionPos.toString(sectionPos)+"] to tree root ["+rootNode+"].");
|
||||||
|
}
|
||||||
|
|
||||||
|
return quadNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recursivelyDisableChildNodes(@NotNull QuadNode<LodRenderSection> quadNode)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
QuadNode<LodRenderSection> childNode = quadNode.getChildByIndex(i);
|
||||||
|
this.tickNodeHolder.removeEnableAndDisableNode(childNode);
|
||||||
|
|
||||||
|
if (childNode != null)
|
||||||
|
{
|
||||||
|
this.recursivelyDisableChildNodes(childNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
|
||||||
//=====================//
|
//=====================//
|
||||||
// tick - work queuing //
|
// tick - work queuing //
|
||||||
@@ -1118,6 +1202,17 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
{
|
{
|
||||||
this.populateListWithEnabledRenderSections(this.debugNodeList);
|
this.populateListWithEnabledRenderSections(this.debugNodeList);
|
||||||
|
|
||||||
|
//// can be uncommented for debugging/finding a specific position
|
||||||
|
//debugRenderer.makeParticle(
|
||||||
|
// new AbstractDebugWireframeRenderer.BoxParticle(
|
||||||
|
// new AbstractDebugWireframeRenderer.Box(
|
||||||
|
// DhSectionPos.encode((byte)7, 3,-1)
|
||||||
|
// , -64, 400,
|
||||||
|
// 0.1f,
|
||||||
|
// Color.YELLOW),
|
||||||
|
// 0.5, 0f
|
||||||
|
// ));
|
||||||
|
|
||||||
for (int i = 0; i < this.debugNodeList.size(); i++)
|
for (int i = 0; i < this.debugNodeList.size(); i++)
|
||||||
{
|
{
|
||||||
LodRenderSection renderSection = this.debugNodeList.get(i);
|
LodRenderSection renderSection = this.debugNodeList.get(i);
|
||||||
@@ -1127,7 +1222,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
|||||||
{
|
{
|
||||||
color = Color.ORANGE;
|
color = Color.ORANGE;
|
||||||
}
|
}
|
||||||
else if (!renderSection.gpuUploadComplete())
|
else if (!renderSection.canRender())
|
||||||
{
|
{
|
||||||
// uploaded but the buffer is missing
|
// uploaded but the buffer is missing
|
||||||
color = Color.PINK;
|
color = Color.PINK;
|
||||||
|
|||||||
+21
-9
@@ -34,11 +34,8 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
|||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
|
||||||
import com.seibel.distanthorizons.core.render.renderer.BeaconRenderHandler;
|
|
||||||
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
|
||||||
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
|
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
|
||||||
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
|
|
||||||
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
|
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
|
||||||
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
|
||||||
@@ -48,10 +45,8 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import javax.annotation.WillNotClose;
|
import javax.annotation.WillNotClose;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A render section represents an area that could be rendered.
|
* A render section represents an area that could be rendered.
|
||||||
@@ -75,8 +70,16 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
|
|
||||||
private boolean renderingEnabled = false;
|
private boolean renderingEnabled = false;
|
||||||
private boolean beaconsRendering = false;
|
/**
|
||||||
public boolean retreivedMissingSectionsForRetreival = false;
|
* Used when a node goes out of render distance
|
||||||
|
* but isn't removed from the underlying quad tree structure. <br><br>
|
||||||
|
*
|
||||||
|
* In those cases we should act as if the node was removed
|
||||||
|
* for cached render data caching purposes, but not
|
||||||
|
* for re-creating missing nodes.
|
||||||
|
*/
|
||||||
|
public boolean renderDataDirty = false;
|
||||||
|
public boolean queuedMissingSectionsForRetrieval = false;
|
||||||
|
|
||||||
/** this reference is necessary so we can determine what VBO to render */
|
/** this reference is necessary so we can determine what VBO to render */
|
||||||
public LodBufferContainer renderBufferContainer;
|
public LodBufferContainer renderBufferContainer;
|
||||||
@@ -320,6 +323,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
|
|
||||||
// upload complete
|
// upload complete
|
||||||
this.renderBufferContainer = buffer.buffersUploaded ? buffer : null;
|
this.renderBufferContainer = buffer.buffersUploaded ? buffer : null;
|
||||||
|
this.renderDataDirty = false;
|
||||||
|
|
||||||
if (previousContainer != null)
|
if (previousContainer != null)
|
||||||
{
|
{
|
||||||
@@ -345,7 +349,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
//=================//
|
//=================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public boolean gpuUploadComplete() { return this.renderBufferContainer != null; }
|
public boolean canRender() { return this.renderBufferContainer != null; }
|
||||||
|
public boolean gpuUploadComplete()
|
||||||
|
{
|
||||||
|
return this.renderBufferContainer != null
|
||||||
|
// render dirty is here so we can trigger new GPU uploads
|
||||||
|
// even if the render data is present
|
||||||
|
&& !this.renderDataDirty;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getRenderingEnabled() { return this.renderingEnabled; }
|
public boolean getRenderingEnabled() { return this.renderingEnabled; }
|
||||||
public void setRenderingEnabled(boolean enabled) { this.renderingEnabled = enabled;}
|
public void setRenderingEnabled(boolean enabled) { this.renderingEnabled = enabled;}
|
||||||
@@ -374,7 +385,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
{
|
{
|
||||||
color = Color.yellow;
|
color = Color.yellow;
|
||||||
}
|
}
|
||||||
else if (this.gpuUploadComplete())
|
else if (this.canRender())
|
||||||
{
|
{
|
||||||
//color = Color.cyan;
|
//color = Color.cyan;
|
||||||
return;
|
return;
|
||||||
@@ -418,6 +429,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.setRenderingEnabled(false);
|
||||||
if (this.renderBufferContainer != null)
|
if (this.renderBufferContainer != null)
|
||||||
{
|
{
|
||||||
this.renderBufferContainer.close();
|
this.renderBufferContainer.close();
|
||||||
|
|||||||
+27
-9
@@ -63,21 +63,39 @@ public class QuadTreeTickNodeHolder
|
|||||||
{
|
{
|
||||||
if(this.presentNodes.add(node))
|
if(this.presentNodes.add(node))
|
||||||
{
|
{
|
||||||
// not a big fan of having to check every node to prevent overlaps, but it does work
|
// in James testing as of 4-21-2026
|
||||||
this.nodesToEnable.removeIf((QuadNode<LodRenderSection> checkNode) ->
|
// this should no longer be needed to prevent overlaps,
|
||||||
|
// however I'm keeping it here as a quick fix solution if
|
||||||
|
// the problem comes up again
|
||||||
|
if (false)
|
||||||
{
|
{
|
||||||
boolean contained = DhSectionPos.contains(node.sectionPos, checkNode.sectionPos);
|
// not a big fan of having to check every node to prevent overlaps, but it does work
|
||||||
if (contained)
|
this.nodesToEnable.removeIf((QuadNode<LodRenderSection> checkNode) ->
|
||||||
{
|
{
|
||||||
this.nodesToDisable.add(checkNode);
|
boolean contained = DhSectionPos.contains(node.sectionPos, checkNode.sectionPos);
|
||||||
}
|
if (contained)
|
||||||
|
{
|
||||||
return contained;
|
this.nodesToDisable.add(checkNode);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return contained;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.nodesToEnable.add(node);
|
this.nodesToEnable.add(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** */
|
||||||
|
public void removeEnableAndDisableNode(QuadNode<LodRenderSection> node)
|
||||||
|
{
|
||||||
|
this.nodesToEnable.remove(node);
|
||||||
|
this.nodesToEnableDeleteChildrenList.remove(node);
|
||||||
|
|
||||||
|
this.presentNodes.add(node); // should already be present, but re-added just in case
|
||||||
|
this.nodesToDisable.add(node); // node shouldn't be rendered since it's being disabled by a parent
|
||||||
|
}
|
||||||
|
|
||||||
public HashSet<QuadNode<LodRenderSection>> getEnabledNodes() { return this.nodesToEnable; }
|
public HashSet<QuadNode<LodRenderSection>> getEnabledNodes() { return this.nodesToEnable; }
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+21
-10
@@ -180,6 +180,24 @@ public class LodRenderer
|
|||||||
renderBufferHandler.buildRenderList(renderParams);
|
renderBufferHandler.buildRenderList(renderParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
boolean renderFog;
|
||||||
|
Boolean apiFogOverride = Config.Client.Advanced.Graphics.Fog.enableDhFog.getApiValue();
|
||||||
|
if (apiFogOverride != null)
|
||||||
|
{
|
||||||
|
// use whatever the API dictates if set
|
||||||
|
// (this could cause issues when underwater if a shader or something
|
||||||
|
// doesn't add their own, but that's relatively unlikely)
|
||||||
|
renderFog = apiFogOverride;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderFog = Config.Client.Advanced.Graphics.Fog.enableDhFog.get();
|
||||||
|
// allow enabling fog when: underwater fog, blind, etc.
|
||||||
|
// otherwise LODs won't appear correctly
|
||||||
|
renderFog |= renderParams.vanillaFogEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -207,8 +225,6 @@ public class LodRenderer
|
|||||||
// opaque LODs
|
// opaque LODs
|
||||||
profiler.popPush("LOD Opaque");
|
profiler.popPush("LOD Opaque");
|
||||||
|
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderParams);
|
|
||||||
|
|
||||||
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ true, profiler);
|
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ true, profiler);
|
||||||
|
|
||||||
// custom objects with SSAO
|
// custom objects with SSAO
|
||||||
@@ -249,9 +265,8 @@ public class LodRenderer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fog
|
// fog
|
||||||
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get()
|
|
||||||
// this is done to fix issues with: underwater fog, blindness effect, etc.
|
if (renderFog)
|
||||||
|| renderParams.vanillaFogEnabled)
|
|
||||||
{
|
{
|
||||||
profiler.popPush("LOD Fog");
|
profiler.popPush("LOD Fog");
|
||||||
|
|
||||||
@@ -294,15 +309,11 @@ public class LodRenderer
|
|||||||
|
|
||||||
if (Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
|
if (Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
|
||||||
{
|
{
|
||||||
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderParams);
|
|
||||||
|
|
||||||
profiler.popPush("LOD Transparent");
|
profiler.popPush("LOD Transparent");
|
||||||
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ false, profiler);
|
this.renderTerrain(this.terrainRenderer, renderBufferHandler, renderParams, /*opaquePass*/ false, profiler);
|
||||||
|
|
||||||
|
|
||||||
if (Config.Client.Advanced.Graphics.Fog.enableDhFog.get()
|
if (renderFog)
|
||||||
// this is done to fix issues with: underwater fog, blindness effect, etc.
|
|
||||||
|| renderParams.vanillaFogEnabled)
|
|
||||||
{
|
{
|
||||||
profiler.popPush("LOD Fog");
|
profiler.popPush("LOD Fog");
|
||||||
|
|
||||||
|
|||||||
@@ -247,10 +247,7 @@ public class RenderDataPointUtil
|
|||||||
{
|
{
|
||||||
return "Y+:" + getYMax(dataPoint) +
|
return "Y+:" + getYMax(dataPoint) +
|
||||||
" Y-:" + getYMin(dataPoint) +
|
" Y-:" + getYMin(dataPoint) +
|
||||||
" argb:" + getAlpha(dataPoint) + " " +
|
" argb:" + getAlpha(dataPoint) + "," + getRed(dataPoint) + "," + getGreen(dataPoint) + "," + getBlue(dataPoint) +
|
||||||
getRed(dataPoint) + " " +
|
|
||||||
getGreen(dataPoint) + " " +
|
|
||||||
getBlue(dataPoint) +
|
|
||||||
" BL:" + getLightBlock(dataPoint) +
|
" BL:" + getLightBlock(dataPoint) +
|
||||||
" SL:" + getLightSky(dataPoint) +
|
" SL:" + getLightSky(dataPoint) +
|
||||||
" MAT:" + getBlockMaterialId(dataPoint) + "["+ EDhApiBlockMaterial.getFromIndex(getBlockMaterialId(dataPoint))+"]";
|
" MAT:" + getBlockMaterialId(dataPoint) + "["+ EDhApiBlockMaterial.getFromIndex(getBlockMaterialId(dataPoint))+"]";
|
||||||
|
|||||||
@@ -123,7 +123,16 @@ public class RenderUtil
|
|||||||
// At low render distances this hides the vanilla RD border
|
// At low render distances this hides the vanilla RD border
|
||||||
|
|
||||||
int chunkRenderDistance = MC_RENDER.getRenderDistance();
|
int chunkRenderDistance = MC_RENDER.getRenderDistance();
|
||||||
if (chunkRenderDistance <= 2)
|
|
||||||
|
if (IRIS_ACCESSOR != null
|
||||||
|
&& IRIS_ACCESSOR.isShaderPackInUse())
|
||||||
|
{
|
||||||
|
// shaders handle the near clip plane/overdraw differently, best to play it
|
||||||
|
// safe and have the plane really close otherwise
|
||||||
|
// there might be cutouts on the screen edges
|
||||||
|
overdraw = 0.2f;
|
||||||
|
}
|
||||||
|
else if (chunkRenderDistance <= 2)
|
||||||
{
|
{
|
||||||
overdraw = 0.2f;
|
overdraw = 0.2f;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -325,7 +325,7 @@ public class QuadNode<T>
|
|||||||
|
|
||||||
public void deleteAllChildren() { this.deleteAllChildren(null); }
|
public void deleteAllChildren() { this.deleteAllChildren(null); }
|
||||||
/** @param removedItemConsumer is only fired for non-null nodes, however the value passed in may be null */
|
/** @param removedItemConsumer is only fired for non-null nodes, however the value passed in may be null */
|
||||||
public void deleteAllChildren(Consumer<? super T> removedItemConsumer)
|
public void deleteAllChildren(@Nullable Consumer<? super T> removedItemConsumer)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
+120
-35
@@ -19,10 +19,12 @@
|
|||||||
|
|
||||||
package com.seibel.distanthorizons.core.util.objects.quadTree;
|
package com.seibel.distanthorizons.core.util.objects.quadTree;
|
||||||
|
|
||||||
|
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLogger;
|
import com.seibel.distanthorizons.core.logging.DhLogger;
|
||||||
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
|
||||||
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
|
||||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||||
|
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree;
|
||||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||||
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
|
||||||
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
import com.seibel.distanthorizons.coreapi.util.MathUtil;
|
||||||
@@ -61,6 +63,11 @@ public class QuadTree<T>
|
|||||||
private final MovableGridRingList<QuadNode<T>> topRingList;
|
private final MovableGridRingList<QuadNode<T>> topRingList;
|
||||||
|
|
||||||
private DhBlockPos2D centerBlockPos;
|
private DhBlockPos2D centerBlockPos;
|
||||||
|
/**
|
||||||
|
* defines how many blocks the center needs to move in blocks
|
||||||
|
* before we check for out-of-bound nodes.
|
||||||
|
*/
|
||||||
|
private int blockDistanceForNodeClearing;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -74,10 +81,13 @@ public class QuadTree<T>
|
|||||||
*
|
*
|
||||||
* @param diameterInBlocks equivalent to the distance between the two opposing sides
|
* @param diameterInBlocks equivalent to the distance between the two opposing sides
|
||||||
*/
|
*/
|
||||||
public QuadTree(int diameterInBlocks, DhBlockPos2D centerBlockPos, byte treeLeafDetailLevel)
|
public QuadTree(
|
||||||
|
int diameterInBlocks, int blockDistanceForNodeClearing,
|
||||||
|
DhBlockPos2D centerBlockPos, byte treeLeafDetailLevel)
|
||||||
{
|
{
|
||||||
this.centerBlockPos = centerBlockPos;
|
this.centerBlockPos = centerBlockPos;
|
||||||
this.diameterInBlocks = diameterInBlocks;
|
this.diameterInBlocks = diameterInBlocks;
|
||||||
|
this.blockDistanceForNodeClearing = blockDistanceForNodeClearing;
|
||||||
|
|
||||||
this.treeLeafDetailLevel = treeLeafDetailLevel;
|
this.treeLeafDetailLevel = treeLeafDetailLevel;
|
||||||
// the min detail level must be greater than 0 (to prevent divide by 0 errors) and greater than the maximum detail level
|
// the min detail level must be greater than 0 (to prevent divide by 0 errors) and greater than the maximum detail level
|
||||||
@@ -130,18 +140,22 @@ public class QuadTree<T>
|
|||||||
public int leafNodeCount()
|
public int leafNodeCount()
|
||||||
{
|
{
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (QuadNode<T> node : this.topRingList)
|
for (QuadNode<T> rootNode : this.topRingList)
|
||||||
{
|
{
|
||||||
if (node == null)
|
if (rootNode == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<QuadNode<T>> leafNodeIterator = node.getLeafNodeIterator();
|
Iterator<QuadNode<T>> leafNodeIterator = rootNode.getLeafNodeIterator();
|
||||||
while (leafNodeIterator.hasNext())
|
while (leafNodeIterator.hasNext())
|
||||||
{
|
{
|
||||||
leafNodeIterator.next();
|
QuadNode<T> node = leafNodeIterator.next();
|
||||||
count++;
|
if (node != null
|
||||||
|
&& this.isSectionPosInBounds(node.sectionPos))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,32 +250,32 @@ public class QuadTree<T>
|
|||||||
int ringListPosX = DhSectionPos.getX(rootPos);
|
int ringListPosX = DhSectionPos.getX(rootPos);
|
||||||
int ringListPosZ = DhSectionPos.getZ(rootPos);
|
int ringListPosZ = DhSectionPos.getZ(rootPos);
|
||||||
|
|
||||||
QuadNode<T> topQuadNode = this.topRingList.get(ringListPosX, ringListPosZ);
|
QuadNode<T> rootQuadNode = this.topRingList.get(ringListPosX, ringListPosZ);
|
||||||
if (topQuadNode == null)
|
if (rootQuadNode == null)
|
||||||
{
|
{
|
||||||
if (!setNewValue)
|
if (!setNewValue)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
topQuadNode = new QuadNode<T>(rootPos, this.treeLeafDetailLevel);
|
rootQuadNode = new QuadNode<T>(rootPos, this.treeLeafDetailLevel);
|
||||||
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, topQuadNode);
|
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, rootQuadNode);
|
||||||
if (!successfullyAdded)
|
if (!successfullyAdded)
|
||||||
{
|
{
|
||||||
LodUtil.assertNotReach("Failed to add top quadTree node at position: " + rootPos);
|
LodUtil.assertNotReach("Failed to add root quadTree node at position: ["+DhSectionPos.toString(rootPos)+"]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!DhSectionPos.contains(topQuadNode.sectionPos, pos))
|
if (!DhSectionPos.contains(rootQuadNode.sectionPos, pos))
|
||||||
{
|
{
|
||||||
LodUtil.assertNotReach("failed to get a root node that contains the input position: " + pos + " root node pos: " + topQuadNode.sectionPos);
|
LodUtil.assertNotReach("failed to get a root node that contains the input position: " + pos + " root node pos: " + rootQuadNode.sectionPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QuadNode<T> returnNode = topQuadNode.getNode(pos);
|
QuadNode<T> returnNode = rootQuadNode.getNode(pos);
|
||||||
if (setNewValue)
|
if (setNewValue)
|
||||||
{
|
{
|
||||||
topQuadNode.setValue(pos, newValue);
|
rootQuadNode.setValue(pos, newValue);
|
||||||
}
|
}
|
||||||
return returnNode;
|
return returnNode;
|
||||||
}
|
}
|
||||||
@@ -354,32 +368,103 @@ public class QuadTree<T>
|
|||||||
//================//
|
//================//
|
||||||
//region
|
//region
|
||||||
|
|
||||||
public void setCenterBlockPos(DhBlockPos2D newCenterPos) { this.setCenterBlockPos(newCenterPos, null); }
|
public void setCenterBlockPos(DhBlockPos2D newCenterPos) { this.setCenterBlockPos(newCenterPos, null, null); }
|
||||||
public void setCenterBlockPos(DhBlockPos2D newCenterPos, Consumer<? super T> removedItemConsumer)
|
/**
|
||||||
|
* @param removedConsumer fired when a root node is completely removed from the underlying data structure
|
||||||
|
* @param mutateOutOfBoundConsumer fired when a child node is out of bounds, but not removed from the underlying data structure
|
||||||
|
*/
|
||||||
|
public void setCenterBlockPos(
|
||||||
|
DhBlockPos2D newCenterPos,
|
||||||
|
@Nullable Consumer<? super T> removedConsumer,
|
||||||
|
@Nullable Consumer<? super T> mutateOutOfBoundConsumer)
|
||||||
{
|
{
|
||||||
this.centerBlockPos = newCenterPos;
|
// did we move significantly?
|
||||||
|
boolean ringListMoved = false;
|
||||||
MovableGridRingList.Pos2D expectedCenterPos = new MovableGridRingList.Pos2D(
|
int newCenterPosX = BitShiftUtil.divideByPowerOfTwo(newCenterPos.x, this.treeRootDetailLevel);
|
||||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeRootDetailLevel),
|
int newCenterPosZ = BitShiftUtil.divideByPowerOfTwo(newCenterPos.z, this.treeRootDetailLevel);
|
||||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeRootDetailLevel));
|
if (this.topRingList.getCenter().getX() != newCenterPosX
|
||||||
|
|| this.topRingList.getCenter().getY() != newCenterPosZ)
|
||||||
if (this.topRingList.getCenter().equals(expectedCenterPos))
|
|
||||||
{
|
{
|
||||||
// tree doesn't need to be moved
|
ringListMoved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// did we move a little bit?
|
||||||
|
boolean recalculateOutOfBoundNodes = false;
|
||||||
|
int centerBlockDistance = this.centerBlockPos.manhattanDist(newCenterPos);
|
||||||
|
if (centerBlockDistance >= this.blockDistanceForNodeClearing)
|
||||||
|
{
|
||||||
|
recalculateOutOfBoundNodes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ringListMoved
|
||||||
|
&& !recalculateOutOfBoundNodes)
|
||||||
|
{
|
||||||
|
// the tree didn't move enough that we need
|
||||||
|
// to re-calculate anything
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// remove out of bounds root nodes
|
|
||||||
this.topRingList.moveTo(expectedCenterPos.getX(), expectedCenterPos.getY(), (quadNode) ->
|
this.centerBlockPos = newCenterPos;
|
||||||
|
|
||||||
|
// remove out of bound root nodes
|
||||||
|
this.topRingList.moveTo(newCenterPosX, newCenterPosZ, (quadNode) ->
|
||||||
{
|
{
|
||||||
if (quadNode != null && removedItemConsumer != null)
|
if (quadNode != null)
|
||||||
{
|
{
|
||||||
quadNode.deleteAllChildren(removedItemConsumer);
|
quadNode.deleteAllChildren(removedConsumer);
|
||||||
|
|
||||||
removedItemConsumer.accept(quadNode.value);
|
if (removedConsumer != null)
|
||||||
|
{
|
||||||
|
removedConsumer.accept(quadNode.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// mutate out of bound child nodes
|
||||||
|
this.topRingList.forEach((rootNode) ->
|
||||||
|
{
|
||||||
|
this.mutateOutOfBoundChildNodes(rootNode, mutateOutOfBoundConsumer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* we don't want to actually remove nodes or node data
|
||||||
|
* since that can cause the {@link LodQuadTree} to
|
||||||
|
* flash low-detail LODs when moving into previously-loaded
|
||||||
|
* LODs, which is really disorienting.
|
||||||
|
*/
|
||||||
|
private void mutateOutOfBoundChildNodes(@Nullable QuadNode<T> quadNode, @Nullable Consumer<? super T> mutateOutOfBoundConsumer)
|
||||||
|
{
|
||||||
|
// nodes shouldn't be null, but just in case
|
||||||
|
if (quadNode == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// go over each child node
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
QuadNode<T> childNode = quadNode.getChildByIndex(i);
|
||||||
|
if (childNode == null
|
||||||
|
|| childNode.value == null)
|
||||||
|
{
|
||||||
|
// no need to go any deeper if this node is already empty
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutate nodes from the bottom up
|
||||||
|
this.mutateOutOfBoundChildNodes(childNode, mutateOutOfBoundConsumer);
|
||||||
|
|
||||||
|
// mutate this node if out of bounds
|
||||||
|
if (!this.isSectionPosInBounds(childNode.sectionPos))
|
||||||
|
{
|
||||||
|
if (mutateOutOfBoundConsumer != null)
|
||||||
|
{
|
||||||
|
mutateOutOfBoundConsumer.accept(childNode.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
|
public final DhBlockPos2D getCenterBlockPos() { return this.centerBlockPos; }
|
||||||
@@ -484,7 +569,7 @@ public class QuadTree<T>
|
|||||||
|
|
||||||
private class QuadTreeNodeIterator implements Iterator<QuadNode<T>>
|
private class QuadTreeNodeIterator implements Iterator<QuadNode<T>>
|
||||||
{
|
{
|
||||||
private final QuadTreeRootPosIterator rootNodeIterator;
|
private final QuadTreeRootPosIterator rootNodePosIterator;
|
||||||
private Iterator<QuadNode<T>> currentNodeIterator;
|
private Iterator<QuadNode<T>> currentNodeIterator;
|
||||||
|
|
||||||
private QuadNode<T> lastNode = null;
|
private QuadNode<T> lastNode = null;
|
||||||
@@ -497,7 +582,7 @@ public class QuadTree<T>
|
|||||||
|
|
||||||
public QuadTreeNodeIterator(boolean onlyReturnLeaves, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
|
public QuadTreeNodeIterator(boolean onlyReturnLeaves, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
|
||||||
{
|
{
|
||||||
this.rootNodeIterator = new QuadTreeRootPosIterator(false, stopIteratingFunc);
|
this.rootNodePosIterator = new QuadTreeRootPosIterator(false, stopIteratingFunc);
|
||||||
this.onlyReturnLeaves = onlyReturnLeaves;
|
this.onlyReturnLeaves = onlyReturnLeaves;
|
||||||
|
|
||||||
this.stopIteratingFunc = stopIteratingFunc;
|
this.stopIteratingFunc = stopIteratingFunc;
|
||||||
@@ -508,7 +593,7 @@ public class QuadTree<T>
|
|||||||
@Override
|
@Override
|
||||||
public boolean hasNext()
|
public boolean hasNext()
|
||||||
{
|
{
|
||||||
if (!this.rootNodeIterator.hasNext()
|
if (!this.rootNodePosIterator.hasNext()
|
||||||
&& this.currentNodeIterator != null
|
&& this.currentNodeIterator != null
|
||||||
&& !this.currentNodeIterator.hasNext())
|
&& !this.currentNodeIterator.hasNext())
|
||||||
{
|
{
|
||||||
@@ -544,9 +629,9 @@ public class QuadTree<T>
|
|||||||
{
|
{
|
||||||
Iterator<QuadNode<T>> nodeIterator = null;
|
Iterator<QuadNode<T>> nodeIterator = null;
|
||||||
while ((nodeIterator == null || !nodeIterator.hasNext())
|
while ((nodeIterator == null || !nodeIterator.hasNext())
|
||||||
&& this.rootNodeIterator.hasNext())
|
&& this.rootNodePosIterator.hasNext())
|
||||||
{
|
{
|
||||||
long sectionPos = this.rootNodeIterator.nextLong();
|
long sectionPos = this.rootNodePosIterator.nextLong();
|
||||||
|
|
||||||
// try-get to prevent concurrency errors if the tree is being moved while we walk through it
|
// try-get to prevent concurrency errors if the tree is being moved while we walk through it
|
||||||
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
|
QuadNode<T> rootNode = QuadTree.this.tryGetNode(sectionPos);
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public abstract class AbstractDhWorld implements IDhWorld, Closeable
|
|||||||
String levelCountStr = F3Screen.NUMBER_FORMAT.format(this.getLoadedLevelCount());
|
String levelCountStr = F3Screen.NUMBER_FORMAT.format(this.getLoadedLevelCount());
|
||||||
|
|
||||||
String readOnlyStr = "";
|
String readOnlyStr = "";
|
||||||
if (DhApiWorldProxy.INSTANCE.getReadOnly())
|
if (DhApiWorldProxy.INSTANCE.tryGetReadOnly())
|
||||||
{
|
{
|
||||||
readOnlyStr += " - ReadOnly";
|
readOnlyStr += " - ReadOnly";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,23 @@ public class DhApiWorldProxy implements IDhApiWorldProxy
|
|||||||
return this.isReadOnly;
|
return this.isReadOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if no world is loaded.
|
||||||
|
* Can be used in places where the world state might be a bit more questionable
|
||||||
|
* without having to worry about the {@link IllegalStateException} thrown by
|
||||||
|
* {@link DhApiWorldProxy#getReadOnly()}
|
||||||
|
*/
|
||||||
|
public boolean tryGetReadOnly()
|
||||||
|
{
|
||||||
|
if (SharedApi.getAbstractDhWorld() == null)
|
||||||
|
{
|
||||||
|
// no world is loaded, use the default value for the next world
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//================//
|
//================//
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class QuadTreeTest
|
|||||||
public void BasicPositiveQuadTreeTest()
|
public void BasicPositiveQuadTreeTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||||
Assert.assertTrue("Tree min/max detail level out of expected bounds: " + tree, tree.treeRootDetailLevel >= 10 && tree.treeLeafDetailLevel <= 10 - 4);
|
Assert.assertTrue("Tree min/max detail level out of expected bounds: " + tree, tree.treeRootDetailLevel >= 10 && tree.treeLeafDetailLevel <= 10 - 4);
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ public class QuadTreeTest
|
|||||||
public void BasicNegativeQuadTreeTest()
|
public void BasicNegativeQuadTreeTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), DhBlockPos2D.ZERO, LodUtil.BLOCK_DETAIL_LEVEL);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), DhBlockPos2D.ZERO, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||||
|
|
||||||
|
|
||||||
// root node //
|
// root node //
|
||||||
@@ -128,7 +128,7 @@ public class QuadTreeTest
|
|||||||
public void OutOfBoundsQuadTreeTest()
|
public void OutOfBoundsQuadTreeTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||||
Assert.assertEquals("tree diameter incorrect", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
Assert.assertEquals("tree diameter incorrect", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
||||||
|
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ public class QuadTreeTest
|
|||||||
public void outOfBoundsInTreeTest()
|
public void outOfBoundsInTreeTest()
|
||||||
{
|
{
|
||||||
// very specific tree parameters to match test results
|
// very specific tree parameters to match test results
|
||||||
QuadTree<Integer> tree = new QuadTree<>(512, new DhBlockPos2D(125, -516), (byte) 6);
|
QuadTree<Integer> tree = new QuadTree<>(512, 8, new DhBlockPos2D(125, -516), (byte) 6);
|
||||||
Assert.assertEquals("Test may need to be re-calculated for different max detail level.", 9, tree.treeRootDetailLevel);
|
Assert.assertEquals("Test may need to be re-calculated for different max detail level.", 9, tree.treeRootDetailLevel);
|
||||||
|
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ public class QuadTreeTest
|
|||||||
public void QuadTreeRootAlignedMovingTest()
|
public void QuadTreeRootAlignedMovingTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||||
|
|
||||||
int pseudoRootNodeWidthInBlocks = BitShiftUtil.powerOfTwo(10);
|
int pseudoRootNodeWidthInBlocks = BitShiftUtil.powerOfTwo(10);
|
||||||
|
|
||||||
@@ -210,7 +210,7 @@ public class QuadTreeTest
|
|||||||
testSet(tree, ne, 3);
|
testSet(tree, ne, 3);
|
||||||
testSet(tree, sw, 4);
|
testSet(tree, sw, 4);
|
||||||
testSet(tree, se, 5);
|
testSet(tree, se, 5);
|
||||||
Assert.assertEquals("incorrect leaf node count", tree.leafNodeCount(), 4);
|
Assert.assertEquals("incorrect leaf node count", 4, tree.leafNodeCount());
|
||||||
|
|
||||||
|
|
||||||
// fake move //
|
// fake move //
|
||||||
@@ -237,7 +237,7 @@ public class QuadTreeTest
|
|||||||
testGet(tree, ne, 3);
|
testGet(tree, ne, 3);
|
||||||
testGet(tree, sw, 4);
|
testGet(tree, sw, 4);
|
||||||
testGet(tree, se, 5);
|
testGet(tree, se, 5);
|
||||||
Assert.assertEquals("incorrect leaf node count", tree.leafNodeCount(), 4);
|
Assert.assertEquals("incorrect leaf node count", 4, tree.leafNodeCount());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ public class QuadTreeTest
|
|||||||
testGet(tree, sw, null, IndexOutOfBoundsException.class);
|
testGet(tree, sw, null, IndexOutOfBoundsException.class);
|
||||||
testGet(tree, se, null, IndexOutOfBoundsException.class);
|
testGet(tree, se, null, IndexOutOfBoundsException.class);
|
||||||
|
|
||||||
Assert.assertEquals("incorrect leaf node count", tree.leafNodeCount(), 0);
|
Assert.assertEquals("incorrect leaf node count", 0, tree.leafNodeCount());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ public class QuadTreeTest
|
|||||||
DhBlockPos2D edgeMoveBlockPos = new DhBlockPos2D(pseudoRootNodeWidthInBlocks - (treeParams.getWidthInRootNodes() * pseudoRootNodeWidthInBlocks), 0);
|
DhBlockPos2D edgeMoveBlockPos = new DhBlockPos2D(pseudoRootNodeWidthInBlocks - (treeParams.getWidthInRootNodes() * pseudoRootNodeWidthInBlocks), 0);
|
||||||
tree.setCenterBlockPos(edgeMoveBlockPos);
|
tree.setCenterBlockPos(edgeMoveBlockPos);
|
||||||
Assert.assertEquals("Tree center incorrect", edgeMoveBlockPos, tree.getCenterBlockPos());
|
Assert.assertEquals("Tree center incorrect", edgeMoveBlockPos, tree.getCenterBlockPos());
|
||||||
Assert.assertEquals("incorrect leaf node count", 2, tree.leafNodeCount());
|
Assert.assertEquals("incorrect leaf node count", 0, tree.leafNodeCount());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +284,7 @@ public class QuadTreeTest
|
|||||||
public void QuadTreeIterationTest()
|
public void QuadTreeIterationTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||||
|
|
||||||
|
|
||||||
// (pseudo) root nodes //
|
// (pseudo) root nodes //
|
||||||
@@ -336,7 +336,7 @@ public class QuadTreeTest
|
|||||||
public void QuadTreeIterationFilterTest()
|
public void QuadTreeIterationFilterTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
AbstractTestTreeParams treeParams = new TinyTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), (byte)0);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), (byte)0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -418,7 +418,10 @@ public class QuadTreeTest
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
private static <T> void assertFilterCount(QuadTree<T> tree, String message, int expectedNodeCount, @Nullable QuadTree.INodeIteratorStoppingFunc<T> stoppingFilterFunc)
|
private static <T> void assertFilterCount(
|
||||||
|
QuadTree<T> tree, String message,
|
||||||
|
int expectedNodeCount,
|
||||||
|
@Nullable QuadTree.INodeIteratorStoppingFunc<T> stoppingFilterFunc)
|
||||||
{
|
{
|
||||||
ArrayList<String> foundNodePositionStrings = new ArrayList<>();
|
ArrayList<String> foundNodePositionStrings = new ArrayList<>();
|
||||||
|
|
||||||
@@ -532,7 +535,7 @@ public class QuadTreeTest
|
|||||||
public void CenteredGridListIterationTest()
|
public void CenteredGridListIterationTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
AbstractTestTreeParams treeParams = new TinyTestTree();
|
||||||
final QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
final QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 0, 0), 0);
|
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 0, 0), 0);
|
||||||
|
|
||||||
// confirm the root node were added
|
// confirm the root node were added
|
||||||
@@ -573,18 +576,18 @@ public class QuadTreeTest
|
|||||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
AbstractTestTreeParams treeParams = new TinyTestTree();
|
||||||
|
|
||||||
// exactly inside (5*0,0)
|
// exactly inside (5*0,0)
|
||||||
testGridListRootCount(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), 1);
|
testGridListRootCount(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), 1);
|
||||||
|
|
||||||
// offset across (5*-1,0) and (5*0,0)
|
// offset across (5*-1,0) and (5*0,0)
|
||||||
testGridListRootCount(treeParams.getWidthInBlocks(), new DhBlockPos2D(-treeParams.getWidthInBlocks() / 4, treeParams.getPositiveEdgeCenterPos().z), 2);
|
testGridListRootCount(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(-treeParams.getWidthInBlocks() / 4, treeParams.getPositiveEdgeCenterPos().z), 2);
|
||||||
|
|
||||||
// offset across the origin: (5*0,0), (5*-1,0), (5*0,-1), and (5*-1,-1)
|
// offset across the origin: (5*0,0), (5*-1,0), (5*0,-1), and (5*-1,-1)
|
||||||
testGridListRootCount(treeParams.getWidthInBlocks(), DhBlockPos2D.ZERO, 4);
|
testGridListRootCount(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), DhBlockPos2D.ZERO, 4);
|
||||||
|
|
||||||
}
|
}
|
||||||
private static void testGridListRootCount(int treeWidth, DhBlockPos2D treeMovePos, int expectedRootNodeCount)
|
private static void testGridListRootCount(int treeWidth, int treeDistanceForNodeClearing, DhBlockPos2D treeMovePos, int expectedRootNodeCount)
|
||||||
{
|
{
|
||||||
final QuadTree<Integer> tree = new QuadTree<>(treeWidth, DhBlockPos2D.ZERO, LodUtil.BLOCK_DETAIL_LEVEL);
|
final QuadTree<Integer> tree = new QuadTree<>(treeWidth, treeDistanceForNodeClearing, DhBlockPos2D.ZERO, LodUtil.BLOCK_DETAIL_LEVEL);
|
||||||
Assert.assertEquals("tree creation failed, incorrect initial position", DhBlockPos2D.ZERO, tree.getCenterBlockPos());
|
Assert.assertEquals("tree creation failed, incorrect initial position", DhBlockPos2D.ZERO, tree.getCenterBlockPos());
|
||||||
|
|
||||||
tree.setCenterBlockPos(treeMovePos);
|
tree.setCenterBlockPos(treeMovePos);
|
||||||
@@ -615,7 +618,7 @@ public class QuadTreeTest
|
|||||||
public void TinyGridAlignedTreeTest()
|
public void TinyGridAlignedTreeTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||||
// minimum size tree should be 3 root nodes wide
|
// minimum size tree should be 3 root nodes wide
|
||||||
Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
|
Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
|
||||||
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
||||||
@@ -644,7 +647,7 @@ public class QuadTreeTest
|
|||||||
public void TinyGridOffsetTreeTest()
|
public void TinyGridOffsetTreeTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(0, 0), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||||
// minimum size tree should be 3 root nodes wide
|
// minimum size tree should be 3 root nodes wide
|
||||||
Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
|
Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
|
||||||
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
||||||
@@ -683,7 +686,7 @@ public class QuadTreeTest
|
|||||||
public void TreeDetailLevelLimitTest()
|
public void TreeDetailLevelLimitTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), (byte) 8);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(0, 0), (byte) 8);
|
||||||
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeRootDetailLevel);
|
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeRootDetailLevel);
|
||||||
|
|
||||||
// valid detail levels
|
// valid detail levels
|
||||||
@@ -705,7 +708,7 @@ public class QuadTreeTest
|
|||||||
public void QuadNodeDetailLimitTest()
|
public void QuadNodeDetailLimitTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), (byte) 6);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(0, 0), (byte) 6);
|
||||||
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeRootDetailLevel);
|
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeRootDetailLevel);
|
||||||
|
|
||||||
// create the root node
|
// create the root node
|
||||||
@@ -817,8 +820,7 @@ public class QuadTreeTest
|
|||||||
@Test
|
@Test
|
||||||
public void quadNodeChildPositionOutOfBoundsTest()
|
public void quadNodeChildPositionOutOfBoundsTest()
|
||||||
{
|
{
|
||||||
int treeWidthInBlocks = 64;
|
QuadTree<Integer> tree = new QuadTree<>(64, 1, new DhBlockPos2D(-2, 0), (byte) 0);
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeWidthInBlocks, new DhBlockPos2D(-2, 0), (byte) 0);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -865,7 +867,7 @@ public class QuadTreeTest
|
|||||||
public void toStringTest()
|
public void toStringTest()
|
||||||
{
|
{
|
||||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), (byte) 6);
|
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getBlockDistanceForNodeClearing(), new DhBlockPos2D(0, 0), (byte) 6);
|
||||||
|
|
||||||
String treeString = tree.toString();
|
String treeString = tree.toString();
|
||||||
Assert.assertNotNull(treeString);
|
Assert.assertNotNull(treeString);
|
||||||
@@ -929,25 +931,28 @@ public class QuadTreeTest
|
|||||||
private abstract static class AbstractTestTreeParams
|
private abstract static class AbstractTestTreeParams
|
||||||
{
|
{
|
||||||
public abstract int getWidthInBlocks();
|
public abstract int getWidthInBlocks();
|
||||||
|
public abstract int getBlockDistanceForNodeClearing();
|
||||||
|
|
||||||
/** the tree should be slightly larger than the width in blocks to account for offset centers */
|
/** the tree should be slightly larger than the width in blocks to account for offset centers */
|
||||||
public int getWidthInRootNodes() { return MathUtil.log2(this.getWidthInBlocks()) + 2; }
|
public int getWidthInRootNodes() { return MathUtil.log2(this.getWidthInBlocks()) + 2; }
|
||||||
/** the top (root) detail level in the tree */
|
/** the top (root) detail level in the tree */
|
||||||
public byte getMinDetailLevel() { return (byte) MathUtil.log2(this.getWidthInBlocks()); }
|
public byte getRootDetailLevel() { return (byte) MathUtil.log2(this.getWidthInBlocks()); }
|
||||||
/** @return the block pos so that the tree's negative corner lines up with (0,0) */
|
/** @return the block pos so that the tree's negative corner lines up with (0,0) */
|
||||||
public DhBlockPos2D getPositiveEdgeCenterPos() { return new DhBlockPos2D(BitShiftUtil.powerOfTwo(this.getMinDetailLevel()) / 2, BitShiftUtil.powerOfTwo(this.getMinDetailLevel()) / 2); }
|
public DhBlockPos2D getPositiveEdgeCenterPos() { return new DhBlockPos2D(BitShiftUtil.powerOfTwo(this.getRootDetailLevel()) / 2, BitShiftUtil.powerOfTwo(this.getRootDetailLevel()) / 2); }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class LargeTestTree extends AbstractTestTreeParams
|
private static class LargeTestTree extends AbstractTestTreeParams
|
||||||
{
|
{
|
||||||
public int getWidthInBlocks() { return 8192; }
|
public int getWidthInBlocks() { return 8192; }
|
||||||
|
public int getBlockDistanceForNodeClearing() { return 16; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MediumTestTree extends AbstractTestTreeParams
|
private static class MediumTestTree extends AbstractTestTreeParams
|
||||||
{
|
{
|
||||||
public int getWidthInBlocks() { return 1024; }
|
public int getWidthInBlocks() { return 1024; }
|
||||||
|
public int getBlockDistanceForNodeClearing() { return 4; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,6 +960,7 @@ public class QuadTreeTest
|
|||||||
{
|
{
|
||||||
// top detail level = 6
|
// top detail level = 6
|
||||||
public int getWidthInBlocks() { return 32; }
|
public int getWidthInBlocks() { return 32; }
|
||||||
|
public int getBlockDistanceForNodeClearing() { return 1; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user