Compare commits

...

88 Commits

Author SHA1 Message Date
James Seibel 0b802ca362 comment about LOD only depth issue 2026-06-08 21:42:29 -05:00
James Seibel ea4375b215 Check if save folder exists for better error messages 2026-06-08 07:15:44 -05:00
s809 a6e94c39a7 Fix level requests not being sent more than once per session 2026-06-07 14:30:49 +05:00
James Seibel 8bd6b4837c Fix auto updater checking when disabled 2026-06-06 19:38:55 -05:00
James Seibel 679f871788 Remove MemUtil references due to native crashing 2026-06-06 18:10:59 -05:00
James Seibel b46f10755e remove cloud darkness and make clouds less dense 2026-06-06 17:59:11 -05:00
James Seibel 3cf799a10f fix hard fade cut-off with fog at low render dist 2026-06-06 16:56:45 -05:00
s809 4e99594a3f Add original level resource to level init 2026-06-07 01:32:41 +05:00
James Seibel 8a4f991906 lower DH cloud min height slightly 2026-06-06 11:04:49 -05:00
James Seibel ab634b3204 Darken DH cloud layers slightly 2026-06-06 11:04:33 -05:00
James Seibel b836f675e1 fix 3-layer clouds culling at low render distances 2026-06-06 11:04:20 -05:00
James Seibel a4e2003e0e Fix underwater fog rendering 2026-06-06 09:12:08 -05:00
s809 28a8bc39f2 Make level key requests work kinda 2026-06-04 23:05:14 +05:00
James Seibel 4f0a3afd93 Improve render engine config description 2026-06-03 07:44:37 -05:00
James Seibel 4ac56774fb Add error messages if DB becomes corrupted 2026-06-02 21:42:26 -05:00
James Seibel 18b0582152 Improve world load fail error messages 2026-06-02 21:42:05 -05:00
James Seibel b85c504995 add javadoc since to useCameraPositionForQualityDropOff 2026-06-02 19:00:50 -05:00
James Seibel cbff0cd9e9 add useCameraPositionForQualityDropOff config/api 2026-06-02 18:05:16 -05:00
James Seibel c28cf643b3 Fix camera position off-by-one with immersive portals 2026-06-02 17:39:54 -05:00
James Seibel 44aed79e78 add Dh prefix to DH math objects 2026-06-01 22:07:03 -05:00
James Seibel e192abe666 Fix LOD clouds not showing the correct colors 2026-06-01 21:45:20 -05:00
James Seibel 9754943752 Reduce rare generic object VBO upload race condition 2026-06-01 21:44:59 -05:00
James Seibel 299ab0f856 remove unused standalone jar files 2026-06-01 19:11:37 -05:00
James Seibel b97c35387c improve world gen/retrieve message 2026-06-01 19:03:01 -05:00
James Seibel f69ad051d1 merge immersive portals compat
Thanks Acuadragon100!
2026-06-01 07:30:59 -05:00
James Seibel 9231a48998 Clarify the world gen progress message 2026-05-30 19:44:29 -05:00
James Seibel e8f27f7da8 Add the option for 3 layer clouds 2026-05-30 16:38:55 -05:00
James Seibel db47a9e99f Remove deprecated EDhApiVanillaOverdraw 2026-05-30 11:39:30 -05:00
James Seibel 16f72066a8 Pool BufferQuad objects 2026-05-30 11:32:46 -05:00
James Seibel ca4d6f158a RenderDataPointReducingList GC optimization attempt 2026-05-30 11:20:03 -05:00
James Seibel ad9092c45c minor LodQuadTree optimization 2026-05-30 11:17:39 -05:00
James Seibel 95e4db2998 Reduce render event GC pressure 2026-05-30 09:17:21 -05:00
James Seibel 9fe5dcc16e minor GC optimization 2026-05-22 14:37:45 -05:00
James Seibel 8cd926f4ac uniform buffer change prep 2026-05-22 14:08:08 -05:00
James Seibel 7239b51073 fix native GL instanced generic rendering 2026-05-22 12:51:01 -05:00
James Seibel 262dcae36b fix renderingEngine lang 2026-05-22 09:59:42 -05:00
James Seibel 062f86c036 add DhApiBeforeFogRenderEvent 2026-05-22 09:36:10 -05:00
James Seibel 5b4029f0ad update comment 2026-05-20 17:30:53 -05:00
James Seibel faa4fa3782 Fix render API vs Engine enum 2026-05-20 07:27:30 -05:00
James Seibel 9db045d614 document openGL interfaces not used on Blaze3D
Also add API logic to determine if DH is handling the rendering natively or using an interpretation layer
2026-05-19 21:52:11 -05:00
James Seibel c81dc83bb1 clean up config menu, remove fake transparency 2026-05-19 19:18:04 -05:00
James Seibel b14701ef6b Fix unit test compiling 2026-05-19 07:26:05 -05:00
James Seibel 78f84f17cd Fix holes on LOD borders 2026-05-18 22:24:27 -05:00
James Seibel 7739e1cafd improve quadBuilder var name 2026-05-18 21:25:21 -05:00
James Seibel d39c04d9cf add a few api event @see's 2026-05-18 20:20:30 -05:00
James Seibel 492afa7328 add IDhApiLevelWrapper.getBlockColorPreApi() 2026-05-18 19:59:06 -05:00
James Seibel 9465512491 Fix blaze3d shader inputs for Vulkan 2026-05-17 18:12:51 -05:00
James Seibel 51b52a7d2a rename render doc LOD buffer label 2026-05-17 16:06:50 -05:00
James Seibel f106867091 Fix LodQuad tree not using a deamon thread 2026-05-17 16:06:08 -05:00
James Seibel 1c4908bbc5 Merge branch 'detached' into 'main'
Make renderingString show correct value if rendering is disabled for this dimension

See merge request distant-horizons-team/distant-horizons-core!100
2026-05-17 00:07:10 +00:00
James Seibel baa9b94a5d minor format cleanup 2026-05-17 00:06:49 +00:00
James Seibel 21136ba1ef remove unneeded TODO lines 2026-05-16 18:46:19 -05:00
James Seibel ec6f8255b5 IMcRenderWrapper.getMcRenderingApi() 2026-05-16 18:25:15 -05:00
James Seibel 2f9504b167 fix reverseZDepth uniform name 2026-05-16 17:54:30 -05:00
James Seibel f67d9e4e04 blaze3D shaders work on both GL and Vulkan 2026-05-16 15:49:41 -05:00
James Seibel c1644ad419 fix inverted Z clip plane 2026-05-16 14:06:10 -05:00
James Seibel e07557e6e3 add EDhRenderDepth 2026-05-16 10:48:16 -05:00
James Seibel 52b8a91dc5 fix cleanup threadpool preventing JVM shutdown 2026-05-16 10:16:10 -05:00
Vojtěch Šokala 64fe804c40 Make renderingString show correct value if rendering is disabled for this dimension 2026-05-16 13:33:42 +02:00
James Seibel 2dd5b82be3 Merge branch 'Vulkan' 2026-05-15 21:58:21 -05:00
James Seibel d1f0325f87 up api version 6.1.1 -> 7.0.0 2026-05-15 21:41:09 -05:00
James Seibel b10a367ce6 Fix DhApiBlockColorOverrideEvent method name 2026-05-15 21:40:55 -05:00
James Seibel 4dec387ca1 remove a todo 2026-05-15 18:23:55 -05:00
James Seibel fd3a8f7ddf Add MC Version locking to the config 2026-05-15 07:44:00 -05:00
James Seibel e3f586da56 temp comment out PooledDataSourceCheckoutTest 2026-05-12 21:56:18 -05:00
James Seibel 775984f651 add TODO 2026-05-11 22:01:00 -05:00
James Seibel 269f2c30fd Initial changes for Vulkan 2026-05-11 21:56:53 -05:00
James Seibel b674f49600 up version number 3.0.3 -> 3.0.4 2026-05-04 07:41:32 -05:00
James Seibel b592012ba8 remove dev from version number 2026-05-03 18:20:22 -05:00
James Seibel 5d1e8a44fd up api version 6.1.0 -> 6.1.1 2026-05-03 18:20:12 -05:00
James Seibel 40b27335ea Add stack getting for render tasks 2026-05-03 16:45:23 -05:00
James Seibel d0b07a5d2f remove accidental debug code 2026-05-03 16:40:35 -05:00
James Seibel cb0fee9780 fix generic renderer buffer leak on level close 2026-05-03 16:36:32 -05:00
James Seibel 895e9276cd Fix GL buffer GC in RenderContainer canceling 2026-05-03 15:46:01 -05:00
James Seibel 9ee0af8b01 Add BasicPhantomReference for debugging 2026-05-03 15:45:52 -05:00
James Seibel 69941fb7f8 DhApiBlockColorOverrideEvent use default alpha 2026-05-02 21:15:48 -05:00
James Seibel 36862a968f fix rare skylight application bug 2026-05-02 21:14:54 -05:00
James Seibel 27204336b2 cleanup lod buffer container closing 2026-05-02 21:14:14 -05:00
James Seibel 4846cf5019 comment out unnecessary shutdown logging 2026-05-02 21:13:07 -05:00
James Seibel f7f3c1146f separate shared phantom logging logic 2026-05-02 21:12:26 -05:00
James Seibel aaa5e958f0 Fix LOD shading applying incorrectly with Iris 2026-05-02 15:14:25 -05:00
James Seibel 726da953bd Merge branch 'distant-horizons-core-optimizations' 2026-05-02 11:35:26 -05:00
James Seibel c4f4935fdd Remove unused mac render code 2026-05-02 10:36:44 -05:00
James Seibel 3ef8bd7e20 Add position finder debug config 2026-04-29 07:35:16 -05:00
James Seibel ec72762067 use camera pos for detail calculations 2026-04-28 07:09:22 -05:00
James Seibel 4d0ed2a6dc fix null pointer on dedicated server shutdown 2026-04-27 07:48:06 -05:00
James Seibel 7b252b173b Fix wyncraft getting stuck at low LOD quality 2026-04-27 07:27:03 -05:00
James Seibel 7b0c66e3ae up version number 3.0.2 -> 3.0.3 2026-04-24 06:51:39 -05:00
127 changed files with 4833 additions and 3271 deletions
@@ -0,0 +1,17 @@
package com.seibel.distanthorizons.api.enums.config;
/**
* VULKAN, <br>
* OPEN_GL, <br>
*
* @see EDhApiRenderingEngine
*
* @since API 7.0.0
* @version 2026-3-10
*/
public enum EDhApiRenderingApi
{
VULKAN,
OPEN_GL;
}
@@ -1,16 +1,16 @@
package com.seibel.distanthorizons.api.enums.config; package com.seibel.distanthorizons.api.enums.config;
import com.seibel.distanthorizons.coreapi.ModInfo;
/** /**
* AUTO, <br> * AUTO, <br>
* OPEN_GL, <br> * OPEN_GL, <br>
* BLAZE_3D, <br><br> * BLAZE_3D, <br><br>
* *
* @since API 6.0.0 * @see EDhApiRenderingApi
*
* @since API 7.0.0
* @version 2026-3-10 * @version 2026-3-10
*/ */
public enum EDhApiRenderApi public enum EDhApiRenderingEngine
{ {
AUTO, AUTO,
OPEN_GL, OPEN_GL,
@@ -1,51 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.enums.config;
/**
* NEVER, <br>
* DYNAMIC, <br>
* ALWAYS <br> <br>
*
* This represents how far the LODs should overlap with
* the vanilla Minecraft terrain.
*
* @author James Seibel
* @since API 2.0.0
* @version 2024-4-6
*/
@Deprecated // not currently in use, if the config this enum represents is re-implemented, the deprecated flag can be removed
public enum EDhApiVanillaOverdraw
{
/**
* Don't draw LODs where a minecraft chunk could be.
* Use Overdraw Offset to tweak the border thickness.
*/
NEVER,
/**
* Draw LODs over the farther minecraft chunks.
* Dynamically decides the border thickness
*/
DYNAMIC,
/** Draw LODs over all minecraft chunks. */
ALWAYS,
}
@@ -21,25 +21,13 @@ package com.seibel.distanthorizons.api.enums.rendering;
/** /**
* DISABLED, <br> * DISABLED, <br>
* FAKE, <br>
* COMPLETE, <br> * COMPLETE, <br>
* *
* @since API 2.0.0 * @since API 2.0.0
* @version 2024-4-6 * @version 2026-05-19
*/ */
public enum EDhApiTransparency public enum EDhApiTransparency
{ {
DISABLED(false, false), DISABLED,
FAKE(true, true), COMPLETE;
COMPLETE(true, false);
public final boolean transparencyEnabled;
public final boolean fakeTransparencyEnabled;
EDhApiTransparency(boolean transparencyEnabled, boolean fakeTransparencyEnabled)
{
this.transparencyEnabled = transparencyEnabled;
this.fakeTransparencyEnabled = fakeTransparencyEnabled;
}
} }
@@ -85,6 +85,18 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
/** Modifies the quadratic function fake chunks use for horizontal quality drop-off. */ /** Modifies the quadratic function fake chunks use for horizontal quality drop-off. */
IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality(); IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality();
/**
* If true DH will try to use the camera position when
* determining LOD quality drop-off. <br>
* If false DH will use the player's position.
* <br><br>
* Enabling helps free-cam mods render correctly. <br>
* Disabling helps multi-camera mods render correctly (ie Immersive Portals or camera mods).
*
* @since API 7.0.0
*/
IDhApiConfigValue<Boolean> useCameraPositionForQualityDropOff();
IDhApiConfigValue<EDhApiTransparency> transparency(); IDhApiConfigValue<EDhApiTransparency> transparency();
/** Defines what blocks won't be rendered as LODs. */ /** Defines what blocks won't be rendered as LODs. */
@@ -19,9 +19,21 @@
package com.seibel.distanthorizons.api.interfaces.override.rendering; package com.seibel.distanthorizons.api.interfaces.override.rendering;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable; import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
/** /**
* <b>Note: </b><br>
* This is only used if Distant Horizons'
* {@link IDhApiRenderProxy#getRenderingApi()} returns {@link EDhApiRenderingEngine#OPEN_GL}
* and {@link IDhApiRenderProxy#isNativeRenderer()} returns true.
* ie this is only used when DH is doing native OpenGL rendering,
* if DH is using Blaze3D this interface will be ignored.
*
* @see IDhApiRenderProxy#getRenderingApi()
* @see IDhApiRenderProxy#isNativeRenderer()
*
* @author James Seibel * @author James Seibel
* @version 2024-1-24 * @version 2024-1-24
* @since API 2.0.0 * @since API 2.0.0
@@ -19,7 +19,9 @@
package com.seibel.distanthorizons.api.interfaces.override.rendering; package com.seibel.distanthorizons.api.interfaces.override.rendering;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable; import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup; import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
@@ -27,7 +29,16 @@ import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading; import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
/** /**
* <b>Note: </b><br>
* This is only used if Distant Horizons'
* {@link IDhApiRenderProxy#getRenderingApi()} returns {@link EDhApiRenderingEngine#OPEN_GL}
* and {@link IDhApiRenderProxy#isNativeRenderer()} returns true.
* ie this is only used when DH is doing native OpenGL rendering,
* if DH is using Blaze3D this interface will be ignored.
*
* @see IDhApiShaderProgram * @see IDhApiShaderProgram
* @see IDhApiRenderProxy#getRenderingApi()
* @see IDhApiRenderProxy#isNativeRenderer()
* *
* @author James Seibel * @author James Seibel
* @version 2024-7-11 * @version 2024-7-11
@@ -19,12 +19,23 @@
package com.seibel.distanthorizons.api.interfaces.override.rendering; package com.seibel.distanthorizons.api.interfaces.override.rendering;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable; import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f; import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
/** /**
* <b>Note: </b><br>
* This is only used if Distant Horizons'
* {@link IDhApiRenderProxy#getRenderingApi()} returns {@link EDhApiRenderingEngine#OPEN_GL}
* and {@link IDhApiRenderProxy#isNativeRenderer()} returns true.
* ie this is only used when DH is doing native OpenGL rendering,
* if DH is using Blaze3D this interface will be ignored.
*
* @see IDhApiGenericObjectShaderProgram * @see IDhApiGenericObjectShaderProgram
* @see IDhApiRenderProxy#getRenderingApi()
* @see IDhApiRenderProxy#isNativeRenderer()
* *
* @author James Seibel * @author James Seibel
* @version 2024-1-24 * @version 2024-1-24
@@ -19,6 +19,9 @@
package com.seibel.distanthorizons.api.interfaces.render; package com.seibel.distanthorizons.api.interfaces.render;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiAfterDhInitEvent;
import com.seibel.distanthorizons.api.objects.DhApiResult; import com.seibel.distanthorizons.api.objects.DhApiResult;
@@ -44,6 +47,33 @@ public interface IDhApiRenderProxy
*/ */
DhApiResult<Boolean> clearRenderDataCache(); DhApiResult<Boolean> clearRenderDataCache();
/**
* Returns which specific {@link EDhApiRenderingApi}
* Distant Horizons will use for rendering. <br><br>
*
* @throws IllegalStateException if no renderer has been bound yet,
* wait till after {@link DhApiAfterDhInitEvent} has been fired
*
* @see DhApiAfterDhInitEvent
* @since API 7.0.0
*/
EDhApiRenderingApi getRenderingApi() throws IllegalStateException;
/**
* Returns true if the current renderer
* is calling the base rendering API's method calls. <br>
* ie GL.drawArrays() for OpenGL. <Br><br>
*
* If DH is using a rendering interpretation layer like Blaze3D (Mojang's rendering API)
* this will return false.
*
* @throws IllegalStateException if no renderer has been bound yet,
* wait till after {@link DhApiAfterDhInitEvent} has been fired
*
* @see DhApiAfterDhInitEvent
* @since API 7.0.0
*/
boolean isNativeRenderer() throws IllegalStateException;
//=======================// //=======================//
@@ -21,8 +21,14 @@ package com.seibel.distanthorizons.api.interfaces.world;
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper; import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType; import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister; import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBlockColorOverrideEvent;
import com.seibel.distanthorizons.api.objects.DhApiResult;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import java.awt.*;
import java.io.File; import java.io.File;
/** /**
@@ -90,6 +96,26 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
*/ */
File getDhSaveFolder(); File getDhSaveFolder();
/**
* Returns the color DH would use for the given block/biome
* pair at the given world position before any API color overrides
* are considered. <br>
* API color overrides are ignored to prevent infinite
* loops if this event is triggered inside said API override.
* <br><br>
*
* Returns {@link DhApiResult#success} = false if {@link IDhApiLevelWrapper#getLevelType()} returns a {@link EDhApiLevelType#SERVER_LEVEL}
* (server levels have no concept of textures or colors).
*
* @see DhApiBlockColorOverrideEvent
* @since API 7.0.0
*/
DhApiResult<Color> getBlockColorPreApi(
IDhApiBlockStateWrapper blockStateWrapper,
IDhApiBiomeWrapper biomeWrapper,
int blockWorldPosX, int blockWorldPosY, int blockWorldPosZ,
IDhApiFullDataSource dataSource);
} }
@@ -59,23 +59,25 @@ public abstract class DhApiBeforeBufferRenderEvent implements IDhApiEvent<DhApiB
* Measured in blocks. * Measured in blocks.
* Should be applied to the model view matrix to move the buffer into its proper place. * Should be applied to the model view matrix to move the buffer into its proper place.
*/ */
public final DhApiVec3f modelPos; public DhApiVec3f modelPos;
public EventParam(DhApiRenderParam parent, DhApiVec3f modelPos)
public EventParam() { }
public void update(DhApiRenderParam parent, DhApiVec3f modelPos)
{ {
super(parent); super.update(parent);
this.modelPos = modelPos; this.modelPos = modelPos;
} }
@Override
public boolean getCopyBeforeFire() { return false; }
@Override @Override
public EventParam copy() public EventParam copy() { return this; }
{
return new EventParam(
this, this.modelPos.copy()
);
}
} }
} }
@@ -0,0 +1,128 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiCancelableEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiFogRenderParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiMutableFogRenderParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import com.seibel.distanthorizons.coreapi.util.ColorUtil;
/**
* Fired before DH renders its fog.
* Canceling this event disables fog for that frame.
*
* @author James Seibel
* @version 2026-05-20
* @since API 7.0.0
*/
public abstract class DhApiBeforeFogRenderEvent implements IDhApiCancelableEvent<DhApiBeforeFogRenderEvent.EventParam>
{
/** Fired before fog is generated. */
public abstract void beforeRender(DhApiCancelableEventParam<DhApiBeforeFogRenderEvent.EventParam> event);
//=========================//
// internal DH API methods //
//=========================//
@Override
public final void fireEvent(DhApiCancelableEventParam<DhApiBeforeFogRenderEvent.EventParam> event) { this.beforeRender(event); }
//==================//
// parameter object //
//==================//
public static class EventParam implements IDhApiEventParam
{
private DhApiRenderParam renderParam;
private DhApiFogRenderParam originalFogRenderParam;
private DhApiMutableFogRenderParam fogRenderParam;
//=============//
// constructor //
//=============//
//region
public EventParam() {}
public void update(DhApiRenderParam renderParam, DhApiFogRenderParam fogRenderParam)
{
this.renderParam = renderParam;
this.originalFogRenderParam = fogRenderParam;
this.fogRenderParam = new DhApiMutableFogRenderParam(fogRenderParam);
}
//endregion
//=================//
// getters/setters //
//=================//
//region
public DhApiRenderParam getRenderParam() { return this.renderParam; }
/** immutable, stores what DH would do without API intervention so API users have a reference point */
public DhApiFogRenderParam getOriginalFogRenderParam() { return this.originalFogRenderParam; }
/** mutable, can be modified by API users */
public DhApiMutableFogRenderParam getFogRenderParam() { return this.fogRenderParam; }
//endregion
//==========================//
// base api event overrides //
//==========================//
//region
/**
* Returns the same instance of this event.
* Copying this event isn't supported
* since the internal parameters must be mutated
* by API users in order to be tracked by DH's internal
* logic.
*/
@Override
public DhApiBeforeFogRenderEvent.EventParam copy() { return this; }
@Override
public boolean getCopyBeforeFire() { return false; }
//endregion
}
}
@@ -54,44 +54,45 @@ public abstract class DhApiBeforeGenericObjectRenderEvent implements IDhApiCance
public static class EventParam extends DhApiRenderParam implements IDhApiEventParam public static class EventParam extends DhApiRenderParam implements IDhApiEventParam
{ {
public final long boxGroupId; public long boxGroupId;
public final String resourceLocationNamespace; public String resourceLocationNamespace;
public final String resourceLocationPath; public String resourceLocationPath;
public EventParam(
DhApiRenderParam renderParam, //==============//
IDhApiRenderableBoxGroup boxGroup // constructors //
) //==============//
//region
public EventParam() { }
/** internal DH method */
public void update(DhApiRenderParam renderParam, IDhApiRenderableBoxGroup boxGroup)
{ {
super(renderParam); super.update(renderParam);
this.boxGroupId = boxGroup.getId(); this.boxGroupId = boxGroup.getId();
this.resourceLocationNamespace = boxGroup.getResourceLocationNamespace(); this.resourceLocationNamespace = boxGroup.getResourceLocationNamespace();
this.resourceLocationPath = boxGroup.getResourceLocationPath(); this.resourceLocationPath = boxGroup.getResourceLocationPath();
} }
public EventParam(
DhApiRenderParam renderParam, //endregion
long boxGroupId, String resourceLocationNamespace, String resourceLocationPath
)
{
super(renderParam);
this.boxGroupId = boxGroupId;
this.resourceLocationNamespace = resourceLocationNamespace;
this.resourceLocationPath = resourceLocationPath;
}
//================//
// base overrides //
//================//
//region
@Override @Override
public EventParam copy() public EventParam copy() { return this; }
{
return new EventParam( //endregion
this,
this.boxGroupId, this.resourceLocationNamespace, this.resourceLocationPath
);
}
} }
} }
@@ -19,20 +19,21 @@
package com.seibel.distanthorizons.api.methods.events.abstractEvents; package com.seibel.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial; import com.seibel.distanthorizons.api.interfaces.block.IDhApiBiomeWrapper;
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper; import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper; import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent; import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam; import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam; import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
import com.seibel.distanthorizons.api.objects.data.IDhApiFullDataSource;
import com.seibel.distanthorizons.coreapi.util.ColorUtil; import com.seibel.distanthorizons.coreapi.util.ColorUtil;
import java.awt.*;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Performance note: this event will be fired thousands of times on concurrent threads, * Performance note: this event will be fired millions of times on concurrent threads,
* make it thread safe and as fast as possible. <Br><Br> * make it thread safe and as fast as possible. <br>
* (If every LOD block goes through this event, On a 512 render distance world,
* at the medium quality preset, it will be triggered around 40,000,000 times.)
* <Br><Br>
* *
* This event is fired when DH needs to convert a {@link IDhApiBlockStateWrapper} * This event is fired when DH needs to convert a {@link IDhApiBlockStateWrapper}
* into a color for rendering. This event is fired after DH attempts to determine * into a color for rendering. This event is fired after DH attempts to determine
@@ -45,13 +46,14 @@ import java.util.concurrent.atomic.AtomicInteger;
* via {@link DhApiBlockStateWrapperCreatedEvent.EventParam#setAllowApiColorOverride(boolean)}. * via {@link DhApiBlockStateWrapperCreatedEvent.EventParam#setAllowApiColorOverride(boolean)}.
* *
* @author James Seibel * @author James Seibel
* @version 2026-04-14 * @version 2026-05-18
* @since API 6.0.0 * @since API 6.0.0
* @see IDhApiBlockStateWrapper * @see IDhApiBlockStateWrapper
*/ */
public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiBlockColorOverrideEvent.EventParam> public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiBlockColorOverrideEvent.EventParam>
{ {
public abstract void blockStateWrapperCreated(DhApiEventParam<EventParam> event); public abstract void onBlockColorOverridden(DhApiEventParam<EventParam> event);
//=========================// //=========================//
@@ -59,7 +61,8 @@ public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiB
//=========================// //=========================//
@Override @Override
public final void fireEvent(DhApiEventParam<EventParam> event) { this.blockStateWrapperCreated(event); } public final void fireEvent(DhApiEventParam<EventParam> event) { this.onBlockColorOverridden(event); }
//==================// //==================//
@@ -69,7 +72,9 @@ public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiB
public static class EventParam implements IDhApiEventParam public static class EventParam implements IDhApiEventParam
{ {
private IDhApiLevelWrapper levelWrapper; private IDhApiLevelWrapper levelWrapper;
private IDhApiFullDataSource dataSource;
private IDhApiBlockStateWrapper blockStateWrapper = null; private IDhApiBlockStateWrapper blockStateWrapper = null;
private IDhApiBiomeWrapper biomeWrapper = null;
private int colorAsInt = -1; private int colorAsInt = -1;
private int blockPosX = 0, blockPosY = 0, blockPosZ = 0; private int blockPosX = 0, blockPosY = 0, blockPosZ = 0;
@@ -78,17 +83,22 @@ public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiB
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
public EventParam() {} public EventParam() {}
public void update( public void update(
IDhApiLevelWrapper levelWrapper, IDhApiLevelWrapper levelWrapper,
IDhApiBlockStateWrapper blockStateWrapper, IDhApiFullDataSource dataSource,
IDhApiBlockStateWrapper blockStateWrapper,
IDhApiBiomeWrapper biomeWrapper,
int colorAsInt, int colorAsInt,
int blockPosX, int blockPosY, int blockPosZ) int blockPosX, int blockPosY, int blockPosZ)
{ {
this.levelWrapper = levelWrapper; this.levelWrapper = levelWrapper;
this.dataSource = dataSource;
this.blockStateWrapper = blockStateWrapper; this.blockStateWrapper = blockStateWrapper;
this.biomeWrapper = biomeWrapper;
this.colorAsInt = colorAsInt; this.colorAsInt = colorAsInt;
this.blockPosX = blockPosX; this.blockPosX = blockPosX;
@@ -96,22 +106,34 @@ public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiB
this.blockPosZ = blockPosZ; this.blockPosZ = blockPosZ;
} }
//endregion
//=================// //=================//
// getters/setters // // getters/setters //
//=================// //=================//
//region
public IDhApiBlockStateWrapper getBlockStateWrapper() { return this.blockStateWrapper; } public IDhApiBlockStateWrapper getBlockStateWrapper() { return this.blockStateWrapper; }
/** @since API 7.0.0 */
public IDhApiBiomeWrapper getBiomeWrapper() { return this.biomeWrapper; }
public IDhApiLevelWrapper getLevelWrapper() { return levelWrapper; } /** the level DH is resolving this block's color in. */
public IDhApiLevelWrapper getLevelWrapper() { return this.levelWrapper; }
/**
* The DH datasource that contains this block's position. Can be used to access adjacent
* {@link IDhApiBlockStateWrapper}'s and {@link IDhApiBiomeWrapper}'s for adjacent aware tinting.
* @since API 7.0.0
*/
public IDhApiFullDataSource getDataSource() { return this.dataSource; }
public int getColorAsInt() { return this.colorAsInt; } public int getColorAsInt() { return this.colorAsInt; }
public int getAlpha() { return ColorUtil.getAlpha(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 { setColor(255, red, green, blue); } public void setColor(int red, int green, int blue) throws IllegalArgumentException { this.setColor(this.getAlpha(), red, green, blue); }
/** /**
* Note: when if you set a partially transparent alpha channel the underlying {@link IDhApiBlockStateWrapper#getOpacity()} * Note: when if you set a partially transparent alpha channel the underlying {@link IDhApiBlockStateWrapper#getOpacity()}
* method should also return a non-opaque value. * method should also return a non-opaque value.
@@ -134,8 +156,15 @@ public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiB
/** @return the block's Z value in the world */ /** @return the block's Z value in the world */
public int getBlockPosZ() { return blockPosZ; } public int getBlockPosZ() { return blockPosZ; }
//endregion
//==========================//
// base api event overrides //
//==========================//
//region
/** /**
* Returns the same instance of this event. * Returns the same instance of this event.
* Copying this event isn't supported * Copying this event isn't supported
@@ -149,6 +178,8 @@ public abstract class DhApiBlockColorOverrideEvent implements IDhApiEvent<DhApiB
@Override @Override
public boolean getCopyBeforeFire() { return false; } public boolean getCopyBeforeFire() { return false; }
//endregion
} }
@@ -34,6 +34,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
* @author James Seibel * @author James Seibel
* @version 2023-6-23 * @version 2023-6-23
* @see IDhApiTerrainDataRepo * @see IDhApiTerrainDataRepo
* @see DhApiChunkProcessingEvent
* @since API 1.0.0 * @since API 1.0.0
*/ */
public abstract class DhApiChunkModifiedEvent implements IDhApiEvent<DhApiChunkModifiedEvent.EventParam> public abstract class DhApiChunkModifiedEvent implements IDhApiEvent<DhApiChunkModifiedEvent.EventParam>
@@ -51,6 +51,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
* *
* @author James Seibel * @author James Seibel
* @version 2025-09-29 * @version 2025-09-29
* @see DhApiChunkModifiedEvent
* @since API 4.1.0 * @since API 4.1.0
*/ */
public abstract class DhApiChunkProcessingEvent implements IDhApiEvent<DhApiChunkProcessingEvent.EventParam> public abstract class DhApiChunkProcessingEvent implements IDhApiEvent<DhApiChunkProcessingEvent.EventParam>
@@ -0,0 +1,202 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogDirection;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFarFogConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFogConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiHeightFogConfig;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import java.awt.*;
/**
* Contains all the information needed to render Distant Horizons' fog.
*
* @see IDhApiFogConfig
* @see IDhApiFarFogConfig
* @see IDhApiHeightFogConfig
*
* @author James Seibel
* @version 2026-05-20
* @since API 7.0.0
*/
public class DhApiFogRenderParam implements IDhApiEventParam
{
protected Color fogColor;
public Color getFogColor() { return this.fogColor; }
// far fog //
//region
protected EDhApiFogFalloff farFogFalloff;
/** @see IDhApiFarFogConfig#farFogFalloff() */
public EDhApiFogFalloff getFarFogFalloff() { return this.farFogFalloff; }
protected float farFogStartPercent;
/** @see IDhApiFarFogConfig#farFogStartDistance() */
public float getFarFogStartPercent() { return this.farFogStartPercent; }
protected float farFogEndPercent;
/** @see IDhApiFarFogConfig#farFogEndDistance() */
public float getFarFogEndPercent() { return this.farFogEndPercent; }
protected float farFogMinThickness;
/** @see IDhApiFarFogConfig#farFogMinThickness() */
public float getFarFogMinThickness() { return this.farFogMinThickness; }
protected float farFogMaxThickness;
/** @see IDhApiFarFogConfig#farFogMaxThickness() */
public float getFarFogMaxThickness() { return this.farFogMaxThickness; }
protected float farFogDensity;
/** @see IDhApiFarFogConfig#farFogDensity() */
public float getFarFogDensity() { return this.farFogDensity; }
//endregion
// height fog //
//region
protected EDhApiFogFalloff heightFogFalloff;
/** @see IDhApiHeightFogConfig#heightFogFalloff() */
public EDhApiFogFalloff getHeightFogFalloff() { return this.heightFogFalloff; }
protected EDhApiHeightFogMixMode heightFogMixingMode;
/** @see IDhApiHeightFogConfig#heightFogMixMode() */
public EDhApiHeightFogMixMode getHeightFogMixingMode() { return this.heightFogMixingMode; }
protected EDhApiHeightFogDirection heightFogDirection;
/** @see IDhApiHeightFogConfig#heightFogDirection() */
public EDhApiHeightFogDirection getHeightFogDirection() { return this.heightFogDirection; }
protected float heightFogBaseHeight;
/** @see IDhApiHeightFogConfig#heightFogBaseHeight() */
public float getHeightFogBaseHeight() { return this.heightFogBaseHeight; }
protected float heightFogStartPercent;
/** @see IDhApiHeightFogConfig#heightFogStartingHeightPercent() */
public float getHeightFogStartPercent() { return this.heightFogStartPercent; }
protected float heightFogEndPercent;
/** @see IDhApiHeightFogConfig#heightFogEndingHeightPercent() */
public float getHeightFogEndPercent() { return this.heightFogEndPercent; }
protected float heightFogMinThickness;
/** @see IDhApiHeightFogConfig#heightFogMinThickness() */
public float getHeightFogMinThickness() { return this.heightFogMinThickness; }
protected float heightFogMaxThickness;
/** @see IDhApiHeightFogConfig#heightFogMaxThickness() */
public float getHeightFogMaxThickness() { return this.heightFogMaxThickness; }
protected float heightFogDensity;
/** @see IDhApiHeightFogConfig#heightFogDensity() */
public float getHeightFogDensity() { return this.heightFogDensity; }
//endregion
//==============//
// constructors //
//==============//
//region
public DhApiFogRenderParam(DhApiFogRenderParam parent)
{
this(
parent.fogColor,
// far fog
parent.farFogFalloff,
parent.farFogStartPercent, parent.farFogEndPercent,
parent.farFogMinThickness, parent.farFogEndPercent,
parent.farFogDensity,
// height fog
parent.heightFogFalloff,
parent.heightFogMixingMode, parent.heightFogDirection,
parent.heightFogBaseHeight,
parent.heightFogStartPercent, parent.heightFogEndPercent,
parent.heightFogMinThickness, parent.heightFogMaxThickness,
parent.heightFogDensity
);
}
public DhApiFogRenderParam(
Color fogColor,
// far fog
EDhApiFogFalloff farFogFalloff,
float farFogStartPercent, float farFogEndPercent,
float farFogMinThickness, float farFogMaxThickness,
float farFogDensity,
// height fog
EDhApiFogFalloff heightFogFalloff,
EDhApiHeightFogMixMode heightFogMixingMode, EDhApiHeightFogDirection heightFogDirection,
float heightFogBaseHeight,
float heightFogStartPercent, float heightFogEndPercent,
float heightFogMinThickness, float heightFogMaxThickness,
float heightFogDensity
)
{
this.fogColor = fogColor;
// far fog
this.farFogFalloff = farFogFalloff;
this.farFogStartPercent = farFogStartPercent;
this.farFogEndPercent = farFogEndPercent;
this.farFogMinThickness = farFogMinThickness;
this.farFogMaxThickness = farFogMaxThickness;
this.farFogDensity = farFogDensity;
// height fog
this.heightFogFalloff = heightFogFalloff;
this.heightFogMixingMode = heightFogMixingMode;
this.heightFogDirection = heightFogDirection;
this.heightFogBaseHeight = heightFogBaseHeight;
this.heightFogStartPercent = heightFogStartPercent;
this.heightFogEndPercent = heightFogEndPercent;
this.heightFogMinThickness = heightFogMinThickness;
this.heightFogMaxThickness = heightFogMaxThickness;
this.heightFogDensity = heightFogDensity;
}
//endregion
//================//
// base overrides //
//================//
//region
@Override
public DhApiFogRenderParam copy() { return new DhApiFogRenderParam(this); }
//endregion
}
@@ -0,0 +1,117 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogDirection;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFarFogConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFogConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiHeightFogConfig;
import java.awt.*;
/**
* A mutable version of {@link DhApiFogRenderParam} to allow
* API modification of DH's fog rendering.
*
* @see IDhApiFogConfig
* @see IDhApiFarFogConfig
* @see IDhApiHeightFogConfig
* @see DhApiFogRenderParam
*
* @author James Seibel
* @version 2026-05-20
* @since API 7.0.0
*/
public class DhApiMutableFogRenderParam extends DhApiFogRenderParam
{
public void setFogColor(Color fogColor) { this.fogColor = fogColor; }
// far fog //
//region
/** @see IDhApiFarFogConfig#farFogFalloff() */
public void setFarFogFalloff(EDhApiFogFalloff farFogFalloff) { this.farFogFalloff = farFogFalloff; }
/** @see IDhApiFarFogConfig#farFogStartDistance() */
public void setFarFogStartPercent(float farFogStartPercent) { this.farFogStartPercent = farFogStartPercent; }
/** @see IDhApiFarFogConfig#farFogEndDistance() */
public void setFarFogEndPercent(float farFogEndPercent) { this.farFogEndPercent = farFogEndPercent; }
/** @see IDhApiFarFogConfig#farFogMinThickness() */
public void setFarFogMinThickness(float farFogMinThickness) { this.farFogMinThickness = farFogMinThickness; }
/** @see IDhApiFarFogConfig#farFogMaxThickness() */
public void setFarFogMaxThickness(float farFogMaxThickness) { this.farFogMaxThickness = farFogMaxThickness; }
/** @see IDhApiFarFogConfig#farFogDensity() */
public void setFarFogDensity(float farFogDensity) { this.farFogDensity = farFogDensity; }
//endregion
// height fog //
//region
/** @see IDhApiHeightFogConfig#heightFogFalloff() */
public void setHeightFogFalloff(EDhApiFogFalloff heightFogFalloff) { this.heightFogFalloff = heightFogFalloff; }
/** @see IDhApiHeightFogConfig#heightFogMixMode() */
public void setHeightFogMixingMode(EDhApiHeightFogMixMode heightFogMixingMode) { this.heightFogMixingMode = heightFogMixingMode; }
/** @see IDhApiHeightFogConfig#heightFogDirection() */
public void setHeightFogDirection(EDhApiHeightFogDirection heightFogDirection) { this.heightFogDirection = heightFogDirection; }
/** @see IDhApiHeightFogConfig#heightFogBaseHeight() */
public void setHeightFogBaseHeight(float heightFogBaseHeight) { this.heightFogBaseHeight = heightFogBaseHeight; }
/** @see IDhApiHeightFogConfig#heightFogStartingHeightPercent() */
public void setHeightFogStartPercent(float heightFogStartPercent) { this.heightFogStartPercent = heightFogStartPercent; }
/** @see IDhApiHeightFogConfig#heightFogEndingHeightPercent() */
public void setHeightFogEndPercent(float heightFogEnd) { this.heightFogEndPercent = heightFogEnd; }
/** @see IDhApiHeightFogConfig#heightFogMinThickness() */
public void setHeightFogMinThickness(float heightFogMinThickness) { this.heightFogMinThickness = heightFogMinThickness; }
/** @see IDhApiHeightFogConfig#heightFogMaxThickness() */
public void setHeightFogMaxThickness(float heightFogMaxThickness) { this.heightFogMaxThickness = heightFogMaxThickness; }
/** @see IDhApiHeightFogConfig#heightFogDensity() */
public void setHeightFogDensity(float heightFogDensity) { this.heightFogDensity = heightFogDensity; }
//endregion
//==============//
// constructors //
//==============//
//region
public DhApiMutableFogRenderParam(DhApiFogRenderParam parent)
{ super(parent); }
//endregion
//================//
// base overrides //
//================//
//region
@Override
public DhApiMutableFogRenderParam copy() { return new DhApiMutableFogRenderParam(this); }
//endregion
}
@@ -35,62 +35,69 @@ import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
public class DhApiRenderParam implements IDhApiEventParam public class DhApiRenderParam implements IDhApiEventParam
{ {
/** Indicates what render pass DH is currently rendering */ /** Indicates what render pass DH is currently rendering */
public final EDhApiRenderPass renderPass; public EDhApiRenderPass renderPass;
/** Indicates how far into this tick the frame is. */ /** Indicates how far into this tick the frame is. */
public final float partialTicks; public float partialTicks;
/** /**
* Indicates DH's near clip plane, measured in blocks. * Indicates DH's near clip plane, measured in blocks.
* Note: this may change based on time, player speed, and other factors. * Note: this may change based on time, player speed, and other factors.
*/ */
public final float nearClipPlane; public float nearClipPlane;
/** /**
* Indicates DH's far clip plane, measured in blocks. * Indicates DH's far clip plane, measured in blocks.
* Note: this may change based on time, player speed, and other factors. * Note: this may change based on time, player speed, and other factors.
*/ */
public final float farClipPlane; public float farClipPlane;
/** The projection matrix Minecraft is using to render this frame. */ /** The projection matrix Minecraft is using to render this frame. */
public final DhApiMat4f mcProjectionMatrix; public final DhApiMat4f mcProjectionMatrix = new DhApiMat4f();
/** The model view matrix Minecraft is using to render this frame. */ /** The model view matrix Minecraft is using to render this frame. */
public final DhApiMat4f mcModelViewMatrix; public final DhApiMat4f mcModelViewMatrix = new DhApiMat4f();
public final DhApiMat4f mcInverseMvmProjectionMatrix = new DhApiMat4f();
/** The projection matrix Distant Horizons is using to render this frame. */ /** The projection matrix Distant Horizons is using to render this frame. */
public final DhApiMat4f dhProjectionMatrix; public final DhApiMat4f dhProjectionMatrix = new DhApiMat4f();
/** The model view matrix Distant Horizons is using to render this frame. */ /** The model view matrix Distant Horizons is using to render this frame. */
public final DhApiMat4f dhModelViewMatrix; public final DhApiMat4f dhModelViewMatrix = new DhApiMat4f();
/** combination of the MVM and projection matrices */ /** combination of the MVM and projection matrices */
public final DhApiMat4f dhMvmProjMatrix; public final DhApiMat4f dhMvmProjMatrix = new DhApiMat4f();
public final DhApiMat4f dhInverseMvmProjectionMatrix = new DhApiMat4f();
public final int worldYOffset; public int worldYOffset;
/** /**
* The level currently being rendered. * The level currently being rendered.
* *
* @since API 5.1.0 * @since API 5.1.0
*/ */
public final IDhApiLevelWrapper clientLevelWrapper; public IDhApiLevelWrapper clientLevelWrapper;
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public DhApiRenderParam(DhApiRenderParam parent) public DhApiRenderParam() {}
/** Internal DH method */
public void update(DhApiRenderParam param)
{ {
this( this.update(
parent.renderPass, param.renderPass,
parent.partialTicks, param.partialTicks,
parent.nearClipPlane, parent.farClipPlane, param.nearClipPlane, param.farClipPlane,
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(), param.mcProjectionMatrix, param.mcModelViewMatrix,
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(), param.dhProjectionMatrix, param.mcModelViewMatrix,
parent.worldYOffset, param.worldYOffset,
parent.clientLevelWrapper param.clientLevelWrapper
); );
} }
public DhApiRenderParam( /** Internal DH method */
public void update(
EDhApiRenderPass renderPass, EDhApiRenderPass renderPass,
float newPartialTicks, float newPartialTicks,
float nearClipPlane, float farClipPlane, float nearClipPlane, float farClipPlane,
@@ -107,29 +114,51 @@ public class DhApiRenderParam implements IDhApiEventParam
this.farClipPlane = farClipPlane; this.farClipPlane = farClipPlane;
this.nearClipPlane = nearClipPlane; this.nearClipPlane = nearClipPlane;
this.mcProjectionMatrix = newMcProjectionMatrix; // mc matricies
this.mcModelViewMatrix = newMcModelViewMatrix; {
this.mcProjectionMatrix.set(newMcProjectionMatrix);
this.mcModelViewMatrix.set(newMcModelViewMatrix);
// inverse mvm Proj
this.mcInverseMvmProjectionMatrix.set(newMcProjectionMatrix);
this.mcInverseMvmProjectionMatrix.invert();
}
this.dhProjectionMatrix = newDhProjectionMatrix; // dh matricies
this.dhModelViewMatrix = newDhModelViewMatrix; {
this.dhProjectionMatrix.set(newDhProjectionMatrix);
DhApiMat4f combinedMatrix = new DhApiMat4f(this.dhProjectionMatrix); this.dhModelViewMatrix.set(newDhModelViewMatrix);
combinedMatrix.multiply(this.dhModelViewMatrix);
this.dhMvmProjMatrix = combinedMatrix; // proj
this.dhMvmProjMatrix.set(this.dhProjectionMatrix);
this.dhMvmProjMatrix.multiply(this.dhModelViewMatrix);
// inverse mvm Proj
this.dhInverseMvmProjectionMatrix.set(this.dhMvmProjMatrix);
this.dhInverseMvmProjectionMatrix.invert();
}
this.worldYOffset = worldYOffset; this.worldYOffset = worldYOffset;
this.clientLevelWrapper = clientLevelWrapper; this.clientLevelWrapper = clientLevelWrapper;
} }
//endregion
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override @Override
public DhApiRenderParam copy() { return new DhApiRenderParam(this); } public boolean getCopyBeforeFire() { return false; }
@Override
public DhApiRenderParam copy() { return this; }
//endregion
@@ -46,7 +46,7 @@ public class DhApiResult<T>
// these constructors are private because the create... methods below are easier to understand // these constructors are private because the create methods below are easier to understand
private DhApiResult(boolean success, String message) { this(success, message, null); } private DhApiResult(boolean success, String message) { this(success, message, null); }
private DhApiResult(boolean success, String message, T payload) private DhApiResult(boolean success, String message, T payload)
{ {
@@ -21,6 +21,8 @@ package com.seibel.distanthorizons.api.objects.math;
import com.seibel.distanthorizons.api.interfaces.util.IDhApiCopyable; import com.seibel.distanthorizons.api.interfaces.util.IDhApiCopyable;
import java.nio.FloatBuffer;
/** /**
* An (almost) exact copy of Minecraft's 1.16.5 * An (almost) exact copy of Minecraft's 1.16.5
* implementation of a 4x4 float matrix. <br><br> * implementation of a 4x4 float matrix. <br><br>
@@ -33,7 +35,7 @@ import com.seibel.distanthorizons.api.interfaces.util.IDhApiCopyable;
* </code> * </code>
* *
* @author James Seibel * @author James Seibel
* @version 2024-6-30 * @version 2026-05-22
*/ */
public class DhApiMat4f implements IDhApiCopyable public class DhApiMat4f implements IDhApiCopyable
{ {
@@ -62,6 +64,7 @@ public class DhApiMat4f implements IDhApiCopyable
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
//region
public DhApiMat4f() { /* all values are 0 */ } public DhApiMat4f() { /* all values are 0 */ }
@@ -71,14 +74,17 @@ public class DhApiMat4f implements IDhApiCopyable
this.m01 = sourceMatrix.m01; this.m01 = sourceMatrix.m01;
this.m02 = sourceMatrix.m02; this.m02 = sourceMatrix.m02;
this.m03 = sourceMatrix.m03; this.m03 = sourceMatrix.m03;
this.m10 = sourceMatrix.m10; this.m10 = sourceMatrix.m10;
this.m11 = sourceMatrix.m11; this.m11 = sourceMatrix.m11;
this.m12 = sourceMatrix.m12; this.m12 = sourceMatrix.m12;
this.m13 = sourceMatrix.m13; this.m13 = sourceMatrix.m13;
this.m20 = sourceMatrix.m20; this.m20 = sourceMatrix.m20;
this.m21 = sourceMatrix.m21; this.m21 = sourceMatrix.m21;
this.m22 = sourceMatrix.m22; this.m22 = sourceMatrix.m22;
this.m23 = sourceMatrix.m23; this.m23 = sourceMatrix.m23;
this.m30 = sourceMatrix.m30; this.m30 = sourceMatrix.m30;
this.m31 = sourceMatrix.m31; this.m31 = sourceMatrix.m31;
this.m32 = sourceMatrix.m32; this.m32 = sourceMatrix.m32;
@@ -109,12 +115,74 @@ public class DhApiMat4f implements IDhApiCopyable
this.m33 = values[15]; this.m33 = values[15];
} }
//endregion
//=========// //=========//
// methods // // methods //
//=========// //=========//
//region
/** Returns the values of this matrix in row major order (AKA rows then columns) */
public float[] getValuesAsArray()
{
float[] array = new float[16];
this.putValuesInArray(array);
return array;
}
/**
* Returns the values of this matrix in row major order (AKA rows then columns)
* @since API 7.0.0
*/
public void putValuesInArray(float[] array)
{
array[0] = this.m00;
array[1] = this.m01;
array[2] = this.m02;
array[3] = this.m03;
array[4] = this.m10;
array[5] = this.m11;
array[6] = this.m12;
array[7] = this.m13;
array[8] = this.m20;
array[9] = this.m21;
array[10] = this.m22;
array[11] = this.m23;
array[12] = this.m30;
array[13] = this.m31;
array[14] = this.m32;
array[15] = this.m33;
}
/** @since API 7.0.0 */
public void set(DhApiMat4f mat)
{
this.m00 = mat.m00;
this.m01 = mat.m01;
this.m02 = mat.m02;
this.m03 = mat.m03;
this.m10 = mat.m10;
this.m11 = mat.m11;
this.m12 = mat.m12;
this.m13 = mat.m13;
this.m20 = mat.m20;
this.m21 = mat.m21;
this.m22 = mat.m22;
this.m23 = mat.m23;
this.m30 = mat.m30;
this.m31 = mat.m31;
this.m32 = mat.m32;
this.m33 = mat.m33;
}
public void setIdentity() public void setIdentity()
{ {
@@ -279,47 +347,14 @@ public class DhApiMat4f implements IDhApiCopyable
this.m33 *= scalar; this.m33 *= scalar;
} }
//endregion
//==================//
// Distant Horizons //
// methods //
//==================//
private static int getArrayIndex(int xIndex, int zIndex) { return (zIndex * 4) + xIndex; }
/** Returns the values of this matrix in row major order (AKA rows then columns) */
public float[] getValuesAsArray()
{
return new float[]{
this.m00,
this.m01,
this.m02,
this.m03,
this.m10,
this.m11,
this.m12,
this.m13,
this.m20,
this.m21,
this.m22,
this.m23,
this.m30,
this.m31,
this.m32,
this.m33,
};
}
//================// //================//
// base overrides // // base overrides //
//================// //================//
//region
@Override @Override
public boolean equals(Object obj) public boolean equals(Object obj)
@@ -388,4 +423,8 @@ public class DhApiMat4f implements IDhApiCopyable
@Override @Override
public DhApiMat4f copy() { return new DhApiMat4f(this); } public DhApiMat4f copy() { return new DhApiMat4f(this); }
//endregion
} }
@@ -35,7 +35,19 @@ import java.util.Map;
*/ */
public class DependencyInjector<BindableType extends IBindable> implements IDependencyInjector<BindableType> // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it public class DependencyInjector<BindableType extends IBindable> implements IDependencyInjector<BindableType> // Note to self: Don't try adding a generic type to IDhApiEvent, the constructor won't accept it
{ {
protected final Map<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>(); /**
* empty list is to reduce GC pressure slightly in the common path
* that {@link DependencyInjector#getInternalLogic(Class, boolean)} is called
* when nothing has been bound.
*/
private static final ArrayList<?> EMPTY_GET_ALL_LIST = new ArrayList<>();
static
{
EMPTY_GET_ALL_LIST.add(null);
}
protected final HashMap<Class<? extends BindableType>, ArrayList<BindableType>> dependencies = new HashMap<>();
/** Internal class reference to BindableType since we can't get it any other way. */ /** Internal class reference to BindableType since we can't get it any other way. */
protected final Class<? extends BindableType> bindableInterface; protected final Class<? extends BindableType> bindableInterface;
@@ -246,9 +258,7 @@ public class DependencyInjector<BindableType extends IBindable> implements IDepe
// return an empty list to prevent null pointers // return an empty list to prevent null pointers
ArrayList<T> emptyList = new ArrayList<T>(); return (ArrayList<T>)EMPTY_GET_ALL_LIST;
emptyList.add(null);
return emptyList;
} }
//endregion //endregion
@@ -31,7 +31,7 @@ public final class ModInfo
public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial"; public static final String DEDICATED_SERVER_INITIAL_PATH = "dedicated_server_initial";
/** 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 = 15;
/** /**
* The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH) * The full plugin channel name (RESOURCE_NAMESPACE:WRAPPER_PACKET_PATH)
@@ -43,14 +43,14 @@ public final class ModInfo
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.2-b"; public static final String VERSION = "3.0.4-b-dev";
/** 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 = 7;
/** 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 = 1; public static final int API_MINOR_VERSION = 0;
/** 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;
@@ -85,17 +85,21 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
public IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality() public IDhApiConfigValue<EDhApiHorizontalQuality> horizontalQuality()
{ return new DhApiConfigValue<EDhApiHorizontalQuality, EDhApiHorizontalQuality>(Config.Client.Advanced.Graphics.Quality.horizontalQuality); } { return new DhApiConfigValue<EDhApiHorizontalQuality, EDhApiHorizontalQuality>(Config.Client.Advanced.Graphics.Quality.horizontalQuality); }
@Override
public IDhApiConfigValue<Boolean> useCameraPositionForQualityDropOff()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Quality.useCameraPositionForQualityDropOff); }
@Override @Override
public IDhApiConfigValue<EDhApiTransparency> transparency() public IDhApiConfigValue<EDhApiTransparency> transparency()
{ return new DhApiConfigValue<EDhApiTransparency, EDhApiTransparency>(Config.Client.Advanced.Graphics.Quality.transparency); } { return new DhApiConfigValue<EDhApiTransparency, EDhApiTransparency>(Config.Client.Advanced.Graphics.Quality.transparency); }
@Override @Override
public IDhApiConfigValue<EDhApiBlocksToAvoid> blocksToAvoid() public IDhApiConfigValue<EDhApiBlocksToAvoid> blocksToAvoid()
{ return new DhApiConfigValue<EDhApiBlocksToAvoid, EDhApiBlocksToAvoid>(Config.Client.Advanced.Graphics.Quality.blocksToIgnore); } { return new DhApiConfigValue<EDhApiBlocksToAvoid, EDhApiBlocksToAvoid>(Config.Client.Advanced.Graphics.Culling.blocksToIgnore); }
@Override @Override
public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks() public IDhApiConfigValue<Boolean> tintWithAvoidedBlocks()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks); } { return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.Culling.tintWithAvoidedBlocks); }
@Override @Override
public IDhApiConfigValue<Integer> getBiomeBlending() public IDhApiConfigValue<Integer> getBiomeBlending()
@@ -39,15 +39,15 @@ import com.seibel.distanthorizons.core.util.DhApiTerrainDataPointUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil; import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RayCastUtil; import com.seibel.distanthorizons.core.util.RayCastUtil;
import com.seibel.distanthorizons.core.util.math.Vec3f; import com.seibel.distanthorizons.core.util.math.DhVec3f;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.util.math.Vec3i; import com.seibel.distanthorizons.core.util.math.DhVec3i;
import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongArrayList;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -71,24 +71,28 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
// debugging values // debugging values
private static volatile boolean debugThreadRunning = false; private static volatile boolean debugThreadRunning = false;
private static DhApiTerrainDataCache debugDataCache = new DhApiTerrainDataCache(); private static DhApiTerrainDataCache debugDataCache = new DhApiTerrainDataCache();
private static DhApiVec3i currentDebugVec3i = new Vec3i(); private static DhApiVec3i currentDebugVec3i = new DhVec3i();
//=============// //=============//
// constructor // // constructor //
//=============// //=============//
//region
private DhApiTerrainDataRepo() private DhApiTerrainDataRepo()
{ {
} }
//endregion
//================// //================//
// Getter Methods // // Getter Methods //
//================// //================//
//region
@Override @Override
public DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache)
@@ -109,9 +113,12 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, IDhApiTerrainDataCache dataCache) public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(detailLevel, posX, posZ), dataCache); } { return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, DhSectionPos.encode(detailLevel, posX, posZ), dataCache); }
//endregion
// private getters // // private getters //
//region
/** Returns a single API terrain datapoint that contains the given Y block position */ /** Returns a single API terrain datapoint that contains the given Y block position */
private static DhApiResult<DhApiTerrainDataPoint> getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, long requestedColumnPos, Integer blockYPos, IDhApiTerrainDataCache dataCache) private static DhApiResult<DhApiTerrainDataPoint> getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, long requestedColumnPos, Integer blockYPos, IDhApiTerrainDataCache dataCache)
@@ -340,11 +347,14 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
} }
} }
//endregion
//====================// //====================//
// raycasting methods // // raycasting methods //
//====================// //====================//
//region
@Override @Override
public DhApiResult<DhApiRaycastResult> raycast( public DhApiResult<DhApiRaycastResult> raycast(
@@ -356,8 +366,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
IDhApiTerrainDataCache dataCache) IDhApiTerrainDataCache dataCache)
{ {
return this.raycastLodData(levelWrapper, return this.raycastLodData(levelWrapper,
new Vec3d(rayOriginX, rayOriginY, rayOriginZ), new DhVec3d(rayOriginX, rayOriginY, rayOriginZ),
new Vec3f(rayDirectionX, rayDirectionY, rayDirectionZ), new DhVec3f(rayDirectionX, rayDirectionY, rayDirectionZ),
maxRayBlockLength, dataCache); maxRayBlockLength, dataCache);
} }
@@ -369,7 +379,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
*/ */
private DhApiResult<DhApiRaycastResult> raycastLodData( private DhApiResult<DhApiRaycastResult> raycastLodData(
IDhApiLevelWrapper levelWrapper, IDhApiLevelWrapper levelWrapper,
Vec3d rayOrigin, Vec3f rayDirection, DhVec3d rayOrigin, DhVec3f rayDirection,
int maxRayBlockLength, int maxRayBlockLength,
@Nullable @Nullable
IDhApiTerrainDataCache dataCache) IDhApiTerrainDataCache dataCache)
@@ -386,9 +396,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
int currentLength = 0; int currentLength = 0;
// the exact position of this step // the exact position of this step
Vec3d exactPos = new Vec3d(rayOrigin.x, rayOrigin.y, rayOrigin.z); DhVec3d exactPos = new DhVec3d(rayOrigin.x, rayOrigin.y, rayOrigin.z);
// the block position for this step // the block position for this step
Vec3i blockPos = new Vec3i((int) Math.round(rayOrigin.x), (int) Math.round(rayOrigin.y), (int) Math.round(rayOrigin.z)); DhVec3i blockPos = new DhVec3i((int) Math.round(rayOrigin.x), (int) Math.round(rayOrigin.y), (int) Math.round(rayOrigin.z));
DhApiRaycastResult closetFoundDataPoint = null; DhApiRaycastResult closetFoundDataPoint = null;
@@ -398,8 +408,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
&& currentLength <= maxRayBlockLength) && currentLength <= maxRayBlockLength)
{ {
// get the LOD columns around this position // get the LOD columns around this position
ArrayList<Vec3i> columnPositions = getIntersectingColumnsAtPosition(blockPos, rayDirection); ArrayList<DhVec3i> columnPositions = getIntersectingColumnsAtPosition(blockPos, rayDirection);
for (Vec3i columnPos : columnPositions) for (DhVec3i columnPos : columnPositions)
{ {
// check each column // check each column
DhApiResult<DhApiTerrainDataPoint[]> result = this.getColumnDataAtBlockPos(levelWrapper, columnPos.x, columnPos.z, dataCache); DhApiResult<DhApiTerrainDataPoint[]> result = this.getColumnDataAtBlockPos(levelWrapper, columnPos.x, columnPos.z, dataCache);
@@ -416,7 +426,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
if (dataPoint.blockStateWrapper != null && !dataPoint.blockStateWrapper.isAir()) if (dataPoint.blockStateWrapper != null && !dataPoint.blockStateWrapper.isAir())
{ {
// does this LOD contain the given Y position? // does this LOD contain the given Y position?
Vec3i dataPointPos = new Vec3i(columnPos.x, dataPoint.bottomYBlockPos, columnPos.z); DhVec3i dataPointPos = new DhVec3i(columnPos.x, dataPoint.bottomYBlockPos, columnPos.z);
if (exactPos.y >= dataPoint.bottomYBlockPos if (exactPos.y >= dataPoint.bottomYBlockPos
&& exactPos.y <= dataPoint.topYBlockPos) && exactPos.y <= dataPoint.topYBlockPos)
{ {
@@ -469,15 +479,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
* *
* Used to make sure the raycast step doesn't accidentally walk over any adjacent data. * Used to make sure the raycast step doesn't accidentally walk over any adjacent data.
*/ */
private static ArrayList<Vec3i> getIntersectingColumnsAtPosition(Vec3i rayEndingPos, Vec3f rayDirection) private static ArrayList<DhVec3i> getIntersectingColumnsAtPosition(DhVec3i rayEndingPos, DhVec3f rayDirection)
{ {
ArrayList<Vec3i> returnList = new ArrayList<>(9); ArrayList<DhVec3i> returnList = new ArrayList<>(9);
for (int x = -1; x <= 1; x++) for (int x = -1; x <= 1; x++)
{ {
for (int z = -1; z <= 1; z++) for (int z = -1; z <= 1; z++)
{ {
Vec3i pos = new Vec3i(rayEndingPos.x + x, rayEndingPos.y, rayEndingPos.z + z); DhVec3i pos = new DhVec3i(rayEndingPos.x + x, rayEndingPos.y, rayEndingPos.z + z);
// check if this column is intersected by the ray // check if this column is intersected by the ray
if (RayCastUtil.rayIntersectsSquare(rayEndingPos.x, rayEndingPos.z, rayDirection.x, rayDirection.z, pos.x, pos.z, 1)) if (RayCastUtil.rayIntersectsSquare(rayEndingPos.x, rayEndingPos.z, rayDirection.x, rayDirection.z, pos.x, pos.z, 1))
@@ -490,11 +500,14 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
return returnList; return returnList;
} }
//endregion
//================// //================//
// setter methods // // setter methods //
//================// //================//
//region
@Override @Override
public DhApiResult<Void> overwriteChunkDataAsync(IDhApiLevelWrapper levelWrapper, Object[] chunkObjectArray) throws ClassCastException public DhApiResult<Void> overwriteChunkDataAsync(IDhApiLevelWrapper levelWrapper, Object[] chunkObjectArray) throws ClassCastException
@@ -524,20 +537,26 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
return DhApiResult.createSuccess(); return DhApiResult.createSuccess();
} }
//endregion
//=============// //=============//
// API helpers // // API helpers //
//=============// //=============//
//region
@Override @Override
public IDhApiTerrainDataCache createSoftCache() { return new DhApiTerrainDataCache(); } public IDhApiTerrainDataCache createSoftCache() { return new DhApiTerrainDataCache(); }
//endregion
//===============// //===============//
// debug methods // // debug methods //
//===============// //===============//
//region
/** /**
* This method is here for debugging the repo and isn't intended for normal use. * This method is here for debugging the repo and isn't intended for normal use.
@@ -618,5 +637,8 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
} }
} }
//endregion
} }
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.api.enums.config.EDhApiMcRenderingFadeMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState; import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure; import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
@@ -35,11 +36,13 @@ import com.seibel.distanthorizons.core.render.RenderParams;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.render.renderer.*; import com.seibel.distanthorizons.core.render.renderer.*;
import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.util.objects.Pair; import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IImmersivePortalsAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhMetaRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhVanillaFadeRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhTestTriangleRenderer;
@@ -51,7 +54,6 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering; import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode; import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld; import com.seibel.distanthorizons.core.world.DhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
@@ -65,6 +67,7 @@ import org.lwjgl.glfw.GLFW;
import java.io.File; import java.io.File;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
@@ -84,6 +87,12 @@ public class ClientApi
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
/** Delayed accessing is necessary since this object will be created before the mod accessors are bound. */
private static class DelayedAccessors
{
public static final IImmersivePortalsAccessor IMMERSIVE_PORTALS = ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class);
}
/** this includes the is dev build message and low allocated memory warning */ /** this includes the is dev build message and low allocated memory warning */
private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000; private static final int MS_BETWEEN_STATIC_STARTUP_MESSAGES = 4_000;
@@ -96,6 +105,11 @@ public class ClientApi
* Only downside is making sure each variable is populated before rendering. * Only downside is making sure each variable is populated before rendering.
*/ */
public static final DhRenderState RENDER_STATE = new DhRenderState(); public static final DhRenderState RENDER_STATE = new DhRenderState();
/**
* static variable so we don't have to re-create it each frame,
* reducing GC pressure.
*/
private static final RenderParams RENDER_PARAMS = new RenderParams();
/** /**
* 50ms = 20 FPS * 50ms = 20 FPS
@@ -116,16 +130,10 @@ public class ClientApi
public boolean rendererDisabledBecauseOfExceptions = false; public boolean rendererDisabledBecauseOfExceptions = false;
private final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi(this::clientLevelLoadEvent, this::clientLevelUnloadEvent);
/** Delay loading the first level to give the server some time to respond with level to actually load */
private Timer firstLevelLoadTimer;
private static final long FIRST_LEVEL_LOAD_DELAY_IN_MS = 1_000;
/** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */ /** Holds any levels that were loaded before the {@link ClientApi#onClientOnlyConnected} was fired. */
public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>(); public final HashSet<IClientLevelWrapper> waitingClientLevels = new HashSet<>();
/** Holds any chunks that were loaded before the {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} was fired. */ /** Holds any chunks that were found before the client levels are loaded. */
public final HashMap<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new HashMap<>(); public final Map<Pair<IClientLevelWrapper, DhChunkPos>, IChunkWrapper> waitingChunkByClientLevelAndPos = new ConcurrentHashMap<>();
/** publicly available so {@link F3Screen} can display the error */ /** publicly available so {@link F3Screen} can display the error */
@Nullable @Nullable
@@ -141,11 +149,19 @@ public class ClientApi
* tracked should also be to keep the ratio roughly the same. * tracked should also be to keep the ratio roughly the same.
* @see ClientApi#MIN_MS_BETWEEN_SPEED_CHECKS * @see ClientApi#MIN_MS_BETWEEN_SPEED_CHECKS
*/ */
public RollingAverage cameraSpeedRollingAverage = new RollingAverage(40); private final RollingAverage cameraSpeedRollingAverage = new RollingAverage(40);
private Vec3d lastCameraPosForSpeedCheck = new Vec3d(); private DhVec3d lastCameraPosForSpeedCheck = new DhVec3d();
private long msSinceLastSpeedCheck = 0L; private long msSinceLastSpeedCheck = 0L;
public double getAvgCameraSpeed() { return cameraSpeedRollingAverage.getAverage(); }
public static long firstRenderTimeMs = 0; /**
* keeping track of this is necessary to fix
* out-of-date LODs from rendering when the shading
* is changed by Iris, causing LODs to often
* lack the side shading, which looks pretty bad
* when shaders are disabled.
*/
private boolean irisShadersEnabledLastFrame = false;
@@ -160,11 +176,11 @@ public class ClientApi
//==============// //==============//
// world events // // world events //
//==============// //==============//
//region //region world events
/** /**
* May be fired slightly before or after the associated * May be fired slightly before or after the associated
* {@link ClientApi#clientLevelLoadEvent(IClientLevelWrapper)} event * level is loaded
* depending on how the host mod loader functions. <br><br> * depending on how the host mod loader functions. <br><br>
* *
* Synchronized shouldn't be necessary, but is present to match {@see onClientOnlyDisconnected} and prevent any unforeseen issues. * Synchronized shouldn't be necessary, but is present to match {@see onClientOnlyDisconnected} and prevent any unforeseen issues.
@@ -198,30 +214,12 @@ public class ClientApi
DhClientWorld world = new DhClientWorld(); DhClientWorld world = new DhClientWorld();
SharedApi.setDhWorld(world); SharedApi.setDhWorld(world);
this.pluginChannelApi.onJoinServer(world.networkState.getSession());
world.networkState.sendConfigMessage();
LOGGER.info("Loading [" + this.waitingClientLevels.size() + "] waiting client level wrappers.");
for (IClientLevelWrapper level : this.waitingClientLevels)
{
this.clientLevelLoadEvent(level);
}
this.waitingClientLevels.clear();
} }
} }
/** Synchronized to prevent a rare issue where multiple disconnect events are triggered on top of each other. */ /** Synchronized to prevent a rare issue where multiple disconnect events are triggered on top of each other. */
public synchronized void onClientOnlyDisconnected() public synchronized void onClientOnlyDisconnected()
{ {
// clear the first time timer
if (this.firstLevelLoadTimer != null)
{
this.firstLevelLoadTimer.cancel();
this.firstLevelLoadTimer = null;
}
AbstractDhWorld world = SharedApi.getAbstractDhWorld(); AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null) if (world != null)
{ {
@@ -231,11 +229,8 @@ public class ClientApi
SharedApi.setDhWorld(null); SharedApi.setDhWorld(null);
} }
this.pluginChannelApi.reset();
// remove any waiting items // remove any waiting items
this.waitingChunkByClientLevelAndPos.clear(); this.waitingChunkByClientLevelAndPos.clear();
this.waitingClientLevels.clear();
} }
//endregion //endregion
@@ -247,95 +242,7 @@ public class ClientApi
//==============// //==============//
//region //region
public void clientLevelUnloadEvent(IClientLevelWrapper level) public void loadWaitingChunksForLevel(IClientLevelWrapper level)
{
try
{
LOGGER.info("Unloading client level [" + level.getClass().getSimpleName() + "]-[" + level.getDhIdentifier() + "].");
if (level instanceof IServerKeyedClientLevel)
{
this.pluginChannelApi.onClientLevelUnload();
}
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
world.unloadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
}
else
{
this.waitingClientLevels.remove(level);
}
}
catch (Exception e)
{
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientLevelUnloadEvent(), error: "+e.getMessage(), e);
}
}
public void clientLevelLoadEvent(@Nullable IClientLevelWrapper levelWrapper)
{
// can happen if there was an issue during level load
if (levelWrapper == null)
{
return;
}
// wait a moment before loading the level to give the server a chance to handle the client's login request
if (MC_CLIENT.clientConnectedToDedicatedServer())
{
if (this.firstLevelLoadTimer == null)
{
this.firstLevelLoadTimer = TimerUtil.CreateTimer("FirstLevelLoadTimer");
this.firstLevelLoadTimer.schedule(new TimerTask()
{
@Override
public void run() { ClientApi.this.clientLevelLoadEvent(levelWrapper); }
}, FIRST_LEVEL_LOAD_DELAY_IN_MS);
return;
}
this.firstLevelLoadTimer.cancel();
}
try
{
LOGGER.info("Loading client level [" + levelWrapper + "]-[" + levelWrapper.getDhIdentifier() + "].");
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null)
{
if (!this.pluginChannelApi.allowLevelLoading(levelWrapper))
{
LOGGER.info("Levels in this connection are managed by the server, skipping auto-load.");
// Instead of attempting to load themselves, send the config and wait for a server provided level key.
((DhClientWorld) world).networkState.sendConfigMessage();
return;
}
world.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
this.loadWaitingChunksForLevel(levelWrapper);
}
else
{
this.waitingClientLevels.add(levelWrapper);
}
}
catch (Exception e)
{
// handle errors here to prevent blowing up a mixin or API up stream
LOGGER.error("Unexpected error in ClientApi.clientLevelLoadEvent(), error: "+e.getMessage(), e);
}
}
private void loadWaitingChunksForLevel(IClientLevelWrapper level)
{ {
HashSet<Pair<IClientLevelWrapper, DhChunkPos>> keysToRemove = new HashSet<>(); HashSet<Pair<IClientLevelWrapper, DhChunkPos>> keysToRemove = new HashSet<>();
for (Pair<IClientLevelWrapper, DhChunkPos> levelChunkPair : this.waitingChunkByClientLevelAndPos.keySet()) for (Pair<IClientLevelWrapper, DhChunkPos> levelChunkPair : this.waitingChunkByClientLevelAndPos.keySet())
@@ -364,7 +271,7 @@ public class ClientApi
//============// //============//
// networking // // networking //
//============// //============//
//region //region networking
/** /**
* Forwards a decoded message into the registered handlers. * Forwards a decoded message into the registered handlers.
@@ -384,7 +291,8 @@ public class ClientApi
{ {
executor.execute(() -> executor.execute(() ->
{ {
NetworkSession networkSession = this.pluginChannelApi.networkSession; DhClientWorld world = (DhClientWorld) Objects.requireNonNull(SharedApi.tryGetDhClientWorld());
NetworkSession networkSession = world.pluginChannelApi.networkSession;
if (networkSession != null) if (networkSession != null)
{ {
networkSession.tryHandleMessage(message); networkSession.tryHandleMessage(message);
@@ -404,7 +312,7 @@ public class ClientApi
//===============// //===============//
// LOD rendering // // LOD rendering //
//===============// //===============//
//region //region lod rendering
/** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */ /** Should be called before {@link ClientApi#renderDeferredLodsForShaders} */
public void renderLods() { this.renderLodLayer(false); } public void renderLods() { this.renderLodLayer(false); }
@@ -428,12 +336,16 @@ public class ClientApi
//===========// //===========//
//region //region
//DhApiTerrainDataRepo.asyncDebugMethod( // only run these tasks once per frame
// RENDER_STATE.clientLevelWrapper, if (!renderingDeferredLayer)
// MC_CLIENT.getPlayerBlockPos().getX(), {
// MC_CLIENT.getPlayerBlockPos().getY(), //DhApiTerrainDataRepo.asyncDebugMethod(
// MC_CLIENT.getPlayerBlockPos().getZ() // RENDER_STATE.clientLevelWrapper,
//); // MC_CLIENT.getPlayerBlockPos().getX(),
// MC_CLIENT.getPlayerBlockPos().getY(),
// MC_CLIENT.getPlayerBlockPos().getZ()
//);
}
//endregion //endregion
@@ -482,14 +394,17 @@ public class ClientApi
//region //region
long nowMs = System.currentTimeMillis(); long nowMs = System.currentTimeMillis();
if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs) if (this.msSinceLastSpeedCheck + MIN_MS_BETWEEN_SPEED_CHECKS < nowMs
// don't track camera speed for dimensions the player isn't in
&& (DelayedAccessors.IMMERSIVE_PORTALS == null
|| !DelayedAccessors.IMMERSIVE_PORTALS.isRenderingPortal()))
{ {
// calc time since last check // calc time since last check
double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0; double secSinceLastCheck = (nowMs - this.msSinceLastSpeedCheck) / 1_000.0;
this.msSinceLastSpeedCheck = nowMs; this.msSinceLastSpeedCheck = nowMs;
// get the distance traveled since last frame // get the distance traveled since last frame
Vec3d camPos = MC_RENDER.getCameraExactPosition(); DhVec3d camPos = MC_RENDER.getCameraExactPosition();
double distanceInBlocks = camPos.getDistance(this.lastCameraPosForSpeedCheck); double distanceInBlocks = camPos.getDistance(this.lastCameraPosForSpeedCheck);
double speed = distanceInBlocks / secSinceLastCheck; double speed = distanceInBlocks / secSinceLastCheck;
@@ -499,6 +414,27 @@ public class ClientApi
} }
//endregion //endregion
//====================//
// Iris data re-build //
//====================//
//region
// delayed getter since ClientApi is created before this accessor is bound
IIrisAccessor irisAccessor = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class);
if (irisAccessor != null)
{
boolean shadersActive = irisAccessor.isShaderPackInUse();
if (this.irisShadersEnabledLastFrame != shadersActive)
{
this.irisShadersEnabledLastFrame = shadersActive;
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
}
//endregion
} }
} }
@@ -533,7 +469,7 @@ public class ClientApi
// render prep and actual rendering into different threads/methods // render prep and actual rendering into different threads/methods
// this is annoying since it's possible to start a render with only // this is annoying since it's possible to start a render with only
// partially complete info, but there isn't a better option at the moment // partially complete info, but there isn't a better option at the moment
RenderParams renderParams = new RenderParams(renderPass, RENDER_STATE); RENDER_PARAMS.update(renderPass, RENDER_STATE);
//endregion //endregion
@@ -544,12 +480,7 @@ public class ClientApi
//============// //============//
//region //region
if (firstRenderTimeMs == 0) String validationMessage = RENDER_PARAMS.getValidationErrorMessage();
{
firstRenderTimeMs = System.currentTimeMillis();
}
String validationMessage = renderParams.getValidationErrorMessage(firstRenderTimeMs);
if (validationMessage != null) if (validationMessage != null)
{ {
// store the error message so it can be seen on the F3 screen // store the error message so it can be seen on the F3 screen
@@ -595,10 +526,10 @@ public class ClientApi
{ {
if (!renderingDeferredLayer) if (!renderingDeferredLayer)
{ {
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, renderParams); boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderEvent.class, RENDER_PARAMS);
if (!renderingCancelled) if (!renderingCancelled)
{ {
LodRenderer.INSTANCE.render(renderParams, profiler); LodRenderer.INSTANCE.render(RENDER_PARAMS, profiler);
} }
if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering()) if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering())
@@ -608,10 +539,10 @@ public class ClientApi
} }
else else
{ {
boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, renderParams); boolean renderingCancelled = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeDeferredRenderEvent.class, RENDER_PARAMS);
if (!renderingCancelled) if (!renderingCancelled)
{ {
LodRenderer.INSTANCE.renderDeferred(renderParams, profiler); LodRenderer.INSTANCE.renderDeferred(RENDER_PARAMS, profiler);
} }
@@ -632,11 +563,11 @@ public class ClientApi
{ {
// meta renderer needed for render state/texture // meta renderer needed for render state/texture
// for setup on some APIs (IE openGL) // for setup on some APIs (IE openGL)
metaRenderer.runRenderPassSetup(renderParams); metaRenderer.runRenderPassSetup(RENDER_PARAMS);
testRenderer.render(renderParams); testRenderer.render(RENDER_PARAMS);
metaRenderer.runRenderPassCleanup(renderParams); metaRenderer.runRenderPassCleanup(RENDER_PARAMS);
} }
else else
{ {
@@ -667,7 +598,7 @@ public class ClientApi
//================// //================//
// fade rendering // // fade rendering //
//================// //================//
//region //region fade rendering
/** /**
* The first fade pass. * The first fade pass.
@@ -690,11 +621,10 @@ public class ClientApi
// or if LOD-only mode is enabled (fading is used to remove the MC render pass) // or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get() || Config.Client.Advanced.Debugging.lodOnlyMode.get()
) )
// don't fade when Iris shaders are active, otherwise the rendering can get weird && shouldRenderFade())
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{ {
RenderParams renderParams = new RenderParams(EDhApiRenderPass.OPAQUE, RENDER_STATE); RENDER_PARAMS.update(EDhApiRenderPass.OPAQUE, RENDER_STATE);
fadeRenderer.render(renderParams); fadeRenderer.render(RENDER_PARAMS);
} }
} }
/** /**
@@ -720,16 +650,34 @@ public class ClientApi
// or if LOD-only mode is enabled (fading is used to remove the MC render pass) // or if LOD-only mode is enabled (fading is used to remove the MC render pass)
|| Config.Client.Advanced.Debugging.lodOnlyMode.get() || Config.Client.Advanced.Debugging.lodOnlyMode.get()
) )
// don't fade when Iris shaders are active, otherwise the rendering can get weird && shouldRenderFade();
&& !DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
if (renderFade) if (renderFade)
{ {
RenderParams renderParams = new RenderParams(EDhApiRenderPass.TRANSPARENT, RENDER_STATE); RENDER_PARAMS.update(EDhApiRenderPass.TRANSPARENT, RENDER_STATE);
fadeRenderer.render(renderParams); fadeRenderer.render(RENDER_PARAMS);
} }
} }
} }
private static boolean shouldRenderFade()
{
// don't fade when Iris shaders are active, otherwise the rendering can get weird
if (DhApiRenderProxy.INSTANCE.getDeferTransparentRendering())
{
return false;
}
// Don't render fade through immersive portals, this causes the fade to apply incorrectly
IImmersivePortalsAccessor immersivePortals = ModAccessorInjector.INSTANCE.get(IImmersivePortalsAccessor.class);
if (immersivePortals != null
&& immersivePortals.isRenderingPortal())
{
return false;
}
return true;
}
//endregion //endregion
@@ -737,7 +685,7 @@ public class ClientApi
//==========// //==========//
// keyboard // // keyboard //
//==========// //==========//
//region //region keyboard
/** Trigger once on key press, with CLIENT PLAYER. */ /** Trigger once on key press, with CLIENT PLAYER. */
public void keyPressedEvent(int glfwKey) public void keyPressedEvent(int glfwKey)
@@ -773,7 +721,7 @@ public class ClientApi
//======// //======//
// chat // // chat //
//======// //======//
//region //region chat
private void sendQueuedChatMessages() private void sendQueuedChatMessages()
{ {
@@ -10,13 +10,13 @@ import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer;
/** /**
* This class is used to manage the level keys. * This class is used to manage the level keys.
@@ -30,9 +30,6 @@ public class ClientPluginChannelApi
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class); private static final IKeyedClientLevelManager KEYED_CLIENT_LEVEL_MANAGER = SingletonInjector.INSTANCE.get(IKeyedClientLevelManager.class);
private final Consumer<IServerKeyedClientLevel> levelLoadHandler;
private final Consumer<IClientLevelWrapper> levelUnloadHandler;
@Nullable @Nullable
public NetworkSession networkSession; public NetworkSession networkSession;
@@ -42,10 +39,8 @@ public class ClientPluginChannelApi
// constructor // // constructor //
//=============// //=============//
public ClientPluginChannelApi(Consumer<IServerKeyedClientLevel> levelLoadHandler, Consumer<IClientLevelWrapper> levelUnloadHandler) public ClientPluginChannelApi()
{ {
this.levelLoadHandler = levelLoadHandler;
this.levelUnloadHandler = levelUnloadHandler;
} }
@@ -88,38 +83,27 @@ public class ClientPluginChannelApi
throw new IllegalArgumentException("Server sent invalid level key."); throw new IllegalArgumentException("Server sent invalid level key.");
} }
LOGGER.info("Server level key received: [" + msg.levelKey + "]."); LOGGER.info("Level init received for [" + msg.dimensionResourceLocation + "]: server key [" + msg.serverKey + "], level key [" + msg.levelKey + "]");
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("ClientPluginChannelApi onLevelInitMessage", () -> RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("ClientPluginChannelApi onLevelInitMessage", () ->
{ {
IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true); IClientLevelWrapper clientLevel = MC.getWrappedClientLevel(true);
IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(); IServerKeyedClientLevel existingKeyedClientLevel = KEYED_CLIENT_LEVEL_MANAGER.getServerKeyedLevel(clientLevel);
if (existingKeyedClientLevel != null)
{
if (!existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{
LOGGER.info("Unloading previous level with key: [" + existingKeyedClientLevel.getServerLevelKey() + "].");
this.levelUnloadHandler.accept(existingKeyedClientLevel);
}
else
{
LOGGER.info("Level key matches the previous level key, ignoring the message.");
}
}
else
{
LOGGER.info("Unloading non-keyed level: [" + clientLevel.getDhIdentifier() + "].");
this.levelUnloadHandler.accept(clientLevel);
}
if (existingKeyedClientLevel == null if (existingKeyedClientLevel == null
|| !existingKeyedClientLevel.getServerKey().equals(msg.serverKey) || !existingKeyedClientLevel.getServerKey().equals(msg.serverKey)
|| !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey)) || !existingKeyedClientLevel.getServerLevelKey().equals(msg.levelKey))
{ {
LOGGER.info("Loading level with key: [" + msg.levelKey + "]."); LOGGER.info("Loading level with key: [" + msg.levelKey + "].");
IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.serverKey, msg.levelKey);
this.levelLoadHandler.accept(keyedLevel); IServerKeyedClientLevel keyedLevel = KEYED_CLIENT_LEVEL_MANAGER.setServerKeyedLevel(clientLevel, msg.dimensionResourceLocation, msg.serverKey, msg.levelKey);
if (keyedLevel != null) {
AbstractDhWorld world = SharedApi.getAbstractDhWorld();
if (world != null) {
world.getOrLoadLevel(keyedLevel);
}
}
} }
}); });
} }
@@ -19,13 +19,10 @@
package com.seibel.distanthorizons.core.api.internal; package com.seibel.distanthorizons.core.api.internal;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.MessageRegistry; import com.seibel.distanthorizons.core.network.messages.MessageRegistry;
import com.seibel.distanthorizons.core.world.*; import com.seibel.distanthorizons.core.world.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
@@ -77,7 +74,6 @@ public class ServerApi
} }
//==============// //==============//
// level events // // level events //
//==============// //==============//
@@ -90,7 +86,6 @@ public class ServerApi
if (serverWorld != null) if (serverWorld != null)
{ {
serverWorld.getOrLoadLevel(levelWrapper); serverWorld.getOrLoadLevel(levelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(levelWrapper));
} }
} }
public void serverLevelUnloadEvent(IServerLevelWrapper level) public void serverLevelUnloadEvent(IServerLevelWrapper level)
@@ -101,12 +96,10 @@ public class ServerApi
if (serverWorld != null) if (serverWorld != null)
{ {
serverWorld.unloadLevel(level); serverWorld.unloadLevel(level);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(level));
} }
} }
//=======================// //=======================//
// chunk modified events // // chunk modified events //
//=======================// //=======================//
@@ -1,7 +1,7 @@
package com.seibel.distanthorizons.core.api.internal.rendering; package com.seibel.distanthorizons.core.api.internal.rendering;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.DhMat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -12,8 +12,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapp
*/ */
public class DhRenderState public class DhRenderState
{ {
public Mat4f mcModelViewMatrix = null; public DhMat4f mcModelViewMatrix = null;
public Mat4f mcProjectionMatrix = null; public DhMat4f mcProjectionMatrix = null;
/** /**
* percentage of time into the current client tick. <br><br> * percentage of time into the current client tick. <br><br>
* *
File diff suppressed because it is too large Load Diff
@@ -55,6 +55,16 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
@Nullable @Nullable
private T apiValue; private T apiValue;
/**
* Will be null if un-set. <br> <br>
*
* Some options aren't supported on all Minecraft versions,
* in those cases this value will be set to override the
* config file option.
*/
@Nullable
private T mcVersionOverrideValue;
//=============// //=============//
@@ -127,7 +137,14 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
return this.allowApiOverride return this.allowApiOverride
&& this.apiValue != null; && this.apiValue != null;
} }
/** setting to null will allow the config to be used normally */
public void setMcVersionOverrideValue(@Nullable T value)
{ this.mcVersionOverrideValue = value; }
public boolean mcVersionOverridePresent()
{ return this.mcVersionOverrideValue != null; }
/** /**
* Should only be used when loading the config from file. <Br> * Should only be used when loading the config from file. <Br>
* Sets the value without informing the rest of the code (ie, it doesn't call listeners, or saving the value to file). * Sets the value without informing the rest of the code (ie, it doesn't call listeners, or saving the value to file).
@@ -183,6 +200,12 @@ public class ConfigEntry<T> extends AbstractConfigBase<T>
@Override @Override
public T get() public T get()
{ {
// always use the MC version specific option if defined
if (this.mcVersionOverrideValue != null)
{
return this.mcVersionOverrideValue;
}
if (this.allowApiOverride if (this.allowApiOverride
&& this.apiValue != null) && this.apiValue != null)
{ {
@@ -41,35 +41,47 @@ public final class BufferQuad
public static final int MAX_QUAD_WIDTH_FOR_EARTH_CURVATURE = LodUtil.CHUNK_WIDTH; public static final int MAX_QUAD_WIDTH_FOR_EARTH_CURVATURE = LodUtil.CHUNK_WIDTH;
public final short x; public short x;
public final short y; public short y;
public final short z; public short z;
public short widthEastWest; public short widthEastWest;
/** This is both North/South and Up/Down since the merging logic is the same either way */ /** This is both North/South and Up/Down since the merging logic is the same either way */
public short widthNorthSouthOrHeight; public short widthNorthSouthOrHeight;
public final int color; public int color;
/** used by the Iris shader mod to determine how each LOD should be rendered */ /** used by the Iris shader mod to determine how each LOD should be rendered */
public final byte irisBlockMaterialId; public byte irisBlockMaterialId;
public final byte skyLight; public byte skyLight;
public final byte blockLight; public byte blockLight;
public final EDhDirection direction; public EDhDirection direction;
public boolean hasError = false; public boolean hasError = false;
// Pre-computed sort keys to avoid recomputing on every comparison
// Slight increase in memory for reduction in cpu usage
public long sortKeyEastWest;
public long sortKeyNorthSouth;
BufferQuad(
short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight, //=============//
int color, byte irisBlockMaterialId, byte skylight, byte blockLight, // constructor //
EDhDirection direction) //=============//
//region
public BufferQuad() {}
public void set(short x, short y, short z, short widthEastWest, short widthNorthSouthOrHeight,
int color, byte irisBlockMaterialId, byte skylight, byte blockLight,
EDhDirection direction)
{ {
if (widthEastWest == 0 || widthNorthSouthOrHeight == 0) if (widthEastWest == 0 || widthNorthSouthOrHeight == 0)
{ {
throw new IllegalArgumentException("Size 0 quad!"); throw new IllegalArgumentException("Size 0 quad!");
} }
if (widthEastWest < 0 || widthNorthSouthOrHeight < 0) if (widthEastWest < 0 || widthNorthSouthOrHeight < 0)
{ {
throw new IllegalArgumentException("Negative sized quad!"); throw new IllegalArgumentException("Negative sized quad!");
@@ -85,64 +97,46 @@ public final class BufferQuad
this.skyLight = skylight; this.skyLight = skylight;
this.blockLight = blockLight; this.blockLight = blockLight;
this.direction = direction; this.direction = direction;
this.sortKeyEastWest = computeSortKey(direction, true);
this.sortKeyNorthSouth = computeSortKey(direction, false);
} }
private long computeSortKey(EDhDirection dir, boolean eastWest)
/** a rough but fast calculation */
double calculateDistance(double relativeX, double relativeY, double relativeZ)
{ {
return Math.pow(relativeX - this.x, 2) + Math.pow(relativeY - this.y, 2) + Math.pow(relativeZ - this.z, 2); if (eastWest)
{
switch (dir.axis)
{
case X: return (long) x << 48 | (long) y << 32 | (long) z << 16;
case Y: return (long) y << 48 | (long) z << 32 | (long) x << 16;
case Z: return (long) z << 48 | (long) y << 32 | (long) x << 16;
default: throw new IllegalArgumentException("Invalid Axis enum: [" + dir.axis + "].");
}
}
else
{
switch (dir.axis)
{
case X: return (long) x << 48 | (long) z << 32 | (long) y << 16;
case Y: return (long) y << 48 | (long) x << 32 | (long) z << 16;
case Z: return (long) z << 48 | (long) x << 32 | (long) y << 16;
default: throw new IllegalArgumentException("Invalid Axis enum: [" + dir.axis + "].");
}
}
} }
/** compares this quad's position to the given quad */ //endregion
/** compares this quad's position to the given quad using pre-computed sort keys */
public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection) public int compare(BufferQuad quad, BufferMergeDirectionEnum compareDirection)
{ {
if (this.direction != quad.direction) if (this.direction != quad.direction)
throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction); throw new IllegalArgumentException("The other quad is not in the same direction: " + quad.direction + " vs " + this.direction);
if (compareDirection == BufferMergeDirectionEnum.EastWest) return compareDirection == BufferMergeDirectionEnum.EastWest
{ ? Long.compare(this.sortKeyEastWest, quad.sortKeyEastWest)
switch (this.direction.axis) : Long.compare(this.sortKeyNorthSouth, quad.sortKeyNorthSouth);
{
case X:
return threeDimensionalCompare(this.x, this.y, this.z, quad.x, quad.y, quad.z);
case Y:
return threeDimensionalCompare(this.y, this.z, this.x, quad.y, quad.z, quad.x);
case Z:
return threeDimensionalCompare(this.z, this.y, this.x, quad.z, quad.y, quad.x);
default:
throw new IllegalArgumentException("Invalid Axis enum: [" + this.direction.axis + "].");
}
}
else
{
switch (this.direction.axis)
{
case X:
return threeDimensionalCompare(this.x, this.z, this.y, quad.x, quad.z, quad.y);
case Y:
return threeDimensionalCompare(this.y, this.x, this.z, quad.y, quad.x, quad.z);
case Z:
return threeDimensionalCompare(this.z, this.x, this.y, quad.z, quad.x, quad.y);
default:
throw new IllegalArgumentException("Invalid Axis enum: [" + this.direction.axis + "].");
}
}
}
/**
* Compares two 3D points A and B. <br>
* The X, Y, and Z coordinates can be passed into parameters 0, 1, and 2 in any order
* provided they are in the same order for both A and B. <br>
* With the 0th parameter being the most significant when comparing.
*/
private static int threeDimensionalCompare(short a0, short a1, short a2, short b0, short b1, short b2)
{
long a = (long) a0 << 48 | (long) a1 << 32 | (long) a2 << 16;
long b = (long) b0 << 48 | (long) b1 << 32 | (long) b2 << 16;
return Long.compare(a, b);
} }
@@ -154,11 +148,15 @@ public final class BufferQuad
public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection) public boolean tryMerge(BufferQuad quad, BufferMergeDirectionEnum mergeDirection)
{ {
if (quad.hasError || this.hasError) if (quad.hasError || this.hasError)
{
return false; return false;
}
// only merge quads that are in the same direction // only merge quads that are in the same direction
if (this.direction != quad.direction) if (this.direction != quad.direction)
{
return false; return false;
}
// make sure these quads share the same perpendicular axis // make sure these quads share the same perpendicular axis
if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y) if ((mergeDirection == BufferMergeDirectionEnum.EastWest && this.y != quad.y)
@@ -175,7 +173,6 @@ public final class BufferQuad
short otherParallelCompareStartPos; short otherParallelCompareStartPos;
switch (this.direction.axis) switch (this.direction.axis)
{ {
default: // shouldn't normally happen, just here to make the compiler happy
case X: case X:
if (mergeDirection == BufferMergeDirectionEnum.EastWest) if (mergeDirection == BufferMergeDirectionEnum.EastWest)
{ {
@@ -232,6 +229,9 @@ public final class BufferQuad
otherParallelCompareStartPos = quad.z; otherParallelCompareStartPos = quad.z;
} }
break; break;
default: // shouldn't normally happen, just here to make the compiler happy
throw new IllegalArgumentException("Unsupported axis: ["+this.direction.axis+"]");
} }
// get the width of this quad in the relevant axis // get the width of this quad in the relevant axis
@@ -333,4 +333,6 @@ public final class BufferQuad
return true; return true;
} }
} }
@@ -19,11 +19,14 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
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.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.util.objects.pooling.PhantomArrayListCheckout;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.ColorUtil; import com.seibel.distanthorizons.coreapi.util.ColorUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
@@ -51,7 +54,7 @@ public class ColumnBox
public static void addBoxQuadsToBuilder( public static void addBoxQuadsToBuilder(
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IDhClientLevel clientLevel, LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IDhClientLevel clientLevel,
short width, short yHeight, short blockWidth, short yHeight,
short minX, short minY, short minZ, short minX, short minY, short minZ,
int color, byte irisBlockMaterialId, byte skyLight, byte blockLight, int color, byte irisBlockMaterialId, byte skyLight, byte blockLight,
long topData, long bottomData, ColumnRenderView[] adjData, boolean[] isAdjDataSameDetailLevel) long topData, long bottomData, ColumnRenderView[] adjData, boolean[] isAdjDataSameDetailLevel)
@@ -60,14 +63,19 @@ public class ColumnBox
// variable setup // // variable setup //
//================// //================//
short maxX = (short) (minX + width); IClientLevelWrapper clientLevelWrapper = clientLevel.getClientLevelWrapper();
if (clientLevelWrapper == null)
{
LodUtil.assertNotReach("addBoxQuadsToBuilder getClientLevelWrapper should always succeed");
}
short maxX = (short) (minX + blockWidth);
short maxY = (short) (minY + yHeight); short maxY = (short) (minY + yHeight);
short maxZ = (short) (minZ + width); short maxZ = (short) (minZ + blockWidth);
byte skyLightTop = skyLight; byte skyLightTop = skyLight;
byte skyLightBot = RenderDataPointUtil.doesDataPointExist(bottomData) ? RenderDataPointUtil.getLightSky(bottomData) : 0; byte skyLightBot = RenderDataPointUtil.doesDataPointExist(bottomData) ? RenderDataPointUtil.getLightSky(bottomData) : 0;
boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get() == EDhApiTransparency.COMPLETE;
boolean fakeOceanFloor = Config.Client.Advanced.Graphics.Quality.transparency.get().fakeTransparencyEnabled;
boolean isTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled; boolean isTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled;
boolean overVoid = !RenderDataPointUtil.doesDataPointExist(bottomData); boolean overVoid = !RenderDataPointUtil.doesDataPointExist(bottomData);
@@ -92,24 +100,6 @@ public class ColumnBox
} }
// fake ocean transparency
if (transparencyEnabled && fakeOceanFloor)
{
if (!isTransparent && isTopTransparent && RenderDataPointUtil.doesDataPointExist(topData))
{
skyLightTop = (byte) MathUtil.clamp(0, 15 - (RenderDataPointUtil.getYMax(topData) - minY), 15);
yHeight = (short) (RenderDataPointUtil.getYMax(topData) - minY - 1);
}
else if (isTransparent && !isBottomTransparent && RenderDataPointUtil.doesDataPointExist(bottomData))
{
minY = (short) (minY + yHeight - 1);
yHeight = 1;
}
maxY = (short) (minY + yHeight);
}
//==========================// //==========================//
// add top and bottom faces // // add top and bottom faces //
@@ -122,7 +112,7 @@ public class ColumnBox
&& !isTopTransparent; && !isTopTransparent;
if (!skipTop) if (!skipTop)
{ {
builder.addQuadUp(minX, maxY, minZ, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight); builder.addQuadUp(minX, maxY, minZ, blockWidth, ColorUtil.applyShade(color, clientLevelWrapper.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight);
} }
} }
@@ -133,7 +123,7 @@ public class ColumnBox
&& !isBottomTransparent; && !isBottomTransparent;
if (!skipBottom) if (!skipBottom)
{ {
builder.addQuadDown(minX, minY, minZ, width, ColorUtil.applyShade(color, MC_RENDER.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); builder.addQuadDown(minX, minY, minZ, blockWidth, ColorUtil.applyShade(color, clientLevelWrapper.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight);
} }
} }
@@ -156,16 +146,16 @@ public class ColumnBox
builder.addQuadAdj( builder.addQuadAdj(
EDhDirection.NORTH, EDhDirection.NORTH,
minX, minY, minZ, minX, minY, minZ,
width, yHeight, blockWidth, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH,
minX, minY, minZ, width, yHeight, minX, minY, minZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
@@ -181,16 +171,16 @@ public class ColumnBox
builder.addQuadAdj( builder.addQuadAdj(
EDhDirection.SOUTH, EDhDirection.SOUTH,
minX, minY, maxZ, minX, minY, maxZ,
width, yHeight, blockWidth, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH,
minX, minY, maxZ, width, yHeight, minX, minY, maxZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
@@ -206,16 +196,16 @@ public class ColumnBox
builder.addQuadAdj( builder.addQuadAdj(
EDhDirection.WEST, EDhDirection.WEST,
minX, minY, minZ, minX, minY, minZ,
width, yHeight, blockWidth, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST,
minX, minY, minZ, width, yHeight, minX, minY, minZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
@@ -231,25 +221,25 @@ public class ColumnBox
builder.addQuadAdj( builder.addQuadAdj(
EDhDirection.EAST, EDhDirection.EAST,
maxX, minY, minZ, maxX, minY, minZ,
width, yHeight, blockWidth, yHeight,
color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
} }
} }
else else
{ {
makeAdjVerticalQuad( makeAdjVerticalQuad(
builder, phantomArrayCheckout, builder, phantomArrayCheckout, clientLevelWrapper,
adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST,
maxX, minY, minZ, width, yHeight, maxX, minY, minZ, blockWidth, yHeight,
color, irisBlockMaterialId, blockLight); color, irisBlockMaterialId, blockLight);
} }
} }
} }
private static void makeAdjVerticalQuad( private static void makeAdjVerticalQuad(
LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IClientLevelWrapper clientLevelWrapper,
@NotNull ColumnRenderView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction, @NotNull ColumnRenderView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction,
short x, short yMin, short z, short horizontalWidth, short ySize, short x, short yMin, short z, short horizontalBlockWidth, short ySize,
int color, byte irisBlockMaterialId, byte blockLight) int color, byte irisBlockMaterialId, byte blockLight)
{ {
// pooled arrays // pooled arrays
@@ -263,12 +253,12 @@ public class ColumnBox
// no adjacent data // // no adjacent data //
//==================// //==================//
color = ColorUtil.applyShade(color, MC_RENDER.getShade(direction)); color = ColorUtil.applyShade(color, clientLevelWrapper.getShade(direction));
if (adjColumnView.size == 0 if (adjColumnView.size == 0
|| RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0))) || RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0)))
{ {
builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); builder.addQuadAdj(direction, x, yMin, z, horizontalBlockWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight);
return; return;
} }
@@ -278,7 +268,7 @@ public class ColumnBox
// determine face visibility/light // // determine face visibility/light //
//=================================// //=================================//
boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get() == EDhApiTransparency.COMPLETE;
boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled; boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled;
short yMax = (short) (yMin + ySize); short yMax = (short) (yMin + ySize);
@@ -320,18 +310,34 @@ public class ColumnBox
if (!adjTransparent) if (!adjTransparent)
{ {
// Adjacent is opaque // Adjacent is opaque
boolean adjacentCoversThis =
!adjacentIsSameDetailLevel
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY
&&
(
(x == 0 && direction == EDhDirection.WEST)
|| (z == 0 && direction == EDhDirection.NORTH)
|| (x == 256 && direction == EDhDirection.EAST)
|| (z == 256 && direction == EDhDirection.SOUTH)
);
lightToApply = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED; // The following logic is done to provide a little bit of overdraw to
// prevent holes when low detail LODs are replaced by higher-detail ones
// when moving.
// If not done higher quality LODs can cause holes due to not
// covering the whole face like the lower detail LODs they replaced,
// while still culling most LODs that are covered by other blocks.
boolean onBorder =
(direction == EDhDirection.WEST && x == 0)
|| (direction == EDhDirection.NORTH && z == 0)
|| (direction == EDhDirection.EAST && x == ((horizontalBlockWidth) * (ColumnRenderSource.WIDTH)))
|| (direction == EDhDirection.SOUTH && z == ((horizontalBlockWidth) * (ColumnRenderSource.WIDTH)));
boolean isLit =
RenderDataPointUtil.getLightSky(adjPoint) != LodUtil.MIN_MC_LIGHT
|| RenderDataPointUtil.getLightBlock(adjPoint) != LodUtil.MIN_MC_LIGHT;
// render the face if...
boolean useAdjLighting =
// we're on the border... (holes can only happen on LOD borders since faces inside an LOD will always be the same detail level)
onBorder
// ...this face has some sort of lighting... (0 light generally means the face is covered by other blocks)
&& isLit
// ...and is above the culling height
&& RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY;
lightToApply = useAdjLighting ? adjSkyLight : SKYLIGHT_COVERED;
} }
else else
{ {
@@ -341,13 +347,24 @@ public class ColumnBox
// Apply light to the range [adjMinY, adjMaxY) // Apply light to the range [adjMinY, adjMaxY)
applyLightToRange(segments, newSegments, adjMinY, adjMaxY, lightToApply); applyLightToRangeAndPopulateNewSgements(segments, newSegments, adjMinY, adjMaxY, lightToApply);
{
// swap references so we can use the newly populated segments
LongArrayList temp = segments;
segments = newSegments;
newSegments = temp;
}
// Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight // Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight
short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint); short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint);
if (adjMaxY < adjAboveMinY) if (adjMaxY < adjAboveMinY)
{ {
applyLightToRange(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight); applyLightToRangeAndPopulateNewSgements(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight);
{
LongArrayList temp = segments;
segments = newSegments;
newSegments = temp;
}
} }
} }
@@ -363,7 +380,7 @@ public class ColumnBox
long segment = segments.getLong(i); long segment = segments.getLong(i);
tryAddVerticalFaceWithSkyLightToBuilder( tryAddVerticalFaceWithSkyLightToBuilder(
builder, direction, builder, direction,
x, z, horizontalWidth, x, z, horizontalBlockWidth,
color, irisBlockMaterialId, blockLight, color, irisBlockMaterialId, blockLight,
YSegmentUtil.getSkyLight(segment), inputTransparent, YSegmentUtil.getEndY(segment), YSegmentUtil.getStartY(segment) YSegmentUtil.getSkyLight(segment), inputTransparent, YSegmentUtil.getEndY(segment), YSegmentUtil.getStartY(segment)
); );
@@ -373,10 +390,11 @@ public class ColumnBox
/** /**
* Apply the new light value over the given y range, * Apply the new light value over the given y range,
* splitting segments as needed * splitting segments as needed
* and putting the new segments into "newSegments"
* <p> * <p>
* source: claude.ai * source: claude.ai
*/ */
private static void applyLightToRange( private static void applyLightToRangeAndPopulateNewSgements(
LongArrayList segments, LongArrayList newSegments, LongArrayList segments, LongArrayList newSegments,
short rangeStart, short rangeEnd, short rangeStart, short rangeEnd,
byte newLight) byte newLight)
@@ -419,9 +437,6 @@ public class ColumnBox
newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight)); newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight));
} }
} }
segments.clear();
segments.addAll(newSegments);
} }
private static void tryAddVerticalFaceWithSkyLightToBuilder( private static void tryAddVerticalFaceWithSkyLightToBuilder(
@@ -55,26 +55,6 @@ public class ColumnRenderBufferBuilder
// vbo building // // vbo building //
//==============// //==============//
/** @link adjData should be null for adjacent sections that cross detail level boundaries */
public static CompletableFuture<LodBufferContainer> uploadBuffersAsync(
IDhClientLevel clientLevel,
long pos,
LodQuadBuilder quadBuilder
)
{
DhBlockPos minBlockPos = new DhBlockPos(DhSectionPos.getMinCornerBlockX(pos), clientLevel.getLevelWrapper().getMinHeight(), DhSectionPos.getMinCornerBlockZ(pos));
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minBlockPos);
CompletableFuture<LodBufferContainer> uploadFuture = bufferContainer.tryMakeAndUploadBuffersAsync(quadBuilder);
uploadFuture.whenComplete((uploadedBuffer, exception) ->
{
// clean up if not uploaded
if (uploadedBuffer != null && !uploadedBuffer.buffersUploaded)
{
uploadedBuffer.close();
}
});
return uploadFuture;
}
public static void makeLodRenderData( public static void makeLodRenderData(
LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel, LodQuadBuilder quadBuilder, ColumnRenderSource renderSource, IDhClientLevel clientLevel,
ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel) ColumnRenderSource[] adjRegions, boolean[] isSameDetailLevel)
@@ -1,7 +1,5 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
@@ -14,11 +12,10 @@ public class IndexBufferBuilder
//==========// //==========//
//region //region
/** Buffer should be freed by {@link MemoryUtil#memFree} */
public static ByteBuffer createBuffer(int quadCount) public static ByteBuffer createBuffer(int quadCount)
{ {
int indexCount = quadCount * 6; // 2 triangles per quad int indexCount = quadCount * 6; // 2 triangles per quad
ByteBuffer buffer = MemoryUtil.memAlloc(indexCount * Integer.BYTES); ByteBuffer buffer = ByteBuffer.allocateDirect(indexCount * Integer.BYTES);
buffer.order(ByteOrder.nativeOrder()); buffer.order(ByteOrder.nativeOrder());
buildBufferInt(quadCount, buffer); buildBufferInt(quadCount, buffer);
@@ -20,24 +20,22 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
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.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.util.ExceptionUtil; import com.seibel.distanthorizons.core.util.ExceptionUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition; import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.IVertexBufferWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.IVertexBufferWrapper;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* Java representation of one or more OpenGL buffers for rendering. * Java representation of one or more OpenGL buffers for rendering.
@@ -63,8 +61,6 @@ public class LodBufferContainer implements AutoCloseable
public ILodContainerUniformBufferWrapper uniformContainer = WRAPPER_FACTORY.createLodContainerUniformWrapper(); public ILodContainerUniformBufferWrapper uniformContainer = WRAPPER_FACTORY.createLodContainerUniformWrapper();
private final AtomicReference<CompletableFuture<LodBufferContainer>> uploadFutureRef = new AtomicReference<>(null);
//==============// //==============//
@@ -72,14 +68,12 @@ public class LodBufferContainer implements AutoCloseable
//==============// //==============//
//region //region
public LodBufferContainer(long pos, DhBlockPos minCornerBlockPos) private LodBufferContainer(long pos, DhBlockPos minCornerBlockPos)
{ {
this.pos = pos; this.pos = pos;
this.minCornerBlockPos = minCornerBlockPos; this.minCornerBlockPos = minCornerBlockPos;
this.vboOpaqueWrappers = new IVertexBufferWrapper[0]; this.vboOpaqueWrappers = new IVertexBufferWrapper[0];
this.vboTransparentWrappers = new IVertexBufferWrapper[0]; this.vboTransparentWrappers = new IVertexBufferWrapper[0];
this.uniformContainer.createUniformData(this);
} }
//endregion //endregion
@@ -92,41 +86,14 @@ public class LodBufferContainer implements AutoCloseable
//region //region
/** Should be run on a DH thread. */ /** Should be run on a DH thread. */
public synchronized CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync(LodQuadBuilder builder) public static CompletableFuture<LodBufferContainer> tryMakeAndUploadBuffersAsync(
long pos, IDhClientLevel clientLevel,
ArrayList<ByteBuffer> opaqueBuffers,
ArrayList<ByteBuffer> transparentBuffers
)
{ {
//================//
// handle futures //
//================//
//region
// separate variable to prevent race condition when checking null
CompletableFuture<LodBufferContainer> oldFuture = this.uploadFutureRef.get();
if (oldFuture != null)
{
// upload already in process
return oldFuture;
}
// new upload needed // new upload needed
CompletableFuture<LodBufferContainer> future = new CompletableFuture<>(); CompletableFuture<LodBufferContainer> future = new CompletableFuture<>();
future.handle((lodBufferContainer, throwable) ->
{
if (!this.uploadFutureRef.compareAndSet(future, null))
{
LOGGER.warn("upload future ref changed for pos ["+DhSectionPos.toString(this.pos)+"].");
}
return null;
});
if (!this.uploadFutureRef.compareAndSet(null, future))
{
oldFuture = this.uploadFutureRef.get();
LodUtil.assertTrue(oldFuture != null, "Concurrency error");
return oldFuture;
}
//endregion
@@ -135,102 +102,105 @@ public class LodBufferContainer implements AutoCloseable
//================// //================//
//region //region
ArrayList<ByteBuffer> opaqueBuffers = builder.makeOpaqueVertexBuffers(); DhBlockPos minCornerBlockPos = new DhBlockPos(
ArrayList<ByteBuffer> transparentBuffers = builder.makeTransparentVertexBuffers(); DhSectionPos.getMinCornerBlockX(pos),
clientLevel.getLevelWrapper().getMinHeight(),
DhSectionPos.getMinCornerBlockZ(pos));
LodBufferContainer bufferContainer = new LodBufferContainer(pos, minCornerBlockPos);
this.vboOpaqueWrappers = resizeWrapperArray(this.vboOpaqueWrappers, opaqueBuffers.size()); // update arrays to contain buffers
this.vboTransparentWrappers = resizeWrapperArray(this.vboTransparentWrappers, transparentBuffers.size()); bufferContainer.vboOpaqueWrappers = resizeWrapperArray(bufferContainer.vboOpaqueWrappers, opaqueBuffers.size());
bufferContainer.vboTransparentWrappers = resizeWrapperArray(bufferContainer.vboTransparentWrappers, transparentBuffers.size());
// mac requires separate IBO objects for each VBO when using OpenGL, // create CPU index buffers if needed.
// Mac requires separate IBO objects for each VBO when using OpenGL,
// all other OS's can share a single IBO for quicker loading times // all other OS's can share a single IBO for quicker loading times
boolean useSingleIbo = RENDER_DEF.useSingleIbo(); boolean useSingleIbo = RENDER_DEF.useSingleIbo();
@Nullable ArrayList<ByteBuffer> opaqueIndexBuffers = useSingleIbo ? null : this.createIndexBuffers(opaqueBuffers); @Nullable ArrayList<ByteBuffer> opaqueIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(opaqueBuffers);
@Nullable ArrayList<ByteBuffer> transparentIndexBuffers = useSingleIbo ? null : this.createIndexBuffers(transparentBuffers); @Nullable ArrayList<ByteBuffer> transparentIndexBuffers = useSingleIbo ? null : bufferContainer.createIndexBuffers(transparentBuffers);
//endregion //endregion
//================// //=============//
// upload buffers // // create VBOs //
//================// //=============//
//region //region
try CompletableFuture<Void> createFuture = new CompletableFuture<Void>();
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Setup", () ->
{ {
//=============// try
// create VBOs //
//=============//
CompletableFuture<Void> createOpaqueFuture = createBufferWrappersAsync(future, this.vboOpaqueWrappers, opaqueBuffers);
CompletableFuture<Void> createTransparentFuture = createBufferWrappersAsync(future, this.vboTransparentWrappers, transparentBuffers);
CompletableFuture<Void> createFuture = CompletableFuture.allOf(createOpaqueFuture, createTransparentFuture);
createFuture.exceptionally((Throwable e) ->
{ {
// create VBOs failed // // skip this event if requested
if (Thread.interrupted()
|| future.isCancelled())
{
throw new InterruptedException();
}
createBufferWrappers(bufferContainer.vboOpaqueWrappers, opaqueBuffers);
createBufferWrappers(bufferContainer.vboTransparentWrappers, transparentBuffers);
createFuture.complete(null);
}
catch (Exception e)
{
if (!ExceptionUtil.isShutdownException(e)) if (!ExceptionUtil.isShutdownException(e))
{ {
LOGGER.error("Unexpected issue creating buffer [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e); LOGGER.error("Unexpected issue creating buffers for pos: ["+DhSectionPos.toString(bufferContainer.pos)+"], error: ["+e.getMessage()+"].", e);
} }
bufferContainer.close();
createFuture.completeExceptionally(e);
}
});
//endregion
//====================//
// upload VBOs to GPU //
//====================//
//region
createFuture.exceptionally((Throwable e) ->
{
// create VBOs failed //
if (!ExceptionUtil.isShutdownException(e))
{
LOGGER.error("Unexpected issue creating buffer [" + bufferContainer.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
}
bufferContainer.close();
future.completeExceptionally(e);
return null;
});
createFuture.thenRun(() ->
{
CompletableFuture<Void> opaqueFuture = uploadBuffersAsync(future, bufferContainer.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers);
CompletableFuture<Void> transparentFuture = uploadBuffersAsync(future, bufferContainer.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers);
CompletableFuture<Void> uploadFuture = CompletableFuture.allOf(opaqueFuture, transparentFuture);
uploadFuture.exceptionally((Throwable e) ->
{
// upload failed //
if (!ExceptionUtil.isShutdownException(e))
{
LOGGER.error("Unexpected issue uploading buffer [" + bufferContainer.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
}
bufferContainer.close();
future.completeExceptionally(e); future.completeExceptionally(e);
return null; return null;
}); });
createFuture.thenRun(() -> uploadFuture.thenRun(() ->
{ {
//=============// // upload success //
// upload VBOs // bufferContainer.buffersUploaded = true;
//=============// future.complete(bufferContainer);
CompletableFuture<Void> opaqueFuture = uploadBuffersAsync(future, this.vboOpaqueWrappers, opaqueBuffers, opaqueIndexBuffers);
CompletableFuture<Void> transparentFuture = uploadBuffersAsync(future, this.vboTransparentWrappers, transparentBuffers, transparentIndexBuffers);
CompletableFuture<Void> uploadFuture = CompletableFuture.allOf(opaqueFuture, transparentFuture);
uploadFuture.exceptionally((Throwable e) ->
{
// upload failed //
if (!ExceptionUtil.isShutdownException(e))
{
LOGGER.error("Unexpected issue uploading buffer [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
}
future.completeExceptionally(e);
return null;
});
uploadFuture.thenRun(() ->
{
// upload success /
this.buffersUploaded = true;
future.complete(this);
});
}); });
}
catch (Exception e)
{
if (!ExceptionUtil.isShutdownException(e))
{
LOGGER.error("Unexpected issue prepping buffer uploading [" + this.minCornerBlockPos + "], error: [" + e.getMessage() + "].", e);
}
future.completeExceptionally(e);
}
//================//
// buffer cleanup //
//================//
future.whenComplete((LodBufferContainer lodBufferContainer, Throwable throwable) ->
{
// all the buffers must be manually freed to prevent memory leaks
tryFreeByteBufferList(opaqueBuffers);
tryFreeByteBufferList(transparentBuffers);
tryFreeByteBufferList(opaqueIndexBuffers);
tryFreeByteBufferList(transparentIndexBuffers);
}); });
//endregion //endregion
@@ -239,16 +209,6 @@ public class LodBufferContainer implements AutoCloseable
return future; return future;
} }
private static void tryFreeByteBufferList(@Nullable ArrayList<ByteBuffer> list)
{
if (list != null)
{
for (ByteBuffer buffer : list)
{
MemoryUtil.memFree(buffer);
}
}
}
private ArrayList<ByteBuffer> createIndexBuffers(ArrayList<ByteBuffer> vertexBuffers) private ArrayList<ByteBuffer> createIndexBuffers(ArrayList<ByteBuffer> vertexBuffers)
@@ -290,11 +250,8 @@ public class LodBufferContainer implements AutoCloseable
return newVbos; return newVbos;
} }
private static CompletableFuture<Void> createBufferWrappersAsync( private static void createBufferWrappers(IVertexBufferWrapper[] vboWrappers, ArrayList<ByteBuffer> vertexBuffers)
CompletableFuture<LodBufferContainer> parentFuture,
IVertexBufferWrapper[] vboWrappers, ArrayList<ByteBuffer> vertexBuffers)
{ {
ArrayList<CompletableFuture<Void>> createVboFutureList = new ArrayList<>();
for (int i = 0; i < vertexBuffers.size(); i++) for (int i = 0; i < vertexBuffers.size(); i++)
{ {
if (i >= vboWrappers.length) if (i >= vboWrappers.length)
@@ -304,45 +261,9 @@ public class LodBufferContainer implements AutoCloseable
if (vboWrappers[i] == null) if (vboWrappers[i] == null)
{ {
final int finalVboIndex = i; vboWrappers[i] = WRAPPER_FACTORY.createVboWrapper("distantHorizons:TerrainRenderer");
CompletableFuture<Void> future = new CompletableFuture<>();
createVboFutureList.add(future);
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Setup", () ->
{
try
{
// skip this event if requested
if (Thread.interrupted()
|| parentFuture.isCancelled())
{
throw new InterruptedException();
}
vboWrappers[finalVboIndex] = WRAPPER_FACTORY.createVboWrapper("distantHorizons:McLodRenderer");
future.complete(null);
}
catch (Exception e)
{
future.completeExceptionally(e);
}
});
} }
} }
if (createVboFutureList.size() == 0)
{
return CompletableFuture.completedFuture(null);
}
CompletableFuture<?>[] futureArray = new CompletableFuture[createVboFutureList.size()];
for (int i = 0; i < createVboFutureList.size(); i++)
{
futureArray[i] = createVboFutureList.get(i);
}
return CompletableFuture.allOf(futureArray);
} }
/** Index buffers should be null if {@link AbstractDhRenderApiDefinition#useSingleIbo()} returns true. */ /** Index buffers should be null if {@link AbstractDhRenderApiDefinition#useSingleIbo()} returns true. */
@@ -365,8 +286,6 @@ public class LodBufferContainer implements AutoCloseable
// final variables for use in lambdas // // final variables for use in lambdas //
final int finalVboIndex = vboIndex;
final IVertexBufferWrapper finalVboWrapper = vboWrappers[vboIndex]; final IVertexBufferWrapper finalVboWrapper = vboWrappers[vboIndex];
final ByteBuffer finalVertexBuffer = vertexBuffers.get(vboIndex); final ByteBuffer finalVertexBuffer = vertexBuffers.get(vboIndex);
@@ -385,6 +304,8 @@ public class LodBufferContainer implements AutoCloseable
CompletableFuture<Void> vertexUploadFuture = new CompletableFuture<>(); CompletableFuture<Void> vertexUploadFuture = new CompletableFuture<>();
uploadFutureList.add(vertexUploadFuture); uploadFutureList.add(vertexUploadFuture);
final StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer VBO Upload", () -> RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer VBO Upload", () ->
{ {
try try
@@ -396,21 +317,12 @@ public class LodBufferContainer implements AutoCloseable
throw new InterruptedException(); throw new InterruptedException();
} }
finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount);
try vertexUploadFuture.complete(null);
{
finalVboWrapper.uploadVertexBuffer(finalVertexBuffer, finalVertexCount);
vertexUploadFuture.complete(null);
}
catch (Exception e)
{
vboWrappers[finalVboIndex] = null;
finalVboWrapper.close();
LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e);
}
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Failed to upload buffer. Error: [" + e.getMessage() + "].", e);
vertexUploadFuture.completeExceptionally(e); vertexUploadFuture.completeExceptionally(e);
} }
}); });
@@ -445,6 +357,7 @@ public class LodBufferContainer implements AutoCloseable
} }
catch (Exception e) catch (Exception e)
{ {
finalVboWrapper.close();
indexUploadFuture.completeExceptionally(e); indexUploadFuture.completeExceptionally(e);
} }
}); });
@@ -532,26 +445,29 @@ public class LodBufferContainer implements AutoCloseable
RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Close", () -> RenderThreadTaskHandler.INSTANCE.queueRunningOnRenderThread("LodBufferContainer Close", () ->
{ {
for (IVertexBufferWrapper buffer : this.vboOpaqueWrappers) tryCloseBufferWrapperArray(this.vboOpaqueWrappers);
{ tryCloseBufferWrapperArray(this.vboTransparentWrappers);
if (buffer != null)
{
buffer.close();
}
}
for (IVertexBufferWrapper buffer : this.vboTransparentWrappers)
{
if (buffer != null)
{
buffer.close();
}
}
this.uniformContainer.close(); this.uniformContainer.close();
}); });
} }
private static void tryCloseBufferWrapperArray(@Nullable IVertexBufferWrapper[] bufferWrappers)
{
if (bufferWrappers != null)
{
for (int i = 0; i < bufferWrappers.length; i++)
{
IVertexBufferWrapper buffer = bufferWrappers[i];
bufferWrappers[i] = null;
if (buffer != null)
{
buffer.close();
}
}
}
}
//endregion //endregion
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*; import java.util.*;
import com.seibel.distanthorizons.api.enums.config.EDhApiGrassSideRendering; import com.seibel.distanthorizons.api.enums.config.EDhApiGrassSideRendering;
@@ -33,35 +34,26 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.coreapi.util.ColorUtil; import com.seibel.distanthorizons.coreapi.util.ColorUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.lwjgl.system.MemoryUtil;
/** /**
* Used to create the quads before they are converted to render-able buffers. <br><br> * Used to create the quads before they are converted to render-able buffers. <br><br>
* *
* Note: the magic number 6 you see throughout this method represents the number of sides on a cube. * Note: the magic number 6 you see throughout this method represents the number of sides on a cube.
*/ */
public class LodQuadBuilder public class LodQuadBuilder implements AutoCloseable
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
@SuppressWarnings("unchecked") /** ThreadLocal is the simplest way to allow each LOD loading thread to have their own builder */
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6]; private static final ThreadLocal<LodQuadBuilder> THREAD_LOCAL = ThreadLocal.withInitial(LodQuadBuilder::new);
@SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] transparentQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
private final boolean doTransparency;
private final IClientLevelWrapper clientLevelWrapper;
private final EDhApiDebugRendering debugRenderingMode;
private final EDhApiGrassSideRendering grassSideRenderingMode;
/** the number of bytes for a single vertex */ /** the number of bytes for a single vertex */
public static final int BYTES_PER_VERTEX = 16; public static final int BYTES_PER_VERTEX = 16;
public static final int BYTES_PER_QUAD = BYTES_PER_VERTEX * 4; public static final int BYTES_PER_QUAD = BYTES_PER_VERTEX * 4;
public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][] public static final int[][][] DIRECTION_VERTEX_IBO_QUAD = new int[][][]
///region //region
{ {
// X,Z // // X,Z //
{ // UP { // UP
@@ -109,7 +101,27 @@ public class LodQuadBuilder
{0, 0}, // 3 {0, 0}, // 3
}, },
}; };
///endregion //endregion
@SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] opaqueQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
@SuppressWarnings("unchecked")
private final ArrayList<BufferQuad>[] transparentQuads = (ArrayList<BufferQuad>[]) new ArrayList[6];
/**
* Caching the BufferQuad objects reduces overhead slightly. <br>
* Caching is handled per builder (vs globally in {@link BufferQuad} itself)
* to prevent concurrency overhead.
*/
private final ArrayList<BufferQuad> bufferQuadCacheList = new ArrayList<>();
private boolean doTransparency;
private IClientLevelWrapper clientLevelWrapper;
private EDhApiDebugRendering debugRenderingMode;
private EDhApiGrassSideRendering grassSideRenderingMode;
private int premergeCount = 0; private int premergeCount = 0;
@@ -120,20 +132,31 @@ public class LodQuadBuilder
//=============// //=============//
//region //region
public LodQuadBuilder(boolean doTransparency, IClientLevelWrapper clientLevelWrapper) private LodQuadBuilder()
{ {
this.doTransparency = doTransparency;
for (int i = 0; i < 6; i++) for (int i = 0; i < 6; i++)
{ {
this.opaqueQuads[i] = new ArrayList<>(); this.opaqueQuads[i] = new ArrayList<>();
this.transparentQuads[i] = new ArrayList<>(); this.transparentQuads[i] = new ArrayList<>();
} }
}
public static LodQuadBuilder getBuilder(boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
{
LodQuadBuilder builder = THREAD_LOCAL.get();
builder.set(doTransparency, clientLevelWrapper);
return builder;
}
private void set(boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
{
this.doTransparency = doTransparency;
this.clientLevelWrapper = clientLevelWrapper; this.clientLevelWrapper = clientLevelWrapper;
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRenderingColors.get(); this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRenderingColors.get();
this.grassSideRenderingMode = Config.Client.Advanced.Graphics.Quality.grassSideRendering.get(); this.grassSideRenderingMode = Config.Client.Advanced.Graphics.Quality.grassSideRendering.get();
this.premergeCount = 0;
} }
//endregion //endregion
@@ -143,7 +166,7 @@ public class LodQuadBuilder
//===========// //===========//
// add quads // // add quads //
//===========// //===========//
///region //region
public void addQuadAdj( public void addQuadAdj(
EDhDirection dir, EDhDirection dir,
@@ -167,7 +190,8 @@ public class LodQuadBuilder
quadList = this.opaqueQuads[dir.ordinal()]; quadList = this.opaqueQuads[dir.ordinal()];
} }
BufferQuad quad = new BufferQuad(x, y, z, width, height, color, irisBlockMaterialId, skyLight, blockLight, dir); BufferQuad quad = this.getOrCreateBufferQuad();
quad.set(x, y, z, width, height, color, irisBlockMaterialId, skyLight, blockLight, dir);
if (!quadList.isEmpty() if (!quadList.isEmpty()
&& ( && (
quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest)
@@ -182,35 +206,37 @@ public class LodQuadBuilder
} }
// XZ // XZ
public void addQuadUp(short minX, short maxY, short minZ, short width, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) public void addQuadUp(short minX, short maxY, short minZ, short blockWidth, int color, byte irisBlockMaterialId, byte skylight, byte blocklight)
{ {
boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255); boolean isTransparent = (this.doTransparency && ColorUtil.getAlpha(color) < 255);
ArrayList<BufferQuad> quadList = isTransparent ArrayList<BufferQuad> quadList = isTransparent
? this.transparentQuads[EDhDirection.UP.ordinal()] ? this.transparentQuads[EDhDirection.UP.ordinal()]
: this.opaqueQuads[EDhDirection.UP.ordinal()]; : this.opaqueQuads[EDhDirection.UP.ordinal()];
BufferQuad quad = new BufferQuad(minX, maxY, minZ, width, width, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP); BufferQuad quad = this.getOrCreateBufferQuad();
quad.set(minX, maxY, minZ, blockWidth, blockWidth, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.UP);
quadList.add(quad); quadList.add(quad);
} }
public void addQuadDown(short x, short y, short z, short width, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) public void addQuadDown(short x, short y, short z, short blockWidth, int color, byte irisBlockMaterialId, byte skylight, byte blocklight)
{ {
ArrayList<BufferQuad> quadArray = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ArrayList<BufferQuad> quadArray = (this.doTransparency && ColorUtil.getAlpha(color) < 255)
? this.transparentQuads[EDhDirection.DOWN.ordinal()] ? this.transparentQuads[EDhDirection.DOWN.ordinal()]
: this.opaqueQuads[EDhDirection.DOWN.ordinal()]; : this.opaqueQuads[EDhDirection.DOWN.ordinal()];
BufferQuad quad = new BufferQuad(x, y, z, width, width, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN); BufferQuad quad = this.getOrCreateBufferQuad();
quad.set(x, y, z, blockWidth, blockWidth, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN);
quadArray.add(quad); quadArray.add(quad);
} }
///endregion //endregion
//=================// //=================//
// data finalizing // // data finalizing //
//=================// //=================//
///region //region
/** Uses Greedy meshing to merge this builder's Quads. */ /** Uses Greedy meshing to merge this builder's Quads. */
public void mergeQuads() public void mergeQuads()
@@ -279,14 +305,14 @@ public class LodQuadBuilder
return mergeCount; return mergeCount;
} }
///endregion //endregion
//==============// //==============//
// buffer setup // // buffer setup //
//==============// //==============//
///region //region
public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); } public ArrayList<ByteBuffer> makeOpaqueVertexBuffers() { return this.makeVertexBuffers(this.opaqueQuads); }
public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); } public ArrayList<ByteBuffer> makeTransparentVertexBuffers() { return this.makeVertexBuffers(this.transparentQuads); }
@@ -311,7 +337,8 @@ public class LodQuadBuilder
if (buffer == null if (buffer == null
|| buffer.remaining() < BYTES_PER_QUAD) || buffer.remaining() < BYTES_PER_QUAD)
{ {
buffer = MemoryUtil.memAlloc(getMaxBufferByteSize()); buffer = ByteBuffer.allocateDirect(getMaxBufferByteSize());
buffer.order(ByteOrder.nativeOrder());
byteBufferList.add(buffer); byteBufferList.add(buffer);
} }
@@ -394,7 +421,7 @@ public class LodQuadBuilder
// for horizontal and bottom faces of grass blocks, use the dirt color to // for horizontal and bottom faces of grass blocks, use the dirt color to
// prevent green cliff walls // prevent green cliff walls
color = this.clientLevelWrapper.getDirtBlockColor(); color = this.clientLevelWrapper.getDirtBlockColor();
color = ColorUtil.applyShade(color, MC_RENDER.getShade(quad.direction)); color = ColorUtil.applyShade(color, this.clientLevelWrapper.getShade(quad.direction));
} }
} }
} }
@@ -451,14 +478,14 @@ public class LodQuadBuilder
bb.putShort((short) 0); // padding to make sure the vertex format as a whole is a multiple of 4 bb.putShort((short) 0); // padding to make sure the vertex format as a whole is a multiple of 4
} }
///endregion //endregion
//=========// //=========//
// getters // // getters //
//=========// //=========//
///region //region
public int getCurrentOpaqueQuadsCount() public int getCurrentOpaqueQuadsCount()
{ {
@@ -513,7 +540,68 @@ public class LodQuadBuilder
return fullSizedBuffer; return fullSizedBuffer;
} }
///endregion //endregion
//=====================//
// buffer quad pooling //
//=====================//
//region
private BufferQuad getOrCreateBufferQuad()
{
// start from the back of the list so we don't have
// to move the array around
int index = bufferQuadCacheList.size() - 1;
if (index < 0)
{
// cache empty, create a new object
return new BufferQuad();
}
BufferQuad quad = bufferQuadCacheList.remove(index);
if (quad != null) // shouldn't happen, but just in case
{
return quad;
}
return new BufferQuad();
}
private static void returnQuadsToCache(ArrayList<BufferQuad> quadCache, ArrayList<BufferQuad>[] quadsToReturn)
{
for (int i = 0; i < quadsToReturn.length; i++)
{
// manual add and loop to reduce GC pressure due to addAll() doing unnecessary
// array copies
for (int j = 0; j < quadsToReturn[i].size(); j++)
{
quadCache.add(quadsToReturn[i].get(j));
}
quadsToReturn[i].clear();
}
}
//endregion
//================//
// base overrides //
//================//
//region
// can be used/closed multiple times
@Override
public void close()
{
returnQuadsToCache(this.bufferQuadCacheList, this.opaqueQuads);
returnQuadsToCache(this.bufferQuadCacheList, this.transparentQuads);
}
//endregion
@@ -129,6 +129,7 @@ public class FullDataToRenderDataTransformer
ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled(); ColumnRenderView tempExpandingColumnView = ColumnRenderView.getPooled();
RenderDataPointReducingList reducingList = new RenderDataPointReducingList()) RenderDataPointReducingList reducingList = new RenderDataPointReducingList())
{ {
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable();
for (int x = 0; x < FullDataSourceV2.WIDTH; x++) for (int x = 0; x < FullDataSourceV2.WIDTH; x++)
{ {
for (int z = 0; z < FullDataSourceV2.WIDTH; z++) for (int z = 0; z < FullDataSourceV2.WIDTH; z++)
@@ -142,7 +143,7 @@ public class FullDataToRenderDataTransformer
baseX + BitShiftUtil.pow(x, dataDetail), baseZ + BitShiftUtil.pow(z, dataDetail), baseX + BitShiftUtil.pow(x, dataDetail), baseZ + BitShiftUtil.pow(z, dataDetail),
columnArrayView, dataColumn, columnArrayView, dataColumn,
// pooled references so we don't need to re-allocate/get them 4000 times per render source // pooled references so we don't need to re-allocate/get them 4000 times per render source
phantomCheckout, tempExpandingColumnView, reducingList); phantomCheckout, tempExpandingColumnView, reducingList, mutableBlockPos);
} }
} }
} }
@@ -157,7 +158,7 @@ public class FullDataToRenderDataTransformer
ColumnRenderView columnArrayView, ColumnRenderView columnArrayView,
LongArrayList fullDataColumn, LongArrayList fullDataColumn,
// pooled references // pooled references
PhantomArrayListCheckout phantomCheckout, ColumnRenderView tempExpandingColumnView, RenderDataPointReducingList reducingList) PhantomArrayListCheckout phantomCheckout, ColumnRenderView tempExpandingColumnView, RenderDataPointReducingList reducingList, DhBlockPosMutable mutableBlockPos)
{ {
// we can't do anything if the full data is missing or empty // we can't do anything if the full data is missing or empty
if (fullDataColumn == null if (fullDataColumn == null
@@ -170,7 +171,7 @@ public class FullDataToRenderDataTransformer
if (fullDataLength <= columnArrayView.maxVerticalSliceCount) if (fullDataLength <= columnArrayView.maxVerticalSliceCount)
{ {
// Directly use the arrayView since it fits. // Directly use the arrayView since it fits.
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, columnArrayView, fullDataColumn, mutableBlockPos);
} }
else else
{ {
@@ -178,7 +179,7 @@ public class FullDataToRenderDataTransformer
// expand the ColumnArrayView to fit the new larger max vertical size // expand the ColumnArrayView to fit the new larger max vertical size
tempExpandingColumnView.populate(dataArrayList, fullDataLength, 0, fullDataLength); tempExpandingColumnView.populate(dataArrayList, fullDataLength, 0, fullDataLength);
setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, tempExpandingColumnView, fullDataColumn); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, tempExpandingColumnView, fullDataColumn, mutableBlockPos);
columnArrayView.changeVerticalSizeFrom(tempExpandingColumnView, reducingList); columnArrayView.changeVerticalSizeFrom(tempExpandingColumnView, reducingList);
} }
@@ -186,14 +187,14 @@ public class FullDataToRenderDataTransformer
private static void setRenderColumnView( private static void setRenderColumnView(
IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource, IClientLevelWrapper levelWrapper, FullDataSourceV2 fullDataSource,
int blockX, int blockZ, int blockX, int blockZ,
ColumnRenderView renderColumnData, LongArrayList fullColumnData) ColumnRenderView renderColumnData, LongArrayList fullColumnData, DhBlockPosMutable mutableBlockPos)
{ {
//===============// //===============//
// config values // // config values //
//===============// //===============//
boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING); boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Culling.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get(); boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Culling.tintWithAvoidedBlocks.get();
final ObjectOpenHashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper); final ObjectOpenHashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(levelWrapper);
final ObjectOpenHashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper); final ObjectOpenHashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(levelWrapper);
@@ -242,7 +243,8 @@ public class FullDataToRenderDataTransformer
FullDataPointIdMap fullDataMapping = fullDataSource.mapping; FullDataPointIdMap fullDataMapping = fullDataSource.mapping;
DhBlockPosMutable mutableBlockPos = new DhBlockPosMutable(blockX, 0, blockZ); mutableBlockPos.setX(blockX);
mutableBlockPos.setZ(blockZ);
// goes from the top down // goes from the top down
for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++) for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++)
@@ -450,8 +452,17 @@ public class FullDataToRenderDataTransformer
// use the previous block's color // use the previous block's color
color = colorToApplyToNextBlock; color = colorToApplyToNextBlock;
colorToApplyToNextBlock = -1; colorToApplyToNextBlock = -1;
skyLight = skylightToApplyToNextBlock;
blockLight = blocklightToApplyToNextBlock; // use the skylight override if present
if (skylightToApplyToNextBlock != -1)
{
skyLight = skylightToApplyToNextBlock;
}
if (blocklightToApplyToNextBlock != -1)
{
blockLight = blocklightToApplyToNextBlock;
}
} }
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.core.enums; package com.seibel.distanthorizons.core.enums;
import com.seibel.distanthorizons.core.util.math.Vec3i; import com.seibel.distanthorizons.core.util.math.DhVec3i;
/** /**
* Up <Br> * Up <Br>
@@ -32,17 +32,17 @@ import com.seibel.distanthorizons.core.util.math.Vec3i;
public enum EDhDirection public enum EDhDirection
{ {
/** negative Y */ /** negative Y */
DOWN("down", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Y, new Vec3i(0, -1, 0), -1), DOWN("down", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Y, new DhVec3i(0, -1, 0), -1),
/** positive Y */ /** positive Y */
UP("up", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Y, new Vec3i(0, 1, 0), -1), UP("up", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Y, new DhVec3i(0, 1, 0), -1),
/** negative Z */ /** negative Z */
NORTH("north", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Z, new Vec3i(0, 0, -1), 0), NORTH("north", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.Z, new DhVec3i(0, 0, -1), 0),
/** positive Z */ /** positive Z */
SOUTH("south", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Z, new Vec3i(0, 0, 1), 1), SOUTH("south", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.Z, new DhVec3i(0, 0, 1), 1),
/** negative X */ /** negative X */
WEST("west", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.X, new Vec3i(-1, 0, 0), 2), WEST("west", EDhDirection.AxisDirection.NEGATIVE, EDhDirection.Axis.X, new DhVec3i(-1, 0, 0), 2),
/** positive X */ /** positive X */
EAST("east", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.X, new Vec3i(1, 0, 0), 3); EAST("east", EDhDirection.AxisDirection.POSITIVE, EDhDirection.Axis.X, new DhVec3i(1, 0, 0), 3);
/** Up, Down, West, East, North, South */ /** Up, Down, West, East, North, South */
@@ -68,7 +68,7 @@ public enum EDhDirection
public final String name; public final String name;
public final EDhDirection.Axis axis; public final EDhDirection.Axis axis;
public final EDhDirection.AxisDirection axisDirection; public final EDhDirection.AxisDirection axisDirection;
public final Vec3i normal; public final DhVec3i normal;
/** -1 if not a {@link EDhDirection#CARDINAL_COMPASS} direction */ /** -1 if not a {@link EDhDirection#CARDINAL_COMPASS} direction */
public final int compassIndex; public final int compassIndex;
@@ -78,7 +78,7 @@ public enum EDhDirection
// constructor // // constructor //
//=============// //=============//
EDhDirection(String name, EDhDirection.AxisDirection axisDirection, EDhDirection.Axis axis, Vec3i normal, int compassIndex) EDhDirection(String name, EDhDirection.AxisDirection axisDirection, EDhDirection.Axis axis, DhVec3i normal, int compassIndex)
{ {
this.name = name; this.name = name;
this.axis = axis; this.axis = axis;
@@ -102,7 +102,7 @@ public abstract class AbstractLodRequestState
remainingChunkCount += this.retrievalQueue.getQueuedChunkCount(); remainingChunkCount += this.retrievalQueue.getQueuedChunkCount();
String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount); String remainingChunkCountStr = F3Screen.NUMBER_FORMAT.format(remainingChunkCount);
String message = "DH is generating chunks. "; String message = "DH is "+this.retrievalQueue.getRetrievalTypeName()+". ";
if (this.dhLevel.getClass() == DhServerLevel.class) if (this.dhLevel.getClass() == DhServerLevel.class)
{ {
// server levels can have multiple world generators running at once, // server levels can have multiple world generators running at once,
@@ -65,6 +65,12 @@ public interface IFullDataSourceRetrievalQueue extends Closeable
*/ */
byte highestDataDetail(); byte highestDataDetail();
/**
* Returns a value like "downloading" or "generating" depending on how the LODs are being retrieved.
* Used to make the progress message easier to understand.
*/
String getRetrievalTypeName();
//endregion //endregion
@@ -123,17 +123,6 @@ public class LodRequestModule implements Closeable
// if the world is read only don't generate anything // if the world is read only don't generate anything
shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.tryGetReadOnly(); shouldDoWorldGen &= !DhApiWorldProxy.INSTANCE.tryGetReadOnly();
// don't generate chunks for client levels that aren't being rendered
// (this can happen when moving between dimensions)
if (this.level instanceof IDhClientLevel)
{
boolean isRendering = ((IDhClientLevel) this.level).isRendering();
if (!isRendering)
{
shouldDoWorldGen = false;
}
}
boolean isWorldGenRunning = this.isWorldGenRunning(); boolean isWorldGenRunning = this.isWorldGenRunning();
@@ -51,6 +51,9 @@ public class RemoteWorldRetrievalQueue extends AbstractFullDataNetworkRequestQue
@Override @Override
public byte highestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; } public byte highestDataDetail() { return LodUtil.BLOCK_DETAIL_LEVEL; }
@Override
public String getRetrievalTypeName() { return "downloading LODs"; }
@Override @Override
public CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long sectionPos, byte requiredDataDetail) public CompletableFuture<DataSourceRetrievalResult> submitRetrievalTask(long sectionPos, byte requiredDataDetail)
{ {
@@ -593,6 +593,8 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
@Override public byte lowestDataDetail() { return this.lowestDataDetail; } @Override public byte lowestDataDetail() { return this.lowestDataDetail; }
@Override public byte highestDataDetail() { return this.highestDataDetail; } @Override public byte highestDataDetail() { return this.highestDataDetail; }
@Override public String getRetrievalTypeName() { return "generating chunks"; }
@Override public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; } @Override public int getEstimatedRemainingTaskCount() { return this.estimatedRemainingTaskCount; }
@Override public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; } @Override public void setEstimatedRemainingTaskCount(int newEstimate) { this.estimatedRemainingTaskCount = newEstimate; }
@@ -1,199 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.jar;
import java.io.*;
import java.util.regex.Pattern;
/**
* A fork of iris' dark mode detector (https://github.com/IrisShaders/Iris-Installer/blob/master/src/main/java/net/hypercubemc/iris_installer/DarkModeDetector.java)
* Which is a fork of HanSolo's dark mode detector (https://gist.github.com/HanSolo/7cf10b86efff8ca2845bf5ec2dd0fe1d)
*
* This fork has better support for Linux
*
* @author HanSolo
* @author IMS
* @author coolGi
*/
public class DarkModeDetector
{
private static final String REGQUERY_UTIL = "reg query ";
private static final String REGDWORD_TOKEN = "REG_DWORD";
private static final String DARK_THEME_CMD = REGQUERY_UTIL + "\"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\"" + " /v AppsUseLightTheme";
public static boolean isDarkMode()
{
switch (EPlatform.get())
{
case WINDOWS:
return isWindowsDarkMode();
case MACOS:
return isMacOsDarkMode();
case LINUX:
// Most Unix(-like) distros also use a lot of the same things as Linux (like desktop environments and window managers)
case BSD:
case UNIX:
return checkLinuxDark();
default:
return false;
}
}
// Needs checking as I dont use Mac
public static boolean isMacOsDarkMode()
{
boolean isDarkMode = false;
String line = query("defaults read -g AppleInterfaceStyle");
if (line.equals("Dark"))
{
isDarkMode = true;
}
return isDarkMode;
}
// Needs checking as I don't use Windows
public static boolean isWindowsDarkMode()
{
try
{
String result = query(DARK_THEME_CMD);
int p = result.indexOf(REGDWORD_TOKEN);
if (p == -1)
{
return false;
}
// 1 == Light Mode, 0 == Dark Mode
String temp = result.substring(p + REGDWORD_TOKEN.length()).trim();
return ((Integer.parseInt(temp.substring("0x".length()), 16))) == 0;
}
catch (Exception e)
{
return false;
}
}
// On Linux there are 2 popular formats for theming
// They are qt and gtk. We check the desktop environment and use that to pick which one to use (if none work then use GTK)
public static boolean checkLinuxDark()
{
// Checks "/usr/bin" as "echo $XDG_CURRENT_DESKTOP" dosnt work in java and dosnt detect window managers
File de_location = new File("/usr/bin");
// System.out.println(de_location.list());
for (String de : de_location.list())
{
// System.out.println(de);
if (de.contains("gnome-session")) // Gnome uses GTK
{
return GTKChecker();
}
if (de.contains("plasma_session")) // KDE plasma uses QT
{
return QTChecker();
}
}
return GTKChecker(); // GTK works best with non plasma desktops (desktops includes window managers)
}
public static boolean GTKChecker()
{
// Checks if the return to "gsettings get org.gnome.desktop.interface color-scheme" in terminal is 'prefer-dark' or contains the word dark in it
final Pattern darkThemeNamePattern = Pattern.compile(".*dark.*", Pattern.CASE_INSENSITIVE);
return darkThemeNamePattern.matcher(query("gsettings get org.gnome.desktop.interface color-scheme")).matches();
}
public static boolean QTChecker()
{
// Get the contents of "~/.config/Trolltech.conf" then check "KWinPalette\activeBackground"
// With that you grayscale the rgb and check if it is over/under 128
// If there is a better way of doing this then please let me know
// This seems like the best way as qt dosnt have a dark/light preference and just stores pure color values
try
{
File themeFile = new File(System.getProperty("user.home") + "/.config/Trolltech.conf");
BufferedReader reader = new BufferedReader(new FileReader(themeFile));
String themeLine = reader.readLine();
while (themeLine != null)
{ // Go through each line till you find "KWinPalette\activeBackground"
if (themeLine.contains("KWinPalette\\activeBackground"))
{
break;
}
themeLine = reader.readLine();
}
reader.close();
// Get where the # is then read the hex numbers after it
short index = (short) themeLine.indexOf("#");
short r = (short) Integer.parseInt("" + themeLine.charAt(index + 1) + themeLine.charAt(index + 2), 16);
short g = (short) Integer.parseInt("" + themeLine.charAt(index + 3) + themeLine.charAt(index + 4), 16);
short b = (short) Integer.parseInt("" + themeLine.charAt(index + 5) + themeLine.charAt(index + 6), 16);
if ((r + g + b) / 2 >= 128)
{
return false;
}
else
{
return true;
}
}
catch (Exception e)
{
e.printStackTrace(); return false;
}
}
/** Runs a command trough command line */
private static String query(String cmd)
{
try
{
Process process = Runtime.getRuntime().exec(cmd);
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())))
{
String actualReadLine;
while ((actualReadLine = reader.readLine()) != null)
{
if (stringBuilder.length() != 0)
{
stringBuilder.append('\n');
}
stringBuilder.append(actualReadLine);
}
}
return stringBuilder.toString();
}
catch (IOException e)
{
System.out.println("Exception caught while querying the OS:");
e.printStackTrace();
return "";
}
}
}
@@ -57,13 +57,13 @@ public class SelfUpdater
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IVersionConstants VERSION_CONSTANTS = SingletonInjector.INSTANCE.get(IVersionConstants.class);
private static final String MC_VERSION = VERSION_CONSTANTS.getMinecraftVersion();
/** As we cannot delete(or replace) the jar while the mod is running, we just have this to delete it once the game closes */ /** As we cannot delete(or replace) the jar while the mod is running, we just have this to delete it once the game closes */
public static boolean deleteOldJarOnJvmShutdown = false; public static boolean deleteOldJarOnJvmShutdown = false;
private static String currentJarSha = "";
private static String mcVersion = SingletonInjector.INSTANCE.get(IVersionConstants.class).getMinecraftVersion();
public static File newFileLocation; public static File newFileLocation;
@@ -75,29 +75,33 @@ public class SelfUpdater
*/ */
public static boolean onStart() public static boolean onStart()
{ {
LOGGER.info("Checking for Distant Horizons update"); if (!Config.Client.Advanced.AutoUpdater.enableAutoUpdater.get())
try
{ {
currentJarSha = JarUtils.getFileChecksum(MessageDigest.getInstance("SHA"), JarUtils.jarFile); LOGGER.info("Distant Horizons auto update disabled.");
}
catch (Exception e)
{
LOGGER.error("Unable to get existing jar checksum, error: ["+e.getMessage()+"].", e);
return false; return false;
} }
boolean returnValue = false;
try try
{ {
EDhApiUpdateBranch updateBranch = EDhApiUpdateBranch.convertAutoToStableOrNightly(Config.Client.Advanced.AutoUpdater.updateBranch.get()); EDhApiUpdateBranch updateBranch = EDhApiUpdateBranch.convertAutoToStableOrNightly(Config.Client.Advanced.AutoUpdater.updateBranch.get());
returnValue = (updateBranch == EDhApiUpdateBranch.STABLE) ? onStableStart() : onNightlyStart();
LOGGER.info("Checking for Distant Horizons ["+updateBranch+"] update for MC ["+MC_VERSION+"]...");
if (updateBranch == EDhApiUpdateBranch.STABLE)
{
return onStableStart();
}
else
{
return onNightlyStart();
}
} }
catch (Exception e) // Shouldn't be needed, but just in case catch (Exception e) // Shouldn't be needed, but just in case
{ {
LOGGER.warn("Unexpected updater startup error: ["+e.getMessage()+"].", e); LOGGER.warn("Unexpected updater startup error: ["+e.getMessage()+"].", e);
return false;
} }
return returnValue;
} }
private static boolean onStableStart() private static boolean onStableStart()
{ {
@@ -108,15 +112,19 @@ public class SelfUpdater
LOGGER.warn("Unable to find any nightly build pipelines, auto update will be unavailable."); LOGGER.warn("Unable to find any nightly build pipelines, auto update will be unavailable.");
return false; return false;
} }
if (!ModrinthGetter.mcVersions.contains(mcVersion)) if (!ModrinthGetter.mcVersions.contains(MC_VERSION))
{ {
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Modrinth, only findable versions are ["+ StringUtil.join(", ", ModrinthGetter.mcVersions) +"]"); LOGGER.warn("Minecraft version ["+ MC_VERSION +"] is not findable on Modrinth, only findable versions are ["+ StringUtil.join(", ", ModrinthGetter.mcVersions) +"]");
return false; return false;
} }
try try
{ {
newFileLocation = JarUtils.jarFile.getParentFile().toPath().resolve("update").resolve(ModInfo.NAME + "-" + ModrinthGetter.getLatestNameForVersion(mcVersion) + "-" + mcVersion + ".jar").toFile(); newFileLocation = JarUtils.jarFile
.getParentFile().toPath()
.resolve("update")
.resolve(ModInfo.NAME + "-" + ModrinthGetter.getLatestNameForVersion(MC_VERSION) + "-" + MC_VERSION + ".jar")
.toFile();
} }
catch (Exception e) catch (Exception e)
{ {
@@ -124,8 +132,19 @@ public class SelfUpdater
return false; return false;
} }
String currentJarSha;
try
{
currentJarSha = JarUtils.getFileChecksum(MessageDigest.getInstance("SHA"), JarUtils.jarFile);
}
catch (Exception e)
{
LOGGER.error("Unable to get existing jar checksum, error: ["+e.getMessage()+"].", e);
return false;
}
// Check the sha's of both our stuff // Check the sha's of both our stuff
if (currentJarSha.equals(ModrinthGetter.getLatestShaForVersion(mcVersion))) if (currentJarSha.equals(ModrinthGetter.getLatestShaForVersion(MC_VERSION)))
{ {
LOGGER.info("Distant Horizons already up to date."); LOGGER.info("Distant Horizons already up to date.");
return false; return false;
@@ -137,21 +156,23 @@ public class SelfUpdater
} }
LOGGER.info("New version (" + ModrinthGetter.getLatestNameForVersion(mcVersion) + ") of Distant Horizons is available"); LOGGER.info("New version (" + ModrinthGetter.getLatestNameForVersion(MC_VERSION) + ") of Distant Horizons is available");
if (Config.Client.Advanced.AutoUpdater.enableSilentUpdates.get()) if (Config.Client.Advanced.AutoUpdater.enableSilentUpdates.get())
{ {
// Auto-update mod // Auto-update mod
updateMod(mcVersion, newFileLocation); updateMod(MC_VERSION, newFileLocation);
return false; return false;
} }
else else
{ {
LOGGER.info("Download link: " + ModrinthGetter.getLatestDownloadForVersion(mcVersion)); LOGGER.info("Download link: " + ModrinthGetter.getLatestDownloadForVersion(MC_VERSION));
} }
return true; return true;
} }
private static boolean onNightlyStart() private static boolean onNightlyStart()
{ {
LOGGER.info("Checking for Distant Horizons Nightly update...");
if (GitlabGetter.INSTANCE.projectPipelines.size() == 0) if (GitlabGetter.INSTANCE.projectPipelines.size() == 0)
{ {
LOGGER.info("Unable to find any nightly build pipelines, auto update will be unavailable."); LOGGER.info("Unable to find any nightly build pipelines, auto update will be unavailable.");
@@ -171,9 +192,9 @@ public class SelfUpdater
return false; return false;
} }
if (!GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).containsKey(mcVersion)) if (!GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).containsKey(MC_VERSION))
{ {
LOGGER.warn("Minecraft version ["+ mcVersion +"] is not findable on Gitlab, findable versions are ["+ StringUtil.join(", ", GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).keySet().toArray()) +"]."); LOGGER.warn("Minecraft version ["+ MC_VERSION +"] is not findable on Gitlab, findable versions are ["+ StringUtil.join(", ", GitlabGetter.INSTANCE.getDownloads(pipeline.get("id")).keySet().toArray()) +"].");
return false; return false;
} }
@@ -200,12 +221,12 @@ public class SelfUpdater
if (Config.Client.Advanced.AutoUpdater.enableSilentUpdates.get()) if (Config.Client.Advanced.AutoUpdater.enableSilentUpdates.get())
{ {
// Auto-update mod // Auto-update mod
updateMod(mcVersion, newFileLocation); updateMod(MC_VERSION, newFileLocation);
return false; return false;
} }
else else
{ {
LOGGER.info("Download link: " + GitlabGetter.getLatestForVersion(mcVersion)); LOGGER.info("Download link: " + GitlabGetter.getLatestForVersion(MC_VERSION));
} }
return true; return true;
} }
@@ -9,7 +9,6 @@ import com.seibel.distanthorizons.core.multiplayer.server.FullDataSourceRequestH
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerState;
import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager; import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManager;
import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException; import com.seibel.distanthorizons.core.network.exceptions.RequestOutOfRangeException;
import com.seibel.distanthorizons.core.network.exceptions.RequestRejectedException;
import com.seibel.distanthorizons.core.network.exceptions.SectionRequiresSplittingException; import com.seibel.distanthorizons.core.network.exceptions.SectionRequiresSplittingException;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage; import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage; import com.seibel.distanthorizons.core.network.messages.AbstractTrackableMessage;
@@ -22,14 +21,14 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.WorldGenUtil; import com.seibel.distanthorizons.core.util.WorldGenUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
@@ -72,9 +71,11 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
boolean runRepoReliantSetup boolean runRepoReliantSetup
) throws SQLException, IOException ) throws SQLException, IOException
{ {
if (saveStructure.getSaveFolder(serverLevelWrapper).mkdirs()) File saveFolder = saveStructure.getSaveFolder(serverLevelWrapper);
saveFolder.mkdirs();
if (!saveFolder.exists())
{ {
LOGGER.warn("unable to create data folder."); throw new IOException("unable to create save folder at ["+saveFolder.getPath()+"]. If you're on Windows you may need to enable long file paths.");
} }
this.serverLevelWrapper = serverLevelWrapper; this.serverLevelWrapper = serverLevelWrapper;
this.serverside = new ServerLevelModule(this, saveStructure); this.serverside = new ServerLevelModule(this, saveStructure);
@@ -114,7 +115,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
this.worldGenPlayerCenteringQueue.add(firstPlayer); this.worldGenPlayerCenteringQueue.add(firstPlayer);
this.worldGenPlayerCenteringQueue.remove(firstPlayer); this.worldGenPlayerCenteringQueue.remove(firstPlayer);
Vec3d position = firstPlayer.getPosition(); DhVec3d position = firstPlayer.getPosition();
return new DhBlockPos2D((int) position.x, (int) position.z); return new DhBlockPos2D((int) position.x, (int) position.z);
} }
@@ -133,7 +134,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
return; return;
} }
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition(); DhVec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(message.sectionPos, new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16; int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(message.sectionPos, new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
ServerPlayerState.RateLimiterSet rateLimiterSet = serverPlayerState.getRateLimiterSet(this); ServerPlayerState.RateLimiterSet rateLimiterSet = serverPlayerState.getRateLimiterSet(this);
@@ -200,26 +201,6 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
LodUtil.assertTrue(message.getSession().serverPlayer != null); LodUtil.assertTrue(message.getSession().serverPlayer != null);
// Check if the player is in this dimension,
// since handling multiple dimensions isn't allowed
if (message.getSession().serverPlayer.getLevel() != this.getLevelWrapper())
{
// If the message can be replied to - reply with an error, otherwise just ignore
if (message instanceof AbstractTrackableMessage)
{
((AbstractTrackableMessage) message).sendResponse(
new RequestRejectedException(
"Generation not allowed. " +
"Requested dimension: ["+((ILevelRelatedMessage) message).getLevelName()+"], " +
"player dimension: [" + message.getSession().serverPlayer.getLevel().getDhIdentifier() + "], " +
"handler dimension: [" + this.getLevelWrapper().getDhIdentifier() + "]"
)
);
}
return false;
}
return true; return true;
} }
@@ -270,7 +251,7 @@ public abstract class AbstractDhServerLevel extends AbstractDhLevel implements I
continue; continue;
} }
Vec3d playerPosition = serverPlayerState.getServerPlayer().getPosition(); DhVec3d playerPosition = serverPlayerState.getServerPlayer().getPosition();
int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16; int distanceFromPlayer = DhSectionPos.getChebyshevSignedBlockDistance(data.getPos(), new DhBlockPos2D((int) playerPosition.x, (int) playerPosition.z)) / 16;
if (distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius()) if (distanceFromPlayer <= serverPlayerState.sessionConfig.getMaxUpdateDistanceRadius())
{ {
@@ -29,8 +29,10 @@ import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree; import com.seibel.distanthorizons.core.render.QuadTree.LodQuadTree;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLogger;
@@ -44,6 +46,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
{ {
private static final DhLogger LOGGER = new DhLoggerBuilder().build(); private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class); private static final IWrapperFactory WRAPPER_FACTORY = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
private final IDhClientLevel clientLevel; private final IDhClientLevel clientLevel;
@@ -106,7 +109,21 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
this.ClientRenderStateRef.set(clientRenderState); this.ClientRenderStateRef.set(clientRenderState);
} }
clientRenderState.quadtree.tryTick(new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos()));
DhBlockPos2D quadTreeTickBlockPos;
if (Config.Client.Advanced.Graphics.Quality.useCameraPositionForQualityDropOff.get())
{
// use camera position allow free cam mods work better
DhVec3d cameraDoublePos = MC_RENDER.getCameraExactPosition();
quadTreeTickBlockPos = new DhBlockPos2D((int)cameraDoublePos.x, (int)cameraDoublePos.z);
}
else
{
// player position allows multi-cam mods to work better
quadTreeTickBlockPos = new DhBlockPos2D(MC_CLIENT.getPlayerBlockPos());
}
clientRenderState.quadtree.tryTick(quadTreeTickBlockPos);
} }
@@ -120,7 +137,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
ClientRenderState clientRenderState = new ClientRenderState(this.clientLevel, this.clientLevel.getFullDataProvider()); ClientRenderState clientRenderState = new ClientRenderState(this.clientLevel, this.clientLevel.getFullDataProvider());
if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState)) if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState))
{ {
LOGGER.warn("Renderer already started for ["+this+"]."); LOGGER.warn("Renderer already started for ["+this.clientLevel.getClientLevelWrapper()+"].");
clientRenderState.close(); clientRenderState.close();
} }
} }
@@ -170,6 +187,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
} }
this.fullDataSourceProvider.removeDataSourceUpdateListener(this); this.fullDataSourceProvider.removeDataSourceUpdateListener(this);
this.genericRenderer.close();
} }
@@ -259,7 +277,7 @@ public class ClientLevelModule implements Closeable, IDataSourceUpdateListenerFu
@Override @Override
public void close() public void close()
{ {
LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName()); //LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName());
this.quadtree.close(); this.quadtree.close();
} }
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.level;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; 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.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
@@ -114,16 +115,19 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
File saveFolder = saveStructure.getSaveFolder(clientLevelWrapper); File saveFolder = saveStructure.getSaveFolder(clientLevelWrapper);
File pre23Folder = saveStructure.getPre23SaveFolder(clientLevelWrapper); File pre23Folder = saveStructure.getPre23SaveFolder(clientLevelWrapper);
saveFolder.mkdirs();
if (pre23Folder.exists()) if (pre23Folder.exists())
{ {
if (!pre23Folder.renameTo(saveFolder)) if (!pre23Folder.renameTo(saveFolder))
{ {
throw new RuntimeException("Could not move old save data folder: " + pre23Folder.getAbsolutePath() + " to " + saveFolder.getAbsolutePath()); throw new RuntimeException("Could not move old save data folder: [" + pre23Folder.getAbsolutePath() + "] to [" + saveFolder.getAbsolutePath() + "].");
} }
} }
else if (saveStructure.getSaveFolder(clientLevelWrapper).mkdirs())
if (!saveFolder.exists())
{ {
LOGGER.warn("unable to create data folder."); throw new IOException("unable to create save folder at ["+saveFolder.getPath()+"]. If you're on Windows you may need to enable long file paths.");
} }
this.levelWrapper = clientLevelWrapper; this.levelWrapper = clientLevelWrapper;
@@ -167,15 +171,17 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
} }
// Check this before decoding data to prevent errors if multiple client levels
// are receiving data at once (Immersive Portals compatibility).
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
//NETWORK_LOGGER.debug("Buffer ["+message.payload.dtoBufferId+"] isSameLevel: ["+isSameLevel+"]");
if (!isSameLevel)
{
return;
}
try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload)) try (FullDataSourceV2DTO dataSourceDto = this.networkState.fullDataPayloadReceiver.decodeDataSource(message.payload))
{ {
boolean isSameLevel = message.isSameLevelAs(this.levelWrapper);
NETWORK_LOGGER.debug("Buffer ["+message.payload.dtoBufferId+"] isSameLevel: ["+isSameLevel+"]");
if (!isSameLevel)
{
return;
}
Executor executor = ThreadPoolUtil.getFileHandlerExecutor(); Executor executor = ThreadPoolUtil.getFileHandlerExecutor();
if (executor != null) if (executor != null)
@@ -219,6 +225,15 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
{ {
try try
{ {
// only tick the level the player is currently in
// (done to prevent ticking LodQuadTree's for levels that aren't rendering)
IClientLevelWrapper clientLevelWrapper = MC_CLIENT.getWrappedClientLevel();
if (clientLevelWrapper == null
|| clientLevelWrapper.getDhLevel() != this)
{
return;
}
this.clientside.clientTick(); this.clientside.clientTick();
if (this.syncOnLoadRequestQueue != null) if (this.syncOnLoadRequestQueue != null)
@@ -333,12 +348,15 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
String o = MinecraftTextFormat.ORANGE; String o = MinecraftTextFormat.ORANGE;
String y = MinecraftTextFormat.YELLOW; String y = MinecraftTextFormat.YELLOW;
String g = MinecraftTextFormat.GREEN; String g = MinecraftTextFormat.GREEN;
String r = MinecraftTextFormat.RED;
String cf = MinecraftTextFormat.CLEAR_FORMATTING; String cf = MinecraftTextFormat.CLEAR_FORMATTING;
String dimName = this.levelWrapper.getDhIdentifier(); String dimName = this.levelWrapper.getDhIdentifier();
boolean rendering = this.clientside.isRendering(); boolean rendering =
String renderingString = rendering ? (g+"yes"+cf) : (o+"no"+cf); this.clientside.isRendering()
&& !IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName);
String renderingString = rendering ? (g+"yes"+cf) : (r+"no"+cf);
messageList.add("["+y+dimName+cf+"] rendering: "+renderingString); messageList.add("["+y+dimName+cf+"] rendering: "+renderingString);
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.level; package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.core.config.eventHandlers.IgnoredDimensionCsvHandler;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ISaveStructure; import com.seibel.distanthorizons.core.file.structure.ISaveStructure;
@@ -71,7 +72,19 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
//region //region
@Override @Override
public void clientTick() { this.clientside.clientTick(); } public void clientTick()
{
// only tick the level the player is currently in
// (done to prevent ticking LodQuadTree's for levels that aren't rendering)
IClientLevelWrapper clientLevelWrapper = MC_CLIENT.getWrappedClientLevel();
if (clientLevelWrapper == null
|| clientLevelWrapper.getDhLevel() != this)
{
return;
}
this.clientside.clientTick();
}
//endregion //endregion
@@ -121,12 +134,15 @@ public class DhClientServerLevel extends AbstractDhServerLevel implements IDhCli
String o = MinecraftTextFormat.ORANGE; String o = MinecraftTextFormat.ORANGE;
String y = MinecraftTextFormat.YELLOW; String y = MinecraftTextFormat.YELLOW;
String g = MinecraftTextFormat.GREEN; String g = MinecraftTextFormat.GREEN;
String r = MinecraftTextFormat.RED;
String cf = MinecraftTextFormat.CLEAR_FORMATTING; String cf = MinecraftTextFormat.CLEAR_FORMATTING;
String dimName = this.serverLevelWrapper.getDhIdentifier(); String dimName = this.serverLevelWrapper.getDhIdentifier();
boolean rendering = this.clientside.isRendering(); boolean rendering =
String renderingString = rendering ? (g+"yes"+cf) : (o+"no"+cf); this.clientside.isRendering()
&& !IgnoredDimensionCsvHandler.INSTANCE.dimensionNameShouldBeIgnored(dimName);
String renderingString = rendering ? (g+"yes"+cf) : (r+"no"+cf);
messageList.add("["+y+dimName+cf+"] rendering: "+renderingString); messageList.add("["+y+dimName+cf+"] rendering: "+renderingString);
super.addDebugMenuStringsToList(messageList); super.addDebugMenuStringsToList(messageList);
@@ -28,9 +28,9 @@ import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindab
*/ */
public interface IKeyedClientLevelManager extends IBindable public interface IKeyedClientLevelManager extends IBindable
{ {
IServerKeyedClientLevel getServerKeyedLevel(); IServerKeyedClientLevel getServerKeyedLevel(IClientLevelWrapper levelWrapper);
/** Called when a client level is wrapped by a ServerEnhancedClientLevel, for integration into mod internals. */ /** Called when a client level is wrapped by a ServerKeyedClientLevel, for integration into mod internals. */
IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String serverKey, String levelKey); IServerKeyedClientLevel setServerKeyedLevel(IClientLevelWrapper clientLevel, String dimensionResource, String serverKey, String levelKey);
void clearKeyedLevel(); void clearKeyedLevel();
boolean isEnabled(); boolean isEnabled();
@@ -128,7 +128,7 @@ public class F3Screen
messageList.add("LOD Pos: "+y+detailLevel+"*"+posX+","+posZ+cf); messageList.add("LOD Pos: "+y+detailLevel+"*"+posX+","+posZ+cf);
AbstractDhRenderApiDefinition renderApiDef = SingletonInjector.INSTANCE.get(AbstractDhRenderApiDefinition.class); AbstractDhRenderApiDefinition renderApiDef = SingletonInjector.INSTANCE.get(AbstractDhRenderApiDefinition.class);
messageList.add("Rendering API: "+a+renderApiDef.getApiName()+cf); messageList.add("Rendering API: "+a+renderApiDef.getEngineName()+cf);
} }
messageList.add(""); messageList.add("");
} }
@@ -13,6 +13,7 @@ import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.messages.base.RequestLevelInitMessage;
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage; import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceResponseMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
@@ -164,7 +165,8 @@ public class ClientNetworkState implements Closeable
// send message // // send message //
//==============// //==============//
public void sendLevelInitRequest(String clientLevelKey)
{ this.getSession().sendMessage(new RequestLevelInitMessage(clientLevelKey)); }
public void sendConfigMessage() { this.sendConfigMessage(true); } public void sendConfigMessage() { this.sendConfigMessage(true); }
public void sendConfigMessage(boolean blocking) public void sendConfigMessage(boolean blocking)
@@ -1,7 +1,7 @@
package com.seibel.distanthorizons.core.multiplayer.server; package com.seibel.distanthorizons.core.multiplayer.server;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel; import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig; import com.seibel.distanthorizons.core.multiplayer.config.SessionConfig;
import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadSender; import com.seibel.distanthorizons.core.multiplayer.fullData.FullDataPayloadSender;
@@ -9,13 +9,16 @@ import com.seibel.distanthorizons.core.multiplayer.fullData.SharedBandwidthLimit
import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.IncompatibleMessageInternalEvent;
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage; import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage; import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.messages.base.RequestLevelInitMessage;
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage; import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent; import com.seibel.distanthorizons.core.network.event.internal.CloseInternalEvent;
import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException; import com.seibel.distanthorizons.core.network.exceptions.RateLimitedException;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
import com.seibel.distanthorizons.core.network.session.NetworkSession; import com.seibel.distanthorizons.core.network.session.NetworkSession;
import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateAndConcurrencyLimiter; import com.seibel.distanthorizons.core.util.ratelimiting.SupplierBasedRateAndConcurrencyLimiter;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.Closeable; import java.io.Closeable;
@@ -23,16 +26,14 @@ import java.util.concurrent.ConcurrentHashMap;
public class ServerPlayerState implements Closeable public class ServerPlayerState implements Closeable
{ {
private final ConfigChangeListener<String> levelKeyPrefixChangeListener private final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
= new ConfigChangeListener<>(Config.Server.levelKeyPrefix, this::onLevelKeyPrefixConfigChanged);
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
private final SessionConfig.AnyChangeListener configAnyChangeListener = new SessionConfig.AnyChangeListener(this::sendConfigMessage);
private final String serverKeyWithoutId = Config.Server.serverKey.get(); private final String serverKeyWithoutId = Config.Server.serverKey.get();
private final String serverKey = (this.serverKeyWithoutId.isEmpty() ? "" : Config.Server.serverId.get() + "_" + this.serverKeyWithoutId.trim()) private final String serverKey = (this.serverKeyWithoutId.isEmpty() ? "" : Config.Server.serverId.get() + "_" + this.serverKeyWithoutId.trim())
.replaceAll("[^" + LevelInitMessage.ALLOWED_CHARS_REGEX + " ]", "") .replaceAll("[^" + LevelInitMessage.ALLOWED_CHARS_REGEX + " ]", "")
.replaceAll(" ", "_"); .replaceAll(" ", "_");
private String lastLevelKey = "";
public final NetworkSession networkSession; public final NetworkSession networkSession;
@@ -61,11 +62,27 @@ public class ServerPlayerState implements Closeable
this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) -> this.networkSession.registerHandler(SessionConfigMessage.class, (sessionConfigMessage) ->
{ {
this.sessionConfig.constrainingConfig = sessionConfigMessage.config; this.sessionConfig.constrainingConfig = sessionConfigMessage.config;
this.sendLevelKey();
this.sendConfigMessage(); this.sendConfigMessage();
}); });
this.networkSession.registerHandler(RequestLevelInitMessage.class, msg ->
{
if (!Config.Server.sendLevelKeys.get())
{
return;
}
IServerLevelWrapper serverLevelWrapper = MC_SHARED.getLevelWrapper(msg.dimensionResourceLocation);
if (serverLevelWrapper == null)
{
return;
}
String levelKey = serverLevelWrapper.getKeyedLevelDimensionName();
this.networkSession.sendMessage(new LevelInitMessage(msg.dimensionResourceLocation, this.serverKey, levelKey));
});
this.networkSession.registerHandler(CloseInternalEvent.class, event -> { this.networkSession.registerHandler(CloseInternalEvent.class, event -> {
// No-op. prevents "Unhandled message" log entries // No-op. prevents "Unhandled message" log entries
}); });
@@ -84,21 +101,6 @@ public class ServerPlayerState implements Closeable
// client updating // // client updating //
//=================// //=================//
private void onLevelKeyPrefixConfigChanged(String newLevelKey) { this.sendLevelKey(); }
private void sendLevelKey()
{
if (Config.Server.sendLevelKeys.get())
{
// let the client's know about the change
String levelKey = this.getServerPlayer().getLevel().getKeyedLevelDimensionName();
if (!levelKey.equals(this.lastLevelKey))
{
this.lastLevelKey = levelKey;
this.networkSession.sendMessage(new LevelInitMessage(this.serverKey, levelKey));
}
}
}
private void sendConfigMessage() private void sendConfigMessage()
{ {
double coordinateScale = this.getServerPlayer().getLevel().getDimensionType().getCoordinateScale(); double coordinateScale = this.getServerPlayer().getLevel().getDimensionType().getCoordinateScale();
@@ -118,7 +120,6 @@ public class ServerPlayerState implements Closeable
public void close() public void close()
{ {
this.fullDataPayloadSender.close(); this.fullDataPayloadSender.close();
this.levelKeyPrefixChangeListener.close();
this.configAnyChangeListener.close(); this.configAnyChangeListener.close();
this.networkSession.close(); this.networkSession.close();
} }
@@ -21,12 +21,9 @@ package com.seibel.distanthorizons.core.network.messages;
import com.google.common.collect.BiMap; import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap; import com.google.common.collect.HashBiMap;
import com.seibel.distanthorizons.core.network.messages.base.CodecCrashMessage; import com.seibel.distanthorizons.core.network.messages.base.*;
import com.seibel.distanthorizons.core.network.messages.base.LevelInitMessage;
import com.seibel.distanthorizons.core.network.messages.base.SessionConfigMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSplitMessage;
import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage; import com.seibel.distanthorizons.core.network.messages.requests.CancelMessage;
import com.seibel.distanthorizons.core.network.messages.base.CloseReasonMessage;
import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage; import com.seibel.distanthorizons.core.network.messages.requests.ExceptionMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage; import com.seibel.distanthorizons.core.network.messages.fullData.FullDataSourceRequestMessage;
@@ -60,6 +57,7 @@ public class MessageRegistry
// Level keys // Level keys
this.registerMessage(LevelInitMessage.class, LevelInitMessage::new); this.registerMessage(LevelInitMessage.class, LevelInitMessage::new);
this.registerMessage(RequestLevelInitMessage.class, RequestLevelInitMessage::new);
// Config (for full DH support) // Config (for full DH support)
this.registerMessage(SessionConfigMessage.class, SessionConfigMessage::new); this.registerMessage(SessionConfigMessage.class, SessionConfigMessage::new);
@@ -21,6 +21,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
MAX_LENGTH, ALLOWED_CHARS_REGEX, ALLOWED_CHARS_REGEX, ALLOWED_CHARS_REGEX); MAX_LENGTH, ALLOWED_CHARS_REGEX, ALLOWED_CHARS_REGEX, ALLOWED_CHARS_REGEX);
public String dimensionResourceLocation;
public String serverKey; public String serverKey;
public String levelKey; public String levelKey;
public long serverTime; public long serverTime;
@@ -32,8 +33,9 @@ public class LevelInitMessage extends AbstractNetworkMessage
//==============// //==============//
public LevelInitMessage() { } public LevelInitMessage() { }
public LevelInitMessage(String serverKey, String levelKey) public LevelInitMessage(String dimensionResourceLocation, String serverKey, String levelKey)
{ {
this.dimensionResourceLocation = dimensionResourceLocation;
this.serverKey = serverKey; this.serverKey = serverKey;
this.levelKey = levelKey; this.levelKey = levelKey;
this.serverTime = System.currentTimeMillis(); this.serverTime = System.currentTimeMillis();
@@ -48,6 +50,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
@Override @Override
public void encode(ByteBuf out) public void encode(ByteBuf out)
{ {
this.writeString(this.dimensionResourceLocation, out);
this.writeString(this.serverKey, out); this.writeString(this.serverKey, out);
this.writeString(this.levelKey, out); this.writeString(this.levelKey, out);
out.writeLong(this.serverTime); out.writeLong(this.serverTime);
@@ -56,6 +59,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
@Override @Override
public void decode(ByteBuf in) public void decode(ByteBuf in)
{ {
this.dimensionResourceLocation = this.readString(in);
this.serverKey = this.readString(in); this.serverKey = this.readString(in);
this.levelKey = this.readString(in); this.levelKey = this.readString(in);
this.serverTime = in.readLong(); this.serverTime = in.readLong();
@@ -71,6 +75,7 @@ public class LevelInitMessage extends AbstractNetworkMessage
public MoreObjects.ToStringHelper toStringHelper() public MoreObjects.ToStringHelper toStringHelper()
{ {
return super.toStringHelper() return super.toStringHelper()
.add("dimensionResourceLocation", this.dimensionResourceLocation)
.add("serverKey", this.serverKey) .add("serverKey", this.serverKey)
.add("levelKey", this.levelKey) .add("levelKey", this.levelKey)
.add("serverTime", this.serverTime); .add("serverTime", this.serverTime);
@@ -0,0 +1,65 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.network.messages.base;
import com.google.common.base.MoreObjects;
import com.seibel.distanthorizons.core.network.messages.AbstractNetworkMessage;
import io.netty.buffer.ByteBuf;
/** used for full DH support */
public class RequestLevelInitMessage extends AbstractNetworkMessage
{
public String dimensionResourceLocation;
//=============//
// constructor //
//=============//
public RequestLevelInitMessage() { }
public RequestLevelInitMessage(String dimensionResourceLocation) { this.dimensionResourceLocation = dimensionResourceLocation; }
//===============//
// serialization //
//===============//
@Override
public void encode(ByteBuf out) { this.writeString(this.dimensionResourceLocation, out); }
@Override
public void decode(ByteBuf in) { this.dimensionResourceLocation = this.readString(in); }
//================//
// base overrides //
//================//
@Override
public MoreObjects.ToStringHelper toStringHelper()
{
return super.toStringHelper()
.add("dimensionResourceLocation", this.dimensionResourceLocation);
}
}
@@ -22,7 +22,7 @@ package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos2D;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
/** /**
* immutable <br><br> * immutable <br><br>
@@ -65,7 +65,7 @@ public class DhChunkPos
// >> 4 is the Same as div 16 // >> 4 is the Same as div 16
this(blockPos.x >> 4, blockPos.z >> 4); this(blockPos.x >> 4, blockPos.z >> 4);
} }
public DhChunkPos(Vec3d pos) public DhChunkPos(DhVec3d pos)
{ {
// >> 4 is the Same as div 16 // >> 4 is the Same as div 16
this(((int)pos.x) >> 4, ((int)pos.z) >> 4); this(((int)pos.x) >> 4, ((int)pos.z) >> 4);
@@ -19,6 +19,8 @@
package com.seibel.distanthorizons.core.render; package com.seibel.distanthorizons.core.render;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy; import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.api.objects.DhApiResult; import com.seibel.distanthorizons.api.objects.DhApiResult;
import com.seibel.distanthorizons.core.api.internal.SharedApi; import com.seibel.distanthorizons.core.api.internal.SharedApi;
@@ -28,6 +30,8 @@ import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.util.RenderUtil; import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.world.AbstractDhWorld; import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition;
import org.jetbrains.annotations.Nullable;
/** /**
* Used to interact with Distant Horizons' rendering systems. * Used to interact with Distant Horizons' rendering systems.
@@ -43,6 +47,18 @@ public class DhApiRenderProxy implements IDhApiRenderProxy
private boolean deferTransparentRendering = false; private boolean deferTransparentRendering = false;
private static AbstractDhRenderApiDefinition renderApiDef = null;
@Nullable
private static AbstractDhRenderApiDefinition tryGetApiDef()
{
if (renderApiDef == null)
{
renderApiDef = SingletonInjector.INSTANCE.get(AbstractDhRenderApiDefinition.class);
}
return renderApiDef;
}
//=============// //=============//
@@ -57,6 +73,7 @@ public class DhApiRenderProxy implements IDhApiRenderProxy
// methods // // methods //
//=========// //=========//
@Override
public DhApiResult<Boolean> clearRenderDataCache() public DhApiResult<Boolean> clearRenderDataCache()
{ {
// make sure this is a valid time to run the method // make sure this is a valid time to run the method
@@ -80,6 +97,32 @@ public class DhApiRenderProxy implements IDhApiRenderProxy
return DhApiResult.createSuccess(); return DhApiResult.createSuccess();
} }
@Override
public EDhApiRenderingApi getRenderingApi() throws IllegalStateException
{
AbstractDhRenderApiDefinition apiDef = tryGetApiDef();
if (apiDef == null)
{
// The rendering API hasn't been set up yet
throw new IllegalStateException("Distant Horizons hasn't finished setup yet. No renderer has been set.");
}
return apiDef.getRenderApi();
}
@Override
public boolean isNativeRenderer() throws IllegalStateException
{
AbstractDhRenderApiDefinition apiDef = tryGetApiDef();
if (apiDef == null)
{
// The rendering API hasn't been set up yet
throw new IllegalStateException("Distant Horizons hasn't finished setup yet. No renderer has been set.");
}
return apiDef.isNativeRenderer();
}
public static int activeOpenGlDhDepthTextureId = -1; public static int activeOpenGlDhDepthTextureId = -1;
@Override @Override
@@ -0,0 +1,32 @@
package com.seibel.distanthorizons.core.render;
/**
* FORWARD_Z, <br>
* REVERSE_Z, <br>
*/
public enum EDhRenderDepth
{
/**
* AKA Zero to One <br>
* MC 26.1.2 and older (OpenGL) = false (near = 0.0f, far = 1.0f)
*/
FORWARD_Z(0.0f, 1.0f),
/**
* AKA One to Zero <br>
* MC 26.2.0 and newer (Vulkan/GL) = true (near = 1.0f, far = 0.0f)
*/
REVERSE_Z(1.0f, 0.0f);
public final float nearDepth;
public final float farDepth;
EDhRenderDepth(float nearDepth, float farDepth)
{
this.nearDepth = nearDepth;
this.farDepth = farDepth;
}
}
@@ -73,7 +73,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
private static final AbstractDebugWireframeRenderer DEBUG_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class); private static final AbstractDebugWireframeRenderer DEBUG_RENDERER = SingletonInjector.INSTANCE.get(AbstractDebugWireframeRenderer.class);
/** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */ /** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */
private static final ThreadPoolExecutor FULL_DATA_RETRIEVAL_QUEUE_THREAD = ThreadUtil.makeSingleThreadPool("LodQuadTree Data Retrieval Queue"); private static final ThreadPoolExecutor FULL_DATA_RETRIEVAL_QUEUE_THREAD = ThreadUtil.makeSingleDaemonThreadPool("LodQuadTree Data Retrieval Queue");
public final int blockRenderDistanceDiameter; public final int blockRenderDistanceDiameter;
@@ -185,7 +185,13 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
this.enabledRenderSectionLock.lock(); this.enabledRenderSectionLock.lock();
tempProcessNodeList.clear(); tempProcessNodeList.clear();
tempProcessNodeList.addAll(this.enabledSections);
// manual add and loop to reduce GC pressure due to addAll() doing unnecessary
// array copies
for (int i = 0; i < this.enabledSections.size(); i++)
{
tempProcessNodeList.add(this.enabledSections.get(i));
}
} }
finally finally
{ {
@@ -1202,17 +1208,6 @@ 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);
@@ -1264,7 +1259,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
@Override @Override
public void close() public void close()
{ {
LOGGER.info("Shutting down LodQuadTree..."); //LOGGER.info("Shutting down LodQuadTree...");
DEBUG_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus); DEBUG_RENDERER.unregister(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus);
Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this); Config.Common.WorldGenerator.enableDistantGeneration.removeListener(this);
@@ -1299,7 +1294,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}); });
LOGGER.info("Finished shutting down LodQuadTree"); //LOGGER.info("Finished shutting down LodQuadTree");
} }
//endregion base methods //endregion base methods
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.render.QuadTree; package com.seibel.distanthorizons.core.render.QuadTree;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
@@ -36,6 +37,7 @@ 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.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.util.ExceptionUtil;
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;
@@ -45,6 +47,8 @@ import org.jetbrains.annotations.Nullable;
import javax.annotation.WillNotClose; import javax.annotation.WillNotClose;
import java.awt.*; import java.awt.*;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@@ -62,7 +66,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public final long pos; public final long pos;
private final IDhClientLevel level; private final IDhClientLevel clientLevel;
private final IClientLevelWrapper levelWrapper; private final IClientLevelWrapper levelWrapper;
@WillNotClose @WillNotClose
private final FullDataSourceProviderV2 fullDataSourceProvider; private final FullDataSourceProviderV2 fullDataSourceProvider;
@@ -97,13 +101,6 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
*/ */
private Runnable getAndBuildRenderDataRunnable = null; private Runnable getAndBuildRenderDataRunnable = null;
/**
* Represents just uploading the {@link LodQuadBuilder} to the GPU. <br>
* Separate from {@link LodRenderSection#getAndBuildRenderDataFutureRef} because they run on
* different threads (buffer uploading is on the MC render thread) and need to be canceled separately.
*/
private final AtomicReference<CompletableFuture<LodBufferContainer>> bufferUploadFutureRef = new AtomicReference<>(null);
//=============// //=============//
@@ -114,12 +111,12 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
public LodRenderSection( public LodRenderSection(
long pos, long pos,
LodQuadTree quadTree, LodQuadTree quadTree,
IDhClientLevel level, FullDataSourceProviderV2 fullDataSourceProvider) IDhClientLevel clientLevel, FullDataSourceProviderV2 fullDataSourceProvider)
{ {
this.pos = pos; this.pos = pos;
this.quadTree = quadTree; this.quadTree = quadTree;
this.level = level; this.clientLevel = clientLevel;
this.levelWrapper = level.getClientLevelWrapper(); this.levelWrapper = clientLevel.getClientLevelWrapper();
this.fullDataSourceProvider = fullDataSourceProvider; this.fullDataSourceProvider = fullDataSourceProvider;
DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus); DEBUG_RENDERER.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus);
@@ -161,6 +158,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
try try
{ {
// shouldn't happen since this method is synchronized, but just in case
// make sure we only ever start one upload task
if (!this.getAndBuildRenderDataFutureRef.compareAndSet(null, future)) if (!this.getAndBuildRenderDataFutureRef.compareAndSet(null, future))
{ {
CompletableFuture<Void> oldFuture = this.getAndBuildRenderDataFutureRef.get(); CompletableFuture<Void> oldFuture = this.getAndBuildRenderDataFutureRef.get();
@@ -173,24 +172,32 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
{ {
try try
{ {
LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData(); // build LOD data on a DH thread
if (lodQuadBuilder == null) try (LodQuadBuilder lodQuadBuilder = this.getAndBuildRenderData())
{ {
future.complete(null); if (lodQuadBuilder == null)
return;
}
this.uploadToGpuAsync(lodQuadBuilder)
.thenRun(() ->
{ {
// the future is passed in separately (IE not using the local var) to prevent any possible race condition null pointers
future.complete(null); future.complete(null);
}); return;
}
// create CPU vertex buffers
ArrayList<ByteBuffer> opaqueBuffers = lodQuadBuilder.makeOpaqueVertexBuffers();
ArrayList<ByteBuffer> transparentBuffers = lodQuadBuilder.makeTransparentVertexBuffers();
// uploading will primarily happen on the render thread
this.uploadToGpuAsync(future, opaqueBuffers, transparentBuffers)
.thenRun(() ->
{
// the future is passed in separately (IE not using the local var) to prevent any possible race condition null pointers
future.complete(null);
});
}
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.error("Unexpected issue creating render data for pos: ["+DhSectionPos.toString(this.pos)+"], error: ["+e.getMessage()+"].", e); LOGGER.error("Unexpected issue creating render data for pos: ["+DhSectionPos.toString(this.pos)+"], error: ["+e.getMessage()+"].", e);
future.complete(null); future.completeExceptionally(e);
} }
}; };
executor.execute(this.getAndBuildRenderDataRunnable); executor.execute(this.getAndBuildRenderDataRunnable);
@@ -205,6 +212,14 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return false; return false;
} }
} }
//=======================//
// Get LOD ID data //
// and build render data //
//=======================//
//region
@Nullable @Nullable
private synchronized LodQuadBuilder getAndBuildRenderData() private synchronized LodQuadBuilder getAndBuildRenderData()
{ {
@@ -217,8 +232,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
} }
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get() == EDhApiTransparency.COMPLETE;
LodQuadBuilder lodQuadBuilder = new LodQuadBuilder(enableTransparency, this.level.getClientLevelWrapper()); LodQuadBuilder lodQuadBuilder = LodQuadBuilder.getBuilder(enableTransparency, this.clientLevel.getClientLevelWrapper());
// get the adjacent positions // get the adjacent positions
@@ -241,7 +256,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
// the render sources are only needed by this synchronous method, // the render sources are only needed by this synchronous method,
// then they can be closed // then they can be closed
ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, thisRenderSource, this.level, adjacentRenderSections, adjIsSameDetailLevel); ColumnRenderBufferBuilder.makeLodRenderData(lodQuadBuilder, thisRenderSource, this.clientLevel, adjacentRenderSections, adjIsSameDetailLevel);
return lodQuadBuilder; return lodQuadBuilder;
} }
catch (Exception e) catch (Exception e)
@@ -291,53 +306,64 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; detailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL;
return detailLevel == DhSectionPos.getDetailLevel(this.pos); return detailLevel == DhSectionPos.getDetailLevel(this.pos);
} }
private synchronized CompletableFuture<LodBufferContainer> uploadToGpuAsync(LodQuadBuilder lodQuadBuilder)
//endregion
private synchronized CompletableFuture<LodBufferContainer> uploadToGpuAsync(
CompletableFuture<Void> parentFuture,
ArrayList<ByteBuffer> opaqueBuffers,
ArrayList<ByteBuffer> transparentBuffers)
{ {
CompletableFuture<LodBufferContainer> oldFuture = this.bufferUploadFutureRef.getAndSet(null); CompletableFuture<LodBufferContainer> uploadFuture = LodBufferContainer.tryMakeAndUploadBuffersAsync(this.pos, this.clientLevel, opaqueBuffers, transparentBuffers);
if (oldFuture != null) uploadFuture.whenComplete((bufferContainer, e) ->
{ {
// canceling the previous future try
// prevents the CPU from working on something that won't be used
oldFuture.cancel(true);
}
CompletableFuture<LodBufferContainer> future = ColumnRenderBufferBuilder.uploadBuffersAsync(this.level, this.pos, lodQuadBuilder);
future.handle((lodBufferContainer, throwable) ->
{
if (!this.bufferUploadFutureRef.compareAndSet(future, null)
// if the old future is canceled then the future ref will be different and that's expected
&& !future.isCancelled()
// if the old future is already done, then we don't care about the ref being swapped
&& !future.isDone())
{ {
LOGGER.warn("Buffer upload future ref changed for pos: ["+DhSectionPos.toString(this.pos)+"]."); // handle errors and early shutdown
if (e != null)
{
if (!ExceptionUtil.isShutdownException(e))
{
LOGGER.error("Unexpected issue uploading buffers for pos: [" + DhSectionPos.toString(this.pos) + "], error: [" + e.getMessage() + "].", e);
}
if (bufferContainer != null)
{
// shouldn't happen, but just in case
bufferContainer.close();
}
return;
}
// close the old container
LodBufferContainer oldContainer = this.renderBufferContainer;
this.renderBufferContainer = bufferContainer.buffersUploaded ? bufferContainer : null;
if (oldContainer != null)
{
oldContainer.close();
}
// upload complete
this.renderDataDirty = false;
if (parentFuture.isCancelled())
{
// if the parent future was canceled that likely means
// this LodRenderSection was closed before this point,
// meaning this buffer will become homeless,
// so we need to clean it up here
bufferContainer.close();
}
} }
catch (Exception finishEx)
return null;
});
future.thenAccept((LodBufferContainer buffer) ->
{
// needed to clean up the old data
LodBufferContainer previousContainer = this.renderBufferContainer;
// upload complete
this.renderBufferContainer = buffer.buffersUploaded ? buffer : null;
this.renderDataDirty = false;
if (previousContainer != null)
{ {
previousContainer.close(); LOGGER.error("Unexpected buffer finish exception: ["+finishEx.getMessage()+"]", finishEx);
} }
}); });
return uploadFuture;
if (!this.bufferUploadFutureRef.compareAndSet(null, future))
{
LodUtil.assertNotReach("Buffer upload future ref couldn't be set due to concurrency error, pos: ["+DhSectionPos.toString(this.pos)+"].");
}
return future;
} }
//endregion render data uploading //endregion render data uploading
@@ -391,8 +417,8 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
return; return;
} }
int levelMinY = this.level.getLevelWrapper().getMinHeight(); int levelMinY = this.clientLevel.getLevelWrapper().getMinHeight();
int levelMaxY = this.level.getLevelWrapper().getMaxHeight(); int levelMaxY = this.clientLevel.getLevelWrapper().getMaxHeight();
// show the wireframe a bit lower than world max height, // show the wireframe a bit lower than world max height,
// since most worlds don't render all the way up to the max height // since most worlds don't render all the way up to the max height
@@ -429,13 +455,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
} }
this.setRenderingEnabled(false); // render loading is no longer needed
if (this.renderBufferContainer != null)
{
this.renderBufferContainer.close();
}
// removes any in-progress futures since they aren't needed any more
CompletableFuture<Void> buildFuture = this.getAndBuildRenderDataFutureRef.get(); CompletableFuture<Void> buildFuture = this.getAndBuildRenderDataFutureRef.get();
if (buildFuture != null) if (buildFuture != null)
{ {
@@ -451,12 +471,17 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
renderLoaderExecutor.remove(runnable); renderLoaderExecutor.remove(runnable);
} }
} }
// cancel the future after removing the runnable
// to make sure the runnable is properly removed
buildFuture.cancel(true);
} }
CompletableFuture<LodBufferContainer> uploadFuture = this.bufferUploadFutureRef.get();
if (uploadFuture != null) this.setRenderingEnabled(false);
if (this.renderBufferContainer != null)
{ {
uploadFuture.cancel(true); this.renderBufferContainer.close();
} }
} }
@@ -36,14 +36,15 @@ import com.seibel.distanthorizons.core.render.QuadTree.LodRenderSection;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.cullingFrustum.DhFrustumBounds; import com.seibel.distanthorizons.core.render.renderer.cullingFrustum.DhFrustumBounds;
import com.seibel.distanthorizons.core.render.renderer.cullingFrustum.NeverCullFrustum; import com.seibel.distanthorizons.core.render.renderer.cullingFrustum.NeverCullFrustum;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.objects.SortedArraySet; import com.seibel.distanthorizons.core.util.objects.SortedArraySet;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.DhMat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import java.util.ArrayList; import java.util.ArrayList;
@@ -58,6 +59,13 @@ public class RenderBufferHandler implements AutoCloseable
private static final IIrisAccessor IRIS_ACCESSOR = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class); private static final IIrisAccessor IRIS_ACCESSOR = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class);
// static values for re-use to reduce GC pressure
private static final float[] JOML_TRANSPOSE_ARRAY = new float[16];
private static final Matrix4f WORLD_VIEW_JOML_MATRIX = new Matrix4f();
private static final Matrix4f WORLD_VIEW_PROJ_JOML_MATRIX = new Matrix4f();
private static final DhMat4f FRUSTOM_DH_MATRIX = new DhMat4f();
/** contains all relevant data */ /** contains all relevant data */
public final LodQuadTree lodQuadTree; public final LodQuadTree lodQuadTree;
@@ -123,6 +131,14 @@ public class RenderBufferHandler implements AutoCloseable
*/ */
public void buildRenderList(RenderParams renderParams) public void buildRenderList(RenderParams renderParams)
{ {
if (ModInfo.IS_DEV_BUILD)
{
if (!RenderThreadTaskHandler.INSTANCE.isCurrentThread())
{
LodUtil.assertNotReach("Should only be run on the render thread");
}
}
// clear the old list so we can start fresh // clear the old list so we can start fresh
this.loadedNearToFarBuffers.clear(); this.loadedNearToFarBuffers.clear();
@@ -154,20 +170,21 @@ public class RenderBufferHandler implements AutoCloseable
int worldMinY = renderParams.clientLevelWrapper.getMinHeight(); int worldMinY = renderParams.clientLevelWrapper.getMinHeight();
int worldHeight = renderParams.clientLevelWrapper.getMaxHeight(); int worldHeight = renderParams.clientLevelWrapper.getMaxHeight();
Vec3d cameraPos = MC_RENDER.getCameraExactPosition(); renderParams.mcModelViewMatrix.putValuesInArray(JOML_TRANSPOSE_ARRAY);
WORLD_VIEW_JOML_MATRIX
.setTransposed(JOML_TRANSPOSE_ARRAY)
.translate(
-(float) renderParams.exactCameraPosition.x,
-(float) renderParams.exactCameraPosition.y,
-(float) renderParams.exactCameraPosition.z);
Matrix4fc matWorldView = new Matrix4f() renderParams.dhProjectionMatrix.putValuesInArray(JOML_TRANSPOSE_ARRAY);
.setTransposed(renderParams.mcModelViewMatrix.getValuesAsArray()) WORLD_VIEW_PROJ_JOML_MATRIX
.translate( .setTransposed(JOML_TRANSPOSE_ARRAY)
-(float) cameraPos.x, .mul(WORLD_VIEW_JOML_MATRIX);
-(float) cameraPos.y,
-(float) cameraPos.z); FRUSTOM_DH_MATRIX.set(WORLD_VIEW_PROJ_JOML_MATRIX);
frustum.update(worldMinY, worldMinY + worldHeight, FRUSTOM_DH_MATRIX);
Matrix4fc matWorldViewProjection = new Matrix4f()
.setTransposed(renderParams.dhProjectionMatrix.getValuesAsArray())
.mul(matWorldView);
frustum.update(worldMinY, worldMinY + worldHeight, new Mat4f(matWorldViewProjection));
} }
@@ -175,6 +192,7 @@ public class RenderBufferHandler implements AutoCloseable
//=========================// //=========================//
// Update the section list // // Update the section list //
//=========================// //=========================//
//region
if (isShadowPass) if (isShadowPass)
{ {
@@ -251,6 +269,9 @@ public class RenderBufferHandler implements AutoCloseable
{ {
this.visibleBufferCount = this.loadedNearToFarBuffers.size(); this.visibleBufferCount = this.loadedNearToFarBuffers.size();
} }
//endregion
} }
//endregion //endregion
@@ -6,18 +6,14 @@ import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState; import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector; import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.jar.EPlatform;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.util.RenderUtil; import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.DhMat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.util.threading.PriorityTaskPicker;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.IDhClientWorld; import com.seibel.distanthorizons.core.world.IDhClientWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.AbstractOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IOptifineAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -34,8 +30,12 @@ public class RenderParams extends DhApiRenderParam
private static final IOptifineAccessor OPTIFINE_ACCESSOR = ModAccessorInjector.INSTANCE.get(IOptifineAccessor.class); private static final IOptifineAccessor OPTIFINE_ACCESSOR = ModAccessorInjector.INSTANCE.get(IOptifineAccessor.class);
private static final long TIME_FOR_MAC_TO_FINISH_COMPILING_IN_MS = 10_000; /**
private static boolean initialLoadingComplete = false; * Copy used for API events. <br>
* A separate copy is used to prevent API users from accidentally setting values
* that screw up DH's copy of the render parameters.
*/
public final DhApiRenderParam apiCopy = new DhApiRenderParam();
public IDhClientWorld dhClientWorld; public IDhClientWorld dhClientWorld;
@@ -45,7 +45,7 @@ public class RenderParams extends DhApiRenderParam
public ILightMapWrapper lightmap; public ILightMapWrapper lightmap;
public RenderBufferHandler renderBufferHandler; public RenderBufferHandler renderBufferHandler;
public IDhGenericRenderer genericRenderer; public IDhGenericRenderer genericRenderer;
public Vec3d exactCameraPosition; public DhVec3d exactCameraPosition;
/** @see DhRenderState#vanillaFogEnabled */ /** @see DhRenderState#vanillaFogEnabled */
public boolean vanillaFogEnabled; public boolean vanillaFogEnabled;
@@ -58,36 +58,27 @@ public class RenderParams extends DhApiRenderParam
//=============// //=============//
//region //region
public RenderParams(EDhApiRenderPass renderPass, DhRenderState renderState) public void update(EDhApiRenderPass renderPass, DhRenderState renderState)
{ {
this(renderPass, RenderUtil.setDhProjectionMatrix(this.dhProjectionMatrix, renderState.mcProjectionMatrix);
renderState.partialTickTime, this.dhModelViewMatrix.set(renderState.mcModelViewMatrix); // DH and MC MVM matrix are the same
renderState.mcProjectionMatrix, renderState.mcModelViewMatrix,
renderState.clientLevelWrapper,
renderState.vanillaFogEnabled
);
}
private RenderParams(
EDhApiRenderPass renderPass,
float newPartialTicks,
Mat4f newMcProjectionMatrix, Mat4f newMcModelViewMatrix,
IClientLevelWrapper clientLevelWrapper,
boolean vanillaFogEnabled
)
{
super(renderPass,
newPartialTicks,
RenderUtil.getNearClipPlaneInBlocks(), RenderUtil.getFarClipPlaneDistanceInBlocks(),
newMcProjectionMatrix, newMcModelViewMatrix,
RenderUtil.createLodProjectionMatrix(newMcProjectionMatrix), RenderUtil.createLodModelViewMatrix(newMcModelViewMatrix),
clientLevelWrapper.getMinHeight(),
clientLevelWrapper);
super.update(renderPass,
renderState.partialTickTime,
RenderUtil.getNearClipPlaneInBlocks(), RenderUtil.getFarClipPlaneDistanceInBlocks(),
renderState.mcProjectionMatrix, renderState.mcModelViewMatrix,
this.dhProjectionMatrix, this.dhModelViewMatrix,
renderState.clientLevelWrapper.getMinHeight(),
renderState.clientLevelWrapper);
this.clientLevelWrapper = renderState.clientLevelWrapper;
this.dhClientWorld = SharedApi.tryGetDhClientWorld(); this.dhClientWorld = SharedApi.tryGetDhClientWorld();
if (this.dhClientWorld != null) if (this.dhClientWorld != null)
{ {
this.dhClientLevel = (IDhClientLevel) this.dhClientWorld.getLevel(clientLevelWrapper); this.dhClientLevel = this.dhClientWorld.getOrLoadClientLevel(clientLevelWrapper);
if (this.dhClientLevel != null) if (this.dhClientLevel != null)
{ {
this.renderBufferHandler = this.dhClientLevel.getRenderBufferHandler(); this.renderBufferHandler = this.dhClientLevel.getRenderBufferHandler();
@@ -95,7 +86,6 @@ public class RenderParams extends DhApiRenderParam
} }
} }
this.clientLevelWrapper = clientLevelWrapper;
this.lightmap = MC_RENDER.getLightmapWrapper(this.clientLevelWrapper); this.lightmap = MC_RENDER.getLightmapWrapper(this.clientLevelWrapper);
if (MC_CLIENT.playerExists()) if (MC_CLIENT.playerExists())
@@ -103,8 +93,9 @@ public class RenderParams extends DhApiRenderParam
this.exactCameraPosition = MC_RENDER.getCameraExactPosition(); this.exactCameraPosition = MC_RENDER.getCameraExactPosition();
} }
this.vanillaFogEnabled = vanillaFogEnabled; this.vanillaFogEnabled = renderState.vanillaFogEnabled;
this.apiCopy.update(this);
} }
//endregion //endregion
@@ -120,7 +111,7 @@ public class RenderParams extends DhApiRenderParam
* Should be called before rendering is done. * Should be called before rendering is done.
* @return a message if LODs shouldn't be rendered, null if the LODs can render * @return a message if LODs shouldn't be rendered, null if the LODs can render
*/ */
public String getValidationErrorMessage(long firstRenderTimeMs) public String getValidationErrorMessage()
{ {
// Note: all strings here should be constants to prevent String allocations // Note: all strings here should be constants to prevent String allocations
@@ -162,12 +153,21 @@ public class RenderParams extends DhApiRenderParam
return "No Generic Renderer Present"; return "No Generic Renderer Present";
} }
if (this.dhModelViewMatrix == null if (this.dhModelViewMatrix.equals(DhMat4f.IDENTITY)
|| this.mcModelViewMatrix == null) || this.dhModelViewMatrix.equals(DhMat4f.EMPTY))
{ {
return "No MVM or Proj Matrix Given"; return "No DH MVM Matrix Given";
} }
if (this.mcModelViewMatrix.equals(DhMat4f.IDENTITY)
|| this.mcModelViewMatrix.equals(DhMat4f.EMPTY))
{
return "No MC MVM Matrix Given";
}
// projection matrix not checked since there are some MC versions where
// the MVM and projection matrices are pre-multiplied together
if (OPTIFINE_ACCESSOR != null if (OPTIFINE_ACCESSOR != null
&& MC_RENDER.getTargetFramebuffer() == -1) && MC_RENDER.getTargetFramebuffer() == -1)
{ {
@@ -176,45 +176,6 @@ public class RenderParams extends DhApiRenderParam
} }
//// potential fix for a segfault when
//// Sodium and DH are running together
//if (EPlatform.get() == EPlatform.MACOS
// && !initialLoadingComplete)
//{
// // Once MC starts rendering, wait a few seconds so
// // MC/Sodium can finish their shader compiling before DH does its own.
// // This will allow DH to compile its own shaders after Sodium finishes
// // compiling its own.
// long nowMs = System.currentTimeMillis();
// long firstAllowedRenderTimeMs = firstRenderTimeMs + TIME_FOR_MAC_TO_FINISH_COMPILING_IN_MS;
// if (nowMs < firstAllowedRenderTimeMs)
// {
// return "Waiting for initial MC compile...";
// }
//
//
// // null shouldn't happen, but just in case
// PriorityTaskPicker.Executor renderLoadExecutor = ThreadPoolUtil.getRenderLoadingExecutor();
// if (renderLoadExecutor == null)
// {
// return "Waiting for DH Threadpool...";
// }
//
// // wait for DH to finish loading, by the time that's done
// // java should have finished all of DH's JIT compiling,
// // which will hopefully mean less concurrency and thus a lower
// // chance of breaking
// // (plus this gives Sodium/vanill a bit longer to finish their setup)
// int taskCount = renderLoadExecutor.getQueueSize();
// if (taskCount > 0)
// {
// return "Waiting for DH JIT compiling...";
// }
//
// initialLoadingComplete = true;
//}
return null; return null;
} }
@@ -10,6 +10,7 @@ import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.RollingAverage; import com.seibel.distanthorizons.core.util.objects.RollingAverage;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.coreapi.ModInfo;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -49,6 +50,14 @@ public class RenderThreadTaskHandler
private long nanoSinceTasksRun = System.nanoTime(); private long nanoSinceTasksRun = System.nanoTime();
private final boolean running;
private Thread renderThread;
/**
* the currently running {@link QueuedRunnable}
* will be null if nothing is running.
*/
private volatile @Nullable QueuedRunnable currentQueuedRunnable;
@@ -57,7 +66,22 @@ public class RenderThreadTaskHandler
//=============// //=============//
//region //region
private RenderThreadTaskHandler() { TIMER.scheduleAtFixedRate(TimerUtil.createTimerTask(this::manualCleanupTick), MS_BETWEEN_CLEANUP_TICKS, MS_BETWEEN_CLEANUP_TICKS); } private RenderThreadTaskHandler()
{
// we only want to run this when the client is available
IMinecraftSharedWrapper mcShared = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
if (!mcShared.isDedicatedServer())
{
LOGGER.debug("Starting ["+RenderThreadTaskHandler.class.getSimpleName()+"]...");
this.running = true;
TIMER.scheduleAtFixedRate(TimerUtil.createTimerTask(this::manualCleanupTick), MS_BETWEEN_CLEANUP_TICKS, MS_BETWEEN_CLEANUP_TICKS);
}
else
{
this.running = false;
LOGGER.debug("Skipping ["+RenderThreadTaskHandler.class.getSimpleName()+"] startup due to running on a dedicated server.");
}
}
//endregion //endregion
@@ -70,6 +94,13 @@ public class RenderThreadTaskHandler
public void queueRunningOnRenderThread(String name, Runnable renderCall) public void queueRunningOnRenderThread(String name, Runnable renderCall)
{ {
// don't queuing tasks if they'll never be run
if (!this.running)
{
return;
}
// don't get the stacktrace on release to reduce GC pressure // don't get the stacktrace on release to reduce GC pressure
StackTraceElement[] stackTrace = null; StackTraceElement[] stackTrace = null;
if (ModInfo.IS_DEV_BUILD) if (ModInfo.IS_DEV_BUILD)
@@ -116,12 +147,21 @@ public class RenderThreadTaskHandler
long loopStartTimeNano = System.nanoTime(); long loopStartTimeNano = System.nanoTime();
this.nanoSinceTasksRun = loopStartTimeNano; this.nanoSinceTasksRun = loopStartTimeNano;
if (this.renderThread == null)
{
this.renderThread = Thread.currentThread();
}
QueuedRunnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll(); QueuedRunnable runnable = RENDER_THREAD_RUNNABLE_QUEUE.poll();
while(runnable != null) while(runnable != null)
{ {
long taskStartNano = System.nanoTime(); long taskStartNano = System.nanoTime();
this.currentQueuedRunnable = runnable;
runnable.run(); runnable.run();
this.currentQueuedRunnable = null;
// only try running for a limited amount of time to prevent lag spikes // only try running for a limited amount of time to prevent lag spikes
long taskNano = System.nanoTime() - taskStartNano; long taskNano = System.nanoTime() - taskStartNano;
@@ -179,8 +219,19 @@ public class RenderThreadTaskHandler
// this means we could have GL jobs building up. // this means we could have GL jobs building up.
// Run the queued tasks on MC's executor (hopefully this should always run, // Run the queued tasks on MC's executor (hopefully this should always run,
// even if DH's render code isn't being hit). // even if DH's render code isn't being hit).
IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); IMinecraftClientWrapper mcClient = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
MC.executeOnRenderThread(() -> this.runRenderThreadTasks(500 * 1_000_000L)); if (mcClient != null)
{
mcClient.executeOnRenderThread(() -> this.runRenderThreadTasks(500 * 1_000_000L));
}
else
{
// shouldn't happen, but just in case
// somehow the timer started when there wasn't a client wrapper
// this probably means the timer was started on a dedicated server
RATE_LIMITED_LOGGER.warn("["+RenderThreadTaskHandler.class.getSimpleName()+"] timer started when ["+IMinecraftClientWrapper.class.getSimpleName()+"] is null. This shouldn't happen but can likely be ignored.");
}
} }
//endregion //endregion
@@ -190,7 +241,7 @@ public class RenderThreadTaskHandler
//===========// //===========//
// debugging // // debugging //
//===========// //===========//
///region //region
/** /**
* if tasks are currently queued the debug * if tasks are currently queued the debug
@@ -246,7 +297,28 @@ public class RenderThreadTaskHandler
}); });
} }
///endregion
/** Returns true if the currently running thread is being run by this handler */
public boolean isCurrentThread()
{
if (this.renderThread != null)
{
return Thread.currentThread() == this.renderThread;
}
// shouldn't normally be needed, but can be used if this
// handler hasn't been run yet
return Thread.currentThread().getName().equals("Render thread");
}
/**
* Only recommended to be used by the task that's currently being run.
* Use {@link RenderThreadTaskHandler#isCurrentThread()} to check. <br>
* Can be used to get stack traces for render thread tasks while they're being run.
*/
public @Nullable QueuedRunnable getCurrentlyRunningTask() { return this.currentQueuedRunnable; }
//endregion
@@ -255,7 +327,7 @@ public class RenderThreadTaskHandler
//================// //================//
//region //region
private static class QueuedRunnable implements Runnable public static class QueuedRunnable implements Runnable
{ {
/** used to easily track what's being done on the render thread */ /** used to easily track what's being done on the render thread */
public final String name; public final String name;
@@ -7,9 +7,9 @@ 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.DhSectionPos; import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.RenderParams; import com.seibel.distanthorizons.core.render.RenderParams;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.DhMat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f; import com.seibel.distanthorizons.core.util.math.DhVec3f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -35,8 +35,8 @@ public abstract class AbstractDebugWireframeRenderer implements IBindable
protected final PriorityBlockingQueue<BoxParticle> particles = new PriorityBlockingQueue<>(); protected final PriorityBlockingQueue<BoxParticle> particles = new PriorityBlockingQueue<>();
// used when rendering // used when rendering
protected Mat4f dhMvmProjMatrixThisFrame; protected DhMat4f dhMvmProjMatrixThisFrame;
protected Vec3f camPosFloatThisFrame; protected DhVec3f camPosFloatThisFrame;
@@ -47,9 +47,8 @@ public abstract class AbstractDebugWireframeRenderer implements IBindable
public void render(RenderParams renderParams) public void render(RenderParams renderParams)
{ {
this.dhMvmProjMatrixThisFrame = new Mat4f(renderParams.dhMvmProjMatrix); this.dhMvmProjMatrixThisFrame = new DhMat4f(renderParams.dhMvmProjMatrix);
Vec3d camPos = MC_RENDER.getCameraExactPosition(); this.camPosFloatThisFrame = new DhVec3f(renderParams.exactCameraPosition);
this.camPosFloatThisFrame = new Vec3f((float) camPos.x, (float) camPos.y, (float) camPos.z);
this.rendererLists.render(this); this.rendererLists.render(this);
@@ -113,8 +112,8 @@ public abstract class AbstractDebugWireframeRenderer implements IBindable
public static final class Box public static final class Box
{ {
public Vec3f minPos; public DhVec3f minPos;
public Vec3f maxPos; public DhVec3f maxPos;
public Color color; public Color color;
@@ -128,13 +127,13 @@ public abstract class AbstractDebugWireframeRenderer implements IBindable
int maxBlockPosX = minBlockPosX + DhSectionPos.getBlockWidth(pos); int maxBlockPosX = minBlockPosX + DhSectionPos.getBlockWidth(pos);
int maxBlockPosZ = minBlockPosZ + DhSectionPos.getBlockWidth(pos); int maxBlockPosZ = minBlockPosZ + DhSectionPos.getBlockWidth(pos);
this.minPos = new Vec3f(minBlockPosX + edgeOffset, minY, minBlockPosZ + edgeOffset); this.minPos = new DhVec3f(minBlockPosX + edgeOffset, minY, minBlockPosZ + edgeOffset);
this.maxPos = new Vec3f(maxBlockPosX - edgeOffset, maxY, maxBlockPosZ - edgeOffset); this.maxPos = new DhVec3f(maxBlockPosX - edgeOffset, maxY, maxBlockPosZ - edgeOffset);
this.color = color; this.color = color;
} }
/** only used for */ /** only used for */
public Box(Vec3f minPos, Vec3f maxPos, Color color) public Box(DhVec3f minPos, DhVec3f maxPos, Color color)
{ {
this.minPos = minPos; this.minPos = minPos;
this.maxPos = maxPos; this.maxPos = maxPos;
@@ -177,8 +176,8 @@ public abstract class AbstractDebugWireframeRenderer implements IBindable
float yDiff = this.yChange * percent; float yDiff = this.yChange * percent;
return new Box( return new Box(
new Vec3f(this.box.minPos.x, this.box.minPos.y + yDiff, this.box.minPos.z), new DhVec3f(this.box.minPos.x, this.box.minPos.y + yDiff, this.box.minPos.z),
new Vec3f(this.box.maxPos.x, this.box.maxPos.y + yDiff, this.box.maxPos.z), new DhVec3f(this.box.maxPos.x, this.box.maxPos.y + yDiff, this.box.maxPos.z),
this.box.color); this.box.color);
} }
@@ -34,7 +34,7 @@ import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO; import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderUtil; import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil; import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
@@ -138,7 +138,7 @@ public class BeaconRenderHandler
// lock to make sure we don't try adding beacons to the arrays while processing them // lock to make sure we don't try adding beacons to the arrays while processing them
this.updateLock.lock(); this.updateLock.lock();
Vec3d cameraPos = MC_RENDER.getCameraExactPosition(); DhVec3d cameraPos = MC_RENDER.getCameraExactPosition();
// fading by the overdraw prevention amount helps reduce beacons from rendering strangely // fading by the overdraw prevention amount helps reduce beacons from rendering strangely
// on the border of DH's render distance // on the border of DH's render distance
@@ -156,7 +156,7 @@ public class BeaconRenderHandler
for (DhApiRenderableBox box : this.fullBeaconBoxList) for (DhApiRenderableBox box : this.fullBeaconBoxList)
{ {
// if a beacon is outside the vanilla render distance render it // if a beacon is outside the vanilla render distance render it
double distance = Vec3d.getHorizontalDistance(cameraPos, box.minPos); double distance = DhVec3d.getHorizontalDistance(cameraPos, box.minPos);
if (distance > dhFadeDistance) if (distance > dhFadeDistance)
{ {
this.activeBeaconBoxRenderGroup.add(box); this.activeBeaconBoxRenderGroup.add(box);
@@ -31,8 +31,8 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f; import com.seibel.distanthorizons.core.util.math.DhVec3f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.IDhGenericRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -74,25 +74,28 @@ public class CloudRenderHandler
*/ */
private static final int CLOUD_INSTANCE_RADIUS_COUNT = 5; private static final int CLOUD_INSTANCE_RADIUS_COUNT = 5;
/** if multi-layer clouds are enabled how many layers should be rendered? */
private static final int CLOUD_LAYER_COUNT = 3;
private static final float MOVE_SPEED_IN_BLOCKS_PER_SECOND = 6.0f; private static final float MOVE_SPEED_IN_BLOCKS_PER_SECOND = 6.0f;
private final IDhApiRenderableBoxGroup[][] boxGroupByOffset private final IDhApiRenderableBoxGroup[][][] boxGroupByOffset
// radius * 2 to get the diameter // radius * 2 to get the diameter
// + 1 so we get an odd number wide (needed so we can have a center position) // + 1 so we get an odd number wide (needed so we can have a center position)
= new IDhApiRenderableBoxGroup[(CLOUD_INSTANCE_RADIUS_COUNT * 2) + 1][(CLOUD_INSTANCE_RADIUS_COUNT * 2) + 1]; = new IDhApiRenderableBoxGroup[CLOUD_LAYER_COUNT][(CLOUD_INSTANCE_RADIUS_COUNT * 2) + 1][(CLOUD_INSTANCE_RADIUS_COUNT * 2) + 1];
private final IDhClientLevel level; private final IDhClientLevel level;
private final IDhGenericRenderer renderer; private final IDhGenericRenderer renderer;
/** cached array so we don't need to re-create it each frame for each cloud group */ /** cached array so we don't need to re-create it each frame for each cloud group */
private final Vec3d[] cullingCorners = new Vec3d[] private final DhVec3d[] cullingCorners = new DhVec3d[]
{ {
// the values of each will be overwritten during the culling pass // the values of each will be overwritten during the culling pass
new Vec3d(), new DhVec3d(),
new Vec3d(), new DhVec3d(),
new Vec3d(), new DhVec3d(),
new Vec3d(), new DhVec3d(),
}; };
@@ -260,32 +263,42 @@ public class CloudRenderHandler
// slightly lighter shading than the default // slightly lighter shading than the default
DhApiRenderableBoxGroupShading cloudShading = DhApiRenderableBoxGroupShading.getUnshaded(); DhApiRenderableBoxGroupShading cloudShading = DhApiRenderableBoxGroupShading.getUnshaded();
cloudShading.north = cloudShading.south = 0.9f;
cloudShading.east = cloudShading.west = 0.8f;
cloudShading.top = 1.0f;
cloudShading.bottom = 0.7f;
for (int x = -CLOUD_INSTANCE_RADIUS_COUNT; x <= CLOUD_INSTANCE_RADIUS_COUNT; x++)
{ {
for (int z = -CLOUD_INSTANCE_RADIUS_COUNT; z <= CLOUD_INSTANCE_RADIUS_COUNT; z++) cloudShading.north = 0.9f;
cloudShading.south = cloudShading.north;
cloudShading.east = 0.8f;
cloudShading.west = cloudShading.east;
cloudShading.top = 1.0f;
cloudShading.bottom = 0.7f;
}
for (int y = CLOUD_LAYER_COUNT-1; y >= 0; y--) // start from the top down so transparency could be attempted
{
for (int x = -CLOUD_INSTANCE_RADIUS_COUNT; x <= CLOUD_INSTANCE_RADIUS_COUNT; x++)
{ {
IDhApiRenderableBoxGroup boxGroup = GENERIC_OBJECT_FACTORY.createRelativePositionedGroup( for (int z = -CLOUD_INSTANCE_RADIUS_COUNT; z <= CLOUD_INSTANCE_RADIUS_COUNT; z++)
{
IDhApiRenderableBoxGroup boxGroup = GENERIC_OBJECT_FACTORY.createRelativePositionedGroup(
ModInfo.NAME + ":Clouds", ModInfo.NAME + ":Clouds",
new DhApiVec3d(0, 0, 0), // the offset will be set during rendering new DhApiVec3d(0, 0, 0), // the offset will be set during rendering
boxList); boxList);
// since cloud colors are set by the level based on the time of day lighting should affect it // since cloud colors are set by the level based on the time of day lighting should affect it
boxGroup.setBlockLight(LodUtil.MAX_MC_LIGHT); boxGroup.setBlockLight(LodUtil.MAX_MC_LIGHT);
boxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT); boxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT);
boxGroup.setSsaoEnabled(false); boxGroup.setSsaoEnabled(false);
boxGroup.setShading(cloudShading); boxGroup.setShading(cloudShading);
CloudParams cloudParams = new CloudParams(textureWidth, x, z); CloudParams cloudParams = new CloudParams(
boxGroup.setPreRenderFunc((renderParam) -> this.preRender(renderParam, cloudParams)); textureWidth,
y, x, z);
this.renderer.add(boxGroup); boxGroup.setPreRenderFunc((renderParam) -> this.preRender(renderParam, cloudParams));
this.boxGroupByOffset[x+CLOUD_INSTANCE_RADIUS_COUNT][z+CLOUD_INSTANCE_RADIUS_COUNT] = boxGroup;
this.renderer.add(boxGroup);
this.boxGroupByOffset[y][x + CLOUD_INSTANCE_RADIUS_COUNT][z + CLOUD_INSTANCE_RADIUS_COUNT] = boxGroup;
}
} }
} }
} }
@@ -301,15 +314,24 @@ public class CloudRenderHandler
private void preRender(DhApiRenderParam renderParam, CloudParams cloudParams) private void preRender(DhApiRenderParam renderParam, CloudParams cloudParams)
{ {
IDhApiRenderableBoxGroup boxGroup = this.boxGroupByOffset[cloudParams.instanceOffsetX+CLOUD_INSTANCE_RADIUS_COUNT][cloudParams.instanceOffsetZ+CLOUD_INSTANCE_RADIUS_COUNT]; IDhApiRenderableBoxGroup boxGroup = this.boxGroupByOffset[cloudParams.instanceOffsetY][cloudParams.instanceOffsetX+CLOUD_INSTANCE_RADIUS_COUNT][cloudParams.instanceOffsetZ+CLOUD_INSTANCE_RADIUS_COUNT];
//===================// //===================//
// should we render? // // should we render? //
//===================// //===================//
//region
boolean renderClouds = Config.Client.Advanced.Graphics.GenericRendering.enableCloudRendering.get(); boolean renderClouds = Config.Client.Advanced.Graphics.GenericRendering.enableCloudRendering.get();
boolean renderSingleCloudLayer = !Config.Client.Advanced.Graphics.GenericRendering.enableMultiLayerClouds.get();
if (renderSingleCloudLayer
&& cloudParams.instanceOffsetY != 0)
{
renderClouds = false;
}
boxGroup.setActive(renderClouds); boxGroup.setActive(renderClouds);
if(!renderClouds) if(!renderClouds)
{ {
@@ -322,27 +344,33 @@ public class CloudRenderHandler
return; return;
} }
//endregion
//================// //================//
// cloud movement // // cloud movement //
//================// //================//
//region
long currentTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis();
float deltaTime = (currentTime - cloudParams.lastFrameTime) / 1000.0f; // Delta time in seconds float deltaTime = (currentTime - cloudParams.lastFrameTime) / 1000.0f; // Delta time in seconds
cloudParams.lastFrameTime = currentTime; cloudParams.lastFrameTime = currentTime;
float deltaX = MOVE_SPEED_IN_BLOCKS_PER_SECOND * deltaTime; float deltaX = (MOVE_SPEED_IN_BLOCKS_PER_SECOND + cloudParams.heightSpeedOffset) * deltaTime;
// negative delta is to match vanilla's cloud movement // negative delta is to match vanilla's cloud movement
cloudParams.deltaOffsetX -= deltaX; cloudParams.deltaOffsetX -= deltaX;
// wrap the cloud around after reaching the edge // wrap the cloud around after reaching the edge
cloudParams.deltaOffsetX %= cloudParams.widthInBlocks; cloudParams.deltaOffsetX %= cloudParams.widthInBlocks;
//endregion
//============================// //============================//
// camera movement and offset // // camera movement and offset //
//============================// //============================//
//region
// camera position // camera position
int cameraPosX = (int)MC_RENDER.getCameraExactPosition().x; int cameraPosX = (int)MC_RENDER.getCameraExactPosition().x;
@@ -360,13 +388,17 @@ public class CloudRenderHandler
float newMinPosX = float newMinPosX =
cloudParams.deltaOffsetX cloudParams.deltaOffsetX
+ (cloudParams.instanceOffsetX * cloudParams.widthInBlocks) + (cloudParams.instanceOffsetX * cloudParams.widthInBlocks)
+ instanceOffsetX + cloudParams.halfWidthInBlocks; + instanceOffsetX + cloudParams.halfWidthInBlocks;
float newMinPosY = this.level.getLevelWrapper().getMaxHeight() + 200; float newMinPosY =
float newMinPosZ = cloudParams.deltaOffsetZ this.level.getLevelWrapper().getMaxHeight()
+ (cloudParams.instanceOffsetZ * cloudParams.widthInBlocks) + 100 // render clouds at least 200 blocks above the height limit to prevent players/blocks from intersecting (since DH always renders behind everything else)
+ instanceOffsetZ + cloudParams.halfWidthInBlocks; + cloudParams.heightOffset;
float newMinPosZ =
cloudParams.deltaOffsetZ
+ (cloudParams.instanceOffsetZ * cloudParams.widthInBlocks)
+ instanceOffsetZ + cloudParams.halfWidthInBlocks;
boolean cullCloud = this.shouldCloudBeCulled( boolean cullCloud = this.shouldCloudBeCulled(
newMinPosX, newMinPosY, newMinPosZ, newMinPosX, newMinPosY, newMinPosZ,
@@ -377,28 +409,25 @@ public class CloudRenderHandler
boxGroup.setActive(false); boxGroup.setActive(false);
} }
boxGroup.setOriginBlockPos(new DhApiVec3d(newMinPosX, newMinPosY, newMinPosZ));
//endregion
//===========================// //===========================//
// update color and position // // update color and position //
//===========================// //===========================//
//region
// if debug colors are enabled don't change them // don't replace the debug colors
if (!DEBUG_BORDER_COLORS if (!DEBUG_BORDER_COLORS)
// don't modify cloud groups that aren't active
&& boxGroup.isActive())
{ {
// cloud color changes based on the time of day and weather so we need to get it from the level // cloud color changes based on the time of day and weather so we need to get it from the level
Color newCloudColor = clientLevelWrapper.getCloudColor(renderParam.partialTicks); Color newCloudColor = clientLevelWrapper.getCloudColor(renderParam.partialTicks);
// all boxes should have the same color, so we can get their current color
// via the first box
DhApiRenderableBox firstBox = boxGroup.get(0);
Color currentBoxColor = firstBox.color;
// update the boxes if their color should be changed // update the boxes if their color should be changed
if (!newCloudColor.equals(currentBoxColor)) if (!newCloudColor.equals(cloudParams.previousColor))
{ {
// Note: cloud instances may share boxes // Note: cloud instances may share boxes
// because of that this method may only need to be called once per all clouds // because of that this method may only need to be called once per all clouds
@@ -406,20 +435,20 @@ public class CloudRenderHandler
{ {
box.color = newCloudColor; box.color = newCloudColor;
} }
}
// trigger an update if this cloud section has a different color
if (!cloudParams.previousColor.equals(newCloudColor))
{
cloudParams.previousColor = newCloudColor; cloudParams.previousColor = newCloudColor;
boxGroup.triggerBoxChange(); boxGroup.triggerBoxChange();
} }
} }
boxGroup.setOriginBlockPos(new DhApiVec3d(newMinPosX, newMinPosY, newMinPosZ)); //endregion
} }
/**
* based on the OpenGL spec <br>
* https://registry.khronos.org/OpenGL-Refpages/gl4/html/mix.xhtml
*/
private float mixColors(float x, float y, float a) { return x * (1 - a) + y * a; }
private boolean shouldCloudBeCulled( private boolean shouldCloudBeCulled(
float minPosX, float minPosY, float minPosZ, float minPosX, float minPosY, float minPosZ,
CloudParams cloudParams) CloudParams cloudParams)
@@ -460,14 +489,17 @@ public class CloudRenderHandler
this.cullingCorners[3].y = minPosY; this.cullingCorners[3].y = minPosY;
this.cullingCorners[3].z = minPosZ + cloudParams.widthInBlocks; this.cullingCorners[3].z = minPosZ + cloudParams.widthInBlocks;
Vec3d cameraPos = MC_RENDER.getCameraExactPosition(); DhVec3d cameraPos = MC_RENDER.getCameraExactPosition();
Vec3f cameraLookAtVector = MC_RENDER.getLookAtVector(); DhVec3f cameraLookAtVector = MC_RENDER.getLookAtVector();
cameraLookAtVector.normalize(); cameraLookAtVector.normalize();
double renderDistance = Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get() double renderDistance =
// * 1.5 is so we have a little extra buffer where clouds will render further than // minimum distance of 256 to handle 3-layer clouds correctly,
// necessary to prevent seeing the cloud border // otherwise the upper layers will be culled
* LodUtil.CHUNK_WIDTH * 1.5; Math.max(Config.Client.Advanced.Graphics.Quality.lodChunkRenderDistanceRadius.get(), 256)
// * 1.5 is so we have a little extra buffer where clouds will render further than
// necessary to prevent seeing the cloud border
* LodUtil.CHUNK_WIDTH * 1.5;
@@ -478,14 +510,14 @@ public class CloudRenderHandler
boolean allOutsideRenderDistance = true; boolean allOutsideRenderDistance = true;
boolean allBehindCamera = true; boolean allBehindCamera = true;
for (Vec3d corner : this.cullingCorners) for (DhVec3d corner : this.cullingCorners)
{ {
// Check if the corner is within the render distance // Check if the corner is within the render distance
// (ignoring height, since LODs also ignore height) // (ignoring height, since LODs also ignore height)
Vec3d cornerNoHeight = new Vec3d(corner); DhVec3d cornerNoHeight = new DhVec3d(corner);
cornerNoHeight.y = 0; cornerNoHeight.y = 0;
Vec3d cameraPosNoHeight = new Vec3d(cameraPos); DhVec3d cameraPosNoHeight = new DhVec3d(cameraPos);
cameraPosNoHeight.y = 0; cameraPosNoHeight.y = 0;
double cornerDistance = cornerNoHeight.getDistance(cameraPosNoHeight); double cornerDistance = cornerNoHeight.getDistance(cameraPosNoHeight);
@@ -496,7 +528,7 @@ public class CloudRenderHandler
// Check if the corner is in front of the camera (dot product > 0 means in front) // Check if the corner is in front of the camera (dot product > 0 means in front)
Vec3f toCorner = new Vec3f( DhVec3f toCorner = new DhVec3f(
(float) (corner.x - cameraPos.x), (float) (corner.x - cameraPos.x),
(float) (corner.y - cameraPos.y), (float) (corner.y - cameraPos.y),
(float) (corner.z - cameraPos.z)); (float) (corner.z - cameraPos.z));
@@ -568,9 +600,13 @@ public class CloudRenderHandler
public final int widthInBlocks; public final int widthInBlocks;
public final int halfWidthInBlocks; public final int halfWidthInBlocks;
public final int instanceOffsetY;
public final int instanceOffsetX; public final int instanceOffsetX;
public final int instanceOffsetZ; public final int instanceOffsetZ;
public final int heightOffset;
public final float heightSpeedOffset;
/** how far this cloud group has moved in the X direction based on time */ /** how far this cloud group has moved in the X direction based on time */
public float deltaOffsetX = 0; public float deltaOffsetX = 0;
@@ -586,14 +622,25 @@ public class CloudRenderHandler
// constructor // // constructor //
public CloudParams(int textureWidth, int instanceOffsetX, int instanceOffsetZ) public CloudParams(int textureWidth, int instanceOffsetY, int instanceOffsetX, int instanceOffsetZ)
{ {
this.textureWidth = textureWidth; this.textureWidth = textureWidth;
this.widthInBlocks = (this.textureWidth * CLOUD_BOX_WIDTH); this.widthInBlocks = (this.textureWidth * CLOUD_BOX_WIDTH);
this.halfWidthInBlocks = this.widthInBlocks / 2; this.halfWidthInBlocks = this.widthInBlocks / 2;
this.instanceOffsetY = instanceOffsetY;
this.instanceOffsetX = instanceOffsetX; this.instanceOffsetX = instanceOffsetX;
this.instanceOffsetZ = instanceOffsetZ; this.instanceOffsetZ = instanceOffsetZ;
// each layer up increases by 512 blocks
this.heightOffset = instanceOffsetY * 512;
// have higher cloud layers move faster
this.heightSpeedOffset = instanceOffsetY * 10f;
// offset each layer a bit so the duplicated texture use isn't as obvious
this.deltaOffsetX = this.widthInBlocks * instanceOffsetY * 0.75f;
this.deltaOffsetZ = this.widthInBlocks * instanceOffsetY * 1.5f;
} }
} }
@@ -0,0 +1,115 @@
package com.seibel.distanthorizons.core.render.renderer;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogColorMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiFogFalloff;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogDirection;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiHeightFogMixMode;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeFogRenderEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiFogRenderParam;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.RenderParams;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import java.awt.*;
/**
* @see DhApiFogRenderParam
* @see DhApiBeforeFogRenderEvent.EventParam
*/
public class FogRenderParamFactory
{
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
/** cached object to reduce GC pressure */
private static final DhApiBeforeFogRenderEvent.EventParam EVENT_PARAM = new DhApiBeforeFogRenderEvent.EventParam();
//=========//
// methods //
//=========//
//region
/** returns a cached object to reduce GC pressure */
public static DhApiBeforeFogRenderEvent.EventParam getRenderParam(RenderParams renderParams)
{
Color fogColor = getFogColor(renderParams.partialTicks);
// far fog
EDhApiFogFalloff farFogFalloff = Config.Client.Advanced.Graphics.Fog.farFogFalloff.get();
float farFogStart = Config.Client.Advanced.Graphics.Fog.farFogStart.get();
float farFogEnd = Config.Client.Advanced.Graphics.Fog.farFogEnd.get();
float farFogMin = Config.Client.Advanced.Graphics.Fog.farFogMin.get();
float farFogMax = Config.Client.Advanced.Graphics.Fog.farFogMax.get();
float farFogDensity = Config.Client.Advanced.Graphics.Fog.farFogDensity.get();
// height fog
EDhApiFogFalloff heightFogFalloff = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogFalloff.get();
EDhApiHeightFogMixMode heightFogMixingMode = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMixMode.get();
EDhApiHeightFogDirection heightFogCameraDirection = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDirection.get();
float heightFogBaseHeight = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogBaseHeight.get();
float heightFogStart = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogStart.get();
float heightFogEnd = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogEnd.get();
float heightFogMin = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMin.get();
float heightFogMax = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogMax.get();
float heightFogDensity = Config.Client.Advanced.Graphics.Fog.HeightFog.heightFogDensity.get();
// override fog if underwater
if (MC_RENDER.isFogStateSpecial())
{
// hide everything behind fog
farFogStart = 0.0f;
farFogEnd = 1.0f;
farFogMin = 1.0f; // minimum fog thickness is 1 so everything renders in fog
farFogMax = 1.0f;
farFogDensity = 1.0f; // always render max density
}
DhApiFogRenderParam fogRenderParam = new DhApiFogRenderParam(
fogColor,
// far fog
farFogFalloff,
farFogStart, farFogEnd,
farFogMin, farFogMax,
farFogDensity,
// height fog
heightFogFalloff,
heightFogMixingMode, heightFogCameraDirection,
heightFogBaseHeight,
heightFogStart, heightFogEnd,
heightFogMin, heightFogMax,
heightFogDensity
);
EVENT_PARAM.update(renderParams, fogRenderParam);
return EVENT_PARAM;
}
private static Color getFogColor(float partialTicks)
{
Color fogColor;
if (Config.Client.Advanced.Graphics.Fog.colorMode.get() == EDhApiFogColorMode.USE_SKY_COLOR
// when underwater or special fogs are being used, don't use the sky color
&& !MC_RENDER.isFogStateSpecial())
{
fogColor = MC_RENDER.getSkyColor();
}
else
{
fogColor = MC_RENDER.getFogColor(partialTicks);
}
return fogColor;
}
//endregion
}
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.render.renderer; package com.seibel.distanthorizons.core.render.renderer;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*; import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer; import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBufferContainer;
@@ -26,6 +27,7 @@ import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
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.DhSectionPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy; import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.render.RenderBufferHandler; import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.RenderParams; import com.seibel.distanthorizons.core.render.RenderParams;
@@ -36,6 +38,8 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccess
import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.*; import com.seibel.distanthorizons.core.wrapperInterfaces.render.renderPass.*;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector; import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import java.awt.*;
/** /**
* This is where all the magic happens. <br> * This is where all the magic happens. <br>
* This is where LODs are draw to the world. * This is where LODs are draw to the world.
@@ -114,9 +118,9 @@ public class LodRenderer
private void renderTerrain(RenderParams renderParams, IProfilerWrapper profiler, boolean runningDeferredPass) private void renderTerrain(RenderParams renderParams, IProfilerWrapper profiler, boolean runningDeferredPass)
{ {
//====================// //===============//
// validate rendering // // validate pass //
//====================// //===============//
//region //region
boolean deferTransparentRendering = DhApiRenderProxy.INSTANCE.getDeferTransparentRendering(); boolean deferTransparentRendering = DhApiRenderProxy.INSTANCE.getDeferTransparentRendering();
@@ -142,7 +146,7 @@ public class LodRenderer
//=================// //=================//
//region //region
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderSetupEvent.class, renderParams); ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderSetupEvent.class, renderParams.apiCopy);
try (IProfilerWrapper.IProfileBlock terrainRender_profile = profiler.push("LOD GL setup")) // starts the new profile block for most DH rendering try (IProfilerWrapper.IProfileBlock terrainRender_profile = profiler.push("LOD GL setup")) // starts the new profile block for most DH rendering
{ {
@@ -198,6 +202,8 @@ public class LodRenderer
renderFog |= renderParams.vanillaFogEnabled; renderFog |= renderParams.vanillaFogEnabled;
} }
DhApiBeforeFogRenderEvent.EventParam fogRenderEventParam = FogRenderParamFactory.getRenderParam(renderParams);
//endregion //endregion
@@ -209,7 +215,7 @@ public class LodRenderer
if (!runningDeferredPass) if (!runningDeferredPass)
{ {
// needs to be fired after all the textures have been created/bound // needs to be fired after all the textures have been created/bound
boolean clearTextures = !ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeTextureClearEvent.class, renderParams); boolean clearTextures = !ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeTextureClearEvent.class, renderParams.apiCopy);
if (clearTextures) if (clearTextures)
{ {
this.metaRenderer.clearDhDepthAndColorTextures(renderParams); this.metaRenderer.clearDhDepthAndColorTextures(renderParams);
@@ -250,12 +256,22 @@ public class LodRenderer
// combined pass transparent rendering // combined pass transparent rendering
if (!deferTransparentRendering if (!deferTransparentRendering
&& Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled) && Config.Client.Advanced.Graphics.Quality.transparency.get() == EDhApiTransparency.COMPLETE)
{ {
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);
} }
// fog
boolean cancelFogEvent = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeFogRenderEvent.class, fogRenderEventParam);
if (renderFog
&& !cancelFogEvent)
{
profiler.popPush("LOD Fog");
this.fogRenderer.render(renderParams, fogRenderEventParam.getFogRenderParam());
}
// far plane clip fading // far plane clip fading
if (Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane.get() if (Config.Client.Advanced.Graphics.Quality.dhFadeFarClipPlane.get()
&& IRIS_ACCESSOR == null) && IRIS_ACCESSOR == null)
@@ -264,15 +280,6 @@ public class LodRenderer
this.farFadeRenderer.render(renderParams); this.farFadeRenderer.render(renderParams);
} }
// fog
if (renderFog)
{
profiler.popPush("LOD Fog");
this.fogRenderer.render(renderParams);
}
//=================// //=================//
@@ -288,12 +295,28 @@ public class LodRenderer
} }
if (Config.Client.Advanced.Debugging.PositionFinder.positionFinderEnable.get())
{
// can be used to find specific positions when debugging
this.debugWireframeRenderer.renderBox(new AbstractDebugWireframeRenderer.Box(
DhSectionPos.encode(
Config.Client.Advanced.Debugging.PositionFinder.positionFinderDetailLevel.get().byteValue(),
Config.Client.Advanced.Debugging.PositionFinder.positionFinderXPos.get(),
Config.Client.Advanced.Debugging.PositionFinder.positionFinderZPos.get()),
Config.Client.Advanced.Debugging.PositionFinder.positionFinderMinBlockY.get(),
Config.Client.Advanced.Debugging.PositionFinder.positionFinderMaxBlockY.get(),
Config.Client.Advanced.Debugging.PositionFinder.positionFinderMarginPercent.get(),
Color.GREEN
));
}
//=============================// //=============================//
// Apply to the MC Framebuffer // // Apply to the MC Framebuffer //
//=============================// //=============================//
boolean cancelApplyShader = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeApplyShaderRenderEvent.class, renderParams); boolean cancelApplyShader = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeApplyShaderRenderEvent.class, renderParams.apiCopy);
if (!cancelApplyShader) if (!cancelApplyShader)
{ {
profiler.popPush("Apply to MC"); profiler.popPush("Apply to MC");
@@ -307,17 +330,19 @@ public class LodRenderer
// deferred rendering // // deferred rendering //
//====================// //====================//
if (Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled) if (Config.Client.Advanced.Graphics.Quality.transparency.get() == EDhApiTransparency.COMPLETE)
{ {
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 (renderFog) boolean cancelFogEvent = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeFogRenderEvent.class, fogRenderEventParam);
if (renderFog
&& !cancelFogEvent)
{ {
profiler.popPush("LOD Fog"); profiler.popPush("LOD Fog");
this.fogRenderer.render(renderParams); this.fogRenderer.render(renderParams, fogRenderEventParam.getFogRenderParam());
} }
} }
} }
@@ -329,7 +354,7 @@ public class LodRenderer
//================// //================//
profiler.popPush("LOD cleanup"); profiler.popPush("LOD cleanup");
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderCleanupEvent.class, renderParams); ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderCleanupEvent.class, renderParams.apiCopy);
this.metaRenderer.runRenderPassCleanup(renderParams); this.metaRenderer.runRenderPassCleanup(renderParams);
} }
@@ -205,21 +205,20 @@ public class RenderableBoxGroup
this.vertexBufferContainer = this.altVertexBufferContainer; this.vertexBufferContainer = this.altVertexBufferContainer;
this.altVertexBufferContainer = temp; this.altVertexBufferContainer = temp;
this.vertexDataDirty = false;
return; return;
} }
// if the vertex data is already up to date, do nothing // executor should always be available, if not that probably means the level is being shut down
if (!this.vertexDataDirty) PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor();
if (executor == null || executor.isTerminated())
{ {
return; return;
} }
PriorityTaskPicker.Executor executor = ThreadPoolUtil.getRenderLoadingExecutor(); // if the vertex data is already up to date, do nothing
if (executor == null || executor.isTerminated()) if (!this.vertexDataDirty)
{ {
return; return;
} }
@@ -250,6 +249,9 @@ public class RenderableBoxGroup
{ {
this.altVertexBufferContainer.updateVertexData(this.uploadBoxList); this.altVertexBufferContainer.updateVertexData(this.uploadBoxList);
this.altVertexBufferContainer.setState(IDhGenericObjectVertexBufferContainer.EState.READY_TO_UPLOAD); this.altVertexBufferContainer.setState(IDhGenericObjectVertexBufferContainer.EState.READY_TO_UPLOAD);
// upload complete
this.vertexDataDirty = false;
} }
catch (Exception e) catch (Exception e)
{ {
@@ -3,7 +3,7 @@ package com.seibel.distanthorizons.core.render.renderer.cullingFrustum;
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiCullingFrustum; import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiCullingFrustum;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f; import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
import com.seibel.distanthorizons.core.util.math.Mat4f; import com.seibel.distanthorizons.core.util.math.DhMat4f;
import org.joml.FrustumIntersection; import org.joml.FrustumIntersection;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Matrix4fc; import org.joml.Matrix4fc;
@@ -41,7 +41,7 @@ public class DhFrustumBounds implements IDhApiCullingFrustum
this.worldMinY = worldMinBlockY; this.worldMinY = worldMinBlockY;
this.worldMaxY = worldMaxBlockY; this.worldMaxY = worldMaxBlockY;
Matrix4f worldViewProjection = new Matrix4f(Mat4f.createJomlMatrix(dhWorldViewProjection)); Matrix4f worldViewProjection = new Matrix4f(DhMat4f.createJomlMatrix(dhWorldViewProjection));
this.frustum.set(worldViewProjection); this.frustum.set(worldViewProjection);
Matrix4fc matWorldViewProjectionInv = new Matrix4f(worldViewProjection).invert(); Matrix4fc matWorldViewProjectionInv = new Matrix4f(worldViewProjection).invert();
@@ -0,0 +1,25 @@
package com.seibel.distanthorizons.core.sql;
import java.sql.SQLException;
/**
* Used to simplify handling when a database is corrupted
* since Java doesn't have a specific exception to handle corrupted databases
*/
public class DbCorruptedException extends SQLException
{
public DbCorruptedException(String message) { super(message); }
public DbCorruptedException(String message, Throwable cause) { super(message, cause); }
public DbCorruptedException(Throwable cause) { super(cause); }
// helper methods //
public static boolean isCorruptedException(SQLException e)
{
String message = e.getMessage().toLowerCase();
return message.contains("sqlite_corrupt")
|| message.contains("malformed");
}
}
@@ -19,10 +19,13 @@
package com.seibel.distanthorizons.core.sql.repo; package com.seibel.distanthorizons.core.sql.repo;
import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
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.sql.DatabaseUpdater; import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.sql.DbConnectionClosedException; import com.seibel.distanthorizons.core.sql.DbConnectionClosedException;
import com.seibel.distanthorizons.core.sql.DbCorruptedException;
import com.seibel.distanthorizons.core.sql.dto.IBaseDTO; import com.seibel.distanthorizons.core.sql.dto.IBaseDTO;
import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper; import com.seibel.distanthorizons.core.sql.repo.phantoms.AutoClosableTrackingWrapper;
import com.seibel.distanthorizons.core.util.ExceptionUtil; import com.seibel.distanthorizons.core.util.ExceptionUtil;
@@ -37,6 +40,7 @@ import java.io.IOException;
import java.sql.*; import java.sql.*;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@@ -55,6 +59,7 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
private static final ConcurrentHashMap<String, Connection> CONNECTIONS_BY_CONNECTION_STRING = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, Connection> CONNECTIONS_BY_CONNECTION_STRING = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<AbstractDhRepo<?, ?>, String> ACTIVE_CONNECTION_STRINGS_BY_REPO = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<AbstractDhRepo<?, ?>, String> ACTIVE_CONNECTION_STRINGS_BY_REPO = new ConcurrentHashMap<>();
private static final Set<String> CORRUPTED_DB_PATHS = Collections.newSetFromMap(new ConcurrentHashMap<>());
@@ -70,6 +75,8 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
protected final KeyedLockContainer<TKey> saveLockContainer = new KeyedLockContainer<>(); protected final KeyedLockContainer<TKey> saveLockContainer = new KeyedLockContainer<>();
private final AtomicBoolean databaseCorruptedRef = new AtomicBoolean(false);
//=============// //=============//
@@ -227,7 +234,6 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
finally finally
{ {
saveLock.unlock(); saveLock.unlock();
//this.tryTriggerWalFlush();
} }
} }
private void insert(TDTO dto) private void insert(TDTO dto)
@@ -392,6 +398,13 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
@Nullable @Nullable
public ResultSet query(@Nullable PreparedStatement statement) throws RuntimeException public ResultSet query(@Nullable PreparedStatement statement) throws RuntimeException
{ {
// if the DB is corrupted act as if it's closed
// that should prevent further harm
if (this.databaseCorruptedRef.get())
{
return null;
}
// This is done so we don't have to add "if null" checks everywhere. // This is done so we don't have to add "if null" checks everywhere.
// Normally this should only happen once the DB has been closed. // Normally this should only happen once the DB has been closed.
if (statement == null) if (statement == null)
@@ -425,6 +438,33 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
{ {
return null; return null;
} }
else if (DbCorruptedException.isCorruptedException(e))
{
// error may trigger on multiple threads at once
synchronized (this)
{
// only trigger this error once per database
if (!this.databaseCorruptedRef.getAndSet(true)
// multiple repos may be open for the same path (client and server levels in singleplayer)
&& CORRUPTED_DB_PATHS.add(this.databaseFile.getPath()))
{
LOGGER.error("DH database file at [" + this.databaseFile.getPath() + "] is corrupted. \n" +
"All operations to this DB are disabled, DH may behave strangely if you continue playing. \n" +
"Please leave the world and delete the corrupted database file to fix. \n" +
"Error: [" + e.getMessage() + "]", e);
ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.DARK_RED + MinecraftTextFormat.BOLD + "DH database is corrupted." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"DH will behave strangely if your continue playing. \n" +
"Please leave the world and delete the corrupted database file at: \n" +
MinecraftTextFormat.YELLOW + this.databaseFile.getPath() + MinecraftTextFormat.CLEAR_FORMATTING + "\n" +
"to resolve the issue. \n"
);
}
}
return null;
}
else else
{ {
String message = "Unexpected Query error: [" + e.getMessage() + "], for prepared statement: [" + statement + "]."; String message = "Unexpected Query error: [" + e.getMessage() + "], for prepared statement: [" + statement + "].";
@@ -512,6 +552,10 @@ public abstract class AbstractDhRepo<TKey, TDTO extends IBaseDTO<TKey>> implemen
LOGGER.error("Unable to close the connection ["+connectionString+"], error: ["+e.getMessage()+"]"); LOGGER.error("Unable to close the connection ["+connectionString+"], error: ["+e.getMessage()+"]");
} }
} }
// clear the errors so they can be re-fired if needed
CORRUPTED_DB_PATHS.clear();
} }
@Override @Override
@@ -25,6 +25,7 @@ public class ExceptionUtil
return throwable instanceof InterruptedException return throwable instanceof InterruptedException
|| throwable instanceof UncheckedInterruptedException || throwable instanceof UncheckedInterruptedException
|| throwable instanceof RejectedExecutionException || throwable instanceof RejectedExecutionException
|| throwable instanceof CancellationException
|| throwable instanceof ClosedByInterruptException; || throwable instanceof ClosedByInterruptException;
} }
@@ -37,8 +38,8 @@ public class ExceptionUtil
unwrapped instanceof CancellationException; unwrapped instanceof CancellationException;
} }
public static Throwable ensureUnwrap(Throwable t) public static Throwable ensureUnwrap(Throwable t)
{ { return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t; }
return t instanceof CompletionException ? ensureUnwrap(t.getCause()) : t;
}
} }
@@ -451,6 +451,21 @@ public class RenderDataPointReducingList extends AbstractPhantomArrayList
this.setBigger(smaller, bigger); this.setBigger(smaller, bigger);
} }
if (writeIndex == 0)
{
// if every data point in the list is NULL (0) the write index will be 0,
// and in order to prevent accessing index -1 below,
// setting the write index to 1 is needed.
// This shouldn't happen normally, however if the lod data is slightly malformed
// (which is specifically the case for the commonly shared wyncraft LODs)
// this check is needed.
// It would probably be best to fix the 6 or so NULL datapoints that are next
// to each other in the full data source, but for now this fix works.
writeIndex = 1;
}
this.smallest = this.sortingArray.getShort(0); this.smallest = this.sortingArray.getShort(0);
this.biggest = this.sortingArray.getShort(writeIndex - 1); this.biggest = this.sortingArray.getShort(writeIndex - 1);
this.setSmaller(this.getSmallest(), NULL); this.setSmaller(this.getSmallest(), NULL);
@@ -464,25 +479,21 @@ public class RenderDataPointReducingList extends AbstractPhantomArrayList
@VisibleForTesting @VisibleForTesting
public void sortBySize(int size) public void sortBySize(int size)
{ {
ShortArrayList array = this.sortingArray;
it.unimi.dsi.fastutil.Arrays.quickSort( it.unimi.dsi.fastutil.Arrays.quickSort(
0, 0, size,
size, this::sortBySizeComparator,
// comparator this::sortBySizeSwapper
(int index1, int index2) ->
{
return Integer.compare(
this.getSize(this.getSortingIndex(index1)),
this.getSize(this.getSortingIndex(index2))
);
},
// swapper
(int index1, int index2) ->
{
ShortArrays.swap(array.elements(), index1, index2);
}
); );
} }
// class methods to try reducing GC pressure vs in-line lambdas
private int sortBySizeComparator(int index1, int index2)
{
return Integer.compare(
this.getSize(this.getSortingIndex(index1)),
this.getSize(this.getSortingIndex(index2))
);
}
private void sortBySizeSwapper(int index1, int index2) { ShortArrays.swap(this.sortingArray.elements(), index1, index2); }
/** /**
* sorts our {@link #sortingArray} in order of lowest-to-highest, * sorts our {@link #sortingArray} in order of lowest-to-highest,
@@ -491,25 +502,21 @@ public class RenderDataPointReducingList extends AbstractPhantomArrayList
@VisibleForTesting @VisibleForTesting
public void sortByPosition(int size) public void sortByPosition(int size)
{ {
ShortArrayList array = this.sortingArray;
it.unimi.dsi.fastutil.Arrays.quickSort( it.unimi.dsi.fastutil.Arrays.quickSort(
0, 0, size,
size, this::sortByPositionComparator,
// comparator this::sortByPositionSwapper
(int index1, int index2) ->
{
return Integer.compare(
this.getMinY(this.getSortingIndex(index1)),
this.getMinY(this.getSortingIndex(index2))
);
},
// swapper
(int index1, int index2) ->
{
ShortArrays.swap(array.elements(), index1, index2);
}
); );
} }
// class methods to try reducing GC pressure vs in-line lambdas
private int sortByPositionComparator(int index1, int index2)
{
return Integer.compare(
this.getMinY(this.getSortingIndex(index1)),
this.getMinY(this.getSortingIndex(index2))
);
}
private void sortByPositionSwapper(int index1, int index2) { ShortArrays.swap(this.sortingArray.elements(), index1, index2); }
/** /**
* moves the smaller node to the correct position in the list, * moves the smaller node to the correct position in the list,
@@ -27,12 +27,13 @@ import com.seibel.distanthorizons.core.dependencyInjection.ModAccessorInjector;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
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.render.EDhRenderDepth;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor; import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.AbstractDhRenderApiDefinition;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f;
/** /**
* This holds miscellaneous helper code * This holds miscellaneous helper code
@@ -45,6 +46,7 @@ public class RenderUtil
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final IIrisAccessor IRIS_ACCESSOR = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class); private static final IIrisAccessor IRIS_ACCESSOR = ModAccessorInjector.INSTANCE.get(IIrisAccessor.class);
private static final AbstractDhRenderApiDefinition RENDER_API_DEF = SingletonInjector.INSTANCE.get(AbstractDhRenderApiDefinition.class);
/** /**
* all speeds are measured in blocks per second * all speeds are measured in blocks per second
@@ -73,7 +75,7 @@ public class RenderUtil
* *
* @param mcProjMat Minecraft's current projection matrix * @param mcProjMat Minecraft's current projection matrix
*/ */
public static Mat4f createLodProjectionMatrix(DhApiMat4f mcProjMat) public static void setDhProjectionMatrix(DhApiMat4f updateMatrix, DhApiMat4f mcProjMat)
{ {
// in James' testing a near clip plane distance of 2 blocks is enough to allow the fragment // in James' testing a near clip plane distance of 2 blocks is enough to allow the fragment
// culling to take effect instead of seeing the near clip plane. // culling to take effect instead of seeing the near clip plane.
@@ -90,18 +92,32 @@ public class RenderUtil
float farClipDist = RenderUtil.getFarClipPlaneDistanceInBlocks(); float farClipDist = RenderUtil.getFarClipPlaneDistanceInBlocks();
// Create a copy of the current matrix, so it won't be modified. // Create a copy of the current matrix, so it won't be modified.
Mat4f lodProj = new Mat4f(mcProjMat); updateMatrix.set(mcProjMat);
// Set new far and near clip plane values. // Set new far and near clip plane values.
lodProj.setClipPlanes(nearClipDist, farClipDist); if (RENDER_API_DEF.getRenderDepth() == EDhRenderDepth.FORWARD_Z)
return lodProj; {
setClipPlanes(updateMatrix, nearClipDist, farClipDist, false);
}
else
{
setClipPlanes(updateMatrix, farClipDist, nearClipDist, true);
}
} }
/** create and return a new projection matrix based on MC's modelView and projection matrices */ /**
public static Mat4f createLodModelViewMatrix(DhApiMat4f mcModelViewMat) * Changes the values that store the clipping planes.
* Formula for calculating matrix values is the same that OpenGL uses when making matrices.
*
* @param nearClip New near clipping plane value.
* @param farClip New far clipping plane value.
*/
public static void setClipPlanes(DhApiMat4f matrix, float nearClip, float farClip, boolean zZeroToOne)
{ {
// nothing beyond copying needs to be done to MC's MVM currently, // formula copied JOML's implementation to match Minecraft
// this method is just here in case that changes in the future matrix.m22 = (zZeroToOne ? farClip : farClip + nearClip) / (nearClip - farClip);
return new Mat4f(mcModelViewMat); matrix.m23 = (zZeroToOne ? farClip : farClip + farClip) * nearClip / (nearClip - farClip);
} }
//endregion //endregion
@@ -163,7 +179,7 @@ public class RenderUtil
if (Config.Client.Advanced.Graphics.Culling.reduceOverdrawWithFastMovement.get()) if (Config.Client.Advanced.Graphics.Culling.reduceOverdrawWithFastMovement.get())
{ {
double avgSpeed = ClientApi.INSTANCE.cameraSpeedRollingAverage.getAverage(); double avgSpeed = ClientApi.INSTANCE.getAvgCameraSpeed();
if (avgSpeed >= DynamicOverdraw.MIN_SPEED) if (avgSpeed >= DynamicOverdraw.MIN_SPEED)
{ {
// if the player is moving fast enough, // if the player is moving fast enough,
@@ -187,6 +203,9 @@ public class RenderUtil
int chunkRenderDistance = MC_RENDER.getRenderDistance(); int chunkRenderDistance = MC_RENDER.getRenderDistance();
int vanillaBlockRenderedDistance = chunkRenderDistance * LodUtil.CHUNK_WIDTH; int vanillaBlockRenderedDistance = chunkRenderDistance * LodUtil.CHUNK_WIDTH;
// Note: setting this to a number lower than 1.0 (ie 1 block)
// can cause distant clouds to flash due to depth buffer precision loss.
// This is not an issue when using Reverse Z depth.
float nearClipPlane; float nearClipPlane;
if (Config.Client.Advanced.Debugging.lodOnlyMode.get()) if (Config.Client.Advanced.Debugging.lodOnlyMode.get())
{ {
@@ -0,0 +1,210 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.math;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import java.nio.FloatBuffer;
/**
* An (almost) exact copy of Minecraft's 1.16.5
* implementation of a 4x4 float matrix.
*
* @author James Seibel
* @version 11-11-2021
*/
public class DhMat4f extends DhApiMat4f
{
/**
* A matrix containing all 0's. <br><br>
*
* Should not be modified. <br>
* Can be used for comparison testing.
*/
public static final DhApiMat4f EMPTY = new DhApiMat4f();
/**
* The 4x4 identity matrix. <br><br>
*
* Should not be modified. <br>
* Can be used for comparison testing.
*/
public static final DhApiMat4f IDENTITY = new DhApiMat4f();
static
{
IDENTITY.setIdentity();
}
//==============//
// constructors //
//==============//
/** all values are 0 */
public DhMat4f() { }
public DhMat4f(DhApiMat4f sourceMatrix) { super(sourceMatrix); }
/** Expects the values of the input buffer to be in row major order (AKA rows then columns) */
public DhMat4f(FloatBuffer buffer) { this(buffer.array()); }
/** Expects the values of the input array to be in row major order (AKA rows then columns) */
public DhMat4f(float[] values) { super(values); }
public DhMat4f(Matrix4fc sourceMatrix) { this.set(sourceMatrix); }
public void set(Matrix4fc sourceMatrix)
{
// JOML matricies are stored transposed vs DH's matricies
this.m00 = sourceMatrix.m00();
this.m01 = sourceMatrix.m10();
this.m02 = sourceMatrix.m20();
this.m03 = sourceMatrix.m30();
this.m10 = sourceMatrix.m01();
this.m11 = sourceMatrix.m11();
this.m12 = sourceMatrix.m21();
this.m13 = sourceMatrix.m31();
this.m20 = sourceMatrix.m02();
this.m21 = sourceMatrix.m12();
this.m22 = sourceMatrix.m22();
this.m23 = sourceMatrix.m32();
this.m30 = sourceMatrix.m03();
this.m31 = sourceMatrix.m13();
this.m32 = sourceMatrix.m23();
this.m33 = sourceMatrix.m33();
}
public static Matrix4f createJomlMatrix(DhApiMat4f matrix)
{
return new Matrix4f(
matrix.m00, matrix.m10, matrix.m20, matrix.m30,
matrix.m01, matrix.m11, matrix.m21, matrix.m31,
matrix.m02, matrix.m12, matrix.m22, matrix.m32,
matrix.m03, matrix.m13, matrix.m23, matrix.m33
);
}
public Matrix4f createJomlMatrix()
{
return new Matrix4f(
this.m00, this.m10, this.m20, this.m30,
this.m01, this.m11, this.m21, this.m31,
this.m02, this.m12, this.m22, this.m32,
this.m03, this.m13, this.m23, this.m33
);
}
//=========//
// methods //
//=========//
public static DhMat4f perspective(double fov, float widthHeightRatio, float nearClipPlane, float farClipPlane)
{
float f = (float) (1.0D / Math.tan(fov * ((float) Math.PI / 180F) / 2.0D));
DhMat4f matrix = new DhMat4f();
matrix.m00 = f / widthHeightRatio;
matrix.m11 = f;
matrix.m22 = (farClipPlane + nearClipPlane) / (nearClipPlane - farClipPlane);
matrix.m32 = -1.0F;
matrix.m23 = 2.0F * farClipPlane * nearClipPlane / (nearClipPlane - farClipPlane);
return matrix;
}
/** originally "translate" from Minecraft's MatrixStack */
public void multiplyTranslationMatrix(double x, double y, double z)
{ multiply(createTranslateMatrix((float) x, (float) y, (float) z)); }
public static DhMat4f createScaleMatrix(float x, float y, float z)
{
DhMat4f matrix = new DhMat4f();
matrix.m00 = x;
matrix.m11 = y;
matrix.m22 = z;
matrix.m33 = 1.0F;
return matrix;
}
public static DhMat4f createTranslateMatrix(float x, float y, float z)
{
DhMat4f matrix = new DhMat4f();
matrix.m00 = 1.0F;
matrix.m11 = 1.0F;
matrix.m22 = 1.0F;
matrix.m33 = 1.0F;
matrix.m03 = x;
matrix.m13 = y;
matrix.m23 = z;
return matrix;
}
//===============//
// Forge methods //
//===============//
public void add(DhApiMat4f other)
{
m00 += other.m00;
m01 += other.m01;
m02 += other.m02;
m03 += other.m03;
m10 += other.m10;
m11 += other.m11;
m12 += other.m12;
m13 += other.m13;
m20 += other.m20;
m21 += other.m21;
m22 += other.m22;
m23 += other.m23;
m30 += other.m30;
m31 += other.m31;
m32 += other.m32;
m33 += other.m33;
}
public void multiplyBackward(DhApiMat4f other)
{
DhApiMat4f copy = other.copy();
copy.multiply(this);
this.set(copy);
}
public void setTranslation(float x, float y, float z)
{
this.m00 = 1.0F;
this.m11 = 1.0F;
this.m22 = 1.0F;
this.m33 = 1.0F;
this.m03 = x;
this.m13 = y;
this.m23 = z;
}
public DhMat4f copy() { return new DhMat4f(this); }
}
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.core.util.math; package com.seibel.distanthorizons.core.util.math;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d; import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.coreapi.util.MathUtil; import com.seibel.distanthorizons.coreapi.util.MathUtil;
/** /**
@@ -31,16 +30,16 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
* @author James Seibel * @author James Seibel
* @version 11-18-2021 * @version 11-18-2021
*/ */
public class Vec3d extends DhApiVec3d public class DhVec3d extends DhApiVec3d
{ {
public static Vec3d XNeg = new Vec3d(-1.0F, 0.0F, 0.0F); public static DhVec3d XNeg = new DhVec3d(-1.0F, 0.0F, 0.0F);
public static Vec3d XPos = new Vec3d(1.0F, 0.0F, 0.0F); public static DhVec3d XPos = new DhVec3d(1.0F, 0.0F, 0.0F);
public static Vec3d YNeg = new Vec3d(0.0F, -1.0F, 0.0F); public static DhVec3d YNeg = new DhVec3d(0.0F, -1.0F, 0.0F);
public static Vec3d YPos = new Vec3d(0.0F, 1.0F, 0.0F); public static DhVec3d YPos = new DhVec3d(0.0F, 1.0F, 0.0F);
public static Vec3d ZNeg = new Vec3d(0.0F, 0.0F, -1.0F); public static DhVec3d ZNeg = new DhVec3d(0.0F, 0.0F, -1.0F);
public static Vec3d ZPos = new Vec3d(0.0F, 0.0F, 1.0F); public static DhVec3d ZPos = new DhVec3d(0.0F, 0.0F, 1.0F);
public static final Vec3d ZERO_VECTOR = new Vec3d(0.0D, 0.0D, 0.0D); public static final DhVec3d ZERO_VECTOR = new DhVec3d(0.0D, 0.0D, 0.0D);
@@ -48,26 +47,26 @@ public class Vec3d extends DhApiVec3d
// constructors // // constructors //
//==============// //==============//
public Vec3d() { } public DhVec3d() { }
public Vec3d(double x, double y, double z) public DhVec3d(double x, double y, double z)
{ {
this.x = x; this.x = x;
this.y = y; this.y = y;
this.z = z; this.z = z;
} }
public Vec3d(DhApiVec3d that) public DhVec3d(DhApiVec3d that)
{ {
this.x = that.x; this.x = that.x;
this.y = that.y; this.y = that.y;
this.z = that.z; this.z = that.z;
} }
public Vec3d(double[] values) { this.set(values); } public DhVec3d(double[] values) { this.set(values); }
public Vec3d copy() { return new Vec3d(this); } public DhVec3d copy() { return new DhVec3d(this); }
@@ -110,29 +109,29 @@ public class Vec3d extends DhApiVec3d
this.z += z; this.z += z;
} }
public void add(Vec3d vector) public void add(DhVec3d vector)
{ {
this.x += vector.x; this.x += vector.x;
this.y += vector.y; this.y += vector.y;
this.z += vector.z; this.z += vector.z;
} }
public void subtract(Vec3d vector) public void subtract(DhVec3d vector)
{ {
this.x -= vector.x; this.x -= vector.x;
this.y -= vector.y; this.y -= vector.y;
this.z -= vector.z; this.z -= vector.z;
} }
public double dotProduct(Vec3d vector) { return this.x * vector.x + this.y * vector.y + this.z * vector.z; } public double dotProduct(DhVec3d vector) { return this.x * vector.x + this.y * vector.y + this.z * vector.z; }
public Vec3d normalize() public DhVec3d normalize()
{ {
double value = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); double value = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z);
return value < 1.0E-4D ? ZERO_VECTOR : new Vec3d(this.x / value, this.y / value, this.z / value); return value < 1.0E-4D ? ZERO_VECTOR : new DhVec3d(this.x / value, this.y / value, this.z / value);
} }
public void crossProduct(Vec3d vector) public void crossProduct(DhVec3d vector)
{ {
double f = this.x; double f = this.x;
double f1 = this.y; double f1 = this.y;
@@ -168,9 +167,9 @@ public class Vec3d extends DhApiVec3d
+ Math.pow(a.z - b.z, 2)); + Math.pow(a.z - b.z, 2));
} }
/** @see Vec3d#getSquaredDistance(DhApiVec3d, DhApiVec3d) */ /** @see DhVec3d#getSquaredDistance(DhApiVec3d, DhApiVec3d) */
public double getSquaredDistance(DhApiVec3d other) { return getSquaredDistance(this, other); } public double getSquaredDistance(DhApiVec3d other) { return getSquaredDistance(this, other); }
/** slightly faster version of {@link Vec3d#getDistance} */ /** slightly faster version of {@link DhVec3d#getDistance} */
public static double getSquaredDistance(DhApiVec3d a, DhApiVec3d b) public static double getSquaredDistance(DhApiVec3d a, DhApiVec3d b)
{ {
return Math.pow(a.x - b.x, 2) return Math.pow(a.x - b.x, 2)
@@ -178,7 +177,7 @@ public class Vec3d extends DhApiVec3d
+ Math.pow(a.z - b.z, 2); + Math.pow(a.z - b.z, 2);
} }
/** @see Vec3d#getHorizontalDistance(DhApiVec3d, DhApiVec3d) */ /** @see DhVec3d#getHorizontalDistance(DhApiVec3d, DhApiVec3d) */
public double getHorizontalDistance(DhApiVec3d other) { return getHorizontalDistance(this, other); } public double getHorizontalDistance(DhApiVec3d other) { return getHorizontalDistance(this, other); }
/** Gets the distance between points A and B, ignoring Y height. */ /** Gets the distance between points A and B, ignoring Y height. */
public static double getHorizontalDistance(DhApiVec3d a, DhApiVec3d b) public static double getHorizontalDistance(DhApiVec3d a, DhApiVec3d b)
@@ -29,29 +29,29 @@ import com.seibel.distanthorizons.coreapi.util.MathUtil;
* @author James Seibel * @author James Seibel
* @version 11-11-2021 * @version 11-11-2021
*/ */
public class Vec3f extends DhApiVec3f public class DhVec3f extends DhApiVec3f
{ {
//==============// //==============//
// constructors // // constructors //
//==============// //==============//
public Vec3f() { this(0,0,0); } public DhVec3f() { this(0,0,0); }
public Vec3f(float x, float y, float z) public DhVec3f(float x, float y, float z)
{ {
this.x = x; this.x = x;
this.y = y; this.y = y;
this.z = z; this.z = z;
} }
public Vec3f(DhApiVec3f pos) public DhVec3f(DhApiVec3f pos)
{ {
this.x = pos.x; this.x = pos.x;
this.y = pos.y; this.y = pos.y;
this.z = pos.z; this.z = pos.z;
} }
public Vec3f(Vec3d pos) public DhVec3f(DhVec3d pos)
{ {
this.x = (float) pos.x; this.x = (float) pos.x;
this.y = (float) pos.y; this.y = (float) pos.y;
@@ -93,21 +93,21 @@ public class Vec3f extends DhApiVec3f
this.z += z; this.z += z;
} }
public void add(Vec3f vector) public void add(DhVec3f vector)
{ {
this.x += vector.x; this.x += vector.x;
this.y += vector.y; this.y += vector.y;
this.z += vector.z; this.z += vector.z;
} }
public void subtract(Vec3f vector) public void subtract(DhVec3f vector)
{ {
this.x -= vector.x; this.x -= vector.x;
this.y -= vector.y; this.y -= vector.y;
this.z -= vector.z; this.z -= vector.z;
} }
public float dotProduct(Vec3f vector) { return this.x * vector.x + this.y * vector.y + this.z * vector.z; } public float dotProduct(DhVec3f vector) { return this.x * vector.x + this.y * vector.y + this.z * vector.z; }
/** @return true if normalization had to be done */ /** @return true if normalization had to be done */
public boolean normalize() public boolean normalize()
@@ -127,7 +127,7 @@ public class Vec3f extends DhApiVec3f
} }
} }
public void crossProduct(Vec3f vector) public void crossProduct(DhVec3f vector)
{ {
float f = this.x; float f = this.x;
float f1 = this.y; float f1 = this.y;
@@ -167,6 +167,6 @@ public class Vec3f extends DhApiVec3f
this.z = z; this.z = z;
} }
public Vec3f copy() { return new Vec3f(this.x, this.y, this.z); } public DhVec3f copy() { return new DhVec3f(this.x, this.y, this.z); }
} }
@@ -29,27 +29,27 @@ import com.seibel.distanthorizons.api.objects.math.DhApiVec3i;
* @author James Seibel * @author James Seibel
* @version 2022-11-19 * @version 2022-11-19
*/ */
public class Vec3i extends DhApiVec3i // extends the API object so it can be returned through the API public class DhVec3i extends DhApiVec3i // extends the API object so it can be returned through the API
{ {
public static Vec3i XNeg = new Vec3i(-1, 0, 0); public static DhVec3i XNeg = new DhVec3i(-1, 0, 0);
public static Vec3i XPos = new Vec3i(1, 0, 0); public static DhVec3i XPos = new DhVec3i(1, 0, 0);
public static Vec3i YNeg = new Vec3i(0, -1, 0); public static DhVec3i YNeg = new DhVec3i(0, -1, 0);
public static Vec3i YPos = new Vec3i(0, 1, 0); public static DhVec3i YPos = new DhVec3i(0, 1, 0);
public static Vec3i ZNeg = new Vec3i(0, 0, -1); public static DhVec3i ZNeg = new DhVec3i(0, 0, -1);
public static Vec3i ZPos = new Vec3i(0, 0, 1); public static DhVec3i ZPos = new DhVec3i(0, 0, 1);
// x,y,z variables are handled in the parent object // x,y,z variables are handled in the parent object
public Vec3i() public DhVec3i()
{ {
this.x = 0; this.x = 0;
this.y = 0; this.y = 0;
this.z = 0; this.z = 0;
} }
public Vec3i(int x, int y, int z) public DhVec3i(int x, int y, int z)
{ {
this.x = x; this.x = x;
this.y = y; this.y = y;
@@ -93,14 +93,14 @@ public class Vec3i extends DhApiVec3i // extends the API object so it can be ret
this.z += z; this.z += z;
} }
public void add(Vec3i vector) public void add(DhVec3i vector)
{ {
this.x += vector.x; this.x += vector.x;
this.y += vector.y; this.y += vector.y;
this.z += vector.z; this.z += vector.z;
} }
public void subtract(Vec3i vector) public void subtract(DhVec3i vector)
{ {
this.x -= vector.x; this.x -= vector.x;
this.y -= vector.y; this.y -= vector.y;
@@ -116,7 +116,7 @@ public class Vec3i extends DhApiVec3i // extends the API object so it can be ret
return (xAdd * xAdd) + (yAdd * yAdd) + (zAdd * zAdd); return (xAdd * xAdd) + (yAdd * yAdd) + (zAdd * zAdd);
} }
public int distManhattan(Vec3i otherVec) public int distManhattan(DhVec3i otherVec)
{ {
float xSub = Math.abs(otherVec.x - this.x); float xSub = Math.abs(otherVec.x - this.x);
float ySub = Math.abs(otherVec.y - this.y); float ySub = Math.abs(otherVec.y - this.y);
@@ -125,30 +125,30 @@ public class Vec3i extends DhApiVec3i // extends the API object so it can be ret
} }
/** inner product */ /** inner product */
public float dotProduct(Vec3i vector) public float dotProduct(DhVec3i vector)
{ {
return (this.x * vector.x) + (this.y * vector.y) + (this.z * vector.z); return (this.x * vector.x) + (this.y * vector.y) + (this.z * vector.z);
} }
/** Cross product */ /** Cross product */
public Vec3i cross(Vec3i otherVec) public DhVec3i cross(DhVec3i otherVec)
{ {
return new Vec3i( return new DhVec3i(
(this.y * otherVec.z) - (this.z * otherVec.y), (this.y * otherVec.z) - (this.z * otherVec.y),
(this.z * otherVec.x) - (this.x * otherVec.z), (this.z * otherVec.x) - (this.x * otherVec.z),
(this.x * otherVec.y) - (this.y * otherVec.x)); (this.x * otherVec.y) - (this.y * otherVec.x));
} }
public Vec3i copy() public DhVec3i copy()
{ {
return new Vec3i(this.x, this.y, this.z); return new DhVec3i(this.x, this.y, this.z);
} }
// Forge start // Forge start
public Vec3i(int[] values) { this.set(values); } public DhVec3i(int[] values) { this.set(values); }
public void set(int[] values) public void set(int[] values)
{ {
@@ -1,249 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util.math;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import java.nio.FloatBuffer;
/**
* An (almost) exact copy of Minecraft's 1.16.5
* implementation of a 4x4 float matrix.
*
* @author James Seibel
* @version 11-11-2021
*/
public class Mat4f extends DhApiMat4f
{
//==============//
// constructors //
//==============//
public Mat4f() { /* all values are 0 */ }
public Mat4f(DhApiMat4f sourceMatrix) { super(sourceMatrix); }
/** Expects the values of the input buffer to be in row major order (AKA rows then columns) */
public Mat4f(FloatBuffer buffer) { this(buffer.array()); }
/** Expects the values of the input array to be in row major order (AKA rows then columns) */
public Mat4f(float[] values) { super(values); }
public Mat4f(Matrix4fc sourceMatrix) { this(convertJomlMatrixToArray(sourceMatrix)); }
private static float[] convertJomlMatrixToArray(Matrix4fc sourceMatrix)
{
FloatBuffer buffer = FloatBuffer.allocate(16);
buffer.put(bufferIndex(0, 0), sourceMatrix.m00());
buffer.put(bufferIndex(0, 1), sourceMatrix.m01());
buffer.put(bufferIndex(0, 2), sourceMatrix.m02());
buffer.put(bufferIndex(0, 3), sourceMatrix.m03());
buffer.put(bufferIndex(1, 0), sourceMatrix.m10());
buffer.put(bufferIndex(1, 1), sourceMatrix.m11());
buffer.put(bufferIndex(1, 2), sourceMatrix.m12());
buffer.put(bufferIndex(1, 3), sourceMatrix.m13());
buffer.put(bufferIndex(2, 0), sourceMatrix.m20());
buffer.put(bufferIndex(2, 1), sourceMatrix.m21());
buffer.put(bufferIndex(2, 2), sourceMatrix.m22());
buffer.put(bufferIndex(2, 3), sourceMatrix.m23());
buffer.put(bufferIndex(3, 0), sourceMatrix.m30());
buffer.put(bufferIndex(3, 1), sourceMatrix.m31());
buffer.put(bufferIndex(3, 2), sourceMatrix.m32());
buffer.put(bufferIndex(3, 3), sourceMatrix.m33());
return buffer.array();
}
private static int bufferIndex(int xIndex, int zIndex) { return (zIndex * 4) + xIndex; }
public void store(FloatBuffer floatBuffer)
{
floatBuffer.put(bufferIndex(0, 0), this.m00);
floatBuffer.put(bufferIndex(0, 1), this.m01);
floatBuffer.put(bufferIndex(0, 2), this.m02);
floatBuffer.put(bufferIndex(0, 3), this.m03);
floatBuffer.put(bufferIndex(1, 0), this.m10);
floatBuffer.put(bufferIndex(1, 1), this.m11);
floatBuffer.put(bufferIndex(1, 2), this.m12);
floatBuffer.put(bufferIndex(1, 3), this.m13);
floatBuffer.put(bufferIndex(2, 0), this.m20);
floatBuffer.put(bufferIndex(2, 1), this.m21);
floatBuffer.put(bufferIndex(2, 2), this.m22);
floatBuffer.put(bufferIndex(2, 3), this.m23);
floatBuffer.put(bufferIndex(3, 0), this.m30);
floatBuffer.put(bufferIndex(3, 1), this.m31);
floatBuffer.put(bufferIndex(3, 2), this.m32);
floatBuffer.put(bufferIndex(3, 3), this.m33);
}
public static Matrix4f createJomlMatrix(DhApiMat4f matrix)
{
return new Matrix4f(
matrix.m00, matrix.m10, matrix.m20, matrix.m30,
matrix.m01, matrix.m11, matrix.m21, matrix.m31,
matrix.m02, matrix.m12, matrix.m22, matrix.m32,
matrix.m03, matrix.m13, matrix.m23, matrix.m33
);
}
public Matrix4f createJomlMatrix()
{
return new Matrix4f(
this.m00, this.m10, this.m20, this.m30,
this.m01, this.m11, this.m21, this.m31,
this.m02, this.m12, this.m22, this.m32,
this.m03, this.m13, this.m23, this.m33
);
}
//=========//
// methods //
//=========//
public static Mat4f perspective(double fov, float widthHeightRatio, float nearClipPlane, float farClipPlane)
{
float f = (float) (1.0D / Math.tan(fov * ((float) Math.PI / 180F) / 2.0D));
Mat4f matrix = new Mat4f();
matrix.m00 = f / widthHeightRatio;
matrix.m11 = f;
matrix.m22 = (farClipPlane + nearClipPlane) / (nearClipPlane - farClipPlane);
matrix.m32 = -1.0F;
matrix.m23 = 2.0F * farClipPlane * nearClipPlane / (nearClipPlane - farClipPlane);
return matrix;
}
/** originally "translate" from Minecraft's MatrixStack */
public void multiplyTranslationMatrix(double x, double y, double z)
{ multiply(createTranslateMatrix((float) x, (float) y, (float) z)); }
public static Mat4f createScaleMatrix(float x, float y, float z)
{
Mat4f matrix = new Mat4f();
matrix.m00 = x;
matrix.m11 = y;
matrix.m22 = z;
matrix.m33 = 1.0F;
return matrix;
}
public static Mat4f createTranslateMatrix(float x, float y, float z)
{
Mat4f matrix = new Mat4f();
matrix.m00 = 1.0F;
matrix.m11 = 1.0F;
matrix.m22 = 1.0F;
matrix.m33 = 1.0F;
matrix.m03 = x;
matrix.m13 = y;
matrix.m23 = z;
return matrix;
}
//===============//
// Forge methods //
//===============//
public void set(DhApiMat4f mat)
{
this.m00 = mat.m00;
this.m01 = mat.m01;
this.m02 = mat.m02;
this.m03 = mat.m03;
this.m10 = mat.m10;
this.m11 = mat.m11;
this.m12 = mat.m12;
this.m13 = mat.m13;
this.m20 = mat.m20;
this.m21 = mat.m21;
this.m22 = mat.m22;
this.m23 = mat.m23;
this.m30 = mat.m30;
this.m31 = mat.m31;
this.m32 = mat.m32;
this.m33 = mat.m33;
}
public void add(DhApiMat4f other)
{
m00 += other.m00;
m01 += other.m01;
m02 += other.m02;
m03 += other.m03;
m10 += other.m10;
m11 += other.m11;
m12 += other.m12;
m13 += other.m13;
m20 += other.m20;
m21 += other.m21;
m22 += other.m22;
m23 += other.m23;
m30 += other.m30;
m31 += other.m31;
m32 += other.m32;
m33 += other.m33;
}
public void multiplyBackward(DhApiMat4f other)
{
DhApiMat4f copy = other.copy();
copy.multiply(this);
this.set(copy);
}
public void setTranslation(float x, float y, float z)
{
this.m00 = 1.0F;
this.m11 = 1.0F;
this.m22 = 1.0F;
this.m33 = 1.0F;
this.m03 = x;
this.m13 = y;
this.m23 = z;
}
/**
* Changes the values that store the clipping planes.
* Formula for calculating matrix values is the same that OpenGL uses when making matrices.
*
* @param nearClip New near clipping plane value.
* @param farClip New far clipping plane value.
*/
public void setClipPlanes(float nearClip, float farClip)
{
//convert to matrix values, formula copied from a textbook / openGL specification.
float matNearClip = -((farClip + nearClip) / (farClip - nearClip));
float matFarClip = -((2 * farClip * nearClip) / (farClip - nearClip));
//set new values for the clip planes.
this.m22 = matNearClip;
this.m23 = matFarClip;
}
public Mat4f copy() { return new Mat4f(this); }
}
@@ -331,7 +331,7 @@ public class PhantomArrayListPool
if (pool.logGarbageCollectedStacks if (pool.logGarbageCollectedStacks
&& checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case && checkout.allocationStackTrace != null) // stack trace shouldn't be null, but just in case
{ {
putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList); PhantomLoggingHelper.putAndIncrementTrackingString(checkout.allocationStackTrace, allocationStackTraceCountPairList);
} }
} }
else else
@@ -363,18 +363,7 @@ public class PhantomArrayListPool
// log stack traces if present // log stack traces if present
if (pool.logGarbageCollectedStacks) if (pool.logGarbageCollectedStacks)
{ {
// high numbers first PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, allocationStackTraceCountPairList);
allocationStackTraceCountPairList.sort((a, b) -> Integer.compare(b.second.get(), a.second.get()));
StringBuilder stringBuilder = new StringBuilder();
for (int j = 0; j < allocationStackTraceCountPairList.size(); j++)
{
int count = allocationStackTraceCountPairList.get(j).second.get();
String stack = allocationStackTraceCountPairList.get(j).first;
stringBuilder.append(count).append(". ").append(stack).append("\n");
}
LOGGER.warn("Stacks: ["+ allocationStackTraceCountPairList.size()+"]\n" + stringBuilder.toString());
} }
} }
} }
@@ -389,36 +378,6 @@ public class PhantomArrayListPool
} }
} }
} }
/**
* This was separated out so it could be used for other string pair lists.
* James originally had an idea to add a shorter static string
* ID to each allocated {@link PhantomArrayListCheckout} as a simpler version of the stack trace,
* however it became a bit more difficult and messy than he wanted to deal with, so for now we just
* have the stack trace.
*/
private static void putAndIncrementTrackingString(
String key,
ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList)
{
// sequential search, for the number of elements we're dealing with (less than 20)
// this should be sufficiently fast
boolean pairFound = false;
for (int i = 0; i < allocationStackTraceCountPairList.size(); i++)
{
Pair<String, AtomicInteger> possiblePair = allocationStackTraceCountPairList.get(i);
if (possiblePair.first.equals(key))
{
possiblePair.second.getAndIncrement();
pairFound = true;
break;
}
}
if (!pairFound)
{
allocationStackTraceCountPairList.add(new Pair<>(key, new AtomicInteger(1)));
}
}
///endregion ///endregion
@@ -0,0 +1,232 @@
package com.seibel.distanthorizons.core.util.objects.pooling;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicInteger;
public class PhantomLoggingHelper
{
/**
* This was separated out so it could be used for other string pair lists.
* James originally had an idea to add a shorter static string
* ID to each allocated {@link PhantomArrayListCheckout} as a simpler version of the stack trace,
* however it became a bit more difficult and messy than he wanted to deal with, so for now we just
* have the stack trace.
*/
public static void putAndIncrementTrackingString(
String key,
ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList)
{
// sequential search, for the number of elements we're dealing with (less than 20)
// this should be sufficiently fast
boolean pairFound = false;
for (int i = 0; i < allocationStackTraceCountPairList.size(); i++)
{
Pair<String, AtomicInteger> possiblePair = allocationStackTraceCountPairList.get(i);
if (possiblePair.first.equals(key))
{
possiblePair.second.getAndIncrement();
pairFound = true;
break;
}
}
if (!pairFound)
{
allocationStackTraceCountPairList.add(new Pair<>(key, new AtomicInteger(1)));
}
}
public static void LogAllocationStackTracePairCounts(DhLogger logger, ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList)
{
// high numbers first
allocationStackTraceCountPairList.sort((a, b) -> Integer.compare(b.second.get(), a.second.get()));
StringBuilder stringBuilder = new StringBuilder();
for (int j = 0; j < allocationStackTraceCountPairList.size(); j++)
{
int count = allocationStackTraceCountPairList.get(j).second.get();
String stack = allocationStackTraceCountPairList.get(j).first;
stringBuilder.append(count).append(". ").append(stack).append("\n");
}
logger.warn("Stacks: ["+ allocationStackTraceCountPairList.size()+"]\n" + stringBuilder.toString());
}
//================//
// helper classes //
//================//
//region
/**
* Can be quickly added to a {@link AutoCloseable} implementing
* class to confirm it's being properly closed.
*/
public static class BasicPhantomReference implements AutoCloseable
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
/** if enabled the number of GC'ed buffers will be logged */
private static final boolean LOG_PHANTOM_RECOVERY = true;
/**
* If enabled the GC'ed buffers allocation/upload stacks will be logged.
* Note: due to how the buffers are often run on the render thread,
* these stacks will likely only be of limited use.
* For more robust debugging it would likely be best to somehow track
* the stacks of where these calls are happening before they're queued
* for the render thread.
*/
private static final boolean LOG_PHANTOM_ALLOCATION_STACKS = true;
private static final int PHANTOM_REF_CHECK_TIME_IN_MS = 5 * 1000;
private static final ReferenceQueue<BasicPhantomReference> PHANTOM_REFERENCE_QUEUE = new ReferenceQueue<>();
private static final ConcurrentHashMap<PhantomReference<? extends BasicPhantomReference>, Class<?>> PHANTOM_TO_PARENT_CLASS = new ConcurrentHashMap<>();
private static final ThreadPoolExecutor CLEANUP_THREAD = ThreadUtil.makeSingleDaemonThreadPool("BasicPhantom Cleanup");
private final Class<?> parentClass;
private final PhantomReference<? extends BasicPhantomReference> phantomReference;
//==============//
// constructors //
//==============//
//region
static { CLEANUP_THREAD.execute(() -> runPhantomReferenceCleanupLoop()); }
public BasicPhantomReference(Class<?> parentClass)
{
this.parentClass = parentClass;
this.phantomReference = new PhantomReference<>(this, PHANTOM_REFERENCE_QUEUE);
PHANTOM_TO_PARENT_CLASS.put(this.phantomReference, this.parentClass);
}
//endregion
//================//
// base overrides //
//================//
//region
@Override
public void close()
{
this.phantomReference.clear();
PHANTOM_TO_PARENT_CLASS.remove(this.phantomReference);
}
//endregion
//================//
// static cleanup //
//================//
//region
private static void runPhantomReferenceCleanupLoop()
{
// these arrays are stored here so they don't have to be re-allocated each loop
ArrayList<Pair<String, AtomicInteger>> allocationStackTraceCountPairList = new ArrayList<>();
ArrayList<Pair<String, AtomicInteger>> parentClassNameCountPairList = new ArrayList<>();
while (true)
{
allocationStackTraceCountPairList.clear();
parentClassNameCountPairList.clear();
try
{
try
{
Thread.sleep(PHANTOM_REF_CHECK_TIME_IN_MS);
}
catch (InterruptedException ignore) { }
int collectedCount = 0;
Reference<? extends BasicPhantomReference> phantomRef = PHANTOM_REFERENCE_QUEUE.poll();
while (phantomRef != null)
{
// destroy the buffer if it hasn't been cleared yet
Class<?> parentClass = PHANTOM_TO_PARENT_CLASS.remove((PhantomReference<? extends BasicPhantomReference>)phantomRef); // cast to make IntelliJ happy
{
String parentClassName = "NULL";
if (parentClass != null)
{
parentClassName = parentClass.getSimpleName();
}
PhantomLoggingHelper.putAndIncrementTrackingString(parentClassName, parentClassNameCountPairList);
//LOGGER.info("Phantom collected for class: [" + parentClassName + "]");
}
//if (LOG_PHANTOM_ALLOCATION_STACKS) // stack trace shouldn't be null, but just in case
//{
// String stack = BUFFER_ID_TO_ALLOCATION_STRING.get(idRef);
// if (stack != null)
// {
// PhantomLoggingHelper.putAndIncrementTrackingString(stack, allocationStackTraceCountPairList);
// }
//}
collectedCount++;
phantomRef = PHANTOM_REFERENCE_QUEUE.poll();
}
if (LOG_PHANTOM_RECOVERY)
{
// we only want to log when something has been returned
if (collectedCount != 0)
{
LOGGER.warn("Phantoms collected: ["+ F3Screen.NUMBER_FORMAT.format(collectedCount)+"].");
PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, parentClassNameCountPairList);
//// log stack traces if present
//if (LOG_PHANTOM_ALLOCATION_STACKS)
//{
// PhantomLoggingHelper.LogAllocationStackTracePairCounts(LOGGER, allocationStackTraceCountPairList);
//}
}
}
}
catch (Exception e)
{
LOGGER.error("Unexpected error in buffer cleanup thread: [" + e.getMessage() + "].", e);
}
}
}
//endregion
}
//endregion
}
@@ -73,10 +73,17 @@ public class RateLimitedThreadPoolExecutor extends ThreadPoolExecutor
{ {
super.afterExecute(runnable, throwable); super.afterExecute(runnable, throwable);
double ratio = this.runTimeRatioConfig.get();
if (ratio >= 1.0)
{
// Avoid sleeping for 0 time
return;
}
try try
{ {
long runTime = System.nanoTime() - this.runStartTime.get(); long runTime = System.nanoTime() - this.runStartTime.get();
Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / this.runTimeRatioConfig.get() - runTime))); Thread.sleep(TimeUnit.NANOSECONDS.toMillis((long) (runTime / ratio - runTime)));
} }
catch (InterruptedException ignore) catch (InterruptedException ignore)
{ {
@@ -60,7 +60,7 @@ public class ThreadPoolUtil
public static PriorityTaskPicker.Executor getWorldGenExecutor() { return worldGenThreadPool; } public static PriorityTaskPicker.Executor getWorldGenExecutor() { return worldGenThreadPool; }
public static final String CLEANUP_THREAD_NAME = "Cleanup"; public static final String CLEANUP_THREAD_NAME = "Cleanup";
private static final ThreadPoolExecutor cleanupThreadPool = ThreadUtil.makeSingleThreadPool(CLEANUP_THREAD_NAME); private static final ThreadPoolExecutor cleanupThreadPool = ThreadUtil.makeSingleDaemonThreadPool(CLEANUP_THREAD_NAME);
/** not null since cleanup always needs to be run even when DH has been shut down */ /** not null since cleanup always needs to be run even when DH has been shut down */
@NotNull @NotNull
public static ThreadPoolExecutor getCleanupExecutor() { return cleanupThreadPool; } public static ThreadPoolExecutor getCleanupExecutor() { return cleanupThreadPool; }
@@ -170,7 +170,7 @@ public class ThreadPoolUtil
*/ */
public static boolean worldGenThreadsCanRun() public static boolean worldGenThreadsCanRun()
{ {
double cameraSpeed = ClientApi.INSTANCE.cameraSpeedRollingAverage.getAverage(); double cameraSpeed = ClientApi.INSTANCE.getAvgCameraSpeed();
// stop these threads if moving a little bit slower than max elytra speed // stop these threads if moving a little bit slower than max elytra speed
double maxAllowedSpeed = (LodUtil.ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC - 10.0); double maxAllowedSpeed = (LodUtil.ROCKET_ELYTRA_SPEED_IN_BLOCKS_PER_SEC - 10.0);
if (cameraSpeed > maxAllowedSpeed) if (cameraSpeed > maxAllowedSpeed)
@@ -1,5 +1,6 @@
package com.seibel.distanthorizons.core.world; package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure; import com.seibel.distanthorizons.core.file.structure.LocalSaveStructure;
import com.seibel.distanthorizons.core.level.AbstractDhServerLevel; import com.seibel.distanthorizons.core.level.AbstractDhServerLevel;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
@@ -8,6 +9,7 @@ import com.seibel.distanthorizons.core.multiplayer.server.ServerPlayerStateManag
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@@ -138,6 +140,7 @@ public abstract class AbstractDhServerWorld<TDhServerLevel extends AbstractDhSer
if (serverLevelWrapper != null) if (serverLevelWrapper != null)
{ {
serverLevelWrapper.onUnload(); serverLevelWrapper.onUnload();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(serverLevelWrapper));
} }
@@ -19,14 +19,18 @@
package com.seibel.distanthorizons.core.world; package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.level.DhClientServerLevel; import com.seibel.distanthorizons.core.level.DhClientServerLevel;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.*; import java.util.*;
@@ -34,7 +38,18 @@ import java.util.concurrent.CompletableFuture;
public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLevel> implements IDhClientWorld
{ {
private final Set<DhClientServerLevel> dhLevels = Collections.synchronizedSet(new HashSet<>()); /**
* Having a set of level wrappers is done to handle an issue where the client
* level would get unloaded when jumping back and forth between dimensions. <br><br>
*
* We might have more than one {@link ILevelWrapper} pointing to the same {@link IDhLevel}
* since they're not immediately unloaded, and we don't want to unload the {@link IDhLevel}
* until all the {@link ILevelWrapper} for that {@link IDhLevel} have been unloaded.
* Any stale {@link IDhLevel} references should disappear on their own after about
* 30 seconds or so thanks to the automatic cleanup.
*/
private final Map<DhClientServerLevel, Set<ILevelWrapper>> clientLevelWrapperSetByDhLevel
= Collections.synchronizedMap(new HashMap<>());
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer"); private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
@@ -47,14 +62,14 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
public DhClientServerWorld() public DhClientServerWorld()
{ {
super(EWorldEnvironment.CLIENT_SERVER); super(EWorldEnvironment.CLIENT_SERVER);
LOGGER.info("Started DhWorld of type " + this.environment); LOGGER.info("Started DhWorld of type [" + this.environment + "].");
this.clientTickTimer.scheduleAtFixedRate(new TimerTask() this.clientTickTimer.scheduleAtFixedRate(new TimerTask()
{ {
@Override @Override
public void run() public void run()
{ {
DhClientServerWorld.this.dhLevels.forEach(DhClientServerLevel::clientTick); DhClientServerWorld.this.clientLevelWrapperSetByDhLevel.keySet().forEach(DhClientServerLevel::clientTick);
} }
}, 0, IDhClientWorld.TICK_RATE_IN_MS); }, 0, IDhClientWorld.TICK_RATE_IN_MS);
} }
@@ -75,16 +90,22 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
try try
{ {
DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager()); DhClientServerLevel level = new DhClientServerLevel(this.saveStructure, (IServerLevelWrapper) levelWrapper, this.getServerPlayerStateManager());
this.dhLevels.add(level); this.clientLevelWrapperSetByDhLevel.computeIfAbsent(level, (clientServerLevel) -> Collections.synchronizedSet(new HashSet<>()));
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(wrapper));
return level; return level;
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.fatal("Failed to load client-server level, error: ["+e.getMessage()+"].", e); LOGGER.fatal("Failed to load client-server level, error: ["+e.getMessage()+"].", e);
String r = MinecraftTextFormat.RED;
String y = MinecraftTextFormat.YELLOW;
String cf = MinecraftTextFormat.CLEAR_FORMATTING;
ClientApi.INSTANCE.showChatMessageNextFrame(// red text ClientApi.INSTANCE.showChatMessageNextFrame(// red text
MinecraftTextFormat.RED + "Distant Horizons: ClientServer level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" + r + "Distant Horizons: ClientServer level loading failed." + cf + "\n" +
"Unable to load level ["+levelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information."); "Unable to load level ["+y+levelWrapper.getDhIdentifier()+cf+"], LODs may not appear. See log for more information.\n" +
"");
return null; return null;
} }
@@ -92,11 +113,22 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
} }
else else
{ {
if (wrapper instanceof IClientLevelWrapper)
{
((IClientLevelWrapper) wrapper).markAccessed();
}
return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) -> return this.dhLevelByLevelWrapper.computeIfAbsent(wrapper, (levelWrapper) ->
{ {
if (!(levelWrapper instanceof IClientLevelWrapper))
{
LodUtil.assertNotReach("tryGetServerSideWrapper given a non-IClientLevelWrapper.");
}
IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper; IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) levelWrapper;
IServerLevelWrapper serverLevelWrapper = clientLevelWrapper.tryGetServerSideWrapper(); IServerLevelWrapper serverLevelWrapper = clientLevelWrapper.tryGetServerSideWrapper();
LodUtil.assertTrue(serverLevelWrapper != null); LodUtil.assertTrue(serverLevelWrapper != null);
if (!clientLevelWrapper.getDimensionType().equals(serverLevelWrapper.getDimensionType())) if (!clientLevelWrapper.getDimensionType().equals(serverLevelWrapper.getDimensionType()))
{ {
LodUtil.assertNotReach("tryGetServerSideWrapper returned a level for a different dimension. ClientLevelWrapper dim: [" + clientLevelWrapper.getDhIdentifier() + "] ServerLevelWrapper dim: [" + serverLevelWrapper.getDhIdentifier() + "]."); LodUtil.assertNotReach("tryGetServerSideWrapper returned a level for a different dimension. ClientLevelWrapper dim: [" + clientLevelWrapper.getDhIdentifier() + "] ServerLevelWrapper dim: [" + serverLevelWrapper.getDhIdentifier() + "].");
@@ -111,13 +143,14 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
level.startRenderer(); level.startRenderer();
clientLevelWrapper.setDhLevel(level); clientLevelWrapper.setDhLevel(level);
clientLevelWrapperSetByDhLevel.get(level).add(wrapper);
return level; return level;
}); });
} }
} }
@Override @Override
public void unloadLevel(@NotNull ILevelWrapper wrapper) public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
{ {
if (this.dhLevelByLevelWrapper.containsKey(wrapper)) if (this.dhLevelByLevelWrapper.containsKey(wrapper))
{ {
@@ -128,16 +161,33 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
DhClientServerLevel clientServerLevel = this.dhLevelByLevelWrapper.remove(wrapper); DhClientServerLevel clientServerLevel = this.dhLevelByLevelWrapper.remove(wrapper);
clientServerLevel.close(); clientServerLevel.close();
this.dhLevels.remove(clientServerLevel); this.clientLevelWrapperSetByDhLevel.remove(clientServerLevel);
} }
else else
{ {
// If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level, // If the level wrapper is a Client Level Wrapper, then that means the client side leaves the level,
// but note that the server side still has the level loaded. So, we don't want to unload the level, // but note that the server side still has the level loaded. So, we don't want to unload the level,
// we just want to stop rendering it. // we just want to stop rendering it.
this.dhLevelByLevelWrapper.remove(wrapper).stopRenderer(); // Ignore resource warning. The level obj is referenced elsewhere. DhClientServerLevel level = this.dhLevelByLevelWrapper.remove(wrapper); // Ignore resource warning. The level obj is referenced elsewhere.
Set<ILevelWrapper> wrappers = clientLevelWrapperSetByDhLevel.get(level);
if (wrappers != null)
{
wrappers.remove(wrapper);
}
if ((wrappers == null || wrappers.isEmpty())
&& level.isRendering())
{
level.stopRenderer();
}
wrapper.onUnload(); // We still want to unload the wrapper though.
} }
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
return true;
} }
return false;
} }
@@ -152,16 +202,24 @@ public class DhClientServerWorld extends AbstractDhServerWorld<DhClientServerLev
{ {
ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>(); ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>();
synchronized (this.dhLevels) synchronized (this.clientLevelWrapperSetByDhLevel)
{ {
// close each level // close each level
for (DhClientServerLevel level : this.dhLevels) for (DhClientServerLevel level : this.clientLevelWrapperSetByDhLevel.keySet())
{ {
// level wrapper shouldn't be null, but just in case // level wrapper shouldn't be null, but just in case
IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper(); IServerLevelWrapper serverLevelWrapper = level.getServerLevelWrapper();
if (serverLevelWrapper != null) if (serverLevelWrapper != null)
{ {
serverLevelWrapper.onUnload(); serverLevelWrapper.onUnload();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(serverLevelWrapper));
}
IClientLevelWrapper clientLevelWrapper = level.getClientLevelWrapper();
if (clientLevelWrapper != null)
{
clientLevelWrapper.onUnload();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(clientLevelWrapper));
} }
// close levels asynchronously to speed up // close levels asynchronously to speed up
@@ -19,32 +19,52 @@
package com.seibel.distanthorizons.core.world; package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.api.internal.ClientPluginChannelApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure; import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.level.DhClientLevel; import com.seibel.distanthorizons.core.level.DhClientLevel;
import com.seibel.distanthorizons.core.level.IDhLevel; import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState; import com.seibel.distanthorizons.core.multiplayer.client.ClientNetworkState;
import com.seibel.distanthorizons.core.util.TimerUtil; import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
{ {
private final ConcurrentHashMap<IClientLevelWrapper, DhClientLevel> levels;
public final ClientOnlySaveStructure saveStructure; public final ClientOnlySaveStructure saveStructure;
public final ClientNetworkState networkState = new ClientNetworkState(); public final ClientNetworkState networkState = new ClientNetworkState();
private final ConcurrentHashMap<String, DhClientLevel> clientLevelByDhId;
/**
* Having a set of level wrappers is done to handle an issue where the client
* level would get unloaded when jumping back and forth between dimensions. <br><br>
*
* We might have more than one {@link ILevelWrapper} pointing to the same {@link IDhLevel}
* since they're not immediately unloaded, and we don't want to unload the {@link IDhLevel}
* until all the {@link ILevelWrapper} for that {@link IDhLevel} have been unloaded.
* Any stale {@link IDhLevel} references should disappear on their own after about
* 30 seconds or so thanks to the automatic cleanup.
*/
private final Map<String, Set<IClientLevelWrapper>> clientLevelWrapperSetByDhId = new ConcurrentHashMap<>();
private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer"); private final Timer clientTickTimer = TimerUtil.CreateTimer("ClientTickTimer");
public final ClientPluginChannelApi pluginChannelApi = new ClientPluginChannelApi();
private static final long FIRST_LEVEL_LOAD_DELAY_IN_MS = 1_000;
/** Delay loading the first level to give the server some time to respond with level to actually load */
private long allowLoadingLevelsAfter = 0;
private final Set</* ClientLevel */ Object> levelInitRequestedClientLevels = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>()));
//==============// //==============//
@@ -56,16 +76,19 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
super(EWorldEnvironment.CLIENT_ONLY); super(EWorldEnvironment.CLIENT_ONLY);
this.saveStructure = new ClientOnlySaveStructure(); this.saveStructure = new ClientOnlySaveStructure();
this.levels = new ConcurrentHashMap<>(); this.clientLevelByDhId = new ConcurrentHashMap<>();
LOGGER.info("Started DhWorld of type " + this.environment); LOGGER.info("Started DhWorld of type " + this.environment);
this.pluginChannelApi.onJoinServer(networkState.getSession());
this.networkState.sendConfigMessage();
this.clientTickTimer.scheduleAtFixedRate(new TimerTask() this.clientTickTimer.scheduleAtFixedRate(new TimerTask()
{ {
@Override @Override
public void run() public void run()
{ {
DhClientWorld.this.levels.values().forEach(DhClientLevel::clientTick); DhClientWorld.this.clientLevelByDhId.values().forEach(DhClientLevel::clientTick);
} }
}, 0, IDhClientWorld.TICK_RATE_IN_MS); }, 0, IDhClientWorld.TICK_RATE_IN_MS);
} }
@@ -84,24 +107,99 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
return null; return null;
} }
return this.levels.computeIfAbsent((IClientLevelWrapper) wrapper, IClientLevelWrapper clientLevelWrapper = (IClientLevelWrapper) wrapper;
(clientLevelWrapper) -> clientLevelWrapper.markAccessed();
DhClientLevel storedLevel = this.clientLevelByDhId.computeIfAbsent(wrapper.getDhIdentifier(),
(key) -> createClientLevel(clientLevelWrapper)
);
if (storedLevel != null
&& storedLevel.getClientLevelWrapper() != wrapper)
{
unloadLevel(storedLevel.getLevelWrapper());
storedLevel = createClientLevel(clientLevelWrapper);
if (storedLevel != null)
{ {
try this.clientLevelByDhId.put(wrapper.getDhIdentifier(), storedLevel);
{ }
return new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState); }
} return storedLevel;
catch (Exception e) }
{ private DhClientLevel createClientLevel(@NotNull IClientLevelWrapper clientLevelWrapper)
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e); {
try
ClientApi.INSTANCE.showChatMessageNextFrame( {
MinecraftTextFormat.RED + "Distant Horizons: Client level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" + if (!this.ensureLevelKeyWhenAvailable(clientLevelWrapper))
"Unable to load level ["+clientLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information."); {
return null;
return null; }
}
}); DhClientLevel level = new DhClientLevel(this.saveStructure, clientLevelWrapper, this.networkState);
clientLevelWrapperSetByDhId.computeIfAbsent(clientLevelWrapper.getDhIdentifier(), (dhId) -> Collections.synchronizedSet(new HashSet<>())).add(clientLevelWrapper);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(clientLevelWrapper));
ClientApi.INSTANCE.loadWaitingChunksForLevel(clientLevelWrapper);
return level;
}
catch (Exception e)
{
LOGGER.fatal("Failed to load client level, error: ["+e.getMessage()+"].", e);
String r = MinecraftTextFormat.RED;
String y = MinecraftTextFormat.YELLOW;
String cf = MinecraftTextFormat.CLEAR_FORMATTING;
ClientApi.INSTANCE.showChatMessageNextFrame(
r + "Distant Horizons: Client level loading failed." + cf + "\n" +
"Unable to load level ["+y+clientLevelWrapper.getDhIdentifier()+cf+"], LODs may not appear. See log for more information. \n" +
"");
return null;
}
}
private boolean ensureLevelKeyWhenAvailable(@NotNull IClientLevelWrapper clientLevelWrapper)
{
if (!this.pluginChannelApi.allowLevelLoading(clientLevelWrapper))
{
LOGGER.debug("Client levels in this connection are managed by the server, skipping auto-load of: ["+clientLevelWrapper+"]");
// Instead of attempting to load themselves, send the config and wait for a server provided level key
this.sendLevelInitRequestIfNeed(clientLevelWrapper);
return false;
}
if (clientLevelWrapper instanceof IServerKeyedClientLevel)
{
this.sendLevelInitRequestIfNeed(clientLevelWrapper);
}
// Make non-keyed levels wait some delay since first attempt to load anything,
// so the server can reply to the level key request
if (!(clientLevelWrapper instanceof IServerKeyedClientLevel))
{
this.sendLevelInitRequestIfNeed(clientLevelWrapper);
if (this.allowLoadingLevelsAfter == 0)
{
this.allowLoadingLevelsAfter = System.currentTimeMillis() + FIRST_LEVEL_LOAD_DELAY_IN_MS;
}
return System.currentTimeMillis() >= this.allowLoadingLevelsAfter;
}
return true;
}
private void sendLevelInitRequestIfNeed(@NotNull IClientLevelWrapper clientLevelWrapper)
{
Object clientLevelObject = clientLevelWrapper.getWrappedMcObject();
if (clientLevelObject != null
&& this.levelInitRequestedClientLevels.add(clientLevelObject))
{
this.networkState.sendLevelInitRequest(clientLevelWrapper.getDimensionName());
}
} }
@Override @Override
@@ -112,28 +210,39 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
return null; return null;
} }
return this.levels.get(wrapper); return this.clientLevelByDhId.get(wrapper.getDhIdentifier());
} }
@Override @Override
public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.levels.values(); } public Iterable<? extends IDhLevel> getAllLoadedLevels() { return this.clientLevelByDhId.values(); }
@Override @Override
public int getLoadedLevelCount() { return this.levels.size(); } public int getLoadedLevelCount() { return this.clientLevelByDhId.size(); }
@Override @Override
public void unloadLevel(@NotNull ILevelWrapper wrapper) public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
{ {
if (!(wrapper instanceof IClientLevelWrapper)) if (!(wrapper instanceof IClientLevelWrapper))
{ {
return; return false;
} }
if (this.levels.containsKey(wrapper)) if (this.clientLevelByDhId.containsKey(wrapper.getDhIdentifier()))
{ {
LOGGER.info("Unloading level " + this.levels.get(wrapper)); LOGGER.info("Unloading level [" + this.clientLevelByDhId.get(wrapper.getDhIdentifier()) + "].");
wrapper.onUnload(); wrapper.onUnload();
this.levels.remove(wrapper).close(); Set<IClientLevelWrapper> wrapperSet = this.clientLevelWrapperSetByDhId.get(wrapper.getDhIdentifier());
wrapperSet.remove(wrapper);
if (wrapperSet.isEmpty())
{
this.clientLevelByDhId.remove(wrapper.getDhIdentifier()).close();
}
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
return true;
} }
return false;
} }
@Override @Override
@@ -147,15 +256,17 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
public void close() public void close()
{ {
this.networkState.close(); this.networkState.close();
this.pluginChannelApi.reset();
ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>(); ArrayList<CompletableFuture<Void>> closeFutures = new ArrayList<>();
for (DhClientLevel dhClientLevel : this.levels.values()) for (DhClientLevel dhClientLevel : this.clientLevelByDhId.values())
{ {
// level wrapper shouldn't be null, but just in case // level wrapper shouldn't be null, but just in case
IClientLevelWrapper clientLevelWrapper = dhClientLevel.getClientLevelWrapper(); IClientLevelWrapper clientLevelWrapper = dhClientLevel.getClientLevelWrapper();
if (clientLevelWrapper != null) if (clientLevelWrapper != null)
{ {
clientLevelWrapper.onUnload(); clientLevelWrapper.onUnload();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(clientLevelWrapper));
} }
@@ -177,7 +288,9 @@ public class DhClientWorld extends AbstractDhWorld implements IDhClientWorld
future.join(); future.join();
} }
this.levels.clear(); this.clientLevelByDhId.clear();
this.clientLevelWrapperSetByDhId.clear();
this.levelInitRequestedClientLevels.clear();
this.clientTickTimer.cancel(); this.clientTickTimer.cancel();
LOGGER.info("Closed DhWorld of type [" + this.environment + "]."); LOGGER.info("Closed DhWorld of type [" + this.environment + "].");
} }
@@ -19,12 +19,15 @@
package com.seibel.distanthorizons.core.world; package com.seibel.distanthorizons.core.world;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelLoadEvent;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiLevelUnloadEvent;
import com.seibel.distanthorizons.core.api.internal.ClientApi; import com.seibel.distanthorizons.core.api.internal.ClientApi;
import com.seibel.distanthorizons.core.enums.MinecraftTextFormat; import com.seibel.distanthorizons.core.enums.MinecraftTextFormat;
import com.seibel.distanthorizons.core.generation.PregenManager; import com.seibel.distanthorizons.core.generation.PregenManager;
import com.seibel.distanthorizons.core.level.DhServerLevel; import com.seibel.distanthorizons.core.level.DhServerLevel;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -64,15 +67,22 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
{ {
try try
{ {
return new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager()); DhServerLevel level = new DhServerLevel(this.saveStructure, (IServerLevelWrapper) serverLevelWrapper, this.getServerPlayerStateManager());
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelLoadEvent.class, new DhApiLevelLoadEvent.EventParam(wrapper));
return level;
} }
catch (Exception e) catch (Exception e)
{ {
LOGGER.fatal("Failed to load server level, error: ["+e.getMessage()+"].", e); LOGGER.fatal("Failed to load server level, error: ["+e.getMessage()+"].", e);
String r = MinecraftTextFormat.RED;
String y = MinecraftTextFormat.YELLOW;
String cf = MinecraftTextFormat.CLEAR_FORMATTING;
ClientApi.INSTANCE.showChatMessageNextFrame( ClientApi.INSTANCE.showChatMessageNextFrame(
MinecraftTextFormat.RED + "Distant Horizons: Server level loading failed." + MinecraftTextFormat.CLEAR_FORMATTING + "\n" + r + "Distant Horizons: Server level loading failed." + cf + "\n" +
"Unable to load level ["+serverLevelWrapper.getDhIdentifier()+"], LODs may not appear. See log for more information."); "Unable to load level ["+y+serverLevelWrapper.getDhIdentifier()+cf+"], LODs may not appear. See log for more information.\n" +
"");
return null; return null;
} }
@@ -80,19 +90,22 @@ public class DhServerWorld extends AbstractDhServerWorld<DhServerLevel>
} }
@Override @Override
public void unloadLevel(@NotNull ILevelWrapper wrapper) public boolean unloadLevel(@NotNull ILevelWrapper wrapper)
{ {
if (!(wrapper instanceof IServerLevelWrapper)) if (!(wrapper instanceof IServerLevelWrapper))
{ {
return; return false;
} }
if (this.dhLevelByLevelWrapper.containsKey(wrapper)) if (this.dhLevelByLevelWrapper.containsKey(wrapper))
{ {
DhServerLevel level = this.dhLevelByLevelWrapper.get(wrapper);
wrapper.onUnload(); wrapper.onUnload();
this.dhLevelByLevelWrapper.remove(wrapper).close(); this.dhLevelByLevelWrapper.remove(wrapper).close();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiLevelUnloadEvent.class, new DhApiLevelUnloadEvent.EventParam(wrapper));
return true;
} }
return false;
} }
@Override @Override
@@ -41,7 +41,6 @@ import java.util.concurrent.CompletableFuture;
*/ */
public interface IDhWorld extends Closeable public interface IDhWorld extends Closeable
{ {
@Nullable @Nullable
IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper); IDhLevel getOrLoadLevel(@NotNull ILevelWrapper levelWrapper);
@Nullable @Nullable
@@ -49,6 +48,14 @@ public interface IDhWorld extends Closeable
Iterable<? extends IDhLevel> getAllLoadedLevels(); Iterable<? extends IDhLevel> getAllLoadedLevels();
int getLoadedLevelCount(); int getLoadedLevelCount();
void unloadLevel(@NotNull ILevelWrapper levelWrapper); /**
* Returns
* true if the level was unloaded,
* false if the level isn't present in this world
* or couldn't be unloaded for some other reason
*/
boolean unloadLevel(@NotNull ILevelWrapper levelWrapper);
} }
@@ -19,7 +19,7 @@
package com.seibel.distanthorizons.core.wrapperInterfaces; package com.seibel.distanthorizons.core.wrapperInterfaces;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderApi; import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
/** /**
@@ -34,6 +34,6 @@ public interface IVersionConstants extends IBindable
{ {
String getMinecraftVersion(); String getMinecraftVersion();
EDhApiRenderApi getDefaultRenderingApi(); EDhApiRenderingEngine getDefaultRenderingEngine();
} }
@@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler; import com.seibel.distanthorizons.core.render.RenderThreadTaskHandler;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import org.jetbrains.annotations.Nullable;
public interface IMinecraftClientWrapper extends IBindable public interface IMinecraftClientWrapper extends IBindable
{ {
@@ -64,11 +65,13 @@ public interface IMinecraftClientWrapper extends IBindable
* Returns the level the client is currently in. <br> * Returns the level the client is currently in. <br>
* Returns null if the client isn't in a level. * Returns null if the client isn't in a level.
*/ */
@Nullable
IClientLevelWrapper getWrappedClientLevel(); IClientLevelWrapper getWrappedClientLevel();
/** /**
* Returns the level the client is currently in. <br> * Returns the level the client is currently in. <br>
* Returns null if the client isn't in a level. * Returns null if the client isn't in a level.
*/ */
@Nullable
IClientLevelWrapper getWrappedClientLevel(boolean bypassLevelKeyManager); IClientLevelWrapper getWrappedClientLevel(boolean bypassLevelKeyManager);
@@ -22,11 +22,11 @@ package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
import java.awt.Color; import java.awt.Color;
import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState; import com.seibel.distanthorizons.core.api.internal.rendering.DhRenderState;
import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingApi;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.misc.ILightMapWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f; import com.seibel.distanthorizons.core.util.math.DhVec3f;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -40,7 +40,7 @@ import org.jetbrains.annotations.Nullable;
*/ */
public interface IMinecraftRenderWrapper extends IBindable public interface IMinecraftRenderWrapper extends IBindable
{ {
Vec3f getLookAtVector(); DhVec3f getLookAtVector();
boolean playerHasBlindingEffect(); boolean playerHasBlindingEffect();
@@ -59,7 +59,14 @@ public interface IMinecraftRenderWrapper extends IBindable
*/ */
float getPartialTickTime(); float getPartialTickTime();
Vec3d getCameraExactPosition(); /**
* When other mods are present and
* this method is called outside the render thread
* this may return undesirable positions.
* (Normally this position is used to get
* the position the player's camera).
*/
DhVec3d getCameraExactPosition();
Color getFogColor(float partialTicks); Color getFogColor(float partialTicks);
@@ -75,14 +82,16 @@ public interface IMinecraftRenderWrapper extends IBindable
boolean mcRendersToFrameBuffer(); boolean mcRendersToFrameBuffer();
boolean runningLegacyOpenGL(); boolean runningLegacyOpenGL();
/** Returns the Graphics API Minecraft is currently using for rendering */
EDhApiRenderingApi getMcRenderingApi();
/** @return -1 if no valid framebuffer is available yet */ /** @return -1 if no valid framebuffer is available yet */
int getTargetFramebuffer(); // Note: Iris is now hooking onto this for DH + Iris compat, try not to change (unless we wanna deal with some annoyances) int getTargetFramebuffer(); // Note: Iris is now hooking onto this for DH + Iris compat, try not to change (unless we wanna deal with some annoyances)
// Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16 // Iris commit: https://github.com/IrisShaders/Iris/commit/a76a240527e93780bbcba57c09bef377419d47a7#diff-7b9ded0c79bbcdb130010373387756a28ee8d3640d522c0a5b7acd0abbfc20aeR16
/** @return -1 if there was an issue or no texture exists */ /** @return -1 if there was an issue or no texture exists */
int getDepthTextureId(); int getGlDepthTextureId();
/** @return -1 if there was an issue or no texture exists */ /** @return -1 if there was an issue or no texture exists */
int getColorTextureId(); int getGlColorTextureId();
int getTargetFramebufferViewportWidth(); int getTargetFramebufferViewportWidth();
int getTargetFramebufferViewportHeight(); int getTargetFramebufferViewportHeight();
@@ -96,8 +105,6 @@ public interface IMinecraftRenderWrapper extends IBindable
@Nullable @Nullable
ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level); ILightMapWrapper getLightmapWrapper(@NotNull ILevelWrapper level);
float getShade(EDhDirection lodDirection);
} }
@@ -19,7 +19,9 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft; package com.seibel.distanthorizons.core.wrapperInterfaces.minecraft;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable; import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
import org.jetbrains.annotations.Nullable;
import java.io.File; import java.io.File;
@@ -31,6 +33,9 @@ public interface IMinecraftSharedWrapper extends IBindable
int getPlayerCount(); int getPlayerCount();
/** If used on the client will only return a non-null object if the client is hosting a LAN server */
@Nullable
IServerLevelWrapper getLevelWrapper(String dimensionResourceLocation);
} }
@@ -21,9 +21,7 @@ package com.seibel.distanthorizons.core.wrapperInterfaces.misc;
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper; import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.core.util.math.Vec3d; import com.seibel.distanthorizons.core.util.math.DhVec3d;
import java.net.SocketAddress;
public interface IServerPlayerWrapper extends IDhApiUnsafeWrapper public interface IServerPlayerWrapper extends IDhApiUnsafeWrapper
{ {
@@ -31,6 +29,6 @@ public interface IServerPlayerWrapper extends IDhApiUnsafeWrapper
IServerLevelWrapper getLevel(); IServerLevelWrapper getLevel();
Vec3d getPosition(); DhVec3d getPosition();
} }
@@ -0,0 +1,157 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiBeforeRenderEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.logging.DhLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import org.jetbrains.annotations.NotNull;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.function.Supplier;
public abstract class AbstractImmersivePortalsAccessor implements IImmersivePortalsAccessor
{
private static final DhLogger LOGGER = new DhLoggerBuilder().build();
private static MethodHandle isRenderingMethodHandle;
//=============//
// constructor //
//=============//
//region
public AbstractImmersivePortalsAccessor()
{
LOGGER.info("Immersive Portals detected: some DH features will be disabled or may only partially function.");
BeforeRenderEvent event = new BeforeRenderEvent(this);
DhApi.events.bind(DhApiBeforeRenderEvent.class, event);
}
//endregion
//=====================//
// reflection handling //
//=====================//
//region
private static Class<?> getPortalRenderingClass()
{
try
{
return Class.forName("qouteall.imm_ptl.core.render.context_management.PortalRendering");
}
catch (ClassNotFoundException first)
{
try
{
return Class.forName("com.qouteall.immersive_portals.render.context_management.PortalRendering"); // 1.16
}
catch (ClassNotFoundException second)
{
RuntimeException err = new RuntimeException(first);
err.addSuppressed(second);
throw err;
}
}
}
//endregion
//===========//
// overrides //
//===========//
//region
@Override
public String getModName() { return "Immersive Portals"; }
@Override
public boolean isRenderingPortal()
{
try
{
if (isRenderingMethodHandle == null)
{
isRenderingMethodHandle = MethodHandles.lookup().findStatic(
getPortalRenderingClass(),
"isRendering", MethodType.methodType(Boolean.TYPE)
);
}
return (boolean) isRenderingMethodHandle.invoke();
}
catch (Throwable e)
{
throw new RuntimeException(e);
}
}
//endregion
//=======//
// event //
//=======//
//region
private static class BeforeRenderEvent extends DhApiBeforeRenderEvent
{
@NotNull
private final IImmersivePortalsAccessor immersivePortals;
public BeforeRenderEvent(@NotNull IImmersivePortalsAccessor portalAccessor) { this.immersivePortals = portalAccessor; }
@Override
public void beforeRender(DhApiCancelableEventParam<DhApiRenderParam> event)
{
// needed because otherwise DH doesn't render to the level anyway
// and will probably render the level the player is currently in instead
if (this.immersivePortals.isRenderingPortal())
{
event.cancelEvent();
}
}
}
//endregion
}
@@ -0,0 +1,77 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos;
import com.seibel.distanthorizons.core.util.math.DhVec3d;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import org.jetbrains.annotations.Nullable;
public interface IImmersivePortalsAccessor extends IModAccessor
{
/**
* Returns true if Immersive Portals is currently rendering a portal.
* This can be used to determine if the level currently being rendered
* is being seen through a portal if called on the render thread.
*/
boolean isRenderingPortal();
/**
* Returns the player's position for the level they're currently in.
* <br><br>
* Necessary since Immersive Portals messes with vanilla MC's
* variables in order to render the camera in multiple dimensions.
*/
@Nullable
DhBlockPos getActualPlayerBlockPos();
/**
* Returns the player's position for the level they're currently in.
* <br><br>
* Necessary since Immersive Portals messes with vanilla MC's
* variables in order to render the camera in multiple dimensions.
*/
@Nullable
DhChunkPos getActualPlayerChunkPos();
/**
* Returns the client level the player is currently in.
* <br><br>
* Necessary since Immersive Portals messes with vanilla MC's
* variables in order to render the camera in multiple dimensions.
*/
@Nullable
IClientLevelWrapper getActualClientLevelWrapper();
/**
* Returns the camera position for the level the player is currently in.
* <br><br>
* Necessary since Immersive Portals messes with vanilla MC's
* variables in order to render the camera in multiple dimensions.
*/
@Nullable
DhVec3d getActualCameraPos();
}
@@ -1,7 +1,10 @@
package com.seibel.distanthorizons.core.wrapperInterfaces.render; package com.seibel.distanthorizons.core.wrapperInterfaces.render;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingApi;
import com.seibel.distanthorizons.api.enums.config.EDhApiRenderingEngine;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.jar.EPlatform; import com.seibel.distanthorizons.core.jar.EPlatform;
import com.seibel.distanthorizons.core.render.EDhRenderDepth;
import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer; import com.seibel.distanthorizons.core.render.renderer.AbstractDebugWireframeRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.IDhGenericObjectVertexBufferContainer; import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.IDhGenericObjectVertexBufferContainer;
import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper; import com.seibel.distanthorizons.core.wrapperInterfaces.render.objects.ILodContainerUniformBufferWrapper;
@@ -17,7 +20,7 @@ public abstract class AbstractDhRenderApiDefinition implements IBindable
//region //region
/** Used for debugging */ /** Used for debugging */
public abstract String getApiName(); public abstract String getEngineName();
private final boolean useSingleIbo = (EPlatform.get() != EPlatform.MACOS); private final boolean useSingleIbo = (EPlatform.get() != EPlatform.MACOS);
/** /**
@@ -29,6 +32,18 @@ public abstract class AbstractDhRenderApiDefinition implements IBindable
*/ */
public boolean useSingleIbo() { return this.useSingleIbo; } public boolean useSingleIbo() { return this.useSingleIbo; }
public abstract EDhRenderDepth getRenderDepth();
public abstract EDhApiRenderingApi getRenderApi();
/**
* Returns true if the current renderer
* is calling the base rendering API's method calls. <br>
* ie GL.drawArrays() for OpenGL. <Br><br>
*
* If DH is using Blaze3D (Mojang's rendering API)
* this will return false.
*/
public abstract boolean isNativeRenderer();
//endregion //endregion
@@ -24,11 +24,11 @@ import com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding.LodBuff
/** /**
* @see LodBufferContainer * @see LodBufferContainer
*/ */
public interface ILodContainerUniformBufferWrapper extends IUniformBufferWrapper public interface ILodContainerUniformBufferWrapper extends AutoCloseable
{ {
/** does nothing if the buffer has already been uploaded */
void tryUpload(LodBufferContainer bufferContainer);
void createUniformData(LodBufferContainer bufferContainer); @Override void close();
void tryUpload();
} }

Some files were not shown because too many files have changed in this diff Show More